/* * 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; if (Cmd_Argc() != 2) { Com_Printf("usage: connect \n"); return; } 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); } else { CL_Disconnect(); } server = Cmd_Argv(1); 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); } /* * 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(); #ifdef OGG OGG_Stop(); #endif #ifdef CDA CDAudio_Stop(); #endif 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; } cls.state = ca_disconnected; snd_is_underwater = false; } 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"); } /* * 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]; 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 = (char *)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); 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; } }