summaryrefslogtreecommitdiff
path: root/engine/client.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/client.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/client.cpp')
-rw-r--r--engine/client.cpp2077
1 files changed, 2077 insertions, 0 deletions
diff --git a/engine/client.cpp b/engine/client.cpp
new file mode 100644
index 0000000..c209837
--- /dev/null
+++ b/engine/client.cpp
@@ -0,0 +1,2077 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "client_pch.h"
+#include "networkstringtabledefs.h"
+#include <checksum_md5.h>
+#include <iregistry.h>
+#include "pure_server.h"
+#include "netmessages.h"
+#include "cl_demo.h"
+#include "host_state.h"
+#include "host.h"
+#include "gl_matsysiface.h"
+#include "vgui_baseui_interface.h"
+#include "tier0/icommandline.h"
+#include <proto_oob.h>
+#include "checksum_engine.h"
+#include "filesystem_engine.h"
+#include "logofile_shared.h"
+#include "sound.h"
+#include "decal.h"
+#include "networkstringtableclient.h"
+#include "dt_send_eng.h"
+#include "ents_shared.h"
+#include "cl_ents_parse.h"
+#include "cl_entityreport.h"
+#include "MapReslistGenerator.h"
+#include "DownloadListGenerator.h"
+#include "GameEventManager.h"
+#include "host_phonehome.h"
+#include "vgui_baseui_interface.h"
+#include "clockdriftmgr.h"
+#include "snd_audio_source.h"
+#include "vgui_controls/Controls.h"
+#include "vgui/ILocalize.h"
+#include "download.h"
+#include "checksum_engine.h"
+#include "ModelInfo.h"
+#include "materialsystem/imaterial.h"
+#include "materialsystem/materialsystem_config.h"
+#include "tier1/fmtstr.h"
+#include "cl_steamauth.h"
+#include "matchmaking.h"
+#include "server.h"
+#include "eiface.h"
+
+#include "tier0/platform.h"
+#include "tier0/systeminformation.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static ConVar cl_timeout( "cl_timeout", "30", FCVAR_ARCHIVE, "After this many seconds without receiving a packet from the server, the client will disconnect itself" );
+ ConVar cl_logofile( "cl_logofile", "materials/decals/spraylogo.vtf", FCVAR_ARCHIVE, "Spraypoint logo decal." ); // TODO must be more generic
+static ConVar cl_soundfile( "cl_soundfile", "sound/player/jingle.wav", FCVAR_ARCHIVE, "Jingle sound file." );
+static ConVar cl_allowdownload ( "cl_allowdownload", "1", FCVAR_ARCHIVE, "Client downloads customization files" );
+static ConVar cl_downloadfilter( "cl_downloadfilter", "all", FCVAR_ARCHIVE, "Determines which files can be downloaded from the server (all, none, nosounds, mapsonly)" );
+
+#ifdef OSX
+ // OS X is barely making it due to virtual memory pressure on 32bit, our behavior of load new models -> unload
+ // unused is far too abusive for its estimated margin of maybe two or three bytes before crashing.
+ #define CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS "1"
+#else
+ #define CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS "0"
+#endif
+static ConVar cl_always_flush_models( "cl_always_flush_models", CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS, FCVAR_INTERNAL_USE,
+ "If set, always flush models between map loads. Useful on systems under memory pressure." );
+
+extern ConVar sv_downloadurl;
+
+#if defined( _DEBUG ) || defined( STAGING_ONLY )
+static ConVar debug_clientstate_fake_hltv( "debug_clientstate_fake_hltv", "0", 0, "If set, spoof as HLTV for testing purposes." );
+#if defined( REPLAY_ENABLED )
+static ConVar debug_clientstate_fake_replay( "debug_clientstate_fake_replay", "0", 0, "If set, spoof as REPLAY for testing purposes." );
+#endif // defined( REPLAY_ENABLED )
+#endif // defined( _DEBUG ) || defined( STAGING_ONLY )
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+CClientState::CClientState()
+{
+ m_bMarkedCRCsUnverified = false;
+ demonum = -1;
+ m_tickRemainder = 0;
+ m_frameTime = 0;
+ m_pAreaBits = NULL;
+ m_hWaitForResourcesHandle = NULL;
+ m_bUpdateSteamResources = false;
+ m_bPrepareClientDLL = false;
+ m_bShownSteamResourceUpdateProgress = false;
+ m_pPureServerWhitelist = NULL;
+ m_pPendingPureFileReloads = NULL;
+ m_bCheckCRCsWithServer = false;
+ m_flLastCRCBatchTime = 0;
+ m_nFriendsID = 0;
+ m_FriendsName[0] = 0;
+#if defined( REPLAY_ENABLED )
+ isreplay = false;
+#endif
+}
+
+CClientState::~CClientState()
+{
+ if ( m_pPureServerWhitelist )
+ m_pPureServerWhitelist->Release();
+}
+
+// HL1 CD Key
+#define GUID_LEN 13
+
+/*
+=======================
+CL_GetCDKeyHash()
+
+Connections will now use a hashed cd key value
+A LAN server will know not to allows more then xxx users with the same CD Key
+=======================
+*/
+const char *CClientState::GetCDKeyHash( void )
+{
+ if ( IsPC() )
+ {
+ char szKeyBuffer[256]; // Keys are about 13 chars long.
+ static char szHashedKeyBuffer[64];
+ int nKeyLength;
+ bool bDedicated = false;
+
+ MD5Context_t ctx;
+ unsigned char digest[16]; // The MD5 Hash
+
+ nKeyLength = Q_snprintf( szKeyBuffer, sizeof( szKeyBuffer ), "%s", registry->ReadString( "key", "" ) );
+
+ if (bDedicated)
+ {
+ ConMsg("Key has no meaning on dedicated server...\n");
+ return "";
+ }
+
+ if ( nKeyLength == 0 )
+ {
+ nKeyLength = 13;
+ Q_strncpy( szKeyBuffer, "1234567890123", sizeof( szKeyBuffer ) );
+ Assert( Q_strlen( szKeyBuffer ) == nKeyLength );
+
+ DevMsg( "Missing CD Key from registry, inserting blank key\n" );
+
+ registry->WriteString( "key", szKeyBuffer );
+ }
+
+ if (nKeyLength <= 0 ||
+ nKeyLength >= 256 )
+ {
+ ConMsg("Bogus key length on CD Key...\n");
+ return "";
+ }
+
+ // Now get the md5 hash of the key
+ memset( &ctx, 0, sizeof( ctx ) );
+ memset( digest, 0, sizeof( digest ) );
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, (unsigned char*)szKeyBuffer, nKeyLength);
+ MD5Final(digest, &ctx);
+ Q_strncpy ( szHashedKeyBuffer, MD5_Print ( digest, sizeof( digest ) ), sizeof( szHashedKeyBuffer ) );
+ return szHashedKeyBuffer;
+ }
+
+ return "12345678901234567890123456789012";
+}
+
+void CClientState::SendClientInfo( void )
+{
+ CLC_ClientInfo info;
+
+ info.m_nSendTableCRC = SendTable_GetCRC();
+ info.m_nServerCount = m_nServerCount;
+ info.m_bIsHLTV = false;
+#if defined( REPLAY_ENABLED )
+ info.m_bIsReplay = false;
+#endif
+#if !defined( NO_STEAM )
+ info.m_nFriendsID = Steam3Client().SteamUser() ? Steam3Client().SteamUser()->GetSteamID().GetAccountID() : 0;
+#else
+ info.m_nFriendsID = 0;
+#endif
+ Q_strncpy( info.m_FriendsName, m_FriendsName, sizeof(info.m_FriendsName) );
+
+ CheckOwnCustomFiles(); // load & verfiy custom player files
+
+ for ( int i=0; i< MAX_CUSTOM_FILES; i++ )
+ info.m_nCustomFiles[i] = m_nCustomFiles[i].crc;
+
+ // Testing to ensure we don't blow up servers by faking our client info
+#if defined( _DEBUG ) || defined( STAGING_ONLY )
+ if ( debug_clientstate_fake_hltv.GetBool() )
+ {
+ Msg( "!! Spoofing connect as HLTV in SendClientInfo\n" );
+ info.m_bIsHLTV = true;
+ }
+#if defined( REPLAY_ENABLED )
+ if ( debug_clientstate_fake_replay.GetBool() )
+ {
+ Msg( "!! Spoofing connect as REPLAY in SendClientInfo\n" );
+ info.m_bIsReplay = true;
+ }
+#endif // defined( REPLAY_ENABLED )
+#endif // defined( _DEBUG ) || defined( STAGING_ONLY )
+ m_NetChannel->SendNetMsg( info );
+}
+
+void CClientState::SendServerCmdKeyValues( KeyValues *pKeyValues )
+{
+ if ( !pKeyValues )
+ return;
+
+ CLC_CmdKeyValues clcCommand( pKeyValues );
+
+ if ( !m_NetChannel )
+ return;
+
+ m_NetChannel->SendNetMsg( clcCommand );
+}
+
+extern IVEngineClient *engineClient;
+
+//-----------------------------------------------------------------------------
+// Purpose: A svc_signonnum has been received, perform a client side setup
+// Output : void CL_SignonReply
+//-----------------------------------------------------------------------------
+bool CClientState::SetSignonState ( int state, int count )
+{
+ if ( !CBaseClientState::SetSignonState( state, count ) )
+ {
+ CL_Retry();
+ return false;
+ }
+
+ // ConDMsg ("Signon state: %i\n", state );
+
+ COM_TimestampedLog( "CClientState::SetSignonState: start %i", state );
+
+ switch ( m_nSignonState )
+ {
+ case SIGNONSTATE_CHALLENGE :
+ m_bMarkedCRCsUnverified = false; // Remember that we just connected to a new server so it'll
+ // reverify any necessary file CRCs on this server.
+ EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCHALLENGE);
+ break;
+
+ case SIGNONSTATE_CONNECTED :
+ {
+ EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCONNECTED);
+
+ // make sure it's turned off when connecting
+ EngineVGui()->HideDebugSystem();
+
+ SCR_BeginLoadingPlaque ();
+ // Clear channel and stuff
+ m_NetChannel->Clear();
+
+ // allow longer timeout
+ m_NetChannel->SetTimeout( SIGNON_TIME_OUT );
+ m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD );
+
+ // set user settings (rate etc)
+ NET_SetConVar convars;
+ Host_BuildConVarUpdateMessage( &convars, FCVAR_USERINFO, false );
+ m_NetChannel->SendNetMsg( convars );
+ }
+ break;
+
+ case SIGNONSTATE_NEW :
+ {
+ EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONNEW);
+
+ if ( IsPC() && !demoplayer->IsPlayingBack() )
+ {
+ // start making sure we have all the specified resources
+ StartUpdatingSteamResources();
+ }
+ else
+ {
+ // during demo playback dont try to download resource
+ FinishSignonState_New();
+ }
+
+ // don't tell the server yet that we've entered this state
+ return true;
+ }
+ break;
+
+ case SIGNONSTATE_PRESPAWN :
+ m_nSoundSequence = 1; // reset sound sequence number after receiving signon sounds
+ break;
+
+ case SIGNONSTATE_SPAWN :
+ {
+ Assert( g_ClientDLL );
+
+ EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONSPAWN);
+
+ // Tell client .dll about the transition
+ char mapname[256];
+ CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) );
+
+ COM_TimestampedLog( "LevelInitPreEntity: start %d", state );
+ g_ClientDLL->LevelInitPreEntity(mapname);
+ COM_TimestampedLog( "LevelInitPreEntity: end %d", state );
+
+ phonehome->Message( IPhoneHome::PHONE_MSG_MAPSTART, mapname );
+
+ audiosourcecache->LevelInit( mapname );
+
+ // stop recording demo header
+ demorecorder->SetSignonState( SIGNONSTATE_SPAWN );
+ }
+ break;
+
+ case SIGNONSTATE_FULL:
+ {
+ CL_FullyConnected();
+ if ( m_NetChannel )
+ {
+ m_NetChannel->SetTimeout( cl_timeout.GetFloat() );
+ m_NetChannel->SetMaxBufferSize( true, NET_MAX_DATAGRAM_PAYLOAD );
+ }
+
+ HostState_OnClientConnected();
+
+ if ( m_nMaxClients > 1 )
+ {
+ g_pMatchmaking->AddLocalPlayersToTeams();
+ }
+ }
+ break;
+
+ case SIGNONSTATE_CHANGELEVEL:
+ m_NetChannel->SetTimeout( SIGNON_TIME_OUT ); // allow 5 minutes timeout
+ if ( m_nMaxClients > 1 )
+ {
+ // start progress bar immediately for multiplayer level transitions
+ EngineVGui()->EnabledProgressBarForNextLoad();
+ }
+ SCR_BeginLoadingPlaque();
+ if ( m_nMaxClients > 1 )
+ {
+ EngineVGui()->UpdateProgressBar(PROGRESS_CHANGELEVEL);
+ }
+ break;
+ }
+
+ COM_TimestampedLog( "CClientState::SetSignonState: end %i", state );
+
+ if ( state >= SIGNONSTATE_CONNECTED && m_NetChannel )
+ {
+ // tell server that we entered now that state
+ m_NetChannel->SendNetMsg( NET_SignonState( state, count) );
+ }
+
+ return true;
+}
+
+bool CClientState::HookClientStringTable( char const *tableName )
+{
+ INetworkStringTable *table = GetStringTable( tableName );
+ if ( !table )
+ {
+ // If engine takes a pass, allow client dll to hook in its callbacks
+ if ( g_ClientDLL )
+ {
+ g_ClientDLL->InstallStringTableCallback( tableName );
+ }
+ return false;
+ }
+
+#if 0
+ // This was added into staging at some point and is not enabled in main or rel.
+ char szDownloadableFileTablename[255] = DOWNLOADABLE_FILE_TABLENAME;
+ char szModelPrecacheTablename[255] = MODEL_PRECACHE_TABLENAME;
+ char szGenericPrecacheTablename[255] = GENERIC_PRECACHE_TABLENAME;
+ char szSoundPrecacheTablename[255] = SOUND_PRECACHE_TABLENAME;
+ char szDecalPrecacheTablename[255] = DECAL_PRECACHE_TABLENAME;
+
+ Q_snprintf( szDownloadableFileTablename, 255, ":%s", DOWNLOADABLE_FILE_TABLENAME );
+ Q_snprintf( szModelPrecacheTablename, 255, ":%s", MODEL_PRECACHE_TABLENAME );
+ Q_snprintf( szGenericPrecacheTablename, 255, ":%s", GENERIC_PRECACHE_TABLENAME );
+ Q_snprintf( szSoundPrecacheTablename, 255, ":%s", SOUND_PRECACHE_TABLENAME );
+ Q_snprintf( szDecalPrecacheTablename, 255, ":%s", DECAL_PRECACHE_TABLENAME );
+#else
+ const char *szDownloadableFileTablename = DOWNLOADABLE_FILE_TABLENAME;
+ const char *szModelPrecacheTablename = MODEL_PRECACHE_TABLENAME;
+ const char *szGenericPrecacheTablename = GENERIC_PRECACHE_TABLENAME;
+ const char *szSoundPrecacheTablename = SOUND_PRECACHE_TABLENAME;
+ const char *szDecalPrecacheTablename = DECAL_PRECACHE_TABLENAME;
+#endif
+
+ // Hook Model Precache table
+ if ( !Q_strcasecmp( tableName, szModelPrecacheTablename ) )
+ {
+ m_pModelPrecacheTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szGenericPrecacheTablename ) )
+ {
+ m_pGenericPrecacheTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szSoundPrecacheTablename ) )
+ {
+ m_pSoundPrecacheTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szDecalPrecacheTablename ) )
+ {
+ // Cache the id
+ m_pDecalPrecacheTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) )
+ {
+ // Cache the id
+ m_pInstanceBaselineTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) )
+ {
+ // Cache the id
+ m_pLightStyleTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) )
+ {
+ // Cache the id
+ m_pUserInfoTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) )
+ {
+ // Cache the id
+ m_pServerStartupTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szDownloadableFileTablename ) )
+ {
+ // Cache the id
+ m_pDownloadableFileTable = table;
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, "DynamicModels" ) )
+ {
+ m_pDynamicModelsTable = table;
+ return true;
+ }
+
+ // If engine takes a pass, allow client dll to hook in its callbacks
+ g_ClientDLL->InstallStringTableCallback( tableName );
+
+ return false;
+}
+
+bool CClientState::InstallEngineStringTableCallback( char const *tableName )
+{
+ INetworkStringTable *table = GetStringTable( tableName );
+
+ if ( !table )
+ return false;
+
+#if 0
+ // This was added into staging at some point and is not enabled in main or rel.
+ char szDownloadableFileTablename[255] = DOWNLOADABLE_FILE_TABLENAME;
+ char szModelPrecacheTablename[255] = MODEL_PRECACHE_TABLENAME;
+ char szGenericPrecacheTablename[255] = GENERIC_PRECACHE_TABLENAME;
+ char szSoundPrecacheTablename[255] = SOUND_PRECACHE_TABLENAME;
+ char szDecalPrecacheTablename[255] = DECAL_PRECACHE_TABLENAME;
+
+ Q_snprintf( szDownloadableFileTablename, 255, ":%s", DOWNLOADABLE_FILE_TABLENAME );
+ Q_snprintf( szModelPrecacheTablename, 255, ":%s", MODEL_PRECACHE_TABLENAME );
+ Q_snprintf( szGenericPrecacheTablename, 255, ":%s", GENERIC_PRECACHE_TABLENAME );
+ Q_snprintf( szSoundPrecacheTablename, 255, ":%s", SOUND_PRECACHE_TABLENAME );
+ Q_snprintf( szDecalPrecacheTablename, 255, ":%s", DECAL_PRECACHE_TABLENAME );
+#else
+ const char *szDownloadableFileTablename = DOWNLOADABLE_FILE_TABLENAME;
+ const char *szModelPrecacheTablename = MODEL_PRECACHE_TABLENAME;
+ const char *szGenericPrecacheTablename = GENERIC_PRECACHE_TABLENAME;
+ const char *szSoundPrecacheTablename = SOUND_PRECACHE_TABLENAME;
+ const char *szDecalPrecacheTablename = DECAL_PRECACHE_TABLENAME;
+#endif
+
+ // Hook Model Precache table
+ if ( !Q_strcasecmp( tableName, szModelPrecacheTablename ) )
+ {
+ table->SetStringChangedCallback( NULL, Callback_ModelChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szGenericPrecacheTablename ) )
+ {
+ // Install the callback
+ table->SetStringChangedCallback( NULL, Callback_GenericChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szSoundPrecacheTablename ) )
+ {
+ // Install the callback
+ table->SetStringChangedCallback( NULL, Callback_SoundChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szDecalPrecacheTablename ) )
+ {
+ // Install the callback
+ table->SetStringChangedCallback( NULL, Callback_DecalChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) )
+ {
+ // Install the callback (already done above)
+ table->SetStringChangedCallback( NULL, Callback_InstanceBaselineChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) )
+ {
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) )
+ {
+ // Install the callback
+ table->SetStringChangedCallback( NULL, Callback_UserInfoChanged );
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) )
+ {
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, szDownloadableFileTablename ) )
+ {
+ return true;
+ }
+
+ if ( !Q_strcasecmp( tableName, "DynamicModels" ) )
+ {
+ table->SetStringChangedCallback( NULL, Callback_DynamicModelsChanged );
+ m_pDynamicModelsTable = table;
+ return true;
+ }
+
+ // The the client.dll have a shot at it
+ return false;
+}
+
+void CClientState::InstallStringTableCallback( char const *tableName )
+{
+ // Let engine hook callbacks before we read in any data values at all
+ if ( !InstallEngineStringTableCallback( tableName ) )
+ {
+ // If engine takes a pass, allow client dll to hook in its callbacks
+ g_ClientDLL->InstallStringTableCallback( tableName );
+ }
+}
+
+bool CClientState::IsPaused() const
+{
+ return m_bPaused || ( g_LostVideoMemory && Host_IsSinglePlayerGame() ) ||
+ !host_initialized ||
+ demoplayer->IsPlaybackPaused() ||
+ EngineVGui()->ShouldPause();
+}
+
+float CClientState::GetTime() const
+{
+ int nTickCount = GetClientTickCount();
+ float flTickTime = nTickCount * host_state.interval_per_tick;
+
+ // Timestamps are rounded to exact tick during simulation
+ if ( insimulation )
+ {
+ return flTickTime;
+ }
+
+ return flTickTime + m_tickRemainder;
+}
+
+float CClientState::GetFrameTime() const
+{
+ if ( CClockDriftMgr::IsClockCorrectionEnabled() )
+ {
+ return IsPaused() ? 0 : m_frameTime;
+ }
+ else
+ {
+ if ( insimulation )
+ {
+ int nElapsedTicks = ( GetClientTickCount() - oldtickcount );
+ return nElapsedTicks * host_state.interval_per_tick;
+ }
+ else
+ {
+ return IsPaused() ? 0 : m_frameTime;
+ }
+ }
+}
+
+float CClientState::GetClientInterpAmount()
+{
+ // we need client cvar cl_interp_ratio
+ static const ConVar *s_cl_interp_ratio = NULL;
+ if ( !s_cl_interp_ratio )
+ {
+ s_cl_interp_ratio = g_pCVar->FindVar( "cl_interp_ratio" );
+ if ( !s_cl_interp_ratio )
+ return 0.1f;
+ }
+ static const ConVar *s_cl_interp = NULL;
+ if ( !s_cl_interp )
+ {
+ s_cl_interp = g_pCVar->FindVar( "cl_interp" );
+ if ( !s_cl_interp )
+ return 0.1f;
+ }
+
+ float flInterpRatio = s_cl_interp_ratio->GetFloat();
+ float flInterp = s_cl_interp->GetFloat();
+
+ const ConVar_ServerBounded *pBounded = static_cast<const ConVar_ServerBounded*>( s_cl_interp_ratio );
+ if ( pBounded )
+ flInterpRatio = pBounded->GetFloat();
+ //#define FIXME_INTERP_RATIO
+ return max( flInterpRatio / cl_updaterate->GetFloat(), flInterp );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: // Clear all the variables in the CClientState.
+//-----------------------------------------------------------------------------
+void CClientState::Clear( void )
+{
+ CBaseClientState::Clear();
+
+ m_pModelPrecacheTable = NULL;
+ m_pGenericPrecacheTable = NULL;
+ m_pSoundPrecacheTable = NULL;
+ m_pDecalPrecacheTable = NULL;
+ m_pInstanceBaselineTable = NULL;
+ m_pLightStyleTable = NULL;
+ m_pUserInfoTable = NULL;
+ m_pServerStartupTable = NULL;
+ m_pDynamicModelsTable = NULL;
+ m_pAreaBits = NULL;
+
+ // Clear all download vars.
+ m_pDownloadableFileTable = NULL;
+ m_hWaitForResourcesHandle = NULL;
+ m_bUpdateSteamResources = false;
+ m_bShownSteamResourceUpdateProgress = false;
+ m_bDownloadResources = false;
+ m_bPrepareClientDLL = false;
+
+ DeleteClientFrames( -1 ); // clear all
+
+ viewangles.Init();
+ m_flLastServerTickTime = 0.0f;
+ oldtickcount = 0;
+ insimulation = false;
+
+
+ addangle.RemoveAll();
+ addangletotal = 0.0f;
+ prevaddangletotal = 0.0f;
+
+ memset(model_precache, 0, sizeof(model_precache));
+ memset(sound_precache, 0, sizeof(sound_precache));
+ ishltv = false;
+#if defined( REPLAY_ENABLED )
+ isreplay = false;
+#endif
+ cdtrack = 0;
+ V_memset( serverMD5.bits, 0, MD5_DIGEST_LENGTH );
+ last_command_ack = 0;
+ command_ack = 0;
+ m_nSoundSequence = 0;
+
+ // make sure the client isn't active anymore, but stay
+ // connected if we are.
+ if ( m_nSignonState > SIGNONSTATE_CONNECTED )
+ {
+ m_nSignonState = SIGNONSTATE_CONNECTED;
+ }
+}
+
+void CClientState::ClearSounds()
+{
+ int c = ARRAYSIZE( sound_precache );
+ for ( int i = 0; i < c; ++i )
+ {
+ sound_precache[ i ].SetSound( NULL );
+ }
+}
+
+bool CClientState::ProcessConnectionlessPacket( netpacket_t *packet )
+{
+ Assert( packet );
+
+ return CBaseClientState::ProcessConnectionlessPacket( packet );
+}
+
+void CClientState::FullConnect( netadr_t &adr )
+{
+ CBaseClientState::FullConnect( adr );
+ m_NetChannel->SetDemoRecorder( g_pClientDemoRecorder );
+ m_NetChannel->SetDataRate( cl_rate->GetFloat() );
+
+ // Not in the demo loop now
+ demonum = -1;
+
+ // We don't have a backed up cmd history yet
+ lastoutgoingcommand = -1;
+
+ // we didn't send commands yet
+ chokedcommands = 0;
+
+ // Report connection success.
+ if ( Q_stricmp("loopback", adr.ToString() ) )
+ {
+ ConMsg( "Connected to %s\n", adr.ToString() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : model_t
+//-----------------------------------------------------------------------------
+model_t *CClientState::GetModel( int index )
+{
+ if ( !m_pModelPrecacheTable )
+ {
+ return NULL;
+ }
+
+ if ( index <= 0 )
+ {
+ return NULL;
+ }
+
+ if ( index >= m_pModelPrecacheTable->GetNumStrings() )
+ {
+ Assert( 0 ); // model index for unkown model requested
+ return NULL;
+ }
+
+ CPrecacheItem *p = &model_precache[ index ];
+ model_t *m = p->GetModel();
+ if ( m )
+ {
+ return m;
+ }
+
+ // The world model has special handling and should not be lazy loaded here
+ if ( index == 1 )
+ {
+ Assert( false );
+ Warning( "Attempting to get world model before it was loaded\n" );
+ return NULL;
+ }
+
+ char const *name = m_pModelPrecacheTable->GetString( index );
+
+ if ( host_showcachemiss.GetBool() )
+ {
+ ConDMsg( "client model cache miss on %s\n", name );
+ }
+
+ m = modelloader->GetModelForName( name, IModelLoader::FMODELLOADER_CLIENT );
+ if ( !m )
+ {
+ const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pModelPrecacheTable, index );
+ if ( data && ( data->flags & RES_FATALIFMISSING ) )
+ {
+ COM_ExplainDisconnection( true, "Cannot continue without model %s, disconnecting\n", name );
+ Host_Disconnect( true, "Missing model" );
+ }
+ }
+
+ p->SetModel( m );
+ return m;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : int -- note -1 if missing
+//-----------------------------------------------------------------------------
+int CClientState::LookupModelIndex( char const *name )
+{
+ if ( !m_pModelPrecacheTable )
+ {
+ return -1;
+ }
+ int idx = m_pModelPrecacheTable->FindStringIndex( name );
+ return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// *name -
+//-----------------------------------------------------------------------------
+void CClientState::SetModel( int tableIndex )
+{
+ if ( !m_pModelPrecacheTable )
+ {
+ return;
+ }
+
+ // Bogus index
+ if ( tableIndex < 0 || tableIndex >= m_pModelPrecacheTable->GetNumStrings() )
+ {
+ return;
+ }
+
+ char const *name = m_pModelPrecacheTable->GetString( tableIndex );
+
+ if ( tableIndex == 1 )
+ {
+ // The world model must match the LevelFileName -- it is the path we just checked the CRC for, and paths may differ
+ // from what the server is using based on what the gameDLL override does
+ name = m_szLevelFileName;
+ }
+
+ CPrecacheItem *p = &model_precache[ tableIndex ];
+ const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pModelPrecacheTable, tableIndex );
+
+ bool bLoadNow = ( data && ( data->flags & RES_PRELOAD ) ) || IsX360();
+ if ( CommandLine()->FindParm( "-nopreload" ) || CommandLine()->FindParm( "-nopreloadmodels" ))
+ {
+ bLoadNow = false;
+ }
+ else if ( CommandLine()->FindParm( "-preload" ) )
+ {
+ bLoadNow = true;
+ }
+
+ if ( bLoadNow )
+ {
+ p->SetModel( modelloader->GetModelForName( name, IModelLoader::FMODELLOADER_CLIENT ) );
+ }
+ else
+ {
+ p->SetModel( NULL );
+ }
+
+ // log the file reference, if necssary
+ if (MapReslistGenerator().IsEnabled())
+ {
+ name = m_pModelPrecacheTable->GetString( tableIndex );
+ MapReslistGenerator().OnModelPrecached( name );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : model_t
+//-----------------------------------------------------------------------------
+char const *CClientState::GetGeneric( int index )
+{
+ if ( !m_pGenericPrecacheTable )
+ {
+ Warning( "Can't GetGeneric( %d ), no precache table [no level loaded?]\n", index );
+ return "";
+ }
+
+ if ( index <= 0 )
+ return "";
+
+ if ( index >= m_pGenericPrecacheTable->GetNumStrings() )
+ {
+ return "";
+ }
+
+ CPrecacheItem *p = &generic_precache[ index ];
+ char const *g = p->GetGeneric();
+ return g;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : int -- note -1 if missing
+//-----------------------------------------------------------------------------
+int CClientState::LookupGenericIndex( char const *name )
+{
+ if ( !m_pGenericPrecacheTable )
+ {
+ Warning( "Can't LookupGenericIndex( %s ), no precache table [no level loaded?]\n", name );
+ return -1;
+ }
+ int idx = m_pGenericPrecacheTable->FindStringIndex( name );
+ return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// *name -
+//-----------------------------------------------------------------------------
+void CClientState::SetGeneric( int tableIndex )
+{
+ if ( !m_pGenericPrecacheTable )
+ {
+ Warning( "Can't SetGeneric( %d ), no precache table [no level loaded?]\n", tableIndex );
+ return;
+ }
+ // Bogus index
+ if ( tableIndex < 0 ||
+ tableIndex >= m_pGenericPrecacheTable->GetNumStrings() )
+ {
+ return;
+ }
+
+ char const *name = m_pGenericPrecacheTable->GetString( tableIndex );
+ CPrecacheItem *p = &generic_precache[ tableIndex ];
+ p->SetGeneric( name );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CClientState::GetSoundName( int index )
+{
+ if ( index <= 0 || !m_pSoundPrecacheTable )
+ return "";
+
+ if ( index >= m_pSoundPrecacheTable->GetNumStrings() )
+ {
+ return "";
+ }
+
+ char const *name = m_pSoundPrecacheTable->GetString( index );
+ return name;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : model_t
+//-----------------------------------------------------------------------------
+CSfxTable *CClientState::GetSound( int index )
+{
+ if ( index <= 0 || !m_pSoundPrecacheTable )
+ return NULL;
+
+ if ( index >= m_pSoundPrecacheTable->GetNumStrings() )
+ {
+ return NULL;
+ }
+
+ CPrecacheItem *p = &sound_precache[ index ];
+ CSfxTable *s = p->GetSound();
+ if ( s )
+ return s;
+
+ char const *name = m_pSoundPrecacheTable->GetString( index );
+
+ if ( host_showcachemiss.GetBool() )
+ {
+ ConDMsg( "client sound cache miss on %s\n", name );
+ }
+
+ s = S_PrecacheSound( name );
+
+ p->SetSound( s );
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : int -- note -1 if missing
+//-----------------------------------------------------------------------------
+int CClientState::LookupSoundIndex( char const *name )
+{
+ if ( !m_pSoundPrecacheTable )
+ return -1;
+
+ int idx = m_pSoundPrecacheTable->FindStringIndex( name );
+ return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// *name -
+//-----------------------------------------------------------------------------
+void CClientState::SetSound( int tableIndex )
+{
+ // Bogus index
+ if ( !m_pSoundPrecacheTable )
+ return;
+
+ if ( tableIndex < 0 || tableIndex >= m_pSoundPrecacheTable->GetNumStrings() )
+ {
+ return;
+ }
+
+ CPrecacheItem *p = &sound_precache[ tableIndex ];
+ const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pSoundPrecacheTable, tableIndex );
+
+ bool bLoadNow = ( data && ( data->flags & RES_PRELOAD ) ) || IsX360();
+ if ( CommandLine()->FindParm( "-nopreload" ) || CommandLine()->FindParm( "-nopreloadsounds" ))
+ {
+ bLoadNow = false;
+ }
+ else if ( CommandLine()->FindParm( "-preload" ) )
+ {
+ bLoadNow = true;
+ }
+
+ if ( bLoadNow )
+ {
+ char const *name = m_pSoundPrecacheTable->GetString( tableIndex );
+ p->SetSound( S_PrecacheSound( name ) );
+ }
+ else
+ {
+ p->SetSound( NULL );
+ }
+
+ // log the file reference, if necssary
+ if (MapReslistGenerator().IsEnabled())
+ {
+ char const *name = m_pSoundPrecacheTable->GetString( tableIndex );
+ MapReslistGenerator().OnSoundPrecached( name );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// Output : model_t
+//-----------------------------------------------------------------------------
+char const *CClientState::GetDecalName( int index )
+{
+ if ( index <= 0 || !m_pDecalPrecacheTable )
+ {
+ return NULL;
+ }
+
+ if ( index >= m_pDecalPrecacheTable->GetNumStrings() )
+ {
+ return NULL;
+ }
+
+ CPrecacheItem *p = &decal_precache[ index ];
+ char const *d = p->GetDecal();
+ return d;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// *name -
+//-----------------------------------------------------------------------------
+void CClientState::SetDecal( int tableIndex )
+{
+ if ( !m_pDecalPrecacheTable )
+ return;
+
+ if ( tableIndex < 0 ||
+ tableIndex >= m_pDecalPrecacheTable->GetNumStrings() )
+ {
+ return;
+ }
+
+ char const *name = m_pDecalPrecacheTable->GetString( tableIndex );
+ CPrecacheItem *p = &decal_precache[ tableIndex ];
+ p->SetDecal( name );
+
+ Draw_DecalSetName( tableIndex, (char *)name );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: sets friends info locally to be sent to other users
+//-----------------------------------------------------------------------------
+void CClientState::SetFriendsID( uint friendsID, const char *friendsName )
+{
+ m_nFriendsID = friendsID;
+ Q_strncpy( m_FriendsName, friendsName, sizeof(m_FriendsName) );
+}
+
+
+void CClientState::CheckOthersCustomFile( CRC32_t crcValue )
+{
+ if ( crcValue == 0 )
+ return; // not a valid custom file
+
+ if ( !cl_allowdownload.GetBool() )
+ return; // client doesn't want to download anything
+
+ CCustomFilename filehex( crcValue );
+
+ if ( g_pFileSystem->FileExists( filehex.m_Filename, "game" ) )
+ return; // we already have this file (assuming the CRC is correct)
+
+ // we don't have it, request download from server
+ m_NetChannel->RequestFile( filehex.m_Filename );
+}
+
+void CClientState::AddCustomFile( int slot, const char *resourceFile)
+{
+ if ( Q_strlen(resourceFile) <= 0 )
+ return; // no resource file given
+
+ if ( !COM_IsValidPath( resourceFile ) )
+ {
+ Msg("Customization file '%s' has invalid path.\n", resourceFile );
+ return;
+ }
+
+ if ( slot < 0 || slot >= MAX_CUSTOM_FILES )
+ return; // wrong slot
+
+ if ( !g_pFileSystem->FileExists( resourceFile ) )
+ {
+ DevMsg("Couldn't find customization file '%s'.\n", resourceFile );
+ return; // resource file doesn't exits
+ }
+
+ if ( g_pFileSystem->Size( resourceFile ) > MAX_CUSTOM_FILE_SIZE )
+ {
+ Msg("Customization file '%s' is too big ( >%i bytes).\n", resourceFile, MAX_CUSTOM_FILE_SIZE );
+ return; // resource file doesn't exits
+ }
+
+ CRC32_t crcValue;
+
+ // Compute checksum of resource file
+ CRC_File( &crcValue, resourceFile );
+
+ // Copy it into materials/downloads if it's not there yet, so the server doesn't have to
+ // transmit the file back to us.
+ bool bCopy = true;
+ CCustomFilename filehex( crcValue );
+ char szAbsFilename[ MAX_PATH ];
+ if ( g_pFileSystem->RelativePathToFullPath( filehex.m_Filename, "game", szAbsFilename, sizeof(szAbsFilename), FILTER_CULLPACK ) )
+ {
+ // check if existing file already has same CRC,
+ // then we don't need to copy it anymore
+ CRC32_t test;
+ CRC_File( &test, szAbsFilename );
+ if ( test == crcValue )
+ bCopy = false;
+ }
+
+ if ( bCopy )
+ {
+ // Copy it over under the new name
+
+ // Load up the file
+ CUtlBuffer buf;
+ if ( !g_pFileSystem->ReadFile( resourceFile, "game", buf ) )
+ {
+ Warning( "CacheCustomFiles: can't read '%s'.\n", resourceFile );
+ return;
+ }
+
+ // Make sure dest directory exists
+ char szParentDir[ MAX_PATH ];
+ V_ExtractFilePath( filehex.m_Filename, szParentDir, sizeof(szParentDir) );
+ g_pFileSystem->CreateDirHierarchy( szParentDir, "download" );
+
+ // Save it
+ if ( !g_pFileSystem->WriteFile( filehex.m_Filename, "download", buf ) )
+ {
+ Warning( "CacheCustomFiles: can't write '%s'.\n", filehex.m_Filename );
+ return;
+ }
+ }
+
+ /* Finally, validate the VTF file. TODO
+ CUtlVector<char> fileData;
+ if ( LogoFile_ReadFile( crcValue, fileData ) )
+ {
+ bValid = true;
+ }
+ else
+ {
+ Warning( "CL_LogoFile_OnConnect: logo file '%s' invalid.\n", logotexture );
+ } */
+
+ m_nCustomFiles[slot].crc = crcValue; // first slot is logo
+ m_nCustomFiles[slot].reqID = 0;
+
+}
+
+void CClientState::CheckOwnCustomFiles()
+{
+ // clear file CRCs
+ Q_memset( m_nCustomFiles, 0, sizeof(m_nCustomFiles) );
+
+ if ( m_nMaxClients == 1 )
+ return; // not in singleplayer
+
+ if ( IsPC() )
+ {
+ AddCustomFile( 0, cl_logofile.GetString() );
+ AddCustomFile( 1, cl_soundfile.GetString() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClientState::DumpPrecacheStats( const char * name )
+{
+ if ( !name || !name[0] )
+ {
+ ConMsg( "Can only dump stats when active in a level\n" );
+ return;
+ }
+
+ CPrecacheItem *items = NULL;
+
+ if ( !Q_strcmp(MODEL_PRECACHE_TABLENAME, name ) )
+ {
+ items = model_precache;
+ }
+ else if ( !Q_strcmp(GENERIC_PRECACHE_TABLENAME, name ) )
+ {
+ items = generic_precache;
+ }
+ else if ( !Q_strcmp(SOUND_PRECACHE_TABLENAME, name ) )
+ {
+ items = sound_precache;
+ }
+ else if ( !Q_strcmp(DECAL_PRECACHE_TABLENAME, name ) )
+ {
+ items = decal_precache;
+ }
+
+ INetworkStringTable *table = GetStringTable( name );
+
+ if ( !items || !table)
+ {
+ ConMsg( "Precache table '%s' not found.\n", name );
+ return;
+ }
+
+ int count = table->GetNumStrings();
+ int maxcount = table->GetMaxStrings();
+
+ ConMsg( "\n" );
+ ConMsg( "Precache table %s: %i of %i slots used\n", table->GetTableName(),
+ count, maxcount );
+
+ for ( int i = 0; i < count; i++ )
+ {
+ char const *pchName = table->GetString( i );
+ CPrecacheItem *slot = &items[ i ];
+ const CPrecacheUserData *p = CL_GetPrecacheUserData( table, i );
+
+ if ( !pchName || !slot || !p )
+ continue;
+
+ ConMsg( "%03i: %s (%s): ",
+ i,
+ pchName,
+ GetFlagString( p->flags ) );
+
+
+ if ( slot->GetReferenceCount() == 0 )
+ {
+ ConMsg( " never used\n" );
+ }
+ else
+ {
+ ConMsg( " %i refs, first %.2f mru %.2f\n",
+ slot->GetReferenceCount(),
+ slot->GetFirstReference(),
+ slot->GetMostRecentReference() );
+ }
+ }
+
+ ConMsg( "\n" );
+}
+
+void CClientState::ReadDeletions( CEntityReadInfo &u )
+{
+ VPROF( "ReadDeletions" );
+ while ( u.m_pBuf->ReadOneBit()!=0 )
+ {
+ int idx = u.m_pBuf->ReadUBitLong( MAX_EDICT_BITS );
+
+ Assert( !u.m_pTo->transmit_entity.Get( idx ) );
+
+ CL_DeleteDLLEntity( idx, "ReadDeletions" );
+ }
+}
+
+void CClientState::ReadEnterPVS( CEntityReadInfo &u )
+{
+ VPROF( "ReadEnterPVS" );
+
+ TRACE_PACKET(( " CL Enter PVS (%d)\n", u.m_nNewEntity ));
+
+ int iClass = u.m_pBuf->ReadUBitLong( m_nServerClassBits );
+
+ int iSerialNum = u.m_pBuf->ReadUBitLong( NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS );
+
+ CL_CopyNewEntity( u, iClass, iSerialNum );
+
+ if ( u.m_nNewEntity == u.m_nOldEntity ) // that was a recreate
+ u.NextOldEntity();
+}
+
+void CClientState::ReadLeavePVS( CEntityReadInfo &u )
+{
+ VPROF( "ReadLeavePVS" );
+ // Sanity check.
+ if ( !u.m_bAsDelta )
+ {
+ Assert(0); // cl.validsequence = 0;
+ ConMsg( "WARNING: LeavePVS on full update" );
+ u.m_UpdateType = Failed; // break out
+ return;
+ }
+
+ Assert( !u.m_pTo->transmit_entity.Get( u.m_nOldEntity ) );
+
+ if ( u.m_UpdateFlags & FHDR_DELETE )
+ {
+ CL_DeleteDLLEntity( u.m_nOldEntity, "ReadLeavePVS" );
+ }
+
+ u.NextOldEntity();
+}
+
+void CClientState::ReadDeltaEnt( CEntityReadInfo &u )
+{
+ VPROF( "ReadDeltaEnt" );
+ CL_CopyExistingEntity( u );
+
+ u.NextOldEntity();
+}
+
+void CClientState::ReadPreserveEnt( CEntityReadInfo &u )
+{
+ VPROF( "ReadPreserveEnt" );
+ if ( !u.m_bAsDelta ) // Should never happen on a full update.
+ {
+ Assert(0); // cl.validsequence = 0;
+ ConMsg( "WARNING: PreserveEnt on full update" );
+ u.m_UpdateType = Failed; // break out
+ return;
+ }
+
+ Assert( u.m_pFrom->transmit_entity.Get(u.m_nOldEntity) );
+
+ // copy one of the old entities over to the new packet unchanged
+
+ // XXX(JohnS): This was historically checking for NewEntity overflow, though this path does not care (and new entity
+ // may be -1). The old entity bounds check here seems like what was intended, but since nNewEntity
+ // should not be overflowed either, I've left that check in case it was guarding against a case I am
+ // overlooking.
+ if ( u.m_nOldEntity >= MAX_EDICTS || u.m_nOldEntity < 0 || u.m_nNewEntity >= MAX_EDICTS )
+ {
+ Host_Error( "CL_ReadPreserveEnt: Entity out of bounds. Old: %i, New: %i",
+ u.m_nOldEntity, u.m_nNewEntity );
+ }
+
+ u.m_pTo->last_entity = u.m_nOldEntity;
+ u.m_pTo->transmit_entity.Set( u.m_nOldEntity );
+
+ // Zero overhead
+ if ( cl_entityreport.GetBool() )
+ CL_RecordEntityBits( u.m_nOldEntity, 0 );
+
+ CL_PreserveExistingEntity( u.m_nOldEntity );
+
+ u.NextOldEntity();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Starts checking that all the necessary files are local
+//-----------------------------------------------------------------------------
+void CClientState::StartUpdatingSteamResources()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ // we can only do this when in SIGNONSTATE_NEW,
+ // since the completion of this triggers the continuation of SIGNONSTATE_NEW
+ Assert(m_nSignonState == SIGNONSTATE_NEW);
+
+ // make sure we have all the necessary resources locally before continuing
+ m_hWaitForResourcesHandle = g_pFileSystem->WaitForResources(m_szLevelBaseName);
+ m_bUpdateSteamResources = false;
+ m_bShownSteamResourceUpdateProgress = false;
+ m_bDownloadResources = false;
+ m_bPrepareClientDLL = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: checks to see if we're done updating files
+//-----------------------------------------------------------------------------
+void CClientState::CheckUpdatingSteamResources()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ VPROF_BUDGET( "CheckUpdatingSteamResources", VPROF_BUDGETGROUP_STEAM );
+
+ if ( m_bPrepareClientDLL )
+ {
+ float flPrepareProgress = 0.f;
+ char szMutableLevelName[ sizeof( m_szLevelBaseName ) ] = { 0 };
+ V_strncpy( szMutableLevelName, m_szLevelBaseName, sizeof( szMutableLevelName ) );
+
+ // if the game .dll doesn't support this call assume everything is prepared
+ IServerGameDLL::ePrepareLevelResourcesResult eResult = IServerGameDLL::ePrepareLevelResources_Prepared;
+ if ( g_iServerGameDLLVersion >= 10 )
+ {
+ eResult = serverGameDLL->AsyncPrepareLevelResources( szMutableLevelName, sizeof( szMutableLevelName ),
+ m_szLevelFileName, sizeof( m_szLevelFileName ), &flPrepareProgress );
+ }
+
+ switch ( eResult )
+ {
+ case IServerGameDLL::ePrepareLevelResources_InProgress:
+ if (!m_bShownSteamResourceUpdateProgress)
+ {
+ // make sure the loading dialog is up
+ EngineVGui()->StartCustomProgress();
+ EngineVGui()->ActivateGameUI();
+ m_bShownSteamResourceUpdateProgress = true;
+ }
+ EngineVGui()->UpdateCustomProgressBar( flPrepareProgress, g_pVGuiLocalize->Find("#Valve_UpdatingSteamResources") );
+ break;
+ default:
+ case IServerGameDLL::ePrepareLevelResources_Prepared:
+ flPrepareProgress = 100.f;
+ m_bPrepareClientDLL = false;
+ m_bUpdateSteamResources = true;
+ break;
+ }
+ }
+
+ if (m_bUpdateSteamResources)
+ {
+ bool bComplete = false;
+ float flProgress = 0.0f;
+ g_pFileSystem->GetWaitForResourcesProgress(m_hWaitForResourcesHandle, &flProgress, &bComplete);
+
+ if (bComplete)
+ {
+ m_hWaitForResourcesHandle = NULL;
+ m_bUpdateSteamResources = false;
+ m_bDownloadResources = false;
+
+ if ( m_pDownloadableFileTable )
+ {
+ bool allowDownloads = true;
+ bool allowSoundDownloads = true;
+ bool allowNonMaps = true;
+ if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "none" ) )
+ {
+ allowDownloads = allowSoundDownloads = allowNonMaps = false;
+ }
+ else if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "nosounds" ) )
+ {
+ allowSoundDownloads = false;
+ }
+ else if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "mapsonly" ) )
+ {
+ allowNonMaps = false;
+ }
+
+ if ( allowDownloads )
+ {
+ char extension[4];
+ for ( int i=0; i<m_pDownloadableFileTable->GetNumStrings(); ++i )
+ {
+ const char *fname = m_pDownloadableFileTable->GetString( i );
+
+ if ( !allowSoundDownloads )
+ {
+ Q_ExtractFileExtension( fname, extension, sizeof( extension ) );
+ if ( !Q_strcasecmp( extension, "wav" ) || !Q_strcasecmp( extension, "mp3" ) )
+ {
+ continue;
+ }
+ }
+
+ if ( !allowNonMaps )
+ {
+ // The user wants maps only.
+ Q_ExtractFileExtension( fname, extension, sizeof( extension ) );
+
+ // If the extension is not bsp, skip it.
+ if ( Q_strcasecmp( extension, "bsp" ) )
+ {
+ continue;
+ }
+ }
+
+ CL_QueueDownload( fname );
+ }
+ }
+
+ if ( CL_GetDownloadQueueSize() )
+ {
+ // make sure the loading dialog is up
+ EngineVGui()->StartCustomProgress();
+ EngineVGui()->ActivateGameUI();
+ m_bDownloadResources = true;
+ }
+ else
+ {
+ m_bDownloadResources = false;
+ FinishSignonState_New();
+ }
+ }
+ else
+ {
+ Host_Error( "Invalid download file table." );
+ }
+ }
+ else if (flProgress > 0.0f)
+ {
+ if (!m_bShownSteamResourceUpdateProgress)
+ {
+ // make sure the loading dialog is up
+ EngineVGui()->StartCustomProgress();
+ EngineVGui()->ActivateGameUI();
+ m_bShownSteamResourceUpdateProgress = true;
+ }
+
+ // change it to be updating steam resources
+ EngineVGui()->UpdateCustomProgressBar( flProgress, g_pVGuiLocalize->Find("#Valve_UpdatingSteamResources") );
+ }
+ }
+
+ if ( m_bDownloadResources )
+ {
+ // Check on any HTTP downloads in progress
+ bool stillDownloading = CL_DownloadUpdate();
+
+ if ( !stillDownloading )
+ {
+ m_bDownloadResources = false;
+ FinishSignonState_New();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: At a certain rate, this function will verify any unverified
+// file CRCs with the server.
+//-----------------------------------------------------------------------------
+void CClientState::CheckFileCRCsWithServer()
+{
+//! !FIXME! Stubbed this. Several reasons:
+//!
+//! 1.) Removed the CRC functionality (because it was broken when we switched to use MD5's for hashes of
+//! loose files, but the server only has CRC's of some files in the VPK headers.). Currently the only
+//! supported pure server mode is "trusted source."
+//! 2.) Sending MD5's of VPK's is a bit too restrictive for most use cases. For example, if a client
+//! has an extra VPK for custom content, the server doesn't know what to do with it. Or if we
+//! release an optional update, the VPK's might legitimately differ.
+//!
+//! Rich has pointed out that we really need pure server client work to be something that the client
+//! cannot easily bypass. Currently that is the case. But I need to ship the SteamPipe conversion now.
+//! We can revisit pure server security after that has shipped.
+//
+// VPROF_( "CheckFileCRCsWithServer", 1, VPROF_BUDGETGROUP_OTHER_NETWORKING, false, BUDGETFLAG_CLIENT );
+// const float flBatchInterval = 1.0f / 5.0f;
+// const int nBatchSize = 5;
+//
+// // Don't do this yet..
+// if ( !m_bCheckCRCsWithServer )
+// return;
+//
+// if ( m_nSignonState != SIGNONSTATE_FULL )
+// return;
+//
+// // Only send a batch every so often.
+// float flCurTime = Plat_FloatTime();
+// if ( (flCurTime - m_flLastCRCBatchTime) < flBatchInterval )
+// return;
+//
+// m_flLastCRCBatchTime = flCurTime;
+//
+// CUnverifiedFileHash rgUnverifiedFiles[nBatchSize];
+// int count = g_pFileSystem->GetUnverifiedFileHashes( rgUnverifiedFiles, ARRAYSIZE( rgUnverifiedFiles ) );
+// if ( count == 0 )
+// return;
+//
+// // Send the messages to the server.
+// for ( int i=0; i < count; i++ )
+// {
+// CLC_FileCRCCheck crcCheck;
+// V_strncpy( crcCheck.m_szPathID, rgUnverifiedFiles[i].m_PathID, sizeof( crcCheck.m_szPathID ) );
+// V_strncpy( crcCheck.m_szFilename, rgUnverifiedFiles[i].m_Filename, sizeof( crcCheck.m_szFilename ) );
+// crcCheck.m_nFileFraction = rgUnverifiedFiles[i].m_nFileFraction;
+// crcCheck.m_MD5 = rgUnverifiedFiles[i].m_FileHash.m_md5contents;
+// crcCheck.m_CRCIOs = rgUnverifiedFiles[i].m_FileHash.m_crcIOSequence;
+// crcCheck.m_eFileHashType = rgUnverifiedFiles[i].m_FileHash.m_eFileHashType;
+// crcCheck.m_cbFileLen = rgUnverifiedFiles[i].m_FileHash.m_cbFileLen;
+// crcCheck.m_nPackFileNumber = rgUnverifiedFiles[i].m_FileHash.m_nPackFileNumber;
+// crcCheck.m_PackFileID = rgUnverifiedFiles[i].m_FileHash.m_PackFileID;
+//
+// m_NetChannel->SendNetMsg( crcCheck );
+// }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: sanity-checks the variables in a VMT file to prevent the client from
+// making player etc. textures that glow or show through walls etc. Anything
+// other than $baseTexture and $bumpmap is hereby verboten.
+//-----------------------------------------------------------------------------
+bool CheckSimpleMaterial( IMaterial *pMaterial )
+{
+ if ( !pMaterial )
+ return false;
+
+ const char *name = pMaterial->GetShaderName();
+ if ( Q_strncasecmp( name, "VertexLitGeneric", 16 ) &&
+ Q_strncasecmp( name, "UnlitGeneric", 12 ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_IGNOREZ ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_WIREFRAME ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SELFILLUM ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_ADDITIVE ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_NOFOG ) )
+ return false;
+
+ if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_HALFLAMBERT ) )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: find a filename in the string table, ignoring case and slash mismatches. Returns the index, or INVALID_STRING_INDEX if not found.
+//-----------------------------------------------------------------------------
+int FindFilenameInStringTable( INetworkStringTable *table, const char *searchFname )
+{
+ char searchFilename[MAX_PATH];
+ char tableFilename[MAX_PATH];
+
+ Q_strncpy( searchFilename, searchFname, MAX_PATH );
+ Q_FixSlashes( searchFilename );
+
+ for ( int i=0; i<table->GetNumStrings(); ++i )
+ {
+ const char *tableFname = table->GetString( i );
+ Q_strncpy( tableFilename, tableFname, MAX_PATH );
+ Q_FixSlashes( tableFilename );
+
+ if ( !Q_strcasecmp( searchFilename, tableFilename ) )
+ {
+ return i;
+ }
+ }
+
+ return INVALID_STRING_INDEX;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: find a filename in the string table, ignoring case and slash mismatches.
+// Returns the consistency type, with CONSISTENCY_NONE being a Not Found result.
+//-----------------------------------------------------------------------------
+ConsistencyType GetFileConsistencyType( INetworkStringTable *table, const char *searchFname )
+{
+ int index = FindFilenameInStringTable( table, searchFname );
+ if ( index == INVALID_STRING_INDEX )
+ {
+ return CONSISTENCY_NONE;
+ }
+
+ int length = 0;
+ unsigned char *userData = NULL;
+ userData = (unsigned char *)table->GetStringUserData( index, &length );
+ if ( userData && length == sizeof( ExactFileUserData ) )
+ {
+ switch ( userData[0] )
+ {
+ case CONSISTENCY_EXACT:
+ case CONSISTENCY_SIMPLE_MATERIAL:
+ return (ConsistencyType)userData[0];
+ default:
+ return CONSISTENCY_NONE;
+ }
+ }
+ else
+ {
+ return CONSISTENCY_NONE;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Does a CRC check compared to the CRC stored in the user data.
+//-----------------------------------------------------------------------------
+bool CheckCRCs( unsigned char *userData, int length, const char *filename )
+{
+ if ( userData && length == sizeof( ExactFileUserData ) )
+ {
+ if ( userData[0] != CONSISTENCY_EXACT && userData[0] != CONSISTENCY_SIMPLE_MATERIAL )
+ {
+ return false;
+ }
+
+ ExactFileUserData *exactFileData = (ExactFileUserData *)userData;
+
+ CRC32_t crc;
+ if ( !CRC_File( &crc, filename ) )
+ {
+ return false;
+ }
+
+ return ( crc == exactFileData->crc );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: completes the SIGNONSTATE_NEW state
+//-----------------------------------------------------------------------------
+void CClientState::FinishSignonState_New()
+{
+ // make sure we're still in the right signon state
+ if (m_nSignonState != SIGNONSTATE_NEW)
+ return;
+
+ if ( !m_bMarkedCRCsUnverified )
+ {
+ // Mark all file CRCs unverified once per server. We may have verified CRCs for certain files on
+ // the previous server, but we need to reverify them on the new server.
+ m_bMarkedCRCsUnverified = true;
+ g_pFileSystem->MarkAllCRCsUnverified();
+ }
+
+ // Verify the map and player .mdl crc's now that we've finished downloading missing resources (maps etc)
+ if ( !CL_CheckCRCs( m_szLevelFileName ) )
+ {
+ Host_Error( "Unable to verify map %s", ( m_szLevelFileName && m_szLevelFileName[0] ) ? m_szLevelFileName : "unknown" );
+ return;
+ }
+
+ // We're going to force-touch a lot of textures and resources below, we don't want the streaming system to try and
+ // pull these in as if they were being used for normal rendering.
+ materials->SuspendTextureStreaming();
+
+ // Only do this if our server is shut down and we're acting as a client. Otherwise the server handles this when it
+ // starts the load.
+ if ( sv.m_State < ss_loading )
+ {
+ // Reset the last used count on all models before beginning the new load -- The nServerCount value on models should
+ // always resolve as different from values from previous servers.
+ modelloader->ResetModelServerCounts();
+ }
+
+ if ( cl_always_flush_models.GetBool() )
+ {
+ modelloader->PurgeUnusedModels();
+ }
+
+ // Our load process is causing some very bad allocation & performance impact in drivers, due to the enormous amount
+ // of resources being used (and, in the case of EnableHDR(), released and re-uploaded) in a single frame. Ensuring
+ // that we push a few frames through before and after the main model loading spree results in dramatically better
+ // results. Without these calls, for instance, OS X on high texture quality cannot survive map load.
+ //
+ // This is pretty janky, but doesn't really have any cost (and even makes our one-frozen-frame load screen slightly
+ // less likely to trigger OS "not responding" warnings)
+ extern void V_RenderVGuiOnly();
+ V_RenderVGuiOnly();
+
+ // Before we do anything with the whitelist, make sure we have the proper map pack mounted
+ // this will load the .bsp by setting the world model the string list at the hardcoded index 1.
+ cl.SetModel( 1 );
+
+ V_RenderVGuiOnly();
+
+ // Check for a new whitelist. It's good to do it early in the connection process here because if we wait until later,
+ // the client may have loaded some files w/o the proper whitelist restrictions and we'd have to reload them.
+ m_bCheckCRCsWithServer = false; // Don't check CRCs yet.. wait until we got a whitelist and cleaned out our files based on it to send CRCs.
+
+ // We check the new whitelist now so loads that happen between here and FullyConnected are checked against it, but
+ // don't trigger a reload of dirty files until after we flush unused resources from the last map in
+ // CL_FullyConnected. This fixes reloading materials that are unused, and trying to reload materials that are no
+ // longer pure, but would be unloaded in fully connected anyway.
+ CL_CheckForPureServerWhitelist( m_pPendingPureFileReloads );
+
+ CL_InstallAndInvokeClientStringTableCallbacks();
+
+ materials->CacheUsedMaterials();
+
+ // force a consistency check
+ ConsistencyCheck( true );
+
+ CL_RegisterResources();
+
+ // Done with all resources, issue prespawn command.
+ // Include server count in case server disconnects and changes level during d/l
+
+ // Tell rendering system we have a new set of models.
+ R_LevelInit();
+
+ // Balanced against SuspendTextureStreaming above
+ materials->ResumeTextureStreaming();
+
+ EngineVGui()->UpdateProgressBar(PROGRESS_SENDCLIENTINFO);
+ if ( !m_NetChannel )
+ return;
+
+ SendClientInfo();
+
+ CL_SetSteamCrashComment();
+
+ // tell server that we entered now that state
+ m_NetChannel->SendNetMsg( NET_SignonState( m_nSignonState, m_nServerCount ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: run a file consistency check if enforced by server
+//-----------------------------------------------------------------------------
+void CClientState::ConsistencyCheck(bool bChanged )
+{
+ // get the default config for the current card as a starting point.
+ // server must have sent us this table
+ if ( !m_pDownloadableFileTable )
+ return;
+
+ // no checks during single player or demo playback
+ if( (m_nMaxClients == 1) || demoplayer->IsPlayingBack() )
+ return;
+
+ // only if we are connected
+ if ( !IsConnected() )
+ return;
+
+ // check if material configuration changed
+ static MaterialSystem_Config_t s_LastConfig;
+ MaterialSystem_Config_t newConfig = materials->GetCurrentConfigForVideoCard();
+
+ if ( Q_memcmp( &s_LastConfig, &newConfig, sizeof(MaterialSystem_Config_t) ) )
+ {
+ // remember last config we tested
+ s_LastConfig = newConfig;
+ bChanged = true;
+ }
+
+ if ( !bChanged )
+ return;
+
+#if 0
+ // This check was removed as GetSteamUniverse() is very expensive and was getting
+ // called every frame on Linux. I don't believe it should have been merged over from
+ // l4d2 in the first place either...
+ // If we're not signed into the steam universe, then bail.
+ // This should only happen when we're developing internally without Steam I hope?
+ if ( k_EUniverseInvalid == GetSteamUniverse() )
+ {
+ return;
+ }
+#endif
+
+ const char *errorMsg = NULL;
+ const char *errorFilename = NULL;
+
+ // check CRCs and model sizes
+ Color red( 200, 20, 20, 255 );
+ Color blue( 100, 100, 200, 255 );
+ for ( int i=0; i<m_pDownloadableFileTable->GetNumStrings(); ++i )
+ {
+ int length = 0;
+ unsigned char *userData = NULL;
+ userData = (unsigned char *)m_pDownloadableFileTable->GetStringUserData( i, &length );
+ const char *filename = m_pDownloadableFileTable->GetString( i );
+
+ //
+ // CRC Check
+ //
+ if ( userData && userData[0] == CONSISTENCY_EXACT && length == sizeof( ExactFileUserData ) )
+ {
+ // Hm, this isn't supported anymore. (Use sv_pure instead.) Under ordinary
+ // circumstances, servers shouldn't be asking for it.
+ Assert( false );
+ continue;
+ }
+
+ //
+ // Bounds Check
+ //
+ // This is simply asking for the model's mins and maxs. Also, it checks each material referenced
+ // by the model, to make sure it doesn't ignore Z, isn't overbright, etc.
+ //
+ // TODO: Animations and facial expressions can still pull verts out past this.
+ //
+ else if ( userData && userData[0] == CONSISTENCY_BOUNDS && length == sizeof( ModelBoundsUserData ) )
+ {
+ ModelBoundsUserData *boundsData = (ModelBoundsUserData *)userData;
+ model_t *pModel = modelloader->GetModelForName( filename, IModelLoader::FMODELLOADER_CLIENT );
+ if ( !pModel )
+ {
+ errorMsg = "Cannot find required model";
+ errorFilename = filename;
+ }
+ else
+ {
+ if ( pModel->mins.x < boundsData->mins.x ||
+ pModel->mins.y < boundsData->mins.y ||
+ pModel->mins.z < boundsData->mins.z )
+ {
+ ConColorMsg( red, "Model %s exceeds mins (%.1f %.1f %.1f vs. %.1f %.1f %.1f)\n", filename,
+ pModel->mins.x, pModel->mins.y, pModel->mins.z,
+ boundsData->mins.x, boundsData->mins.y, boundsData->mins.z);
+ errorMsg = "Server is enforcing model bounds";
+ errorFilename = filename;
+ }
+ if ( pModel->maxs.x > boundsData->maxs.x ||
+ pModel->maxs.y > boundsData->maxs.y ||
+ pModel->maxs.z > boundsData->maxs.z )
+ {
+ ConColorMsg( red, "Model %s exceeds maxs (%.1f %.1f %.1f vs. %.1f %.1f %.1f)\n", filename,
+ pModel->maxs.x, pModel->maxs.y, pModel->maxs.z,
+ boundsData->maxs.x, boundsData->maxs.y, boundsData->maxs.z);
+ errorMsg = "Server is enforcing model bounds";
+ errorFilename = filename;
+ }
+
+ // Check each texture
+ IMaterial *pMaterials[ 128 ];
+ int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials );
+
+ for ( int j = 0; j<materialCount; ++j )
+ {
+ IMaterial *pMaterial = pMaterials[j];
+
+ if ( !CheckSimpleMaterial( pMaterial ) )
+ {
+ ConColorMsg( red, "Model %s has a bad texture %s\n", filename, pMaterial->GetName() );
+ errorMsg = "Server is enforcing simple material";
+ errorFilename = pMaterial->GetName();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if ( errorFilename && *errorFilename )
+ {
+ COM_ExplainDisconnection( true, "%s:\n%s\n", errorMsg, errorFilename );
+ Host_Error( "%s: %s\n", errorMsg, errorFilename );
+ }
+}
+
+void CClientState::UpdateAreaBits_BackwardsCompatible()
+{
+ if ( m_pAreaBits )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ memcpy( m_chAreaBits, m_pAreaBits, sizeof( m_chAreaBits ) );
+
+ // The whole point of adding this array was that the client could react to closed portals.
+ // If they're using the old interface to set area portal bits, then we use the old
+ // behavior of assuming all portals are open on the clent.
+ memset( m_chAreaPortalBits, 0xFF, sizeof( m_chAreaPortalBits ) );
+
+ m_bAreaBitsValid = true;
+ }
+}
+
+
+unsigned char** CClientState::GetAreaBits_BackwardCompatibility()
+{
+ return &m_pAreaBits;
+}
+
+
+void CClientState::RunFrame()
+{
+ CBaseClientState::RunFrame();
+
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // Since cl_rate is a virtualized cvar, make sure to pickup changes in it.
+ if ( m_NetChannel )
+ m_NetChannel->SetDataRate( cl_rate->GetFloat() );
+
+ ConsistencyCheck( false );
+
+ // Check if paged pool is low ( < 8% free )
+ static bool s_bLowPagedPoolMemoryWarning = false;
+ PAGED_POOL_INFO_t ppi;
+ if ( ( SYSCALL_SUCCESS == Plat_GetPagedPoolInfo( &ppi ) ) &&
+ ( ( ppi.numPagesFree * 12 ) < ( ppi.numPagesUsed + ppi.numPagesFree ) ) )
+ {
+ con_nprint_t np;
+ np.time_to_live = 1.0;
+ np.index = 1;
+ np.fixed_width_font = false;
+ np.color[ 0 ] = 1.0;
+ np.color[ 1 ] = 0.2;
+ np.color[ 2 ] = 0.0;
+ Con_NXPrintf( &np, "WARNING: OS Paged Pool Memory Low" );
+
+ // Also print a warning to console
+ static float s_flLastWarningTime = 0.0f;
+ if ( !s_bLowPagedPoolMemoryWarning ||
+ ( Plat_FloatTime() - s_flLastWarningTime > 3.0f ) ) // print a warning no faster than once every 3 sec
+ {
+ s_bLowPagedPoolMemoryWarning = true;
+ s_flLastWarningTime = Plat_FloatTime();
+ Warning( "OS Paged Pool Memory Low!\n" );
+ Warning( " Currently using %lu pages (%lu Kb) of total %lu pages (%lu Kb total)\n",
+ ppi.numPagesUsed, ppi.numPagesUsed * Plat_GetMemPageSize(),
+ ( ppi.numPagesFree + ppi.numPagesUsed ), ( ppi.numPagesFree + ppi.numPagesUsed ) * Plat_GetMemPageSize() );
+ Warning( " Please see http://www.steampowered.com for more information.\n" );
+ }
+ }
+ else if ( s_bLowPagedPoolMemoryWarning )
+ {
+ s_bLowPagedPoolMemoryWarning = false;
+ Msg( "Info: OS Paged Pool Memory restored - currently %lu pages free (%lu Kb) of total %lu pages (%lu Kb total).\n",
+ ppi.numPagesFree, ppi.numPagesFree * Plat_GetMemPageSize(),
+ ( ppi.numPagesFree + ppi.numPagesUsed ), ( ppi.numPagesFree + ppi.numPagesUsed ) * Plat_GetMemPageSize() );
+ }
+
+}