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