#include #include #include #include #include #include #include "tracker.h" #include "gameinitexit.h" #include "gametime.h" #include "carphysics.h" #include "config.h" #include "text.h" #include "gameframe.h" #include "error.h" #include "initexit.h" #include "network.h" #include "interfaceutil.h" #include "gamesystem.h" #include "screen.h" ReggieRef gReggieRef=NULL; int gReggieConnected=false; int gReggieSearching=false; int gReggieLastUpdateGInfoVersion; int gHasCheckedLonelyServer; int gReggiePortMapped=false; int gReggieCounting=false; float gLastQuery=-1000; float gConnectionTime; float gReportedIdle=false; char gMOTD[1024]; extern NT_SessionRef gSessionRef; extern NetworkDatagramRef NT_GetSessionEndpoint(NT_SessionRef ref); #define kReggieUpdateInterval 1 int CouldMyNATCauseMyServerToBeLonely() { NetworkAddress local; DatagramGetAddress(NT_GetSessionEndpoint(gSessionRef),&local); ReggieNetworkType network = ReggieProbeCheck(gReggieRef,false,&local,nil); return((network != kReggieNetworkOffline) && (network <=kReggieNetworkProtective) ? true : false); } #define kMessageInputBufferSize 8 #define kMaxMessageSize 1024 char gMessageInputBuffer[kMessageInputBufferSize][kMaxMessageSize]; int gMessageInputBufferPos=0; int gCheckingVersion=false; char *gRedlineURL=NULL; char *gAutoUpdateURL=NULL; void TrackerStartVersionCheck() { if(NetworkStackGetActive(eNetworkStackTCPIP)) { ReggieVersionBegin(nil,"REDLINE"); gCheckingVersion = true; } } int TrackerVersionCheck(char **newVersion) { if(gCheckingVersion) { HashRef hashRef=ReggieVersionCheck(nil,"REDLINE"); if(hashRef) { if(gConfig->useBetaBuilds) { gRedlineURL=HashLookup(hashRef, "$BETAURL", nil); gAutoUpdateURL=HashLookup(hashRef, "$BETAAUTOUPDATEURL", nil); Char8 *hashLookupResult; int versionNum=0; if(hashLookupResult=HashLookup(hashRef, "$BETAVERSIONNUM", nil)) sscanf(hashLookupResult,"%d",&versionNum); if(*newVersion=HashLookup(hashRef, "$BETAVERSION", nil)) { gCheckingVersion = false; /* Don't check again */ if(versionNum) return versionNum>kVersion; else return _stricmp(*newVersion,kVersionString); } } else { gRedlineURL=HashLookup(hashRef, "$URL", nil); gAutoUpdateURL=HashLookup(hashRef, "$AUTOUPDATEURL", nil); Char8 *hashLookupResult; int versionNum=0; if(hashLookupResult=HashLookup(hashRef, "$VERSIONNUM", nil)) sscanf(hashLookupResult,"%d",&versionNum); if(*newVersion=HashLookup(hashRef, "$VERSION", nil)) { gCheckingVersion = false; /* Don't check again */ if(versionNum) return versionNum>kVersion; else return _stricmp(*newVersion,kVersionString); } } } } return false; } int TrackerConnect() { gStartIdleTime=TimeGetSeconds(); if(!gSessionRef) NetworkPreSession(); if(!ReggieCreateShared(&gReggieRef,NULL,NULL,NT_GetSessionEndpoint(gSessionRef),NULL,"redline",gConfig->playerName,"")) { gReggieLastUpdateGInfoVersion=-1; gHasCheckedLonelyServer=false; gConnectionTime=0; gReggiePortMapped=false; gReggieConnected=true; gReggieSearching=false; gReggieCounting=false; return true; } return false; } int ValidateUpdateURL() { if(!gAutoUpdateURL) return false; char *pos=strstr(gAutoUpdateURL,"://"); if(!pos)return false; pos+=3; char *dash=strstr(pos,"/"); if(!dash)return false; char validurl[]=".ambrosiasw.com"; char test[256]; memcpy(test,dash-strlen(validurl),strlen(validurl)); test[strlen(validurl)]='\0'; if(_stricmp(test,validurl)) {printf("update URL invaild\n");return false;} return true; } void TrackerGetNewVersion() { if(ValidateUpdateURL()) { if(!ScreenNoWindow()) { if(gConfig->fullscreen) { gConfig->fullscreen=false; ScreenReInit(); gConfig->fullscreen=true; } InterfaceDrawStrings("Trying automatic update","Please wait a moment...",-1); char updateURL[1024]; char failStr[1024]=""; sprintf(updateURL,"%s%s",gAutoUpdateURL,kVersionString); if(AutoUpdateRedline(updateURL,failStr)) // if(AutoUpdateRedline(gAutoUpdateURL)) { InterfaceDisplayMessage(-1,"Update successful!","You will have to relaunch Redline now."); return; } else InterfaceDisplayMessage(-1,"Automatic update failed.",failStr); } } if(gRedlineURL) { CFStringRef str=CFStringCreateWithCString(kCFAllocatorDefault,gRedlineURL, kCFStringEncodingASCII); CFURLRef url=CFURLCreateWithString(kCFAllocatorDefault,str,NULL); //LSOpenCFURLRef(url,NULL); LSLaunchURLSpec inLaunchSpec; inLaunchSpec.appURL=NULL; inLaunchSpec.itemURLs=CFArrayCreate(kCFAllocatorDefault,(const void**)&url,1,NULL); inLaunchSpec.passThruParams=NULL; inLaunchSpec.launchFlags=kLSLaunchNoParams; inLaunchSpec.asyncRefCon=NULL; LSOpenFromURLSpec(&inLaunchSpec,NULL); } else { CFURLRef url=CFURLCreateWithString(kCFAllocatorDefault,CFSTR("http://www.AmbrosiaSW.com/"),NULL); //LSOpenCFURLRef(url,NULL); LSLaunchURLSpec inLaunchSpec; inLaunchSpec.appURL=NULL; inLaunchSpec.itemURLs=CFArrayCreate(kCFAllocatorDefault,(const void**)&url,1,NULL); inLaunchSpec.passThruParams=NULL; inLaunchSpec.launchFlags=kLSLaunchNoParams; inLaunchSpec.asyncRefCon=NULL; LSOpenFromURLSpec(&inLaunchSpec,NULL); } } enum{ kReggieAdvGameChannel=1, kReggieSearchGameChannel, kReggieSearchPlayersChannel=4, kReggiePortMapChannel, kReggieInsertRecordChannel, kReggieRegistryChannel, kReggieRecordChannel }; void TrackerMessageSend(char *message) { if(gReggieConnected) { HashRef hashRef = HashCreate(kHashFlagReggieKeys); HashAppend(hashRef, "$KIND", "REDLINE"); HashAppend(hashRef, "$MESSAGE", message); ReggieMessageSend(gReggieRef,nil,hashRef); HashDispose(hashRef); if(gMessageInputBufferPosstr,"### %s",motd); msg->flags=kChatFlagSystem|kChatFlagSilent; return true; } HashRef hash=ReggieMessageCheck(gReggieRef); if(hash) { char *hashResult=nil; do{ hashResult=HashLookup(hash,"$MESSAGE",hashResult); if(hashResult) if(gMessageInputBufferPosstr,gMessageInputBuffer[--gMessageInputBufferPos]); msg->flags=0; if(strcmp(lastMessage,msg->str)) { strcpy(lastMessage,msg->str); return true; } else return false; } } return false; } void NetworkAdvertiseIdle(tGameInfo *gInfo,int *showLonelyMessage,int locked,int forceUpdate) { if(showLonelyMessage) *showLonelyMessage=false; if(gReggieConnected) { UInt32 state=ReggieGetStatus(gReggieRef,gMOTD,NULL); if(state==kReggieStateOnline) { if(gConnectionTime==0) gConnectionTime=TimeGetSeconds(); if((gReggieLastUpdateGInfoVersion!=gInfo->version||forceUpdate)&&gConfig->trackerEnable) { char hashString[1024]; if(gInfo->map==kFileErr) return; tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(gInfo->map,kParserTypeMapInfoDesc,sizeof(tMapInfo)); while(char *ch=strchr(gConfig->gameName,'\'')) *ch='`'; sprintf(hashString,"$REDLINE$HOSTING=1 $REDLINE$PASSWORD='%d' $REDLINE$NUMPLAYERS=%d $REDLINE$NUMNETPLAYERS=%d $REDLINE$MAXPLAYERS=%d $REDLINE$ARCADE=%d $REDLINE$LOCKED=%d $REDLINE$ONLYREGISTERED=%d $REDLINE$LAPS=%d",strlen(gConfig->password)?1:0,gInfo->numPlayers,gInfo->numNetPlayers,gConfig->maxPlayers,gInfo->arcade,locked,gConfig->onlyRegisteredPlayers,gInfo->numLaps); HashRef hashRef = HashInitialize(kHashFlagReggieKeys,hashString); HashAppend(hashRef, "$MESSAGE", gConfig->gameName); HashAppend(hashRef, "$REDLINE$MAP", mapInfo->name); HashAppend(hashRef, "$REDLINE$MAPFILE",FileGetName(gInfo->map)); for(int i=0;inumPlayers;i++) { NT_MemberInfo memberInfo; char key[256]; sprintf(key,"$REDLINE$PLAYER%dNAME",i); HashAppend(hashRef, key, gInfo->playerNames[i]); if(gInfo->playerCars[i]!=-1) { tCarDefinition *car=(tCarDefinition*)FileGetParsedDataPtr(gInfo->playerCars[i],kParserTypeCarDesc,sizeof(tCarDefinition)); sprintf(key,"$REDLINE$PLAYER%dCAR",i); HashAppend(hashRef, key, car->carName); } if(i>0&&inumNetPlayers) { if(NT_GetMemberInfo(gSessionRef,gInfo->netID[i],&memberInfo)) { char address[256]; AddressTranscribe(&memberInfo.address,address); sprintf(key,"$ADDRESS$PLAYER%d",i); HashAppend(hashRef, key, address); } } else if(i==0) { NetworkAddress local,publicAdd; char address[256]; DatagramGetAddress(NT_GetSessionEndpoint(gSessionRef),&local); ReggieProbeCheck(gReggieRef,false,&local,&publicAdd); if(!AddressIsEmpty(&publicAdd)) { AddressTranscribe(&publicAdd,address); HashAppend(hashRef, "$ADDRESS$PLAYER0", address); } } } ReggieInsertBegin(gReggieRef,kReggieAdvGameChannel, kReggieRecordAdvertise ,hashRef);/* Makes own copy of hash */ HashDispose(hashRef); /* Not needed any more */ gReggieLastUpdateGInfoVersion=gInfo->version; } if(!gHasCheckedLonelyServer) if(TimeGetSeconds()>gConnectionTime+3) if(showLonelyMessage) { *showLonelyMessage=CouldMyNATCauseMyServerToBeLonely(); //printf("CouldMyNATCauseMyServerToBeLonely sez %d\n",*showLonelyMessage); gHasCheckedLonelyServer=true; } if(!gReggiePortMapped) if(TimeGetSeconds()>gConnectionTime+1) { NetworkAddress local; UInt16 localPort; DatagramGetAddress(NT_GetSessionEndpoint(gSessionRef),&local); AddressDecomposeTCPIP(&local,&localPort,nil); U16Swap(localPort); //printf("PORTMAP: %d\n",ReggiePortmapStatus(gReggieRef, nil, nil, &local)); //if (ReggiePortmapStatus(gReggieRef, nil, nil, &local) > kReggiePortmapNone) ReggiePortmapBegin(gReggieRef,kReggiePortMapChannel,false,localPort,localPort,"Redline"); gReggiePortMapped=true; } } else if(state!=kReggieStatePending) { NetworkStopAdvertising(); ReggieDispose(gReggieRef,true); gReggieConnected=false; } } else TrackerConnect(); } void NetworkStopAdvertising() { if(gReggieConnected) { ReggieChannelClose(gReggieRef,kReggieAdvGameChannel); // ReggieDispose(gReggieRef,true); // gReggieConnected=false; } } void QueryGameSearch() { HashRef hashRef = HashInitialize(kHashFlagReggieKeys, "$KIND=redline $REDLINE$HOSTING=1"); ReggieSearchBegin(gReggieRef,kReggieSearchGameChannel,kReggieRecordAdvertise,kReggieSearchFlagNotify,hashRef); /* Makes own copy of hash */ HashDispose(hashRef); /* Not needed any more */ } void QueryPlayerSearch() { HashRef hashRef = HashInitialize(kHashFlagReggieKeys,"$KIND=redline $REDLINE$HOSTING=0"); ReggieSearchBegin(gReggieRef,kReggieSearchPlayersChannel,kReggieRecordAdvertise,kReggieSearchFlagNotify,hashRef); HashDispose(hashRef); /* Not needed any more */ } void AdvertisePlayer() { char hashString[1024]; float idleAdd=TimeGetSeconds()-gStartIdleTime; int reportIdle=(idleAdd>300); gReportedIdle=reportIdle; UInt32 startIdleLocalTime=TimerGetLocalSeconds()-idleAdd; if(reportIdle) sprintf(hashString,"$REDLINE$HOSTING=0 $TIME$IDLE=%d",startIdleLocalTime); else sprintf(hashString,"$REDLINE$HOSTING=0"); HashRef hashRef = HashInitialize(kHashFlagReggieKeys,hashString); char name[1024]; sprintf(name,"%s%s",RT3_IsRegistered()?"":"\255demo.png\255 ",gConfig->playerName); HashAppend(hashRef, "$NAME", name); ReggieInsertBegin(gReggieRef,kReggieAdvGameChannel,kReggieRecordAdvertise,hashRef); HashDispose(hashRef); } void NetworkSearchGamesInit() { gLastQuery=0; QueryGameSearch(); QueryPlayerSearch(); AdvertisePlayer(); //ReggieMessageBlock(gReggieRef,NULL,false); gReggieSearching=true; } int NetworkCountPlayers() { static UInt32 lastPlayerStamp=-1; static int lastCount=0; if(gReggieConnected) { UInt32 state=ReggieGetStatus(gReggieRef,gMOTD,NULL); if(!(state==kReggieStateRefused||state==kReggieStateOffline)) { if(!gReggieCounting) { HashRef hashRef = HashInitialize(kHashFlagReggieKeys,"$KIND=redline $REDLINE$HOSTING=0 COUNT"); ReggieSearchBegin(gReggieRef,kReggieSearchPlayersChannel,kReggieRecordAdvertise,kReggieSearchFlagNotify,hashRef); HashDispose(hashRef); /* Not needed any more */ gReggieCounting=true; } SInt32 reggieCount,totalCount; UInt32 reggieStamp; Bool8 outdated; reggieStamp=ReggieSearchStatus(gReggieRef,kReggieSearchPlayersChannel,®gieCount,&totalCount,&outdated); if(reggieStamp!=lastPlayerStamp) { if(outdated) { HashRef hashRef = HashInitialize(kHashFlagReggieKeys,"$KIND=redline $REDLINE$HOSTING=0 COUNT"); ReggieSearchBegin(gReggieRef,kReggieSearchPlayersChannel,kReggieRecordAdvertise,kReggieSearchFlagNotify,hashRef); HashDispose(hashRef); /* Not needed any more */ } lastPlayerStamp=reggieStamp; } if(reggieCount!=-1) lastCount=totalCount; return lastCount; } else { ReggieDispose(gReggieRef,true); gReggieConnected=false; } } else TrackerConnect(); return 0; } void NetworkSearchGames(int *updated,tGameListEntry *games,int *numGames,int maxGames,tGameListPlayerEntry *players,int *numPlayers,int maxPlayers) { static UInt32 lastGameStamp=-1,lastPlayerStamp=-1; static float lastIdleAdd; *updated=false; if(gReggieConnected) { UInt32 state=ReggieGetStatus(gReggieRef,gMOTD,NULL); if(!(state==kReggieStateRefused||state==kReggieStateOffline)) { if(!gReggieSearching) { NetworkSearchGamesInit(); lastGameStamp=-1; lastPlayerStamp=-1; gReportedIdle=0; } float idleAdd=TimeGetSeconds()-gStartIdleTime; if(idleAdd>lastIdleAdd+15) AdvertisePlayer(); lastIdleAdd=idleAdd; int reportIdle=(idleAdd>300); if(reportIdle!=gReportedIdle) AdvertisePlayer(); SInt32 reggieCount,totalCount; UInt32 reggieStamp; Bool8 outdated; reggieStamp=ReggieSearchStatus(gReggieRef,kReggieSearchGameChannel,®gieCount,&totalCount,&outdated); if(reggieStamp!=lastGameStamp) { if(reggieCount==totalCount||reggieCount>*numGames) // if(reggieCount>-1) { int oldNumGames=*numGames; *numGames=0; lastGameStamp=reggieStamp; *updated=true; NetworkAddress local; DatagramGetAddress(NT_GetSessionEndpoint(gSessionRef),&local); int index=0; for(;index0) if(strcmp(games[*numGames].players[i].location,"Reserved Location")==0) strcpy(games[*numGames].players[i].location,games[*numGames].players[0].location); if(char *loc=strrchr(games[*numGames].players[i].location,',')) memmove(games[*numGames].players[i].location,loc+1,strlen(loc)+1); } (*numGames)++; } HashDispose(hashRef); } } for(;index*numPlayers) //if(reggieCount>-1) { int oldNumPlayers=*numPlayers; *numPlayers=0; lastPlayerStamp=reggieStamp; *updated=true; int index=0; for(;index%d\n",reggieCount,totalCount,*numPlayers); } if(outdated&®gieCount==totalCount) QueryPlayerSearch(); } } else { NetworkSearchGamesStop(); ReggieDispose(gReggieRef,true); gReggieConnected=false; } } else { *numGames=0; TrackerConnect(); } } void NetworkSearchGamesStop() { if(gReggieConnected) { //ReggieMessageBlock(gReggieRef,NULL,true); ReggieChannelClose(gReggieRef,kReggieAdvGameChannel); ReggieChannelClose(gReggieRef, kReggieSearchGameChannel); ReggieChannelClose(gReggieRef, kReggieSearchPlayersChannel); } gReggieSearching=false; } void NetworkCountPlayersStop() { if(gReggieConnected) { ReggieChannelClose(gReggieRef, kReggieSearchPlayersChannel); } gReggieCounting=false; } int gWaitingForRegistry=false; int gWaitingForRecord=false; void TrackerFetchRecord(tFileRef track,tFileRef car,int arcade,int reverse) { if(gConfig->registerLapTimes) { if(!gReggieConnected) if(!TrackerConnect()) { TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.07,kTextAlignMiddle,1,2,"Unable to Connect lap time server"); return; } char hashString[512]; HashRef hashRef; tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(track,kParserTypeMapInfoDesc,sizeof(tMapInfo)); if(!mapInfo->builtIn)return; tCarDefinition *carDef=(tCarDefinition*)FileGetParsedDataPtr(car,kParserTypeCarDesc,sizeof(tCarDefinition)); if(!carDef->builtIn)return; sprintf(hashString,"$REDLINE$MODE='%d' $REDLINE$REVERSE='%d'",arcade,reverse); hashRef = HashInitialize(kHashFlagReggieKeys, hashString); HashAppend(hashRef, "$REDLINE$MAP", mapInfo->name); HashAppend(hashRef, "$REDLINE$CAR", carDef->carName); HashAppend(hashRef, "SORT", ">$REDLINE$TIME,>$TIME"); HashAppend(hashRef, "LIMIT", "1"); //printf("hash:%s\n",(char*)(*HashSave(hashRef))); ReggieSearchBegin(gReggieRef,kReggieRecordChannel,kReggieRecordHighScore,0,hashRef); HashDispose(hashRef); gWaitingForRecord=true; } } void TrackerRegisterLapTime(tFileRef track,tFileRef car,int time,int arcade,int reverse) { if(gConfig->registerLapTimes) { if(!gReggieConnected) if(!TrackerConnect()) { TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.07,kTextAlignMiddle,1,2,"Unable to Connect to lap time server"); return; } char hashString[512]; HashRef hashRef; tMapInfo *mapInfo=(tMapInfo*)FileGetParsedDataPtr(track,kParserTypeMapInfoDesc,sizeof(tMapInfo)); if(!mapInfo->builtIn)return; tCarDefinition *carDef=(tCarDefinition*)FileGetParsedDataPtr(car,kParserTypeCarDesc,sizeof(tCarDefinition)); if(!carDef->builtIn)return; sprintf(hashString,"$REDLINE$TIME='%d' $REDLINE$MODE='%d' $REDLINE$REVERSE='%d' $REDLINE$MAPCHECK='%8X' $REDLINE$CARCHECK='%8X'",time,arcade,reverse,FileGetChecksum(track),FileGetChecksum(car)); hashRef = HashInitialize(kHashFlagReggieKeys, hashString); HashAppend(hashRef, "$REDLINE$MAPFILE", FileGetName(track)); HashAppend(hashRef, "$REDLINE$CARFILE", FileGetName(car)); HashAppend(hashRef, "$REDLINE$MAP", mapInfo->name); HashAppend(hashRef, "$REDLINE$CAR", carDef->carName); HashAppend(hashRef, "$REDLINE$NAME", gConfig->playerName); ReggieInsertBegin(gReggieRef, kReggieInsertRecordChannel, kReggieRecordHighScore ,hashRef); HashDispose(hashRef); sprintf(hashString,"$REDLINE$TIME<='%d' $REDLINE$TIME!='%d' $REDLINE$MODE='%d' $REDLINE$REVERSE='%d' COUNT",time,time,arcade,reverse); hashRef = HashInitialize(kHashFlagReggieKeys, hashString); HashAppend(hashRef, "$REDLINE$MAP", mapInfo->name); HashAppend(hashRef, "$REDLINE$CAR", carDef->carName); ReggieSearchBegin(gReggieRef,kReggieRegistryChannel,kReggieRecordHighScore,kReggieSearchFlagCount,hashRef); HashDispose(hashRef); gWaitingForRegistry=true; } } void TrackerLapTimeRegistryClose() { if(gWaitingForRegistry||gWaitingForRecord) { ReggieChannelClose(gReggieRef,kReggieInsertRecordChannel); ReggieChannelClose(gReggieRef,kReggieRegistryChannel); ReggieChannelClose(gReggieRef,kReggieRecordChannel); gWaitingForRegistry=gWaitingForRecord=false; } } void TrackerWaitForLapTimeRegistry() { if(gWaitingForRegistry||gWaitingForRecord) { if(gWaitingForRegistry) TextPrintfToBufferFormated(Vector(0,-0.2),0.05,kTextAlignMiddle,"Registering Lap Time"); UInt32 state=ReggieGetStatus(gReggieRef,gMOTD,NULL); if(state==kReggieStateRefused||state==kReggieStateOffline) { if(state==kReggieStateRefused) TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.07,kTextAlignMiddle,1,2,"Connection Refused by lap time server"); else TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.07,kTextAlignMiddle,1,2,"No response from lap time server"); gWaitingForRegistry=false; gWaitingForRecord=false; gReggieConnected=false; ReggieDispose(gReggieRef,false); } else { if(gWaitingForRegistry) { SInt32 reggieCount,totalCount; ReggieSearchStatus(gReggieRef,kReggieRegistryChannel,®gieCount,&totalCount,NULL); if(reggieCount!=-1) { if(totalCount==0) TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.1,kTextAlignMiddle,1,2,"World Record!!!"); else if(totalCount<=25) TextPrintfToBufferFormatedFading(Vector(0,-0.2),0.1,kTextAlignMiddle,1,2,"Place %d on World List",totalCount+1); gWaitingForRegistry=false; ReggieChannelClose(gReggieRef,kReggieRegistryChannel); TrackerFetchRecord(gGameInfo->map,gGameInfo->playerCars[0],gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse); } } if(gWaitingForRecord) { SInt32 reggieCount,totalCount; ReggieSearchStatus(gReggieRef,kReggieRecordChannel,®gieCount,&totalCount,NULL); if(reggieCount!=-1) { if(totalCount==0) { gWorldRecord=0; gWaitingForRecord=false; } else if(reggieCount>0){ HashRef hashRef = ReggieSearchIndex(gReggieRef,kReggieRecordChannel,0); //printf("$REDLINE$TIME%s\n",HashLookup(hashRef,"$REDLINE$TIME",nil)); //printf("$REDLINE$NAME%s\n",HashLookup(hashRef,"$REDLINE$NAME",nil)); //printf("hash:%s\n",(char*)(*HashSave(hashRef))); sscanf(HashLookup(hashRef,"$REDLINE$TIME",nil),"%d",&gWorldRecord); strcpy(gRecordName,HashLookup(hashRef,"$REDLINE$NAME",nil)); int numChars=0,index=0; while(numChars<12) { if(gRecordName[index]=='\255') while(gRecordName[++index]!='\255'); index++; numChars++; } gRecordName[index]='\0'; gWaitingForRecord=false; ReggieChannelClose(gReggieRef,kReggieRecordChannel); } } } } } }