2019-03-13 19:20:07 +00:00
/*
2020-06-04 21:01:28 +00:00
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2019-03-13 19:20:07 +00:00
Copyright ( C ) 1997 - 2001 Id Software , Inc .
2020-06-04 21:01:28 +00:00
This file is part of Quake 2 source code .
2019-03-13 19:20:07 +00:00
2020-06-04 21:01:28 +00:00
Quake 2 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 .
2019-03-13 19:20:07 +00:00
2020-06-04 21:01:28 +00:00
Quake 2 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 .
2019-03-13 19:20:07 +00:00
You should have received a copy of the GNU General Public License
2020-06-04 21:01:28 +00:00
along with Quake 2 source code ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2019-03-13 19:20:07 +00:00
*/
2020-06-04 21:01:28 +00:00
// sv_main.c -- server main program
2019-03-13 19:20:07 +00:00
# include "server.h"
netadr_t master_adr [ MAX_MASTERS ] ; // address of group servers
client_t * sv_client ; // current client
cvar_t * sv_paused ;
cvar_t * sv_timedemo ;
cvar_t * sv_enforcetime ;
cvar_t * timeout ; // seconds without any message
cvar_t * zombietime ; // seconds to sink messages after disconnect
cvar_t * rcon_password ; // password for remote server commands
cvar_t * allow_download ;
cvar_t * allow_download_players ;
cvar_t * allow_download_models ;
cvar_t * allow_download_sounds ;
cvar_t * allow_download_maps ;
cvar_t * allow_download_pics ;
cvar_t * allow_download_textures ;
// Knightmare- whether to allow downloading 24-bit textures
cvar_t * allow_download_textures_24bit ;
cvar_t * sv_downloadserver ; // from R1Q2
cvar_t * sv_baselines_maxlen ; // Knightmare- max packet size for connect messages
cvar_t * sv_limit_msglen ; // Knightmare- whether to use MAX_MSGLEN_MP for multiplayer games
cvar_t * sv_airaccelerate ;
cvar_t * sv_noreload ; // don't reload level state when reentering
cvar_t * maxclients ; // FIXME: rename sv_maxclients
cvar_t * sv_showclamp ;
cvar_t * hostname ;
cvar_t * public_server ; // should heartbeats be sent
cvar_t * sv_iplimit ; // r1ch: max connections from a single IP (prevent DoS)
cvar_t * sv_reconnect_limit ; // minimum seconds between connect messages
cvar_t * sv_entfile ; // whether to use .ent file
void Master_Shutdown ( void ) ;
//============================================================================
/*
= = = = = = = = = = = = = = = = = = = = =
SV_DropClient
Called when the player is totally leaving the server , either willingly
or unwillingly . This is NOT called if the entire server is quiting
or crashing .
= = = = = = = = = = = = = = = = = = = = =
*/
void SV_DropClient ( client_t * drop )
{
// add the disconnect
MSG_WriteByte ( & drop - > netchan . message , svc_disconnect ) ;
if ( drop - > state = = cs_spawned )
{
// call the prog function for removing a client
// this will remove the body, among other things
ge - > ClientDisconnect ( drop - > edict ) ;
}
if ( drop - > download )
{
FS_FreeFile ( drop - > download ) ;
drop - > download = NULL ;
}
// r1ch: fix for mods that don't clean score
if ( drop - > edict & & drop - > edict - > client )
drop - > edict - > client - > ps . stats [ STAT_FRAGS ] = 0 ;
drop - > state = cs_zombie ; // become free in a few seconds
drop - > name [ 0 ] = 0 ;
}
//Knightmare added
/*
= = = = = = = = = = = = = = = = = = = = =
GetClientFromAdr
Given an netadr_t , returns the matching client .
= = = = = = = = = = = = = = = = = = = = =
*/
client_t * GetClientFromAdr ( netadr_t address )
{
client_t * cl ;
int i ;
qboolean found = false ;
for ( i = 0 ; i < maxclients - > value ; i + + )
{
cl = & svs . clients [ i ] ;
if ( NET_CompareBaseAdr ( cl - > netchan . remote_address , address ) ) {
found = true ; break ; }
}
if ( found )
return cl ;
else // don't return non-matching client
return NULL ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
SV_DropClientFromAdr
Calls SV_DropClient , takes netadr_t instead of client pointer .
= = = = = = = = = = = = = = = = = = = = =
*/
void SV_DropClientFromAdr ( netadr_t address )
{ // adapted Pat Aftermoon's simplified version of this
client_t * drop = GetClientFromAdr ( address ) ;
if ( ! drop ) return ; // make sure we have a client to drop
SV_BroadcastPrintf ( PRINT_HIGH , " dropping client %s \n " , drop - > name ) ;
SV_DropClient ( drop ) ;
drop - > state = cs_free ; // don't bother with zombie state
}
// end Knightmare
/*
= = = = = = = = = = = = = = = = = = = = =
SV_KickClient
From R1Q2
= = = = = = = = = = = = = = = = = = = = =
*/
void SV_KickClient ( client_t * cl , const char * reason , const char * cprintf )
{
if ( reason & & cl - > state = = cs_spawned & & cl - > name [ 0 ] )
SV_BroadcastPrintf ( PRINT_HIGH , " %s was dropped: %s \n " , cl - > name , reason ) ;
if ( cprintf )
SV_ClientPrintf ( cl , PRINT_HIGH , " %s " , cprintf ) ;
Com_Printf ( " Dropping %s, %s. \n " , cl - > name , reason ? reason : " SV_KickClient " ) ;
SV_DropClient ( cl ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
SV_CleanClient
From R1Q2
r1ch : this does the final cleaning up of a client after zombie state .
= = = = = = = = = = = = = = = = = = = = =
*/
void SV_CleanClient ( client_t * drop )
{
if ( drop - > download )
{
Z_Free ( drop - > download ) ;
drop - > download = NULL ;
}
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
CONNECTIONLESS COMMANDS
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = =
SV_StatusString
Builds the string that is sent as heartbeats and status replies
= = = = = = = = = = = = = = =
*/
char * SV_StatusString ( void )
{
char player [ 1024 ] ;
static char status [ MAX_MSGLEN - 16 ] ;
int i ;
client_t * cl ;
int statusLength ;
int playerLength ;
// strncpy (status, Cvar_Serverinfo());
// strncat (status, "\n");
Q_strncpyz ( status , Cvar_Serverinfo ( ) , sizeof ( status ) ) ;
Q_strncatz ( status , " \n " , sizeof ( status ) ) ;
2020-07-29 10:05:09 +00:00
statusLength = ( int ) strlen ( status ) ;
2019-03-13 19:20:07 +00:00
for ( i = 0 ; i < maxclients - > value ; i + + )
{
cl = & svs . clients [ i ] ;
if ( cl - > state = = cs_connected | | cl - > state = = cs_spawned )
{
Com_sprintf ( player , sizeof ( player ) , " %i %i \" %s \" \n " ,
cl - > edict - > client - > ps . stats [ STAT_FRAGS ] , cl - > ping , cl - > name ) ;
2020-07-29 10:05:09 +00:00
playerLength = ( int ) strlen ( player ) ;
2019-03-13 19:20:07 +00:00
if ( statusLength + playerLength > = sizeof ( status ) )
break ; // can't hold any more
// strncpy (status + statusLength, player);
Q_strncpyz ( status + statusLength , player , sizeof ( status ) ) ;
statusLength + = playerLength ;
}
}
return status ;
}
/*
= = = = = = = = = = = = = = = =
SVC_Status
Responds with all the info that qplug or qspy can see
= = = = = = = = = = = = = = = =
*/
void SVC_Status ( void )
{
Netchan_OutOfBandPrint ( NS_SERVER , net_from , " print \n %s " , SV_StatusString ( ) ) ;
#if 0
Com_BeginRedirect ( RD_PACKET , sv_outputbuf , SV_OUTPUTBUF_LENGTH , SV_FlushRedirect ) ;
Com_Printf ( SV_StatusString ( ) ) ;
Com_EndRedirect ( ) ;
# endif
}
/*
= = = = = = = = = = = = = = = =
SVC_Ack
= = = = = = = = = = = = = = = =
*/
void SVC_Ack ( void )
{
Com_Printf ( " Ping acknowledge from %s \n " , NET_AdrToString ( net_from ) ) ;
}
/*
= = = = = = = = = = = = = = = =
SVC_Info
Responds with short info for broadcast scans
The second parameter should be the current protocol version number .
= = = = = = = = = = = = = = = =
*/
void SVC_Info ( void )
{
char string [ 64 ] ;
int i , count ;
int version ;
if ( maxclients - > value = = 1 )
return ; // ignore in single player
version = atoi ( Cmd_Argv ( 1 ) ) ;
if ( version ! = PROTOCOL_VERSION )
{ // According to r1ch, this can be used to make servers endlessly ping each other
// Com_sprintf (string, sizeof(string), "%s: wrong version\n", hostname->string, sizeof(string));
return ;
}
else
{
count = 0 ;
for ( i = 0 ; i < maxclients - > value ; i + + )
if ( svs . clients [ i ] . state > = cs_connected )
count + + ;
Com_sprintf ( string , sizeof ( string ) , " %16s %8s %2i/%2i \n " , hostname - > string , sv . name , count , ( int ) maxclients - > value ) ;
}
Netchan_OutOfBandPrint ( NS_SERVER , net_from , " info \n %s " , string ) ;
}
/*
= = = = = = = = = = = = = = = =
SVC_Ping
Just responds with an acknowledgement
= = = = = = = = = = = = = = = =
*/
void SVC_Ping ( void )
{
Netchan_OutOfBandPrint ( NS_SERVER , net_from , " ack " ) ;
}
/*
= = = = = = = = = = = = = = = = =
SVC_GetChallenge
Returns a challenge number that can be used
in a subsequent client_connect command .
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs . With a
challenge , they must give a valid IP address .
= = = = = = = = = = = = = = = = =
*/
void SVC_GetChallenge ( void )
{
int i ;
int oldest ;
int oldestTime ;
oldest = 0 ;
oldestTime = 0x7fffffff ;
// see if we already have a challenge for this ip
for ( i = 0 ; i < MAX_CHALLENGES ; i + + )
{
if ( NET_CompareBaseAdr ( net_from , svs . challenges [ i ] . adr ) )
break ;
if ( svs . challenges [ i ] . time < oldestTime )
{
oldestTime = svs . challenges [ i ] . time ;
oldest = i ;
}
}
if ( i = = MAX_CHALLENGES )
{
// overwrite the oldest
svs . challenges [ oldest ] . challenge = rand ( ) & 0x7fff ;
svs . challenges [ oldest ] . adr = net_from ;
svs . challenges [ oldest ] . time = curtime ;
i = oldest ;
}
// send it back
Netchan_OutOfBandPrint ( NS_SERVER , net_from , " challenge %i " , svs . challenges [ i ] . challenge ) ;
}
/*
= = = = = = = = = = = = = = = = = =
SVC_DirectConnect
A connection request that did not come from the master
= = = = = = = = = = = = = = = = = =
*/
void SVC_DirectConnect ( void )
{
char userinfo [ MAX_INFO_STRING ] ;
netadr_t adr ;
int i ;
client_t * cl , * newcl ;
client_t temp ;
edict_t * ent ;
int edictnum ;
int version ;
int qport ;
int challenge ;
int previousclients ; // rich: connection limit per IP
adr = net_from ;
Com_DPrintf ( " SVC_DirectConnect () \n " ) ;
version = atoi ( Cmd_Argv ( 1 ) ) ;
if ( version ! = PROTOCOL_VERSION )
{
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Server is version %4.2f. \n " , VERSION ) ;
Com_DPrintf ( " rejected connect from version %i \n " , version ) ;
return ;
}
qport = atoi ( Cmd_Argv ( 2 ) ) ;
challenge = atoi ( Cmd_Argv ( 3 ) ) ;
// r1ch: limit connections from a single IP
previousclients = 0 ;
for ( i = 0 , cl = svs . clients ; i < ( int ) maxclients - > value ; i + + , cl + + )
{
if ( cl - > state = = cs_free )
continue ;
if ( NET_CompareBaseAdr ( adr , cl - > netchan . remote_address ) )
{
// r1ch: zombies are less dangerous
if ( cl - > state = = cs_zombie )
previousclients + + ;
else
previousclients + = 2 ;
}
}
if ( previousclients > = ( int ) sv_iplimit - > value * 2 )
{
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Too many connections from your host. \n " ) ;
Com_DPrintf ( " too many connections \n " ) ;
return ;
}
// end r1ch fix
strncpy ( userinfo , Cmd_Argv ( 4 ) , sizeof ( userinfo ) - 1 ) ;
userinfo [ sizeof ( userinfo ) - 1 ] = 0 ;
// force the IP key/value pair so the game can filter based on ip
Info_SetValueForKey ( userinfo , " ip " , NET_AdrToString ( net_from ) ) ;
// attractloop servers are ONLY for local clients
if ( sv . attractloop )
{
if ( ! NET_IsLocalAddress ( adr ) )
{
Com_Printf ( " Remote connect in attract loop. Ignored. \n " ) ;
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Connection refused. \n " ) ;
return ;
}
}
// see if the challenge is valid
if ( ! NET_IsLocalAddress ( adr ) )
{
for ( i = 0 ; i < MAX_CHALLENGES ; i + + )
{
if ( NET_CompareBaseAdr ( net_from , svs . challenges [ i ] . adr ) )
{
if ( challenge = = svs . challenges [ i ] . challenge )
break ; // good
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Bad challenge. \n " ) ;
return ;
}
}
if ( i = = MAX_CHALLENGES )
{
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n No challenge for address. \n " ) ;
return ;
}
}
newcl = & temp ;
memset ( newcl , 0 , sizeof ( client_t ) ) ;
// if there is already a slot for this ip, reuse it
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
{
if ( cl - > state = = cs_free )
continue ;
if ( NET_CompareBaseAdr ( adr , cl - > netchan . remote_address )
& & ( cl - > netchan . qport = = qport
| | adr . port = = cl - > netchan . remote_address . port ) )
{
if ( ! NET_IsLocalAddress ( adr ) & & ( svs . realtime - cl - > lastconnect ) < ( ( int ) sv_reconnect_limit - > value * 1000 ) )
{
Com_DPrintf ( " %s:reconnect rejected : too soon \n " , NET_AdrToString ( adr ) ) ;
return ;
}
// r1ch: !! fix nasty bug where non-disconnected clients (from dropped disconnect
// packets) could be overwritten!
if ( cl - > state ! = cs_zombie )
{
Com_DPrintf ( " client already found \n " ) ;
// If we legitly get here, spoofed udp isn't possible (passed challenge) and client addr/port combo
// is exactly the same, so we can assume its really a dropped/crashed client. i hope...
Com_Printf ( " Dropping %s, ghost reconnect \n " , cl - > name ) ;
SV_DropClient ( cl ) ;
}
// end r1ch fix
Com_Printf ( " %s:reconnect \n " , NET_AdrToString ( adr ) ) ;
SV_CleanClient ( cl ) ; // r1ch: clean up last client data
newcl = cl ;
goto gotnewcl ;
}
}
// find a client slot
newcl = NULL ;
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
{
if ( cl - > state = = cs_free )
{
newcl = cl ;
break ;
}
}
if ( ! newcl )
{
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Server is full. \n " ) ;
Com_DPrintf ( " Rejected a connection. \n " ) ;
return ;
}
gotnewcl :
// build a new connection
// accept the new client
// this is the only place a client_t is ever initialized
* newcl = temp ;
sv_client = newcl ;
edictnum = ( newcl - svs . clients ) + 1 ;
ent = EDICT_NUM ( edictnum ) ;
newcl - > edict = ent ;
newcl - > challenge = challenge ; // save challenge for checksumming
// get the game a chance to reject this connection or modify the userinfo
if ( ! ( ge - > ClientConnect ( ent , userinfo ) ) )
{
if ( * Info_ValueForKey ( userinfo , " rejmsg " ) )
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n %s \n Connection refused. \n " ,
Info_ValueForKey ( userinfo , " rejmsg " ) ) ;
else
Netchan_OutOfBandPrint ( NS_SERVER , adr , " print \n Connection refused. \n " ) ;
Com_DPrintf ( " Game rejected a connection. \n " ) ;
return ;
}
// parse some info from the info strings
strncpy ( newcl - > userinfo , userinfo , sizeof ( newcl - > userinfo ) - 1 ) ;
SV_UserinfoChanged ( newcl ) ;
// send the connect packet to the client
// r1ch: note we could ideally send this twice but it prints unsightly message on original client.
if ( sv_downloadserver - > string [ 0 ] )
Netchan_OutOfBandPrint ( NS_SERVER , adr , " client_connect dlserver=%s " , sv_downloadserver - > string ) ;
else
Netchan_OutOfBandPrint ( NS_SERVER , adr , " client_connect " ) ;
Netchan_Setup ( NS_SERVER , & newcl - > netchan , adr , qport ) ;
newcl - > state = cs_connected ;
SZ_Init ( & newcl - > datagram , newcl - > datagram_buf , sizeof ( newcl - > datagram_buf ) ) ;
newcl - > datagram . allowoverflow = true ;
newcl - > lastmessage = svs . realtime ; // don't timeout
newcl - > lastconnect = svs . realtime ;
}
int Rcon_Validate ( void )
{
if ( ! strlen ( rcon_password - > string ) )
return 0 ;
if ( strcmp ( Cmd_Argv ( 1 ) , rcon_password - > string ) )
return 0 ;
return 1 ;
}
/*
= = = = = = = = = = = = = = =
SVC_RemoteCommand
A client issued an rcon command .
Shift down the remaining args
Redirect all printfs
= = = = = = = = = = = = = = =
*/
void SVC_RemoteCommand ( void )
{
int i ;
char remaining [ 1024 ] ;
i = Rcon_Validate ( ) ;
if ( i = = 0 )
Com_Printf ( " Bad rcon from %s: \n %s \n " , NET_AdrToString ( net_from ) , net_message . data + 4 ) ;
else
Com_Printf ( " Rcon from %s: \n %s \n " , NET_AdrToString ( net_from ) , net_message . data + 4 ) ;
Com_BeginRedirect ( RD_PACKET , sv_outputbuf , SV_OUTPUTBUF_LENGTH , SV_FlushRedirect ) ;
if ( ! Rcon_Validate ( ) )
{
Com_Printf ( " Bad rcon_password. \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 ( void )
{
char * s ;
char * c ;
// r1ch fix: make sure we never talk to ourselves
// if (NET_IsLocalAddress (net_from.ip[0] == 127) && !NET_IsLocalHost(net_from) && ShortSwap(net_from.port) == server_port)
/* if ( (net_from.ip[0] == 127) && (net_from.type != NA_LOOPBACK) && (ShortSwap(net_from.port) == server_port) )
{
Com_DPrintf ( " dropped %d byte connectionless packet from self! (spoofing attack?) \n " , net_message . cursize ) ;
return ;
} */
MSG_BeginReading ( & net_message ) ;
MSG_ReadLong ( & net_message ) ; // skip the -1 marker
s = MSG_ReadStringLine ( & net_message ) ;
Cmd_TokenizeString ( s , false ) ;
c = Cmd_Argv ( 0 ) ;
Com_DPrintf ( " Packet %s : %s \n " , NET_AdrToString ( net_from ) , c ) ;
if ( ! strcmp ( c , " ping " ) )
SVC_Ping ( ) ;
else if ( ! strcmp ( c , " ack " ) )
SVC_Ack ( ) ;
else if ( ! strcmp ( c , " status " ) )
SVC_Status ( ) ;
else if ( ! strcmp ( c , " info " ) )
SVC_Info ( ) ;
else if ( ! strcmp ( c , " getchallenge " ) )
SVC_GetChallenge ( ) ;
else if ( ! strcmp ( c , " connect " ) )
SVC_DirectConnect ( ) ;
else if ( ! strcmp ( c , " rcon " ) )
SVC_RemoteCommand ( ) ;
else
Com_Printf ( " bad connectionless packet from %s: \n %s \n "
, NET_AdrToString ( net_from ) , s ) ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = = = = =
SV_CalcPings
Updates the cl - > ping variables
= = = = = = = = = = = = = = = = = = =
*/
void SV_CalcPings ( void )
{
int i , j ;
client_t * cl ;
int total , count ;
for ( i = 0 ; i < maxclients - > value ; i + + )
{
cl = & svs . clients [ i ] ;
if ( cl - > state ! = cs_spawned )
continue ;
#if 0
if ( cl - > lastframe > 0 )
cl - > frame_latency [ sv . framenum & ( LATENCY_COUNTS - 1 ) ] = sv . framenum - cl - > lastframe + 1 ;
else
cl - > frame_latency [ sv . framenum & ( LATENCY_COUNTS - 1 ) ] = 0 ;
# endif
total = 0 ;
count = 0 ;
for ( j = 0 ; j < LATENCY_COUNTS ; j + + )
{
if ( cl - > frame_latency [ j ] > 0 )
{
count + + ;
total + = cl - > frame_latency [ j ] ;
}
}
if ( ! count )
cl - > ping = 0 ;
else
#if 0
cl - > ping = total * 100 / count - 100 ;
# else
cl - > ping = total / count ;
# endif
// let the game dll know about the ping
cl - > edict - > client - > ping = cl - > ping ;
}
}
/*
= = = = = = = = = = = = = = = = = = =
SV_GiveMsec
Every few frames , gives all clients an allotment of milliseconds
for their command moves . If they exceed it , assume cheating .
= = = = = = = = = = = = = = = = = = =
*/
void SV_GiveMsec ( void )
{
int i ;
client_t * cl ;
if ( sv . framenum & 15 )
return ;
for ( i = 0 ; i < maxclients - > value ; i + + )
{
cl = & svs . clients [ i ] ;
if ( cl - > state = = cs_free )
continue ;
cl - > commandMsec = 1800 ; // 1600 + some slop
}
}
/*
= = = = = = = = = = = = = = = = =
SV_ReadPackets
= = = = = = = = = = = = = = = = =
*/
void SV_ReadPackets ( void )
{
int i ;
client_t * cl ;
int qport ;
while ( NET_GetPacket ( NS_SERVER , & net_from , & net_message ) )
{
// check for connectionless packet (0xffffffff) first
if ( * ( int * ) net_message . data = = - 1 )
{
SV_ConnectionlessPacket ( ) ;
continue ;
}
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_BeginReading ( & net_message ) ;
MSG_ReadLong ( & net_message ) ; // sequence number
MSG_ReadLong ( & net_message ) ; // sequence number
qport = MSG_ReadShort ( & net_message ) & 0xffff ;
// check for packets from connected clients
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
{
if ( cl - > state = = cs_free )
continue ;
if ( ! NET_CompareBaseAdr ( net_from , cl - > netchan . remote_address ) )
continue ;
if ( cl - > netchan . qport ! = qport )
continue ;
if ( cl - > netchan . remote_address . port ! = net_from . port )
{
Com_Printf ( " SV_ReadPackets: fixing up a translated port \n " ) ;
cl - > netchan . remote_address . port = net_from . port ;
}
if ( Netchan_Process ( & cl - > netchan , & net_message ) )
{ // this is a valid, sequenced packet, so process it
if ( cl - > state ! = cs_zombie )
{
cl - > lastmessage = svs . realtime ; // don't timeout
SV_ExecuteClientMessage ( cl ) ;
}
}
break ;
}
if ( i ! = maxclients - > value )
continue ;
}
}
/*
= = = = = = = = = = = = = = = = = =
SV_CheckTimeouts
If a packet has not been received from a client for timeout - > value
seconds , drop the conneciton . Server frames are 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 . realtime - 1000 * timeout - > value ;
zombiepoint = svs . realtime - 1000 * zombietime - > value ;
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
{
// message times may be wrong across a changelevel
if ( cl - > lastmessage > svs . realtime )
cl - > lastmessage = svs . realtime ;
if ( cl - > state = = cs_zombie
& & cl - > lastmessage < zombiepoint )
{
SV_CleanClient ( cl ) ; // r1ch fix: make sure client is cleaned up
cl - > state = cs_free ; // can now be reused
continue ;
}
if ( ( cl - > state = = cs_connected | | cl - > state = = cs_spawned )
& & cl - > lastmessage < droppoint )
{
// r1ch fix: only message if they spawned (less spam plz)
if ( cl - > state = = cs_spawned & & cl - > name [ 0 ] )
SV_BroadcastPrintf ( PRINT_HIGH , " %s timed out \n " , cl - > name ) ;
SV_DropClient ( cl ) ;
cl - > state = cs_free ; // don't bother with zombie state
}
}
}
/*
= = = = = = = = = = = = = = = =
SV_PrepWorldFrame
This has to be done before the world logic , because
player processing happens outside RunWorldFrame
= = = = = = = = = = = = = = = =
*/
void SV_PrepWorldFrame ( void )
{
edict_t * ent ;
int i ;
for ( i = 0 ; i < ge - > num_edicts ; i + + , ent + + )
{
ent = EDICT_NUM ( i ) ;
// events only last for a single message
ent - > s . event = 0 ;
}
}
/*
= = = = = = = = = = = = = = = = =
SV_RunGameFrame
= = = = = = = = = = = = = = = = =
*/
void SV_RunGameFrame ( void )
{
if ( host_speeds - > value )
time_before_game = Sys_Milliseconds ( ) ;
// we always need to bump framenum, even if we
// don't run the world, otherwise the delta
// compression can get confused when a client
// has the "current" frame
sv . framenum + + ;
sv . time = sv . framenum * 100 ;
// don't run if paused
if ( ! sv_paused - > value | | maxclients - > value > 1 )
{
ge - > RunFrame ( ) ;
// never get more than one tic behind
if ( sv . time < svs . realtime )
{
if ( sv_showclamp - > value )
Com_Printf ( " sv highclamp \n " ) ;
svs . realtime = sv . time ;
}
}
if ( host_speeds - > value )
time_after_game = Sys_Milliseconds ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
SV_Frame
= = = = = = = = = = = = = = = = = =
*/
void SV_Frame ( int msec )
{
time_before_game = time_after_game = 0 ;
// if server is not active, do nothing
if ( ! svs . initialized )
return ;
svs . realtime + = msec ;
// keep the random time dependent
rand ( ) ;
// check timeouts
SV_CheckTimeouts ( ) ;
// get packets from clients
SV_ReadPackets ( ) ;
// move autonomous things around if enough time has passed
if ( ! sv_timedemo - > value & & svs . realtime < sv . time )
{
// never let the time get too far off
if ( sv . time - svs . realtime > 100 )
{
if ( sv_showclamp - > value )
Com_Printf ( " sv lowclamp \n " ) ;
svs . realtime = sv . time - 100 ;
}
NET_Sleep ( sv . time - svs . realtime ) ;
return ;
}
// update ping based on the last known frame from all clients
SV_CalcPings ( ) ;
// give the clients some timeslices
SV_GiveMsec ( ) ;
// let everything in the world think and move
SV_RunGameFrame ( ) ;
// send messages back to the clients that had packets read this frame
SV_SendClientMessages ( ) ;
// save the entire world state if recording a serverdemo
SV_RecordDemoMessage ( ) ;
// send a heartbeat to the master if needed
Master_Heartbeat ( ) ;
// clear teleport flags, etc for next frame
SV_PrepWorldFrame ( ) ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = =
Master_Heartbeat
Send a message to the master every few minutes to
let it know we are alive , and log information
= = = = = = = = = = = = = = = =
*/
# define HEARTBEAT_SECONDS 300
void Master_Heartbeat ( void )
{
char * string ;
int i ;
// pgm post 3.19 change, cvar pointer not validated before dereferencing
if ( ! dedicated | | ! dedicated - > value )
return ; // only dedicated servers send heartbeats
// pgm post 3.19 change, cvar pointer not validated before dereferencing
if ( ! public_server | | ! public_server - > value )
return ; // a private dedicated game
// check for time wraparound
if ( svs . last_heartbeat > svs . realtime )
svs . last_heartbeat = svs . realtime ;
if ( svs . realtime - svs . last_heartbeat < HEARTBEAT_SECONDS * 1000 )
return ; // not time to send yet
svs . last_heartbeat = svs . realtime ;
// send the same string that we would give for a status OOB command
string = SV_StatusString ( ) ;
// send to group master
for ( i = 0 ; i < MAX_MASTERS ; i + + )
if ( master_adr [ i ] . port )
{
Com_Printf ( " Sending heartbeat to %s \n " , NET_AdrToString ( master_adr [ i ] ) ) ;
Netchan_OutOfBandPrint ( NS_SERVER , master_adr [ i ] , " heartbeat \n %s " , string ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
Master_Shutdown
Informs all masters that this server is going down
= = = = = = = = = = = = = = = = =
*/
void Master_Shutdown ( void )
{
int i ;
// pgm post3.19 change, cvar pointer not validated before dereferencing
if ( ! dedicated | | ! dedicated - > value )
return ; // only dedicated servers send heartbeats
// pgm post3.19 change, cvar pointer not validated before dereferencing
if ( ! public_server | | ! public_server - > value )
return ; // a private dedicated game
// send to group master
for ( i = 0 ; i < MAX_MASTERS ; i + + )
if ( master_adr [ i ] . port )
{
if ( i > 0 )
Com_Printf ( " Sending heartbeat to %s \n " , NET_AdrToString ( master_adr [ i ] ) ) ;
Netchan_OutOfBandPrint ( NS_SERVER , master_adr [ i ] , " shutdown " ) ;
}
}
//============================================================================
/*
= = = = = = = = = = = = = = = = =
SV_UserinfoChanged
Pull specific info from a newly changed userinfo string
into a more C freindly form .
= = = = = = = = = = = = = = = = =
*/
void SV_UserinfoChanged ( client_t * cl )
{
char * val ;
int i ;
// call prog code to allow overrides
ge - > ClientUserinfoChanged ( cl - > edict , cl - > userinfo ) ;
// name for C code
strncpy ( cl - > name , Info_ValueForKey ( cl - > userinfo , " name " ) , sizeof ( cl - > name ) - 1 ) ;
// mask off high bit
for ( i = 0 ; i < sizeof ( cl - > name ) ; i + + )
cl - > name [ i ] & = 127 ;
// rate command
val = Info_ValueForKey ( cl - > userinfo , " rate " ) ;
if ( strlen ( val ) )
{
i = atoi ( val ) ;
cl - > rate = i ;
if ( cl - > rate < 100 )
cl - > rate = 100 ;
if ( cl - > rate > 15000 )
cl - > rate = 15000 ;
}
else
cl - > rate = 5000 ;
// msg command
val = Info_ValueForKey ( cl - > userinfo , " msg " ) ;
if ( strlen ( val ) )
{
cl - > messagelevel = atoi ( val ) ;
}
}
//============================================================================
/*
= = = = = = = = = = = = = = =
SV_Init
Only called at quake2 . exe startup , not for each game
= = = = = = = = = = = = = = =
*/
void SV_Init ( void )
{
SV_InitOperatorCommands ( ) ;
rcon_password = Cvar_Get ( " rcon_password " , " " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " rcon_password " , " Sets password for rcon commands. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " skill " , " 1 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " skill " , " Sets skill level. 0 = Easy, 1 = Medium, 2 = Hard, 3 = Nightmare. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " deathmatch " , " 0 " , CVAR_LATCH ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " deathmatch " , " Enables non-coop multiplayer. This is enabled for both Deathmatch and CTF. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " coop " , " 0 " , CVAR_LATCH ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " coop " , " Enables cooperative multiplayer. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " dmflags " , va ( " %i " , DF_INSTANT_ITEMS ) , CVAR_SERVERINFO ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " dmflags " , " Bitfield for deathmatch options. Options and values vary by mod. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " fraglimit " , " 0 " , CVAR_SERVERINFO ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " fraglimit " , " Number of frags reached in Deathmatch that triggers a map change. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " timelimit " , " 0 " , CVAR_SERVERINFO ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " timelimit " , " Time in minutes between Deathmatch map changes. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " cheats " , " 0 " , CVAR_SERVERINFO | CVAR_LATCH ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " cheats " , " Enables cheat codes in multiplayer games. " ) ;
2019-03-13 19:20:07 +00:00
Cvar_Get ( " protocol " , va ( " %i " , PROTOCOL_VERSION ) , CVAR_SERVERINFO | CVAR_NOSET ) ; ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " protocol " , " Protocol version for this server. " ) ;
2019-03-13 19:20:07 +00:00
maxclients = Cvar_Get ( " maxclients " , " 1 " , CVAR_SERVERINFO | CVAR_LATCH ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " maxclients " , " Maximum number of players allowed on this server. " ) ;
2019-03-13 19:20:07 +00:00
hostname = Cvar_Get ( " hostname " , " noname " , CVAR_SERVERINFO | CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " hostname " , " Name of this server visible to clients. " ) ;
2019-03-13 19:20:07 +00:00
timeout = Cvar_Get ( " timeout " , " 125 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " timeout " , " Time in seconds after which non-responsive clients are dropped. " ) ;
2019-03-13 19:20:07 +00:00
zombietime = Cvar_Get ( " zombietime " , " 2 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " zombietime " , " Time in seconds which client slots are kept in zombie state. " ) ;
2019-03-13 19:20:07 +00:00
sv_showclamp = Cvar_Get ( " showclamp " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " showclamp " , " Enables output of high and low frame clamps. " ) ;
2019-03-13 19:20:07 +00:00
sv_paused = Cvar_Get ( " paused " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " paused " , " Value that determines if the game is paused. Considered a client cheat in multiplayer. " ) ;
2019-03-13 19:20:07 +00:00
sv_timedemo = Cvar_Get ( " timedemo " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " timedemo " , " Set to 1 for timing playback of demos. Useful for old-school bencmarking. " ) ;
2019-03-13 19:20:07 +00:00
sv_enforcetime = Cvar_Get ( " sv_enforcetime " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_enforcetime " , " Enables check for underflow of client command time. " ) ;
2019-03-13 19:20:07 +00:00
allow_download = Cvar_Get ( " allow_download " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download " , " Enables autodownload of game assets in multiplayer. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_players = Cvar_Get ( " allow_download_players " , " 0 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_players " , " Enables download of player models. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_models = Cvar_Get ( " allow_download_models " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_models " , " Enables download of models. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_sounds = Cvar_Get ( " allow_download_sounds " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_sounds " , " Enables download of sounds. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_maps = Cvar_Get ( " allow_download_maps " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_maps " , " Enables download of maps. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_pics = Cvar_Get ( " allow_download_pics " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_pics " , " Enables download of .pcx images. " ) ;
2019-03-13 19:20:07 +00:00
allow_download_textures = Cvar_Get ( " allow_download_textures " , " 1 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_textures " , " Enables download of .wal textures. " ) ;
2019-03-13 19:20:07 +00:00
// Knightmare- whether to allow downloading 24-bit textures
allow_download_textures_24bit = Cvar_Get ( " allow_download_textures_24bit " , " 0 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " allow_download_textures_24bit " , " Enables download of TGA/JPG/PNG textures. " ) ;
2019-03-13 19:20:07 +00:00
sv_downloadserver = Cvar_Get ( " sv_downloadserver " , " " , 0 ) ; // r1ch: http dl server
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_downloadserver " , " Sets URL of HTTP autodownload server where clients can download game content over HTTP. Default empty. Path leads to game dir name, e.g. quake2.com/baseq2/maps. " ) ;
2019-03-13 19:20:07 +00:00
sv_baselines_maxlen = Cvar_Get ( " sv_baselines_maxlen " , " 1200 " , 0 ) ; // Knightmare- max packet size for connect messages
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_baselines_maxlen " , " Sets max packet size for non-local network connect messages. " ) ;
2019-03-13 19:20:07 +00:00
sv_limit_msglen = Cvar_Get ( " sv_limit_msglen " , " 1 " , 0 ) ; // Knightmare- whether to use MAX_MSGLEN_MP for multiplayer games
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_limit_msglen " , " Enables limited packet size for non-local clients. " ) ;
2019-03-13 19:20:07 +00:00
sv_noreload = Cvar_Get ( " sv_noreload " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_noreload " , " Disables loading of savegames on map change when set to 1. " ) ;
2019-03-13 19:20:07 +00:00
sv_airaccelerate = Cvar_Get ( " sv_airaccelerate " , " 0 " , CVAR_LATCH ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_airaccelerate " , " Sets client air acceleration for non-coop multiplayer games. " ) ;
2019-03-13 19:20:07 +00:00
public_server = Cvar_Get ( " public " , " 0 " , 0 ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " public " , " Sets whether this is a public server. " ) ;
2019-03-13 19:20:07 +00:00
sv_iplimit = Cvar_Get ( " sv_iplimit " , " 3 " , 0 ) ; // r1ch: limit connections per ip address (stop zombie dos/flood)
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_iplimit " , " Sets connection limit per IP address. Stops zombie DoS/Flood. " ) ;
2019-03-13 19:20:07 +00:00
sv_reconnect_limit = Cvar_Get ( " sv_reconnect_limit " , " 3 " , CVAR_ARCHIVE ) ;
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_reconnect_limit " , " Sets time in seconds before a client can reconnect. " ) ;
2019-03-13 19:20:07 +00:00
sv_entfile = Cvar_Get ( " sv_entfile " , " 1 " , CVAR_ARCHIVE ) ; // whether to use .ent file
2020-07-04 05:31:44 +00:00
Cvar_SetDescription ( " sv_entfile " , " Enables automatic loading of .ent files to replace BSP entity lump. " ) ;
2019-03-13 19:20:07 +00:00
SZ_Init ( & net_message , net_message_buffer , sizeof ( net_message_buffer ) ) ;
}
/*
= = = = = = = = = = = = = = = = = =
SV_FinalMessage
Used by SV_Shutdown to send a final message to all
connected clients before the server goes down . The messages are sent immediately ,
not just stuck on the outgoing message list , because the server is going
to totally exit after returning from this function .
= = = = = = = = = = = = = = = = = =
*/
void SV_FinalMessage ( char * message , qboolean reconnect )
{
int i ;
client_t * cl ;
SZ_Clear ( & net_message ) ;
MSG_WriteByte ( & net_message , svc_print ) ;
MSG_WriteByte ( & net_message , PRINT_HIGH ) ;
MSG_WriteString ( & net_message , message ) ;
if ( reconnect )
MSG_WriteByte ( & net_message , svc_reconnect ) ;
else
MSG_WriteByte ( & net_message , svc_disconnect ) ;
// send it twice
// stagger the packets to crutch operating system limited buffers
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
if ( cl - > state > = cs_connected )
Netchan_Transmit ( & cl - > netchan , net_message . cursize
, net_message . data ) ;
for ( i = 0 , cl = svs . clients ; i < maxclients - > value ; i + + , cl + + )
if ( cl - > state > = cs_connected )
Netchan_Transmit ( & cl - > netchan , net_message . cursize
, net_message . data ) ;
}
/*
= = = = = = = = = = = = = = = =
SV_Shutdown
Called when each game quits ,
before Sys_Quit or Sys_Error
= = = = = = = = = = = = = = = =
*/
void SV_Shutdown ( char * finalmsg , qboolean reconnect )
{
if ( svs . clients )
SV_FinalMessage ( finalmsg , reconnect ) ;
Master_Shutdown ( ) ;
SV_ShutdownGameProgs ( ) ;
// free current level
if ( sv . demofile )
FS_FCloseFile ( sv . demofile ) ;
memset ( & sv , 0 , sizeof ( sv ) ) ;
Com_SetServerState ( sv . state ) ;
// free server static data
if ( svs . clients )
Z_Free ( svs . clients ) ;
if ( svs . client_entities )
Z_Free ( svs . client_entities ) ;
if ( svs . demofile )
fclose ( svs . demofile ) ;
memset ( & svs , 0 , sizeof ( svs ) ) ;
}