diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 526b4338..40a05a51 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -147,6 +147,7 @@ cvar_t cl_gunanglex = CVAR("cl_gunanglex", "0"); cvar_t cl_gunangley = CVAR("cl_gunangley", "0"); cvar_t cl_gunanglez = CVAR("cl_gunanglez", "0"); +cvar_t cl_proxyaddr = CVAR("cl_proxyaddr", ""); cvar_t cl_sendguid = CVARD("cl_sendguid", "0", "Send a randomly generated 'globally unique' id to servers, which can be used by servers for score rankings and stuff. Different servers will see different guids. Delete the 'qkey' file in order to appear as a different user.\nIf set to 2, all servers will see the same guid. Be warned that this can show other people the guid that you're using."); cvar_t cl_downloads = CVARFD("cl_downloads", "1", CVAR_NOTFROMSERVER, "Allows you to block all automatic downloads."); cvar_t cl_download_csprogs = CVARFD("cl_download_csprogs", "1", CVAR_NOTFROMSERVER, "Download updated client gamecode if available. Warning: If you clear this to avoid downloading vm code, you should also clear cl_download_packages."); @@ -505,6 +506,7 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, #endif int clients; int c; + char *a; // JACK: Fixed bug where DNS lookups would cause two connects real fast // Now, adds lookup time to the connect time. @@ -610,11 +612,21 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, Q_strncatz(data, va(" %i %i", cls.qport, connectinfo.challenge), sizeof(data)); - //userinfo 0 + zquake extension info. - if (connectinfo.protocol == CP_QUAKEWORLD) - Q_strncatz(data, va(" \"%s\\*z_ext\\%i\"", cls.userinfo[0], SUPPORTED_Z_EXTENSIONS), sizeof(data)); - else - Q_strncatz(data, va(" \"%s\"", cls.userinfo[0]), sizeof(data)); + //userinfo0 has some twiddles for extensions from other qw engines. + Q_strncatz(data, " \"", sizeof(data)); + //qwfwd proxy routing + if ((a = strrchr(cls.servername, '@'))) + { + *a = 0; + Q_strncatz(data, va("\\prx\\%s", cls.servername), sizeof(data)); + *a = '@'; + } + //the info itself + Q_strncatz(data, cls.userinfo[0], sizeof(data)); + if (connectinfo.protocol == CP_QUAKEWORLD) //zquake extension info. + Q_strncatz(data, va("\\*z_ext\\%i", cls.userinfo[0], SUPPORTED_Z_EXTENSIONS), sizeof(data)); + + Q_strncatz(data, "\"", sizeof(data)); for (c = 1; c < clients; c++) { Q_strncatz(data, va(" \"%s\"", cls.userinfo[c]), sizeof(data)); @@ -690,6 +702,7 @@ void CL_CheckForResend (void) double t1, t2; int contype = 0; qboolean keeptrying = true; + char *host; #ifndef CLIENTONLY if (!cls.state && (!connectinfo.trying || sv.state != ss_clustermode) && sv.state) @@ -872,7 +885,13 @@ void CL_CheckForResend (void) t1 = Sys_DoubleTime (); if (!connectinfo.istransfer) { - if (!NET_StringToAdr (cls.servername, connectinfo.defaultport, &connectinfo.adr)) + host = strrchr(cls.servername, '@'); + if (host) + host++; + else + host = cls.servername; + + if (!NET_StringToAdr (host, connectinfo.defaultport, &connectinfo.adr)) { Con_TPrintf ("Bad server address\n"); connectinfo.trying = false; @@ -971,8 +990,13 @@ void CL_CheckForResend (void) } } -void CL_BeginServerConnect(int port) +void CL_BeginServerConnect(const char *host, int port, qboolean noproxy) { + if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy) + Q_strncpyz (cls.servername, host, sizeof(cls.servername)); + else + Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string); + if (!port) port = cl_defaultport.value; memset(&connectinfo, 0, sizeof(connectinfo)); @@ -1060,11 +1084,44 @@ void CL_Connect_f (void) #endif CL_Disconnect_f (); - Q_strncpyz (cls.servername, server, sizeof(cls.servername)); - CL_BeginServerConnect(0); + CL_BeginServerConnect(server, 0, false); +} +static void CL_ConnectBestRoute_f (void) +{ + char server[1024]; + int proxies; + int directcost, chainedcost; + if (Cmd_Argc() != 2) + { + Con_TPrintf ("usage: connectbr \n"); + return; + } + + proxies = Master_FindBestRoute(Cmd_Argv(1), server, sizeof(server), &directcost, &chainedcost); + if (!*server) + { + Con_TPrintf ("Unable to route to server\n"); + return; + } + else if (proxies < 0) + Con_TPrintf ("Routing database is not initialised, connecting directly\n"); + else if (!proxies) + Con_TPrintf ("Routing table favours a direct connection\n"); + else if (proxies == 1) + Con_TPrintf ("Routing table favours a single proxy (%ims vs %ims)\n", chainedcost, directcost); + else + Con_TPrintf ("Routing table favours chaining through %i proxies (%ims vs %ims)\n", proxies, chainedcost, directcost); + +#ifndef CLIENTONLY + if (sv.state == ss_clustermode) + CL_Disconnect (); + else +#endif + CL_Disconnect_f (); + CL_BeginServerConnect(server, 0, true); } -void CL_Join_f (void) +static void CL_Join_f (void) { char *server; @@ -1088,8 +1145,7 @@ void CL_Join_f (void) Cvar_Set(&spectator, "0"); - Q_strncpyz (cls.servername, server, sizeof(cls.servername)); - CL_BeginServerConnect(0); + CL_BeginServerConnect(server, 0, false); } void CL_Observe_f (void) @@ -1116,8 +1172,7 @@ void CL_Observe_f (void) Cvar_Set(&spectator, "1"); - Q_strncpyz (cls.servername, server, sizeof(cls.servername)); - CL_BeginServerConnect(0); + CL_BeginServerConnect(server, 0, false); } #ifdef NQPROT @@ -1135,8 +1190,7 @@ void CLNQ_Connect_f (void) CL_Disconnect_f (); - Q_strncpyz (cls.servername, server, sizeof(cls.servername)); - CL_BeginServerConnect(26000); + CL_BeginServerConnect(server, 26000, true); } #endif @@ -1150,9 +1204,7 @@ void CL_IRCConnect_f (void) char *server; server = Cmd_Argv (1); - strcpy(cls.servername, "irc://"); - Q_strncpyz (cls.servername+6, server, sizeof(cls.servername)-6); - CL_BeginServerConnect(0); + CL_BeginServerConnect(va("irc://%s", server), 0, true); } } #endif @@ -3613,6 +3665,7 @@ void CL_Init (void) Cvar_Register (&cfg_save_name, cl_controlgroup); + Cvar_Register (&cl_proxyaddr, cl_controlgroup); Cvar_Register (&cl_sendguid, cl_controlgroup); Cvar_Register (&cl_defaultport, cl_controlgroup); Cvar_Register (&cl_servername, cl_controlgroup); @@ -3789,7 +3842,14 @@ void CL_Init (void) Cmd_AddCommand ("cl_status", CL_Status_f); Cmd_AddCommandD ("quit", CL_Quit_f, "Use this command when you get angry. Does not save any cvars. Use cfg_save to save settings, or use the menu for a prompt."); - Cmd_AddCommandD ("connect", CL_Connect_f, "connect scheme://address:port\nConnect to a server. Use a scheme of tcp:// or tls:// to connect via non-udp protocols." + Cmd_AddCommandD ("connectbr", CL_ConnectBestRoute_f, "connect address:port\nConnect to a qw server using the best route we can detect."); + + Cmd_AddCommandD ("connect", CL_Connect_f, "connect scheme://address:port\nConnect to a server. " +#if defined(FTE_TARGET_WEB) + "Use a scheme of ws:// or wss:// to connect via using websockets." +#else + "Use a scheme of tcp:// or tls:// to connect via non-udp protocols." +#endif #if defined(NQPROT) || defined(Q2CLIENT) || defined(Q3CLIENT) "\nDefault port is port "STRINGIFY(PORT_QWSERVER)"." #ifdef NQPROT diff --git a/engine/client/cl_master.h b/engine/client/cl_master.h index ae7a6691..0b1e0a57 100644 --- a/engine/client/cl_master.h +++ b/engine/client/cl_master.h @@ -143,6 +143,15 @@ typedef struct serverinfo_s serverdetailedinfo_t *moreinfo; + struct serverinfo_s *prevpeer; + unsigned short cost; + unsigned short numpeers; + struct peers_s + { + struct serverinfo_s *peer; + unsigned short ping; + } *peers; + struct serverinfo_s *next; } serverinfo_t; @@ -216,3 +225,4 @@ void Master_ClearMasks(void); serverinfo_t *Master_SortedServer(int idx); void Master_SetMaskString(qboolean or, hostcachekey_t field, const char *param, slist_test_t testop); void Master_SetMaskInteger(qboolean or, hostcachekey_t field, int param, slist_test_t testop); +serverinfo_t *Master_FindRoute(netadr_t target); diff --git a/engine/client/client.h b/engine/client/client.h index 80c0fcb0..69368b96 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -971,7 +971,7 @@ qboolean CL_DemoBehind(void); void CL_SaveInfo(vfsfile_t *f); void CL_SetInfo (int pnum, char *key, char *value); -void CL_BeginServerConnect(int port); +void CL_BeginServerConnect(const char *host, int port, qboolean noproxy); char *CL_TryingToConnect(void); void CL_ExecInitialConfigs(char *defaultexec); @@ -1045,6 +1045,7 @@ int CL_ReadFromServer (void); void CL_WriteToServer (usercmd_t *cmd); void CL_BaseMove (usercmd_t *cmd, int pnum, float extra, float wantfps); +int Master_FindBestRoute(char *server, char *out, size_t outsize, int *directcost, int *chainedcost); float CL_KeyState (kbutton_t *key, int pnum, qboolean noslowstart); char *Key_KeynumToString (int keynum); diff --git a/engine/client/m_master.c b/engine/client/m_master.c index d371d063..4aab7fc7 100644 --- a/engine/client/m_master.c +++ b/engine/client/m_master.c @@ -28,7 +28,7 @@ static cvar_t sb_alpha = CVARF("sb_alpha", "0.7", CVAR_ARCHIVE); vrect_t joinbutton; static float refreshedtime; static int isrefreshing; -static qboolean serverpreview; +static int serverpreview; extern cvar_t slist_writeserverstxt; extern cvar_t slist_cacheinfo; @@ -411,6 +411,7 @@ static void SL_PostDraw (menu_t *menu) "rmb: cancel", "j: join", "o: observe", + "b: join with automatic best route", "v: say server info", "ctrl-v: say_team server info", "c: copy server info to clipboard", @@ -438,7 +439,15 @@ static void SL_PostDraw (menu_t *menu) if (server && server->moreinfo) { int lx, x, y, i; - if (serverpreview == 3) + if (serverpreview == 4) + { + //count the number of proxies the best route will need + serverinfo_t *prox; + for (h = 1, prox = server; prox; h++, prox = prox->prevpeer) + ; + w += 120; + } + else if (serverpreview == 3) h = countof(helpstrings); else if (serverpreview == 2) { @@ -474,7 +483,18 @@ static void SL_PostDraw (menu_t *menu) Draw_FunStringWidth (x, y, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f", w, 2, false); y+=8; - if (serverpreview == 3) + if (serverpreview == 4) + { + serverinfo_t *prox; + for (prox = server; prox; prox = prox->prevpeer) + { + Draw_FunStringWidth (x, y, va("%i", prox->cost), 32-8, true, false); + Draw_FunStringWidth (x + 32, y, NET_AdrToString(buf, sizeof(buf), &prox->adr), w/2 - 8 - 32, true, false); + Draw_FunStringWidth (x + w/2, y, prox->name, w/2, false, false); + y += 8; + } + } + else if (serverpreview == 3) { x = lx; for (i = 0; i < countof(helpstrings); i++) @@ -653,16 +673,27 @@ static qboolean SL_Key (int key, menu_t *menu) else if (key == K_LEFTARROW) { if (--serverpreview < 1) - serverpreview = 3; + serverpreview = 4; + + if (serverpreview == 4) + Master_FindRoute(server->adr); return true; } else if (key == K_RIGHTARROW) { - if (++serverpreview > 3) + if (++serverpreview > 4) serverpreview = 1; + + if (serverpreview == 4) + Master_FindRoute(server->adr); return true; } - else if (key == 'o' || key == 'j' || key == K_ENTER || key == K_KP_ENTER) //join + else if (key == 'b' && serverpreview != 4) + { + Master_FindRoute(server->adr); + serverpreview = 4; + } + else if (key == 'b' || key == 'o' || key == 'j' || key == K_ENTER || key == K_KP_ENTER) //join { if (key == 's' || key == 'o') Cbuf_AddText("spectator 1\n", RESTRICT_LOCAL); @@ -672,10 +703,23 @@ doconnect: Cbuf_AddText("spectator 0\n", RESTRICT_LOCAL); } + //which connect command are we using? if ((server->special & SS_PROTOCOLMASK) == SS_NETQUAKE) - Cbuf_AddText(va("nqconnect %s\n", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText("nqconnect ", RESTRICT_LOCAL); else - Cbuf_AddText(va("connect %s\n", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + Cbuf_AddText("connect ", RESTRICT_LOCAL); + + //output the server's address + Cbuf_AddText(va("%s", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + if (serverpreview == 4 || key == 'b') + { //and postfix it with routing info if we're going for a proxied route. + if (serverpreview != 4) + Master_FindRoute(server->adr); + for (server = server->prevpeer; server; server = server->prevpeer) + Cbuf_AddText(va("@%s", NET_AdrToString(buf, sizeof(buf), &server->adr)), RESTRICT_LOCAL); + } + Cbuf_AddText("\n", RESTRICT_LOCAL); + M_RemoveAllMenus(); return true; @@ -754,6 +798,9 @@ doconnect: { selectedserver.inuse = true; SListOptionChanged(server); + + if (serverpreview == 4) + Master_FindRoute(server->adr); } } diff --git a/engine/client/net_master.c b/engine/client/net_master.c index a10abbce..bac0e81a 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -1225,6 +1225,121 @@ int Master_KeyForName(const char *keyname) } } + +static void Master_FloodRoute(serverinfo_t *node) +{ + unsigned int i; + struct peers_s *peer = node->peers; + for (i = 0; i < node->numpeers; i++, peer++) + { + if (peer->ping) + if ((unsigned int)(peer->peer->cost) > (unsigned int)(node->cost + peer->ping)) + { //we found a shorter route. flood into it. + peer->peer->prevpeer = node; + peer->peer->cost = node->cost + peer->ping; + Master_FloodRoute(peer->peer); + } + } +} +serverinfo_t *Master_FindRoute(netadr_t target) +{ + serverinfo_t *info, *targ, *prox; + extern cvar_t cl_proxyaddr; + targ = Master_InfoForServer(&target); + if (!targ) //you wot? + return NULL; + + //never flood into a peer if its just going to be more expensive than a direct connection + if (*cl_proxyaddr.string) + { + //fixme: we don't handle chained proxies properly, as we assume we can directly hop to the named final proxy. + //fixme: we'll find the same route, we just won't display the correct expected ping. + netadr_t pa; + char *chain = strchr(cl_proxyaddr.string, '@'); + if (chain) + *chain = 0; + + NET_StringToAdr(cl_proxyaddr.string, 0, &pa); + + prox = Master_InfoForServer(&pa); + if (chain) + *chain = '@'; + } + else + prox = NULL; + + if (prox) + { + for (info = firstserver; info; info = info->next) + { + info->cost = info->ping; + info->prevpeer = prox; + } + prox->cost = prox->ping; + prox->prevpeer = NULL; + Master_FloodRoute(prox); + } + else + { + for (info = firstserver; info; info = info->next) + { + info->cost = info->ping; + info->prevpeer = NULL; + } + + //flood through all proxies + for (info = firstserver; info; info = info->next) + Master_FloodRoute(info); + } + + if (targ->prevpeer) + return targ; + return NULL; +} + +int Master_FindBestRoute(char *server, char *out, size_t outsize, int *directcost, int *chainedcost) +{ + serverinfo_t *route; + netadr_t adr; + int ret = 0; + char buf[256]; + *out = 0; + *directcost = 0; + *chainedcost = 0; + if (!NET_StringToAdr(server, 0, &adr)) + return -1; + + if (!firstserver) + { //routing isn't initialised. you do actually need to refresh the serverbrowser for this junk + Q_strncpyz(out, server, outsize); + return -1; + } + + route = Master_FindRoute(adr); + + if (!route) + { //routing didn't find anything, just go directly. + Q_strncpyz(out, server, outsize); + return 0; + } + + *directcost = route->ping; + *chainedcost = route->cost; + + Q_strncatz(out, NET_AdrToString(buf, sizeof(buf), &route->adr), outsize); + for (ret = 0, route = route->prevpeer; route; route = route->prevpeer, ret++) + Q_strncatz(out, va("@%s", NET_AdrToString(buf, sizeof(buf), &route->adr)), outsize); + + return ret; +} + + + + + + + + //main thread void CLMaster_AddMaster_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) { @@ -1365,6 +1480,8 @@ void MasterInfo_Shutdown(void) { sv = firstserver; firstserver = sv->next; + if (sv->peers) + Z_Free(sv->peers); Z_Free(sv); } while(master) @@ -2633,8 +2750,6 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor } else { - MasterInfo_RemovePlayers(&info->adr); - //determine the ping if (info->refreshtime) { @@ -2653,6 +2768,59 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor *nl = '\0'; nl++; } + + if (info->special & SS_PROXY) + { + if (!*Info_ValueForKey(msg, "hostname")) + { //qq, you suck + //this is a proxy peer list, not an actual serverinfo update. + unsigned char *ptr = net_message.data + 5; + int remaining = net_message.cursize - 5; + struct peers_s *peer; + netadr_t pa; + memset(&pa, 0, sizeof(pa)); + remaining /= 8; + + NET_AdrToString(adr, sizeof(adr), &info->adr); + + Z_Free(info->peers); + info->numpeers = remaining; + peer = info->peers = Z_Malloc(sizeof(*peer)*info->numpeers); + + while (remaining --> 0) + { + pa.type = NA_IP; + pa.address.ip[0] = *ptr++; + pa.address.ip[1] = *ptr++; + pa.address.ip[2] = *ptr++; + pa.address.ip[3] = *ptr++; + + pa.port = *ptr++<<8; + pa.port |= *ptr++; + peer->ping = *ptr++; + peer->ping |= *ptr++<<8; + + peer->peer = Master_InfoForServer(&pa); + if (!peer->peer) + { + //generate some lame peer node that we can use. + peer->peer = Z_Malloc(sizeof(serverinfo_t)); + peer->peer->adr = pa; + peer->peer->sends = 1; + peer->peer->special = 0; + peer->peer->refreshtime = 0; + peer->peer->ping = 0xffff; + peer->peer->next = firstserver; + firstserver = peer->peer; + } + peer++; + } + return false; + } + } + + MasterInfo_RemovePlayers(&info->adr); + name = Info_ValueForKey(msg, "hostname"); if (!*name) name = Info_ValueForKey(msg, "sv_hostname"); @@ -2717,7 +2885,12 @@ int CL_ReadServerInfo(char *msg, enum masterprotocol_e prototype, qboolean favor if (!strcmp(Info_ValueForKey(msg, "*progs"), "666") && !strcmp(Info_ValueForKey(msg, "*version"), "2.91")) info->special |= SS_PROXY; //qizmo if (!Q_strncmp(Info_ValueForKey(msg, "*version"), "qwfwd", 5)) + { + char *msg = "\xff\xff\xff\xffpingstatus"; + NET_SendPollPacket(strlen(msg), msg, info->adr); + info->special |= SS_PROXY; //qwfwd + } if (!Q_strncasecmp(Info_ValueForKey(msg, "*version"), "qtv ", 4)) info->special |= SS_PROXY; //eztv diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 750e9936..28831891 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -2381,7 +2381,7 @@ static IShellLinkW *CreateShellLink(char *command, char *target, char *title, ch if (FAILED(hr)) return NULL; - GetModuleFileNameW(NULL, buf, sizeof(buf)/sizeof(wchar_t)-1); + GetModuleFileNameW(NULL, buf, countof(buf)-1); IShellLinkW_SetIconLocation(link, buf, 0); /*grab the first icon from our exe*/ IShellLinkW_SetPath(link, buf); /*program to run*/ @@ -2394,10 +2394,8 @@ static IShellLinkW *CreateShellLink(char *command, char *target, char *title, ch else *s = tolower(*s); } - _snwprintf(buf, sizeof(buf), L"%ls \"%ls\" -basedir \"%ls\"", command, target, tmp); - IShellLinkW_SetArguments(link, buf); /*args*/ - _snwprintf(buf, sizeof(buf), L"%ls", desc); - IShellLinkW_SetDescription(link, buf); /*tooltip*/ + IShellLinkW_SetArguments(link, widen(buf, sizeof(buf), va("%s \"%s\" -basedir \"%s\"", command, target, tmp))); /*args*/ + IShellLinkW_SetDescription(link, widen(buf, sizeof(buf), desc)); /*tooltip*/ hr = IShellLinkW_QueryInterface(link, &qIID_IPropertyStore, (void**)&prop_store); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index a8ab03a3..cefd7cc9 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -5095,7 +5095,7 @@ void NET_PrintAddresses(ftenet_connections_t *collection) } if (warn) - Con_Printf("net address (%s): no addresses\n", con[i]->name); + Con_Printf("net address: no addresses\n"); } //============================================================================= diff --git a/engine/qclib/qcctui.c b/engine/qclib/qcctui.c index c4ad3054..ad84ac2f 100644 --- a/engine/qclib/qcctui.c +++ b/engine/qclib/qcctui.c @@ -118,7 +118,7 @@ int main (int argc, char **argv) fprintf(logfile, " %s", argv[i]); } fprintf(logfile, "\n"); - sucess = CompileParams(&funcs, true, argc, argv); + sucess = CompileParams(&funcs, NULL, argc, argv); qccClearHunk(); if (logfile) fclose(logfile);