diff --git a/game/client/c_baseentity.cpp b/game/client/c_baseentity.cpp index 5f3dc7160..bde884984 100644 --- a/game/client/c_baseentity.cpp +++ b/game/client/c_baseentity.cpp @@ -1090,6 +1090,28 @@ bool C_BaseEntity::Init( int entnum, int iSerialNum ) index = entnum; + if ( this->IsPlayer() ) + { + // Josh: If we ever have a player that could ever cause us + // an out of bounds access to any player sized arrays. + // Just get out now! + // + // All these issues should be bounds checked now anyway, + // but I'd much rather be safe than sorry here. + // + // Additionally, make sure we aren't 0 or negative, + // the player CANNOT be worldspawn. + // Someone is going to try that to get an extra player and frog something up! + // + // Player index is entindex - 1. + // MAX_PLAYERS_ARRAY_SAFE is MAX_PLAYERS + 1. + if ( index <= 0 || index >= MAX_PLAYERS_ARRAY_SAFE ) + { + Warning("Player with out of bounds entindex! Got: %d Expected to be in inclusive range: %d - %d\n", index, 1, MAX_PLAYERS ); + return false; + } + } + cl_entitylist->AddNetworkableEntity( GetIClientUnknown(), entnum, iSerialNum ); CollisionProp()->CreatePartitionHandle(); diff --git a/game/client/c_baseplayer.cpp b/game/client/c_baseplayer.cpp index 1602f7bf4..dc42d6eef 100644 --- a/game/client/c_baseplayer.cpp +++ b/game/client/c_baseplayer.cpp @@ -523,12 +523,12 @@ bool C_BasePlayer::IsReplay() const CBaseEntity *C_BasePlayer::GetObserverTarget() const // returns players target or NULL { #ifndef _XBOX - if ( IsHLTV() ) + if ( IsHLTV() && HLTVCamera() ) { return HLTVCamera()->GetPrimaryTarget(); } #if defined( REPLAY_ENABLED ) - if ( IsReplay() ) + if ( IsReplay() && ReplayCamera() ) { return ReplayCamera()->GetPrimaryTarget(); } @@ -624,12 +624,12 @@ void C_BasePlayer::SetObserverMode ( int iNewMode ) int C_BasePlayer::GetObserverMode() const { #ifndef _XBOX - if ( IsHLTV() ) + if ( IsHLTV() && HLTVCamera() ) { return HLTVCamera()->GetMode(); } #if defined( REPLAY_ENABLED ) - if ( IsReplay() ) + if ( IsReplay() && ReplayCamera() ) { return ReplayCamera()->GetMode(); } diff --git a/game/client/c_playerresource.cpp b/game/client/c_playerresource.cpp index 62a04dd99..8b6c855c5 100644 --- a/game/client/c_playerresource.cpp +++ b/game/client/c_playerresource.cpp @@ -32,16 +32,16 @@ END_RECV_TABLE() BEGIN_PREDICTION_DATA( C_PlayerResource ) - DEFINE_PRED_ARRAY( m_szName, FIELD_STRING, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_bConnected, FIELD_BOOLEAN, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iTeam, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_bAlive, FIELD_BOOLEAN, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iHealth, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_iAccountID, FIELD_INTEGER, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), - DEFINE_PRED_ARRAY( m_bValid, FIELD_BOOLEAN, MAX_PLAYERS+1, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_szName, FIELD_STRING, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_bConnected, FIELD_BOOLEAN, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iTeam, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_bAlive, FIELD_BOOLEAN, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iHealth, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_iAccountID, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), + DEFINE_PRED_ARRAY( m_bValid, FIELD_BOOLEAN, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), END_PREDICTION_DATA() diff --git a/game/client/c_playerresource.h b/game/client/c_playerresource.h index 9fe896c24..ddf92dee7 100644 --- a/game/client/c_playerresource.h +++ b/game/client/c_playerresource.h @@ -64,17 +64,17 @@ public : // IGameResources interface // Data for each player that's propagated to all clients // Stored in individual arrays so they can be sent down via datatables - string_t m_szName[MAX_PLAYERS+1]; - int m_iPing[MAX_PLAYERS+1]; - int m_iScore[MAX_PLAYERS+1]; - int m_iDeaths[MAX_PLAYERS+1]; - bool m_bConnected[MAX_PLAYERS+1]; - int m_iTeam[MAX_PLAYERS+1]; - bool m_bAlive[MAX_PLAYERS+1]; - int m_iHealth[MAX_PLAYERS+1]; + string_t m_szName[MAX_PLAYERS_ARRAY_SAFE]; + int m_iPing[MAX_PLAYERS_ARRAY_SAFE]; + int m_iScore[MAX_PLAYERS_ARRAY_SAFE]; + int m_iDeaths[MAX_PLAYERS_ARRAY_SAFE]; + bool m_bConnected[MAX_PLAYERS_ARRAY_SAFE]; + int m_iTeam[MAX_PLAYERS_ARRAY_SAFE]; + bool m_bAlive[MAX_PLAYERS_ARRAY_SAFE]; + int m_iHealth[MAX_PLAYERS_ARRAY_SAFE]; Color m_Colors[MAX_TEAMS]; - uint32 m_iAccountID[MAX_PLAYERS+1]; - bool m_bValid[MAX_PLAYERS+1]; + uint32 m_iAccountID[MAX_PLAYERS_ARRAY_SAFE]; + bool m_bValid[MAX_PLAYERS_ARRAY_SAFE]; string_t m_szUnconnectedName; }; diff --git a/game/client/c_sceneentity.cpp b/game/client/c_sceneentity.cpp index a19f72171..d50e045eb 100644 --- a/game/client/c_sceneentity.cpp +++ b/game/client/c_sceneentity.cpp @@ -219,11 +219,11 @@ void C_SceneEntity::SetupClientOnlyScene( const char *pszFilename, C_BaseFlex *p m_hOwner = pOwner; m_bClientOnly = true; - char szFilename[128]; - Assert( V_strlen( pszFilename ) < 128 ); + char szFilename[MAX_PATH]; + Assert( V_strlen( pszFilename ) < MAX_PATH ); V_strcpy_safe( szFilename, pszFilename ); - char szSceneHWM[128]; + char szSceneHWM[ MAX_PATH ]; if ( GetHWMorphSceneFileName( szFilename, szSceneHWM ) ) { V_strcpy_safe( szFilename, szSceneHWM ); diff --git a/game/client/c_team.cpp b/game/client/c_team.cpp index bf36917cf..d09b56cd4 100644 --- a/game/client/c_team.cpp +++ b/game/client/c_team.cpp @@ -151,7 +151,11 @@ int C_Team::Get_Ping( void ) //----------------------------------------------------------------------------- int C_Team::Get_Number_Players( void ) { - return m_aPlayers.Count(); + int nCount = m_aPlayers.Size(); + if ( nCount > MAX_PLAYERS ) + return MAX_PLAYERS; + + return nCount; } //----------------------------------------------------------------------------- diff --git a/game/client/viewangleanim.cpp b/game/client/viewangleanim.cpp index c01d5f2e3..918ed9b62 100644 --- a/game/client/viewangleanim.cpp +++ b/game/client/viewangleanim.cpp @@ -101,7 +101,9 @@ CON_COMMAND( viewanim_save, "Save current animation to file" ) if ( g_pTestAnimation ) { - g_pTestAnimation->SaveAsAnimFile( args[1] ); + char szOutput[ MAX_PATH ]; + V_FixupPathName( szOutput, sizeof(szOutput), args[1] ); + g_pTestAnimation->SaveAsAnimFile( szOutput ); } else { diff --git a/game/client/viewdebug.cpp b/game/client/viewdebug.cpp index 810a08571..ffe9de178 100644 --- a/game/client/viewdebug.cpp +++ b/game/client/viewdebug.cpp @@ -629,7 +629,8 @@ CON_COMMAND_F( r_screenoverlay, "Draw specified material as an overlay", FCVAR_C { if( args.ArgC() == 2 ) { - if ( !Q_stricmp( "off", args[1] ) ) + // This command is silly an undocumented, but, users are expecting r_screenoverlay 0 to function as 'off'. + if ( !Q_stricmp( "off", args[1] ) || !Q_stricmp( "0", args[1] ) ) { view->SetScreenOverlayMaterial( NULL ); } diff --git a/game/server/CommentarySystem.cpp b/game/server/CommentarySystem.cpp index f30d2f631..2527e554f 100644 --- a/game/server/CommentarySystem.cpp +++ b/game/server/CommentarySystem.cpp @@ -352,7 +352,10 @@ class CCommentarySystem : public CAutoGameSystemPerFrame InitCommentary(); IGameEvent *event = gameeventmanager->CreateEvent( "playing_commentary" ); - gameeventmanager->FireEventClientSide( event ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } } CPointCommentaryNode *GetNodeUnderCrosshair() diff --git a/game/server/TemplateEntities.cpp b/game/server/TemplateEntities.cpp index 9b2b26e94..39393743f 100644 --- a/game/server/TemplateEntities.cpp +++ b/game/server/TemplateEntities.cpp @@ -22,6 +22,7 @@ #include "eventqueue.h" #include "TemplateEntities.h" #include "utldict.h" +#include "fgdlib/entitydefs.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -225,7 +226,13 @@ void Templates_ReconnectIOForGroup( CPointTemplate *pGroup ) // Entity I/O values are stored as "Targetname,", so we need to see if there's a ',' in the string char *sValue = value; // FIXME: This is very brittle. Any key with a , will not be found. - char *s = strchr( value, ',' ); + char delimiter = VMF_IOPARAM_STRING_DELIMITER; + if( strchr( value, delimiter ) == NULL ) + { + delimiter = ','; + } + + char *s = strchr( value, delimiter ); if ( s ) { // Grab just the targetname of the receiver diff --git a/game/server/baseanimating.cpp b/game/server/baseanimating.cpp index fffd0d014..c0635e75b 100644 --- a/game/server/baseanimating.cpp +++ b/game/server/baseanimating.cpp @@ -1116,7 +1116,8 @@ void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler ) (float)flCycleRate ); } */ - eventHandler->HandleAnimEvent( &event ); + if ( eventHandler ) + eventHandler->HandleAnimEvent( &event ); // FAILSAFE: // If HandleAnimEvent has somehow reset my internal pointer diff --git a/game/server/cbase.cpp b/game/server/cbase.cpp index 2defe0036..2233d3eb8 100644 --- a/game/server/cbase.cpp +++ b/game/server/cbase.cpp @@ -129,10 +129,16 @@ CEventAction::CEventAction( const char *ActionData ) char szToken[256]; + char chDelim = VMF_IOPARAM_STRING_DELIMITER; + if (!strchr(ActionData, VMF_IOPARAM_STRING_DELIMITER)) + { + chDelim = ','; + } + // // Parse the target name. // - const char *psz = nexttoken(szToken, ActionData, ','); + const char *psz = nexttoken(szToken, ActionData, chDelim); if (szToken[0] != '\0') { m_iTarget = AllocPooledString(szToken); @@ -141,7 +147,7 @@ CEventAction::CEventAction( const char *ActionData ) // // Parse the input name. // - psz = nexttoken(szToken, psz, ','); + psz = nexttoken(szToken, psz, chDelim); if (szToken[0] != '\0') { m_iTargetInput = AllocPooledString(szToken); @@ -154,7 +160,7 @@ CEventAction::CEventAction( const char *ActionData ) // // Parse the parameter override. // - psz = nexttoken(szToken, psz, ','); + psz = nexttoken(szToken, psz, chDelim); if (szToken[0] != '\0') { m_iParameter = AllocPooledString(szToken); @@ -163,7 +169,7 @@ CEventAction::CEventAction( const char *ActionData ) // // Parse the delay. // - psz = nexttoken(szToken, psz, ','); + psz = nexttoken(szToken, psz, chDelim); if (szToken[0] != '\0') { m_flDelay = strtof(szToken, nullptr); @@ -172,7 +178,7 @@ CEventAction::CEventAction( const char *ActionData ) // // Parse the number of times to fire. // - nexttoken(szToken, psz, ','); + nexttoken(szToken, psz, chDelim); if (szToken[0] != '\0') { m_nTimesToFire = atoi(szToken); @@ -186,7 +192,7 @@ CEventAction::CEventAction( const char *ActionData ) // this memory pool stores blocks around the size of CEventAction/inputitem_t structs // can be used for other blocks; will error if to big a block is tried to be allocated -CUtlMemoryPool g_EntityListPool( MAX(sizeof(CEventAction),sizeof(CMultiInputVar::inputitem_t)), 512, CUtlMemoryPool::GROW_FAST, "g_EntityListPool" ); +CUtlMemoryPool g_EntityListPool( MAX(sizeof(CEventAction),sizeof(CMultiInputVar::inputitem_t)), 512, CUtlMemoryPool::GROW_FAST, "g_EntityListPool", Max( alignof( CEventAction ), alignof( CMultiInputVar::inputitem_t ) ) ); #include "tier0/memdbgoff.h" diff --git a/game/server/client.cpp b/game/server/client.cpp index 3749c617d..e46a4b591 100644 --- a/game/server/client.cpp +++ b/game/server/client.cpp @@ -135,6 +135,32 @@ char * CheckChatText( CBasePlayer *pPlayer, char *text ) p[length] = 0; } + // Josh: + // Cheaters can send us whatever data they want through this channel + // Let's validate they aren't trying to clear the chat. + // If we detect any of these blacklisted characters (which players cannot type anyway.) + // Let's just end the string here. + static const char s_blacklist[] = { + // CLRF LF ESC + '\r', '\n', '\x1b' + }; + + int oldLength = length; + for (int i = 0; i < length && oldLength == length; i++) { + for (int j = 0; j < ARRAYSIZE(s_blacklist); j++) { + if (p[i] == s_blacklist[j]) { + p[i] = '\0'; + length = i; + } + } + } + + // Josh: + // If the whole string was garbage characters + // Let's just not print anything. + if ( !*p ) + return NULL; + // cut off after 127 chars if ( length > 127 ) text[127] = 0; @@ -215,6 +241,12 @@ void Host_Say( edict_t *pEdict, const CCommand &args, bool teamonly ) pPlayer->CheckChatText( p, 127 ); // though the buffer szTemp that p points to is 256, // chat text is capped to 127 in CheckChatText above + // make sure the text has valid content + p = CheckChatText( pPlayer, p ); + + if ( !p ) + return; + Assert( pPlayer->GetPlayerName()[0] != '\0' ); bSenderDead = ( pPlayer->m_lifeState != LIFE_ALIVE ); diff --git a/game/server/game_ui.cpp b/game/server/game_ui.cpp index 0e57ada9b..a514a0f83 100644 --- a/game/server/game_ui.cpp +++ b/game/server/game_ui.cpp @@ -170,7 +170,7 @@ void CGameUI::Deactivate( CBaseEntity *pActivator ) } else { - Warning("%s Deactivate(): I have no player when called by %s!\n", GetEntityName().ToCStr(), pActivator->GetEntityName().ToCStr()); + Warning("%s Deactivate(): I have no player when called by %s!\n", GetEntityName().ToCStr(), pActivator ? pActivator->GetEntityName().ToCStr() : NULL); } // Stop thinking diff --git a/game/server/gameinterface.cpp b/game/server/gameinterface.cpp index a985187cd..ab3080e20 100644 --- a/game/server/gameinterface.cpp +++ b/game/server/gameinterface.cpp @@ -1388,6 +1388,12 @@ void CServerGameDLL::LevelShutdown( void ) // In case we quit out during initial load CBaseEntity::SetAllowPrecache( false ); + // Josh: Uncache all the particle systems on level shutdown + // otherwise we leak them constantly on changelevel in the + // particle precache stringtable list. + g_pParticleSystemMgr->UncacheAllParticleSystems(); + g_pParticleSystemMgr->RecreateDictionary(); + g_nCurrentChapterIndex = -1; #ifndef _XBOX @@ -3058,6 +3064,8 @@ void CServerGameClients::ClientSetupVisibility( edict_t *pViewEntity, edict_t *p //----------------------------------------------------------------------------- #define CMD_MAXBACKUP 64 +static ConVar sv_max_usercmd_move_magnitude( "sv_max_usercmd_move_magnitude", "1000", 0, "Maximum move magnitude that can be requested by client." ); + float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int numcmds, int totalcmds, int dropped_packets, bool ignore, bool paused ) { @@ -3080,7 +3088,7 @@ float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int nu pPlayer = static_cast< CBasePlayer * >( pEnt ); } // Too many commands? - if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) ) + if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) || numcmds < 0 || numcmds > totalcmds ) { const char *name = "unknown"; if ( pPlayer ) @@ -3103,6 +3111,15 @@ float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int nu to = &cmds[ i ]; ReadUsercmd( buf, to, from ); from = to; + + if ( ( fabs( to->forwardmove ) > sv_max_usercmd_move_magnitude.GetFloat() ) || + ( fabs( to->sidemove ) > sv_max_usercmd_move_magnitude.GetFloat() ) || + ( fabs( to->upmove ) > sv_max_usercmd_move_magnitude.GetFloat() ) ) + { + to->forwardmove = 0; + to->sidemove = 0; + to->upmove = 0; + } } // Client not fully connected or server has gone inactive or is paused, just ignore diff --git a/game/server/hl2/grenade_frag.cpp b/game/server/hl2/grenade_frag.cpp index c6f71a4e2..f900d3504 100644 --- a/game/server/hl2/grenade_frag.cpp +++ b/game/server/hl2/grenade_frag.cpp @@ -155,7 +155,10 @@ void CGrenadeFrag::OnRestore( void ) void CGrenadeFrag::CreateEffects( void ) { // Start up the eye glow - m_pMainGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false ); + if ( !m_pMainGlow.Get() ) + { + m_pMainGlow = CSprite::SpriteCreate("sprites/redglow1.vmt", GetLocalOrigin(), false); + } int nAttachment = LookupAttachment( "fuse" ); @@ -169,7 +172,10 @@ void CGrenadeFrag::CreateEffects( void ) } // Start up the eye trail - m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false ); + if ( !m_pGlowTrail.Get() ) + { + m_pGlowTrail = CSpriteTrail::SpriteTrailCreate("sprites/bluelaser1.vmt", GetLocalOrigin(), false); + } if ( m_pGlowTrail != NULL ) { diff --git a/game/server/hl2/npc_attackchopper.cpp b/game/server/hl2/npc_attackchopper.cpp index fd999d47d..4178f4b91 100644 --- a/game/server/hl2/npc_attackchopper.cpp +++ b/game/server/hl2/npc_attackchopper.cpp @@ -3745,6 +3745,8 @@ void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info ) } } + SendOnKilledGameEvent( info ); + Chopper_BecomeChunks( this ); StopLoopingSounds(); diff --git a/game/server/hl2/npc_combinedropship.cpp b/game/server/hl2/npc_combinedropship.cpp index 0cf07408f..1e8143c9f 100644 --- a/game/server/hl2/npc_combinedropship.cpp +++ b/game/server/hl2/npc_combinedropship.cpp @@ -1865,8 +1865,15 @@ void CNPC_CombineDropship::InputPickup( inputdata_t &inputdata ) return; } + CBaseAnimating *pTargetAnimating = pTarget->GetBaseAnimating(); + if ( !pTargetAnimating ) + { + Warning("npc_combinedropship %s with target %s wasn't a CBaseAnimating\n", STRING(GetEntityName()), STRING(iszTargetName) ); + return; + } + // Start heading to the point - m_hPickupTarget = pTarget; + m_hPickupTarget = pTargetAnimating; m_bHasDroppedOff = false; diff --git a/game/server/hl2mp/hl2mp_player.cpp b/game/server/hl2mp/hl2mp_player.cpp index 1469d8420..43d11daa7 100644 --- a/game/server/hl2mp/hl2mp_player.cpp +++ b/game/server/hl2mp/hl2mp_player.cpp @@ -952,7 +952,7 @@ bool CHL2MP_Player::HandleCommand_JoinTeam( int team ) if ( team == TEAM_SPECTATOR ) { // Prevent this is the cvar is set - if ( !mp_allowspectators.GetInt() ) + if ( !mp_allowspectators.GetInt() && !IsHLTV() ) { ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); return false; diff --git a/game/server/hltvdirector.cpp b/game/server/hltvdirector.cpp index a0be9f016..a75673e45 100644 --- a/game/server/hltvdirector.cpp +++ b/game/server/hltvdirector.cpp @@ -354,7 +354,7 @@ void CHLTVDirector::StartDelayMessage() void CHLTVDirector::StartBestPlayerCameraShot() { - float flPlayerRanking[MAX_PLAYERS]; + float flPlayerRanking[MAX_PLAYERS_ARRAY_SAFE]; memset( flPlayerRanking, 0, sizeof(flPlayerRanking) ); diff --git a/game/server/hltvdirector.h b/game/server/hltvdirector.h index 1b390e834..24891c80c 100644 --- a/game/server/hltvdirector.h +++ b/game/server/hltvdirector.h @@ -112,7 +112,7 @@ class CHLTVDirector : public CGameEventListener, public CBaseGameSystemPerFrame, CBaseEntity *m_pFixedCameras[MAX_NUM_CAMERAS]; // fixed cameras (point_viewcontrol) int m_nNumActivePlayers; //number of cameras in current map - CBasePlayer *m_pActivePlayers[MAX_PLAYERS]; // fixed cameras (point_viewcontrol) + CBasePlayer *m_pActivePlayers[MAX_PLAYERS_ARRAY_SAFE]; // fixed cameras (point_viewcontrol) int m_iCameraManIndex; // entity index of current camera man or 0 CUtlRBTree m_EventHistory; diff --git a/game/server/movehelper_server.cpp b/game/server/movehelper_server.cpp index d477780c7..b2e6e29bf 100644 --- a/game/server/movehelper_server.cpp +++ b/game/server/movehelper_server.cpp @@ -362,8 +362,11 @@ bool CMoveHelperServer::PlayerFallingDamage( void ) float flFallDamage = g_pGameRules->FlPlayerFallDamage( m_pHostPlayer ); if ( flFallDamage > 0 ) { - m_pHostPlayer->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flFallDamage, DMG_FALL ) ); - StartSound( m_pHostPlayer->GetAbsOrigin(), "Player.FallDamage" ); + int iDamageTaken = m_pHostPlayer->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flFallDamage, DMG_FALL ) ); + if ( iDamageTaken > 0 ) + { + StartSound( m_pHostPlayer->GetAbsOrigin(), "Player.FallDamage" ); + } //============================================================================= // HPE_BEGIN: diff --git a/game/server/nav_file.cpp b/game/server/nav_file.cpp index 9d2450bb9..89ae3cd5e 100644 --- a/game/server/nav_file.cpp +++ b/game/server/nav_file.cpp @@ -14,6 +14,8 @@ #include "gamerules.h" #include "datacache/imdlcache.h" +#include "tier1/fmtstr.h" + #include "tier2/tier2.h" #include "tier2/p4helpers.h" #include "tier2/fileutils.h" @@ -30,6 +32,8 @@ #include "cs_nav_area.h" #endif +#include "util_shared.h" + // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" @@ -1199,7 +1203,17 @@ bool CNavMesh::Save( void ) const if ( !filesystem->WriteFile( filename, "MOD", fileBuffer ) ) { + // XXX(JohnS): Nav bails out after analyze regardless of it failed to save work, meaning if your .nav is + // read-only you're about to throw away everything. This code is old and bad. Just make a generous + // effort to save a backup, since this is common with e.g. read-only p4 nav files. + CFmtStrN< MAX_PATH > sBackupFile( "%s.failedsave", filename ); // .bak voted too likely to conflict with user + // saved files Warning( "Unable to save %d bytes to %s\n", fileBuffer.Size(), filename ); + + if ( filesystem->WriteFile( sBackupFile, "MOD", fileBuffer ) ) + { + Warning( "NAV failed to save, saved backup copy to '%s'\n", sBackupFile.Get() ); + } return false; } @@ -1313,10 +1327,13 @@ static ConCommand nav_check_file_consistency( "nav_check_file_consistency", Comm */ const CUtlVector< Place > *CNavMesh::GetPlacesFromNavFile( bool *hasUnnamedPlaces ) { + char maptmp[256]; + const char *pszMapName = GetCleanMapName( STRING( gpGlobals->mapname ), maptmp ); + placeDirectory.Reset(); // nav filename is derived from map filename char filename[256]; - Q_snprintf( filename, sizeof( filename ), FORMAT_NAVFILE, STRING( gpGlobals->mapname ) ); + Q_snprintf( filename, sizeof( filename ), FORMAT_NAVFILE, pszMapName ); CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::READ_ONLY ); if ( GetNavDataFromFile( fileBuffer ) != NAV_OK ) diff --git a/game/server/player.cpp b/game/server/player.cpp index eed38d5be..f7dc0c197 100644 --- a/game/server/player.cpp +++ b/game/server/player.cpp @@ -593,9 +593,11 @@ CBasePlayer::CBasePlayer( ) m_hZoomOwner = NULL; + m_bPendingClientSettings = false; m_nUpdateRate = 20; // cl_updaterate defualt m_fLerpTime = 0.1f; // cl_interp default m_bPredictWeapons = true; + m_bRequestPredict = true; m_bLagCompensation = false; m_flLaggedMovementValue = 1.0f; m_StuckLast = 0; @@ -636,7 +638,7 @@ CBasePlayer::CBasePlayer( ) m_vecConstraintCenter = vec3_origin; m_flLastUserCommandTime = 0.f; - m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; + m_nMovementTicksForUserCmdProcessingRemaining = 0; m_flLastObjectiveTime = -1.f; } @@ -3072,6 +3074,16 @@ int CBasePlayer::DetermineSimulationTicks( void ) simulation_ticks += ctx->numcmds + ctx->dropped_packets; } + // Only allow rewinding if we actually behind by at least that many ticks. + // This doesn't quite guarantee that m_nTickBase is monotonically increasing, but it gets close and prevents users + // from manipulating the game time by more than 0.25s. + // + // REI- Ideally I'd like to put more serious restrictions on user command timing here, to lockstep the clients + // a bit harder and prevent even this 0.25s manipulation, but my experiments so far led to unacceptable hitching + // even for legitimate users. + if ( simulation_ticks > m_nMovementTicksForUserCmdProcessingRemaining ) + simulation_ticks = m_nMovementTicksForUserCmdProcessingRemaining; + return simulation_ticks; } @@ -3208,6 +3220,9 @@ void CBasePlayer::PhysicsSimulate( void ) m_nSimulationTick = gpGlobals->tickcount; + // Grant the client some time buffer to execute user commands + m_nMovementTicksForUserCmdProcessingRemaining++; + // See how many CUserCmds are queued up for running int simulation_ticks = DetermineSimulationTicks(); @@ -3314,6 +3329,15 @@ void CBasePlayer::PhysicsSimulate( void ) float vphysicsArrivalTime = TICK_INTERVAL; + // Now run the commands + MoveHelperServer()->SetHost( this ); + + // Suppress predicted events, etc. + if ( IsPredictingWeapons() ) + { + IPredictionSystem::SuppressHostEvents( this ); + } + #ifdef _DEBUG if ( sv_player_net_suppress_usercommands.GetBool() ) { @@ -3321,39 +3345,56 @@ void CBasePlayer::PhysicsSimulate( void ) } #endif // _DEBUG - int numUsrCmdProcessTicksMax = sv_maxusrcmdprocessticks.GetInt(); - if ( gpGlobals->maxClients != 1 && numUsrCmdProcessTicksMax ) // don't apply this filter in SP games + // Process user commands + if ( commandsToRun > 0 ) { - // Grant the client some time buffer to execute user commands - m_flMovementTimeForUserCmdProcessingRemaining += TICK_INTERVAL; - - // but never accumulate more than N ticks - if ( m_flMovementTimeForUserCmdProcessingRemaining > numUsrCmdProcessTicksMax * TICK_INTERVAL ) - m_flMovementTimeForUserCmdProcessingRemaining = numUsrCmdProcessTicksMax * TICK_INTERVAL; + for ( int i = 0; i < commandsToRun; ++i ) + { + PlayerRunCommand( &vecAvailCommands[ i ], MoveHelperServer() ); + m_flLastUserCommandTime = savetime; + + // Update our vphysics object. + if ( m_pPhysicsController ) + { + VPROF( "CBasePlayer::PhysicsSimulate-UpdateVPhysicsPosition" ); + // If simulating at 2 * TICK_INTERVAL, add an extra TICK_INTERVAL to position arrival computation + UpdateVPhysicsPosition( m_vNewVPhysicsPosition, m_vNewVPhysicsVelocity, vphysicsArrivalTime ); + vphysicsArrivalTime += TICK_INTERVAL; + } + } } - else + else if ( GetTimeSinceLastUserCommand() > sv_player_usercommand_timeout.GetFloat() ) { - // Otherwise we don't care to track time - m_flMovementTimeForUserCmdProcessingRemaining = FLT_MAX; + // no usercommand from player after some threshold + // server should start RunNullCommand as if client sends an empty command so that Think and gamestate related things run properly + RunNullCommand(); } - // Now run the commands - if ( commandsToRun > 0 ) + int nMaxTicks = sv_maxusrcmdprocessticks.GetInt(); + if ( nMaxTicks && gpGlobals->maxClients != 1 ) // Don't apply this filter in SP games { - m_flLastUserCommandTime = savetime; - - MoveHelperServer()->SetHost( this ); - - // Suppress predicted events, etc. - if ( IsPredictingWeapons() ) + if ( m_nMovementTicksForUserCmdProcessingRemaining > nMaxTicks ) { - IPredictionSystem::SuppressHostEvents( this ); - } - - for ( int i = 0; i < commandsToRun; ++i ) - { - PlayerRunCommand( &vecAvailCommands[ i ], MoveHelperServer() ); + //DevMsg( "Client %s dropped too many packets, simulating last cmd\n", m_szNetname ); + + // Run a copy of the user's last command + // but make sure it's valid + CUserCmd cmd = m_LastCmd; + cmd.tick_count = gpGlobals->tickcount; + cmd.viewangles = EyeAngles(); + pl.fixangle = FIXANGLE_NONE; // this forces use of cmd.viewangles directly, not as a relative value + PlayerRunCommand( &cmd, MoveHelperServer() ); + + if ( m_nMovementTicksForUserCmdProcessingRemaining > nMaxTicks ) + { + // If this happens the user managed to execute a 'null' command that didn't make it through simulation. + // This means we should adjust the code above to make sure it always generates a valid command. + //Assert( false ); // security failure, airstuck! + // still make sure to avoid speedhax + m_nMovementTicksForUserCmdProcessingRemaining = nMaxTicks; + } + // Update our vphysics object. if ( m_pPhysicsController ) { @@ -3363,28 +3404,22 @@ void CBasePlayer::PhysicsSimulate( void ) vphysicsArrivalTime += TICK_INTERVAL; } } + } - // Always reset after running commands - IPredictionSystem::SuppressHostEvents( NULL ); + // Always reset after running commands + IPredictionSystem::SuppressHostEvents( NULL ); - MoveHelperServer()->SetHost( NULL ); + MoveHelperServer()->SetHost( NULL ); - // Copy in final origin from simulation - CPlayerSimInfo *pi = NULL; - if ( m_vecPlayerSimInfo.Count() > 0 ) - { - pi = &m_vecPlayerSimInfo[ m_vecPlayerSimInfo.Tail() ]; - pi->m_flTime = Plat_FloatTime(); - pi->m_vecAbsOrigin = GetAbsOrigin(); - pi->m_flGameSimulationTime = gpGlobals->curtime; - pi->m_nNumCmds = commandsToRun; - } - } - else if ( GetTimeSinceLastUserCommand() > sv_player_usercommand_timeout.GetFloat() ) + // Copy in final origin from simulation + CPlayerSimInfo *pi = NULL; + if ( m_vecPlayerSimInfo.Count() > 0 ) { - // no usercommand from player after some threshold - // server should start RunNullCommand as if client sends an empty command so that Think and gamestate related things run properly - RunNullCommand(); + pi = &m_vecPlayerSimInfo[ m_vecPlayerSimInfo.Tail() ]; + pi->m_flTime = Plat_FloatTime(); + pi->m_vecAbsOrigin = GetAbsOrigin(); + pi->m_flGameSimulationTime = gpGlobals->curtime; + pi->m_nNumCmds = commandsToRun; } // Restore the true server clock @@ -3407,6 +3442,169 @@ void CBasePlayer::ForceSimulation() m_nSimulationTick = -1; } +//----------------------------------------------------------------------------- +// Purpose: Callback from engine when this player's client settings (userinfo) change +//----------------------------------------------------------------------------- +void CBasePlayer::ClientSettingsChanged() +{ + if ( !g_pGameRules->IsConnectedUserInfoChangeAllowed( this ) ) + { + m_bPendingClientSettings = true; + return; + } + + #define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( this->entindex(), v )) + + // get network setting for prediction & lag compensation + + // Unfortunately, we have to duplicate the code in cdll_bounded_cvars.cpp here because the client + // doesn't send the virtualized value up (because it has no way to know when the virtualized value + // changes). Possible todo: put the responsibility on the bounded cvar to notify the engine when + // its virtualized value has changed. + + this->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); + static const ConVar *pMinUpdateRate = g_pCVar->FindVar( "sv_minupdaterate" ); + static const ConVar *pMaxUpdateRate = g_pCVar->FindVar( "sv_maxupdaterate" ); + if ( pMinUpdateRate && pMaxUpdateRate ) + this->m_nUpdateRate = clamp( this->m_nUpdateRate, (int) pMinUpdateRate->GetFloat(), (int) pMaxUpdateRate->GetFloat() ); + + bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; + if ( useInterpolation ) + { + float flLerpRatio = Q_atof( QUICKGETCVARVALUE("cl_interp_ratio") ); + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + float flLerpAmount = Q_atof( QUICKGETCVARVALUE("cl_interp") ); + + static const ConVar *pMin = g_pCVar->FindVar( "sv_client_min_interp_ratio" ); + static const ConVar *pMax = g_pCVar->FindVar( "sv_client_max_interp_ratio" ); + if ( pMin && pMax && pMin->GetFloat() != -1 ) + { + flLerpRatio = clamp( flLerpRatio, pMin->GetFloat(), pMax->GetFloat() ); + } + else + { + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + } + // #define FIXME_INTERP_RATIO + this->m_fLerpTime = MAX( flLerpAmount, flLerpRatio / this->m_nUpdateRate ); + } + else + { + this->m_fLerpTime = 0.0f; + } + +#if !defined( NO_ENTITY_PREDICTION ) + bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; + + if ( usePrediction ) + { + this->m_bRequestPredict = true; + this->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; + this->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; + } + else +#endif + { + this->m_bRequestPredict = false; + this->m_bPredictWeapons = false; + this->m_bLagCompensation = false; + } + + #undef QUICKGETCVARVALUE + + m_bPendingClientSettings = false; +} + +unsigned int CBasePlayer::PhysicsSolidMaskForEntity() const +{ + return MASK_PLAYERSOLID; +} + +//----------------------------------------------------------------------------- +// Purpose: This will force usercmd processing to actually consume commands even if the global tick counter isn't incrementing +//----------------------------------------------------------------------------- +void CBasePlayer::ForceSimulation() +{ + m_nSimulationTick = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Callback from engine when this player's client settings (userinfo) change +//----------------------------------------------------------------------------- +void CBasePlayer::ClientSettingsChanged() +{ + if ( !g_pGameRules->IsConnectedUserInfoChangeAllowed( this ) ) + { + m_bPendingClientSettings = true; + return; + } + + #define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( this->entindex(), v )) + + // get network setting for prediction & lag compensation + + // Unfortunately, we have to duplicate the code in cdll_bounded_cvars.cpp here because the client + // doesn't send the virtualized value up (because it has no way to know when the virtualized value + // changes). Possible todo: put the responsibility on the bounded cvar to notify the engine when + // its virtualized value has changed. + + this->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); + static const ConVar *pMinUpdateRate = g_pCVar->FindVar( "sv_minupdaterate" ); + static const ConVar *pMaxUpdateRate = g_pCVar->FindVar( "sv_maxupdaterate" ); + if ( pMinUpdateRate && pMaxUpdateRate ) + this->m_nUpdateRate = clamp( this->m_nUpdateRate, (int) pMinUpdateRate->GetFloat(), (int) pMaxUpdateRate->GetFloat() ); + + bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; + if ( useInterpolation ) + { + float flLerpRatio = Q_atof( QUICKGETCVARVALUE("cl_interp_ratio") ); + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + float flLerpAmount = Q_atof( QUICKGETCVARVALUE("cl_interp") ); + + static const ConVar *pMin = g_pCVar->FindVar( "sv_client_min_interp_ratio" ); + static const ConVar *pMax = g_pCVar->FindVar( "sv_client_max_interp_ratio" ); + if ( pMin && pMax && pMin->GetFloat() != -1 ) + { + flLerpRatio = clamp( flLerpRatio, pMin->GetFloat(), pMax->GetFloat() ); + } + else + { + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + } + // #define FIXME_INTERP_RATIO + this->m_fLerpTime = MAX( flLerpAmount, flLerpRatio / this->m_nUpdateRate ); + } + else + { + this->m_fLerpTime = 0.0f; + } + +#if !defined( NO_ENTITY_PREDICTION ) + bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; + + if ( usePrediction ) + { + this->m_bRequestPredict = true; + this->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; + this->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; + } + else +#endif + { + this->m_bRequestPredict = false; + this->m_bPredictWeapons = false; + this->m_bLagCompensation = false; + } + + #undef QUICKGETCVARVALUE + + m_bPendingClientSettings = false; +} + ConVar sv_usercmd_custom_random_seed( "sv_usercmd_custom_random_seed", "1", FCVAR_CHEAT, "When enabled server will populate an additional random seed independent of the client" ); //----------------------------------------------------------------------------- @@ -4511,6 +4709,12 @@ void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) //----------------------------------------------------------------------------- void CBasePlayer::PostThink() { + // Attempt to apply pending client settings + if ( m_bPendingClientSettings ) + { + ClientSettingsChanged(); + } + m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); if ( !g_fGameOver && !m_iPlayerLocked ) @@ -5695,7 +5899,21 @@ CBaseEntity *CBasePlayer::GiveNamedItem( const char *pszName, int iSubType ) if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) { - pent->Touch( this ); +#ifdef HL2MP + // misyl: Fix player's spawned weapons being dropped + // if they can't pick them up at spawn or died too quickly, etc. + if ( pWeapon ) + { + if ( !BumpWeapon( pWeapon ) ) + { + UTIL_Remove( pWeapon ); + } + } + else +#endif + { + pent->Touch( this ); + } } return pent; @@ -6517,7 +6735,17 @@ bool CBasePlayer::ClientCommand( const CCommand &args ) angle.y = strtof( args[5], nullptr ); angle.z = 0.0f; - JumptoPosition( origin, angle ); + // If the player jumps outside world extents it will hangs the server + CWorld *world = GetWorldEntity(); + if ( world ) + { + Extent worldExtent; + world->GetWorldBounds( worldExtent.lo, worldExtent.hi ); + VectorMax( origin, worldExtent.lo, origin ); + VectorMin( origin, worldExtent.hi, origin ); + + JumptoPosition( origin, angle ); + } } return true; diff --git a/game/server/player.h b/game/server/player.h index bb65787d8..a837d9fbc 100644 --- a/game/server/player.h +++ b/game/server/player.h @@ -315,6 +315,9 @@ class CBasePlayer : public CBaseCombatCharacter // Forces processing of usercmds (e.g., even if game is paused, etc.) void ForceSimulation(); + // Process new user settings from the engine + void ClientSettingsChanged(); + unsigned int PhysicsSolidMaskForEntity( void ) const override; virtual void PreThink( void ); @@ -783,31 +786,31 @@ class CBasePlayer : public CBaseCombatCharacter uint64 GetSteamIDAsUInt64( void ); #endif - float GetRemainingMovementTimeForUserCmdProcessing() const { return m_flMovementTimeForUserCmdProcessingRemaining; } + float GetRemainingMovementTimeForUserCmdProcessing() const { return m_nMovementTicksForUserCmdProcessingRemaining; } float ConsumeMovementTimeForUserCmdProcessing( float flTimeNeeded ) { - if ( m_flMovementTimeForUserCmdProcessingRemaining <= 0.0f ) + if ( m_nMovementTicksForUserCmdProcessingRemaining <= 0.0f ) { return 0.0f; } - else if ( flTimeNeeded > m_flMovementTimeForUserCmdProcessingRemaining + FLT_EPSILON ) + else if ( flTimeNeeded > m_nMovementTicksForUserCmdProcessingRemaining + FLT_EPSILON ) { - float flResult = m_flMovementTimeForUserCmdProcessingRemaining; - m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; + float flResult = m_nMovementTicksForUserCmdProcessingRemaining; + m_nMovementTicksForUserCmdProcessingRemaining = 0.0f; return flResult; } else { - m_flMovementTimeForUserCmdProcessingRemaining -= flTimeNeeded; - if ( m_flMovementTimeForUserCmdProcessingRemaining < 0.0f ) - m_flMovementTimeForUserCmdProcessingRemaining = 0.0f; + m_nMovementTicksForUserCmdProcessingRemaining -= flTimeNeeded; + if ( m_nMovementTicksForUserCmdProcessingRemaining < 0.0f ) + m_nMovementTicksForUserCmdProcessingRemaining = 0.0f; return flTimeNeeded; } } private: // How much of a movement time buffer can we process from this user? - float m_flMovementTimeForUserCmdProcessingRemaining; + float m_nMovementTicksForUserCmdProcessingRemaining; // For queueing up CUserCmds and running them from PhysicsSimulate int GetCommandContextCount( void ) const; @@ -877,10 +880,13 @@ class CBasePlayer : public CBaseCombatCharacter char m_szAnimExtension[32]; + bool m_bPendingClientSettings; // User client settings changed, but we're not importing them + int m_nUpdateRate; // user snapshot rate cl_updaterate float m_fLerpTime; // users cl_interp bool m_bLagCompensation; // user wants lag compenstation bool m_bPredictWeapons; // user has client side predicted weapons + bool m_bRequestPredict; // user has client prediction enabled float GetDeathTime( void ) { return m_flDeathTime; } diff --git a/game/server/player_command.cpp b/game/server/player_command.cpp index 68fb3b52f..3ad5dff4f 100644 --- a/game/server/player_command.cpp +++ b/game/server/player_command.cpp @@ -315,10 +315,8 @@ void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd ); //----------------------------------------------------------------------------- void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *moveHelper ) { - const float playerCurTime = player->m_nTickBase * TICK_INTERVAL; - const float playerFrameTime = player->m_bGamePaused ? 0 : TICK_INTERVAL; - const float flTimeAllowedForProcessing = player->ConsumeMovementTimeForUserCmdProcessing( playerFrameTime ); - if ( !player->IsBot() && ( flTimeAllowedForProcessing < playerFrameTime ) ) + const int nTicksAllowedForProcessing = player->ConsumeMovementTicksForUserCmdProcessing( 1 ); + if ( !player->IsBot() && !player->IsHLTV() && ( nTicksAllowedForProcessing < 1 ) ) { // Make sure that the activity in command is erased because player cheated or dropped too many packets double dblWarningFrequencyThrottle = sv_maxusrcmdprocessticks_warning.GetFloat(); @@ -329,12 +327,15 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper if ( !s_dblLastWarningTime || ( dblTimeNow - s_dblLastWarningTime >= dblWarningFrequencyThrottle ) ) { s_dblLastWarningTime = dblTimeNow; - Warning( "sv_maxusrcmdprocessticks_warning at server tick %u: Ignored client %s usrcmd (%.6f < %.6f)!\n", gpGlobals->tickcount, player->GetPlayerName(), flTimeAllowedForProcessing, playerFrameTime ); + Warning( "sv_maxusrcmdprocessticks_warning at server tick %u: Ignored client %s usrcmd (more commands than available movement ticks)!\n", gpGlobals->tickcount, player->GetPlayerName() ); } } return; // Don't process this command } + const float playerCurTime = player->m_nTickBase * TICK_INTERVAL; + const float playerFrameTime = TICK_INTERVAL; + StartCommand( player, ucmd ); // Set globals appropriately diff --git a/game/server/player_resource.cpp b/game/server/player_resource.cpp index 450ada633..786e1214e 100644 --- a/game/server/player_resource.cpp +++ b/game/server/player_resource.cpp @@ -29,15 +29,15 @@ END_SEND_TABLE() BEGIN_DATADESC( CPlayerResource ) - // DEFINE_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_iPacketloss, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_bConnected, FIELD_INTEGER, MAX_PLAYERS+1 ), + // DEFINE_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_iPacketloss, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_bConnected, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), // DEFINE_FIELD( m_flNextPingUpdate, FIELD_FLOAT ), - // DEFINE_ARRAY( m_iTeam, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_bAlive, FIELD_INTEGER, MAX_PLAYERS+1 ), - // DEFINE_ARRAY( m_iHealth, FIELD_INTEGER, MAX_PLAYERS+1 ), + // DEFINE_ARRAY( m_iTeam, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_bAlive, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), + // DEFINE_ARRAY( m_iHealth, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE ), // DEFINE_FIELD( m_nUpdateCounter, FIELD_INTEGER ), // Function Pointers @@ -54,7 +54,7 @@ CPlayerResource *g_pPlayerResource; //----------------------------------------------------------------------------- void CPlayerResource::Spawn( void ) { - for ( int i=0; i < MAX_PLAYERS+1; i++ ) + for ( int i=0; i < MAX_PLAYERS_ARRAY_SAFE; i++ ) { Init( i ); } diff --git a/game/server/player_resource.h b/game/server/player_resource.h index a22850a48..76c4373ce 100644 --- a/game/server/player_resource.h +++ b/game/server/player_resource.h @@ -33,15 +33,15 @@ class CPlayerResource : public CBaseEntity // Data for each player that's propagated to all clients // Stored in individual arrays so they can be sent down via datatables - CNetworkArray( int, m_iPing, MAX_PLAYERS+1 ); - CNetworkArray( int, m_iScore, MAX_PLAYERS+1 ); - CNetworkArray( int, m_iDeaths, MAX_PLAYERS+1 ); - CNetworkArray( int, m_bConnected, MAX_PLAYERS+1 ); - CNetworkArray( int, m_iTeam, MAX_PLAYERS+1 ); - CNetworkArray( int, m_bAlive, MAX_PLAYERS+1 ); - CNetworkArray( int, m_iHealth, MAX_PLAYERS+1 ); - CNetworkArray( uint32, m_iAccountID, MAX_PLAYERS+1 ); - CNetworkArray( int, m_bValid, MAX_PLAYERS+1 ); + CNetworkArray( int, m_iPing, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_iScore, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_iDeaths, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_bConnected, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_iTeam, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_bAlive, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_iHealth, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( uint32, m_iAccountID, MAX_PLAYERS_ARRAY_SAFE ); + CNetworkArray( int, m_bValid, MAX_PLAYERS_ARRAY_SAFE ); int m_nUpdateCounter; }; diff --git a/game/server/smokestack.cpp b/game/server/smokestack.cpp index 21e9209a7..1736d0e89 100644 --- a/game/server/smokestack.cpp +++ b/game/server/smokestack.cpp @@ -203,7 +203,7 @@ bool CSmokeStack::KeyValue( const char *szKeyName, const char *szValue ) char szStrippedName[512]; m_iMaterialModel = PrecacheModel( pName ); - Q_StripExtension( pName, szStrippedName, Q_strlen(pName)+1 ); + Q_StripExtension( pName, szStrippedName, ARRAYSIZE( szStrippedName ) ); intp iLength = Q_strlen( szStrippedName ); szStrippedName[iLength-1] = '\0'; diff --git a/game/server/team_control_point_master.cpp b/game/server/team_control_point_master.cpp index 51aa49377..8759eb59b 100644 --- a/game/server/team_control_point_master.cpp +++ b/game/server/team_control_point_master.cpp @@ -1292,6 +1292,9 @@ void CTeamControlPointMaster::ListRounds( void ) //----------------------------------------------------------------------------- void cc_ListRounds( void ) { + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + { return; } + CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; if ( pMaster ) { @@ -1299,13 +1302,16 @@ void cc_ListRounds( void ) } } -static ConCommand listrounds( "listrounds", cc_ListRounds, "List the rounds for the current map", FCVAR_CHEAT ); +static ConCommand tf_listrounds( "tf_listrounds", cc_ListRounds, "List the rounds for the current map", FCVAR_CHEAT ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void cc_PlayRound( const CCommand& args ) { + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + { return; } + if ( args.ArgC() > 1 ) { CTeamplayRoundBasedRules *pRules = dynamic_cast( GameRules() ); @@ -1332,9 +1338,9 @@ void cc_PlayRound( const CCommand& args ) } else { - ConMsg( "Usage: playround < round name >\n" ); + ConMsg( "Usage: tf_playround < round name >\n" ); } } -static ConCommand playround( "playround", cc_PlayRound, "Play the selected round\n\tArgument: {round name given by \"listrounds\" command}", FCVAR_CHEAT ); +static ConCommand tf_playround( "tf_playround", cc_PlayRound, "Play the selected round\n\tArgument: {round name given by \"tf_listrounds\" command}", FCVAR_CHEAT ); #endif \ No newline at end of file diff --git a/game/server/team_train_watcher.cpp b/game/server/team_train_watcher.cpp index 7b9a5aec5..06a644dbd 100644 --- a/game/server/team_train_watcher.cpp +++ b/game/server/team_train_watcher.cpp @@ -421,8 +421,10 @@ void CTeamTrainWatcher::FireGameEvent( IGameEvent *event ) const char *pszEventName = event->GetName(); if ( FStrEq( pszEventName, "path_track_passed" ) ) { - int iIndex = event->GetInt( "index" ); - CPathTrack *pNode = dynamic_cast< CPathTrack* >( UTIL_EntityByIndex( iIndex ) ); + // Josh: This comes straight through, server->server + // from a GetRefEHandle().ToInt() so this is safe. + CBaseHandle hNode = CBaseHandle::UnsafeFromIndex( event->GetInt( "index" ) ); + CPathTrack *pNode = dynamic_cast< CPathTrack* >( CBaseEntity::Instance( hNode ) ); if ( pNode ) { diff --git a/game/server/triggers.cpp b/game/server/triggers.cpp index da3ee5c1f..97ab0a331 100644 --- a/game/server/triggers.cpp +++ b/game/server/triggers.cpp @@ -155,11 +155,13 @@ void CBaseTrigger::InputDisable( inputdata_t &inputdata ) //------------------------------------------------------------------------------ void CBaseTrigger::InputDisableAndEndTouch( inputdata_t &inputdata ) { - FOR_EACH_VEC_BACK( m_hTouchingEntities, i ) + // EndTouch may delete an arbitrary number of entities from the touch list + for ( int i = m_hTouchingEntities.Count() - 1; i >= 0; i = m_hTouchingEntities.Count() - 1 ) { if ( m_hTouchingEntities[i] ) { EndTouch( m_hTouchingEntities[ i ] ); + Assert( i > m_hTouchingEntities.Count() - 1 ); } else { diff --git a/game/shared/SoundEmitterSystem.cpp b/game/shared/SoundEmitterSystem.cpp index 5024c7fe2..457ebdf6c 100644 --- a/game/shared/SoundEmitterSystem.cpp +++ b/game/shared/SoundEmitterSystem.cpp @@ -15,6 +15,7 @@ #include "tier0/vprof.h" #include "checksum_crc.h" #include "tier0/icommandline.h" +#include "util_shared.h" #if defined( TF_CLIENT_DLL ) || defined( TF_DLL ) #include "tf_shareddefs.h" @@ -49,7 +50,7 @@ static ConVar *g_pClosecaption = NULL; int LookupStringFromCloseCaptionToken( char const *token ); const wchar_t *GetStringForIndex( int index ); #endif -static bool g_bPermitDirectSoundPrecache = false; +bool g_bPermitDirectSoundPrecache = false; #if !defined( CLIENT_DLL ) @@ -264,12 +265,15 @@ class CSoundEmitterSystem : public CBaseGameSystem StartLog(); Q_snprintf( mapname, sizeof( mapname ), "maps/%s", STRING( gpGlobals->mapname ) ); #else - Q_strncpy( mapname, engine->GetLevelName(), sizeof( mapname ) ); + Q_snprintf( mapname, sizeof( mapname ), "maps/%s", V_GetFileName( engine->GetLevelName() ) ); #endif Q_FixSlashes( mapname ); Q_strlower( mapname ); + char maptmp[256]; + const char *pszCleanMapName = GetCleanMapName( mapname, maptmp ); + // Load in any map specific overrides char scriptfile[ 512 ]; #if defined( TF_CLIENT_DLL ) || defined( TF_DLL ) @@ -296,7 +300,7 @@ class CSoundEmitterSystem : public CBaseGameSystem } else { - Q_StripExtension( mapname, scriptfile, sizeof( scriptfile ) ); + Q_StripExtension( pszCleanMapName, scriptfile, sizeof( scriptfile ) ); Q_strncat( scriptfile, "_level_sounds.txt", sizeof( scriptfile ), COPY_ALL_CHARACTERS ); if ( filesystem->FileExists( scriptfile, "GAME" ) ) { @@ -304,7 +308,7 @@ class CSoundEmitterSystem : public CBaseGameSystem } } #else - Q_StripExtension( mapname, scriptfile, sizeof( scriptfile ) ); + Q_StripExtension( pszCleanMapName, scriptfile, sizeof( scriptfile ) ); Q_strncat( scriptfile, "_level_sounds.txt", sizeof( scriptfile ), COPY_ALL_CHARACTERS ); if ( filesystem->FileExists( scriptfile, "GAME" ) ) diff --git a/game/shared/animation.cpp b/game/shared/animation.cpp index 8d1273174..9cbd31a63 100644 --- a/game/shared/animation.cpp +++ b/game/shared/animation.cpp @@ -861,7 +861,7 @@ void SetBodygroup( CStudioHdr *pstudiohdr, int& body, int iGroup, int iValue ) if (! pstudiohdr) return; - if (iGroup >= pstudiohdr->numbodyparts()) + if (iGroup < 0 || iGroup >= pstudiohdr->numbodyparts()) return; mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); @@ -880,7 +880,7 @@ int GetBodygroup( CStudioHdr *pstudiohdr, int body, int iGroup ) if (! pstudiohdr) return 0; - if (iGroup >= pstudiohdr->numbodyparts()) + if (iGroup < 0 || iGroup >= pstudiohdr->numbodyparts()) return 0; mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); @@ -898,7 +898,7 @@ const char *GetBodygroupName( CStudioHdr *pstudiohdr, int iGroup ) if ( !pstudiohdr) return ""; - if (iGroup >= pstudiohdr->numbodyparts()) + if (iGroup < 0 || iGroup >= pstudiohdr->numbodyparts()) return ""; mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); @@ -928,7 +928,7 @@ int GetBodygroupCount( CStudioHdr *pstudiohdr, int iGroup ) if ( !pstudiohdr ) return 0; - if (iGroup >= pstudiohdr->numbodyparts()) + if (iGroup < 0 || iGroup >= pstudiohdr->numbodyparts()) return 0; mstudiobodyparts_t *pbodypart = pstudiohdr->pBodypart( iGroup ); diff --git a/game/shared/baseachievement.h b/game/shared/baseachievement.h index ddf9c82c6..c89cabe45 100644 --- a/game/shared/baseachievement.h +++ b/game/shared/baseachievement.h @@ -241,10 +241,18 @@ class CBaseAchievementHelper static CBaseAchievementHelper *s_pFirst; }; +#if defined( TF_DLL ) || defined ( TF_CLIENT_DLL ) +#define CHECK_ACHIEVEMENT_LIST(x) x##_YouForgotTheAchievementList; +#else +// Other games dont use this. +#define CHECK_ACHIEVEMENT_LIST(x) +#endif + #define DECLARE_ACHIEVEMENT_( className, achievementID, achievementName, gameDirFilter, iPointValue, bHidden ) \ static CBaseAchievement *Create_##className( void ) \ { \ CBaseAchievement *pAchievement = new className( ); \ + CHECK_ACHIEVEMENT_LIST( className ); \ pAchievement->SetAchievementID( achievementID ); \ pAchievement->SetName( achievementName ); \ pAchievement->SetPointValue( iPointValue ); \ diff --git a/game/shared/cs_achievements_and_stats_interface.cpp b/game/shared/cs_achievements_and_stats_interface.cpp index 66665c16d..ad7a60dd7 100644 --- a/game/shared/cs_achievements_and_stats_interface.cpp +++ b/game/shared/cs_achievements_and_stats_interface.cpp @@ -43,7 +43,7 @@ void CSAchievementsAndStatsInterface::CreatePanel( vgui::Panel* pParent ) // Create achievement & stats dialog if not already created if ( !m_pAchievementAndStatsSummary ) { - m_pAchievementAndStatsSummary = new CAchievementAndStatsSummary(NULL); + m_pAchievementAndStatsSummary = new CAchievementAndStatsSummary( pParent ); } if ( m_pAchievementAndStatsSummary ) diff --git a/game/shared/mapentities_shared.cpp b/game/shared/mapentities_shared.cpp index 784721678..d6fa96f3b 100644 --- a/game/shared/mapentities_shared.cpp +++ b/game/shared/mapentities_shared.cpp @@ -175,6 +175,11 @@ const char *MapEntity_ParseToken( const char *data, char *newToken ) { newToken[len] = c; len++; + + if ( len >= MAPKEY_MAXLENGTH ) + { + len--; + } newToken[len] = 0; return data+1; } @@ -185,9 +190,6 @@ const char *MapEntity_ParseToken( const char *data, char *newToken ) newToken[len] = c; data++; len++; - c = *data; - if ( s_BraceCharacters[c] /*c=='{' || c=='}'|| c==')'|| c=='(' || c=='\''*/ ) - break; if ( len >= MAPKEY_MAXLENGTH ) { @@ -195,8 +197,16 @@ const char *MapEntity_ParseToken( const char *data, char *newToken ) newToken[len] = 0; } + c = *data; + if ( s_BraceCharacters[c] /*c=='{' || c=='}'|| c==')'|| c=='(' || c=='\''*/ ) + break; + } while (c>32); + if ( len >= MAPKEY_MAXLENGTH ) + { + len--; + } newToken[len] = 0; return data; } diff --git a/game/shared/multiplay_gamerules.cpp b/game/shared/multiplay_gamerules.cpp index 339f59cd9..e5cf146d7 100644 --- a/game/shared/multiplay_gamerules.cpp +++ b/game/shared/multiplay_gamerules.cpp @@ -42,6 +42,11 @@ #include "NextBotManager.h" #endif +#ifdef TF_DLL + #include + #include "hl2orange.spa.h" +#endif + // TODO Why did we add this to the base class guys. #if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL ) #include "player_vs_environment/tf_population_manager.h" @@ -1175,7 +1180,7 @@ ConVarRef suitcharger( "sk_suitcharger" ); DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false ); // Check the time of the mapcycle file and re-populate the list of level names if the file has been modified - const time_t nMapCycleTimeStamp = filesystem->GetPathTime( mapcfile, "GAME" ); + const time_t nMapCycleTimeStamp = filesystem->GetPathTime( mapcfile, "MOD" ); if ( 0 == nMapCycleTimeStamp ) { @@ -1227,12 +1232,30 @@ ConVarRef suitcharger( "sk_suitcharger" ); return; } - char szRecommendedName[ MAX_PATH ]; - V_sprintf_safe( szRecommendedName, "cfg/%s", pszVar ); + // Check cfg/foo first. Resolve dot-slashes only on the concatonated path, since "../foo" is valid if it + // matches "cfg/../foo". + // + // XXX Everything is awful bonus, V_RemoveDotSlashes("a/../b") returns false and the invalid "/b" parse, and the + // comment there says "for backwards compat". So we do "/cfg/%s" and then trim the first character on + // success because why not. + char szRecommendedNameWithSlash[ MAX_PATH ] = { 0 }; + V_sprintf_safe( szRecommendedNameWithSlash, "/cfg/%s", pszVar ); + char *pszRecommendedName = szRecommendedNameWithSlash + 1; + if ( !V_RemoveDotSlashes( szRecommendedNameWithSlash ) || + szRecommendedNameWithSlash[0] != CORRECT_PATH_SEPARATOR || !*pszRecommendedName ) + { + if ( bForceSpew || V_stricmp( szLastResult, "__novar") ) + { + Msg( "mapcyclefile convar is not a valid path.\n" ); + V_strcpy_safe( szLastResult, "__novar" ); + } + *pszResult = '\0'; + return; + } // First, look for a mapcycle file in the cfg directory, which is preferred - V_strncpy( pszResult, szRecommendedName, nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) + V_strncpy( pszResult, pszRecommendedName, nSizeResult ); + if ( filesystem->FileExists( pszResult, "MOD" ) ) { if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) { @@ -1242,27 +1265,43 @@ ConVarRef suitcharger( "sk_suitcharger" ); return; } - // Nope? Try the root. - V_strncpy( pszResult, pszVar, nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) + // Nope? Try the root. Resolve dot-slashes in the path in isolation since "../foo" is now not allowed from + // there. Same note as above about V_RemoveDotSlashes being actually broken. + char szCleanPathWithSlash[ MAX_PATH ] = { 0 }; + V_sprintf_safe( szCleanPathWithSlash, "/%s", pszVar ); + char *pszCleanPath = szCleanPathWithSlash + 1; + if ( !V_RemoveDotSlashes( szCleanPathWithSlash ) || szCleanPathWithSlash[0] != CORRECT_PATH_SEPARATOR || !pszCleanPath ) + { + if ( bForceSpew || V_stricmp( szLastResult, "__novar") ) + { + Msg( "mapcyclefile convar is not a valid path.\n" ); + V_strcpy_safe( szLastResult, "__novar" ); + } + *pszResult = '\0'; + return; + } + + + V_strncpy( pszResult, pszCleanPath, nSizeResult ); + if ( filesystem->FileExists( pszResult, "MOD" ) ) { if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) { - Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); + Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, pszRecommendedName ); V_strcpy_safe( szLastResult, pszResult ); } return; } // Nope? Use the default. - if ( !V_stricmp( pszVar, "mapcycle.txt" ) ) + if ( !V_stricmp( pszCleanPath, "mapcycle.txt" ) ) { V_strncpy( pszResult, "cfg/mapcycle_default.txt", nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) + if ( filesystem->FileExists( pszResult, "MOD" ) ) { if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) { - Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); + Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, pszRecommendedName ); V_strcpy_safe( szLastResult, pszResult ); } return; @@ -1273,7 +1312,7 @@ ConVarRef suitcharger( "sk_suitcharger" ); *pszResult = '\0'; if ( bForceSpew || V_stricmp( szLastResult, "__notfound") ) { - Msg( "Map cycle file '%s' was not found.\n", szRecommendedName ); + Msg( "Map cycle file '%s' was not found.\n", pszRecommendedName ); V_strcpy_safe( szLastResult, "__notfound" ); } } @@ -1286,7 +1325,7 @@ ConVarRef suitcharger( "sk_suitcharger" ); void CMultiplayRules::RawLoadMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector &mapList ) { CUtlBuffer buf; - if ( !filesystem->ReadFile( pszMapCycleFile, "GAME", buf ) ) + if ( !filesystem->ReadFile( pszMapCycleFile, "MOD", buf ) ) return; buf.PutChar( 0 ); V_SplitString( buf.Base(), "\n", mapList ); @@ -1602,7 +1641,7 @@ ConVarRef suitcharger( "sk_suitcharger" ); CBaseMultiplayerPlayer *pMultiPlayerPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( pPlayer ); - if ( pMultiPlayerPlayer ) + if ( pMultiPlayerPlayer && pMultiPlayerPlayer->ShouldRunRateLimitedCommand( pcmd ) ) { int iMenu = atoi( args[1] ); int iItem = atoi( args[2] ); @@ -1616,6 +1655,18 @@ ConVarRef suitcharger( "sk_suitcharger" ); return BaseClass::ClientCommand( pEdict, args ); } +#ifdef TF_DLL + #define ACHIEVEMENT_LIST_(id) id + #define ACHIEVEMENT_LIST(className, achievementID, achievementName, iPointValue) \ + ACHIEVEMENT_LIST_(achievementID), + + static const std::unordered_set g_ValidAchiementIdxs = {{ + #include "achievements_tf_list.inc" + }}; + + #undef ACHIEVEMENT_LIST +#endif + void CMultiplayRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) { CBaseMultiplayerPlayer *pPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( CBaseEntity::Instance( pEntity ) ); @@ -1628,20 +1679,32 @@ ConVarRef suitcharger( "sk_suitcharger" ); { if ( FStrEq( pszCommand, "AchievementEarned" ) ) { - if ( pPlayer->ShouldAnnounceAchievement() ) - { - int nAchievementID = pKeyValues->GetInt( "achievementID" ); + if ( !pPlayer->ShouldAnnounceAchievement() ) + return; - IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned" ); - if ( event ) - { - event->SetInt( "player", pPlayer->entindex() ); - event->SetInt( "achievement", nAchievementID ); - gameeventmanager->FireEvent( event ); - } +int nAchievementID = pKeyValues->GetInt( "achievementID" ); + +#ifdef TF_DLL + // Josh: + // Bots are using this as a back-channel to communicate on our servers + // with invalid achievement indexes. + // I did want to use achievementmgr but that isn't available on the server -- + // nor are the achievement's actually DECLARED (they rely on a bunch of client code) + // so we have a list of achievements in achievements_tf_list.inc. + // Let's validate the achievement is actually valid before continuing... + if ( g_ValidAchiementIdxs.find( nAchievementID ) == g_ValidAchiementIdxs.end() ) + return; +#endif - pPlayer->OnAchievementEarned( nAchievementID ); + IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned" ); + if ( event ) + { + event->SetInt( "player", pPlayer->entindex() ); + event->SetInt( "achievement", nAchievementID ); + gameeventmanager->FireEvent( event ); } + + pPlayer->OnAchievementEarned( nAchievementID ); } } } diff --git a/game/shared/particle_property.cpp b/game/shared/particle_property.cpp index 9dec9a42d..bb59518b3 100644 --- a/game/shared/particle_property.cpp +++ b/game/shared/particle_property.cpp @@ -367,6 +367,9 @@ void CParticleProperty::StopParticlesInvolving( CBaseEntity *pEntity ) //----------------------------------------------------------------------------- void CParticleProperty::StopParticlesNamed( const char *pszEffectName, bool bForceRemoveInstantly /* =false */, bool bInverse /*= false*/ ) { + if ( !pszEffectName || !pszEffectName[0] ) + return; + CParticleSystemDefinition *pDef = g_pParticleSystemMgr->FindParticleSystem( pszEffectName ); AssertMsg1(pDef, "Could not find particle definition %s", pszEffectName ); if (!pDef) diff --git a/game/shared/physics_shared.cpp b/game/shared/physics_shared.cpp index c08501d98..ad88e7249 100644 --- a/game/shared/physics_shared.cpp +++ b/game/shared/physics_shared.cpp @@ -588,7 +588,7 @@ IPhysicsObject *PhysCreateWorld_Shared( CBaseEntity *pWorld, vcollide_t *pWorldC solid_t solid; fluid_t fluid; - if ( !physenv ) + if ( !physenv || !pWorldCollide || pWorldCollide->solidCount < 1 ) return NULL; int surfaceData = physprops->GetSurfaceIndex( "default" ); @@ -625,7 +625,7 @@ IPhysicsObject *PhysCreateWorld_Shared( CBaseEntity *pWorld, vcollide_t *pWorldC if ( solid.index == 0 ) continue; - if ( !pWorldCollide->solids[solid.index] ) + if ( !pWorldCollide->solids[solid.index] || solid.index >= pWorldCollide->solidCount ) { // this implies that the collision model is a mopp and the physics DLL doesn't support that. bCreateVirtualTerrain = true; @@ -653,7 +653,7 @@ IPhysicsObject *PhysCreateWorld_Shared( CBaseEntity *pWorld, vcollide_t *pWorldC pParse->ParseFluid( &fluid, NULL ); // create a fluid for floating - if ( fluid.index > 0 ) + if ( fluid.index > 0 && fluid.index < pWorldCollide->solidCount ) { solid.params = defaultParams; // copy world's params solid.params.enableCollisions = true; diff --git a/game/shared/shareddefs.h b/game/shared/shareddefs.h index 4cc547167..9456f8890 100644 --- a/game/shared/shareddefs.h +++ b/game/shared/shareddefs.h @@ -229,10 +229,24 @@ enum CastVote //Since this is decided by the gamerules (and it can be whatever number as long as its less than MAX_PLAYERS). #if defined( CSTRIKE_DLL ) #define MAX_PLAYERS 65 // Absolute max players supported +#elif defined( TF_DLL ) || defined ( TF_CLIENT_DLL ) || defined( HL2MP ) + #define MAX_PLAYERS 101 #else #define MAX_PLAYERS 33 // Absolute max players supported #endif +// Josh: Accounts for code that may index this array by an entindex +// of player rather than the player index... :s +#define MAX_PLAYERS_ARRAY_SAFE ( MAX_PLAYERS + 1 ) + +inline bool IsIndexIntoPlayerArrayValid( int iIndex ) +{ + if ( iIndex < 0 || iIndex >= MAX_PLAYERS_ARRAY_SAFE ) + return false; + + return true; +} + #define MAX_PLACE_NAME_LENGTH 18 #define MAX_FOV 110 @@ -252,6 +266,8 @@ enum CastVote #define MAX_TEAMS 32 // Max number of teams in a game #define MAX_TEAM_NAME_LENGTH 32 // Max length of a team's name +#define MAX_TEAMS_ARRAY_SAFE MAX_TEAMS + // Weapon m_iState #define WEAPON_IS_ONTARGET 0x40 diff --git a/game/shared/teamplayroundbased_gamerules.cpp b/game/shared/teamplayroundbased_gamerules.cpp index a715c0461..e5ed038e9 100644 --- a/game/shared/teamplayroundbased_gamerules.cpp +++ b/game/shared/teamplayroundbased_gamerules.cpp @@ -374,7 +374,16 @@ CON_COMMAND_F( mp_forcewin, "Forces team to win", FCVAR_CHEAT ) if ( args.ArgC() == 1 ) { // if no team specified, use player 1's team - iTeam = UTIL_PlayerByIndex( 1 )->GetTeamNumber(); + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if ( pPlayer ) + { + iTeam = pPlayer->GetTeamNumber(); + } + else + { + Msg( "Unable to determine default team. Usage: mp_forcewin \n" ); + return; + } } else if ( args.ArgC() == 2 ) { @@ -383,7 +392,7 @@ CON_COMMAND_F( mp_forcewin, "Forces team to win", FCVAR_CHEAT ) } else { - Msg( "Usage: mp_forcewin " ); + Msg( "Usage: mp_forcewin \n" ); return; } @@ -560,7 +569,7 @@ float CTeamplayRoundBasedRules::GetNextRespawnWave( int iTeam, CBasePlayer *pPla // If we are purely checking when the next respawn wave is for this team if ( pPlayer == NULL ) { - return m_flNextRespawnWave[iTeam]; + return GetNextRespawnWave(iTeam); } // The soonest this player may spawn @@ -571,7 +580,7 @@ float CTeamplayRoundBasedRules::GetNextRespawnWave( int iTeam, CBasePlayer *pPla } // the next scheduled respawn wave time - float flNextRespawnTime = m_flNextRespawnWave[iTeam]; + float flNextRespawnTime = GetNextRespawnWave( iTeam ); // the length of one respawn wave. We'll check in increments of this float flRespawnWaveMaxLen = GetRespawnWaveMaxLength( iTeam ); @@ -2899,6 +2908,12 @@ void CTeamplayRoundBasedRules::CleanUpMap() // Really remove the entities so we can have access to their slots below. gEntList.CleanupDeleteList(); + // Josh: Purge any template/script fixup name strings or whatever here! + // That way we don't run out of memory running the same map forever. + // We cannot free instantly due to string dependencies on events + // after the entity's death. + PurgeDeferredPooledStrings(); + engine->AllowImmediateEdictReuse(); if ( mp_showcleanedupents.GetInt() & 2 ) @@ -3425,6 +3440,9 @@ CTeamRoundTimer *CTeamplayRoundBasedRules::GetActiveRoundTimer( void ) //----------------------------------------------------------------------------- float CTeamplayRoundBasedRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers /* = true */ ) { + if ( iTeam >= MAX_TEAMS ) + return 0; + if ( State_Get() != GR_STATE_RND_RUNNING ) return 0; diff --git a/game/shared/teamplayroundbased_gamerules.h b/game/shared/teamplayroundbased_gamerules.h index 64e0424e8..15ec7160f 100644 --- a/game/shared/teamplayroundbased_gamerules.h +++ b/game/shared/teamplayroundbased_gamerules.h @@ -228,14 +228,36 @@ class CTeamplayRoundBasedRules : public CTeamplayRules, public CGameEventListene bool IsTeamReady( int iTeamNumber ) { + if ( iTeamNumber < 0 || iTeamNumber >= MAX_TEAMS_ARRAY_SAFE ) + return false; + return m_bTeamReady[iTeamNumber]; } bool IsPlayerReady( int iIndex ) { + if ( !IsIndexIntoPlayerArrayValid(iIndex) ) + return false; + return m_bPlayerReady[iIndex]; } + float GetNextRespawnWave( int iTeamNumber ) + { + if ( iTeamNumber < 0 || iTeamNumber >= MAX_TEAMS_ARRAY_SAFE ) + return 0.0f; + + return m_flNextRespawnWave[iTeamNumber]; + } + + void SetNextRespawnWave( int iTeamNumber, float flValue ) + { + if ( iTeamNumber < 0 || iTeamNumber >= MAX_TEAMS_ARRAY_SAFE ) + return; + + m_flNextRespawnWave.Set( iTeamNumber, flValue ); + } + virtual void HandleTeamScoreModify( int iTeam, int iScore) { }; float GetRoundRestartTime( void ) const { return m_flRestartRoundTime; } diff --git a/game/shared/util_shared.cpp b/game/shared/util_shared.cpp index 8a39f97f8..2cf57d864 100644 --- a/game/shared/util_shared.cpp +++ b/game/shared/util_shared.cpp @@ -1421,3 +1421,48 @@ EUniverse GetUniverse() static EUniverse steamUniverse = GetSteamUtils()->GetConnectedUniverse(); return steamUniverse; } + +#define WORKSHOP_PREFIX_1 "workshop/" +#define MAP_WORKSHOP_PREFIX_1 "maps/" WORKSHOP_PREFIX_1 + +#define WORKSHOP_PREFIX_2 "workshop\\" +#define MAP_WORKSHOP_PREFIX_2 "maps\\" WORKSHOP_PREFIX_2 + +const char *GetCleanMapName( const char *pszUnCleanMapName, char (&pszTmp)[256]) +{ +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + bool bPrefixMaps = true; + const char *pszMapAfterPrefix = StringAfterPrefixCaseSensitive( pszUnCleanMapName, MAP_WORKSHOP_PREFIX_1 ); + if ( !pszMapAfterPrefix ) + pszMapAfterPrefix = StringAfterPrefixCaseSensitive( pszUnCleanMapName, MAP_WORKSHOP_PREFIX_2 ); + + if ( !pszMapAfterPrefix ) + { + bPrefixMaps = false; + pszMapAfterPrefix = StringAfterPrefixCaseSensitive( pszUnCleanMapName, WORKSHOP_PREFIX_1 ); + if ( !pszMapAfterPrefix ) + pszMapAfterPrefix = StringAfterPrefixCaseSensitive( pszUnCleanMapName, WORKSHOP_PREFIX_2 ); + } + + if ( pszMapAfterPrefix ) + { + if ( bPrefixMaps ) + { + V_strcpy_safe( pszTmp, "maps" CORRECT_PATH_SEPARATOR_S ); + V_strcat_safe( pszTmp, pszMapAfterPrefix ); + } + else + { + V_strcpy_safe( pszTmp, pszMapAfterPrefix ); + } + + char *pszUGC = V_strstr( pszTmp, ".ugc" ); + if ( pszUGC ) + *pszUGC = '\0'; + + return pszTmp; + } +#endif + + return pszUnCleanMapName; +} \ No newline at end of file diff --git a/game/shared/util_shared.h b/game/shared/util_shared.h index 3c1b92810..e7dd5aa9c 100644 --- a/game/shared/util_shared.h +++ b/game/shared/util_shared.h @@ -659,4 +659,5 @@ Color LerpColor( const Color &c0, const Color &c1, float t ); // Global econ-level helper functionality. EUniverse GetUniverse(); +const char *GetCleanMapName( const char *pszUnCleanMapName, char (&pszTmp)[256] ); #endif // UTIL_SHARED_H diff --git a/game/shared/vehicle_viewblend_shared.cpp b/game/shared/vehicle_viewblend_shared.cpp index fab2d3096..c6faa343e 100644 --- a/game/shared/vehicle_viewblend_shared.cpp +++ b/game/shared/vehicle_viewblend_shared.cpp @@ -290,7 +290,10 @@ void SharedVehicleViewSmoothing(CBasePlayer *pPlayer, pData->pVehicle->GetAttachmentLocal( eyeAttachmentIndex, localEyeOrigin, localEyeAngles ); #ifdef CLIENT_DLL - engine->SetViewAngles( localEyeAngles ); + if ( pPlayer->IsLocalPlayer() ) + { + engine->SetViewAngles( localEyeAngles ); + } #endif } } diff --git a/public/dt_utlvector_recv.cpp b/public/dt_utlvector_recv.cpp index 40a6afac8..a93ed7256 100644 --- a/public/dt_utlvector_recv.cpp +++ b/public/dt_utlvector_recv.cpp @@ -29,6 +29,20 @@ class CRecvPropExtra_UtlVector void RecvProxy_UtlVectorLength( const CRecvProxyData *pData, void *pStruct, void *pOut ) { CRecvPropExtra_UtlVector *pExtra = (CRecvPropExtra_UtlVector*)pData->m_pRecvProp->GetExtraData(); + if ( pData->m_Value.m_Int < 0 || pData->m_Value.m_Int > pExtra->m_nMaxElements ) + { + // If this happens we're most likely talking to a malicious server. + // Protect against remote code execution by crashing ourselves. + // A malicious server can send an invalid lengthprop attribute and cause the below code + // to "successfully" resize the vector to -1, which eventually translates into a call to realloc(0) + // due to integer math overflow. + // Then the remaining payload ( the actual elements of the vector ) can be used + // to write arbitrary data to out of bounds memory. + // There isn't much we can do at this point - we're deep in the networking stack, it's hard to recover + // gracefully and we shouldn't be talking to this server anymore. + // So we crash. + *(int *) 1 = 2; + } pExtra->m_ResizeFn( pStruct, pExtra->m_Offset, pData->m_Value.m_Int ); } diff --git a/public/fgdlib/entitydefs.h b/public/fgdlib/entitydefs.h index f2fcd8d46..f6ea0b6d9 100644 --- a/public/fgdlib/entitydefs.h +++ b/public/fgdlib/entitydefs.h @@ -16,5 +16,6 @@ #define MAX_IO_NAME_LEN 256 +#define VMF_IOPARAM_STRING_DELIMITER 0x1b // Use ESC as a delimiter so we can pass commas etc. in I/O parameters #endif // ENTITYDEFS_H diff --git a/public/filesystem_helpers.cpp b/public/filesystem_helpers.cpp index 555923b3d..b1e7c5fa3 100644 --- a/public/filesystem_helpers.cpp +++ b/public/filesystem_helpers.cpp @@ -36,6 +36,9 @@ const char* ParseFileInternal( const char* pFileBytes, OUT_Z_CAP(nMaxTokenLen) c if (!pFileBytes) return 0; + if ( nMaxTokenLen <= 1 ) + return 0; + InitializeCharacterSets(); // YWB: Ignore colons as token separators in COM_Parse @@ -100,7 +103,15 @@ const char* ParseFileInternal( const char* pFileBytes, OUT_Z_CAP(nMaxTokenLen) c return pFileBytes; } pTokenOut[len] = c; - len += ( len < nMaxTokenLen-1 ) ? 1 : 0; + len++; + + // Ensure buffer length is not overrunning! + if ( len == nMaxTokenLen - 1 ) + { + pTokenOut[len] = 0; + Assert( 0 ); + return pFileBytes; + } } } @@ -118,7 +129,15 @@ const char* ParseFileInternal( const char* pFileBytes, OUT_Z_CAP(nMaxTokenLen) c { pTokenOut[len] = c; pFileBytes++; - len += ( len < nMaxTokenLen-1 ) ? 1 : 0; + len++; + + // Ensure buffer length is not overrunning! + if ( len == nMaxTokenLen - 1 ) + { + pTokenOut[ len ] = 0; + Assert( 0 ); + return pFileBytes; + } c = *pFileBytes; if ( IN_CHARACTERSET( breaks, c ) ) break; diff --git a/public/particles/particles.h b/public/particles/particles.h index 180ca7567..6d6cee35d 100644 --- a/public/particles/particles.h +++ b/public/particles/particles.h @@ -390,6 +390,7 @@ class CParticleSystemMgr // Cache/uncache materials used by particle systems void PrecacheParticleSystem( const char *pName ); void UncacheAllParticleSystems(); + void RecreateDictionary() { Assert(0); }; // ToDo! // Sets the last simulation time, used for particle system sleeping logic void SetLastSimulationTime( float flTime ); diff --git a/public/sentence.cpp b/public/sentence.cpp index 8579f960f..2edd653ee 100644 --- a/public/sentence.cpp +++ b/public/sentence.cpp @@ -600,7 +600,12 @@ void CSentence::ParseCloseCaption( CUtlBuffer& buf ) buf.GetString( token ); cc_length = atoi( token ); - Assert( cc_length >= 0 && cc_length < ssize( cc_stream ) ); + if ( cc_length < 0 || (unsigned int)cc_length >= ARRAYSIZE( cc_stream ) ) + { + Warning( "Invalid CloseCaption data - segment length %d is out of bounds\n", cc_length ); + AssertMsg( false, "Invalid CloseCaption data" ); + break; + } // Skip space (void)buf.GetChar(); buf.Get( cc_stream, cc_length ); diff --git a/public/studio.h b/public/studio.h index c56deeadb..72ee4254a 100644 --- a/public/studio.h +++ b/public/studio.h @@ -3272,6 +3272,12 @@ inline int Studio_LoadVertexes( const vertexFileHeader_t *pTempVvdHdr, vertexFil continue; } + if ( ( pFixupTable[ i ].numVertexes < 0 ) || ( target + pFixupTable[ i ].numVertexes > numVertexes ) ) + { + Assert( !"Malicious map attempting to write off the end of our fixup verts. Sad face." ); + Error( "Unable to load corrupted map." ); + } + // copy vertexes memcpy( (mstudiovertex_t *)((byte *)pNewVvdHdr+pNewVvdHdr->vertexDataStart) + target,