refactor: rename mis-labeled DisplayGfx slot 33 to GetSpriteFrameForBlit
Browse filesSlot 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>
|
@@ -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,
|
| 97 |
-
|
| 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! {
|
|
@@ -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
|
|
|
|
| 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],
|
|
@@ -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 |
-
///
|
|
|
|
| 278 |
///
|
| 279 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
#[slot(33)]
|
| 281 |
-
pub
|
| 282 |
this: *mut DisplayGfx,
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
) ->
|
| 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 |
-
//
|
| 2051 |
-
//
|
| 2052 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
@@ -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
|
| 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 |
-
///
|
| 270 |
-
|
| 271 |
-
///
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|
|
@@ -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
|
| 895 |
// ---------------------------------------------------------------
|
| 896 |
-
|
| 897 |
-
|
| 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
|
| 915 |
-
|
| 916 |
-
let
|
| 917 |
-
|
| 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
|
| 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 |
-
//
|
| 1478 |
-
//
|
| 1479 |
-
//
|
|
|
|
| 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,
|