Prerequisites
Game Version
- Command & Conquer Generals: Zero Hour
Operating System
Bug Description
During a skirmish against the AI (superweapons enabled), the game crashes with SIGSEGV (null pointer dereference) roughly 24 minutes in. The crash is triggered by the skirmish AI script action SKIRMISH_FIRE_SPECIAL_POWER_AT_MOST_COST firing a special power at a missile-launcher building (GLA SCUD Storm) that is still under construction.
Stack (macOS arm64, GeneralsXZH Beta 18):
0 MissileLauncherBuildingUpdate::initiateIntentToDoSpecialPower + 4 (MissileLauncherBuildingUpdate.cpp:213)
1 SpecialPowerModule::initiateIntentToDoSpecialPower + 184 (SpecialPowerModule.cpp:443)
2 SpecialPowerModule::doSpecialPowerAtLocation + 68 (SpecialPowerModule.cpp:725)
3 OCLSpecialPower::doSpecialPowerAtLocation + 192 (OCLSpecialPower.cpp:177)
4 ScriptActions::doSkirmishFireSpecialPowerAtMostCost + 556 (ScriptActions.cpp:4291)
5 ScriptEngine::executeActions + 724 (ScriptEngine.cpp:7633)
...
Faulting instruction in the shipped binary (registers: x0 = 0, far = 0):
10021d520: ldr x0, [x0, #0x8] ; x0 = m_specialPowerModule -> null
10021d524: ldr x8, [x0] ; <-- crash: vtable load from null
10021d528: ldr x8, [x8, #0x28]
10021d52c: blr x8 ; virtual call getSpecialPowerTemplate()
Reproduction Steps
- Start a skirmish versus AI with Superweapons enabled.
- Play until the AI fires its SCUD Storm (GLA AI) while the building is still under construction or newly rebuilt.
- The game segfaults when
initiateIntentToDoSpecialPower runs with m_specialPowerModule == nullptr.
The timing is nondeterministic because the AI script fires the power on its own schedule; the attached crash happened ~24 min into a game.
Root Cause
MissileLauncherBuildingUpdate::update() skips the lazy init of m_specialPowerModule while the building is UNDER_CONSTRUCTION:
if( getObject()->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) )
return UPDATE_SLEEP_NONE; // m_specialPowerModule stays null
initiateIntentToDoSpecialPower() then dereferences m_specialPowerModule unconditionally in release builds.
- A null check exists, but only inside
#if RETAIL_COMPATIBLE_CRC (added by TheSuperHackers/Mauller, 29/06/2025) — and Core/GameEngine/Include/Common/GameDefines.h:97 hardcodes #define RETAIL_COMPATIBLE_CRC (0), so the fix is dead code in every build and the crash still ships (confirmed by disassembling the Beta 18 binary, above).
- As a secondary issue, the existing guarded fix itself calls
getSpecialPowerModule(...)->setReadyFrame(...) without checking the returned pointer, so it can crash the same way if the module lookup fails.
Proposed Fix
Make the null check unconditional and guard the getSpecialPowerModule result (also makes the game resilient if the module is absent for any other reason):
Bool MissileLauncherBuildingUpdate::initiateIntentToDoSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions )
{
-#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @bugfix Mauller 29/06/2025 prevent a game crash when told to launch before ready to do so.
+ // Note: RETAIL_COMPATIBLE_CRC is hardcoded to 0 (GameDefines.h), so the guard was compiled out and the
+ // crash still occurs in release builds. The null check must be unconditional.
if (!m_specialPowerModule) {
Object* us = getObject();
- us->getSpecialPowerModule(specialPowerTemplate)->setReadyFrame(0xFFFFFFFF);
+ SpecialPowerModuleInterface* spm = us->getSpecialPowerModule(specialPowerTemplate);
+ if (spm)
+ spm->setReadyFrame(0xFFFFFFFF);
return FALSE;
}
-#endif
if( m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate )
Testing
-
Fixed build compiled locally on macOS (Apple Silicon, macos-vulkan preset). Disassembly of the built binary shows the null check is now present where the shipped build had none:
Shipped (Beta 18) — crashes:
ldr x0, [x0, #0x8] ; m_specialPowerModule
ldr x8, [x0] ; crash: no branch, vtable load from null
Fixed build:
ldr x0, [x0, #0x40] ; m_specialPowerModule
cbz x0, <null path> ; null check
... ; getSpecialPowerModule() -> cbz -> setReadyFrame(0xFFFFFFFF) -> return FALSE
-
Boot smoke test passed: the fixed binary launches, renders (DXVK frames active), and exits cleanly (GameMain() returned with code 0). No regression.
-
The crash itself is AI-timing dependent, so there is no scripted repro; the fix eliminates the only unchecked null dereference on this code path.
Crash report
~/Library/Logs/DiagnosticReports/GeneralsXZH-2026-09-05-222444.ips (available on request)
Prerequisites
Game Version
Operating System
Bug Description
During a skirmish against the AI (superweapons enabled), the game crashes with SIGSEGV (null pointer dereference) roughly 24 minutes in. The crash is triggered by the skirmish AI script action
SKIRMISH_FIRE_SPECIAL_POWER_AT_MOST_COSTfiring a special power at a missile-launcher building (GLA SCUD Storm) that is still under construction.Stack (macOS arm64, GeneralsXZH Beta 18):
Faulting instruction in the shipped binary (registers:
x0 = 0,far = 0):Reproduction Steps
initiateIntentToDoSpecialPowerruns withm_specialPowerModule == nullptr.The timing is nondeterministic because the AI script fires the power on its own schedule; the attached crash happened ~24 min into a game.
Root Cause
MissileLauncherBuildingUpdate::update()skips the lazy init ofm_specialPowerModulewhile the building isUNDER_CONSTRUCTION:initiateIntentToDoSpecialPower()then dereferencesm_specialPowerModuleunconditionally in release builds.#if RETAIL_COMPATIBLE_CRC(added by TheSuperHackers/Mauller, 29/06/2025) — andCore/GameEngine/Include/Common/GameDefines.h:97hardcodes#define RETAIL_COMPATIBLE_CRC (0), so the fix is dead code in every build and the crash still ships (confirmed by disassembling the Beta 18 binary, above).getSpecialPowerModule(...)->setReadyFrame(...)without checking the returned pointer, so it can crash the same way if the module lookup fails.Proposed Fix
Make the null check unconditional and guard the
getSpecialPowerModuleresult (also makes the game resilient if the module is absent for any other reason):Bool MissileLauncherBuildingUpdate::initiateIntentToDoSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions ) { -#if RETAIL_COMPATIBLE_CRC // TheSuperHackers @bugfix Mauller 29/06/2025 prevent a game crash when told to launch before ready to do so. + // Note: RETAIL_COMPATIBLE_CRC is hardcoded to 0 (GameDefines.h), so the guard was compiled out and the + // crash still occurs in release builds. The null check must be unconditional. if (!m_specialPowerModule) { Object* us = getObject(); - us->getSpecialPowerModule(specialPowerTemplate)->setReadyFrame(0xFFFFFFFF); + SpecialPowerModuleInterface* spm = us->getSpecialPowerModule(specialPowerTemplate); + if (spm) + spm->setReadyFrame(0xFFFFFFFF); return FALSE; } -#endif if( m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate )Testing
Fixed build compiled locally on macOS (Apple Silicon,
macos-vulkanpreset). Disassembly of the built binary shows the null check is now present where the shipped build had none:Shipped (Beta 18) — crashes:
Fixed build:
Boot smoke test passed: the fixed binary launches, renders (DXVK frames active), and exits cleanly (
GameMain() returned with code 0). No regression.The crash itself is AI-timing dependent, so there is no scripted repro; the fix eliminates the only unchecked null dereference on this code path.
Crash report
~/Library/Logs/DiagnosticReports/GeneralsXZH-2026-09-05-222444.ips(available on request)