Skip to content

bugfix(contain): Prevent riders from being added to destroyed container object when Reinforcement Pad is destroyed before Troop Crawler drop#2746

Merged
xezon merged 9 commits into
TheSuperHackers:mainfrom
Caball009:fix_bug_addToContain
Jun 5, 2026
Merged

bugfix(contain): Prevent riders from being added to destroyed container object when Reinforcement Pad is destroyed before Troop Crawler drop#2746
xezon merged 9 commits into
TheSuperHackers:mainfrom
Caball009:fix_bug_addToContain

Conversation

@Caball009
Copy link
Copy Markdown

@Caball009 Caball009 commented May 25, 2026

When a reinforcement plane with an Infantry General Troop Crawler is destroyed en route, the plane and vehicle are destroyed, but the infantry spawned and remains in an invalid state (see issue for more details).

Beside being a gameplay and logic bug, there's also another issue because the infantry objects will hold on the container pointer even though that object has been destroyed. This can lead to use-after-free bugs and a crash (see related PR).

This PR makes 4 changes:

  1. Objects that are spawned in such a state are now visible should now trigger a debug assertion.
  2. Added code to prevent the creation of the transport payload (e.g. the Mini-Gunners) if the container object is destroyed (non-retail only).
  3. Added an early return to OpenContain::addToContain to prevent units from being added to a destroyed object (non-retail only).
  4. Added debug assertions for dead container objects or occupants.

TODO:

  • Replicate in Generals.

@Caball009 Caball009 added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Stability Concerns stability of the runtime NoRetail This fix or change is not applicable with Retail game compatibility labels May 25, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 25, 2026

Greptile Summary

This PR guards against use-after-free and invalid-state bugs that arise when a reinforcement plane (carrying a Troop Crawler with infantry payload) is destroyed en route, leaving spawned units holding a dangling container pointer. The fix adds debug assertions and non-retail early-return guards in OpenContain::addToContain and TransportContain::update to prevent objects from being added to or created inside a destroyed container.

  • OpenContain (both copies): A DEBUG_ASSERTCRASH is added in addOrRemoveObjFromWorld for the containment path, and an #if !RETAIL_COMPATIBLE_CRC-guarded early return (with DEBUG_CRASH) is added in addToContain when the container is already destroyed. The pre-existing rider-destroyed check is also wrapped with a DEBUG_CRASH message.
  • TransportContain (both copies): createPayload() is conditionally skipped in the non-retail path when the container is destroyed, preventing the spawned payload from ending up in an invalid state.

Confidence Score: 5/5

Safe to merge — all changes are guarded to non-retail builds or are no-ops in release, and the core logic correctly prevents invalid containment operations.

The guards in OpenContain are correctly scoped and the assertions use macros that compile away in retail builds. The only rough edge is in TransportContain: skipping createPayload() without setting m_payloadCreated = TRUE causes redundant isDestroyed() checks on subsequent update frames, but the object is being destroyed and the repeated check is harmless in practice.

Both copies of TransportContain.cpp — the skipped-payload path leaves m_payloadCreated as FALSE.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Adds a DEBUG_ASSERTCRASH in addOrRemoveObjFromWorld for containment of dead/destroyed objects, and a #if !RETAIL_COMPATIBLE_CRC-guarded early return with DEBUG_CRASH in addToContain when the container is destroyed. Logic is sound.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Mirror of the Generals/ change: same DEBUG_ASSERTCRASH in addOrRemoveObjFromWorld and guarded early return in addToContain. Logic mirrors the Generals copy correctly.
Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp Guards createPayload() behind a destroyed-container check in the non-retail path, but when the container is destroyed and payload creation is skipped, m_payloadCreated is never set to TRUE, causing the check to repeat every update cycle.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp Same as the Generals/ copy — m_payloadCreated is not set to TRUE when payload creation is skipped for a destroyed container, repeating the check on every subsequent update.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[TransportContain::update] --> B{m_payloadCreated == FALSE?}
    B -- No --> Z[Continue update]
    B -- Yes --> C{RETAIL_COMPATIBLE_CRC?}
    C -- Yes --> D[createPayload]
    C -- No --> E{getObject isDestroyed?}
    E -- No --> D
    E -- Yes --> F[Skip payload creation\nm_payloadCreated stays FALSE]
    D --> G[m_payloadCreated = TRUE]
    G --> Z
    F --> Z

    H[OpenContain::addToContain] --> I{rider == nullptr?}
    I -- Yes --> Return1[return]
    I -- No --> J{RETAIL_COMPATIBLE_CRC?}
    J -- No --> K{getObject isDestroyed?}
    K -- Yes --> L[DEBUG_CRASH + return]
    K -- No --> M{rider isDestroyed?}
    J -- Yes --> M
    M -- Yes --> N[DEBUG_CRASH + return]
    M -- No --> O[Proceed with containment]

    P[OpenContain::addOrRemoveObjFromWorld] --> Q{add == TRUE?}
    Q -- Yes --> R[Register + show object]
    Q -- No --> S[DEBUG_ASSERTCRASH container not dead/destroyed]
    S --> T[Hide + unregister object]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp:370-375
`m_payloadCreated` is never set to `TRUE` when payload creation is skipped because the container is destroyed. Since `createPayload()` is the only place that sets `m_payloadCreated = TRUE`, this flag stays `FALSE` for the lifetime of the destroyed object. Every subsequent `update()` call re-enters the branch, evaluates `isDestroyed()`, and silently skips again. Setting `m_payloadCreated = TRUE` when skipping is the cleaner fix — the flag's purpose is "has payload been handled", not "was payload successfully created".

```suggestion
		// TheSuperHackers @bugfix Caball009 25/05/2026 Don't create payload
		// for destroyed object to avoid leaving the payload in an invalid state.
		if (!getObject()->isDestroyed())
		{
			createPayload();
		}
		else
		{
			m_payloadCreated = TRUE;
		}
```

### Issue 2 of 2
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp:477-482
Same issue as in the `Generals/` copy: `m_payloadCreated` is never set to `TRUE` when payload creation is skipped because the container is destroyed. Every subsequent `update()` call will re-enter the branch and repeat the `isDestroyed()` check. Setting `m_payloadCreated = TRUE` in the skipped path avoids the repeated re-evaluation.

```suggestion
		// TheSuperHackers @bugfix Caball009 25/05/2026 Don't create payload
		// for destroyed object to avoid leaving the payload in an invalid state.
		if (!getObject()->isDestroyed())
		{
			createPayload();
		}
		else
		{
			m_payloadCreated = TRUE;
		}
```

Reviews (9): Last reviewed commit: "Replicated in Generals." | Re-trigger Greptile

Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/HelixContain.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/GarrisonContain.cpp Outdated
@Caball009 Caball009 changed the title fix(contain): Prevent objects from being added to destroyed container object bugfix(contain): Prevent objects from being added to destroyed container object May 25, 2026
@Caball009 Caball009 force-pushed the fix_bug_addToContain branch from 0d39be3 to 735d827 Compare May 26, 2026 14:25
@Caball009 Caball009 force-pushed the fix_bug_addToContain branch from f2480a5 to 6544358 Compare May 26, 2026 15:09
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp Outdated
@Caball009 Caball009 force-pushed the fix_bug_addToContain branch from ec82747 to cf0daed Compare June 4, 2026 15:12
@xezon xezon added this to the Stability fixes milestone Jun 4, 2026
@Caball009
Copy link
Copy Markdown
Author

Replicated in Generals.

@xezon xezon changed the title bugfix(contain): Prevent objects from being added to destroyed container object bugfix(contain): Prevent riders from being added to destroyed container object when Reinforcement Pad is destroyed before Troop Crawler drop Jun 5, 2026
@xezon xezon merged commit f334383 into TheSuperHackers:main Jun 5, 2026
17 checks passed
@Caball009 Caball009 deleted the fix_bug_addToContain branch June 5, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility Stability Concerns stability of the runtime ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

China Assault Troop Crawler can become a firing ghost unit after Reinforcement Pad drop

2 participants