From 70f7f338382f3420e7a24a465c8077eb0403bbd4 Mon Sep 17 00:00:00 2001 From: Spoike Date: Fri, 15 Oct 2004 00:47:51 +0000 Subject: [PATCH] server browser plugin files. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@343 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- plugins/serverb/cl_master.h | 149 +++++ plugins/serverb/m_master.c | 951 +++++++++++++++++++++++++++++++ plugins/serverb/net_master.c | 1027 ++++++++++++++++++++++++++++++++++ plugins/serverb/serverb.dsp | 130 +++++ 4 files changed, 2257 insertions(+) create mode 100644 plugins/serverb/cl_master.h create mode 100644 plugins/serverb/m_master.c create mode 100644 plugins/serverb/net_master.c create mode 100644 plugins/serverb/serverb.dsp diff --git a/plugins/serverb/cl_master.h b/plugins/serverb/cl_master.h new file mode 100644 index 000000000..27b7f3bd8 --- /dev/null +++ b/plugins/serverb/cl_master.h @@ -0,0 +1,149 @@ +#include "../plugin.h" + + +#define MAX_INFO_STRING 196 +#define MAX_SERVERINFO_STRING 512 +#define MAX_LOCALINFO_STRING 32768 +#define MAX_CLIENTS 32 + + +#define A2C_PRINT 'n' // print a message on client +#define M2C_MASTER_REPLY 'd' // + \n + qw server port list + + +#define PORT_ANY -1 + +#ifdef _WIN32 +#include "winsock.h" +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNREFUSED WSAECONNREFUSED +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define EMSGSIZE WSAEMSGSIZE +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET + +#define qerrno WSAGetLastError() +#else +#define qerrno errno + +#include +#include +#include +#include +typedef int SOCKET; +#endif + + + + + +#define SS_FTESERVER 1 //hehehe... +#define SS_QUAKE2 2 //useful (and cool). Could be blamed for swamping. +#define SS_NETQUAKE 4 +#define SS_FAVORITE 8 //filter all others. +#define SS_KEEPINFO 16 + + +//despite not supporting nq or q2, we still load them. We just filter them. This is to make sure we properly write the listing files. +#define MT_BAD 0 //this would be an error +#define MT_BCASTQW 1 //-1status +#define MT_BCASTQ2 2 //-1status +#define MT_BCASTNQ 3 //see code +#define MT_SINGLEQW 4 //-1status +#define MT_SINGLEQ2 5 //-1status +#define MT_SINGLENQ 6 //see code. +#define MT_MASTERQW 7 //c\n\0 +#define MT_MASTERQ2 8 //query + + +//contains info about a server in greater detail. Could be too mem intensive. +typedef struct serverdetailedinfo_s { + char info[MAX_SERVERINFO_STRING]; + + int numplayers; + + struct { + int userid; + int frags; + float time; + int ping; + char name[64]; + char skin[64]; + char topc; + char botc; + } players[MAX_CLIENTS]; +} serverdetailedinfo_t; + +//hold minimum info. +typedef struct serverinfo_s { + char name[64]; //hostname. + struct sockaddr_in adr; + + short players; + short maxplayers; + + short tl; + short fl; + char gamedir[8+1]; + char map[8+1]; + + int refreshtime; //msecs + qbyte special; //flags + unsigned short ping; + int sends; + + serverdetailedinfo_t *moreinfo; + + struct serverinfo_s *next; +} serverinfo_t; + +typedef struct master_s{ + struct master_s *next; + struct sockaddr_in adr; + int type; + char name[1]; +} master_t; + +struct { + qboolean inuse; + struct sockaddr_in adr; + + serverdetailedinfo_t *detail; + + int linenum; +} selectedserver; + +typedef struct player_s { + char name[16]; + int frags; + int colour; + char skin[8]; + struct sockaddr_in adr; + + struct player_s *next; +} player_t; + +void SListOptionChanged(serverinfo_t *newserver); + +extern serverinfo_t *firstserver; +extern master_t *master; +extern player_t *mplayers; + +void CL_QueryServers(void); +int NET_CheckPollSockets(void); +void MasterInfo_Request(master_t *mast); +serverinfo_t *Master_InfoForServer (struct sockaddr_in addr); +serverinfo_t *Master_InfoForNum (int num); +int Master_TotalCount(void); +void Master_QueryServer(serverinfo_t *server); +void MasterInfo_WriteServers(void); +void MasterInfo_Begin(void); + + +char *NET_AdrToString(const struct sockaddr_in *a); + + +#define Q_strncpyS(d, s, n) do{const char *____in=(s);char *____out=(d);int ____i; for (____i=0;*(____in); ____i++){if (____i == (n))break;*____out++ = *____in++;}if (____i < (n))*____out='\0';}while(0) //only use this when it should be used. If undiciided, use N +#define Q_strncpyN(d, s, n) do{if (n < 0)Sys_Error("Bad length in strncpyz");Q_strncpyS((d), (s), (n));((char *)(d))[n] = '\0';}while(0) //this'll stop me doing buffer overflows. (guarenteed to overflow if you tried the wrong size.) +#define Q_strncpyNCHECKSIZE(d, s, n) do{if (n < 1)Sys_Error("Bad length in strncpyz");Q_strncpyS((d), (s), (n));((char *)(d))[n-1] = '\0';((char *)(d))[n] = '255';}while(0) //This forces nothing else to be within the buffer. Should be used for testing and nothing else. +#define Q_strncpyz(d, s, n) Q_strncpyN(d, s, (n)-1) diff --git a/plugins/serverb/m_master.c b/plugins/serverb/m_master.c new file mode 100644 index 000000000..f44eb3308 --- /dev/null +++ b/plugins/serverb/m_master.c @@ -0,0 +1,951 @@ +#include "cl_master.h" + + + + +int K_UPARROW, + K_DOWNARROW, + K_ENTER, + K_DEL, + K_BACKSPACE, + K_ESCAPE, + K_PGDN, + K_PGUP, + K_SPACE, + K_LEFTARROW, + K_RIGHTARROW; + +#define RESTRICT_LOCAL 64 + +enum { +SLISTTYPE_SERVERS, +SLISTTYPE_FAVORITES, +SLISTTYPE_SOURCES, +SLISTTYPE_OPTIONS //must be last +} slist_option; + +int slist_numoptions; +int slist_firstoption; + +int slist_type; + +void M_DrawServers(void); +void M_SListKey(int key); + +#define CVAR_ARCHIVE 0 + +#define cvargroup "Server Browser Vars" +//filtering +vmcvar_t sb_hideempty = {"sb_hideempty", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidenotempty = {"sb_hidenotempty", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidefull = {"sb_hidefull", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidedead = {"sb_hidedead", "1", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidequake2 = {"sb_hidequake2", "1", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidenetquake = {"sb_hidenetquake", "1", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_hidequakeworld = {"sb_hidequakeworld", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_maxping = {"sb_maxping", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_gamedir = {"sb_gamedir", "", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_mapname = {"sb_mapname", "", cvargroup, CVAR_ARCHIVE}; + +vmcvar_t sb_showping = {"sb_showping", "1", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showaddress = {"sb_showaddress", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showmap = {"sb_showmap", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showgamedir = {"sb_showgamedir", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showplayers = {"sb_showplayers", "1", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showfraglimit = {"sb_showfraglimit", "0", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_showtimelimit = {"sb_showtimelimit", "0", cvargroup, CVAR_ARCHIVE}; + +vmcvar_t sb_filterkey = {"sb_filterkey", "hostname", cvargroup, CVAR_ARCHIVE}; +vmcvar_t sb_filtervalue = {"sb_filtervalue", "", cvargroup, CVAR_ARCHIVE}; +#undef cvargroup + +vmcvar_t *cvarlist[] ={ + &sb_hideempty, + &sb_hidenotempty, + &sb_hidefull, + &sb_hidedead, + &sb_hidequake2, + &sb_hidenetquake, + &sb_hidequakeworld, + &sb_maxping, + &sb_gamedir, + &sb_mapname, + + &sb_showping, + &sb_showaddress, + &sb_showmap, + &sb_showgamedir, + &sb_showplayers, + &sb_showfraglimit, + &sb_showtimelimit, + + &sb_filterkey, + &sb_filtervalue +}; + +void M_Serverlist_InitCvars(void) +{ + vmcvar_t *v; + int i; + for (v = cvarlist[0],i=0; i < sizeof(cvarlist)/sizeof(cvarlist[0]); v++, i++) + v->handle = Cvar_Register(v->name, v->string, v->flags, v->group); +} + +int msecstime; +int Plug_Tick(int *args) +{ + msecstime = args[0]; + + return true; +} + +int Plug_MenuEvent(int *args) +{ + switch(args[0]) + { + case 0: //draw + M_DrawServers(); + break; + case 1: //keydown + M_SListKey(args[1]); + break; + case 2: //keyup + break; + case 3: //menu closed (this is called even if we change it). + break; + } + + return 0; +} + +void Plug_StartBrowser(void) +{ + Menu_Control(1); + if (BUILTINISVALID(LocalSound)) + LocalSound("misc/menu2.wav"); + + MasterInfo_Begin(); +} + +int Plug_ExecuteCommand(int *args) +{ + char cmd[256]; + Cmd_Argv(0, cmd, sizeof(cmd)); + if (!strcmp("plug_browser", cmd)) + { + Plug_StartBrowser(); + return 1; + } + return 0; +} + +int Plug_Init(int *args) +{ + if (Plug_Export("Tick", Plug_Tick) && + Plug_Export("ExecuteCommand", Plug_ExecuteCommand) && + Plug_Export("MenuEvent", Plug_MenuEvent)) + Con_Print("IRC Client Plugin Loaded\n"); + else + { + Con_Print("IRC Client Plugin failed\n"); + return false; + } + + K_UPARROW = Key_GetKeyCode("uparrow"); + K_DOWNARROW = Key_GetKeyCode("downarrow"); + K_ENTER = Key_GetKeyCode("enter"); + K_DEL = Key_GetKeyCode("del"); + K_BACKSPACE = Key_GetKeyCode("backspace"); + K_ESCAPE = Key_GetKeyCode("escape"); + K_PGDN = Key_GetKeyCode("pgdn"); + K_PGUP = Key_GetKeyCode("pgup"); + K_SPACE = Key_GetKeyCode("space"); + K_LEFTARROW = Key_GetKeyCode("leftarrow"); + K_RIGHTARROW = Key_GetKeyCode("rightarrow"); + + M_Serverlist_InitCvars(); + return true; + +} + +//doesn't use args or return value +int Plugin_CvarUpdate(int *args) +{ + vmcvar_t *v; + int i; + for (v = cvarlist[0],i=0; i < sizeof(cvarlist)/sizeof(cvarlist[0]); v++, i++) + v->modificationcount = Cvar_Update(v->handle, v->modificationcount, v->string, &v->value); + return 0; +} + +static void NM_DrawCharacter (int cx, int line, unsigned int num) +{ + Draw_Character(cx, line, num); +} +static void NM_Print (int cx, int cy, qbyte *str) +{ + while (*str) + { + Draw_Character (cx, cy, (*str)|128); + str++; + cx += 8; + } +} + + + + + +qboolean M_IsFiltered(serverinfo_t *server) //figure out if we should filter a server. +{ + if (slist_type == SLISTTYPE_FAVORITES) + if (!(server->special & SS_FAVORITE)) + return true; +#ifdef Q2CLIENT + if (sb_hidequake2.value) +#endif + if (server->special & SS_QUAKE2) + return true; +#ifdef NQPROT + if (sb_hidenetquake.value) +#endif + if (server->special & SS_NETQUAKE) + return true; + if (sb_hidequakeworld.value) + if (!(server->special & (SS_QUAKE2|SS_NETQUAKE))) + return true; + if (sb_hideempty.value) + if (!server->players) + return true; + if (sb_hidenotempty.value) + if (server->players) + return true; + if (sb_hidefull.value) + if (server->players == server->maxplayers) + return true; + if (sb_hidedead.value) + if (server->maxplayers == 0) + return true; + if (sb_maxping.value) + if (server->ping > sb_maxping.value) + return true; + if (*sb_gamedir.string) + if (strcmp(server->gamedir, sb_gamedir.string)) + return true; + if (*sb_mapname.string) + if (!strstr(server->map, sb_mapname.string)) + return true; + + return false; +} + +qboolean M_MasterIsFiltered(master_t *mast) +{ +#ifndef Q2CLIENT + if (mast->type == MT_BCASTQ2 || mast->type == MT_SINGLEQ2 || mast->type == MT_MASTERQ2) + return true; +#endif +#ifndef NQPROT + if (mast->type == MT_BCASTNQ || mast->type == MT_SINGLENQ) + return true; +#endif + return false; +} + +static int Sbar_ColorForMap (int m) +{ + m = (m < 0) ? 0 : ((m > 13) ? 13 : m); + + m *= 16; + return m < 128 ? m + 8 : m + 8; +} + +void M_DrawOneServer (int inity) +{ + char key[512]; + char value[512]; + char *o; + int l, i; + char *s; + + int miny=8*5; + int y=8*(5-selectedserver.linenum); + + miny += inity; + y += inity; + + if (!selectedserver.detail) + { + NM_Print (0, y, "No details\n"); + return; + } + + s = selectedserver.detail->info; + + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; +// if (l < 20) +// { +// memset (o, ' ', 20-l); +// key[20] = 0; +// } +// else + *o = 0; + if (y>=miny) + NM_Print (0, y, va("%19s", key)); + + if (!*s) + { + if (y>=miny) + NM_Print (0, y, "MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + if (y>=miny) + NM_Print (320/2, y, va("%s\n", value)); + + y+=8; + } + + for ( i = 0; i < selectedserver.detail->numplayers; i++) + { + if (y>=miny) + { + if (selectedserver.detail->players[i].frags>=-999) //wow, too low, assume mvd spectators... + { + Draw_Colourp(Sbar_ColorForMap(selectedserver.detail->players[i].topc)); + Draw_Fill (12, y, 28, 4); + Draw_Colourp(Sbar_ColorForMap(selectedserver.detail->players[i].botc)); + Draw_Fill (12, y+4, 28, 4); + Draw_Colour3f(1,1,1); + NM_Print (12, y, va("%3i", selectedserver.detail->players[i].frags)); + } + NM_Print (12+8*4, y, selectedserver.detail->players[i].name); + } + y+=8; + } + + if (y<=miny) //whoops, there was a hole at the end, try scrolling up. + selectedserver.linenum--; +} + +char *Info_ValueForKey(char *buffer, char *key) +{ + char *s; + char *e; + static char ret[64]; + for (s = buffer; *s; s++) + { + if (*s == '\\') + { //key starts here + for (e = s+1; *e; e++) + { + //find value start + if (*e == '\\') + { + if (!strncmp(s+1, key, e - s-1)) + { + //find the next \\ or \0 + s = e; + for (e = s+1; *e && *e != '\\'; e++) + { + + } + if (e-s>=sizeof(ret)) + e = s + 63; + strncpy(ret, s+1, e-s-1); + ret[e-s-1] = '\0'; + return ret; + } + break; + } + } + s = e; + } + } + return ""; +} + +int M_AddColumn (int right, int y, char *text, int maxchars) +{ + int left; + left = right - maxchars*8; + if (left < 0) + return right; + + right = left; + while (*text && maxchars>0) + { + NM_DrawCharacter (right, y, *text); + text++; + right += 8; + maxchars--; + } + return left; +} +void M_DrawServerList(void) +{ + serverinfo_t *server; + int op=0, filtered=0; + int snum=0; + int blink = 0; + + int x; + int y = 8*3; + + CL_QueryServers(); + + slist_numoptions = 0; + + //find total servers. + for (server = firstserver; server; server = server->next) + if (M_IsFiltered(server)) + filtered++; + else + slist_numoptions++; + + if (!slist_numoptions) + { + char *text, *text2="", *text3=""; + if (filtered) + { + if (slist_type == SLISTTYPE_FAVORITES) + { + text = "Highlight a server"; + text2 = "and press \'f\'"; + text3 = "to add it to this list"; + } + else + text = "All servers were filtered out"; + } + else + text = "No servers found"; + NM_Print((vid.width-strlen(text)*8)/2, 8*5, text); + NM_Print((vid.width-strlen(text2)*8)/2, 8*5+8, text2); + NM_Print((vid.width-strlen(text3)*8)/2, 8*5+16, text3); + + return; + } + + + if (slist_option >= slist_numoptions) + slist_option = slist_numoptions-1; + op = vid.height/2/8; + op/=2; + op=slist_option-op; + snum = op; + + + if (selectedserver.inuse == true) + { + M_DrawOneServer(8*5); + return; + } + + if (op < 0) + op = 0; + if (snum < 0) + snum = 0; + //find the server that we want + for (server = firstserver; op>0; server=server->next) + { + if (M_IsFiltered(server)) + continue; + op--; + } + + y = 8*2; + x = vid.width; + if (sb_showtimelimit.value) + x = M_AddColumn(x, y, "tl", 3); + if (sb_showfraglimit.value) + x = M_AddColumn(x, y, "fl", 3); + if (sb_showplayers.value) + x = M_AddColumn(x, y, "plyrs", 6); + if (sb_showmap.value) + x = M_AddColumn(x, y, "map", 9); + if (sb_showgamedir.value) + x = M_AddColumn(x, y, "gamedir", 9); + if (sb_showping.value) + x = M_AddColumn(x, y, "png", 4); + if (sb_showaddress.value) + x = M_AddColumn(x, y, "address", 21); + x = M_AddColumn(x, y, "name", x/8-1); + + y = 8*3; + while(server) + { + if (M_IsFiltered(server)) + { + server = server->next; + continue; //doesn't count + } + + if (y > vid.height/2) + break; + + if (slist_option == snum) + blink = (msecstime/333)&1; + if (*server->name) + { + if (blink) + Draw_Colour3f(1,1,0); + else if (server->special & SS_FAVORITE) + Draw_Colour3f(0,1,0); + else if (server->special & SS_FTESERVER) + Draw_Colour3f(1,0,0); + else if (server->special & SS_QUAKE2) + Draw_Colour3f(0,0,1); + else if (server->special & SS_NETQUAKE) + Draw_Colour3f(1,0,1); + else + Draw_Colour3f(1,1,1); + + x = vid.width; + + if (sb_showtimelimit.value) + x = M_AddColumn(x, y, va("%i", server->tl), 3); //time limit + if (sb_showfraglimit.value) + x = M_AddColumn(x, y, va("%i", server->fl), 3); //frag limit + if (sb_showplayers.value) + x = M_AddColumn(x, y, va("%i/%i", server->players, server->maxplayers), 6); + if (sb_showmap.value) + x = M_AddColumn(x, y, server->map, 9); + if (sb_showgamedir.value) + x = M_AddColumn(x, y, server->gamedir, 9); + if (sb_showping.value) + x = M_AddColumn(x, y, va("%i", server->ping), 4); //frag limit + if (sb_showaddress.value) + x = M_AddColumn(x, y, NET_AdrToString(&server->adr), 21); + x = M_AddColumn(x, y, server->name, x/8-1); + } + + blink = 0; + if (*server->name) + y+=8; + + server = server->next; + + snum++; + } + + Draw_Colour3f(1,1,1); + + selectedserver.inuse=2; + M_DrawOneServer(vid.height/2-4*8); +} + +void M_DrawSources (void) +{ + int blink; + int snum=0; + int op; + int y = 3*8; + master_t *mast; + + slist_numoptions = 0; + //find total sources. + for (mast = master; mast; mast = mast->next) + slist_numoptions++; + + if (!slist_numoptions) + { + char *text; + if (0)//filtered) + text = "All servers were filtered out\n"; + else + text = "No sources were found\n"; + NM_Print((vid.width-strlen(text)*8)/2, 8*5, text); + + return; + } + + if (slist_option >= slist_numoptions) + slist_option = slist_numoptions-1; + op=slist_option-vid.height/2/8; + snum = op; + + if (op < 0) + op = 0; + if (snum < 0) + snum = 0; + //find the server that we want + for (mast = master; op>0; mast=mast->next) + { + if (M_MasterIsFiltered(mast)) + continue; + op--; + } + + for (; mast; mast = mast->next) + { + if (M_MasterIsFiltered(mast)) + continue; + + if (slist_option == snum) + blink = (msecstime/333)&1; + else + blink = 0; + + if (blink) + Draw_Colour3f(0,1,1); + else + if (mast->type == MT_MASTERQW || mast->type == MT_MASTERQ2) + Draw_Colour3f(1,1,1); +#ifdef NQPROT + else if (mast->type == MT_SINGLENQ) + Draw_Colour3f(0,1,0); +#endif + else if (mast->type == MT_SINGLEQW || mast->type == MT_SINGLEQ2) + Draw_Colour3f(0,0,1); + else + Draw_Colour3f(1,0,0); + + NM_Print(46, y, va("%s", mast->name)); //white. + y+=8; + snum++; + } +} + +#define NUMSLISTOPTIONS (7+7+3) + struct { + char *title; + vmcvar_t *cvar; + int type; + } options[NUMSLISTOPTIONS] = { + {"Hide Empty", &sb_hideempty}, + {"Hide Not Empty", &sb_hidenotempty}, + {"Hide Full", &sb_hidefull}, + {"Hide Dead", &sb_hidedead}, + {"Hide Quake 2", &sb_hidequake2}, + {"Hide Quake 1", &sb_hidenetquake}, + {"Hide QuakeWorld", &sb_hidequakeworld}, + + {"Show pings", &sb_showping}, + {"Show Addresses", &sb_showaddress}, + {"Show map", &sb_showmap}, + {"Show Game Dir", &sb_showgamedir}, + {"Show Players", &sb_showplayers}, + {"Show Fraglimit", &sb_showfraglimit}, + {"Show Timelimit", &sb_showtimelimit}, + + {"Max ping", &sb_maxping, 1}, + {"GameDir", &sb_gamedir, 2}, + {"Using map", &sb_mapname, 2} + }; + +void M_DrawSListOptions (void) +{ + int op; + + slist_numoptions = NUMSLISTOPTIONS; + + for (op = 0; op < NUMSLISTOPTIONS; op++) + { + if (slist_option == op && (msecstime/333)&1) + Draw_Colour3f(0.5, 0.5, 1); + else + { + if (options[op].cvar->value>0 || (*options[op].cvar->string && *options[op].cvar->string != '0')) + Draw_Colour3f(1, 0, 0); + else + Draw_Colour3f(1, 1, 1); + } + switch(options[op].type) + { + default: + NM_Print(46, op*8+8*3, options[op].title); + break; + case 1: + if (!options[op].cvar->value) + { + NM_Print(46, op*8+8*3, va("%s ", options[op].title)); + break; + } + case 2: + NM_Print(46, op*8+8*3, va("%s %s", options[op].title, options[op].cvar->string)); + break; + } + } +} + +void M_SListOptions_Key (int key) +{ + if (key == K_UPARROW) + { + slist_option--; + if (slist_option<0) + slist_option=0; + } + else if (key == K_DOWNARROW) + { + slist_option++; + if (slist_option >= slist_numoptions) + slist_option = slist_numoptions-1; + } + + switch(options[slist_option].type) + { + default: + if (key == K_ENTER) + { + if (options[slist_option].cvar->value) + VMCvar_SetString(options[slist_option].cvar, "0"); + else + VMCvar_SetString(options[slist_option].cvar, "1"); + } + break; + case 1: + if (key >= '0' && key <= '9') + VMCvar_SetFloat(options[slist_option].cvar, options[slist_option].cvar->value*10+key-'0'); + else if (key == K_DEL) + VMCvar_SetFloat(options[slist_option].cvar, 0); + else if (key == K_BACKSPACE) + VMCvar_SetFloat(options[slist_option].cvar, (int)options[slist_option].cvar->value/10); + break; + case 2: + if ((key >= '0' && key <= '9') || (key >= 'a' && key <= 'z') || key == '_') + VMCvar_SetString(options[slist_option].cvar, va("%s%c", options[slist_option].cvar->string, key)); + else if (key == K_DEL) + VMCvar_SetString(options[slist_option].cvar, ""); + else if (key == K_BACKSPACE) //FIXME + VMCvar_SetString(options[slist_option].cvar, ""); + break; + } +} + + +void M_DrawServers(void) +{ +#define NUMSLISTHEADERS (SLISTTYPE_OPTIONS+1) + char *titles[NUMSLISTHEADERS] = { + "Servers", + "Favorites", + "Sources", +// "Players", + "Options" + }; + int snum=0; + + int width, lofs; + + NET_CheckPollSockets(); //see if we were told something important. + + width = vid.width / NUMSLISTHEADERS; + lofs = width/2 - 7*4; + for (snum = 0; snum < NUMSLISTHEADERS; snum++) + { + NM_Print(width*snum+width/2 - strlen(titles[snum])*4, slist_type==snum, titles[snum]); + } + NM_Print(8, 8, "\35"); + for (snum = 16; snum < vid.width-16; snum+=8) + NM_Print(snum, 8, "\36"); + NM_Print(snum, 8, "\37"); + + switch(slist_type) + { + case SLISTTYPE_SERVERS: + case SLISTTYPE_FAVORITES: + M_DrawServerList(); + break; + case SLISTTYPE_SOURCES: + M_DrawSources (); + break; + case SLISTTYPE_OPTIONS: + M_DrawSListOptions (); + break; + } +} + +serverinfo_t *M_FindCurrentServer(void) +{ + serverinfo_t *server; + int op = slist_option; + for (server = firstserver; server; server = server->next) + { + if (M_IsFiltered(server)) + continue; //doesn't count + if (!op--) + return server; + } + return NULL; +} + +master_t *M_FindCurrentMaster(void) +{ + master_t *mast; + int op = slist_option; + for (mast = master; mast; mast = mast->next) + { + if (M_MasterIsFiltered(mast)) + continue; + if (!op--) + return mast; + } + return NULL; +} + +void M_SListKey(int key) +{ + if (key == K_ESCAPE) + { +// if (selectedserver.inuse) +// selectedserver.inuse = false; +// else + { + Menu_Control(0); +// Cmd_AddText("togglemenu\n", false); + } + return; + } + else if (key == K_LEFTARROW) + { + slist_type--; + if (slist_type<0) + slist_type=0; + + selectedserver.linenum--; + if (selectedserver.linenum<0) + selectedserver.linenum=0; + + slist_numoptions=0; + return; + } + else if (key == K_RIGHTARROW) + { + slist_type++; + if (slist_type>NUMSLISTHEADERS-1) + slist_type=NUMSLISTHEADERS-1; + + selectedserver.linenum++; + + slist_numoptions = 0; + return; + } + else if (key == 'q') + selectedserver.linenum--; + else if (key == 'a') + selectedserver.linenum++; + + if (!slist_numoptions) + return; + + if (slist_type == SLISTTYPE_OPTIONS) + { + M_SListOptions_Key(key); + return; + } + + if (key == K_UPARROW) + { + slist_option--; + if (slist_option<0) + slist_option=0; + + if (slist_type == SLISTTYPE_SERVERS) + SListOptionChanged(M_FindCurrentServer()); //go for these early. + } + else if (key == K_DOWNARROW) + { + slist_option++; + if (slist_option >= slist_numoptions) + slist_option = slist_numoptions-1; + + if (slist_type == SLISTTYPE_SERVERS) + SListOptionChanged(M_FindCurrentServer()); //go for these early. + } + else if (key == K_PGDN) + { + slist_option+=10; + if (slist_option >= slist_numoptions) + slist_option = slist_numoptions-1; + + if (slist_type == SLISTTYPE_SERVERS) + SListOptionChanged(M_FindCurrentServer()); //go for these early. + } + else if (key == K_PGUP) + { + slist_option-=10; + if (slist_option<0) + slist_option=0; + + if (slist_type == SLISTTYPE_SERVERS) + SListOptionChanged(M_FindCurrentServer()); //go for these early. + } + else if (key == 'r') + MasterInfo_Begin(); + else if (key == K_SPACE) + { + if (slist_type == SLISTTYPE_SERVERS) + { + selectedserver.inuse = !selectedserver.inuse; + if (selectedserver.inuse) + SListOptionChanged(M_FindCurrentServer()); + } + } + else if (key == 'f') + { + serverinfo_t *server; + if (slist_type == SLISTTYPE_SERVERS) //add to favorites + { + server = M_FindCurrentServer(); + if (server) + { + server->special |= SS_FAVORITE; + MasterInfo_WriteServers(); + } + } + if (slist_type == SLISTTYPE_FAVORITES) //remove from favorites + { + server = M_FindCurrentServer(); + if (server) + { + server->special &= ~SS_FAVORITE; + MasterInfo_WriteServers(); + } + } + } + else if (key==K_ENTER) + { + serverinfo_t *server; + if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) + { + if (!selectedserver.inuse) + { + selectedserver.inuse = true; + SListOptionChanged(M_FindCurrentServer()); + return; + } + server = M_FindCurrentServer(); + if (!server) + return; //ah. off the end. + + if (server->special & SS_NETQUAKE) + Cmd_AddText(va("nqconnect %s\n", NET_AdrToString(&server->adr)), false); + else + Cmd_AddText(va("connect %s\n", NET_AdrToString(&server->adr)), false); + } + else if (slist_type == SLISTTYPE_SOURCES) + { + MasterInfo_Request(M_FindCurrentMaster()); + } + + return; + } +} + diff --git a/plugins/serverb/net_master.c b/plugins/serverb/net_master.c new file mode 100644 index 000000000..62968bd2f --- /dev/null +++ b/plugins/serverb/net_master.c @@ -0,0 +1,1027 @@ +#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. + + +extern int msecstime; + +//the number of servers should be limited only by memory. + +vmcvar_t slist_cacheinfo = {"slist_cacheinfo", "0"}; //this proves dangerous, memory wise. +vmcvar_t slist_writeserverstxt = {"slist_writeservers", "0"}; + +void CL_MasterListParse(qbyte *buffer, qbyte *maxbuffer, qboolean isq2); +void CL_QueryServers(void); +int CL_ReadServerInfo(char *msg, int servertype, qboolean favorite, struct sockaddr_in net_from); + +master_t *master; +player_t *mplayers; +serverinfo_t *firstserver; + + +#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; + +qboolean NET_StringToAdr(const char *s, struct sockaddr_in *a) +{ + char copy[128]; + char *colon; + struct hostent *he; + + strcpy(copy, s); + colon = strchr(copy, ':'); + if (colon) + { + a->sin_port = htons((unsigned short)atoi(colon+1)); + *colon = '\0'; + } + else + a->sin_port = 0; + + *(unsigned int*)&a->sin_addr = inet_addr(copy); + a->sin_family = AF_INET; + + if (*(unsigned int*)&a->sin_addr != INADDR_NONE) + return true; + + he = gethostbyname(copy); + if (he) + { + if (he->h_addrtype != AF_INET) + return false; //whoops... + + *(int*)&a->sin_addr = *(int*)he->h_addr; + + return true; + } + + return false; +} +qboolean NET_CompareAdr(const struct sockaddr_in *a, const struct sockaddr_in *b) +{ + if (a->sin_family != b->sin_family) + return false; + if (*(int*)&a->sin_addr != *(int*)&b->sin_addr) + return false; + if (a->sin_port != b->sin_port) + return false; + return true; +} +char *NET_AdrToString(const struct sockaddr_in *a) +{ + static char buffer[24]; + const qbyte *ad = (const qbyte *)&a->sin_addr; + snprintf(buffer, sizeof(buffer), "%i.%i.%i.%i:%i", ad[0], ad[1], ad[2], ad[3], ntohs(a->sin_port)); + return buffer; +} + +void Master_AddMaster (char *address, int type, char *description) +{ + struct sockaddr_in adr; + master_t *mast; + + if (!NET_StringToAdr(address, &adr)) + { + Con_Printf("Failed to resolve address \"%s\"\n", address); + return; + } + + for (mast = master; mast; mast = mast->next) + { + if (NET_CompareAdr(&mast->adr, &adr)) //already exists. + return; + } + mast = malloc(sizeof(master_t)+strlen(description)); + mast->adr = adr; + mast->type = type; + strcpy(mast->name, description); + + mast->next = master; + master = mast; +} + +//build a linked list of masters. Doesn't duplicate addresses. +qboolean Master_LoadMasterList (char *filename, int defaulttype, int depth) +{ + + return false; +/* + extern char com_basedir[MAX_OSPATH]; + FILE *f; + char line[1024]; + char file[1024]; + char *name, *next; + int servertype; + + if (depth <= 0) + return false; + depth--; + + f = fopen(va("%s/%s", com_basedir, filename), "rb"); + if (!f) + return false; + + while(fgets(line, sizeof(line)-1, f)) + { + 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:nq") || !strcmp(com_token, "single:q1")) + servertype = MT_SINGLENQ; + else if (!strcmp(com_token, "single")) + servertype = MT_SINGLEQW; + + 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")) //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:nq")) + servertype = MT_BCASTNQ; + 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: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 + Master_AddMaster(line, servertype, name); + } + fclose(f); + + return true; +*/ +} + +int UDP_OpenSocket (int port, qboolean bcast) +{ + int newsocket; + struct sockaddr_in address; + unsigned long _true = true; + int i; +int maxport = port + 100; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + Sys_Errorf ("UDP_OpenSocket: socket: %s", strerror(qerrno)); + + if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) + Sys_Errorf ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno)); + + if (bcast) + { + _true = true; + if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1) + { + Con_Printf("Cannot create broadcast socket\n"); + return 0; + } + } + + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + + for(;;) + { + if (port == PORT_ANY) + address.sin_port = 0; + else + address.sin_port = htons((short)port); + + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + if (!port) + Sys_Errorf ("UDP_OpenSocket: bind: %s", strerror(qerrno)); + port++; + if (port > maxport) + Sys_Errorf ("UDP_OpenSocket: bind: %s", strerror(qerrno)); + } + else + break; + } + + return newsocket; +} + +void NET_SendPollPacket(int len, void *data, struct sockaddr_in addr) +{ + int ret; + +#ifdef USEIPX + if (addr.sa_family == AF_IPX) + { + lastpollsockIPX++; + if (lastpollsockIPX>=POLLIPXSOCKETS) + lastpollsockIPX=0; + if (!pollsocketsIPX[lastpollsockIPX]) + pollsocketsIPX[lastpollsockIPX] = IPX_OpenSocket(PORT_ANY, true); + ret = sendto (pollsocketsIPX[lastpollsockIPX], data, len, 0, (struct sockaddr *)&addr, sizeof(addr) ); + } + else +#endif + { + lastpollsockUDP++; + if (lastpollsockUDP>=POLLUDPSOCKETS) + lastpollsockUDP=0; + if (!pollsocketsUDP[lastpollsockUDP]) + pollsocketsUDP[lastpollsockUDP] = UDP_OpenSocket(PORT_ANY, true); + ret = sendto (pollsocketsUDP[lastpollsockUDP], data, len, 0, (struct sockaddr *)&addr, sizeof(addr) ); + } + + if (ret == -1) + { +// wouldblock is silent + if (qerrno == EWOULDBLOCK) + return; + + if (qerrno == ECONNREFUSED) + return; + + if (qerrno != EADDRNOTAVAIL) + Con_Printf ("NET_SendPacket ERROR: %i\n", qerrno); +// else +// Con_DPrintf("NET_SendPacket Warning: %i\n", qerrno); + } +} + +int NET_CheckPollSockets(void) +{ +#define MAX_UDP_PACKET 8192 //hmm... while on windows, the max size is 1400 or so... + qbyte net_message_buffer[MAX_UDP_PACKET]; + qbyte *readpoint; + qbyte *maxread; + int sock; + SOCKET usesocket; + for (sock = 0; sock < POLLUDPSOCKETS; sock++) + { + int ret; + struct sockaddr_in from; + int fromlen; + + usesocket = pollsocketsUDP[sock]; + + if (!usesocket) + 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) + { + Con_Printf ("Warning: Oversize packet from %s\n", + NET_AdrToString (&from)); + continue; + } + if (qerrno == ECONNABORTED || qerrno == ECONNRESET) + { +// Con_Printf ("Connection lost or aborted\n"); + continue; + } + + + Con_Printf ("NET_CheckPollSockets: %s", strerror(qerrno)); + continue; + } + + if (ret >= sizeof(net_message_buffer) ) + { + Con_Printf ("Oversize packet from %s\n", NET_AdrToString (&from)); + continue; + } + readpoint = net_message_buffer; + maxread = readpoint+ret; + + if (*(int*)net_message_buffer == 0xffffffff) + { + int c; +#ifdef Q2CLIENT + char *s; +#endif + readpoint += 4; + +#ifdef Q2CLIENT + s = strchr('\n'); + if (!strncmp(readpoint, "print", 5)) + { + CL_ReadServerInfo(s+1, MT_SINGLEQ2, false); + continue; + } + else if (!strncmp(readpoint, "info", 4)) //parse a bit more... + { + CL_ReadServerInfo(s+1, MT_SINGLEQ2, false); + continue; + } + else if (!strncmp(s, "servers", 6)) //parse a bit more... + { + readpoint = orp+7; + CL_MasterListParse(readpoint, maxread, true); + continue; + } + readpoint = orp; +#endif + + c = *readpoint++; + + if (c == A2C_PRINT) //qw server reply. + { + CL_ReadServerInfo(readpoint, MT_SINGLEQW, false, from); + continue; + } + + if (c == M2C_MASTER_REPLY) //qw master reply. + { + CL_MasterListParse(readpoint, maxread, 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) + { + free(sv->moreinfo); + sv->moreinfo = NULL; + } +} + +void SListOptionChanged(serverinfo_t *newserver) +{ + if (selectedserver.inuse) + { + char data[16]; + 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) + { + 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 = malloc(sizeof(serverdetailedinfo_t)); + + newserver->moreinfo->numplayers = newserver->players; + snprintf(newserver->moreinfo->info, sizeof(newserver->moreinfo->info), "\\%s\\%s", "hostname", newserver->name); + + newserver->refreshtime = msecstime; + snprintf(data, sizeof(data), "%c%c%c%cstatus\n", 255, 255, 255, 255); + NET_SendPollPacket (strlen(data), data, newserver->adr); + } +} + + +//don't try sending to servers we don't support +void MasterInfo_Request(master_t *mast) +{ + if (!mast) + return; + switch(mast->type) + { +#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); + 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; +#endif + case MT_MASTERQW: + NET_SendPollPacket (3, "c\n", mast->adr); + break; +#ifdef Q2CLIENT + case MT_MASTERQ2: + if (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 + } +} + + +void MasterInfo_WriteServers(void) +{ +/* + char *typename; + master_t *mast; + serverinfo_t *server; + FILE *mf, *qws; + + 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_BCASTQW: + typename = "bcast:qw"; + break; + case MT_BCASTQ2: + typename = "bcast:q2"; + break; + case MT_BCASTNQ: + typename = "bcast:nq"; + break; + case MT_SINGLEQW: + typename = "single:qw"; + break; + case MT_SINGLEQ2: + typename = "single:q2"; + break; + case MT_SINGLENQ: + typename = "single:nq"; + break; + default: + typename = "writeerror"; + } + fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(mast->adr), typename, mast->name); + } + + if (slist_writeserverstxt.value) + qws = fopen("server.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_QUAKE2) + fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(server->adr), "favorite:q2", server->name); + else if (server->special & SS_NETQUAKE) + fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(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(server->adr), server->name); + else //read only? damn them! + fprintf(mf, "%s\t%s\t%s\n", NET_AdrToString(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) + { + Master_AddMaster("255.255.255.255:26000", MT_BCASTNQ, "Nearby Quake1 servers"); + Master_AddMaster("255.255.255.255:27500", MT_BCASTQW, "Nearby QuakeWorld UDP servers."); + } + +// if (q2servers) + { + Master_AddMaster("255.255.255.255:27910", MT_BCASTQ2, "Nearby Quake2 UDP servers."); +// Master_AddMaster("00000000:ffffffffffff:27910", MT_BCASTQ2, "Nearby Quake2 IPX servers."); + } + + +// if (q1servers) + { + 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."); + } + +// if (q2servers) + { + Master_AddMaster("192.246.40.37:27900", MT_MASTERQ2, "id q2 Master."); + } + + } + + for (mast = master; mast; mast=mast->next) + { + MasterInfo_Request(mast); + } +} + +void Master_QueryServer(serverinfo_t *server) +{ + char data[2048]; + server->sends++; + server->refreshtime = msecstime; + snprintf(data, sizeof(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; + op = poll; + + + for (server = firstserver; op>0 && server; server=server->next, op--); + + if (!server) + { + poll = 0; + return; + } + + if (op == 0) + { + if (server->sends < 1) + { + 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 (struct sockaddr_in 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(struct sockaddr_in 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; + free(p); + p=prev; + + continue; + } + else + prev = p; + + p = p->next; + } +} + +void MasterInfo_AddPlayer(struct sockaddr_in serveradr, char *name, int ping, int frags, int colours, char *skin) +{ + player_t *p; + p = malloc(sizeof(player_t)); + p->next = mplayers; + p->adr = serveradr; + p->colour = colours; + p->frags = frags; + strncpy(p->name, name, sizeof(p->name)); + p->name[sizeof(p->name)-1] = '\0'; + strncpy(p->skin, skin, sizeof(p->skin)); + p->skin[sizeof(p->skin)-1] = '\0'; + mplayers = p; +} + +//we got told about a server, parse it's info +int CL_ReadServerInfo(char *msg, int servertype, qboolean favorite, struct sockaddr_in net_from) +{ + serverdetailedinfo_t details; + + char *token; + char *nl; + char *name; + int ping; + int len; + serverinfo_t *info; + + info = Master_InfoForServer(net_from); + + if (!info) //not found... + { + info = malloc(sizeof(serverinfo_t)); + memset(info, 0, sizeof(*info)); + + info->adr = net_from; + + snprintf(info->name, sizeof(info->name), "%s", NET_AdrToString(&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"); + Q_strncpyz(info->name, name, sizeof(info->name)); + info->special = info->special & (SS_FAVORITE | SS_KEEPINFO); //favorite is never cleared + if (!strcmp("FTE", Info_ValueForKey(msg, "*distrib"))) + info->special |= SS_FTESERVER; + else if (!strncmp("FTE", Info_ValueForKey(msg, "*version"), 3)) + info->special |= SS_FTESERVER; + + + if (servertype == MT_SINGLEQ2) + info->special |= SS_QUAKE2; + else if (servertype == MT_SINGLENQ) + info->special |= SS_NETQUAKE; + if (favorite) //was specifically named, not retrieved from a master. + info->special |= SS_FAVORITE; + + ping = msecstime - info->refreshtime; + if (ping > 0xffff) + info->ping = 0xffff; + else + info->ping = ping; + + info->players = 0; + info->maxplayers = atoi(Info_ValueForKey(msg, "maxclients")); + + info->tl = atoi(Info_ValueForKey(msg, "timelimit")); + info->fl = atoi(Info_ValueForKey(msg, "fraglimit")); + + if (servertype == MT_SINGLEQ2) + { + 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)); + } + + { + int clnum; + strcpy(details.info, msg); + msg = msg+strlen(msg)+1; + + info->players=details.numplayers = 0; + 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 = (float)atof(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 = (float)atof(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 = 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(qbyte *buffer, qbyte *maxbuffer, qboolean isq2) +{ + serverinfo_t *info; + serverinfo_t *last, *old; + + int p1, p2; + buffer++; + + last = firstserver; + + while(buffer+1 <= maxbuffer) + { + info = malloc(sizeof(serverinfo_t)); + memset(info, 0, sizeof(*info)); + info->adr.sin_family = AF_INET; + ((qbyte *)&info->adr.sin_addr)[0] = *buffer++; + ((qbyte *)&info->adr.sin_addr)[1] = *buffer++; + ((qbyte *)&info->adr.sin_addr)[2] = *buffer++; + ((qbyte *)&info->adr.sin_addr)[3] = *buffer++; + + p1 = *buffer++; + p2 = *buffer++; + info->adr.sin_port = (short)(p1 + (p2<<8)); + if ((old = Master_InfoForServer(info->adr))) //remove if the server already exists. + { + old->sends = 0; //reset. + free(info); + } + else + { + info->special = isq2?SS_QUAKE2:0; + info->refreshtime = 0; + + snprintf(info->name, sizeof(info->name), "%s", NET_AdrToString(&info->adr)); + + info->next = last; + last = info; + } + } + + firstserver = last; +} diff --git a/plugins/serverb/serverb.dsp b/plugins/serverb/serverb.dsp new file mode 100644 index 000000000..91394ac46 --- /dev/null +++ b/plugins/serverb/serverb.dsp @@ -0,0 +1,130 @@ +# Microsoft Developer Studio Project File - Name="serverb" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=serverb - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "serverb.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "serverb.mak" CFG="serverb - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "serverb - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "serverb - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "serverb - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "SERVERB_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "SERVERB_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "serverb - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "SERVERB_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "SERVERB_EXPORTS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib /nologo /dll /debug /machine:I386 /out:"D:\Quake\id1\plugins/serverbx86.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "serverb - Win32 Release" +# Name "serverb - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\m_master.c +# End Source File +# Begin Source File + +SOURCE=.\net_master.c +# End Source File +# Begin Source File + +SOURCE=.\plugin.c +# End Source File +# Begin Source File + +SOURCE=.\plugin.def +# End Source File +# Begin Source File + +SOURCE=.\qvm_api.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\cl_master.h +# End Source File +# Begin Source File + +SOURCE=.\plugin.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project