From 2909a9e6c5ee751dd6c3604132b75ed288aa5917 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 14 May 2026 20:53:29 +0200 Subject: [PATCH 1/4] bugfix(aigroup): Prevent game crash when a player is selected in Replay playback. --- .../Source/GameLogic/System/GameLogicDispatch.cpp | 7 +++---- GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h | 1 + GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c002ff8f239..b9406e5c534 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -2120,10 +2120,9 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } #if RETAIL_COMPATIBLE_AIGROUP - // TheSuperHackers @bugfix xezon 28/06/2025 This hack avoids crashing when players are selected during Replay playback. - // It can read data from an already deleted AIGroup and return this function when its member size is 0, signifying that - // it is indeed deleted. - if (currentlySelectedGroup && currentlySelectedGroup->getCount() == 0) + // TheSuperHackers @bugfix xezon/Caball009 14/05/2026 This hack avoids crashing when players are selected during Replay playback. + // The current AI group may have been destroyed, and its memory deallocated, in which case it shouldn't be used. + if (currentlySelectedGroup && !TheAI->doesGroupExist(currentlySelectedGroup)) return; #endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h index 90c3dd32785..ca6560aa7d3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h @@ -279,6 +279,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID + Bool doesGroupExist(AIGroup* group); ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 9959cb42546..3b6c98154c2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -497,6 +497,11 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } +Bool AI::doesGroupExist(AIGroup* group) +{ + return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); +} + //-------------------------------------------------------------------------------------------------------- /** * Get the next formation id. From e1d7f34f3ea2033e9e714f3d92f2d51d909370bd Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 15 May 2026 19:39:33 +0200 Subject: [PATCH 2/4] Tweaked TSH comment. --- Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index b9406e5c534..fa9d2292f7e 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -2120,7 +2120,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } #if RETAIL_COMPATIBLE_AIGROUP - // TheSuperHackers @bugfix xezon/Caball009 14/05/2026 This hack avoids crashing when players are selected during Replay playback. + // TheSuperHackers @bugfix xezon/Caball009 14/05/2026 This fix avoids crashing when players are selected during Replay playback. // The current AI group may have been destroyed, and its memory deallocated, in which case it shouldn't be used. if (currentlySelectedGroup && !TheAI->doesGroupExist(currentlySelectedGroup)) return; From a03c2757ee01a8b2f33472a9a0750529d9987da7 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 15 May 2026 19:40:21 +0200 Subject: [PATCH 3/4] Replicated in Generals. --- Generals/Code/GameEngine/Include/GameLogic/AI.h | 1 + Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Generals/Code/GameEngine/Include/GameLogic/AI.h b/Generals/Code/GameEngine/Include/GameLogic/AI.h index ff4dc881042..826e6f3fe88 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AI.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AI.h @@ -273,6 +273,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID + Bool doesGroupExist(AIGroup* group); ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index fb576b640fd..28b6db6fc18 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -494,6 +494,11 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } +Bool AI::doesGroupExist(AIGroup* group) +{ + return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); +} + //-------------------------------------------------------------------------------------------------------- /** * Get the next formation id. From 5429a2a35edb28b360d3cb9fa9c663f9efc3e3f4 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 17 May 2026 16:34:50 +0200 Subject: [PATCH 4/4] Made 'AI::doesGroupExist' member function const. --- Generals/Code/GameEngine/Include/GameLogic/AI.h | 2 +- Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 2 +- GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h | 2 +- GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/AI.h b/Generals/Code/GameEngine/Include/GameLogic/AI.h index 826e6f3fe88..ec72bc9f6d4 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AI.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AI.h @@ -273,7 +273,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID - Bool doesGroupExist(AIGroup* group); ///< return whether the given AI Group exists, i.e. is part of the group list + Bool doesGroupExist(AIGroup* group) const; ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 28b6db6fc18..f3b2f362db5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -494,7 +494,7 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } -Bool AI::doesGroupExist(AIGroup* group) +Bool AI::doesGroupExist(AIGroup* group) const { return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h index ca6560aa7d3..3b46117adee 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h @@ -279,7 +279,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID - Bool doesGroupExist(AIGroup* group); ///< return whether the given AI Group exists, i.e. is part of the group list + Bool doesGroupExist(AIGroup* group) const; ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 3b6c98154c2..ff0d155d9f2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -497,7 +497,7 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } -Bool AI::doesGroupExist(AIGroup* group) +Bool AI::doesGroupExist(AIGroup* group) const { return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); }