Files
Hellfire/Storm/SOURCE/SNET.CPP
T
Justin Marshall 101266ab72 Initial commit.
2026-04-27 10:35:40 -07:00

5262 lines
174 KiB
C++

/****************************************************************************
*
* 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;
}