/****** gstats.c GameSpy Stats/Tracking SDK GameSpy Persistent Storage SDK Copyright 1999-2007 GameSpy Industries, Inc ****** Please see the GameSpy Stats and Tracking SDK documentation for more info ******/ /******** INCLUDES ********/ #include "gstats.h" #include "gpersist.h" #include "../common/gsAvailable.h" #include "../darray.h" #include "../md5.h" #ifdef __cplusplus extern "C" { #endif /******** TYPEDEFS ********/ struct statsgame_s { int connid; int sesskey; int usebuckets; bucketset_t buckets; char challenge[9]; DArray playernums; //for player number translation DArray teamnums; //for team number translation int totalplayers, totalteams; gsi_time sttime; }; typedef enum {rt_authcb, rt_datacb, rt_savecb, rt_profilecb} reqtype_t; typedef struct { reqtype_t reqtype; int localid; int profileid; persisttype_t pdtype; int pdindex; void *instance; void *callback; } serverreq_t; /******** PROTOTYPES ********/ static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); static char *CreateBucketSnapShot(bucketset_t buckets); #ifdef ALLOW_DISK static void CheckDiskFile(); static void DiskWrite(char *line, int len); #endif static void InternalInit(); static int SendChallengeResponse(const char *indata, int gameport); static int RecvSessionKey(); static int DoSend(char *data, int len); static void xcode_buf(char *buf, int len); static int g_crc32(char *s, int len); static void create_challenge(int challenge, char chstr[9]); static char *value_for_key(const char *s, const char *key); static char *value_for_key_safe(const char *s, const char *key); static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); /************** PERSISTENT STORAGE PROTOTYPES **************/ static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance); static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); static int SocketReadable(SOCKET s); static char *FindFinal(char *buff, int len); static int FindRequest(reqtype_t reqtype, int localid, int profileid); static void ProcessPlayerAuth(const char *buf, int len); static void ProcessGetPid(const char *buf, int len); static void ProcessGetData(const char *buf, int len); static void ProcessSetData(const char *buf, int len); static void ProcessStatement(char *buff, int len); static int ProcessInBuffer(char *buff, int len); static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length); static void ClosePendingCallbacks(); static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset); void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance); void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance); /******** DEFINES ********/ //#define SSHOST "207.199.80.230" #define SSHOST "gamestats." GSI_DOMAIN_NAME #define SSPORT 29920 #define FIXGAME(g,r) if (g == NULL) g = g_statsgame; if (g == NULL) return r; #define DoFunc(f,g, n, v, t, r) \ if (g == NULL) g = g_statsgame; \ if (!g) r = v; \ else { \ r = f(g->buckets, n, v); \ if (!r) \ r = BucketNew(g->buckets, n, t, v); } #define DOXCODE(b, l, e) enc = e; xcode_buf(b,l); /******** VARS ********/ char gcd_gamename[256] = ""; char gcd_secret_key[256] = ""; static statsgame_t g_statsgame = NULL; static int connid = 0; static int sesskey = 0; static SOCKET sock = INVALID_SOCKET; /* #define enc1 "GameSpy 3D" #define enc2 "Industries" #define enc3 "ProjectAphex" #define STATSFILE "gstats.dat" */ /* A couple vars to help avoid the string table */ static char enc1[16] = {'\0','a','m','e','S','p','y','3','D','\0'}; static char enc3[16] = {'\0','r','o','j','e','c','t','A','p','h','e','x','\0'}; #ifdef ALLOW_DISK static char statsfile[16] = {'\0','s','t','a','t','s','.','d','a','t','\0'}; static char enc2[16]= {'\0','n','d','u','s','t','r','i','e','s','\0'}; #endif static char finalstr[10] = {'\0','f','i','n','a','l','\\','\0'}; static char *enc = enc1; static int internal_init = 0; static char *rcvbuffer = NULL; static int rcvmax = 0; static int rcvlen = 0; // Changed By Saad Nader, 09-16-2004 // Due to confliction with MacOS X /////////////////////////////////////////// static int stats_initstate = init_none; static int gameport = 0; static gsi_time initstart = 0; static gsi_time inittimeout = 20000; // 20 seconds char StatsServerHostname[64] = SSHOST; static DArray serverreqs = NULL; //for pre-authentication requests BucketFunc bucketfuncs[NUMOPS] = {BucketSet, BucketAdd, BucketSub, BucketMult, BucketDiv, BucketConcat, BucketAvg}; void * bopfuncs[][3] = { {ServerOpInt, ServerOpFloat, ServerOpString}, {TeamOpInt, TeamOpFloat, TeamOpString}, {PlayerOpInt, PlayerOpFloat, PlayerOpString}, }; /****************************************************************************/ /* PUBLIC FUNCTIONS */ /****************************************************************************/ #define RAWSIZE 128 char *GenerateAuthA(const char *challenge, const char *password, char response[33]) { char rawout[RAWSIZE]; /* check to make sure we weren't passed a huge pass/challenge */ if (strlen(password) + strlen(challenge) + 20>= RAWSIZE) { strcpy(response,"CD Key or challenge too long"); return response; } /* response = MD5(pass + challenge) */ sprintf(rawout, "%s%s",password, challenge ); /* do the response md5 */ MD5Digest((unsigned char *)rawout, strlen(rawout), response); return response; } #ifdef GSI_UNICODE char *GenerateAuthW(const char* challenge, const unsigned short *password, char response[33]) { char* password_A = UCS2ToUTF8StringAlloc(password); GenerateAuthA(challenge, password_A, response); gsifree(password_A); return response; } #endif /****************************************************************************/ int InitStatsAsync(int theGamePort, gsi_time theInitTimeout) { struct sockaddr_in saddr; char tempHostname[128]; int ret; gameport = theGamePort; if (theInitTimeout != 0) inittimeout = theInitTimeout; /* check if the backend is available */ if(__GSIACResult != GSIACAvailable) return GE_NOSOCKET; /* Init our hidden strings if needed */ if (!internal_init) InternalInit(); SocketStartUp(); sesskey = (int)current_time(); /* Get connected */ if (sock != INVALID_SOCKET) CloseStatsConnection(); rcvlen = 0; //make sure ther receive buffer is cleared if (inet_addr(StatsServerHostname) == INADDR_NONE) { strcpy(tempHostname, gcd_gamename); strcat(tempHostname,"."); strcat(tempHostname,StatsServerHostname); } else strcpy(tempHostname, StatsServerHostname); //it's already been resolved if (get_sockaddrin(tempHostname,SSPORT,&saddr,NULL) == 0) return GE_NODNS; #ifdef INSOCK sock = socket ( AF_INET, SOCK_STREAM, 0 ); #else sock = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); #endif if (sock == INVALID_SOCKET) return GE_NOSOCKET; SetSockBlocking(sock, 0); ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr)); if (gsiSocketIsError(ret)) { int anError = GOAGetLastError(sock); if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) { stats_initstate = init_failed; closesocket(sock); return GE_NOCONNECT; } } // allocate the recv buffer rcvbuffer = gsimalloc(64); if (rcvbuffer == NULL) return GE_NOCONNECT; // add a new error code for out of mem? rcvmax = 64; rcvlen = 0; initstart = current_time(); stats_initstate = init_connecting; return GE_CONNECTING; } /****************************************************************************/ int InitStatsThink() { switch(stats_initstate) { case init_failed: return GE_NOCONNECT; case init_connecting: { // Check if socket is writeable yet int aWriteFlag = 0; int aExceptFlag = 0; int aResult = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); if ((gsiSocketIsError(aResult)) || // socket error (aResult == 1 && aExceptFlag == 1)) // exception { stats_initstate = init_failed; CloseStatsConnection(); return GE_NOCONNECT; } else if (aResult == 0) // no progress yet { // Should we continue to wait? if (current_time() - initstart > inittimeout) { stats_initstate = init_failed; CloseStatsConnection(); return GE_TIMEDOUT; } else return GE_CONNECTING; } // Otherwise connected assert(aResult == 1 && aWriteFlag == 1); stats_initstate = init_awaitchallenge; // fall through } case init_awaitchallenge: { int ret = 0; // Try to receive data if (!CanReceiveOnSocket(sock)) { // should we continue to wait? if (current_time() - initstart > inittimeout) { stats_initstate = init_failed; CloseStatsConnection(); return GE_TIMEDOUT; } return GE_CONNECTING; } // Receive the 38 byte challenge ret = recv(sock, rcvbuffer+rcvlen, rcvmax-rcvlen, 0); if (gsiSocketIsError(ret)) { stats_initstate = init_failed; CloseStatsConnection(); return GE_NOCONNECT; } rcvlen += ret; rcvmax -= ret; // need at least 38 bytes if (rcvlen < 38) return GE_CONNECTING; // Process challenge rcvbuffer[rcvlen] = '\0'; stats_initstate = init_awaitsessionkey; /* Decode it */ DOXCODE(rcvbuffer, rcvlen, enc1); /* Send a response */ ret = SendChallengeResponse(rcvbuffer, gameport); if (ret != GE_NOERROR) { stats_initstate = init_failed; CloseStatsConnection(); return ret; } stats_initstate = init_awaitsessionkey; // clear receive buffer for next stage rcvmax += rcvlen; // reclaim the used bytes as free space rcvlen = 0; memset(rcvbuffer, 0, (unsigned int)rcvmax); // fall through } case init_awaitsessionkey: { int ret = 0; // Try to receive data if (!CanReceiveOnSocket(sock)) { // should we continue to wait? if (current_time() - initstart > inittimeout) { stats_initstate = init_failed; CloseStatsConnection(); return GE_TIMEDOUT; } return GE_CONNECTING; } ret = RecvSessionKey(); if (ret != GE_NOERROR) { stats_initstate = init_failed; CloseStatsConnection(); return ret; } // Init complete // Clear the receive buffer rcvmax += rcvlen; rcvlen = 0; memset(rcvbuffer, 0, (unsigned int)rcvmax); #ifdef ALLOW_DISK /* Check for old data */ CheckDiskFile(); #endif stats_initstate = init_complete; // fall through } case init_complete: return GE_NOERROR; default: return GE_NOCONNECT; }; } /****************************************************************************/ // Blocking version of InitStatsAsync, for backwards compatability int InitStatsConnection(int gameport) { int aResult = InitStatsAsync(gameport, 0); while (aResult == GE_CONNECTING) { aResult = InitStatsThink(); msleep(5); } return aResult; } /****************************************************************************/ void CloseStatsConnection() { if (sock != INVALID_SOCKET) { shutdown(sock,2); closesocket(sock); } sock = INVALID_SOCKET; //call any pending callbacks with the data as lost ClosePendingCallbacks(); if (rcvbuffer != NULL) { gsifree(rcvbuffer); rcvbuffer = NULL; rcvmax = 0; rcvlen = 0; } } /****************************************************************************/ int IsStatsConnected() { return (sock != INVALID_SOCKET); } /****************************************************************************/ #define CHALLENGEXOR 0x38F371E6 char *GetChallenge(statsgame_t game) { static char challenge[9]; if (game == NULL) game = g_statsgame; if (game == NULL) { create_challenge(connid ^ CHALLENGEXOR,challenge); return challenge; } return game->challenge; } /****************************************************************************/ statsgame_t NewGame(int usebuckets) { statsgame_t game = (statsgame_t)gsimalloc(sizeof (struct statsgame_s)); char data[256]; int len; if (!internal_init) InternalInit(); game->connid = connid; game->sesskey = sesskey++; game->buckets = NULL; game->playernums = NULL; game->teamnums = NULL; game->usebuckets = usebuckets; /* If connected, try to send */ if (sock != INVALID_SOCKET) { char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x6\x17\x3E\x1C\x6\xE\x39\x46\x10\x1D\x3\xD\x16\xB\x3B\x17\x16\x36\x40\x7"; //"\newgame\\connid\%d\sesskey\%d" DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data,respformat,game->connid, game->sesskey); len = DoSend(data, len); if (len <= 0) { CloseStatsConnection(); } create_challenge(game->connid ^ CHALLENGEXOR,game->challenge); } /* If send failed then write to disk */ if (sock == INVALID_SOCKET) { #ifdef ALLOW_DISK char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xD\x19\x3C\x1E\xA\x4\x2\x6\x28\x64\x14"; // "\newgame\\sesskey\%d\challenge\%d"; DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data,respformat,game->sesskey, game->sesskey ^ CHALLENGEXOR); DiskWrite(data, len); game->connid = 0; create_challenge(game->sesskey ^ CHALLENGEXOR,game->challenge); #else gsifree(game); game = NULL; #endif } if (game && game->usebuckets) { game->buckets = NewBucketSet(); game->playernums = ArrayNew(sizeof(int),32,NULL); game->teamnums = ArrayNew(sizeof(int),2,NULL); game->totalplayers = game->totalteams = 0; } if (game) game->sttime = current_time(); g_statsgame = game; return game; } /****************************************************************************/ void FreeGame(statsgame_t game) { if (!game || game == g_statsgame) { game = g_statsgame; g_statsgame = NULL; } if (!game) return; if (game->usebuckets) { if (game->buckets != NULL) FreeBucketSet(game->buckets); if (game->playernums != NULL) ArrayFree(game->playernums); if (game->teamnums != NULL) ArrayFree(game->teamnums); } gsifree(game); } /****************************************************************************/ int SendGameSnapShotA(statsgame_t game, const char *snapshot, int final) { int snaplen; int len; int ret = GE_NOERROR; char *snapcopy; char *data; FIXGAME(game, GE_DATAERROR); /* If using buckets, get the data out of the buckets */ if (game->usebuckets) snapcopy = CreateBucketSnapShot(game->buckets); else snapcopy = goastrdup(snapshot); snaplen = (int)strlen(snapcopy); data = (char *)gsimalloc((unsigned int)snaplen + 256); /* Escape the data */ while (snaplen--) if (snapcopy[snaplen] == '\\') snapcopy[snaplen] = '\x1'; /* If connected, try to send it */ if (sock != INVALID_SOCKET) { // Updated response format to contain connid //char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xC\xA\x16\x35\x2E\x4A\xE\x39\x4\x15\x2C\x15\xC\x4\xC\x31\x2E\x4A\x19"; char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x00\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1"; // "\updgame\\sesskey\%d\done\%d\gamedata\%s" // The above string is now: // "\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s" DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); snaplen = DoSend(data, len); /* If the send failed, close the socket */ if (snaplen <= 0) { CloseStatsConnection(); } } /* If not connected, or send failed, return error or log to disk */ if (sock == INVALID_SOCKET) { #ifdef ALLOW_DISK char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x0\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1\x33\xE\x9\x3F\x45"; //"\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s\dl\1" DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); DiskWrite(data, len); #else ret = GE_NOCONNECT; #endif } gsifree(snapcopy); gsifree(data); return ret; } #ifdef GSI_UNICODE int SendGameSnapShotW(statsgame_t game, const unsigned short*snapshot, int final) { char* snapshot_A = UCS2ToUTF8StringAlloc(snapshot); int result = SendGameSnapShotA(game, snapshot_A, final); gsifree(snapshot_A); return result; } #endif /****************************************************************************/ void NewPlayerA(statsgame_t game, int pnum, char *name) { int i = -1; FIXGAME(game, ;) while (pnum >= ArrayLength(game->playernums)) ArrayAppend(game->playernums, &i); i = game->totalplayers++; /* update the pnum array */ ArrayReplaceAt(game->playernums,&i, pnum); BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_player,pnum); BucketStringOp(game,"player",bo_set,name, bl_player,pnum); } #ifdef GSI_UNICODE void NewPlayerW(statsgame_t game, int pnum, unsigned short *name) { char* name_A = UCS2ToUTF8StringAlloc(name); NewPlayerA(game, pnum, name_A); gsifree(name_A); } #endif /****************************************************************************/ void RemovePlayer(statsgame_t game,int pnum) { FIXGAME(game, ;); BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_player, pnum); } /****************************************************************************/ void NewTeamA(statsgame_t game,int tnum, char *name) { int i = -1; FIXGAME(game, ;) while (tnum >= ArrayLength(game->teamnums)) ArrayAppend(game->teamnums, &i); i = game->totalteams++; /* update the tnum array */ ArrayReplaceAt(game->teamnums,&i, tnum); BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_team,tnum); BucketStringOp(game,"team",bo_set,name, bl_team,tnum); } #ifdef GSI_UNICODE void NewTeamW(statsgame_t game,int tnum, unsigned short *name) { char* name_A = UCS2ToUTF8StringAlloc(name); NewTeamA(game, tnum, name_A); gsifree(name_A); } #endif /****************************************************************************/ void RemoveTeam(statsgame_t game, int tnum) { FIXGAME(game, ;); BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_team, tnum); } /**************************************************************************** * PERSISTENT STORAGE FUNCTIONS ****************************************************************************/ /****************************************************************************/ void PreAuthenticatePlayerPartner(int localid, const char * authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) { char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x11\x1D\x11\x10\x24\x1D\x04\x0F\x0B\x3F\x51\x32\x2C\x1A\x00\x0B\x20\x2E\x4A\x19\x39\x0F\x1D\x25\x2C\x4D\x01"; //\authp\\authtoken\%s\resp\%s\lid\%d"; int len; char data[256]; DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, authtoken, challengeresponse, localid); SendPlayerAuthRequest(data, len, localid, callback, instance); } /****************************************************************************/ void PreAuthenticatePlayerPM(int localid, int profileid, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) { char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; //\authp\\pid\%d\resp\%s\lid\%d int len; char data[256]; DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, profileid, challengeresponse,localid); SendPlayerAuthRequest(data, len, localid, callback, instance); } /****************************************************************************/ void PreAuthenticatePlayerCDA(int localid, const char *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) { char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x1E\x1\x6\x13\xC\x57\x1C\x36\xE\x6\xD\x29\x11\x1B\xD\x24\x75\x1\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; //\authp\\nick\%s\keyhash\%s\resp\%s\lid\%d int len; char data[256]; DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, nick, keyhash, challengeresponse,localid); SendPlayerAuthRequest(data, len, localid, callback, instance); } #ifdef GSI_UNICODE void PreAuthenticatePlayerCDW(int localid, const unsigned short *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) { char* nick_A = UCS2ToUTF8StringAlloc(nick); PreAuthenticatePlayerCDA(localid, nick_A, keyhash, challengeresponse, callback, instance); gsifree(nick_A); } #endif /****************************************************************************/ void GetProfileIDFromCDA(int localid, const char *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) { char respformat[] = "\xC\x15\xA\x1E\x15\xA\x10\x1D\x2C\x6\xC\x1B\x3B\x2E\x4A\x19\x39\x8\x11\x38\x18\x9\x16\x10\xC\x57\x1C\x36\x9\xA\x10\x1D\x55\xC"; //\getpid\\nick\%s\keyhash\%s\lid\%d int len; char data[512]; DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(data, respformat, nick, keyhash,localid); if (sock != INVALID_SOCKET) len = DoSend(data, len); /* If the send failed, close the socket */ if (len <= 0) { CloseStatsConnection(); if (callback) callback(0,-1,0,instance); } else { /* set up the callback */ AddRequestCallback(rt_profilecb, localid, 0,(persisttype_t)0,0,callback, instance); } } #ifdef GSI_UNICODE void GetProfileIDFromCDW(int localid, const unsigned short *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) { char* nick_A = UCS2ToUTF8StringAlloc(nick); GetProfileIDFromCDA(localid, nick_A, keyhash, callback, instance); gsifree(nick_A); } #endif /****************************************************************************/ void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance) { GetPersistDataValuesModifiedA(localid,profileid, type,index,0,"",callback, instance); } void GetPersistDataModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, PersDataCallbackFn callback, void *instance) { GetPersistDataValuesModifiedA(localid,profileid, type,index, modifiedsince, "",callback, instance); } /****************************************************************************/ void SetPersistData(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance) { SetPersistDataHelper(localid, profileid, type, index, data, len, callback, instance, 0); } /****************************************************************************/ void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance) { char respformat[] = "\xC\x15\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\x1D\x29\x1\x33\x4F\x16\x3F\x18\x28\x14\x34\x40\x1C"; char modformat[] = {'\\','m','o','d','\\','%','d','\0'}; //\\mod\\%d //\getpd\\pid\%d\ptype\%d\dindex\%d\keys\%s\lid\%d int len; char data[512]; char tempkeys[256]; char *p; DOXCODE(respformat, sizeof(respformat)-1, enc3); strcpy(tempkeys, keys); //replace the \ chars with #1 for (p = tempkeys; *p != 0; p++) if (*p == '\\') *p = '\x1'; len = sprintf(data, respformat, profileid, type, index, tempkeys, localid); if (modifiedsince != 0) //append it { len += sprintf(data + len, modformat, modifiedsince); } if (sock != INVALID_SOCKET) len = DoSend(data, len); /* If the send failed, close the socket */ if (len <= 0) { CloseStatsConnection(); if (callback) callback(localid, profileid,type, index, 0,0,"",0,instance); } else { /* set up the callback */ AddRequestCallback(rt_datacb, localid, profileid,type, index, callback, instance); } } void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance) { GetPersistDataValuesModifiedA(localid,profileid, type,index, 0, keys,callback, instance); } #ifdef GSI_UNICODE void GetPersistDataValuesModifiedW(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, unsigned short*keys, PersDataCallbackFn callback, void *instance) { char* keys_A = UCS2ToUTF8StringAlloc(keys); GetPersistDataValuesModifiedA(localid, profileid, type, index, modifiedsince, keys_A, callback, instance); gsifree(keys_A); } #endif #ifdef GSI_UNICODE void GetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, unsigned short*keys, PersDataCallbackFn callback, void *instance) { GetPersistDataValuesModifiedW(localid,profileid, type,index, 0, keys,callback, instance); } #endif /****************************************************************************/ void SetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, const char *keyvalues, PersDataSaveCallbackFn callback, void *instance) { SetPersistDataHelper(localid, profileid, type, index, keyvalues, (int)strlen(keyvalues) + 1, callback, instance, 1); } #ifdef GSI_UNICODE void SetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, const unsigned short *keyvalues, PersDataSaveCallbackFn callback, void *instance) { char* keyvalues_A = UCS2ToUTF8StringAlloc(keyvalues); SetPersistDataValuesA(localid, profileid, type, index, keyvalues_A, callback, instance); gsifree(keyvalues_A); } #endif /****************************************************************************/ int PersistThink() { int len; int processed; if (sock == INVALID_SOCKET) return 0; if (stats_initstate != init_complete) return 0; while (SocketReadable(sock)) { if (rcvmax - rcvlen < 128) //make sure there are at least 128 bytes gsifree in the buffer { if (rcvmax < 256) rcvmax = 256; else rcvmax *= 2; rcvbuffer = gsirealloc(rcvbuffer, (unsigned int)(rcvmax+1)); if (rcvbuffer == NULL) return 0; //errcon } len = recv(sock, rcvbuffer + rcvlen, rcvmax - rcvlen, 0); if (len <= 0) //lost the connection { CloseStatsConnection(); return 0; } rcvlen += len; rcvbuffer[rcvlen] = 0; processed = ProcessInBuffer(rcvbuffer, rcvlen); if (processed == rcvlen) //then we can just zero it rcvlen = 0; else { //shift the remaining data down memmove(rcvbuffer,rcvbuffer + processed, (unsigned int)(rcvlen - processed)); rcvlen -= processed; } } if (sock == INVALID_SOCKET) return 0; else return 1; } /****************************************************************************/ int StatsThink() { return PersistThink(); } /**************************************************************************** * UTILITY FUNCTIONS ****************************************************************************/ void InternalInit() { internal_init = 1; enc1[0] = 'G'; enc3[0] = 'P'; finalstr[0] = '\\'; #ifdef ALLOW_DISK statsfile[0] = 'g'; enc2[0] = 'I'; #endif } static int SendChallengeResponse(const char *indata, int gameport) { static char challengestr[] = {'\0','h','a','l','l','e','n','g','e','\0'}; char *challenge; char resp[128]; char md5val[33]; /* make this harder to find in the string table */ char respformat[] = "\xC\x13\x1A\x1E\xD\x3F\x28\x26\x11\x5\x0\x16\x31\x1F\xA\x36\x40\x10\x28\x33\x15\x1B\x15\x17\x3E\x1\xA\x36\x40\x10\x28\x31\x1F\x1A\x11\x24\x75\x16\x33\x3\x1\x3F\x45"; /* \auth\\gamename\%s\response\%s\port\%d\id\1 */ int len; challengestr[0] = 'c'; challenge = value_for_key(indata,challengestr ); if (challenge == NULL) { closesocket(sock); return GE_DATAERROR; } len = sprintf(resp, "%d%s",g_crc32(challenge,(int)strlen(challenge)), gcd_secret_key); MD5Digest((unsigned char *)resp, (unsigned int)len, md5val); DOXCODE(respformat, sizeof(respformat)-1, enc3); len = sprintf(resp,respformat,gcd_gamename, md5val, gameport); if ( DoSend(resp, len) <= 0 ) { closesocket(sock); return GE_NOCONNECT; } return GE_NOERROR; } // 09-13-2004 BED Unused parameter removed static int RecvSessionKey() { /* get the response */ static char sesskeystr[] = {'\0','e','s','s','k','e','y','\0'}; char resp[128]; char *stext; int len = (int)recv(sock, resp,128,0); if (gsiSocketIsError(len)) { int anError = GOAGetLastError(sock); closesocket(sock); if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) return GE_NOCONNECT; else return GE_DATAERROR; //temp fix in case len == -1, SOCKET_ERROR } resp[len] = 0; DOXCODE(resp, len, enc1); sesskeystr[0] = 's'; stext = value_for_key(resp, sesskeystr); if (stext == NULL) { closesocket(sock); return GE_DATAERROR; } else connid = atoi(stext); return GE_NOERROR; } static int DoSend(char *data, int len) { int sent = 0; DOXCODE(data,len, enc1); strcpy(data+len,finalstr); // Loop to make sure async send goes through! while(sent < (len+7)) { // Send remaining data int ret = send(sock, (data+sent), (len+7-sent), 0); if (gsiSocketIsError(ret)) { int anError = GOAGetLastError(sock); if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) return anError; } else if (ret == 0) { // socket was closed return -1; } else sent += ret; }; return sent; } #ifdef ALLOW_DISK /* Note: lots of this is byte order and type size specific, but it shouldn't matter since the data is read/written always on the same machine */ #define DISKLENXOR 0x70F33A5F static void CheckDiskFile() { FILE *f; char *line; char *alldata; int len, check, alllen, fsize; char filemode[3]; /* hide our file access from the string table */ filemode[0] = 'r'; filemode[1] = 'b'; filemode[2] = 0; f = fopen(statsfile,filemode); if (!f) return; /* get the size */ fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); /* make room for the whole thing */ alldata = (char *)gsimalloc(fsize + 2); alldata[0] = 0; alllen = 0; while (!feof(f) && !ferror(f)) { /* read the check and line values */ if (fread(&check,sizeof(check),1,f) == 0 || fread(&len,sizeof(len),1,f) == 0) break; len ^= DISKLENXOR; line = (char *)gsimalloc(len + 1); /* read the data */ if (fread(line, 1, len, f) != (size_t)len) break; line[len] = 0; /* decode for checking */ DOXCODE(line, len, enc2); /* double "check" */ if (check != g_crc32(line, len)) { gsifree(line); break; } /* encode for xmission */ DOXCODE(line, len, enc1); memcpy(alldata + alllen, line, len); alllen += len; memcpy(alldata + alllen, finalstr, 7); alllen += 7; gsifree(line); } fclose(f); /* try to send */ len = send(sock, alldata, alllen, 0); if (len <= 0) { closesocket(sock); sock = INVALID_SOCKET; } else remove(statsfile); } static void DiskWrite(char *line, int len) { FILE *f; int check; int temp; char filemode[3]; /* hide our file access from the string table */ filemode[0] = 'a'; filemode[1] = 'b'; filemode[2] = 0; f = fopen(statsfile,filemode); if (!f) return; check = g_crc32(line, len); fwrite(&check, sizeof(check),1,f); temp = len ^ DISKLENXOR; fwrite(&temp, sizeof(temp), 1, f); DOXCODE(line, len, enc2); fwrite(line, 1, len, f); fclose(f); } #endif /* ALLOW_DISK */ /* simple xor encoding */ static void xcode_buf(char *buf, int len) { int i; char *pos = enc; for (i = 0 ; i < len ; i++) { buf[i] ^= *pos++; if (*pos == 0) pos = enc; } } #define MULTIPLIER -1664117991 static int g_crc32(char *s, int len) { int i; int hashcode = 0; for (i = 0; i < len; i++) hashcode = hashcode * MULTIPLIER + s[i]; return hashcode; } static void create_challenge(int challenge, char chstr[9]) { char *p = chstr; sprintf(chstr, "%08x",challenge); while (*p != 0) { *p = (char)((*p) + ('A' - '0') + (p-chstr)); p++; } } /* value_for_key: this returns a value for a certain key in s, where s is a string containing key\value pairs. If the key does not exist, it returns NULL. Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ static char *value_for_key(const char *s, const char *key) { static int valueindex; char *pos,*pos2; char keyspec[256]="\\"; static char value[2][256]; valueindex ^= 1; strcat(keyspec,key); strcat(keyspec,"\\"); pos = strstr(s,keyspec); if (!pos) return NULL; pos += strlen(keyspec); pos2 = value[valueindex]; while (*pos && *pos != '\\') *pos2++ = *pos++; *pos2 = '\0'; return value[valueindex]; } /* like value_for_key, but returns an empty string instead of NULL in the not-found case */ static char *value_for_key_safe(const char *s, const char *key) { char *temp; temp = value_for_key(s, key); if (!temp) return ""; else return temp; } /* Return a sockaddrin for the given host (numeric or DNS) and port) Returns the hostent in savehent if it is not NULL */ static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) { struct hostent *hent = NULL; memset(saddr,0,sizeof(struct sockaddr_in)); saddr->sin_family = AF_INET; saddr->sin_port = htons((unsigned short)port); if (host == NULL) saddr->sin_addr.s_addr = INADDR_ANY; else saddr->sin_addr.s_addr = inet_addr(host); if (saddr->sin_addr.s_addr == INADDR_NONE) { hent = gethostbyname(host); if (!hent) return 0; saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; } if (savehent != NULL) *savehent = hent; return 1; } /* adds a request callback to the list */ static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance) { serverreq_t req; req.callback = callback; req.instance = instance; req.localid = localid; req.reqtype = reqtype; req.profileid = profileid; req.pdtype = pdtype; req.pdindex = pdindex; if (serverreqs == NULL) //create the callback array { serverreqs = ArrayNew(sizeof(serverreq_t),2,NULL); } ArrayAppend(serverreqs,&req); } /* sends the player authentication request (GP or CD) */ static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance) { int sentlen = 0; char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; //"Connection Lost" if (sock != INVALID_SOCKET) sentlen = DoSend(data, len); /* If the send failed, close the socket */ if (sentlen <= 0) { CloseStatsConnection(); DOXCODE(connerror, sizeof(connerror)-1, enc3); if (callback) { #ifndef GSI_UNICODE callback(localid, 0,0, connerror ,instance); #else unsigned short connerror_W[] = L"\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; callback(localid, 0, 0, connerror_W, instance); #endif } } else { /* set up the callback */ AddRequestCallback(rt_authcb, localid, 0,(persisttype_t)0,0,callback, instance); } } /* send a set request, if kvset, then only those keys/values will bet updated */ static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset) { char respformat[] = "\xC\x1\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\xE\xC\x57\xB\x36\x9\xA\x10\x1D\x55\xC\x39\x14\x35\x1C\x8\x1E\xD\x3F\x51\x25\x2C\xC\x4\xC\x31\x2E"; //\setpd\\pid\%d\ptype\%d\dindex\%d\kv\%d\lid\%d\length\%d\data\ -- int tlen; char tdata[512]; char *senddata; DOXCODE(respformat, sizeof(respformat)-1, enc3); if (type == pd_private_ro || type == pd_public_ro) { //can't set read-only types, check that client side if (callback) callback(localid, profileid, type, index, 0, 0, instance); return; } tlen = sprintf(tdata, respformat, profileid, type, index, kvset, localid, len); if (tlen + len < 480) //we have enough room to put it in the data block { memcpy(tdata + tlen, data, (unsigned int)len); senddata = tdata; } else //need to alloc a temp buffer { senddata = (char *)gsimalloc((unsigned int)(len + tlen + 256)); memcpy(senddata, tdata, (unsigned int)tlen); memcpy(senddata + tlen, data, (unsigned int)len); } if (sock != INVALID_SOCKET) tlen = DoSend(senddata, tlen + len); /* If the send failed, close the socket */ if (tlen <= 0) { CloseStatsConnection(); if (callback) callback(localid, profileid, type, index, 0, 0, instance); } else { /* set up the callback */ AddRequestCallback(rt_savecb, localid, profileid,type, index, callback, instance); } if (senddata != tdata) //if we alloc'd before sending gsifree(senddata); } /* returns 1 if the socket is readable, 0 otherwise */ static int SocketReadable(SOCKET s) { return CanReceiveOnSocket(s); /* fd_set set; struct timeval tv = {0,0}; int n; if (s == INVALID_SOCKET) return 0; FD_ZERO(&set); FD_SET(s, &set); n = select(FD_SETSIZE, &set, NULL, NULL, &tv); return n; */ } /* find the \final\ string */ static char *FindFinal(char *buff, int len) { char *pos = buff; while (pos - buff < len - 6) { if (pos[0] == '\\' && pos[1] == 'f' && pos[2] == 'i' && pos[3] == 'n' && pos[4] == 'a' && pos[5] == 'l' && pos[6] == '\\') { return pos; } else pos++; } return NULL; } /* find the request in the callback list */ static int FindRequest(reqtype_t reqtype, int localid, int profileid) { int i; serverreq_t *req; if (serverreqs == NULL) return -1; for (i = 0 ; i < ArrayLength(serverreqs); i++) { req = (serverreq_t *)ArrayNth(serverreqs, i); if (req->reqtype == reqtype && req->localid == localid && req->profileid == profileid) return i; } return -1; } /* process the playerauth result */ static void ProcessPlayerAuth(const char *buf, int len) { // \\pauthr\\100000\\lid\\1 int reqindex; char *errmsg; int pid; int lid; pid = atoi(value_for_key_safe(buf,"pauthr")); lid = atoi(value_for_key_safe(buf,"lid")); errmsg = value_for_key_safe(buf,"errmsg"); reqindex = FindRequest(rt_authcb,lid,0); if (reqindex == -1) return; ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; CallReqCallback(reqindex,(pid > 0),0, errmsg,0); GSI_UNUSED(len); } /* process the get profileid result */ static void ProcessGetPid(const char *buf, int len) { // \\getpidr\\100000\\lid\\1 int reqindex; int pid; int lid; pid = atoi(value_for_key_safe(buf,"getpidr")); lid = atoi(value_for_key_safe(buf,"lid")); reqindex = FindRequest(rt_profilecb,lid,0); if (reqindex == -1) return; ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; CallReqCallback(reqindex,(pid > 0),0,NULL,0); GSI_UNUSED(len); } /* process the get data result */ static void ProcessGetData(const char *buf, int len) { // \\getpdr\\1\\lid\\1\\mod\\1234\\length\\5\\data\\mydata\\final int reqindex; int pid; int lid; int success; int length; time_t modified; char *data; success = atoi(value_for_key_safe(buf,"getpdr")); lid = atoi(value_for_key_safe(buf,"lid")); pid = atoi(value_for_key_safe(buf,"pid")); modified = atoi(value_for_key_safe(buf,"mod")); reqindex = FindRequest(rt_datacb,lid,pid); if (reqindex == -1) return; length = atoi(value_for_key_safe(buf,"length")); data = strstr(buf,"\\data\\"); if (!data) { length = 0; data = ""; } else data += 6; //skip the key CallReqCallback(reqindex,success,modified, data,length); GSI_UNUSED(len); } /* process the set data result */ static void ProcessSetData(const char *buf, int len) { // \\setpdr\\1\\lid\\2\\pid\\100000\\mod\\12345 int reqindex; int pid; int lid; int success; int modified; success = atoi(value_for_key_safe(buf,"setpdr")); pid = atoi(value_for_key_safe(buf,"pid")); lid = atoi(value_for_key_safe(buf,"lid")); modified = atoi(value_for_key_safe(buf,"mod")); reqindex = FindRequest(rt_savecb,lid,pid); if (reqindex == -1) return; CallReqCallback(reqindex,success,modified,NULL,0); GSI_UNUSED(len); } /* process a single statement */ static void ProcessStatement(char *buff, int len) { //determine the type buff[len] = 0; // printf("GOT: %s\n",buff); if (strncmp(buff,"\\pauthr\\",8) == 0) { ProcessPlayerAuth(buff, len); } else if (strncmp(buff,"\\getpidr\\",9) == 0) { ProcessGetPid(buff, len); } else if (strncmp(buff,"\\getpidr\\",9) == 0) { ProcessGetPid(buff, len); } else if (strncmp(buff,"\\getpdr\\",8) == 0) { ProcessGetData(buff, len); } else if (strncmp(buff,"\\setpdr\\",8) == 0) { ProcessSetData(buff, len); } } /* processes statements in the buffer and returns amount processed */ // 09-13-2004 BED Modified loop to silence compiler warning static int ProcessInBuffer(char *buff, int len) { char *pos; int oldlen = len; pos = FindFinal(buff, len); //while (len > 0 && (pos = FindFinal(buff, len))) while ((len > 0) && (pos != NULL)) { DOXCODE(buff,pos - buff, enc1); ProcessStatement(buff, pos - buff); len -= (pos - buff) + 7; buff = pos + 7; //skip the final if (len>0) pos = FindFinal(buff, len); } return oldlen - len; //amount processed } /* call a single callback function */ static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length) { serverreq_t *req; if (reqindex < 0 || reqindex >= ArrayLength(serverreqs)) return; req = (serverreq_t *)ArrayNth(serverreqs, reqindex); if (req->callback) switch (req->reqtype) { case rt_authcb: #ifndef GSI_UNICODE ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data, req->instance); #else { unsigned short* data_W = UTF8ToUCS2StringAlloc(data); ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data_W, req->instance); gsifree(data_W); } #endif break; case rt_datacb: ((PersDataCallbackFn )req->callback)(req->localid,req->profileid,req->pdtype, req->pdindex, success, modified, data, length, req->instance); break; case rt_savecb: ((PersDataSaveCallbackFn )req->callback)(req->localid, req->profileid, req->pdtype, req->pdindex,success, modified, req->instance); break; case rt_profilecb: ((ProfileCallbackFn )req->callback)(req->localid, req->profileid, success, req->instance); break; } ArrayDeleteAt(serverreqs,reqindex); } /* if we get disconnected while callbacks are still pending, make sure we call all of them, with a success of 0 */ static void ClosePendingCallbacks() { int i; if (serverreqs == NULL) return; for (i = ArrayLength(serverreqs) - 1 ; i >= 0 ; i--) { char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; //"Connection Lost" DOXCODE(connerror, sizeof(connerror)-1, enc3); CallReqCallback(i,0,0,connerror,0); } ArrayFree(serverreqs); serverreqs = NULL; } /****************************************************************************/ /* BUCKET FUNCTIONS */ /****************************************************************************/ int GetTeamIndex(statsgame_t game, int tnum) { FIXGAME(game, tnum); return *(int *)ArrayNth(game->teamnums,tnum); } int GetPlayerIndex(statsgame_t game, int pnum) { FIXGAME(game, pnum); return *(int *)ArrayNth(game->playernums,pnum); } static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) { int *ret; DoFunc(func, game, name, &value, bt_int, ret); GSI_UNUSED(index); return *(int *)ret; } static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) { double *ret; DoFunc(func, game, name, &value, bt_float, ret); GSI_UNUSED(index); return *(double *)ret; } static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) { char *ret; DoFunc(func, game, name, value, bt_string, ret); GSI_UNUSED(index); return ret; } static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) { char fullname[64]; sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); return ServerOpInt(game, fullname, func, value, index); } static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) { char fullname[64]; sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); return ServerOpFloat(game, fullname, func, value, index); } static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) { char fullname[64]; sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); return ServerOpString(game, fullname, func, value, index); } static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) { char fullname[64]; sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); return ServerOpInt(game, fullname, func, value, index); } static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) { char fullname[64]; sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); return ServerOpFloat(game, fullname, func, value, index); } static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) { char fullname[64]; sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); return ServerOpString(game, fullname, func, value, index); } static char *CreateBucketSnapShot(bucketset_t buckets) { return DumpBucketSet(buckets); } #ifdef __cplusplus } #endif