fix: TSM init missing ESI, unguard display calls for headless
Browse filesFrom audit:
- call_tsm_init now passes ESI=object (TSM allocation) β the function
writes bitmap/dimension fields through ESI. Without it, writes to
garbage memory.
- FUN_570E20 and display vtable[5] unguarded for headless β verified
headless vtable slot 4 is 0x5231E0 (same as headful, not _purecall).
Constructor completes in headless. Remaining crash: GfxDir__LoadImage
write violation inside call_gfx_resource_create β gfx_handler internal
state may be corrupted. Sprite resources + end sections disabled for
bisection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -636,14 +636,11 @@ unsafe fn init_graphics_and_resources(
|
|
| 636 |
}
|
| 637 |
|
| 638 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 639 |
-
//
|
| 640 |
-
|
| 641 |
-
if !is_headless {
|
| 642 |
-
call_usercall_esi(wrapper, FUN_570E20_ADDR);
|
| 643 |
-
}
|
| 644 |
|
| 645 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 646 |
-
|
| 647 |
let vt = *((*ddgame).display as *const *const u32);
|
| 648 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 649 |
core::mem::transmute(*vt.add(5));
|
|
@@ -812,7 +809,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 812 |
if !tsm.is_null() {
|
| 813 |
let width = *((ddgame as *const u8).add(0x77C0) as *const u32);
|
| 814 |
let height = *((ddgame as *const u8).add(0x77C4) as *const u32);
|
| 815 |
-
call_tsm_init(width, height);
|
| 816 |
*(tsm as *mut u32) = rb(0x664118); // TaskStateMachine__vtable
|
| 817 |
}
|
| 818 |
(*ddgame).task_state_machine = tsm;
|
|
@@ -999,13 +996,13 @@ unsafe fn init_graphics_and_resources(
|
|
| 999 |
let land_layer = *(landscape_ptr.add(0xB34) as *const *mut u8);
|
| 1000 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 1001 |
|
|
|
|
|
|
|
|
|
|
| 1002 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1003 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1004 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1005 |
) = core::mem::transmute(*wrapper_vt);
|
| 1006 |
-
|
| 1007 |
-
// return point moved up to isolate
|
| 1008 |
-
let _ = crate::log::log_line("[DDGame] calling load_resource_list #1");
|
| 1009 |
// Load resources for layer 1 (main sprites)
|
| 1010 |
load_resource_list(
|
| 1011 |
wrapper, 1, gfx_handler,
|
|
@@ -1116,50 +1113,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 1116 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1117 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1118 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1119 |
-
//
|
| 1120 |
-
//
|
| 1121 |
-
|
| 1122 |
-
let hud_load: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 1123 |
-
core::mem::transmute(rb(0x53D0E0) as usize);
|
| 1124 |
-
hud_load((*wrapper)._field_4c0, ddgame, (*wrapper)._field_4c4);
|
| 1125 |
-
}
|
| 1126 |
-
|
| 1127 |
-
// ββ Two sprite init calls β stdcall(wrapper), RET 0x4 ββ
|
| 1128 |
-
{
|
| 1129 |
-
let f1: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 1130 |
-
core::mem::transmute(rb(0x5706D0) as usize);
|
| 1131 |
-
f1(wrapper);
|
| 1132 |
-
let f2: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 1133 |
-
core::mem::transmute(rb(0x5703E0) as usize);
|
| 1134 |
-
f2(wrapper);
|
| 1135 |
-
}
|
| 1136 |
-
|
| 1137 |
-
// ββ Close primary GfxHandler ββ
|
| 1138 |
-
if !(*wrapper)._field_4c0.is_null() {
|
| 1139 |
-
let gfx_vt = *((*wrapper)._field_4c0 as *const *const u32);
|
| 1140 |
-
let close: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1141 |
-
core::mem::transmute(*gfx_vt.add(3));
|
| 1142 |
-
close((*wrapper)._field_4c0, 1);
|
| 1143 |
-
}
|
| 1144 |
-
|
| 1145 |
-
// ββ Display finalization (non-headless) ββ
|
| 1146 |
-
if !is_headless {
|
| 1147 |
-
let init_display_final: unsafe extern "stdcall" fn(*mut u8) =
|
| 1148 |
-
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 1149 |
-
init_display_final((*wrapper).display as *mut u8);
|
| 1150 |
-
}
|
| 1151 |
-
if *(rb(0x88E485) as *const u8) == 0 {
|
| 1152 |
-
call_usercall_eax(wrapper, FUN_570A90_ADDR);
|
| 1153 |
-
}
|
| 1154 |
-
|
| 1155 |
-
// Display layer visibility
|
| 1156 |
-
let disp_vis = (*wrapper).display as *mut u8;
|
| 1157 |
-
let disp_vis_vt = *(disp_vis as *const *const u32);
|
| 1158 |
-
let set_vis: unsafe extern "thiscall" fn(*mut u8, i32, i32) -> *mut u8 =
|
| 1159 |
-
core::mem::transmute(*disp_vis_vt.add(0x17));
|
| 1160 |
-
set_vis(disp_vis, 1, 0);
|
| 1161 |
-
set_vis(disp_vis, 2, 0);
|
| 1162 |
-
set_vis(disp_vis, 3, 1);
|
| 1163 |
}
|
| 1164 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1165 |
}
|
|
@@ -1240,17 +1196,20 @@ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
|
|
| 1240 |
static mut TSM_INIT_ADDR: u32 = 0;
|
| 1241 |
|
| 1242 |
/// Bridge to TaskStateMachine__Init_Maybe (0x4F6370).
|
| 1243 |
-
/// Convention:
|
| 1244 |
#[cfg(target_arch = "x86")]
|
| 1245 |
#[unsafe(naked)]
|
| 1246 |
-
unsafe extern "C" fn call_tsm_init(_width: u32, _height: u32) {
|
| 1247 |
core::arch::naked_asm!(
|
|
|
|
| 1248 |
"pushl %edi",
|
| 1249 |
-
"movl 12(%esp), %
|
| 1250 |
-
"
|
|
|
|
| 1251 |
"movl $1, %ecx", // ECX = 1
|
| 1252 |
"calll *({addr})", // RET 0x4 cleans the pushed width
|
| 1253 |
"popl %edi",
|
|
|
|
| 1254 |
"retl",
|
| 1255 |
addr = sym TSM_INIT_ADDR,
|
| 1256 |
options(att_syntax),
|
|
|
|
| 636 |
}
|
| 637 |
|
| 638 |
// ββ FUN_00570E20: usercall(ESI=wrapper), plain RET ββ
|
| 639 |
+
// Runs for all modes β headless vtable[4] is 0x5231E0 (same as headful).
|
| 640 |
+
call_usercall_esi(wrapper, FUN_570E20_ADDR);
|
|
|
|
|
|
|
|
|
|
| 641 |
|
| 642 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 643 |
+
{
|
| 644 |
let vt = *((*ddgame).display as *const *const u32);
|
| 645 |
let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
|
| 646 |
core::mem::transmute(*vt.add(5));
|
|
|
|
| 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(tsm, width, height);
|
| 813 |
*(tsm as *mut u32) = rb(0x664118); // TaskStateMachine__vtable
|
| 814 |
}
|
| 815 |
(*ddgame).task_state_machine = tsm;
|
|
|
|
| 996 |
let land_layer = *(landscape_ptr.add(0xB34) as *const *mut u8);
|
| 997 |
let gfx_handler = (*wrapper)._field_4c0;
|
| 998 |
|
| 999 |
+
// Sprite resource loading + remaining: disabled to isolate DEP crash
|
| 1000 |
+
let _ = crate::log::log_line("[DDGame] skipping sprite resources+");
|
| 1001 |
+
if false {
|
| 1002 |
let wrapper_vt = *(wrapper as *const *const u32);
|
| 1003 |
let load_resource_list: unsafe extern "thiscall" fn(
|
| 1004 |
*mut DDGameWrapper, u32, *mut u8, *const u8, *const u8, u32,
|
| 1005 |
) = core::mem::transmute(*wrapper_vt);
|
|
|
|
|
|
|
|
|
|
| 1006 |
// Load resources for layer 1 (main sprites)
|
| 1007 |
load_resource_list(
|
| 1008 |
wrapper, 1, gfx_handler,
|
|
|
|
| 1113 |
// But decompiler says thiscall β ECX is implicit this.
|
| 1114 |
// RET 0x8 = 2 Γ 4 bytes cleaned. For thiscall: ECX + 1 stack = RET 0x4.
|
| 1115 |
// RET 0x8 means 2 stack params, NO ECX implicit β it's really stdcall(2 params).
|
| 1116 |
+
// End sections: disabled for headless testing
|
| 1117 |
+
// TODO: enable one by one to find which causes DEP crash
|
| 1118 |
+
} // if false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1119 |
}
|
| 1120 |
let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
|
| 1121 |
}
|
|
|
|
| 1196 |
static mut TSM_INIT_ADDR: u32 = 0;
|
| 1197 |
|
| 1198 |
/// Bridge to TaskStateMachine__Init_Maybe (0x4F6370).
|
| 1199 |
+
/// Convention: usercall(ESI=object, ECX=1, EDI=height) + 1 stack(width), RET 0x4.
|
| 1200 |
#[cfg(target_arch = "x86")]
|
| 1201 |
#[unsafe(naked)]
|
| 1202 |
+
unsafe extern "C" fn call_tsm_init(_object: *mut u8, _width: u32, _height: u32) {
|
| 1203 |
core::arch::naked_asm!(
|
| 1204 |
+
"pushl %esi",
|
| 1205 |
"pushl %edi",
|
| 1206 |
+
"movl 12(%esp), %esi", // ESI = object (1st param, shifted by 2 pushes)
|
| 1207 |
+
"movl 20(%esp), %edi", // EDI = height (3rd param, shifted by 2 pushes)
|
| 1208 |
+
"pushl 16(%esp)", // push width (2nd param, shifted by 2 pushes + 1 push)
|
| 1209 |
"movl $1, %ecx", // ECX = 1
|
| 1210 |
"calll *({addr})", // RET 0x4 cleans the pushed width
|
| 1211 |
"popl %edi",
|
| 1212 |
+
"popl %esi",
|
| 1213 |
"retl",
|
| 1214 |
addr = sym TSM_INIT_ADDR,
|
| 1215 |
options(att_syntax),
|