paavohtl Claude Opus 4.6 (1M context) commited on
Commit
c38e471
·
1 Parent(s): 3bfaac0

refactor: rename mis-labeled DisplayGfx slot 33 to GetSpriteFrameForBlit

Browse files

Slot 33 (0x5237C0) was historically called LoadSpriteComplex everywhere —
in the vtable definition, the address registry, the inner-helper names
(SPRITE_COMPLEX_SPRITE_PATH / SPRITE_COMPLEX_BANK_PATH), the doc strings
on SpriteBank/DisplayBase, and the "NOT ported because complex usercall"
rationale. It is not a load function. It's the per-blit frame lookup that
BlitSprite (slot 19) calls on every sprite render: clamps an animation
value, finds the matching frame entry in the sprite's frame table,
lazily decompresses the frame surface, and returns *DisplayBitGrid +
bounding box.

Cascade renames:

vtable slot load_sprite_complex → get_sprite_frame_for_blit
Ghidra fn LoadSpriteComplex → DisplayGfx__GetSpriteFrameForBlit
Ghidra fn FUN_004FAD30 (alias → Sprite__GetFrameForBlit
SPRITE_COMPLEX_SPRITE_PATH)
Ghidra fn FUN_004F9710 (alias → SpriteBank__GetFrameForBlit
SPRITE_COMPLEX_BANK_PATH)

Replace render.rs's raw `vtable_ptr.add(33)` transmute in `blit_sprite`
with the typed `DisplayGfx::get_sprite_frame_for_blit_raw(...)` wrapper
generated by the #[vtable] macro — much harder to mis-call now that the
slot has a real signature instead of nine `u32` placeholders. Also fix
the cascading "load complex" wording in SpriteBank/DisplayBase docs and
the Ghidra plate comments on all three renamed functions.

All 15 headless replay tests pass.

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

crates/openwa-core/src/address.rs CHANGED
@@ -93,8 +93,8 @@ pub mod va {
93
  pub use crate::render::sprite::sprite::SPRITE_BANK_VTABLE;
94
  pub use crate::render::sprite::sprite::{
95
  CONSTRUCT_SPRITE, DESTROY_SPRITE, FREE_SPRITE_OBJECT, LOAD_SPRITE_BY_NAME,
96
- LOAD_SPRITE_FROM_VFS, PROCESS_SPRITE, SPRITE_BANK_GET_INFO, SPRITE_BANK_INIT,
97
- SPRITE_COMPLEX_BANK_PATH, SPRITE_COMPLEX_SPRITE_PATH, SPRITE_GET_INFO, SPRITE_VTABLE,
98
  };
99
 
100
  crate::define_addresses! {
 
93
  pub use crate::render::sprite::sprite::SPRITE_BANK_VTABLE;
94
  pub use crate::render::sprite::sprite::{
95
  CONSTRUCT_SPRITE, DESTROY_SPRITE, FREE_SPRITE_OBJECT, LOAD_SPRITE_BY_NAME,
96
+ LOAD_SPRITE_FROM_VFS, PROCESS_SPRITE, SPRITE_BANK_GET_FRAME_FOR_BLIT, SPRITE_BANK_GET_INFO,
97
+ SPRITE_BANK_INIT, SPRITE_GET_FRAME_FOR_BLIT, SPRITE_GET_INFO, SPRITE_VTABLE,
98
  };
99
 
100
  crate::define_addresses! {
crates/openwa-core/src/render/display/base.rs CHANGED
@@ -41,7 +41,8 @@ pub struct DisplayBase<V: Vtable = *const DisplayBaseVtable> {
41
  // Points to Sprite objects (0x70 bytes, vtable 0x66418C). Checked by IsSpriteLoaded/GetSpriteInfo.
42
  pub sprite_ptrs: [*mut Sprite; 0x400],
43
  // +0x2008..0x3008: per-slot SpriteBank* pointers (0x400 entries), zeroed by ctor.
44
- // Points to SpriteBank objects (0x17C bytes). Fallback path in GetSpriteInfo/LoadSpriteComplex.
 
45
  pub sprite_banks: [*mut SpriteBank; 0x400],
46
  // +0x3008..0x3018: gap (0x10 bytes, 4 u32s: indices 0xC02..0xC05)
47
  pub _gap_3008: [u32; 4],
 
41
  // Points to Sprite objects (0x70 bytes, vtable 0x66418C). Checked by IsSpriteLoaded/GetSpriteInfo.
42
  pub sprite_ptrs: [*mut Sprite; 0x400],
43
  // +0x2008..0x3008: per-slot SpriteBank* pointers (0x400 entries), zeroed by ctor.
44
+ // Points to SpriteBank objects (0x17C bytes). Fallback path in
45
+ // GetSpriteInfo and GetSpriteFrameForBlit (vtable slot 33).
46
  pub sprite_banks: [*mut SpriteBank; 0x400],
47
  // +0x3008..0x3018: gap (0x10 bytes, 4 u32s: indices 0xC02..0xC05)
48
  pub _gap_3008: [u32; 4],
crates/openwa-core/src/render/display/vtable.rs CHANGED
@@ -274,22 +274,49 @@ pub struct DisplayVtable {
274
  /// Returns 1 if the sprite exists in any of the three sprite arrays.
275
  #[slot(32)]
276
  pub is_sprite_loaded: fn(this: *mut DisplayGfx, id: i32) -> u32,
277
- /// load sprite with complex params (0x5237C0, RET 0x24)
 
278
  ///
279
- /// 9 stack params. Dual-path loading depending on layer type.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  #[slot(33)]
281
- pub load_sprite_complex: fn(
282
  this: *mut DisplayGfx,
283
- layer: i32,
284
- p3: u32,
285
- p4: u32,
286
- p5: u32,
287
- p6: u32,
288
- p7: u32,
289
- p8: u32,
290
- p9: u32,
291
- p10: u32,
292
- ) -> u32,
293
  /// load .fnt bitmap font into a font slot (0x523560, RET 0x10)
294
  #[slot(34)]
295
  pub load_font: fn(
@@ -2047,9 +2074,13 @@ pub unsafe fn load_sprite_by_layer(
2047
  1
2048
  }
2049
 
2050
- // LoadSpriteComplex (vtable slot 33, 0x5237C0) is NOT ported — the internal
2051
- // dispatch functions (0x4FAD30, 0x4F9710) use ESI for sprite/bank pointers
2052
- // in complex usercall conventions that are impractical to bridge.
 
 
 
 
2053
 
2054
  // =========================================================================
2055
  // Font loading
 
274
  /// Returns 1 if the sprite exists in any of the three sprite arrays.
275
  #[slot(32)]
276
  pub is_sprite_loaded: fn(this: *mut DisplayGfx, id: i32) -> u32,
277
+ /// look up a sprite frame's surface and metadata for blitting
278
+ /// (0x5237C0, RET 0x24) — `DisplayGfx::GetSpriteFrameForBlit`.
279
  ///
280
+ /// **Not a "load" function** despite the historic mis-name. Called every
281
+ /// frame from `BlitSprite` (slot 19) to resolve a sprite ID + animation
282
+ /// value into a renderable frame: clamps the animation value, looks up
283
+ /// the matching frame entry in the sprite's frame table, lazily
284
+ /// decompresses the frame's surface via `FUN_004FA950`, and returns
285
+ /// the surface pointer plus the frame's bounding box.
286
+ ///
287
+ /// Dispatches via the two arrays at `DisplayGfx + 0x1008` (Sprite*)
288
+ /// and `DisplayGfx + 0x2008` (SpriteBank*) — both lookups go through
289
+ /// the same outputs:
290
+ ///
291
+ /// | Output | Meaning |
292
+ /// |--------------------------------|--------------------------------------------------|
293
+ /// | return value | `*mut DisplayBitGrid` — decompressed frame surface |
294
+ /// | `out_w`, `out_h` | full sprite cell width / height (for centering) |
295
+ /// | `out_left`/`top`/`right`/`bot` | frame bounding box within the cell |
296
+ /// | `out_anim_frac` | sub-frame interpolation value (Fixed16) or 0 |
297
+ ///
298
+ /// `Sprite*` path → `Sprite__GetFrameForBlit` (FUN_004FAD30, ESI=sprite).
299
+ /// `SpriteBank*` path → `SpriteBank__GetFrameForBlit` (FUN_004F9710,
300
+ /// ESI=bank). Both inner helpers use ESI for the receiver in a complex
301
+ /// usercall convention, which is why this slot has not been ported yet —
302
+ /// it would require porting the FrameCache and the surface decompression
303
+ /// helper (`FUN_004FA950` / `FUN_005B29E0`) first.
304
+ ///
305
+ /// `BlitSprite` calls this directly via `vtable[33]` today; see
306
+ /// `replacements/render.rs::blit_sprite`.
307
  #[slot(33)]
308
+ pub get_sprite_frame_for_blit: fn(
309
  this: *mut DisplayGfx,
310
+ sprite_id: u32,
311
+ anim_value: u32,
312
+ out_w: *mut i32,
313
+ out_h: *mut i32,
314
+ out_left: *mut i32,
315
+ out_top: *mut i32,
316
+ out_right: *mut i32,
317
+ out_bottom: *mut i32,
318
+ out_anim_frac: *mut u32,
319
+ ) -> *mut DisplayBitGrid,
320
  /// load .fnt bitmap font into a font slot (0x523560, RET 0x10)
321
  #[slot(34)]
322
  pub load_font: fn(
 
2074
  1
2075
  }
2076
 
2077
+ // GetSpriteFrameForBlit (vtable slot 33, 0x5237C0) is NOT ported — it is
2078
+ // hot-path though, called from our own `blit_sprite` (slot 19) on every
2079
+ // sprite render via the raw vtable pointer. The two inner helpers
2080
+ // (Sprite__GetFrameForBlit at 0x4FAD30, SpriteBank__GetFrameForBlit at
2081
+ // 0x4F9710) use ESI for the sprite/bank receiver in a usercall convention
2082
+ // that's impractical to bridge directly. Porting requires first porting
2083
+ // the FrameCache + decompression chain (FUN_004FA950 → FUN_005B29E0).
2084
 
2085
  // =========================================================================
2086
  // Font loading
crates/openwa-core/src/render/sprite/sprite.rs CHANGED
@@ -104,8 +104,8 @@ const _: () = assert!(core::mem::offset_of!(Sprite, raw_frame_header_ptr) == 0x6
104
  /// Created by LoadSpriteEx (0x523310): allocated 0x17C bytes, first 0x15C zeroed,
105
  /// then constructed and initialized via FUN_004f95a0.
106
  ///
107
- /// Used by GetSpriteInfo (0x523500) and LoadSpriteComplex (0x5237C0) as the
108
- /// fallback path when sprite_ptrs[id] is null.
109
  #[repr(C)]
110
  pub struct SpriteBank {
111
  /// 0x00: Vtable pointer (0x664180, 3 slots)
@@ -266,9 +266,13 @@ crate::define_addresses! {
266
  fn/Usercall LOAD_SPRITE_BY_NAME = 0x0057_33B0;
267
  /// Free sprite object (with sub-object cleanup) — usercall(EDI=sprite), plain RET
268
  fn/Usercall FREE_SPRITE_OBJECT = 0x0056_A2F0;
269
- /// Sprite-path dispatch for LoadSpriteComplex — usercall on Sprite*
270
- fn/Usercall SPRITE_COMPLEX_SPRITE_PATH = 0x004F_AD30;
271
- /// SpriteBank-path dispatch for LoadSpriteComplex — usercall on SpriteBank*
272
- fn/Usercall SPRITE_COMPLEX_BANK_PATH = 0x004F_9710;
 
 
 
 
273
  }
274
  }
 
104
  /// Created by LoadSpriteEx (0x523310): allocated 0x17C bytes, first 0x15C zeroed,
105
  /// then constructed and initialized via FUN_004f95a0.
106
  ///
107
+ /// Used by GetSpriteInfo (0x523500) and GetSpriteFrameForBlit (0x5237C0,
108
+ /// vtable slot 33) as the fallback path when sprite_ptrs[id] is null.
109
  #[repr(C)]
110
  pub struct SpriteBank {
111
  /// 0x00: Vtable pointer (0x664180, 3 slots)
 
266
  fn/Usercall LOAD_SPRITE_BY_NAME = 0x0057_33B0;
267
  /// Free sprite object (with sub-object cleanup) — usercall(EDI=sprite), plain RET
268
  fn/Usercall FREE_SPRITE_OBJECT = 0x0056_A2F0;
269
+ /// `Sprite__GetFrameForBlit` — usercall on Sprite* (ESI=sprite).
270
+ /// Looks up an animation frame in a Sprite, lazily decompresses its
271
+ /// surface, and returns the frame metadata. Called by
272
+ /// `DisplayGfx::GetSpriteFrameForBlit` (slot 33) for sprite_ptrs IDs.
273
+ fn/Usercall SPRITE_GET_FRAME_FOR_BLIT = 0x004F_AD30;
274
+ /// `SpriteBank__GetFrameForBlit` — usercall on SpriteBank* (ESI=bank).
275
+ /// Same as above but for SpriteBank-backed sprite IDs (sprite_banks).
276
+ fn/Usercall SPRITE_BANK_GET_FRAME_FOR_BLIT = 0x004F_9710;
277
  }
278
  }
crates/openwa-dll/src/replacements/render.rs CHANGED
@@ -756,7 +756,6 @@ unsafe extern "thiscall" fn blit_sprite(
756
  sprite_flags: u32,
757
  palette: u32,
758
  ) {
759
- use openwa_core::bitgrid::DisplayBitGrid;
760
  use openwa_core::render::display::gfx::DisplayGfx;
761
  use openwa_core::render::display::vtable as display_vtable;
762
 
@@ -891,43 +890,21 @@ unsafe extern "thiscall" fn blit_sprite(
891
  }
892
 
893
  // ---------------------------------------------------------------
894
- // Sprite data lookup via vtable[33]
895
  // ---------------------------------------------------------------
896
- let vtable_ptr = *(this as *const *const u32);
897
- let slot33_addr = *vtable_ptr.add(33);
898
-
899
- // vtable[33] is thiscall with 9 stack params (RET 0x24).
900
- // Output semantics (traced from ASM ESP offsets through LEA/PUSH sequence):
901
- // param 3 -> sprite full width (for centering)
902
- // param 4 -> sprite full height (for centering)
903
- // param 5 -> render rect LEFT
904
- // param 6 -> render rect TOP (overwrites palette on original stack!)
905
- // param 7 -> render rect RIGHT
906
- // param 8 -> render rect BOTTOM
907
- // param 9 -> unknown (unused)
908
  let mut out_sprite_w: i32 = 0;
909
  let mut out_sprite_h: i32 = 0;
910
  let mut out_rect_left: i32 = 0;
911
  let mut out_rect_top: i32 = 0;
912
  let mut out_rect_right: i32 = 0;
913
  let mut out_rect_bottom: i32 = 0;
914
- let mut out_unknown: u32 = 0;
915
-
916
- let fn33: unsafe extern "thiscall" fn(
917
- *mut DisplayGfx,
918
- u32,
919
- u32,
920
- *mut i32,
921
- *mut i32,
922
- *mut i32,
923
- *mut i32,
924
- *mut i32,
925
- *mut i32,
926
- *mut u32,
927
- ) -> *mut DisplayBitGrid = core::mem::transmute(slot33_addr as usize);
928
-
929
- let mut sprite_surface = fn33(
930
- this,
931
  sprite_id,
932
  pal,
933
  &mut out_sprite_w,
@@ -936,7 +913,7 @@ unsafe extern "thiscall" fn blit_sprite(
936
  &mut out_rect_top,
937
  &mut out_rect_right,
938
  &mut out_rect_bottom,
939
- &mut out_unknown,
940
  );
941
 
942
  if sprite_surface.is_null() {
@@ -1474,9 +1451,10 @@ unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
1474
 
1475
  static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
1476
 
1477
- // LoadSpriteComplex (slot 33) is NOT ported — its internal functions
1478
- // (0x4FAD30, 0x4F9710) use ESI for sprite/bank pointers in complex
1479
- // usercall conventions that are impractical to bridge.
 
1480
 
1481
  unsafe extern "thiscall" fn load_sprite_by_layer(
1482
  this: *mut DisplayGfx,
 
756
  sprite_flags: u32,
757
  palette: u32,
758
  ) {
 
759
  use openwa_core::render::display::gfx::DisplayGfx;
760
  use openwa_core::render::display::vtable as display_vtable;
761
 
 
890
  }
891
 
892
  // ---------------------------------------------------------------
893
+ // Sprite frame lookup via DisplayGfx::GetSpriteFrameForBlit (slot 33).
894
  // ---------------------------------------------------------------
895
+ // Resolves the sprite ID + animation value into the actual decompressed
896
+ // frame surface plus its bounding box and full sprite-cell dimensions
897
+ // (used below for centering and clipped blits).
 
 
 
 
 
 
 
 
 
898
  let mut out_sprite_w: i32 = 0;
899
  let mut out_sprite_h: i32 = 0;
900
  let mut out_rect_left: i32 = 0;
901
  let mut out_rect_top: i32 = 0;
902
  let mut out_rect_right: i32 = 0;
903
  let mut out_rect_bottom: i32 = 0;
904
+ let mut out_anim_frac: u32 = 0;
905
+
906
+ let mut sprite_surface = DisplayGfx::get_sprite_frame_for_blit_raw(
907
+ gfx,
 
 
 
 
 
 
 
 
 
 
 
 
 
908
  sprite_id,
909
  pal,
910
  &mut out_sprite_w,
 
913
  &mut out_rect_top,
914
  &mut out_rect_right,
915
  &mut out_rect_bottom,
916
+ &mut out_anim_frac,
917
  );
918
 
919
  if sprite_surface.is_null() {
 
1451
 
1452
  static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
1453
 
1454
+ // GetSpriteFrameForBlit (slot 33) is NOT ported — see the docstring on
1455
+ // `DisplayVtable::get_sprite_frame_for_blit` in openwa-core for the full
1456
+ // rationale. Our `blit_sprite` (slot 19) above calls it via the bound
1457
+ // vtable wrapper on every sprite render.
1458
 
1459
  unsafe extern "thiscall" fn load_sprite_by_layer(
1460
  this: *mut DisplayGfx,