#include "quakedef.h" #ifdef CL_MASTER #include "cl_master.h" #define NET_GAMENAME_NQ "QUAKE" //rename to cl_master.c sometime //the networking operates seperatly from the main code. This is so we can have full control over all parts of the server sending prints. //when we send status to the server, it replys with a print command. The text to print contains the serverinfo. //Q2's print command is a compleate 'print', while qw is just a 'p', thus we can distinguish the two easily. //save favorites and allow addition of new ones from game? //add filters some time //remove dead servers. //master was polled a minute ago and server was not on list - server on multiple masters would be awkward. #ifdef _WIN32 #include "winquake.h" #define USEIPX #else typedef int SOCKET; #endif #include "netinc.h" #ifdef AF_IPX #define USEIPX #endif //the number of servers should be limited only by memory. cvar_t slist_cacheinfo = SCVAR("slist_cacheinfo", "0"); //this proves dangerous, memory wise. cvar_t slist_writeserverstxt = SCVAR("slist_writeservers", "0"); void CL_MasterListParse(int type, qboolean slashpad); void CL_QueryServers(void); int CL_ReadServerInfo(char *msg, int servertype, qboolean favorite); master_t *master; player_t *mplayers; serverinfo_t *firstserver; static serverinfo_t **visibleservers; static int numvisibleservers; static int maxvisibleservers; static double nextsort; static hostcachekey_t sortfield; static qboolean decreasingorder; typedef struct { hostcachekey_t fieldindex; float operandi; char *operands; qboolean or; int compareop; } visrules_t; #define MAX_VISRULES 8 visrules_t visrules[MAX_VISRULES]; int numvisrules; #define SLIST_MAXKEYS 64 char slist_keyname[SLIST_MAXKEYS][MAX_INFO_KEY]; int slist_customkeys; #ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif #define POLLUDPSOCKETS 64 //it's big so we can have lots of messages when behind a firewall. Basically if a firewall only allows replys, and only remembers 3 servers per socket, we need this big cos it can take a while for a packet to find a fast optimised route and we might be waiting for a few secs for a reply the first time around. SOCKET pollsocketsUDP[POLLUDPSOCKETS]; int lastpollsockUDP; #ifdef USEIPX #define POLLIPXSOCKETS 2 //ipx isn't used as much. In fact, we only expect local servers to be using it. I'm not sure why I implemented it anyway. SOCKET pollsocketsIPX[POLLIPXSOCKETS]; int lastpollsockIPX; #else #define POLLIPXSOCKETS 0 #endif void Master_SetupSockets(void) { int i; for (i = 0; i < POLLUDPSOCKETS; i++) pollsocketsUDP[i] = INVALID_SOCKET; #ifdef USEIPX for (i = 0; i < POLLIPXSOCKETS; i++) pollsocketsIPX[i] = INVALID_SOCKET; #endif } void Master_HideServer(serverinfo_t *server) { int i, j; for (i = 0; i < numvisibleservers;) { if (visibleservers[i] == server) { for (j = i; j < numvisibleservers-1; j++) visibleservers[j] = visibleservers[j+1]; visibleservers--; } else i++; } server->insortedlist = false; } void Master_InsertAt(serverinfo_t *server, int pos) { int i; for (i = numvisibleservers; i > pos; i--) { visibleservers[i] = visibleservers[i-1]; } visibleservers[pos] = server; numvisibleservers++; server->insortedlist = true; } qboolean Master_CompareInteger(int a, int b, slist_test_t rule) { switch(rule) { case SLIST_TEST_CONTAINS: return !!(a&b); case SLIST_TEST_NOTCONTAIN: return !(a&b); case SLIST_TEST_LESSEQUAL: return a<=b; case SLIST_TEST_LESS: return ab; case SLIST_TEST_GREATEREQUAL: return a>=b; case SLIST_TEST_NOTEQUAL: return a!=b; } return false; } qboolean Master_CompareString(char *a, char *b, slist_test_t rule) { switch(rule) { case SLIST_TEST_CONTAINS: return !!strstr(a, b); case SLIST_TEST_NOTCONTAIN: return !strstr(a, b); case SLIST_TEST_LESSEQUAL: return stricmp(a, b)<=0; case SLIST_TEST_LESS: return stricmp(a, b)<0; case SLIST_TEST_EQUAL: return stricmp(a, b)==0; case SLIST_TEST_GREATER: return stricmp(a, b)>0; case SLIST_TEST_GREATEREQUAL: return stricmp(a, b)>=0; case SLIST_TEST_NOTEQUAL: return stricmp(a, b)!=0; } return false; } qboolean Master_ServerIsGreater(serverinfo_t *a, serverinfo_t *b) { switch(sortfield) { case SLKEY_ADDRESS: break; case SLKEY_BASEGAME: return Master_CompareInteger(a->special, b->special, SLIST_TEST_LESS); case SLKEY_CUSTOM: break; case SLKEY_FRAGLIMIT: return Master_CompareInteger(a->fl, b->fl, SLIST_TEST_LESS); case SLKEY_FREEPLAYERS: return Master_CompareInteger(a->maxplayers - a->players, b->maxplayers - b->players, SLIST_TEST_LESS); case SLKEY_GAMEDIR: return Master_CompareString(a->gamedir, b->gamedir, SLIST_TEST_LESS); case SLKEY_MAP: return Master_CompareString(a->map, b->map, SLIST_TEST_LESS); case SLKEY_MAXPLAYERS: return Master_CompareInteger(a->maxplayers, b->maxplayers, SLIST_TEST_LESS); case SLKEY_NAME: return Master_CompareString(a->name, b->name, SLIST_TEST_LESS); case SLKEY_NUMPLAYERS: return Master_CompareInteger(a->players, b->players, SLIST_TEST_LESS); case SLKEY_PING: return Master_CompareInteger(a->ping, b->ping, SLIST_TEST_LESS); case SLKEY_TIMELIMIT: return Master_CompareInteger(a->tl, b->tl, SLIST_TEST_LESS); case SLKEY_TOOMANY: break; } return false; } qboolean Master_PassesMasks(serverinfo_t *a) { int i; qboolean val, res; //always filter out dead unresponsive servers. if (!a->ping) return false; val = 1; for (i = 0; i < numvisrules; i++) { switch(visrules[i].fieldindex) { case SLKEY_PING: res = Master_CompareInteger(a->ping, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_NUMPLAYERS: res = Master_CompareInteger(a->players, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_MAXPLAYERS: res = Master_CompareInteger(a->maxplayers, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_FREEPLAYERS: res = Master_CompareInteger(a->freeslots, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_NUMBOTS: res = Master_CompareInteger(a->numbots, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_NUMHUMANS: res = Master_CompareInteger(a->numhumans, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_TIMELIMIT: res = Master_CompareInteger(a->tl, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_FRAGLIMIT: res = Master_CompareInteger(a->fl, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_PROTOCOL: res = Master_CompareInteger(a->fl, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_MAP: res = Master_CompareString(a->map, visrules[i].operands, visrules[i].compareop); break; case SLKEY_NAME: res = Master_CompareString(a->name, visrules[i].operands, visrules[i].compareop); break; case SLKEY_GAMEDIR: res = Master_CompareString(a->gamedir, visrules[i].operands, visrules[i].compareop); break; case SLKEY_BASEGAME: res = Master_CompareInteger(a->special, visrules[i].operandi, visrules[i].compareop); break; case SLKEY_MOD: res = Master_CompareString(a->modname, visrules[i].operands, visrules[i].compareop); break; case SLKEY_QCSTATUS: res = Master_CompareString(a->qcstatus, visrules[i].operands, visrules[i].compareop); break; default: continue; } if (visrules[i].or) val |= res; else val &= res; } return val; } void Master_ClearMasks(void) { numvisrules = 0; } void Master_SetMaskString(qboolean or, hostcachekey_t field, char *param, slist_test_t testop) { if (numvisrules == MAX_VISRULES) return; //just don't add it. nextsort = 0; visrules[numvisrules].fieldindex = field; visrules[numvisrules].compareop = testop; visrules[numvisrules].operands = param; visrules[numvisrules].or = or; numvisrules++; } void Master_SetMaskInteger(qboolean or, hostcachekey_t field, int param, slist_test_t testop) { if (numvisrules == MAX_VISRULES) return; //just don't add it. nextsort = 0; visrules[numvisrules].fieldindex = field; visrules[numvisrules].compareop = testop; visrules[numvisrules].operandi = param; visrules[numvisrules].or = or; numvisrules++; } void Master_SetSortField(hostcachekey_t field, qboolean descending) { nextsort = 0; sortfield = field; decreasingorder = descending; } hostcachekey_t Master_GetSortField(void) { return sortfield; } qboolean Master_GetSortDescending(void) { return decreasingorder; } void Master_ShowServer(serverinfo_t *server) { int i; if (!numvisibleservers) { Master_InsertAt(server, 0); return; } if (!decreasingorder) { for (i = 0; i < numvisibleservers; i++) { if (!Master_ServerIsGreater(server, visibleservers[i])) { Master_InsertAt(server, i); return; } } } else { for (i = 0; i < numvisibleservers; i++) { if (Master_ServerIsGreater(server, visibleservers[i])) { Master_InsertAt(server, i); return; } } } Master_InsertAt(server, numvisibleservers); } void Master_ResortServer(serverinfo_t *server) { if (server->insortedlist) { if (!Master_PassesMasks(server)) Master_HideServer(server); } else { if (Master_PassesMasks(server)) Master_ShowServer(server); } } void Master_SortServers(void) { serverinfo_t *server; int total = Master_TotalCount(); if (maxvisibleservers < total) { maxvisibleservers = total; visibleservers = BZ_Realloc(visibleservers, maxvisibleservers*sizeof(serverinfo_t*)); } { numvisibleservers = 0; for (server = firstserver; server; server = server->next) server->insortedlist = false; } for (server = firstserver; server; server = server->next) { Master_ResortServer(server); } nextsort = Sys_DoubleTime() + 8; } serverinfo_t *Master_SortedServer(int idx) { // if (nextsort < Sys_DoubleTime()) // Master_SortServers(); if (idx < 0 || idx >= numvisibleservers) return NULL; return visibleservers[idx]; } int Master_NumSorted(void) { if (nextsort < Sys_DoubleTime()) Master_SortServers(); return numvisibleservers; } float Master_ReadKeyFloat(serverinfo_t *server, int keynum) { if (!server) return -1; else if (keynum < SLKEY_CUSTOM) { switch(keynum) { case SLKEY_PING: return server->ping; case SLKEY_NUMPLAYERS: return server->players; case SLKEY_MAXPLAYERS: return server->maxplayers; case SLKEY_FREEPLAYERS: return server->maxplayers - server->players; case SLKEY_BASEGAME: return server->special; case SLKEY_TIMELIMIT: return server->tl; case SLKEY_FRAGLIMIT: return server->fl; case SLKEY_PROTOCOL: return server->protocol; case SLKEY_NUMBOTS: return server->numbots; case SLKEY_NUMHUMANS: return server->numhumans; case SLKEY_ISFAVORITE: return !!(server->special & SS_FAVORITE); default: return atof(Master_ReadKeyString(server, keynum)); } } else if (server->moreinfo) return atof(Info_ValueForKey(server->moreinfo->info, slist_keyname[keynum-SLKEY_CUSTOM])); return 0; } char *Master_ReadKeyString(serverinfo_t *server, int keynum) { char adr[MAX_ADR_SIZE]; if (keynum < SLKEY_CUSTOM) { switch(keynum) { case SLKEY_MAP: return server->map; case SLKEY_NAME: return server->name; case SLKEY_ADDRESS: return NET_AdrToString(adr, sizeof(adr), server->adr); case SLKEY_GAMEDIR: return server->gamedir; case SLKEY_MOD: return server->modname; case SLKEY_QCSTATUS: return server->qcstatus; default: { static char s[64]; sprintf(s, "%f", Master_ReadKeyFloat(server, keynum)); return s; } } } else if (server->moreinfo) return Info_ValueForKey(server->moreinfo->info, slist_keyname[keynum-SLKEY_CUSTOM]); return ""; } int Master_KeyForName(char *keyname) { int i; if (!strcmp(keyname, "map")) return SLKEY_MAP; else if (!strcmp(keyname, "ping")) return SLKEY_PING; else if (!strcmp(keyname, "name")) return SLKEY_NAME; else if (!strcmp(keyname, "address") || !strcmp(keyname, "cname")) return SLKEY_ADDRESS; else if (!strcmp(keyname, "maxplayers")) return SLKEY_MAXPLAYERS; else if (!strcmp(keyname, "numplayers")) return SLKEY_NUMPLAYERS; else if (!strcmp(keyname, "freeplayers") || !strcmp(keyname, "freeslots")) return SLKEY_FREEPLAYERS; else if (!strcmp(keyname, "gamedir") || !strcmp(keyname, "game") || !strcmp(keyname, "*gamedir")) return SLKEY_GAMEDIR; else if (!strcmp(keyname, "special")) return SLKEY_BASEGAME; else if (!strcmp(keyname, "mod")) return SLKEY_MOD; else if (!strcmp(keyname, "protocol")) return SLKEY_PROTOCOL; else if (!strcmp(keyname, "numbots")) return SLKEY_NUMBOTS; else if (!strcmp(keyname, "numhumans")) return SLKEY_NUMHUMANS; else if (!strcmp(keyname, "qcstatus")) return SLKEY_QCSTATUS; else if (!strcmp(keyname, "isfavorite")) return SLKEY_ISFAVORITE; else if (slist_customkeys == SLIST_MAXKEYS) return SLKEY_TOOMANY; else { for (i = 0; i < slist_customkeys; i++) { if (!strcmp(slist_keyname[i], keyname)) { return i + SLKEY_CUSTOM; } } Q_strncpyz(slist_keyname[slist_customkeys], keyname, MAX_INFO_KEY); slist_customkeys++; return slist_customkeys-1 + SLKEY_CUSTOM; } } void Master_AddMaster (char *address, int type, char *description) { netadr_t adr; master_t *mast; if (!NET_StringToAdr(address, &adr)) { Con_Printf("Failed to resolve address \"%s\"\n", address); return; } #pragma message("Master_AddMaster: add ipv6. don't care about tcp/irc.") if (adr.type != NA_IP && adr.type != NA_IPX) { Con_Printf("Fixme: unable to poll address family\n", address); return; } if (type < MT_SINGLEQW) //broadcasts { if (adr.type == NA_IP) adr.type = NA_BROADCAST_IP; if (adr.type == NA_IPX) adr.type = NA_BROADCAST_IPX; } for (mast = master; mast; mast = mast->next) { if (NET_CompareAdr(mast->adr, adr) && mast->type == type) //already exists. return; } mast = Z_Malloc(sizeof(master_t)+strlen(description)+1+strlen(address)+1); mast->adr = adr; mast->address = mast->name + strlen(description)+1; mast->type = type; strcpy(mast->name, description); strcpy(mast->address, address); mast->next = master; master = mast; } void Master_AddMasterHTTP (char *address, int mastertype, char *description) { master_t *mast; int servertype; if (mastertype == MT_MASTERHTTPQW) servertype = 0; else servertype = SS_NETQUAKE; for (mast = master; mast; mast = mast->next) { if (!strcmp(mast->address, address) && mast->type == mastertype) //already exists. return; } mast = Z_Malloc(sizeof(master_t)+strlen(description)+1+strlen(address)+1); mast->address = mast->name + strlen(description)+1; mast->type = mastertype; mast->servertype = servertype; strcpy(mast->name, description); strcpy(mast->address, address); mast->next = master; master = mast; } //build a linked list of masters. Doesn't duplicate addresses. qboolean Master_LoadMasterList (char *filename, int defaulttype, int depth) { vfsfile_t *f; char line[1024]; char file[1024]; char *name, *next; int servertype; if (depth <= 0) return false; depth--; f = FS_OpenVFS(filename, "rb", FS_BASE); if (!f) return false; while(VFS_GETS(f, line, sizeof(line)-1)) { if (*line == '#') //comment continue; next = COM_Parse(line); if (!*com_token) continue; if (!strcmp(com_token, "file")) //special case. Add a port if you have a server named 'file'... (unlikly) { next = COM_Parse(next); if (!next) continue; Q_strncpyz(file, com_token, sizeof(file)); } else *file = '\0'; *next = '\0'; next++; name = COM_Parse(next); servertype = -1; if (!strcmp(com_token, "single:qw")) servertype = MT_SINGLEQW; else if (!strcmp(com_token, "single:q2")) servertype = MT_SINGLEQ2; else if (!strcmp(com_token, "single:q3")) servertype = MT_SINGLEQ3; else if (!strcmp(com_token, "single:dp")) servertype = MT_SINGLEDP; else if (!strcmp(com_token, "single:nq") || !strcmp(com_token, "single:q1")) servertype = MT_SINGLENQ; else if (!strcmp(com_token, "single")) servertype = MT_SINGLEQW; else if (!strcmp(com_token, "master:dp")) servertype = MT_MASTERDP; else if (!strcmp(com_token, "master:qw")) servertype = MT_MASTERQW; else if (!strcmp(com_token, "master:q2")) servertype = MT_MASTERQ2; else if (!strcmp(com_token, "master:q3")) servertype = MT_MASTERQ3; else if (!strcmp(com_token, "master:http")) servertype = MT_MASTERHTTP; else if (!strcmp(com_token, "master:httpqw")) servertype = MT_MASTERHTTPQW; else if (!strcmp(com_token, "master")) //any other sort of master, assume it's a qw master. servertype = MT_MASTERQW; else if (!strcmp(com_token, "bcast:qw")) servertype = MT_BCASTQW; else if (!strcmp(com_token, "bcast:q2")) servertype = MT_BCASTQ2; else if (!strcmp(com_token, "bcast:q3")) servertype = MT_BCASTQ3; else if (!strcmp(com_token, "bcast:nq")) servertype = MT_BCASTNQ; else if (!strcmp(com_token, "bcast:dp")) servertype = MT_BCASTDP; else if (!strcmp(com_token, "bcast")) servertype = MT_BCASTQW; else if (!strcmp(com_token, "favorite:qw")) servertype = -MT_SINGLEQW; else if (!strcmp(com_token, "favorite:q2")) servertype = -MT_SINGLEQ2; else if (!strcmp(com_token, "favorite:q3")) servertype = -MT_SINGLEQ3; else if (!strcmp(com_token, "favorite:nq")) servertype = -MT_SINGLENQ; else if (!strcmp(com_token, "favorite")) servertype = -MT_SINGLEQW; else { name = next; //go back one token. servertype = defaulttype; } while(*name <= ' ' && *name != 0) //skip whitespace name++; next = name + strlen(name)-1; while(*next <= ' ' && next > name) { *next = '\0'; next--; } if (*file) Master_LoadMasterList(file, servertype, depth); else if (servertype < 0) { if (NET_StringToAdr(line, &net_from)) CL_ReadServerInfo(va("\\hostname\\%s", name), -servertype, true); else Con_Printf("Failed to resolve address - \"%s\"\n", line); } else { switch (servertype) { case MT_MASTERHTTP: case MT_MASTERHTTPQW: Master_AddMasterHTTP(line, servertype, name); break; default: Master_AddMaster(line, servertype, name); } } } VFS_CLOSE(f); return true; } void NET_SendPollPacket(int len, void *data, netadr_t to) { int ret; struct sockaddr_qstorage addr; #pragma message("NET_SendPollPacket: no support for ipv6") NetadrToSockadr (&to, &addr); #ifdef USEIPX if (((struct sockaddr*)&addr)->sa_family == AF_IPX) { lastpollsockIPX++; if (lastpollsockIPX>=POLLIPXSOCKETS) lastpollsockIPX=0; if (pollsocketsIPX[lastpollsockIPX]==INVALID_SOCKET) pollsocketsIPX[lastpollsockIPX] = IPX_OpenSocket(PORT_ANY, true); if (pollsocketsIPX[lastpollsockIPX]==INVALID_SOCKET) return; //bother ret = sendto (pollsocketsIPX[lastpollsockIPX], data, len, 0, (struct sockaddr *)&addr, sizeof(addr) ); } else #endif if (((struct sockaddr*)&addr)->sa_family == AF_INET) { lastpollsockUDP++; if (lastpollsockUDP>=POLLUDPSOCKETS) lastpollsockUDP=0; if (pollsocketsUDP[lastpollsockUDP]==INVALID_SOCKET) pollsocketsUDP[lastpollsockUDP] = UDP_OpenSocket(PORT_ANY, true); if (pollsocketsUDP[lastpollsockUDP]==INVALID_SOCKET) return; //bother ret = sendto (pollsocketsUDP[lastpollsockUDP], data, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in) ); } else return; if (ret == -1) { // wouldblock is silent if (qerrno == EWOULDBLOCK) return; if (qerrno == ECONNREFUSED) return; if (qerrno == EADDRNOTAVAIL) Con_DPrintf("NET_SendPollPacket Warning: %i\n", qerrno); else Con_Printf ("NET_SendPollPacket ERROR: %i\n", qerrno); } } int NET_CheckPollSockets(void) { #define MAX_UDP_PACKET 8192 // one more than msg + header extern qbyte net_message_buffer[MAX_UDP_PACKET]; int sock; SOCKET usesocket; char adr[MAX_ADR_SIZE]; for (sock = 0; sock < POLLUDPSOCKETS+POLLIPXSOCKETS; sock++) { int ret; struct sockaddr_qstorage from; int fromlen; #ifdef USEIPX if (sock >= POLLUDPSOCKETS) usesocket = pollsocketsIPX[sock-POLLUDPSOCKETS]; else #endif usesocket = pollsocketsUDP[sock]; if (usesocket == INVALID_SOCKET) continue; fromlen = sizeof(from); ret = recvfrom (usesocket, (char *)net_message_buffer, sizeof(net_message_buffer), 0, (struct sockaddr *)&from, &fromlen); if (ret == -1) { if (qerrno == EWOULDBLOCK) continue; if (qerrno == EMSGSIZE) { SockadrToNetadr (&from, &net_from); Con_Printf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), net_from)); continue; } if (qerrno == ECONNABORTED || qerrno == ECONNRESET) { // Con_Printf ("Connection lost or aborted\n"); continue; } Con_Printf ("NET_CheckPollSockets: %i, %s\n", qerrno, strerror(qerrno)); continue; } SockadrToNetadr (&from, &net_from); net_message.cursize = ret; if (ret == sizeof(net_message_buffer) ) { Con_Printf ("Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), net_from)); continue; } if (*(int *)net_message.data == -1) { int c; char *s; MSG_BeginReading (); MSG_ReadLong (); // skip the -1 c = msg_readcount; s = MSG_ReadStringLine(); //peek for q2 messages. #ifdef Q2CLIENT if (!strcmp(s, "print")) { CL_ReadServerInfo(MSG_ReadString(), MT_SINGLEQ2, false); continue; } if (!strcmp(s, "info")) //parse a bit more... { CL_ReadServerInfo(MSG_ReadString(), MT_SINGLEQ2, false); continue; } if (!strncmp(s, "servers", 6)) //parse a bit more... { msg_readcount = c+7; CL_MasterListParse(SS_QUAKE2, false); continue; } #endif #ifdef Q3CLIENT if (!strcmp(s, "statusResponse")) { CL_ReadServerInfo(MSG_ReadString(), MT_SINGLEQ3, false); continue; } #endif if (!strncmp(s, "getserversResponse\\", 19)) //parse a bit more... { msg_readcount = c+18-1; CL_MasterListParse(SS_DARKPLACES, true); continue; } if (!strcmp(s, "infoResponse")) //parse a bit more... { CL_ReadServerInfo(MSG_ReadString(), MT_SINGLEDP, false); continue; } msg_readcount = c; c = MSG_ReadByte (); if (c == A2C_PRINT) //qw server reply. { CL_ReadServerInfo(MSG_ReadString(), MT_SINGLEQW, false); continue; } if (c == M2C_MASTER_REPLY) //qw master reply. { CL_MasterListParse(false, false); continue; } } #ifdef NQPROT else { //connected packet? Must be a NQ packet. char name[32]; char map[16]; int users, maxusers; int control; MSG_BeginReading (); control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); if (control == -1) continue; if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL) continue; if ((control & NETFLAG_LENGTH_MASK) != ret) continue; if (MSG_ReadByte() != CCREP_SERVER_INFO) continue; NET_StringToAdr(MSG_ReadString(), &net_from); Q_strncpyz(name, MSG_ReadString(), sizeof(name)); Q_strncpyz(map, MSG_ReadString(), sizeof(map)); users = MSG_ReadByte(); maxusers = MSG_ReadByte(); if (MSG_ReadByte() != NET_PROTOCOL_VERSION) { // Q_strcpy(name, "*"); // Q_strcat(name, name); } CL_ReadServerInfo(va("\\hostname\\%s\\map\\%s\\maxclients\\%i", name, map, maxusers), MT_SINGLENQ, false); } #endif continue; } return 0; } void Master_RemoveKeepInfo(serverinfo_t *sv) { sv->special &= ~SS_KEEPINFO; if (sv->moreinfo) { Z_Free(sv->moreinfo); sv->moreinfo = NULL; } } void SListOptionChanged(serverinfo_t *newserver) { if (selectedserver.inuse) { serverinfo_t *oldserver; selectedserver.detail = NULL; if (!slist_cacheinfo.value) //we have to flush it. That's the rules. { for (oldserver = firstserver; oldserver; oldserver=oldserver->next) { if (NET_CompareAdr(selectedserver.adr, oldserver->adr))//*(int*)selectedserver.ipaddress == *(int*)server->ipaddress && selectedserver.port == server->port) { if (oldserver->moreinfo) { Z_Free(oldserver->moreinfo); oldserver->moreinfo = NULL; } break; } } } if (!newserver) return; selectedserver.adr = newserver->adr; if (newserver->moreinfo) //we cached it. { selectedserver.detail = newserver->moreinfo; return; } //we don't know all the info, so send a request for it. selectedserver.detail = newserver->moreinfo = Z_Malloc(sizeof(serverdetailedinfo_t)); newserver->moreinfo->numplayers = newserver->players; strcpy(newserver->moreinfo->info, ""); Info_SetValueForKey(newserver->moreinfo->info, "hostname", newserver->name, sizeof(newserver->moreinfo->info)); newserver->sends++; Master_QueryServer(newserver); } } #ifdef WEBCLIENT void MasterInfo_ProcessHTTP(char *name, qboolean success, int type) { netadr_t adr; char *s; char *el; serverinfo_t *info; char adrbuf[MAX_ADR_SIZE]; if (!success) return; el = COM_LoadTempFile(name); if (!el) return; while(*el) { s = el; while(*s <= ' ' && *s != '\n' && *s) s++; el = strchr(s, '\n'); if (!el) el = s + strlen(s); else if (el>s && el[-1] == '\r') el[-1] = '\0'; if (*s == '#') //hash is a comment, apparently. continue; *el = '\0'; el++; if (!NET_StringToAdr(s, &adr)) continue; if ((info = Master_InfoForServer(adr))) //remove if the server already exists. { info->sends = 1; //reset. } else { info = Z_Malloc(sizeof(serverinfo_t)); info->adr = adr; info->sends = 1; info->special = type; info->refreshtime = 0; snprintf(info->name, sizeof(info->name), "%s", NET_AdrToString(adrbuf, sizeof(adrbuf), info->adr)); info->next = firstserver; firstserver = info; Master_ResortServer(info); } } FS_Remove(name, FS_GAME); } // wrapper functions for the different server types void MasterInfo_ProcessHTTPNQ(char *name, qboolean success) { MasterInfo_ProcessHTTP(name, success, SS_NETQUAKE); } void MasterInfo_ProcessHTTPQW(char *name, qboolean success) { MasterInfo_ProcessHTTP(name, success, 0); } #endif //don't try sending to servers we don't support void MasterInfo_Request(master_t *mast, qboolean evenifwedonthavethefiles) { static int mastersequence; if (!mast) return; switch(mast->type) { #ifdef Q3CLIENT case MT_BCASTQ3: case MT_SINGLEQ3: NET_SendPollPacket (14, va("%c%c%c%cgetstatus\n", 255, 255, 255, 255), mast->adr); break; case MT_MASTERQ3: { char *str; str = va("%c%c%c%cgetservers %u empty full\x0A\n", 255, 255, 255, 255, 68); NET_SendPollPacket (strlen(str), str, mast->adr); } break; #endif #ifdef Q2CLIENT case MT_BCASTQ2: case MT_SINGLEQ2: #endif case MT_SINGLEQW: case MT_BCASTQW: NET_SendPollPacket (11, va("%c%c%c%cstatus\n", 255, 255, 255, 255), mast->adr); break; #ifdef NQPROT case MT_BCASTNQ: case MT_SINGLENQ: SZ_Clear(&net_message); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; MSG_WriteLong(&net_message, 0);// save space for the header, filled in later MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); MSG_WriteString(&net_message, NET_GAMENAME_NQ); //look for either sort of server MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); NET_SendPollPacket(net_message.cursize, net_message.data, mast->adr); SZ_Clear(&net_message); break; case MT_MASTERDP: { char *str; str = va("%c%c%c%cgetservers %s %u empty full\x0A\n", 255, 255, 255, 255, com_gamename.string, 3); NET_SendPollPacket (strlen(str), str, mast->adr); } break; case MT_SINGLEDP: case MT_BCASTDP: { char *str; str = va("%c%c%c%cgetinfo", 255, 255, 255, 255); NET_SendPollPacket (strlen(str), str, mast->adr); } break; #endif case MT_MASTERQW: NET_SendPollPacket (3, "c\n", mast->adr); break; #ifdef Q2CLIENT case MT_MASTERQ2: if (evenifwedonthavethefiles || COM_FDepthFile("pics/colormap.pcx", true)!=0x7fffffff) //only query this master if we expect to be able to load it's maps. NET_SendPollPacket (6, "query", mast->adr); break; #endif #ifdef WEBCLIENT case MT_MASTERHTTP: HTTP_CL_Get(mast->address, va("master_%i_%i.tmp", mastersequence++, mast->servertype), MasterInfo_ProcessHTTPNQ); break; case MT_MASTERHTTPQW: HTTP_CL_Get(mast->address, va("master_%i_%i.tmp", mastersequence++, mast->servertype), MasterInfo_ProcessHTTPQW); break; #endif } } void MasterInfo_WriteServers(void) { char *typename; master_t *mast; serverinfo_t *server; FILE *mf, *qws; char adr[MAX_ADR_SIZE]; mf = fopen("masters.txt", "wt"); if (!mf) { Con_Printf("Couldn't write masters.txt"); return; } for (mast = master; mast; mast=mast->next) { switch(mast->type) { case MT_MASTERQW: typename = "master:qw"; break; case MT_MASTERQ2: typename = "master:q2"; break; case MT_MASTERQ3: typename = "master:q3"; break; case MT_MASTERDP: typename = "master:dp"; break; case MT_MASTERHTTP: typename = "master:http"; break; case MT_MASTERHTTPQW: typename = "master:httpqw"; break; case MT_BCASTQW: typename = "bcast:qw"; break; case MT_BCASTQ2: typename = "bcast:q2"; break; case MT_BCASTQ3: typename = "bcast:q3"; break; case MT_BCASTNQ: typename = "bcast:nq"; break; case MT_SINGLEQW: typename = "single:qw"; break; case MT_SINGLEQ2: typename = "single:q2"; break; case MT_SINGLEQ3: typename = "single:q3"; break; case MT_SINGLENQ: typename = "single:nq"; break; case MT_SINGLEDP: typename = "single:dp"; break; default: typename = "writeerror"; } if (mast->address) fprintf(mf, "%s\t%s\t%s\n", mast->address , typename, mast->name); else fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), mast->adr), typename, mast->name); } if (slist_writeserverstxt.value) qws = fopen("servers.txt", "wt"); else qws = NULL; if (qws) fprintf(mf, "\n%s\t%s\t%s\n\n", "file servers.txt", "favorite:qw", "personal server list"); for (server = firstserver; server; server = server->next) { if (server->special & SS_FAVORITE) { if (server->special & SS_QUAKE3) fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), server->adr), "favorite:q3", server->name); else if (server->special & SS_QUAKE2) fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), server->adr), "favorite:q2", server->name); else if (server->special & SS_NETQUAKE) fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), server->adr), "favorite:nq", server->name); else if (qws) //servers.txt doesn't support the extra info. fprintf(qws, "%s\t%s\n", NET_AdrToString(adr, sizeof(adr), server->adr), server->name); else //read only? damn them! fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(adr, sizeof(adr), server->adr), "favorite:qw", server->name); } } if (qws) fclose(qws); fclose(mf); } //poll master servers for server lists. void MasterInfo_Begin(void) { master_t *mast; if (!Master_LoadMasterList("masters.txt", MT_MASTERQW, 5)) { Master_LoadMasterList("servers.txt", MT_SINGLEQW, 1); // if (q1servers) //qw master servers { Master_AddMasterHTTP("http://www.gameaholic.com/servers/qspy-quakeworld", MT_MASTERHTTPQW, "gameaholic's QW master"); Master_AddMaster("192.246.40.37:27000", MT_MASTERQW, "id Limbo"); Master_AddMaster("192.246.40.37:27002", MT_MASTERQW, "id CTF"); Master_AddMaster("192.246.40.37:27003", MT_MASTERQW, "id TeamFortress"); Master_AddMaster("192.246.40.37:27004", MT_MASTERQW, "id Miscilaneous"); Master_AddMaster("192.246.40.37:27006", MT_MASTERQW, "id Deathmatch Only"); Master_AddMaster("150.254.66.120:27000", MT_MASTERQW, "Poland's master server."); Master_AddMaster("62.112.145.129:27000", MT_MASTERQW, "Ocrana master server."); // Master_AddMaster("master.edome.net", MT_MASTERQW, "edome master server."); //down Master_AddMaster("qwmaster.barrysworld.com", MT_MASTERQW, "barrysworld master server."); Master_AddMaster("qwmaster.ocrana.de:27000", MT_MASTERQW, "Ocrana2 master server."); Master_AddMaster("213.221.174.165:27000", MT_MASTERQW, "unknown1 master server."); Master_AddMaster("195.74.0.8", MT_MASTERQW, "unknown2 master server."); Master_AddMaster("192.246.40.37", MT_MASTERQW, "unknown3 master server."); Master_AddMaster("192.246.40.37:27006", MT_MASTERQW, "unknown4 master server."); Master_AddMaster("204.182.161.2", MT_MASTERQW, "unknown5 master server."); Master_AddMaster("255.255.255.255:27500", MT_BCASTQW, "Nearby QuakeWorld UDP servers."); } // if (q1servers) //nq master servers { Master_AddMasterHTTP("http://www.gameaholic.com/servers/qspy-quake", MT_MASTERHTTP, "gameaholic's NQ master"); Master_AddMaster("255.255.255.255:26000", MT_BCASTNQ, "Nearby Quake1 servers"); Master_AddMaster("ghdigital.com:27950", MT_MASTERDP, "DarkPlaces Master 1"); Master_AddMaster("dpmaster.deathmask.net:27950", MT_MASTERDP, "DarkPlaces Master 2"); Master_AddMaster("12.166.196.192:27950", MT_MASTERDP, "DarkPlaces Master 3"); Master_AddMaster("255.255.255.255:26000", MT_BCASTDP, "Nearby DarkPlaces servers"); } // if (q2servers) //q2 { Master_AddMaster("255.255.255.255:27910", MT_BCASTQ2, "Nearby Quake2 UDP servers."); Master_AddMaster("00000000:ffffffffffff:27910", MT_BCASTQ2, "Nearby Quake2 IPX servers."); Master_AddMaster("192.246.40.37:27900", MT_MASTERQ2, "id q2 Master."); } //q3 { Master_AddMaster("255.255.255.255:27960", MT_BCASTQ3, "Nearby Quake3 UDP servers."); Master_AddMaster("master.quake3arena.com:27950", MT_MASTERQ3, "Quake3 master server."); } } for (mast = master; mast; mast=mast->next) { MasterInfo_Request(mast, false); } } void Master_QueryServer(serverinfo_t *server) { char data[2048]; server->sends--; server->refreshtime = Sys_DoubleTime(); if (server->special & SS_QUAKE3) sprintf(data, "%c%c%c%cgetstatus", 255, 255, 255, 255); else if (server->special & SS_DARKPLACES) sprintf(data, "%c%c%c%cgetinfo", 255, 255, 255, 255); else if (server->special & SS_NETQUAKE) { #ifdef NQPROT SZ_Clear(&net_message); net_message.packing = SZ_RAWBYTES; net_message.currentbit = 0; MSG_WriteLong(&net_message, 0);// save space for the header, filled in later MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); MSG_WriteString(&net_message, NET_GAMENAME_NQ); //look for either sort of server MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); NET_SendPollPacket(net_message.cursize, net_message.data, server->adr); SZ_Clear(&net_message); #endif return; } else sprintf(data, "%c%c%c%cstatus", 255, 255, 255, 255); NET_SendPollPacket (strlen(data), data, server->adr); } //send a packet to each server in sequence. void CL_QueryServers(void) { static int poll; int op; serverinfo_t *server; extern cvar_t sb_hidequake2; extern cvar_t sb_hidequake3; extern cvar_t sb_hidenetquake; extern cvar_t sb_hidequakeworld; op = poll; for (server = firstserver; op>0 && server; server=server->next, op--); if (!server) { poll = 0; return; } if (op == 0) { //we only want to send poll packets to servers which will not be filtered (otherwise it's pointless) while(server) { if (server->special & SS_QUAKE3 && !sb_hidequake3.value) break; if (server->special & SS_QUAKE2 && !sb_hidequake2.value) break; if ((server->special & (SS_NETQUAKE|SS_DARKPLACES)) && !sb_hidenetquake.value) break; if ((server->special & (SS_QUAKE3|SS_QUAKE2|SS_DARKPLACES|SS_NETQUAKE))==0 && !sb_hidequakeworld.value) break; server = server->next; poll++; } if (!server) { server = firstserver; while (server) { if (server->special & SS_QUAKE3 && !sb_hidequake3.value) break; if (server->special & SS_QUAKE2 && !sb_hidequake2.value) break; if ((server->special & (SS_NETQUAKE|SS_DARKPLACES)) && !sb_hidenetquake.value) break; if ((server->special & (SS_QUAKE3|SS_QUAKE2|SS_DARKPLACES|SS_NETQUAKE))==0 && !sb_hidequakeworld.value) break; server = server->next; poll++; } } if (server && server->sends > 0) { Master_QueryServer(server); } poll++; return; } poll = 0; } int Master_TotalCount(void) { int count=0; serverinfo_t *info; for (info = firstserver; info; info = info->next) { count++; } return count; } //true if server is on a different master's list. serverinfo_t *Master_InfoForServer (netadr_t addr) { serverinfo_t *info; for (info = firstserver; info; info = info->next) { if (NET_CompareAdr(info->adr, addr)) return info; } return NULL; } serverinfo_t *Master_InfoForNum (int num) { serverinfo_t *info; for (info = firstserver; info; info = info->next) { if (num-- <=0) return info; } return NULL; } void MasterInfo_RemovePlayers(netadr_t adr) { player_t *p, *prev; prev = NULL; for (p = mplayers; p; ) { if (NET_CompareAdr(p->adr, adr)) { if (prev) prev->next = p->next; else mplayers = p->next; Z_Free(p); p=prev; continue; } else prev = p; p = p->next; } } void MasterInfo_AddPlayer(netadr_t serveradr, char *name, int ping, int frags, int colours, char *skin) { player_t *p; p = Z_Malloc(sizeof(player_t)); p->next = mplayers; p->adr = serveradr; p->colour = colours; p->frags = frags; Q_strncpyz(p->name, name, sizeof(p->name)); Q_strncpyz(p->skin, skin, sizeof(p->skin)); mplayers = p; } //we got told about a server, parse it's info int CL_ReadServerInfo(char *msg, int servertype, qboolean favorite) { serverdetailedinfo_t details; char *token; char *nl; char *name; int ping; int len; serverinfo_t *info; char adr[MAX_ADR_SIZE]; info = Master_InfoForServer(net_from); if (!info) //not found... { if (atoi(Info_ValueForKey(msg, "sv_punkbuster"))) return false; //never add servers that require punkbuster. :( if (atoi(Info_ValueForKey(msg, "sv_pure"))) return false; //we don't support the filesystem hashing. :( info = Z_Malloc(sizeof(serverinfo_t)); info->adr = net_from; snprintf(info->name, sizeof(info->name), "%s", NET_AdrToString(adr, sizeof(adr), info->adr)); info->next = firstserver; firstserver = info; } else { MasterInfo_RemovePlayers(info->adr); } nl = strchr(msg, '\n'); if (nl) { *nl = '\0'; nl++; } name = Info_ValueForKey(msg, "hostname"); if (!*name) name = Info_ValueForKey(msg, "sv_hostname"); Q_strncpyz(info->name, name, sizeof(info->name)); info->special = info->special & (SS_FAVORITE | SS_KEEPINFO); //favorite is never cleared if (!strcmp(DISTRIBUTION, Info_ValueForKey(msg, "*distrib"))) info->special |= SS_FTESERVER; else if (!strncmp(DISTRIBUTION, Info_ValueForKey(msg, "*version"), 3)) info->special |= SS_FTESERVER; if (servertype == MT_SINGLEDP) { if (atoi(Info_ValueForKey(msg, "protocol")) > 60) info->special |= SS_QUAKE3; else info->special |= SS_DARKPLACES; } else if (servertype == MT_SINGLEQ2) info->special |= SS_QUAKE2; else if (servertype == MT_SINGLEQ3) info->special |= SS_QUAKE3; else if (servertype == MT_SINGLENQ) info->special |= SS_NETQUAKE; if (favorite) //was specifically named, not retrieved from a master. info->special |= SS_FAVORITE; ping = (Sys_DoubleTime() - info->refreshtime)*1000; if (ping > 0xffff) info->ping = 0xffff; else info->ping = ping; info->players = 0; info->maxplayers = atoi(Info_ValueForKey(msg, "maxclients")); if (!info->maxplayers) info->maxplayers = atoi(Info_ValueForKey(msg, "sv_maxclients")); info->tl = atoi(Info_ValueForKey(msg, "timelimit")); info->fl = atoi(Info_ValueForKey(msg, "fraglimit")); if (*Info_ValueForKey(msg, "*qtv")) info->special |= SS_PROXY|SS_FTESERVER; if (!strcmp(Info_ValueForKey(msg, "*progs"), "666") && !strcmp(Info_ValueForKey(msg, "*version"), "2.91")) info->special |= SS_PROXY; //qizmo if (servertype == MT_SINGLEQ3 || servertype == MT_SINGLEQ2 || servertype == MT_SINGLEDP) { Q_strncpyz(info->gamedir, Info_ValueForKey(msg, "gamename"), sizeof(info->gamedir)); Q_strncpyz(info->map, Info_ValueForKey(msg, "mapname"), sizeof(info->map)); } else { Q_strncpyz(info->gamedir, Info_ValueForKey(msg, "*gamedir"), sizeof(info->gamedir)); Q_strncpyz(info->map, Info_ValueForKey(msg, "map"), sizeof(info->map)); } Q_strncpyz(info->qcstatus, Info_ValueForKey(msg, "qcstatus"), sizeof(info->qcstatus)); Q_strncpyz(info->modname, Info_ValueForKey(msg, "modname"), sizeof(info->modname)); info->protocol = atoi(Info_ValueForKey(msg, "protocol")); info->gameversion = atoi(Info_ValueForKey(msg, "gameversion")); info->numbots = atoi(Info_ValueForKey(msg, "bots")); info->numhumans = info->players - info->numbots; info->freeslots = info->maxplayers - info->players; strcpy(details.info, msg); msg = msg+strlen(msg)+1; info->players=details.numplayers = 0; if (!strchr(msg, '\n')) info->players = atoi(Info_ValueForKey(details.info, "clients")); else { int clnum; for (clnum=0; clnum < MAX_CLIENTS; clnum++) { nl = strchr(msg, '\n'); if (!nl) break; *nl = '\0'; token = msg; if (!token) break; details.players[clnum].userid = atoi(token); token = strchr(token+1, ' '); if (!token) break; details.players[clnum].frags = atoi(token); token = strchr(token+1, ' '); if (!token) break; details.players[clnum].time = atoi(token); msg = token; token = strchr(msg+1, ' '); if (!token) //probably q2 response { //see if this is actually a Quake2 server. token = strchr(msg+1, '\"'); if (!token) //it wasn't. break; details.players[clnum].ping = details.players[clnum].frags; details.players[clnum].frags = details.players[clnum].userid; msg = strchr(token+1, '\"'); if (!msg) break; len = msg - token; if (len >= sizeof(details.players[clnum].name)) len = sizeof(details.players[clnum].name); Q_strncpyz(details.players[clnum].name, token+1, len); details.players[clnum].skin[0] = '\0'; details.players[clnum].topc = 0; details.players[clnum].botc = 0; details.players[clnum].time = 0; } else //qw responce { details.players[clnum].time = atoi(token); msg = token; token = strchr(msg+1, ' '); if (!token) break; details.players[clnum].ping = atoi(token); token = strchr(token+1, '\"'); if (!token) break; msg = strchr(token+1, '\"'); if (!msg) break; len = msg - token; if (len >= sizeof(details.players[clnum].name)) len = sizeof(details.players[clnum].name); Q_strncpyz(details.players[clnum].name, token+1, len); details.players[clnum].name[len] = '\0'; token = strchr(msg+1, '\"'); if (!token) break; msg = strchr(token+1, '\"'); if (!msg) break; len = msg - token; if (len >= sizeof(details.players[clnum].skin)) len = sizeof(details.players[clnum].skin); Q_strncpyz(details.players[clnum].skin, token+1, len); details.players[clnum].skin[len] = '\0'; token = strchr(msg+1, ' '); if (!token) break; details.players[clnum].topc = atoi(token); token = strchr(token+1, ' '); if (!token) break; details.players[clnum].botc = atoi(token); } MasterInfo_AddPlayer(info->adr, details.players[clnum].name, details.players[clnum].ping, details.players[clnum].frags, details.players[clnum].topc*4 | details.players[clnum].botc, details.players[clnum].skin); info->players = ++details.numplayers; msg = nl; if (!msg) break; //erm... msg++; } } if (!info->moreinfo && ((slist_cacheinfo.value == 2 || NET_CompareAdr(info->adr, selectedserver.adr)) || (info->special & SS_KEEPINFO))) info->moreinfo = Z_Malloc(sizeof(serverdetailedinfo_t)); if (NET_CompareAdr(info->adr, selectedserver.adr)) selectedserver.detail = info->moreinfo; if (info->moreinfo) memcpy(info->moreinfo, &details, sizeof(serverdetailedinfo_t)); return true; } //rewrite to scan for existing server instead of wiping all. void CL_MasterListParse(int type, qboolean slashpad) { serverinfo_t *info; serverinfo_t *last, *old; int p1, p2; char adr[MAX_ADR_SIZE]; MSG_ReadByte (); last = firstserver; while(msg_readcount+6 < net_message.cursize) { if (slashpad) { if (MSG_ReadByte() != '\\') break; } info = Z_Malloc(sizeof(serverinfo_t)); info->adr.type = NA_IP; info->adr.address.ip[0] = MSG_ReadByte(); info->adr.address.ip[1] = MSG_ReadByte(); info->adr.address.ip[2] = MSG_ReadByte(); info->adr.address.ip[3] = MSG_ReadByte(); p1 = MSG_ReadByte(); p2 = MSG_ReadByte(); info->adr.port = htons((unsigned short)((p1<<8)|p2)); if (!info->adr.port) { Z_Free(info); break; } if ((old = Master_InfoForServer(info->adr))) //remove if the server already exists. { old->sends = 1; //reset. Z_Free(info); } else { info->sends = 1; info->special = type; info->refreshtime = 0; snprintf(info->name, sizeof(info->name), "%s", NET_AdrToString(adr, sizeof(adr), info->adr)); info->next = last; last = info; Master_ResortServer(info); } } firstserver = last; } #endif