diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/baseclientstate.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/baseclientstate.cpp')
| -rw-r--r-- | engine/baseclientstate.cpp | 1855 |
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 ) ); +} + + |