/****** 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 #else #define assert(a) #endif #include #include #include // Added in 2.0 #include "gcrypt.h" /** * @brief Custom function used to return the master host, based on game settings * * @return const char* The master host */ extern const char *ServerListGetHost(); extern int ServerGetMsPort(); #define MSHOST ServerListGetHost() #define MSPORT ServerListGetMsPort() #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; if (querytype != qt_grouprooms && querytype != qt_masterinfo) { 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 if (serverlist->querytype == qt_grouprooms || serverlist->querytype == qt_masterinfo) { ServerListModeChange(serverlist, sl_idle); } else { 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