/**************************************************************************** * * SNET.CPP * Storm networking functions * * By Michael O'Brien (4/19/96) * * SNet implements multiple independent communication streams between each * player. It guarantees delivery of all messages, using different * techniques depending on the message type. * ***/ #include "pch.h" #pragma hdrstop #ifdef _DEBUG #define TRACING #define UNSIGNEDSNPS #endif #pragma warning(disable:4200) // don't warn about zero-sized arrays #define MF_ACK 0x01 #define MF_RESENDREQUEST 0x02 #define MF_FORWARDED 0x04 #define PF_JOINING 0x00000004 #define PF_LEAVING 0x00000008 #define NOPLAYER 0xFF #define ESTIMATEDPACKETOVERHEAD 64 #define DELETECONNTIME 50000 #define MINSPISIZE (20*sizeof(DWORD)) #define PINGFREQUENCY 20000 #define REGISTERTYPE 'SNET' #define REGISTERSUBTYPE_SNETEVENT 1 #define REGISTERSUBTYPE_SYSEVENT 2 #define SENDBUFSIZE 8192 #define SNET_NETWORKVERSION 1 #define SYS_UNUSED 0 #define SYS_INITIALCONTACT 1 #define SYS_CIRCUITCHECK 2 #define SYS_CIRCUITCHECKRESPONSE 3 #define SYS_PING 4 #define SYS_PINGRESPONSE 5 #define SYS_PLAYERINFO 6 #define SYS_PLAYERJOIN 7 #define SYS_PLAYERJOIN_ACCEPTSTART 8 #define SYS_PLAYERJOIN_ACCEPTDONE 9 #define SYS_PLAYERJOIN_REJECT 10 #define SYS_PLAYERLEAVE 11 #define SYS_DROPPLAYER 12 #define SYS_NEWGAMEOWNER 13 #define SYSMSGS 14 #define TYPE_SYSTEM 0 #define TYPE_MESSAGE 1 #define TYPE_TURN 2 #define TYPES 3 typedef struct _CLIENTDATA { DWORD bytes; DWORD numplayers; DWORD maxplayers; } CLIENTDATA, *CLIENTDATAPTR; typedef struct _HEADER { WORD checksum; // checksum must be first field WORD bytes; // bytes including header WORD sequence; WORD acksequence; BYTE type; BYTE subtype; BYTE playerid; BYTE flags; } HEADER, *HEADERPTR; typedef struct _PACKET { HEADER header; BYTE data[0]; } PACKET, *PACKETPTR; NODEDECL(MESSAGE) { SNETADDRPTR addr; PACKETPTR data; DWORD databytes; BOOL local; DWORD sendtime; DWORD resendtime; } *MESSAGEPTR; NODEDECL(CONNREC) { char name[SNETSPI_MAXSTRINGLENGTH]; char desc[SNETSPI_MAXSTRINGLENGTH]; SNETADDR addr; DWORD flags; DWORD lastreceivetime; // last time any packet was received DWORD lastrequesttime; // last time we requested a turn DWORD lastpingtime; // last time we sent a ping DWORD latency; DWORD peaklatency; LIST(MESSAGE) outgoingqueue[TYPES]; // already sent; waiting for ack LIST(MESSAGE) incomingqueue[TYPES]; // waiting to be processed LIST(MESSAGE) processing[TYPES]; // given to app; valid until next call LIST(MESSAGE) oldturns; // old turns from this player WORD outgoingsequence[TYPES]; // (in case we need to send them WORD incomingsequence[TYPES]; // to another player) WORD lastprocessedturn; // last turn ack from this player WORD availablesequence[TYPES]; // highest available incoming sequence WORD acksequence[TYPES]; // last message we acked DWORD acktime[TYPES]; // time of unacked message processing BOOL gameowner; BOOL establishing; DWORD exitcode; WORD finalsequence; BYTE playerid; } *CONNPTR; typedef struct _PERFDATAREC { DWORD value; DWORD type; LONG scale; BOOL providerspecific; } PERFDATAREC, *PERFDATAPTR; NODEDECL(PROVIDERINFO) { char filename[MAX_PATH]; DWORD index; DWORD id; char desc[SNETSPI_MAXSTRINGLENGTH]; char req[SNETSPI_MAXSTRINGLENGTH]; SNETCAPS caps; } *PROVIDERINFOPTR; typedef struct _UIPARAMS { SNETCAPSPTR mincaps; SNETPROGRAMDATAPTR programdata; SNETPLAYERDATAPTR playerdata; SNETUIDATAPTR interfacedata; SNETVERSIONDATAPTR versiondata; } UIPARAMS, *UIPARAMSPTR; NODEDECL(USEREVENT) { SNETEVENT event; } *USEREVENTPTR; typedef struct _SYSEVENT { SNETADDRPTR senderaddr; LPVOID data; DWORD databytes; BYTE senderplayerid; BYTE eventid; } SYSEVENT, *SYSEVENTPTR; typedef struct _SYSEVENTDATA_DROPPLAYER { DWORD playerid; DWORD finalsequence; DWORD exitcode; } SYSEVENTDATA_DROPPLAYER, *SYSEVENTDATA_DROPPLAYERPTR; typedef struct _SYSEVENTDATA_PLAYERJOIN { char namedescpass[3*SNETSPI_MAXSTRINGLENGTH]; } SYSEVENTDATA_PLAYERJOIN, *SYSEVENTDATA_PLAYERJOINPTR; typedef struct _SYSEVENTDATA_PLAYERJOIN_ACCEPTSTART { DWORD playerid; DWORD playersallowed; DWORD nextturn; DWORD gamemode; DWORD runningtime; char namedescpass[3*SNETSPI_MAXSTRINGLENGTH]; } SYSEVENTDATA_PLAYERJOIN_ACCEPTSTART, *SYSEVENTDATA_PLAYERJOIN_ACCEPTSTARTPTR; typedef struct _SYSEVENTDATA_PLAYERINFO { DWORD bytes; DWORD playerid; BOOL gameowner; DWORD flags; DWORD startingturn; SNETADDR addr; char namedesc[SNETSPI_MAXSTRINGLENGTH*2]; } SYSEVENTDATA_PLAYERINFO, *SYSEVENTDATA_PLAYERINFOPTR; typedef struct _SYSEVENTDATA_PLAYERLEAVE { DWORD finalsequence; DWORD exitcode; } SYSEVENTDATA_PLAYERLEAVE, *SYSEVENTDATA_PLAYERLEAVEPTR; typedef void (CALLBACK *SYSEVENTPROC)(SYSEVENTPTR); static CCritSect s_api_critsect; static DWORD s_api_playeroffset = 0; static CONNPTR inline ConnFindByAddr (SNETADDRPTR addr); static CONNPTR inline ConnFindLocal (); static void ConnSetCurrentMessage (CONNPTR conn, BYTE type, MESSAGEPTR message); static void RecvProcessExternalMessages (); static void RecvProcessIncomingPackets (); static void SysQueueUserEvent (DWORD eventid, DWORD playerid, LPVOID data, DWORD databytes); static void UiGetProgramDescription (LPCSTR programname, SNETVERSIONDATAPTR versionptr, LPSTR buffer, DWORD buffersize); /**************************************************************************** * * TRACE FUNCTIONS * ***/ #ifdef TRACING static CSLog s_trace_log("Internal","SNet Trace File"); //=========================================================================== static void TraceDumpAddr (HSLOG log, LPCSTR addrname, SNETADDRPTR addr, BYTE playerid) { char outstr[80]; wsprintf(outstr," %s=",addrname); for (unsigned loop = 0; loop < sizeof(SNETADDR); ++loop) wsprintf(outstr+SStrLen(outstr),"%02x",(DWORD)*((LPBYTE)addr+loop)); wsprintf(outstr+SStrLen(outstr)," (%x)",(DWORD)playerid); SLogWrite(log,outstr); } //=========================================================================== static void TraceDumpDataBlocks (HSLOG log, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata) { if (programdata && (programdata->size >= sizeof(SNETPROGRAMDATA))) { SLogWrite(log," programdata.size = %u" ,programdata->size); SLogWrite(log," programdata.programname = \"%s\"",programdata->programname); SLogWrite(log," programdata.programdescription = \"%s\"",programdata->programdescription); SLogWrite(log," programdata.programid = 0x%08x",programdata->programid); SLogWrite(log," programdata.versionid = 0x%08x",programdata->versionid); SLogWrite(log," programdata.reserved1 = %u" ,programdata->reserved1); SLogWrite(log," programdata.maxplayers = %u" ,programdata->maxplayers); SLogWrite(log," programdata.initdata = 0x%08x",programdata->initdata); SLogWrite(log," programdata.initdatabytes = %u" ,programdata->initdatabytes); SLogWrite(log," programdata.reserved2 = 0x%08x",programdata->reserved2); SLogWrite(log," programdata.optcategorybits = 0x%08x",programdata->optcategorybits); } if (playerdata && (playerdata->size >= sizeof(SNETPLAYERDATA))) { SLogWrite(log," playerdata.size = %u" ,playerdata->size); SLogWrite(log," playerdata.playername = \"%s\"",playerdata->playername); SLogWrite(log," playerdata.playerdescription = \"%s\"",playerdata->playerdescription); } if (interfacedata && (interfacedata->size >= sizeof(SNETUIDATA))) { SLogWrite(log," interfacedata.size = %u" ,interfacedata->size); SLogWrite(log," interfacedata.uiflags = 0x%08x",interfacedata->uiflags); SLogWrite(log," interfacedata.parentwindow = 0x%08x",interfacedata->parentwindow); SLogWrite(log," interfacedata.artcallback = 0x%08x",interfacedata->artcallback); SLogWrite(log," interfacedata.authcallback = 0x%08x",interfacedata->authcallback); SLogWrite(log," interfacedata.createcallback = 0x%08x",interfacedata->createcallback); SLogWrite(log," interfacedata.drawdesccallback = 0x%08x",interfacedata->drawdesccallback); SLogWrite(log," interfacedata.selectedcallback = 0x%08x",interfacedata->selectedcallback); SLogWrite(log," interfacedata.messageboxcallback = 0x%08x",interfacedata->messageboxcallback); SLogWrite(log," interfacedata.soundcallback = 0x%08x",interfacedata->soundcallback); SLogWrite(log," interfacedata.statuscallback = 0x%08x",interfacedata->statuscallback); SLogWrite(log," interfacedata.getdatacallback = 0x%08x",interfacedata->getdatacallback); SLogWrite(log," interfacedata.categorycallback = 0x%08x",interfacedata->categorycallback); } if (versiondata && (versiondata->size >= sizeof(SNETVERSIONDATA))) { SLogWrite(log," versiondata.size = %u" ,versiondata->size); SLogWrite(log," versiondata.versionstring = \"%s\"",versiondata->versionstring); SLogWrite(log," versiondata.executablefile = \"%s\"",versiondata->executablefile); SLogWrite(log," versiondata.originalarchivefile = \"%s\"",versiondata->originalarchivefile); SLogWrite(log," versiondata.patcharchivefile = \"%s\"",versiondata->patcharchivefile); } } //=========================================================================== static void TraceDumpMsg (HSLOG log, DWORD playerid, DWORD sequence, LPVOID data, DWORD databytes) { // DUMP THE PLAYER ID if (playerid != 0xFFFFFFFF) SLogWrite(log, "(from player %u, sequence=%u)\n", playerid+s_api_playeroffset,sequence); else if (sequence != 0xFFFFFFFF) SLogWrite(log, "(sequence=%u)\n",sequence); // DUMP THE DATA SLogDump(log,data,databytes); } #define TRACEDUMP SLogDump #define TRACEDUMPADDR TraceDumpAddr #define TRACEDUMPDATABLOCKS TraceDumpDataBlocks #define TRACEDUMPMSG TraceDumpMsg #define TRACEHANDLE s_trace_log.GetHandle() #define TRACEOUT SLogWrite #define TRACEPEND SLogPend #else #define TRACEDUMP #define TRACEDUMPADDR #define TRACEDUMPDATABLOCKS #define TRACEDUMPMSG #define TRACEHANDLE 0 #define TRACEOUT #define TRACEPEND #endif /**************************************************************************** * * PERFORMANCE FUNCTIONS * ***/ static PERFDATAREC s_perf_data[SNET_PERFIDNUM] = {{0,0 , 0,0}, // unused {0,SNET_PERFTYPE_RAWCOUNT,-1,0}, // SNET_PERFID_TURN {0,0 , 0,0}, // unused {0,0 , 0,0}, // unused {0,SNET_PERFTYPE_COUNTER ,-1,0}, // SNET_PERFID_TURNSSENT {0,SNET_PERFTYPE_COUNTER ,-1,0}, // SNET_PERFID_TURNSRECV {0,SNET_PERFTYPE_COUNTER ,-1,0}, // SNET_PERFID_MSGSENT {0,SNET_PERFTYPE_COUNTER ,-1,0}, // SNET_PERFID_MSGRECV {0,SNET_PERFTYPE_COUNTER ,-4,0}, // SNET_PERFID_USERBYTESSENT {0,SNET_PERFTYPE_COUNTER ,-4,0}, // SNET_PERFID_USERBYTESRECV {0,SNET_PERFTYPE_COUNTER ,-4,0}, // SNET_PERFID_TOTALBYTESSENT {0,SNET_PERFTYPE_COUNTER ,-4,0}, // SNET_PERFID_TOTALBYTESRECV {0,SNET_PERFTYPE_COUNTER ,-1,1}, // SNET_PERFID_PKTSENTONWIRE {0,SNET_PERFTYPE_COUNTER ,-1,1}, // SNET_PERFID_PKTRECVONWIRE {0,SNET_PERFTYPE_COUNTER ,-4,1}, // SNET_PERFID_BYTESSENTONWIRE {0,SNET_PERFTYPE_COUNTER ,-4,1}}; // SNET_PERFID_BYTESRECVONWIRE //=========================================================================== static void inline PerfAdd (DWORD id, DWORD value) { VALIDATEBEGIN; VALIDATE(id < SNET_PERFIDNUM); VALIDATEENDVOID; s_perf_data[id].value += value; } //=========================================================================== static void inline PerfDecrement (DWORD id) { VALIDATEBEGIN; VALIDATE(id < SNET_PERFIDNUM); VALIDATEENDVOID; InterlockedDecrement((LONG *)&s_perf_data[id].value); } //=========================================================================== static void inline PerfIncrement (DWORD id) { VALIDATEBEGIN; VALIDATE(id < SNET_PERFIDNUM); VALIDATEENDVOID; InterlockedIncrement((LONG *)&s_perf_data[id].value); } //=========================================================================== static void inline PerfSet (DWORD id, DWORD value) { VALIDATEBEGIN; VALIDATE(id < SNET_PERFIDNUM); VALIDATEENDVOID; s_perf_data[id].value = value; } /**************************************************************************** * * SERVICE PROVIDER MANAGEMENT FUNCTIONS * ***/ static SNETSPIPTR s_spi = NULL; static HINSTANCE s_spi_lib = (HINSTANCE)0; static DWORD s_spi_outgoingtime = 0; static LIST(PROVIDERINFO) s_spi_providerlist; static PROVIDERINFOPTR s_spi_providerptr = NULL; static BOOL s_spi_providersfound = FALSE; static LPVOID s_spi_sendbuffer = NULL; static DWORD s_spi_timetoackturn = 250; static DWORD s_spi_timetoblock = 5000; static DWORD s_spi_timetogiveup = 1000; static DWORD s_spi_timetorequest = 25; static DWORD s_spi_timetoresend = 50; static LPVOID SpiLoadCapsSignature (LPCSTR filename); //=========================================================================== static LPVOID inline SpiExtractCaps (LPVOID capssig, DWORD *id, LPCSTR *desc, LPCSTR *req, SNETCAPSPTR *caps) { VALIDATEBEGIN; VALIDATE(capssig); VALIDATEEND; DWORD bytes = *(LPDWORD)capssig; LPCSTR start = (LPCSTR)capssig+sizeof(DWORD); LPCSTR curr = start; *id = *(LPDWORD)curr; curr += sizeof(DWORD); *desc = curr; curr += SStrLen(curr)+1; *req = curr; curr += SStrLen(curr)+1; *caps = (SNETCAPSPTR)curr; curr += ((SNETCAPSPTR)curr)->size; if ((DWORD)(curr-start) != bytes) return NULL; return (LPVOID)curr; } //=========================================================================== static int inline SpiCheckProviderOrder (PROVIDERINFOPTR first, PROVIDERINFOPTR second) { // CHECK WHETHER ONE OR BOTH OF THE PROVIDERS IN IN OUR PREFERRED LIST #define BASENUM 6 static const DWORD baseorder[BASENUM] = {'BNET','IPXN','IPXW','MODM','SCBL','MSDP'}; int firstindex = 0x7FFFFFFF; int secondindex = 0x7FFFFFFF; for (int loop = 0; loop < BASENUM; ++loop) { if (first->id == baseorder[loop]) firstindex = loop; if (second->id == baseorder[loop]) secondindex = loop; } #undef BASENUM // IF SO, SORT BASED ON THE ORDER IN THE LIST if ((firstindex != 0x7FFFFFFF) || (secondindex != 0x7FFFFFFF)) return secondindex-firstindex; // OTHERWISE, SORT ON ALPHABETICAL ORDER else return _stricmp(first->desc,second->desc); } //=========================================================================== static void SpiDestroy (BOOL clearproviderlist) { // CALL THE PROVIDER'S DESTROY FUNCTION if (s_spi_lib && s_spi) { TRACEOUT(TRACEHANDLE," spiDestroy()"); s_spi->Destroy(); } // UNBIND FROM THE CURRENT PROVIDER if (s_spi_lib) { FreeLibrary(s_spi_lib); s_spi_lib = (HINSTANCE)0; } if (s_spi) { DEL(s_spi); s_spi = NULL; } // FREE THE SEND BUFFER if (s_spi_sendbuffer) { VirtualUnlock(s_spi_sendbuffer,SENDBUFSIZE); VirtualFree(s_spi_sendbuffer,0,MEM_RELEASE); s_spi_sendbuffer = NULL; } // DESTROY THE LIST OF PROVIDERS if (clearproviderlist) { s_spi_providerlist.Clear(); s_spi_providersfound = FALSE; } } //=========================================================================== static void SpiFindAllProviders () { // MAKE SURE THIS FUNCTION IS NOT CALLED MORE THAN ONCE if (s_spi_providersfound) return; else s_spi_providersfound = TRUE; // CREATE THE SEARCH SPECIFICATION char basepath[MAX_PATH] = ""; GetModuleFileName((HMODULE)StormGetInstance(),basepath,MAX_PATH); { LPTSTR curr = basepath; while (SStrChr(curr,'\\')) curr = SStrChr(curr,'\\')+1; *curr = 0; } char filespec[MAX_PATH] = ""; wsprintf(filespec,"%s*.snp",basepath); // SEARCH FOR PROVIDER LIBRARIES WIN32_FIND_DATA finddata; HANDLE findhandle = FindFirstFile(filespec,&finddata); if (findhandle == INVALID_HANDLE_VALUE) return; do { // BUILD THE COMPLETE FILENAME char filename[MAX_PATH]; wsprintf(filename,"%s%s",basepath,finddata.cFileName); // LOAD THE CAPABILITIES SIGNATURE BLOCK FOR THE NEXT LIBRARY LPVOID capssig = NULL; #ifndef UNSIGNEDSNPS capssig = SpiLoadCapsSignature(filename); if (!capssig) continue; #endif // LOAD THE NEXT LIBRARY HINSTANCE lib = LoadLibrary(filename); if (!lib) { if (capssig) FREE(capssig); continue; } // GET THE ADDRESS OF THE QUERY FUNCTION SNETSPIQUERY query = (SNETSPIQUERY)GetProcAddress(lib,"SnpQuery"); if (!query) { if (capssig) FREE(capssig); FreeLibrary(lib); continue; } // QUERY ALL PROVIDER INTERFACES CONTAINED IN THIS LIBRARY LPVOID currcapssig = capssig; DWORD index = 0; for (;;) { // QUERY THE PROVIDER'S CAPABILITIES TO DETERMINE WHETHER IT RETURNS // SUCCESS OR FAILURE; HOWEVER, DON'T USE THE CAPABILITIES IT RETURNS, // BECAUSE THEY'RE TOO EASY TO HACK #ifndef UNSIGNEDSNPS { #endif DWORD id; LPCSTR desc; LPCSTR req; SNETCAPSPTR caps = NULL; if (!query(index,&id,&desc,&req,&caps)) break; if (!(caps && (caps->size >= sizeof(SNETCAPS)))) break; #ifdef _DEBUG if (caps->flags & SNET_CAPS_RETAILONLY) break; #else if (caps->flags & SNET_CAPS_DEBUGONLY) break; #endif #ifndef UNSIGNEDSNPS } // RETRIEVE THE REAL CAPABILITIES FROM THE SIGNATURE DATA DWORD id; LPCSTR desc; LPCSTR req; SNETCAPSPTR caps = NULL; currcapssig = SpiExtractCaps(currcapssig,&id,&desc,&req,&caps); if (!currcapssig) break; if (!(caps && (caps->size >= sizeof(SNETCAPS)))) break; #endif // ALLOCATE A NEW PROVIDER STRUCTURE PROVIDERINFOPTR info = s_spi_providerlist.NewNode(LIST_UNLINKED); // FILL OUT THE PROVIDER INFORMATION STRUCTURE SStrCopy(info->filename,filename,MAX_PATH); info->index = index; info->id = id; if (desc) SStrCopy(info->desc,desc,SNETSPI_MAXSTRINGLENGTH); if (req) SStrCopy(info->req,req,SNETSPI_MAXSTRINGLENGTH); CopyMemory(&info->caps,caps,sizeof(SNETCAPS)); info->caps.maxmessagesize -= (sizeof(HEADER)+sizeof(DWORD)); // ADD IT TO THE LIST IN SORTED ORDER PROVIDERINFOPTR curr = s_spi_providerlist.Head(); while (curr && (SpiCheckProviderOrder(curr,info) <= 0)) curr = curr->Next(); s_spi_providerlist.LinkNode(info,LIST_LINK_BEFORE,curr); ++index; } // FREE THE LIBRARY FreeLibrary(lib); // FREE THE CAPABILITIES SIGNATURE if (capssig) FREE(capssig); } while (FindNextFile(findhandle,&finddata)); FindClose(findhandle); } //=========================================================================== static BOOL SpiInitialize (DWORD providerid, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata, HANDLE recvevent) { // DESTROY THE EXISTING PROVIDER IF ONE IS INITIALIZED SpiDestroy(FALSE); // BUILD A LIST OF PROVIDERS IF WE DON'T ALREADY HAVE ONE SpiFindAllProviders(); // FIND THE DESIRED PROVIDER s_spi_providerptr = s_spi_providerlist.Head(); while (s_spi_providerptr && (s_spi_providerptr->id != providerid)) s_spi_providerptr = s_spi_providerptr->Next(); if (!s_spi_providerptr) { SpiDestroy(FALSE); SErrSetLastError(SNET_ERROR_BAD_PROVIDER); return FALSE; } // DETERMINE TIMEOUTS BASED ON THE LATENCY OF THIS PROVIDER s_spi_timetoackturn = max( 250, 5*s_spi_providerptr->caps.latencyms); s_spi_timetoblock = max(5000,12*s_spi_providerptr->caps.latencyms); s_spi_timetogiveup = max(1000, 4*s_spi_providerptr->caps.latencyms); s_spi_timetorequest = max( 25,s_spi_providerptr->caps.latencyms/2); s_spi_timetoresend = max( 50,s_spi_providerptr->caps.latencyms); // IF THIS PROVIDER REQUIRES PACKETS TO BE IN PAGE-LOCKED MEMORY, ALLOCATE // A SEND BUFFER if (s_spi_providerptr->caps.flags & SNET_CAPS_PAGELOCKEDBUFFERS) { s_spi_sendbuffer = VirtualAlloc(NULL,SENDBUFSIZE,MEM_COMMIT,PAGE_READWRITE); if (s_spi_sendbuffer) VirtualLock(s_spi_sendbuffer,SENDBUFSIZE); else { SpiDestroy(FALSE); SErrSetLastError(SNET_ERROR_NOT_ENOUGH_MEMORY); return FALSE; } } // LOAD THE LIBRARY CONTAINING THE PROVIDER s_spi_lib = LoadLibrary(s_spi_providerptr->filename); if (!s_spi_lib) { SpiDestroy(FALSE); SErrSetLastError(SNET_ERROR_BAD_PROVIDER); return FALSE; } // GET A POINTER TO THE BIND FUNCTION SNETSPIBIND bind = (SNETSPIBIND)GetProcAddress(s_spi_lib,"SnpBind"); if (!bind) { SpiDestroy(FALSE); SErrSetLastError(SNET_ERROR_BAD_PROVIDER); return FALSE; } // BIND TO THE PROVIDER SNETSPIPTR returnedspi = NULL; bind(s_spi_providerptr->index,&returnedspi); if ((!returnedspi) || (returnedspi->size < MINSPISIZE)) { SpiDestroy(FALSE); SErrSetLastError(SNET_ERROR_BAD_PROVIDER); return FALSE; } // SAVE THE PROVIDER INTERFACE POINTERS if (!s_spi) s_spi = NEW(SNETSPI); ZeroMemory(s_spi,sizeof(SNETSPI)); CopyMemory(s_spi,returnedspi,min(returnedspi->size,sizeof(SNETSPI))); // CALL THE PROVIDER'S INITIALIZE FUNCTION TRACEOUT(TRACEHANDLE, " spiInitialize(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", programdata,playerdata,interfacedata,versiondata,recvevent); if (!s_spi->Initialize(programdata, playerdata, interfacedata, versiondata, recvevent)) { DWORD lasterror = SErrGetLastError(); SpiDestroy(FALSE); SErrSetLastError(lasterror); return FALSE; } return TRUE; } //=========================================================================== static LPVOID SpiLoadCapsSignature (LPCSTR filename) { HSARCHIVE archive = (HSARCHIVE)0; LPVOID buffer = NULL; HSFILE file = (HSFILE)0; TRY { // OPEN THE MPQ ARCHIVE EMBEDDED IN THE FILE if (!SFileOpenArchive(filename,0,0,&archive)) LEAVE; // AUTHENTICATE THE ARCHIVE { DWORD authtype; SFileAuthenticateArchive(archive,&authtype); if ((authtype != SFILE_AUTH_UNABLETOAUTHENTICATE) && (authtype < SFILE_AUTH_FIRSTAUTHENTIC)) LEAVE; } // OPEN THE CAPABILITIES DATA FROM THE ARCHIVE if (!SFileOpenFileEx(archive,"caps.dat",0,&file)) LEAVE; // CREATE A BUFFER FOR THE CAPABILITIES DATA DWORD bytes = SFileGetFileSize(file,NULL); buffer = ALLOC(bytes); // READ THE CAPABILITIES DATA { DWORD bytesread; SFileReadFile(file,buffer,bytes,&bytesread,NULL); } } FINALLY { if (file) SFileCloseFile(file); if (archive) SFileCloseArchive(archive); } return buffer; } //=========================================================================== static BOOL SpiMeetsMinimumCaps (SNETCAPSPTR curr, SNETCAPSPTR mincaps) { if (!(curr && mincaps)) return TRUE; return (((mincaps->flags & curr->flags) == mincaps->flags) && (mincaps->maxmessagesize <= curr->maxmessagesize) && (mincaps->maxqueuesize <= curr->maxqueuesize ) && (mincaps->maxplayers <= curr->maxplayers ) && (mincaps->bytessec <= curr->bytessec ) && ((!mincaps->latencyms) || (mincaps->latencyms >= curr->latencyms))); } //=========================================================================== static BOOL SpiNormalizeDataBlocks (SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata, SNETPROGRAMDATAPTR modprogramdata, SNETPLAYERDATAPTR modplayerdata, SNETUIDATAPTR modinterfacedata, SNETVERSIONDATAPTR modversiondata) { // NORMALIZE THE PROGRAM DATA ZeroMemory(modprogramdata,sizeof(SNETPROGRAMDATA)); if (programdata) CopyMemory(modprogramdata,programdata,min(sizeof(SNETPROGRAMDATA),programdata->size)); modprogramdata->size = sizeof(SNETPROGRAMDATA); if (!modprogramdata->programname) modprogramdata->programname = ""; char generateddescription[256] = ""; if (!modprogramdata->programdescription) { UiGetProgramDescription(modprogramdata->programname, versiondata, generateddescription, 256); modprogramdata->programdescription = generateddescription; } // NORMALIZE THE PLAYER DATA ZeroMemory(modplayerdata,sizeof(SNETPLAYERDATA)); if (playerdata) CopyMemory(modplayerdata,playerdata,min(sizeof(SNETPLAYERDATA),playerdata->size)); modplayerdata->size = sizeof(SNETPLAYERDATA); if (!modplayerdata->playername) modplayerdata->playername = ""; if (!modplayerdata->playerdescription) modplayerdata->playerdescription = ""; // NORMALIZE THE INTERFACE DATA ZeroMemory(modinterfacedata,sizeof(SNETUIDATA)); if (interfacedata) CopyMemory(modinterfacedata,interfacedata,min(sizeof(SNETUIDATA),interfacedata->size)); modinterfacedata->size = sizeof(SNETUIDATA); if (!modinterfacedata->parentwindow) modinterfacedata->parentwindow = SDrawGetFrameWindow(); if (!modinterfacedata->messageboxcallback) modinterfacedata->messageboxcallback = MessageBoxA; // NORMALIZE THE VERSION DATA ZeroMemory(modversiondata,sizeof(SNETVERSIONDATA)); if (versiondata) CopyMemory(modversiondata,versiondata,min(sizeof(SNETVERSIONDATA),versiondata->size)); modversiondata->size = sizeof(SNETVERSIONDATA); return TRUE; } //=========================================================================== static BOOL SpiSend (DWORD addresses, SNETADDRPTR *addrlist, LPVOID data, DWORD databytes) { if (!s_spi) return FALSE; // UPDATE OUR ESTIMATE OF HOW LONG IT WILL TAKE TO SEND ALL THE OUTGOING // DATA THAT WE HAVE QUEUED { DWORD currtime = GetTickCount(); if (currtime-s_spi_outgoingtime < 0x7FFFFFFF) s_spi_outgoingtime = currtime; s_spi_outgoingtime += (addresses*(databytes+ESTIMATEDPACKETOVERHEAD)) *1000 /s_spi_providerptr->caps.bytessec; } // IF THIS SERVICE PROVIDER REQUIRES ITS DATA TO BE PAGE LOCKED, MOVE // THE ADDRESSES AND DATA INTO A PAGE LOCKED BUFFER if (s_spi_sendbuffer) { DWORD ptrbytes = 16*sizeof(SNETADDRPTR); DWORD addrbytes = 16*sizeof(SNETADDR); if ((addresses > 16) || (databytes > SENDBUFSIZE-addrbytes)) return FALSE; DWORD loop; for (loop = 0; loop < addresses; ++loop) *((SNETADDRPTR *)s_spi_sendbuffer+loop) = (SNETADDRPTR)((LPBYTE)s_spi_sendbuffer+ptrbytes+loop*sizeof(SNETADDR)); for (loop = 0; loop < addresses; ++loop) CopyMemory((LPBYTE)s_spi_sendbuffer+ptrbytes+loop*sizeof(SNETADDR),*(addrlist+loop),sizeof(SNETADDR)); CopyMemory((LPBYTE)s_spi_sendbuffer+ptrbytes+addrbytes,data,databytes); data = (LPBYTE)s_spi_sendbuffer+ptrbytes+addrbytes; } // IF WE ARE IN DEBUG MODE, DUMP THE ADDRESSES AND PACKET DATA #ifdef TRACING TRACEOUT(TRACEHANDLE, " spiSend(%u,*addrlist,0x%08x,%u)", addresses,data,databytes); for (DWORD loop = 0; loop < addresses; ++loop) { CONNPTR targetconn = ConnFindByAddr(*(addrlist+loop)); TRACEDUMPADDR(TRACEHANDLE, "target", *(addrlist+loop), targetconn ? targetconn->playerid : NOPLAYER); } PACKETPTR pkt = (PACKETPTR)data; TRACEDUMP(TRACEHANDLE,&pkt->header,min(databytes,sizeof(HEADER))); if (databytes > sizeof(HEADER)) TRACEDUMP(TRACEHANDLE,&pkt->data[0],databytes-sizeof(HEADER)); #endif PerfAdd(SNET_PERFID_TOTALBYTESSENT,databytes); return s_spi->Send(addresses,addrlist,data,databytes); } /**************************************************************************** * * PACKET UTILITY FUNCTIONS * ***/ //=========================================================================== static void PktAllocateLocalMessage (SNETADDRPTR *addr, LPVOID *data, DWORD databytes) { *addr = NEW(SNETADDR); databytes += 4-(databytes & 3); *data = ALLOC(max(4,databytes)); } //=========================================================================== static WORD PktComputeChecksum (LPVOID data, DWORD databytes) { DWORD checkval1 = 0; DWORD checkval2 = 0; LPBYTE ptr = ((LPBYTE)data)+databytes-1; while (databytes--) { checkval1 += *ptr--; if (checkval1 >= 0xFF) checkval1 -= 0xFF; checkval2 += checkval1; } checkval2 %= 255; return MAKEWORD((checkval2 & 0xFF),(checkval1 & 0xFF)); } //=========================================================================== static void PktFreeLocalMessage (SNETADDRPTR addr, LPVOID data, DWORD databytes) { if (addr) DEL(addr); if (data) FREE(data); } //=========================================================================== static WORD PktGenerateChecksum (PACKETPTR pkt) { // COMPUTE THE CURRENT CHECKSUM FOR THE MESSAGE WORD checksum = PktComputeChecksum(((LPBYTE)pkt)+sizeof(WORD), pkt->header.bytes-sizeof(WORD)); // COMPUTE A NEW VALUE FOR THE CHECKSUM FIELD THAT WILL MAKE THE NEW // CHECKSUM OF THE ENTIRE MESSAGE ZERO BYTE hibyte = 0xFF-((checksum >> 8)+(checksum & 0xFF)) % 0xFF; BYTE lobyte = 0xFF-((checksum >> 8)+hibyte) % 0xFF; return MAKEWORD(lobyte,hibyte); } /**************************************************************************** * * CONNECTION MANAGEMENT FUNCTIONS * ***/ static LIST(CONNREC) s_conn_connlist; static LIST(CONNREC) s_conn_local; static void ConnFree (CONNPTR conn); static BOOL ConnResendMessage (CONNPTR conn, LPVOID data, DWORD databytes); static void ConnSendPacket (CONNPTR conn, PACKETPTR pkt); static MESSAGEPTR ConnSendMessage (CONNPTR target, BYTE type, BYTE subtype, LPVOID data, DWORD databytes); //=========================================================================== static CONNPTR ConnAddRec (LISTPTR(CONNREC) list, SNETADDRPTR addr) { TRACEDUMPADDR(TRACEHANDLE, " ConnAddRec() addr", addr,NOPLAYER); CONNPTR newconn = list->NewNode(); CopyMemory(&newconn->addr,addr,sizeof(SNETADDR)); newconn->playerid = NOPLAYER; newconn->lastreceivetime = GetTickCount(); return newconn; } //=========================================================================== static void ConnClearOldTurns (CONNPTR onlyconn) { CONNPTR localptr = ConnFindLocal(); if (!localptr) return; // DETERMINE THE SEQUENCE OF THE NEXT TURN WE WILL PROCESS WORD sequence = localptr->incomingsequence[TYPE_TURN]; // DETERMINE THE EARLIEST POSSIBLE SEQUENCE FOR WHICH WE MAY STILL NEED // TURN DATA FOR THE PURPOSES OF RESENDING TURNS ON BEHALF OF UNRESPONSIVE // CLIENTS WORD acksequence = sequence; { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if ((WORD)(acksequence-conn->lastprocessedturn) < 0x7FFF) acksequence = conn->lastprocessedturn; conn = conn->Next(); } } // ITERATE THROUGH ALL CONNECTIONS { for (int local = 0; local <= 1; ++local) { CONNPTR conn = local ? localptr : s_conn_connlist.Head(); while (conn) { if ((!onlyconn) || (conn == onlyconn)) { // IF THERE ARE ANY TURNS ON THIS CONNECTION'S INCOMING QUEUE // WITH SEQUENCE NUMBERS PRIOR TO THE CURRENT SEQUENCE NUMBER, // MOVE THEM TO THE OLD TURNS QUEUE while ((!conn->incomingqueue[TYPE_TURN].IsEmpty()) && ((WORD)(sequence-conn->incomingqueue[TYPE_TURN].Head()->data->header.sequence) > 0) && ((WORD)(sequence-conn->incomingqueue[TYPE_TURN].Head()->data->header.sequence) <= 0x7FFF)) { MESSAGEPTR message = conn->incomingqueue[TYPE_TURN].Head(); conn->incomingqueue[TYPE_TURN].UnlinkNode(message); ConnSetCurrentMessage(conn,TYPE_TURN,message); } // IF THERE ARE ANY TURNS ON THIS CONNECTION'S OLD TURNS QUEUE // WITH SEQUENCE NUMBERS PRIOR TO THE EARLIEST POSSIBLE NEEDED // SEQUENCE, FREE THEM while ((!conn->oldturns.IsEmpty()) && ((WORD)(acksequence-conn->oldturns.Head()->data->header.sequence) > 0) && ((WORD)(acksequence-conn->oldturns.Head()->data->header.sequence) <= 0x7FFF)) { MESSAGEPTR message = conn->oldturns.Head(); if (local) PktFreeLocalMessage(message->addr,message->data,message->databytes); else { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [all acks above turn sequence (%u>%u)]", message->addr,message->data,message->databytes,acksequence,message->data->header.sequence); s_spi->Free(message->addr,message->data,message->databytes); } conn->oldturns.DeleteNode(message); } } conn = conn->Next(); } } } } //=========================================================================== static void ConnDestroy () { for (BOOL local = FALSE; local <= TRUE; ++local) { LISTPTR(CONNREC) list = local ? &s_conn_local : &s_conn_connlist; CONNPTR currconn; while ((currconn = list->Head()) != NULL) ConnFree(currconn); } } //=========================================================================== static void ConnDestroyQueue (LISTPTR(MESSAGE) queue) { MESSAGEPTR currmsg; while ((currmsg = queue->Head()) != NULL) { if (currmsg->local) PktFreeLocalMessage(currmsg->addr,currmsg->data,currmsg->databytes); else if (s_spi) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [closing connection]", currmsg->addr,currmsg->data,currmsg->databytes); s_spi->Free(currmsg->addr,currmsg->data,currmsg->databytes); } queue->DeleteNode(currmsg); } } //=========================================================================== static CONNPTR inline ConnFindByAddr (SNETADDRPTR addr) { CONNPTR curr = s_conn_connlist.Head(); while (curr && memcmp(&curr->addr,addr,sizeof(SNETADDR))) curr = curr->Next(); if (!curr) curr = ConnAddRec(&s_conn_connlist,addr); return curr; } //=========================================================================== static CONNPTR inline ConnFindByPlayerId (DWORD playerid) { if (playerid == NOPLAYER) return NULL; for (BOOL local = TRUE; local >= FALSE; --local) { LISTPTR(CONNREC) list = local ? &s_conn_local : &s_conn_connlist; CONNPTR curr = list->Head(); while (curr) if (curr->playerid == (BYTE)playerid) return curr; else curr = curr->Next(); } return NULL; } //=========================================================================== static CONNPTR inline ConnFindLocal () { if (!s_conn_local.IsEmpty()) return s_conn_local.Head(); else { SNETADDR addr; ZeroMemory(&addr,sizeof(SNETADDR)); return ConnAddRec(&s_conn_local,&addr); } } //=========================================================================== static void ConnFree (CONNPTR conn) { TRACEDUMPADDR(TRACEHANDLE, " ConnFree() addr", &conn->addr,conn->playerid); for (int type = 0; type < TYPES; ++type) { ConnDestroyQueue(&conn->outgoingqueue[type]); ConnDestroyQueue(&conn->incomingqueue[type]); ConnDestroyQueue(&conn->processing[type]); } ConnDestroyQueue(&conn->oldturns); if (conn == s_conn_local.Head()) s_conn_local.DeleteNode(conn); else s_conn_connlist.DeleteNode(conn); } //=========================================================================== static DWORD ConnMaintainConnections () { DWORD currtime = GetTickCount(); DWORD wait = INFINITE; CONNPTR local = ConnFindLocal(); CONNPTR conn = s_conn_connlist.Head(); if (!(conn && local)) return wait; CONNPTR next; do { next = conn->Next(); // DELETE ANY CONNECTION WHICH WE HAVEN'T HEARD FROM IN FIFTY SECONDS if ((conn->playerid == NOPLAYER) && (currtime-conn->lastreceivetime >= DELETECONNTIME)) { TRACEOUT(TRACEHANDLE, " deleting unresponsive connection: player=%x", conn->playerid); ConnFree(conn); continue; } // DON'T SEND CONNECTION MAINTENANCE PACKETS TO PLAYERS WHO AREN'T // IN THE GAME if ((conn->playerid == NOPLAYER) && !conn->establishing) continue; // PING EACH ESTABLISHED CONNECTION ONCE EVERY TWENTY SECONDS if ((currtime-conn->lastpingtime >= PINGFREQUENCY) && !conn->establishing) { TRACEOUT(TRACEHANDLE, " pinging: player=%x", conn->playerid); conn->lastpingtime = currtime; ConnSendMessage(conn,TYPE_SYSTEM,SYS_PING,NULL,0); } // SEND OUT RESEND REQUESTS AS NECESSARY { for (int type = 0; type < TYPES; ++type) { WORD lastsequence = conn->incomingsequence[type]-1; MESSAGEPTR message = conn->incomingqueue[type].Head(); while (message) { if (message->data->header.sequence-lastsequence > 1) { if (message->resendtime && ((LONG)(message->resendtime+s_spi_timetoresend-currtime) > 0)) wait = min(wait,message->resendtime+s_spi_timetoresend-currtime); else { TRACEOUT(TRACEHANDLE, " requesting resend: type=%u sequence=%04x player=%x", type,lastsequence+1,conn->playerid); message->resendtime = currtime; PACKET pkt; pkt.header.checksum = 0; pkt.header.bytes = sizeof(HEADER); pkt.header.sequence = lastsequence+1; pkt.header.acksequence = conn->availablesequence[type]; pkt.header.type = type; pkt.header.subtype = 0; pkt.header.playerid = local->playerid; pkt.header.flags = MF_RESENDREQUEST; pkt.header.checksum = PktGenerateChecksum(&pkt); ConnSendPacket(conn,&pkt); } break; } lastsequence = message->data->header.sequence; message = message->Next(); } } } // AUTOMATICALLY RESEND UNACKNOWLEDGED SYSTEM MESSAGES AND USER // ASYNCHRONOUS MESSAGES // (THESE TYPES OF MESSAGES ARE NOT EXPECTED BY THE RECIPIENT, SO // WE CAN'T RELY ON HIM TO REQUEST A RESEND) { for (int type = 0; type < TYPES; ++type) if ((!conn->outgoingqueue[type].IsEmpty()) && (type != TYPE_TURN)) { // DETERMINE HOW QUICKLY WE SHOULD EXPECT A RESPONSE TO THIS // MESSAGE, BASED ON BOTH LATENCY AND BANDWIDTH MESSAGEPTR message = conn->outgoingqueue[type].Head(); DWORD bandwidth = s_spi_providerptr->caps.bytessec; DWORD maxpacket = s_spi_providerptr->caps.maxmessagesize; DWORD totaldata = message->databytes +maxpacket +2*ESTIMATEDPACKETOVERHEAD; DWORD responsetime = 2*s_spi_timetoresend +totaldata*1000/bandwidth +200; // IF WE HAVEN'T RECEIVED A RESPONSE IN THE EXPECTED AMOUNT // OF TIME, RESEND THE MESSAGE if (message->resendtime && ((LONG)(message->resendtime+responsetime-currtime) > 0)) wait = min(wait,message->resendtime+responsetime-currtime); else { TRACEOUT(TRACEHANDLE, " auto-resending: type=%u sequence=%04x player=%x", type,message->data->header.sequence,conn->playerid); message->resendtime = currtime; ConnResendMessage(conn,message->data,message->databytes); } } } // SEND OUT EXPLICIT ACKNOWLEDGEMENT PACKETS TO THIS CONNECTION IF // WE HAVEN'T SENT ANY EXPLICIT OR PIGGY-BACKED ACKNOWLEDGEMENTS RECENTLY { for (int type = 0; type < TYPES; ++type) { DWORD acktime = (type == TYPE_TURN) ? s_spi_timetoackturn : s_spi_timetorequest; if ((conn->acksequence[type] != conn->availablesequence[type]) && conn->acktime[type]) if ((LONG)(conn->acktime[type]+acktime-currtime) > 0) wait = min(wait,conn->acktime[type]+acktime-currtime); else { TRACEOUT(TRACEHANDLE, " sending explicit ack: type=%u sequence=%04x player=%x", type,conn->availablesequence[type],conn->playerid); PACKET pkt; pkt.header.checksum = 0; pkt.header.bytes = sizeof(HEADER); pkt.header.sequence = conn->availablesequence[type]; pkt.header.acksequence = conn->availablesequence[type]; pkt.header.type = type; pkt.header.subtype = 0; pkt.header.playerid = local->playerid; pkt.header.flags = MF_ACK; pkt.header.checksum = PktGenerateChecksum(&pkt); SNETADDRPTR addr = &conn->addr; ConnSendPacket(conn,&pkt); } } } } while ((conn = next) != NULL); return wait; } //=========================================================================== static void ConnProcessAck (CONNPTR conn, BYTE type, WORD acksequence) { // REMOVE ACKNOWLEDGED MESSAGES FROM THE OUTGOING QUEUE BOOL found = FALSE; while ((!conn->outgoingqueue[type].IsEmpty()) && ((WORD)(acksequence-conn->outgoingqueue[type].Head()->data->header.sequence) > 0) && ((WORD)(acksequence-conn->outgoingqueue[type].Head()->data->header.sequence) <= 0x7FFF)) { found = TRUE; MESSAGEPTR message = conn->outgoingqueue[type].Head(); if (message->local) PktFreeLocalMessage(message->addr,message->data,message->databytes); else { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [acknowledged]", message->addr, message->data, message->databytes); s_spi->Free(message->addr,message->data,message->databytes); } conn->outgoingqueue[type].DeleteNode(message); } // SAVE THE SEQUENCE OF THE LAST PROCESSED TURN if (type == TYPE_TURN) conn->lastprocessedturn = acksequence; // RESET THE TIME STAMP OF THE MESSAGE THAT IS NOW AT THE HEAD OF THE // OUTGOING QUEUE, SO WE DON'T IMMEDIATELY RESEND IT if (found && !conn->outgoingqueue[type].IsEmpty()) { DWORD currtime = GetTickCount(); if (currtime-conn->outgoingqueue[type].Head()->resendtime < 0x7FFFFFFF) conn->outgoingqueue[type].Head()->resendtime = currtime; } } //=========================================================================== static BOOL ConnResendMessage (CONNPTR conn, LPVOID data, DWORD databytes) { // MAKE A COPY OF THE MESSAGE PACKETPTR localpkt = (PACKETPTR)ALLOC(databytes); CopyMemory(localpkt,data,databytes); // FILL IN THE CURRENT ACKNOWLEDGEMENT SEQUENCE localpkt->header.acksequence = conn->availablesequence[localpkt->header.type]; // FILL IN THE NEW CHECKSUM localpkt->header.checksum = PktGenerateChecksum(localpkt); // SEND THE MESSAGE ConnSendPacket(conn,localpkt); // FREE THE COPY FREE(localpkt); return TRUE; } //=========================================================================== static void ConnSendPacket (CONNPTR conn, PACKETPTR pkt) { SNETADDRPTR addr = &conn->addr; SpiSend(1,&addr,pkt,pkt->header.bytes); conn->acksequence[pkt->header.type] = pkt->header.acksequence; conn->acktime[pkt->header.type] = 0; } //=========================================================================== static MESSAGEPTR ConnSendMessage (CONNPTR target, BYTE type, BYTE subtype, LPVOID data, DWORD databytes) { // GET A POINTER TO THE LOCAL PLAYER CONNPTR local = ConnFindLocal(); if (!local) return NULL; // ALLOCATE MEMORY TO HOLD THE ADDRESS AND MESSAGE DATA SNETADDRPTR pktaddr = NULL; LPVOID pktdata = NULL; DWORD pktdatabytes = sizeof(HEADER)+databytes; PktAllocateLocalMessage(&pktaddr,&pktdata,pktdatabytes); // FILL IN THE ADDRESS CopyMemory(pktaddr,&target->addr,sizeof(SNETADDR)); // FILL IN THE HEADER PACKETPTR pkt = (PACKETPTR)pktdata; pkt->header.bytes = (WORD)pktdatabytes; pkt->header.acksequence = target->availablesequence[type]; pkt->header.type = type; pkt->header.subtype = subtype; pkt->header.playerid = local->playerid; pkt->header.flags = 0; // FILL IN THE SEQUENCE NUMBER FOR THIS MESSAGE if ((type == TYPE_SYSTEM) && (subtype == SYS_INITIALCONTACT)) pkt->header.sequence = 0; else pkt->header.sequence = target->outgoingsequence[type]++; // FILL IN THE DATA if (data && databytes) CopyMemory((LPBYTE)pktdata+sizeof(HEADER),data,databytes); // FILL IN THE CHECKSUM pkt->header.checksum = PktGenerateChecksum(pkt); // ALLOCATE A MESSAGE RECORD MESSAGEPTR msg; if (target == local) msg = target->incomingqueue[type].NewNode(); else msg = target->outgoingqueue[type].NewNode(); // FILL IN THE MESSAGE RECORD msg->addr = pktaddr; msg->data = (PACKETPTR)pktdata; msg->databytes = pktdatabytes; msg->local = TRUE; msg->sendtime = GetTickCount(); // SEND THE MESSAGE if (target != local) { ConnSendPacket(target,pkt); PerfAdd(SNET_PERFID_USERBYTESSENT,databytes); } // INITIALIZE THE MESSAGE'S RESEND TIMER TO THE ESTIMATED TIME THAT // THE COMPLETE MESSAGE WILL HIT THE WIRE msg->resendtime = s_spi_outgoingtime; return msg; } //=========================================================================== static void ConnSetCurrentMessage (CONNPTR conn, BYTE type, MESSAGEPTR message) { // message must be unlinked upon entry to this function if (type == TYPE_TURN) conn->oldturns.LinkNode(message,LIST_TAIL,NULL); else { if (!conn->processing[type].IsEmpty()) { MESSAGEPTR processing = conn->processing[type].Head(); if (processing->local) PktFreeLocalMessage(processing->addr, processing->data, processing->databytes); else { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [processed]", processing->addr, processing->data, processing->databytes); s_spi->Free(processing->addr, processing->data, processing->databytes); } conn->processing[type].DeleteNode(processing); } conn->processing[type].LinkNode(message,LIST_TAIL,NULL); } } /**************************************************************************** * * GAME/PLAYER MANAGEMENT FUNCTIONS * ***/ static DWORD s_game_categorybits = 0; static DWORD s_game_creationtime = 0; static LPVOID s_game_initdata = NULL; static DWORD s_game_initdatabytes = 0; static char s_game_gamedesc[SNETSPI_MAXSTRINGLENGTH] = ""; static DWORD s_game_gamemode = 0; static char s_game_gamename[SNETSPI_MAXSTRINGLENGTH] = ""; static char s_game_gamepass[SNETSPI_MAXSTRINGLENGTH] = ""; static BOOL s_game_joining = FALSE; static DWORD s_game_optcategorybits = 0; static BYTE s_game_playerid = NOPLAYER; static DWORD s_game_playersallowed = 0; //=========================================================================== static void GameBuildClientData (LPVOID buffer, DWORD *bytes) { CLIENTDATAPTR ptr = (CLIENTDATAPTR)buffer; ptr->bytes = sizeof(CLIENTDATA); SNetGetNumPlayers(NULL,NULL,&ptr->numplayers); ptr->maxplayers = s_game_playersallowed; *bytes = ptr->bytes; } //=========================================================================== static void GameBuildGameData (SNETGAMEPTR dest, SNETSPI_GAMELISTPTR source) { ZeroMemory(dest,sizeof(SNETGAME)); dest->size = sizeof(SNETGAMEPTR); dest->id = source->gameid; dest->gamename = source->gamename; dest->gamedescription = source->gamedescription; dest->categorybits = source->gamecategorybits; CLIENTDATAPTR clientdata = (CLIENTDATAPTR)(source->clientdata); if (clientdata) { dest->numplayers = clientdata->numplayers; dest->maxplayers = clientdata->maxplayers; } } //=========================================================================== static void GameCopyGameList (DWORD categorybits, DWORD categorymask, SNETSPI_GAMELISTPTR *gamearray, DWORD *games, DWORD *hintnextcall) { *games = 0; // LOCK THE GAME LIST SNETSPI_GAMELISTPTR head; TRACEOUT(TRACEHANDLE, " spiLockGameList(0x%08x,0x%08x,*gamelist)", categorybits,categorymask); if (!s_spi->LockGameList(categorybits,categorymask,&head)) return; // DETERMINE THE NUMBER OF MATCHING GAMES *games = 0; { SNETSPI_GAMELISTPTR curr = head; while (curr) { if (!(curr->gamemode & SNET_GM_UNLISTEDMASK)) ++*games; curr = curr->next; } } // ALLOCATE AN ARRAY TO HOLD THE GAMES if (*games) { *gamearray = (SNETSPI_GAMELISTPTR)ALLOC((*games)*(sizeof(SNETSPI_GAMELIST)+SNETSPI_MAXCLIENTDATA)); // COPY THE GAMES SNETSPI_GAMELISTPTR source = head; LPBYTE dest = (LPBYTE)*gamearray; SNETSPI_GAMELISTPTR lastdest = NULL; while (source) { if (!(source->gamemode & SNET_GM_UNLISTEDMASK)) { // LINK THE LAST RECORD TO THIS ONE if (lastdest) lastdest->next = (SNETSPI_GAMELISTPTR)dest; lastdest = (SNETSPI_GAMELISTPTR)dest; // COPY THIS RECORD CopyMemory(dest,source,sizeof(SNETSPI_GAMELIST)); dest += sizeof(SNETSPI_GAMELIST); // COPY THE CLIENT DATA FOR THIS RECORD if (source->clientdata) { CopyMemory(dest,source->clientdata,source->clientdatabytes); lastdest->clientdata = dest; dest += source->clientdatabytes; } } source = source->next; } } // UNLOCK THE GAME LIST TRACEOUT(TRACEHANDLE, " spiUnlockGameList(0x%08x,*hintnextcall)", head); s_spi->UnlockGameList(head,hintnextcall); } //=========================================================================== static void GameDestroy () { // FREE THE GAME INITIALIZATION DATA if (s_game_initdata) { FREE(s_game_initdata); s_game_initdata = NULL; s_game_initdatabytes = 0; } // RESET THE GAME INFORMATION s_game_gamename[0] = 0; s_game_gamedesc[0] = 0; s_game_gamepass[0] = 0; s_game_gamemode = 0; // RESET THE PLAYER INFORMATION s_game_playerid = NOPLAYER; s_game_playersallowed = 0; // DESTROY ALL CONNECTION RECORDS ConnDestroy(); } //=========================================================================== static void GameProcessLeavingPlayers () { CONNPTR conn = s_conn_connlist.Head(); CONNPTR local = ConnFindLocal(); if (!local) return; while (conn) { if ((conn->flags & PF_LEAVING) && (conn->playerid != NOPLAYER)) { // IF WE HAVE PROCESSED THE FINAL TURN FOR THIS PLAYER, REMOVE HIM if ((WORD)(conn->incomingsequence[TYPE_TURN]-conn->finalsequence) <= 0x7FFF) { // QUEUE A USER-LEVEL EVENT SysQueueUserEvent(SNET_EVENT_PLAYERLEAVE,conn->playerid,&conn->exitcode,sizeof(DWORD)); // MARK THE PLAYER SLOT AS UNUSED conn->flags = 0; conn->playerid = NOPLAYER; // IF THE GAME OWNER JUST LEFT AND WE ARE NOW THE LOWEST NUMBERED // PLAYER, TAKE OWNERSHIP OF THE GAME if ((s_game_playerid != NOPLAYER) && !local->gameowner) { // FIND THE LOWEST NUMBERED PLAYED BESIDES THE LOCAL PLAYER WHICH // IS STILL IN THE GAME DWORD lowestplayerid = 0xFFFFFFFF; CONNPTR checkconn = s_conn_connlist.Head(); while (checkconn) { if (checkconn->playerid != NOPLAYER) { if (checkconn->gameowner) lowestplayerid = 0; else if (checkconn->playerid < lowestplayerid) lowestplayerid = checkconn->playerid; } checkconn = checkconn->Next(); } // IF OUR PLAYER NUMBER IS LOWER, TAKE OWNERSHIP if (s_game_playerid < lowestplayerid) { // LOG IT TRACEOUT(TRACEHANDLE," taking ownership"); // MARK THE LOCAL PLAYER AS THE OWNER local->gameowner = TRUE; // SEND MESSAGES TO ALL OTHER PLAYERS NOTIFYING THEM OF THE // TRANSFER OF GAME OWNERSHIP { CONNPTR checkconn = s_conn_connlist.Head(); while (checkconn) { if (checkconn->playerid != NOPLAYER) { DWORD playerid = s_game_playerid; ConnSendMessage(checkconn, TYPE_SYSTEM, SYS_NEWGAMEOWNER, &playerid, sizeof(DWORD)); } checkconn = checkconn->Next(); } } } } // IF THE GAME IS NO LONGER FULL, NOTIFY THE NETWORK PROVIDER BY // CHANGING THE GAME MODE { CONNPTR local = ConnFindLocal(); if (local && local->gameowner) { DWORD activeplayers = 0; SNetGetNumPlayers(NULL,NULL,&activeplayers); if (activeplayers < s_game_playersallowed) s_game_gamemode &= ~SNET_GM_FULL; } } // UPDATE THE SERVER WITH THE NEW GAME MODE, FULL VS NOT FULL, // NUMBER OF PLAYERS, ETC. SNetSetGameMode(s_game_gamemode); } } conn = conn->Next(); } } /**************************************************************************** * * SYSTEM MESSAGE FUNCTIONS * ***/ static LIST(USEREVENT) s_sys_usereventlist; static BOOL s_sys_event[SYSMSGS]; //=========================================================================== static DWORD SysBuildPlayerInfo (SYSEVENTDATA_PLAYERINFOPTR data, CONNPTR conn, DWORD startingturn) { data->playerid = conn->playerid; data->gameowner = conn->gameowner; data->flags = conn->flags; data->startingturn = startingturn; CopyMemory(&data->addr,&conn->addr,sizeof(SNETADDR)); LPTSTR curr = data->namedesc; curr += SStrCopy(curr,conn->name,SNETSPI_MAXSTRINGLENGTH)+1; curr += SStrCopy(curr,conn->desc,SNETSPI_MAXSTRINGLENGTH)+1; data->bytes = (LPBYTE)curr-(LPBYTE)data; return data->bytes; } //=========================================================================== static void SysDestroy () { USEREVENTPTR curr; while ((curr = s_sys_usereventlist.Head()) != NULL) { if (curr->event.data) FREE(curr->event.data); s_sys_usereventlist.DeleteNode(curr); } } //=========================================================================== static void SysDispatchUserEvents () { USEREVENTPTR curr; while ((curr = s_sys_usereventlist.Head()) != NULL) { USEREVENT event; CopyMemory(&event,curr,sizeof(USEREVENT)); s_sys_usereventlist.DeleteNode(curr); event.event.playerid += s_api_playeroffset; TRACEOUT(TRACEHANDLE, " dispatch event=%u player=%u data=0x%08x databytes=%u", event.event.eventid,event.event.playerid,event.event.data,event.event.databytes); SEvtDispatch(REGISTERTYPE, REGISTERSUBTYPE_SNETEVENT, event.event.eventid, &event.event); if (event.event.data) FREE(event.event.data); } } //=========================================================================== static void CALLBACK SysOnCircuitCheck (SYSEVENTPTR event) { if ((event->databytes == sizeof(DWORD)) && (*(LPDWORD)event->data == SNET_NETWORKVERSION)) ConnSendMessage(ConnFindByAddr(event->senderaddr), TYPE_SYSTEM, SYS_CIRCUITCHECKRESPONSE, event->data, event->databytes); } //=========================================================================== static void CALLBACK SysOnDropPlayer (SYSEVENTPTR event) { SYSEVENTDATA_DROPPLAYERPTR eventdataptr = (SYSEVENTDATA_DROPPLAYERPTR)event->data; // MARK THE PLAYER AS LEAVING. WE DON'T ACTUALLY REMOVE HIM FROM THE // GAME, OR SEND A USER-LEVEL NOTIFICATION, UNTIL THE APPLICATION HAS // PROCESSED ALL REMAINING MESSAGES FROM THIS PLAYER. CONNPTR conn = ConnFindByPlayerId(eventdataptr->playerid); if (conn && (conn->playerid != NOPLAYER)) { conn->flags |= PF_LEAVING; conn->finalsequence = (WORD)eventdataptr->finalsequence; conn->exitcode = eventdataptr->exitcode; } } //=========================================================================== static void CALLBACK SysOnNewGameOwner (SYSEVENTPTR event) { // REMOVE OWNERSHIP FROM THE EXISTING GAME OWNER for (BOOL local = TRUE; local >= FALSE; --local) { LISTPTR(CONNREC) list = local ? &s_conn_local : &s_conn_connlist; ITERATELISTPTR(CONNREC,list,curr) curr->gameowner = FALSE; } // MAKE THE SENDER THE NEW GAME OWNER DWORD playerid = *(LPDWORD)event->data; CONNPTR conn = ConnFindByPlayerId(playerid); if (conn) conn->gameowner = TRUE; } //=========================================================================== static void CALLBACK SysOnPing (SYSEVENTPTR event) { ConnSendMessage(ConnFindByAddr(event->senderaddr), TYPE_SYSTEM, SYS_PINGRESPONSE, event->data, event->databytes); } //=========================================================================== static void CALLBACK SysOnPingResponse (SYSEVENTPTR event) { CONNPTR conn = ConnFindByAddr(event->senderaddr); DWORD currtime = GetTickCount(); if (conn) { conn->latency = currtime-conn->lastpingtime; conn->peaklatency = max(conn->peaklatency,conn->latency); } } //=========================================================================== static void CALLBACK SysOnPlayerInfo (SYSEVENTPTR event) { SYSEVENTDATA_PLAYERINFOPTR eventdataptr = (SYSEVENTDATA_PLAYERINFOPTR)event->data; SNETADDRPTR addr = &eventdataptr->addr; // IF THIS IS THE PLAYER INFO FOR THE SYSTEM SENDING THE PACKET, USE THE // PACKET'S ORIGIN ADDRESS RATHER THAN THE ONE IN THE PLAYER INFORMATION, // BECAUSE THE SENDER PRESUMABLY DOESN'T KNOW HIS OWN ADDRESS. if (eventdataptr->playerid == event->senderplayerid) addr = event->senderaddr; // OTHERWISE, IF THIS IS THE PLAYER INFO FOR A THIRD PARTY SYSTEM, THEN // RESET THE CONNECTION RECORD FOR THAT SYSTEM. else ConnFree(ConnFindByAddr(addr)); // UPDATE THE PLAYER INFORMATION CONNPTR conn = ConnFindByAddr(addr); CONNPTR local = ConnFindLocal(); if (conn && local) { conn->flags = eventdataptr->flags; conn->playerid = (BYTE)eventdataptr->playerid; conn->gameowner = eventdataptr->gameowner; conn->incomingsequence[TYPE_TURN] = (WORD)eventdataptr->startingturn; conn->availablesequence[TYPE_TURN] = (WORD)eventdataptr->startingturn; conn->outgoingsequence[TYPE_TURN] = local->outgoingsequence[TYPE_TURN]; if ((conn->incomingsequence[TYPE_TURN] != local->incomingsequence[TYPE_TURN]) && ((WORD)(conn->incomingsequence[TYPE_TURN]-local->incomingsequence[TYPE_TURN]) < 0x7FFF)) conn->flags |= PF_JOINING; CopyMemory(&conn->addr,addr,sizeof(SNETADDR)); SStrCopy(conn->name, eventdataptr->namedesc, SNETSPI_MAXSTRINGLENGTH); SStrCopy(conn->desc, eventdataptr->namedesc+SStrLen(eventdataptr->namedesc)+1, SNETSPI_MAXSTRINGLENGTH); } // IF THIS IS A NEW PLAYER, SEND HIM ALL OF OUR TURNS FROM HIS STARTING // TURN NUMBER { MESSAGEPTR currmsg; currmsg = local->oldturns.Head(); while (currmsg) { if ((WORD)(currmsg->data->header.sequence-eventdataptr->startingturn) <= 0x7FFF) ConnResendMessage(conn,currmsg->data,currmsg->databytes); currmsg = currmsg->Next(); } currmsg = local->incomingqueue[TYPE_TURN].Head(); while (currmsg) { if ((WORD)(currmsg->data->header.sequence-eventdataptr->startingturn) <= 0x7FFF) ConnResendMessage(conn,currmsg->data,currmsg->databytes); currmsg = currmsg->Next(); } } // QUEUE A USER-LEVEL EVENT if ((s_game_playerid != NOPLAYER) && conn && (!(conn->flags & PF_JOINING))) SysQueueUserEvent(SNET_EVENT_PLAYERJOIN, eventdataptr->playerid, NULL, 0); } //=========================================================================== static void CALLBACK SysOnPlayerJoin (SYSEVENTPTR event) { SYSEVENTDATA_PLAYERJOINPTR eventdataptr = (SYSEVENTDATA_PLAYERJOINPTR)event->data; // IF THE GAME IS FULL OR NOT JOINABLE, SEND BACK A REJECTION NOTICE { DWORD activeplayers = 0; SNetGetNumPlayers(NULL,NULL,&activeplayers); if ((activeplayers >= s_game_playersallowed) || (s_game_gamemode & SNET_GM_UNJOINABLE)) { ConnSendMessage(ConnFindByAddr(event->senderaddr), TYPE_SYSTEM, SYS_PLAYERJOIN_REJECT, NULL, 0); return; } } // BREAK OUT THE NAME, DESCRIPTION, AND PASSWORD LPCSTR playername = eventdataptr->namedescpass; LPCSTR playerdesc = playername+SStrLen(playername)+1; LPCSTR gamepass = playerdesc+SStrLen(playerdesc)+1; // IF THE PASSWORD IS WRONG, SEND BACK A REJECTION NOTICE if (s_game_gamepass[0] && _stricmp(s_game_gamepass,gamepass)) { ConnSendMessage(ConnFindByAddr(event->senderaddr), TYPE_SYSTEM, SYS_PLAYERJOIN_REJECT, NULL, 0); return; } // DETERMINE THE PLAYER'S ID BYTE playerid = 0; do { if (!ConnFindByPlayerId(playerid)) break; } while (++playerid != NOPLAYER); if ((playerid == NOPLAYER) || (playerid >= s_game_playersallowed)) { ConnSendMessage(ConnFindByAddr(event->senderaddr), TYPE_SYSTEM, SYS_PLAYERJOIN_REJECT, NULL, 0); return; } // ADD THE PLAYER CONNPTR local = ConnFindLocal(); CONNPTR conn = ConnFindByAddr(event->senderaddr); if (!(conn && local)) return; { conn->flags = 0; conn->playerid = playerid; conn->establishing = FALSE; SStrCopy(conn->name,playername,SNETSPI_MAXSTRINGLENGTH); SStrCopy(conn->desc,playerdesc,SNETSPI_MAXSTRINGLENGTH); conn->incomingsequence[TYPE_TURN] = local->outgoingsequence[TYPE_TURN]; conn->availablesequence[TYPE_TURN] = local->outgoingsequence[TYPE_TURN]; conn->outgoingsequence[TYPE_TURN] = local->outgoingsequence[TYPE_TURN]; if ((conn->incomingsequence[TYPE_TURN] != local->incomingsequence[TYPE_TURN]) && ((WORD)(conn->incomingsequence[TYPE_TURN]-local->incomingsequence[TYPE_TURN]) < 0x7FFF)) conn->flags |= PF_JOINING; } // NOTIFY THE NETWORK PROVIDER OF THE NEW NUMBER OF PLAYERS IN THE GAME { DWORD activeplayers = 0; SNetGetNumPlayers(NULL,NULL,&activeplayers); DWORD gamemode = s_game_gamemode; if (activeplayers >= s_game_playersallowed) gamemode |= SNET_GM_FULL; SNetSetGameMode(gamemode); } // SEND BACK AN ACCEPT START NOTICE DWORD startingturn = local->outgoingsequence[TYPE_TURN]; { SYSEVENTDATA_PLAYERJOIN_ACCEPTSTART data; data.playerid = playerid; data.playersallowed = s_game_playersallowed; data.nextturn = startingturn; data.gamemode = s_game_gamemode; data.runningtime = (GetTickCount()-s_game_creationtime)/1000; LPSTR currptr = data.namedescpass; currptr += SStrCopy(currptr,s_game_gamename,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(currptr,s_game_gamedesc,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(currptr,s_game_gamepass,SNETSPI_MAXSTRINGLENGTH)+1; ConnSendMessage(conn, TYPE_SYSTEM, SYS_PLAYERJOIN_ACCEPTSTART, &data, (LPBYTE)currptr-(LPBYTE)&data); } // SEND BACK INFORMATION ABOUT THE LOCAL PLAYER { SYSEVENTDATA_PLAYERINFO data; DWORD databytes = SysBuildPlayerInfo(&data,local,startingturn); ConnSendMessage(conn, TYPE_SYSTEM, SYS_PLAYERINFO, &data, databytes); } // SEND BACK INFORMATION FOR EACH OTHER PLAYER IN THE GAME { CONNPTR sendconn = s_conn_connlist.Head(); while (sendconn) { if ((sendconn != conn) && (sendconn->playerid != NOPLAYER)) { SYSEVENTDATA_PLAYERINFO data; DWORD databytes = SysBuildPlayerInfo(&data,sendconn,startingturn); ConnSendMessage(conn, TYPE_SYSTEM, SYS_PLAYERINFO, &data, databytes); } sendconn = sendconn->Next(); } } // SEND BACK AN ACCEPT DONE NOTICE ConnSendMessage(conn, TYPE_SYSTEM, SYS_PLAYERJOIN_ACCEPTDONE, s_game_initdata, s_game_initdatabytes); // SEND AN UPDATE MESSAGE TO ALL OTHER PLAYERS IN THE GAME { CONNPTR destconn = s_conn_connlist.Head(); while (destconn) { if ((destconn != conn) && (destconn->playerid != NOPLAYER)) { SYSEVENTDATA_PLAYERINFO data; DWORD databytes = SysBuildPlayerInfo(&data,conn,startingturn); ConnSendMessage(destconn, TYPE_SYSTEM, SYS_PLAYERINFO, &data, databytes); } destconn = destconn->Next(); } } // SEND THE NEW PLAYER A COPY OF ALL OUR UNPROCESSED TURNS { MESSAGEPTR currmsg = local->incomingqueue[TYPE_TURN].Head(); while (currmsg) { ConnResendMessage(conn,currmsg->data,currmsg->databytes); currmsg = currmsg->Next(); } } // QUEUE A USER-LEVEL EVENT if (!(conn->flags & PF_JOINING)) SysQueueUserEvent(SNET_EVENT_PLAYERJOIN,playerid,NULL,0); } //=========================================================================== static void CALLBACK SysOnPlayerJoinAcceptStart (SYSEVENTPTR event) { SYSEVENTDATA_PLAYERJOIN_ACCEPTSTARTPTR eventdataptr = (SYSEVENTDATA_PLAYERJOIN_ACCEPTSTARTPTR)event->data; CONNPTR conn = ConnFindLocal(); if (conn) { conn->playerid = (BYTE)eventdataptr->playerid; conn->incomingsequence[TYPE_TURN] = (WORD)eventdataptr->nextturn; conn->availablesequence[TYPE_TURN] = (WORD)eventdataptr->nextturn; conn->outgoingsequence[TYPE_TURN] = (WORD)eventdataptr->nextturn; PerfSet(SNET_PERFID_TURN,eventdataptr->nextturn); } s_game_playersallowed = eventdataptr->playersallowed; s_game_gamemode = eventdataptr->gamemode; s_game_creationtime = GetTickCount()-eventdataptr->runningtime*1000; LPCSTR currptr = eventdataptr->namedescpass; currptr += SStrCopy(s_game_gamename,currptr,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(s_game_gamedesc,currptr,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(s_game_gamepass,currptr,SNETSPI_MAXSTRINGLENGTH)+1; } //=========================================================================== static void CALLBACK SysOnPlayerJoinAcceptDone (SYSEVENTPTR event) { CONNPTR conn = ConnFindLocal(); if (conn) s_game_playerid = conn->playerid; if (s_game_initdata) { FREE(s_game_initdata); s_game_initdata = NULL; } s_game_initdatabytes = 0; if (event->data && event->databytes) { s_game_initdata = ALLOC(event->databytes); s_game_initdatabytes = event->databytes; CopyMemory(s_game_initdata,event->data,s_game_initdatabytes); SysQueueUserEvent(SNET_EVENT_INITDATA, s_game_playerid, s_game_initdata, s_game_initdatabytes); } } //=========================================================================== static void CALLBACK SysOnPlayerJoinReject (SYSEVENTPTR event) { } //=========================================================================== static void CALLBACK SysOnPlayerLeave (SYSEVENTPTR event) { SYSEVENTDATA_PLAYERLEAVEPTR eventdataptr = (SYSEVENTDATA_PLAYERLEAVEPTR)event->data; // MARK THE PLAYER AS LEAVING. WE DON'T ACTUALLY REMOVE HIM FROM THE // GAME, OR SEND A USER-LEVEL NOTIFICATION, UNTIL THE APPLICATION HAS // PROCESSED ALL REMAINING TURNS FROM THIS PLAYER. CONNPTR conn = ConnFindByAddr(event->senderaddr); if (conn && (conn->playerid != NOPLAYER)) { conn->flags |= PF_LEAVING; conn->finalsequence = (WORD)eventdataptr->finalsequence; conn->exitcode = eventdataptr->exitcode; } } //=========================================================================== static void SysProcessIncomingMessages (CONNPTR conn) { while ((!conn->incomingqueue[TYPE_SYSTEM].IsEmpty()) && (conn->incomingqueue[TYPE_SYSTEM].Head()->data->header.sequence == conn->incomingsequence[TYPE_SYSTEM])) { MESSAGEPTR message = conn->incomingqueue[TYPE_SYSTEM].Head(); PACKETPTR pkt = (PACKETPTR)message->data; TRACEOUT(TRACEHANDLE, " processing system message: sequence=%04x event=%u player=%x", pkt->header.sequence,pkt->header.subtype,pkt->header.playerid); // SIGNAL THE EVENT if (pkt->header.subtype < SYSMSGS) s_sys_event[pkt->header.subtype] = TRUE; // CALL INTERNAL THE SYSTEM EVENT HANDLER FOR THIS EVENT TYPE { SYSEVENT eventdata; eventdata.senderplayerid = pkt->header.playerid; eventdata.senderaddr = message->addr; eventdata.eventid = pkt->header.subtype; eventdata.data = pkt->data; eventdata.databytes = pkt->header.bytes-sizeof(HEADER); SEvtDispatch(REGISTERTYPE, REGISTERSUBTYPE_SYSEVENT, eventdata.eventid, &eventdata); } // ADJUST THE INCOMING SEQUENCE NUMBER ++(conn->incomingsequence[TYPE_SYSTEM]); // FREE THE MESSAGE DATA TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [system message]", message->addr,message->data,message->databytes); s_spi->Free(message->addr,message->data,message->databytes); // FREE THE MESSAGE RECORD conn->incomingqueue[TYPE_SYSTEM].DeleteNode(message); } } //=========================================================================== static void SysQueueUserEvent (DWORD eventid, DWORD playerid, LPVOID data, DWORD databytes) { USEREVENTPTR userevent = s_sys_usereventlist.NewNode(); userevent->event.eventid = eventid; userevent->event.playerid = playerid; if (data && databytes) { userevent->event.data = ALLOC(databytes); CopyMemory(userevent->event.data,data,databytes); userevent->event.databytes = databytes; } } //=========================================================================== static BOOL SysWaitForMultipleEvents (DWORD numevents, LPDWORD eventlist, BOOL waitforall, DWORD timeout) { // BLANK OUT THE RECEIVED EVENT ARRAY ZeroMemory(s_sys_event,SYSMSGS*sizeof(BOOL)); // LOOP UNTIL THE CONDITIONS ARE SATISFIED OR THE TIMEOUT HAS ELAPSED DWORD starttime = GetTickCount(); BOOL firstiter = TRUE; do { // IF THIS IS NOT THE FIRST ITERATION, SLEEP FOR A CLOCK TICK if (!firstiter) Sleep(10); firstiter = FALSE; // PROCESS INCOMING PACKETS RecvProcessExternalMessages(); RecvProcessIncomingPackets(); // MAINTAIN CONNECTIONS ConnMaintainConnections(); // IF THE CONDITIONS HAVE BEEN SATISFIED, RETURN SUCCESS { DWORD signalled = 0; for (DWORD loop = 0; loop < numevents; ++loop) if ((*(eventlist+loop) < SYSMSGS) && s_sys_event[*(eventlist+loop)]) ++signalled; if ((signalled >= numevents) || (signalled && !waitforall)) return TRUE; } } while ((timeout == INFINITE) || (GetTickCount()-starttime < timeout)); return FALSE; } /**************************************************************************** * * RECEIVING THREAD * ***/ static HANDLE s_recv_event = (HANDLE)0; static BOOL s_recv_shutdown = FALSE; static HANDLE s_recv_thread = (HANDLE)0; static DWORD CALLBACK RecvThreadProc (LPVOID param); //=========================================================================== static void RecvDestroy () { if (s_recv_event && s_recv_thread) { s_recv_shutdown = TRUE; SetEvent(s_recv_event); WaitForSingleObject(s_recv_thread,INFINITE); s_recv_shutdown = FALSE; CloseHandle(s_recv_event); CloseHandle(s_recv_thread); s_recv_event = (HANDLE)0; s_recv_thread = (HANDLE)0; } } //=========================================================================== static BOOL RecvInitialize (HANDLE *eventptr) { // CREATE THE EVENT if (!s_recv_event) s_recv_event = CreateEvent((LPSECURITY_ATTRIBUTES)NULL, 0, 0, NULL); // CREATE THE THREAD if (!s_recv_thread) { s_recv_shutdown = FALSE; DWORD threadid; s_recv_thread = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES)NULL, 0, RecvThreadProc, NULL, 0, &threadid); SetThreadPriority(s_recv_thread,THREAD_PRIORITY_HIGHEST); } // RETURN A HANDLE TO THE EVENT if (eventptr) *eventptr = s_recv_event; return (s_recv_event && s_recv_thread); } //=========================================================================== static void RecvProcessExternalMessages () { for (;;) { LPCSTR senderpath = NULL; LPCSTR sendername = NULL; LPCSTR message = NULL; TRACEOUT(TRACEHANDLE, " spiReceiveExternalMessage(*senderpath,*sendername,*message)"); if (!s_spi->ReceiveExternalMessage(&senderpath,&sendername,&message)) break; if (!(senderpath && sendername && message)) break; if ((!*senderpath) && (!*sendername)) SysQueueUserEvent(SNET_EVENT_SERVERMESSAGE, SNET_BROADCASTPLAYERID, (LPVOID)message, SStrLen(message)+1); TRACEOUT(TRACEHANDLE, " spiFreeExternalMessage(0x%08x,0x%08x,0x%08x)", senderpath,sendername,message); s_spi->FreeExternalMessage(senderpath,sendername,message); } } //=========================================================================== static void RecvProcessIncomingPackets () { for (;;) { SNETADDRPTR addr = NULL; LPVOID data = NULL; DWORD databytes = 0; TRACEOUT(TRACEHANDLE," spiReceive(*addr,*data,*databytes)"); /* note: restore this if (!s_spi->Receive(&addr,&data,&databytes)) break; */ if (!s_spi->Receive(&data,&databytes,&addr)) break; if (!(addr && data)) break; PACKETPTR pkt = (PACKETPTR)data; // IF WE ARE IN DEBUG MODE, PRINT TRACE INFO FOR THE MESSAGE #ifdef TRACING TRACEOUT(TRACEHANDLE, " addr=0x%08x data=0x%08x databytes=%u", addr,data,databytes); TRACEDUMPADDR(TRACEHANDLE, "sender", addr, (databytes >= sizeof(HEADER)) ? pkt->header.playerid : NOPLAYER); TRACEDUMP(TRACEHANDLE,&pkt->header,min(databytes,sizeof(HEADER))); if (databytes > sizeof(HEADER)) TRACEDUMP(TRACEHANDLE,&pkt->data[0],databytes-sizeof(HEADER)); #endif PerfAdd(SNET_PERFID_TOTALBYTESRECV,databytes); // FIND THE CONNECTION RECORD FOR THE SENDER OF THIS MESSAGE CONNPTR local = ConnFindLocal(); CONNPTR conn = (pkt->header.flags & MF_FORWARDED) ? ConnFindByPlayerId(pkt->header.playerid) : ConnFindByAddr(addr); if (!(conn && local)) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [no connection]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // CONFIRM THAT THIS IS A VALID MESSAGE. WE PERFORM THE FOLLOWING // CHECKS: // - CHECK FOR MINIMUM PACKET SIZE // - COMPARE LENGTH IN HEADER TO SIZE OF PACKET // - CHECK FOR VALID MESSAGE TYPE // - CHECK PLAYER NUMBER // - COMPUTE CHECKSUM // - COMPARE SENDER ADDRESS TO ADDRESS OF THE PLAYER NUMBER IN HEADER if ((databytes < sizeof(HEADER)) || (databytes < pkt->header.bytes) || (pkt->header.type >= TYPES) || PktComputeChecksum(data,pkt->header.bytes)) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [invalid message: corrupt]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } if ((!s_game_joining) && (pkt->header.playerid != NOPLAYER) && (!(pkt->header.flags & MF_FORWARDED)) && (ConnFindByPlayerId(pkt->header.playerid) != conn)) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [invalid message: bad sender]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // IF THIS IS AN INITIAL CONTACT MESSAGE, RESET THE SENDER'S CONNECTION // RECORD AND INITIATE A CIRCUIT CHECK if ((pkt->header.type == TYPE_SYSTEM) && (pkt->header.subtype == SYS_INITIALCONTACT)) { ConnFree(ConnFindByAddr(addr)); if ((pkt->header.bytes == sizeof(HEADER)+sizeof(DWORD)) && (*(LPDWORD)&pkt->data[0] == SNET_NETWORKVERSION)) { CONNPTR conn = ConnFindByAddr(addr); if (conn) { conn->establishing = TRUE; conn->incomingsequence[TYPE_SYSTEM] = 1; conn->availablesequence[TYPE_SYSTEM] = 1; conn->outgoingsequence[TYPE_SYSTEM] = 1; DWORD networkversion = SNET_NETWORKVERSION; ConnSendMessage(conn, TYPE_SYSTEM, SYS_CIRCUITCHECK, &networkversion, sizeof(DWORD)); } } TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [initial contact]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // IF THIS MESSAGE WAS NOT FORWARDED THEN RESET THE "LAST HEARD FROM" // TIME STAMP OF THE PLAYER WHO SENT IT if (!(pkt->header.flags & MF_FORWARDED)) conn->lastreceivetime = GetTickCount(); // IF THIS MESSAGE CONTAINS A PIGGY-BACKED ACKNOWLEDGEMENT THEN // FREE MESSAGES THAT WE SENT TO THE OTHER COMPUTER UP TO THE // ACKNOWLEDGEMENT SEQUENCE if (!(pkt->header.flags & MF_FORWARDED)) ConnProcessAck(conn,pkt->header.type,pkt->header.acksequence); // IF THIS MESSAGE CONTAINS ONLY AN ACKNOWLEDGEMENT, AND NO ACTUAL // DATA, THEN FREE IT if (pkt->header.flags & MF_ACK) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [ack only]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // IF THIS MESSAGE IS A RESEND REQUEST THEN FIND AND RESEND THE // REQUESTED MESSAGE if (pkt->header.flags & MF_RESENDREQUEST) { MESSAGEPTR currmsg = NULL; // PROCESS REQUESTS TO RESEND ONE OF OUR OWN MESSAGES OR TURNS if ((pkt->header.bytes == sizeof(HEADER)) || (pkt->data[0] == (s_game_playerid & 0xFF))) if (pkt->header.type == TYPE_TURN) { currmsg = local->incomingqueue[TYPE_TURN].Head(); while (currmsg && (currmsg->data->header.sequence != pkt->header.sequence)) currmsg = currmsg->Next(); if (!currmsg) { currmsg = local->oldturns.Head(); while (currmsg && (currmsg->data->header.sequence != pkt->header.sequence)) currmsg = currmsg->Next(); } } else { currmsg = conn->outgoingqueue[pkt->header.type].Head(); while (currmsg && (currmsg->data->header.sequence != pkt->header.sequence)) currmsg = currmsg->Next(); } // PROCESS REQUESTS TO FORWARD A TURN FROM ANOTHER PLAYER else if (pkt->header.type == TYPE_TURN) { CONNPTR findconn = ConnFindByPlayerId(pkt->data[0]); if (findconn) { currmsg = findconn->incomingqueue[TYPE_TURN].Head(); while (currmsg && (currmsg->data->header.sequence != pkt->header.sequence)) currmsg = currmsg->Next(); if (!currmsg) { currmsg = findconn->oldturns.Head(); while (currmsg && (currmsg->data->header.sequence != pkt->header.sequence)) currmsg = currmsg->Next(); } } if (currmsg) { currmsg->data->header.checksum = 0; currmsg->data->header.flags |= MF_FORWARDED; currmsg->data->header.checksum = PktGenerateChecksum(currmsg->data); } } // IF WE FOUND THE REQUESTED DATA, RESEND IT if (currmsg) ConnResendMessage(conn,currmsg->data,currmsg->databytes); TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [resend request]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // IF THIS MESSAGE HAS ALREADY BEEN PROCESSED, IGNORE IT AND MARK THIS // CONNECTION AS REQUIRING ANOTHER EXPLICIT ACK if (((WORD)(conn->incomingsequence[pkt->header.type]-pkt->header.sequence) > 0) && ((WORD)(conn->incomingsequence[pkt->header.type]-pkt->header.sequence) < 0x7FFF)) { if (pkt->header.type != TYPE_TURN) { conn->acksequence[pkt->header.type] = conn->availablesequence[pkt->header.type]-1; conn->acktime[pkt->header.type] = GetTickCount(); } TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [already processed]", addr,data,databytes); s_spi->Free(addr,data,databytes); continue; } // CREATE A MESSAGE RECORD FOR THIS PACKET MESSAGEPTR message = conn->incomingqueue[pkt->header.type].NewNode(LIST_UNLINKED); message->addr = addr; message->data = (PACKETPTR)data; message->databytes = databytes; message->sendtime = GetTickCount(); message->resendtime = message->sendtime+s_spi_timetorequest-s_spi_timetoresend; PerfAdd(SNET_PERFID_USERBYTESRECV,databytes-sizeof(HEADER)); // ADD THE MESSAGE TO THE APPROPRIATE INCOMING QUEUE IN SEQUENCE ORDER { MESSAGEPTR curr = conn->incomingqueue[pkt->header.type].Head(); while (curr && ((WORD)(pkt->header.sequence-curr->data->header.sequence) > 0) && ((WORD)(pkt->header.sequence-curr->data->header.sequence) < 0x7FFF)) curr = curr->Next(); // IF WE FOUND ANOTHER PACKET IN THE QUEUE WITH THE SAME SEQUENCE // NUMBER, FREE THIS PACKET if (curr && (pkt->header.sequence == curr->data->header.sequence)) { TRACEOUT(TRACEHANDLE, " spiFree(0x%08x,0x%08x,%u) [already received]", addr,data,databytes); conn->incomingqueue[pkt->header.type].DeleteNode(message); s_spi->Free(addr,data,databytes); continue; } // OTHERWISE, LINK THIS PACKET INTO THE QUEUE else conn->incomingqueue[pkt->header.type].LinkNode(message,LIST_LINK_BEFORE,curr); } // UPDATE THE AVAILABLE SEQUENCE NUMBER FOR THIS MESSAGE TYPE. // IF THE AVAILABLE SEQUENCE NUMBER HAS CHANGED AND WE ARE NOT CURRENTLY // SET TO SEND AN EXPLICIT ACKNOWLEDGEMENT, SET THE EXPLICIT ACK TIMER. if (pkt->header.type != TYPE_TURN) { WORD oldavailablesequence = conn->availablesequence[pkt->header.type]; conn->availablesequence[pkt->header.type] = conn->incomingsequence[pkt->header.type]; MESSAGEPTR curr = conn->incomingqueue[pkt->header.type].Head(); while (curr && (curr->data->header.sequence == conn->availablesequence[pkt->header.type])) { conn->availablesequence[pkt->header.type]++; curr = curr->Next(); } if ((conn->availablesequence[pkt->header.type] != oldavailablesequence) && (!conn->acktime[pkt->header.type])) conn->acktime[pkt->header.type] = GetTickCount(); } else { // FOR TURNS, WE DON'T ACKNOWLEDGE A TURN FROM ONE CONNECTION UNTIL // WE HAVE RECEIVED IT FROM ALL CONNECTIONS. THAT WAY, OTHER COMPUTERS // KEEP OLD TURNS AROUND SO THAT THEY CAN RESEND THEM ON BEHALF OF // UNRESPONSIVE SYSTEMS. WORD curravailablesequence = conn->availablesequence[pkt->header.type]; { BOOL receivedany; BOOL receivedall; do { receivedany = FALSE; receivedall = TRUE; CONNPTR currconn = s_conn_connlist.Head(); while (currconn) { if ((currconn->playerid != NOPLAYER) && ((!(currconn->flags & PF_JOINING)) || ((LONG)(curravailablesequence-currconn->incomingsequence[TYPE_TURN]) >= 0))) { MESSAGEPTR currmsg = currconn->incomingqueue[TYPE_TURN].Head(); while (currmsg && (currmsg->data->header.sequence != curravailablesequence)) currmsg = currmsg->Next(); if (currmsg) receivedany = TRUE; else receivedall = FALSE; } currconn = currconn->Next(); } if (receivedany && receivedall) ++curravailablesequence; } while (receivedany && receivedall); } // IF THE AVAILABLE SEQUENCE FOR TURNS HAS CHANGED, UPDATE ALL // CONNECTIONS AND MARK EACH AS REQUIRING AN EXPLICIT ACKNOWLEDGEMENT. { CONNPTR currconn = s_conn_connlist.Head(); while (currconn) { if ((curravailablesequence != currconn->availablesequence[TYPE_TURN]) && (currconn->playerid != NOPLAYER) && ((!(currconn->flags & PF_JOINING)) || ((LONG)(curravailablesequence-currconn->incomingsequence[TYPE_TURN]) >= 0))) { currconn->availablesequence[TYPE_TURN] = curravailablesequence; if (!currconn->acktime[TYPE_TURN]) currconn->acktime[TYPE_TURN] = GetTickCount(); } currconn = currconn->Next(); } } } // IF THIS IS A SYSTEM MESSAGE, PROCESS SYSTEM MESSAGES IN ORDER // FOR THIS CONNECTION if (pkt->header.type == TYPE_SYSTEM) SysProcessIncomingMessages(conn); } } //=========================================================================== static DWORD CALLBACK RecvThreadProc (LPVOID param) { DWORD wait = INFINITE; for (;;) { // WAIT FOR AT LEAST ONE PACKET TO BE RECEIVED AND QUEUED if (s_recv_shutdown) { _endthreadex(0); return 0; } BOOL msgwaiting = (WaitForSingleObject(s_recv_event,wait) == WAIT_OBJECT_0); if (s_recv_shutdown) { _endthreadex(0); return 0; } // ENTER THE API LOCK s_api_critsect.Enter(); if (!s_spi) { s_api_critsect.Leave(); _endthreadex(0); return 0; } // PROCESS ALL OUTSTANDING RECEIVED PACKETS if (msgwaiting) { TRACEOUT(TRACEHANDLE,"Background thread processing incoming packets:"); RecvProcessExternalMessages(); RecvProcessIncomingPackets(); } else TRACEOUT(TRACEHANDLE,"Background thread maintaining connections:"); // MAINTAIN CONNECTIONS wait = ConnMaintainConnections(); // LEAVE THE API LOCK s_api_critsect.Leave(); TRACEOUT(TRACEHANDLE," done"); } } /**************************************************************************** * * USER INTERFACE FUNCTIONS * ***/ //=========================================================================== static PROVIDERINFOPTR UiFindProvider (LPCSTR desc) { PROVIDERINFOPTR curr = s_spi_providerlist.Head(); while (curr) if (!strcmp(curr->desc,desc)) return curr; else curr = curr->Next(); return NULL; } //=========================================================================== static void UiGetProgramDescription (LPCSTR programname, SNETVERSIONDATAPTR versionptr, LPSTR buffer, DWORD buffersize) { // ADD THE NAME OF THE PROGRAM SStrCopy(buffer,programname,buffersize-2); strcat(buffer," "); // DETERMINE THE NAME OF THIS PROGRAM'S EXE FILE char programfilename[MAX_PATH] = ""; GetModuleFileName((HMODULE)0,programfilename,MAX_PATH); // IF WE WERE GIVEN A HUMAN READABLE VERSION STRING, ADD THAT if (versionptr && versionptr->versionstring && *versionptr->versionstring) SStrPack(buffer,versionptr->versionstring,buffersize); // OTHERWISE, ADD THE HUMAN READABLE VERSION STRING FROM THE EXE FILE'S // VERSION RESOURCE else if (programfilename[0]) { DWORD handle; DWORD versioninfosize = GetFileVersionInfoSize((char *)programfilename,&handle); LPVOID versioninfo = ALLOC(versioninfosize); if (GetFileVersionInfo((char *)programfilename,handle,versioninfosize,versioninfo)) { LPCTSTR info = NULL; UINT bytes = 0; if (VerQueryValueA(versioninfo, "\\StringFileInfo\\040904b0\\ProductVersion", (LPVOID *)&info, &bytes) && info && *info) SStrPack(buffer,info,buffersize); } FREE(versioninfo); } } //=========================================================================== static BOOL UiLoadArtwork (SNETGETARTPROC artcallback, DWORD providerid, DWORD artid, BOOL setpalette, LPBYTE *data, SIZE *size) { *data = 0; size->cx = 0; size->cy = 0; // VERIFY THAT THE APPLICATION HAS REGISTERED AN ARTWORK CALLBACK if (!artcallback) return FALSE; // CALL THE ARTWORK CALLBACK TO DETERMINE THE IMAGE DIMENSIONS int width; int height; int bitdepth; if (!artcallback(providerid, artid, NULL, NULL, 0, &width, &height, &bitdepth)) return FALSE; if (size) { size->cx = width; size->cy = height; } // ALLOCATE MEMORY FOR THE IMAGE DWORD bytes = width*height*bitdepth/8; *data = (LPBYTE)ALLOC(bytes); // LOAD THE IMAGE PALETTEENTRY pe[256]; if (!artcallback(providerid, artid, &pe[0], *data, bytes, &width, &height, &bitdepth)) { FREE(*data); *data = NULL; return FALSE; } // IF REQUESTED, UPDATE THE SYSTEM PALETTE if (setpalette) SDrawUpdatePalette(1,254,&pe[1]); return TRUE; } //=========================================================================== static BOOL CALLBACK UiSelectProviderDialogProc (HWND window, UINT message, WPARAM wparam, LPARAM lparam) { static LPBYTE background = NULL; static LPBYTE buttontexture = NULL; static UIPARAMSPTR uiparams = NULL; switch (message) { case WM_COMMAND: // IF THE USER CLICKED THE CANCEL BUTTON, RETURN ZERO if (LOWORD(wparam) == IDCANCEL) SDlgEndDialog(window,0); // IF THE USER CLICKED THE CONNECT BUTTON, INITIALIZE THE SELECTED // PROVIDER AND RETURNS ITS PROVIDER ID else if (LOWORD(wparam) == IDOK) { char buffer[256] = ""; int cursel = SendDlgItemMessage(window,IDC_PROVIDERLIST,LB_GETCURSEL,0,0); SendDlgItemMessageA(window,IDC_PROVIDERLIST,LB_GETTEXT,cursel,(LPARAM)(LPSTR)buffer); PROVIDERINFOPTR ptr = UiFindProvider(buffer); if (ptr) { // SAVE THIS AS THE NEW PREFERRED PROVIDER SRegSaveValue("Network Providers","Preferred Provider",0,ptr->id); // BUILD A NEW INTERFACE DATA STRUCTURE CONTAINING OUR WINDOW // HANDLE SNETUIDATA interfacedata; ZeroMemory(&interfacedata,sizeof(SNETUIDATA)); if (uiparams->interfacedata) CopyMemory(&interfacedata,uiparams->interfacedata,sizeof(SNETUIDATA)); interfacedata.size = sizeof(SNETUIDATA); interfacedata.parentwindow = window; // CALL THE SELECTED CALLBACK IF AVAILABLE if (uiparams->interfacedata && uiparams->interfacedata->selectedcallback) if (!uiparams->interfacedata->selectedcallback(ptr->id, &ptr->caps, &interfacedata, uiparams->versiondata)) break; // INITIALIZE THE PROVIDER if (SNetInitializeProvider(ptr->id, uiparams->programdata, uiparams->playerdata, &interfacedata, uiparams->versiondata)) SDlgEndDialog(window,ptr->id); } } // IF THE USER HIGHLIGHTED A NEW LISTBOX ITEM, UPDATE // THE STATIC TEXT. IF THE USER DOUBLE-CLICKED AN ITEM, // POST AN 'OK' COMMAND. else if (LOWORD(wparam) == IDC_PROVIDERLIST) if (HIWORD(wparam) == LBN_SELCHANGE) PostMessage(window,WM_USER,0,0); else if (HIWORD(wparam) == LBN_DBLCLK) PostMessage(window,WM_COMMAND,MAKELONG(IDOK,BN_CLICKED),(LPARAM)GetDlgItem(window,IDOK)); break; case WM_DESTROY: if (background) { FREE(background); background = NULL; } if (buttontexture) { FREE(buttontexture); buttontexture = NULL; } break; case WM_INITDIALOG: // SAVE A POINTER TO THE USER INTERFACE PARAMETERS uiparams = (UIPARAMSPTR)lparam; // LOAD THE ARTWORK FOR THIS DIALOG { SIZE size; if (UiLoadArtwork(uiparams->interfacedata->artcallback, 0, SNET_ART_BACKGROUND, 1, &background, &size)) { SDlgSetBitmap(window, NULL, "", SDLG_STYLE_ANY, SDLG_USAGE_BACKGROUND, background, NULL, size.cx, size.cy); int controllist[3] = {IDC_MAXPLAYERS,IDC_REQUIREMENTS,0}; SDlgSetControlBitmaps(window, &controllist[0], NULL, background, &size, SDLG_ADJUST_CONTROLPOS); } if (UiLoadArtwork(uiparams->interfacedata->artcallback, 0, SNET_ART_BUTTONTEXTURE, 0, &buttontexture, &size)) { int controllist[3] = {IDOK,IDCANCEL,0}; SDlgSetControlBitmaps(window, &controllist[0], NULL, buttontexture, &size, SDLG_ADJUST_VERTICAL); } } // FILL IN THE LIST BOX WITH THE LIST OF PROVIDERS { DWORD selectid = 0; PROVIDERINFOPTR selectptr = NULL; PROVIDERINFOPTR curr = s_spi_providerlist.Head(); SRegLoadValue("Network Providers","Preferred Provider",0,&selectid); while (curr) { if (curr->id && SpiMeetsMinimumCaps(&curr->caps,uiparams->mincaps)) { SendDlgItemMessageA(window,IDC_PROVIDERLIST,LB_ADDSTRING,0,(LPARAM)(LPCSTR)curr->desc); if (curr->id == selectid) selectptr = curr; } curr = curr->Next(); } WPARAM selectindex = 0; if (selectptr) selectindex = (WPARAM)SendDlgItemMessage(window,IDC_PROVIDERLIST,LB_FINDSTRINGEXACT,(WPARAM)-1,(LPARAM)selectptr->desc); SendDlgItemMessage(window,IDC_PROVIDERLIST,LB_SETCURSEL,selectindex,0); } // FILL IN THE PROGRAM DESCRIPTION { char buffer[256] = ""; UiGetProgramDescription(uiparams->programdata->programname, uiparams->versiondata, buffer, 256); SetDlgItemTextA(window,IDC_PROGRAMDESCRIPTION,buffer); } // UPDATE THE PLAYERS SUPPORTED AND REQUIREMENTS TEXT PostMessage(window,WM_USER,0,0); return 1; case WM_USER: { // FIND THE HIGHLIGHTED PROVIDER char buffer[256] = ""; int cursel = SendDlgItemMessage(window,IDC_PROVIDERLIST,LB_GETCURSEL,0,0); SendDlgItemMessageA(window,IDC_PROVIDERLIST,LB_GETTEXT,cursel,(LPARAM)(LPSTR)buffer); PROVIDERINFOPTR ptr = UiFindProvider(buffer); if (!ptr) break; // DISPLAY THE MAXIMUM NUMBER OF PLAYERS { char buffer[64]; GetDlgItemTextA(window,IDC_MAXPLAYERS,buffer,63); if (SStrChr(buffer,':')) wsprintf(SStrChr(buffer,':')+1, " %u", min(uiparams->programdata->maxplayers,ptr->caps.maxplayers)); SetDlgItemTextA(window,IDC_MAXPLAYERS,buffer); } // DISPLAY THE REQUIREMENTS { char buffer[256]; GetDlgItemTextA(window,IDC_REQUIREMENTS,buffer,255); if (SStrChr(buffer,':')) wsprintf(SStrChr(buffer,':')+1, "\n%s", ptr->req); SetDlgItemTextA(window,IDC_REQUIREMENTS,buffer); } } break; } return SDlgDefDialogProc(window,message,wparam,lparam); } /**************************************************************************** * * EXPORTED FUNCTIONS * ***/ #define ENTER_APILOCK_READ s_api_critsect.Enter() #define ENTER_APILOCK_WRITE s_api_critsect.Enter() #define FAILOUT_APILOCK_READ do { \ TRACEOUT(TRACEHANDLE, \ " [fail: %08x]", \ SErrGetLastError()); \ s_api_critsect.Leave(); \ return FALSE; \ } while (FALSE) #define FAILOUT_APILOCK_WRITE do { \ TRACEOUT(TRACEHANDLE, \ " [fail: %08x]", \ SErrGetLastError()); \ s_api_critsect.Leave(); \ return FALSE; \ } while (FALSE) #define LEAVE_APILOCK_READ s_api_critsect.Leave() #define LEAVE_APILOCK_WRITE s_api_critsect.Leave() //=========================================================================== BOOL APIENTRY SNetCreateGame (LPCSTR gamename, LPCSTR gamepassword, LPCSTR gamedescription, DWORD gamecategorybits, LPVOID initdata, DWORD initdatabytes, DWORD maxplayers, LPCSTR playername, LPCSTR playerdescription, DWORD *playerid) { // VALIDATE PARAMETERS if (playerid) *playerid = SNET_INVALIDPLAYERID+s_api_playeroffset; VALIDATEBEGIN; VALIDATE(gamename); VALIDATE(*gamename); VALIDATE(maxplayers); VALIDATE(playerid); VALIDATEEND; // LOG THE CALL ENTER_APILOCK_WRITE; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (!gamepassword) gamepassword = ""; if (!gamedescription) gamedescription = ""; if (!playername) playername = ""; if (!playerdescription) playerdescription = ""; TRACEOUT(TRACEHANDLE, "SNetCreateGame(\"%s\",\"%s\",\"%s\",0x%08x,0x%08x,%u,%u,\"%s\",\"%s\",*playerid)", gamename,gamepassword,gamedescription,gamecategorybits,initdata,initdatabytes,maxplayers,playername,playerdescription); // IF NO PLAYER NAME WAS PROVIDED, AND THE CURRENT NETWORK PROVIDER // IS CAPABLE OF PROVIDING IT, THEN GET THE NAME OF THE LOGGED ON // USER FROM THE NETWORK PROVIDER char localplayername[SNETSPI_MAXSTRINGLENGTH] = ""; char localplayerdesc[SNETSPI_MAXSTRINGLENGTH] = ""; if ((!*playername) && s_spi->GetLocalPlayerName) { s_spi->GetLocalPlayerName(localplayername, SNETSPI_MAXSTRINGLENGTH, localplayerdesc, SNETSPI_MAXSTRINGLENGTH); playername = localplayername; playerdescription = localplayerdesc; } if (!*playername) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); return FALSE; } // IF WE ARE ALREADY IN A GAME, LEAVE IT if (s_game_playerid != NOPLAYER) SNetLeaveGame(SNET_EXIT_AUTO_NEWGAME); // RESET THE CONNECTION TABLE ConnDestroy(); // SET THE MAXIMUM NUMBER OF PLAYERS ALLOWED s_game_playersallowed = maxplayers; // SAVE THE GAME NAME AND DESCRIPTION SStrCopy(s_game_gamename,gamename,SNETSPI_MAXSTRINGLENGTH); SStrCopy(s_game_gamedesc,gamedescription,SNETSPI_MAXSTRINGLENGTH); SStrCopy(s_game_gamepass,gamepassword,SNETSPI_MAXSTRINGLENGTH); s_game_creationtime = GetTickCount(); s_game_categorybits = gamecategorybits; // SET THE GAME MODE s_game_gamemode = 0; if (*gamepassword) s_game_gamemode |= SNET_GM_PRIVATE; if (s_game_playersallowed <= 1) s_game_gamemode |= SNET_GM_FULL; // SAVE THE INITIALIZATION DATA if (s_game_initdata) FREE(s_game_initdata); s_game_initdata = ALLOC(initdatabytes); s_game_initdatabytes = initdatabytes; CopyMemory(s_game_initdata,initdata,initdatabytes); // ASSIGN THE LOCAL PLAYER A PLAYER ID, AND MARK HIM AS THE GAME OWNER s_game_playerid = 0; CONNPTR conn = ConnFindLocal(); if (conn) { conn->flags = 0; conn->playerid = s_game_playerid; conn->gameowner = TRUE; SStrCopy(conn->name,playername ,SNETSPI_MAXSTRINGLENGTH); SStrCopy(conn->desc,playerdescription,SNETSPI_MAXSTRINGLENGTH); } if (playerid) *playerid = s_game_playerid+s_api_playeroffset; // BUILD THE CLIENT DATA BLOCK BYTE clientdata[SNETSPI_MAXCLIENTDATA]; DWORD clientdatabytes; GameBuildClientData(clientdata, &clientdatabytes); // START ADVERTISING THE GAME SO THAT OTHERS CAN JOIN IT TRACEOUT(TRACEHANDLE, " spiStartAdvertisingGame(\"%s\",\"%s\",\"%s\",%u,%u,0x%08x,0x%08x,0x%08x,%u)", s_game_gamename, s_game_gamepass, s_game_gamedesc, s_game_gamemode, 0, s_game_categorybits, s_game_optcategorybits, clientdata, clientdatabytes); if (!s_spi->StartAdvertisingGame(s_game_gamename, s_game_gamepass, s_game_gamedesc, s_game_gamemode, 0, s_game_categorybits, s_game_optcategorybits, clientdata, clientdatabytes)) { // SERVICE PROVIDER IS RESPONSIBLE FOR CALLING SETLASTERROR() DWORD lasterror = SErrGetLastError(); SNetLeaveGame(SNET_EXIT_AUTO_JOINING); SErrSetLastError(lasterror); FAILOUT_APILOCK_WRITE; } // MARK THE GAME AS ADVERTISED s_game_gamemode |= SNET_GM_ADVERTISED; LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetDestroy () { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE,"SNetDestroy()"); // IF WE ARE IN A GAME, LEAVE IT if (s_game_playerid != NOPLAYER) SNetLeaveGame(SNET_EXIT_AUTO_SHUTDOWN); // TERMINATE THE RECEIVE THREAD LEAVE_APILOCK_WRITE; RecvDestroy(); ENTER_APILOCK_WRITE; // REMOVE ALL MESSAGES FROM THE SYSTEM MESSAGE QUEUE SysDestroy(); // UNREGISTER ALL EVENT HANDLERS SEvtUnregisterType(REGISTERTYPE,REGISTERSUBTYPE_SNETEVENT); SEvtUnregisterType(REGISTERTYPE,REGISTERSUBTYPE_SYSEVENT); // DESTROY ALL CONNECTIONS ConnDestroy(); // UNBIND FROM THE SERVICE PROVIDER AND CLEAR THE PROVIDER LIST SpiDestroy(TRUE); LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetDropPlayer (DWORD playerid, DWORD exitcode) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetDropPlayer(%u,0x%08x)", playerid,exitcode); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if ((playerid == NOPLAYER) || (playerid == s_game_playerid)) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // FIND THE REFERENCED PLAYER CONNPTR conn = ConnFindByPlayerId(playerid); if (!conn) { SErrSetLastError(SNET_ERROR_INVALID_PLAYER); FAILOUT_APILOCK_WRITE; } // MARK THE PLAYER AS LEAVING conn->flags |= PF_LEAVING; conn->finalsequence = conn->incomingsequence[TYPE_TURN]; conn->exitcode = exitcode; // SEND A DROP PLAYER MESSAGE TO ALL OTHER PLAYERS { CONNPTR checkconn = s_conn_connlist.Head(); while (checkconn) { if ((checkconn != conn) && (checkconn->playerid != NOPLAYER)) { SYSEVENTDATA_DROPPLAYER eventdata; eventdata.playerid = playerid; eventdata.finalsequence = conn->incomingsequence[TYPE_TURN]; eventdata.exitcode = exitcode; ConnSendMessage(checkconn, TYPE_SYSTEM, SYS_DROPPLAYER, &eventdata, sizeof(SYSEVENTDATA_DROPPLAYER)); } checkconn = checkconn->Next(); } } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetEnumDevices (SNETENUMDEVICESPROC callback) { VALIDATEBEGIN; VALIDATE(callback); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetEnumDevices(0x%08x)", callback); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } // MAKE A LOCAL COPY OF ALL DEVICES SNETSPI_DEVICELISTPTR devicearray = NULL; DWORD devices = 0; { // LOCK THE DEVICE LIST SNETSPI_DEVICELISTPTR head; TRACEOUT(TRACEHANDLE," spiLockDeviceList(*devicelist)"); if (!s_spi->LockDeviceList(&head)) { // SERVICE PROVIDER IS RESPONSIBLE FOR CALLING SETLASTERROR() FAILOUT_APILOCK_READ; } // DETERMINE THE TOTAL NUMBER OF DEVICES { SNETSPI_DEVICELISTPTR curr = head; while (curr) { ++devices; curr = curr->next; } } // ALLOCATE AN ARRAY TO HOLD THE DEVICES if (devices) { devicearray = (SNETSPI_DEVICELISTPTR)ALLOC(devices*sizeof(SNETSPI_DEVICELIST)); // COPY THE DEVICES SNETSPI_DEVICELISTPTR source = head; SNETSPI_DEVICELISTPTR dest = devicearray; while (source) { CopyMemory(dest,source,sizeof(SNETSPI_DEVICELIST)); dest->next = dest+1; dest = dest->next; source = source->next; } } // UNLOCK THE DEVICE LIST TRACEOUT(TRACEHANDLE, " spiUnlockDeviceList(0x%08x)", head); s_spi->UnlockDeviceList(head); } // LEAVE THE API LOCK LEAVE_APILOCK_READ; // IF THERE WERE NO DEVICES, RETURN NOW if (!(devices && devicearray)) return TRUE; // CALL THE ENUMERATION FUNCTION ONCE FOR EACH DEVICE { SNETSPI_DEVICELISTPTR curr = devicearray; while (devices--) { TRACEOUT(TRACEHANDLE, " callback(0x%08x,\"%s\",\"%s\")", curr->deviceid,curr->devicename,curr->devicedescription); if (callback(curr->deviceid,curr->devicename,curr->devicedescription)) curr = curr->next; else devices = 0; } } // FREE THE LOCAL COPY FREE(devicearray); return TRUE; } //=========================================================================== BOOL APIENTRY SNetEnumGames (DWORD categorybits, DWORD categorymask, SNETENUMGAMESPROC callback, DWORD *hintnextcall) { VALIDATEBEGIN; VALIDATE(callback); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetEnumGames(0x%08x,0x%08x,0x%08x,*hintnextcall)", categorybits,categorymask,callback); // VALIDATE PARAMETERS if (hintnextcall) *hintnextcall = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } // MAKE A LOCAL COPY OF ALL MATCHING GAMES SNETSPI_GAMELISTPTR gamearray = NULL; DWORD games = 0; GameCopyGameList(categorybits, categorymask, &gamearray, &games, hintnextcall); // LEAVE THE API LOCK LEAVE_APILOCK_READ; // CALL THE ENUMERATION FUNCTION ONCE FOR EACH GAME { SNETSPI_GAMELISTPTR curr = gamearray; while (games--) { TRACEOUT(TRACEHANDLE, " callback(0x%08x,\"%s\",\"%s\")", curr->gameid,curr->gamename,curr->gamedescription); TRACEDUMP(TRACEHANDLE,&curr->owner,sizeof(SNETADDR)); if (callback(curr->gameid,curr->gamename,curr->gamedescription)) curr = curr->next; else games = 0; } } // FREE THE LOCAL COPY FREEIFUSED(gamearray); return TRUE; } //=========================================================================== BOOL APIENTRY SNetEnumGamesEx (DWORD categorybits, DWORD categorymask, SNETENUMGAMESEXPROC callback, DWORD *hintnextcall) { VALIDATEBEGIN; VALIDATE(callback); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetEnumGamesEx(0x%08x,0x%08x,0x%08x,*hintnextcall)", categorybits,categorymask,callback); // VALIDATE PARAMETERS if (hintnextcall) *hintnextcall = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } // MAKE A LOCAL COPY OF ALL MATCHING GAMES SNETSPI_GAMELISTPTR gamearray = NULL; DWORD games = 0; GameCopyGameList(categorybits, categorymask, &gamearray, &games, hintnextcall); // LEAVE THE API LOCK LEAVE_APILOCK_READ; // CALL THE ENUMERATION FUNCTION ONCE FOR EACH GAME { SNETSPI_GAMELISTPTR curr = gamearray; SNETGAME gamedata; while (games--) { GameBuildGameData(&gamedata,curr); TRACEOUT(TRACEHANDLE, " callback(0x%08x)", &gamedata); TRACEDUMP(TRACEHANDLE,&curr->owner,sizeof(SNETADDR)); if (callback(&gamedata)) curr = curr->next; else games = 0; } } // FREE THE LOCAL COPY FREEIFUSED(gamearray); return TRUE; } //=========================================================================== BOOL APIENTRY SNetEnumProviders (SNETCAPSPTR mincaps, SNETENUMPROVIDERSPROC callback) { VALIDATEBEGIN; VALIDATE(callback); VALIDATE((!mincaps) || (mincaps->size == sizeof(SNETCAPS))); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetEnumProviders(mincaps,0x%08x)", callback); // BUILD A LIST OF PROVIDERS IF WE DON'T ALREADY HAVE ONE SpiFindAllProviders(); // CALL THE CALLBACK FUNCTION ONCE FOR EACH PROVIDER IN THE LIST // WHICH MEETS THE MINIMUM CAPABILITIES PROVIDERINFOPTR curr = s_spi_providerlist.Head(); while (curr) { if (curr->id && curr->desc && *curr->desc && SpiMeetsMinimumCaps(&curr->caps,mincaps)) { TRACEOUT(TRACEHANDLE, " callback(0x%08x,\"%s\",\"%s\",caps)", curr->id,curr->desc,curr->req); if (!callback(curr->id,curr->desc,curr->req,&curr->caps)) curr = NULL; } if (s_spi_providersfound && (!s_spi_providerlist.IsEmpty()) && curr) curr = curr->Next(); else curr = NULL; } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetGameInfo (DWORD index, LPVOID buffer, DWORD buffersize, DWORD *byteswritten) { ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetGameInfo(%u,0x%08x,%u,*byteswritten)", index,buffer,buffersize); // VALIDATE PARAMETERS if (buffer && buffersize) ZeroMemory(buffer,buffersize); if (byteswritten) *byteswritten = NULL; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_READ; } // FIND THE REQUESTED INFORMATION LPVOID info = NULL; DWORD infobytes = 0; BOOL nullterm = FALSE; switch (index) { case SNET_INFO_GAMENAME: info = s_game_gamename; infobytes = SStrLen(s_game_gamename)+1; nullterm = TRUE; break; case SNET_INFO_GAMEPASSWORD: info = s_game_gamepass; infobytes = SStrLen(s_game_gamepass)+1; nullterm = TRUE; break; case SNET_INFO_GAMEDESCRIPTION: info = s_game_gamedesc; infobytes = SStrLen(s_game_gamedesc)+1; nullterm = TRUE; break; case SNET_INFO_GAMEMODE: info = &s_game_gamemode; infobytes = sizeof(s_game_gamemode); break; case SNET_INFO_INITDATA: info = s_game_initdata; infobytes = s_game_initdatabytes; break; case SNET_INFO_MAXPLAYERS: info = &s_game_playersallowed; infobytes = sizeof(s_game_playersallowed); break; } if (!info) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_READ; } // IF THE USER DIDN'T PASS A BUFFER, SIMPLY RETURN THE NUMBER OF BYTES // OF INFORMATION WE HAVE AVAILABLE if (!(buffer && buffersize)) { if (byteswritten) *byteswritten = infobytes; } // OTHERWISE, COPY THE INFORMATION INTO THE USER'S BUFFER else { CopyMemory(buffer,info,min(buffersize,infobytes)); if (byteswritten) *byteswritten = min(buffersize,infobytes); if (nullterm) *((LPSTR)buffer+buffersize-1) = 0; } LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetNetworkLatency (DWORD measurementtype, DWORD *result) { VALIDATEBEGIN; VALIDATE(result); VALIDATE(measurementtype >= SNET_LMT_EXPECTED); VALIDATE(measurementtype <= SNET_LMT_PEAK); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetNetworkLatency(%u,*result)", measurementtype); // VALIDATE PARAMETERS if (result) *result = 0; if (!(s_spi && s_spi_providerptr)) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } // RETURN THE LATENCY switch (measurementtype) { case SNET_LMT_EXPECTED: *result = s_spi_providerptr->caps.latencyms; break; case SNET_LMT_CURRENT: case SNET_LMT_PEAK: { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if (conn->playerid != NOPLAYER) { DWORD latency = 0; if (measurementtype == SNET_LMT_CURRENT) latency = conn->peaklatency; if (latency < conn->latency) latency = conn->latency; if (latency > *result) *result = latency; } conn = conn->Next(); } } break; default: SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_READ; } LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetNumPlayers (DWORD *firstplayerid, DWORD *lastplayerid, DWORD *activeplayers) { ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetNumPlayers(*firstplayerid,*lastplayerid,*activeplayers)"); // VALIDATE PARAMETERS if (firstplayerid) *firstplayerid = SNET_INVALIDPLAYERID+s_api_playeroffset; if (lastplayerid) *lastplayerid = SNET_INVALIDPLAYERID+s_api_playeroffset; if (activeplayers) *activeplayers = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_READ; } // START WITH THE LOCAL PLAYER if (firstplayerid) *firstplayerid = s_game_playerid+s_api_playeroffset; if (lastplayerid) *lastplayerid = s_game_playerid+s_api_playeroffset; if (activeplayers) *activeplayers = 1; // TAKE INTO ACCOUNT ALL OTHER ACTIVE PLAYERS { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if (conn->playerid != NOPLAYER) { if (firstplayerid) *firstplayerid = min(conn->playerid+s_api_playeroffset,*firstplayerid); if (lastplayerid) *lastplayerid = max(conn->playerid+s_api_playeroffset,*lastplayerid); if (activeplayers) ++*activeplayers; } conn = conn->Next(); } } // LOG THE RETURNED INFORMATION if (firstplayerid) TRACEOUT(TRACEHANDLE," firstplayerid=%u",*firstplayerid); if (lastplayerid) TRACEOUT(TRACEHANDLE," lastplayerid=%u",*lastplayerid); if (activeplayers) TRACEOUT(TRACEHANDLE," activeplayers=%u",*activeplayers); LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetOwnerId (DWORD *playerid) { VALIDATEBEGIN; VALIDATE(playerid); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEPEND(TRACEHANDLE,"SNetGetOwnerId(*playerid)"); // VALIDATE PARAMETERS *playerid = SNET_INVALIDPLAYERID+s_api_playeroffset; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // CHECK FOR TRANSFER OF OWNERSHIP if (s_game_playerid != NOPLAYER) GameProcessLeavingPlayers(); // FIND THE GAME OWNER CONNPTR local = ConnFindLocal(); if (!local) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } CONNPTR conn = local; if (!(conn && conn->gameowner)) { conn = s_conn_connlist.Head(); while (conn && !conn->gameowner) conn = conn->Next(); } if (!(conn && conn->gameowner)) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // RETURN THE GAME OWNER'S PLAYER ID *playerid = conn->playerid+s_api_playeroffset; LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetOwnerTurnsWaiting (DWORD *turns) { VALIDATEBEGIN; VALIDATE(turns); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEPEND(TRACEHANDLE,"SNetGetOwnerTurnsWaiting(*turns)"); *turns = 0; // GET THE PLAYER ID OF THE GAME OWNER DWORD playerid; if (!SNetGetOwnerId(&playerid)) { // SNETGETOWNERID() IS RESPONSIBLE FOR CALLING SETLASTERROR() FAILOUT_APILOCK_WRITE; } playerid -= s_api_playeroffset; // FIND THE CONNECTION RECORD ASSOCIATED WITH THE GAME OWNER CONNPTR conn = ConnFindByPlayerId(playerid); if (!(conn && conn->gameowner)) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // CLEAR OUT ANY UNNEEDED OR ALREADY PROCESSED TURNS FROM THE GAME OWNER ConnClearOldTurns(conn); // DETERMINE THE NUMBER OF TURNS WE STILL HAVE QUEUED FROM THE GAME OWNER MESSAGEPTR message = conn->incomingqueue[TYPE_TURN].Head(); while (message) { ++*turns; message = message->Next(); } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetPerformanceData (DWORD counterid, DWORD *countervalue, DWORD *countertype, LONG *counterscale, LARGE_INTEGER *measurementtime, LARGE_INTEGER *measurementfreq) { VALIDATEBEGIN; VALIDATE(counterid); VALIDATE(counterid < SNET_PERFIDNUM); VALIDATE(countervalue); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetPerformanceData(%u,*countervalue,*countertype,*counterscale,*measurementtime,*measurementfreq)", counterid); // SET THE VALUES BASED ON OUR INTERNAL PERFORMANCE DATA *countervalue = s_perf_data[counterid].value; if (countertype) *countertype = s_perf_data[counterid].type; if (counterscale) *counterscale = s_perf_data[counterid].scale; if (measurementtime) { SYSTEMTIME systime; GetSystemTime(&systime); SystemTimeToFileTime(&systime,(FILETIME *)measurementtime); } if (measurementfreq) { measurementfreq->LowPart = 10000000; measurementfreq->HighPart = 0; } // IF THIS COUNTER IS PROVIDER SPECIFIC, ALLOW THE CURRENT PROVIDER TO // OVERWRITE OUR RESULTS if (s_perf_data[counterid].providerspecific && s_spi) { LARGE_INTEGER localtime; LARGE_INTEGER localfreq; if (!measurementtime) measurementtime = &localtime; if (!measurementfreq) measurementfreq = &localfreq; TRACEOUT(TRACEHANDLE, " spiGetPerformanceData(%u,*countervalue,*measurementtime,*measurementfreq)", counterid); s_spi->GetPerformanceData(counterid,countervalue,measurementtime,measurementfreq); } LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetPlayerCaps (DWORD playerid, SNETCAPSPTR caps) { VALIDATEBEGIN; VALIDATE(caps); VALIDATE(caps->size == sizeof(SNETCAPS)); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetPlayerCaps(%u,*caps)", playerid); // VALIDATE PARAMETERS ZeroMemory(((LPDWORD)caps)+1,sizeof(SNETCAPS)-sizeof(DWORD)); if (!(s_spi && s_spi_providerptr)) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_READ; } // FIND THE REQUESTED PLAYER CONNPTR conn = ConnFindByPlayerId(playerid-s_api_playeroffset); if (!conn) { SErrSetLastError(SNET_ERROR_INVALID_PLAYER); FAILOUT_APILOCK_READ; } // COPY THE PROVIDER CAPS CopyMemory(caps,&s_spi_providerptr->caps,sizeof(SNETCAPS)); // ADD THE USER'S LATENCY if (conn->latency) caps->latencyms = conn->latency; LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetPlayerName (DWORD playerid, LPSTR buffer, DWORD buffersize) { VALIDATEBEGIN; VALIDATE(buffer); VALIDATE(buffersize); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetPlayerName(%u,0x%08x,%u)", playerid,buffer,buffersize); // VALIDATE PARAMETERS *buffer = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_READ; } // FIND THE REQUESTED PLAYER CONNPTR conn = ConnFindByPlayerId(playerid-s_api_playeroffset); if ((!conn) || (conn->flags & PF_JOINING)) { SErrSetLastError(SNET_ERROR_INVALID_PLAYER); FAILOUT_APILOCK_READ; } // RETURN THE PLAYER'S NAME SStrCopy(buffer,conn->name,buffersize); LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetProviderCaps (SNETCAPSPTR caps) { VALIDATEBEGIN; VALIDATE(caps); VALIDATE(caps->size == sizeof(SNETCAPS)); VALIDATEEND; ENTER_APILOCK_READ; TRACEOUT(TRACEHANDLE, "SNetGetProviderCaps(*caps)"); // VALIDATE PARAMETERS ZeroMemory(((LPDWORD)caps)+1,sizeof(SNETCAPS)-sizeof(DWORD)); if (!(s_spi && s_spi_providerptr)) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } // COPY THE PROVIDER CAPS CopyMemory(caps,&s_spi_providerptr->caps,sizeof(SNETCAPS)); LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetGetTurnsInTransit (DWORD *turns) { VALIDATEBEGIN; VALIDATE(turns); VALIDATEEND; ENTER_APILOCK_READ; TRACEPEND(TRACEHANDLE,"SNetGetTurnsInTransit(*turns)"); // VALIDATE PARAMETERS *turns = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_READ; } // FIND OUR PLAYER RECORD CONNPTR conn = ConnFindLocal(); if (!conn) { SErrSetLastError(SNET_ERROR_NOT_ENOUGH_MEMORY); FAILOUT_APILOCK_READ; } // DETERMINE THE NUMBER OF TURNS WE HAVE SENT OUT BEYOND WHAT WE HAVE // PROCESSED *turns = conn->outgoingsequence[TYPE_TURN] -conn->incomingsequence[TYPE_TURN]; LEAVE_APILOCK_READ; return TRUE; } //=========================================================================== BOOL APIENTRY SNetInitializeDevice (DWORD deviceid, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetInitializeDevice(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", deviceid,programdata,playerdata,interfacedata,versiondata); TRACEDUMPDATABLOCKS(TRACEHANDLE, programdata,playerdata,interfacedata,versiondata); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_READ; } SNETPROGRAMDATA modprogramdata; SNETPLAYERDATA modplayerdata; SNETUIDATA modinterfacedata; SNETVERSIONDATA modversiondata; if (!SpiNormalizeDataBlocks(programdata,playerdata,interfacedata,versiondata, &modprogramdata,&modplayerdata,&modinterfacedata,&modversiondata)) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } // INITIALIZE THE DEVICE if (!s_spi->InitializeDevice(deviceid, &modprogramdata, &modplayerdata, &modinterfacedata, &modversiondata)) { // SERVICE PROVIDER IS RESPONSIBLE FOR CALLING SETLASTERROR() FAILOUT_APILOCK_WRITE; } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetInitializeProvider (DWORD providerid, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "(using Storm build " __DATE__ " " __TIME__ ")"); TRACEOUT(TRACEHANDLE, "SNetInitializeProvider(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", providerid,programdata,playerdata,interfacedata,versiondata); TRACEDUMPDATABLOCKS(TRACEHANDLE, programdata,playerdata,interfacedata,versiondata); // VALIDATE PARAMETERS SNETPROGRAMDATA modprogramdata; SNETPLAYERDATA modplayerdata; SNETUIDATA modinterfacedata; SNETVERSIONDATA modversiondata; if (!SpiNormalizeDataBlocks(programdata,playerdata,interfacedata,versiondata, &modprogramdata,&modplayerdata,&modinterfacedata,&modversiondata)) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } // UNREGISTER ALL EXISTING EVENT HANDLERS SEvtUnregisterType(REGISTERTYPE,REGISTERSUBTYPE_SNETEVENT); SEvtUnregisterType(REGISTERTYPE,REGISTERSUBTYPE_SYSEVENT); // REGISTER DEFAULT EVENT HANDLERS #define REGISTER(a,b) SEvtRegisterHandler(REGISTERTYPE, \ REGISTERSUBTYPE_SYSEVENT, \ (a), \ 0, \ (SEVTHANDLER)(b)) REGISTER(SYS_CIRCUITCHECK ,SysOnCircuitCheck); REGISTER(SYS_DROPPLAYER ,SysOnDropPlayer); REGISTER(SYS_NEWGAMEOWNER ,SysOnNewGameOwner); REGISTER(SYS_PING ,SysOnPing); REGISTER(SYS_PINGRESPONSE ,SysOnPingResponse); REGISTER(SYS_PLAYERINFO ,SysOnPlayerInfo); REGISTER(SYS_PLAYERJOIN ,SysOnPlayerJoin); REGISTER(SYS_PLAYERJOIN_ACCEPTSTART,SysOnPlayerJoinAcceptStart); REGISTER(SYS_PLAYERJOIN_ACCEPTDONE ,SysOnPlayerJoinAcceptDone); REGISTER(SYS_PLAYERJOIN_REJECT ,SysOnPlayerJoinReject); REGISTER(SYS_PLAYERLEAVE ,SysOnPlayerLeave); #undef REGISTER // INITIALIZE THE RECEIVING THREAD HANDLE event = (HANDLE)0; if (!RecvInitialize(&event)) { SErrSetLastError(SNET_ERROR_MAX_THRDS_REACHED); FAILOUT_APILOCK_WRITE; } // INITIALIZE THE PROVIDER if (!SpiInitialize(providerid, &modprogramdata, &modplayerdata, &modinterfacedata, &modversiondata, event)) { // EITHER SPIINITIALIZE() OR THE SERVICE PROVIDER IS RESPONSIBLE // FOR CALLING SETLASTERROR() FAILOUT_APILOCK_WRITE; } // SAVE THE CATEGORY OPTIMIZATION HINT s_game_optcategorybits = modprogramdata.optcategorybits; LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetJoinGame (DWORD gameid, LPCSTR gamename, LPCSTR gamepassword, LPCSTR playername, LPCSTR playerdescription, DWORD *playerid) { if (playerid) *playerid = SNET_INVALIDPLAYERID+s_api_playeroffset; VALIDATEBEGIN; VALIDATE(playerid); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetJoinGame(0x%08x,\"%s\",\"%s\",\"%s\",\"%s\",*playerid)", gameid,gamename,gamepassword,playername,playerdescription); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (!gamename) gamename = ""; if (!gamepassword) gamepassword = ""; if (!playername) playername = ""; if (!playerdescription) playerdescription = ""; // IF NO PLAYER NAME WAS PROVIDED, AND THE CURRENT NETWORK PROVIDER // IS CAPABLE OF PROVIDING IT, THEN GET THE NAME OF THE LOGGED ON // USER FROM THE NETWORK PROVIDER char localplayername[SNETSPI_MAXSTRINGLENGTH] = ""; char localplayerdesc[SNETSPI_MAXSTRINGLENGTH] = ""; if ((!*playername) && s_spi->GetLocalPlayerName) { s_spi->GetLocalPlayerName(localplayername, SNETSPI_MAXSTRINGLENGTH, localplayerdesc, SNETSPI_MAXSTRINGLENGTH); playername = localplayername; playerdescription = localplayerdesc; } if (!*playername) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); return FALSE; } // IF WE ARE ALREADY IN A GAME, LEAVE IT if (s_game_playerid != NOPLAYER) SNetLeaveGame(SNET_EXIT_AUTO_NEWGAME); // RESET THE CONNECTION TABLE ConnDestroy(); // GET THE ADDRESS OF THE GAME OWNER SNETSPI_GAMELIST gameinfo; TRACEOUT(TRACEHANDLE, " spiGetGameInfo(0x%08x,\"%s\",\"%s\",*gameinfo)", gameid,gamename,gamepassword); if (!s_spi->GetGameInfo(gameid,gamename,gamepassword,&gameinfo)) { // SERVICE PROVIDER IS RESPONSIBLE FOR CALLING SETLASTERROR() FAILOUT_APILOCK_WRITE; } // CREATE A CONNECTION RECORD FOR THE GAME OWNER AND SEND HIM AN // INITIAL CONTACT MESSAGE s_game_joining = TRUE; CONNPTR ownerconn = ConnFindByAddr(&gameinfo.owner); if (!ownerconn) { s_game_joining = FALSE; TRACEOUT(TRACEHANDLE," out of memory"); SNetLeaveGame(SNET_EXIT_AUTO_JOINING); SErrSetLastError(SNET_ERROR_NOT_ENOUGH_MEMORY); FAILOUT_APILOCK_WRITE; } ownerconn->establishing = TRUE; ownerconn->incomingsequence[TYPE_SYSTEM] = 1; ownerconn->availablesequence[TYPE_SYSTEM] = 1; ownerconn->outgoingsequence[TYPE_SYSTEM] = 1; { DWORD netversion = SNET_NETWORKVERSION; MESSAGEPTR msg = ConnSendMessage(ownerconn, TYPE_SYSTEM, SYS_INITIALCONTACT, &netversion, sizeof(DWORD)); if (msg) { ConnResendMessage(ownerconn,msg->data,msg->databytes); ConnResendMessage(ownerconn,msg->data,msg->databytes); } ConnProcessAck(ownerconn,TYPE_SYSTEM,1); } // WAIT FOR THE CIRCUIT CHECK MESSAGE FROM THE GAME OWNER { DWORD events[1] = {SYS_CIRCUITCHECK}; if (!SysWaitForMultipleEvents(1,events,0,s_spi_timetoblock)) { s_game_joining = FALSE; TRACEOUT(TRACEHANDLE," host unreachable"); SNetLeaveGame(SNET_EXIT_AUTO_JOINING); SErrSetLastError(SNET_ERROR_HOST_UNREACHABLE); FAILOUT_APILOCK_WRITE; } } // SEND A REQUEST TO JOIN { SYSEVENTDATA_PLAYERJOIN data; LPSTR currptr = data.namedescpass; currptr += SStrCopy(currptr,playername,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(currptr,playerdescription,SNETSPI_MAXSTRINGLENGTH)+1; currptr += SStrCopy(currptr,gamepassword,SNETSPI_MAXSTRINGLENGTH)+1; ConnSendMessage(ConnFindByAddr(&gameinfo.owner), TYPE_SYSTEM, SYS_PLAYERJOIN, &data, (LPBYTE)currptr-(LPBYTE)&data); } // WAIT FOR AN ACCEPT OR REJECT MESSAGE FROM THE OWNER { DWORD events[2] = {SYS_PLAYERJOIN_ACCEPTDONE, SYS_PLAYERJOIN_REJECT}; if (!SysWaitForMultipleEvents(2,events,0,s_spi_timetoblock)) { ownerconn->establishing = FALSE; s_game_joining = FALSE; TRACEOUT(TRACEHANDLE," host unreachable"); SNetLeaveGame(SNET_EXIT_AUTO_JOINING); SErrSetLastError(SNET_ERROR_HOST_UNREACHABLE); FAILOUT_APILOCK_WRITE; } } // IF WE WERE REJECTED, RETURN FAILURE if (s_game_playerid == NOPLAYER) { ownerconn->establishing = FALSE; s_game_joining = FALSE; TRACEOUT(TRACEHANDLE," rejected"); SNetLeaveGame(SNET_EXIT_AUTO_JOINING); SErrSetLastError(SNET_ERROR_GAME_FULL); FAILOUT_APILOCK_WRITE; } // OTHERWISE, SAVE THE GAME CATEGORY s_game_categorybits = gameinfo.gamecategorybits; // SET OUR PLAYER INFORMATION CONNPTR conn = ConnFindLocal(); if (conn) { conn->playerid = s_game_playerid; SStrCopy(conn->name,playername,SNETSPI_MAXSTRINGLENGTH); SStrCopy(conn->desc,playerdescription,SNETSPI_MAXSTRINGLENGTH); } *playerid = s_game_playerid+s_api_playeroffset; TRACEOUT(TRACEHANDLE," joined as player %u",*playerid); // DISPATCH ANY PENDING EVENTS LEAVE_APILOCK_WRITE; SysDispatchUserEvents(); ENTER_APILOCK_WRITE; // COMPLETE THE JOIN PROCESS ownerconn->establishing = FALSE; s_game_joining = FALSE; // MAKE SURE WE ARE STILL IN A GAME if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_GAME_TERMINATED); FAILOUT_APILOCK_WRITE; } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetLeaveGame (DWORD exitcode) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetLeaveGame(0x%08x)", exitcode); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } // FIND OUR PLAYER RECORD CONNPTR conn = ConnFindLocal(); if (!conn) { SErrSetLastError(SNET_ERROR_NOT_ENOUGH_MEMORY); FAILOUT_APILOCK_WRITE; } // IF WE ARE THE GAME OWNER, STOP ADVERTISING THE GAME if (conn->gameowner) { TRACEOUT(TRACEHANDLE," spiStopAdvertisingGame()"); s_spi->StopAdvertisingGame(); } // SEND MESSAGES TO ALL OTHER PLAYERS INFORMING THEM THAT WE'RE DROPPING // OUT OF THE GAME { CONNPTR checkconn = s_conn_connlist.Head(); while (checkconn) { if (checkconn->playerid != NOPLAYER) { SYSEVENTDATA_PLAYERLEAVE data; data.finalsequence = checkconn->outgoingsequence[TYPE_TURN]; data.exitcode = exitcode; MESSAGEPTR msg = ConnSendMessage(checkconn, TYPE_SYSTEM, SYS_PLAYERLEAVE, &data, sizeof(SYSEVENTDATA_PLAYERLEAVE)); if (msg) { ConnResendMessage(checkconn,msg->data,msg->databytes); ConnResendMessage(checkconn,msg->data,msg->databytes); } } checkconn = checkconn->Next(); } } // DESTROY THE GAME LOCALLY GameDestroy(); // LEAVE THE API LOCK LEAVE_APILOCK_WRITE; // DELAY A FRACTION OF A SECOND TO GIVE THE BACKGROUND THREAD TIME TO // FINISH ANY PACKETS IT'S PROCESSING Sleep(s_spi_timetoresend); return TRUE; } //=========================================================================== BOOL APIENTRY SNetPerformUpgrade (DWORD *upgradestatus) { VALIDATEBEGIN; VALIDATE(upgradestatus); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE,"SNetPerformUpgrade(*upgradestatus)"); // VERIFY THAT THERE IS AT LEAST ONE PATCH READY TO BE INSTALLED char buffer[1024] = ""; if (!SRegLoadData("Patch","Patches",SREG_FLAG_BATTLENET,buffer,1023,NULL)) { *upgradestatus = SNET_UPGRADE_NOT_NEEDED; FAILOUT_APILOCK_WRITE; } buffer[1022] = 0; buffer[1023] = 0; // DETERMINE THE PROGRAM DIRECTORY char directory[MAX_PATH] = ""; GetModuleFileName((HMODULE)0,directory,MAX_PATH); directory[MAX_PATH-1] = 0; { LPSTR curr = directory; while (*curr && SStrChr(curr+1,'\\')) curr = SStrChr(curr+1,'\\'); *curr = 0; } // CREATE A LIST OF FILES TO EXECUTE char execute[1024] = ""; LPSTR currexec = execute; // PREPROCESS EACH PATCH { LPSTR curr = buffer; while (*curr) { // OPEN THE PATCH HSARCHIVE archive; if (!SFileOpenArchive(curr,0,0,&archive)) { *upgradestatus = (DWORD)SNET_UPGRADE_FAILED; FAILOUT_APILOCK_WRITE; } // VERIFY ITS AUTHENTICITY { DWORD authtype; SFileAuthenticateArchive(archive,&authtype); if ((authtype != SFILE_AUTH_UNABLETOAUTHENTICATE) && (authtype < SFILE_AUTH_FIRSTAUTHENTIC)) { SFileCloseArchive(archive); *upgradestatus = (DWORD)SNET_UPGRADE_FAILED; FAILOUT_APILOCK_WRITE; } } // LOOK FOR A FILE CALLED 'PREPATCH.LST' INSIDE THE PATCH FILE { HSFILE listfile; if (SFileOpenFileEx(archive,"Prepatch.lst",0,&listfile)) { // IF WE FOUND ONE, READ IT INTO MEMORY DWORD bytes = SFileGetFileSize(listfile); LPSTR buffer = (LPSTR)ALLOC(bytes); if (SFileReadFile(listfile,buffer,bytes,NULL,NULL)) { LPCSTR curr = buffer; while ((DWORD)(curr-buffer) < bytes) { // EXTRACT THE NEXT LINE char line[256] = ""; { LPSTR currout = line; while (((DWORD)(curr-buffer) < bytes) && (currout-line < 255) && *curr && (*curr != '\r') && (*curr != '\n')) { *currout++ = *curr++; *currout = 0; } } while (((DWORD)(curr-buffer) < bytes) && ((*curr == '\r') || (*curr == '\n'))) ++curr; // IF THIS LINE HAS A FILENAME ARGUMENT, TURN IT INTO A FULLY // QUALIFIED FILENAME char relpath[MAX_PATH] = ""; char abspath[MAX_PATH] = ""; if (SStrChr(line,' ')) { LPCSTR currline = SStrChr(line,' ')+1; while (*currline == ' ') ++currline; SStrCopy(relpath,currline,MAX_PATH); wsprintf(abspath,"%s\\%s",directory,currline); } // IF THIS LINE CONTAINS A DELETE COMMAND, DELETE THE FILE if (!_strnicmp(line,"delete ",7)) DeleteFile(abspath); // IF THIS LINE CONTAINS AN EXTRACT COMMAND, EXTRACT THE FILE if (!_strnicmp(line,"extract ",8)) { HSFILE extractfile; if (SFileOpenFileEx(archive,relpath,0,&extractfile)) { DWORD extractbytes = SFileGetFileSize(extractfile); LPVOID extractbuffer = ALLOC(extractbytes); if (SFileReadFile(extractfile,extractbuffer,extractbytes,NULL,NULL)) { HANDLE outfile = CreateFile(abspath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (outfile != INVALID_HANDLE_VALUE) { DWORD byteswritten; WriteFile(outfile,extractbuffer,extractbytes,&byteswritten,NULL); CloseHandle(outfile); } } FREE(extractbuffer); SFileCloseFile(extractfile); } } // IF THIS LINE CONTAINS AN EXECUTE COMMAND, ADD THE PROGRAM // TO OUR LIST OF PROGRAMS TO EXECUTE if (!_strnicmp(line,"execute ",8)) currexec += SStrCopy(currexec,abspath)+1; } } FREE(buffer); SFileCloseFile(listfile); } } // CLOSE THE PATCH SFileCloseArchive(archive); curr += SStrLen(curr)+1; } } // IF THE EXECUTE LIST IS BLANK, ADD 'BNUPDATE' if (currexec == execute) { wsprintf(currexec,"%s\\bnupdate.exe",directory); currexec += SStrLen(currexec)+1; } *currexec = 0; // RUN ALL OF THE PROGRAMS ON THE EXECUTE LIST currexec = execute; while (*currexec) { STARTUPINFO startupinfo; PROCESS_INFORMATION processinfo; ZeroMemory(&startupinfo,sizeof(STARTUPINFO)); startupinfo.cb = sizeof(STARTUPINFO); if (CreateProcess(currexec, NULL, NULL, NULL, 0, NORMAL_PRIORITY_CLASS, NULL, directory, &startupinfo, &processinfo)) { CloseHandle(processinfo.hThread); CloseHandle(processinfo.hProcess); } else { *upgradestatus = (DWORD)SNET_UPGRADE_FAILED; FAILOUT_APILOCK_WRITE; } currexec += SStrLen(currexec)+1; } LEAVE_APILOCK_WRITE; *upgradestatus = SNET_UPGRADING_TERMINATE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetReceiveMessage (DWORD *senderplayerid, LPVOID *data, DWORD *databytes) { if (senderplayerid) *senderplayerid = SNET_INVALIDPLAYERID+s_api_playeroffset; if (data) *data = NULL; if (databytes) *databytes = 0; VALIDATEBEGIN; VALIDATE(data); VALIDATE(databytes); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEPEND(TRACEHANDLE,"SNetReceiveMessage(*senderplayerid,*data,*databytes)"); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // CHECK FOR TRANSFER OF GAME OWNERSHIP if (s_game_playerid != NOPLAYER) GameProcessLeavingPlayers(); // DISPATCH ANY PENDING EVENTS LEAVE_APILOCK_WRITE; SysDispatchUserEvents(); ENTER_APILOCK_WRITE; // MAKE SURE WE ARE STILL IN A GAME if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_GAME_TERMINATED); FAILOUT_APILOCK_WRITE; } // FIND A VALID MESSAGE THAT IS THE NEXT IN SEQUENCE FROM ITS SENDER. // IF THERE IS MORE THAN ONE MESSAGE AVAILABLE, USE THE ONE THAT WAS // RECEIVED EARLIEST. { DWORD currtime = GetTickCount(); DWORD earliesttime = 0; CONNPTR earliestconn = NULL; for (int local = FALSE; local <= TRUE; ++local) { CONNPTR conn = local ? ConnFindLocal() : s_conn_connlist.Head(); while (conn) { if ((conn->playerid != NOPLAYER) && (!conn->incomingqueue[TYPE_MESSAGE].IsEmpty()) && (conn->incomingqueue[TYPE_MESSAGE].Head()->data->header.sequence == conn->incomingsequence[TYPE_MESSAGE])) { // DETERMINE THE TIME STAMP OF THIS MESSAGE MESSAGEPTR curr = conn->incomingqueue[TYPE_MESSAGE].Head(); DWORD timestamp = curr->sendtime; // IF THIS MESSAGE'S TIME STAMP IS GREATER THAN ANY MESSAGES // THAT WERE SENT LATER BY THE SAME PLAYER, UPDATE IT TO BE // EARLIER while (curr->Next()) { curr = curr->Next(); if (timestamp-curr->sendtime < 0x7FFFFFFF) timestamp = curr->sendtime-1; } // IF THIS MESSAGE IS NOW THE EARLIEST WE HAVE SEEN, MARK THIS // CONNECTION AS THE NEW EARLIEST CONNECTION if (currtime-timestamp >= earliesttime) { earliesttime = currtime-timestamp; earliestconn = conn; } } conn = conn->Next(); } } // RETURN THE NEXT MESSAGE IN SEQUENCE FROM THE EARLIEST CONNECTION // TO THE USER if (earliestconn) { MESSAGEPTR message = earliestconn->incomingqueue[TYPE_MESSAGE].Head(); earliestconn->incomingqueue[TYPE_MESSAGE].UnlinkNode(message); ++(earliestconn->incomingsequence[TYPE_MESSAGE]); ConnSetCurrentMessage(earliestconn,TYPE_MESSAGE,message); if (senderplayerid) *senderplayerid = earliestconn->playerid+s_api_playeroffset; *data = message->data->data; *databytes = message->data->header.bytes-sizeof(HEADER); PerfIncrement(SNET_PERFID_MSGRECV); TRACEDUMPMSG(TRACEHANDLE, earliestconn->playerid, message->data->header.sequence, message->data->data, message->data->header.bytes-sizeof(HEADER)); LEAVE_APILOCK_WRITE; return TRUE; } } LEAVE_APILOCK_WRITE; SErrSetLastError(SNET_ERROR_NO_MESSAGES_WAITING); return FALSE; } //=========================================================================== BOOL APIENTRY SNetReceiveTurns (DWORD firstplayerid, DWORD arraysize, LPVOID *arraydata, LPDWORD arraydatabytes, LPDWORD arrayplayerstatus) { if (arraysize && arraydata) ZeroMemory(arraydata,arraysize*sizeof(LPVOID)); if (arraysize && arraydatabytes) ZeroMemory(arraydatabytes,arraysize*sizeof(DWORD)); if (arraysize && arrayplayerstatus) ZeroMemory(arrayplayerstatus,arraysize*sizeof(DWORD)); VALIDATEBEGIN; VALIDATE(arraysize); VALIDATE(arraydata); VALIDATE(arraydatabytes); VALIDATE(arrayplayerstatus); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetReceiveTurns(%u,%u,*arraydata,*arraydatabytes,*arrayplayerstatus)", firstplayerid,arraysize); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } firstplayerid -= s_api_playeroffset; // CHECK FOR TRANSFER OF GAME OWNERSHIP if (s_game_playerid != NOPLAYER) GameProcessLeavingPlayers(); // DETERMINE THE SEQUENCE NUMBER TO RECEIVE CONNPTR local = ConnFindLocal(); if (!local) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } WORD sequence = local->incomingsequence[TYPE_TURN]; // CHECK ALL PLAYERS THAT ARE IN A JOINING STATE, MEANING THAT WE HAVEN'T // CAUGHT UP TO THEIR FIRST EVER TURN, TO SEE IF THEY ARE READY TO SWITCH // OVER TO ACTIVE STATE { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if ((conn->flags & PF_JOINING) && (conn->incomingsequence[TYPE_TURN] == sequence)) { conn->flags &= ~PF_JOINING; SysQueueUserEvent(SNET_EVENT_PLAYERJOIN, conn->playerid, NULL, 0); } conn = conn->Next(); } } // DISPATCH ANY PENDING EVENTS LEAVE_APILOCK_WRITE; SysDispatchUserEvents(); ENTER_APILOCK_WRITE; // MAKE SURE WE ARE STILL IN A GAME if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_GAME_TERMINATED); FAILOUT_APILOCK_WRITE; } // CLEAR OUT ANY UNNEEDED OR ALREADY PROCESSED TURNS ConnClearOldTurns(NULL); // FILL IN THE PLAYER STATUS ARRAY DWORD currtime = GetTickCount(); BOOL ready = TRUE; #ifdef TRACING char outstr[256]; wsprintf(outstr,"%u ",sequence); #endif { for (DWORD playerid = firstplayerid; playerid < firstplayerid+arraysize; ++playerid) { // FIND THE CONNECTION RECORD FOR THIS PLAYER CONNPTR conn; if (playerid == s_game_playerid) conn = local; else conn = ConnFindByPlayerId((BYTE)playerid); // IF THE PLAYER IS ACTIVE AND HAS COMPLETED JOINING THE GAME, FILL // IN HIS STATUS FLAGS DWORD statusflags = 0; if (conn && !(conn->flags & PF_JOINING)) { statusflags |= SNET_PSF_ACTIVE; if ((!conn->incomingqueue[TYPE_TURN].IsEmpty()) && (conn->incomingqueue[TYPE_TURN].Head()->data->header.sequence == sequence)) statusflags |= SNET_PSF_TURNAVAILABLE; else ready = FALSE; if ((playerid == s_game_playerid) || (currtime-conn->lastreceivetime < s_spi_timetogiveup) || ((conn->flags & PF_LEAVING) && (statusflags & SNET_PSF_TURNAVAILABLE))) statusflags |= SNET_PSF_RESPONDING; // IF THIS IS A TRACING BUILD, LOG EACH PLAYER'S STATUS #ifdef TRACING char buffer[16]; wsprintf(buffer,"%u?%u ",playerid,conn->incomingsequence[TYPE_TURN]); if (statusflags & SNET_PSF_TURNAVAILABLE) buffer[1] = '='; else if (statusflags & SNET_PSF_RESPONDING) buffer[1] = 'W'; else buffer[1] = 'X'; SStrPack(outstr,buffer,256); #endif } *(arrayplayerstatus+playerid-firstplayerid) = statusflags; } } #ifdef TRACING TRACEOUT(TRACEHANDLE,outstr); #endif // MAINTAIN ACTIVE CONNECTIONS { for (DWORD playerid = firstplayerid; playerid < firstplayerid+arraysize; ++playerid) if (*(arrayplayerstatus+playerid-firstplayerid) & SNET_PSF_ACTIVE) { // FIND THE PLAYER RECORD CONNPTR conn = NULL; if (playerid != s_game_playerid) conn = ConnFindByPlayerId((BYTE)playerid); if (!conn) continue; // IF WE ARE STILL WAITING FOR THIS PLAYER'S TURN AND MORE THAN THE // RESEND TIME HAS ELAPSED, REQUEST A RESEND OF THIS TURN FROM ALL // PLAYERS if ((!((*(arrayplayerstatus+playerid-firstplayerid)) & SNET_PSF_TURNAVAILABLE)) && ((!conn->lastrequesttime) || ((currtime-conn->lastrequesttime >= s_spi_timetoresend) && (currtime-conn->lastrequesttime <= 0x7FFFFFFF)))) { PACKETPTR pkt = (PACKETPTR)ALLOC(sizeof(PACKET)+1); conn->lastrequesttime = currtime; CONNPTR otherconn = s_conn_connlist.Head(); while (otherconn) { if ((otherconn->playerid != NOPLAYER) && ((conn->playerid != NOPLAYER) || (otherconn == conn))) { TRACEOUT(TRACEHANDLE, " requesting resend: type=%u sequence=%04x player=%x", TYPE_TURN,conn->incomingsequence[TYPE_TURN],conn->playerid); pkt->header.checksum = 0; pkt->header.bytes = sizeof(HEADER); pkt->header.sequence = conn->incomingsequence[TYPE_TURN]; pkt->header.acksequence = otherconn->availablesequence[TYPE_TURN]; pkt->header.type = TYPE_TURN; pkt->header.subtype = 0; pkt->header.playerid = local->playerid; pkt->header.flags = MF_RESENDREQUEST; if (otherconn != conn) { pkt->header.bytes++; pkt->data[0] = conn->playerid; } pkt->header.checksum = PktGenerateChecksum(pkt); ConnSendPacket(otherconn,pkt); } otherconn = otherconn->Next(); } FREE(pkt); } // IF WE'RE IN DANGER OF GIVING UP ON THIS PLAYER, SEND A PING // REQUEST TO SEE IF HE'S STILL ALIVE. (OPTIMIZATION: IF THIS // PLAYER ALREADY HAS UNACKNOWLEDGED SYSTEM MESSAGES THEN HE'S // NOT GOING TO RESPOND TO A PING UNTIL HE PROCESSES THOSE, SO // DON'T BOTHER TO SEND ONE.) if ((conn->outgoingqueue[TYPE_SYSTEM].IsEmpty()) && (currtime-conn->lastreceivetime >= s_spi_timetogiveup/2) && (currtime-conn->lastpingtime >= s_spi_timetogiveup/2)) { conn->lastpingtime = currtime; ConnSendMessage(conn,TYPE_SYSTEM,SYS_PING,NULL,0); } } } // EXIT IF WE DON'T HAVE TURN DATA FOR ALL PLAYERS if (!ready) { SErrSetLastError(SNET_ERROR_NO_MESSAGES_WAITING); LEAVE_APILOCK_WRITE; return FALSE; } // FILL IN THE TURN DATA ARRAYS { for (DWORD playerid = firstplayerid; playerid < firstplayerid+arraysize; ++playerid) { CONNPTR conn; if (playerid == s_game_playerid) conn = local; else conn = ConnFindByPlayerId((BYTE)playerid); if (conn && (*(arrayplayerstatus+playerid-firstplayerid) & SNET_PSF_TURNAVAILABLE)) { MESSAGEPTR message = conn->incomingqueue[TYPE_TURN].Head(); conn->incomingqueue[TYPE_TURN].UnlinkNode(message); conn->incomingsequence[TYPE_TURN] = (WORD)(sequence+1); ConnSetCurrentMessage(conn,TYPE_TURN,message); *(arraydata +playerid-firstplayerid) = message->data->data; *(arraydatabytes+playerid-firstplayerid) = message->data->header.bytes-sizeof(HEADER); TRACEDUMPMSG(TRACEHANDLE, conn->playerid, message->data->header.sequence, message->data->data, message->data->header.bytes-sizeof(HEADER)); } else { *(arraydata +playerid-firstplayerid) = NULL; *(arraydatabytes+playerid-firstplayerid) = 0; } } } // INCREMENT THE SEQUENCE NUMBER local->incomingsequence[TYPE_TURN] = (WORD)(sequence+1); PerfSet(SNET_PERFID_TURN,local->incomingsequence[TYPE_TURN]); PerfIncrement(SNET_PERFID_TURNSRECV); LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetRegisterEventHandler (DWORD eventid, SNETEVENTPROC callback) { VALIDATEBEGIN; VALIDATE(callback); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetRegisterEventHandler(%u,0x%08x)", eventid,callback); // REGISTER THE EVENT HANDLER BOOL success = SEvtRegisterHandler(REGISTERTYPE, REGISTERSUBTYPE_SNETEVENT, eventid, 0, (SEVTHANDLER)callback); LEAVE_APILOCK_WRITE; if (!success) SErrSetLastError(SNET_ERROR_NOT_ENOUGH_MEMORY); return success; } //=========================================================================== BOOL APIENTRY SNetResetLatencyMeasurements () { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetResetLatencyMeasurements()"); CONNPTR conn = s_conn_connlist.Head(); while (conn) { conn->latency = 0; conn->peaklatency = 0; conn->lastpingtime = 0; conn = conn->Next(); } LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetSelectGame (DWORD flags, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata, DWORD *playerid) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSelectGame(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,*playerid)", flags,programdata,playerdata,interfacedata,versiondata); TRACEDUMPDATABLOCKS(TRACEHANDLE, programdata,playerdata,interfacedata,versiondata); // VALIDATE PARAMETERS if (playerid) *playerid = 0; if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } SNETPROGRAMDATA modprogramdata; SNETPLAYERDATA modplayerdata; SNETUIDATA modinterfacedata; SNETVERSIONDATA modversiondata; if (!SpiNormalizeDataBlocks(programdata,playerdata,interfacedata,versiondata, &modprogramdata,&modplayerdata,&modinterfacedata,&modversiondata)) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } // SAVE A POINTER TO THE SELECT GAME FUNCTION AND LEAVE THE API LOCK BOOL (CALLBACK *selectfunc)(DWORD,SNETPROGRAMDATAPTR,SNETPLAYERDATAPTR,SNETUIDATAPTR,SNETVERSIONDATAPTR,DWORD *); selectfunc = s_spi->SelectGame; LEAVE_APILOCK_WRITE; // CALL THE SELECT GAME FUNCTION TRACEOUT(TRACEHANDLE, " spiSelectGame(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,*playerid)", flags,&modprogramdata,&modplayerdata,&modinterfacedata,&modversiondata); BOOL success = selectfunc(flags, &modprogramdata, &modplayerdata, &modinterfacedata, &modversiondata, playerid); TRACEOUT(TRACEHANDLE," spiSelectGame() returns %u",success); return success; } //=========================================================================== BOOL APIENTRY SNetSelectProvider (SNETCAPSPTR mincaps, SNETPROGRAMDATAPTR programdata, SNETPLAYERDATAPTR playerdata, SNETUIDATAPTR interfacedata, SNETVERSIONDATAPTR versiondata, DWORD *providerid) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSelectProvider(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,*providerid)", mincaps,programdata,playerdata,interfacedata,versiondata); TRACEDUMPDATABLOCKS(TRACEHANDLE, programdata,playerdata,interfacedata,versiondata); // VALIDATE PARAMETERS if (providerid) *providerid = 0; SNETPROGRAMDATA modprogramdata; SNETPLAYERDATA modplayerdata; SNETUIDATA modinterfacedata; SNETVERSIONDATA modversiondata; if (!SpiNormalizeDataBlocks(programdata,playerdata,interfacedata,versiondata, &modprogramdata,&modplayerdata,&modinterfacedata,&modversiondata)) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } // BUILD A LIST OF PROVIDERS IF WE DON'T ALREADY HAVE ONE SpiFindAllProviders(); // BUILD A USER INTERFACE PARAMETERS BLOCK UIPARAMS uiparams; ZeroMemory(&uiparams,sizeof(UIPARAMS)); uiparams.mincaps = mincaps; uiparams.programdata = &modprogramdata; uiparams.playerdata = &modplayerdata; uiparams.interfacedata = &modinterfacedata; uiparams.versiondata = &modversiondata; // DISPLAY THE DIALOG BOX DWORD result = (DWORD)SDlgDialogBoxParam(StormGetInstance(), "SELECTPROVIDER_DIALOG", SDrawGetFrameWindow(), UiSelectProviderDialogProc, (LPARAM)&uiparams); if (providerid) *providerid = result; LEAVE_APILOCK_WRITE; return (result && (result != 0xFFFFFFFF)); } //=========================================================================== BOOL APIENTRY SNetSendMessage (DWORD targetplayerid, LPVOID data, DWORD databytes) { VALIDATEBEGIN; VALIDATE(data); VALIDATE(databytes); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSendMessage(%u,0x%08x,%u)", targetplayerid,data,databytes); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } if ((targetplayerid != SNET_BROADCASTNONLOCALPLAYERID) && (targetplayerid != SNET_BROADCASTPLAYERID)) targetplayerid -= s_api_playeroffset; // IF THIS IS A BROADCAST, SEND IT TO ALL PLAYERS IN THE GAME, // INCLUDING THE LOCAL PLAYER if ((targetplayerid == SNET_BROADCASTNONLOCALPLAYERID) || (targetplayerid == SNET_BROADCASTPLAYERID)) { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if (conn->playerid != NOPLAYER) { TRACEDUMPMSG(TRACEHANDLE, 0xFFFFFFFF, conn->outgoingsequence[TYPE_MESSAGE], data, databytes); ConnSendMessage(conn, TYPE_MESSAGE, 0, data, databytes); } conn = conn->Next(); } if (targetplayerid == SNET_BROADCASTPLAYERID) { conn = ConnFindLocal(); if (conn) { TRACEDUMPMSG(TRACEHANDLE, 0xFFFFFFFF, conn->outgoingsequence[TYPE_MESSAGE], data, databytes); ConnSendMessage(conn, TYPE_MESSAGE, 0, data, databytes); } } } // OTHERWISE, SEND IT TO JUST THE REQUESTED PLAYER else { CONNPTR conn = ConnFindByPlayerId(targetplayerid); if (conn) { TRACEDUMPMSG(TRACEHANDLE, 0xFFFFFFFF, conn->outgoingsequence[TYPE_MESSAGE], data, databytes); ConnSendMessage(conn, TYPE_MESSAGE, 0, data, databytes); } else { SErrSetLastError(SNET_ERROR_INVALID_PLAYER); FAILOUT_APILOCK_WRITE; } } PerfIncrement(SNET_PERFID_MSGSENT); LEAVE_APILOCK_WRITE; // TRIGGER THE BACKGROUND RECEIVE THREAD SO IT WILL MAINTAIN CONNECTIONS SetEvent(s_recv_event); return TRUE; } //=========================================================================== BOOL APIENTRY SNetSendServerChatCommand (LPCSTR command) { VALIDATEBEGIN; VALIDATE(command); VALIDATE(*command); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSetServerChatCommand(0x%08x)", command); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } CONNPTR local = ConnFindLocal(); if (!local) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // SEND THE MESSAGE TO THE SERVER char senderpath[SNETSPI_MAXSTRINGLENGTH+16]; wsprintf(senderpath,"\\\\.\\game\\%s",s_game_gamename); BOOL result = s_spi->SendExternalMessage(senderpath, local->name, "", "", command); LEAVE_APILOCK_WRITE; return result; } //=========================================================================== BOOL APIENTRY SNetSendTurn (LPVOID data, DWORD databytes) { VALIDATEBEGIN; VALIDATE(data); VALIDATE(databytes); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSendTurn(0x%08x,%u)", data,databytes); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_BAD_PROVIDER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } CONNPTR local = ConnFindLocal(); if (!local) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // SEND THE TURN TO ALL PLAYERS IN THE GAME, INCLUDING THE LOCAL PLAYER { CONNPTR conn = s_conn_connlist.Head(); while (conn) { if ((conn->playerid != NOPLAYER) && (conn->outgoingsequence[TYPE_TURN] == local->outgoingsequence[TYPE_TURN])) { TRACEDUMPMSG(TRACEHANDLE, 0xFFFFFFFF, conn->outgoingsequence[TYPE_TURN], data, databytes); ConnSendMessage(conn, TYPE_TURN, 0, data, databytes); } conn = conn->Next(); } TRACEDUMPMSG(TRACEHANDLE, 0xFFFFFFFF, local->outgoingsequence[TYPE_TURN], data, databytes); ConnSendMessage(local, TYPE_TURN, 0, data, databytes); } PerfIncrement(SNET_PERFID_TURNSSENT); LEAVE_APILOCK_WRITE; // TRIGGER THE BACKGROUND RECEIVE THREAD SO IT WILL MAINTAIN CONNECTIONS SetEvent(s_recv_event); return TRUE; } //=========================================================================== BOOL APIENTRY SNetSetBasePlayer (DWORD playerid) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSetBasePlayer(%u)", playerid); s_api_playeroffset = playerid; LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetSetGameMode (DWORD modeflags) { ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetSetGameMode(0x%08x)", modeflags); // VALIDATE PARAMETERS if (!s_spi) { SErrSetLastError(SNET_ERROR_INVALID_PARAMETER); FAILOUT_APILOCK_WRITE; } if (s_game_playerid == NOPLAYER) { SErrSetLastError(SNET_ERROR_NOT_IN_GAME); FAILOUT_APILOCK_WRITE; } // IF WE ARE NOT THE GAME OWNER, RETURN FAILURE { CONNPTR local = ConnFindLocal(); if (!(local && local->gameowner)) { SErrSetLastError(SNET_ERROR_NOT_OWNER); FAILOUT_APILOCK_WRITE; } } // SET THE NEW GAME MODE s_game_gamemode = modeflags; // BUILD THE CLIENT DATA BLOCK BYTE clientdata[SNETSPI_MAXCLIENTDATA]; DWORD clientdatabytes; GameBuildClientData(clientdata, &clientdatabytes); // START ADVERTISING WITH THE NEW MODE DWORD gameage = (GetTickCount()-s_game_creationtime)/1000; TRACEOUT(TRACEHANDLE, " spiStartAdvertisingGame(\"%s\",\"%s\",\"%s\",%u,%u,0x%08x,0x%08x,0x%08x,%u)", s_game_gamename, s_game_gamepass, s_game_gamedesc, s_game_gamemode, gameage, s_game_categorybits, s_game_optcategorybits, clientdata, clientdatabytes); if (!s_spi->StartAdvertisingGame(s_game_gamename, s_game_gamepass, s_game_gamedesc, s_game_gamemode, gameage, s_game_categorybits, s_game_optcategorybits, clientdata, clientdatabytes)) FAILOUT_APILOCK_WRITE; // note: broadcast the new mode to other players LEAVE_APILOCK_WRITE; return TRUE; } //=========================================================================== BOOL APIENTRY SNetUnregisterEventHandler (DWORD eventid, SNETEVENTPROC callback) { VALIDATEBEGIN; VALIDATE(callback); VALIDATEEND; ENTER_APILOCK_WRITE; TRACEOUT(TRACEHANDLE, "SNetUnregisterEventHandler(%u,0x%08x)", eventid,callback); // UNREGISTER THE EVENT HANDLER BOOL success = SEvtUnregisterHandler(REGISTERTYPE, REGISTERSUBTYPE_SNETEVENT, eventid, (SEVTHANDLER)callback); LEAVE_APILOCK_WRITE; if (!success) SErrSetLastError(SNET_ERROR_NOT_REGISTERED); return success; }