4848#include " GameClient/GameClient.h"
4949#include " GameClient/GameText.h"
5050#include " GameClient/MapUtil.h"
51+ #include " GameClient/MessageBox.h"
5152#include " GameClient/InGameUI.h"
5253#include " GameClient/ParticleSys.h"
5354#include " GameClient/TerrainVisual.h"
5758#include " GameLogic/ScriptEngine.h"
5859#include " GameLogic/SidesList.h"
5960#include " GameLogic/TerrainLogic.h"
61+ #include " Lib/PathUtil.h"
6062
6163
6264// PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
@@ -656,8 +658,9 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )
656658 //
657659 TheGameStateMap->clearScratchPadMaps ();
658660
659- // construct path to file
660- AsciiString filepath = getFilePathInSaveDirectory (gameInfo.filename );
661+ // Relative names come from the save menu. Absolute paths come from the command line
662+ // and are opened in place.
663+ AsciiString filepath = getSaveGamePathForRead (gameInfo.filename );
661664
662665 // open the save file
663666 XferLoad xferLoad;
@@ -740,6 +743,18 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )
740743
741744}
742745
746+ // -------------------------------------------------------------------------------------------------
747+ // TheSuperHackers @feature bobtista 29/08/2026 A save game requested on the command line can come
748+ // from a file association, where there is no console to print to. Report the failure in the shell
749+ // and leave the shell running so the player can carry on from the main menu.
750+ static void showQueuedSaveGameLoadFailure ( void )
751+ {
752+ UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE (" GUI:SaveGameLoadFailedTitle" , L" CANNOT LOAD SAVE" );
753+ UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE (" GUI:SaveGameLoadFailed" , L" The saved game file could not be opened or is invalid." );
754+
755+ MessageBoxOk (title, body, nullptr );
756+ }
757+
743758// ------------------------------------------------------------------------------------------------
744759/* * Load the save game requested on startup, after the shell has been initialized */
745760// ------------------------------------------------------------------------------------------------
@@ -752,24 +767,30 @@ void GameState::loadQueuedSaveGame()
752767
753768 TheWritableGlobalData->m_loadSaveGame .clear ();
754769
770+ if ( gameInfo.filename .endsWithNoCase ( SAVE_GAME_EXTENSION ) == FALSE )
771+ {
772+ DEBUG_LOG ((" Save game '%s' is not a save game file" , gameInfo.filename .str ()));
773+ showQueuedSaveGameLoadFailure ();
774+ return ;
775+ }
776+
755777 // getSaveGameInfoFromFile throws when the file is missing, so check before reading it
756778 if ( doesSaveGameExist ( gameInfo.filename ) == FALSE )
757779 {
758780 DEBUG_LOG ((" Save game '%s' was not found" , gameInfo.filename .str ()));
759- TheGameEngine-> setQuitting ( TRUE );
781+ showQueuedSaveGameLoadFailure ( );
760782 return ;
761783 }
762784
763785 // getSaveGameInfoFromFile throws on a malformed file instead of returning a SaveCode
764786 try
765787 {
766- AsciiString filepath = getFilePathInSaveDirectory ( gameInfo.filename );
767- getSaveGameInfoFromFile ( filepath, &gameInfo.saveGameInfo );
788+ getSaveGameInfoFromFile ( gameInfo.filename , &gameInfo.saveGameInfo );
768789 }
769790 catch ( ... )
770791 {
771792 DEBUG_LOG ((" Save game '%s' could not be read" , gameInfo.filename .str ()));
772- TheGameEngine-> setQuitting ( TRUE );
793+ showQueuedSaveGameLoadFailure ( );
773794 return ;
774795 }
775796
@@ -802,6 +823,19 @@ AsciiString GameState::getFilePathInSaveDirectory(const AsciiString& leaf) const
802823 return tmp;
803824}
804825
826+ // -------------------------------------------------------------------------------------------------
827+ // TheSuperHackers @feature bobtista 08/08/2026 Open explicitly selected save paths in place while
828+ // preserving the managed Save directory for the relative names the menus use.
829+ AsciiString GameState::getSaveGamePathForRead (const AsciiString& filenameOrPath) const
830+ {
831+ if (isAbsolutePath (filenameOrPath.str ()))
832+ {
833+ return filenameOrPath;
834+ }
835+
836+ return getFilePathInSaveDirectory (filenameOrPath);
837+ }
838+
805839// -------------------------------------------------------------------------------------------------
806840Bool GameState::isInSaveDirectory (const AsciiString& path) const
807841{
@@ -959,8 +993,7 @@ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const
959993Bool GameState::doesSaveGameExist ( AsciiString filename )
960994{
961995
962- // construct full path to file
963- AsciiString filepath = getFilePathInSaveDirectory (filename);
996+ AsciiString filepath = getSaveGamePathForRead (filename);
964997
965998 // open file
966999 XferLoad xfer;
@@ -1005,6 +1038,8 @@ void GameState::getSaveGameInfoFromFile( AsciiString filename, SaveGameInfo *sav
10051038
10061039 }
10071040
1041+ filename = getSaveGamePathForRead ( filename );
1042+
10081043 // open file for partial loading
10091044 XferLoad xferLoad;
10101045 xferLoad.open ( filename );
0 commit comments