paavohtl Claude Opus 4.6 (1M context) commited on
Commit
dbded04
Β·
1 Parent(s): fd089dc

fix: 3 calling convention bugs found by systematic audit

Browse files

Bugs found by audit agent:
1. FUN_004F5F80: was cdecl, is stdcall with RET 0xC (3 params)
β†’ 12 bytes stack corruption per call
2. FUN_005411A0: was extern "C" fn() with no args, is usercall(EAX=ptr)
β†’ reads garbage from EAX
3. GFX_RESOURCE_CREATE at color entries: was extern "C" fn() with no
args, should use call_gfx_resource_create naked bridge
4. GFX_HANDLER_LOAD_SPRITES: was thiscall(1 param), is stdcall(4 params)
with RET 0x10 β†’ 16 bytes stack corruption per call!

Also removed incorrect headless guards around FUN_570E20 and display
vtable[5] β€” the original runs these for all modes.

Headless now reaches create_ddgame complete. DEP crash after return
suggests remaining stack corruption from another call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -541,6 +541,7 @@ unsafe fn init_graphics_and_resources(
541
  let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
542
  let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
543
 
 
544
  // ── GfxHandler #1 (primary) ──
545
  let gfx1 = wa_malloc(0x19C);
546
  core::ptr::write_bytes(gfx1, 0, 0x19C);
@@ -647,24 +648,25 @@ unsafe fn init_graphics_and_resources(
647
  }
648
 
649
  // ── FUN_00570E20: usercall(ESI=wrapper), plain RET ──
650
- // Calls display->vtable[4] which may not exist in headless mode.
651
- if !is_headless {
652
- call_usercall_esi(wrapper, FUN_570E20_ADDR);
653
- }
654
 
655
  // ── Display vtable slot 5 (offset 0x14) ──
656
- if !is_headless {
657
  let vt = *((*ddgame).display as *const *const u32);
658
- let f: unsafe extern "thiscall" fn(*mut DDDisplay) -> *mut u8 =
659
  core::mem::transmute(*vt.add(5));
660
- f((*ddgame).display);
661
  }
662
 
 
663
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
664
  if (*wrapper).gfx_mode != 0 {
665
- let create: unsafe extern "C" fn() -> *mut u8 =
666
- core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
667
- let res = create();
 
 
668
  if !res.is_null() {
669
  let rvt = *(res as *const *const u32);
670
  let get_color: unsafe extern "thiscall" fn(*mut u8) -> u32 =
@@ -679,26 +681,29 @@ unsafe fn init_graphics_and_resources(
679
  release(res);
680
  }
681
  } else {
682
- let f: unsafe extern "thiscall" fn(*mut u8) =
 
 
683
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
684
- f(wrapper as *mut u8);
685
  }
686
 
 
687
  // ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
688
  if !(*wrapper)._field_4c4.is_null() {
689
  let gfxdir2 = wa_malloc(0x70C);
690
  core::ptr::write_bytes(gfxdir2, 0, 0x70C);
691
  *(gfxdir2 as *mut u16) = 1;
692
  *(gfxdir2.add(2) as *mut u16) = 0x5A;
693
- {
694
- let f: unsafe extern "C" fn() = core::mem::transmute(rb(0x5411A0) as usize);
695
- f();
696
- }
697
  *(gfxdir2.add(0x708) as *mut u16) = 0;
698
  (*ddgame).secondary_gfxdir = gfxdir2;
699
- let f: unsafe extern "thiscall" fn(*mut u8) =
 
 
700
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
701
- f(wrapper as *mut u8);
702
  }
703
 
704
  // ── DDGameWrapper field inits ──
@@ -1305,8 +1310,8 @@ unsafe fn call_gfx_load_and_wrap(
1305
  if image.is_null() {
1306
  return core::ptr::null_mut();
1307
  }
1308
- // FUN_004F5F80(display_ctx, image, 1) β€” cdecl, 3 params
1309
- let f: unsafe extern "cdecl" fn(*mut u8, *mut u8, u32) -> *mut u8 =
1310
  core::mem::transmute(rb(0x4F5F80) as usize);
1311
  let result = f(display_ctx, image, 1);
1312
  // Release the raw image
 
541
  let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
542
  let gfx_handler_vtable = rb(0x66B280) as u32; // GfxHandler__vtable (from asm: MOV [ESI], 0x66B280)
543
 
544
+ let _ = crate::log::log_line("[DDGame] gfx_and_res: entry");
545
  // ── GfxHandler #1 (primary) ──
546
  let gfx1 = wa_malloc(0x19C);
547
  core::ptr::write_bytes(gfx1, 0, 0x19C);
 
648
  }
649
 
650
  // ── FUN_00570E20: usercall(ESI=wrapper), plain RET ──
651
+ // Runs for all modes (including headless) β€” the original doesn't guard this.
652
+ call_usercall_esi(wrapper, FUN_570E20_ADDR);
 
 
653
 
654
  // ── Display vtable slot 5 (offset 0x14) ──
655
+ {
656
  let vt = *((*ddgame).display as *const *const u32);
657
+ let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
658
  core::mem::transmute(*vt.add(5));
659
+ f((*ddgame).display, 1);
660
  }
661
 
662
+ let _ = crate::log::log_line("[DDGame] past gfxhandler+palette");
663
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
664
  if (*wrapper).gfx_mode != 0 {
665
+ // GfxResource__Create: usercall(ECX=gfx_handler, EAX=0x66A3B4) + 1 stack, RET 0x4
666
+ // In original: PUSH ESI(display_layer?), MOV EAX=0x66A3B4, CALL 0x4F6300
667
+ let res = call_gfx_resource_create(
668
+ (*wrapper)._field_4c0, rb(0x66A3B4) as *const u8, core::ptr::null_mut(),
669
+ );
670
  if !res.is_null() {
671
  let rvt = *(res as *const *const u32);
672
  let get_color: unsafe extern "thiscall" fn(*mut u8) -> u32 =
 
681
  release(res);
682
  }
683
  } else {
684
+ // GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
685
+ let gi = (*ddgame).game_info as *const u8;
686
+ let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
687
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
688
+ f(wrapper, (ddgame as *mut u8).add(0x7308), *(gi.add(0xF374) as *const u32), 0);
689
  }
690
 
691
+ let _ = crate::log::log_line("[DDGame] past color entries");
692
  // ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
693
  if !(*wrapper)._field_4c4.is_null() {
694
  let gfxdir2 = wa_malloc(0x70C);
695
  core::ptr::write_bytes(gfxdir2, 0, 0x70C);
696
  *(gfxdir2 as *mut u16) = 1;
697
  *(gfxdir2.add(2) as *mut u16) = 0x5A;
698
+ // FUN_5411A0: usercall(EAX=gfxdir2), plain RET
699
+ call_usercall_eax(gfxdir2 as *mut DDGameWrapper, rb(0x5411A0));
 
 
700
  *(gfxdir2.add(0x708) as *mut u16) = 0;
701
  (*ddgame).secondary_gfxdir = gfxdir2;
702
+ // GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
703
+ let gi = (*ddgame).game_info as *const u8;
704
+ let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
705
  core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
706
+ f(wrapper, (ddgame as *mut u8).add(0x7308), *(gi.add(0xF374) as *const u32), 0);
707
  }
708
 
709
  // ── DDGameWrapper field inits ──
 
1310
  if image.is_null() {
1311
  return core::ptr::null_mut();
1312
  }
1313
+ // FUN_004F5F80(display_ctx, image, 1) β€” stdcall, RET 0xC (3 params)
1314
+ let f: unsafe extern "stdcall" fn(*mut u8, *mut u8, u32) -> *mut u8 =
1315
  core::mem::transmute(rb(0x4F5F80) as usize);
1316
  let result = f(display_ctx, image, 1);
1317
  // Release the raw image