You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2077 lines
59 KiB
2077 lines
59 KiB
//========= 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_RenderSwapBuffers(); |
|
V_RenderSwapBuffers(); |
|
|
|
// 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_RenderSwapBuffers(); |
|
|
|
// 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() ); |
|
} |
|
|
|
}
|
|
|