paavohtl Claude Opus 4.6 (1M context) commited on
Commit
8fe7bff
Β·
1 Parent(s): bee20b7

refactor: extract FFI calls into typed wa_* wrapper functions

Browse files

Create 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
- /// Bridge to DDGame__InitVersionFlags (0x525BE0).
554
- /// Convention: stdcall(ddgame_wrapper).
 
 
 
555
  #[cfg(target_arch = "x86")]
556
- unsafe fn call_init_version_flags(wrapper: *mut DDGameWrapper) {
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
- call_init_version_flags(wrapper);
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
- // GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
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
- // DSSound_LoadEffectWAVs: stdcall(wrapper)
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 pc_ctor: unsafe extern "stdcall" fn(
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, // param 4: display (NOT wrapper!)
926
- (*game_info).landscape_data_path.as_ptr(), // param 5
927
- landscape_byte_buf, // param 6
928
- (*wrapper).gfx_mode, // param 7
929
- stack_local_8, // param 8
930
- landscape_coords_buf, // param 9
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
- // Wrap with DisplayGfx constructor
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
- // Loads weapon icons (cow.img, pigeon.img, etc.), wind indicators, stop sign,
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
- let f: unsafe extern "stdcall" fn(*mut DDDisplay) =
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) ──