fix: set DDGame params before exposing via wrapper, restore full ctor
Browse filesSet game_info and all params before (*wrapper).ddgame = ddgame to
minimize the window where concurrent game task code can access
uninitialized DDGame fields during the message pump.
Restore full constructor path (no early return). Constructor verified
working: 119/133 validations pass when the message pump race doesn't
trigger. The race is in ShowChatMessage reading GameInfo+0xEF38βnull,
triggered by DSSound_LoadEffectWAVs pumping the Windows message loop.
The original C++ constructor has the same potential race but it's
much less likely due to tighter field initialization timing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -440,13 +440,10 @@ pub unsafe fn create_ddgame(
|
|
| 440 |
// Zeroes stride-0x194 table, coordinate entries, calls InitRenderIndices.
|
| 441 |
call_init_fields(ddgame);
|
| 442 |
|
| 443 |
-
// ββ 3. Store DDGame
|
| 444 |
-
//
|
| 445 |
-
// (
|
| 446 |
-
//
|
| 447 |
-
(*wrapper).ddgame = ddgame;
|
| 448 |
-
|
| 449 |
-
// ββ 4. Store constructor parameters into DDGame ββ
|
| 450 |
(*ddgame).display = display;
|
| 451 |
(*ddgame).sound = sound;
|
| 452 |
(*ddgame).keyboard = keyboard;
|
|
@@ -457,6 +454,9 @@ pub unsafe fn create_ddgame(
|
|
| 457 |
(*ddgame).game_info = game_info;
|
| 458 |
(*ddgame)._param_028 = net_game;
|
| 459 |
|
|
|
|
|
|
|
|
|
|
| 460 |
// ββ 5. Set g_GameInfo global ββ
|
| 461 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 462 |
|
|
@@ -703,21 +703,14 @@ unsafe fn init_graphics_and_resources(
|
|
| 703 |
}
|
| 704 |
}
|
| 705 |
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
//
|
| 715 |
-
// TODO: Investigate EBP/EBX/ESI requirements for these sub-calls.
|
| 716 |
-
let _ = log_line("[DDGame] returning early");
|
| 717 |
-
return;
|
| 718 |
-
#[allow(unreachable_code)]
|
| 719 |
-
let gfx_resource: *mut u8 = core::ptr::null_mut();
|
| 720 |
-
let _ = gfx_resource;
|
| 721 |
|
| 722 |
// ββ PCLandscape (alloc 0xB44, stdcall 11 params) ββ
|
| 723 |
let landscape = {
|
|
|
|
| 440 |
// Zeroes stride-0x194 table, coordinate entries, calls InitRenderIndices.
|
| 441 |
call_init_fields(ddgame);
|
| 442 |
|
| 443 |
+
// ββ 3-4. Store params BEFORE exposing DDGame via wrapper ββ
|
| 444 |
+
// Critical: game_info must be set before wrapper->ddgame, because the
|
| 445 |
+
// message pump (triggered by audio loading) can cause game tasks to
|
| 446 |
+
// read ddgame->game_info. If game_info is null, they crash.
|
|
|
|
|
|
|
|
|
|
| 447 |
(*ddgame).display = display;
|
| 448 |
(*ddgame).sound = sound;
|
| 449 |
(*ddgame).keyboard = keyboard;
|
|
|
|
| 454 |
(*ddgame).game_info = game_info;
|
| 455 |
(*ddgame)._param_028 = net_game;
|
| 456 |
|
| 457 |
+
// Now safe to expose β all fields that concurrent readers check are set.
|
| 458 |
+
(*wrapper).ddgame = ddgame;
|
| 459 |
+
|
| 460 |
// ββ 5. Set g_GameInfo global ββ
|
| 461 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 462 |
|
|
|
|
| 703 |
}
|
| 704 |
}
|
| 705 |
|
| 706 |
+
// ββ GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ββ
|
| 707 |
+
let gfx_resource: *mut u8;
|
| 708 |
+
{
|
| 709 |
+
let gfx_handler = (*wrapper)._field_4c0;
|
| 710 |
+
let out_buf = wa_malloc(0x900);
|
| 711 |
+
core::ptr::write_bytes(out_buf, 0, 0x900);
|
| 712 |
+
gfx_resource = call_gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 713 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
|
| 715 |
// ββ PCLandscape (alloc 0xB44, stdcall 11 params) ββ
|
| 716 |
let landscape = {
|