refactor: extract FFI calls into typed wa_* wrapper functions
Browse filesCreate 7 named wrapper functions for WA stdcall/thiscall FFI calls:
- wa_init_version_flags: DDGame__InitVersionFlags (0x525BE0)
- wa_load_sprites: GfxHandler__LoadSprites (0x570B50)
- wa_load_effect_wavs: DSSound_LoadEffectWAVs (0x571660)
- wa_pc_landscape_ctor: PCLandscape__Constructor (0x57ACB0)
- wa_displaygfx_ctor: DisplayGfx__Constructor (0x4F5E80)
- wa_init_display_final: DDGame__InitDisplayFinal (0x56A830)
- wa_load_hud_sprites: DDGame__LoadHudAndWeaponSprites (0x53D0E0)
Replaces 7 inline transmute+call patterns with readable function
calls. Each wrapper is a single place to replace with pure Rust
when the WA function gets ported.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -550,15 +550,107 @@ pub fn init_constructor_addrs() {
|
|
| 550 |
crate::render::gfx_handler::init_addrs();
|
| 551 |
}
|
| 552 |
|
| 553 |
-
//
|
| 554 |
-
//
|
|
|
|
|
|
|
|
|
|
| 555 |
#[cfg(target_arch = "x86")]
|
| 556 |
-
unsafe fn
|
| 557 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 558 |
core::mem::transmute(rb(va::DDGAME_INIT_VERSION_FLAGS) as usize);
|
| 559 |
f(wrapper);
|
| 560 |
}
|
| 561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
/// Create and initialize DDGame, matching DDGame__Constructor (0x56E220).
|
| 563 |
///
|
| 564 |
/// Allocates 0x98B8 bytes from WA's heap, initializes all fields, and creates
|
|
@@ -634,7 +726,7 @@ pub unsafe fn create_ddgame(
|
|
| 634 |
}
|
| 635 |
|
| 636 |
// ββ 9. InitVersionFlags β sets DDGame+0x7E2E/0x7E2F/0x7E3F ββ
|
| 637 |
-
|
| 638 |
|
| 639 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 640 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
|
@@ -837,11 +929,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 837 |
call_usercall_eax(gfxdir2 as *mut DDGameWrapper, rb(va::PALETTE_CONTEXT_INIT));
|
| 838 |
*(gfxdir2.add(0x708) as *mut u16) = 0;
|
| 839 |
(*ddgame).secondary_gfxdir = gfxdir2;
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 843 |
-
core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
|
| 844 |
-
f(
|
| 845 |
wrapper,
|
| 846 |
(*ddgame).gfx_sprite_data.as_mut_ptr(),
|
| 847 |
(*game_info).display_flags,
|
|
@@ -865,10 +953,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 865 |
// FUN_570F30: usercall(ESI=wrapper)
|
| 866 |
call_usercall_esi(wrapper, FUN_570F30_ADDR);
|
| 867 |
if !(*ddgame).sound.is_null() {
|
| 868 |
-
|
| 869 |
-
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 870 |
-
core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
|
| 871 |
-
f(wrapper);
|
| 872 |
// DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
|
| 873 |
// replacement (speech.rs), so the usercall bridge calls our code.
|
| 874 |
call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
|
|
@@ -904,30 +989,16 @@ unsafe fn init_graphics_and_resources(
|
|
| 904 |
let alloc = wa_malloc(0xB44);
|
| 905 |
core::ptr::write_bytes(alloc, 0, 0xB44);
|
| 906 |
if !alloc.is_null() {
|
| 907 |
-
let
|
| 908 |
-
*mut u8,
|
| 909 |
-
*mut DDGame,
|
| 910 |
-
*mut u8, // 1=this, 2=ddgame, 3=gfx_resource
|
| 911 |
-
*mut DDDisplay,
|
| 912 |
-
*const u8, // 4=display, 5=game_info+0xDAAC
|
| 913 |
-
*mut u8,
|
| 914 |
-
u32, // 6=&landscape_byte, 7=gfx_mode
|
| 915 |
-
*mut u8,
|
| 916 |
-
*mut u8, // 8=stack local, 9=coord output
|
| 917 |
-
*mut u8,
|
| 918 |
-
*mut u8, // 10=&ddgame.level_width_raw, 11=&ddgame.level_height_raw
|
| 919 |
-
) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
|
| 920 |
-
|
| 921 |
-
let result = pc_ctor(
|
| 922 |
alloc,
|
| 923 |
ddgame,
|
| 924 |
gfx_resource,
|
| 925 |
-
(*wrapper).display,
|
| 926 |
-
(*game_info).landscape_data_path.as_ptr(),
|
| 927 |
-
landscape_byte_buf,
|
| 928 |
-
(*wrapper).gfx_mode,
|
| 929 |
-
stack_local_8,
|
| 930 |
-
landscape_coords_buf,
|
| 931 |
&mut (*ddgame).level_width_raw as *mut u32 as *mut u8,
|
| 932 |
&mut (*ddgame).level_height_raw as *mut u32 as *mut u8,
|
| 933 |
);
|
|
@@ -1022,10 +1093,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1022 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1023 |
let cached = load_cached(gfx_handler, entry_val);
|
| 1024 |
if !cached.is_null() {
|
| 1025 |
-
|
| 1026 |
-
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 1027 |
-
core::mem::transmute(rb(va::DISPLAYGFX_CONSTRUCTOR) as usize);
|
| 1028 |
-
sprite = ctor(cached);
|
| 1029 |
} else {
|
| 1030 |
// Fallback: load from file via GfxDir__LoadImage + IMG_Decode
|
| 1031 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
|
@@ -1276,15 +1344,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1276 |
}
|
| 1277 |
|
| 1278 |
// ββ DDGame__LoadHudAndWeaponSprites (0x53D0E0) ββ
|
| 1279 |
-
|
| 1280 |
-
// girder sprites, and creates the DDGame+0x37C DisplayGfx object needed by
|
| 1281 |
-
// CTaskLand__InitLandscape.
|
| 1282 |
-
// Convention: thiscall(ECX=gfx_handler_4c0) + 2 stack(ddgame, wrapper_4c4), RET 0x8.
|
| 1283 |
-
{
|
| 1284 |
-
let hud_load: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 1285 |
-
core::mem::transmute(rb(va::DDGAME_LOAD_HUD_AND_WEAPON_SPRITES) as usize);
|
| 1286 |
-
hud_load(gfx_handler, ddgame, (*wrapper)._field_4c4);
|
| 1287 |
-
}
|
| 1288 |
|
| 1289 |
// ββ Loading progress ticks (2 of 4 β after LoadHudAndWeaponSprites) ββ
|
| 1290 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
@@ -1311,11 +1371,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1311 |
release(gfx_handler_4c0, 1);
|
| 1312 |
}
|
| 1313 |
|
| 1314 |
-
// ββ DDGame__InitDisplayFinal_Maybe (0x56A830): non-headless display finalization ββ
|
| 1315 |
if !is_headless {
|
| 1316 |
-
|
| 1317 |
-
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 1318 |
-
f((*wrapper).display);
|
| 1319 |
}
|
| 1320 |
|
| 1321 |
// ββ FUN_00570A90 (second call, conditional) ββ
|
|
|
|
| 550 |
crate::render::gfx_handler::init_addrs();
|
| 551 |
}
|
| 552 |
|
| 553 |
+
// ββ Typed wrappers for WA stdcall functions ββββββββββββββββββββββββββββββββ
|
| 554 |
+
// Each wraps a single WA function with a typed Rust signature.
|
| 555 |
+
// Replace with pure Rust implementations as functions are ported.
|
| 556 |
+
|
| 557 |
+
/// DDGame__InitVersionFlags (0x525BE0): sets DDGame+0x7E2E/0x7E2F/0x7E3F.
|
| 558 |
#[cfg(target_arch = "x86")]
|
| 559 |
+
unsafe fn wa_init_version_flags(wrapper: *mut DDGameWrapper) {
|
| 560 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 561 |
core::mem::transmute(rb(va::DDGAME_INIT_VERSION_FLAGS) as usize);
|
| 562 |
f(wrapper);
|
| 563 |
}
|
| 564 |
|
| 565 |
+
/// GfxHandler__LoadSprites (0x570B50): load sprites for secondary GfxHandler.
|
| 566 |
+
#[cfg(target_arch = "x86")]
|
| 567 |
+
unsafe fn wa_load_sprites(
|
| 568 |
+
wrapper: *mut DDGameWrapper,
|
| 569 |
+
sprite_data: *mut u8,
|
| 570 |
+
display_flags: u32,
|
| 571 |
+
param4: u32,
|
| 572 |
+
) {
|
| 573 |
+
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 574 |
+
core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
|
| 575 |
+
f(wrapper, sprite_data, display_flags, param4);
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
/// DSSound_LoadEffectWAVs (0x571660): load sound effect WAVs.
|
| 579 |
+
#[cfg(target_arch = "x86")]
|
| 580 |
+
unsafe fn wa_load_effect_wavs(wrapper: *mut DDGameWrapper) {
|
| 581 |
+
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
|
| 582 |
+
core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
|
| 583 |
+
f(wrapper);
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
/// PCLandscape__Constructor (0x57ACB0): construct landscape object (0xB44 bytes, 11 params).
|
| 587 |
+
#[cfg(target_arch = "x86")]
|
| 588 |
+
unsafe fn wa_pc_landscape_ctor(
|
| 589 |
+
this: *mut u8,
|
| 590 |
+
ddgame: *mut DDGame,
|
| 591 |
+
gfx_resource: *mut u8,
|
| 592 |
+
display: *mut DDDisplay,
|
| 593 |
+
landscape_path: *const u8,
|
| 594 |
+
byte_output: *mut u8,
|
| 595 |
+
gfx_mode: u32,
|
| 596 |
+
temp_buf: *mut u8,
|
| 597 |
+
coord_output: *mut u8,
|
| 598 |
+
width_output: *mut u8,
|
| 599 |
+
height_output: *mut u8,
|
| 600 |
+
) -> *mut u8 {
|
| 601 |
+
let f: unsafe extern "stdcall" fn(
|
| 602 |
+
*mut u8,
|
| 603 |
+
*mut DDGame,
|
| 604 |
+
*mut u8,
|
| 605 |
+
*mut DDDisplay,
|
| 606 |
+
*const u8,
|
| 607 |
+
*mut u8,
|
| 608 |
+
u32,
|
| 609 |
+
*mut u8,
|
| 610 |
+
*mut u8,
|
| 611 |
+
*mut u8,
|
| 612 |
+
*mut u8,
|
| 613 |
+
) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
|
| 614 |
+
f(
|
| 615 |
+
this,
|
| 616 |
+
ddgame,
|
| 617 |
+
gfx_resource,
|
| 618 |
+
display,
|
| 619 |
+
landscape_path,
|
| 620 |
+
byte_output,
|
| 621 |
+
gfx_mode,
|
| 622 |
+
temp_buf,
|
| 623 |
+
coord_output,
|
| 624 |
+
width_output,
|
| 625 |
+
height_output,
|
| 626 |
+
)
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
/// DisplayGfx__Constructor (0x4F5E80): wrap raw image data in DisplayGfx object.
|
| 630 |
+
#[cfg(target_arch = "x86")]
|
| 631 |
+
unsafe fn wa_displaygfx_ctor(raw_image: *mut u8) -> *mut u8 {
|
| 632 |
+
let f: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 633 |
+
core::mem::transmute(rb(va::DISPLAYGFX_CONSTRUCTOR) as usize);
|
| 634 |
+
f(raw_image)
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
/// DDGame__InitDisplayFinal (0x56A830): finalize display for non-headless mode.
|
| 638 |
+
#[cfg(target_arch = "x86")]
|
| 639 |
+
unsafe fn wa_init_display_final(display: *mut DDDisplay) {
|
| 640 |
+
let f: unsafe extern "stdcall" fn(*mut DDDisplay) =
|
| 641 |
+
core::mem::transmute(rb(va::DDGAME_INIT_DISPLAY_FINAL) as usize);
|
| 642 |
+
f(display);
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
/// DDGame__LoadHudAndWeaponSprites (0x53D0E0): load weapon icons and HUD sprites.
|
| 646 |
+
/// thiscall(ECX=gfx_handler) + 2 stack(ddgame, secondary_gfx_handler), RET 0x8.
|
| 647 |
+
#[cfg(target_arch = "x86")]
|
| 648 |
+
unsafe fn wa_load_hud_sprites(gfx_handler: *mut u8, ddgame: *mut DDGame, secondary: *mut u8) {
|
| 649 |
+
let f: unsafe extern "thiscall" fn(*mut u8, *mut DDGame, *mut u8) =
|
| 650 |
+
core::mem::transmute(rb(va::DDGAME_LOAD_HUD_AND_WEAPON_SPRITES) as usize);
|
| 651 |
+
f(gfx_handler, ddgame, secondary);
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
/// Create and initialize DDGame, matching DDGame__Constructor (0x56E220).
|
| 655 |
///
|
| 656 |
/// Allocates 0x98B8 bytes from WA's heap, initializes all fields, and creates
|
|
|
|
| 726 |
}
|
| 727 |
|
| 728 |
// ββ 9. InitVersionFlags β sets DDGame+0x7E2E/0x7E2F/0x7E3F ββ
|
| 729 |
+
wa_init_version_flags(wrapper);
|
| 730 |
|
| 731 |
// ββ 10. GfxHandler, landscape, sprites, audio, resources ββ
|
| 732 |
init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
|
|
|
|
| 929 |
call_usercall_eax(gfxdir2 as *mut DDGameWrapper, rb(va::PALETTE_CONTEXT_INIT));
|
| 930 |
*(gfxdir2.add(0x708) as *mut u16) = 0;
|
| 931 |
(*ddgame).secondary_gfxdir = gfxdir2;
|
| 932 |
+
wa_load_sprites(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 933 |
wrapper,
|
| 934 |
(*ddgame).gfx_sprite_data.as_mut_ptr(),
|
| 935 |
(*game_info).display_flags,
|
|
|
|
| 953 |
// FUN_570F30: usercall(ESI=wrapper)
|
| 954 |
call_usercall_esi(wrapper, FUN_570F30_ADDR);
|
| 955 |
if !(*ddgame).sound.is_null() {
|
| 956 |
+
wa_load_effect_wavs(wrapper);
|
|
|
|
|
|
|
|
|
|
| 957 |
// DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
|
| 958 |
// replacement (speech.rs), so the usercall bridge calls our code.
|
| 959 |
call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
|
|
|
|
| 989 |
let alloc = wa_malloc(0xB44);
|
| 990 |
core::ptr::write_bytes(alloc, 0, 0xB44);
|
| 991 |
if !alloc.is_null() {
|
| 992 |
+
let result = wa_pc_landscape_ctor(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
alloc,
|
| 994 |
ddgame,
|
| 995 |
gfx_resource,
|
| 996 |
+
(*wrapper).display,
|
| 997 |
+
(*game_info).landscape_data_path.as_ptr(),
|
| 998 |
+
landscape_byte_buf,
|
| 999 |
+
(*wrapper).gfx_mode,
|
| 1000 |
+
stack_local_8,
|
| 1001 |
+
landscape_coords_buf,
|
| 1002 |
&mut (*ddgame).level_width_raw as *mut u32 as *mut u8,
|
| 1003 |
&mut (*ddgame).level_height_raw as *mut u32 as *mut u8,
|
| 1004 |
);
|
|
|
|
| 1093 |
let entry_val = *(entry.add(4) as *const u32);
|
| 1094 |
let cached = load_cached(gfx_handler, entry_val);
|
| 1095 |
if !cached.is_null() {
|
| 1096 |
+
sprite = wa_displaygfx_ctor(cached);
|
|
|
|
|
|
|
|
|
|
| 1097 |
} else {
|
| 1098 |
// Fallback: load from file via GfxDir__LoadImage + IMG_Decode
|
| 1099 |
sprite = call_gfx_load_and_wrap(gfx_handler, name_buf.as_ptr(), layer_ctx);
|
|
|
|
| 1344 |
}
|
| 1345 |
|
| 1346 |
// ββ DDGame__LoadHudAndWeaponSprites (0x53D0E0) ββ
|
| 1347 |
+
wa_load_hud_sprites(gfx_handler, ddgame, (*wrapper)._field_4c4);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
|
| 1349 |
// ββ Loading progress ticks (2 of 4 β after LoadHudAndWeaponSprites) ββ
|
| 1350 |
call_usercall_ecx(wrapper, LOADING_PROGRESS_TICK_ADDR);
|
|
|
|
| 1371 |
release(gfx_handler_4c0, 1);
|
| 1372 |
}
|
| 1373 |
|
|
|
|
| 1374 |
if !is_headless {
|
| 1375 |
+
wa_init_display_final((*wrapper).display);
|
|
|
|
|
|
|
| 1376 |
}
|
| 1377 |
|
| 1378 |
// ββ FUN_00570A90 (second call, conditional) ββ
|