openmohaa/code/gamespy/serverbrowsing/sb_serverlist.c

1817 lines
46 KiB
C
Raw Permalink Normal View History

2023-02-04 21:00:01 +01:00
#include "sb_serverbrowsing.h"
#include "sb_internal.h"
#include "sb_ascii.h"
#define SERVER_GROWBY 100
//for the master server info
#define INCOMING_BUFFER_SIZE 4096
#define MAX_OUTGOING_REQUEST_SIZE (MAX_FIELD_LIST_LEN + MAX_FILTER_LEN + 255)
static SBServerList *g_sortserverlist; //global serverlist for sorting info!!
//private function used to compare the key values based on a previously defined sortkey
static int prevKeyCompare(SBServer server1, SBServer server2)
{
const char *prevsortkey = (const char *)g_sortserverlist->prevsortinfo.sortkey;
int diff;
double f;
//test which type of sort
switch(g_sortserverlist->prevsortinfo.comparemode)
{
case sbcm_int:
diff = SBServerGetIntValueA(server1, prevsortkey, 0) -
SBServerGetIntValueA(server2, prevsortkey, 0);
break;
case sbcm_float:
f = SBServerGetFloatValueA(server1, prevsortkey, 0) -
SBServerGetFloatValueA(server2, prevsortkey, 0);
if (!g_sortserverlist->sortascending)
f = -f;
if ((float)f > (float)0.0)
return 1;
else if ((float)f < (float)0.0)
return -1;
else
return 0;
//break;
case sbcm_strcase:
diff = strcmp(SBServerGetStringValueA(server1, prevsortkey, ""),
SBServerGetStringValueA(server2, prevsortkey, ""));
break;
case sbcm_stricase:
diff = strcasecmp(SBServerGetStringValueA(server1, prevsortkey, ""),
SBServerGetStringValueA(server2, prevsortkey, ""));
break;
default:
return 0;
}
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
/****
Comparision Functions
***/
static int GS_STATIC_CALLBACK IntKeyCompare(const void *entry1, const void *entry2)
{
SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2;
int diff;
const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey;
diff = SBServerGetIntValueA(server1, currsortkey, 0) -
SBServerGetIntValueA(server2, currsortkey, 0);
if (diff == 0) //if equal, sort by previous sort value to retain earlier sort
return prevKeyCompare(server1, server2);
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
static int GS_STATIC_CALLBACK FloatKeyCompare(const void *entry1, const void *entry2)
{
SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2;
double f;
const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey;
f = SBServerGetFloatValueA(server1, currsortkey, 0) -
SBServerGetFloatValueA(server2, currsortkey, 0);
//if equal, sort by previous sort value to retain earlier sort
if ( !((float)f > (float)0.0) && !((float)f < (float)0.0) )
return prevKeyCompare(server1, server2);
if (!g_sortserverlist->sortascending)
f = -f;
if ((float)f > (float)0.0)
return 1;
else if ((float)f < (float)0.0)
return -1;
else
return 0;
}
static int GS_STATIC_CALLBACK StrCaseKeyCompare(const void *entry1, const void *entry2)
{
SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2;
int diff;
const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey;
diff = strcmp(SBServerGetStringValueA(server1, currsortkey, ""),
SBServerGetStringValueA(server2, currsortkey, ""));
if (diff == 0) //if equal, sort by previous sort value to retain earlier sort
return prevKeyCompare(server1, server2);
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
static int GS_STATIC_CALLBACK StrNoCaseKeyCompare(const void *entry1, const void *entry2)
{
SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2;
int diff;
const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey;
diff = strcasecmp(SBServerGetStringValueA(server1, currsortkey, ""),
SBServerGetStringValueA(server2, currsortkey, ""));
if (diff == 0) //if equal, sort by previous sort value to retain earlier sort
return prevKeyCompare(server1, server2);
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
/* ServerListSort
-----------------
Sort the server list in either ascending or descending order using the
specified comparemode.
sortkey can be a normal server key, or "ping" or "hostaddr" */
void SBServerListSort(SBServerList *slist, SBBool ascending, SortInfo sortinfo)
{
ArrayCompareFn comparator;
switch (sortinfo.comparemode)
{
case sbcm_int: comparator = IntKeyCompare;
break;
case sbcm_float: comparator = FloatKeyCompare;
break;
case sbcm_strcase: comparator = StrCaseKeyCompare;
break;
case sbcm_stricase: comparator = StrNoCaseKeyCompare;
break;
default:
comparator = StrNoCaseKeyCompare;
}
//set last used sortkey
if (_tcslen(slist->prevsortinfo.sortkey) == 0) //set prev to current if not initialized
slist->prevsortinfo = sortinfo;
else if (strcmp((const char *)sortinfo.sortkey, (const char *)slist->currsortinfo.sortkey) != 0)
slist->prevsortinfo = slist->currsortinfo; //only set new sort if different than prev
slist->currsortinfo = sortinfo;
slist->sortascending = ascending;
g_sortserverlist = slist;
ArraySort(slist->servers,comparator);
}
void SBServerListAppendServer(SBServerList *slist, SBServer server)
{
ArrayAppend(slist->servers, &server);
slist->ListCallback(slist, slc_serveradded, server, slist->instance);
}
int SBServerListFindServer(SBServerList *slist, SBServer findserver)
{
int numservers;
int i;
numservers = ArrayLength(slist->servers);
for (i = 0 ; i < numservers ; i++)
{
if (findserver == *(SBServer *)ArrayNth(slist->servers, i))
{
return i;
}
}
return -1;
}
int SBServerListFindServerByIP(SBServerList *slist, goa_uint32 ip, unsigned short port)
{
int numservers;
SBServer server;
int i;
numservers = ArrayLength(slist->servers);
for (i = 0 ; i < numservers ; i++)
{
server = *(SBServer *)ArrayNth(slist->servers, i);
if (SBServerGetPublicInetAddress(server) == ip && SBServerGetPublicQueryPortNBO(server) == port)
{
return i;
}
}
return -1;
}
//add to the singly linked list of dead servers
static void AddServerToDeadlist(SBServerList *slist, SBServer server)
{
if (slist->deadlist == NULL)
{
SBServerSetNext(server, NULL);
} else
SBServerSetNext(server, slist->deadlist);
slist->deadlist = server;
}
void SBServerListRemoveAt(SBServerList *slist, int index)
{
SBServer server = *(SBServer *)ArrayNth(slist->servers, index);
slist->ListCallback(slist, slc_serverdeleted, server, slist->instance);
//need to remove it...
ArrayDeleteAt(slist->servers, index);
//now add it to the dead list
AddServerToDeadlist(slist, server);
}
int SBServerListCount(SBServerList *slist)
{
return ArrayLength(slist->servers);
}
SBServer SBServerListNth(SBServerList *slist, int i)
{
return *(SBServer *)ArrayNth(slist->servers, i);
}
void SBFreeDeadList(SBServerList *slist)
{
SBServer server, next;
if (slist->deadlist == NULL)
return;
server = slist->deadlist;
while (server != NULL)
{
next = SBServerGetNext(server);
SBServerFree(&server);
server = next;
}
slist->deadlist = NULL;
}
void SBServerListClear(SBServerList *slist)
{
//we need to add each server to the dead list so it can be freed after the clear
int i;
int nservers = ArrayLength(slist->servers);
for (i = 0 ; i < nservers ; i++)
{
AddServerToDeadlist(slist, *(SBServer *)ArrayNth(slist->servers, i));
}
ArrayClear(slist->servers);
//now free the dead list
SBFreeDeadList(slist);
}
void SBAllocateServerList(SBServerList *slist)
{
slist->servers = ArrayNew(sizeof(SBServer ), SERVER_GROWBY, NULL); //don't free the server automatically - it goes into the dead list
slist->deadlist = NULL;
}
const char *SBRefStr(SBServerList *slist, const char *str)
{
SBRefString ref, *val;
ref.str = str;
val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref);
if (val != NULL)
{
val->refcount++;
return val->str;
}
//else we need to add.
ref.str = goastrdup(str);
ref.refcount = 1;
#ifdef GSI_UNICODE
ref.str_W = UTF8ToUCS2StringAlloc(str);
#endif
TableEnter(SBRefStrHash(slist), &ref);
return ref.str;
}
void SBReleaseStr(SBServerList *slist, const char *str)
{
SBRefString ref, *val;
ref.str = str;
val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref);
assert(val != NULL);
if (val == NULL)
return; //not found!
val->refcount--;
if (val->refcount == 0)
TableRemove(SBRefStrHash(slist), &ref);
}
#ifdef VENGINE_SUPPORT
#define FTABLE_ASSIGN
#include "../../VEngine/ve_gm3ftable.h"
#endif
#ifdef VENGINE_SUPPORT
#define FTABLE_DEFINES
#include "../../VEngine/ve_gm3ftable.h"
#endif
//global pointer to alternate master server name/IP
char *SBOverrideMasterServer = NULL;
int NTSLengthSB(char *buf, int len)
{
int i;
for (i = 0 ; i < len ; i++)
{
if (buf[i] == '\0') //found a full NTS
return i + 1; //return the length including the null
}
return -1;
}
void SBServerListInit(SBServerList *slist, const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, SBBool lanBrowse, SBListCallBackFn callback, void *instance)
{
assert(slist != NULL);
// 11-03-2004 : Added by Saad Nader
// fix for LANs and unnecessary availability check
///////////////////////////////////////////////////
if (lanBrowse == SBFalse)
{
if(__GSIACResult != GSIACAvailable)
return;
}
#ifdef VENGINE_SUPPORT
SBServerListSetPointers(slist);
#endif
slist->state = sl_disconnected;
SBAllocateServerList(slist);
SBRefStrHash(slist); //make sure it's initialized
strcpy(slist->queryforgamename, queryForGamename);
strcpy(slist->queryfromgamename, queryFromGamename);
strcpy(slist->queryfromkey, queryFromKey);
slist->ListCallback = callback;
slist->MaploopCallback = NULL; //populate when requested
assert(callback != NULL);
slist->instance = instance;
slist->mypublicip = 0;
slist->slsocket = INVALID_SOCKET;
slist->inbuffer = NULL;
slist->inbufferlen = 0;
slist->keylist = NULL;
slist->expectedelements = -1;
slist->numpopularvalues = 0;
slist->srcip = 0;
slist->fromgamever = queryFromVersion;
#ifdef GSI_UNICODE
slist->lasterror = NULL;
slist->lasterror_W = NULL;
_tcscpy(slist->currsortinfo.sortkey, (const unsigned short *)"");
_tcscpy(slist->prevsortinfo.sortkey, (const unsigned short *)"");
#else
_tcscpy(slist->currsortinfo.sortkey, "");
_tcscpy(slist->prevsortinfo.sortkey, "");
#endif
SBSetLastListErrorPtr(slist, "");
slist->mLanAdapterOverride = NULL;
slist->backendgameflags = QR2_USE_QUERY_CHALLENGE;
srand((unsigned int)current_time());
SocketStartUp();
}
static void ErrorDisconnect(SBServerList *slist)
{
static char* QUERY_ERROR = "Query Error: ";
// check to see if there is a Query Error string in the inbuffer
if ((slist->inbufferlen > 0) && ((unsigned int)slist->inbufferlen > strlen(QUERY_ERROR)) &&
(0 == strncmp(slist->inbuffer, QUERY_ERROR, strlen(QUERY_ERROR))))
{
// call the callback with queryerror first
SBSetLastListErrorPtr(slist, slist->inbuffer + strlen(QUERY_ERROR));
slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance);
}
//call the callback..
slist->ListCallback(slist, slc_disconnected, SBNullServer, slist->instance);
SBServerListDisconnect(slist);
}
#define MULTIPLIER -1664117991
static int StringHash(const char *s, int numbuckets)
{
goa_uint32 hashcode = 0;
while (*s != 0)
{
hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s));
s++;
}
return (int)(hashcode % numbuckets);
}
static SBError ServerListConnect(SBServerList *slist)
{
struct sockaddr_in saddr;
struct hostent *hent;
char masterHostname[128];
int masterIndex;
masterIndex = StringHash(slist->queryforgamename, NUM_MASTER_SERVERS);
if (SBOverrideMasterServer != NULL)
strcpy(masterHostname, SBOverrideMasterServer);
else //use the default format...
sprintf(masterHostname,"%s.ms%d." GSI_DOMAIN_NAME, slist->queryforgamename, masterIndex);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(MSPORT2);
saddr.sin_addr.s_addr = inet_addr(masterHostname);
if (saddr.sin_addr.s_addr == INADDR_NONE)
{
hent = gethostbyname(masterHostname);
if (!hent)
return sbe_dnserror;
memcpy(&saddr.sin_addr.s_addr,hent->h_addr_list[0], sizeof(saddr.sin_addr.s_addr));
}
if (slist->slsocket == INVALID_SOCKET)
{
slist->slsocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (slist->slsocket == INVALID_SOCKET)
return sbe_socketerror;
}
if (connect ( slist->slsocket, (struct sockaddr *) &saddr, sizeof saddr ) != 0)
{
closesocket(slist->slsocket);
slist->slsocket = INVALID_SOCKET;
return sbe_connecterror;
}
//else we are connected
return sbe_noerror;
}
static void BufferAddNTS(char **buffer, const char *str, int *len)
{
int slen;
if (str == NULL)
str = "";
slen = (int)strlen(str) + 1;
memcpy(*buffer, str, (size_t)slen);
(*len) += slen;
(*buffer) += slen;
}
static void BufferAddByte(char **buffer, unsigned char bval, int *len)
{
*(*buffer) = (char)bval;
(*len) += 1;
(*buffer) += 1;
}
static void BufferAddInt(char **buffer, int ival, int *len)
{
memcpy(*buffer, &ival, 4);
(*len) += 4;
(*buffer) += 4;
}
// This a utility to write ival as a little-endian (PC) byte order number
static void BufferAddIntLE(char **buffer, int ival, int *len)
{
unsigned int ivalNBO = htonl(ival);
unsigned int ivalBE = 0;
ivalBE |= ((0x000000FF&ivalNBO)<<24);
ivalBE |= ((0x0000FF00&ivalNBO)<< 8);
ivalBE |= ((0x00FF0000&ivalNBO)>> 8);
ivalBE |= ((0xFF000000&ivalNBO)>>24);
BufferAddInt(buffer, (int)ivalBE, len);
}
static void BufferAddData(char **buffer, char *data, int dlen, int *len)
{
memcpy(*buffer, data, (size_t)dlen);
(*len) += dlen;
(*buffer) += dlen;
}
#define CALCULATEODDMODE(buffer, i, oddmode) ((buffer[i-1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i-1] < buffer[0]) ? 1 : 0));
static void SetupListChallenge(SBServerList *slist)
{
int i;
int oddmode;
slist->mychallenge[0] = (char)(33 + rand() % 93); //use chars in the range 33 - 125
oddmode = 0;
for (i = 1; i < LIST_CHALLENGE_LEN ; i++)
{
oddmode = CALCULATEODDMODE(slist->mychallenge,i, oddmode);
slist->mychallenge[i] = (char)(33 + rand() % 93); //use chars in the range 33 - 125
//if oddmode make sure the char is odd, otherwise make sure it's even
if ((oddmode && (slist->mychallenge[i] & 1) == 0) || (!oddmode && ((slist->mychallenge[i] & 1) == 1)))
slist->mychallenge[i]++;
}
}
static SBError SendWithRetry(SBServerList *slist, char *data, int len)
{
SBError err;
int ret = 0;
int retryCount = 1;
while (retryCount >= 0)
{
retryCount--;
ret = send(slist->slsocket, data, len, 0);
if (ret <= 0 && retryCount >= 0) //error! try to reconnect
{
if (slist->inbufferlen > 0)
break;
else
SBServerListDisconnect(slist);
err = SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0);
if (err != sbe_noerror)
{
ErrorDisconnect(slist);
return err; //couldn't reconnect
}
} else
break; //send ok
}
#ifdef INSOCK
GSI_UNUSED(data);
GSI_UNUSED(len);
#endif
if (ret <= 0)
return sbe_connecterror;
else
return sbe_noerror;
}
/*
1 bytes - message type (0)
2 bytes - message length
Protocol Version - Byte
Requested Encoding - Byte
Query For Game - NTS
Query From Game - NTS
Server Challenge - 8 bytes
Filter - NTS
Field List - NTS
Options - 4 bytes
Send Fields for non-NAT servers too (TurboQuery!)
No-server-slist-requested (just initiate connection and/or receive push updates)
Push Live-Updates
Log Alternate Source IP
*/
#define ALTERNATE_SOURCE_IP 8
SBError SBServerListConnectAndQuery(SBServerList *slist, const char *fieldList, const char *serverFilter, int options, int maxServers)
{
SBError err;
int ret;
int requestLen;
unsigned short netLen;
char *requestBuf;
char outgoingRequest[MAX_OUTGOING_REQUEST_SIZE + 1];
assert(slist->state == sl_disconnected);
if (fieldList == NULL)
fieldList = "";
if (serverFilter == NULL)
serverFilter = "";
if (strlen(fieldList) > MAX_FIELD_LIST_LEN)
return sbe_paramerror;
if (strlen(serverFilter) > MAX_FILTER_LEN)
return sbe_paramerror;
err = ServerListConnect(slist);
if (err != sbe_noerror)
return err;
slist->queryoptions = options;
//setup our challenge value
SetupListChallenge(slist);
//skip the length field for now
requestLen = 2;
requestBuf = outgoingRequest + 2;
BufferAddByte(&requestBuf, SERVER_LIST_REQUEST, &requestLen);
BufferAddByte(&requestBuf, LIST_PROTOCOL_VERSION, &requestLen);
BufferAddByte(&requestBuf, LIST_ENCODING_VERSION, &requestLen);
BufferAddIntLE(&requestBuf, slist->fromgamever, &requestLen);
BufferAddNTS(&requestBuf, slist->queryforgamename, &requestLen);
BufferAddNTS(&requestBuf, slist->queryfromgamename, &requestLen);
BufferAddData(&requestBuf, slist->mychallenge, LIST_CHALLENGE_LEN, &requestLen);
BufferAddNTS(&requestBuf, serverFilter, &requestLen);
BufferAddNTS(&requestBuf, fieldList, &requestLen);
options = (int)htonl((unsigned int)options);
BufferAddInt(&requestBuf, options, &requestLen);
if (slist->queryoptions & ALTERNATE_SOURCE_IP)
{
BufferAddInt(&requestBuf, (int)slist->srcip, &requestLen);
}
if (slist->queryoptions & LIMIT_RESULT_COUNT)
{
BufferAddIntLE(&requestBuf, maxServers, &requestLen);
}
netLen = htons((unsigned short)requestLen);
memcpy(outgoingRequest, &netLen, 2); //set the length...
//now send!
ret = send(slist->slsocket, outgoingRequest, requestLen, 0);
if (ret <= 0)
{
SBServerListDisconnect(slist);
return sbe_connecterror;
}
slist->state = sl_mainlist;
slist->pstate = pi_cryptheader;
//allocate an incoming buffer
if (slist->inbuffer == NULL)
{
slist->inbuffer = (char *)gsimalloc(INCOMING_BUFFER_SIZE);
if (slist->inbuffer == NULL)
return sbe_allocerror;
slist->inbufferlen = 0;
}
return sbe_noerror;
}
SBError SBServerListGetLANList(SBServerList *slist, unsigned short startport, unsigned short endport, int queryversion)
{
struct sockaddr_in saddr;
unsigned short i;
unsigned char qr2_echo_request[] = {QR2_MAGIC_1, QR2_MAGIC_2, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
int qr2requestlen = sizeof(qr2_echo_request) / sizeof(qr2_echo_request[0]);
if (slist->state != sl_disconnected)
SBServerListDisconnect(slist);
slist->slsocket = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if (slist->slsocket == INVALID_SOCKET)
return sbe_socketerror;
// enable broadcasting where needed
#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION)
{
int optval = 1;
if (setsockopt(slist->slsocket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0)
return sbe_socketerror;
}
#endif
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = gsiGetBroadcastIP(); //broadcast
// Added for multihomed machines. Allows the user to specify which adapter to use
if (slist->mLanAdapterOverride != NULL)
{
struct sockaddr_in aClassCAddr;
aClassCAddr.sin_family = AF_INET;
aClassCAddr.sin_addr.s_addr = inet_addr(slist->mLanAdapterOverride);
aClassCAddr.sin_port = 0; // bind to any port
if (0 != bind(slist->slsocket, (struct sockaddr*)&aClassCAddr, sizeof(aClassCAddr)))
return sbe_socketerror;
}
if (endport - startport > 500) //the max we will search
endport = (unsigned short)(startport + 500);
for (i = startport ; i <= endport ; i += 1)
{
saddr.sin_port = htons(i);
if (queryversion == QVERSION_QR2) //send a QR2 echo request
sendto(slist->slsocket, (char*)qr2_echo_request,qr2requestlen,0,(struct sockaddr *)&saddr,sizeof(saddr));
else //send a GOA echo request
sendto(slist->slsocket, "\\echo\\test",10,0,(struct sockaddr *)&saddr,sizeof(saddr));
}
slist->state = sl_lanbrowse;
slist->lanstarttime = current_time();
return sbe_noerror;
}
static void FreePopularValues(SBServerList *slist)
{
int i;
for (i = 0 ; i < slist->numpopularvalues ; i++)
SBReleaseStr(slist, slist->popularvalues[i]);
slist->numpopularvalues = 0;
}
static void FreeKeyList(SBServerList *slist)
{
int i;
if (slist->keylist == NULL)
return;
for (i = 0 ; i < ArrayLength(slist->keylist) ; i++)
SBReleaseStr(slist, ((KeyInfo *)ArrayNth(slist->keylist,i))->keyName);
ArrayFree(slist->keylist);
slist->keylist = NULL;
}
void SBServerListDisconnect(SBServerList *slist)
{
if (slist->inbuffer != NULL)
gsifree(slist->inbuffer);
slist->inbuffer = NULL;
slist->inbufferlen = 0;
if (slist->slsocket != INVALID_SOCKET)
closesocket(slist->slsocket);
slist->slsocket = INVALID_SOCKET;
slist->state = sl_disconnected;
FreeKeyList(slist);
slist->expectedelements = -1;
FreePopularValues(slist);
}
void SBServerListCleanup(SBServerList *slist)
{
SBServerListDisconnect(slist);
SBServerListClear(slist);
SBRefStrHashCleanup(slist);
if(slist->servers)
ArrayFree(slist->servers);
slist->servers = NULL;
#ifdef GSI_UNICODE
if (slist->lasterror_W != NULL)
{
gsifree(slist->lasterror_W);
slist->lasterror_W = NULL;
}
#endif
}
static void InitCryptKey(SBServerList *slist, char *key, int keylen)
{
//combine our secret key, our challenge, and the server's challenge into a crypt key
int i;
int seckeylen = (int)strlen(slist->queryfromkey);
char *seckey = slist->queryfromkey;
for (i = 0 ; i < keylen ; i++)
{
slist->mychallenge[(i * seckey[i % seckeylen]) % LIST_CHALLENGE_LEN] ^= (char)((slist->mychallenge[i % LIST_CHALLENGE_LEN] ^ key[i]) & 0xFF);
}
GOACryptInit(&(slist->cryptkey), (unsigned char *)(slist->mychallenge), LIST_CHALLENGE_LEN);
}
static int ServerSizeForFlags(int flags)
{
int size = 5; //all servers are at least 5 ..
if (flags & PRIVATE_IP_FLAG)
size += 4;
if (flags & ICMP_IP_FLAG)
size += 4;
if (flags & NONSTANDARD_PORT_FLAG)
size += 2;
if (flags & NONSTANDARD_PRIVATE_PORT_FLAG)
size += 2;
return size;
}
static int FullRulesPresent(char *buf, int len)
{
int i;
while (len > 0 && *buf)
{
i = NTSLengthSB(buf, len);
if (i < 0)
return 0; //no full key
buf += i;
len -= i;
i = NTSLengthSB(buf, len);
if (i < 0)
return 0; //no full value
buf += i;
len -= i;
}
if (len == 0)
return 0; //not even enough space for the null term
if (*buf == 0)
return 1; //all there
return 0;
}
//checks to see if all the keys for the given servers are there
static int AllKeysPresent(SBServerList *slist, char *buf, int len)
{
int numkeys;
int i;
int strindex;
numkeys = ArrayLength(slist->keylist);
for (i = 0 ; i < numkeys ; i++)
{
switch (((KeyInfo *)ArrayNth(slist->keylist, i))->keyType)
{
case KEYTYPE_BYTE:
buf++;
len--;
break;
case KEYTYPE_SHORT:
buf += 2;
len -= 2;
break;
case KEYTYPE_STRING:
if (len < 1)
return 0; //not enough
strindex = (unsigned char)(buf[0]);
buf++;
len--;
if (strindex == 0xFF) //a NTS string
{
strindex = NTSLengthSB(buf, len);
if (strindex == -1) //not all there..
return 0;
buf += strindex;
len -= strindex;
} //else it's a popular string - just the index is present
break;
default:
assert(0);
return 0; //error - unknown key type
}
if (len < 0)
return 0; //not enough..
}
return 1;
}
#define LAST_SERVER_MARKER "\xFF\xFF\xFF\xFF"
#define SERVER_MARKER_LEN 4
//parse only the IP/port from the server buffer
static void ParseServerIPPort(SBServerList *slist, char *buf, int len, goa_uint32 *ip, unsigned short *port)
{
unsigned char flags;
if (len < 5)
return; //invalid buffer
flags = (unsigned char)buf[0];
buf++;
len--;
memcpy(ip, buf, 4);
buf += 4;
len -= 4;
if (flags & NONSTANDARD_PORT_FLAG)
{
if (len < 2)
return; //invalid buffer
memcpy(port, buf, 2);
} else
*port = slist->defaultport;
}
//parse a server buffer
static int ParseServer(SBServerList *slist, SBServer server, char *buf, int len, int usepopularlist)
{
int numkeys;
int i;
short sval;
int strindex;
int holdlen = len;
goa_uint32 ip;
unsigned short port;
unsigned char flags;
flags = (unsigned char)buf[0];
SBServerSetFlags(server, flags);
buf++;
len--;
//skip the IP, it's already set
buf += 4;
len -= 4;
//skip the port, if needed
if (flags & NONSTANDARD_PORT_FLAG)
{
buf += 2;
len -= 2;
}
if (flags & PRIVATE_IP_FLAG)
{
memcpy(&ip, buf, 4);
buf += 4;
len -= 4;
} else
ip = 0;
if (flags & NONSTANDARD_PRIVATE_PORT_FLAG)
{
memcpy(&port, buf, 2);
buf += 2;
len -= 2;
} else
port = slist->defaultport;
SBServerSetPrivateAddr(server, ip, port);
if (flags & ICMP_IP_FLAG)
{
memcpy(&ip, buf, 4);
buf += 4;
len -= 4;
SBServerSetICMPIP(server, ip);
}
if (flags & HAS_KEYS_FLAG) //parse the keys
{
numkeys = ArrayLength(slist->keylist);
for (i = 0 ; i < numkeys ; i++)
{
KeyInfo *ki = (KeyInfo *)ArrayNth(slist->keylist, i);
switch (ki->keyType)
{
case KEYTYPE_BYTE:
SBServerAddIntKeyValue(server, ki->keyName, (unsigned char)buf[0]);
buf++;
len--;
break;
case KEYTYPE_SHORT:
memcpy(&sval, buf, 2);
SBServerAddIntKeyValue(server, ki->keyName, ntohs((unsigned short)sval));
buf += 2;
len -= 2;
break;
case KEYTYPE_STRING:
if (usepopularlist)
{
strindex = (unsigned char)(buf[0]);
buf++;
len--;
} else
strindex = 0xFF;
if (strindex == 0xFF) //a NTS string
{
SBServerAddKeyValue(server, ki->keyName, buf);
strindex = (int)strlen(buf) + 1;
buf += strindex;
len -= strindex;
} else //else it's a popular string - just the index is present
{
SBServerAddKeyValue(server, ki->keyName, slist->popularvalues[strindex]);
}
break;
}
}
SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_BASICKEYS));
}
if (flags & HAS_FULL_RULES_FLAG) //got the full rules in there
{
while (*buf && len > 0)
{
char *k = buf;
i = (int)strlen(k) + 1;
buf += i;
len -= i;
SBServerAddKeyValue(server, k, buf);
i = (int)strlen(buf) + 1;
buf += i;
len -= i;
}
buf++;
len--; //get the last null out
SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_FULLKEYS));
}
// the server was an empty server message, thus it should be cleared of
// basic key and full key states
{
unsigned char state = SBServerGetState(server);
if ((flags & (HAS_FULL_RULES_FLAG|HAS_KEYS_FLAG)) == 0 && ((state & STATE_BASICKEYS) | (state & STATE_FULLKEYS)))
{
state &= (unsigned char)~(STATE_BASICKEYS|STATE_FULLKEYS);
SBServerSetState(server, state);
}
}
return holdlen - len;
}
static int IncomingListParseServer(SBServerList *slist, char *buf, int len)
{
int i;
goa_uint32 ip;
unsigned short port;
SBServer server;
unsigned char flags;
//fields depends on the flags..
if (len < 1)
return 0;
flags = (unsigned char)(buf[0]);
i = ServerSizeForFlags(flags);
if (len < i)
return 0;
if (flags & HAS_KEYS_FLAG)
{
if (!AllKeysPresent(slist, buf + i, len - i))
return 0;
}
if (flags & HAS_FULL_RULES_FLAG)
{
if (!FullRulesPresent(buf + i, len - i))
return 0;
}
//else we have a whole server!
//see if it's the "last" server (0xffffffff)
if (memcmp(buf + 1, LAST_SERVER_MARKER, SERVER_MARKER_LEN) == 0)
return -1;
ParseServerIPPort(slist, buf, len, &ip, &port);
server = SBAllocServer(slist, ip, port);
if (SBIsNullServer(server))
return -2;
i = ParseServer(slist, server, buf, len, 1);
SBServerListAppendServer(slist, server);
return i;
}
const char *SBLastListErrorA(SBServerList *slist)
{
return slist->lasterror;
}
#ifdef GSI_UNICODE
const unsigned short *SBLastListErrorW(SBServerList *slist)
{
return slist->lasterror_W;
}
#endif
void SBSetLastListErrorPtr(SBServerList *slist, char* theError)
{
#ifdef GSI_UNICODE
if (slist->lasterror != theError)
{
// set the ascii error
slist->lasterror = theError;
// free the current unicode error and allocate a new one
if (slist->lasterror_W != NULL)
gsifree(slist->lasterror_W);
slist->lasterror_W = UTF8ToUCS2StringAlloc(slist->lasterror);
}
#else
slist->lasterror = theError;
#endif
}
#define FIXED_HEADER_LEN 6
/*
--challenge header
1 byte - number of random bytes to follow xor 0xEC
random bytes
1 byte - length xor 0xEA
keylen bytes - server challenge
--fixed header
YOUR public IP - 4 bytes
Standard query port - 2 bytes - or 0xFFFF if the master does not know about this gamename yet
[rest only follows if they requested a server slist]
--key slist
Number of Keys - 1 byte
List of Keys - with key type
Key Type - 1 Byte (0 = string, 1 = byte, 2 = short)
KeyName - NTS
--unique value slist
number of unique string values - 1 byte
Table of Unique String Values (255 most popular string keys)
Key NTS
--servers
Server Table (for each server - terminated with IP of 0xFFFFFFFF, flags 0)
1 byte flags
-Connect negotiate support?
-Has private IP
-Unsolicited UDP support?
-Has ICMP IP
-Nonstandard Public Port
-Nonstandard Private Port
-Has Keys
4 bytes public ip
[optional]
4 bytes port
[optional]
4 bytes private IP
[optional]
4 bytes private port
[optional]
4 bytes ICMP IP
[optional - keys]
String values
1-bytes - value ID
or
FF + valuestring + 00
Byte Values
1 byte - value
Short values?
2 bytes - value
*/
static SBError ProcessMainListData(SBServerList *slist)
{
int reqlen;
int keyoffset;
int keylen;
char *inbuf = slist->inbuffer;
int inlen = slist->inbufferlen;
switch (slist->pstate)
{
case pi_cryptheader:
if (inlen < 1)
break;
reqlen = (((unsigned char)(inbuf[0])) ^ 0xEC) + 2;
if (inlen < reqlen)
break; //not there yet
keyoffset = reqlen;
keylen = ((unsigned char)(inbuf[reqlen - 1])) ^ 0xEA;
reqlen += keylen;
if (inlen < reqlen)
break; //not there yet
//otherwise we have the whole crypt header and can init our crypt key
InitCryptKey(slist, inbuf + keyoffset, keylen);
slist->pstate = pi_fixedheader;
// the first byte of the "random" data is actually
// the game options flag
memcpy(&slist->backendgameflags, &inbuf[1], 2);
slist->backendgameflags = ntohs(slist->backendgameflags);
inbuf += reqlen;
inlen -= reqlen;
//decrypt any remaining data!
GOADecrypt(&(slist->cryptkey), (unsigned char *)inbuf, inlen);
//and fall through
case pi_fixedheader:
if (inlen < FIXED_HEADER_LEN)
break;
memcpy(&slist->mypublicip, inbuf, 4);
slist->ListCallback(slist, slc_publicipdetermined, SBNullServer, slist->instance);
memcpy(&slist->defaultport, inbuf + 4, 2);
if (slist->defaultport == 0xFFFF) //there was an error- grab the error string if present..
{
if (NTSLengthSB(inbuf + FIXED_HEADER_LEN, inlen - FIXED_HEADER_LEN) == -1) //not all there
break;
SBSetLastListErrorPtr(slist, inbuf + FIXED_HEADER_LEN);
//make the error callback
slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance);
if (slist->inbuffer == NULL)
break;
}
inbuf += FIXED_HEADER_LEN;
inlen -= FIXED_HEADER_LEN;
if ((slist->queryoptions & NO_SERVER_LIST) || slist->defaultport == 0xFFFF)
{
slist->pstate = pi_finished;
slist->state = sl_connected;
break; //no more data expected (if we didn't request any more, or the server doesn't know about this game)
}
slist->pstate = pi_keylist;
slist->expectedelements = -1;
//and fall through
case pi_keylist:
if (slist->expectedelements == -1) //we haven't read the initial count of keys yet..
{
if (inlen < 1)
break;
slist->expectedelements = (unsigned char)(inbuf[0]);
slist->keylist = ArrayNew(sizeof(KeyInfo), slist->expectedelements, NULL);
if (slist->keylist == NULL) //error
return sbe_allocerror;
inbuf++;
inlen--;
}
//try to read elements, up to the expected amount...
while (slist->expectedelements > ArrayLength(slist->keylist))
{
KeyInfo ki;
int keylen;
if (inlen < 2)
break; //can't possibly be a full key (keytype + string)
keylen = NTSLengthSB(inbuf + 1, inlen - 1);
if (keylen == -1)
break; //no full NTS string there
ki.keyType = (unsigned char)(inbuf[0]);
ki.keyName = SBRefStr(slist, inbuf + 1);
ArrayAppend(slist->keylist, &ki);
inbuf += keylen + 1;
inlen -= keylen + 1;
}
if (slist->expectedelements > ArrayLength(slist->keylist))
break; //didn't read them all...
//else we have read all the keys, fall through..
slist->pstate = pi_uniquevaluelist;
slist->expectedelements = -1;
case pi_uniquevaluelist:
if (slist->expectedelements == -1) //we haven't read the # of unique values yet..
{
if (inlen < 1)
break;
slist->expectedelements = (unsigned char)(inbuf[0]);
slist->numpopularvalues = 0;
inbuf++;
inlen--;
}
while (slist->expectedelements > slist->numpopularvalues)
{
int keylen = NTSLengthSB(inbuf, inlen);
if (keylen == -1)
break; //no full NTS string
slist->popularvalues[slist->numpopularvalues++] = SBRefStr(slist, inbuf);
inbuf += keylen;
inlen -= keylen;
}
if (slist->expectedelements > slist->numpopularvalues)
break; //didn't read all the popular values
//else we've got them all - move on to servers!
slist->pstate = pi_servers;
case pi_servers :
if (inlen < 5) //not enough for a full servers
break;
do
{
reqlen = IncomingListParseServer(slist, inbuf, inlen);
if (reqlen == -2)
return sbe_allocerror;
else if (reqlen == -1) //that was the last server!
{
inlen -= 5;
inbuf += 5;
slist->pstate = pi_finished;
slist->state = sl_connected;
slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance);
break;
}
inbuf += reqlen;
inlen -= reqlen;
if (slist->inbuffer == NULL)
reqlen = 0; //break out - they disconnected
} while (reqlen != 0);
break;
default:
break;
}
assert(inlen >= 0);
if (slist->inbuffer == NULL)
return sbe_noerror; //don't keep processing - they disconnected
if (inlen != 0) //need to shift it over..
{
memmove(slist->inbuffer, inbuf, (size_t)inlen);
}
slist->inbufferlen = inlen;
//we could clear the key list here if we wanted
return sbe_noerror;
}
/*
Number of Keys - Byte
List of Keys - with key type
Key Type - 1 Byte (0 = string, 1 = byte, 2 = short)
KeyName - NTS
*/
static SBError ProcessPushKeyList(SBServerList *slist, char *buf, int len)
{
int i;
int numkeys;
//first byte is the # of keys
numkeys = (unsigned char)buf[0];
buf++;
len--;
//set up our key list...
if (slist->keylist != NULL) //free the existing key list
{
FreeKeyList(slist);
}
slist->keylist = ArrayNew(sizeof(KeyInfo), numkeys, NULL);
if (slist->keylist == NULL)
return sbe_allocerror;
for (i = 0 ; i < numkeys ; i++)
{
int keylen;
KeyInfo ki;
if (len < 2)
return sbe_dataerror; //can't possibly be a full key (keytype + string)
keylen = NTSLengthSB(buf + 1, len - 1);
if (keylen == -1)
return sbe_dataerror; //no full NTS string there
ki.keyType = (unsigned char)(buf[0]);
ki.keyName = SBRefStr(slist, buf + 1);
ArrayAppend(slist->keylist, &ki);
buf += keylen + 1;
len -= keylen + 1;
}
return sbe_noerror;
}
/*
Is Final Packet - Byte
Result Count - 1 bytes (up to 255 per packet)
Results
Player Name - NTS
6 bytes - server addr
4 bytes - last seen time (UTC Unix time)
gamename - NTS (empty if they are searching for only a single game)
*/
static SBError ProcessPlayerSearch(SBServerList *slist, char *buf, int len)
{
unsigned char isFinal;
unsigned char resultCount;
unsigned int ip;
unsigned short port;
char *nick;
time_t lastSeen;
int i;
if (len < 2)
return sbe_dataerror; //not a full packet
isFinal = (unsigned char)buf[0];
resultCount = (unsigned char)buf[1];
buf += 2;
len -= 2;
for (i = 0 ; i < resultCount ; i++)
{
int slen;
nick = buf;
slen = NTSLengthSB(buf, len);
if (slen == -1)
return sbe_dataerror;
buf += slen;
len -= slen;
if (len < 11)
return sbe_dataerror; //not a full entry
memcpy(&ip, buf, 4);
memcpy(&port, buf + 4, 2);
memcpy(&lastSeen, buf + 6, 4);
lastSeen = (time_t)ntohl((unsigned long)lastSeen);
buf += 10;
len -= 10;
slen = NTSLengthSB(buf, len);
if (slen == -1)
return sbe_dataerror; //not a valid gamename
slist->PlayerSearchCallback(slist, nick, ip, port, lastSeen, buf, slist->instance);
//now skip the gamename
buf += slen;
len -= slen;
}
if (isFinal) //send a final callback
slist->PlayerSearchCallback(slist, NULL, 0, 0, 0, NULL, slist->instance);
return sbe_noerror;
}
/*
Server IP - 4 bytes
Server Port - 2 bytes
Map change time - 4 bytes
Number of maps - 1 byte
Maps
Mapname - NTS
*/
static SBError ProcessMaploop(SBServerList *slist, char *buf, int len)
{
time_t changeTime;
unsigned int ip;
unsigned short port;
SBServer server;
unsigned char mapCount;
int i;
char *mapList[MAX_MAPLOOP_LENGTH];
if (len < 11)
return sbe_dataerror; //not a valid packet
memcpy(&ip, buf, 4);
memcpy(&port, buf + 4, 2);
i = SBServerListFindServerByIP(slist, ip, port);
if (i == -1)
return sbe_noerror; //not present any more
server = SBServerListNth(slist, i);
memcpy(&changeTime, buf + 6, 4);
changeTime = (time_t)ntohl((unsigned long)changeTime);
mapCount = (unsigned char)buf[10];
buf += 11;
len -= 11;
for (i = 0 ; i < mapCount && i < MAX_MAPLOOP_LENGTH; i++)
{
int maplen;
if (len < 1)
break;
maplen = NTSLengthSB(buf, len);
if (maplen == -1)
return sbe_dataerror; //not a full NTS string
mapList[i] = buf;
buf += maplen;
len -= maplen;
}
if (slist->MaploopCallback == NULL)
return sbe_noerror;
slist->MaploopCallback(slist, server, changeTime, i, mapList, slist->instance);
return sbe_noerror;
}
static SBError ProcessDeleteServer(SBServerList *slist, char *buf, int len)
{
goa_uint32 ip;
unsigned short port;
int i;
if (len < 6)
return sbe_dataerror;
//need to grab out the ip & port to find it in our list
memcpy(&ip, buf, 4);
memcpy(&port, buf + 4, 2);
//see if we can find the server in the list - if not, then add it
i = SBServerListFindServerByIP(slist, ip, port);
if (i == -1)
return sbe_noerror;
SBServerListRemoveAt(slist, i);
return sbe_noerror;
}
/*
same as server list server format, except without using popular value lists
*/
static SBError ProcessPushServer(SBServerList *slist, char *buf, int len)
{
SBServer server;
goa_uint32 ip;
unsigned short port;
int foundexisting;
int i;
if (len < 5)
return sbe_dataerror;
//need to grab out the ip & port to find it in our list
ParseServerIPPort(slist, buf, len, &ip, &port);
foundexisting = SBServerListFindServerByIP(slist, ip, port);
if (foundexisting == -1) //need to add it
{
server = SBAllocServer(slist, ip, port);
if (SBIsNullServer(server))
return sbe_allocerror;
} else
server = SBServerListNth(slist, foundexisting);
i = ParseServer(slist, server, buf, len, 0);
if (i < 0)
return sbe_dataerror; //whole thing wasn't there
if (foundexisting == -1)
SBServerListAppendServer(slist, server);
slist->ListCallback(slist, slc_serverupdated, server, slist->instance);
return sbe_noerror;
}
static SBError ProcessAdHocData(SBServerList *slist)
{
unsigned short msglen;
int i;
SBError ret = sbe_noerror;
while (slist->inbufferlen >= 3) //while there is at least 1 message in there.. (2 bytes for the size + 1 for the type)
{
//first two bytes are the length - see if we have the whole message..
memcpy(&msglen, slist->inbuffer, 2);
msglen = ntohs(msglen);
if (msglen > INCOMING_BUFFER_SIZE) //ack! won't fit..
{
ret = sbe_dataerror;
break;
}
if (slist->inbufferlen < msglen)
return sbe_noerror; //whole message not yet here
//else process the message
switch (slist->inbuffer[2])
{
case PUSH_KEYS_MESSAGE:
ret = ProcessPushKeyList(slist, slist->inbuffer + 3, msglen - 3);
break;
case PUSH_SERVER_MESSAGE:
ret = ProcessPushServer(slist, slist->inbuffer + 3, msglen - 3);
break;
case KEEPALIVE_MESSAGE: //just need to send it back..
i = (int)send(slist->slsocket, slist->inbuffer, msglen, 0);
if (i <= 0)
return sbe_connecterror;
break;
case DELETE_SERVER_MESSAGE:
ret = ProcessDeleteServer(slist, slist->inbuffer + 3, msglen - 3);
break;
case MAPLOOP_MESSAGE:
ret = ProcessMaploop(slist, slist->inbuffer + 3, msglen - 3);
break;
case PLAYERSEARCH_MESSAGE:
ret = ProcessPlayerSearch(slist, slist->inbuffer + 3, msglen - 3);
break;
}
slist->inbufferlen -= msglen;
assert(slist->inbufferlen >= 0);
if (slist->inbufferlen != 0 && slist->inbuffer != NULL) //if anything is left, shift it over..
{
memmove(slist->inbuffer, slist->inbuffer + msglen, (size_t)slist->inbufferlen);
}
if (ret != sbe_noerror)
break;
}
if (ret != sbe_noerror)
ErrorDisconnect(slist);
return ret;
}
static SBError ProcessIncomingData(SBServerList *slist)
{
SBError err;
int len;
int oldlen;
if(!CanReceiveOnSocket(slist->slsocket))
return sbe_noerror;
//append to data
oldlen = slist->inbufferlen;
len = recv(slist->slsocket, slist->inbuffer + slist->inbufferlen, INCOMING_BUFFER_SIZE - slist->inbufferlen, 0);
if (gsiSocketIsError(len)|| len == 0)
{
ErrorDisconnect(slist);
return sbe_connecterror;
}
slist->inbufferlen += len;
err = sbe_noerror;
if (slist->state == sl_connected || slist->pstate > pi_cryptheader) //decrypt any new data..
{
GOADecrypt(&(slist->cryptkey), (unsigned char *)(slist->inbuffer + oldlen), slist->inbufferlen - oldlen);
}
if (slist->state == sl_mainlist)
err = ProcessMainListData(slist);
if (err != sbe_noerror)
return err;
//always need to check this after mainlistdata, in case some extra data has some in (e.g. key list for push)
if (slist->state == sl_connected && slist->inbufferlen > 0)
return ProcessAdHocData(slist);
return sbe_noerror;
}
SBError SBGetServerRulesFromMaster(SBServerList *slist, goa_uint32 ip, unsigned short port)
{
char requestBuffer[16];
unsigned short tmp;
unsigned short msgLen = 9;
if (slist->state == sl_disconnected) //try to connect
SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0);
if (slist->state == sl_disconnected)
return sbe_connecterror;
//the length
tmp = htons(msgLen);
memcpy(requestBuffer, &tmp, 2);
//the message type
requestBuffer[2] = SERVER_INFO_REQUEST;
memcpy(requestBuffer + 3, &ip, sizeof(ip));
memcpy(requestBuffer + 7, &port, sizeof(port));
return SendWithRetry(slist, requestBuffer, msgLen);
}
SBError SBSendMessageToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, const char *data, int len)
{
char requestBuffer[16];
unsigned short tmp;
unsigned short msgLen = 9;
SBError ret;
int i;
if (slist->state == sl_disconnected) //try to connect
SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0);
if (slist->state == sl_disconnected)
return sbe_connecterror;
//the length
tmp = htons((unsigned short)(msgLen + len));
memcpy(requestBuffer, &tmp, 2);
//the message type
requestBuffer[2] = SEND_MESSAGE_REQUEST;
memcpy(requestBuffer + 3, &ip, sizeof(ip));
memcpy(requestBuffer + 7, &port, sizeof(port));
ret = SendWithRetry(slist, requestBuffer, msgLen);
if (ret != sbe_noerror)
return ret;
i = send(slist->slsocket, data, len, 0); //send the actual push data
if (i < 0)
return sbe_connecterror;
return sbe_noerror;
}
SBError SBSendPlayerSearchRequest(SBServerList *slist, char *searchName, int searchOptions, int maxResults, SBPlayerSearchCallbackFn callback)
{
char requestBuffer[256];
unsigned short msgLen = 11;
int namelen;
SBError ret;
if (slist->state == sl_disconnected) //try to connect
SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0);
if (slist->state == sl_disconnected)
return sbe_connecterror;
slist->PlayerSearchCallback = callback;
requestBuffer[2] = PLAYERSEARCH_REQUEST;
searchOptions = (int)htonl((unsigned int)searchOptions);
memcpy(requestBuffer + 3, &searchOptions, 4);
maxResults = (int)htonl((unsigned int)maxResults);
memcpy(requestBuffer + 7, &maxResults, 4);
namelen = (int)strlen(searchName);
msgLen = (unsigned short)(msgLen + (namelen + 1));
if (msgLen > sizeof(requestBuffer))
return sbe_paramerror; //search string too long
memcpy(requestBuffer + 11, searchName, (size_t)namelen + 1);
msgLen = htons((unsigned short)(msgLen));
memcpy(requestBuffer, &msgLen, 2);
ret = SendWithRetry(slist, requestBuffer, ntohs(msgLen));
if (ret != sbe_noerror)
return ret;
return sbe_noerror;
}
SBError SBSendMaploopRequest(SBServerList *slist, SBServer server, SBMaploopCallbackFn callback)
{
char requestBuffer[16];
unsigned short tmp;
unsigned short msgLen = 9;
unsigned short port;
unsigned int ip;
SBError ret;
if (slist->state == sl_disconnected) //try to connect
SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0);
if (slist->state == sl_disconnected)
return sbe_connecterror;
slist->MaploopCallback = callback;
//the length
tmp = htons((unsigned short)(msgLen));
memcpy(requestBuffer, &tmp, 2);
//the message type
requestBuffer[2] = MAPLOOP_REQUEST;
ip = SBServerGetPublicInetAddress(server);
port = SBServerGetPublicQueryPortNBO(server);
memcpy(requestBuffer + 3, &ip, sizeof(ip));
memcpy(requestBuffer + 7, &port, sizeof(port));
ret = SendWithRetry(slist, requestBuffer, msgLen);
if (ret != sbe_noerror)
return ret;
return sbe_noerror;
}
SBError SBSendNatNegotiateCookieToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, int cookie)
{
unsigned char negotiateBuffer[NATNEG_MAGIC_LEN + 4];
negotiateBuffer[0] = NN_MAGIC_0;
negotiateBuffer[1] = NN_MAGIC_1;
negotiateBuffer[2] = NN_MAGIC_2;
negotiateBuffer[3] = NN_MAGIC_3;
negotiateBuffer[4] = NN_MAGIC_4;
negotiateBuffer[5] = NN_MAGIC_5;
cookie = (int)htonl((unsigned int)cookie);
memcpy(negotiateBuffer + NATNEG_MAGIC_LEN, &cookie, 4);
return SBSendMessageToServer(slist, ip, port, (char*)negotiateBuffer, NATNEG_MAGIC_LEN + 4);
}
static SBError ProcessLanData(SBServerList *slist)
{
char indata[1500]; // make sure we have enough room for a large UDP packet
struct sockaddr_in saddr;
int saddrlen = sizeof(saddr);
int error;
int foundexisting;
SBServer server;
while (CanReceiveOnSocket(slist->slsocket)) //we break if the select fails
{
#if defined(_PS3)
error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, (socklen_t*)&saddrlen );
#else
error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen );
#endif
if (gsiSocketIsError(error))
continue;
//if we got data, then add it to the list...
foundexisting = SBServerListFindServerByIP(slist, saddr.sin_addr.s_addr, saddr.sin_port);
if (foundexisting != -1) //already exists
continue;
server = SBAllocServer(slist, saddr.sin_addr.s_addr, saddr.sin_port);
if (SBIsNullServer(server))
return sbe_allocerror;
SBServerSetFlags(server, UNSOLICITED_UDP_FLAG|NONSTANDARD_PORT_FLAG);
SBServerListAppendServer(slist, server);
}
if (current_time() - slist->lanstarttime > SL_LAN_SEARCH_TIME) //done waiting for replies
{
closesocket(slist->slsocket);
slist->slsocket = INVALID_SOCKET;
slist->state = sl_disconnected;
slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance);
}
return sbe_noerror;
}
SBError SBListThink(SBServerList *slist)
{
SBFreeDeadList(slist); //free any pending deleted servers
switch (slist->state)
{
case sl_disconnected:
break;
case sl_connected:
case sl_mainlist:
return ProcessIncomingData(slist);
//break;
case sl_lanbrowse:
return ProcessLanData(slist);
//break;
}
return sbe_noerror;
//see if any data is available...
}