paavohtl Claude Opus 4.6 (1M context) commited on
Commit
00e3778
Β·
1 Parent(s): 4563f4c

fix: TaskStateMachine__Init_Maybe was fastcall+stdcall, not pure fastcall

Browse files

ROOT 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(0x2C);
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 _height = *((ddgame as *const u8).add(0x77C4) as *const u32);
811
- let tsm_init: unsafe extern "fastcall" fn(u32, u32) =
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
- let ddgame_ptr = ddgame as *mut u8;
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 cached = load_cached(gfx_handler, *(entry.add(4) as *const u32));
 
 
 
 
 
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 for testing ──
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
- // ── HUD_LoadWeaponSprites (0x53D0E0) ──
1098
- // thiscall(ECX=gfx_handler, ddgame, wrapper_4c4), RET 0x8
1099
- // asm: MOV ECX,[EBP+0x4C0]; PUSH [EBP+0x4C4]; PUSH [EBP+0x488]; CALL
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