Compare commits

...

6 Commits

16 changed files with 1034 additions and 812 deletions

View File

@ -2,6 +2,7 @@ BUGS
=======
This is a list of the software bugs (as opposed to the bugs in the game that you shoot) that still need attention:
* Should detect when running on system 5 or lower and quit gracefully. Today, the game crashes.
FIXED

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 53;
objects = {
/* Begin PBXBuildFile section */
@ -23,7 +23,7 @@
9DE37B742694E0B0005FC562 /* level.s in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B332694E0B0005FC562 /* level.s */; };
9DE37B752694E0B0005FC562 /* tileConvert.s in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B342694E0B0005FC562 /* tileConvert.s */; };
9DE37B762694E0B0005FC562 /* gameSpider.s in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B362694E0B0005FC562 /* gameSpider.s */; };
9DE37B772694E0B0005FC562 /* globalScores.c in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B372694E0B0005FC562 /* globalScores.c */; };
9DE37B772694E0B0005FC562 /* netScores.c in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B372694E0B0005FC562 /* netScores.c */; };
9DE37B782694E0B0005FC562 /* game.s in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B382694E0B0005FC562 /* game.s */; };
9DE37B792694E0B0005FC562 /* gameShot.s in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B3A2694E0B0005FC562 /* gameShot.s */; };
9DE37B7A2694E0B0005FC562 /* loadSounds.c in Sources */ = {isa = PBXBuildFile; fileRef = 9DE37B3B2694E0B0005FC562 /* loadSounds.c */; };
@ -106,14 +106,14 @@
9DE37B342694E0B0005FC562 /* tileConvert.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = tileConvert.s; sourceTree = "<group>"; };
9DE37B352694E0B0005FC562 /* main.rez */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.rez; path = main.rez; sourceTree = "<group>"; };
9DE37B362694E0B0005FC562 /* gameSpider.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = gameSpider.s; sourceTree = "<group>"; };
9DE37B372694E0B0005FC562 /* globalScores.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = globalScores.c; sourceTree = "<group>"; };
9DE37B372694E0B0005FC562 /* netScores.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = netScores.c; sourceTree = "<group>"; };
9DE37B382694E0B0005FC562 /* game.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = game.s; sourceTree = "<group>"; };
9DE37B392694E0B0005FC562 /* main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = main.h; sourceTree = "<group>"; };
9DE37B3A2694E0B0005FC562 /* gameShot.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = gameShot.s; sourceTree = "<group>"; };
9DE37B3B2694E0B0005FC562 /* loadSounds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = loadSounds.c; sourceTree = "<group>"; };
9DE37B3C2694E0B0005FC562 /* loadSounds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = loadSounds.h; sourceTree = "<group>"; };
9DE37B3D2694E0B0005FC562 /* gameMushroom.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = gameMushroom.s; sourceTree = "<group>"; };
9DE37B3E2694E0B0005FC562 /* globalScores.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = globalScores.h; sourceTree = "<group>"; };
9DE37B3E2694E0B0005FC562 /* netScores.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = netScores.h; sourceTree = "<group>"; };
9DE37B3F2694E0B0005FC562 /* globals.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = globals.s; sourceTree = "<group>"; };
9DE37B402694E0B0005FC562 /* gameSegments.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = gameSegments.s; sourceTree = "<group>"; };
9DE37B412694E0B0005FC562 /* score.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = score.s; sourceTree = "<group>"; };
@ -221,8 +221,8 @@
9DE37B732694E0B0005FC562 /* genData.pl */,
9DE37B442694E0B0005FC562 /* global.macros */,
9DE37B3F2694E0B0005FC562 /* globals.s */,
9DE37B372694E0B0005FC562 /* globalScores.c */,
9DE37B3E2694E0B0005FC562 /* globalScores.h */,
9DE37B372694E0B0005FC562 /* netScores.c */,
9DE37B3E2694E0B0005FC562 /* netScores.h */,
9DE37B332694E0B0005FC562 /* level.s */,
9DE37B3B2694E0B0005FC562 /* loadSounds.c */,
9DE37B3C2694E0B0005FC562 /* loadSounds.h */,
@ -412,7 +412,8 @@
9DE37ADF2694E070005FC562 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1240;
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
TargetAttributes = {
9DE37AE32694E070005FC562 = {
CreatedOnToolsVersion = 12.4;
@ -478,7 +479,7 @@
9DE37B852694E0B0005FC562 /* solid.s in Sources */,
9DE37B7C2694E0B0005FC562 /* globals.s in Sources */,
9DE37B872694E0B0005FC562 /* symbols.s in Sources */,
9DE37B772694E0B0005FC562 /* globalScores.c in Sources */,
9DE37B772694E0B0005FC562 /* netScores.c in Sources */,
9DE37B8B2694E0B0005FC562 /* scores.s in Sources */,
9DE37B932694E0B0005FC562 /* gameSound.s in Sources */,
9DE37B922694E0B0005FC562 /* gameScorpion.s in Sources */,
@ -531,6 +532,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -588,6 +590,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -608,6 +611,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = VD9FGCW36C;
@ -623,6 +627,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = VD9FGCW36C;
OTHER_CFLAGS = "";
@ -635,6 +640,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS;
@ -645,6 +651,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS;
@ -655,6 +662,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS.2mg;
@ -665,6 +673,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS.2mg;
@ -675,6 +684,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS.shk;
@ -685,6 +695,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = BuGS.shk;
@ -695,6 +706,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
@ -717,6 +729,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VD9FGCW36C;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREPROCESSOR_DEFINITIONS = (

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9D0B917C1F526C2D004D7E0B"
BlueprintIdentifier = "9DE37AE32694E070005FC562"
BuildableName = "BuGS"
BlueprintName = "BuGS"
ReferencedContainer = "container:BuGS.xcodeproj">
@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -49,7 +47,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9D0B917C1F526C2D004D7E0B"
BlueprintIdentifier = "9DE37AE32694E070005FC562"
BuildableName = "BuGS"
BlueprintName = "BuGS"
ReferencedContainer = "container:BuGS.xcodeproj">
@ -81,8 +79,6 @@
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -93,7 +89,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9D0B917C1F526C2D004D7E0B"
BlueprintIdentifier = "9DE37AE32694E070005FC562"
BuildableName = "BuGS"
BlueprintName = "BuGS"
ReferencedContainer = "container:BuGS.xcodeproj">

View File

@ -12,7 +12,7 @@
<key>Binary.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
<integer>1</integer>
</dict>
<key>BuGS.xcscheme_^#shared#^_</key>
<dict>
@ -22,7 +22,7 @@
<key>DiskImage.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>4</integer>
</dict>
<key>doNotBuild.xcscheme_^#shared#^_</key>
<dict>

View File

@ -97,7 +97,7 @@ gameLoop_skipPreload anop
; The following is the network poll code which runs when not playing a game
lda gameState
bne gameLoop_skipNetwork
jsl pollNetwork
jsl NSGS_PollNetwork
lda hasGlobalHighScores
beq gameLoop_skipNetwork
lda highScoreCountdown
@ -247,7 +247,7 @@ startGame_singlePlayer anop
jsl spiderInitGame
jsl levelInit
jsl soundInit
jsl disconnectNetwork
jsl NSGS_DisconnectNetwork
; Fall through intentionally here...
startLevel entry
jsl segmentsInitLevel

View File

@ -224,53 +224,53 @@ _dirtyNonGameTile_skip&SYSCNT anop
macro
_globalHighScoreRow &nthScore
ldy #2+&nthScore*SETTINGS_HIGH_SCORE_SIZE
lda highScoreResponse,y
ldy #&nthScore*SETTINGS_HIGH_SCORE_SIZE
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
@ -278,17 +278,17 @@ _dirtyNonGameTile_skip&SYSCNT anop
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
iny
lda highScoreResponse,y
lda globalHighScores,y
jsl asciiToTileType
jsl setGameTile
mend

View File

@ -1,731 +0,0 @@
/*
* globalScores.c
* BuGS
*
* Created by Jeremy Rand on 2021-05-23.
* Copyright © 2021 Jeremy Rand. All rights reserved.
*/
#include <Hash.h>
#include <locator.h>
#include <misctool.h>
#include <stdint.h>
#include <tcpip.h>
#include <types.h>
#include <stdio.h>
#include <stdlib.h>
#include "globalScores.h"
// Defines
#define REQUEST_TYPE_GET_HIGH_SCORES 0
#define REQUEST_TYPE_SET_SCORE 1
#define RESPONSE_TYPE_HELLO 0
#define RESPONSE_TYPE_SCORES 1
#define RESPONSE_TYPE_STATUS 2
#define GLOBAL_SCORE_REFRESH_TIME (15 * 60 * 60)
#define SHUTDOWN_NETWORK_TIMEOUT (2 * 60)
#define TCP_CONNECT_TIMEOUT (8 * 60)
#define READ_NETWORK_TIMEOUT (5 * 60)
#define NETWORK_RETRY_TIMEOUT (3 * 60 * 60)
// Types
typedef struct tSessionSecrets {
uint32_t secret;
uint32_t nonce;
} tSessionSecrets;
typedef struct tHighScoreRequest {
uint16_t requestType;
} tHighScoreRequest;
typedef struct tHighScoreRequestWithHash {
tHighScoreRequest highScoreRequest;
uint8_t md5Digest[16];
} tHighScoreRequestWithHash;
typedef struct tSetHighScoreRequest {
uint16_t requestType;
char who[4];
uint32_t score;
Word is60Hz;
} tSetHighScoreRequest;
typedef struct tSetHighScoreRequestWithHash {
tSetHighScoreRequest setHighScoreRequest;
uint8_t md5Digest[16];
} tSetHighScoreRequestWithHash;
typedef struct tHelloResponse {
uint16_t responseType;
uint32_t nonce;
} tHelloResponse;
typedef struct tScoresResponse {
uint16_t responseType;
tHighScore highScores[10];
} tScoresResponse;
typedef struct tStatusResponse {
uint16_t responseType;
Boolean success;
uint16_t position;
uint16_t numberOfScores;
} tStatusResponse;
typedef enum tProtocolErrors {
TCP_CONNECT_TIMEOUT_ERROR = 1,
HELLO_TIMEOUT_ERROR,
HELLO_TOO_BIG_ERROR,
HELLO_UNEXPECTED_RESPONSE_ERROR,
HIGH_SCORE_TIMEOUT_ERROR,
HIGH_SCORE_TOO_BIG_ERROR,
HIGH_SCORE_UNEXPECTED_RESPONSE_ERROR,
SET_SCORE_TIMEOUT_ERROR,
SET_SCORE_TOO_BIG_ERROR,
SET_SCORE_UNEXPECTED_RESPONSE_ERROR,
SET_SCORE_FAILED_ERROR,
} tProtocolErrors;
typedef enum tGameNetworkState {
GAME_NETWORK_CONNECT_FAILED = 0,
GAME_NETWORK_UNCONNECTED,
/* All states below this should have Marinetti disconnected */
GAME_NETWORK_CONNECTED,
GAME_NETWORK_RESOLVING_NAME,
/* These are all of the error states. Marinetti is still connected but the
TCP connection is closed and ipid released. */
GAME_NETWORK_LOOKUP_FAILED,
GAME_NETWORK_SOCKET_ERROR,
GAME_NETWORK_PROTOCOL_FAILED,
GAME_NETWORK_FAILURE,
/* This is the state we are in during a game on a machine with a working network
(other possible states are fail states). */
GAME_NETWORK_TCP_UNCONNECTED,
/* All of these states below here have an ipid open and the TCP connection is
open and in some state. */
GAME_NETWORK_WAITING_FOR_TCP,
/* All of these states below this point have the TCP connection established
and the score protocol is exchanging information. */
GAME_NETWORK_WAITING_FOR_HELLO,
GAME_NETWORK_REQUEST_SCORES,
GAME_NETWORK_WAITING_FOR_SCORES,
GAME_NETWORK_SET_HIGH_SCORE,
GAME_NETWORK_WAITING_FOR_SCORE_ACK,
GAME_NETWORK_CLOSING_TCP,
} tGameNetworkState;
typedef struct tGameNetworkGlobals {
Boolean networkStartedConnected;
tHighScoreInitParams initParams;
tGameNetworkState gameNetworkState;
dnrBuffer domainNameResolution;
srBuff tcpStatus;
Word ipid;
rrBuff readResponseBuf;
tHelloResponse helloResponse;
uint32_t bytesRead;
md5WorkBlock hashWorkBlock;
uint32_t secrets[3];
tHighScoreRequestWithHash highScoreRequest;
Boolean hasHighScoreToSend;
tStatusResponse setHighScoreResponse;
uint16_t errorCode;
uint16_t timeout;
} tGameNetworkGlobals;
// Globals
// I am running out of space in the main segment so I am moving these globals into
// a dynamically allocated struct. It is only allocated if network capabilities are
// detected.
static tGameNetworkGlobals * networkGlobals = NULL;
// The following globals are accessed by name from assembly so are not in the
// tGameNetworkGlobals structure.
// TODO - Make real interfaces for these things.
Boolean hasGlobalHighScores = FALSE;
tScoresResponse highScoreResponse;
Word globalScoreAge = 0; // TODO - Replace this with a call to a MiscTool function?
tSetHighScoreRequestWithHash setHighScoreRequest;
// Implementation
segment "highscores";
void initNetwork(tHighScoreInitParams * params)
{
networkGlobals = NULL;
if ((params->scoreServer == NULL) ||
(params->scorePort == 0) ||
(params->waitForVbl == NULL))
return;
LoadOneTool(54, 0x200); // Load Marinetti
if (toolerror())
return;
LoadOneTool(128, 0x103); // Load the Hash toolset
if (toolerror()) {
UnloadOneTool(54);
return;
}
TCPIPStartUp();
if (toolerror()) {
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
hashStartUp();
if (toolerror()) {
TCPIPShutDown();
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
networkGlobals = malloc(sizeof(tGameNetworkGlobals));
if (networkGlobals == NULL) {
hashShutDown();
TCPIPShutDown();
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
memcpy(&(networkGlobals->initParams), params, sizeof(networkGlobals->initParams));
networkGlobals->networkStartedConnected = TCPIPGetConnectStatus();
if (networkGlobals->networkStartedConnected) {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECTED;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_UNCONNECTED;
}
networkGlobals->secrets[0] = params->secret1;
networkGlobals->secrets[1] = params->secret2;
networkGlobals->hasHighScoreToSend = FALSE;
setHighScoreRequest.setHighScoreRequest.is60Hz = !ReadBParam(hrtz50or60);
}
void shutdownNetwork(void)
{
if ((!networkGlobals->networkStartedConnected) &&
(networkGlobals->gameNetworkState > GAME_NETWORK_UNCONNECTED)) {
TCPIPDisconnect(TRUE, NULL);
}
free(networkGlobals);
hashShutDown();
TCPIPShutDown();
UnloadOneTool(128); // Unload the Hash toolset
UnloadOneTool(54); // Unload Marinetti
}
static void abortConnection(void)
{
TCPIPAbortTCP(networkGlobals->ipid);
TCPIPLogout(networkGlobals->ipid);
}
void disconnectNetwork(void)
{
if (networkGlobals == NULL)
return;
if (networkGlobals->gameNetworkState > GAME_NETWORK_TCP_UNCONNECTED) {
if (networkGlobals->gameNetworkState < GAME_NETWORK_CLOSING_TCP) {
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = SHUTDOWN_NETWORK_TIMEOUT;
}
while (networkGlobals->gameNetworkState > GAME_NETWORK_TCP_UNCONNECTED) {
networkGlobals->initParams.waitForVbl();
pollNetwork();
}
}
}
static char hexDigitToAscii(Word digit)
{
digit &= 0xf;
if (digit < 10)
return '0' + digit;
if (digit > 15)
return 'X';
return 'A' + digit - 10;
}
static void displayString(Word row, char * string)
{
strcpy(&(highScoreResponse.highScores[row].scoreText[2]), string);
}
static void displayNetworkError(char * line1, char * line2)
{
Word row;
Word column;
networkGlobals->gameNetworkState = GAME_NETWORK_FAILURE;
for (row = 0; row < sizeof(highScoreResponse.highScores) / sizeof(highScoreResponse.highScores[0]); row++) {
for (column = 0;
column < sizeof(highScoreResponse.highScores[0].scoreText) / sizeof(highScoreResponse.highScores[0].scoreText[0]);
column++) {
highScoreResponse.highScores[row].scoreText[column] = ' ';
}
for (column = 0;
column < sizeof(highScoreResponse.highScores[0].who) / sizeof(highScoreResponse.highScores[0].who[0]);
column++) {
highScoreResponse.highScores[row].who[column] = ' ';
}
}
displayString(1, "NETWORK");
displayString(2, "PROBLEM");
displayString(4, line1);
displayString(5, line2);
highScoreResponse.highScores[7].scoreText[0] = 'C';
highScoreResponse.highScores[7].scoreText[1] = 'O';
highScoreResponse.highScores[7].scoreText[2] = 'D';
highScoreResponse.highScores[7].scoreText[3] = 'E';
highScoreResponse.highScores[7].scoreText[4] = ':';
highScoreResponse.highScores[7].scoreText[5] = ' ';
highScoreResponse.highScores[7].scoreText[6] = hexDigitToAscii(networkGlobals->errorCode >> 12);
highScoreResponse.highScores[7].scoreText[7] = hexDigitToAscii(networkGlobals->errorCode >> 8);
highScoreResponse.highScores[7].scoreText[8] = hexDigitToAscii(networkGlobals->errorCode >> 4);
highScoreResponse.highScores[7].scoreText[9] = hexDigitToAscii(networkGlobals->errorCode);
globalScoreAge = 0;
hasGlobalHighScores = TRUE;
}
void pollNetwork(void)
{
if (networkGlobals == NULL)
return;
TCPIPPoll();
switch (networkGlobals->gameNetworkState) {
case GAME_NETWORK_SOCKET_ERROR:
displayNetworkError("SOCKET", "ERROR");
networkGlobals->timeout = NETWORK_RETRY_TIMEOUT;
break;
case GAME_NETWORK_LOOKUP_FAILED:
displayNetworkError("LOOKUP", "FAILED");
break;
case GAME_NETWORK_CONNECT_FAILED:
displayNetworkError("CONNECT", "FAILED");
break;
case GAME_NETWORK_PROTOCOL_FAILED:
abortConnection();
displayNetworkError("PROTOCOL", "FAILED");
networkGlobals->timeout = NETWORK_RETRY_TIMEOUT;
break;
case GAME_NETWORK_FAILURE:
// All of the different failure modes except protocol failure above end up here ultimately. And the state
// machine stays here once it arrives here.
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
if (networkGlobals->timeout == 0)
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
break;
case GAME_NETWORK_UNCONNECTED:
if (networkGlobals->initParams.displayConnectionString != NULL)
networkGlobals->initParams.displayConnectionString(TRUE);
TCPIPConnect(NULL);
if ((!toolerror()) &&
(TCPIPGetConnectStatus())) {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECTED;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECT_FAILED;
}
if (networkGlobals->initParams.displayConnectionString != NULL)
networkGlobals->initParams.displayConnectionString(FALSE);
break;
case GAME_NETWORK_CONNECTED:
TCPIPDNRNameToIP(networkGlobals->initParams.scoreServer, &(networkGlobals->domainNameResolution));
if (toolerror()) {
networkGlobals->gameNetworkState = GAME_NETWORK_LOOKUP_FAILED;
networkGlobals->errorCode = toolerror();
} else
networkGlobals->gameNetworkState = GAME_NETWORK_RESOLVING_NAME;
break;
case GAME_NETWORK_RESOLVING_NAME:
if (networkGlobals->domainNameResolution.DNRstatus == DNR_Pending)
break;
if (networkGlobals->domainNameResolution.DNRstatus != DNR_OK) {
networkGlobals->gameNetworkState = GAME_NETWORK_LOOKUP_FAILED;
networkGlobals->errorCode = networkGlobals->domainNameResolution.DNRstatus;
break;
}
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
break;
case GAME_NETWORK_TCP_UNCONNECTED:
if ((hasGlobalHighScores) &&
(globalScoreAge > 0) &&
(!networkGlobals->hasHighScoreToSend))
break;
networkGlobals->ipid = TCPIPLogin(networkGlobals->initParams.userId, networkGlobals->domainNameResolution.DNRIPaddress, networkGlobals->initParams.scorePort, 0, 64);
if (toolerror()) {
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
networkGlobals->errorCode = toolerror();
break;
}
networkGlobals->errorCode = TCPIPOpenTCP(networkGlobals->ipid);
if (networkGlobals->errorCode != tcperrOK) {
TCPIPLogout(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_TCP;
networkGlobals->timeout = TCP_CONNECT_TIMEOUT;
break;
case GAME_NETWORK_WAITING_FOR_TCP:
networkGlobals->errorCode = TCPIPStatusTCP(networkGlobals->ipid, &(networkGlobals->tcpStatus));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
if ((networkGlobals->tcpStatus.srState == TCPSSYNSENT) ||
(networkGlobals->tcpStatus.srState == TCPSSYNRCVD)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = TCP_CONNECT_TIMEOUT_ERROR;
}
break;
}
if (networkGlobals->tcpStatus.srState != TCPSESTABLISHED) {
abortConnection();
networkGlobals->errorCode = networkGlobals->tcpStatus.srState | 0x8000;
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_HELLO;
networkGlobals->timeout = READ_NETWORK_TIMEOUT;
networkGlobals->bytesRead = 0;
break;
case GAME_NETWORK_WAITING_FOR_HELLO:
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&(networkGlobals->helloResponse))) + networkGlobals->bytesRead,
sizeof(networkGlobals->helloResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(networkGlobals->helloResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HELLO_TIMEOUT_ERROR;
}
break;
}
if (networkGlobals->bytesRead > sizeof(networkGlobals->helloResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HELLO_TOO_BIG_ERROR;
break;
}
if (networkGlobals->helloResponse.responseType != RESPONSE_TYPE_HELLO) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HELLO_UNEXPECTED_RESPONSE_ERROR;
break;
}
networkGlobals->secrets[2] = networkGlobals->helloResponse.nonce;
if (networkGlobals->hasHighScoreToSend) {
networkGlobals->gameNetworkState = GAME_NETWORK_SET_HIGH_SCORE;
} else if ((!hasGlobalHighScores) ||
(globalScoreAge == 0)) {
networkGlobals->gameNetworkState = GAME_NETWORK_REQUEST_SCORES;
} else {
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = SHUTDOWN_NETWORK_TIMEOUT;
}
break;
case GAME_NETWORK_REQUEST_SCORES:
networkGlobals->highScoreRequest.highScoreRequest.requestType = REQUEST_TYPE_GET_HIGH_SCORES;
md5Init(&(networkGlobals->hashWorkBlock));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->secrets), sizeof(networkGlobals->secrets));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->highScoreRequest.highScoreRequest), sizeof(networkGlobals->highScoreRequest.highScoreRequest));
md5Finish(&(networkGlobals->hashWorkBlock), &(networkGlobals->highScoreRequest.md5Digest[0]));
networkGlobals->errorCode = TCPIPWriteTCP(networkGlobals->ipid, (Pointer)&(networkGlobals->highScoreRequest), sizeof(networkGlobals->highScoreRequest), FALSE, FALSE);
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_SCORES;
networkGlobals->timeout = READ_NETWORK_TIMEOUT;
networkGlobals->bytesRead = 0;
break;
case GAME_NETWORK_WAITING_FOR_SCORES:
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&highScoreResponse)) + networkGlobals->bytesRead,
sizeof(highScoreResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(highScoreResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HIGH_SCORE_TIMEOUT_ERROR;
}
break;
}
if (networkGlobals->bytesRead > sizeof(highScoreResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HIGH_SCORE_TOO_BIG_ERROR;
break;
}
if (highScoreResponse.responseType != RESPONSE_TYPE_SCORES) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = HIGH_SCORE_UNEXPECTED_RESPONSE_ERROR;
break;
}
globalScoreAge = GLOBAL_SCORE_REFRESH_TIME;
hasGlobalHighScores = TRUE;
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = SHUTDOWN_NETWORK_TIMEOUT;
break;
case GAME_NETWORK_SET_HIGH_SCORE:
setHighScoreRequest.setHighScoreRequest.requestType = REQUEST_TYPE_SET_SCORE;
setHighScoreRequest.setHighScoreRequest.who[3] = '\0';
md5Init(&(networkGlobals->hashWorkBlock));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->secrets), sizeof(networkGlobals->secrets));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(setHighScoreRequest.setHighScoreRequest), sizeof(setHighScoreRequest.setHighScoreRequest));
md5Finish(&(networkGlobals->hashWorkBlock), &(setHighScoreRequest.md5Digest[0]));
networkGlobals->errorCode = TCPIPWriteTCP(networkGlobals->ipid, (Pointer)&setHighScoreRequest, sizeof(setHighScoreRequest), FALSE, FALSE);
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_SCORE_ACK;
networkGlobals->timeout = READ_NETWORK_TIMEOUT;
networkGlobals->bytesRead = 0;
break;
case GAME_NETWORK_WAITING_FOR_SCORE_ACK:
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&(networkGlobals->setHighScoreResponse))) + networkGlobals->bytesRead,
sizeof(networkGlobals->setHighScoreResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
break;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(networkGlobals->setHighScoreResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = SET_SCORE_TIMEOUT_ERROR;
}
break;
}
if (networkGlobals->bytesRead > sizeof(networkGlobals->setHighScoreResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = SET_SCORE_TOO_BIG_ERROR;
break;
}
if (networkGlobals->setHighScoreResponse.responseType != RESPONSE_TYPE_STATUS) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = SET_SCORE_UNEXPECTED_RESPONSE_ERROR;
break;
}
if (!networkGlobals->setHighScoreResponse.success) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = SET_SCORE_FAILED_ERROR;
break;
}
networkGlobals->hasHighScoreToSend = FALSE;
globalScoreAge = 0;
networkGlobals->gameNetworkState = GAME_NETWORK_REQUEST_SCORES;
break;
case GAME_NETWORK_CLOSING_TCP:
networkGlobals->errorCode = TCPIPStatusTCP(networkGlobals->ipid, &(networkGlobals->tcpStatus));
if ((networkGlobals->tcpStatus.srState != TCPSCLOSED) &&
(networkGlobals->tcpStatus.srState != TCPSTIMEWAIT)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
break;
}
TCPIPLogout(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
break;
}
}
BOOLEAN canSendHighScore(void)
{
if (networkGlobals == NULL)
return FALSE;
if (networkGlobals->gameNetworkState < GAME_NETWORK_TCP_UNCONNECTED) {
if ((networkGlobals->gameNetworkState == GAME_NETWORK_FAILURE) &&
(networkGlobals->timeout > 0))
return TRUE;
return FALSE;
}
return TRUE;
}
BOOLEAN sendHighScore(void)
{
uint16_t cycleCount = 0;
networkGlobals->hasHighScoreToSend = TRUE;
if (networkGlobals->gameNetworkState < GAME_NETWORK_TCP_UNCONNECTED)
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
do {
networkGlobals->initParams.waitForVbl();
pollNetwork();
cycleCount++;
if ((cycleCount & 0x7) == 0) {
switch (cycleCount & 0x18) {
case 0x00:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(0);
break;
case 0x08:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(1);
break;
case 0x10:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(2);
break;
case 0x18:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(3);
break;
}
}
} while (networkGlobals->gameNetworkState > GAME_NETWORK_TCP_UNCONNECTED);
if (networkGlobals->gameNetworkState != GAME_NETWORK_TCP_UNCONNECTED)
return FALSE;
if (networkGlobals->initParams.scorePosition != NULL)
networkGlobals->initParams.scorePosition(networkGlobals->setHighScoreResponse.position,
networkGlobals->setHighScoreResponse.numberOfScores);
return TRUE;
}

View File

@ -195,7 +195,6 @@ collisionAddr dc i2'0'
numSegments dc i2'0'
displayGlobalHighScores dc i2'0'
shouldQuit dc i2'1'
scoreIndex dc i2'0'
tileJumpTable dc a4'solid0'

View File

@ -14,13 +14,16 @@
#include "main.h"
#if __ORCAC__
segment "loadSounds";
#endif
static void loadSound(Word addr, Word soundNum)
{
Handle handle = LoadResource(rRawSound, soundNum);
HLock(handle);
WriteRamBlock(*handle, addr, GetHandleSize(handle));
WriteRamBlock(*handle, addr, (Word)GetHandleSize(handle));
HUnlock(handle);
}

View File

@ -10,7 +10,7 @@
#define _GUARD_PROJECTBuGS_FILEloadSounds_
#include <types.h>
#include <TYPES.h>
extern void loadSpiderSound(Word addr);

View File

@ -7,23 +7,33 @@
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <orca.h>
#include <Memory.h>
#include <Locator.h>
#include <MiscTool.h>
#include "main.h"
#include "game.h"
#include "globalScores.h"
#include "netScores.h"
#include "settings.h"
#include "tileData.h"
/* Defines and macros */
#define GLOBAL_SCORE_REFRESH_TIME (15 * 60 * 60)
#define SHUTDOWN_NETWORK_TIMEOUT (2 * 60)
#define TCP_CONNECT_TIMEOUT (8 * 60)
#define READ_NETWORK_TIMEOUT (5 * 60)
#define NETWORK_RETRY_TIMEOUT (3 * 60 * 60)
#define TOOLFAIL(string) \
if (toolerror()) SysFailMgr(toolerror(), "\p" string "\n\r Error Code -> $");
if (toolerror()) SysFailMgr(toolerror(), (Pointer)"\p" string "\n\r Error Code -> $");
/* Globals */
@ -34,6 +44,11 @@ unsigned int randomSeed;
// This symbol is used also from assembly directly so be careful with it...
char globalScoreInfo[GAME_NUM_TILES_WIDE + 1];
tNSGSHighScores globalHighScores;
Boolean hasGlobalHighScores = FALSE;
Word globalScoreAge = 0;
unsigned int scoreIndex = 0;
/* Implementation */
@ -46,7 +61,7 @@ word randomMushroomOffset(void)
}
void uploadSpin(int val)
static void uploadSpin(int val)
{
switch (val)
{
@ -69,12 +84,12 @@ void uploadSpin(int val)
}
void scorePosition(unsigned int position, unsigned int numberOfScores)
static void scorePosition(unsigned int position, unsigned int numberOfScores)
{
int i;
sprintf(globalScoreInfo, " %u OF %u SCORES", position, numberOfScores);
for (i = strlen(globalScoreInfo); i < sizeof(globalScoreInfo); i++) {
for (i = (int)strlen(globalScoreInfo); i < sizeof(globalScoreInfo); i++) {
globalScoreInfo[i] = ' ';
}
globalScoreInfo[GAME_NUM_TILES_WIDE] = '\0';
@ -83,19 +98,121 @@ void scorePosition(unsigned int position, unsigned int numberOfScores)
for (i = 6 * 60; i > 0; i--) {
waitForVbl();
}
globalScoreAge = 0;
}
void showConnectionString(BOOLEAN display)
static void showConnectionString(BOOLEAN display)
{
if (display)
displayConnectionString();
}
static char hexDigitToAscii(Word digit)
{
digit &= 0xf;
if (digit < 10)
return '0' + digit;
if (digit > 15)
return 'X';
return 'A' + digit - 10;
}
static void displayString(Word row, char * string)
{
strcpy(&(globalHighScores.score[row].scoreText[2]), string);
}
static void displayNetworkError(char * line1, char * line2, unsigned int errorCode)
{
Word row;
Word column;
for (row = 0; row < sizeof(globalHighScores.score) / sizeof(globalHighScores.score[0]); row++) {
for (column = 0;
column < sizeof(globalHighScores.score[0].scoreText) / sizeof(globalHighScores.score[0].scoreText[0]);
column++) {
globalHighScores.score[row].scoreText[column] = ' ';
}
for (column = 0;
column < sizeof(globalHighScores.score[0].who) / sizeof(globalHighScores.score[0].who[0]);
column++) {
globalHighScores.score[row].who[column] = ' ';
}
}
displayString(1, "NETWORK");
displayString(2, "PROBLEM");
displayString(4, line1);
displayString(5, line2);
globalHighScores.score[7].scoreText[0] = 'C';
globalHighScores.score[7].scoreText[1] = 'O';
globalHighScores.score[7].scoreText[2] = 'D';
globalHighScores.score[7].scoreText[3] = 'E';
globalHighScores.score[7].scoreText[4] = ':';
globalHighScores.score[7].scoreText[5] = ' ';
globalHighScores.score[7].scoreText[6] = hexDigitToAscii(errorCode >> 12);
globalHighScores.score[7].scoreText[7] = hexDigitToAscii(errorCode >> 8);
globalHighScores.score[7].scoreText[8] = hexDigitToAscii(errorCode >> 4);
globalHighScores.score[7].scoreText[9] = hexDigitToAscii(errorCode);
hasGlobalHighScores = TRUE;
globalScoreAge = 0;
}
static void displayError(tNSGSErrorType errorType, unsigned int errorCode)
{
switch (errorType) {
case NSGS_CONNECT_ERROR:
displayNetworkError("CONNECT", "FAILED", errorCode);
break;
case NSGS_LOOKUP_ERROR:
displayNetworkError("LOOKUP", "FAILED", errorCode);
break;
case NSGS_SOCKET_ERROR:
displayNetworkError("SOCKET", "ERROR", errorCode);
break;
case NSGS_PROTOCOL_ERROR:
displayNetworkError("PROTOCOL", "FAILED", errorCode);
break;
}
}
static void setHighScores(const tNSGSHighScores * highScores)
{
memcpy(&globalHighScores, highScores, sizeof(globalHighScores));
hasGlobalHighScores = TRUE;
globalScoreAge = GLOBAL_SCORE_REFRESH_TIME;
}
static BOOLEAN shouldRefreshHighScores(void)
{
return globalScoreAge == 0;
}
BOOLEAN sendHighScore(void)
{
const tNSGSHighScore * highScore = getHighScore(scoreIndex / sizeof(tNSGSHighScore));
return NSGS_SendHighScore(highScore->who, highScore->score);
}
int main(void)
{
static tHighScoreInitParams highScoreInitParams;
static tNSGSHighScoreInitParams highScoreInitParams;
int event;
Ref toolStartupRef;
@ -124,24 +241,30 @@ int main(void)
SetMouse(transparent);
highScoreInitParams.userId = myUserId;
highScoreInitParams.scoreServer = "\p" NETWORK_SERVER;
highScoreInitParams.scoreServer = (const char *)"\p" NETWORK_SERVER;
highScoreInitParams.scorePort = NETWORK_SERVERPORT;
highScoreInitParams.secret1 = NETWORK_SERVERSECRET1;
highScoreInitParams.secret2 = NETWORK_SERVERSECRET2;
highScoreInitParams.shutdownTimeout = SHUTDOWN_NETWORK_TIMEOUT;
highScoreInitParams.connectTimeout = TCP_CONNECT_TIMEOUT;
highScoreInitParams.readTimeout = READ_NETWORK_TIMEOUT;
highScoreInitParams.retryTimeout = NETWORK_RETRY_TIMEOUT;
highScoreInitParams.displayConnectionString = showConnectionString;
highScoreInitParams.waitForVbl = waitForVbl;
highScoreInitParams.uploadSpin = uploadSpin;
highScoreInitParams.scorePosition = scorePosition;
highScoreInitParams.displayError = displayError;
highScoreInitParams.setHighScores = setHighScores;
initNetwork(&highScoreInitParams);
NSGS_InitNetwork(&highScoreInitParams);
if (!loadSettings())
saveSettings();
game();
shutdownNetwork();
NSGS_ShutdownNetwork();
ShutDownTools(refIsHandle, toolStartupRef);
TOOLFAIL("Unable to shutdown tools");

745
BuGS/netScores.c Normal file
View File

@ -0,0 +1,745 @@
/*
* netScores.c
* NetScoresGS
*
* Created by Jeremy Rand on 2021-05-23.
* Copyright © 2021 Jeremy Rand. All rights reserved.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <Hash.h>
#include <locator.h>
#include <misctool.h>
#include <orca.h>
#include <tcpip.h>
#include "netScores.h"
// Defines
#define REQUEST_TYPE_GET_HIGH_SCORES 0
#define REQUEST_TYPE_SET_SCORE 1
#define RESPONSE_TYPE_HELLO 0
#define RESPONSE_TYPE_SCORES 1
#define RESPONSE_TYPE_STATUS 2
#define DEFAULT_SHUTDOWN_NETWORK_TIMEOUT (2 * 60)
#define DEFAULT_TCP_CONNECT_TIMEOUT (8 * 60)
#define DEFAULT_READ_NETWORK_TIMEOUT (5 * 60)
#define DEFAULT_NETWORK_RETRY_TIMEOUT (3 * 60 * 60)
// Types
typedef struct tSessionSecrets {
uint32_t secret;
uint32_t nonce;
} tSessionSecrets;
typedef struct tHighScoreRequest {
uint16_t requestType;
} tHighScoreRequest;
typedef struct tHighScoreRequestWithHash {
tHighScoreRequest highScoreRequest;
uint8_t md5Digest[16];
} tHighScoreRequestWithHash;
typedef struct tSetHighScoreRequest {
uint16_t requestType;
char who[4];
uint32_t score;
Word is60Hz;
} tSetHighScoreRequest;
typedef struct tSetHighScoreRequestWithHash {
tSetHighScoreRequest setHighScoreRequest;
uint8_t md5Digest[16];
} tSetHighScoreRequestWithHash;
typedef struct tHelloResponse {
uint16_t responseType;
uint32_t nonce;
} tHelloResponse;
typedef struct tScoresResponse {
uint16_t responseType;
tNSGSHighScores highScores;
} tScoresResponse;
typedef struct tStatusResponse {
uint16_t responseType;
Boolean success;
uint16_t position;
uint16_t numberOfScores;
} tStatusResponse;
typedef enum tGameNetworkState {
GAME_NETWORK_CONNECT_FAILED = 0,
GAME_NETWORK_UNCONNECTED,
/* All states below this should have Marinetti disconnected */
GAME_NETWORK_CONNECTED,
GAME_NETWORK_RESOLVING_NAME,
/* These are all of the error states. Marinetti is still connected but the
TCP connection is closed and ipid released. */
GAME_NETWORK_LOOKUP_FAILED,
GAME_NETWORK_SOCKET_ERROR,
GAME_NETWORK_PROTOCOL_FAILED,
GAME_NETWORK_FAILURE,
/* This is the state we are in during a game on a machine with a working network
(other possible states are fail states). */
GAME_NETWORK_TCP_UNCONNECTED,
/* All of these states below here have an ipid open and the TCP connection is
open and in some state. */
GAME_NETWORK_WAITING_FOR_TCP,
/* All of these states below this point have the TCP connection established
and the score protocol is exchanging information. */
GAME_NETWORK_WAITING_FOR_HELLO,
GAME_NETWORK_REQUEST_SCORES,
GAME_NETWORK_WAITING_FOR_SCORES,
GAME_NETWORK_SET_HIGH_SCORE,
GAME_NETWORK_WAITING_FOR_SCORE_ACK,
GAME_NETWORK_CLOSING_TCP,
NUM_GAME_NETWORK_STATES
} tGameNetworkState;
typedef void (*tGameNetworkStateHandler)(void);
typedef struct tGameNetworkGlobals {
Boolean networkStartedConnected;
tNSGSHighScoreInitParams initParams;
tGameNetworkState gameNetworkState;
dnrBuffer domainNameResolution;
srBuff tcpStatus;
Word ipid;
rrBuff readResponseBuf;
tHelloResponse helloResponse;
uint32_t bytesRead;
md5WorkBlock hashWorkBlock;
uint32_t secrets[3];
tHighScoreRequestWithHash highScoreRequest;
tScoresResponse highScoreResponse;
Boolean hasHighScoreToSend;
tSetHighScoreRequestWithHash setHighScoreRequest;
tStatusResponse setHighScoreResponse;
uint16_t errorCode;
uint16_t timeout;
Boolean hasGlobalHighScores;
} tGameNetworkGlobals;
// Forward declarations
static void handleConnectFailed(void);
static void handleUnconnected(void);
static void handleConnected(void);
static void handleResolvingName(void);
static void handleLookupFailed(void);
static void handleSocketError(void);
static void handleProtocolFailed(void);
static void handleFailure(void);
static void handleTcpUnconnected(void);
static void handleWaitingForTcp(void);
static void handleWaitingForHello(void);
static void handleRequestScores(void);
static void handleWaitingForScores(void);
static void handleSetHighScore(void);
static void handleWaitingForScoreAck(void);
static void handleClosingTcp(void);
// Globals
#if __ORCAC__
segment "highscores";
#endif
static tGameNetworkStateHandler handlers[NUM_GAME_NETWORK_STATES] = {
handleConnectFailed,
handleUnconnected,
handleConnected,
handleResolvingName,
handleLookupFailed,
handleSocketError,
handleProtocolFailed,
handleFailure,
handleTcpUnconnected,
handleWaitingForTcp,
handleWaitingForHello,
handleRequestScores,
handleWaitingForScores,
handleSetHighScore,
handleWaitingForScoreAck,
handleClosingTcp
};
// I am running out of space in the main segment so I am moving these globals into
// a dynamically allocated struct. It is only allocated if network capabilities are
// detected.
static tGameNetworkGlobals * networkGlobals = NULL;
// Implementation
void NSGS_InitNetwork(tNSGSHighScoreInitParams * params)
{
networkGlobals = NULL;
if ((params->scoreServer == NULL) ||
(params->scorePort == 0) ||
(params->waitForVbl == NULL))
return;
LoadOneTool(54, 0x200); // Load Marinetti
if (toolerror())
return;
LoadOneTool(128, 0x103); // Load the Hash toolset
if (toolerror()) {
UnloadOneTool(54);
return;
}
TCPIPStartUp();
if (toolerror()) {
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
hashStartUp();
if (toolerror()) {
TCPIPShutDown();
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
networkGlobals = malloc(sizeof(tGameNetworkGlobals));
if (networkGlobals == NULL) {
hashShutDown();
TCPIPShutDown();
UnloadOneTool(128);
UnloadOneTool(54);
return;
}
memcpy(&(networkGlobals->initParams), params, sizeof(networkGlobals->initParams));
if (networkGlobals->initParams.shutdownTimeout == 0)
networkGlobals->initParams.shutdownTimeout = DEFAULT_SHUTDOWN_NETWORK_TIMEOUT;
if (networkGlobals->initParams.connectTimeout == 0)
networkGlobals->initParams.connectTimeout = DEFAULT_TCP_CONNECT_TIMEOUT;
if (networkGlobals->initParams.readTimeout == 0)
networkGlobals->initParams.readTimeout = DEFAULT_READ_NETWORK_TIMEOUT;
if (networkGlobals->initParams.retryTimeout == 0)
networkGlobals->initParams.retryTimeout = DEFAULT_NETWORK_RETRY_TIMEOUT;
networkGlobals->networkStartedConnected = TCPIPGetConnectStatus();
if (networkGlobals->networkStartedConnected) {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECTED;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_UNCONNECTED;
}
networkGlobals->secrets[0] = params->secret1;
networkGlobals->secrets[1] = params->secret2;
networkGlobals->hasHighScoreToSend = FALSE;
networkGlobals->hasGlobalHighScores = FALSE;
networkGlobals->setHighScoreRequest.setHighScoreRequest.is60Hz = !ReadBParam(hrtz50or60);
}
void NSGS_ShutdownNetwork(void)
{
if ((!networkGlobals->networkStartedConnected) &&
(networkGlobals->gameNetworkState > GAME_NETWORK_UNCONNECTED)) {
TCPIPDisconnect(TRUE, NULL);
}
free(networkGlobals);
hashShutDown();
TCPIPShutDown();
UnloadOneTool(128); // Unload the Hash toolset
UnloadOneTool(54); // Unload Marinetti
}
static void abortConnection(void)
{
TCPIPAbortTCP(networkGlobals->ipid);
TCPIPLogout(networkGlobals->ipid);
}
void NSGS_DisconnectNetwork(void)
{
if (networkGlobals == NULL)
return;
if (networkGlobals->gameNetworkState > GAME_NETWORK_TCP_UNCONNECTED) {
if (networkGlobals->gameNetworkState < GAME_NETWORK_CLOSING_TCP) {
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = networkGlobals->initParams.shutdownTimeout;
}
}
while ((networkGlobals->gameNetworkState != GAME_NETWORK_TCP_UNCONNECTED) &&
(networkGlobals->gameNetworkState != GAME_NETWORK_FAILURE)) {
networkGlobals->initParams.waitForVbl();
NSGS_PollNetwork();
}
}
static void handleConnectFailed(void)
{
if (networkGlobals->initParams.displayError != NULL)
networkGlobals->initParams.displayError(NSGS_CONNECT_ERROR, networkGlobals->errorCode);
networkGlobals->gameNetworkState = GAME_NETWORK_FAILURE;
}
static void handleUnconnected(void)
{
if (networkGlobals->initParams.displayConnectionString != NULL)
networkGlobals->initParams.displayConnectionString(TRUE);
TCPIPConnect(NULL);
if ((!toolerror()) &&
(TCPIPGetConnectStatus())) {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECTED;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_CONNECT_FAILED;
networkGlobals->errorCode = toolerror();
}
if (networkGlobals->initParams.displayConnectionString != NULL)
networkGlobals->initParams.displayConnectionString(FALSE);
}
static void handleConnected(void)
{
TCPIPDNRNameToIP((char *)networkGlobals->initParams.scoreServer, &(networkGlobals->domainNameResolution));
if (toolerror()) {
networkGlobals->gameNetworkState = GAME_NETWORK_LOOKUP_FAILED;
networkGlobals->errorCode = toolerror();
} else
networkGlobals->gameNetworkState = GAME_NETWORK_RESOLVING_NAME;
}
static void handleResolvingName(void)
{
if (networkGlobals->domainNameResolution.DNRstatus == DNR_Pending)
return;
if (networkGlobals->domainNameResolution.DNRstatus != DNR_OK) {
networkGlobals->gameNetworkState = GAME_NETWORK_LOOKUP_FAILED;
networkGlobals->errorCode = networkGlobals->domainNameResolution.DNRstatus;
return;
}
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
static void handleLookupFailed(void)
{
if (networkGlobals->initParams.displayError != NULL)
networkGlobals->initParams.displayError(NSGS_LOOKUP_ERROR, networkGlobals->errorCode);
networkGlobals->gameNetworkState = GAME_NETWORK_FAILURE;
}
static void handleSocketError(void)
{
if (networkGlobals->initParams.displayError != NULL)
networkGlobals->initParams.displayError(NSGS_SOCKET_ERROR, networkGlobals->errorCode);
networkGlobals->gameNetworkState = GAME_NETWORK_FAILURE;
networkGlobals->timeout = networkGlobals->initParams.retryTimeout;
}
static void handleProtocolFailed(void)
{
abortConnection();
if (networkGlobals->initParams.displayError != NULL)
networkGlobals->initParams.displayError(NSGS_PROTOCOL_ERROR, networkGlobals->errorCode);
networkGlobals->gameNetworkState = GAME_NETWORK_FAILURE;
networkGlobals->timeout = networkGlobals->initParams.retryTimeout;
}
static void handleFailure(void)
{
// All of the different failure modes except protocol failure above end up here ultimately. And the state
// machine stays here once it arrives here.
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
if (networkGlobals->timeout == 0)
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
}
static void handleTcpUnconnected(void)
{
if ((networkGlobals->hasGlobalHighScores) &&
(!networkGlobals->hasHighScoreToSend)) {
if (networkGlobals->initParams.shouldRefreshHighScores == NULL)
return;
if (!networkGlobals->initParams.shouldRefreshHighScores())
return;
}
networkGlobals->ipid = TCPIPLogin(networkGlobals->initParams.userId, networkGlobals->domainNameResolution.DNRIPaddress, networkGlobals->initParams.scorePort, 0, 64);
if (toolerror()) {
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
networkGlobals->errorCode = toolerror();
return;
}
networkGlobals->errorCode = TCPIPOpenTCP(networkGlobals->ipid);
if (networkGlobals->errorCode != tcperrOK) {
TCPIPLogout(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_TCP;
networkGlobals->timeout = networkGlobals->initParams.connectTimeout;
}
static void handleWaitingForTcp(void)
{
networkGlobals->errorCode = TCPIPStatusTCP(networkGlobals->ipid, &(networkGlobals->tcpStatus));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
if ((networkGlobals->tcpStatus.srState == TCPSSYNSENT) ||
(networkGlobals->tcpStatus.srState == TCPSSYNRCVD)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_TCP_CONNECT_TIMEOUT_ERROR;
}
return;
}
if (networkGlobals->tcpStatus.srState != TCPSESTABLISHED) {
abortConnection();
networkGlobals->errorCode = networkGlobals->tcpStatus.srState | 0x8000;
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_HELLO;
networkGlobals->timeout = networkGlobals->initParams.readTimeout;
networkGlobals->bytesRead = 0;
}
static void handleWaitingForHello(void)
{
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&(networkGlobals->helloResponse))) + networkGlobals->bytesRead,
sizeof(networkGlobals->helloResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(networkGlobals->helloResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HELLO_TIMEOUT_ERROR;
}
return;
}
if (networkGlobals->bytesRead > sizeof(networkGlobals->helloResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HELLO_TOO_BIG_ERROR;
return;
}
if (networkGlobals->helloResponse.responseType != RESPONSE_TYPE_HELLO) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HELLO_UNEXPECTED_RESPONSE_ERROR;
return;
}
networkGlobals->secrets[2] = networkGlobals->helloResponse.nonce;
if (networkGlobals->hasHighScoreToSend) {
networkGlobals->gameNetworkState = GAME_NETWORK_SET_HIGH_SCORE;
} else if ((!networkGlobals->hasGlobalHighScores) ||
((networkGlobals->initParams.shouldRefreshHighScores != NULL) &&
(networkGlobals->initParams.shouldRefreshHighScores()))) {
networkGlobals->gameNetworkState = GAME_NETWORK_REQUEST_SCORES;
} else {
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = networkGlobals->initParams.shutdownTimeout;
}
}
static void handleRequestScores(void)
{
networkGlobals->highScoreRequest.highScoreRequest.requestType = REQUEST_TYPE_GET_HIGH_SCORES;
md5Init(&(networkGlobals->hashWorkBlock));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->secrets), sizeof(networkGlobals->secrets));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->highScoreRequest.highScoreRequest), sizeof(networkGlobals->highScoreRequest.highScoreRequest));
md5Finish(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->highScoreRequest.md5Digest[0]));
networkGlobals->errorCode = TCPIPWriteTCP(networkGlobals->ipid, (Pointer)&(networkGlobals->highScoreRequest), sizeof(networkGlobals->highScoreRequest), FALSE, FALSE);
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_SCORES;
networkGlobals->timeout = networkGlobals->initParams.readTimeout;
networkGlobals->bytesRead = 0;
}
static void handleWaitingForScores(void)
{
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&networkGlobals->highScoreResponse)) + networkGlobals->bytesRead,
sizeof(networkGlobals->highScoreResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(networkGlobals->highScoreResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HIGH_SCORE_TIMEOUT_ERROR;
}
return;
}
if (networkGlobals->bytesRead > sizeof(networkGlobals->highScoreResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HIGH_SCORE_TOO_BIG_ERROR;
return;
}
if (networkGlobals->highScoreResponse.responseType != RESPONSE_TYPE_SCORES) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_HIGH_SCORE_UNEXPECTED_RESPONSE_ERROR;
return;
}
networkGlobals->hasGlobalHighScores = TRUE;
if (networkGlobals->initParams.setHighScores != NULL)
networkGlobals->initParams.setHighScores(&(networkGlobals->highScoreResponse.highScores));
TCPIPCloseTCP(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_CLOSING_TCP;
networkGlobals->timeout = networkGlobals->initParams.shutdownTimeout;
}
static void handleSetHighScore(void)
{
networkGlobals->setHighScoreRequest.setHighScoreRequest.requestType = REQUEST_TYPE_SET_SCORE;
networkGlobals->setHighScoreRequest.setHighScoreRequest.who[3] = '\0';
md5Init(&(networkGlobals->hashWorkBlock));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->secrets), sizeof(networkGlobals->secrets));
md5Append(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->setHighScoreRequest.setHighScoreRequest), sizeof(networkGlobals->setHighScoreRequest.setHighScoreRequest));
md5Finish(&(networkGlobals->hashWorkBlock), (Pointer)&(networkGlobals->setHighScoreRequest.md5Digest[0]));
networkGlobals->errorCode = TCPIPWriteTCP(networkGlobals->ipid, (Pointer)&(networkGlobals->setHighScoreRequest), sizeof(networkGlobals->setHighScoreRequest), FALSE, FALSE);
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->gameNetworkState = GAME_NETWORK_WAITING_FOR_SCORE_ACK;
networkGlobals->timeout = networkGlobals->initParams.readTimeout;
networkGlobals->bytesRead = 0;
}
static void handleWaitingForScoreAck(void)
{
networkGlobals->errorCode = TCPIPReadTCP(networkGlobals->ipid, 0,
((uint32_t)(&(networkGlobals->setHighScoreResponse))) + networkGlobals->bytesRead,
sizeof(networkGlobals->setHighScoreResponse) - networkGlobals->bytesRead,
&(networkGlobals->readResponseBuf));
if (networkGlobals->errorCode != tcperrOK) {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_SOCKET_ERROR;
return;
}
networkGlobals->bytesRead += networkGlobals->readResponseBuf.rrBuffCount;
if (networkGlobals->bytesRead < sizeof(networkGlobals->setHighScoreResponse)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_SET_SCORE_TIMEOUT_ERROR;
}
return;
}
if (networkGlobals->bytesRead > sizeof(networkGlobals->setHighScoreResponse)) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_SET_SCORE_TOO_BIG_ERROR;
return;
}
if (networkGlobals->setHighScoreResponse.responseType != RESPONSE_TYPE_STATUS) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_SET_SCORE_UNEXPECTED_RESPONSE_ERROR;
return;
}
if (!networkGlobals->setHighScoreResponse.success) {
networkGlobals->gameNetworkState = GAME_NETWORK_PROTOCOL_FAILED;
networkGlobals->errorCode = NSGS_SET_SCORE_FAILED_ERROR;
return;
}
networkGlobals->hasHighScoreToSend = FALSE;
networkGlobals->gameNetworkState = GAME_NETWORK_REQUEST_SCORES;
}
static void handleClosingTcp(void)
{
networkGlobals->errorCode = TCPIPStatusTCP(networkGlobals->ipid, &(networkGlobals->tcpStatus));
if ((networkGlobals->tcpStatus.srState != TCPSCLOSED) &&
(networkGlobals->tcpStatus.srState != TCPSTIMEWAIT)) {
if (networkGlobals->timeout > 0) {
networkGlobals->timeout--;
} else {
abortConnection();
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
return;
}
TCPIPLogout(networkGlobals->ipid);
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
}
void NSGS_PollNetwork(void)
{
if (networkGlobals == NULL)
return;
TCPIPPoll();
handlers[networkGlobals->gameNetworkState]();
}
BOOLEAN NSGS_CanSendHighScore(void)
{
if (networkGlobals == NULL)
return FALSE;
if (networkGlobals->gameNetworkState < GAME_NETWORK_TCP_UNCONNECTED) {
if ((networkGlobals->gameNetworkState == GAME_NETWORK_FAILURE) &&
(networkGlobals->timeout > 0))
return TRUE;
return FALSE;
}
return TRUE;
}
BOOLEAN NSGS_SendHighScore(const char * who, unsigned long score)
{
uint16_t cycleCount = 0;
if (strlen(who) != 3)
return FALSE;
networkGlobals->hasHighScoreToSend = TRUE;
memcpy(networkGlobals->setHighScoreRequest.setHighScoreRequest.who, who, sizeof(networkGlobals->setHighScoreRequest.setHighScoreRequest.who));
networkGlobals->setHighScoreRequest.setHighScoreRequest.score = score;
if (networkGlobals->gameNetworkState < GAME_NETWORK_TCP_UNCONNECTED)
networkGlobals->gameNetworkState = GAME_NETWORK_TCP_UNCONNECTED;
do {
networkGlobals->initParams.waitForVbl();
NSGS_PollNetwork();
cycleCount++;
if ((cycleCount & 0x7) == 0) {
switch (cycleCount & 0x18) {
case 0x00:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(0);
break;
case 0x08:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(1);
break;
case 0x10:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(2);
break;
case 0x18:
if (networkGlobals->initParams.uploadSpin != NULL)
networkGlobals->initParams.uploadSpin(3);
break;
}
}
} while (networkGlobals->gameNetworkState > GAME_NETWORK_TCP_UNCONNECTED);
if (networkGlobals->gameNetworkState != GAME_NETWORK_TCP_UNCONNECTED)
return FALSE;
if (networkGlobals->initParams.scorePosition != NULL)
networkGlobals->initParams.scorePosition(networkGlobals->setHighScoreResponse.position,
networkGlobals->setHighScoreResponse.numberOfScores);
return TRUE;
}

View File

@ -1,27 +1,54 @@
/*
* globalScores.h
* BuGS
* netScores.h
* NetScoresGS
*
* Created by Jeremy Rand on 2021-05-23.
* Copyright © 2021 Jeremy Rand. All rights reserved.
*/
#ifndef _GUARD_PROJECTBuGS_FILEglobalScores_
#define _GUARD_PROJECTBuGS_FILEglobalScores_
#ifndef _GUARD_PROJECTNetScoresGS_FILEnetScores_
#define _GUARD_PROJECTNetScoresGS_FILEnetScores_
#include <types.h>
#include <TYPES.h>
typedef enum tNSGSErrorType
{
NSGS_CONNECT_ERROR,
NSGS_LOOKUP_ERROR,
NSGS_SOCKET_ERROR,
NSGS_PROTOCOL_ERROR
} tNSGSErrorType;
typedef struct tHighScore
typedef enum ttNSGSProtocolErrors {
NSGS_TCP_CONNECT_TIMEOUT_ERROR = 1,
NSGS_HELLO_TIMEOUT_ERROR,
NSGS_HELLO_TOO_BIG_ERROR,
NSGS_HELLO_UNEXPECTED_RESPONSE_ERROR,
NSGS_HIGH_SCORE_TIMEOUT_ERROR,
NSGS_HIGH_SCORE_TOO_BIG_ERROR,
NSGS_HIGH_SCORE_UNEXPECTED_RESPONSE_ERROR,
NSGS_SET_SCORE_TIMEOUT_ERROR,
NSGS_SET_SCORE_TOO_BIG_ERROR,
NSGS_SET_SCORE_UNEXPECTED_RESPONSE_ERROR,
NSGS_SET_SCORE_FAILED_ERROR,
} ttNSGSProtocolErrors;
typedef struct tNSGSHighScore
{
char scoreText[10];
char who[4];
unsigned long score;
} tHighScore;
} tNSGSHighScore;
typedef struct tNSGSHighScores
{
tNSGSHighScore score[10];
} tNSGSHighScores;
typedef struct tHighScoreInitParams
typedef struct tNSGSHighScoreInitParams
{
/* This structure is initialized by the game and passed by pointer to the high score code. It consists of
a series of data members and a series of callbacks which are provided by the game.
@ -42,11 +69,31 @@ typedef struct tHighScoreInitParams
unsigned long secret1;
unsigned long secret2;
/* Each of these timeouts are measured in poll periods. So, if you call NSGS_PollNetwork() every 60th of a
second, then you would use 60 to set the timeout to 1 second. If these are zero, then the "good value"
in the comment will be used as a default.
The shutdown timeout controls how long we wait for a clean TCP disconnection before forcing an abort of
the connection. Two seconds is a good value for this timeout. */
unsigned int shutdownTimeout;
/* The connect timeout is the amount of time we wait for a TCP connection to come up before declaring a
timeout protocol error. Eight seconds is a good value for this timeout. */
unsigned int connectTimeout;
/* The read timeout is the amount of time we wait for a response from the server after we have made a
request of it, whether that is getting the high score list or setting a new high score. Five seconds
is a good value for this timeout. */
unsigned int readTimeout;
/* The retry timeout is the amount of time we wait in an error state before retrying. This only happens
for "soft" errors where a retry is worthwhile. Three minutes is a good value for this timeout. */
unsigned int retryTimeout;
/* This function should display a message to the user that the network is being brought up and they should
be patient when the argument is TRUE and when the argument is FALSE, it should clear that message. This
function shouldn't block and just put something on the screen to say that the connection is being brought
up or clear that message. It is called sometimes (rarely) by pollNetwork(). It is called with argument
up or clear that message. It is called sometimes (rarely) by NSGS_PollNetwork(). It is called with argument
TRUE and will always be called with FALSE sometime after that.
*/
void (*displayConnectionString)(BOOLEAN display);
@ -59,7 +106,7 @@ typedef struct tHighScoreInitParams
/* This argument iterates over 0, 1, 2, 3 and then back to 0, 1, 2, etc and is intended to show some kind
of spinner to the user while uploading a high score to the server. This function shouldn't block for
any real amount of time and just cause something on the screen to change to make sure the player doesn't
think something has hung while the upload is in progress. It is called when sendHighScore() is called
think something has hung while the upload is in progress. It is called when NSGS_SendHighScore() is called
by the game.
*/
void (*uploadSpin)(int);
@ -68,52 +115,68 @@ typedef struct tHighScoreInitParams
of this player's score among the total number of scores recorded for this game. This information
should be displayed to the user. The function can block while this information is being displayed
and that message should be cleaned up before the function returns to the caller. This function is
called by sendHighScore().
called by NSGS_SendHighScore().
*/
void (*scorePosition)(unsigned int position, unsigned int numberOfScores);
} tHighScoreInitParams;
/* This function is only called from NSGS_PollNetwork() when something unexpected has occurred.
The meaning of the error code depends on the error type. For a protocol error, the error code
is one of tNSGSProtocolErrors. For other error codes, they come from Marinetti error codes.
if the error code > $8000, then the error code is the socket state or-ed with $8000. */
void (*displayError)(tNSGSErrorType errorType, unsigned int errorCode);
/* This function is only called from NSGS_PollNetwork() when new scores have been downloaded.
The scores passed should be copied because the pointer is not guaranteed to be valid after
the callback returns. */
void (*setHighScores)(const tNSGSHighScores * scores);
/* This function is called to ask if it is time to refresh the global high score list. This should
be based on watching the elapsed time and it should return true if say 5 minutes has elapsed
since high scores have been updated. */
BOOLEAN (*shouldRefreshHighScores)(void);
} tNSGSHighScoreInitParams;
/* Call this function once at launch. The pointer to the parameters is copied so the structure does not need
to remain valid after the call to this function.
*/
extern void initNetwork(tHighScoreInitParams * params);
extern void NSGS_InitNetwork(tNSGSHighScoreInitParams * params);
/* Call this when a game is about to start. It will interrupt any network operation in progress and get ready for
a quiet period where polling will stop until the game is over. That way, all CPU time can be focused on the game.
This function may call the waitForVbl() callback.
*/
extern void disconnectNetwork(void);
extern void NSGS_DisconnectNetwork(void);
/* Call this every frame refresh period when a game is _not_ in progress. This does any network operations required
to download high scores. During this function call, the displayConnectionString() callback may be called.
*/
extern void pollNetwork(void);
extern void NSGS_PollNetwork(void);
/* Call this function once when the game is quitting. */
extern void shutdownNetwork(void);
extern void NSGS_ShutdownNetwork(void);
/* Call this function when the player has a high score that should be recorded online. This function will return
TRUE if the network is up and a high score can be sent and if so, the game should call sendHighScore() to send
TRUE if the network is up and a high score can be sent and if so, the game should call NSGS_SendHighScore() to send
the high score. If FALSE is returned, then the user is playing while offline and no attempt should be made to
send the high score.
*/
extern BOOLEAN canSendHighScore(void);
extern BOOLEAN NSGS_CanSendHighScore(void);
/* Assuming canSendHighScore() returned TRUE, the game can call this function to actually try to send the high score
/* Assuming NSGS_CanSendHighScore() returned TRUE, the game can call this function to actually try to send the high score
to the server. If this function returns TRUE, then the score was successfully sent to the server. If FALSE
is returned, then an error has occurred. The game can offer the user the option to retry to the upload of the
score and if the user would like to retry, just call sendHighScore() again. During this function call, the
score and if the user would like to retry, just call NSGS_SendHighScore() again. During this function call, the
waitForVbl(), uploadSpin() and scorePosition() callbacks may be called.
TODO - Pass the score as an argument rather than through globals.
*/
extern BOOLEAN sendHighScore(void);
extern BOOLEAN NSGS_SendHighScore(const char * who, unsigned long score);
#endif /* define _GUARD_PROJECTBuGS_FILEglobalScores_ */
#endif /* define _GUARD_PROJECTNetScoresGS_FILEnetScores_ */

View File

@ -438,10 +438,8 @@ checkHighScore_doneCopy anop
tax
lda gameScore,x
sta settings+SETTINGS_HIGH_SCORE_OFFSET+HIGH_SCORE_SCORE_OFFSET,y
sta setHighScoreRequest+6
lda gameScore+2,x
sta settings+SETTINGS_HIGH_SCORE_OFFSET+HIGH_SCORE_SCORE_OFFSET+2,y
sta setHighScoreRequest+8
lda playerNum
cmp #PLAYER_ONE
@ -782,12 +780,8 @@ checkHighScore_isInvalid anop
checkHighScore_doneInitials anop
_overwriteGameTile TILE_EMPTY
lda settings+SETTINGS_HIGH_SCORE_OFFSET+HIGH_SCORE_WHO_OFFSET-3,y
sta setHighScoreRequest+2
lda settings+SETTINGS_HIGH_SCORE_OFFSET+HIGH_SCORE_WHO_OFFSET-1,y
sta setHighScoreRequest+4
jsl saveSettings
jsl canSendHighScore
jsl NSGS_CanSendHighScore
bne checkHighScore_retry
brl checkHighScore_doneNetwork

View File

@ -6,11 +6,14 @@
* Copyright © 2021 Jeremy Rand. All rights reserved.
*/
#include <string.h>
#include <gsos.h>
#include <orca.h>
#include <Memory.h>
#include "globalScores.h"
#include "game.h"
#include "netScores.h"
#include "settings.h"
#include "tileData.h"
@ -27,7 +30,7 @@ typedef struct tSettingsData
char magic[4];
int version;
Boolean stereoCorrect;
tHighScore highScores[NUM_HIGH_SCORES];
tNSGSHighScore highScores[NUM_HIGH_SCORES];
} tSettingsData;
@ -62,8 +65,17 @@ static Handle filenameHandle = NULL;
// Implementation
#if __ORCAC__
segment "settings";
#endif
const tNSGSHighScore * getHighScore(unsigned int index)
{
if (index >= NUM_HIGH_SCORES)
return NULL;
return &(settings.highScores[index]);
}
static void allocateFilenameHandle(void)
{

View File

@ -9,14 +9,18 @@
#ifndef _GUARD_PROJECTBuGS_FILEsettings_
#define _GUARD_PROJECTBuGS_FILEsettings_
#include <types.h>
#include <TYPES.h>
typedef struct tNSGSHighScore tNSGSHighScore;
extern unsigned int myUserId;
extern void saveSettings(void);
BOOLEAN loadSettings(void);
extern BOOLEAN loadSettings(void);
extern void swapStereoSettings(void);
extern const tNSGSHighScore * getHighScore(unsigned int index);
#endif /* define _GUARD_PROJECTBuGS_FILEsettings_ */