summaryrefslogtreecommitdiff
path: root/engine/baseclientstate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/baseclientstate.cpp')
-rw-r--r--engine/baseclientstate.cpp1855
1 files changed, 1855 insertions, 0 deletions
diff --git a/engine/baseclientstate.cpp b/engine/baseclientstate.cpp
new file mode 100644
index 0000000..591f367
--- /dev/null
+++ b/engine/baseclientstate.cpp
@@ -0,0 +1,1855 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: baseclientstate.cpp: implementation of the CBaseClientState class.
+//
+//===============================================================================
+
+#include "client_pch.h"
+#include "baseclientstate.h"
+#include "vstdlib/random.h"
+#include <inetchannel.h>
+#include <netmessages.h>
+#include <proto_oob.h>
+#include <ctype.h>
+#include "cl_main.h"
+#include "net.h"
+#include "dt_recv_eng.h"
+#include "ents_shared.h"
+#include "net_synctags.h"
+#include "filesystem_engine.h"
+#include "host_cmd.h"
+#include "GameEventManager.h"
+#include "sv_rcon.h"
+#include "cl_rcon.h"
+#ifndef SWDS
+#include "vgui_baseui_interface.h"
+#include "cl_pluginhelpers.h"
+#include "vgui_askconnectpanel.h"
+#endif
+#include "sv_steamauth.h"
+#include "tier0/icommandline.h"
+#include "tier0/vcrmode.h"
+#include "snd_audio_source.h"
+#include "cl_steamauth.h"
+#include "server.h"
+#include "steam/steam_api.h"
+#include "matchmaking.h"
+#include "sv_plugin.h"
+#include "sys_dll.h"
+#include "host.h"
+#if defined( REPLAY_ENABLED )
+#include "replay_internal.h"
+#include "replayserver.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef ENABLE_RPT
+void CL_NotifyRPTOfDisconnect( );
+#endif // ENABLE_RPT
+
+#if !defined( NO_STEAM )
+void UpdateNameFromSteamID( IConVar *pConVar, CSteamID *pSteamID )
+{
+#ifndef SWDS
+ if ( !pConVar || !pSteamID || !Steam3Client().SteamFriends() )
+ return;
+
+ const char *pszName = Steam3Client().SteamFriends()->GetFriendPersonaName( *pSteamID );
+ pConVar->SetValue( pszName );
+#endif // SWDS
+}
+
+void SetNameToSteamIDName( IConVar *pConVar )
+{
+#ifndef SWDS
+ if ( Steam3Client().SteamUtils() && Steam3Client().SteamFriends() && Steam3Client().SteamUser() )
+ {
+ CSteamID steamID = Steam3Client().SteamUser()->GetSteamID();
+ UpdateNameFromSteamID( pConVar, &steamID );
+ }
+#endif // SWDS
+}
+#endif // NO_STEAM
+
+
+void CL_NameCvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var( pConVar );
+
+ static bool bPreventRent = false;
+ if ( !bPreventRent )
+ {
+ bPreventRent = true;
+#if !defined( NO_STEAM )
+ SetNameToSteamIDName( pConVar );
+#endif
+ // Remove any evil characters (ex: zero width no-break space)
+ char pchName[MAX_PLAYER_NAME_LENGTH];
+ V_strcpy_safe( pchName, var.GetString() );
+ Q_RemoveAllEvilCharacters( pchName );
+ pConVar->SetValue( pchName );
+
+ // store off the last known name, that isn't default, in the registry
+ // this is a transition step so it can be used to display in friends
+ if ( 0 != Q_stricmp( var.GetString(), var.GetDefault() )
+ && 0 != Q_stricmp( var.GetString(), "player" ) )
+ {
+ Sys_SetRegKeyValue( "Software\\Valve\\Steam", "LastGameNameUsed", var.GetString() );
+ }
+
+ bPreventRent = false;
+ }
+}
+
+
+
+
+#ifndef SWDS
+void askconnect_accept_f()
+{
+ char szHostName[256];
+ if ( IsAskConnectPanelActive( szHostName, sizeof( szHostName ) ) )
+ {
+ char szCommand[512];
+ V_snprintf( szCommand, sizeof( szCommand ), "connect %s redirect", szHostName );
+ Cbuf_AddText( szCommand );
+ HideAskConnectPanel();
+ }
+}
+ConCommand askconnect_accept( "askconnect_accept", askconnect_accept_f, "Accept a redirect request by the server.", FCVAR_DONTRECORD );
+#endif
+
+#ifndef SWDS
+extern IVEngineClient *engineClient;
+// ---------------------------------------------------------------------------------------- //
+static void SendClanTag( const char *pTag )
+{
+ KeyValues *kv = new KeyValues( "ClanTagChanged" );
+ kv->SetString( "tag", pTag );
+ engineClient->ServerCmdKeyValues( kv );
+}
+#endif
+
+// ---------------------------------------------------------------------------------------- //
+void CL_ClanIdChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+#ifndef SWDS
+ // Get the clan ID we're trying to select
+ ConVarRef var( pConVar );
+ uint32 newId = var.GetInt();
+ if ( newId == 0 )
+ {
+ // Default value, equates to no tag
+ SendClanTag( "" );
+ return;
+ }
+
+#if !defined( NO_STEAM )
+ // Make sure this player is actually part of the desired clan
+ ISteamFriends *pFriends = Steam3Client().SteamFriends();
+ if ( pFriends )
+ {
+ int iGroupCount = pFriends->GetClanCount();
+ for ( int k = 0; k < iGroupCount; ++ k )
+ {
+ CSteamID clanID = pFriends->GetClanByIndex( k );
+ if ( clanID.GetAccountID() == newId )
+ {
+ // valid clan, accept the change
+ CSteamID clanIDNew( newId, Steam3Client().SteamUtils()->GetConnectedUniverse(), k_EAccountTypeClan );
+ SendClanTag( pFriends->GetClanTag( clanIDNew ) );
+ return;
+ }
+ }
+ }
+#endif // NO_STEAM
+
+ // Couldn't validate the ID, so clear to the default (no tag)
+ var.SetValue( 0 );
+#endif // !SWDS
+}
+
+ConVar cl_resend ( "cl_resend","6", FCVAR_NONE, "Delay in seconds before the client will resend the 'connect' attempt", true, CL_MIN_RESEND_TIME, true, CL_MAX_RESEND_TIME );
+ConVar cl_name ( "name","unnamed", FCVAR_ARCHIVE | FCVAR_USERINFO | FCVAR_PRINTABLEONLY | FCVAR_SERVER_CAN_EXECUTE, "Current user name", CL_NameCvarChanged );
+ConVar password ( "password", "", FCVAR_ARCHIVE | FCVAR_SERVER_CANNOT_QUERY | FCVAR_DONTRECORD, "Current server access password" );
+ConVar cl_interpolate( "cl_interpolate", "1.0", FCVAR_USERINFO | FCVAR_DEVELOPMENTONLY | FCVAR_NOT_CONNECTED, "Interpolate entities on the client." );
+ConVar cl_clanid( "cl_clanid", "0", FCVAR_ARCHIVE | FCVAR_USERINFO | FCVAR_HIDDEN, "Current clan ID for name decoration", CL_ClanIdChanged );
+ConVar cl_show_connectionless_packet_warnings( "cl_show_connectionless_packet_warnings", "0", FCVAR_NONE, "Show console messages about ignored connectionless packets on the client." );
+
+// ---------------------------------------------------------------------------------------- //
+// C_ServerClassInfo implementation.
+// ---------------------------------------------------------------------------------------- //
+
+C_ServerClassInfo::C_ServerClassInfo()
+{
+ m_ClassName = NULL;
+ m_DatatableName = NULL;
+ m_InstanceBaselineIndex = INVALID_STRING_INDEX;
+}
+
+C_ServerClassInfo::~C_ServerClassInfo()
+{
+ delete [] m_ClassName;
+ delete [] m_DatatableName;
+}
+
+// Returns false if you should stop reading entities.
+inline static bool CL_DetermineUpdateType( CEntityReadInfo &u )
+{
+ if ( !u.m_bIsEntity || ( u.m_nNewEntity > u.m_nOldEntity ) )
+ {
+ // If we're at the last entity, preserve whatever entities followed it in the old packet.
+ // If newnum > oldnum, then the server skipped sending entities that it wants to leave the state alone for.
+ if ( !u.m_pFrom || ( u.m_nOldEntity > u.m_pFrom->last_entity ) )
+ {
+ Assert( !u.m_bIsEntity );
+ u.m_UpdateType = Finished;
+ return false;
+ }
+
+ // Preserve entities until we reach newnum (ie: the server didn't send certain entities because
+ // they haven't changed).
+ u.m_UpdateType = PreserveEnt;
+ }
+ else
+ {
+ if( u.m_UpdateFlags & FHDR_ENTERPVS )
+ {
+ u.m_UpdateType = EnterPVS;
+ }
+ else if( u.m_UpdateFlags & FHDR_LEAVEPVS )
+ {
+ u.m_UpdateType = LeavePVS;
+ }
+ else
+ {
+ u.m_UpdateType = DeltaEnt;
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: When a delta command is received from the server
+// We need to grab the entity # out of it any the bit settings, too.
+// Returns -1 if there are no more entities.
+// Input : &bRemove -
+// &bIsNew -
+// Output : int
+//-----------------------------------------------------------------------------
+static inline void CL_ParseDeltaHeader( CEntityReadInfo &u )
+{
+ u.m_UpdateFlags = FHDR_ZERO;
+
+#ifdef DEBUG_NETWORKING
+ int startbit = u.m_pBuf->GetNumBitsRead();
+#endif
+ SyncTag_Read( u.m_pBuf, "Hdr" );
+
+ u.m_nNewEntity = u.m_nHeaderBase + 1 + u.m_pBuf->ReadUBitVar();
+
+
+ u.m_nHeaderBase = u.m_nNewEntity;
+
+ // leave pvs flag
+ if ( u.m_pBuf->ReadOneBit() == 0 )
+ {
+ // enter pvs flag
+ if ( u.m_pBuf->ReadOneBit() != 0 )
+ {
+ u.m_UpdateFlags |= FHDR_ENTERPVS;
+ }
+ }
+ else
+ {
+ u.m_UpdateFlags |= FHDR_LEAVEPVS;
+
+ // Force delete flag
+ if ( u.m_pBuf->ReadOneBit() != 0 )
+ {
+ u.m_UpdateFlags |= FHDR_DELETE;
+ }
+ }
+ // Output the bitstream...
+#ifdef DEBUG_NETWORKING
+ int lastbit = u.m_pBuf->GetNumBitsRead();
+ {
+ void SpewBitStream( unsigned char* pMem, int bit, int lastbit );
+ SpewBitStream( (byte *)u.m_pBuf->m_pData, startbit, lastbit );
+ }
+#endif
+}
+
+CBaseClientState::CBaseClientState()
+{
+ m_Socket = NS_CLIENT;
+ m_pServerClasses = NULL;
+ m_StringTableContainer = NULL;
+ m_NetChannel = NULL;
+ m_nSignonState = SIGNONSTATE_NONE;
+ m_nChallengeNr = 0;
+ m_flConnectTime = 0;
+ m_nRetryNumber = 0;
+ m_szRetryAddress[0] = 0;
+ m_ulGameServerSteamID = 0;
+ m_retryChallenge = 0;
+ m_bRestrictServerCommands = true;
+ m_bRestrictClientCommands = true;
+ m_nServerCount = 0;
+ m_nCurrentSequence = 0;
+ m_nDeltaTick = 0;
+ m_bPaused = 0;
+ m_flPausedExpireTime = -1.f;
+ m_nViewEntity = 0;
+ m_nPlayerSlot = 0;
+ m_szLevelFileName[0] = 0;
+ m_szLevelBaseName[0] = 0;
+ m_nMaxClients = 0;
+ Q_memset( m_pEntityBaselines, 0, sizeof( m_pEntityBaselines ) );
+ m_nServerClasses = 0;
+ m_nServerClassBits = 0;
+ m_szEncrytionKey[0] = 0;
+}
+
+CBaseClientState::~CBaseClientState()
+{
+
+}
+
+void CBaseClientState::Clear( void )
+{
+ m_nServerCount = -1;
+ m_nDeltaTick = -1;
+
+ m_ClockDriftMgr.Clear();
+
+ m_nCurrentSequence = 0;
+ m_nServerClasses = 0;
+ m_nServerClassBits = 0;
+ m_nPlayerSlot = 0;
+ m_szLevelFileName[0] = 0;
+ m_szLevelBaseName[ 0 ] = 0;
+ m_nMaxClients = 0;
+
+ if ( m_pServerClasses )
+ {
+ delete[] m_pServerClasses;
+ m_pServerClasses = NULL;
+ }
+
+ if ( m_StringTableContainer )
+ {
+#ifndef SHARED_NET_STRING_TABLES
+ m_StringTableContainer->RemoveAllTables();
+#endif
+
+ m_StringTableContainer = NULL;
+ }
+
+ FreeEntityBaselines();
+
+ RecvTable_Term( false );
+
+ if ( m_NetChannel )
+ m_NetChannel->Reset();
+
+ m_bPaused = 0;
+ m_flPausedExpireTime = -1.f;
+ m_nViewEntity = 0;
+ m_nChallengeNr = 0;
+ m_flConnectTime = 0.0f;
+}
+
+void CBaseClientState::FileReceived( const char * fileName, unsigned int transferID )
+{
+ ConMsg( "CBaseClientState::FileReceived: %s.\n", fileName );
+}
+
+void CBaseClientState::FileDenied(const char *fileName, unsigned int transferID )
+{
+ ConMsg( "CBaseClientState::FileDenied: %s.\n", fileName );
+}
+
+void CBaseClientState::FileRequested(const char *fileName, unsigned int transferID )
+{
+ ConMsg( "File '%s' requested from %s.\n", fileName, m_NetChannel->GetAddress() );
+
+ m_NetChannel->SendFile( fileName, transferID ); // CBaseCLisntState always sends file
+}
+
+void CBaseClientState::FileSent(const char *fileName, unsigned int transferID )
+{
+ ConMsg( "File '%s' sent.\n", fileName );
+}
+
+#define REGISTER_NET_MSG( name ) \
+ NET_##name * p##name = new NET_##name(); \
+ p##name->m_pMessageHandler = this; \
+ chan->RegisterMessage( p##name ); \
+
+#define REGISTER_SVC_MSG( name ) \
+ SVC_##name * p##name = new SVC_##name(); \
+ p##name->m_pMessageHandler = this; \
+ chan->RegisterMessage( p##name ); \
+
+void CBaseClientState::ConnectionStart(INetChannel *chan)
+{
+ REGISTER_NET_MSG( Tick );
+ REGISTER_NET_MSG( StringCmd );
+ REGISTER_NET_MSG( SetConVar );
+ REGISTER_NET_MSG( SignonState );
+
+ REGISTER_SVC_MSG( Print );
+ REGISTER_SVC_MSG( ServerInfo );
+ REGISTER_SVC_MSG( SendTable );
+ REGISTER_SVC_MSG( ClassInfo );
+ REGISTER_SVC_MSG( SetPause );
+ REGISTER_SVC_MSG( CreateStringTable );
+ REGISTER_SVC_MSG( UpdateStringTable );
+ REGISTER_SVC_MSG( VoiceInit );
+ REGISTER_SVC_MSG( VoiceData );
+ REGISTER_SVC_MSG( Sounds );
+ REGISTER_SVC_MSG( SetView );
+ REGISTER_SVC_MSG( FixAngle );
+ REGISTER_SVC_MSG( CrosshairAngle );
+ REGISTER_SVC_MSG( BSPDecal );
+ REGISTER_SVC_MSG( GameEvent );
+ REGISTER_SVC_MSG( UserMessage );
+ REGISTER_SVC_MSG( EntityMessage );
+ REGISTER_SVC_MSG( PacketEntities );
+ REGISTER_SVC_MSG( TempEntities );
+ REGISTER_SVC_MSG( Prefetch );
+ REGISTER_SVC_MSG( Menu );
+ REGISTER_SVC_MSG( GameEventList );
+ REGISTER_SVC_MSG( GetCvarValue );
+ REGISTER_SVC_MSG( CmdKeyValues );
+ REGISTER_SVC_MSG( SetPauseTimed );
+}
+
+void CBaseClientState::ConnectionClosing( const char *reason )
+{
+ ConMsg( "Disconnect: %s.\n", reason?reason:"unknown reason" );
+ Disconnect( reason ? reason : "Connection closing", true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A svc_signonnum has been received, perform a client side setup
+// Output : void CL_SignonReply
+//-----------------------------------------------------------------------------
+bool CBaseClientState::SetSignonState ( int state, int count )
+{
+ // ConDMsg ("CL_SignonReply: %i\n", cl.signon);
+
+ if ( state < SIGNONSTATE_NONE || state > SIGNONSTATE_CHANGELEVEL )
+ {
+ ConMsg ("Received signon %i when at %i\n", state, m_nSignonState );
+ Assert( 0 );
+ return false;
+ }
+
+ if ( (state > SIGNONSTATE_CONNECTED) && (state <= m_nSignonState) && !m_NetChannel->IsPlayback() )
+ {
+ ConMsg ("Received signon %i when at %i\n", state, m_nSignonState);
+ Assert( 0 );
+ return false;
+ }
+
+ if ( (count != m_nServerCount) && (count != -1) && (m_nServerCount != -1) && !m_NetChannel->IsPlayback() )
+ {
+ ConMsg ("Received wrong spawn count %i when at %i\n", count, m_nServerCount );
+ Assert( 0 );
+ return false;
+ }
+
+ if ( state == SIGNONSTATE_FULL )
+ {
+#if defined( REPLAY_ENABLED ) && !defined( DEDICATED )
+ if ( g_pClientReplayContext )
+ {
+ g_pClientReplayContext->OnSignonStateFull();
+ }
+#endif
+
+ if ( IsX360() &&
+ g_pMatchmaking->PreventFullServerStartup() )
+ {
+ return true;
+ }
+ }
+
+ m_nSignonState = state;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called by CL_Connect and CL_CheckResend
+// If we are in ca_connecting state and we have gotten a challenge
+// response before the timeout, send another "connect" request.
+// Output : void CL_SendConnectPacket
+//-----------------------------------------------------------------------------
+void CBaseClientState::SendConnectPacket (int challengeNr, int authProtocol, uint64 unGSSteamID, bool bGSSecure )
+{
+ COM_TimestampedLog( "SendConnectPacket" );
+
+ netadr_t adr;
+ char szServerName[MAX_OSPATH];
+ const char *CDKey = "NOCDKEY";
+
+ Q_strncpy(szServerName, m_szRetryAddress, MAX_OSPATH);
+
+ if ( !NET_StringToAdr (szServerName, &adr) )
+ {
+ ConMsg ("Bad server address (%s)\n", szServerName );
+ Disconnect( "Bad server address", true );
+ // Host_Disconnect(); MOTODO
+ return;
+ }
+
+ if ( adr.GetPort() == (unsigned short)0 )
+ {
+ adr.SetPort( PORT_SERVER );
+ }
+
+ ALIGN4 char msg_buffer[MAX_ROUTABLE_PAYLOAD] ALIGN4_POST;
+ bf_write msg( msg_buffer, sizeof(msg_buffer) );
+
+ msg.WriteLong( CONNECTIONLESS_HEADER );
+ msg.WriteByte( C2S_CONNECT );
+ msg.WriteLong( PROTOCOL_VERSION );
+ msg.WriteLong( authProtocol );
+ msg.WriteLong( challengeNr );
+ msg.WriteLong( m_retryChallenge );
+ msg.WriteString( GetClientName() ); // Name
+ msg.WriteString( password.GetString() ); // password
+ msg.WriteString( GetSteamInfIDVersionInfo().szVersionString ); // product version
+// msg.WriteByte( ( g_pServerPluginHandler->GetNumLoadedPlugins() > 0 ) ? 1 : 0 ); // have any client-side server plug-ins been loaded?
+
+ switch ( authProtocol )
+ {
+ // Fall through, bogus protocol type, use CD key hash.
+ case PROTOCOL_HASHEDCDKEY: CDKey = GetCDKeyHash();
+ msg.WriteString( CDKey ); // cdkey
+ break;
+
+ case PROTOCOL_STEAM: if ( !PrepareSteamConnectResponse( unGSSteamID, bGSSecure, adr, msg ) )
+ {
+ return;
+ }
+ break;
+
+ default: Host_Error( "Unexepected authentication protocol %i!\n", authProtocol );
+ return;
+ }
+
+ // Mark time of this attempt for retransmit requests
+ m_flConnectTime = net_time;
+
+ // remember challengenr for TCP connection
+ m_nChallengeNr = challengeNr;
+
+ // Remember Steam ID, if any
+ m_ulGameServerSteamID = unGSSteamID;
+
+ // Send protocol and challenge value
+ NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: append steam specific data to a connection response
+//-----------------------------------------------------------------------------
+bool CBaseClientState::PrepareSteamConnectResponse( uint64 unGSSteamID, bool bGSSecure, const netadr_t &adr, bf_write &msg )
+{
+ // X360TBD: Network - Steam Dedicated Server hack
+ if ( IsX360() )
+ {
+ return true;
+ }
+
+#if !defined( NO_STEAM ) && !defined( SWDS )
+ if ( !Steam3Client().SteamUser() )
+ {
+ COM_ExplainDisconnection( true, "#GameUI_ServerRequireSteam" );
+ Disconnect( "#GameUI_ServerRequireSteam", true );
+ return false;
+ }
+#endif
+
+ netadr_t checkAdr = adr;
+ if ( adr.GetType() == NA_LOOPBACK || adr.IsLocalhost() )
+ {
+ checkAdr.SetIP( net_local_adr.GetIPHostByteOrder() );
+ }
+
+#ifndef SWDS
+ // now append the steam3 cookie
+ char steam3Cookie[ STEAM_KEYSIZE ];
+ uint32 steam3CookieLen = 0;
+
+ Steam3Client().GetAuthSessionTicket( steam3Cookie, sizeof(steam3Cookie), &steam3CookieLen, checkAdr.GetIPHostByteOrder(), checkAdr.GetPort(), unGSSteamID, bGSSecure );
+
+ if ( steam3CookieLen == 0 )
+ {
+ COM_ExplainDisconnection( true, "#GameUI_ServerRequireSteam" );
+ Disconnect( "#GameUI_ServerRequireSteam", true );
+ return false;
+ }
+
+ msg.WriteShort( steam3CookieLen );
+ if ( steam3CookieLen > 0 )
+ msg.WriteBytes( steam3Cookie, steam3CookieLen );
+#endif
+
+ return true;
+}
+
+// Tracks how we connected to the current server.
+static ConVar cl_connectmethod( "cl_connectmethod", "", FCVAR_USERINFO | FCVAR_HIDDEN, "Method by which we connected to the current server." );
+
+/* static */ bool CBaseClientState::ConnectMethodAllowsRedirects()
+{
+ // Only HLTV should be allowed to redirect clients, but malicious servers can answer a connect
+ // attempt as HLTV and then redirect elsewhere. A somewhat-more complete fix for this would
+ // involve tracking our redirected status and refusing to interact with non-HLTV servers. For
+ // now, however, we just blacklist server browser / matchmaking connect methods from allowing
+ // redirects and allow it for other types.
+ const char *pConnectMethod = cl_connectmethod.GetString();
+ if ( V_strcmp( pConnectMethod, "serverbrowser_internet" ) == 0 ||
+ V_strncmp( pConnectMethod, "quickpick", 9 ) == 0 ||
+ V_strncmp( pConnectMethod, "quickplay", 9 ) == 0 ||
+ V_strcmp( pConnectMethod, "matchmaking" ) == 0 ||
+ V_strcmp( pConnectMethod, "coaching" ) == 0 )
+ {
+ return false;
+ }
+ return true;
+}
+
+void CBaseClientState::Connect(const char* adr, const char *pszSourceTag)
+{
+#if !defined( NO_STEAM )
+ // Get our name from steam. Needs to be done before connecting
+ // because we won't have triggered a check by changing our name.
+ IConVar *pVar = g_pCVar->FindVar( "name" );
+ if ( pVar )
+ {
+ SetNameToSteamIDName( pVar );
+ }
+#endif
+
+
+ Q_strncpy( m_szRetryAddress, adr, sizeof(m_szRetryAddress) );
+ m_retryChallenge = (RandomInt(0,0x0FFF) << 16) | RandomInt(0,0xFFFF);
+ m_ulGameServerSteamID = 0;
+ m_sRetrySourceTag = pszSourceTag;
+ cl_connectmethod.SetValue( m_sRetrySourceTag.String() );
+
+ // For the check for resend timer to fire a connection / getchallenge request.
+ SetSignonState( SIGNONSTATE_CHALLENGE, -1 );
+
+ // Force connection request to fire.
+ m_flConnectTime = -FLT_MAX;
+
+ m_nRetryNumber = 0;
+}
+
+INetworkStringTable *CBaseClientState::GetStringTable( const char * name ) const
+{
+ if ( !m_StringTableContainer )
+ {
+ Assert( m_StringTableContainer );
+ return NULL;
+ }
+
+ return m_StringTableContainer->FindTable( name );
+}
+
+void CBaseClientState::ForceFullUpdate( void )
+{
+ if ( m_nDeltaTick == -1 )
+ return;
+
+ FreeEntityBaselines();
+ m_nDeltaTick = -1;
+ DevMsg( "Requesting full game update...\n");
+}
+
+void CBaseClientState::FullConnect( netadr_t &adr )
+{
+ // Initiate the network channel
+
+ COM_TimestampedLog( "CBaseClientState::FullConnect" );
+
+ m_NetChannel = NET_CreateNetChannel( m_Socket, &adr, "CLIENT", this );
+
+ Assert( m_NetChannel );
+
+ m_NetChannel->StartStreaming( m_nChallengeNr ); // open TCP stream
+
+ // Bump connection time to now so we don't resend a connection
+ // Request
+ m_flConnectTime = net_time;
+
+ // We'll request a full delta from the baseline
+ m_nDeltaTick = -1;
+
+ // We can send a cmd right away
+ m_flNextCmdTime = net_time;
+
+ // Mark client as connected
+ SetSignonState( SIGNONSTATE_CONNECTED, -1 );
+#if !defined(SWDS)
+ RCONClient().SetAddress( m_NetChannel->GetRemoteAddress() );
+#endif
+
+ // Fire an event when we get our connection
+ IGameEvent *event = g_GameEventManager.CreateEvent( "client_connected" );
+ if ( event )
+ {
+ event->SetString( "address", m_NetChannel->GetRemoteAddress().ToString( true ) );
+ event->SetInt( "ip", m_NetChannel->GetRemoteAddress().GetIPNetworkByteOrder() ); // <<< Network byte order?
+ event->SetInt( "port", m_NetChannel->GetRemoteAddress().GetPort() );
+ g_GameEventManager.FireEventClientSide( event );
+ }
+
+}
+
+void CBaseClientState::ConnectionCrashed(const char *reason)
+{
+ DebuggerBreakIfDebugging_StagingOnly();
+ ConMsg( "Connection lost: %s.\n", reason?reason:"unknown reason" );
+ Disconnect( reason ? reason : "Connection crashed", true );
+}
+
+void CBaseClientState::Disconnect( const char *pszReason, bool bShowMainMenu )
+{
+ m_flConnectTime = -FLT_MAX;
+ m_nRetryNumber = 0;
+ m_ulGameServerSteamID = 0;
+
+ if ( m_nSignonState == SIGNONSTATE_NONE )
+ return;
+
+#if !defined( SWDS ) && defined( ENABLE_RPT )
+ CL_NotifyRPTOfDisconnect( );
+#endif
+
+ m_nSignonState = SIGNONSTATE_NONE;
+
+ netadr_t adr;
+ if ( m_NetChannel )
+ {
+ adr = m_NetChannel->GetRemoteAddress();
+ }
+ else
+ {
+ NET_StringToAdr (m_szRetryAddress, &adr);
+ }
+
+#ifndef SWDS
+ netadr_t checkAdr = adr;
+ if ( adr.GetType() == NA_LOOPBACK || adr.IsLocalhost() )
+ {
+ checkAdr.SetIP( net_local_adr.GetIPHostByteOrder() );
+ }
+
+ Steam3Client().CancelAuthTicket();
+#endif
+
+ if ( m_NetChannel )
+ {
+ m_NetChannel->Shutdown( ( pszReason && *pszReason ) ? pszReason : "Disconnect by user." );
+ m_NetChannel = NULL;
+ }
+}
+
+void CBaseClientState::RunFrame (void)
+{
+ VPROF("CBaseClientState::RunFrame");
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( (m_nSignonState > SIGNONSTATE_NEW) && m_NetChannel && g_GameEventManager.HasClientListenersChanged() )
+ {
+ // assemble a list of all events we listening to and tell the server
+ CLC_ListenEvents msg;
+ g_GameEventManager.WriteListenEventList( &msg );
+ m_NetChannel->SendNetMsg( msg );
+ }
+
+ if ( m_nSignonState == SIGNONSTATE_CHALLENGE )
+ {
+ CheckForResend();
+ }
+}
+
+/*
+=================
+CL_CheckForResend
+
+Resend a connect message if the last one has timed out
+=================
+*/
+void CBaseClientState::CheckForResend (void)
+{
+ // resend if we haven't gotten a reply yet
+ // We only resend during the connection process.
+ if ( m_nSignonState != SIGNONSTATE_CHALLENGE )
+ return;
+
+ // Wait at least the resend # of seconds.
+ if ( ( net_time - m_flConnectTime ) < cl_resend.GetFloat())
+ return;
+
+ netadr_t adr;
+
+ if (!NET_StringToAdr (m_szRetryAddress, &adr))
+ {
+ ConMsg ("Bad server address (%s)\n", m_szRetryAddress);
+ //Host_Disconnect();
+ Disconnect( "Bad server address", true );
+ return;
+ }
+ if (adr.GetPort() == 0)
+ {
+ adr.SetPort( PORT_SERVER );
+ }
+
+ // Only retry so many times before failure.
+ if ( m_nRetryNumber >= GetConnectionRetryNumber() )
+ {
+ COM_ExplainDisconnection( true, "Connection failed after %i retries.\n", CL_CONNECTION_RETRIES );
+ // Host_Disconnect();
+ Disconnect( "Connection failed", true );
+ return;
+ }
+
+ // Mark time of this attempt.
+ m_flConnectTime = net_time; // for retransmit requests
+
+ // Display appropriate message
+ if ( Q_strncmp(m_szRetryAddress, "localhost", 9) )
+ {
+ if ( m_nRetryNumber == 0 )
+ ConMsg ("Connecting to %s...\n", m_szRetryAddress);
+ else
+ ConMsg ("Retrying %s...\n", m_szRetryAddress);
+ }
+
+ // Fire an event when we attempt connection
+ if ( m_nRetryNumber == 0 )
+ {
+ IGameEvent *event = g_GameEventManager.CreateEvent( "client_beginconnect" );
+ if ( event )
+ {
+ event->SetString( "address", m_szRetryAddress);
+ event->SetInt( "ip", adr.GetIPNetworkByteOrder() ); // <<< Network byte order?
+ event->SetInt( "port", adr.GetPort() );
+ //event->SetInt( "retry_number", m_nRetryNumber );
+ event->SetString( "source", m_sRetrySourceTag );
+ g_GameEventManager.FireEventClientSide( event );
+ }
+ }
+
+ m_nRetryNumber++;
+
+ // Request another challenge value.
+ {
+ ALIGN4 char msg_buffer[MAX_ROUTABLE_PAYLOAD] ALIGN4_POST;
+ bf_write msg( msg_buffer, sizeof(msg_buffer) );
+
+ msg.WriteLong( CONNECTIONLESS_HEADER );
+ msg.WriteByte( A2S_GETCHALLENGE );
+ msg.WriteLong( m_retryChallenge );
+ msg.WriteString( "0000000000" ); // pad out
+ NET_SendPacket( NULL, m_Socket, adr, msg.GetData(), msg.GetNumBytesWritten() );
+ }
+}
+
+bool CBaseClientState::ProcessConnectionlessPacket( netpacket_t *packet )
+{
+ VPROF( "ProcessConnectionlessPacket" );
+
+ Assert( packet );
+
+ bf_read &msg = packet->message; // handy shortcut
+
+ int c = msg.ReadByte();
+
+ // ignoring a specific packet that DOTA2 broadcasts
+ if ( c == C2C_MOD )
+ return false;
+
+ char string[MAX_ROUTABLE_PAYLOAD];
+
+ netadr_t adrServerConnectingTo;
+ NET_StringToAdr ( m_szRetryAddress, &adrServerConnectingTo );
+ if ( ( packet->from.GetType() != NA_LOOPBACK ) && ( packet->from.GetIPNetworkByteOrder() != adrServerConnectingTo.GetIPNetworkByteOrder() ) )
+ {
+ if ( cl_show_connectionless_packet_warnings.GetBool() )
+ {
+ ConDMsg ( "Discarding connectionless packet ( CL '%c' ) from %s.\n", c, packet->from.ToString() );
+ }
+ return false;
+ }
+
+ switch ( c )
+ {
+
+ case S2C_CONNECTION: if ( m_nSignonState == SIGNONSTATE_CHALLENGE )
+ {
+ int myChallenge = msg.ReadLong();
+ if ( myChallenge != m_retryChallenge )
+ {
+ Msg( "Server connection did not have the correct challenge, ignoring.\n" );
+ return false;
+ }
+
+ // server accepted our connection request
+ FullConnect( packet->from );
+ }
+ break;
+
+ case S2C_CHALLENGE: // Response from getchallenge we sent to the server we are connecting to
+ // Blow it off if we are not connected.
+ if ( m_nSignonState == SIGNONSTATE_CHALLENGE )
+ {
+ int magicVersion = msg.ReadLong();
+ if ( magicVersion != S2C_MAGICVERSION )
+ {
+ COM_ExplainDisconnection( true, "#GameUI_ServerConnectOutOfDate" );
+ Disconnect( "#GameUI_ServerConnectOutOfDate", true );
+ return false;
+ }
+
+ int challenge = msg.ReadLong();
+ int myChallenge = msg.ReadLong();
+ if ( myChallenge != m_retryChallenge )
+ {
+ Msg( "Server challenge did not have the correct challenge, ignoring.\n" );
+ return false;
+ }
+
+ int authprotocol = msg.ReadLong();
+ uint64 unGSSteamID = 0;
+ bool bGSSecure = false;
+ if ( authprotocol == PROTOCOL_STEAM )
+ {
+ if ( msg.ReadShort() != 0 )
+ {
+ Msg( "Invalid Steam key size.\n" );
+ Disconnect( "Invalid Steam key size", true );
+ return false;
+ }
+ if ( msg.GetNumBytesLeft() > sizeof(unGSSteamID) )
+ {
+ if ( !msg.ReadBytes( &unGSSteamID, sizeof(unGSSteamID) ) )
+ {
+ Msg( "Invalid GS Steam ID.\n" );
+ Disconnect( "Invalid GS Steam ID", true );
+ return false;
+ }
+
+ bGSSecure = ( msg.ReadByte() == 1 );
+ }
+ // The host can disable access to secure servers if you load unsigned code (mods, plugins, hacks)
+ if ( bGSSecure && !Host_IsSecureServerAllowed() )
+ {
+ COM_ExplainDisconnection( true, "#GameUI_ServerInsecure" );
+ Disconnect( "#GameUI_ServerInsecure", true );
+ return false;
+ }
+ }
+ SendConnectPacket( challenge, authprotocol, unGSSteamID, bGSSecure );
+ }
+ break;
+
+ case S2C_CONNREJECT: if ( m_nSignonState == SIGNONSTATE_CHALLENGE ) // Spoofed?
+ {
+ int myChallenge = msg.ReadLong();
+ if ( myChallenge != m_retryChallenge )
+ {
+ Msg( "Received connection rejection that didn't match my challenge, ignoring.\n" );
+ return false;
+ }
+
+ msg.ReadString( string, sizeof(string) );
+ // Force failure dialog to come up now.
+ COM_ExplainDisconnection( true, "%s", string );
+ Disconnect( string, true );
+ // Host_Disconnect();
+ }
+ break;
+
+ // Unknown?
+ default:
+ // Otherwise, don't do anything.
+ if ( cl_show_connectionless_packet_warnings.GetBool() )
+ {
+ ConDMsg ( "Bad connectionless packet ( CL '%c' ) from %s.\n", c, packet->from.ToString() );
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool CBaseClientState::ProcessTick( NET_Tick *msg )
+{
+ VPROF( "ProcessTick" );
+
+ m_NetChannel->SetRemoteFramerate( msg->m_flHostFrameTime, msg->m_flHostFrameTimeStdDeviation );
+
+ // Note: CClientState separates the client and server clock states and drifts
+ // the client's clock to match the server's, but right here, we keep the two clocks in sync.
+ SetClientTickCount( msg->m_nTick );
+ SetServerTickCount( msg->m_nTick );
+
+ if ( m_StringTableContainer )
+ {
+ m_StringTableContainer->SetTick( GetServerTickCount() );
+ }
+
+ return (GetServerTickCount()>0);
+}
+
+void CBaseClientState::SendStringCmd(const char * command)
+{
+ if ( m_NetChannel)
+ {
+ NET_StringCmd stringCmd( command );
+ m_NetChannel->SendNetMsg( stringCmd );
+ }
+}
+
+bool CBaseClientState::ProcessStringCmd( NET_StringCmd *msg )
+{
+ VPROF( "ProcessStringCmd" );
+
+ // Don't restrict commands from the server in single player or if cl_restrict_stuffed_commands is 0.
+ if ( !m_bRestrictServerCommands || sv.IsActive() )
+ {
+ Cbuf_AddText ( msg->m_szCommand );
+ return true;
+ }
+
+ // Check that we can add the two execution markers
+ if ( !Cbuf_HasRoomForExecutionMarkers( 2 ) )
+ {
+ AssertMsg( false, "CBaseClientState::ProcessStringCmd called but there is no room for the execution markers. Ignoring command." );
+ return true;
+ }
+
+ // Run the command, but make sure the command parser knows to only execute commands marked with FCVAR_SERVER_CAN_EXECUTE.
+ Cbuf_AddTextWithMarkers( eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE, msg->m_szCommand, eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE );
+
+ return true;
+}
+
+
+bool CBaseClientState::ProcessSetConVar( NET_SetConVar *msg )
+{
+ VPROF( "ProcessSetConVar" );
+
+ // Never process on local client, since the ConVar is directly linked here
+ if ( m_NetChannel->IsLoopback() )
+ return true;
+
+ for ( int i=0; i<msg->m_ConVars.Count(); i++ )
+ {
+ const char *name = msg->m_ConVars[i].name;
+ const char *value = msg->m_ConVars[i].value;
+
+ // De-constify
+ ConVarRef var( name );
+
+ if ( !var.IsValid() )
+ {
+ ConMsg( "SetConVar: No such cvar ( %s set to %s), skipping\n",
+ name, value );
+ continue;
+ }
+
+ // Make sure server is only setting replicated game ConVars
+ if ( !var.IsFlagSet( FCVAR_REPLICATED ) )
+ {
+ ConMsg( "SetConVar: Can't set server cvar %s to %s, not marked as FCVAR_REPLICATED on client\n",
+ name, value );
+ continue;
+ }
+
+ // Set value directly ( don't call through cv->DirectSet!!! )
+ if ( !sv.IsActive() )
+ {
+ var.SetValue( value );
+ DevMsg( "SetConVar: %s = \"%s\"\n", name, value );
+ }
+ }
+
+ return true;
+}
+
+bool CBaseClientState::ProcessSignonState( NET_SignonState *msg )
+{
+ VPROF( "ProcessSignonState" );
+
+ return SetSignonState( msg->m_nSignonState, msg->m_nSpawnCount ) ;
+}
+
+bool CBaseClientState::ProcessPrint( SVC_Print *msg )
+{
+ VPROF( "ProcessPrint" );
+
+ ConMsg( "%s", msg->m_szText );
+ return true;
+}
+
+bool CBaseClientState::ProcessMenu( SVC_Menu *msg )
+{
+ VPROF( "ProcessMenu" );
+
+#if !defined(SWDS)
+ PluginHelpers_Menu( msg );
+#endif
+ return true;
+}
+
+bool CBaseClientState::ProcessServerInfo( SVC_ServerInfo *msg )
+{
+ VPROF( "ProcessServerInfo" );
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_PROCESSSERVERINFO);
+#endif
+
+ COM_TimestampedLog( " CBaseClient::ProcessServerInfo" );
+
+ if ( msg->m_nProtocol != PROTOCOL_VERSION
+#if defined( DEMO_BACKWARDCOMPATABILITY ) && (! defined( SWDS ) )
+ && !( demoplayer->IsPlayingBack() && msg->m_nProtocol >= PROTOCOL_VERSION_12 )
+#endif
+ )
+ {
+ ConMsg ( "Server returned version %i, expected %i.\n", msg->m_nProtocol, PROTOCOL_VERSION );
+ return false;
+ }
+
+ // Parse servercount (i.e., # of servers spawned since server .exe started)
+ // So that we can detect new server startup during download, etc.
+ m_nServerCount = msg->m_nServerCount;
+
+ m_nMaxClients = msg->m_nMaxClients;
+
+ m_nServerClasses = msg->m_nMaxClasses;
+ m_nServerClassBits = Q_log2( m_nServerClasses ) + 1;
+
+ if ( m_nMaxClients < 1 || m_nMaxClients > ABSOLUTE_PLAYER_LIMIT )
+ {
+ ConMsg ("Bad maxclients (%u) from server.\n", m_nMaxClients);
+ return false;
+ }
+
+ if ( m_nServerClasses < 1 || m_nServerClasses > MAX_SERVER_CLASSES )
+ {
+ ConMsg ("Bad maxclasses (%u) from server.\n", m_nServerClasses);
+ return false;
+ }
+
+#ifndef SWDS
+ if ( !sv.IsActive() &&
+ !( m_NetChannel->IsLoopback() || m_NetChannel->IsNull() ) )
+ {
+ // if you are joing a remote server it MUST be multipler and have maxplayer set to more than 1
+ // this prevents a spoofed ServerInfo packet from making the client think its in singleplayer
+ // and turning off a bunch of security checks
+ if ( m_nMaxClients <= 1 )
+ {
+ ConMsg ("Bad maxclients (%u) from server.\n", m_nMaxClients);
+ return false;
+ }
+
+ // reset server enforced cvars
+ g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED );
+
+ // Cheats were disabled; revert all cheat cvars to their default values.
+ // This must be done heading into multiplayer games because people can play
+ // demos etc and set cheat cvars with sv_cheats 0.
+ g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT );
+
+ DevMsg( "FCVAR_CHEAT cvars reverted to defaults.\n" );
+ }
+#endif
+
+ // clear all baselines still around from last game
+ FreeEntityBaselines();
+
+ // force changed flag to being reset
+ g_GameEventManager.HasClientListenersChanged( true );
+
+ m_nPlayerSlot = msg->m_nPlayerSlot;
+ m_nViewEntity = m_nPlayerSlot + 1;
+
+ if ( msg->m_fTickInterval < MINIMUM_TICK_INTERVAL ||
+ msg->m_fTickInterval > MAXIMUM_TICK_INTERVAL )
+ {
+ ConMsg ("Interval_per_tick %f out of range [%f to %f]\n",
+ msg->m_fTickInterval, MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
+ return false;
+ }
+
+ if ( !COM_CheckGameDirectory( msg->m_szGameDir ) )
+ {
+ return false;
+ }
+
+ Q_strncpy( m_szLevelBaseName, msg->m_szMapName, sizeof( m_szLevelBaseName ) );
+
+#if !defined(SWDS)
+ audiosourcecache->LevelInit( m_szLevelBaseName );
+#endif
+
+ ConVarRef skyname( "sv_skyname" );
+ if ( skyname.IsValid() )
+ {
+ skyname.SetValue( msg->m_szSkyName );
+ }
+
+ m_nDeltaTick = -1; // no valid snapshot for this game yet
+
+ // fire a client side event about server data
+
+ IGameEvent *event = g_GameEventManager.CreateEvent( "server_spawn" );
+
+ if ( event )
+ {
+ event->SetString( "hostname", msg->m_szHostName );
+ event->SetString( "address", m_NetChannel->GetRemoteAddress().ToString( true ) );
+ event->SetInt( "ip", m_NetChannel->GetRemoteAddress().GetIPNetworkByteOrder() ); // <<< Network byte order?
+ event->SetInt( "port", m_NetChannel->GetRemoteAddress().GetPort() );
+ event->SetString( "game", msg->m_szGameDir );
+ event->SetString( "mapname", msg->m_szMapName );
+ event->SetInt( "maxplayers", msg->m_nMaxClients );
+ event->SetInt( "password", 0 ); // TODO
+ event->SetString( "os", va("%c", toupper( msg->m_cOS ) ) );
+ event->SetInt( "dedicated", msg->m_bIsDedicated ? 1 : 0 );
+ if ( m_ulGameServerSteamID != 0 )
+ {
+ event->SetString( "steamid", CSteamID(m_ulGameServerSteamID).Render() );
+ }
+
+ g_GameEventManager.FireEventClientSide( event );
+ }
+
+ // Set default filename, but this is finalized by ClientState later, so it should not be depended on yet. See PrepareLevelResources call
+ Host_DefaultMapFileName( msg->m_szMapName, m_szLevelFileName, sizeof( m_szLevelFileName ) );
+
+ COM_TimestampedLog( " CBaseClient::ProcessServerInfo(done)" );
+
+ return true;
+}
+
+bool CBaseClientState::ProcessSendTable( SVC_SendTable *msg )
+{
+ VPROF( "ProcessSendTable" );
+
+ if ( !RecvTable_RecvClassInfos( &msg->m_DataIn, msg->m_bNeedsDecoder ) )
+ {
+ Host_EndGame(true, "ProcessSendTable: RecvTable_RecvClassInfos failed.\n" );
+ return false;
+ }
+
+ return true;
+}
+
+bool CBaseClientState::ProcessClassInfo( SVC_ClassInfo *msg )
+{
+ VPROF( "ProcessClassInfo" );
+
+ COM_TimestampedLog( " CBaseClient::ProcessClassInfo" );
+
+ if ( msg->m_bCreateOnClient )
+ {
+ ConMsg ( "Can't create class tables.\n");
+ Assert( 0 );
+ return false;
+ }
+
+ if( m_pServerClasses )
+ {
+ delete [] m_pServerClasses;
+ }
+
+ m_nServerClasses = msg->m_Classes.Count();
+ m_pServerClasses = new C_ServerClassInfo[m_nServerClasses];
+
+ if ( !m_pServerClasses )
+ {
+ Host_EndGame(true, "ProcessClassInfo: can't allocate %d C_ServerClassInfos.\n", m_nServerClasses);
+ return false;
+ }
+
+ // copy class names and class IDs from message to CClientState
+ for (int i=0; i<m_nServerClasses; i++)
+ {
+ SVC_ClassInfo::class_t * svclass = &msg->m_Classes[ i ];
+
+ if( svclass->classID >= m_nServerClasses )
+ {
+ Host_EndGame(true, "ProcessClassInfo: invalid class index (%d).\n", svclass->classID);
+ return false;
+ }
+
+ C_ServerClassInfo * svclassinfo = &m_pServerClasses[svclass->classID];
+
+ int len = Q_strlen(svclass->classname) + 1;
+ svclassinfo->m_ClassName = new char[ len ];
+ Q_strncpy( svclassinfo->m_ClassName, svclass->classname, len );
+ len = Q_strlen(svclass->datatablename) + 1;
+ svclassinfo->m_DatatableName = new char[ len ];
+ Q_strncpy( svclassinfo->m_DatatableName,svclass->datatablename, len );
+ }
+
+ COM_TimestampedLog( " CBaseClient::ProcessClassInfo(done)" );
+
+ return LinkClasses(); // link server and client classes
+}
+
+bool CBaseClientState::ProcessSetPause( SVC_SetPause *msg )
+{
+ VPROF( "ProcessSetPause" );
+
+ m_bPaused = msg->m_bPaused;
+ return true;
+}
+
+bool CBaseClientState::ProcessSetPauseTimed( SVC_SetPauseTimed *msg )
+{
+ VPROF( "ProcessSetPauseTimed" );
+
+ m_bPaused = msg->m_bPaused;
+ m_flPausedExpireTime = msg->m_flExpireTime;
+ return true;
+}
+
+
+bool CBaseClientState::ProcessCreateStringTable( SVC_CreateStringTable *msg )
+{
+ VPROF( "ProcessCreateStringTable" );
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_PROCESSSTRINGTABLE);
+#endif
+
+ COM_TimestampedLog( " CBaseClient::ProcessCreateStringTable(%s)", msg->m_szTableName );
+ m_StringTableContainer->AllowCreation( true );
+
+ int startbit = msg->m_DataIn.GetNumBitsRead();
+
+#ifndef SHARED_NET_STRING_TABLES
+
+ CNetworkStringTable *table = (CNetworkStringTable*)
+ m_StringTableContainer->CreateStringTableEx( msg->m_szTableName, msg->m_nMaxEntries, msg->m_nUserDataSize, msg->m_nUserDataSizeBits, msg->m_bIsFilenames );
+
+ Assert ( table );
+
+ table->SetTick( GetServerTickCount() ); // set creation tick
+
+ HookClientStringTable( msg->m_szTableName );
+
+ if ( msg->m_bDataCompressed )
+ {
+ unsigned int msgUncompressedSize = msg->m_DataIn.ReadLong();
+ unsigned int msgCompressedSize = msg->m_DataIn.ReadLong();
+ unsigned int uncompressedSize = msgUncompressedSize;
+ /// XXX(JohnS): 11/08/2016 - The PAD_NUMBER() call below was overflowing on UINT32_MAX-3 values. Enforcing
+ // resource-usage limits at this level is a lost cause without a massive overhaul, but clamp these
+ // to somewhat reasonable ranges to prevent overflows with less-audited code in the engine.
+ bool bSuccess = false;
+ if ( msg->m_DataIn.TotalBytesAvailable() > 0 &&
+ msgCompressedSize <= (unsigned int)msg->m_DataIn.TotalBytesAvailable() &&
+ msgCompressedSize < UINT_MAX/2 &&
+ msgUncompressedSize < UINT_MAX/2 )
+ {
+ // allocate buffer for uncompressed data, align to 4 bytes boundary
+ char *uncompressedBuffer = new char[PAD_NUMBER( msgUncompressedSize, 4 )];
+ char *compressedBuffer = new char[PAD_NUMBER( msgCompressedSize, 4 )];
+
+ msg->m_DataIn.ReadBits( compressedBuffer, msgCompressedSize * 8 );
+
+ // uncompress data
+ bSuccess = COM_BufferToBufferDecompress( uncompressedBuffer, &uncompressedSize, compressedBuffer, msgCompressedSize );
+ bSuccess &= ( uncompressedSize == msgUncompressedSize );
+
+ if ( bSuccess )
+ {
+ bf_read data( uncompressedBuffer, uncompressedSize );
+ table->ParseUpdate( data, msg->m_nNumEntries );
+ }
+
+ delete[] uncompressedBuffer;
+ delete[] compressedBuffer;
+ }
+
+ if ( !bSuccess )
+ {
+ Assert( false );
+ Warning("Malformed message in CBaseClientState::ProcessCreateStringTable\n");
+ }
+ }
+ else
+ {
+ table->ParseUpdate( msg->m_DataIn, msg->m_nNumEntries );
+ }
+
+#endif
+
+ m_StringTableContainer->AllowCreation( false );
+
+ int endbit = msg->m_DataIn.GetNumBitsRead();
+
+ COM_TimestampedLog( " CBaseClient::ProcessCreateStringTable(%s)-done", msg->m_szTableName );
+
+ return ( endbit - startbit ) == msg->m_nLength;
+}
+
+bool CBaseClientState::ProcessUpdateStringTable( SVC_UpdateStringTable *msg )
+{
+ VPROF( "ProcessUpdateStringTable" );
+
+ int startbit = msg->m_DataIn.GetNumBitsRead();
+
+#ifndef SHARED_NET_STRING_TABLES
+
+ //m_StringTableContainer is NULL on level transitions, Seems to be caused by a UpdateStringTable packet comming in before the ServerInfo packet
+ // I'm not sure this is safe, but at least we won't crash. The realy odd thing is this can happen on the server as well.//tmauer
+ if(m_StringTableContainer != NULL)
+ {
+ CNetworkStringTable *table = (CNetworkStringTable*)
+ m_StringTableContainer->GetTable( msg->m_nTableID );
+
+ table->ParseUpdate( msg->m_DataIn, msg->m_nChangedEntries );
+ }
+ else
+ {
+ Warning("m_StringTableContainer is NULL in CBaseClientState::ProcessUpdateStringTable\n");
+ }
+
+#endif
+
+ int endbit = msg->m_DataIn.GetNumBitsRead();
+
+ return ( endbit - startbit ) == msg->m_nLength;
+}
+
+
+
+bool CBaseClientState::ProcessSetView( SVC_SetView *msg )
+{
+ VPROF( "ProcessSetView" );
+
+ m_nViewEntity = msg->m_nEntityIndex;
+ return true;
+}
+
+bool CBaseClientState::ProcessPacketEntities( SVC_PacketEntities *msg )
+{
+ VPROF( "ProcessPacketEntities" );
+
+ // First update is the final signon stage where we actually receive an entity (i.e., the world at least)
+
+ if ( m_nSignonState < SIGNONSTATE_SPAWN )
+ {
+ ConMsg("Received packet entities while connecting!\n");
+ return false;
+ }
+
+ if ( m_nSignonState == SIGNONSTATE_SPAWN )
+ {
+ if ( !msg->m_bIsDelta )
+ {
+ // We are done with signon sequence.
+ SetSignonState( SIGNONSTATE_FULL, m_nServerCount );
+ }
+ else
+ {
+ ConMsg("Received delta packet entities while spawing!\n");
+ return false;
+ }
+ }
+
+ // overwrite a -1 delta_tick only if packet was uncompressed
+ if ( (m_nDeltaTick >= 0) || !msg->m_bIsDelta )
+ {
+ // we received this snapshot successfully, now this is our delta reference
+ m_nDeltaTick = GetServerTickCount();
+ }
+
+ return true;
+}
+
+void CBaseClientState::ReadPacketEntities( CEntityReadInfo &u )
+{
+ VPROF( "ReadPacketEntities" );
+
+ // Loop until there are no more entities to read
+
+ u.NextOldEntity();
+
+ while ( u.m_UpdateType < Finished )
+ {
+ u.m_nHeaderCount--;
+
+ u.m_bIsEntity = ( u.m_nHeaderCount >= 0 ) ? true : false;
+
+ if ( u.m_bIsEntity )
+ {
+ CL_ParseDeltaHeader( u );
+ }
+
+ u.m_UpdateType = PreserveEnt;
+
+ while( u.m_UpdateType == PreserveEnt )
+ {
+ // Figure out what kind of an update this is.
+ if( CL_DetermineUpdateType( u ) )
+ {
+ switch( u.m_UpdateType )
+ {
+ case EnterPVS: ReadEnterPVS( u );
+ break;
+
+ case LeavePVS: ReadLeavePVS( u );
+ break;
+
+ case DeltaEnt: ReadDeltaEnt( u );
+ break;
+
+ case PreserveEnt: ReadPreserveEnt( u );
+ break;
+
+ default: DevMsg(1, "ReadPacketEntities: unknown updatetype %i\n", u.m_UpdateType );
+ break;
+ }
+ }
+ }
+ }
+
+ // Now process explicit deletes
+ if ( u.m_bAsDelta && u.m_UpdateType == Finished )
+ {
+ ReadDeletions( u );
+ }
+
+ // Something didn't parse...
+ if ( u.m_pBuf->IsOverflowed() )
+ {
+ Host_Error ( "CL_ParsePacketEntities: buffer read overflow\n" );
+ }
+
+ // If we get an uncompressed packet, then the server is waiting for us to ack the validsequence
+ // that we got the uncompressed packet on. So we stop reading packets here and force ourselves to
+ // send the clc_move on the next frame.
+
+ if ( !u.m_bAsDelta )
+ {
+ m_flNextCmdTime = 0.0; // answer ASAP to confirm full update tick
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pHead -
+// *pClassName -
+// Output : static ClientClass*
+//-----------------------------------------------------------------------------
+ClientClass* CBaseClientState::FindClientClass(const char *pClassName)
+{
+ if ( !pClassName )
+ return NULL;
+
+ for(ClientClass *pCur=ClientDLL_GetAllClasses(); pCur; pCur=pCur->m_pNext)
+ {
+ if( Q_stricmp(pCur->m_pNetworkName, pClassName) == 0)
+ return pCur;
+ }
+
+ return NULL;
+}
+
+
+bool CBaseClientState::LinkClasses()
+{
+// // Verify that we have received info about all classes.
+// for ( int i=0; i < m_nServerClasses; i++ )
+// {
+// if ( !m_pServerClasses[i].m_DatatableName )
+// {
+// Host_EndGame(true, "CL_ParseClassInfo_EndClasses: class %d not initialized.\n", i);
+// return false;
+// }
+// }
+
+ // Match the server classes to the client classes.
+ for ( int i=0; i < m_nServerClasses; i++ )
+ {
+ C_ServerClassInfo *pServerClass = &m_pServerClasses[i];
+ if ( !pServerClass->m_DatatableName )
+ continue;
+
+ // (this can be null in which case we just use default behavior).
+ pServerClass->m_pClientClass = FindClientClass(pServerClass->m_ClassName);
+
+ if ( pServerClass->m_pClientClass )
+ {
+ // If the class names match, then their datatables must match too.
+ // It's ok if the client is missing a class that the server has. In that case,
+ // if the server actually tries to use it, the client will bomb out.
+ const char *pServerName = pServerClass->m_DatatableName;
+ const char *pClientName = pServerClass->m_pClientClass->m_pRecvTable->GetName();
+
+ if ( Q_stricmp( pServerName, pClientName ) != 0 )
+ {
+ Host_EndGame( true, "CL_ParseClassInfo_EndClasses: server and client classes for '%s' use different datatables (server: %s, client: %s)",
+ pServerClass->m_ClassName, pServerName, pClientName );
+
+ return false;
+ }
+
+ // copy class ID
+ pServerClass->m_pClientClass->m_ClassID = i;
+ }
+ else
+ {
+ Msg( "Client missing DT class %s\n", pServerClass->m_ClassName );
+ }
+ }
+
+ return true;
+}
+
+PackedEntity *CBaseClientState::GetEntityBaseline(int iBaseline, int nEntityIndex)
+{
+ Assert( (iBaseline == 0) || (iBaseline == 1) );
+ return m_pEntityBaselines[iBaseline][nEntityIndex];
+}
+
+void CBaseClientState::FreeEntityBaselines()
+{
+ for ( int i=0; i<2; i++ )
+ {
+ for ( int j=0; j<MAX_EDICTS; j++ )
+ if ( m_pEntityBaselines[i][j] )
+ {
+ delete m_pEntityBaselines[i][j];
+ m_pEntityBaselines[i][j] = NULL;
+ }
+ }
+}
+
+void CBaseClientState::SetEntityBaseline(int iBaseline, ClientClass *pClientClass, int index, char *packedData, int length)
+{
+ VPROF( "CBaseClientState::SetEntityBaseline" );
+
+ Assert( index >= 0 && index < MAX_EDICTS );
+ Assert( pClientClass );
+ Assert( (iBaseline == 0) || (iBaseline == 1) );
+
+ PackedEntity *entitybl = m_pEntityBaselines[iBaseline][index];
+
+ if ( !entitybl )
+ {
+ entitybl = m_pEntityBaselines[iBaseline][index] = new PackedEntity();
+ }
+
+ entitybl->m_pClientClass = pClientClass;
+ entitybl->m_nEntityIndex = index;
+ entitybl->m_pServerClass = NULL;
+
+ // Copy out the data we just decoded.
+ entitybl->AllocAndCopyPadded( packedData, length );
+}
+
+void CBaseClientState::CopyEntityBaseline( int iFrom, int iTo )
+{
+ Assert ( iFrom != iTo );
+
+
+ for ( int i=0; i<MAX_EDICTS; i++ )
+ {
+ PackedEntity *blfrom = m_pEntityBaselines[iFrom][i];
+ PackedEntity *blto = m_pEntityBaselines[iTo][i];
+
+ if( !blfrom )
+ {
+ // make sure blto doesn't exists
+ if ( blto )
+ {
+ // ups, we already had this entity but our ack got lost
+ // we have to remove it again to stay in sync
+ delete m_pEntityBaselines[iTo][i];
+ m_pEntityBaselines[iTo][i] = NULL;
+ }
+ continue;
+ }
+
+ if ( !blto )
+ {
+ // create new to baseline if none existed before
+ blto = m_pEntityBaselines[iTo][i] = new PackedEntity();
+ blto->m_pClientClass = NULL;
+ blto->m_pServerClass = NULL;
+ blto->m_ReferenceCount = 0;
+ }
+
+ Assert( blfrom->m_nEntityIndex == i );
+ Assert( !blfrom->IsCompressed() );
+
+ blto->m_nEntityIndex = blfrom->m_nEntityIndex;
+ blto->m_pClientClass = blfrom->m_pClientClass;
+ blto->m_pServerClass = blfrom->m_pServerClass;
+ blto->AllocAndCopyPadded( blfrom->GetData(), blfrom->GetNumBytes() );
+ }
+}
+
+ClientClass *CBaseClientState::GetClientClass( int index )
+{
+ Assert( index < m_nServerClasses );
+ return m_pServerClasses[index].m_pClientClass;
+}
+
+bool CBaseClientState::GetClassBaseline( int iClass, void const **pData, int *pDatalen )
+{
+ ErrorIfNot(
+ iClass >= 0 && iClass < m_nServerClasses,
+ ("GetDynamicBaseline: invalid class index '%d'", iClass) );
+
+ // We lazily update these because if you connect to a server that's already got some dynamic baselines,
+ // you'll get the baselines BEFORE you get the class descriptions.
+ C_ServerClassInfo *pInfo = &m_pServerClasses[iClass];
+
+ INetworkStringTable *pBaselineTable = GetStringTable( INSTANCE_BASELINE_TABLENAME );
+
+ ErrorIfNot( pBaselineTable != NULL, ("GetDynamicBaseline: NULL baseline table" ) );
+
+ if ( pInfo->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
+ {
+ // The key is the class index string.
+ char str[64];
+ Q_snprintf( str, sizeof( str ), "%d", iClass );
+
+ pInfo->m_InstanceBaselineIndex = pBaselineTable->FindStringIndex( str );
+
+ if ( pInfo->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
+ {
+ for (int i = 0; i < pBaselineTable->GetNumStrings(); ++i )
+ {
+ DevMsg( "%i: %s\n", i, pBaselineTable->GetString( i ) );
+ }
+
+ // Gets a callstack, whereas ErrorIfNot(), does not.
+ Assert( 0 );
+ }
+ ErrorIfNot(
+ pInfo->m_InstanceBaselineIndex != INVALID_STRING_INDEX,
+ ("GetDynamicBaseline: FindStringIndex(%s-%s) failed.", str, pInfo->m_ClassName );
+ );
+ }
+ *pData = pBaselineTable->GetStringUserData( pInfo->m_InstanceBaselineIndex, pDatalen );
+
+ return *pData != NULL;
+}
+
+bool CBaseClientState::ProcessGameEventList( SVC_GameEventList *msg )
+{
+ VPROF( "ProcessGameEventList" );
+
+ return g_GameEventManager.ParseEventList( msg );
+}
+
+
+bool CBaseClientState::ProcessGetCvarValue( SVC_GetCvarValue *msg )
+{
+ VPROF( "ProcessGetCvarValue" );
+
+ // Prepare the response.
+ CLC_RespondCvarValue returnMsg;
+
+ returnMsg.m_iCookie = msg->m_iCookie;
+ returnMsg.m_szCvarName = msg->m_szCvarName;
+ returnMsg.m_szCvarValue = "";
+ returnMsg.m_eStatusCode = eQueryCvarValueStatus_CvarNotFound;
+
+ char tempValue[256];
+
+ // Does any ConCommand exist with this name?
+ const ConVar *pVar = g_pCVar->FindVar( msg->m_szCvarName );
+ if ( pVar )
+ {
+ if ( pVar->IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) )
+ {
+ // The server isn't allowed to query this.
+ returnMsg.m_eStatusCode = eQueryCvarValueStatus_CvarProtected;
+ }
+ else
+ {
+ returnMsg.m_eStatusCode = eQueryCvarValueStatus_ValueIntact;
+
+ if ( pVar->IsFlagSet( FCVAR_NEVER_AS_STRING ) )
+ {
+ // The cvar won't store a string, so we have to come up with a string for it ourselves.
+ if ( fabs( pVar->GetFloat() - pVar->GetInt() ) < 0.001f )
+ {
+ Q_snprintf( tempValue, sizeof( tempValue ), "%d", pVar->GetInt() );
+ }
+ else
+ {
+ Q_snprintf( tempValue, sizeof( tempValue ), "%f", pVar->GetFloat() );
+ }
+ returnMsg.m_szCvarValue = tempValue;
+ }
+ else
+ {
+ // The easy case..
+ returnMsg.m_szCvarValue = pVar->GetString();
+ }
+ }
+ }
+ else
+ {
+ if ( g_pCVar->FindCommand( msg->m_szCvarName ) )
+ returnMsg.m_eStatusCode = eQueryCvarValueStatus_NotACvar; // It's a command, not a cvar.
+ else
+ returnMsg.m_eStatusCode = eQueryCvarValueStatus_CvarNotFound;
+ }
+
+ // Send back.
+ m_NetChannel->SendNetMsg( returnMsg );
+ return true;
+}
+
+// Returns dem file protocol version, or, if not playing a demo, just returns PROTOCOL_VERSION
+int CBaseClientState::GetDemoProtocolVersion() const
+{
+#ifndef SWDS // why is demo play undefined? it shuold be fine on a dedicated server
+ if ( demoplayer->IsPlayingBack() )
+ {
+ return demoplayer->GetProtocolVersion();
+ }
+#endif
+ return PROTOCOL_VERSION;
+}
+
+bool CBaseClientState::ProcessCmdKeyValues( SVC_CmdKeyValues *msg )
+{
+ return true;
+}
+
+bool CBaseClientState::IsClientConnectionViaMatchMaking( void )
+{
+ return ( V_strnistr( cl_connectmethod.GetString(), "quickplay", 9 ) || V_strnistr( cl_connectmethod.GetString(), "matchmaking", 11 ) );
+}
+
+