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 Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ set(GAMEENGINE_SRC
Include/GameClient/ProcessAnimateWindow.h
Include/GameClient/RadiusDecal.h
Include/GameClient/RayEffect.h
Include/GameClient/SaveLoadFeedback.h
Include/GameClient/SelectionInfo.h
Include/GameClient/SelectionXlat.h
# Include/GameClient/Shadow.h
Expand Down Expand Up @@ -795,6 +796,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
24 changes: 24 additions & 0 deletions Core/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( const SaveResult &result );
void presentLoadResult( SaveCode result, const AsciiString &filename );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new files should go to Core.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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( const SaveResult &result )
{
switch( result.saveCode )
{
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(result.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 );
}
}
16 changes: 13 additions & 3 deletions Generals/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ enum SaveCode CPP_11(: Int)
SC_ERROR,
};

// The result of a save, pairing the outcome with the file it resolved to so the two cannot drift.
struct SaveResult
{
explicit SaveResult( SaveCode code ) : saveCode(code) { }
SaveResult( SaveCode code, const AsciiString &file ) : saveCode(code), filename(file) { }

SaveCode saveCode;
AsciiString filename; ///< the file that was written, empty when no filename could be found
};

enum SnapshotType CPP_11(: Int) {
SNAPSHOT_SAVELOAD,
SNAPSHOT_DEEPCRC_LOGICONLY,
Expand All @@ -156,11 +166,11 @@ class GameState : public SubsystemInterface,
virtual void update() override { }

// save game methods
SaveCode saveGame( AsciiString filename,
SaveResult 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 ); ///< save a game
SaveResult missionSave(); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }

Expand Down
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 @@ -532,8 +531,8 @@ AsciiString GameState::findNextSaveFilename( UnicodeString desc )
/** Save the current state of the engine in a save file
* NOTE: filename is a *filename only* */
// ------------------------------------------------------------------------------------------------
SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
SaveFileType saveType, SnapshotType which )
SaveResult GameState::saveGame( AsciiString filename, UnicodeString desc,
SaveFileType saveType, SnapshotType which )
{

// if there is no filename, this is a new file being created, find an appropriate filename
Expand All @@ -543,7 +542,7 @@ SaveCode GameState::saveGame( AsciiString filename, UnicodeString desc,
{

DEBUG_CRASH(( "GameState::saveGame - Unable to find valid filename for save game" ));
return SC_NO_FILE_AVAILABLE;
return SaveResult( SC_NO_FILE_AVAILABLE );

}

Expand All @@ -561,10 +560,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 SaveResult( SC_UNABLE_TO_OPEN_FILE, filename );
}

// save our save file type
Expand Down Expand Up @@ -592,35 +589,23 @@ 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;
return SaveResult( SC_ERROR, filename );

}

// 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;
return SaveResult( SC_OK, filename );

}

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

// get campaign
Expand Down Expand Up @@ -717,15 +702,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,8 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
// save the game
AsciiString filename;
filename = selectedGameInfo->filename;
TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType );
presentSaveResult( TheGameState->saveGame( filename,
selectedGameInfo->saveGameInfo.description, fileType ) );

/*
// set the description text entry field to default value
Expand Down Expand Up @@ -835,7 +840,7 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg,
AsciiString filename;
if( selectedGameInfo )
filename = selectedGameInfo->filename;
TheGameState->saveGame( filename, desc, fileType );
presentSaveResult( TheGameState->saveGame( filename, desc, fileType ) );

}
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,7 @@ void finishSinglePlayerInit()
GadgetButtonSetText(buttonContinue, TheGameText->fetch("GUI:SaveAndContinue"));

// auto save game
TheGameState->missionSave();
presentSaveResult( TheGameState->missionSave() );
if(staticTextGameSaved)
staticTextGameSaved->winHide(FALSE);
}
Expand Down
16 changes: 13 additions & 3 deletions GeneralsMD/Code/GameEngine/Include/Common/GameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ enum SaveCode CPP_11(: Int)
SC_ERROR,
};

// The result of a save, pairing the outcome with the file it resolved to so the two cannot drift.
struct SaveResult
{
explicit SaveResult( SaveCode code ) : saveCode(code) { }
SaveResult( SaveCode code, const AsciiString &file ) : saveCode(code), filename(file) { }

SaveCode saveCode;
AsciiString filename; ///< the file that was written, empty when no filename could be found
};

enum SnapshotType CPP_11(: Int) {
SNAPSHOT_SAVELOAD,
SNAPSHOT_DEEPCRC_LOGICONLY,
Expand All @@ -156,11 +166,11 @@ class GameState : public SubsystemInterface,
virtual void update() override { }

// save game methods
SaveCode saveGame( AsciiString filename,
SaveResult 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 ); ///< save a game
SaveResult missionSave(); ///< do a in between mission save
SaveCode loadGame( AvailableGameInfo gameInfo ); ///< load a save file
SaveGameInfo *getSaveGameInfo() { return &m_gameInfo; }

Expand Down
Loading
Loading