//network.cpp //basic game hosting/joining and message sending code based on Network_Tool #include #include #include #include "gametime.h" #include "config.h" #include "network.h" #include "error.h" #include "gameframe.h" #include "controls.h" #include "gamesound.h" #include "carphysics.h" #include "interfaceutil.h" #include "gamemem.h" #include #include "gamesystem.h" #include "interfacemultiplayer.h" #include "initexit.h" extern ReggieRef gReggieRef; extern int gReggieConnected; int gAllowCompression=true; #define kGameName "Redline" #define kConnectionTimeOut 5.0 #define kCompressionSize 32 #define kCompressionFlag 0x80 NT_SessionRef gSessionRef=nil; typedef struct{ QStub qstub; tNetworkPacket packet; }tQueuedPacket; Queue gPacketQueue; int gInternetAvailable=true; int gTerminateSession=false; float gLastPingReceived=0,gLastRTT=0; char gHistoryBuffer[1024*500]=""; int gHistoryDumps=0; void NetworkLockOutNewPlayers() { // HandleError(NT_SetSessionLock(gSessionRef,true)); int err=NT_SetSessionLock(gSessionRef,true); //PrintConsoleString("Locking session. returns %d",err); } void NetworkUnlockOutNewPlayers() { // HandleError(NT_SetSessionLock(gSessionRef,false)); int err=NT_SetSessionLock(gSessionRef,false); //PrintConsoleString("Unlocking session. returns %d",err); } int NetworkGetLocalNetID() { NT_MemberInfo info; NT_GetSelfMemberInfo(gSessionRef,&info); return info.member; } void NetworkClearPacketQueue() { int temp=gTerminateSession; gTerminateSession=false; tNetworkPacket packet; while(NetworkReceivePacket(&packet)) if(packet.data) MemoryFreeBlock(packet.data); gTerminateSession=temp; } Result32 CompressPacket(void *buffer, long *length) { Result32 error = eCommonErrorNone; void ** indirect = NULL; qThrowIfNull(indirect = IndirectInitialize(buffer, *length), eCommonErrorOutOfMem, kCommonErrorOutOfMemStr); qThrowIfError(IndirectCompress(indirect, kIndirectCompressorZLib, NULL), 0); MemoryCopy(buffer, *indirect, *length = IndirectGetSize(indirect)); CLEANUP: if (indirect) IndirectDeallocate(indirect); return(error); } Result32 DecompressPacket(void *buffer, long *length) { Result32 error = eCommonErrorNone; void ** indirect = NULL; qThrowIfNull(indirect = IndirectInitialize(buffer, *length), eCommonErrorOutOfMem, kCommonErrorOutOfMemStr); qThrowIfError(IndirectDecompress(indirect, kIndirectCompressorZLib, NULL), 0); MemoryCopy(buffer, *indirect, *length = IndirectGetSize(indirect)); CLEANUP: if (indirect) IndirectDeallocate(indirect); return(error); } void NetworkGetStatusString(char *str) { NT_ProblemInfo prob; if(NT_GetProblemInfo(gSessionRef,0,&prob)) { if(prob.timeout<10) str[0]='.'; else if(prob.timeout<50) str[0]='t'; else str[0]='T'; if(prob.latency<10) str[1]='.'; else if(prob.latency<50) str[1]='l'; else str[1]='L'; if(prob.stalled<10) str[2]='.'; else if(prob.stalled<50) str[2]='s'; else str[2]='S'; if(prob.backlog<10) str[3]='.'; else if(prob.backlog<50) str[3]='b'; else str[3]='B'; if(prob.failure<10) str[4]='.'; else if(prob.failure<50) str[4]='f'; else str[4]='F'; if(prob.errors<10) str[5]='.'; else if(prob.errors<50) str[5]='e'; else str[5]='E'; str[6]='\0'; } } void NetworkGetBandwidth(float *rcv,float *snd) { NT_LatencyInfo lat; NT_GetTotalLatencyInfo(gSessionRef,&lat); *rcv=lat.recvPackets10Sec*0.1; *snd=lat.sendPackets10Sec*0.1; } void NetworkQueuePacket(tNetworkPacket *insert) { tQueuedPacket *q=(tQueuedPacket*)MemoryAllocateZeroedBlock(sizeof(tQueuedPacket)); q->packet=*insert; QueueInsert(&gPacketQueue,(QStubPtr)q); } //#define PACKET_DUMP #ifdef PACKET_DUMP void DumpPacket(tNetworkPacket *packet) { char st[256]; sprintf(st,"what: %d",packet->what); PrintConsoleString(st); sprintf(st,"from: %d",packet->from); PrintConsoleString(st); sprintf(st,"size: 0x%x",packet->size); PrintConsoleString(st); int line=0; while(line<=packet->size/16) { char dataStr[256]; sprintf(dataStr,"%04x ",line*16); for(int pos=line*16;possize) sprintf(dataStr,"%s%02x",dataStr,*((UInt8*)packet->data+pos)); else sprintf(dataStr,"%s ",dataStr); sprintf(dataStr,"%s ",dataStr); for(int pos=line*16;possize) if(*((UInt8*)packet->data+pos)>=32) sprintf(dataStr,"%s%c",dataStr,*((UInt8*)packet->data+pos)); else sprintf(dataStr,"%s.",dataStr); else sprintf(dataStr,"%s ",dataStr); PrintConsoleString(dataStr); line++; } PrintConsoleString(""); } #endif void NTEventHandler(NT_SessionRef sessionRef,void *refcon,NT_SessionEventType event,NT_MemberIDType member) { gAllowCompression=false; switch(event) { case eSessionEventSessionStart:{ //PrintConsoleString("eSessionEventSessionStart"); tNetworkPacket packet; packet.what=kMessageTypePlayerJoined; packet.from=0; packet.size=0; packet.data=NULL; NetworkQueuePacket(&packet); } break; case eSessionEventMemberJoin:{ //PrintConsoleString("eSessionEventMemberJoin, member=%d",member); tNetworkPacket packet; packet.what=kMessageTypePlayerJoined; packet.from=member; packet.size=0; packet.data=NULL; NetworkQueuePacket(&packet); /*NT_ToleranceInfo tolerance; NT_GetToleranceInfo(gSessionRef,&tolerance); PrintConsoleString("Tolerance info:"); PrintConsoleString("avgPackets %d.",tolerance.avgPackets); PrintConsoleString("avgRequests %d.",tolerance.avgRequests); PrintConsoleString("maxRequests %d.",tolerance.maxRequests); PrintConsoleString("minHeartbeat %d.",tolerance.minHeartbeat); PrintConsoleString("avgHeartbeat %d.",tolerance.avgHeartbeat); PrintConsoleString("maxHeartbeat %d.",tolerance.maxHeartbeat); PrintConsoleString("maxTimeout %d.",tolerance.maxTimeout); PrintConsoleString("maxFailures %d.",tolerance.maxFailures); PrintConsoleString("maxErrors %d.",tolerance.maxErrors); PrintConsoleString("minLinger %d.",tolerance.minLinger);*/ } break; case eSessionEventMemberLeave:{ //PrintConsoleString("eSessionEventMemberLeave, member=%d",member); tNetworkPacket packet; if(member==0) { if(*gDisconnectString=='\0') sprintf(gDisconnectString,"Network Failure (Failure on Host side)."); packet.what=kMessageTypeGameTerminated; } else packet.what=kMessageTypePlayerLeft; packet.from=member; packet.size=0; packet.data=NULL; NetworkQueuePacket(&packet); NT_LatencyInfo latency; if(NT_GetLatencyInfo(gSessionRef,member,&latency)) { PrintConsoleString("Latency info for dropped member:"); PrintConsoleString("minLinkRTT %d.",latency.minLinkRTT); PrintConsoleString("avgLinkRTT %d.",latency.avgLinkRTT); PrintConsoleString("p80LinkRTT %d.",latency.p80LinkRTT); PrintConsoleString("devLinkRTT %d.",latency.devLinkRTT); PrintConsoleString("wAvgLinkRTT %d.",latency.wAvgLinkRTT); PrintConsoleString("wDevLinkRTT %d.",latency.wDevLinkRTT); PrintConsoleString("minTranRTT %d.",latency.minTranRTT); PrintConsoleString("avgTranRTT %d.",latency.avgTranRTT); PrintConsoleString("p80TranRTT %d.",latency.p80TranRTT); PrintConsoleString("maxTranRTT %d.",latency.maxTranRTT); PrintConsoleString("devTranRTT %d.",latency.devTranRTT); PrintConsoleString("wAvgTranRTT %d.",latency.wAvgTranRTT); PrintConsoleString("wDevTranRTT %d.",latency.wDevTranRTT); } NT_ProblemInfo prob; if(NT_GetProblemInfo(gSessionRef,member,&prob)) { PrintConsoleString("Problem info for dropped member:"); PrintConsoleString("timeout %d.",prob.timeout); PrintConsoleString("latency %d.",prob.latency); PrintConsoleString("stalled %d.",prob.stalled); PrintConsoleString("backlog %d.",prob.backlog); PrintConsoleString("failure %d.",prob.failure); PrintConsoleString("errors %d.",prob.errors); } NT_PacketHistory(gSessionRef,gHistoryBuffer,1024*500); } break; case eSessionEventSessionEnd:{ //PrintConsoleString("eSessionEventSessionEnd"); /* tNetworkPacket packet; packet.what=kMessageTypeGameTerminated; packet.from=member; packet.size=0; packet.data=NULL; NetworkQueuePacket(&packet);*/ } break; case eSessionEventRecvPacket:{ UInt8 data[kNetworkToolPacketSize]; UInt16 len; if(NT_RecvPacket(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len)) { tNetworkPacket packet; packet.data=MemoryAllocateBlock(kNetworkToolPacketSize); MemoryMove(packet.data,data+1,len-1); packet.size=len-1; packet.from=member; packet.what=*data; if(packet.what==kMessageTypeGameTerminated) gTerminateSession=true; /*#ifdef PACKET_DUMP PrintConsoleString("Packet Received:"); DumpPacket(&packet); #endif */ //got a Ping request? if(packet.what==kMessageTypePing) //send out a reply packet with the original ping packets time stamp. NetworkSendPacket(kMessageTypePong,packet.data,sizeof(float),kMessagePriorityLow,packet.from); else if(packet.what==kMessageTypePong) { gLastPingReceived=TimeGetSeconds(); gLastRTT=gLastPingReceived-*(float*)packet.data; } else NetworkQueuePacket(&packet); } } break; case eSessionEventRecvRequest:{ UInt8 data[kNetworkToolPacketSize]; UInt16 len; if(NT_RecvRequest(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len)) { NT_SendResponse(gSessionRef,NULL,0); tNetworkPacket packet; packet.data=MemoryAllocateBlock(kNetworkToolPacketSize); MemoryMove(packet.data,data+1,len-1); packet.size=len-1; packet.from=member; packet.what=*data; if(packet.what==kMessageTypeGameTerminated) gTerminateSession=true; #ifdef PACKET_DUMP PrintConsoleString("Request Packet Received:"); DumpPacket(&packet); #endif NetworkQueuePacket(&packet); } } break; case eSessionEventRecvResponse:{ UInt8 data[kNetworkToolPacketSize]; UInt16 len; NT_RecvResponse(gSessionRef,&member,(char*)data,kNetworkToolPacketSize,&len); } break; } gAllowCompression=true; } void NTErrorHandler(NT_SessionRef sessionRef,void *refcon,NT_SessionProblemType problem,NT_SessionSeverityType severity,NT_MemberIDType member,Bool8 *disconnectSelf,Bool8 *disconnectMember) { gAllowCompression=false; if(severity>=100) { PrintConsoleString("NT Error %d",problem); NT_MemberInfo info; NT_GetSelfMemberInfo(gSessionRef,&info); if(info.member==0) { if(member!=0) { *disconnectMember=true; tChatMessage m; m.flags=kChatFlagSystem; switch(problem) { case eSessionProblemTimeout: sprintf(m.str,"### Player %d is disconnecting (Connection timed out).",member+1); break; case eSessionProblemLatency: sprintf(m.str,"### Player %d is disconnecting (Latency too high).",member+1); break; case eSessionProblemStalled: sprintf(m.str,"### Player %d is disconnecting (Stalled).",member+1); break; case eSessionProblemBacklog: sprintf(m.str,"### Player %d is disconnecting (Backlog).",member+1); break; case eSessionProblemFailure: sprintf(m.str,"### Player %d is disconnecting (Failure).",member+1); break; case eSessionProblemErrors: sprintf(m.str,"### Player %d is disconnecting (Too many errors).",member+1); break; default: sprintf(m.str,"### Player %d is disconnecting (Unknown error).",member+1); break; } printf("%s\n",m.str); //NetworkSendPacket(kMessageTypeChat,&m,sizeof(m),kMessagePriorityHigh,kMessageSendToAll); } } else if(member==0) { *disconnectMember=false; *disconnectSelf=true; tNetworkPacket packet; packet.data=NULL; packet.size=0; packet.from=member; packet.what=kMessageTypeGameTerminated; NetworkQueuePacket(&packet); switch(problem) { case eSessionProblemTimeout: sprintf(gDisconnectString,"Network Failure (Connection timed out)."); break; case eSessionProblemLatency: sprintf(gDisconnectString,"Network Failure (Latency too high)."); break; case eSessionProblemStalled: sprintf(gDisconnectString,"Network Failure (Stalled)."); break; case eSessionProblemBacklog: sprintf(gDisconnectString,"Network Failure (Backlog)."); break; case eSessionProblemFailure: sprintf(gDisconnectString,"Network Failure (Failure)."); break; case eSessionProblemErrors: sprintf(gDisconnectString,"Network Failure (Too many errors)."); break; default: sprintf(gDisconnectString,"Network Failure (unknown error)."); break; } } } gAllowCompression=true; } void NetworkPreSession() { if(gSessionRef==NULL) HandleError(NT_SessionStartParam(&gSessionRef,NTEventHandler,NTErrorHandler,NULL,NULL,6,kGameName,"")); else HandleError(NT_SessionRestart(gSessionRef,6,kGameName,"")); } float NetworkGetPlayerPing(int netID) { NT_MemberInfo info; NT_GetSelfMemberInfo(gSessionRef,&info); if(netID==info.member||netID==kMessageNoRecipients) return 0; NT_LatencyInfo latency; if(NT_GetLatencyInfo(gSessionRef,netID,&latency)); { /* PrintConsoleString("Player %d latency.",netID); PrintConsoleString("minLinkRTT %d.",latency.minLinkRTT); PrintConsoleString("avgLinkRTT %d.",latency.avgLinkRTT); PrintConsoleString("p80LinkRTT %d.",latency.p80LinkRTT); PrintConsoleString("devLinkRTT %d.",latency.devLinkRTT); PrintConsoleString("wAvgLinkRTT %d.",latency.wAvgLinkRTT); PrintConsoleString("wDevLinkRTT %d.",latency.wDevLinkRTT); PrintConsoleString("minTranRTT %d.",latency.minTranRTT); PrintConsoleString("avgTranRTT %d.",latency.avgTranRTT); PrintConsoleString("p80TranRTT %d.",latency.p80TranRTT); PrintConsoleString("maxTranRTT %d.",latency.maxTranRTT); PrintConsoleString("devTranRTT %d.",latency.devTranRTT); PrintConsoleString("wAvgTranRTT %d.",latency.wAvgTranRTT); PrintConsoleString("wDevTranRTT %d.",latency.wDevTranRTT);*/ return latency.avgLinkRTT*0.001; } return 0; } //Host a new Network Game void HostNetGame(int maxPlayers,char *pass) { HandleError(NT_SessionRestart(gSessionRef,maxPlayers,kGameName,pass)); gTerminateSession=false; } void NetworkDisconnectPlayer(int netID,UInt8 type) { NT_MemberInfo info; if(NT_GetMemberInfo(gSessionRef,netID,&info)) { if(type!=kNetworkDisconnectLicenseCopies) HandleError(NT_SessionBan(gSessionRef,&info.address,false,NULL)); NetworkSendPacket(kMessageTypeKicked,&type,sizeof(UInt8),kMessagePriorityHigh,netID); } } void NetworkChangePassword(char *pass) { HandleError(NT_SetSessionPassword(gSessionRef,pass)); } //join a network game at the ip address given in the string address. //returns an error code or 0. passes the player's id in id. int JoinNetGame(char *address,char *errorString) { if(!*address) { sprintf(errorString,"No Address Entered."); return false; } //PrintConsoleString(address); NetworkAddress addy; Result32 err=SimpleResolve(eNetworkStackTCPIP,address,&addy); if(err) { sprintf(errorString,"Can't resolve host. (%d)",err); return false; } gTerminateSession=false; NetworkClearPacketQueue(); HandleError(NT_SessionRejoin(gSessionRef,&addy,kGameName,NULL,&err)); if(err==eNetworkToolErrorBadPass) { err=0; char password[256]; if(InterfaceGetUserInputString("Please enter Game Password",password,256,true,true)) { NetworkClearPacketQueue(); HandleError(NT_SessionRejoin(gSessionRef,&addy,kGameName,password,&err)); } else return false; } if(err) { switch(err) { case eNetworkToolErrorNoServer: sprintf(errorString,"No Server Running on that Machine",err); break; case eNetworkToolErrorTimeout: sprintf(errorString,"Network_Tool Timeout",err); break; case eNetworkToolErrorLatency: sprintf(errorString,"Latency is too high.",err); break; case eNetworkToolErrorBanned: sprintf(errorString,"You are banned from this host.",err); break; case eNetworkToolErrorTooMany: sprintf(errorString,"The game is full.",err); break; case eNetworkToolErrorBadPass: sprintf(errorString,"Wrong Password",err); break; case eNetworkToolErrorLocked: sprintf(errorString,"Can't join in the middle of a game.",err); break; default: sprintf(errorString,"Connection Error. (%d)",err); } return false; } float startTime=TimeGetSeconds(); while(TimeGetSeconds()kNetworkToolPacketSize-1) FailWithErrorString("Packet too large."); NT_MemberInfo info; NT_GetSelfMemberInfo(gSessionRef,&info); NT_MemberListType self=0x00000001<kCompressionSize&&gAllowCompression) { int oldsize=size; if(CompressPacket(data+1,&size)==eCommonErrorNone) if(oldsizewhat=kMessageTypeGameTerminated; packet->data=NULL; return true; } else if(QStubPtr rcv=QueueRemove(&gPacketQueue)) { *packet=((tQueuedPacket*)rcv)->packet; if(packet->what&kCompressionFlag) { DecompressPacket(packet->data,&packet->size); packet->what^=kCompressionFlag; } MemoryFreeBlock(rcv); return true; } if(gReggieConnected) ReggieMessageCheck(gReggieRef); return false; } void NetworkDisposePacket(tNetworkPacket* packet) { if(packet->data) MemoryFreeBlock(packet->data); } void NetworkInit() { HandleError(NT_Open()); QueueCreate(&gPacketQueue); } void NetworkIdle() { static float lastStatus=0; NT_Idle(); if(gSessionRef) { /* if(TimeGetSeconds()-lastStatus<0.5) return; lastStatus=TimeGetSeconds(); char str[16]; NetworkGetStatusString(str); // printf("%s\n",str); NT_MemberInfo info; NT_GetSelfMemberInfo(gSessionRef,&info); if(info.member) { if(gLastRTT<0) printf("No Ping Response since %f seconds.\n",lastStatus-gLastPingReceived); else { //printf("Last RTT: %f\n",gLastRTT); gLastRTT=-1; } NetworkSendPacket(kMessageTypePing,&lastStatus,sizeof(float),kMessagePriorityLow,kMessageSendToHostOnly); }*/ } } void NetworkExit() { if(gSessionRef) NetworkSendPacket(kMessageTypeBye,0,NULL,kMessagePriorityHigh,kMessageSendToAll); if(gReggieConnected) ReggieDispose(gReggieRef,true); NT_Close(); while(QStubPtr rcv=QueueRemove(&gPacketQueue)) MemoryFreeBlock(rcv); QueueEmpty(&gPacketQueue); } //wait for a response from all players, to synchronize the start of the game. int NetworkSynch(int numPlayers) { //PrintConsoleString("Synching %d...",numPlayers); NetworkSendPacket(kMessageTypeSynch,nil,0,kMessagePriorityHigh,kMessageSendToAll); UInt32 lastSynch=0; UInt32 startClock; int start=false; tNetworkPacket leftMessage[kMaxPlayers]; int numLeftMessages=0; int synchMessagesReceived=0; while(synchMessagesReceived