From de54dab72b5610e33360d0d35aabc1b568fe39be Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 3 Feb 2026 11:58:35 -0600 Subject: [PATCH 1/5] fix(particle): Fix use-after-free crash in headless replay by using ParticleSystemManager::update() instead of reset() --- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index d59578dff2c..ae101d790f4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -786,11 +786,16 @@ void GameClient::updateHeadless() // TheSuperHackers @info helmutbuhler 03/05/2025 // When we play a replay back in headless mode, we want to skip the update of GameClient // because it's not necessary for CRC checking. - // But we do reset the particles. The problem is that particles can be generated during + // But we do update the particles. The problem is that particles can be generated during // GameLogic and are only cleaned up during rendering. If we don't clean this up here, // the particles accumulate and slow things down a lot and can even cause a crash on // longer replays. - TheParticleSystemManager->reset(); + // TheSuperHackers @fix bobtista 02/02/2026 Use update() instead of reset() to avoid + // use-after-free crash. reset() deletes all particle systems immediately, but DrawModules + // (W3DTruckDraw, W3DTankDraw, etc.) hold raw pointers to particle systems. When those + // DrawModules are later destroyed during game shutdown, they crash accessing freed memory. + // update() only cleans up finished particle systems, leaving active ones intact. + TheParticleSystemManager->update(); } /** ----------------------------------------------------------------------------------------------- From 3543c67e6c48db688b2fa1fb6082da033e210173 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 9 Feb 2026 16:52:05 -0500 Subject: [PATCH 2/5] Simplify particle system update comment --- .../GameEngine/Source/GameClient/GameClient.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index ae101d790f4..5e6525ad569 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -783,18 +783,9 @@ void GameClient::step() void GameClient::updateHeadless() { - // TheSuperHackers @info helmutbuhler 03/05/2025 - // When we play a replay back in headless mode, we want to skip the update of GameClient - // because it's not necessary for CRC checking. - // But we do update the particles. The problem is that particles can be generated during - // GameLogic and are only cleaned up during rendering. If we don't clean this up here, - // the particles accumulate and slow things down a lot and can even cause a crash on - // longer replays. - // TheSuperHackers @fix bobtista 02/02/2026 Use update() instead of reset() to avoid - // use-after-free crash. reset() deletes all particle systems immediately, but DrawModules - // (W3DTruckDraw, W3DTankDraw, etc.) hold raw pointers to particle systems. When those - // DrawModules are later destroyed during game shutdown, they crash accessing freed memory. - // update() only cleans up finished particle systems, leaving active ones intact. + // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 + // Update particles to prevent accumulation in headless mode. update() has slightly more + // CPU overhead than reset() but is semantically correct - particles finish naturally. TheParticleSystemManager->update(); } From 2db9f12b4ddecd6c670dd8c5ac823027a74ed648 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 24 Feb 2026 10:01:26 -0500 Subject: [PATCH 3/5] nit: formatting conditional to use braces --- Generals/Code/Main/WinMain.cpp | 2 ++ GeneralsMD/Code/Main/WinMain.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 1b502b2d90f..61f8bcae43f 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -826,7 +826,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #endif // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) + { return exitcode; + } // save our application instance for future use ApplicationHInstance = hInstance; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index b1912b97fe9..435d8daf850 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -873,7 +873,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) + { return exitcode; + } // save our application instance for future use ApplicationHInstance = hInstance; From cda636698a36fe539472181de1b6ccbf51f67673 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 24 Feb 2026 10:08:27 -0500 Subject: [PATCH 4/5] fix(GeneralsMD): Revise headless particle update comment and use update() over reset() --- GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 5e6525ad569..5682dc22699 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -784,8 +784,9 @@ void GameClient::step() void GameClient::updateHeadless() { // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 - // Update particles to prevent accumulation in headless mode. update() has slightly more - // CPU overhead than reset() but is semantically correct - particles finish naturally. + // Update particles to prevent accumulation in headless mode. Particles are generated + // during GameLogic and only cleaned up during rendering. update() lets particles finish + // their lifecycle naturally instead of abruptly removing them with reset(). TheParticleSystemManager->update(); } From d96eeafb041739f38d7bb2a866bfc0a25f6bedb6 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 24 Feb 2026 10:08:33 -0500 Subject: [PATCH 5/5] fix(Generals): Revise headless particle update comment and use update() over reset() --- .../GameEngine/Source/GameClient/GameClient.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 78f6f63984b..45d980a42ea 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -745,14 +745,11 @@ void GameClient::step() void GameClient::updateHeadless() { - // TheSuperHackers @info helmutbuhler 03/05/2025 - // When we play a replay back in headless mode, we want to skip the update of GameClient - // because it's not necessary for CRC checking. - // But we do reset the particles. The problem is that particles can be generated during - // GameLogic and are only cleaned up during rendering. If we don't clean this up here, - // the particles accumulate and slow things down a lot and can even cause a crash on - // longer replays. - TheParticleSystemManager->reset(); + // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 + // Update particles to prevent accumulation in headless mode. Particles are generated + // during GameLogic and only cleaned up during rendering. update() lets particles finish + // their lifecycle naturally instead of abruptly removing them with reset(). + TheParticleSystemManager->update(); } /** -----------------------------------------------------------------------------------------------