Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Core/GameEngine/Include/Common/Snapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ friend class XferCRC;
Snapshot();
~Snapshot();

// Snapshots can disable xfer to be excluded from save game data,
// for example dummy implementations in headless mode.
virtual Bool isXferEnabled() const { return TRUE; }

protected:

/// run the "light" crc check on this data structure
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,8 @@ class ParticleSystemManagerDummy : public ParticleSystemManager

virtual Bool isDummy() const override { return true; }

virtual Bool isXferEnabled() const override { return FALSE; }

virtual Int getOnScreenParticleCount() override { return 0; }
virtual void doParticles(RenderInfoClass &rinfo) override {}
virtual void queueParticleRender() override {}
Expand Down
15 changes: 15 additions & 0 deletions Core/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,18 @@ Int parseVTune ( char *args[], int num )

#endif // defined(RTS_DEBUG)

Int parseLoadSave(char *args[], int num)
{
if (num > 1)
{
TheWritableGlobalData->m_loadSaveGame = args[1];
TheWritableGlobalData->m_shellMapOn = FALSE;
TheWritableGlobalData->m_playIntro = FALSE;
TheWritableGlobalData->m_playSizzle = FALSE;
}
return 2;
}

//=============================================================================
//=============================================================================

Expand Down Expand Up @@ -1160,6 +1172,9 @@ static CommandLineParam paramsForEngineInit[] =
{ "-quickstart", parseQuickStart },
{ "-useWaveEditor", parseUseWaveEditor },

// TheSuperHackers @feature bobtista 22/07/2026 Load a save game file from the command line.
{ "-loadsave", parseLoadSave },
Comment thread
xezon marked this conversation as resolved.

// TheSuperHackers @feature xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it.
{ "-forcefullviewport", parseFullViewport },

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class W3DTerrainVisual : public TerrainVisual
protected:

// snapshot methods
virtual Bool isXferEnabled() const override;
virtual void crc( Xfer *xfer ) override;
virtual void xfer( Xfer *xfer ) override;
virtual void loadPostProcess() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,14 @@ void W3DTerrainVisual::replaceSkyboxTextures(const AsciiString *oldTexName[5], c
}
}

// ------------------------------------------------------------------------------------------------
/** Terrain visual state is only partially initialized in headless mode, so it is excluded from save game data */
// ------------------------------------------------------------------------------------------------
Bool W3DTerrainVisual::isXferEnabled() const
{
return TheGlobalData->m_headless == FALSE;
}

// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class GameState : public SubsystemInterface,
SnapshotType which = SNAPSHOT_SAVELOAD ); ///< save a game
SaveResult missionSave(); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
void loadQueuedSaveGame(); ///< load the save file requested on startup
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }

// snapshot interaction
Expand Down
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ class GlobalData : public SubsystemInterface
Bool m_buildMapCache;
AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line
AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start
AsciiString m_loadSaveGame; ///< If this is specified, load a save game file from the command-line

std::vector<AsciiString> m_simulateReplays; ///< If not empty, simulate this list of replays and exit.
Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation
Expand Down
2 changes: 1 addition & 1 deletion Generals/Code/GameEngine/Include/GameLogic/GhostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ inline Bool GhostObjectManager::trackAllPlayers() const

// TheSuperHackers @feature bobtista 19/01/2026
// GhostObjectManager that does nothing for headless mode.
// Note: Does NOT override crc/xfer/loadPostProcess to maintain save compatibility.
class GhostObjectManagerDummy : public GhostObjectManager
{
public:
virtual Bool isXferEnabled() const override { return FALSE; }
virtual void reset() override {}
virtual GhostObject *addGhostObject(Object *object, PartitionData *pd) override { return nullptr; }
virtual void removeGhostObject(GhostObject *mod) override {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,13 @@ void GameState::addSnapshotBlock( AsciiString blockName, Snapshot *snapshot, Sna

}

// Snapshots with xfer disabled are not registered, so their blocks are omitted when saving
// and are skipped like unknown blocks when loading.
if( !snapshot->isXferEnabled() )
{
return;
}

// add to the list
SnapshotBlock blockInfo;
blockInfo.snapshot = snapshot;
Expand Down Expand Up @@ -733,6 +740,52 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )

}

// ------------------------------------------------------------------------------------------------
/** Load the save game requested on startup, after the shell has been initialized */
// ------------------------------------------------------------------------------------------------
void GameState::loadQueuedSaveGame()
{
AvailableGameInfo gameInfo;
gameInfo.filename = TheGlobalData->m_loadSaveGame;
gameInfo.next = nullptr;
gameInfo.prev = nullptr;

TheWritableGlobalData->m_loadSaveGame.clear();

// getSaveGameInfoFromFile throws when the file is missing, so check before reading it
if( doesSaveGameExist( gameInfo.filename ) == FALSE )
{
DEBUG_LOG(("Save game '%s' was not found", gameInfo.filename.str()));
TheGameEngine->setQuitting( TRUE );
return;
}

// getSaveGameInfoFromFile throws on a malformed file instead of returning a SaveCode
try
{
AsciiString filepath = getFilePathInSaveDirectory( gameInfo.filename );
getSaveGameInfoFromFile( filepath, &gameInfo.saveGameInfo );
}
catch( ... )
{
DEBUG_LOG(("Save game '%s' could not be read", gameInfo.filename.str()));
TheGameEngine->setQuitting( TRUE );
return;
}

// this hides the shell, keeping the menu screens on the stack for when the game ends
TheGameLogic->prepareNewGame( GAME_SINGLE_PLAYER, DIFFICULTY_NORMAL, 0 );

if( loadGame( gameInfo ) != SC_OK )
{
DEBUG_LOG(("Failed to load save game '%s'", gameInfo.filename.str()));
if( TheGameLogic->isInGame() )
TheGameLogic->clearGameData( FALSE );
TheGameEngine->reset();
TheGameEngine->setQuitting( TRUE );
}
}

//-------------------------------------------------------------------------------------------------
AsciiString GameState::getSaveDirectory() const
{
Expand Down
5 changes: 5 additions & 0 deletions Generals/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,11 @@ void GameClient::update()

TheShell->showShellMap(TRUE);
TheShell->showShell();

if (TheGlobalData->m_loadSaveGame.isNotEmpty())
{
TheGameState->loadQueuedSaveGame();
}
}
}

Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class GameState : public SubsystemInterface,
SnapshotType which = SNAPSHOT_SAVELOAD ); ///< save a game
SaveResult missionSave(); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
void loadQueuedSaveGame(); ///< load the save file requested on startup
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }

// snapshot interaction
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ class GlobalData : public SubsystemInterface
Bool m_buildMapCache;
AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line
AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start
AsciiString m_loadSaveGame; ///< If this is specified, load a save game file from the command-line

std::vector<AsciiString> m_simulateReplays; ///< If not empty, simulate this list of replays and exit.
Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation
Expand Down
2 changes: 1 addition & 1 deletion GeneralsMD/Code/GameEngine/Include/GameLogic/GhostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ inline Bool GhostObjectManager::trackAllPlayers() const

// TheSuperHackers @feature bobtista 19/01/2026
// GhostObjectManager that does nothing for headless mode.
// Note: Does NOT override crc/xfer/loadPostProcess to maintain save compatibility.
class GhostObjectManagerDummy : public GhostObjectManager
{
public:
virtual Bool isXferEnabled() const override { return FALSE; }
virtual void reset() override {}
virtual GhostObject *addGhostObject(Object *object, PartitionData *pd) override { return nullptr; }
virtual void removeGhostObject(GhostObject *mod) override {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,13 @@ void GameState::addSnapshotBlock( AsciiString blockName, Snapshot *snapshot, Sna

}

// Snapshots with xfer disabled are not registered, so their blocks are omitted when saving
// and are skipped like unknown blocks when loading.
if( !snapshot->isXferEnabled() )
{
return;
}

// add to the list
SnapshotBlock blockInfo;
blockInfo.snapshot = snapshot;
Expand Down Expand Up @@ -733,6 +740,52 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )

}

// ------------------------------------------------------------------------------------------------
/** Load the save game requested on startup, after the shell has been initialized */
// ------------------------------------------------------------------------------------------------
void GameState::loadQueuedSaveGame()
{
AvailableGameInfo gameInfo;
gameInfo.filename = TheGlobalData->m_loadSaveGame;
gameInfo.next = nullptr;
gameInfo.prev = nullptr;

TheWritableGlobalData->m_loadSaveGame.clear();

// getSaveGameInfoFromFile throws when the file is missing, so check before reading it
if( doesSaveGameExist( gameInfo.filename ) == FALSE )
{
DEBUG_LOG(("Save game '%s' was not found", gameInfo.filename.str()));
TheGameEngine->setQuitting( TRUE );
return;
}

// getSaveGameInfoFromFile throws on a malformed file instead of returning a SaveCode
try
{
AsciiString filepath = getFilePathInSaveDirectory( gameInfo.filename );
getSaveGameInfoFromFile( filepath, &gameInfo.saveGameInfo );
}
catch( ... )
{
DEBUG_LOG(("Save game '%s' could not be read", gameInfo.filename.str()));
TheGameEngine->setQuitting( TRUE );
return;
}

// this hides the shell, keeping the menu screens on the stack for when the game ends
TheGameLogic->prepareNewGame( GAME_SINGLE_PLAYER, DIFFICULTY_NORMAL, 0 );

if( loadGame( gameInfo ) != SC_OK )
{
DEBUG_LOG(("Failed to load save game '%s'", gameInfo.filename.str()));
if( TheGameLogic->isInGame() )
TheGameLogic->clearGameData( FALSE );
TheGameEngine->reset();
TheGameEngine->setQuitting( TRUE );
}
}

//-------------------------------------------------------------------------------------------------
AsciiString GameState::getSaveDirectory() const
{
Expand Down
5 changes: 5 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,11 @@ void GameClient::update()

TheShell->showShellMap(TRUE);
TheShell->showShell();

if (TheGlobalData->m_loadSaveGame.isNotEmpty())
{
TheGameState->loadQueuedSaveGame();
}
}
}

Expand Down
Loading