jedi-academy/codemp/server/sv_main.cpp
Jonathan Gray 983705084a CVE-2005-0983 Fixed q3msgboom
CVE-2005-0983
Quake 3 engine, as used in multiple games, allows remote attackers to
cause a denial of service (client disconnect) via a long message, which
is not properly truncated and causes the engine to process the remaining
data as if it were network data.

Luigi Auriemma q3msgboom

from Tim Angus in ioquake
svn 95 git 33a48a0336865a9d21983e4836920cd9f3401101

Fixed q3msgboom

from http://www.quakesrc.org/forums/viewtopic.php?t=5374
2013-05-07 22:18:44 +10:00

957 lines
25 KiB
C++

//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
#include "server.h"
//rww - RAGDOLL_BEGIN
#include "../ghoul2/ghoul2_shared.h"
//rww - RAGDOLL_END
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_maxclients;
cvar_t *sv_privateClients; // number of clients reserved for password
cvar_t *sv_hostname;
#ifndef _XBOX // No master or downloads on Xbox
cvar_t *sv_allowDownload;
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
#endif
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
cvar_t *sv_showghoultraces; // report ghoul2 traces
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_needpass;
#ifdef USE_CD_KEY
cvar_t *sv_allowAnonymous;
#endif
/*
=============================================================================
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);
vsprintf ((char *)message, fmt,argptr);
va_end (argptr);
// Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
// The actual cause of the bug is probably further downstream
// and should maybe be addressed later, but this certainly
// fixes the problem for now
if ( strlen ((char *)message) > 1022 ) {
return;
}
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
==============================================================================
*/
#ifndef _XBOX // No master on Xbox
#define NEW_RESOLVE_DURATION 86400000 //24 hours
static int g_lastResolveTime[MAX_MASTER_SERVERS];
static inline bool SV_MasterNeedsResolving(int server, int time)
{ //refresh every so often regardless of if the actual address was modified -rww
if (g_lastResolveTime[server] > time)
{ //time flowed backwards?
return true;
}
if ((time-g_lastResolveTime[server]) > NEW_RESOLVE_DURATION)
{ //it's time again
return true;
}
return false;
}
/*
================
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;
int time;
// "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;
//we need to use this instead of svs.time since svs.time resets over map changes (or rather
//every time the game restarts), and we don't really need to resolve every map change
time = Com_Milliseconds();
// 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_MasterNeedsResolving(i, time) ) {
sv_master[i]->modified = qfalse;
g_lastResolveTime[i] = time;
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
}
#endif // _XBOX - No master on Xbox
/*
==============================================================================
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, wDisable;
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;
}
*/
#ifdef _XBOX
// don't send system link info if in Xbox Live
if (logged_on)
return;
#endif
if (Cvar_VariableValue("ui_singlePlayerActive"))
{
return;
}
/*
* Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
* to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
*/
// A maximum challenge length of 128 should be more than plenty.
if(strlen(Cmd_Argv(1)) > 128)
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, "needpass", va("%i", sv_needpass->integer ) );
Info_SetValueForKey( infostring, "truejedi", va("%i", Cvar_VariableIntegerValue( "g_jediVmerc" ) ) );
if ( sv_gametype->integer == GT_DUEL || sv_gametype->integer == GT_POWERDUEL )
{
wDisable = Cvar_VariableIntegerValue( "g_duelWeaponDisable" );
}
else
{
wDisable = Cvar_VariableIntegerValue( "g_weaponDisable" );
}
Info_SetValueForKey( infostring, "wdisable", va("%i", wDisable ) );
Info_SetValueForKey( infostring, "fdisable", va("%i", Cvar_VariableIntegerValue( "g_forcePowerDisable" ) ) );
//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 );
}
#ifdef USE_CD_KEY
Info_SetValueForKey( infostring, "sv_allowAnonymous", va("%i", sv_allowAnonymous->integer) );
#endif
#ifdef _XBOX
// Include Xbox specific networking info
char sxnkid[XNKID_STRING_LEN];
XNKIDToString(SysLink_GetXNKID(), sxnkid);
Info_SetValueForKey(infostring, "xnkid", sxnkid);
char sxnkey[XNKEY_STRING_LEN];
XNKEYToString(SysLink_GetXNKEY(), sxnkey);
Info_SetValueForKey(infostring, "xnkey", sxnkey);
char sxnaddr[XNADDR_STRING_LEN];
XnAddrToString(Net_GetXNADDR(), sxnaddr);
Info_SetValueForKey(infostring, "xnaddr", sxnaddr);
#endif
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 i, time;
char remaining[1024];
#define SV_OUTPUTBUF_LENGTH (MAX_MSGLEN - 16)
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
static unsigned int lasttime = 0;
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_DPrintf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
} else {
valid = qtrue;
Com_DPrintf ("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.\n");
} else if ( !valid ) {
Com_Printf ("Bad rconpassword.\n");
} else {
remaining[0] = 0;
for (i=2 ; i<Cmd_Argc() ; i++) {
strcat (remaining, Cmd_Argv(i) );
strcat (remaining, " ");
}
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", (const char *)&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 );
#ifndef _XBOX // No authorization on Xbox
} else if (!Q_stricmp(c, "ipAuthorize")) {
SV_AuthorizeIpPacket( from );
#endif
} 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_ReadPackets: 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 reckognize,
// 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) {
Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for %s\n", cl->name );
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
sv_paused->integer = 0;
return qfalse;
}
sv_paused->integer = 1;
return qtrue;
}
/*
==================
SV_CheckCvars
==================
*/
void SV_CheckCvars( void ) {
static int lastMod = -1;
qboolean changed = qfalse;
if ( sv_hostname->modificationCount != lastMod ) {
char hostname[MAX_INFO_STRING];
char *c = hostname;
lastMod = sv_hostname->modificationCount;
strcpy( hostname, sv_hostname->string );
while( *c )
{
if ( (*c == '\\') || (*c == ';') || (*c == '"'))
{
*c = '.';
changed = qtrue;
}
c++;
}
if( changed )
{
Cvar_Set("sv_hostname", hostname );
}
}
}
/*
==================
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 && (!com_timescale || com_timescale->value >= 1) ) {
// 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" );
Cbuf_AddText( "map_restart 0\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" );
Cbuf_AddText( "map_restart 0\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 );
}
//rww - RAGDOLL_BEGIN
G2API_SetTime(svs.time,0);
//rww - RAGDOLL_END
if ( com_speeds->integer ) {
time_game = Sys_Milliseconds () - startTime;
}
// check timeouts
SV_CheckTimeouts();
// send messages back to the clients
SV_SendClientMessages();
SV_CheckCvars();
// send a heartbeat to the master if needed
#ifndef _XBOX // No master on Xbox
SV_MasterHeartbeat();
#endif
}
//============================================================================