/* * 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 ); } } }
void SV_EndClientSnapshot(client_t *client, msg_t *msg) { if ( client->state != CS_ZOMBIE ) SV_WriteDownloadToClient(client, msg); MSG_WriteByte(msg, svc_EOF); if ( msg->overflowed == qtrue) { Com_PrintWarning( "WARNING: msg overflowed for %s, trying to recover\n", client->shortname); if ( client->state == CS_ACTIVE || client->state == CS_ZOMBIE ) { SV_ShowClientUnAckCommands(client); MSG_Clear( msg ); MSG_WriteLong(msg, client->lastClientCommand); SV_UpdateServerCommandsToClientRecover( client, msg ); MSG_WriteByte(msg, svc_EOF); } if ( msg->overflowed == qtrue) { Com_PrintWarning("WARNING: client disconnected for msg overflow: %s\n", client->shortname); NET_OutOfBandPrint(NS_SERVER, &client->netchan.remoteAddress, "disconnect"); SV_DropClient(client, "EXE_SERVERMESSAGEOVERFLOW"); } } SV_SendMessageToClient(msg, client); }
/* ======================= SV_SendClientSnapshot ======================= */ void SV_SendClientSnapshot( client_t *client ) { byte msg_buf[MAX_MSGLEN]; msg_t msg; // build the snapshot SV_BuildClientSnapshot( client ); // bots need to have their snapshots build, but // the query them directly without needing to be sent if ( client->gentity && client->gentity->svFlags & SVF_BOT ) { return; } MSG_Init (&msg, msg_buf, sizeof(msg_buf)); msg.allowoverflow = qtrue; // (re)send any reliable server commands SV_UpdateServerCommandsToClient( client, &msg ); // send over all the relevant entityState_t // and the playerState_t SV_WriteSnapshotToClient( client, &msg ); // check for overflow if ( msg.overflowed ) { Com_Printf ("WARNING: msg overflowed for %s\n", client->name); MSG_Clear (&msg); } SV_SendMessageToClient( &msg, client ); }
/* ======================= SV_SendClientEmptyMessage This is just an empty message so that we can tell if the client dropped the gamestate that went out before ======================= */ void SV_SendClientEmptyMessage( client_t *client ) { msg_t msg; byte buffer[10]; MSG_Init( &msg, buffer, sizeof( buffer ) ); SV_SendMessageToClient( &msg, client ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { msg_t msg; byte msgBuffer[MAX_MSGLEN]; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); if ( client->state == CS_CONNECTED ) client->state = CS_PRIMED; client->pureAuthentic = 0; client->gotCP = qfalse; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; SV_CreateClientGameStateMessage( client, &msg ); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
int SV_SendDownloadMessages(void) { int i, numDLs = 0; client_t *cl; msg_t msg; byte msgBuffer[MAX_MSGLEN]; for(i=0; i < sv_maxclients->integer; i++) { cl = &svs.clients[i]; if(cl->state && *cl->downloadName) { int basesize; MSG_Init(&msg, msgBuffer, sizeof(msgBuffer)); MSG_WriteLong(&msg, cl->lastClientCommand); basesize = msg.cursize; SV_WriteDownloadToClient(cl, &msg); if (msg.cursize != basesize) { SV_SendMessageToClient(&msg, cl); numDLs++; } } } return numDLs; }
/* * 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_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; Com_DPrintf ("SV_SendGameState() for %s\n", client->name); client->state = CS_PRIMED; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; // clear the reliable message list for this client client->reliableSequence = 0; client->reliableAcknowledge = 0; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteString( &msg, sv.configstrings[start] ); } } // write the baselines memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, 0 ); // check for overflow if ( msg.overflowed ) { Com_Printf ("WARNING: GameState overflowed for %s\n", client->name); } // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* * 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_SendClientSnapshot Also called by SV_FinalMessage ======================= */ void SV_SendClientSnapshot( client_t* client ) { byte msg_buf[MAX_MSGLEN]; msg_s msg; // build the snapshot SV_BuildClientSnapshot( client ); // bots need to have their snapshots build, but // the query them directly without needing to be sent //if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) { // return; //} MSG_Init( &msg, msg_buf, sizeof( msg_buf ) ); msg.allowoverflow = true; // compression byte is the first byte in all server->client messages msg.oob = true; MSG_WriteByte( &msg, 0 ); msg.oob = false; // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // (re)send any reliable server commands SV_UpdateServerCommandsToClient( client, &msg ); // send over all the relevant entityState_s // and the playerState_s SV_WriteSnapshotToClient( client, &msg ); #ifdef USE_VOIP SV_WriteVoipToClient( client, &msg ); #endif // check for overflow if ( msg.overflowed ) { Com_Printf( "WARNING: msg overflowed for %s\n", client->name ); MSG_Clear( &msg ); } SV_SendMessageToClient( &msg, client ); }
/* * 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 ); }
void SV_SendClientMapChange( client_t *client ) { msg_t msg; byte msgBuffer[MAX_MSGLEN]; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_mapchange ); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ static void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; client->gotCP = qfalse; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); // There's something unknown going on here at the moment. // For now we're just going with default of adding a 0 short to keep jampded compatability. MSG_WriteShort( &msg, 0); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ======================= SV_SendMessageToClient Called by SV_SendClientSnapshot and SV_SendClientGameState ======================= */ void SV_SendMessageToClient( msg_t *msg, client_t *client ) { int rateMsec; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } // record information about the message client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; // send the datagram SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); // set nextSnapshotTime based on rate and requested number of updates // local clients get snapshots every frame if ( client->netchan.remoteAddress.type == NA_LOOPBACK || Sys_IsLANAddress (client->netchan.remoteAddress) ) { client->nextSnapshotTime = svs.time - 1; return; } // normal rate / snapshotMsec calculation rateMsec = SV_RateMsec( client, msg->cursize ); if ( rateMsec < client->snapshotMsec ) { // never send more packets than this, no matter what the rate is at rateMsec = client->snapshotMsec; client->rateDelayed = qfalse; } else { client->rateDelayed = qtrue; } client->nextSnapshotTime = svs.time + rateMsec; // don't pile up empty snapshots while connecting if ( client->state != CS_ACTIVE ) { // a gigantic connection message may have already put the nextSnapshotTime // more than a second away, so don't shorten it // do shorten if client is downloading #ifdef _XBOX // No downloads on Xbox if ( client->nextSnapshotTime < svs.time + 1000 ) { #else if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) { #endif client->nextSnapshotTime = svs.time + 1000; } } } /* ======================= SV_SendClientSnapshot Also called by SV_FinalMessage ======================= */ extern cvar_t *fs_gamedirvar; void SV_SendClientSnapshot( client_t *client ) { byte msg_buf[MAX_MSGLEN]; msg_t msg; if (!client->sentGamedir) { //rww - if this is the case then make sure there is an svc_setgame sent before this snap int i = 0; MSG_Init (&msg, msg_buf, sizeof(msg_buf)); //have to include this for each message. MSG_WriteLong( &msg, client->lastClientCommand ); MSG_WriteByte (&msg, svc_setgame); while (fs_gamedirvar->string[i]) { MSG_WriteByte(&msg, fs_gamedirvar->string[i]); i++; } MSG_WriteByte(&msg, 0); // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. //rww - reusing this code here while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } // record information about the message client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg.cursize; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; // send the datagram SV_Netchan_Transmit( client, &msg ); //msg->cursize, msg->data ); client->sentGamedir = qtrue; } // build the snapshot SV_BuildClientSnapshot( client ); // bots need to have their snapshots build, but // the query them directly without needing to be sent if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) { return; } MSG_Init (&msg, msg_buf, sizeof(msg_buf)); msg.allowoverflow = qtrue; // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // (re)send any reliable server commands SV_UpdateServerCommandsToClient( client, &msg ); // send over all the relevant entityState_t // and the playerState_t SV_WriteSnapshotToClient( client, &msg ); // Add any download data if the client is downloading #ifndef _XBOX // No downloads on Xbox SV_WriteDownloadToClient( client, &msg ); #endif // check for overflow if ( msg.overflowed ) { Com_Printf ("WARNING: msg overflowed for %s\n", client->name); MSG_Clear (&msg); } SV_SendMessageToClient( &msg, client ); }
/* * 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_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[ MAX_MSGLEN ]; Log::Debug( "SV_SendClientGameState() for %s", client->name ); Log::Debug( "Going from CS_CONNECTED to CS_PRIMED for %s", client->name ); client->state = clientState_t::CS_PRIMED; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0; start < MAX_CONFIGSTRINGS; start++ ) { if ( sv.configstrings[ start ][ 0 ] ) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[ start ] ); } } // write the baselines memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[ start ].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, true ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients ); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed ); // NERVE - SMF - debug info Log::Debug( "Sending %i bytes in gamestate to client: %li", msg.cursize, ( long )( client - svs.clients ) ); // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* * 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; }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); //rwwRMG - send info for the terrain if ( TheRandomMissionManager ) { z_stream zdata; // Send the height map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); unsigned char heightmap[15000]; zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Send the flatten map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Seed is needed for misc ents and noise MSG_WriteLong ( &msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); SV_WriteRMGAutomapSymbols ( &msg ); } else { MSG_WriteShort ( &msg, 0 ); } // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].baseline; if ( !base->number ) { continue; } MSG_WriteByte( &msg, svc_baseline ); MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); } MSG_WriteByte( &msg, svc_EOF ); MSG_WriteLong( &msg, client - svs.clients); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); // deliver this to the client SV_SendMessageToClient( &msg, client ); }