debug: isolate crash to InitGameState accessing uninitialized fields
Browse filesSystematic 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>
|
@@ -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 |
-
|
| 692 |
-
|
| 693 |
-
|
| 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 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 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 |
-
// ββ
|
| 933 |
-
|
| 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")]
|