openmohaa/code/gamespy/gserverlist.c
2025-02-22 22:24:19 +01:00

1074 lines
30 KiB
C

/******
gserverlist.c
GameSpy C Engine SDK
Copyright 1999 GameSpy Industries, Inc
Suite E-204
2900 Bristol Street
Costa Mesa, CA 92626
(714)549-7689
Fax(714)549-0757
******
Please see the GameSpy C Engine SDK documentation for more
information
The goaceng.h file has extensive comments on each of the public functions
for this SDK. It also has a change history for the SDK.
******/
#include "goaceng.h"
#include "gserver.h"
#if defined(applec) || defined(THINK_C) || defined(__MWERKS__) && !defined(__KATANA__)
#include "::nonport.h"
#else
#include "../nonport.h"
#endif
#ifdef _MACOS
#include "::darray.h"
#include "::hashtable.h"
#else
#include "../darray.h"
#include "../hashtable.h"
#endif
#include "common/gsPlatformSocket.h"
#include "gutil.h"
#include "sv_gqueryreporting.h"
#ifndef UNDER_CE
#include <assert.h>
#else
#define assert(a)
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Added in 2.0
#include "gcrypt.h"
#define MSHOST MASTER_SERVER_HOST
#define MSPORT 28900
#define SERVER_GROWBY 64
#define LAN_SEARCH_TIME 3000 //3 sec
#define LIST_NUMKEYBUCKETS 500
#define LIST_NUMKEYCHAINS 4
#ifdef __cplusplus
extern "C" {
#endif
//todo: check state changes on error
typedef struct
{
SOCKET s;
GServer currentserver;
unsigned long starttime;
struct sockaddr_in saddr;
} UpdateInfo;
typedef enum { pi_fieldcount, pi_fields, pi_servers } GParseInfoState;
struct GServerListImplementation
{
GServerListState state;
DArray servers;
UpdateInfo *updatelist; //dynamic array of updateinfos
char gamename[32];
char seckey[32];
char enginename[32];
int maxupdates;
int nextupdate;
int abortupdate;
ListCallBackFn CallBackFn;
void *instance;
char *sortkey;
gbool sortascending;
SOCKET slsocket;
unsigned long lanstarttime;
GQueryType querytype;
HashTable keylist;
// Added in 2.0
int numservers;
// Added in 2.0
GCryptInfo cryptinfo;
// Added in OPM
int encryptdata;
GParseInfoState pistate;
};
GServerList g_sortserverlist; //global serverlist for sorting info!!
/* these correspond to the qt_ constants */
#define NUM_QUERYTYPES 6
const char *querystrings[NUM_QUERYTYPES] = {"\\basic\\","\\info\\","\\rules\\",
"\\players\\","\\info\\\\rules\\","\\status\\"};
const int querylengths[NUM_QUERYTYPES] = {7,6,7,9,13,8};
static void KeyValFree(void *elem);
static int KeyValCompareKeyA(const void *entry1, const void *entry2);
static int KeyValHashKeyA(const void *elem, int numbuckets);
/* ServerListNew
----------------
Creates and returns a new (empty) GServerList. */
GServerList ServerListNew(const char *gamename, const char *enginename, const char *seckey, int maxconcupdates, void *CallBackFn, int CallBackFnType, void *instance)
{
GServerList list;
list = (GServerList) malloc(sizeof(struct GServerListImplementation));
assert(list != NULL);
list->state = sl_idle;
list->servers = ArrayNew(sizeof(GServer), SERVER_GROWBY, ServerFree);
list->keylist = TableNew2(sizeof(char *),LIST_NUMKEYBUCKETS,LIST_NUMKEYCHAINS,GStringHash, GCaseInsensitiveCompare, GStringFree);
list->maxupdates = maxconcupdates;
list->updatelist = malloc(maxconcupdates * sizeof(UpdateInfo));
memset(list->updatelist, 0, maxconcupdates * sizeof(UpdateInfo));
assert(list->updatelist != NULL);
strcpy(list->gamename, gamename);
strcpy(list->seckey, seckey);
strcpy(list->enginename, enginename);
list->CallBackFn = CallBackFn;
assert(CallBackFn != NULL);
list->instance = instance;
list->sortkey = "";
// Added in 2.0
list->numservers = 0;
// Added in OPM
list->encryptdata = 1;
SocketStartUp();
return list;
}
/* ServerListFree
-----------------
Free a GServerList and all internal sturctures and servers */
void ServerListFree(GServerList serverlist)
{
ArrayFree(serverlist->servers);
TableFree(serverlist->keylist);
free(serverlist->updatelist);
free(serverlist);
SocketShutDown();
}
//create update sockets and init structures
static GError InitUpdateList(GServerList serverlist)
{
int i;
for (i = 0 ; i < serverlist->maxupdates ; i++)
{
serverlist->updatelist[i].s = socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP);
if (serverlist->updatelist[i].s == INVALID_SOCKET)
{ //ran out of sockets, just cap maxupdates here, unless we don't have any
if (i == 0)
return GE_NOSOCKET;
serverlist->maxupdates = i;
return 0;
}
serverlist->updatelist[i].currentserver = NULL;
serverlist->updatelist[i].starttime = 0;
}
return 0;
}
//free update sockets
static GError FreeUpdateList(GServerList serverlist)
{
int i;
for (i = 0 ; i < serverlist->maxupdates ; i++)
{
closesocket(serverlist->updatelist[i].s);
}
return 0;
}
//create and connect a server list socket
static GError CreateServerListSocket(GServerList serverlist)
{
struct sockaddr_in saddr;
struct hostent *hent;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(MSPORT);
saddr.sin_addr.s_addr = inet_addr(MSHOST);
if (saddr.sin_addr.s_addr == INADDR_NONE)
{
hent = gethostbyname(MSHOST);
if (!hent)
return GE_NODNS;
saddr.sin_addr.s_addr = *(u_long *)hent->h_addr_list[0];
}
serverlist->slsocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (serverlist->slsocket == INVALID_SOCKET)
return GE_NOSOCKET;
if (connect ( serverlist->slsocket, (struct sockaddr *) &saddr, sizeof saddr ) != 0)
{
closesocket(serverlist->slsocket);
return GE_NOCONNECT;
}
//else we are connected
return 0;
}
//create and connect a server list socket
static GError CreateServerListLANSocket(GServerList serverlist)
{
int optval = 1;
serverlist->slsocket = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if (serverlist->slsocket == INVALID_SOCKET)
return GE_NOSOCKET;
if (setsockopt(serverlist->slsocket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0)
return GE_NOSOCKET;
//else we are ready to broadcast
return 0;
}
//trigger the callback and set the new mode
static void ServerListModeChange(GServerList serverlist, GServerListState newstate)
{
serverlist->state = newstate;
serverlist->CallBackFn(serverlist, LIST_STATECHANGED, serverlist->instance, NULL, NULL);
}
// validate us to the master and send a list request
#define SECURE "\\secure\\"
static GError SendListRequest(GServerList serverlist, char *filter)
{
char data[256], *ptr, result[64];
int len;
int i;
char *modifier;
len = recv(serverlist->slsocket, data, sizeof(data) - 1, 0);
if (gsiSocketIsError(len))
return GE_NOCONNECT;
data[len] = '\0'; //null terminate it
ptr = strstr ( data, SECURE );
if (!ptr)
return GE_DATAERROR;
ptr = ptr + strlen(SECURE);
gs_encrypt ( (uchar *) serverlist->seckey, 6, (uchar *)ptr, 6 );
if (serverlist->encryptdata) {
// Added in 2.0
for(i = 0; i < 6; i++) {
ptr[i] ^= serverlist->seckey[i];
}
}
gs_encode ( (uchar *)ptr, 6, (uchar *) result );
if (serverlist->encryptdata) {
// Added in 2.0
// Encrypt data
//validate to the master
sprintf(data, "\\gamename\\%s\\gamever\\%s\\location\\0\\validate\\%s\\enctype\\2\\final\\\\queryid\\1.1\\",
serverlist->enginename, "2", result); //validate us
} else {
//validate to the master
sprintf(data, "\\gamename\\%s\\gamever\\%s\\location\\0\\validate\\%s\\final\\\\queryid\\1.1\\",
serverlist->enginename, ENGINE_VERSION, result); //validate us
}
len = send ( serverlist->slsocket, data, strlen(data), 0 );
if (gsiSocketIsError(len) || len == 0)
return GE_NOCONNECT;
if (serverlist->querytype == qt_grouprooms) {
modifier = "groups";
serverlist->pistate = pi_fieldcount;
} else if (serverlist->querytype == qt_masterinfo) {
modifier = "info2";
serverlist->pistate = pi_fieldcount;
} else {
modifier = "cmp";
}
//send the list request
if (filter)
sprintf(data, "\\list\\%s\\gamename\\%s\\where\\%s\\final\\", modifier, serverlist->gamename, filter);
else
sprintf(data, "\\list\\%s\\gamename\\%s\\final\\", modifier, serverlist->gamename);
len = send ( serverlist->slsocket, data, strlen(data), 0 );
if (gsiSocketIsError(len) || len == 0)
return GE_NOCONNECT;
ServerListModeChange(serverlist, sl_listxfer);
return 0;
}
static GError SendBroadcastRequest(GServerList serverlist, int startport, int endport, int delta)
{
struct sockaddr_in saddr;
short i;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = 0xFFFFFFFF; //broadcast
for (i = startport ; i <= endport ; i += delta)
{
saddr.sin_port = htons(i);
sendto(serverlist->slsocket, "\\status\\",8,0,(struct sockaddr *)&saddr,sizeof(saddr));
}
ServerListModeChange(serverlist, sl_lanlist);
serverlist->lanstarttime = current_time();
return 0;
}
//just wait for the server list to become idle
static void DoSyncLoop(GServerList serverlist)
{
while (serverlist->state != sl_idle)
{
ServerListThink(serverlist);
msleep(10);
}
}
/* ServerListUpdate
-------------------
Start updating a GServerList without filters */
GError ServerListUpdate(GServerList serverlist, gbool async)
{
return ServerListUpdate2(serverlist, async, NULL, qt_status);
}
/* ServerListUpdate2
-------------------------
Start updating a GServerList. */
GError ServerListUpdate2(GServerList serverlist, gbool async, char *filter, GQueryType querytype)
{
GError error;
if (serverlist->state != sl_idle)
return GE_BUSY;
serverlist->querytype = querytype;
error = CreateServerListSocket(serverlist);
if (error) return error;
error = SendListRequest(serverlist, filter);
if (error) return error;
error = InitUpdateList(serverlist);
if (error) return error;
serverlist->nextupdate = 0;
serverlist->abortupdate = 0;
// Added in 2.0
serverlist->numservers = ServerListCount(serverlist);
// Added in 2.0
serverlist->cryptinfo.offset = -1;
if (!async)
DoSyncLoop(serverlist);
return 0;
}
/* ServerListLANUpdate
-------------------
Start updating a GServerList from servers on the LAN. */
GError ServerListLANUpdate(GServerList serverlist, gbool async, int startsearchport, int endsearchport, int searchdelta)
{
GError error;
assert(searchdelta > 0);
if (serverlist->state != sl_idle)
return GE_BUSY;
error = InitUpdateList(serverlist);
if (error) return error;
error = CreateServerListLANSocket(serverlist);
if (error) return error;
error = SendBroadcastRequest(serverlist, startsearchport, endsearchport, searchdelta);
if (error) return error;
serverlist->nextupdate = 0;
serverlist->abortupdate = 0;
if (!async)
DoSyncLoop(serverlist);
return 0;
}
//add a new server based on the data
static GServer ServerListAddServerData(GServerList serverlist, char **fieldlist, int fieldcount, char *serverdata, GQueryType qtype)
{
GServer server;
server = ServerNewData(fieldlist, fieldcount, serverdata, qtype, serverlist->keylist);
ArrayAppend(serverlist->servers, &server);
return server;
}
//add the server to the list with the given ip, port
static void ServerListAddServer(GServerList serverlist, unsigned long ip, unsigned short port, GQueryType qtype)
{
GServer server;
server = ServerNew(ip, port, qtype, serverlist->keylist);
ArrayAppend(serverlist->servers,&server);
//printf("%d %s:%d\n",++count, ip,port);
}
//add the server to the list with the given ip, port
static void ServerListInsertServer(GServerList serverlist, unsigned long ip, unsigned short port, int pos, GQueryType qtype)
{
GServer server;
server = ServerNew(ip, port, qtype, serverlist->keylist);
ArrayInsertAt(serverlist->servers,&server,pos);
//printf("%d %s:%d\n",++count, ip,port);
}
//find the server in the list, returns -1 if it does not exist
static int ServerListFindServerMax(GServerList serverlist, unsigned int ip, int port, int count)
{
int i;
GServer server;
for (i = 0; i < count ; i++)
{
server = *(GServer *)ArrayNth(serverlist->servers,i);
if (port == ServerGetQueryPort(server) && ServerGetInetAddress(server)==ip)
{
return i;
}
}
return -1;
}
//find the server in the list, returns -1 if it does not exist
static int ServerListFindServer(GServerList serverlist, unsigned int ip, int port)
{
return ServerListFindServerMax(serverlist, ip, port, ArrayLength(serverlist->servers));
}
//finds the server in the list of servers currently being queried
// returns -1 if it does not exist
static int ServerListFindServerInUpdateList(GServerList serverlist, GServer server)
{
int i;
for (i = 0 ; i < serverlist->maxupdates ; i++)
{
if (serverlist->updatelist[i].currentserver == server)
return i;
}
return -1;
}
/* ServerListRemoveServer
-------------------------
Removes a single server from the list. Frees the memory associated with the GServer */
void ServerListRemoveServer(GServerList serverlist, char *ip, int port)
{
int currentindex = ServerListFindServer(serverlist, inet_addr(ip), port);
int updateindex;
if (currentindex == -1)
return; //can't do anything, it doesn't exist
//check to see whether we need to change the updatelist or move the nextupdate
if (serverlist->state != sl_idle && serverlist->nextupdate > currentindex)
{
GServer holdserver = *(GServer *)ArrayNth(serverlist->servers,currentindex);
updateindex = ServerListFindServerInUpdateList(serverlist, holdserver);
if (updateindex != -1) //is currently being queried, stop it
serverlist->updatelist[updateindex].currentserver = NULL;
serverlist->nextupdate--; //decrement the next update, since we are removing a server
}
ArrayDeleteAt(serverlist->servers, currentindex);
}
/* ServerListUpdate
-------------------
Adds an auxilliary (non-fetched) server to the update list.
If the engine is idle, the server is added and the engine started. */
GError ServerListAuxUpdate(GServerList serverlist, const char *ip, int port, gbool async, GQueryType querytype)
{
GError error;
int currentindex;
int updateindex;
unsigned int real_ip = inet_addr(ip);
//first, see if the server already exists
currentindex = ServerListFindServer(serverlist,real_ip,port);
//if we're idle, start things up
if (serverlist->state == sl_idle)
{
//prepare as if we're going to do a normal list fetch,
//but skip the call to SendListRequest().
error = InitUpdateList(serverlist);
if (error) return error;
if (currentindex != -1) //we need to "move" this server to the end of the list
{ //move the server to the end of the array
GServer holdserver = *(GServer *)ArrayNth(serverlist->servers,currentindex);
holdserver->ping = 9999;//clear the ping so it gets recalculated
ArrayRemoveAt(serverlist->servers,currentindex);
ArrayAppend(serverlist->servers,&holdserver);
} else
{ //add the aux server
ServerListAddServer(serverlist, real_ip, (unsigned short)port, querytype);
}
serverlist->nextupdate = ArrayLength(serverlist->servers) - 1;
serverlist->abortupdate = 0;
//chane the mode straight to querying
ServerListModeChange(serverlist, sl_querying);
//is it's a sync call, do it until done
if (!async)
DoSyncLoop(serverlist);
}
else
{
//if we're in the middle of an update, we should
//be able to just slip the aux server in for querying
//ServerListAddServer(serverlist, ip, port);
//crt -- make it the next server to be queried
//note: this should NEVER be called in a different thread from think!!
if (currentindex == -1) //it doesn't exist yet
ServerListInsertServer(serverlist, real_ip, (unsigned short)port, serverlist->nextupdate, querytype);
else
{ //it exists, find out whats happening to it
GServer holdserver = *(GServer *)ArrayNth(serverlist->servers,currentindex);
if (currentindex >= serverlist->nextupdate) //hasn't been queried yet!
return 0; //it will be queried soon anyway
holdserver->ping = 9999;//clear the ping so it gets recalculated
updateindex = ServerListFindServerInUpdateList(serverlist, holdserver);
if (updateindex != -1) //is currently being queried, stop it
serverlist->updatelist[updateindex].currentserver = NULL;
ArrayInsertAt(serverlist->servers,&holdserver, serverlist->nextupdate); //insert at new place
ArrayRemoveAt(serverlist->servers,currentindex); //remove the old one
serverlist->nextupdate--; //decrement the next update, since we are removing a server
}
}
return 0;
}
static GError ServerListLANList(GServerList serverlist)
{
struct timeval timeout = {0,0};
fd_set set;
char indata[1500];
struct sockaddr_in saddr;
int saddrlen = sizeof(saddr);
int error;
while (1) //we break if the select fails
{
FD_ZERO(&set);
FD_SET( serverlist->slsocket, &set);
error = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
if (gsiSocketIsError(error) || 0 == error) //no data
break;
error = recvfrom(serverlist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen );
if (gsiSocketIsError(error))
continue;
//we got data, add the server to the list to update
if (strstr(indata,"\\final\\") != NULL)
ServerListAddServer(serverlist,saddr.sin_addr.s_addr, ntohs(saddr.sin_port), qt_status);
}
if (current_time() - serverlist->lanstarttime > LAN_SEARCH_TIME) //done waiting for replies
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
ServerListModeChange(serverlist, sl_querying);
}
return 0;
}
static int CountSlashOffset(char *data, int len, int slashcount)
{
char *p;
for (p = data; slashcount && p != data + len; ++p) {
if (*p == '\\') {
slashcount--;
}
}
if (slashcount) {
return -1;
}
return p - data;
}
//parses and retrieve servers based on fields
static int ServerListParseInfoList(GServerList serverlist, char *data, int len)
{
char *fieldlist[20];
char tempfield[64];
char *tempptr;
static int fieldcount;
int offset;
int i;
GServer server;
switch (serverlist->pistate) {
case pi_fieldcount:
offset = CountSlashOffset(data, len, 3);
if (offset == -1) {
return 0;
}
if (offset < 12) {
return 0;
}
strncpy(tempfield, data + 12, offset - 12);
tempfield[offset - 13] = 0;
fieldcount = atoi(tempfield);
if (fieldcount > 20) {
return -1;
}
serverlist->pistate = pi_fields;
for (i = 0; i < fieldcount; ++i) {
fieldlist[i] = 0;
}
return offset - 1;
case pi_fields:
offset = CountSlashOffset(data, len, 2);
if (offset == -1) {
return 0;
}
strncpy(tempfield, data + 1, offset - 2);
tempfield[offset - 2] = 0;
tempptr = goastrdup(tempfield);
for (i = 0; i < fieldcount; ++i) {
if (!fieldlist[i]) {
fieldlist[i] = tempptr;
if (i == fieldcount - 1) {
serverlist->pistate = pi_servers;
}
return offset - 1;
}
}
return -1;
case pi_servers:
offset = CountSlashOffset(data, len, fieldcount + 1);
if (offset == -1) {
return 0;
}
server = ServerListAddServerData(serverlist, (char **)fieldlist, fieldcount, data, serverlist->querytype);
data[offset - 1] = '\\';
serverlist->CallBackFn(serverlist, 2, serverlist->instance, server, 0);
return offset - 1;
default:
return -1;
}
}
//reads the server list from the socket and parses it
static GError ServerListReadList(GServerList serverlist)
{
static char data[2048]; //static input buffer
static int oldlen = 0;
fd_set set;
struct timeval timeout = {0,0};
int len, i;
char *p;
unsigned long ip;
unsigned short port;
FD_ZERO(&set);
FD_SET(serverlist->slsocket, &set);
#ifndef KGTRN_ACCESS
i = select( FD_SETSIZE, &set, NULL, NULL, &timeout );
if (i <= 0)
return GE_NOERROR;
#endif
//append to data
len = recv(serverlist->slsocket, data + oldlen, sizeof(data) - oldlen - 1, 0);
if (gsiSocketIsError(len) || len == 0)
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
oldlen = 0; //clear data so it can be used again
ServerListHalt(serverlist);
ServerListModeChange(serverlist, sl_querying);
return GE_NOCONNECT;
}
if (serverlist->encryptdata && serverlist->cryptinfo.offset != -1) {
// Added in 2.0
crypt_docrypt(&serverlist->cryptinfo, data + oldlen, len);
}
oldlen += len;
p = data;
if (!serverlist->encryptdata) {
serverlist->cryptinfo.offset = 0;
} else if (serverlist->cryptinfo.offset == -1) {
// Added in 2.0
if (oldlen > (*p ^ 0xEC)) {
*p ^= 0xEC;
len = strlen(serverlist->seckey);
for (i = 0; i < len; i++) {
p[i + 1] ^= serverlist->seckey[i];
}
init_crypt_key((unsigned char *)p + 1, *p, &serverlist->cryptinfo);
p += *p + 1;
crypt_docrypt(&serverlist->cryptinfo, (unsigned char *)p, oldlen - (p - data));
}
}
if (serverlist->cryptinfo.offset != -1)
{
while (p - data <= oldlen - 6)
{
if (strncmp(p,"\\final\\",7) == 0 || serverlist->abortupdate)
{
closesocket(serverlist->slsocket);
serverlist->slsocket = INVALID_SOCKET;
oldlen = 0; //clear data so it can be used again
ServerListModeChange(serverlist, sl_querying);
return 0; //get out!!
}
if (oldlen < 6) //no way it could be a full IP, quit
break;
if (serverlist->querytype == qt_grouprooms || serverlist->querytype == qt_masterinfo) {
i = ServerListParseInfoList(serverlist, p, oldlen - (p - data));
if (i < 0) {
serverlist->abortupdate = 1;
} else if (!i) {
// finished
break;
}
} else {
memcpy(&ip, p, 4);
p += 4;
memcpy(&port, p, 2);
p += 2;
// Added in 2.0
// Skip adding the server if already exists
if (ServerListFindServerMax(serverlist, ip, ntohs(port), serverlist->numservers) == -1) {
ServerListAddServer(serverlist, ip, ntohs(port), serverlist->querytype);
}
}
}
}
oldlen = oldlen - (p - data);
memmove(data,p,oldlen); //shift it over
return 0;
}
//loop through pending queries and send out new ones
static GError ServerListQueryLoop(GServerList serverlist)
{
int i, scount = 0, error, final;
fd_set set;
struct timeval timeout = {0,0};
char indata[1500];
struct sockaddr_in saddr;
int saddrlen = sizeof(saddr);
GServer server;
//first, check for available data
FD_ZERO(&set);
for (i = 0 ; i < serverlist->maxupdates ; i++)
if (serverlist->updatelist[i].currentserver != NULL) //there is a server waiting
{
scount++;
FD_SET( serverlist->updatelist[i].s, &set);
}
if (scount > 0) //there are sockets to check for data
{
error = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
if (!gsiSocketIsError(error) && 0 != error)
for (i = 0 ; i < serverlist->maxupdates ; i++)
if (serverlist->updatelist[i].currentserver != NULL && FD_ISSET(serverlist->updatelist[i].s, &set) ) //there is a server waiting
{ //we can read data!!
saddrlen = sizeof(saddr);
error = recvfrom(serverlist->updatelist[i].s, indata, sizeof(indata) - 1, 0,(struct sockaddr *)&saddr, &saddrlen);
if (saddr.sin_addr.s_addr != serverlist->updatelist[i].saddr.sin_addr.s_addr ||
saddr.sin_port != serverlist->updatelist[i].saddr.sin_port)
continue; //it wasn't from this server
if (!gsiSocketIsError(error)) //we got data
{
indata[error] = 0; //truncate and parse it
final = (strstr(indata,"\\final\\") != NULL);
server = serverlist->updatelist[i].currentserver;
if (server->ping == 9999) //set the ping
server->ping = (short)(current_time() - serverlist->updatelist[i].starttime);
ServerParseKeyVals(server, indata);
if (final) //it's all done
{
serverlist->CallBackFn(serverlist,
LIST_PROGRESS,
serverlist->instance,
server,
(void *)((serverlist->nextupdate * 100) / ArrayLength(serverlist->servers))); //percent done
serverlist->updatelist[i].currentserver = NULL; //reuse the updatelist
}
} else
serverlist->updatelist[i].currentserver = NULL; //reuse the updatelist
}
}
//kill expired ones
for (i = 0 ; i < serverlist->maxupdates ; i++)
if (serverlist->updatelist[i].currentserver != NULL && current_time() - serverlist->updatelist[i].starttime > SERVER_TIMEOUT )
{
/* serverlist->CallBackFn(serverlist, //do we want to notify of dead servers? if so, uncomment!
LIST_PROGRESS,
serverlist->instance,
*(GServer *)serverlist->updatelist[i].currentserver,
(void *)((serverlist->nextupdate * 100) / ArrayLength(serverlist->servers))); //percent done
*/
serverlist->updatelist[i].currentserver = NULL; //reuse the updatelist
}
if (serverlist->abortupdate || (serverlist->nextupdate >= ArrayLength(serverlist->servers) && scount == 0))
{ //we are done!!
FreeUpdateList(serverlist);
ServerListModeChange(serverlist, sl_idle);
return 0;
}
//now, send out queries on available sockets
for (i = 0 ; i < serverlist->maxupdates && serverlist->nextupdate < ArrayLength(serverlist->servers) ; i++)
if (serverlist->updatelist[i].currentserver == NULL) //it's availalbe
{
server = *(GServer *)ArrayNth(serverlist->servers,serverlist->nextupdate);
serverlist->nextupdate++;
serverlist->updatelist[i].currentserver = server;
serverlist->updatelist[i].saddr.sin_family = AF_INET;
serverlist->updatelist[i].saddr.sin_addr.s_addr = inet_addr(ServerGetAddress(server));
serverlist->updatelist[i].saddr.sin_port = htons((short)ServerGetQueryPort(server));
sendto(serverlist->updatelist[i].s,querystrings[server->querytype] ,querylengths[server->querytype],0,(struct sockaddr *)&serverlist->updatelist[i].saddr,sizeof(struct sockaddr_in));
serverlist->updatelist[i].starttime = current_time();
}
return 0;
}
/* ServerListThink
------------------
For use with Async Updates. This needs to be called every ~10ms for list processing and
updating to occur during async server list updates */
GError ServerListThink(GServerList serverlist)
{
switch (serverlist->state)
{
case sl_idle: return 0;
case sl_listxfer:
//read the data
return ServerListReadList(serverlist);
break;
case sl_lanlist:
return ServerListLANList(serverlist);
case sl_querying:
//do some queries
return ServerListQueryLoop(serverlist);
break;
}
return 0;
}
/* ServerListHalt
-----------------
Halts the current update batch */
GError ServerListHalt(GServerList serverlist)
{
if (serverlist->state != sl_idle)
serverlist->abortupdate = 1;
return 0;
}
/* ServerListClear
------------------
Clear and free all of the servers from the server list.
List must be in the sl_idle state */
GError ServerListClear(GServerList serverlist)
{
if (serverlist->state != sl_idle)
return GE_BUSY;
//fastest way to clear is kill and recreate
ArrayFree(serverlist->servers);
serverlist->servers = ArrayNew(sizeof(GServer), SERVER_GROWBY, ServerFree);
return 0;
}
/* ServerListState
------------------
Returns the current state of the server list */
GServerListState ServerListState(GServerList serverlist)
{
return serverlist->state;
}
/* ServerListErrorDesc
----------------------
Returns a static string description of the specified error */
char *ServerListErrorDesc(GServerList serverlist, GError error)
{
switch (error)
{
case GE_NOERROR: return "";
case GE_NOSOCKET: return "Unable to create socket";
case GE_NODNS: return "Unable to resolve master";
case GE_NOCONNECT: return "Connection to master reset";
case GE_BUSY: return "Server List is busy";
case GE_DATAERROR: return "Unexpected data in server list";
}
return "UNKNOWN ERROR CODE";
}
/* ServerListGetServer
----------------------
Returns the server at the specified index, or NULL if the index is out of bounds */
GServer ServerListGetServer(GServerList serverlist, int index)
{
if (index < 0 || index >= ArrayLength(serverlist->servers))
return NULL;
return *(GServer *)ArrayNth(serverlist->servers,index);
}
/* ServerListCount
------------------
Returns the number of servers on the specified list. Indexing is 0 based, so
the actual server indexes are 0 <= valid index < Count */
int ServerListCount(GServerList serverlist)
{
return ArrayLength(serverlist->servers);
}
/****
Comparision Functions
***/
static int IntKeyCompare(const void *entry1, const void *entry2)
{
GServer server1 = *(GServer *)entry1, server2 = *(GServer *)entry2;
int diff;
diff = ServerGetIntValue(server1, g_sortserverlist->sortkey, 0) -
ServerGetIntValue(server2, g_sortserverlist->sortkey, 0);
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
static int FloatKeyCompare(const void *entry1, const void *entry2)
{
GServer server1 = *(GServer *)entry1, server2 = *(GServer *)entry2;
double f = ServerGetFloatValue(server1, g_sortserverlist->sortkey, 0) -
ServerGetFloatValue(server2, g_sortserverlist->sortkey, 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;
}
static int StrCaseKeyCompare(const void *entry1, const void *entry2)
{
GServer server1 = *(GServer *)entry1, server2 = *(GServer *)entry2;
int diff = strcmp(ServerGetStringValue(server1, g_sortserverlist->sortkey, ""),
ServerGetStringValue(server2, g_sortserverlist->sortkey, ""));
if (!g_sortserverlist->sortascending)
diff = -diff;
return diff;
}
static int StrNoCaseKeyCompare(const void *entry1, const void *entry2)
{
GServer server1 = *(GServer *)entry1, server2 = *(GServer *)entry2;
int diff = strcasecmp(ServerGetStringValue(server1, g_sortserverlist->sortkey, ""),
ServerGetStringValue(server2, g_sortserverlist->sortkey, ""));
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 ServerListSort(GServerList serverlist, gbool ascending, char *sortkey, GCompareMode comparemode)
{
ArrayCompareFn comparator;
switch (comparemode)
{
case cm_int: comparator = IntKeyCompare;
break;
case cm_float: comparator = FloatKeyCompare;
break;
case cm_strcase: comparator = StrCaseKeyCompare;
break;
case cm_stricase: comparator = StrNoCaseKeyCompare;
break;
}
serverlist->sortkey = sortkey;
serverlist->sortascending = ascending;
g_sortserverlist = serverlist;
ArraySort(serverlist->servers,comparator);
}
#ifdef __cplusplus
}
#endif