diff options
Diffstat (limited to 'src/sv_user.c')
-rw-r--r-- | src/sv_user.c | 1362 |
1 files changed, 1362 insertions, 0 deletions
diff --git a/src/sv_user.c b/src/sv_user.c new file mode 100644 index 0000000..b4ba1be --- /dev/null +++ b/src/sv_user.c @@ -0,0 +1,1362 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_user.c -- server code for moving users + +#include "sv_local.h" + +edict_t *sv_player; + +/* +============================================================ + +USER STRINGCMD EXECUTION + +sv_client and sv_player will be valid. +============================================================ +*/ + +/* +================ +SV_CreateBaselines + +Entity baselines are used to compress the update messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +static void create_baselines( void ) { + int i; + edict_t *ent; + entity_state_t *base, **chunk; + + // clear baselines from previous level + for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) { + base = sv_client->baselines[i]; + if( !base ) { + continue; + } + memset( base, 0, sizeof( *base ) * SV_BASELINES_PER_CHUNK ); + } + + for( i = 1; i < sv_client->pool->num_edicts; i++ ) { + ent = EDICT_POOL( sv_client, i ); + + if( ( g_features->integer & GMF_PROPERINUSE ) && !ent->inuse ) { + continue; + } + + if( !ES_INUSE( &ent->s ) ) { + continue; + } + + ent->s.number = i; + + chunk = &sv_client->baselines[i >> SV_BASELINES_SHIFT]; + if( *chunk == NULL ) { + *chunk = SV_Mallocz( sizeof( *base ) * SV_BASELINES_PER_CHUNK ); + } + + base = *chunk + ( i & SV_BASELINES_MASK ); + + *base = ent->s; + + if( sv_client->esFlags & MSG_ES_LONGSOLID ) { + base->solid = sv.entities[i].solid32; + } + } +} + +static void write_plain_configstrings( void ) { + int i; + char *string; + size_t length; + + // write a packet full of data + string = sv_client->configstrings; + for( i = 0; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) { + if( !string[0] ) { + continue; + } + length = strlen( string ); + if( length > MAX_QPATH ) { + length = MAX_QPATH; + } + // check if this configstring will overflow + if( msg_write.cursize + length + 64 > sv_client->netchan->maxpacketlen ) { + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + } + + MSG_WriteByte( svc_configstring ); + MSG_WriteShort( i ); + MSG_WriteData( string, length ); + MSG_WriteByte( 0 ); + } + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); +} + +static inline void write_baseline( entity_state_t *base ) { + MSG_WriteDeltaEntity( NULL, base, sv_client->esFlags | MSG_ES_FORCE ); +} + +static void write_plain_baselines( void ) { + int i, j; + entity_state_t *base; + + // write a packet full of data + for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) { + base = sv_client->baselines[i]; + if( !base ) { + continue; + } + for( j = 0; j < SV_BASELINES_PER_CHUNK; j++ ) { + if( base->number ) { + // check if this baseline will overflow + if( msg_write.cursize + 64 > sv_client->netchan->maxpacketlen ) { + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + } + + MSG_WriteByte( svc_spawnbaseline ); + write_baseline( base ); + } + base++; + } + } + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); +} + +#if USE_ZLIB + +static void write_compressed_gamestate( void ) { + sizebuf_t *buf = &sv_client->netchan->message; + entity_state_t *base; + int i, j; + size_t length; + uint8_t *patch; + char *string; + + MSG_WriteByte( svc_gamestate ); + + // write configstrings + string = sv_client->configstrings; + for( i = 0; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) { + if( !string[0] ) { + continue; + } + length = strlen( string ); + if( length > MAX_QPATH ) { + length = MAX_QPATH; + } + + MSG_WriteShort( i ); + MSG_WriteData( string, length ); + MSG_WriteByte( 0 ); + } + MSG_WriteShort( MAX_CONFIGSTRINGS ); // end of configstrings + + // write baselines + for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) { + base = sv_client->baselines[i]; + if( !base ) { + continue; + } + for( j = 0; j < SV_BASELINES_PER_CHUNK; j++ ) { + if( base->number ) { + write_baseline( base ); + } + base++; + } + } + MSG_WriteShort( 0 ); // end of baselines + + SZ_WriteByte( buf, svc_zpacket ); + patch = SZ_GetSpace( buf, 2 ); + SZ_WriteShort( buf, msg_write.cursize ); + + deflateReset( &svs.z ); + svs.z.next_in = msg_write.data; + svs.z.avail_in = ( uInt )msg_write.cursize; + svs.z.next_out = buf->data + buf->cursize; + svs.z.avail_out = ( uInt )( buf->maxsize - buf->cursize ); + SZ_Clear( &msg_write ); + + if( deflate( &svs.z, Z_FINISH ) != Z_STREAM_END ) { + SV_DropClient( sv_client, "deflate() failed on gamestate" ); + return; + } + + SV_DPrintf( 0, "%s: comp: %lu into %lu\n", + sv_client->name, svs.z.total_in, svs.z.total_out ); + + patch[0] = svs.z.total_out & 255; + patch[1] = ( svs.z.total_out >> 8 ) & 255; + buf->cursize += svs.z.total_out; +} + +static inline int z_flush( byte *buffer ) { + int ret; + + ret = deflate( &svs.z, Z_FINISH ); + if( ret != Z_STREAM_END ) { + return ret; + } + + SV_DPrintf( 0, "%s: comp: %lu into %lu\n", + sv_client->name, svs.z.total_in, svs.z.total_out ); + + MSG_WriteByte( svc_zpacket ); + MSG_WriteShort( svs.z.total_out ); + MSG_WriteShort( svs.z.total_in ); + MSG_WriteData( buffer, svs.z.total_out ); + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + + return ret; +} + +static inline void z_reset( byte *buffer ) { + deflateReset( &svs.z ); + svs.z.next_out = buffer; + svs.z.avail_out = ( uInt )( sv_client->netchan->maxpacketlen - 5 ); +} + +static void write_compressed_configstrings( void ) { + int i; + size_t length; + byte buffer[MAX_PACKETLEN_WRITABLE]; + char *string; + + z_reset( buffer ); + + // write a packet full of data + string = sv_client->configstrings; + for( i = 0; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) { + if( !string[0] ) { + continue; + } + length = strlen( string ); + if( length > MAX_QPATH ) { + length = MAX_QPATH; + } + + // check if this configstring will overflow + if( svs.z.avail_out < length + 32 ) { + // then flush compressed data + if( z_flush( buffer ) != Z_STREAM_END ) { + goto fail; + } + z_reset( buffer ); + } + + MSG_WriteByte( svc_configstring ); + MSG_WriteShort( i ); + MSG_WriteData( string, length ); + MSG_WriteByte( 0 ); + + svs.z.next_in = msg_write.data; + svs.z.avail_in = ( uInt )msg_write.cursize; + SZ_Clear( &msg_write ); + + if( deflate( &svs.z, Z_SYNC_FLUSH ) != Z_OK ) { + goto fail; + } + } + + // finally flush all remaining compressed data + if( z_flush( buffer ) != Z_STREAM_END ) { +fail: + SV_DropClient( sv_client, "deflate() failed on configstrings" ); + } +} + +#endif // USE_ZLIB + +static void stuff_cmds( list_t *list ) { + stuffcmd_t *stuff; + + LIST_FOR_EACH( stuffcmd_t, stuff, list, entry ) { + MSG_WriteByte( svc_stufftext ); + MSG_WriteData( stuff->string, stuff->len ); + MSG_WriteByte( '\n' ); + MSG_WriteByte( 0 ); + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + } +} + +static void stuff_junk( void ) { + static const char junkchars[] = + "!~#``&'()*`+,-./~01~2`3`4~5`67`89:~<=`>?@~ab~c" + "d`ef~j~k~lm`no~pq`rst`uv`w``x`yz[`\\]^_`|~"; + char junk[8][16]; + int i, j, k; + + for( i = 0; i < 8; i++ ) { + for( j = 0; j < 15; j++ ) { + k = rand_byte() % ( sizeof( junkchars ) - 1 ); + junk[i][j] = junkchars[k]; + } + junk[i][15] = 0; + } + + strcpy( sv_client->reconnect_var, junk[2] ); + strcpy( sv_client->reconnect_val, junk[3] ); + + SV_ClientCommand( sv_client, "set %s set\n", junk[0] ); + SV_ClientCommand( sv_client, "$%s %s connect\n", junk[0], junk[1] ); + if( rand_byte() & 1 ) { + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[2], junk[3] ); + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[4], + sv_force_reconnect->string ); + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[5], junk[6] ); + } else { + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[4], + sv_force_reconnect->string ); + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[5], junk[6] ); + SV_ClientCommand( sv_client, "$%s %s %s\n", junk[0], junk[2], junk[3] ); + } + SV_ClientCommand( sv_client, "$%s %s \"\"\n", junk[0], junk[0] ); + SV_ClientCommand( sv_client, "$%s $%s\n", junk[1], junk[4] ); +} + +/* +================ +SV_New_f + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +void SV_New_f( void ) { + clstate_t oldstate; + + Com_DPrintf( "New() from %s\n", sv_client->name ); + + oldstate = sv_client->state; + if( sv_client->state < cs_connected ) { + Com_DPrintf( "Going from cs_assigned to cs_connected for %s\n", + sv_client->name ); + sv_client->state = cs_connected; + sv_client->lastmessage = svs.realtime; // don't timeout + time( &sv_client->connect_time ); + } else if( sv_client->state > cs_connected ) { + Com_DPrintf( "New not valid -- already primed\n" ); + return; + } + + // stuff some junk, drop them and expect them to be back soon + if( sv_force_reconnect->string[0] && !sv_client->reconnect_var[0] && + !NET_IsLocalAddress( &sv_client->netchan->remote_address ) ) + { + stuff_junk(); + SV_DropClient( sv_client, NULL ); + return; + } + + SV_ClientCommand( sv_client, "\n" ); + + // + // serverdata needs to go over for all types of servers + // to make sure the protocol is right, and to set the gamedir + // + + // create baselines for this client + create_baselines(); + + // send the serverdata + MSG_WriteByte( svc_serverdata ); + MSG_WriteLong( sv_client->protocol ); + MSG_WriteLong( sv_client->spawncount ); + MSG_WriteByte( 0 ); // no attract loop + MSG_WriteString( sv_client->gamedir ); + MSG_WriteShort( sv_client->slot ); + MSG_WriteString( &sv_client->configstrings[CS_NAME*MAX_QPATH] ); + + // send protocol specific stuff + switch( sv_client->protocol ) { + case PROTOCOL_VERSION_R1Q2: + MSG_WriteByte( 0 ); // not enhanced + MSG_WriteShort( sv_client->version ); + MSG_WriteByte( 0 ); // no advanced deltas + MSG_WriteByte( sv_client->pmp.strafehack ); + break; + case PROTOCOL_VERSION_Q2PRO: + MSG_WriteShort( sv_client->version ); + MSG_WriteByte( 2 ); // used to be GT_DEATHMATCH + MSG_WriteByte( sv_client->pmp.strafehack ); + MSG_WriteByte( sv_client->pmp.qwmode ); + if( sv_client->version >= PROTOCOL_VERSION_Q2PRO_WATERJUMP_HACK ) { + MSG_WriteByte( sv_client->pmp.waterhack ); + } + break; + default: + break; + } + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + + SV_ClientCommand( sv_client, "\n" ); + + // send version string request + if( oldstate == cs_assigned ) { + SV_ClientCommand( sv_client, "cmd \177c version $version\n" +#if USE_AC_SERVER + "cmd \177c actoken $actoken\n" +#endif + ); + stuff_cmds( &sv_cmdlist_connect ); + } + + // send reconnect var request + if( sv_force_reconnect->string[0] && !sv_client->reconnected ) { + SV_ClientCommand( sv_client, "cmd \177c connect $%s\n", + sv_client->reconnect_var ); + } + + Com_DPrintf( "Going from cs_connected to cs_primed for %s\n", + sv_client->name ); + sv_client->state = cs_primed; + + memset( &sv_client->lastcmd, 0, sizeof( sv_client->lastcmd ) ); + +#if USE_ZLIB + if( sv_client->has_zlib ) { + if( sv_client->netchan->type == NETCHAN_NEW ) { + write_compressed_gamestate(); + } else { + // FIXME: Z_SYNC_FLUSH is not efficient for baselines + write_compressed_configstrings(); + write_plain_baselines(); + } + } else +#endif // USE_ZLIB + { + write_plain_configstrings(); + write_plain_baselines(); + } + + // send next command + SV_ClientCommand( sv_client, "precache %i\n", sv_client->spawncount ); +} + +/* +================== +SV_Begin_f +================== +*/ +void SV_Begin_f( void ) { + Com_DPrintf( "Begin() from %s\n", sv_client->name ); + + // handle the case of a level changing while a client was connecting + if( sv_client->state < cs_primed ) { + Com_DPrintf( "Begin not valid -- not yet primed\n" ); + SV_New_f(); + return; + } + if( sv_client->state > cs_primed ) { + Com_DPrintf( "Begin not valid -- already spawned\n" ); + return; + } + + if( sv_force_reconnect->string[0] && !sv_client->reconnected ) { + if( Com_IsDedicated() ) { + Com_Printf( "%s[%s]: failed to reconnect\n", sv_client->name, + NET_AdrToString( &sv_client->netchan->remote_address ) ); + } + SV_DropClient( sv_client, NULL ); + return; + } + +#if USE_AC_SERVER + if( !AC_ClientBegin( sv_client ) ) { + return; + } +#endif + + Com_DPrintf( "Going from cs_primed to cs_spawned for %s\n", + sv_client->name ); + sv_client->state = cs_spawned; + sv_client->send_delta = 0; + sv_client->commandMsec = 1800; + sv_client->surpressCount = 0; + sv_client->http_download = qfalse; + + stuff_cmds( &sv_cmdlist_begin ); + + // call the game begin function + ge->ClientBegin( sv_player ); + +#if USE_AC_SERVER + AC_ClientAnnounce( sv_client ); +#endif +} + +//============================================================================= + +#define MAX_DOWNLOAD_CHUNK 1024 + +void SV_CloseDownload( client_t *client ) { + if( client->download ) { + Z_Free( client->download ); + client->download = NULL; + } + if( client->downloadname ) { + Z_Free( client->downloadname ); + client->downloadname = NULL; + } + client->downloadsize = 0; + client->downloadcount = 0; +} + +/* +================== +SV_NextDownload_f +================== +*/ +static void SV_NextDownload_f( void ) { + int r; + int percent; + int size; + + if ( !sv_client->download ) + return; + + r = sv_client->downloadsize - sv_client->downloadcount; + if ( r > MAX_DOWNLOAD_CHUNK ) + r = MAX_DOWNLOAD_CHUNK; + + MSG_WriteByte( svc_download ); + MSG_WriteShort( r ); + + sv_client->downloadcount += r; + size = sv_client->downloadsize; + if( !size ) + size = 1; + percent = sv_client->downloadcount*100/size; + MSG_WriteByte( percent ); + MSG_WriteData( sv_client->download + sv_client->downloadcount - r, r ); + + if( sv_client->downloadcount == sv_client->downloadsize ) { + SV_CloseDownload( sv_client ); + } + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + +} + +/* +================== +SV_BeginDownload_f +================== +*/ +static void SV_BeginDownload_f( void ) { + char name[MAX_QPATH]; + byte *download; + ssize_t downloadsize, maxdownloadsize, result; + int offset = 0; + cvar_t *allow; + size_t len; + unsigned flags; + qhandle_t f; + + len = Cmd_ArgvBuffer( 1, name, sizeof( name ) ); + if( len >= MAX_QPATH ) { + goto fail1; + } + + // hack for 'status' command + if( !strcmp( name, "http" ) ) { + sv_client->http_download = qtrue; + return; + } + + len = COM_strclr( name ); + Q_strlwr( name ); + + if( Cmd_Argc() > 2 ) + offset = atoi( Cmd_Argv( 2 ) ); // downloaded offset + + // hacked by zoid to allow more conrol over download + // first off, no .. or global allow check + if( !allow_download->integer + // check for empty paths + || !len + // check for illegal negative offsets + || offset < 0 + // don't allow anything with .. path + || strstr( name, ".." ) + // leading dots, slashes, etc are no good + || !Q_ispath( name[0] ) + // trailing dots, slashes, etc are no good + || !Q_ispath( name[ len - 1 ] ) + // back slashes should be never sent + || strchr( name, '\\' ) + // colons are bad also + || strchr( name, ':' ) + // MUST be in a subdirectory + || !strchr( name, '/' ) ) + { + goto fail1; + } + + if( strncmp( name, "players/", 8 ) == 0 ) { + allow = allow_download_players; + } else if( strncmp( name, "models/", 7 ) == 0 || + strncmp( name, "sprites/", 8 ) == 0 ) + { + allow = allow_download_models; + } else if( strncmp( name, "sound/", 6 ) == 0 ) { + allow = allow_download_sounds; + } else if( strncmp( name, "maps/", 5 ) == 0 ) { + allow = allow_download_maps; + } else if( strncmp( name, "textures/", 9 ) == 0 || + strncmp( name, "env/", 4 ) == 0 ) + { + allow = allow_download_textures; + } else if( strncmp( name, "pics/", 5 ) == 0 ) { + allow = allow_download_pics; + } else { + allow = allow_download_others; + } + + if( !allow->integer ) { + Com_DPrintf( "Refusing download of %s to %s\n", name, sv_client->name ); + goto fail1; + } + + if( sv_client->download ) { + Com_DPrintf( "Closing existing download for %s (should not happen)\n", sv_client->name ); + SV_CloseDownload( sv_client ); + } + + flags = FS_MODE_READ; + + // special check for maps, if it came from a pak file, don't allow + // download ZOID + if( allow == allow_download_maps && allow->integer < 2 ) { + flags |= FS_TYPE_REAL; + } + + downloadsize = FS_FOpenFile( name, &f, flags ); + if( !f ) { + Com_DPrintf( "Couldn't download %s to %s\n", name, sv_client->name ); + goto fail1; + } + + maxdownloadsize = MAX_LOADFILE; +// if( sv_max_download_size->integer ) { +// maxdownloadsize = Cvar_ClampInteger( sv_max_download_size, 1, MAX_LOADFILE ); +// } + + if( downloadsize > maxdownloadsize ) { + Com_DPrintf( "Refusing oversize download of %s to %s\n", name, sv_client->name ); + goto fail2; + } + + if( offset > downloadsize ) { + Com_DPrintf( "Refusing download, %s has wrong version of %s (%d > %d)\n", + sv_client->name, name, offset, (int)downloadsize ); + SV_ClientPrintf( sv_client, PRINT_HIGH, "File size differs from server.\n" + "Please delete the corresponding .tmp file from your system.\n" ); + goto fail2; + } + + if( offset == downloadsize ) { + Com_DPrintf( "Refusing download, %s already has %s (%d bytes)\n", + sv_client->name, name, offset ); + FS_FCloseFile( f ); + MSG_WriteByte( svc_download ); + MSG_WriteShort( 0 ); + MSG_WriteByte( 100 ); + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + return; + } + + download = SV_Malloc( downloadsize ); + result = FS_Read( download, downloadsize, f ); + if( result != downloadsize ) { + Com_DPrintf( "Couldn't download %s to %s\n", name, sv_client->name ); + goto fail3; + } + + FS_FCloseFile( f ); + + sv_client->download = download; + sv_client->downloadsize = downloadsize; + sv_client->downloadcount = offset; + sv_client->downloadname = SV_CopyString( name ); + + Com_DPrintf( "Downloading %s to %s\n", name, sv_client->name ); + + SV_NextDownload_f(); + return; + +fail3: + Z_Free( download ); +fail2: + FS_FCloseFile( f ); +fail1: + MSG_WriteByte( svc_download ); + MSG_WriteShort( -1 ); + MSG_WriteByte( 0 ); + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); +} + +static void SV_StopDownload_f( void ) { + int size, percent; + + if( !sv_client->download ) { + return; + } + + size = sv_client->downloadsize; + if( !size ) { + percent = 0; + } else { + percent = sv_client->downloadcount*100/size; + } + + MSG_WriteByte( svc_download ); + MSG_WriteShort( -1 ); + MSG_WriteByte( percent ); + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); + + Com_DPrintf( "Download of %s to %s stopped by user request\n", + sv_client->downloadname, sv_client->name ); + SV_CloseDownload( sv_client ); +} + +//============================================================================ + + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately +================= +*/ +static void SV_Disconnect_f( void ) { + if( Com_IsDedicated() && sv_client->netchan ) { + Com_Printf( "%s[%s] disconnected\n", sv_client->name, + NET_AdrToString( &sv_client->netchan->remote_address ) ); + } + SV_DropClient( sv_client, NULL ); + SV_RemoveClient( sv_client ); // don't bother with zombie state +} + + +/* +================== +SV_ShowServerinfo_f + +Dumps the serverinfo info string +================== +*/ +static void SV_ShowServerinfo_f( void ) { + char serverinfo[MAX_INFO_STRING]; + + Cvar_BitInfo( serverinfo, CVAR_SERVERINFO ); + + SV_BeginRedirect( RD_CLIENT ); + Info_Print( serverinfo ); + Com_EndRedirect(); +} + +static void SV_NoGameData_f( void ) { + sv_client->nodata ^= 1; +} + +static void SV_Lag_f( void ) { + client_t *cl; + + if( Cmd_Argc() > 1 ) { + SV_BeginRedirect( RD_CLIENT ); + cl = SV_GetPlayer( Cmd_Argv( 1 ), qtrue ); + Com_EndRedirect(); + if( !cl ) { + return; + } + } else { + cl = sv_client; + } + + SV_ClientPrintf( sv_client, PRINT_HIGH, + "Lag stats for: %s\n" + "RTT (min/avg/max): %d/%d/%d ms\n" + "Server to client PL: %.2f%% (approx)\n" + "Client to server PL: %.2f%%\n", + cl->name, cl->min_ping, AVG_PING( cl ), cl->max_ping, + PL_S2C( cl ), PL_C2S( cl ) ); +} + +#if USE_PACKETDUP +static void SV_PacketdupHack_f( void ) { + int numdups = sv_client->numpackets - 1; + + if( Cmd_Argc() > 1 ) { + numdups = atoi( Cmd_Argv( 1 ) ); + if( numdups < 0 || numdups > sv_packetdup_hack->integer ) { + SV_ClientPrintf( sv_client, PRINT_HIGH, + "Packetdup of %d is not allowed on this server.\n", numdups ); + return; + } + + sv_client->numpackets = numdups + 1; + } + + SV_ClientPrintf( sv_client, PRINT_HIGH, + "Server is sending %d duplicate packet%s to you.\n", + numdups, numdups == 1 ? "" : "s" ); + if( numdups > 1 ) { + SV_ClientPrintf( sv_client, PRINT_MEDIUM, "Poor, poor server...\n" ); + } +} +#endif + +static void SV_CvarResult_f( void ) { + char *c, *v; + + c = Cmd_Argv( 1 ); + if( !strcmp( c, "version" ) ) { + if( !sv_client->versionString ) { + v = Cmd_RawArgsFrom( 2 ); + if( Com_IsDedicated() ) { + Com_Printf( "%s[%s]: %s\n", sv_client->name, + NET_AdrToString( &sv_client->netchan->remote_address ), v ); + } + sv_client->versionString = SV_CopyString( v ); + } + } else if( !strcmp( c, "connect" ) ) { + if( sv_client->reconnect_var[0] ) { + v = Cmd_Argv( 2 ); + if( !strcmp( v, sv_client->reconnect_val ) ) { + sv_client->reconnected = qtrue; + } + } + } +#if USE_AC_SERVER + else if( !strcmp( c, "actoken" ) ) { + AC_ClientToken( sv_client, Cmd_Argv( 2 ) ); + } +#endif +} + +#if USE_AC_SERVER + +static void SV_AC_List_f( void ) { + SV_BeginRedirect( RD_CLIENT ); + AC_List_f(); + Com_EndRedirect(); +} + +static void SV_AC_Info_f( void ) { + SV_BeginRedirect( RD_CLIENT ); + AC_Info_f(); + Com_EndRedirect(); +} + +#else + +static void SV_AC_Null_f( void ) { + SV_ClientPrintf( sv_client, PRINT_HIGH, + "This server does not support anticheat.\n" ); +} + +#endif + +static const ucmd_t ucmds[] = { + // auto issued + { "new", SV_New_f }, + { "begin", SV_Begin_f }, + { "baselines", NULL }, + { "configstrings", NULL }, + { "nextserver", NULL }, + { "disconnect", SV_Disconnect_f }, + + // issued by hand at client consoles + { "info", SV_ShowServerinfo_f }, + + { "download", SV_BeginDownload_f }, + { "nextdl", SV_NextDownload_f }, + { "stopdl", SV_StopDownload_f }, + + { "\177c", SV_CvarResult_f }, + { "nogamedata", SV_NoGameData_f }, + { "lag", SV_Lag_f }, +#if USE_PACKETDUP + { "packetdup", SV_PacketdupHack_f }, +#endif +#if USE_AC_SERVER + { "aclist", SV_AC_List_f }, + { "acinfo", SV_AC_Info_f }, +#else + { "aclist", SV_AC_Null_f }, + { "acinfo", SV_AC_Null_f }, +#endif + + { NULL, NULL } +}; + +static void handle_filtercmd( filtercmd_t *filter ) { + size_t len; + + switch( filter->action ) { + case FA_PRINT: + MSG_WriteByte( svc_print ); + MSG_WriteByte( PRINT_HIGH ); + break; + case FA_STUFF: + MSG_WriteByte( svc_stufftext ); + break; + case FA_KICK: + SV_DropClient( sv_client, filter->comment[0] ? + filter->comment : "issued banned command" ); + // fall through + default: + return; + } + + len = strlen( filter->comment ); + MSG_WriteData( filter->comment, len ); + MSG_WriteByte( '\n' ); + MSG_WriteByte( 0 ); + + SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR ); +} + +/* +================== +SV_ExecuteUserCommand +================== +*/ +static void SV_ExecuteUserCommand( const char *s ) { + const ucmd_t *u; + filtercmd_t *filter; + char *c; + + Cmd_TokenizeString( s, qfalse ); + sv_player = sv_client->edict; + + c = Cmd_Argv( 0 ); + if( !c[0] ) { + return; + } + + if( ( u = Com_Find( ucmds, c ) ) != NULL ) { + if( u->func ) { + u->func(); + } + return; + } + if( sv.state < ss_game ) { + return; + } + LIST_FOR_EACH( filtercmd_t, filter, &sv_filterlist, entry ) { + if( !Q_stricmp( filter->string, c ) ) { + handle_filtercmd( filter ); + return; + } + } + ge->ClientCommand( sv_player ); +} + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +================== +SV_ClientThink +================== +*/ +static inline void SV_ClientThink( usercmd_t *cmd ) { + sv_client->commandMsec -= cmd->msec; + sv_client->numMoves++; + + if( sv_client->commandMsec < 0 && sv_enforcetime->integer ) { +#ifdef _DEBUG + Com_DPrintf( "commandMsec underflow from %s: %d\n", + sv_client->name, sv_client->commandMsec ); +#endif + return; + } + + ge->ClientThink( sv_player, cmd ); +} + +static inline void SV_SetLastFrame( int lastframe ) { + unsigned sentTime; + + if( lastframe > 0 ) { + if( lastframe > sv.framenum ) { + return; // ignore bogus acks + } + if( lastframe <= sv_client->lastframe ) { + return; // ignore duplicate acks + } + + sentTime = sv_client->frames[lastframe & UPDATE_MASK].sentTime; + if( sentTime <= com_eventTime ) { + sv_client->frame_latency[lastframe & LATENCY_MASK] = com_eventTime - sentTime; + } + + if( sv_client->state == cs_spawned ) { + sv_client->frames_acked++; + } + } + + sv_client->lastframe = lastframe; +} + +/* +================== +SV_OldClientExecuteMove +================== +*/ +static void SV_OldClientExecuteMove( int net_drop ) { + usercmd_t oldest, oldcmd, newcmd; + int lastframe; + + if( sv_client->protocol == PROTOCOL_VERSION_DEFAULT ) { + MSG_ReadByte(); // skip over checksum + } + + lastframe = MSG_ReadLong(); + SV_SetLastFrame( lastframe ); + + if( sv_client->protocol == PROTOCOL_VERSION_R1Q2 && + sv_client->version >= PROTOCOL_VERSION_R1Q2_UCMD ) + { + MSG_ReadDeltaUsercmd_Hacked( NULL, &oldest ); + MSG_ReadDeltaUsercmd_Hacked( &oldest, &oldcmd ); + MSG_ReadDeltaUsercmd_Hacked( &oldcmd, &newcmd ); + } else { + MSG_ReadDeltaUsercmd( NULL, &oldest ); + MSG_ReadDeltaUsercmd( &oldest, &oldcmd ); + MSG_ReadDeltaUsercmd( &oldcmd, &newcmd ); + } + + if( sv_client->state != cs_spawned ) { + sv_client->lastframe = -1; + return; + } + + if( net_drop > 2 ) { + sv_client->frameflags |= FF_CLIENTPRED; + } + + if( net_drop < 20 ) { + while( net_drop > 2 ) { + SV_ClientThink( &sv_client->lastcmd ); + net_drop--; + } + if( net_drop > 1 ) + SV_ClientThink( &oldest ); + + if( net_drop > 0 ) + SV_ClientThink( &oldcmd ); + + } + SV_ClientThink( &newcmd ); + + sv_client->lastcmd = newcmd; +} + + + +/* +================== +SV_NewClientExecuteMove +================== +*/ +static void SV_NewClientExecuteMove( int c, int net_drop ) { + usercmd_t cmds[MAX_PACKET_FRAMES][MAX_PACKET_USERCMDS]; + usercmd_t *lastcmd, *cmd; + int lastframe; + int numCmds[MAX_PACKET_FRAMES], numDups; + int i, j, lightlevel; + + numDups = c >> SVCMD_BITS; + c &= SVCMD_MASK; + + if( numDups >= MAX_PACKET_FRAMES ) { + SV_DropClient( sv_client, "too many frames in packet" ); + return; + } + + if( c == clc_move_nodelta ) { + lastframe = -1; + } else { + lastframe = MSG_ReadLong(); + } + + SV_SetLastFrame( lastframe ); + + lightlevel = MSG_ReadByte(); + + // read all cmds + lastcmd = NULL; + for( i = 0; i <= numDups; i++ ) { + numCmds[i] = MSG_ReadBits( 5 ); + if( numCmds[i] == -1 ) { + SV_DropClient( sv_client, "read past end of message" ); + return; + } + if( numCmds[i] >= MAX_PACKET_USERCMDS ) { + SV_DropClient( sv_client, "too many usercmds in frame" ); + return; + } + for( j = 0; j < numCmds[i]; j++ ) { + if( msg_read.readcount > msg_read.cursize ) { + SV_DropClient( sv_client, "read past end of message" ); + return; + } + cmd = &cmds[i][j]; + MSG_ReadDeltaUsercmd_Enhanced( lastcmd, cmd, sv_client->version ); + cmd->lightlevel = lightlevel; + lastcmd = cmd; + } + } + if( sv_client->state != cs_spawned ) { + sv_client->lastframe = -1; + return; + } + + if( q_unlikely( !lastcmd ) ) { + return; // should never happen + } + + if( net_drop > numDups ) { + sv_client->frameflags |= FF_CLIENTPRED; + } + + if( net_drop < 20 ) { + // run lastcmd multiple times if no backups available + while( net_drop > numDups ) { + SV_ClientThink( &sv_client->lastcmd ); + net_drop--; + } + + // run backup cmds, if any + while( net_drop > 0 ) { + i = numDups - net_drop; + for( j = 0; j < numCmds[i]; j++ ) { + SV_ClientThink( &cmds[i][j] ); + } + net_drop--; + } + + } + + // run new cmds + for( j = 0; j < numCmds[numDups]; j++ ) { + SV_ClientThink( &cmds[numDups][j] ); + } + + sv_client->lastcmd = *lastcmd; +} + +/* +=================== +SV_ExecuteClientMessage + +The current net_message is parsed for the given client +=================== +*/ +void SV_ExecuteClientMessage( client_t *client ) { + int c; + qboolean move_issued; + int stringCmdCount; + int userinfoUpdateCount; + int net_drop; + size_t len; + + sv_client = client; + sv_player = sv_client->edict; + + // only allow one move command + move_issued = qfalse; + stringCmdCount = 0; + userinfoUpdateCount = 0; + + net_drop = client->netchan->dropped; + if( net_drop > 0 ) { + client->frameflags |= FF_CLIENTDROP; + } + + while( 1 ) { + if( msg_read.readcount > msg_read.cursize ) { + SV_DropClient( client, "read past end of message" ); + break; + } + + c = MSG_ReadByte(); + if( c == -1 ) + break; + + switch( c & SVCMD_MASK ) { + default: + badbyte: + SV_DropClient( client, "unknown command byte" ); + break; + + case clc_nop: + break; + + case clc_userinfo: { + char buffer[MAX_INFO_STRING]; + + len = MSG_ReadString( buffer, sizeof( buffer ) ); + if( len >= sizeof( buffer ) ) { + SV_DropClient( client, "oversize userinfo" ); + break; + } + + // malicious users may try sending too many userinfo updates + if( userinfoUpdateCount == MAX_PACKET_USERINFOS ) { + Com_DPrintf( "Too many userinfos from %s\n", client->name ); + break; + } + + SV_UpdateUserinfo( buffer ); + userinfoUpdateCount++; + } + break; + + case clc_move: + if( move_issued ) { + SV_DropClient( client, "multiple clc_move commands in packet" ); + break; // someone is trying to cheat... + } + + move_issued = qtrue; + + SV_OldClientExecuteMove( net_drop ); + break; + + case clc_stringcmd: { + char buffer[MAX_STRING_CHARS]; + + len = MSG_ReadString( buffer, sizeof( buffer ) ); + if( len >= sizeof( buffer ) ) { + SV_DropClient( client, "oversize stringcmd" ); + break; + } + + Com_DPrintf( "ClientCommand( %s ): %s\n", + client->name, buffer ); + + if( !NET_IsLocalAddress( &client->netchan->remote_address ) ) { + // malicious users may try using too many string commands + if( stringCmdCount == MAX_PACKET_STRINGCMDS ) { + Com_DPrintf( "Too many stringcmds from %s\n", client->name ); + break; + } + } + SV_ExecuteUserCommand( buffer ); + stringCmdCount++; + } + break; + + // r1q2 specific operations + case clc_setting: { + uint16_t idx, value; + + if( client->protocol < PROTOCOL_VERSION_R1Q2 ) { + goto badbyte; + } + + idx = MSG_ReadShort(); + value = MSG_ReadShort(); + if( idx < CLS_MAX ) { + client->settings[idx] = value; + } + } + break; + + + // q2pro specific operations + case clc_move_nodelta: + case clc_move_batched: + if( client->protocol != PROTOCOL_VERSION_Q2PRO ) { + goto badbyte; + } + + if( move_issued ) { + SV_DropClient( client, "multiple clc_move commands in packet" ); + break; // someone is trying to cheat... + } + + move_issued = qtrue; + SV_NewClientExecuteMove( c, net_drop ); + break; + + case clc_userinfo_delta: { + char key[MAX_INFO_KEY], value[MAX_INFO_VALUE]; + char buffer[MAX_INFO_STRING]; + + if( client->protocol != PROTOCOL_VERSION_Q2PRO ) { + goto badbyte; + } + + len = MSG_ReadString( key, sizeof( key ) ); + if( len >= sizeof( key ) ) { + SV_DropClient( client, "oversize delta key" ); + break; + } + + len = MSG_ReadString( value, sizeof( value ) ); + if( len >= sizeof( value ) ) { + SV_DropClient( client, "oversize delta value" ); + break; + } + + // malicious users may try sending too many userinfo updates + if( userinfoUpdateCount == MAX_PACKET_USERINFOS ) { + Com_DPrintf( "Too many userinfos from %s\n", client->name ); + break; + } + userinfoUpdateCount++; + + strcpy( buffer, client->userinfo ); + if( !Info_SetValueForKey( buffer, key, value ) ) { + SV_DropClient( client, "malformed delta userinfo" ); + break; + } + + SV_UpdateUserinfo( buffer ); + } + break; + } + + if( client->state < cs_assigned ) { + break; // disconnect command + } + } + + sv_client = NULL; + sv_player = NULL; +} + |