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

debug: isolate crash to InitGameState accessing uninitialized fields

Browse files

Systematic bisection reveals:
- GfxHandler, palette, FUN_570E20, color entries, GfxDir, audio init:
ALL CORRECT. Game starts with these alone (119 validations pass).
- GfxResource__Create (0x4F6300): crashes with DEP violation when
called from Rust context. Likely needs callee-saved register state
(EBP/EBX) maintained from the DDGame constructor's stack frame.
- When returning early (skipping PCLandscape+), the crash moves to
DDGame__InitGameState which reads uninitialized DDGame fields.

Key insight: The first ~50% of the constructor (GfxHandler through
audio) works correctly. The remaining sections (GfxResource, PCLandscape,
SpriteRegions, etc.) need investigation of register context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -373,6 +373,7 @@ pub fn init_constructor_addrs() {
373
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
374
  FUN_570E20_ADDR = rb(va::FUN_570E20);
375
  FUN_570A90_ADDR = rb(va::FUN_570A90);
 
376
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
377
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
378
  LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
@@ -687,12 +688,10 @@ unsafe fn init_graphics_and_resources(
687
  call_usercall_esi(wrapper, FUN_570F30_ADDR);
688
 
689
  if !(*ddgame).sound.is_null() {
690
- {
691
- // DSSound_LoadEffectWAVs: stdcall(wrapper)
692
- let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
693
- core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
694
- f(wrapper);
695
- }
696
  // DSSound_LoadAllSpeechBanks: usercall(ESI=wrapper)
697
  call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
698
  // Allocate ActiveSoundTable (0x608 bytes)
@@ -704,13 +703,21 @@ unsafe fn init_graphics_and_resources(
704
  }
705
  }
706
 
707
- // ── GfxResource (used by PCLandscape and later by gradients) ──
708
- let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
709
- core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
710
- let gfx_resource = gfx_resource_create(
711
- (*wrapper)._field_4c0,
712
- (ddgame as *mut u8).add(0x77A4), // output: pass ddgame+0x77A4 area
713
- );
 
 
 
 
 
 
 
 
714
 
715
  // ── PCLandscape (alloc 0xB44, stdcall 11 params) ──
716
  let landscape = {
@@ -929,9 +936,10 @@ unsafe fn init_graphics_and_resources(
929
  // TODO: populate coord_list from landscape data (complex loop)
930
  }
931
 
932
- // ── Weapon sprites (FUN_005717A0 Γ—2): usercall(ECX=wrapper) ──
933
- call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
934
- call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
 
935
  let _ = log_line("[DDGame] past audio init");
936
 
937
  // ── Sprite resource loading via DDGameWrapper vtable[0] ──
@@ -1190,6 +1198,27 @@ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
1190
  f(wrapper);
1191
  }
1192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1194
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1195
  #[cfg(target_arch = "x86")]
 
373
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
374
  FUN_570E20_ADDR = rb(va::FUN_570E20);
375
  FUN_570A90_ADDR = rb(va::FUN_570A90);
376
+ GFX_RESOURCE_CREATE_ADDR = rb(va::GFX_RESOURCE_CREATE);
377
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
378
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
379
  LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
 
688
  call_usercall_esi(wrapper, FUN_570F30_ADDR);
689
 
690
  if !(*ddgame).sound.is_null() {
691
+ // DSSound_LoadEffectWAVs: stdcall(wrapper)
692
+ let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
693
+ core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
694
+ f(wrapper);
 
 
695
  // DSSound_LoadAllSpeechBanks: usercall(ESI=wrapper)
696
  call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
697
  // Allocate ActiveSoundTable (0x608 bytes)
 
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 = {
 
936
  // TODO: populate coord_list from landscape data (complex loop)
937
  }
938
 
939
+ // ── Loading tick (FUN_005717A0 Γ—2): usercall(ECX=wrapper) ──
940
+ // This pumps the message loop β€” commented out to test if it causes crashes.
941
+ // call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
942
+ // call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
943
  let _ = log_line("[DDGame] past audio init");
944
 
945
  // ── Sprite resource loading via DDGameWrapper vtable[0] ──
 
1198
  f(wrapper);
1199
  }
1200
 
1201
+ /// Runtime address of GfxResource__Create_Maybe (set by init_constructor_addrs()).
1202
+ static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
1203
+
1204
+ /// Bridge to GfxResource__Create_Maybe (0x4F6300).
1205
+ /// Convention: usercall(ECX=gfx_handler, EAX=data_ptr) + 1 stack(output), RET 0x4.
1206
+ #[cfg(target_arch = "x86")]
1207
+ #[unsafe(naked)]
1208
+ unsafe extern "C" fn call_gfx_resource_create(
1209
+ _gfx_handler: *mut u8, _data_ptr: *const u8, _output: *mut u8,
1210
+ ) -> *mut u8 {
1211
+ core::arch::naked_asm!(
1212
+ "movl 4(%esp), %ecx", // ECX = gfx_handler
1213
+ "movl 8(%esp), %eax", // EAX = data_ptr
1214
+ "pushl 12(%esp)", // push output (stdcall param for callee)
1215
+ "calll *({addr})", // callee does RET 0x4, cleans our push
1216
+ "retl", // return to caller (cdecl, caller cleans 3 params)
1217
+ addr = sym GFX_RESOURCE_CREATE_ADDR,
1218
+ options(att_syntax),
1219
+ );
1220
+ }
1221
+
1222
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1223
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1224
  #[cfg(target_arch = "x86")]