fix: TaskStateMachine__Init_Maybe was fastcall+stdcall, not pure fastcall
Browse filesROOT CAUSE of stack corruption: TSM init at 0x4F6370 is
fastcall(ECX=1) + 1 stack param(width) with RET 0x4, plus
implicit EDI=height. Was called as fastcall(ECX=1, EDX=width)
which doesn't push on stack β RET 0x4 then pops 4 bytes of
unrelated stack data, corrupting return addresses.
Fixed with naked bridge that pushes width, sets ECX=1, sets
EDI=height, calls, and restores EDI.
Also fixed alloc size: 0x4C (not 0x2C) matching original.
Arrow loop now sometimes completes (was always hanging before).
Remaining hang is intermittent β needs further investigation.
Updated Ghidra signature for TaskStateMachine__Init_Maybe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -374,6 +374,7 @@ pub fn init_constructor_addrs() {
|
|
| 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 |
GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
|
| 378 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 379 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
|
@@ -799,22 +800,19 @@ unsafe fn init_graphics_and_resources(
|
|
| 799 |
}
|
| 800 |
};
|
| 801 |
|
| 802 |
-
// ββ TaskStateMachine at DDGame+0x380 (alloc 0x2C) ββ
|
|
|
|
|
|
|
| 803 |
{
|
| 804 |
-
let tsm = wa_malloc(
|
| 805 |
core::ptr::write_bytes(tsm, 0, 0x2C);
|
| 806 |
if !tsm.is_null() {
|
| 807 |
-
// TaskStateMachine__Init_Maybe is fastcall(ECX=width, EDX=?)
|
| 808 |
-
// but called here with ECX=1, then set vtable
|
| 809 |
let width = *((ddgame as *const u8).add(0x77C0) as *const u32);
|
| 810 |
-
let
|
| 811 |
-
|
| 812 |
-
core::mem::transmute(rb(va::TASK_STATE_MACHINE_INIT) as usize);
|
| 813 |
-
tsm_init(1, width); // ECX=1, EDX=width
|
| 814 |
*(tsm as *mut u32) = rb(0x664118); // TaskStateMachine__vtable
|
| 815 |
}
|
| 816 |
-
|
| 817 |
-
*(ddgame_ptr.add(0x380) as *mut *mut u8) = tsm;
|
| 818 |
}
|
| 819 |
|
| 820 |
// ββ 8Γ SpriteRegion at DDGame+0x46C..0x488 ββ
|
|
@@ -866,7 +864,6 @@ unsafe fn init_graphics_and_resources(
|
|
| 866 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
| 867 |
// and called through naked bridges (call_gfx_find_entry, call_gfx_load_image).
|
| 868 |
|
| 869 |
-
let _ = crate::log::log_line("[DDGame] starting arrow loop");
|
| 870 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 871 |
{
|
| 872 |
let gfx_handler = (*wrapper)._field_4c0;
|
|
@@ -882,9 +879,18 @@ unsafe fn init_graphics_and_resources(
|
|
| 882 |
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 883 |
core::mem::transmute(*disp_vt.add(5));
|
| 884 |
let layer_ctx = set_layer((*ddgame).display, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
|
| 886 |
// GfxDir__FindEntry: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4
|
| 887 |
let entry = call_gfx_find_entry(name_buf.as_ptr(), gfx_handler);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
|
| 889 |
let sprite: *mut u8;
|
| 890 |
if !entry.is_null() {
|
|
@@ -892,7 +898,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 892 |
let gfx_vt = *(gfx_handler as *const *const u32);
|
| 893 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 894 |
core::mem::transmute(*gfx_vt.add(2));
|
| 895 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 896 |
if !cached.is_null() {
|
| 897 |
// Wrap with DisplayGfx constructor
|
| 898 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
@@ -904,6 +915,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 904 |
} else {
|
| 905 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
| 906 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 907 |
|
| 908 |
// Store arrow sprite at DDGame+0x38+i*4
|
| 909 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
|
@@ -965,7 +980,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 965 |
// TODO: populate coord_list from landscape data (complex loop)
|
| 966 |
}
|
| 967 |
|
| 968 |
-
// ββ Loading tick: disabled
|
| 969 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 970 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 971 |
|
|
@@ -1094,16 +1109,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 1094 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1095 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1096 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1097 |
-
//
|
| 1098 |
-
//
|
| 1099 |
-
//
|
| 1100 |
-
// End sections disabled while debugging field comparison.
|
| 1101 |
-
// HUD, sprite inits, GfxHandler close, display finalization,
|
| 1102 |
-
// layer visibility β all crash. Need field diff first.
|
| 1103 |
-
|
| 1104 |
-
// GfxHandler close, display finalization, layer visibility:
|
| 1105 |
-
// Temporarily disabled to test stability.
|
| 1106 |
-
// TODO: re-enable after verifying calling conventions
|
| 1107 |
}
|
| 1108 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1109 |
}
|
|
@@ -1180,6 +1188,27 @@ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
|
|
| 1180 |
f(wrapper);
|
| 1181 |
}
|
| 1182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1183 |
/// Runtime address of GfxResource__Create_Maybe (set by init_constructor_addrs()).
|
| 1184 |
static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
|
| 1185 |
|
|
|
|
| 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 |
+
TSM_INIT_ADDR = rb(va::TASK_STATE_MACHINE_INIT);
|
| 378 |
GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
|
| 379 |
GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
|
| 380 |
FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
|
|
|
|
| 800 |
}
|
| 801 |
};
|
| 802 |
|
| 803 |
+
// ββ TaskStateMachine at DDGame+0x380 (alloc 0x4C, memset 0x2C) ββ
|
| 804 |
+
// Original: alloc 0x4C, memset 0x2C, PUSH width, MOV ECX=1, CALL 0x4F6370 (RET 0x4)
|
| 805 |
+
// Also EDI=height before the call (implicit param).
|
| 806 |
{
|
| 807 |
+
let tsm = wa_malloc(0x4C);
|
| 808 |
core::ptr::write_bytes(tsm, 0, 0x2C);
|
| 809 |
if !tsm.is_null() {
|
|
|
|
|
|
|
| 810 |
let width = *((ddgame as *const u8).add(0x77C0) as *const u32);
|
| 811 |
+
let height = *((ddgame as *const u8).add(0x77C4) as *const u32);
|
| 812 |
+
call_tsm_init(width, height);
|
|
|
|
|
|
|
| 813 |
*(tsm as *mut u32) = rb(0x664118); // TaskStateMachine__vtable
|
| 814 |
}
|
| 815 |
+
(*ddgame).task_state_machine = tsm;
|
|
|
|
| 816 |
}
|
| 817 |
|
| 818 |
// ββ 8Γ SpriteRegion at DDGame+0x46C..0x488 ββ
|
|
|
|
| 864 |
// GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
|
| 865 |
// and called through naked bridges (call_gfx_find_entry, call_gfx_load_image).
|
| 866 |
|
|
|
|
| 867 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 868 |
{
|
| 869 |
let gfx_handler = (*wrapper)._field_4c0;
|
|
|
|
| 879 |
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 880 |
core::mem::transmute(*disp_vt.add(5));
|
| 881 |
let layer_ctx = set_layer((*ddgame).display, 1);
|
| 882 |
+
if i == 0 {
|
| 883 |
+
let _ = crate::log::log_line(&format!(
|
| 884 |
+
"[DDGame] arrow[0]: layer_ctx=0x{:08X}, gfx=0x{:08X}",
|
| 885 |
+
layer_ctx as u32, gfx_handler as u32));
|
| 886 |
+
}
|
| 887 |
|
| 888 |
// GfxDir__FindEntry: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4
|
| 889 |
let entry = call_gfx_find_entry(name_buf.as_ptr(), gfx_handler);
|
| 890 |
+
if i == 0 {
|
| 891 |
+
let _ = crate::log::log_line(&format!(
|
| 892 |
+
"[DDGame] arrow[0]: entry=0x{:08X}", entry as u32));
|
| 893 |
+
}
|
| 894 |
|
| 895 |
let sprite: *mut u8;
|
| 896 |
if !entry.is_null() {
|
|
|
|
| 898 |
let gfx_vt = *(gfx_handler as *const *const u32);
|
| 899 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 900 |
core::mem::transmute(*gfx_vt.add(2));
|
| 901 |
+
let entry_val = *(entry.add(4) as *const u32);
|
| 902 |
+
let cached = load_cached(gfx_handler, entry_val);
|
| 903 |
+
if i == 0 {
|
| 904 |
+
let _ = crate::log::log_line(&format!(
|
| 905 |
+
"[DDGame] arrow[0]: cached=0x{:08X}", cached as u32));
|
| 906 |
+
}
|
| 907 |
if !cached.is_null() {
|
| 908 |
// Wrap with DisplayGfx constructor
|
| 909 |
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
|
|
|
| 915 |
} else {
|
| 916 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
| 917 |
}
|
| 918 |
+
if i == 0 {
|
| 919 |
+
let _ = crate::log::log_line(&format!(
|
| 920 |
+
"[DDGame] arrow[0]: sprite=0x{:08X}", sprite as u32));
|
| 921 |
+
}
|
| 922 |
|
| 923 |
// Store arrow sprite at DDGame+0x38+i*4
|
| 924 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
|
|
|
| 980 |
// TODO: populate coord_list from landscape data (complex loop)
|
| 981 |
}
|
| 982 |
|
| 983 |
+
// ββ Loading tick: disabled (pumps message loop) ββ
|
| 984 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 985 |
// call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
|
| 986 |
|
|
|
|
| 1109 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1110 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1111 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1112 |
+
// End sections temporarily disabled β need to verify conventions
|
| 1113 |
+
// after TSM stack corruption fix. The core constructor now completes.
|
| 1114 |
+
// TODO: re-enable HUD, sprite inits, close, finalization one by one
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1115 |
}
|
| 1116 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1117 |
}
|
|
|
|
| 1188 |
f(wrapper);
|
| 1189 |
}
|
| 1190 |
|
| 1191 |
+
/// Runtime address of TaskStateMachine__Init_Maybe.
|
| 1192 |
+
static mut TSM_INIT_ADDR: u32 = 0;
|
| 1193 |
+
|
| 1194 |
+
/// Bridge to TaskStateMachine__Init_Maybe (0x4F6370).
|
| 1195 |
+
/// Convention: fastcall(ECX=1) + 1 stack(width), EDI=height, RET 0x4.
|
| 1196 |
+
#[cfg(target_arch = "x86")]
|
| 1197 |
+
#[unsafe(naked)]
|
| 1198 |
+
unsafe extern "C" fn call_tsm_init(_width: u32, _height: u32) {
|
| 1199 |
+
core::arch::naked_asm!(
|
| 1200 |
+
"pushl %edi",
|
| 1201 |
+
"movl 12(%esp), %edi", // EDI = height (2nd param, shifted by push)
|
| 1202 |
+
"pushl 8(%esp)", // push width (1st param, shifted by push+push)
|
| 1203 |
+
"movl $1, %ecx", // ECX = 1
|
| 1204 |
+
"calll *({addr})", // RET 0x4 cleans the pushed width
|
| 1205 |
+
"popl %edi",
|
| 1206 |
+
"retl",
|
| 1207 |
+
addr = sym TSM_INIT_ADDR,
|
| 1208 |
+
options(att_syntax),
|
| 1209 |
+
);
|
| 1210 |
+
}
|
| 1211 |
+
|
| 1212 |
/// Runtime address of GfxResource__Create_Maybe (set by init_constructor_addrs()).
|
| 1213 |
static mut GFX_RESOURCE_CREATE_ADDR: u32 = 0;
|
| 1214 |
|