From 382313af70610cc623a769ffa4aff66b12fb87f2 Mon Sep 17 00:00:00 2001 From: Ioannis Marios Tsiakoulias Date: Wed, 25 Feb 2026 23:21:22 +0200 Subject: [PATCH] Fix dedicated restart crash: gate Steam stats until activated --- src/game/server/gameinterface.cpp | 54 +++++++++++++++++++ src/game/server/gameinterface.h | 7 ++- src/game/server/swarm/asw_player.cpp | 2 + src/game/server/swarm/asw_player.h | 2 + .../shared/swarm/asw_player_experience.cpp | 10 ++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/game/server/gameinterface.cpp b/src/game/server/gameinterface.cpp index 87ee6d245..393adee21 100644 --- a/src/game/server/gameinterface.cpp +++ b/src/game/server/gameinterface.cpp @@ -111,6 +111,7 @@ #include "missionchooser/iasw_mission_chooser_source.h" #include "matchmaking/swarm/imatchext_swarm.h" #include "asw_gamerules.h" +#include "asw_player.h" #include "asw_util_shared.h" #include "iconsistency.h" #endif @@ -613,6 +614,16 @@ static bool InitGameSystems( CreateInterfaceFn appSystemFactory ) CServerGameDLL g_ServerGameDLL; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); +// Crash fix/hardening: the engine can call into game code during lobby soft-close -> restart +// before Steam is fully activated for the new session. Track activation so code can safely +// skip Steam calls until the engine signals readiness. +static bool g_bRDSteamAPIActivated = false; +static float g_flNextDeferredSteamStatsRequestTime = 0.0f; + +bool RD_IsSteamAPIActivated() +{ + return g_bRDSteamAPIActivated; +} bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, @@ -1064,6 +1075,7 @@ bool CServerGameDLL::SupportsSaveRestore() bool CServerGameDLL::LevelInit( const char *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) { VPROF("CServerGameDLL::LevelInit"); + g_bRDSteamAPIActivated = false; ResetWindspeed(); UpdateChapterRestrictions( pMapName ); @@ -1266,6 +1278,11 @@ void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int cl void CServerGameDLL::GameServerSteamAPIActivated( void ) { // the Steam API pointers used to be initialized here, but that happens automatically now. + // Crash fix/hardening: mark Steam as activated so code can safely call Steam APIs. + g_bRDSteamAPIActivated = true; + + // If any players tried to request XP before Steam activation (restart/join window), retry now. + g_flNextDeferredSteamStatsRequestTime = 0.0f; } //----------------------------------------------------------------------------- @@ -1281,6 +1298,42 @@ void CServerGameDLL::GameFrame( bool simulating ) if ( g_InRestore ) return; + // Crash fix/hardening: if we deferred any Steam stats requests because Steam wasn't activated + // yet (e.g. lobby soft-close -> restart), retry them once Steam is ready. + static const float k_flDeferredSteamStatsRequestTimeout = 30.0f; + if ( g_bRDSteamAPIActivated && gpGlobals && gpGlobals->curtime >= g_flNextDeferredSteamStatsRequestTime ) + { + g_flNextDeferredSteamStatsRequestTime = gpGlobals->curtime + 1.0f; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CASW_Player *pPlayer = ToASW_Player( UTIL_PlayerByIndex( i ) ); + if ( !pPlayer ) + continue; + + if ( pPlayer->m_bDeferredSteamStatsRequest && !pPlayer->m_bPendingSteamStats ) + { + if ( pPlayer->m_flDeferredSteamStatsRequestStart < 0.0f ) + { + pPlayer->m_flDeferredSteamStatsRequestStart = gpGlobals->curtime; + } + + if ( ( gpGlobals->curtime - pPlayer->m_flDeferredSteamStatsRequestStart ) > k_flDeferredSteamStatsRequestTimeout ) + { + pPlayer->m_bDeferredSteamStatsRequest = false; + pPlayer->m_flDeferredSteamStatsRequestStart = -1.0f; + continue; + } + + pPlayer->RequestExperience(); + if ( pPlayer->m_bPendingSteamStats ) + { + pPlayer->m_bDeferredSteamStatsRequest = false; + pPlayer->m_flDeferredSteamStatsRequestStart = -1.0f; + } + } + } + } + #ifndef NO_STEAM // All the calls to us from the engine prior to gameframe (like LevelInit & ServerActivate) // are done before the engine has got the Steam API connected, so we have to wait until now to connect ourselves. @@ -1518,6 +1571,7 @@ void CServerGameDLL::LevelShutdown( void ) MDLCACHE_CRITICAL_SECTION(); IGameSystem::LevelShutdownPreEntityAllSystems(); + g_bRDSteamAPIActivated = false; // YWB: // This entity pointer is going away now and is corrupting memory on level transitions/restarts CSoundEnt::ShutdownSoundEnt(); diff --git a/src/game/server/gameinterface.h b/src/game/server/gameinterface.h index 3b38b0bcb..5e417baf1 100644 --- a/src/game/server/gameinterface.h +++ b/src/game/server/gameinterface.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Expose things from GameInterface.cpp. Mostly the engine interfaces. // @@ -211,6 +211,11 @@ class CMapLoadEntityFilter : public IMapEntityFilter bool IsEngineThreaded(); +// Crash fix support: during lobby soft-close -> restart, the engine can call into game code +// (including PlayerSpawn) before Steam is fully activated for the new session. +// This helper lets server-side code skip Steam calls until GameServerSteamAPIActivated fires. +bool RD_IsSteamAPIActivated(); + class CServerGameTags : public IServerGameTags { public: diff --git a/src/game/server/swarm/asw_player.cpp b/src/game/server/swarm/asw_player.cpp index 0d2f59926..dc7bbaecb 100644 --- a/src/game/server/swarm/asw_player.cpp +++ b/src/game/server/swarm/asw_player.cpp @@ -407,6 +407,8 @@ CASW_Player::CASW_Player() m_bPendingSteamStats = false; m_flPendingSteamStatsStart = 0.0f; + m_bDeferredSteamStatsRequest = false; + m_flDeferredSteamStatsRequestStart = -1.0f; m_bWelcomed = false; diff --git a/src/game/server/swarm/asw_player.h b/src/game/server/swarm/asw_player.h index c875a5566..c80abd10a 100644 --- a/src/game/server/swarm/asw_player.h +++ b/src/game/server/swarm/asw_player.h @@ -278,6 +278,8 @@ class CASW_Player : public CBaseMultiplayerPlayer, public IASWPlayerAnimStateHel bool m_bHasAwardedXP; bool m_bPendingSteamStats; float m_flPendingSteamStatsStart; + bool m_bDeferredSteamStatsRequest; + float m_flDeferredSteamStatsRequestStart; bool m_bSentPromotedMessage; // static inventory (medals) diff --git a/src/game/shared/swarm/asw_player_experience.cpp b/src/game/shared/swarm/asw_player_experience.cpp index 036672b79..7e64c541a 100644 --- a/src/game/shared/swarm/asw_player_experience.cpp +++ b/src/game/shared/swarm/asw_player_experience.cpp @@ -467,6 +467,16 @@ void CASW_Player::RequestExperience() #else if ( engine->IsDedicatedServer() ) { + // Crash fix/hardening: during lobby soft-close -> restart, PlayerSpawn can run before + // Steam is activated for the new session. Avoid calling RequestUserStats until the + // engine signals Steam readiness (GameServerSteamAPIActivated). + if ( !RD_IsSteamAPIActivated() ) + { + // Defer rather than skip: once Steam is activated, game code will retry this request. + m_bDeferredSteamStatsRequest = true; + return; + } + Assert( SteamGameServerStats() ); if ( SteamGameServerStats() ) {