/* * 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" 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; strncpy (cls.servername, "localhost", sizeof(cls.servername)-1); /* 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 (va("Server quit\n", msg), false); } else { CL_Disconnect (); } server = Cmd_Argv (1); NET_Config (true); /* allow remote */ CL_Disconnect (); cls.state = ca_connecting; strncpy (cls.servername, server, sizeof(cls.servername)-1); 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= 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); re.CinematicSetPalette(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; } 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= 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 = atoi(Cmd_Argv(1)); 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; }