debug: isolate call_gfx_resource_create as stack corruption source
Browse filesBisection confirms:
- GfxHandler + palette + color entries + audio: OK (returns cleanly)
- Adding GfxResource__Create_Maybe: DEP crash on return
- GfxResource naked bridge and inline asm bridge both fail
- PCLandscape, TSM, SpriteRegions: all verified OK independently
Next: investigate why GfxResource_Create corrupts the stack.
The sub esp/call/RET 0x4 pattern should be balanced but isn't.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -647,6 +647,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 647 |
f((*ddgame).display, 1);
|
| 648 |
}
|
| 649 |
|
|
|
|
| 650 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 651 |
if (*wrapper).gfx_mode != 0 {
|
| 652 |
// GfxResource__Create: usercall(ECX=gfx_handler, EAX=0x66A3B4) + 1 stack, RET 0x4
|
|
@@ -668,11 +669,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 668 |
release(res);
|
| 669 |
}
|
| 670 |
} else {
|
| 671 |
-
// GFX_HANDLER_LOAD_SPRITES:
|
| 672 |
-
let gi = (*ddgame).game_info as *const u8;
|
| 673 |
-
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 674 |
-
core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
|
| 675 |
-
f(wrapper, (ddgame as *mut u8).add(0x7308), *(gi.add(0xF374) as *const u32), 0);
|
| 676 |
}
|
| 677 |
|
| 678 |
let _ = crate::log::log_line("[DDGame] past color entries");
|
|
@@ -736,7 +733,11 @@ unsafe fn init_graphics_and_resources(
|
|
| 736 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 737 |
|
| 738 |
// ββ GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ββ
|
| 739 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
{
|
| 741 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 742 |
let out_buf = wa_malloc(0x900);
|
|
@@ -746,6 +747,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 746 |
gfx_resource = call_gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 747 |
let _ = crate::log::log_line(&format!("[DDGame] GfxResource=0x{:08X}", gfx_resource as u32));
|
| 748 |
}
|
|
|
|
| 749 |
|
| 750 |
// ββ PCLandscape (alloc 0xB44, stdcall 11 params) ββ
|
| 751 |
let landscape = {
|
|
@@ -833,6 +835,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 833 |
(0x480, 0x2D, 0x08, 0x2D, 0x07, 0x2E, 0x07, gr),
|
| 834 |
];
|
| 835 |
|
|
|
|
|
|
|
| 836 |
for &(offset, ecx, edx, p2, p3, p4, p5, p6) in ®ions {
|
| 837 |
let alloc = wa_malloc(0x9C);
|
| 838 |
core::ptr::write_bytes(alloc, 0, 0x9C);
|
|
@@ -843,6 +847,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 843 |
};
|
| 844 |
*((ddgame as *mut u8).add(offset as usize) as *mut *mut u8) = result;
|
| 845 |
}
|
|
|
|
| 846 |
}
|
| 847 |
|
| 848 |
// ββ Landscape-derived value at DDGame+0x468 ββ
|
|
@@ -1222,19 +1227,23 @@ static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
|
|
| 1222 |
/// Bridge to GfxResource__Create_Maybe (0x4F6300).
|
| 1223 |
/// Convention: usercall(ECX=gfx_handler, EAX=data_ptr) + 1 stack(output), RET 0x4.
|
| 1224 |
#[cfg(target_arch = "x86")]
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
_gfx_handler: *mut u8, _data_ptr: *const u8, _output: *mut u8,
|
| 1228 |
) -> *mut u8 {
|
| 1229 |
-
|
| 1230 |
-
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
"
|
| 1234 |
-
"
|
| 1235 |
-
|
| 1236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1237 |
);
|
|
|
|
| 1238 |
}
|
| 1239 |
|
| 1240 |
/// Runtime address of GfxDir__FindEntry (set by init_constructor_addrs()).
|
|
@@ -1328,26 +1337,24 @@ unsafe extern "C" fn call_sprite_region_ctor(
|
|
| 1328 |
) -> *mut u8 {
|
| 1329 |
// Stack on entry: [ret] [this] [ecx] [edx] [p2] [p3] [p4] [p5] [p6]
|
| 1330 |
// Need: ECX=ecx, EDX=edx, push p6 p5 p4 p3 p2 this, call
|
|
|
|
|
|
|
| 1331 |
core::arch::naked_asm!(
|
|
|
|
| 1332 |
"pushl %ebx",
|
| 1333 |
-
// Load ECX and EDX
|
| 1334 |
-
"movl
|
| 1335 |
-
"movl
|
| 1336 |
-
// Push stack params in reverse: p6, p5, p4, p3, p2, this
|
| 1337 |
-
"
|
| 1338 |
-
"pushl %
|
| 1339 |
-
"
|
| 1340 |
-
"pushl %
|
| 1341 |
-
"
|
| 1342 |
-
"pushl %
|
| 1343 |
-
"movl 36(%esp), %eax", // p3 (shifted by 12)
|
| 1344 |
-
"pushl %eax",
|
| 1345 |
-
"movl 36(%esp), %eax", // p2 (shifted by 16)
|
| 1346 |
-
"pushl %eax",
|
| 1347 |
-
"movl 28(%esp), %eax", // this (shifted by 20)
|
| 1348 |
-
"pushl %eax",
|
| 1349 |
"calll *({addr})", // fastcall, callee cleans 6Γ4=24 bytes
|
| 1350 |
"popl %ebx",
|
|
|
|
| 1351 |
"retl",
|
| 1352 |
addr = sym SPRITE_REGION_CTOR_ADDR,
|
| 1353 |
options(att_syntax),
|
|
|
|
| 647 |
f((*ddgame).display, 1);
|
| 648 |
}
|
| 649 |
|
| 650 |
+
let _ = crate::log::log_line(&format!("[DDGame] gfx_mode={}", (*wrapper).gfx_mode));
|
| 651 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 652 |
if (*wrapper).gfx_mode != 0 {
|
| 653 |
// GfxResource__Create: usercall(ECX=gfx_handler, EAX=0x66A3B4) + 1 stack, RET 0x4
|
|
|
|
| 669 |
release(res);
|
| 670 |
}
|
| 671 |
} else {
|
| 672 |
+
// GFX_HANDLER_LOAD_SPRITES: SKIP for testing (gfx_mode==0 path)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
}
|
| 674 |
|
| 675 |
let _ = crate::log::log_line("[DDGame] past color entries");
|
|
|
|
| 733 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 734 |
|
| 735 |
// ββ GfxResource: thiscall(ECX=gfx_handler) + EAX=name + 1 stack(output), RET 0x4 ββ
|
| 736 |
+
// GfxResource: skip for now (stack corruption from bridge). PCLandscape handles null.
|
| 737 |
+
let gfx_resource: *mut u8 = core::ptr::null_mut();
|
| 738 |
+
#[allow(unused_assignments)]
|
| 739 |
+
if false {
|
| 740 |
+
let mut gfx_resource = gfx_resource;
|
| 741 |
{
|
| 742 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 743 |
let out_buf = wa_malloc(0x900);
|
|
|
|
| 747 |
gfx_resource = call_gfx_resource_create(gfx_handler, rb(0x66A3C0) as *const u8, out_buf);
|
| 748 |
let _ = crate::log::log_line(&format!("[DDGame] GfxResource=0x{:08X}", gfx_resource as u32));
|
| 749 |
}
|
| 750 |
+
}
|
| 751 |
|
| 752 |
// ββ PCLandscape (alloc 0xB44, stdcall 11 params) ββ
|
| 753 |
let landscape = {
|
|
|
|
| 835 |
(0x480, 0x2D, 0x08, 0x2D, 0x07, 0x2E, 0x07, gr),
|
| 836 |
];
|
| 837 |
|
| 838 |
+
// SKIP: testing if SpriteRegion bridge corrupts stack
|
| 839 |
+
if false {
|
| 840 |
for &(offset, ecx, edx, p2, p3, p4, p5, p6) in ®ions {
|
| 841 |
let alloc = wa_malloc(0x9C);
|
| 842 |
core::ptr::write_bytes(alloc, 0, 0x9C);
|
|
|
|
| 847 |
};
|
| 848 |
*((ddgame as *mut u8).add(offset as usize) as *mut *mut u8) = result;
|
| 849 |
}
|
| 850 |
+
} // if false
|
| 851 |
}
|
| 852 |
|
| 853 |
// ββ Landscape-derived value at DDGame+0x468 ββ
|
|
|
|
| 1227 |
/// Bridge to GfxResource__Create_Maybe (0x4F6300).
|
| 1228 |
/// Convention: usercall(ECX=gfx_handler, EAX=data_ptr) + 1 stack(output), RET 0x4.
|
| 1229 |
#[cfg(target_arch = "x86")]
|
| 1230 |
+
unsafe fn call_gfx_resource_create(
|
| 1231 |
+
gfx_handler: *mut u8, data_ptr: *const u8, output: *mut u8,
|
|
|
|
| 1232 |
) -> *mut u8 {
|
| 1233 |
+
let result: *mut u8;
|
| 1234 |
+
// Manual stack: sub 4 to make room, write output, call. Callee RET 0x4 cleans it.
|
| 1235 |
+
// Use sub/mov instead of push to avoid compiler stack tracking mismatch.
|
| 1236 |
+
core::arch::asm!(
|
| 1237 |
+
"sub esp, 4",
|
| 1238 |
+
"mov [esp], {output}",
|
| 1239 |
+
"call {addr}",
|
| 1240 |
+
output = in(reg) output,
|
| 1241 |
+
addr = in(reg) GFX_RESOURCE_CREATE_ADDR,
|
| 1242 |
+
inlateout("eax") data_ptr => result,
|
| 1243 |
+
in("ecx") gfx_handler,
|
| 1244 |
+
out("edx") _,
|
| 1245 |
);
|
| 1246 |
+
result
|
| 1247 |
}
|
| 1248 |
|
| 1249 |
/// Runtime address of GfxDir__FindEntry (set by init_constructor_addrs()).
|
|
|
|
| 1337 |
) -> *mut u8 {
|
| 1338 |
// Stack on entry: [ret] [this] [ecx] [edx] [p2] [p3] [p4] [p5] [p6]
|
| 1339 |
// Need: ECX=ecx, EDX=edx, push p6 p5 p4 p3 p2 this, call
|
| 1340 |
+
// SpriteRegion__Constructor: fastcall(ECX, EDX) + 6 stack, RET 0x18
|
| 1341 |
+
// Params: (this, ecx, edx, p2, p3, p4, p5, p6)
|
| 1342 |
core::arch::naked_asm!(
|
| 1343 |
+
"pushl %ebp",
|
| 1344 |
"pushl %ebx",
|
| 1345 |
+
// Load ECX and EDX (shifted by 2 pushes = 8 bytes)
|
| 1346 |
+
"movl 16(%esp), %ecx", // ecx param (offset 4+8=12? No: 0=ebx,4=ebp,8=ret,12=this,16=ecx)
|
| 1347 |
+
"movl 20(%esp), %edx", // edx param
|
| 1348 |
+
// Push 6 stack params in reverse: p6, p5, p4, p3, p2, this
|
| 1349 |
+
"pushl 40(%esp)", // p6 (0=ebx,4=ebp,8=ret,...,40=p6)
|
| 1350 |
+
"pushl 40(%esp)", // p5 (shifted by 4)
|
| 1351 |
+
"pushl 40(%esp)", // p4 (shifted by 8)
|
| 1352 |
+
"pushl 40(%esp)", // p3 (shifted by 12)
|
| 1353 |
+
"pushl 40(%esp)", // p2 (shifted by 16)
|
| 1354 |
+
"pushl 32(%esp)", // this (shifted by 20)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1355 |
"calll *({addr})", // fastcall, callee cleans 6Γ4=24 bytes
|
| 1356 |
"popl %ebx",
|
| 1357 |
+
"popl %ebp",
|
| 1358 |
"retl",
|
| 1359 |
addr = sym SPRITE_REGION_CTOR_ADDR,
|
| 1360 |
options(att_syntax),
|