aboutsummaryrefslogtreecommitdiffstats
path: root/code/server/sv_main.c
diff options
context:
space:
mode:
authorzakk <zakk@edf5b092-35ff-0310-97b2-ce42778d08ea>2005-08-26 17:39:27 +0000
committerzakk <zakk@edf5b092-35ff-0310-97b2-ce42778d08ea>2005-08-26 17:39:27 +0000
commit6bf20c78f5b69d40bcc4931df93d29198435ab67 (patch)
treee3eda937a05d7db42de725b7013bd0344b987f34 /code/server/sv_main.c
parent872d4d7f55af706737ffb361bb76ad13e7496770 (diff)
downloadioquake3-aero-6bf20c78f5b69d40bcc4931df93d29198435ab67.tar.gz
ioquake3-aero-6bf20c78f5b69d40bcc4931df93d29198435ab67.zip
newlines fixed
git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea
Diffstat (limited to 'code/server/sv_main.c')
-rwxr-xr-xcode/server/sv_main.c1710
1 files changed, 855 insertions, 855 deletions
diff --git a/code/server/sv_main.c b/code/server/sv_main.c
index 98c74ce..d5b7353 100755
--- a/code/server/sv_main.c
+++ b/code/server/sv_main.c
@@ -1,855 +1,855 @@
-/*
-===========================================================================
-Copyright (C) 1999-2005 Id Software, Inc.
-
-This file is part of Quake III Arena source code.
-
-Quake III Arena source code 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.
-
-Quake III Arena source code 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 Foobar; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-===========================================================================
-*/
-
-#include "server.h"
-
-serverStatic_t svs; // persistant server info
-server_t sv; // local server
-vm_t *gvm = NULL; // game virtual machine // bk001212 init
-
-cvar_t *sv_fps; // time rate for running non-clients
-cvar_t *sv_timeout; // seconds without any message
-cvar_t *sv_zombietime; // seconds to sink messages after disconnect
-cvar_t *sv_rconPassword; // password for remote server commands
-cvar_t *sv_privatePassword; // password for the privateClient slots
-cvar_t *sv_allowDownload;
-cvar_t *sv_maxclients;
-
-cvar_t *sv_privateClients; // number of clients reserved for password
-cvar_t *sv_hostname;
-cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
-cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
-cvar_t *sv_showloss; // report when usercmds are lost
-cvar_t *sv_padPackets; // add nop bytes to messages
-cvar_t *sv_killserver; // menu system can set to 1 to shut server down
-cvar_t *sv_mapname;
-cvar_t *sv_mapChecksum;
-cvar_t *sv_serverid;
-cvar_t *sv_maxRate;
-cvar_t *sv_minPing;
-cvar_t *sv_maxPing;
-cvar_t *sv_gametype;
-cvar_t *sv_pure;
-cvar_t *sv_floodProtect;
-cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
-cvar_t *sv_strictAuth;
-
-/*
-=============================================================================
-
-EVENT MESSAGES
-
-=============================================================================
-*/
-
-/*
-===============
-SV_ExpandNewlines
-
-Converts newlines to "\n" so a line prints nicer
-===============
-*/
-char *SV_ExpandNewlines( char *in ) {
- static char string[1024];
- int l;
-
- l = 0;
- while ( *in && l < sizeof(string) - 3 ) {
- if ( *in == '\n' ) {
- string[l++] = '\\';
- string[l++] = 'n';
- } else {
- string[l++] = *in;
- }
- in++;
- }
- string[l] = 0;
-
- return string;
-}
-
-/*
-======================
-SV_ReplacePendingServerCommands
-
- This is ugly
-======================
-*/
-int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
- int i, index, csnum1, csnum2;
-
- for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
- index = i & ( MAX_RELIABLE_COMMANDS - 1 );
- //
- if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
- sscanf(cmd, "cs %i", &csnum1);
- sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
- if ( csnum1 == csnum2 ) {
- Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
- /*
- if ( client->netchan.remoteAddress.type != NA_BOT ) {
- Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
- }
- */
- return qtrue;
- }
- }
- }
- return qfalse;
-}
-
-/*
-======================
-SV_AddServerCommand
-
-The given command will be transmitted to the client, and is guaranteed to
-not have future snapshot_t executed before it is executed
-======================
-*/
-void SV_AddServerCommand( client_t *client, const char *cmd ) {
- int index, i;
-
- // this is very ugly but it's also a waste to for instance send multiple config string updates
- // for the same config string index in one snapshot
-// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
-// return;
-// }
-
- client->reliableSequence++;
- // if we would be losing an old command that hasn't been acknowledged,
- // we must drop the connection
- // we check == instead of >= so a broadcast print added by SV_DropClient()
- // doesn't cause a recursive drop client
- if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
- Com_Printf( "===== pending server commands =====\n" );
- for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
- Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
- }
- Com_Printf( "cmd %5d: %s\n", i, cmd );
- SV_DropClient( client, "Server command overflow" );
- return;
- }
- index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
- Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
-}
-
-
-/*
-=================
-SV_SendServerCommand
-
-Sends a reliable command string to be interpreted by
-the client game module: "cp", "print", "chat", etc
-A NULL client will broadcast to all clients
-=================
-*/
-void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
- va_list argptr;
- byte message[MAX_MSGLEN];
- client_t *client;
- int j;
-
- va_start (argptr,fmt);
- Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
- va_end (argptr);
-
- if ( cl != NULL ) {
- SV_AddServerCommand( cl, (char *)message );
- return;
- }
-
- // hack to echo broadcast prints to console
- if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
- Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
- }
-
- // send the data to all relevent clients
- for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
- if ( client->state < CS_PRIMED ) {
- continue;
- }
- SV_AddServerCommand( client, (char *)message );
- }
-}
-
-
-/*
-==============================================================================
-
-MASTER SERVER FUNCTIONS
-
-==============================================================================
-*/
-
-/*
-================
-SV_MasterHeartbeat
-
-Send a message to the masters every few minutes to
-let it know we are alive, and log information.
-We will also have a heartbeat sent when a server
-changes from empty to non-empty, and full to non-full,
-but not on every player enter or exit.
-================
-*/
-#define HEARTBEAT_MSEC 300*1000
-#define HEARTBEAT_GAME "QuakeArena-1"
-void SV_MasterHeartbeat( void ) {
- static netadr_t adr[MAX_MASTER_SERVERS];
- int i;
-
- // "dedicated 1" is for lan play, "dedicated 2" is for inet public play
- if ( !com_dedicated || com_dedicated->integer != 2 ) {
- return; // only dedicated servers send heartbeats
- }
-
- // if not time yet, don't send anything
- if ( svs.time < svs.nextHeartbeatTime ) {
- return;
- }
- svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
-
-
- // send to group masters
- for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
- if ( !sv_master[i]->string[0] ) {
- continue;
- }
-
- // see if we haven't already resolved the name
- // resolving usually causes hitches on win95, so only
- // do it when needed
- if ( sv_master[i]->modified ) {
- sv_master[i]->modified = qfalse;
-
- Com_Printf( "Resolving %s\n", sv_master[i]->string );
- if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
- // if the address failed to resolve, clear it
- // so we don't take repeated dns hits
- Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
- Cvar_Set( sv_master[i]->name, "" );
- sv_master[i]->modified = qfalse;
- continue;
- }
- if ( !strstr( ":", sv_master[i]->string ) ) {
- adr[i].port = BigShort( PORT_MASTER );
- }
- Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
- adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
- BigShort( adr[i].port ) );
- }
-
-
- Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
- // this command should be changed if the server info / status format
- // ever incompatably changes
- NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
- }
-}
-
-/*
-=================
-SV_MasterShutdown
-
-Informs all masters that this server is going down
-=================
-*/
-void SV_MasterShutdown( void ) {
- // send a hearbeat right now
- svs.nextHeartbeatTime = -9999;
- SV_MasterHeartbeat();
-
- // send it again to minimize chance of drops
- svs.nextHeartbeatTime = -9999;
- SV_MasterHeartbeat();
-
- // when the master tries to poll the server, it won't respond, so
- // it will be removed from the list
-}
-
-
-/*
-==============================================================================
-
-CONNECTIONLESS COMMANDS
-
-==============================================================================
-*/
-
-/*
-================
-SVC_Status
-
-Responds with all the info that qplug or qspy can see about the server
-and all connected players. Used for getting detailed information after
-the simple info query.
-================
-*/
-void SVC_Status( netadr_t from ) {
- char player[1024];
- char status[MAX_MSGLEN];
- int i;
- client_t *cl;
- playerState_t *ps;
- int statusLength;
- int playerLength;
- char infostring[MAX_INFO_STRING];
-
- // ignore if we are in single player
- if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
- return;
- }
-
- strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
-
- // echo back the parameter to status. so master servers can use it as a challenge
- // to prevent timed spoofed reply packets that add ghost servers
- Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
-
- // add "demo" to the sv_keywords if restricted
- if ( Cvar_VariableValue( "fs_restrict" ) ) {
- char keywords[MAX_INFO_STRING];
-
- Com_sprintf( keywords, sizeof( keywords ), "demo %s",
- Info_ValueForKey( infostring, "sv_keywords" ) );
- Info_SetValueForKey( infostring, "sv_keywords", keywords );
- }
-
- status[0] = 0;
- statusLength = 0;
-
- for (i=0 ; i < sv_maxclients->integer ; i++) {
- cl = &svs.clients[i];
- if ( cl->state >= CS_CONNECTED ) {
- ps = SV_GameClientNum( i );
- Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
- ps->persistant[PERS_SCORE], cl->ping, cl->name);
- playerLength = strlen(player);
- if (statusLength + playerLength >= sizeof(status) ) {
- break; // can't hold any more
- }
- strcpy (status + statusLength, player);
- statusLength += playerLength;
- }
- }
-
- NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
-}
-
-/*
-================
-SVC_Info
-
-Responds with a short info message that should be enough to determine
-if a user is interested in a server to do a full status
-================
-*/
-void SVC_Info( netadr_t from ) {
- int i, count;
- char *gamedir;
- char infostring[MAX_INFO_STRING];
-
- // ignore if we are in single player
- if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
- return;
- }
-
- // don't count privateclients
- count = 0;
- for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
- if ( svs.clients[i].state >= CS_CONNECTED ) {
- count++;
- }
- }
-
- infostring[0] = 0;
-
- // echo back the parameter to status. so servers can use it as a challenge
- // to prevent timed spoofed reply packets that add ghost servers
- Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
-
- Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
- Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
- Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
- Info_SetValueForKey( infostring, "clients", va("%i", count) );
- Info_SetValueForKey( infostring, "sv_maxclients",
- va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
- Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
- Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
-
- if( sv_minPing->integer ) {
- Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
- }
- if( sv_maxPing->integer ) {
- Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
- }
- gamedir = Cvar_VariableString( "fs_game" );
- if( *gamedir ) {
- Info_SetValueForKey( infostring, "game", gamedir );
- }
-
- NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
-}
-
-/*
-================
-SVC_FlushRedirect
-
-================
-*/
-void SV_FlushRedirect( char *outputbuf ) {
- NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
-}
-
-/*
-===============
-SVC_RemoteCommand
-
-An rcon packet arrived from the network.
-Shift down the remaining args
-Redirect all printfs
-===============
-*/
-void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
- qboolean valid;
- unsigned int time;
- char remaining[1024];
- // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
- // (OOB messages are the bottleneck here)
-#define SV_OUTPUTBUF_LENGTH (1024 - 16)
- char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
- static unsigned int lasttime = 0;
- char *cmd_aux;
-
- // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
- time = Com_Milliseconds();
- if (time<(lasttime+500)) {
- return;
- }
- lasttime = time;
-
- if ( !strlen( sv_rconPassword->string ) ||
- strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
- valid = qfalse;
- Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
- } else {
- valid = qtrue;
- Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
- }
-
- // start redirecting all print outputs to the packet
- svs.redirectAddress = from;
- Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
-
- if ( !strlen( sv_rconPassword->string ) ) {
- Com_Printf ("No rconpassword set on the server.\n");
- } else if ( !valid ) {
- Com_Printf ("Bad rconpassword.\n");
- } else {
- remaining[0] = 0;
-
- // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
- // get the command directly, "rcon <pass> <command>" to avoid quoting issues
- // extract the command by walking
- // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
- cmd_aux = Cmd_Cmd();
- cmd_aux+=4;
- while(cmd_aux[0]==' ')
- cmd_aux++;
- while(cmd_aux[0] && cmd_aux[0]!=' ') // password
- cmd_aux++;
- while(cmd_aux[0]==' ')
- cmd_aux++;
-
- Q_strcat( remaining, sizeof(remaining), cmd_aux);
-
- Cmd_ExecuteString (remaining);
-
- }
-
- Com_EndRedirect ();
-}
-
-/*
-=================
-SV_ConnectionlessPacket
-
-A connectionless packet has four leading 0xff
-characters to distinguish it from a game channel.
-Clients that are in the game can still send
-connectionless packets.
-=================
-*/
-void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
- char *s;
- char *c;
-
- MSG_BeginReadingOOB( msg );
- MSG_ReadLong( msg ); // skip the -1 marker
-
- if (!Q_strncmp("connect", &msg->data[4], 7)) {
- Huff_Decompress(msg, 12);
- }
-
- s = MSG_ReadStringLine( msg );
- Cmd_TokenizeString( s );
-
- c = Cmd_Argv(0);
- Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
-
- if (!Q_stricmp(c, "getstatus")) {
- SVC_Status( from );
- } else if (!Q_stricmp(c, "getinfo")) {
- SVC_Info( from );
- } else if (!Q_stricmp(c, "getchallenge")) {
- SV_GetChallenge( from );
- } else if (!Q_stricmp(c, "connect")) {
- SV_DirectConnect( from );
- } else if (!Q_stricmp(c, "ipAuthorize")) {
- SV_AuthorizeIpPacket( from );
- } else if (!Q_stricmp(c, "rcon")) {
- SVC_RemoteCommand( from, msg );
- } else if (!Q_stricmp(c, "disconnect")) {
- // if a client starts up a local server, we may see some spurious
- // server disconnect messages when their new server sees our final
- // sequenced messages to the old client
- } else {
- Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
- , NET_AdrToString (from), s);
- }
-}
-
-//============================================================================
-
-/*
-=================
-SV_ReadPackets
-=================
-*/
-void SV_PacketEvent( netadr_t from, msg_t *msg ) {
- int i;
- client_t *cl;
- int qport;
-
- // check for connectionless packet (0xffffffff) first
- if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
- SV_ConnectionlessPacket( from, msg );
- return;
- }
-
- // read the qport out of the message so we can fix up
- // stupid address translating routers
- MSG_BeginReadingOOB( msg );
- MSG_ReadLong( msg ); // sequence number
- qport = MSG_ReadShort( msg ) & 0xffff;
-
- // find which client the message is from
- for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
- if (cl->state == CS_FREE) {
- continue;
- }
- if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
- continue;
- }
- // it is possible to have multiple clients from a single IP
- // address, so they are differentiated by the qport variable
- if (cl->netchan.qport != qport) {
- continue;
- }
-
- // the IP port can't be used to differentiate them, because
- // some address translating routers periodically change UDP
- // port assignments
- if (cl->netchan.remoteAddress.port != from.port) {
- Com_Printf( "SV_PacketEvent: fixing up a translated port\n" );
- cl->netchan.remoteAddress.port = from.port;
- }
-
- // make sure it is a valid, in sequence packet
- if (SV_Netchan_Process(cl, msg)) {
- // zombie clients still need to do the Netchan_Process
- // to make sure they don't need to retransmit the final
- // reliable message, but they don't do any other processing
- if (cl->state != CS_ZOMBIE) {
- cl->lastPacketTime = svs.time; // don't timeout
- SV_ExecuteClientMessage( cl, msg );
- }
- }
- return;
- }
-
- // if we received a sequenced packet from an address we don't recognize,
- // send an out of band disconnect packet to it
- NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
-}
-
-
-/*
-===================
-SV_CalcPings
-
-Updates the cl->ping variables
-===================
-*/
-void SV_CalcPings( void ) {
- int i, j;
- client_t *cl;
- int total, count;
- int delta;
- playerState_t *ps;
-
- for (i=0 ; i < sv_maxclients->integer ; i++) {
- cl = &svs.clients[i];
- if ( cl->state != CS_ACTIVE ) {
- cl->ping = 999;
- continue;
- }
- if ( !cl->gentity ) {
- cl->ping = 999;
- continue;
- }
- if ( cl->gentity->r.svFlags & SVF_BOT ) {
- cl->ping = 0;
- continue;
- }
-
- total = 0;
- count = 0;
- for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
- if ( cl->frames[j].messageAcked <= 0 ) {
- continue;
- }
- delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
- count++;
- total += delta;
- }
- if (!count) {
- cl->ping = 999;
- } else {
- cl->ping = total/count;
- if ( cl->ping > 999 ) {
- cl->ping = 999;
- }
- }
-
- // let the game dll know about the ping
- ps = SV_GameClientNum( i );
- ps->ping = cl->ping;
- }
-}
-
-/*
-==================
-SV_CheckTimeouts
-
-If a packet has not been received from a client for timeout->integer
-seconds, drop the conneciton. Server time is used instead of
-realtime to avoid dropping the local client while debugging.
-
-When a client is normally dropped, the client_t goes into a zombie state
-for a few seconds to make sure any final reliable message gets resent
-if necessary
-==================
-*/
-void SV_CheckTimeouts( void ) {
- int i;
- client_t *cl;
- int droppoint;
- int zombiepoint;
-
- droppoint = svs.time - 1000 * sv_timeout->integer;
- zombiepoint = svs.time - 1000 * sv_zombietime->integer;
-
- for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
- // message times may be wrong across a changelevel
- if (cl->lastPacketTime > svs.time) {
- cl->lastPacketTime = svs.time;
- }
-
- if (cl->state == CS_ZOMBIE
- && cl->lastPacketTime < zombiepoint) {
- // using the client id cause the cl->name is empty at this point
- Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
- cl->state = CS_FREE; // can now be reused
- continue;
- }
- if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
- // wait several frames so a debugger session doesn't
- // cause a timeout
- if ( ++cl->timeoutCount > 5 ) {
- SV_DropClient (cl, "timed out");
- cl->state = CS_FREE; // don't bother with zombie state
- }
- } else {
- cl->timeoutCount = 0;
- }
- }
-}
-
-
-/*
-==================
-SV_CheckPaused
-==================
-*/
-qboolean SV_CheckPaused( void ) {
- int count;
- client_t *cl;
- int i;
-
- if ( !cl_paused->integer ) {
- return qfalse;
- }
-
- // only pause if there is just a single client connected
- count = 0;
- for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
- if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
- count++;
- }
- }
-
- if ( count > 1 ) {
- // don't pause
- if (sv_paused->integer)
- Cvar_Set("sv_paused", "0");
- return qfalse;
- }
-
- if (!sv_paused->integer)
- Cvar_Set("sv_paused", "1");
- return qtrue;
-}
-
-/*
-==================
-SV_Frame
-
-Player movement occurs as a result of packet events, which
-happen before SV_Frame is called
-==================
-*/
-void SV_Frame( int msec ) {
- int frameMsec;
- int startTime;
-
- // the menu kills the server with this cvar
- if ( sv_killserver->integer ) {
- SV_Shutdown ("Server was killed.\n");
- Cvar_Set( "sv_killserver", "0" );
- return;
- }
-
- if ( !com_sv_running->integer ) {
- return;
- }
-
- // allow pause if only the local client is connected
- if ( SV_CheckPaused() ) {
- return;
- }
-
- // if it isn't time for the next frame, do nothing
- if ( sv_fps->integer < 1 ) {
- Cvar_Set( "sv_fps", "10" );
- }
- frameMsec = 1000 / sv_fps->integer ;
-
- sv.timeResidual += msec;
-
- if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );
-
- if ( com_dedicated->integer && sv.timeResidual < frameMsec ) {
- // NET_Sleep will give the OS time slices until either get a packet
- // or time enough for a server frame has gone by
- NET_Sleep(frameMsec - sv.timeResidual);
- return;
- }
-
- // if time is about to hit the 32nd bit, kick all clients
- // and clear sv.time, rather
- // than checking for negative time wraparound everywhere.
- // 2giga-milliseconds = 23 days, so it won't be too often
- if ( svs.time > 0x70000000 ) {
- SV_Shutdown( "Restarting server due to time wrapping" );
- Cbuf_AddText( "vstr nextmap\n" );
- return;
- }
- // this can happen considerably earlier when lots of clients play and the map doesn't change
- if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
- SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
- Cbuf_AddText( "vstr nextmap\n" );
- return;
- }
-
- if( sv.restartTime && svs.time >= sv.restartTime ) {
- sv.restartTime = 0;
- Cbuf_AddText( "map_restart 0\n" );
- return;
- }
-
- // update infostrings if anything has been changed
- if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
- SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
- cvar_modifiedFlags &= ~CVAR_SERVERINFO;
- }
- if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
- SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
- cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
- }
-
- if ( com_speeds->integer ) {
- startTime = Sys_Milliseconds ();
- } else {
- startTime = 0; // quite a compiler warning
- }
-
- // update ping based on the all received frames
- SV_CalcPings();
-
- if (com_dedicated->integer) SV_BotFrame( svs.time );
-
- // run the game simulation in chunks
- while ( sv.timeResidual >= frameMsec ) {
- sv.timeResidual -= frameMsec;
- svs.time += frameMsec;
-
- // let everything in the world think and move
- VM_Call( gvm, GAME_RUN_FRAME, svs.time );
- }
-
- if ( com_speeds->integer ) {
- time_game = Sys_Milliseconds () - startTime;
- }
-
- // check timeouts
- SV_CheckTimeouts();
-
- // send messages back to the clients
- SV_SendClientMessages();
-
- // send a heartbeat to the master if needed
- SV_MasterHeartbeat();
-}
-
-//============================================================================
-
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "server.h"
+
+serverStatic_t svs; // persistant server info
+server_t sv; // local server
+vm_t *gvm = NULL; // game virtual machine // bk001212 init
+
+cvar_t *sv_fps; // time rate for running non-clients
+cvar_t *sv_timeout; // seconds without any message
+cvar_t *sv_zombietime; // seconds to sink messages after disconnect
+cvar_t *sv_rconPassword; // password for remote server commands
+cvar_t *sv_privatePassword; // password for the privateClient slots
+cvar_t *sv_allowDownload;
+cvar_t *sv_maxclients;
+
+cvar_t *sv_privateClients; // number of clients reserved for password
+cvar_t *sv_hostname;
+cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
+cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
+cvar_t *sv_showloss; // report when usercmds are lost
+cvar_t *sv_padPackets; // add nop bytes to messages
+cvar_t *sv_killserver; // menu system can set to 1 to shut server down
+cvar_t *sv_mapname;
+cvar_t *sv_mapChecksum;
+cvar_t *sv_serverid;
+cvar_t *sv_maxRate;
+cvar_t *sv_minPing;
+cvar_t *sv_maxPing;
+cvar_t *sv_gametype;
+cvar_t *sv_pure;
+cvar_t *sv_floodProtect;
+cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
+cvar_t *sv_strictAuth;
+
+/*
+=============================================================================
+
+EVENT MESSAGES
+
+=============================================================================
+*/
+
+/*
+===============
+SV_ExpandNewlines
+
+Converts newlines to "\n" so a line prints nicer
+===============
+*/
+char *SV_ExpandNewlines( char *in ) {
+ static char string[1024];
+ int l;
+
+ l = 0;
+ while ( *in && l < sizeof(string) - 3 ) {
+ if ( *in == '\n' ) {
+ string[l++] = '\\';
+ string[l++] = 'n';
+ } else {
+ string[l++] = *in;
+ }
+ in++;
+ }
+ string[l] = 0;
+
+ return string;
+}
+
+/*
+======================
+SV_ReplacePendingServerCommands
+
+ This is ugly
+======================
+*/
+int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
+ int i, index, csnum1, csnum2;
+
+ for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
+ index = i & ( MAX_RELIABLE_COMMANDS - 1 );
+ //
+ if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
+ sscanf(cmd, "cs %i", &csnum1);
+ sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
+ if ( csnum1 == csnum2 ) {
+ Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
+ /*
+ if ( client->netchan.remoteAddress.type != NA_BOT ) {
+ Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
+ }
+ */
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+======================
+SV_AddServerCommand
+
+The given command will be transmitted to the client, and is guaranteed to
+not have future snapshot_t executed before it is executed
+======================
+*/
+void SV_AddServerCommand( client_t *client, const char *cmd ) {
+ int index, i;
+
+ // this is very ugly but it's also a waste to for instance send multiple config string updates
+ // for the same config string index in one snapshot
+// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
+// return;
+// }
+
+ client->reliableSequence++;
+ // if we would be losing an old command that hasn't been acknowledged,
+ // we must drop the connection
+ // we check == instead of >= so a broadcast print added by SV_DropClient()
+ // doesn't cause a recursive drop client
+ if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
+ Com_Printf( "===== pending server commands =====\n" );
+ for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
+ Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
+ }
+ Com_Printf( "cmd %5d: %s\n", i, cmd );
+ SV_DropClient( client, "Server command overflow" );
+ return;
+ }
+ index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
+ Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
+}
+
+
+/*
+=================
+SV_SendServerCommand
+
+Sends a reliable command string to be interpreted by
+the client game module: "cp", "print", "chat", etc
+A NULL client will broadcast to all clients
+=================
+*/
+void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
+ va_list argptr;
+ byte message[MAX_MSGLEN];
+ client_t *client;
+ int j;
+
+ va_start (argptr,fmt);
+ Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
+ va_end (argptr);
+
+ if ( cl != NULL ) {
+ SV_AddServerCommand( cl, (char *)message );
+ return;
+ }
+
+ // hack to echo broadcast prints to console
+ if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
+ Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
+ }
+
+ // send the data to all relevent clients
+ for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
+ if ( client->state < CS_PRIMED ) {
+ continue;
+ }
+ SV_AddServerCommand( client, (char *)message );
+ }
+}
+
+
+/*
+==============================================================================
+
+MASTER SERVER FUNCTIONS
+
+==============================================================================
+*/
+
+/*
+================
+SV_MasterHeartbeat
+
+Send a message to the masters every few minutes to
+let it know we are alive, and log information.
+We will also have a heartbeat sent when a server
+changes from empty to non-empty, and full to non-full,
+but not on every player enter or exit.
+================
+*/
+#define HEARTBEAT_MSEC 300*1000
+#define HEARTBEAT_GAME "QuakeArena-1"
+void SV_MasterHeartbeat( void ) {
+ static netadr_t adr[MAX_MASTER_SERVERS];
+ int i;
+
+ // "dedicated 1" is for lan play, "dedicated 2" is for inet public play
+ if ( !com_dedicated || com_dedicated->integer != 2 ) {
+ return; // only dedicated servers send heartbeats
+ }
+
+ // if not time yet, don't send anything
+ if ( svs.time < svs.nextHeartbeatTime ) {
+ return;
+ }
+ svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
+
+
+ // send to group masters
+ for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
+ if ( !sv_master[i]->string[0] ) {
+ continue;
+ }
+
+ // see if we haven't already resolved the name
+ // resolving usually causes hitches on win95, so only
+ // do it when needed
+ if ( sv_master[i]->modified ) {
+ sv_master[i]->modified = qfalse;
+
+ Com_Printf( "Resolving %s\n", sv_master[i]->string );
+ if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
+ // if the address failed to resolve, clear it
+ // so we don't take repeated dns hits
+ Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
+ Cvar_Set( sv_master[i]->name, "" );
+ sv_master[i]->modified = qfalse;
+ continue;
+ }
+ if ( !strstr( ":", sv_master[i]->string ) ) {
+ adr[i].port = BigShort( PORT_MASTER );
+ }
+ Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
+ adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
+ BigShort( adr[i].port ) );
+ }
+
+
+ Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
+ // this command should be changed if the server info / status format
+ // ever incompatably changes
+ NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
+ }
+}
+
+/*
+=================
+SV_MasterShutdown
+
+Informs all masters that this server is going down
+=================
+*/
+void SV_MasterShutdown( void ) {
+ // send a hearbeat right now
+ svs.nextHeartbeatTime = -9999;
+ SV_MasterHeartbeat();
+
+ // send it again to minimize chance of drops
+ svs.nextHeartbeatTime = -9999;
+ SV_MasterHeartbeat();
+
+ // when the master tries to poll the server, it won't respond, so
+ // it will be removed from the list
+}
+
+
+/*
+==============================================================================
+
+CONNECTIONLESS COMMANDS
+
+==============================================================================
+*/
+
+/*
+================
+SVC_Status
+
+Responds with all the info that qplug or qspy can see about the server
+and all connected players. Used for getting detailed information after
+the simple info query.
+================
+*/
+void SVC_Status( netadr_t from ) {
+ char player[1024];
+ char status[MAX_MSGLEN];
+ int i;
+ client_t *cl;
+ playerState_t *ps;
+ int statusLength;
+ int playerLength;
+ char infostring[MAX_INFO_STRING];
+
+ // ignore if we are in single player
+ if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
+ return;
+ }
+
+ strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
+
+ // echo back the parameter to status. so master servers can use it as a challenge
+ // to prevent timed spoofed reply packets that add ghost servers
+ Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
+
+ // add "demo" to the sv_keywords if restricted
+ if ( Cvar_VariableValue( "fs_restrict" ) ) {
+ char keywords[MAX_INFO_STRING];
+
+ Com_sprintf( keywords, sizeof( keywords ), "demo %s",
+ Info_ValueForKey( infostring, "sv_keywords" ) );
+ Info_SetValueForKey( infostring, "sv_keywords", keywords );
+ }
+
+ status[0] = 0;
+ statusLength = 0;
+
+ for (i=0 ; i < sv_maxclients->integer ; i++) {
+ cl = &svs.clients[i];
+ if ( cl->state >= CS_CONNECTED ) {
+ ps = SV_GameClientNum( i );
+ Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
+ ps->persistant[PERS_SCORE], cl->ping, cl->name);
+ playerLength = strlen(player);
+ if (statusLength + playerLength >= sizeof(status) ) {
+ break; // can't hold any more
+ }
+ strcpy (status + statusLength, player);
+ statusLength += playerLength;
+ }
+ }
+
+ NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
+}
+
+/*
+================
+SVC_Info
+
+Responds with a short info message that should be enough to determine
+if a user is interested in a server to do a full status
+================
+*/
+void SVC_Info( netadr_t from ) {
+ int i, count;
+ char *gamedir;
+ char infostring[MAX_INFO_STRING];
+
+ // ignore if we are in single player
+ if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
+ return;
+ }
+
+ // don't count privateclients
+ count = 0;
+ for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
+ if ( svs.clients[i].state >= CS_CONNECTED ) {
+ count++;
+ }
+ }
+
+ infostring[0] = 0;
+
+ // echo back the parameter to status. so servers can use it as a challenge
+ // to prevent timed spoofed reply packets that add ghost servers
+ Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
+
+ Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
+ Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
+ Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
+ Info_SetValueForKey( infostring, "clients", va("%i", count) );
+ Info_SetValueForKey( infostring, "sv_maxclients",
+ va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
+ Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
+ Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
+
+ if( sv_minPing->integer ) {
+ Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
+ }
+ if( sv_maxPing->integer ) {
+ Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
+ }
+ gamedir = Cvar_VariableString( "fs_game" );
+ if( *gamedir ) {
+ Info_SetValueForKey( infostring, "game", gamedir );
+ }
+
+ NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
+}
+
+/*
+================
+SVC_FlushRedirect
+
+================
+*/
+void SV_FlushRedirect( char *outputbuf ) {
+ NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
+}
+
+/*
+===============
+SVC_RemoteCommand
+
+An rcon packet arrived from the network.
+Shift down the remaining args
+Redirect all printfs
+===============
+*/
+void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
+ qboolean valid;
+ unsigned int time;
+ char remaining[1024];
+ // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
+ // (OOB messages are the bottleneck here)
+#define SV_OUTPUTBUF_LENGTH (1024 - 16)
+ char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
+ static unsigned int lasttime = 0;
+ char *cmd_aux;
+
+ // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
+ time = Com_Milliseconds();
+ if (time<(lasttime+500)) {
+ return;
+ }
+ lasttime = time;
+
+ if ( !strlen( sv_rconPassword->string ) ||
+ strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
+ valid = qfalse;
+ Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
+ } else {
+ valid = qtrue;
+ Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
+ }
+
+ // start redirecting all print outputs to the packet
+ svs.redirectAddress = from;
+ Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
+
+ if ( !strlen( sv_rconPassword->string ) ) {
+ Com_Printf ("No rconpassword set on the server.\n");
+ } else if ( !valid ) {
+ Com_Printf ("Bad rconpassword.\n");
+ } else {
+ remaining[0] = 0;
+
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
+ // get the command directly, "rcon <pass> <command>" to avoid quoting issues
+ // extract the command by walking
+ // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
+ cmd_aux = Cmd_Cmd();
+ cmd_aux+=4;
+ while(cmd_aux[0]==' ')
+ cmd_aux++;
+ while(cmd_aux[0] && cmd_aux[0]!=' ') // password
+ cmd_aux++;
+ while(cmd_aux[0]==' ')
+ cmd_aux++;
+
+ Q_strcat( remaining, sizeof(remaining), cmd_aux);
+
+ Cmd_ExecuteString (remaining);
+
+ }
+
+ Com_EndRedirect ();
+}
+
+/*
+=================
+SV_ConnectionlessPacket
+
+A connectionless packet has four leading 0xff
+characters to distinguish it from a game channel.
+Clients that are in the game can still send
+connectionless packets.
+=================
+*/
+void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
+ char *s;
+ char *c;
+
+ MSG_BeginReadingOOB( msg );
+ MSG_ReadLong( msg ); // skip the -1 marker
+
+ if (!Q_strncmp("connect", &msg->data[4], 7)) {
+ Huff_Decompress(msg, 12);
+ }
+
+ s = MSG_ReadStringLine( msg );
+ Cmd_TokenizeString( s );
+
+ c = Cmd_Argv(0);
+ Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c);
+
+ if (!Q_stricmp(c, "getstatus")) {
+ SVC_Status( from );
+ } else if (!Q_stricmp(c, "getinfo")) {
+ SVC_Info( from );
+ } else if (!Q_stricmp(c, "getchallenge")) {
+ SV_GetChallenge( from );
+ } else if (!Q_stricmp(c, "connect")) {
+ SV_DirectConnect( from );
+ } else if (!Q_stricmp(c, "ipAuthorize")) {
+ SV_AuthorizeIpPacket( from );
+ } else if (!Q_stricmp(c, "rcon")) {
+ SVC_RemoteCommand( from, msg );
+ } else if (!Q_stricmp(c, "disconnect")) {
+ // if a client starts up a local server, we may see some spurious
+ // server disconnect messages when their new server sees our final
+ // sequenced messages to the old client
+ } else {
+ Com_DPrintf ("bad connectionless packet from %s:\n%s\n"
+ , NET_AdrToString (from), s);
+ }
+}
+
+//============================================================================
+
+/*
+=================
+SV_ReadPackets
+=================
+*/
+void SV_PacketEvent( netadr_t from, msg_t *msg ) {
+ int i;
+ client_t *cl;
+ int qport;
+
+ // check for connectionless packet (0xffffffff) first
+ if ( msg->cursize >= 4 && *(int *)msg->data == -1) {
+ SV_ConnectionlessPacket( from, msg );
+ return;
+ }
+
+ // read the qport out of the message so we can fix up
+ // stupid address translating routers
+ MSG_BeginReadingOOB( msg );
+ MSG_ReadLong( msg ); // sequence number
+ qport = MSG_ReadShort( msg ) & 0xffff;
+
+ // find which client the message is from
+ for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ if (cl->state == CS_FREE) {
+ continue;
+ }
+ if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) {
+ continue;
+ }
+ // it is possible to have multiple clients from a single IP
+ // address, so they are differentiated by the qport variable
+ if (cl->netchan.qport != qport) {
+ continue;
+ }
+
+ // the IP port can't be used to differentiate them, because
+ // some address translating routers periodically change UDP
+ // port assignments
+ if (cl->netchan.remoteAddress.port != from.port) {
+ Com_Printf( "SV_PacketEvent: fixing up a translated port\n" );
+ cl->netchan.remoteAddress.port = from.port;
+ }
+
+ // make sure it is a valid, in sequence packet
+ if (SV_Netchan_Process(cl, msg)) {
+ // zombie clients still need to do the Netchan_Process
+ // to make sure they don't need to retransmit the final
+ // reliable message, but they don't do any other processing
+ if (cl->state != CS_ZOMBIE) {
+ cl->lastPacketTime = svs.time; // don't timeout
+ SV_ExecuteClientMessage( cl, msg );
+ }
+ }
+ return;
+ }
+
+ // if we received a sequenced packet from an address we don't recognize,
+ // send an out of band disconnect packet to it
+ NET_OutOfBandPrint( NS_SERVER, from, "disconnect" );
+}
+
+
+/*
+===================
+SV_CalcPings
+
+Updates the cl->ping variables
+===================
+*/
+void SV_CalcPings( void ) {
+ int i, j;
+ client_t *cl;
+ int total, count;
+ int delta;
+ playerState_t *ps;
+
+ for (i=0 ; i < sv_maxclients->integer ; i++) {
+ cl = &svs.clients[i];
+ if ( cl->state != CS_ACTIVE ) {
+ cl->ping = 999;
+ continue;
+ }
+ if ( !cl->gentity ) {
+ cl->ping = 999;
+ continue;
+ }
+ if ( cl->gentity->r.svFlags & SVF_BOT ) {
+ cl->ping = 0;
+ continue;
+ }
+
+ total = 0;
+ count = 0;
+ for ( j = 0 ; j < PACKET_BACKUP ; j++ ) {
+ if ( cl->frames[j].messageAcked <= 0 ) {
+ continue;
+ }
+ delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
+ count++;
+ total += delta;
+ }
+ if (!count) {
+ cl->ping = 999;
+ } else {
+ cl->ping = total/count;
+ if ( cl->ping > 999 ) {
+ cl->ping = 999;
+ }
+ }
+
+ // let the game dll know about the ping
+ ps = SV_GameClientNum( i );
+ ps->ping = cl->ping;
+ }
+}
+
+/*
+==================
+SV_CheckTimeouts
+
+If a packet has not been received from a client for timeout->integer
+seconds, drop the conneciton. Server time is used instead of
+realtime to avoid dropping the local client while debugging.
+
+When a client is normally dropped, the client_t goes into a zombie state
+for a few seconds to make sure any final reliable message gets resent
+if necessary
+==================
+*/
+void SV_CheckTimeouts( void ) {
+ int i;
+ client_t *cl;
+ int droppoint;
+ int zombiepoint;
+
+ droppoint = svs.time - 1000 * sv_timeout->integer;
+ zombiepoint = svs.time - 1000 * sv_zombietime->integer;
+
+ for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ // message times may be wrong across a changelevel
+ if (cl->lastPacketTime > svs.time) {
+ cl->lastPacketTime = svs.time;
+ }
+
+ if (cl->state == CS_ZOMBIE
+ && cl->lastPacketTime < zombiepoint) {
+ // using the client id cause the cl->name is empty at this point
+ Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i );
+ cl->state = CS_FREE; // can now be reused
+ continue;
+ }
+ if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) {
+ // wait several frames so a debugger session doesn't
+ // cause a timeout
+ if ( ++cl->timeoutCount > 5 ) {
+ SV_DropClient (cl, "timed out");
+ cl->state = CS_FREE; // don't bother with zombie state
+ }
+ } else {
+ cl->timeoutCount = 0;
+ }
+ }
+}
+
+
+/*
+==================
+SV_CheckPaused
+==================
+*/
+qboolean SV_CheckPaused( void ) {
+ int count;
+ client_t *cl;
+ int i;
+
+ if ( !cl_paused->integer ) {
+ return qfalse;
+ }
+
+ // only pause if there is just a single client connected
+ count = 0;
+ for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
+ if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) {
+ count++;
+ }
+ }
+
+ if ( count > 1 ) {
+ // don't pause
+ if (sv_paused->integer)
+ Cvar_Set("sv_paused", "0");
+ return qfalse;
+ }
+
+ if (!sv_paused->integer)
+ Cvar_Set("sv_paused", "1");
+ return qtrue;
+}
+
+/*
+==================
+SV_Frame
+
+Player movement occurs as a result of packet events, which
+happen before SV_Frame is called
+==================
+*/
+void SV_Frame( int msec ) {
+ int frameMsec;
+ int startTime;
+
+ // the menu kills the server with this cvar
+ if ( sv_killserver->integer ) {
+ SV_Shutdown ("Server was killed.\n");
+ Cvar_Set( "sv_killserver", "0" );
+ return;
+ }
+
+ if ( !com_sv_running->integer ) {
+ return;
+ }
+
+ // allow pause if only the local client is connected
+ if ( SV_CheckPaused() ) {
+ return;
+ }
+
+ // if it isn't time for the next frame, do nothing
+ if ( sv_fps->integer < 1 ) {
+ Cvar_Set( "sv_fps", "10" );
+ }
+ frameMsec = 1000 / sv_fps->integer ;
+
+ sv.timeResidual += msec;
+
+ if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );
+
+ if ( com_dedicated->integer && sv.timeResidual < frameMsec ) {
+ // NET_Sleep will give the OS time slices until either get a packet
+ // or time enough for a server frame has gone by
+ NET_Sleep(frameMsec - sv.timeResidual);
+ return;
+ }
+
+ // if time is about to hit the 32nd bit, kick all clients
+ // and clear sv.time, rather
+ // than checking for negative time wraparound everywhere.
+ // 2giga-milliseconds = 23 days, so it won't be too often
+ if ( svs.time > 0x70000000 ) {
+ SV_Shutdown( "Restarting server due to time wrapping" );
+ Cbuf_AddText( "vstr nextmap\n" );
+ return;
+ }
+ // this can happen considerably earlier when lots of clients play and the map doesn't change
+ if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) {
+ SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" );
+ Cbuf_AddText( "vstr nextmap\n" );
+ return;
+ }
+
+ if( sv.restartTime && svs.time >= sv.restartTime ) {
+ sv.restartTime = 0;
+ Cbuf_AddText( "map_restart 0\n" );
+ return;
+ }
+
+ // update infostrings if anything has been changed
+ if ( cvar_modifiedFlags & CVAR_SERVERINFO ) {
+ SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) );
+ cvar_modifiedFlags &= ~CVAR_SERVERINFO;
+ }
+ if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) {
+ SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
+ cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
+ }
+
+ if ( com_speeds->integer ) {
+ startTime = Sys_Milliseconds ();
+ } else {
+ startTime = 0; // quite a compiler warning
+ }
+
+ // update ping based on the all received frames
+ SV_CalcPings();
+
+ if (com_dedicated->integer) SV_BotFrame( svs.time );
+
+ // run the game simulation in chunks
+ while ( sv.timeResidual >= frameMsec ) {
+ sv.timeResidual -= frameMsec;
+ svs.time += frameMsec;
+
+ // let everything in the world think and move
+ VM_Call( gvm, GAME_RUN_FRAME, svs.time );
+ }
+
+ if ( com_speeds->integer ) {
+ time_game = Sys_Milliseconds () - startTime;
+ }
+
+ // check timeouts
+ SV_CheckTimeouts();
+
+ // send messages back to the clients
+ SV_SendClientMessages();
+
+ // send a heartbeat to the master if needed
+ SV_MasterHeartbeat();
+}
+
+//============================================================================
+