You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Caball009
added
Minor
Severity: Minor < Major < Critical < Blocker
Gen
Relates to Generals
ZH
Relates to Zero Hour
Fix
Is fixing something, but is not user facing
NoRetail
This fix or change is not applicable with Retail game compatibility
labels
Apr 10, 2026
This PR fixes a bug in Pathfinder::crc() where &m_wallPieces[MAX_WALL_PIECES] (an out-of-bounds address pointing to m_numWallPieces) was being passed to xferObjectID in a loop, effectively serializing m_numWallPieces 128 times instead of each wall piece. The fix gates the behavior behind RETAIL_COMPATIBLE_CRC: the legacy path replicates the original (buggy) behavior for save/replay compatibility, while the corrected path serializes the actual m_wallPieces array via xferUser.
The verification that xferObjectID and xferInt both delegate to xferImplementation(data, sizeof(T)) confirms the static_assert(sizeof(Int) == sizeof(ObjectID)) is sufficient to guarantee identical CRC output on the compatibility path.
Confidence Score: 5/5
Safe to merge; the fix is logically sound, the compatibility path is correctly guarded, and the static_assert prevents silent breakage if type sizes ever diverge.
No P0/P1 findings. The RETAIL_COMPATIBLE_CRC path faithfully replicates the original behavior (confirmed by reading Xfer.cpp — both xferObjectID and xferInt delegate to xferImplementation with the same size), and the corrected path properly serializes the actual array. The static_assert is a well-placed defensive guard.
Fixes out-of-bounds UB in the CRC loop by gating the legacy (buggy) behavior behind RETAIL_COMPATIBLE_CRC and introducing a correct xferUser path; static_assert guards type-size equivalence.
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Pathfinder::crc(Xfer *xfer)"] --> B["xferInt(&m_numWallPieces)"]
B --> C{RETAIL_COMPATIBLE_CRC?}
C -- Yes --> D["Loop 128x: xferInt(&m_numWallPieces)\n(replicates original buggy behavior)\nstatic_assert sizeof(Int)==sizeof(ObjectID)"]
C -- No --> E["xferUser(m_wallPieces, sizeof(m_wallPieces))\n(correct: serializes all ObjectIDs)"]
D --> F["CRCDEBUG_LOG m_wallPieces"]
E --> F
F --> G["xferReal(&m_wallHeight)"]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FixIs fixing something, but is not user facingGenRelates to GeneralsMinorSeverity: Minor < Major < Critical < BlockerNoRetailThis fix or change is not applicable with Retail game compatibilityZHRelates to Zero Hour
4 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Original EA code links
https://github.com/electronicarts/CnC_Generals_Zero_Hour/blob/main/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h#L846-L847
https://github.com/electronicarts/CnC_Generals_Zero_Hour/blob/main/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp#L10395-L10398
https://github.com/electronicarts/CnC_Generals_Zero_Hour/blob/main/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h#L858-L859
https://github.com/electronicarts/CnC_Generals_Zero_Hour/blob/main/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp#L11095-L11098
GeneralsGameCode/Core/GameEngine/Include/GameLogic/AIPathfind.h
Lines 908 to 909 in 64b5eaa
GeneralsGameCode/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Lines 11341 to 11344 in 64b5eaa
This loop uses the address of the first element after the
m_numWallPiecesarray, which is effectively the same as:This should be a change that's not user facing, but I need to check some replays to verify that everything works as expected.
TODO: