From adf466c48cf69308707e57bb17f9d6de1d3bf639 Mon Sep 17 00:00:00 2001 From: bobtista Date: Tue, 3 Feb 2026 11:58:35 -0600 Subject: [PATCH 1/2] 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 3d7c95fbec5bca55f8c9e67191ecf6db8ce4e10e Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 9 Feb 2026 16:52:05 -0500 Subject: [PATCH 2/2] 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(); }