938 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			938 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| ===========================================================================
 | |
| Copyright (C) 1999-2005 Id Software, Inc.
 | |
| 
 | |
| This file is part of Quake III Arena source code.
 | |
| 
 | |
| Quake III Arena 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.
 | |
| 
 | |
| Quake III Arena 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.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with Quake III Arena source code; if not, write to the Free Software
 | |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | |
| ===========================================================================
 | |
| */
 | |
| // cl_parse.c  -- parse a message received from the server
 | |
| 
 | |
| #include "client.h"
 | |
| 
 | |
| char *svc_strings[256] = {
 | |
| 	"svc_bad",
 | |
| 
 | |
| 	"svc_nop",
 | |
| 	"svc_gamestate",
 | |
| 	"svc_configstring",
 | |
| 	"svc_baseline",	
 | |
| 	"svc_serverCommand",
 | |
| 	"svc_download",
 | |
| 	"svc_snapshot",
 | |
| 	"svc_EOF",
 | |
| 	"svc_voip",
 | |
| };
 | |
| 
 | |
| void SHOWNET( msg_t *msg, char *s) {
 | |
| 	if ( cl_shownet->integer >= 2) {
 | |
| 		Com_Printf ("%3i:%s\n", msg->readcount-1, s);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| =========================================================================
 | |
| 
 | |
| MESSAGE PARSING
 | |
| 
 | |
| =========================================================================
 | |
| */
 | |
| 
 | |
| /*
 | |
| ==================
 | |
| CL_DeltaEntity
 | |
| 
 | |
| Parses deltas from the given base and adds the resulting entity
 | |
| to the current frame
 | |
| ==================
 | |
| */
 | |
| void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, 
 | |
| 					 qboolean unchanged) {
 | |
| 	entityState_t	*state;
 | |
| 
 | |
| 	// save the parsed entity state into the big circular buffer so
 | |
| 	// it can be used as the source for a later delta
 | |
| 	state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)];
 | |
| 
 | |
| 	if ( unchanged ) {
 | |
| 		*state = *old;
 | |
| 	} else {
 | |
| 		MSG_ReadDeltaEntity( msg, old, state, newnum );
 | |
| 	}
 | |
| 
 | |
| 	if ( state->number == (MAX_GENTITIES-1) ) {
 | |
| 		return;		// entity was delta removed
 | |
| 	}
 | |
| 	cl.parseEntitiesNum++;
 | |
| 	frame->numEntities++;
 | |
| }
 | |
| 
 | |
| /*
 | |
| ==================
 | |
| CL_ParsePacketEntities
 | |
| 
 | |
| ==================
 | |
| */
 | |
| void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) {
 | |
| 	int			newnum;
 | |
| 	entityState_t	*oldstate;
 | |
| 	int			oldindex, oldnum;
 | |
| 
 | |
| 	newframe->parseEntitiesNum = cl.parseEntitiesNum;
 | |
| 	newframe->numEntities = 0;
 | |
| 
 | |
| 	// delta from the entities present in oldframe
 | |
| 	oldindex = 0;
 | |
| 	oldstate = NULL;
 | |
| 	if (!oldframe) {
 | |
| 		oldnum = 99999;
 | |
| 	} else {
 | |
| 		if ( oldindex >= oldframe->numEntities ) {
 | |
| 			oldnum = 99999;
 | |
| 		} else {
 | |
| 			oldstate = &cl.parseEntities[
 | |
| 				(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
 | |
| 			oldnum = oldstate->number;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	while ( 1 ) {
 | |
| 		// read the entity index number
 | |
| 		newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
 | |
| 
 | |
| 		if ( newnum == (MAX_GENTITIES-1) ) {
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if ( msg->readcount > msg->cursize ) {
 | |
| 			Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");
 | |
| 		}
 | |
| 
 | |
| 		while ( oldnum < newnum ) {
 | |
| 			// one or more entities from the old packet are unchanged
 | |
| 			if ( cl_shownet->integer == 3 ) {
 | |
| 				Com_Printf ("%3i:  unchanged: %i\n", msg->readcount, oldnum);
 | |
| 			}
 | |
| 			CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
 | |
| 			
 | |
| 			oldindex++;
 | |
| 
 | |
| 			if ( oldindex >= oldframe->numEntities ) {
 | |
| 				oldnum = 99999;
 | |
| 			} else {
 | |
| 				oldstate = &cl.parseEntities[
 | |
| 					(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
 | |
| 				oldnum = oldstate->number;
 | |
| 			}
 | |
| 		}
 | |
| 		if (oldnum == newnum) {
 | |
| 			// delta from previous state
 | |
| 			if ( cl_shownet->integer == 3 ) {
 | |
| 				Com_Printf ("%3i:  delta: %i\n", msg->readcount, newnum);
 | |
| 			}
 | |
| 			CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse );
 | |
| 
 | |
| 			oldindex++;
 | |
| 
 | |
| 			if ( oldindex >= oldframe->numEntities ) {
 | |
| 				oldnum = 99999;
 | |
| 			} else {
 | |
| 				oldstate = &cl.parseEntities[
 | |
| 					(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
 | |
| 				oldnum = oldstate->number;
 | |
| 			}
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if ( oldnum > newnum ) {
 | |
| 			// delta from baseline
 | |
| 			if ( cl_shownet->integer == 3 ) {
 | |
| 				Com_Printf ("%3i:  baseline: %i\n", msg->readcount, newnum);
 | |
| 			}
 | |
| 			CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse );
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	// any remaining entities in the old frame are copied over
 | |
| 	while ( oldnum != 99999 ) {
 | |
| 		// one or more entities from the old packet are unchanged
 | |
| 		if ( cl_shownet->integer == 3 ) {
 | |
| 			Com_Printf ("%3i:  unchanged: %i\n", msg->readcount, oldnum);
 | |
| 		}
 | |
| 		CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
 | |
| 		
 | |
| 		oldindex++;
 | |
| 
 | |
| 		if ( oldindex >= oldframe->numEntities ) {
 | |
| 			oldnum = 99999;
 | |
| 		} else {
 | |
| 			oldstate = &cl.parseEntities[
 | |
| 				(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
 | |
| 			oldnum = oldstate->number;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| ================
 | |
| CL_ParseSnapshot
 | |
| 
 | |
| If the snapshot is parsed properly, it will be copied to
 | |
| cl.snap and saved in cl.snapshots[].  If the snapshot is invalid
 | |
| for any reason, no changes to the state will be made at all.
 | |
| ================
 | |
| */
 | |
| void CL_ParseSnapshot( msg_t *msg ) {
 | |
| 	int			len;
 | |
| 	clSnapshot_t	*old;
 | |
| 	clSnapshot_t	newSnap;
 | |
| 	int			deltaNum;
 | |
| 	int			oldMessageNum;
 | |
| 	int			i, packetNum;
 | |
| 
 | |
| 	// get the reliable sequence acknowledge number
 | |
| 	// NOTE: now sent with all server to client messages
 | |
| 	//clc.reliableAcknowledge = MSG_ReadLong( msg );
 | |
| 
 | |
| 	// read in the new snapshot to a temporary buffer
 | |
| 	// we will only copy to cl.snap if it is valid
 | |
| 	Com_Memset (&newSnap, 0, sizeof(newSnap));
 | |
| 
 | |
| 	// we will have read any new server commands in this
 | |
| 	// message before we got to svc_snapshot
 | |
| 	newSnap.serverCommandNum = clc.serverCommandSequence;
 | |
| 
 | |
| 	newSnap.serverTime = MSG_ReadLong( msg );
 | |
| 
 | |
| 	// if we were just unpaused, we can only *now* really let the
 | |
| 	// change come into effect or the client hangs.
 | |
| 	cl_paused->modified = 0;
 | |
| 
 | |
| 	newSnap.messageNum = clc.serverMessageSequence;
 | |
| 
 | |
| 	deltaNum = MSG_ReadByte( msg );
 | |
| 	if ( !deltaNum ) {
 | |
| 		newSnap.deltaNum = -1;
 | |
| 	} else {
 | |
| 		newSnap.deltaNum = newSnap.messageNum - deltaNum;
 | |
| 	}
 | |
| 	newSnap.snapFlags = MSG_ReadByte( msg );
 | |
| 
 | |
| 	// If the frame is delta compressed from data that we
 | |
| 	// no longer have available, we must suck up the rest of
 | |
| 	// the frame, but not use it, then ask for a non-compressed
 | |
| 	// message 
 | |
| 	if ( newSnap.deltaNum <= 0 ) {
 | |
| 		newSnap.valid = qtrue;		// uncompressed frame
 | |
| 		old = NULL;
 | |
| 		clc.demowaiting = qfalse;	// we can start recording now
 | |
| 	} else {
 | |
| 		old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
 | |
| 		if ( !old->valid ) {
 | |
| 			// should never happen
 | |
| 			Com_Printf ("Delta from invalid frame (not supposed to happen!).\n");
 | |
| 		} else if ( old->messageNum != newSnap.deltaNum ) {
 | |
| 			// The frame that the server did the delta from
 | |
| 			// is too old, so we can't reconstruct it properly.
 | |
| 			Com_Printf ("Delta frame too old.\n");
 | |
| 		} else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) {
 | |
| 			Com_Printf ("Delta parseEntitiesNum too old.\n");
 | |
| 		} else {
 | |
| 			newSnap.valid = qtrue;	// valid delta parse
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// read areamask
 | |
| 	len = MSG_ReadByte( msg );
 | |
| 	
 | |
| 	if(len > sizeof(newSnap.areamask))
 | |
| 	{
 | |
| 		Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len);
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	MSG_ReadData( msg, &newSnap.areamask, len);
 | |
| 
 | |
| 	// read playerinfo
 | |
| 	SHOWNET( msg, "playerstate" );
 | |
| 	if ( old ) {
 | |
| 		MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps );
 | |
| 	} else {
 | |
| 		MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps );
 | |
| 	}
 | |
| 
 | |
| 	// read packet entities
 | |
| 	SHOWNET( msg, "packet entities" );
 | |
| 	CL_ParsePacketEntities( msg, old, &newSnap );
 | |
| 
 | |
| 	// if not valid, dump the entire thing now that it has
 | |
| 	// been properly read
 | |
| 	if ( !newSnap.valid ) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// clear the valid flags of any snapshots between the last
 | |
| 	// received and this one, so if there was a dropped packet
 | |
| 	// it won't look like something valid to delta from next
 | |
| 	// time we wrap around in the buffer
 | |
| 	oldMessageNum = cl.snap.messageNum + 1;
 | |
| 
 | |
| 	if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) {
 | |
| 		oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 );
 | |
| 	}
 | |
| 	for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) {
 | |
| 		cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
 | |
| 	}
 | |
| 
 | |
| 	// copy to the current good spot
 | |
| 	cl.snap = newSnap;
 | |
| 	cl.snap.ping = 999;
 | |
| 	// calculate ping time
 | |
| 	for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
 | |
| 		packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
 | |
| 		if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
 | |
| 			cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	// save the frame off in the backup array for later delta comparisons
 | |
| 	cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
 | |
| 
 | |
| 	if (cl_shownet->integer == 3) {
 | |
| 		Com_Printf( "   snapshot:%i  delta:%i  ping:%i\n", cl.snap.messageNum,
 | |
| 		cl.snap.deltaNum, cl.snap.ping );
 | |
| 	}
 | |
| 
 | |
| 	cl.newSnapshots = qtrue;
 | |
| }
 | |
| 
 | |
| 
 | |
| //=====================================================================
 | |
| 
 | |
| int cl_connectedToPureServer;
 | |
| int cl_connectedToCheatServer;
 | |
| 
 | |
| /*
 | |
| ==================
 | |
| CL_SystemInfoChanged
 | |
| 
 | |
| The systeminfo configstring has been changed, so parse
 | |
| new information out of it.  This will happen at every
 | |
| gamestate, and possibly during gameplay.
 | |
| ==================
 | |
| */
 | |
| void CL_SystemInfoChanged( void ) {
 | |
| 	char			*systemInfo;
 | |
| 	const char		*s, *t;
 | |
| 	char			key[BIG_INFO_KEY];
 | |
| 	char			value[BIG_INFO_VALUE];
 | |
| 	qboolean		gameSet;
 | |
| 
 | |
| 	systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
 | |
| 	// NOTE TTimo:
 | |
| 	// when the serverId changes, any further messages we send to the server will use this new serverId
 | |
| 	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
 | |
| 	// in some cases, outdated cp commands might get sent with this news serverId
 | |
| 	cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
 | |
| 
 | |
| 	// don't set any vars when playing a demo
 | |
| 	if ( clc.demoplaying ) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| #ifdef USE_VOIP
 | |
| #ifdef LEGACY_PROTOCOL
 | |
| 	if(clc.compat)
 | |
| 		clc.voipEnabled = qfalse;
 | |
| 	else
 | |
| #endif
 | |
| 	{
 | |
| 		s = Info_ValueForKey( systemInfo, "sv_voip" );
 | |
| 		if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
 | |
| 			clc.voipEnabled = qfalse;
 | |
| 		else
 | |
| 			clc.voipEnabled = atoi(s);
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	s = Info_ValueForKey( systemInfo, "sv_cheats" );
 | |
| 	cl_connectedToCheatServer = atoi( s );
 | |
| 	if ( !cl_connectedToCheatServer ) {
 | |
| 		Cvar_SetCheatState();
 | |
| 	}
 | |
| 
 | |
| 	// check pure server string
 | |
| 	s = Info_ValueForKey( systemInfo, "sv_paks" );
 | |
| 	t = Info_ValueForKey( systemInfo, "sv_pakNames" );
 | |
| 	FS_PureServerSetLoadedPaks( s, t );
 | |
| 
 | |
| 	s = Info_ValueForKey( systemInfo, "sv_referencedPaks" );
 | |
| 	t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" );
 | |
| 	FS_PureServerSetReferencedPaks( s, t );
 | |
| 
 | |
| 	gameSet = qfalse;
 | |
| 	// scan through all the variables in the systeminfo and locally set cvars to match
 | |
| 	s = systemInfo;
 | |
| 	while ( s ) {
 | |
| 		int cvar_flags;
 | |
| 		
 | |
| 		Info_NextPair( &s, key, value );
 | |
| 		if ( !key[0] ) {
 | |
| 			break;
 | |
| 		}
 | |
| 		
 | |
| 		// ehw!
 | |
| 		if (!Q_stricmp(key, "fs_game"))
 | |
| 		{
 | |
| 			if(FS_CheckDirTraversal(value))
 | |
| 			{
 | |
| 				Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value);
 | |
| 				continue;
 | |
| 			}
 | |
| 				
 | |
| 			gameSet = qtrue;
 | |
| 		}
 | |
| 
 | |
| 		if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT)
 | |
| 			Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM);
 | |
| 		else
 | |
| 		{
 | |
| 			// If this cvar may not be modified by a server discard the value.
 | |
| 			if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED)))
 | |
| 			{
 | |
| #ifndef STANDALONE
 | |
| 				if(Q_stricmp(key, "g_synchronousClients") && Q_stricmp(key, "pmove_fixed") &&
 | |
| 				   Q_stricmp(key, "pmove_msec"))
 | |
| #endif
 | |
| 				{
 | |
| 					Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			Cvar_SetSafe(key, value);
 | |
| 		}
 | |
| 	}
 | |
| 	// if game folder should not be set and it is set at the client side
 | |
| 	if ( !gameSet && *Cvar_VariableString("fs_game") ) {
 | |
| 		Cvar_Set( "fs_game", "" );
 | |
| 	}
 | |
| 	cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" );
 | |
| }
 | |
| 
 | |
| /*
 | |
| ==================
 | |
| CL_ParseServerInfo
 | |
| ==================
 | |
| */
 | |
| static void CL_ParseServerInfo(void)
 | |
| {
 | |
| 	const char *serverInfo;
 | |
| 
 | |
| 	serverInfo = cl.gameState.stringData
 | |
| 		+ cl.gameState.stringOffsets[ CS_SERVERINFO ];
 | |
| 
 | |
| 	clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo,
 | |
| 		"sv_allowDownload"));
 | |
| 	Q_strncpyz(clc.sv_dlURL,
 | |
| 		Info_ValueForKey(serverInfo, "sv_dlURL"),
 | |
| 		sizeof(clc.sv_dlURL));
 | |
| }
 | |
| 
 | |
| /*
 | |
| ==================
 | |
| CL_ParseGamestate
 | |
| ==================
 | |
| */
 | |
| void CL_ParseGamestate( msg_t *msg ) {
 | |
| 	int				i;
 | |
| 	entityState_t	*es;
 | |
| 	int				newnum;
 | |
| 	entityState_t	nullstate;
 | |
| 	int				cmd;
 | |
| 	char			*s;
 | |
| 	char oldGame[MAX_QPATH];
 | |
| 
 | |
| 	Con_Close();
 | |
| 
 | |
| 	clc.connectPacketCount = 0;
 | |
| 
 | |
| 	// wipe local client state
 | |
| 	CL_ClearState();
 | |
| 
 | |
| 	// a gamestate always marks a server command sequence
 | |
| 	clc.serverCommandSequence = MSG_ReadLong( msg );
 | |
| 
 | |
| 	// parse all the configstrings and baselines
 | |
| 	cl.gameState.dataCount = 1;	// leave a 0 at the beginning for uninitialized configstrings
 | |
| 	while ( 1 ) {
 | |
| 		cmd = MSG_ReadByte( msg );
 | |
| 
 | |
| 		if ( cmd == svc_EOF ) {
 | |
| 			break;
 | |
| 		}
 | |
| 		
 | |
| 		if ( cmd == svc_configstring ) {
 | |
| 			int		len;
 | |
| 
 | |
| 			i = MSG_ReadShort( msg );
 | |
| 			if ( i < 0 || i >= MAX_CONFIGSTRINGS ) {
 | |
| 				Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
 | |
| 			}
 | |
| 			s = MSG_ReadBigString( msg );
 | |
| 			len = strlen( s );
 | |
| 
 | |
| 			if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) {
 | |
| 				Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" );
 | |
| 			}
 | |
| 
 | |
| 			// append it to the gameState string buffer
 | |
| 			cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
 | |
| 			Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 );
 | |
| 			cl.gameState.dataCount += len + 1;
 | |
| 		} else if ( cmd == svc_baseline ) {
 | |
| 			newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
 | |
| 			if ( newnum < 0 || newnum >= MAX_GENTITIES ) {
 | |
| 				Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum );
 | |
| 			}
 | |
| 			Com_Memset (&nullstate, 0, sizeof(nullstate));
 | |
| 			es = &cl.entityBaselines[ newnum ];
 | |
| 			MSG_ReadDeltaEntity( msg, &nullstate, es, newnum );
 | |
| 		} else {
 | |
| 			Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" );
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	clc.clientNum = MSG_ReadLong(msg);
 | |
| 	// read the checksum feed
 | |
| 	clc.checksumFeed = MSG_ReadLong( msg );
 | |
| 
 | |
| 	// save old gamedir
 | |
| 	Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame));
 | |
| 
 | |
| 	// parse useful values out of CS_SERVERINFO
 | |
| 	CL_ParseServerInfo();
 | |
| 
 | |
| 	// parse serverId and other cvars
 | |
| 	CL_SystemInfoChanged();
 | |
| 
 | |
| 	// stop recording now so the demo won't have an unnecessary level load at the end.
 | |
| 	if(cl_autoRecordDemo->integer && clc.demorecording)
 | |
| 		CL_StopRecord_f();
 | |
| 	
 | |
| 	// reinitialize the filesystem if the game directory has changed
 | |
| 	if(!cls.oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED))
 | |
| 	{
 | |
| 		cls.oldGameSet = qtrue;
 | |
| 		Q_strncpyz(cls.oldGame, oldGame, sizeof(cls.oldGame));
 | |
| 	}
 | |
| 
 | |
| 	FS_ConditionalRestart(clc.checksumFeed, qfalse);
 | |
| 
 | |
| 	// This used to call CL_StartHunkUsers, but now we enter the download state before loading the
 | |
| 	// cgame
 | |
| 	CL_InitDownloads();
 | |
| 
 | |
| 	// make sure the game starts
 | |
| 	Cvar_Set( "cl_paused", "0" );
 | |
| }
 | |
| 
 | |
| 
 | |
| //=====================================================================
 | |
| 
 | |
| /*
 | |
| =====================
 | |
| CL_ParseDownload
 | |
| 
 | |
| A download message has been received from the server
 | |
| =====================
 | |
| */
 | |
| void CL_ParseDownload ( msg_t *msg ) {
 | |
| 	int		size;
 | |
| 	unsigned char data[MAX_MSGLEN];
 | |
| 	uint16_t block;
 | |
| 
 | |
| 	if (!*clc.downloadTempName) {
 | |
| 		Com_Printf("Server sending download, but no download was requested\n");
 | |
| 		CL_AddReliableCommand("stopdl", qfalse);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// read the data
 | |
| 	block = MSG_ReadShort ( msg );
 | |
| 
 | |
| 	if(!block && !clc.downloadBlock)
 | |
| 	{
 | |
| 		// block zero is special, contains file size
 | |
| 		clc.downloadSize = MSG_ReadLong ( msg );
 | |
| 
 | |
| 		Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
 | |
| 
 | |
| 		if (clc.downloadSize < 0)
 | |
| 		{
 | |
| 			Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) );
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	size = MSG_ReadShort ( msg );
 | |
| 	if (size < 0 || size > sizeof(data))
 | |
| 	{
 | |
| 		Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size);
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	MSG_ReadData(msg, data, size);
 | |
| 
 | |
| 	if((clc.downloadBlock & 0xFFFF) != block)
 | |
| 	{
 | |
| 		Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// open the file if not opened yet
 | |
| 	if (!clc.download)
 | |
| 	{
 | |
| 		clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName );
 | |
| 
 | |
| 		if (!clc.download) {
 | |
| 			Com_Printf( "Could not create %s\n", clc.downloadTempName );
 | |
| 			CL_AddReliableCommand("stopdl", qfalse);
 | |
| 			CL_NextDownload();
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (size)
 | |
| 		FS_Write( data, size, clc.download );
 | |
| 
 | |
| 	CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse);
 | |
| 	clc.downloadBlock++;
 | |
| 
 | |
| 	clc.downloadCount += size;
 | |
| 
 | |
| 	// So UI gets access to it
 | |
| 	Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
 | |
| 
 | |
| 	if (!size) { // A zero length block means EOF
 | |
| 		if (clc.download) {
 | |
| 			FS_FCloseFile( clc.download );
 | |
| 			clc.download = 0;
 | |
| 
 | |
| 			// rename the file
 | |
| 			FS_SV_Rename ( clc.downloadTempName, clc.downloadName );
 | |
| 		}
 | |
| 
 | |
| 		// send intentions now
 | |
| 		// We need this because without it, we would hold the last nextdl and then start
 | |
| 		// loading right away.  If we take a while to load, the server is happily trying
 | |
| 		// to send us that last block over and over.
 | |
| 		// Write it twice to help make sure we acknowledge the download
 | |
| 		CL_WritePacket();
 | |
| 		CL_WritePacket();
 | |
| 
 | |
| 		// get another file if needed
 | |
| 		CL_NextDownload ();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #ifdef USE_VOIP
 | |
| static
 | |
| qboolean CL_ShouldIgnoreVoipSender(int sender)
 | |
| {
 | |
| 	if (!cl_voip->integer)
 | |
| 		return qtrue;  // VoIP is disabled.
 | |
| 	else if ((sender == clc.clientNum) && (!clc.demoplaying))
 | |
| 		return qtrue;  // ignore own voice (unless playing back a demo).
 | |
| 	else if (clc.voipMuteAll)
 | |
| 		return qtrue;  // all channels are muted with extreme prejudice.
 | |
| 	else if (clc.voipIgnore[sender])
 | |
| 		return qtrue;  // just ignoring this guy.
 | |
| 	else if (clc.voipGain[sender] == 0.0f)
 | |
| 		return qtrue;  // too quiet to play.
 | |
| 
 | |
| 	return qfalse;
 | |
| }
 | |
| 
 | |
| /*
 | |
| =====================
 | |
| CL_PlayVoip
 | |
| 
 | |
| Play raw data
 | |
| =====================
 | |
| */
 | |
| 
 | |
| static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags)
 | |
| {
 | |
| 	if(flags & VOIP_DIRECT)
 | |
| 	{
 | |
| 		S_RawSamples(sender + 1, samplecnt, clc.speexSampleRate, 2, 1,
 | |
| 	             data, clc.voipGain[sender], -1);
 | |
| 	}
 | |
| 
 | |
| 	if(flags & VOIP_SPATIAL)
 | |
| 	{
 | |
| 		S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, clc.speexSampleRate, 2, 1,
 | |
| 	             data, 1.0f, sender);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
| =====================
 | |
| CL_ParseVoip
 | |
| 
 | |
| A VoIP message has been received from the server
 | |
| =====================
 | |
| */
 | |
| static
 | |
| void CL_ParseVoip ( msg_t *msg ) {
 | |
| 	static short decoded[4096];  // !!! FIXME: don't hardcode.
 | |
| 
 | |
| 	const int sender = MSG_ReadShort(msg);
 | |
| 	const int generation = MSG_ReadByte(msg);
 | |
| 	const int sequence = MSG_ReadLong(msg);
 | |
| 	const int frames = MSG_ReadByte(msg);
 | |
| 	const int packetsize = MSG_ReadShort(msg);
 | |
| 	const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
 | |
| 	char encoded[1024];
 | |
| 	int seqdiff = sequence - clc.voipIncomingSequence[sender];
 | |
| 	int written = 0;
 | |
| 	int i;
 | |
| 
 | |
| 	Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);
 | |
| 
 | |
| 	if (sender < 0)
 | |
| 		return;   // short/invalid packet, bail.
 | |
| 	else if (generation < 0)
 | |
| 		return;   // short/invalid packet, bail.
 | |
| 	else if (sequence < 0)
 | |
| 		return;   // short/invalid packet, bail.
 | |
| 	else if (frames < 0)
 | |
| 		return;   // short/invalid packet, bail.
 | |
| 	else if (packetsize < 0)
 | |
| 		return;   // short/invalid packet, bail.
 | |
| 
 | |
| 	if (packetsize > sizeof (encoded)) {  // overlarge packet?
 | |
| 		int bytesleft = packetsize;
 | |
| 		while (bytesleft) {
 | |
| 			int br = bytesleft;
 | |
| 			if (br > sizeof (encoded))
 | |
| 				br = sizeof (encoded);
 | |
| 			MSG_ReadData(msg, encoded, br);
 | |
| 			bytesleft -= br;
 | |
| 		}
 | |
| 		return;   // overlarge packet, bail.
 | |
| 	}
 | |
| 
 | |
| 	if (!clc.speexInitialized) {
 | |
| 		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
 | |
| 		return;   // can't handle VoIP without libspeex!
 | |
| 	} else if (sender >= MAX_CLIENTS) {
 | |
| 		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
 | |
| 		return;   // bogus sender.
 | |
| 	} else if (CL_ShouldIgnoreVoipSender(sender)) {
 | |
| 		MSG_ReadData(msg, encoded, packetsize);  // skip payload.
 | |
| 		return;   // Channel is muted, bail.
 | |
| 	}
 | |
| 
 | |
| 	// !!! FIXME: make sure data is narrowband? Does decoder handle this?
 | |
| 
 | |
| 	Com_DPrintf("VoIP: packet accepted!\n");
 | |
| 
 | |
| 	// This is a new "generation" ... a new recording started, reset the bits.
 | |
| 	if (generation != clc.voipIncomingGeneration[sender]) {
 | |
| 		Com_DPrintf("VoIP: new generation %d!\n", generation);
 | |
| 		speex_bits_reset(&clc.speexDecoderBits[sender]);
 | |
| 		clc.voipIncomingGeneration[sender] = generation;
 | |
| 		seqdiff = 0;
 | |
| 	} else if (seqdiff < 0) {   // we're ahead of the sequence?!
 | |
| 		// This shouldn't happen unless the packet is corrupted or something.
 | |
| 		Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
 | |
| 		            sequence, clc.voipIncomingSequence[sender]);
 | |
| 		// reset the bits just in case.
 | |
| 		speex_bits_reset(&clc.speexDecoderBits[sender]);
 | |
| 		seqdiff = 0;
 | |
| 	} else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
 | |
| 		// just start over.
 | |
| 		Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
 | |
| 		            seqdiff, sender);
 | |
| 		speex_bits_reset(&clc.speexDecoderBits[sender]);
 | |
| 		seqdiff = 0;
 | |
| 	}
 | |
| 
 | |
| 	if (seqdiff != 0) {
 | |
| 		Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
 | |
| 		            seqdiff, sender);
 | |
| 		// tell speex that we're missing frames...
 | |
| 		for (i = 0; i < seqdiff; i++) {
 | |
| 			assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
 | |
| 			speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
 | |
| 			written += clc.speexFrameSize;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < frames; i++) {
 | |
| 		char encoded[256];
 | |
| 		const int len = MSG_ReadByte(msg);
 | |
| 		if (len < 0) {
 | |
| 			Com_DPrintf("VoIP: Short packet!\n");
 | |
| 			break;
 | |
| 		}
 | |
| 		MSG_ReadData(msg, encoded, len);
 | |
| 
 | |
| 		// shouldn't happen, but just in case...
 | |
| 		if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
 | |
| 			Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
 | |
| 			            written * 2, written, i);
 | |
| 			
 | |
| 			CL_PlayVoip(sender, written, (const byte *) decoded, flags);
 | |
| 			written = 0;
 | |
| 		}
 | |
| 
 | |
| 		speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
 | |
| 		speex_decode_int(clc.speexDecoder[sender],
 | |
| 		                 &clc.speexDecoderBits[sender], decoded + written);
 | |
| 
 | |
| 		#if 0
 | |
| 		static FILE *encio = NULL;
 | |
| 		if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
 | |
| 		if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
 | |
| 		static FILE *decio = NULL;
 | |
| 		if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
 | |
| 		if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
 | |
| 		#endif
 | |
| 
 | |
| 		written += clc.speexFrameSize;
 | |
| 	}
 | |
| 
 | |
| 	Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
 | |
| 	            written * 2, written, i);
 | |
| 
 | |
| 	if(written > 0)
 | |
| 		CL_PlayVoip(sender, written, (const byte *) decoded, flags);
 | |
| 
 | |
| 	clc.voipIncomingSequence[sender] = sequence + frames;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /*
 | |
| =====================
 | |
| CL_ParseCommandString
 | |
| 
 | |
| Command strings are just saved off until cgame asks for them
 | |
| when it transitions a snapshot
 | |
| =====================
 | |
| */
 | |
| void CL_ParseCommandString( msg_t *msg ) {
 | |
| 	char	*s;
 | |
| 	int		seq;
 | |
| 	int		index;
 | |
| 
 | |
| 	seq = MSG_ReadLong( msg );
 | |
| 	s = MSG_ReadString( msg );
 | |
| 
 | |
| 	// see if we have already executed stored it off
 | |
| 	if ( clc.serverCommandSequence >= seq ) {
 | |
| 		return;
 | |
| 	}
 | |
| 	clc.serverCommandSequence = seq;
 | |
| 
 | |
| 	index = seq & (MAX_RELIABLE_COMMANDS-1);
 | |
| 	Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) );
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| =====================
 | |
| CL_ParseServerMessage
 | |
| =====================
 | |
| */
 | |
| void CL_ParseServerMessage( msg_t *msg ) {
 | |
| 	int			cmd;
 | |
| 
 | |
| 	if ( cl_shownet->integer == 1 ) {
 | |
| 		Com_Printf ("%i ",msg->cursize);
 | |
| 	} else if ( cl_shownet->integer >= 2 ) {
 | |
| 		Com_Printf ("------------------\n");
 | |
| 	}
 | |
| 
 | |
| 	MSG_Bitstream(msg);
 | |
| 
 | |
| 	// get the reliable sequence acknowledge number
 | |
| 	clc.reliableAcknowledge = MSG_ReadLong( msg );
 | |
| 	// 
 | |
| 	if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) {
 | |
| 		clc.reliableAcknowledge = clc.reliableSequence;
 | |
| 	}
 | |
| 
 | |
| 	//
 | |
| 	// parse the message
 | |
| 	//
 | |
| 	while ( 1 ) {
 | |
| 		if ( msg->readcount > msg->cursize ) {
 | |
| 			Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message");
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		cmd = MSG_ReadByte( msg );
 | |
| 
 | |
| 		if (cmd == svc_EOF) {
 | |
| 			SHOWNET( msg, "END OF MESSAGE" );
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if ( cl_shownet->integer >= 2 ) {
 | |
| 			if ( (cmd < 0) || (!svc_strings[cmd]) ) {
 | |
| 				Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd );
 | |
| 			} else {
 | |
| 				SHOWNET( msg, svc_strings[cmd] );
 | |
| 			}
 | |
| 		}
 | |
| 	
 | |
| 	// other commands
 | |
| 		switch ( cmd ) {
 | |
| 		default:
 | |
| 			Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message");
 | |
| 			break;			
 | |
| 		case svc_nop:
 | |
| 			break;
 | |
| 		case svc_serverCommand:
 | |
| 			CL_ParseCommandString( msg );
 | |
| 			break;
 | |
| 		case svc_gamestate:
 | |
| 			CL_ParseGamestate( msg );
 | |
| 			break;
 | |
| 		case svc_snapshot:
 | |
| 			CL_ParseSnapshot( msg );
 | |
| 			break;
 | |
| 		case svc_download:
 | |
| 			CL_ParseDownload( msg );
 | |
| 			break;
 | |
| 		case svc_voip:
 | |
| #ifdef USE_VOIP
 | |
| 			CL_ParseVoip( msg );
 | |
| #endif
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | 
