diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt
index f4945d29ab6..388f69116db 100644
--- a/Generals/Code/GameEngine/CMakeLists.txt
+++ b/Generals/Code/GameEngine/CMakeLists.txt
@@ -192,6 +192,7 @@ set(GAMEENGINE_SRC
# Include/GameClient/LookAtXlat.h
# Include/GameClient/MapUtil.h
Include/GameClient/MessageBox.h
+ Include/GameClient/SaveLoadFeedback.h
# Include/GameClient/MetaEvent.h
# Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h
# Include/GameClient/Module/BeaconClientUpdate.h
@@ -737,6 +738,7 @@ set(GAMEENGINE_SRC
Source/GameClient/GUI/GUICallbacks/Menus/WOLWelcomeMenu.cpp
Source/GameClient/GUI/GUICallbacks/MessageBox.cpp
Source/GameClient/GUI/GUICallbacks/ReplayControls.cpp
+ Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
# Source/GameClient/GUI/HeaderTemplate.cpp
# Source/GameClient/GUI/IMEManager.cpp
# Source/GameClient/GUI/LoadScreen.cpp
diff --git a/Generals/Code/GameEngine/Include/Common/GameState.h b/Generals/Code/GameEngine/Include/Common/GameState.h
index 20e8910174f..cdf77721b76 100644
--- a/Generals/Code/GameEngine/Include/Common/GameState.h
+++ b/Generals/Code/GameEngine/Include/Common/GameState.h
@@ -159,8 +159,9 @@ class GameState : public SubsystemInterface,
SaveCode saveGame( AsciiString filename,
UnicodeString desc,
SaveFileType saveType,
- SnapshotType which = SNAPSHOT_SAVELOAD ); ///< save a game
- SaveCode missionSave(); ///< do a in between mission save
+ SnapshotType which = SNAPSHOT_SAVELOAD,
+ AsciiString *resolvedFilename = nullptr ); ///< save a game
+ SaveCode missionSave( AsciiString *resolvedFilename = nullptr ); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }
diff --git a/Generals/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h b/Generals/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
new file mode 100644
index 00000000000..58939bff1b6
--- /dev/null
+++ b/Generals/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
@@ -0,0 +1,24 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "Common/GameState.h"
+
+void presentSaveResult( SaveCode result, const AsciiString &filename );
+void presentLoadResult( SaveCode result, const AsciiString &filename );
diff --git a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
index 7390ff5ed85..a57500cb1d0 100644
--- a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
+++ b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
@@ -48,7 +48,6 @@
#include "GameClient/GameClient.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
-#include "GameClient/MessageBox.h"
#include "GameClient/InGameUI.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/TerrainVisual.h"
@@ -533,7 +532,8 @@ AsciiString GameState::findNextSaveFilename( UnicodeString desc )
* NOTE: filename is a *filename only* */
// ------------------------------------------------------------------------------------------------
SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
- SaveFileType saveType, SnapshotType which )
+ SaveFileType saveType, SnapshotType which,
+ AsciiString *resolvedFilename )
{
// if there is no filename, this is a new file being created, find an appropriate filename
@@ -546,6 +546,10 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
return SC_NO_FILE_AVAILABLE;
}
+ if( resolvedFilename != nullptr )
+ {
+ *resolvedFilename = filename;
+ }
// make absolutely sure the save directory exists
CreateDirectory( getSaveDirectory().str(), nullptr );
@@ -561,10 +565,8 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
try {
xferSave.open( filepath );
} catch(...) {
- // print error message to the user
- TheInGameUI->message( "GUI:Error" );
DEBUG_LOG(( "Error opening file '%s'", filepath.str() ));
- return SC_ERROR;
+ return SC_UNABLE_TO_OPEN_FILE;
}
// save our save file type
@@ -592,14 +594,6 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
catch( ... )
{
- UnicodeString ufilepath;
- ufilepath.translate(filepath);
-
- UnicodeString msg;
- msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), ufilepath.str() );
-
- MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr);
-
// close the file and get out of here
xferSave.close();
return SC_ERROR;
@@ -609,10 +603,6 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
// close the file
xferSave.close();
- // print message to the user for game successfully saved
- UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" );
- TheInGameUI->message( msg );
-
return SC_OK;
}
@@ -620,7 +610,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
// ------------------------------------------------------------------------------------------------
/** A mission save */
// ------------------------------------------------------------------------------------------------
-SaveCode GameState::missionSave()
+SaveCode GameState::missionSave( AsciiString *resolvedFilename )
{
// get campaign
@@ -635,7 +625,7 @@ SaveCode GameState::missionSave()
desc.format( format, TheGameText->fetch( campaign->m_campaignNameLabel ).str(), missionNumber );
// do an automatic mission save
- return saveGame( "", desc, SAVE_FILE_TYPE_MISSION );
+ return saveGame( "", desc, SAVE_FILE_TYPE_MISSION, SNAPSHOT_SAVELOAD, resolvedFilename );
}
@@ -717,15 +707,6 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )
TheGameLogic->clearGameData( FALSE );
TheGameEngine->reset();
- // print error message to the user
- UnicodeString ufilepath;
- ufilepath.translate(filepath);
-
- UnicodeString msg;
- msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() );
-
- MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr);
-
return SC_INVALID_DATA; // you can't use a naked "throw" outside of a catch statement!
}
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
index 6dbbacb9c0c..6e1a882d239 100644
--- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
@@ -56,6 +56,7 @@
#include "GameClient/GameText.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GUICallbacks.h"
+#include "GameClient/SaveLoadFeedback.h"
#include "GameClient/Shell.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/GameWindowTransitions.h"
@@ -404,7 +405,10 @@ static void doLoadGame()
// loose these allocated user data pointers attached as listbox item data when the
// engine resets
//
- if (TheGameState->loadGame( *selectedGameInfo ) != SC_OK)
+ AsciiString filename = selectedGameInfo->filename;
+ SaveCode result = TheGameState->loadGame( *selectedGameInfo );
+ presentLoadResult( result, filename );
+ if (result != SC_OK)
{
if (TheGameLogic->isInGame())
TheGameLogic->clearGameData( FALSE );
@@ -771,7 +775,9 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
// save the game
AsciiString filename;
filename = selectedGameInfo->filename;
- TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType );
+ SaveCode result = TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description,
+ fileType, SNAPSHOT_SAVELOAD, &filename );
+ presentSaveResult( result, filename );
/*
// set the description text entry field to default value
@@ -835,7 +841,9 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
AsciiString filename;
if( selectedGameInfo )
filename = selectedGameInfo->filename;
- TheGameState->saveGame( filename, desc, fileType );
+ SaveCode result = TheGameState->saveGame( filename, desc, fileType,
+ SNAPSHOT_SAVELOAD, &filename );
+ presentSaveResult( result, filename );
}
else if( controlID == buttonSaveDescCancel )
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
index a217b8023c5..530fe5d9248 100644
--- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
@@ -74,6 +74,7 @@
#include "GameLogic/VictoryConditions.h"
#include "GameClient/Display.h"
#include "GameClient/GUICallbacks.h"
+#include "GameClient/SaveLoadFeedback.h"
#include "GameClient/WindowLayout.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/Gadget.h"
@@ -763,7 +764,9 @@ void finishSinglePlayerInit()
GadgetButtonSetText(buttonContinue, TheGameText->fetch("GUI:SaveAndContinue"));
// auto save game
- TheGameState->missionSave();
+ AsciiString filename;
+ SaveCode result = TheGameState->missionSave( &filename );
+ presentSaveResult( result, filename );
if(staticTextGameSaved)
staticTextGameSaved->winHide(FALSE);
}
diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
new file mode 100644
index 00000000000..95147fad526
--- /dev/null
+++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
@@ -0,0 +1,71 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#include "PreRTS.h"
+#include "GameClient/GameText.h"
+#include "GameClient/InGameUI.h"
+#include "GameClient/MessageBox.h"
+#include "GameClient/SaveLoadFeedback.h"
+
+static UnicodeString getUnicodeSavePath( const AsciiString &filename )
+{
+ UnicodeString path;
+ path.translate( TheGameState->getFilePathInSaveDirectory(filename) );
+ return path;
+}
+
+void presentSaveResult( SaveCode result, const AsciiString &filename )
+{
+ switch( result )
+ {
+ case SC_OK:
+ {
+ TheInGameUI->message( TheGameText->fetch("GUI:GameSaveComplete") );
+ break;
+ }
+ case SC_UNABLE_TO_OPEN_FILE:
+ {
+ TheInGameUI->message( "GUI:Error" );
+ break;
+ }
+ case SC_ERROR:
+ {
+ UnicodeString msg;
+ msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), getUnicodeSavePath(filename).str() );
+ MessageBoxOk( TheGameText->fetch("GUI:Error"), msg, nullptr );
+ break;
+ }
+ default:
+ {
+ // SC_NO_FILE_AVAILABLE (and any other early-out) returned no UI in retail
+ break;
+ }
+ }
+}
+
+void presentLoadResult( SaveCode result, const AsciiString &filename )
+{
+ // Retail loadGame only surfaced a dialog on the exception path; SC_FILE_NOT_FOUND
+ // and SC_OK presented nothing.
+ if( result == SC_INVALID_DATA )
+ {
+ UnicodeString msg;
+ msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), getUnicodeSavePath(filename).str() );
+ MessageBoxOk( TheGameText->fetch("GUI:Error"), msg, nullptr );
+ }
+}
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 5218ff802d9..f0d85209526 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -198,6 +198,7 @@ set(GAMEENGINE_SRC
# Include/GameClient/LookAtXlat.h
# Include/GameClient/MapUtil.h
Include/GameClient/MessageBox.h
+ Include/GameClient/SaveLoadFeedback.h
# Include/GameClient/MetaEvent.h
# Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h
# Include/GameClient/Module/BeaconClientUpdate.h
@@ -779,6 +780,7 @@ set(GAMEENGINE_SRC
Source/GameClient/GUI/GUICallbacks/Menus/WOLWelcomeMenu.cpp
Source/GameClient/GUI/GUICallbacks/MessageBox.cpp
Source/GameClient/GUI/GUICallbacks/ReplayControls.cpp
+ Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
# Source/GameClient/GUI/HeaderTemplate.cpp
# Source/GameClient/GUI/IMEManager.cpp
# Source/GameClient/GUI/LoadScreen.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameState.h b/GeneralsMD/Code/GameEngine/Include/Common/GameState.h
index 16bc991b69a..3703a95ba56 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/GameState.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/GameState.h
@@ -159,8 +159,9 @@ class GameState : public SubsystemInterface,
SaveCode saveGame( AsciiString filename,
UnicodeString desc,
SaveFileType saveType,
- SnapshotType which = SNAPSHOT_SAVELOAD ); ///< save a game
- SaveCode missionSave(); ///< do a in between mission save
+ SnapshotType which = SNAPSHOT_SAVELOAD,
+ AsciiString *resolvedFilename = nullptr ); ///< save a game
+ SaveCode missionSave( AsciiString *resolvedFilename = nullptr ); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }
diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h b/GeneralsMD/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
new file mode 100644
index 00000000000..58939bff1b6
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
@@ -0,0 +1,24 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "Common/GameState.h"
+
+void presentSaveResult( SaveCode result, const AsciiString &filename );
+void presentLoadResult( SaveCode result, const AsciiString &filename );
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
index 04cc701b5e1..25e23632d2b 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp
@@ -48,7 +48,6 @@
#include "GameClient/GameClient.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
-#include "GameClient/MessageBox.h"
#include "GameClient/InGameUI.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/TerrainVisual.h"
@@ -533,7 +532,8 @@ AsciiString GameState::findNextSaveFilename( UnicodeString desc )
* NOTE: filename is a *filename only* */
// ------------------------------------------------------------------------------------------------
SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
- SaveFileType saveType, SnapshotType which )
+ SaveFileType saveType, SnapshotType which,
+ AsciiString *resolvedFilename )
{
// if there is no filename, this is a new file being created, find an appropriate filename
@@ -546,6 +546,10 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
return SC_NO_FILE_AVAILABLE;
}
+ if( resolvedFilename != nullptr )
+ {
+ *resolvedFilename = filename;
+ }
// make absolutely sure the save directory exists
CreateDirectory( getSaveDirectory().str(), nullptr );
@@ -561,10 +565,8 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
try {
xferSave.open( filepath );
} catch(...) {
- // print error message to the user
- TheInGameUI->message( "GUI:Error" );
DEBUG_LOG(( "Error opening file '%s'", filepath.str() ));
- return SC_ERROR;
+ return SC_UNABLE_TO_OPEN_FILE;
}
// save our save file type
@@ -592,14 +594,6 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
catch( ... )
{
- UnicodeString ufilepath;
- ufilepath.translate(filepath);
-
- UnicodeString msg;
- msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), ufilepath.str() );
-
- MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr);
-
// close the file and get out of here
xferSave.close();
return SC_ERROR;
@@ -609,10 +603,6 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
// close the file
xferSave.close();
- // print message to the user for game successfully saved
- UnicodeString msg = TheGameText->fetch( "GUI:GameSaveComplete" );
- TheInGameUI->message( msg );
-
return SC_OK;
}
@@ -620,7 +610,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
// ------------------------------------------------------------------------------------------------
/** A mission save */
// ------------------------------------------------------------------------------------------------
-SaveCode GameState::missionSave()
+SaveCode GameState::missionSave( AsciiString *resolvedFilename )
{
// get campaign
@@ -635,7 +625,7 @@ SaveCode GameState::missionSave()
desc.format( format, TheGameText->fetch( campaign->m_campaignNameLabel ).str(), missionNumber );
// do an automatic mission save
- return saveGame( "", desc, SAVE_FILE_TYPE_MISSION );
+ return saveGame( "", desc, SAVE_FILE_TYPE_MISSION, SNAPSHOT_SAVELOAD, resolvedFilename );
}
@@ -717,15 +707,6 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo )
TheGameLogic->clearGameData( FALSE );
TheGameEngine->reset();
- // print error message to the user
- UnicodeString ufilepath;
- ufilepath.translate(filepath);
-
- UnicodeString msg;
- msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() );
-
- MessageBoxOk(TheGameText->fetch("GUI:Error"), msg, nullptr);
-
return SC_INVALID_DATA; // you can't use a naked "throw" outside of a catch statement!
}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
index 45cb58fd4b9..5185579bd32 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp
@@ -56,6 +56,7 @@
#include "GameClient/GameText.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GUICallbacks.h"
+#include "GameClient/SaveLoadFeedback.h"
#include "GameClient/Shell.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/GameWindowTransitions.h"
@@ -414,7 +415,10 @@ static void doLoadGame()
// loose these allocated user data pointers attached as listbox item data when the
// engine resets
//
- if (TheGameState->loadGame( *selectedGameInfo ) != SC_OK)
+ AsciiString filename = selectedGameInfo->filename;
+ SaveCode result = TheGameState->loadGame( *selectedGameInfo );
+ presentLoadResult( result, filename );
+ if (result != SC_OK)
{
if (TheGameLogic->isInGame())
TheGameLogic->clearGameData( FALSE );
@@ -787,7 +791,9 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
// save the game
AsciiString filename;
filename = selectedGameInfo->filename;
- TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType );
+ SaveCode result = TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description,
+ fileType, SNAPSHOT_SAVELOAD, &filename );
+ presentSaveResult( result, filename );
/*
// set the description text entry field to default value
@@ -851,7 +857,9 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
AsciiString filename;
if( selectedGameInfo )
filename = selectedGameInfo->filename;
- TheGameState->saveGame( filename, desc, fileType );
+ SaveCode result = TheGameState->saveGame( filename, desc, fileType,
+ SNAPSHOT_SAVELOAD, &filename );
+ presentSaveResult( result, filename );
}
else if( controlID == buttonSaveDescCancel )
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
index 38c88601e42..9f1e2430c06 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp
@@ -77,6 +77,7 @@
#include "GameLogic/VictoryConditions.h"
#include "GameClient/Display.h"
#include "GameClient/GUICallbacks.h"
+#include "GameClient/SaveLoadFeedback.h"
#include "GameClient/WindowLayout.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/Gadget.h"
@@ -924,7 +925,9 @@ void finishSinglePlayerInit()
GadgetButtonSetText(buttonContinue, TheGameText->fetch("GUI:SaveAndContinue"));
// auto save game
- TheGameState->missionSave();
+ AsciiString filename;
+ SaveCode result = TheGameState->missionSave( &filename );
+ presentSaveResult( result, filename );
if(staticTextGameSaved)
staticTextGameSaved->winHide(FALSE);
}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
new file mode 100644
index 00000000000..95147fad526
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/SaveLoadFeedback.cpp
@@ -0,0 +1,71 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#include "PreRTS.h"
+#include "GameClient/GameText.h"
+#include "GameClient/InGameUI.h"
+#include "GameClient/MessageBox.h"
+#include "GameClient/SaveLoadFeedback.h"
+
+static UnicodeString getUnicodeSavePath( const AsciiString &filename )
+{
+ UnicodeString path;
+ path.translate( TheGameState->getFilePathInSaveDirectory(filename) );
+ return path;
+}
+
+void presentSaveResult( SaveCode result, const AsciiString &filename )
+{
+ switch( result )
+ {
+ case SC_OK:
+ {
+ TheInGameUI->message( TheGameText->fetch("GUI:GameSaveComplete") );
+ break;
+ }
+ case SC_UNABLE_TO_OPEN_FILE:
+ {
+ TheInGameUI->message( "GUI:Error" );
+ break;
+ }
+ case SC_ERROR:
+ {
+ UnicodeString msg;
+ msg.format( TheGameText->fetch("GUI:ErrorSavingGame"), getUnicodeSavePath(filename).str() );
+ MessageBoxOk( TheGameText->fetch("GUI:Error"), msg, nullptr );
+ break;
+ }
+ default:
+ {
+ // SC_NO_FILE_AVAILABLE (and any other early-out) returned no UI in retail
+ break;
+ }
+ }
+}
+
+void presentLoadResult( SaveCode result, const AsciiString &filename )
+{
+ // Retail loadGame only surfaced a dialog on the exception path; SC_FILE_NOT_FOUND
+ // and SC_OK presented nothing.
+ if( result == SC_INVALID_DATA )
+ {
+ UnicodeString msg;
+ msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), getUnicodeSavePath(filename).str() );
+ MessageBoxOk( TheGameText->fetch("GUI:Error"), msg, nullptr );
+ }
+}