Skip to content
Open
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
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions Generals/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
24 changes: 24 additions & 0 deletions Generals/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Common/GameState.h"

void presentSaveResult( SaveCode result, const AsciiString &filename );
void presentLoadResult( SaveCode result, const AsciiString &filename );
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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 );
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -609,18 +603,14 @@ 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;

}

// ------------------------------------------------------------------------------------------------
/** A mission save */
// ------------------------------------------------------------------------------------------------
SaveCode GameState::missionSave()
SaveCode GameState::missionSave( AsciiString *resolvedFilename )
{

// get campaign
Expand All @@ -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 );

}

Expand Down Expand Up @@ -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!

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#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 );
}
}
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions GeneralsMD/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
24 changes: 24 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/SaveLoadFeedback.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Common/GameState.h"

void presentSaveResult( SaveCode result, const AsciiString &filename );
void presentLoadResult( SaveCode result, const AsciiString &filename );
Loading
Loading