Skip to content

bugfix(aigroup): Prevent game crash when a player is selected in Replay playback (2)#2711

Merged
xezon merged 4 commits into
TheSuperHackers:mainfrom
Caball009:fix_object_selection_replay_crash
May 20, 2026
Merged

bugfix(aigroup): Prevent game crash when a player is selected in Replay playback (2)#2711
xezon merged 4 commits into
TheSuperHackers:mainfrom
Caball009:fix_object_selection_replay_crash

Conversation

@Caball009
Copy link
Copy Markdown

@Caball009 Caball009 commented May 14, 2026

With the fix from #1212 AIGroupPtr currentlySelectedGroup would be accessed even if it had been destroyed and its memory deallocated. This is a use-after-free bug. It relies on the memory not being overwritten on deallocation.

If macro MEMORYPOOL_DEBUG (enabled by RTS_DEBUG) is enabled, freed memory is overwritten with a garbage value, so the game crashes when currentlySelectedGroup is dereferenced (at line 2132).

TODO:

  • Tweak TSH comment.
  • Replicate in Generals.

@Caball009 Caball009 added Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Debug Is mostly debug functionality Stability Concerns stability of the runtime Crash This is a crash, very bad labels May 14, 2026
@Caball009 Caball009 force-pushed the fix_object_selection_replay_crash branch from 29b2761 to 2909a9e Compare May 14, 2026 19:03
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

Greptile Summary

This PR fixes a use-after-free bug introduced by PR #1212, where currentlySelectedGroup could be dereferenced after its memory was already freed. The previous guard checked getCount() == 0 on the potentially-freed pointer, which was unsafe.

  • doesGroupExist added to both Generals and GeneralsMD builds: The new method does a pointer-identity scan of m_groupList without dereferencing the group, making it safe to call even when the pointer is stale.
  • Guard updated in GameLogicDispatch.cpp: The check now calls TheAI->doesGroupExist(currentlySelectedGroup) instead of accessing a member of the potentially-freed group, closing the use-after-free window.

Confidence Score: 5/5

Safe to merge — the fix correctly eliminates a use-after-free by replacing a dereference of a potentially freed pointer with a safe pointer-identity list scan.

The guard in GameLogicDispatch.cpp now calls doesGroupExist before any member access, and doesGroupExist itself only compares pointer values without dereferencing the candidate. Both Generals and GeneralsMD builds receive the matching header declaration and implementation, closing the compilation gap called out on the previous PR.

No files require special attention.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Replaces unsafe use-after-free guard (getCount() == 0) with TheAI->doesGroupExist(), preventing dereference of freed memory.
Generals/Code/GameEngine/Include/GameLogic/AI.h Adds doesGroupExist declaration to the Generals build header, needed for compilation under RETAIL_COMPATIBLE_AIGROUP.
Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp Implements doesGroupExist via a pointer-identity search of m_groupList — no dereference of the candidate pointer.
GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h Adds doesGroupExist declaration to the Zero Hour build header, mirroring the Generals change.
GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp Implements doesGroupExist for Zero Hour, identical to the Generals implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[logicMessageDispatcher called] --> B{currentlySelectedGroup != null?}
    B -- No --> Z[Continue to destroyGroup block]
    B -- Yes --> C{TheAI->doesGroupExist currentlySelectedGroup?}
    C -- No: group was freed --> R[return early — use-after-free avoided]
    C -- Yes: group is live --> D[Safe to dereference: currentlySelectedGroup->getAllIDs]
    D --> E[Deselect / reselect drawables]
    E --> Z
    Z --> F{currentlySelectedGroup != null?}
    F -- Yes --> G[TheAI->destroyGroup — group still in list]
    F -- No --> H[End]
    G --> H
Loading

Reviews (4): Last reviewed commit: "Made 'AI::doesGroupExist' member functio..." | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
Comment thread Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp Outdated
Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Makes sense. I have not thought of that.

@xezon xezon added Major Severity: Minor < Major < Critical < Blocker and removed Minor Severity: Minor < Major < Critical < Blocker labels May 15, 2026
@xezon xezon added this to the Major bug fixes milestone May 15, 2026
@Caball009
Copy link
Copy Markdown
Author

Replicated in Generals.

Comment thread GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h Outdated
Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Looking good

@Caball009 Caball009 force-pushed the fix_object_selection_replay_crash branch from 5429a2a to c5f8c47 Compare May 19, 2026 22:13
@Caball009
Copy link
Copy Markdown
Author

Rebased to include the fix for the CI Replay checker.

@xezon xezon merged commit 5852010 into TheSuperHackers:main May 20, 2026
17 checks passed
@Caball009 Caball009 deleted the fix_object_selection_replay_crash branch May 20, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Crash This is a crash, very bad Debug Is mostly debug functionality Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Stability Concerns stability of the runtime ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants