From 1a8d339d3a39327b084cbe2aa9b1843c73d89dfb Mon Sep 17 00:00:00 2001 From: bobtista Date: Fri, 16 Jan 2026 19:10:26 -0500 Subject: [PATCH 1/3] bugfix(savegame): Add headless mode checks to prevent UI calls and skip visual blocks --- .../Common/System/SaveGame/GameState.cpp | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index f46cb876301..50c0b9fa1f9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -34,6 +34,7 @@ #include "Common/GameEngine.h" #include "Common/GameState.h" #include "Common/GameStateMap.h" +#include "Common/GlobalData.h" #include "Common/LatchRestore.h" #include "Common/MapObject.h" #include "Common/PlayerList.h" @@ -563,7 +564,8 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, xferSave.open( filepath ); } catch(...) { // print error message to the user - TheInGameUI->message( "GUI:Error" ); + if (!TheGlobalData->m_headless) + TheInGameUI->message( "GUI:Error" ); DEBUG_LOG(( "Error opening file '%s'", filepath.str() )); return SC_ERROR; } @@ -593,13 +595,16 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, catch( ... ) { - UnicodeString ufilepath; - ufilepath.translate(filepath); + if (!TheGlobalData->m_headless) + { + UnicodeString ufilepath; + ufilepath.translate(filepath); - UnicodeString msg; - msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), ufilepath.str() ); + UnicodeString msg; + msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), ufilepath.str() ); - MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr); + MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr); + } // close the file and get out of here xferSave.close(); @@ -611,8 +616,11 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, xferSave.close(); // print message to the user for game successfully saved - UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" ); - TheInGameUI->message( msg ); + if (!TheGlobalData->m_headless) + { + UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" ); + TheInGameUI->message( msg ); + } return SC_OK; @@ -719,13 +727,16 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) TheGameEngine->reset(); // print error message to the user - UnicodeString ufilepath; - ufilepath.translate(filepath); + if (!TheGlobalData->m_headless) + { + UnicodeString ufilepath; + ufilepath.translate(filepath); - UnicodeString msg; - msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() ); + UnicodeString msg; + msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() ); - MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr); + MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr); + } return SC_INVALID_DATA; // you can't use a naked "throw" outside of a catch statement! @@ -1365,6 +1376,24 @@ void GameState::xferSaveData( Xfer *xfer, SnapshotType which ) DEBUG_LOG(("Looking at block '%s'", blockName.str())); + // Skip blocks with nullptr snapshot (can happen in headless mode) + if( blockInfo->snapshot == nullptr ) + { + DEBUG_LOG(("Skipping block '%s' because snapshot is nullptr", blockName.str())); + continue; + } + + // Skip visual-only blocks when saving in headless mode + if( TheGlobalData->m_headless && + (blockName.compareNoCase( "CHUNK_TerrainVisual" ) == 0 || + blockName.compareNoCase( "CHUNK_TacticalView" ) == 0 || + blockName.compareNoCase( "CHUNK_ParticleSystem" ) == 0 || + blockName.compareNoCase( "CHUNK_GhostObject" ) == 0) ) + { + DEBUG_LOG(("Skipping block '%s' in headless mode", blockName.str())); + continue; + } + // // for mission save files, we only save the game state block and campaign manager // because anything else is not needed. From 795a39c8246d0ddb47e0b1a3acf072d29744f6cc Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 17 Jan 2026 13:17:08 -0500 Subject: [PATCH 2/3] bugfix(savegame): Add nullptr snapshot check to load path for headless mode --- .../Source/Common/System/SaveGame/GameState.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index 50c0b9fa1f9..c6069875df1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -1484,6 +1484,15 @@ void GameState::xferSaveData( Xfer *xfer, SnapshotType which ) } + // Skip blocks with nullptr snapshot (can happen in headless mode) + if( blockInfo->snapshot == nullptr ) + { + DEBUG_LOG(("Skipping block '%s' because snapshot is nullptr", blockInfo->blockName.str())); + Int dataSize = xfer->beginBlock(); + xfer->skip( dataSize ); + continue; + } + try { From 87c471feedb1bf3b3b60d06ebcb3bd2ecc4ee425 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 17 Jan 2026 13:28:16 -0500 Subject: [PATCH 3/3] bugfix(savegame): Add null checks for TheGlobalData before accessing m_headless --- .../Source/Common/System/SaveGame/GameState.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index c6069875df1..9e8cc41dbac 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -564,7 +564,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, xferSave.open( filepath ); } catch(...) { // print error message to the user - if (!TheGlobalData->m_headless) + if (TheGlobalData && !TheGlobalData->m_headless) TheInGameUI->message( "GUI:Error" ); DEBUG_LOG(( "Error opening file '%s'", filepath.str() )); return SC_ERROR; @@ -595,7 +595,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, catch( ... ) { - if (!TheGlobalData->m_headless) + if (TheGlobalData && !TheGlobalData->m_headless) { UnicodeString ufilepath; ufilepath.translate(filepath); @@ -616,7 +616,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc, xferSave.close(); // print message to the user for game successfully saved - if (!TheGlobalData->m_headless) + if (TheGlobalData && !TheGlobalData->m_headless) { UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" ); TheInGameUI->message( msg ); @@ -727,7 +727,7 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) TheGameEngine->reset(); // print error message to the user - if (!TheGlobalData->m_headless) + if (TheGlobalData && !TheGlobalData->m_headless) { UnicodeString ufilepath; ufilepath.translate(filepath); @@ -1384,7 +1384,7 @@ void GameState::xferSaveData( Xfer *xfer, SnapshotType which ) } // Skip visual-only blocks when saving in headless mode - if( TheGlobalData->m_headless && + if( TheGlobalData && TheGlobalData->m_headless && (blockName.compareNoCase( "CHUNK_TerrainVisual" ) == 0 || blockName.compareNoCase( "CHUNK_TacticalView" ) == 0 || blockName.compareNoCase( "CHUNK_ParticleSystem" ) == 0 ||