mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
1681 lines
46 KiB
C
1681 lines
46 KiB
C
/******
|
|
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
|