/* * SV_FinalMessage * * Used by SV_ShutdownGame 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. */ static void SV_FinalMessage( const char *message, qboolean reconnect ) { int i, j; client_t *cl; for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->edict && ( cl->edict->r.svflags & SVF_FAKECLIENT ) ) continue; if( cl->state >= CS_CONNECTING ) { if( reconnect ) SV_SendServerCommand( cl, "forcereconnect \"%s\"", message ); else SV_SendServerCommand( cl, "disconnect %i \"%s\"", DROP_TYPE_GENERAL, message ); SV_InitClientMessage( cl, &tmpMessage, NULL, 0 ); SV_AddReliableCommandsToMessage( cl, &tmpMessage ); // send it twice for( j = 0; j < 2; j++ ) SV_SendMessageToClient( cl, &tmpMessage ); } } }
/* * SV_DenyDownload * Helper function for generating initdownload packets for denying download */ static void SV_DenyDownload( client_t *client, const char *reason ) { // size -1 is used to signal that it's refused // URL field is used for deny reason SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_SendServerCommand( client, "initdownload \"%s\" %i %u %i \"%s\"", "", -1, 0, qfalse, reason ? reason : "" ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); }
/* * SV_SendClientMessages */ void SV_SendClientMessages( void ) { int i; client_t *client; // send a message to each connected client for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if( client->state == CS_FREE || client->state == CS_ZOMBIE ) continue; if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) ) { client->lastSentFrameNum = sv.framenum; continue; } if( !client->tvclient ) { SV_UpdateActivity(); } if( client->state == CS_SPAWNED ) { if( !SV_SendClientDatagram( client ) ) { Com_Printf( "Error sending message to %s: %s\n", client->name, NET_ErrorString() ); if( client->reliable ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() ); } } } else { // send pending reliable commands, or send heartbeats for not timing out if( client->reliableSequence > client->reliableAcknowledge || svs.realtime - client->lastPacketSentTime > 1000 ) { SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); if( !SV_SendMessageToClient( client, &tmpMessage ) ) { Com_Printf( "Error sending message to %s: %s\n", client->name, NET_ErrorString() ); if( client->reliable ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() ); } } } } } }
/* * SV_Baselines_f */ static void SV_Baselines_f( client_t *client ) { int start; entity_state_t nullstate; entity_state_t *base; Com_DPrintf( "Baselines() from %s\n", client->name ); if( client->state != CS_CONNECTED ) { Com_Printf( "baselines not valid -- already spawned\n" ); return; } // handle the case of a level changing while a client was connecting if( atoi( Cmd_Argv( 1 ) ) != svs.spawncount ) { Com_Printf( "SV_Baselines_f from different level\n" ); SV_New_f( client ); return; } start = atoi( Cmd_Argv( 2 ) ); if( start < 0 ) start = 0; memset( &nullstate, 0, sizeof( nullstate ) ); // write a packet full of data SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); while( tmpMessage.cursize < FRAGMENT_SIZE * 3 && start < MAX_EDICTS ) { base = &sv.baselines[start]; if( base->modelindex || base->sound || base->effects ) { MSG_WriteByte( &tmpMessage, svc_spawnbaseline ); MSG_WriteDeltaEntity( &nullstate, base, &tmpMessage, qtrue, qtrue ); } start++; } // send next command if( start == MAX_EDICTS ) SV_SendServerCommand( client, "precache %i", svs.spawncount ); else SV_SendServerCommand( client, "cmd baselines %i %i", svs.spawncount, start ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); }
/* * SV_SendClientDatagram */ static bool SV_SendClientDatagram( client_t *client ) { if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) ) return true; SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); // send over all the relevant entity_state_t // and the player_state_t SV_BuildClientFrameSnap( client ); SV_WriteFrameSnapToClient( client, &tmpMessage ); return SV_SendMessageToClient( client, &tmpMessage ); }
/* * SV_BeginDownload_f * Responds to reliable download packet with reliable initdownload packet */ static void SV_BeginDownload_f( client_t *client ) { const char *requestname; const char *uploadname; size_t alloc_size; unsigned checksum; char *url; const char *errormsg = NULL; qboolean allow, requestpak; qboolean local_http = SV_Web_Running() && sv_uploads_http->integer != 0; requestpak = ( atoi( Cmd_Argv( 1 ) ) == 1 ); requestname = Cmd_Argv( 2 ); if( !requestname[0] || !COM_ValidateRelativeFilename( requestname ) ) { SV_DenyDownload( client, "Invalid filename" ); return; } if( !SV_FilenameForDownloadRequest( requestname, requestpak, &uploadname, &errormsg ) ) { assert( errormsg != NULL ); SV_DenyDownload( client, errormsg ); return; } if( FS_CheckPakExtension( uploadname ) ) { allow = qfalse; // allow downloading paks from the pure list, if not spawned if( client->state < CS_SPAWNED ) { purelist_t *purefile; purefile = svs.purelist; while( purefile ) { if( !strcmp( uploadname, purefile->filename ) ) { allow = qtrue; break; } purefile = purefile->next; } } // game module has a change to allow extra downloads if( !allow && !SV_GameAllowDownload( client, requestname, uploadname ) ) { SV_DenyDownload( client, "Downloading of this file is not allowed" ); return; } } else { if( !SV_GameAllowDownload( client, requestname, uploadname ) ) { SV_DenyDownload( client, "Downloading of this file is not allowed" ); return; } } // we will just overwrite old download, if any if( client->download.name ) { if( client->download.data ) { FS_FreeBaseFile( client->download.data ); client->download.data = NULL; } Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; } client->download.size = FS_LoadBaseFile( uploadname, NULL, NULL, 0 ); if( client->download.size == -1 ) { Com_Printf( "Error getting size of %s for uploading\n", uploadname ); client->download.size = 0; SV_DenyDownload( client, "Error getting file size" ); return; } checksum = FS_ChecksumBaseFile( uploadname ); client->download.timeout = svs.realtime + 1000 * 60 * 60; // this is web download timeout alloc_size = sizeof( char ) * ( strlen( uploadname ) + 1 ); client->download.name = Mem_ZoneMalloc( alloc_size ); Q_strncpyz( client->download.name, uploadname, alloc_size ); Com_Printf( "Offering %s to %s\n", client->download.name, client->name ); if( FS_CheckPakExtension( uploadname ) && ( local_http || sv_uploads_baseurl->string[0] != 0 ) ) { // .pk3 and .pak download from the web if( local_http ) { url = TempCopyString( va( "files/%s", uploadname ) ); } else { alloc_size = sizeof( char ) * ( strlen( sv_uploads_baseurl->string ) + 1 ); url = Mem_TempMalloc( alloc_size ); Q_snprintfz( url, alloc_size, "%s/", sv_uploads_baseurl->string ); } } else if( SV_IsDemoDownloadRequest( requestname ) && ( local_http || sv_uploads_demos_baseurl->string[0] != 0 ) ) { // demo file download from the web if( local_http ) { url = TempCopyString( va( "files/%s", uploadname ) ); } else { alloc_size = sizeof( char ) * ( strlen( sv_uploads_demos_baseurl->string ) + 1 ); url = Mem_TempMalloc( alloc_size ); Q_snprintfz( url, alloc_size, "%s/", sv_uploads_demos_baseurl->string ); } } else { url = NULL; } // start the download SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_SendServerCommand( client, "initdownload \"%s\" %i %u %i \"%s\"", client->download.name, client->download.size, checksum, local_http ? 1 : 0, ( url ? url : "" ) ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); if( url ) { Mem_TempFree( url ); url = NULL; } }
/* * SV_NextDownload_f * * Responds to reliable nextdl packet with unreliable download packet * If nextdl packet's offet information is negative, download will be stopped */ static void SV_NextDownload_f( client_t *client ) { int blocksize; int offset; if( !client->download.name ) { Com_Printf( "nextdl message for client with no download active, from: %s\n", client->name ); return; } if( Q_stricmp( client->download.name, Cmd_Argv( 1 ) ) ) { Com_Printf( "nextdl message for wrong filename, from: %s\n", client->name ); return; } offset = atoi( Cmd_Argv( 2 ) ); if( offset > client->download.size ) { Com_Printf( "nextdl message with too big offset, from: %s\n", client->name ); return; } if( offset == -1 ) { Com_Printf( "Upload of %s to %s%s completed\n", client->download.name, client->name, S_COLOR_WHITE ); if( client->download.data ) { FS_FreeBaseFile( client->download.data ); client->download.data = NULL; } Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; return; } if( offset < 0 ) { Com_Printf( "Upload of %s to %s%s failed\n", client->download.name, client->name, S_COLOR_WHITE ); if( client->download.data ) { FS_FreeBaseFile( client->download.data ); client->download.data = NULL; } Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; return; } if( !client->download.data ) { Com_Printf( "Starting server upload of %s to %s\n", client->download.name, client->name ); FS_LoadBaseFile( client->download.name, (void **)&client->download.data, NULL, 0 ); if( !client->download.data ) { Com_Printf( "Error loading %s for uploading\n", client->download.name ); Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; return; } } SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); blocksize = client->download.size - offset; // jalfixme: adapt download to user rate setting and sv_maxrate setting. if( blocksize > FRAGMENT_SIZE * 2 ) blocksize = FRAGMENT_SIZE * 2; if( offset + blocksize > client->download.size ) blocksize = client->download.size - offset; MSG_WriteByte( &tmpMessage, svc_download ); MSG_WriteString( &tmpMessage, client->download.name ); MSG_WriteLong( &tmpMessage, offset ); MSG_WriteLong( &tmpMessage, blocksize ); MSG_CopyData( &tmpMessage, client->download.data + offset, blocksize ); SV_SendMessageToClient( client, &tmpMessage ); client->download.timeout = svs.realtime + 10000; }
/* * 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. */ static void SV_New_f( client_t *client ) { int playernum; unsigned int numpure; purelist_t *purefile; edict_t *ent; int sv_bitflags = 0; Com_DPrintf( "New() from %s\n", client->name ); // if in CS_AWAITING we have sent the response packet the new once already, // but client might have not got it so we send it again if( client->state >= CS_SPAWNED ) { Com_Printf( "New not valid -- already spawned\n" ); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); // send the serverdata MSG_WriteByte( &tmpMessage, svc_serverdata ); MSG_WriteLong( &tmpMessage, APP_PROTOCOL_VERSION ); MSG_WriteLong( &tmpMessage, svs.spawncount ); MSG_WriteShort( &tmpMessage, (unsigned short)svc.snapFrameTime ); MSG_WriteString( &tmpMessage, FS_BaseGameDirectory() ); MSG_WriteString( &tmpMessage, FS_GameDirectory() ); playernum = client - svs.clients; MSG_WriteShort( &tmpMessage, playernum ); // send full levelname MSG_WriteString( &tmpMessage, sv.mapname ); // // game server // if( sv.state == ss_game ) { // set up the entity for the client ent = EDICT_NUM( playernum+1 ); ent->s.number = playernum+1; client->edict = ent; if( sv_pure->integer ) sv_bitflags |= SV_BITFLAGS_PURE; if( client->reliable ) sv_bitflags |= SV_BITFLAGS_RELIABLE; if( SV_Web_Running() ) { const char *baseurl = SV_Web_UpstreamBaseUrl(); sv_bitflags |= SV_BITFLAGS_HTTP; if( baseurl[0] ) sv_bitflags |= SV_BITFLAGS_HTTP_BASEURL; } MSG_WriteByte( &tmpMessage, sv_bitflags ); } if( sv_bitflags & SV_BITFLAGS_HTTP ) { if( sv_bitflags & SV_BITFLAGS_HTTP_BASEURL ) MSG_WriteString( &tmpMessage, sv_http_upstream_baseurl->string ); else MSG_WriteShort( &tmpMessage, sv_http_port->integer ); // HTTP port number } // always write purelist numpure = Com_CountPureListFiles( svs.purelist ); if( numpure > (short)0x7fff ) Com_Error( ERR_DROP, "Error: Too many pure files." ); MSG_WriteShort( &tmpMessage, numpure ); purefile = svs.purelist; while( purefile ) { MSG_WriteString( &tmpMessage, purefile->filename ); MSG_WriteLong( &tmpMessage, purefile->checksum ); purefile = purefile->next; } SV_ClientResetCommandBuffers( client ); SV_SendMessageToClient( client, &tmpMessage ); Netchan_PushAllFragments( &client->netchan ); // don't let it send reliable commands until we get the first configstring request client->state = CS_CONNECTING; }
/* * 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, int type, const char *format, ... ) { va_list argptr; char *reason; char string[1024]; if( format ) { va_start( argptr, format ); Q_vsnprintfz( string, sizeof( string ), format, argptr ); va_end( argptr ); reason = string; } else { Q_strncpyz( string, "User disconnected", sizeof( string ) ); reason = NULL; } // remove the rating of the client if( drop->edict ) ge->RemoveRating( drop->edict ); // add the disconnect if( drop->edict && ( drop->edict->r.svflags & SVF_FAKECLIENT ) ) { ge->ClientDisconnect( drop->edict, reason ); SV_ClientResetCommandBuffers( drop ); // make sure everything is clean } else { SV_InitClientMessage( drop, &tmpMessage, NULL, 0 ); SV_SendServerCommand( drop, "disconnect %i \"%s\"", type, string ); SV_AddReliableCommandsToMessage( drop, &tmpMessage ); SV_SendMessageToClient( drop, &tmpMessage ); Netchan_PushAllFragments( &drop->netchan ); if( drop->state >= CS_CONNECTED ) { // call the prog function for removing a client // this will remove the body, among other things ge->ClientDisconnect( drop->edict, reason ); } else if( drop->name[0] ) { Com_Printf( "Connecting client %s%s disconnected (%s%s)\n", drop->name, S_COLOR_WHITE, reason, S_COLOR_WHITE ); } } SV_MM_ClientDisconnect( drop ); SNAP_FreeClientFrames( drop ); if( drop->download.name ) { if( drop->download.data ) { FS_FreeBaseFile( drop->download.data ); drop->download.data = NULL; } Mem_ZoneFree( drop->download.name ); drop->download.name = NULL; drop->download.size = 0; drop->download.timeout = 0; } if( drop->individual_socket ) NET_CloseSocket( &drop->socket ); if( drop->mv ) { sv.num_mv_clients--; drop->mv = qfalse; } drop->tvclient = qfalse; drop->state = CS_ZOMBIE; // become free in a few seconds drop->name[0] = 0; }
/* * SV_NextDownload_f * * Responds to reliable nextdl packet with unreliable download packet * If nextdl packet's offet information is negative, download will be stopped */ static void SV_NextDownload_f( client_t *client ) { int blocksize; int offset; uint8_t data[FRAGMENT_SIZE*2]; if( !client->download.name ) { Com_Printf( "nextdl message for client with no download active, from: %s\n", client->name ); return; } if( Q_stricmp( client->download.name, Cmd_Argv( 1 ) ) ) { Com_Printf( "nextdl message for wrong filename, from: %s\n", client->name ); return; } offset = atoi( Cmd_Argv( 2 ) ); if( offset > client->download.size ) { Com_Printf( "nextdl message with too big offset, from: %s\n", client->name ); return; } if( offset == -1 ) { Com_Printf( "Upload of %s to %s%s completed\n", client->download.name, client->name, S_COLOR_WHITE ); SV_ClientCloseDownload( client ); return; } if( offset < 0 ) { Com_Printf( "Upload of %s to %s%s failed\n", client->download.name, client->name, S_COLOR_WHITE ); SV_ClientCloseDownload( client ); return; } if( !client->download.file ) { Com_Printf( "Starting server upload of %s to %s\n", client->download.name, client->name ); client->download.size = FS_FOpenBaseFile( client->download.name, &client->download.file, FS_READ ); if( !client->download.file || client->download.size < 0 ) { Com_Printf( "Error opening %s for uploading\n", client->download.name ); SV_ClientCloseDownload( client ); return; } } SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); blocksize = client->download.size - offset; // jalfixme: adapt download to user rate setting and sv_maxrate setting. if( blocksize > sizeof( data ) ) blocksize = sizeof( data ); if( offset + blocksize > client->download.size ) blocksize = client->download.size - offset; if( blocksize < 0 ) blocksize = 0; if( blocksize > 0 ) { FS_Seek( client->download.file, offset, FS_SEEK_SET ); blocksize = FS_Read( data, blocksize, client->download.file ); } MSG_WriteByte( &tmpMessage, svc_download ); MSG_WriteString( &tmpMessage, client->download.name ); MSG_WriteLong( &tmpMessage, offset ); MSG_WriteLong( &tmpMessage, blocksize ); if( blocksize > 0 ) MSG_CopyData( &tmpMessage, data, blocksize ); SV_SendMessageToClient( client, &tmpMessage ); client->download.timeout = svs.realtime + 10000; }