//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "client_pch.h" #include "networkstringtabledefs.h" #include #include #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 #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( 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 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; iGetNumStrings(); ++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; iGetNumStrings(); ++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; iGetNumStrings(); ++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; jGetName() ); 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() ); } }