/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * ======================================================================= * * This file implements generic network functions * * ======================================================================= */ #include "header/client.h" #include "../client/sound/header/local.h" void CL_ParseStatusMessage(void); extern cvar_t *msg; extern cvar_t *rcon_client_password; extern cvar_t *rcon_address; extern cvar_t *cl_timeout; /* * adds the current command line as a clc_stringcmd to the client * message. things like godmode, noclip, etc, are commands directed to * the server, so when they are typed in at the console, they will need * to be forwarded. */ void Cmd_ForwardToServer(void) { char *cmd; cmd = Cmd_Argv(0); if ((cls.state <= ca_connected) || (*cmd == '-') || (*cmd == '+')) { Com_Printf("Unknown command \"%s\"\n", cmd); return; } MSG_WriteByte(&cls.netchan.message, clc_stringcmd); SZ_Print(&cls.netchan.message, cmd); if (Cmd_Argc() > 1) { SZ_Print(&cls.netchan.message, " "); SZ_Print(&cls.netchan.message, Cmd_Args()); } } void CL_ForwardToServer_f(void) { if ((cls.state != ca_connected) && (cls.state != ca_active)) { Com_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0)); return; } /* don't forward the first argument */ if (Cmd_Argc() > 1) { MSG_WriteByte(&cls.netchan.message, clc_stringcmd); SZ_Print(&cls.netchan.message, Cmd_Args()); } } /* * Called after an ERR_DROP was thrown */ void CL_Drop(void) { if (cls.state == ca_uninitialized) { return; } if (cls.state == ca_disconnected) { return; } CL_Disconnect(); /* drop loading plaque unless this is the initial game start */ if (cls.disable_servercount != -1) { SCR_EndLoadingPlaque(); /* get rid of loading plaque */ } } /* * We have gotten a challenge from the server, so try and * connect. */ void CL_SendConnectPacket(void) { netadr_t adr; int port; memset(&adr, 0, sizeof(adr)); if (!NET_StringToAdr(cls.servername, &adr)) { Com_Printf("Bad server address\n"); cls.connect_time = 0; return; } if (adr.port == 0) { adr.port = BigShort(PORT_SERVER); } port = Cvar_VariableValue("qport"); userinfo_modified = false; Netchan_OutOfBandPrint(NS_CLIENT, adr, "connect %i %i %i \"%s\"\n", PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo()); } /* * Resend a connect message if the last one has timed out */ void CL_CheckForResend(void) { netadr_t adr; /* if the local server is running and we aren't just connect */ if ((cls.state == ca_disconnected) && Com_ServerState()) { cls.state = ca_connecting; Q_strlcpy(cls.servername, "localhost", sizeof(cls.servername)); /* we don't need a challenge on the localhost */ CL_SendConnectPacket(); return; } /* resend if we haven't gotten a reply yet */ if (cls.state != ca_connecting) { return; } if (cls.realtime - cls.connect_time < 3000) { return; } if (!NET_StringToAdr(cls.servername, &adr)) { Com_Printf("Bad server address\n"); cls.state = ca_disconnected; return; } if (adr.port == 0) { adr.port = BigShort(PORT_SERVER); } cls.connect_time = cls.realtime; Com_Printf("Connecting to %s...\n", cls.servername); Netchan_OutOfBandPrint(NS_CLIENT, adr, "getchallenge\n"); } void CL_Connect_f(void) { char server[256]; if (Cmd_Argc() != 2) { Com_Printf("usage: connect \n"); return; } Q_strlcpy(server, Cmd_Argv(1), sizeof(server)); if (Com_ServerState()) { /* if running a local server, kill it and reissue note: this is connect with the save game system */ SV_Shutdown("Server quit\n", false); } NET_Config(true); /* allow remote */ CL_Disconnect(); cls.state = ca_connecting; Q_strlcpy(cls.servername, server, sizeof(cls.servername)); cls.connect_time = -99999; /* HACK: CL_CheckForResend() will fire immediately */ } /* * Send the rest of the command line over as * an unconnected command. */ void CL_Rcon_f(void) { char message[1024]; int i; netadr_t to; if (!rcon_client_password->string) { Com_Printf("You must set 'rcon_password' before\n" "issuing an rcon command.\n"); return; } memset(&to, 0, sizeof(to)); message[0] = (char)255; message[1] = (char)255; message[2] = (char)255; message[3] = (char)255; message[4] = 0; NET_Config(true); /* allow remote */ strcat(message, "rcon "); strcat(message, rcon_client_password->string); strcat(message, " "); for (i = 1; i < Cmd_Argc(); i++) { strcat(message, Cmd_Argv(i)); strcat(message, " "); } if (cls.state >= ca_connected) { to = cls.netchan.remote_address; } else { if (!strlen(rcon_address->string)) { Com_Printf("You must either be connected,\n" "or set the 'rcon_address' cvar\n" "to issue rcon commands\n"); return; } NET_StringToAdr(rcon_address->string, &to); if (to.port == 0) { to.port = BigShort(PORT_SERVER); } } NET_SendPacket(NS_CLIENT, strlen(message) + 1, message, to); } void CL_WriteConfiguration(void); /* * Goes from a connected state to full screen * console state Sends a disconnect message to * the server This is also called on Com_Error, so * it shouldn't cause any errors */ void CL_Disconnect(void) { byte final[32]; if (cls.state == ca_disconnected) { return; } if (cl_timedemo && cl_timedemo->value) { int time; time = Sys_Milliseconds() - cl.timedemo_start; if (time > 0) { Com_Printf("%i frames, %3.1f seconds: %3.1f fps\n", cl.timedemo_frames, time / 1000.0, cl.timedemo_frames * 1000.0 / time); } } VectorClear(cl.refdef.blend); R_SetPalette(NULL); M_ForceMenuOff(); cls.connect_time = 0; SCR_StopCinematic(); OGG_Stop(); if (cls.demorecording) { CL_Stop_f(); } /* send a disconnect message to the server */ final[0] = clc_stringcmd; strcpy((char *)final + 1, "disconnect"); Netchan_Transmit(&cls.netchan, strlen((const char *)final), final); Netchan_Transmit(&cls.netchan, strlen((const char *)final), final); Netchan_Transmit(&cls.netchan, strlen((const char *)final), final); CL_ClearState(); /* stop file download */ if (cls.download) { fclose(cls.download); cls.download = NULL; } #ifdef USE_CURL CL_CancelHTTPDownloads(true); cls.downloadReferer[0] = 0; cls.downloadname[0] = 0; cls.downloadposition = 0; #endif cls.state = ca_disconnected; snd_is_underwater = false; // save config for old game/mod CL_WriteConfiguration(); // we disconnected, so revert to default game/mod (might have been different mod on MP server) Cvar_Set("game", userGivenGame); } void CL_Disconnect_f(void) { Com_Error(ERR_DROP, "Disconnected from server"); } /* * packet * * Contents allows \n escape character */ void CL_Packet_f(void) { char send[2048]; int i, l; char *in, *out; netadr_t adr; if (Cmd_Argc() != 3) { Com_Printf("packet \n"); return; } NET_Config(true); /* allow remote */ if (!NET_StringToAdr(Cmd_Argv(1), &adr)) { Com_Printf("Bad address\n"); return; } if (!adr.port) { adr.port = BigShort(PORT_SERVER); } in = Cmd_Argv(2); out = send + 4; send[0] = send[1] = send[2] = send[3] = (char)0xff; l = strlen(in); for (i = 0; i < l; i++) { if ((in[i] == '\\') && (in[i + 1] == 'n')) { *out++ = '\n'; i++; } else { *out++ = in[i]; } } *out = 0; NET_SendPacket(NS_CLIENT, out - send, send, adr); } /* * Just sent as a hint to the client that they should * drop to full console */ void CL_Changing_f(void) { /* if we are downloading, we don't change! This so we don't suddenly stop downloading a map */ if (cls.download) { return; } SCR_BeginLoadingPlaque(); cls.state = ca_connected; /* not active anymore, but not disconnected */ Com_Printf("\nChanging map...\n"); #ifdef USE_CURL if (cls.downloadServerRetry[0] != 0) { CL_SetHTTPServer(cls.downloadServerRetry); } #endif } /* * The server is changing levels */ void CL_Reconnect_f(void) { /* if we are downloading, we don't change! This so we don't suddenly stop downloading a map */ if (cls.download) { return; } S_StopAllSounds(); if (cls.state == ca_connected) { Com_Printf("reconnecting...\n"); cls.state = ca_connected; MSG_WriteChar(&cls.netchan.message, clc_stringcmd); MSG_WriteString(&cls.netchan.message, "new"); return; } if (*cls.servername) { if (cls.state >= ca_connected) { CL_Disconnect(); cls.connect_time = cls.realtime - 1500; } else { cls.connect_time = -99999; /* Hack: fire immediately */ } cls.state = ca_connecting; Com_Printf("reconnecting...\n"); } } void CL_PingServers_f(void) { int i; netadr_t adr; char name[32]; const char *adrstring; cvar_t *noudp; cvar_t *noipx; NET_Config(true); /* allow remote but do we even need lokal pings? */ /* send a broadcast packet */ Com_Printf("pinging broadcast...\n"); noudp = Cvar_Get("noudp", "0", CVAR_NOSET); if (!noudp->value) { adr.type = NA_BROADCAST; adr.port = BigShort(PORT_SERVER); Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); Com_Printf("pinging multicast...\n"); adr.type = NA_MULTICAST6; adr.port = BigShort(PORT_SERVER); Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); } noipx = Cvar_Get("noipx", "0", CVAR_NOSET); if (!noipx->value) { adr.type = NA_BROADCAST_IPX; adr.port = BigShort(PORT_SERVER); Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); } /* send a packet to each address book entry */ for (i = 0; i < 16; i++) { Com_sprintf(name, sizeof(name), "adr%i", i); adrstring = Cvar_VariableString(name); if (!adrstring || !adrstring[0]) { continue; } Com_Printf("pinging %s...\n", adrstring); if (!NET_StringToAdr(adrstring, &adr)) { Com_Printf("Bad address: %s\n", adrstring); continue; } if (!adr.port) { adr.port = BigShort(PORT_SERVER); } Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); } } /* * Responses to broadcasts, etc */ void CL_ConnectionlessPacket(void) { char *s; char *c; MSG_BeginReading(&net_message); MSG_ReadLong(&net_message); /* skip the -1 */ s = MSG_ReadStringLine(&net_message); Cmd_TokenizeString(s, false); c = Cmd_Argv(0); Com_Printf("%s: %s\n", NET_AdrToString(net_from), c); /* server connection */ if (!strcmp(c, "client_connect")) { if (cls.state == ca_connected) { Com_Printf("Dup connect received. Ignored.\n"); return; } Netchan_Setup(NS_CLIENT, &cls.netchan, net_from, cls.quakePort); char *buff = NET_AdrToString(cls.netchan.remote_address); for(int i = 1; i < Cmd_Argc(); i++) { char *p = Cmd_Argv(i); if(!strncmp(p, "dlserver=", 9)) { #ifdef USE_CURL p += 9; Com_sprintf(cls.downloadReferer, sizeof(cls.downloadReferer), "quake2://%s", buff); CL_SetHTTPServer (p); if (cls.downloadServer[0]) { Com_Printf("HTTP downloading enabled, URL: %s\n", cls.downloadServer); } #else Com_Printf("HTTP downloading supported by server but not the client.\n"); #endif } } /* Put client into pause mode when connecting to a local server. This prevents the world from being forwarded while the client is connecting, loading assets, etc. It's not 100%, there're still 4 world frames (for baseq2) processed in the game and 100 frames by the server if the player enters a level that he or she already visited. In practise both shouldn't be a big problem. 4 frames are hardly enough for monsters staring to attack and in most levels the starting area in unreachable by monsters and free from environmental effects. Com_Serverstate() returns 2 if the server is local and we're running a real game and no timedemo, cinematic, etc. The 2 is taken from the server_state_t enum value 'ss_game'. If it's a local server, maxclients aus either 0 (for single player), or 2 to 8 (coop and deathmatch) if we're reaching this code. For remote servers it's always 1. So this should trigger only if it's a local single player server. Since the player can load savegames from a paused state (e.g. through the console) we'll need to communicate if we entered paused mode (and it should left as soon as the player joined the server) or if it was already there. Last but not least this can be disabled by cl_loadpaused 0. */ if (Com_ServerState() == 2 && (Cvar_VariableValue("maxclients") <= 1)) { if (cl_loadpaused->value) { if (!cl_paused->value) { paused_at_load = true; Cvar_Set("paused", "1"); } } } MSG_WriteChar(&cls.netchan.message, clc_stringcmd); MSG_WriteString(&cls.netchan.message, "new"); cls.state = ca_connected; return; } /* server responding to a status broadcast */ if (!strcmp(c, "info")) { CL_ParseStatusMessage(); return; } /* remote command from gui front end */ if (!strcmp(c, "cmd")) { if (!NET_IsLocalAddress(net_from)) { Com_Printf("Command packet from remote host. Ignored.\n"); return; } s = MSG_ReadString(&net_message); Cbuf_AddText(s); Cbuf_AddText("\n"); return; } /* print command from somewhere */ if (!strcmp(c, "print")) { s = MSG_ReadString(&net_message); Com_Printf("%s", s); return; } /* ping from somewhere */ if (!strcmp(c, "ping")) { Netchan_OutOfBandPrint(NS_CLIENT, net_from, "ack"); return; } /* challenge from the server we are connecting to */ if (!strcmp(c, "challenge")) { cls.challenge = (int)strtol(Cmd_Argv(1), (char **)NULL, 10); CL_SendConnectPacket(); return; } /* echo request from server */ if (!strcmp(c, "echo")) { Netchan_OutOfBandPrint(NS_CLIENT, net_from, "%s", Cmd_Argv(1)); return; } Com_Printf("Unknown command.\n"); } void CL_ReadPackets(void) { while (NET_GetPacket(NS_CLIENT, &net_from, &net_message)) { /* remote command packet */ if (*(int *)net_message.data == -1) { CL_ConnectionlessPacket(); continue; } if ((cls.state == ca_disconnected) || (cls.state == ca_connecting)) { continue; /* dump it if not connected */ } if (net_message.cursize < 8) { Com_Printf("%s: Runt packet\n", NET_AdrToString(net_from)); continue; } /* packet from server */ if (!NET_CompareAdr(net_from, cls.netchan.remote_address)) { Com_DPrintf("%s:sequenced packet without connection\n", NET_AdrToString(net_from)); continue; } if (!Netchan_Process(&cls.netchan, &net_message)) { continue; /* wasn't accepted for some reason */ } CL_ParseServerMessage(); } /* check timeout */ if ((cls.state >= ca_connected) && (cls.realtime - cls.netchan.last_received > cl_timeout->value * 1000)) { if (++cl.timeoutcount > 5) { Com_Printf("\nServer connection timed out.\n"); CL_Disconnect(); return; } } else { cl.timeoutcount = 0; } }