paavohtl Claude Opus 4.6 (1M context) commited on
Commit
d06e127
Β·
1 Parent(s): 3c325a3

fix: TSM init missing ESI, unguard display calls for headless

Browse files

From 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
- // The original runs this for all modes, but the headless display stub
640
- // may not support vtable[4]. Guard for now β€” investigate separately.
641
- if !is_headless {
642
- call_usercall_esi(wrapper, FUN_570E20_ADDR);
643
- }
644
 
645
  // ── Display vtable slot 5 (offset 0x14) ──
646
- if !is_headless {
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
- // ── HUD_LoadWeaponSprites (0x53D0E0) ──
1120
- // thiscall(ECX=gfx_handler, ddgame, wrapper_4c4), RET 0x8
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: fastcall(ECX=1) + 1 stack(width), EDI=height, RET 0x4.
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), %edi", // EDI = height (2nd param, shifted by push)
1250
- "pushl 8(%esp)", // push width (1st param, shifted by push+push)
 
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),