From 765d92b50a60dfd0dbbbe1773e31b7550a58bc84 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:40:02 +0100 Subject: [PATCH 01/16] Implement graceful exit for Alt+F4 and window close events --- .../GameEngine/Include/GameLogic/GameLogic.h | 2 + .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 29 +--------- .../GameClient/MessageStream/CommandXlat.cpp | 20 +------ .../Source/GameLogic/System/GameLogic.cpp | 58 +++++++++++++++++++ .../GameLogic/System/GameLogicDispatch.cpp | 3 +- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 48522894f90..9623d848a44 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -136,6 +136,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate( void ) const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame( void ); ///< Returns the current simulation frame number + void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -405,6 +406,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void xferObjectTOC( Xfer *xfer ); ///< save/load object TOC for current state of map void prepareLogicForObjectLoad( void ); ///< prepare engine for object data from game file + Bool m_quitToDesktopAfterMatch; }; // INLINE ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index fc0c4520266..5547d31df29 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -139,21 +139,7 @@ static void exitQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - // clear out all the game data - if ( TheGameLogic->isInMultiplayerGame() && !TheGameLogic->isInSkirmishGame() && !TheGameInfo->isSandbox() ) - { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); - } - TheGameLogic->exitGame(); - // TheGameLogic->clearGameData(); - // display the menu on top of the shell stack - // TheShell->showShell(); - - // this will trigger an exit - // TheGameEngine->setQuitting( TRUE ); - TheInGameUI->setClientQuiet( TRUE ); + TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -164,18 +150,7 @@ static void quitToDesktopQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); - TheInGameUI->setClientQuiet( TRUE ); - + TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index c90804d231f..239bdc2a452 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -91,10 +91,6 @@ #include "ww3d.h" - -#define dont_ALLOW_ALT_F4 - - #if defined(RTS_DEBUG) /*non-static*/ Real TheSkateDistOverride = 0.0f; @@ -4076,26 +4072,12 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage } - - -#ifdef ALLOW_ALT_F4 case GameMessage::MSG_META_DEMO_INSTANT_QUIT: { - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; } -#endif - - //------------------------------------------------------------------------------- DEMO MESSAGES diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dcb20cc70cd..fc880969e51 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -141,6 +141,8 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; +extern GameInfo *TheGameInfo; + static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -262,6 +264,7 @@ GameLogic::GameLogic( void ) m_loadingMap = FALSE; m_loadingSave = FALSE; m_clearingGameData = FALSE; + m_quitToDesktopAfterMatch = FALSE; } //------------------------------------------------------------------------------------------------- @@ -4170,6 +4173,61 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop) +{ + if (isInGame()) + { + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } + + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) + { + TheRecorder->stopRecording(); + } + + setGamePaused(FALSE); + if (TheScriptEngine) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + exitGame(); + } + else + { + clearGameData(); + } + } + else + { + exitGame(); + } + } + + if (toDesktop) + { + if (!isInMultiplayerGame()) + { + TheGameEngine->setQuitting(TRUE); + } + } + + if (TheInGameUI) + { + TheInGameUI->setClientQuiet(TRUE); + } +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index a3a2c757854..41eca907e0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -280,9 +280,10 @@ void GameLogic::clearGameData( Bool showScoreScreen ) // if(shellGame) - if (TheGlobalData->m_initialFile.isEmpty() == FALSE) + if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch) { TheGameEngine->setQuitting(TRUE); + m_quitToDesktopAfterMatch = FALSE; } HideControlBar(); From 4fda496d03ad85a01eef13e727f442ceac796e95 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:44:03 +0100 Subject: [PATCH 02/16] Implement graceful exit during loading and movie screens --- .../Source/GameClient/GUI/LoadScreen.cpp | 47 ++++++++++--------- .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Include/GameLogic/GameLogic.h | 3 +- .../Source/GameClient/GameClient.cpp | 32 +++++++++++++ .../GameClient/MessageStream/CommandXlat.cpp | 1 + .../Source/GameLogic/System/GameLogic.cpp | 14 +++++- 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 59154f13f45..1a0dfccbd81 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -68,6 +68,7 @@ #include "GameClient/Display.h" #include "GameClient/GadgetProgressBar.h" #include "GameClient/GadgetStaticText.h" +#include "GameClient/GameClient.h" #include "GameClient/GameText.h" #include "GameClient/GameWindowManager.h" #include "GameClient/GameWindowTransitions.h" @@ -75,6 +76,7 @@ #include "GameClient/LoadScreen.h" #include "GameClient/MapUtil.h" #include "GameClient/Mouse.h" +#include "Common/MessageStream.h" #include "GameClient/Shell.h" #include "GameClient/VideoPlayer.h" #include "GameClient/WindowLayout.h" @@ -157,7 +159,8 @@ LoadScreen::~LoadScreen( void ) void LoadScreen::update( Int percent ) { TheGameEngine->serviceWindowsOS(); - if (TheGameEngine->getQuitting()) + TheMessageStream->propagateMessages(); + if (TheGameEngine->getQuitting() || TheGameLogic->m_quitToDesktopAfterMatch) return; //don't bother with any of this if the player is exiting game. TheWindowManager->update(); @@ -539,20 +542,11 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - // TheSuperHackers @feature User can now skip video by pressing ESC - if (TheKeyboard) + if (TheGameClient->isMovieAbortRequested()) { - TheKeyboard->UPDATE(); - KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); - if (io && BitIsSet(io->state, KEY_STATE_DOWN)) - { - io->setUsed(); - break; - } + break; } - TheGameEngine->serviceWindowsOS(); - if(!m_videoStream->isFrameReady()) { Sleep(1); @@ -634,6 +628,11 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); TheDisplay->draw(); Sleep(100); @@ -1054,20 +1053,11 @@ void ChallengeLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - // TheSuperHackers @feature User can now skip video by pressing ESC - if (TheKeyboard) + if (TheGameClient->isMovieAbortRequested()) { - TheKeyboard->UPDATE(); - KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); - if (io && BitIsSet(io->state, KEY_STATE_DOWN)) - { - io->setUsed(); - break; - } + break; } - TheGameEngine->serviceWindowsOS(); - if(!m_videoStream->isFrameReady()) { Sleep(1); @@ -1109,7 +1099,13 @@ void ChallengeLoadScreen::init( GameInfo *game ) // if we're min speced m_videoStream->frameGoto(m_videoStream->frameCount()); // zero based while(!m_videoStream->isFrameReady()) + { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } Sleep(1); + } m_videoStream->frameDecompress(); m_videoStream->frameRender(m_videoBuffer); if(m_videoBuffer) @@ -1126,6 +1122,11 @@ void ChallengeLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); TheDisplay->draw(); Sleep(100); diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index e1427047606..a27a6901513 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,6 +155,8 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; + virtual Bool isMovieAbortRequested( void ); + protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 9623d848a44..d705265525f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -262,6 +262,8 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); + Bool m_quitToDesktopAfterMatch; + protected: // snapshot methods @@ -406,7 +408,6 @@ class GameLogic : public SubsystemInterface, public Snapshot void xferObjectTOC( Xfer *xfer ); ///< save/load object TOC for current state of map void prepareLogicForObjectLoad( void ); ///< prepare engine for object data from game file - Bool m_quitToDesktopAfterMatch; }; // INLINE ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index d59578dff2c..7ce3211a463 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -556,6 +556,11 @@ void GameClient::update( void ) Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); // redraw all views, update the GUI TheDisplay->draw(); @@ -793,6 +798,33 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } + // TheSuperHackers Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested( void ) +{ + // User can skip video by pressing ESC + if (TheKeyboard) + { + TheKeyboard->UPDATE(); + KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); + if (io && BitIsSet(io->state, KEY_STATE_DOWN)) + { + io->setUsed(); + return TRUE; + } + } + + // Service OS for Window Close / Alt-F4 events + TheGameEngine->serviceWindowsOS(); + TheMessageStream->propagateMessages(); + + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + { + return TRUE; + } + + return FALSE; +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 239bdc2a452..d8365a79707 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -5568,6 +5568,7 @@ static Bool isSystemMessage( const GameMessage *msg ) case GameMessage::MSG_LOGIC_CRC: case GameMessage::MSG_SET_REPLAY_CAMERA: case GameMessage::MSG_FRAME_TICK: + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: return TRUE; } return FALSE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index fc880969e51..33bdaa0257d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1055,6 +1055,8 @@ static void populateRandomStartPosition( GameInfo *game ) } } +struct QuitGameException {}; + // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -1064,6 +1066,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -1105,6 +1111,8 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool loadingSaveGame ) { + try + { #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2372,7 +2380,11 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - + } + catch (QuitGameException&) + { + // TheSuperHackers: The application is cleanly aborting the loading process. + } } //----------------------------------------------------------------------------------------- From 4465409ccac071d4b3aa983f92505a6880afeee5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:54:48 +0100 Subject: [PATCH 03/16] Backport graceful exit logic from GeneralsMD --- .../Include/GameClient/GameClient.h | 2 + .../GameEngine/Include/GameLogic/GameLogic.h | 3 + .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 29 +------- .../Source/GameClient/GameClient.cpp | 32 +++++++++ .../GameClient/MessageStream/CommandXlat.cpp | 10 +-- .../Source/GameLogic/System/GameLogic.cpp | 72 ++++++++++++++++++- .../GameLogic/System/GameLogicDispatch.cpp | 3 +- 7 files changed, 114 insertions(+), 37 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index 9f73f894b84..f3dd95b709a 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,6 +150,8 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } + virtual Bool isMovieAbortRequested( void ); + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index ec6e60bb01d..0e55e9e7b84 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -131,6 +131,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate( void ) const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame( void ); ///< Returns the current simulation frame number + void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -243,6 +244,8 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); + Bool m_quitToDesktopAfterMatch; + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index e9d1f7267c5..8b728eb587e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -139,21 +139,7 @@ static void exitQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - // clear out all the game data - if ( TheGameLogic->isInMultiplayerGame() && !TheGameLogic->isInSkirmishGame() && !TheGameInfo->isSandbox() ) - { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); - } - TheGameLogic->exitGame(); - // TheGameLogic->clearGameData(); - // display the menu on top of the shell stack - // TheShell->showShell(); - - // this will trigger an exit - // TheGameEngine->setQuitting( TRUE ); - TheInGameUI->setClientQuiet( TRUE ); + TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -164,18 +150,7 @@ static void quitToDesktopQuitMenu() { // destroy the quit menu destroyQuitMenu(); - - if (TheGameLogic->isInGame()) - { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); - TheInGameUI->setClientQuiet( TRUE ); - + TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 78f6f63984b..48814c78399 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -535,6 +535,11 @@ void GameClient::update( void ) Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { + if (TheGameClient->isMovieAbortRequested()) + { + break; + } + TheWindowManager->update(); // redraw all views, update the GUI TheDisplay->draw(); @@ -755,6 +760,33 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } + // TheSuperHackers Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested( void ) +{ + // User can skip video by pressing ESC + if (TheKeyboard) + { + TheKeyboard->UPDATE(); + KeyboardIO *io = TheKeyboard->findKey(KEY_ESC, KeyboardIO::STATUS_UNUSED); + if (io && BitIsSet(io->state, KEY_STATE_DOWN)) + { + io->setUsed(); + return TRUE; + } + } + + // Service OS for Window Close / Alt-F4 events + TheGameEngine->serviceWindowsOS(); + TheMessageStream->propagateMessages(); + + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + { + return TRUE; + } + + return FALSE; +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 42932540419..415b8f842ca 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3706,15 +3706,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //------------------------------------------------------------------------------- DEMO MESSAGES //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - if (TheGameLogic->isInGame()) { - if (TheRecorder->getMode() == RECORDERMODETYPE_RECORD) - { - TheRecorder->stopRecording(); - } - TheGameLogic->clearGameData(); - } - TheGameEngine->setQuitting(TRUE); + TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; @@ -5053,6 +5046,7 @@ static Bool isSystemMessage( const GameMessage *msg ) case GameMessage::MSG_LOGIC_CRC: case GameMessage::MSG_SET_REPLAY_CAMERA: case GameMessage::MSG_FRAME_TICK: + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: return TRUE; } return FALSE; diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index ed6339f87e6..ea28c0e5358 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -130,6 +130,8 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; +extern GameInfo *TheGameInfo; + static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -244,6 +246,7 @@ GameLogic::GameLogic( void ) m_logicTimeScaleEnabledMemory = FALSE; m_loadScreen = nullptr; m_forceGameStartByTimeOut = FALSE; + m_quitToDesktopAfterMatch = FALSE; #ifdef DUMP_PERF_STATS m_overallFailedPathfinds = 0; #endif @@ -911,6 +914,8 @@ static void populateRandomStartPosition( GameInfo *game ) } } +struct QuitGameException {}; + // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -920,6 +925,10 @@ void GameLogic::updateLoadProgress( Int progress ) if( m_loadScreen ) m_loadScreen->update( progress ); + if (TheGameEngine->getQuitting() || m_quitToDesktopAfterMatch) + { + throw QuitGameException(); + } } // ------------------------------------------------------------------------------------------------ @@ -966,6 +975,8 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool saveGame ) { + try + { #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2067,7 +2078,11 @@ void GameLogic::startNewGame( Bool saveGame ) TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - + } + catch (QuitGameException&) + { + // TheSuperHackers: The application is cleanly aborting the loading process. + } } //----------------------------------------------------------------------------------------- @@ -3619,6 +3634,61 @@ void GameLogic::exitGame() TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); } +// ------------------------------------------------------------------------------------------------ +void GameLogic::quit(Bool toDesktop) +{ + if (isInGame()) + { + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } + + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) + { + TheRecorder->stopRecording(); + } + + setGamePaused(FALSE); + if (TheScriptEngine) + { + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + } + + if (toDesktop) + { + if (isInMultiplayerGame()) + { + m_quitToDesktopAfterMatch = TRUE; + exitGame(); + } + else + { + clearGameData(); + } + } + else + { + exitGame(); + } + } + + if (toDesktop) + { + if (!isInMultiplayerGame()) + { + TheGameEngine->setQuitting(TRUE); + } + } + + if (TheInGameUI) + { + TheInGameUI->setClientQuiet(TRUE); + } +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index ef4e62d534e..79b5b6a915b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -275,9 +275,10 @@ void GameLogic::clearGameData( Bool showScoreScreen ) // if(shellGame) - if (TheGlobalData->m_initialFile.isEmpty() == FALSE) + if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch) { TheGameEngine->setQuitting(TRUE); + m_quitToDesktopAfterMatch = FALSE; } HideControlBar(); From 036ed84a3da6940cd2202d7609282977c1c01af6 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Sun, 22 Feb 2026 03:32:51 +0100 Subject: [PATCH 04/16] Implemented feedback --- .../GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 4 ++-- .../Code/GameEngine/Include/Common/MessageStream.h | 2 +- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- .../GameEngine/Source/GameClient/GameClient.cpp | 8 ++++---- .../Source/GameClient/MessageStream/CommandXlat.cpp | 13 ++++--------- .../Source/GameLogic/System/GameLogic.cpp | 4 +--- .../Code/GameEngine/Include/GameClient/GameClient.h | 3 +-- .../GameEngine/Source/GameClient/GameClient.cpp | 8 ++++---- .../Source/GameLogic/System/GameLogic.cpp | 4 +--- 9 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 1a0dfccbd81..aff0c1e657a 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -60,6 +60,7 @@ #include "Common/GameEngine.h" #include "Common/GameLOD.h" #include "Common/GameState.h" +#include "Common/MessageStream.h" #include "Common/MultiplayerSettings.h" #include "Common/Player.h" #include "Common/PlayerList.h" @@ -76,7 +77,6 @@ #include "GameClient/LoadScreen.h" #include "GameClient/MapUtil.h" #include "GameClient/Mouse.h" -#include "Common/MessageStream.h" #include "GameClient/Shell.h" #include "GameClient/VideoPlayer.h" #include "GameClient/WindowLayout.h" @@ -160,7 +160,7 @@ void LoadScreen::update( Int percent ) { TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); - if (TheGameEngine->getQuitting() || TheGameLogic->m_quitToDesktopAfterMatch) + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) return; //don't bother with any of this if the player is exiting game. TheWindowManager->update(); diff --git a/Generals/Code/GameEngine/Include/Common/MessageStream.h b/Generals/Code/GameEngine/Include/Common/MessageStream.h index 25413a73f80..a4fa3621cef 100644 --- a/Generals/Code/GameEngine/Include/Common/MessageStream.h +++ b/Generals/Code/GameEngine/Include/Common/MessageStream.h @@ -277,6 +277,7 @@ class GameMessage : public MemoryPoolObject MSG_META_TOGGLE_PAUSE_ALT, ///< TheSuperHackers @feature Toggle game pause (alternative mapping) MSG_META_STEP_FRAME, ///< TheSuperHackers @feature Step one frame MSG_META_STEP_FRAME_ALT, ///< TheSuperHackers @feature Step one frame (alternative mapping) + MSG_META_DEMO_INSTANT_QUIT, ///< bail out of game immediately // META items that are really for debug/demo/development use only... @@ -289,7 +290,6 @@ class GameMessage : public MemoryPoolObject MSG_META_DEMO_LOD_INCREASE, ///< increase LOD by 1 MSG_META_DEMO_TOGGLE_ZOOM_LOCK, ///< Toggle the camera zoom lock on/off MSG_META_DEMO_PLAY_CAMEO_MOVIE, ///< Play a movie in the cameo spot - MSG_META_DEMO_INSTANT_QUIT, ///< bail out of game immediately MSG_META_DEMO_TOGGLE_SPECIAL_POWER_DELAYS, ///< Toggle special power delays on/off MSG_META_DEMO_BATTLE_CRY, ///< battle cry MSG_META_DEMO_SWITCH_TEAMS, ///< switch local control to another team diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index f3dd95b709a..b27659389df 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,7 +150,7 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } - virtual Bool isMovieAbortRequested( void ); + virtual Bool isMovieAbortRequested(); protected: diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 48814c78399..4895398ccec 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -760,10 +760,10 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } - // TheSuperHackers Check if the user has requested to abort movie -Bool GameClient::isMovieAbortRequested( void ) +// TheSuperHackers @feature Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested() { - // User can skip video by pressing ESC + // TheSuperHackers @feature User can skip video by pressing ESC if (TheKeyboard) { TheKeyboard->UPDATE(); @@ -775,7 +775,7 @@ Bool GameClient::isMovieAbortRequested( void ) } } - // Service OS for Window Close / Alt-F4 events +// TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 415b8f842ca..164c9cd88f3 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3697,20 +3697,15 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - - -#if defined(RTS_DEBUG) - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------- BEGIN DEMO MESSAGES - //------------------------------------------------------------------------------- DEMO MESSAGES - //----------------------------------------------------------------------------------------- + case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - { + { TheGameLogic->quit(TRUE); disp = DESTROY_MESSAGE; break; + } +#if defined(RTS_DEBUG) //------------------------------------------------------------------------------- DEMO MESSAGES //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_SWITCH_TEAMS: diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index ea28c0e5358..9f1ef16c53e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -130,8 +130,6 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; -extern GameInfo *TheGameInfo; - static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -2081,7 +2079,7 @@ void GameLogic::startNewGame( Bool saveGame ) } catch (QuitGameException&) { - // TheSuperHackers: The application is cleanly aborting the loading process. + // TheSuperHackers @info The application is cleanly aborting the loading process } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index a27a6901513..4a528905dd5 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,8 +155,7 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; - virtual Bool isMovieAbortRequested( void ); - + virtual Bool isMovieAbortRequested(); protected: diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 7ce3211a463..a5cd0e78d6d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -798,10 +798,10 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } - // TheSuperHackers Check if the user has requested to abort movie -Bool GameClient::isMovieAbortRequested( void ) +// TheSuperHackers @feature Check if the user has requested to abort movie +Bool GameClient::isMovieAbortRequested() { - // User can skip video by pressing ESC + // TheSuperHackers @feature User can skip video by pressing ESC if (TheKeyboard) { TheKeyboard->UPDATE(); @@ -813,7 +813,7 @@ Bool GameClient::isMovieAbortRequested( void ) } } - // Service OS for Window Close / Alt-F4 events + // TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 33bdaa0257d..296a34f77df 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -141,8 +141,6 @@ enum { OBJ_HASH_SIZE = 8192 }; /// The GameLogic singleton instance GameLogic *TheGameLogic = nullptr; -extern GameInfo *TheGameInfo; - static void findAndSelectCommandCenter(Object *obj, void* alreadyFound); @@ -2383,7 +2381,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } catch (QuitGameException&) { - // TheSuperHackers: The application is cleanly aborting the loading process. + // TheSuperHackers @info The application is cleanly aborting the loading process } } From 6b28aedfdcc128aad10f0f7e12869c8993c74e2b Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:27:56 +0100 Subject: [PATCH 05/16] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- Generals/Code/GameEngine/Source/GameClient/GameClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index e684077e792..75c8032670e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -775,7 +775,7 @@ Bool GameClient::isMovieAbortRequested() } } -// TheSuperHackers @feature Service OS for Window Close / Alt-F4 events + // TheSuperHackers @feature Service OS for Window Close / Alt-F4 events TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); From 6f2b0b88f9ba9b94701f7a84da51bde05a6359aa Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:22:28 +0100 Subject: [PATCH 06/16] refine graceful exit logic and apply suggestions --- Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 10 +++++----- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- Generals/Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 3 +-- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 5 ++++- .../Code/GameEngine/Include/GameClient/GameClient.h | 2 +- .../Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 3 +-- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 5 ++++- 9 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index eae715316b9..b0a5b065429 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -542,7 +542,7 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -628,7 +628,7 @@ void SinglePlayerLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1053,7 +1053,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) Int shiftedPercent = -FRAME_FUDGE_ADD + 1; while (m_videoStream->frameIndex() < m_videoStream->frameCount() - 1 ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1100,7 +1100,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) m_videoStream->frameGoto(m_videoStream->frameCount()); // zero based while(!m_videoStream->isFrameReady()) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -1122,7 +1122,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) fudgeFactor = 30 * ((currTime - begin)/ INT_TO_REAL(delay )); GadgetProgressBarSetProgress(m_progressBar, fudgeFactor); - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index e0c277d3805..2bd00f36d00 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -150,7 +150,7 @@ class GameClient : public SubsystemInterface, UnsignedInt getRenderedObjectCount() const { return m_renderedObjectCount; } void incrementRenderedObjectCount() { m_renderedObjectCount++; } - virtual Bool isMovieAbortRequested(); + static Bool isMovieAbortRequested(); protected: diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index bf17974bb89..ff5f4cf8c1d 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -131,7 +131,6 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate() const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame(); ///< Returns the current simulation frame number - void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -197,6 +196,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); + void quit(Bool toDesktop); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 75c8032670e..8fb8fcde69a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -535,7 +535,7 @@ void GameClient::update() Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -760,7 +760,6 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } -// TheSuperHackers @feature Check if the user has requested to abort movie Bool GameClient::isMovieAbortRequested() { // TheSuperHackers @feature User can skip video by pressing ESC diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 47839bd5c88..0bc6ff01c31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3637,7 +3637,9 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); + + if (isInMultiplayerGame() && TheGameInfo && !isSandbox) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); @@ -3649,6 +3651,7 @@ void GameLogic::quit(Bool toDesktop) } setGamePaused(FALSE); + if (TheScriptEngine) { TheScriptEngine->forceUnfreezeTime(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index 283752df1f4..e143fbd2b05 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -155,7 +155,7 @@ class GameClient : public SubsystemInterface, void incrementRenderedObjectCount() { m_renderedObjectCount++; } virtual void notifyTerrainObjectMoved(Object *obj) = 0; - virtual Bool isMovieAbortRequested(); + static Bool isMovieAbortRequested(); protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 8cd3c395eb1..1e33cfa563b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -136,7 +136,6 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isInGameLogicUpdate() const { return m_isInUpdate; } Bool hasUpdated() const { return m_hasUpdated; } ///< Returns true if the logic frame has advanced in the current client/render update UnsignedInt getFrame(); ///< Returns the current simulation frame number - void quit(Bool toDesktop); UnsignedInt getCRC( Int mode = CRC_CACHED, AsciiString deepCRCFileName = AsciiString::TheEmptyString ); ///< Returns the CRC void setObjectIDCounter( ObjectID nextObjID ) { m_nextObjID = nextObjID; } @@ -212,6 +211,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); + void quit(Bool toDesktop); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 9e4f82ebc57..238cf5b0051 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -556,7 +556,7 @@ void GameClient::update() Int beginTime = timeGetTime(); while(beginTime + 4000 > timeGetTime() ) { - if (TheGameClient->isMovieAbortRequested()) + if (GameClient::isMovieAbortRequested()) { break; } @@ -798,7 +798,6 @@ void GameClient::updateHeadless() TheParticleSystemManager->reset(); } -// TheSuperHackers @feature Check if the user has requested to abort movie Bool GameClient::isMovieAbortRequested() { // TheSuperHackers @feature User can skip video by pressing ESC diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 52cf669fb95..abb6c01a21f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4188,7 +4188,9 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) + const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); + + if (isInMultiplayerGame() && TheGameInfo && !isSandbox) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); @@ -4200,6 +4202,7 @@ void GameLogic::quit(Bool toDesktop) } setGamePaused(FALSE); + if (TheScriptEngine) { TheScriptEngine->forceUnfreezeTime(); From c655da42563aca9258da4ca4299dfbb93022b9a5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:10:07 +0100 Subject: [PATCH 07/16] added tryStartNewGame to avoid long catch --- .../Code/GameEngine/Include/GameLogic/GameLogic.h | 1 + .../Source/GameLogic/System/GameLogic.cpp | 15 +++++++++------ .../Code/GameEngine/Include/GameLogic/GameLogic.h | 1 + .../Source/GameLogic/System/GameLogic.cpp | 15 +++++++++------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index ff5f4cf8c1d..7d27118e0ae 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -153,6 +153,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool saveGame ); + void tryStartNewGame( Bool saveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 0bc6ff01c31..12d8ed3c04a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -975,7 +975,15 @@ void GameLogic::startNewGame( Bool saveGame ) { try { + tryStartNewGame(saveGame); + } + catch (QuitGameException&) + { + } +} +void GameLogic::tryStartNewGame( Bool saveGame ) +{ #ifdef DUMP_PERF_STATS __int64 startTime64; __int64 endTime64,freq64; @@ -2075,14 +2083,9 @@ void GameLogic::startNewGame( Bool saveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } - catch (QuitGameException&) - { - // TheSuperHackers @info The application is cleanly aborting the loading process - } } + //----------------------------------------------------------------------------------------- static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 1e33cfa563b..2bd30a053dd 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -158,6 +158,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool loadSaveGame ); + void tryStartNewGame( Bool loadSaveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index abb6c01a21f..2ca8cf9a452 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1111,6 +1111,15 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { try { + tryStartNewGame(loadingSaveGame); + } + catch (QuitGameException&) + { + } +} + +void GameLogic::tryStartNewGame( Bool loadingSaveGame ) +{ #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -2377,12 +2386,6 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { TheInGameUI->messageNoFormat( TheGameText->FETCH_OR_SUBSTITUTE( "GUI:FastForwardInstructions", L"Press F to toggle Fast Forward" ) ); } - - } - catch (QuitGameException&) - { - // TheSuperHackers @info The application is cleanly aborting the loading process - } } //----------------------------------------------------------------------------------------- From 4b7a87252b0a6a3b45fdc9794e6b6c5734dba30f Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:18:14 +0100 Subject: [PATCH 08/16] added comment and removed blank link --- Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 12d8ed3c04a..e4bc4efa649 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -979,6 +979,7 @@ void GameLogic::startNewGame( Bool saveGame ) } catch (QuitGameException&) { + // TheSuperHackers @info The application is cleanly aborting the loading process } } @@ -2085,7 +2086,6 @@ void GameLogic::tryStartNewGame( Bool saveGame ) } } - //----------------------------------------------------------------------------------------- static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 2ca8cf9a452..bc1a6da4fa1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1115,6 +1115,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } catch (QuitGameException&) { + // TheSuperHackers @info The application is cleanly aborting the loading process } } From c6ee10659173d829c81b660bfcdeb0e4ce40f100 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:25:57 +0100 Subject: [PATCH 09/16] Added isquitmenuvisible as check --- .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 4 ++-- .../Source/GameLogic/System/GameLogic.cpp | 17 ++++++++++++----- .../GUI/GUICallbacks/Menus/QuitMenu.cpp | 4 ++-- .../Source/GameLogic/System/GameLogic.cpp | 17 ++++++++++++----- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index 910f0b157bc..2bf1e959f41 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -137,9 +137,9 @@ void destroyQuitMenu() */ static void exitQuitMenu() { + TheGameLogic->quit(FALSE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -148,9 +148,9 @@ static void noExitQuitMenu() static void quitToDesktopQuitMenu() { + TheGameLogic->quit(TRUE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index e4bc4efa649..75790105739 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3640,12 +3640,19 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); - - if (isInMultiplayerGame() && TheGameInfo && !isSandbox) + if (isInInteractiveGame()) { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); + if (!TheInGameUI->isQuitMenuVisible()) + { + ToggleQuitMenu(); + return; + } + + if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } } if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index ab68e8183b4..e35afd50b21 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -137,9 +137,9 @@ void destroyQuitMenu() */ static void exitQuitMenu() { + TheGameLogic->quit(FALSE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(FALSE); } static void noExitQuitMenu() { @@ -148,9 +148,9 @@ static void noExitQuitMenu() static void quitToDesktopQuitMenu() { + TheGameLogic->quit(TRUE); // destroy the quit menu destroyQuitMenu(); - TheGameLogic->quit(TRUE); } static void surrenderQuitMenu() diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index bc1a6da4fa1..a77b33a0d4c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4192,12 +4192,19 @@ void GameLogic::quit(Bool toDesktop) { if (isInGame()) { - const Bool isSandbox = TheGameInfo && TheGameInfo->isSandbox(); - - if (isInMultiplayerGame() && TheGameInfo && !isSandbox) + if (isInInteractiveGame()) { - GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); - msg->appendBooleanArgument(TRUE); + if (!TheInGameUI->isQuitMenuVisible()) + { + ToggleQuitMenu(); + return; + } + + if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); + msg->appendBooleanArgument(TRUE); + } } if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD) From 5249bf996dcb95f5dc19e4c5a461bd1eb06d97b1 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 18 Mar 2026 02:32:37 +0100 Subject: [PATCH 10/16] Make m_quitToDesktopAfterMatch private. Fix silent no-op during loading screen when triggered via MSG_META_DEMO_INSTANT_QUIT (Alt+F4). Allow sending disconnect message depending on game type loaded. Differentiate between Alt+F4 and Window X button. Skip confirmation on Alt+F4 but keep disconnect messages if in multiplayer. Add if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) check. Fix game crash when closing immediately from taskbar (checking ThePlayerList initialization). --- .../Source/GameClient/GUI/LoadScreen.cpp | 2 +- .../GameEngine/Include/GameLogic/GameLogic.h | 6 ++- .../Source/GameClient/GameClient.cpp | 2 +- .../GameClient/MessageStream/CommandXlat.cpp | 7 +++- .../Source/GameLogic/System/GameLogic.cpp | 24 +++++++++--- Generals/Code/Main/WinMain.cpp | 25 ++++++++++-- .../GameEngine/Include/GameLogic/GameLogic.h | 6 ++- .../Source/GameClient/GameClient.cpp | 2 +- .../GameClient/MessageStream/CommandXlat.cpp | 11 ++++-- .../Source/GameLogic/System/GameLogic.cpp | 24 +++++++++--- GeneralsMD/Code/Main/WinMain.cpp | 38 +++++++++++-------- 11 files changed, 105 insertions(+), 42 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index b0a5b065429..a69746d773b 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -160,7 +160,7 @@ void LoadScreen::update( Int percent ) { TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); - if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->isQuitToDesktopRequested())) return; //don't bother with any of this if the player is exiting game. TheWindowManager->update(); diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index 7d27118e0ae..2a3731aae90 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -197,7 +197,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); - void quit(Bool toDesktop); + void quit(Bool toDesktop, Bool force = FALSE); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); @@ -245,7 +245,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); - Bool m_quitToDesktopAfterMatch; + Bool isQuitToDesktopRequested() const { return m_quitToDesktopAfterMatch; } protected: @@ -256,6 +256,8 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + Bool m_quitToDesktopAfterMatch; + void updateDisplayBusyState(); void pauseGameLogic(Bool paused); diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 8fb8fcde69a..de53079f3cc 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -778,7 +778,7 @@ Bool GameClient::isMovieAbortRequested() TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); - if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->isQuitToDesktopRequested())) { return TRUE; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 32486bf77ae..5c3ec6a3d59 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3700,7 +3700,12 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage case GameMessage::MSG_META_DEMO_INSTANT_QUIT: { - TheGameLogic->quit(TRUE); + Bool force = FALSE; + if (msg->getArgumentCount() > 0) + { + force = msg->getArgument(0)->boolean; + } + TheGameLogic->quit(TRUE, force); disp = DESTROY_MESSAGE; break; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 75790105739..3759c775c1f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3636,16 +3636,22 @@ void GameLogic::exitGame() } // ------------------------------------------------------------------------------------------------ -void GameLogic::quit(Bool toDesktop) +void GameLogic::quit(Bool toDesktop, Bool force) { if (isInGame()) { if (isInInteractiveGame()) { - if (!TheInGameUI->isQuitMenuVisible()) + if (force == FALSE) { - ToggleQuitMenu(); - return; + if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) + { + if (!isLoadingGame()) + { + ToggleQuitMenu(); + return; + } + } } if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) @@ -3673,11 +3679,17 @@ void GameLogic::quit(Bool toDesktop) if (isInMultiplayerGame()) { m_quitToDesktopAfterMatch = TRUE; - exitGame(); + if (!isLoadingGame()) + { + exitGame(); + } } else { - clearGameData(); + if (!isLoadingGame()) + { + clearGameData(); + } } } else diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 0ae105dc755..16a7bbe638d 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -56,6 +56,7 @@ #include "GameClient/InGameUI.h" #include "GameClient/GameClient.h" #include "GameLogic/GameLogic.h" ///< @todo for demo, remove +#include "Common/PlayerList.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" #include "Win32Device/GameClient/Win32Mouse.h" @@ -366,10 +367,26 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, // ------------------------------------------------------------------------ case WM_CLOSE: - TheGameEngine->checkAbnormalQuitting(); - TheGameEngine->reset(); - TheGameEngine->setQuitting(TRUE); - _exit(EXIT_SUCCESS); + if (TheGameEngine) + { + if (TheMessageStream && ThePlayerList) + { + Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + Bool f4Down = (GetAsyncKeyState(VK_F4) & 0x8000) != 0; + Bool isAltF4 = altDown && f4Down; + + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(isAltF4); + } + else + { + TheGameEngine->setQuitting(TRUE); + } + } + else + { + _exit(EXIT_SUCCESS); + } return 0; //------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 2bd30a053dd..81d44dfe150 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -212,7 +212,7 @@ class GameLogic : public SubsystemInterface, public Snapshot UnsignedInt getFrameObjectsChangedTriggerAreas() {return m_frameObjectsChangedTriggerAreas;} void exitGame(); - void quit(Bool toDesktop); + void quit(Bool toDesktop, Bool force = FALSE); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows(); @@ -263,7 +263,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // this should be called only by UpdateModule, thanks. void friend_awakenUpdateModule(Object* obj, UpdateModulePtr update, UnsignedInt whenToWakeUp); - Bool m_quitToDesktopAfterMatch; + Bool isQuitToDesktopRequested() const { return m_quitToDesktopAfterMatch; } protected: @@ -274,6 +274,8 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + Bool m_quitToDesktopAfterMatch; + void updateDisplayBusyState(); void pauseGameLogic(Bool paused); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 238cf5b0051..ad9ae0800a7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -816,7 +816,7 @@ Bool GameClient::isMovieAbortRequested() TheGameEngine->serviceWindowsOS(); TheMessageStream->propagateMessages(); - if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->m_quitToDesktopAfterMatch)) + if (TheGameEngine->getQuitting() || (TheGameLogic && TheGameLogic->isQuitToDesktopRequested())) { return TRUE; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index aa07e502c7f..2acb302ecee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -4073,11 +4073,16 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage } case GameMessage::MSG_META_DEMO_INSTANT_QUIT: - { - TheGameLogic->quit(TRUE); + { + Bool force = FALSE; + if (msg->getArgumentCount() > 0) + { + force = msg->getArgument(0)->boolean; + } + TheGameLogic->quit(TRUE, force); disp = DESTROY_MESSAGE; break; - } + } //------------------------------------------------------------------------------- DEMO MESSAGES diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index a77b33a0d4c..5de716746c9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4188,16 +4188,22 @@ void GameLogic::exitGame() } // ------------------------------------------------------------------------------------------------ -void GameLogic::quit(Bool toDesktop) +void GameLogic::quit(Bool toDesktop, Bool force) { if (isInGame()) { if (isInInteractiveGame()) { - if (!TheInGameUI->isQuitMenuVisible()) + if (force == FALSE) { - ToggleQuitMenu(); - return; + if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) + { + if (!isLoadingMap() && !isLoadingSave()) + { + ToggleQuitMenu(); + return; + } + } } if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) @@ -4225,11 +4231,17 @@ void GameLogic::quit(Bool toDesktop) if (isInMultiplayerGame()) { m_quitToDesktopAfterMatch = TRUE; - exitGame(); + if (!isLoadingMap() && !isLoadingSave()) + { + exitGame(); + } } else { - clearGameData(); + if (!isLoadingMap() && !isLoadingSave()) + { + clearGameData(); + } } } else diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 8c1871d6a3d..bcfd35da9d3 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -57,6 +57,7 @@ #include "GameClient/InGameUI.h" #include "GameClient/GameClient.h" #include "GameLogic/GameLogic.h" ///< @todo for demo, remove +#include "Common/PlayerList.h" #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" #include "Win32Device/GameClient/Win32Mouse.h" @@ -369,28 +370,35 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_QUERYENDSESSION: { - TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + if (TheMessageStream && ThePlayerList) + { + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(TRUE); // Force quit on Windows shutdown + } + else + { + TheGameEngine->setQuitting(TRUE); + } return 0; //don't allow Windows to shutdown while game is running. } // ------------------------------------------------------------------------ case WM_CLOSE: - if (!TheGameEngine->getQuitting()) + if (TheGameEngine && !TheGameEngine->getQuitting()) { - //user is exiting without using the menus - - //This method didn't work in cinematics because we don't process messages. - //But it's the cleanest way to exit that's similar to using menus. - TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); - - //This method used to disable quitting. We just put up the options screen instead. - //TheMessageStream->appendMessage(GameMessage::MSG_META_OPTIONS); + if (TheMessageStream && ThePlayerList) + { + Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + Bool f4Down = (GetAsyncKeyState(VK_F4) & 0x8000) != 0; + Bool isAltF4 = altDown && f4Down; - //This method works everywhere but isn't as clean at shutting down. - //TheGameEngine->checkAbnormalQuitting(); //old way to log disconnections for ALT-F4 - //TheGameEngine->reset(); - //TheGameEngine->setQuitting(TRUE); - //_exit(EXIT_SUCCESS); + GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + msg->appendBooleanArgument(isAltF4); + } + else + { + TheGameEngine->setQuitting(TRUE); + } } return 0; From f8e6218f2a56a76d1b15257cae622e3b16d8cecb Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 18 Mar 2026 03:16:30 +0100 Subject: [PATCH 11/16] fix upstream gamelogic --- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 237e796a808..4fa78ea3fb6 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -974,7 +974,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) { try { - tryStartNewGame(saveGame); + tryStartNewGame(loadingSaveGame); } catch (QuitGameException&) { @@ -982,7 +982,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } } -void GameLogic::tryStartNewGame( Bool saveGame ) +void GameLogic::tryStartNewGame( Bool loadingSaveGame ) { #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -3648,7 +3648,7 @@ void GameLogic::quit(Bool toDesktop, Bool force) { if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) { - if (!isLoadingGame()) + if (!isLoadingMap() && !isLoadingSave()) { ToggleQuitMenu(); return; @@ -3681,14 +3681,14 @@ void GameLogic::quit(Bool toDesktop, Bool force) if (isInMultiplayerGame()) { m_quitToDesktopAfterMatch = TRUE; - if (!isLoadingGame()) + if (!isLoadingMap() && !isLoadingSave()) { exitGame(); } } else { - if (!isLoadingGame()) + if (!isLoadingMap() && !isLoadingSave()) { clearGameData(); } From 9058ad792bd6793d5f1039fca79e9c48f09f1662 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:48:36 +0100 Subject: [PATCH 12/16] added skirmish guard back moved trystartnewgame to private --- Generals/Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h | 2 +- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index 037c621fd9d..1facfe883d2 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -153,7 +153,6 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool saveGame ); - void tryStartNewGame( Bool loadSaveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); @@ -266,6 +265,7 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + void tryStartNewGame( Bool loadSaveGame ); Bool m_quitToDesktopAfterMatch; void updateDisplayBusyState(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 4fa78ea3fb6..8c01bf1ab27 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3656,7 +3656,7 @@ void GameLogic::quit(Bool toDesktop, Bool force) } } - if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 81d44dfe150..675bc1474bb 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -158,7 +158,6 @@ class GameLogic : public SubsystemInterface, public Snapshot // super hack void startNewGame( Bool loadSaveGame ); - void tryStartNewGame( Bool loadSaveGame ); void loadMapINI( AsciiString mapName ); void updateLoadProgress( Int progress ); @@ -274,6 +273,7 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + void tryStartNewGame( Bool loadSaveGame ); Bool m_quitToDesktopAfterMatch; void updateDisplayBusyState(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 580fecaa013..c3761c9c959 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4206,7 +4206,7 @@ void GameLogic::quit(Bool toDesktop, Bool force) } } - if (isInMultiplayerGame() && TheGameInfo && !TheGameInfo->isSandbox()) + if (isInMultiplayerGame() && !isInSkirmishGame() && TheGameInfo && !TheGameInfo->isSandbox()) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); From b7ded01a672e59042c5d194ed15033e22b82298f Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:00:49 +0100 Subject: [PATCH 13/16] added Missing !getQuitting() guard in Generals WM_CLOSE fixed MSG_SELF_DESTRUCT sent for single-player loading abort --- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- Generals/Code/Main/WinMain.cpp | 6 +----- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 8c01bf1ab27..adec5e8e888 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3670,7 +3670,7 @@ void GameLogic::quit(Bool toDesktop, Bool force) setGamePaused(FALSE); - if (TheScriptEngine) + if (TheScriptEngine && !isLoadingMap() && !isLoadingSave()) { TheScriptEngine->forceUnfreezeTime(); TheScriptEngine->doUnfreezeTime(); diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 20442b5cd98..a4bbc610c9f 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -367,7 +367,7 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, // ------------------------------------------------------------------------ case WM_CLOSE: - if (TheGameEngine) + if (TheGameEngine && !TheGameEngine->getQuitting()) { if (TheMessageStream && ThePlayerList) { @@ -383,10 +383,6 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, TheGameEngine->setQuitting(TRUE); } } - else - { - _exit(EXIT_SUCCESS); - } return 0; //------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index c3761c9c959..bb66f0c1bd6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4220,7 +4220,7 @@ void GameLogic::quit(Bool toDesktop, Bool force) setGamePaused(FALSE); - if (TheScriptEngine) + if (TheScriptEngine && !isLoadingMap() && !isLoadingSave()) { TheScriptEngine->forceUnfreezeTime(); TheScriptEngine->doUnfreezeTime(); From f5133f367f238c9be5ec7f15e973aa2ec7c2b2d3 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:38:18 +0100 Subject: [PATCH 14/16] implement review feedback added comments moved QuitGameException --- Generals/Code/GameEngine/Source/Common/MessageStream.cpp | 2 +- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 8 +++++--- Generals/Code/Main/WinMain.cpp | 2 ++ .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 8 +++++--- GeneralsMD/Code/Main/WinMain.cpp | 2 ++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp index 5df63dfb0e3..875abf9e85e 100644 --- a/Generals/Code/GameEngine/Source/Common/MessageStream.cpp +++ b/Generals/Code/GameEngine/Source/Common/MessageStream.cpp @@ -380,6 +380,7 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t) CASE_LABEL(MSG_META_TOGGLE_PAUSE_ALT) CASE_LABEL(MSG_META_STEP_FRAME) CASE_LABEL(MSG_META_STEP_FRAME_ALT) + CASE_LABEL(MSG_META_DEMO_INSTANT_QUIT) #if defined(RTS_DEBUG) CASE_LABEL(MSG_META_DEMO_TOGGLE_BEHIND_BUILDINGS) @@ -389,7 +390,6 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t) CASE_LABEL(MSG_META_DEMO_LOD_INCREASE) CASE_LABEL(MSG_META_DEMO_TOGGLE_ZOOM_LOCK) CASE_LABEL(MSG_META_DEMO_PLAY_CAMEO_MOVIE) - CASE_LABEL(MSG_META_DEMO_INSTANT_QUIT) CASE_LABEL(MSG_META_DEMO_TOGGLE_SPECIAL_POWER_DELAYS) CASE_LABEL(MSG_META_DEMO_BATTLE_CRY) CASE_LABEL(MSG_META_DEMO_SWITCH_TEAMS) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index adec5e8e888..6681a30b479 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -106,6 +106,8 @@ #include "GameNetwork/LANAPICallbacks.h" #include "GameNetwork/NetworkInterface.h" +struct QuitGameException {}; + DECLARE_PERF_TIMER(SleepyMaintenance) #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -916,8 +918,6 @@ static void populateRandomStartPosition( GameInfo *game ) } } -struct QuitGameException {}; - // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -3646,8 +3646,10 @@ void GameLogic::quit(Bool toDesktop, Bool force) { if (force == FALSE) { - if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) + // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. + if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) { + // TheSuperHackers @info Skip the quit menu and fall through to send the disconnect multi-player ping. if (!isLoadingMap() && !isLoadingSave()) { ToggleQuitMenu(); diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index a4bbc610c9f..ee41808aa84 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -369,6 +369,8 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_CLOSE: if (TheGameEngine && !TheGameEngine->getQuitting()) { + // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. + // If not, we're likely in early startup/late shutdown, so we hard-quit instead. if (TheMessageStream && ThePlayerList) { Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index bb66f0c1bd6..21a3c189b06 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -113,6 +113,8 @@ #include +struct QuitGameException {}; + DECLARE_PERF_TIMER(SleepyMaintenance) #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -1053,8 +1055,6 @@ static void populateRandomStartPosition( GameInfo *game ) } } -struct QuitGameException {}; - // ------------------------------------------------------------------------------------------------ /** Update the load screen progress */ // ------------------------------------------------------------------------------------------------ @@ -4196,8 +4196,10 @@ void GameLogic::quit(Bool toDesktop, Bool force) { if (force == FALSE) { - if (TheGameEngine->isActive() && !TheInGameUI->isQuitMenuVisible()) + // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. + if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) { + // TheSuperHackers @info Skip the quit menu and fall through to send the disconnect multi-player ping. if (!isLoadingMap() && !isLoadingSave()) { ToggleQuitMenu(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index c7bec6c69e5..9b30e3c89ad 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -386,6 +386,8 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_CLOSE: if (TheGameEngine && !TheGameEngine->getQuitting()) { + // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. + // If not, we're likely in early startup/late shutdown, so we hard-quit instead. if (TheMessageStream && ThePlayerList) { Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; From 90258594b5a85df1d28bc699c716d2da20cbd6ce Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:03:24 +0100 Subject: [PATCH 15/16] small changes to comments --- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 3 ++- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 3 ++- GeneralsMD/Code/Main/WinMain.cpp | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 6681a30b479..d85d03c27ac 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3649,7 +3649,8 @@ void GameLogic::quit(Bool toDesktop, Bool force) // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) { - // TheSuperHackers @info Skip the quit menu and fall through to send the disconnect multi-player ping. + // TheSuperHackers @info For window X-button clicks (force == FALSE), show the quit menu. + // We verify we are not loading. Toggling the UI during a load screen would crash. if (!isLoadingMap() && !isLoadingSave()) { ToggleQuitMenu(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 21a3c189b06..fb542f65cd6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -4199,7 +4199,8 @@ void GameLogic::quit(Bool toDesktop, Bool force) // TheSuperHackers @info Check TheInGameUI to prevent a potential early-startup or late-shutdown crash. if (TheGameEngine->isActive() && (!TheInGameUI || !TheInGameUI->isQuitMenuVisible())) { - // TheSuperHackers @info Skip the quit menu and fall through to send the disconnect multi-player ping. + // TheSuperHackers @info For window X-button clicks (force == FALSE), show the quit menu. + // We verify we are not loading. Toggling the UI during a load screen would crash. if (!isLoadingMap() && !isLoadingSave()) { ToggleQuitMenu(); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 9b30e3c89ad..3a0a6ac7dd9 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -370,6 +370,8 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_QUERYENDSESSION: { + // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. + // If not, we're likely in early startup/late shutdown, so we hard-quit instead. if (TheMessageStream && ThePlayerList) { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); @@ -386,8 +388,6 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, case WM_CLOSE: if (TheGameEngine && !TheGameEngine->getQuitting()) { - // TheSuperHackers @info ThePlayerList initialized ensures we can safely append messages. - // If not, we're likely in early startup/late shutdown, so we hard-quit instead. if (TheMessageStream && ThePlayerList) { Bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; From 5e2f09696370961f28db6f344323bead3d122d35 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:10:27 +0100 Subject: [PATCH 16/16] fixes frameDecompress() being called on a potentially non-ready frame --- Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index a69746d773b..f0c0512a8e4 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -1106,6 +1106,10 @@ void ChallengeLoadScreen::init( GameInfo *game ) } Sleep(1); } + if (!m_videoStream->isFrameReady()) + { + return; + } m_videoStream->frameDecompress(); m_videoStream->frameRender(m_videoBuffer); if(m_videoBuffer)