paavohtl Claude Opus 4.6 (1M context) commited on
Commit
57a95a3
Β·
1 Parent(s): 46b6be9

fix: set DDGame params before exposing via wrapper, restore full ctor

Browse files

Set 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* at DDGameWrapper+0x488 ──
444
- // NOTE: The original constructor stores this immediately, but sub-calls
445
- // (like GfxHandler__LoadDir) read wrapper+0x488 to access DDGame.
446
- // We must set it early like the original does.
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
- let _ = log_line("[DDGame] past audio");
707
- // ── GfxResource: usercall(ECX=gfx_handler, EAX=0x66A3C0) + 1 stack(output), RET 0x4 ──
708
- // RET 0x4 means callee cleans the stack β€” must use a naked bridge to avoid
709
- // the compiler's stack tracking being thrown off.
710
- // GfxResource + PCLandscape: skip for now, call via original code path.
711
- // The crash in GfxResource__Create is likely due to missing callee-saved
712
- // register context (EBP, EBX, ESI) that the original DDGame constructor
713
- // maintains throughout. These functions read "unaffected" registers.
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 = {