diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index bbaa17a3f20..6466ca26c39 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -118,9 +118,10 @@ class RecorderClass : public SubsystemInterface { Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls - AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. - AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. + static AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. + static AsciiString getReplayArchiveDir(); ///< Returns the directory that holds the archived replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -133,10 +134,12 @@ class RecorderClass : public SubsystemInterface { Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository + void setArchiveEnabled(Bool enable) { m_archiveReplays = enable; } ///< Enable or disable replay archiving. void stopRecording(); ///< Stop recording and close m_file. protected: void startRecording(GameDifficulty diff, Int originalGameMode, Int rankPoints, Int maxFPS); ///< Start recording to m_file. void writeToFile(GameMessage *msg); ///< Write this GameMessage to m_file. + void archiveReplay(AsciiString fileName); ///< Move the specified replay file to the archive directory. void logGameStart(AsciiString options); void logGameEnd( void ); @@ -167,6 +170,7 @@ class RecorderClass : public SubsystemInterface { Bool m_wasDesync; Bool m_doingAnalysis; + Bool m_archiveReplays; ///< if true, each replay is archived to the replay archive folder after recording Int m_originalGameMode; // valid in replays diff --git a/Generals/Code/GameEngine/Include/Common/UserPreferences.h b/Generals/Code/GameEngine/Include/Common/UserPreferences.h index abe79797fb0..279a15f91b6 100644 --- a/Generals/Code/GameEngine/Include/Common/UserPreferences.h +++ b/Generals/Code/GameEngine/Include/Common/UserPreferences.h @@ -91,6 +91,7 @@ class OptionPreferences : public UserPreferences void setOnlineIPAddress(AsciiString IP); // convenience function void setLANIPAddress(UnsignedInt IP); // convenience function void setOnlineIPAddress(UnsignedInt IP); // convenience function + Bool getArchiveReplaysEnabled() const; // convenience function Bool getAlternateMouseModeEnabled(void); // convenience function Real getScrollFactor(void); // convenience function Bool getDrawScrollAnchor(void); diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index b7a6df6e412..78b34abc42b 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -45,6 +45,7 @@ #include "GameLogic/GameLogic.h" #include "Common/RandomValue.h" #include "Common/CRCDebug.h" +#include "Common/UserPreferences.h" #include "Common/version.h" constexpr const char s_genrep[] = "GENREP"; @@ -370,6 +371,7 @@ RecorderClass::RecorderClass() //Added By Sadullah Nader //Initializtion(s) inserted m_doingAnalysis = FALSE; + m_archiveReplays = FALSE; m_nextFrame = 0; m_wasDesync = FALSE; // @@ -406,6 +408,9 @@ void RecorderClass::init() { m_wasDesync = FALSE; m_doingAnalysis = FALSE; m_playbackFrameCount = 0; + + OptionPreferences optionPref; + m_archiveReplays = optionPref.getArchiveReplaysEnabled(); } /** @@ -726,10 +731,42 @@ void RecorderClass::stopRecording() { if (m_file != NULL) { m_file->close(); m_file = NULL; + + if (m_archiveReplays) + archiveReplay(m_fileName); } m_fileName.clear(); } +/** + * TheSuperHackers @feature Stubbjax 17/10/2025 Copy the replay file to the archive directory and rename it using the current timestamp. + */ +void RecorderClass::archiveReplay(AsciiString fileName) +{ + SYSTEMTIME st; + GetLocalTime(&st); + + AsciiString archiveFileName; + // Use a standard YYYYMMDD_HHMMSS format for simplicity and to avoid conflicts. + archiveFileName.format("%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + AsciiString extension = getReplayExtention(); + AsciiString sourcePath = getReplayDir(); + sourcePath.concat(fileName); + + if (!sourcePath.endsWith(extension)) + sourcePath.concat(extension); + + AsciiString destPath = getReplayArchiveDir(); + TheFileSystem->createDirectory(destPath.str()); + + destPath.concat(archiveFileName); + destPath.concat(extension); + + if (!CopyFile(sourcePath.str(), destPath.str(), FALSE)) + DEBUG_LOG(("RecorderClass::archiveReplay: Failed to copy %s to %s", sourcePath.str(), destPath.str())); +} + /** * Write this game message to the record file. This also writes the game message's execution frame. */ @@ -1595,10 +1632,18 @@ RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { */ AsciiString RecorderClass::getReplayDir() { - const char* replayDir = "Replays\\"; + AsciiString tmp = TheGlobalData->getPath_UserData(); + tmp.concat("Replays\\"); + return tmp; +} +/** + * returns the directory that holds the archived replay files. + */ +AsciiString RecorderClass::getReplayArchiveDir() +{ AsciiString tmp = TheGlobalData->getPath_UserData(); - tmp.concat(replayDir); + tmp.concat("ArchivedReplays\\"); return tmp; } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index d884d867e01..1e62b663256 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -38,6 +38,7 @@ #include "Common/GameEngine.h" #include "Common/UserPreferences.h" #include "Common/GameLOD.h" +#include "Common/Recorder.h" #include "Common/Registry.h" #include "Common/version.h" @@ -310,6 +311,18 @@ void OptionPreferences::setOnlineIPAddress( UnsignedInt IP ) (*this)["GameSpyIPAddress"] = tmp; } +Bool OptionPreferences::getArchiveReplaysEnabled() const +{ + OptionPreferences::const_iterator it = find("ArchiveReplays"); + if (it == end()) + return FALSE; + + if (stricmp(it->second.str(), "yes") == 0) { + return TRUE; + } + return FALSE; +} + Bool OptionPreferences::getAlternateMouseModeEnabled(void) { OptionPreferences::const_iterator it = find("UseAlternateMouse"); @@ -1296,6 +1309,13 @@ static void saveOptions( void ) TheWritableGlobalData->m_enablePlayerObserver = enabled; } + // TheSuperHackers @todo Add checkbox ? + { + Bool enabled = pref->getArchiveReplaysEnabled(); + (*pref)["ArchiveReplays"] = enabled ? "yes" : "no"; + TheRecorder->setArchiveEnabled(enabled); + } + //------------------------------------------------------------------------------------------------- // scroll speed val val = GadgetSliderGetPosition(sliderScrollSpeed); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 461b38cd870..a2a4a93b80f 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -118,9 +118,10 @@ class RecorderClass : public SubsystemInterface { Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls - AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. - AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. + static AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. + static AsciiString getReplayArchiveDir(); ///< Returns the directory that holds the archived replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -133,10 +134,12 @@ class RecorderClass : public SubsystemInterface { Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository + void setArchiveEnabled(Bool enable) { m_archiveReplays = enable; } ///< Enable or disable replay archiving. void stopRecording(); ///< Stop recording and close m_file. protected: void startRecording(GameDifficulty diff, Int originalGameMode, Int rankPoints, Int maxFPS); ///< Start recording to m_file. void writeToFile(GameMessage *msg); ///< Write this GameMessage to m_file. + void archiveReplay(AsciiString fileName); ///< Move the specified replay file to the archive directory. void logGameStart(AsciiString options); void logGameEnd( void ); @@ -167,6 +170,7 @@ class RecorderClass : public SubsystemInterface { Bool m_wasDesync; Bool m_doingAnalysis; + Bool m_archiveReplays; ///< if true, each replay is archived to the replay archive folder after recording Int m_originalGameMode; // valid in replays diff --git a/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h b/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h index b62a0afbe29..dbe3e77b2a2 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/UserPreferences.h @@ -92,6 +92,7 @@ class OptionPreferences : public UserPreferences void setOnlineIPAddress(AsciiString IP); // convenience function void setLANIPAddress(UnsignedInt IP); // convenience function void setOnlineIPAddress(UnsignedInt IP); // convenience function + Bool getArchiveReplaysEnabled() const; // convenience function Bool getAlternateMouseModeEnabled(void); // convenience function Bool getRetaliationModeEnabled(); // convenience function Bool getDoubleClickAttackMoveEnabled(void); // convenience function diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index b59813da066..00271e4472b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -45,6 +45,7 @@ #include "GameLogic/GameLogic.h" #include "Common/RandomValue.h" #include "Common/CRCDebug.h" +#include "Common/UserPreferences.h" #include "Common/version.h" constexpr const char s_genrep[] = "GENREP"; @@ -370,6 +371,7 @@ RecorderClass::RecorderClass() //Added By Sadullah Nader //Initializtion(s) inserted m_doingAnalysis = FALSE; + m_archiveReplays = FALSE; m_nextFrame = 0; m_wasDesync = FALSE; // @@ -406,6 +408,9 @@ void RecorderClass::init() { m_wasDesync = FALSE; m_doingAnalysis = FALSE; m_playbackFrameCount = 0; + + OptionPreferences optionPref; + m_archiveReplays = optionPref.getArchiveReplaysEnabled(); } /** @@ -728,10 +733,42 @@ void RecorderClass::stopRecording() { if (m_file != NULL) { m_file->close(); m_file = NULL; + + if (m_archiveReplays) + archiveReplay(m_fileName); } m_fileName.clear(); } +/** + * TheSuperHackers @feature Stubbjax 17/10/2025 Copy the replay file to the archive directory and rename it using the current timestamp. + */ +void RecorderClass::archiveReplay(AsciiString fileName) +{ + SYSTEMTIME st; + GetLocalTime(&st); + + AsciiString archiveFileName; + // Use a standard YYYYMMDD_HHMMSS format for simplicity and to avoid conflicts. + archiveFileName.format("%04d%02d%02d_%02d%02d%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + AsciiString extension = getReplayExtention(); + AsciiString sourcePath = getReplayDir(); + sourcePath.concat(fileName); + + if (!sourcePath.endsWith(extension)) + sourcePath.concat(extension); + + AsciiString destPath = getReplayArchiveDir(); + TheFileSystem->createDirectory(destPath.str()); + + destPath.concat(archiveFileName); + destPath.concat(extension); + + if (!CopyFile(sourcePath.str(), destPath.str(), FALSE)) + DEBUG_LOG(("RecorderClass::archiveReplay: Failed to copy %s to %s", sourcePath.str(), destPath.str())); +} + /** * Write this game message to the record file. This also writes the game message's execution frame. */ @@ -1598,10 +1635,18 @@ RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { */ AsciiString RecorderClass::getReplayDir() { - const char* replayDir = "Replays\\"; + AsciiString tmp = TheGlobalData->getPath_UserData(); + tmp.concat("Replays\\"); + return tmp; +} +/** + * returns the directory that holds the archived replay files. + */ +AsciiString RecorderClass::getReplayArchiveDir() +{ AsciiString tmp = TheGlobalData->getPath_UserData(); - tmp.concat(replayDir); + tmp.concat("ArchivedReplays\\"); return tmp; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 1e0498ea1c4..0385a3a6956 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -38,6 +38,7 @@ #include "Common/GameEngine.h" #include "Common/UserPreferences.h" #include "Common/GameLOD.h" +#include "Common/Recorder.h" #include "Common/Registry.h" #include "Common/version.h" @@ -319,6 +320,18 @@ void OptionPreferences::setOnlineIPAddress( UnsignedInt IP ) (*this)["GameSpyIPAddress"] = tmp; } +Bool OptionPreferences::getArchiveReplaysEnabled() const +{ + OptionPreferences::const_iterator it = find("ArchiveReplays"); + if (it == end()) + return FALSE; + + if (stricmp(it->second.str(), "yes") == 0) { + return TRUE; + } + return FALSE; +} + Bool OptionPreferences::getAlternateMouseModeEnabled(void) { OptionPreferences::const_iterator it = find("UseAlternateMouse"); @@ -1356,6 +1369,13 @@ static void saveOptions( void ) TheWritableGlobalData->m_enablePlayerObserver = enabled; } + // TheSuperHackers @todo Add checkbox ? + { + Bool enabled = pref->getArchiveReplaysEnabled(); + (*pref)["ArchiveReplays"] = enabled ? "yes" : "no"; + TheRecorder->setArchiveEnabled(enabled); + } + //------------------------------------------------------------------------------------------------- // scroll speed val val = GadgetSliderGetPosition(sliderScrollSpeed);