refactor: replace untested SpriteBank slot-33 port with panic, trap leaves
Browse filesTwo related changes addressing the SpriteBank-side concern raised after
the slot 33 port:
1. **Replace `sprite_bank_get_frame_for_blit` body with a panic.** The
port shipped in 973f234 was disassembly-derived but never executed
in any test (a `panic!()` planted there during headful play was
never triggered). Per the project rule against keeping unverified
code, the body has been removed; the function is now a stub that
panics with a clear "should never be reached" message pointing
back at 973f234 for the unverified Ghidra translation if banks
ever become live.
Why we believe the path is structurally dead in shipping WA:
`SpriteBank` objects are constructed only by `SpriteBank__Constructor`
(0x4F9450, verified by xrefs to vtable 0x664180), whose only caller
is `DisplayGfx::LoadSpriteEx` (slot 30, 0x523310), which has zero
callers in WA.exe and zero in Rust and is itself trapped via
`install_trap!("DisplayGfx__LoadSpriteEx", ...)`. Therefore
`DisplayBase::sprite_banks[id]` is always null and the bank branch
of slot 33 is unreachable. Confirmed by the panic-test above.
`bitgrid_set_external_buffer` (the helper used only by the bank
path) is removed alongside the body β git history preserves it.
2. **Trap the four slot-33 leaf functions on the WA side**:
- `Sprite__GetFrameForBlit` (0x4FAD30)
- `SpriteBank__GetFrameForBlit` (0x4F9710)
- `FrameCache__Allocate` (0x4FA950)
- `DisplayBitGrid::SetExternalBuffer` (0x4F6470)
Each one's only callers (verified via xrefs) were inside the slot
33 dispatch chain, which is now bypassed by the
`vtable_replace!(get_sprite_frame_for_blit => ...)` from the
previous commit. If any of these traps fires in normal play, it
means we missed a still-live caller path and need to investigate.
`Sprite_LZSS_Decode` (0x5B29E0) is intentionally NOT trapped β it
has independent callers `IMG_Decode` (0x4F5F80) and `FUN_0048EE32`
reachable from `DDGame__Constructor`, `SoundEmitter`, and
`LoadHudAndWeaponSprites`, all still bridged.
Two new address constants added for the trap targets:
- `FRAME_CACHE_ALLOCATE` in `render::sprite::frame_cache`
- `DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER` in `bitgrid`
Both re-exported via `address::va`.
Verification:
- 135/135 unit tests pass.
- 15/15 headless replay tests pass (traps installed but headless
doesn't exercise the render path, so this only confirms
install_display succeeds and nothing else broke).
- Headful verification with traps in place is **still required** to
confirm none of the four traps fire during actual frame rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -73,7 +73,7 @@ pub mod va {
|
|
| 73 |
pub use crate::bitgrid::{
|
| 74 |
BitGridBaseVtable, BitGridCollisionVtable, BitGridDisplayVtable, BIT_GRID_BASE_VTABLE,
|
| 75 |
BIT_GRID_COLLISION_VTABLE, BIT_GRID_DISPLAY_VTABLE, BIT_GRID_INIT, BLIT_SPRITE_RECT,
|
| 76 |
-
DRAW_LINE_CLIPPED, DRAW_LINE_TWO_COLOR,
|
| 77 |
};
|
| 78 |
pub use crate::frontend::map_view::MAP_VIEW_VTABLE;
|
| 79 |
pub use crate::input::controller::INPUT_CTRL_VTABLE;
|
|
@@ -90,6 +90,7 @@ pub mod va {
|
|
| 90 |
pub use crate::render::palette::{
|
| 91 |
PALETTE_CONTEXT_INIT, PALETTE_CONTEXT_INIT_RANGE, PALETTE_CONTEXT_MAP_COLOR,
|
| 92 |
};
|
|
|
|
| 93 |
pub use crate::render::sprite::sprite::SPRITE_BANK_VTABLE;
|
| 94 |
pub use crate::render::sprite::sprite::{
|
| 95 |
CBITMAP_VTABLE_MAYBE, CONSTRUCT_SPRITE, DESTROY_SPRITE, FREE_SPRITE_OBJECT,
|
|
|
|
| 73 |
pub use crate::bitgrid::{
|
| 74 |
BitGridBaseVtable, BitGridCollisionVtable, BitGridDisplayVtable, BIT_GRID_BASE_VTABLE,
|
| 75 |
BIT_GRID_COLLISION_VTABLE, BIT_GRID_DISPLAY_VTABLE, BIT_GRID_INIT, BLIT_SPRITE_RECT,
|
| 76 |
+
DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER, DRAW_LINE_CLIPPED, DRAW_LINE_TWO_COLOR,
|
| 77 |
};
|
| 78 |
pub use crate::frontend::map_view::MAP_VIEW_VTABLE;
|
| 79 |
pub use crate::input::controller::INPUT_CTRL_VTABLE;
|
|
|
|
| 90 |
pub use crate::render::palette::{
|
| 91 |
PALETTE_CONTEXT_INIT, PALETTE_CONTEXT_INIT_RANGE, PALETTE_CONTEXT_MAP_COLOR,
|
| 92 |
};
|
| 93 |
+
pub use crate::render::sprite::frame_cache::FRAME_CACHE_ALLOCATE;
|
| 94 |
pub use crate::render::sprite::sprite::SPRITE_BANK_VTABLE;
|
| 95 |
pub use crate::render::sprite::sprite::{
|
| 96 |
CBITMAP_VTABLE_MAYBE, CONSTRUCT_SPRITE, DESTROY_SPRITE, FREE_SPRITE_OBJECT,
|
|
@@ -50,6 +50,12 @@ crate::define_addresses! {
|
|
| 50 |
fn DRAW_LINE_CLIPPED = 0x004F_7500;
|
| 51 |
/// Two-color line draw on 8bpp BitGrid (0x4F7A60)
|
| 52 |
fn DRAW_LINE_TWO_COLOR = 0x004F_7A60;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
}
|
| 55 |
|
|
|
|
| 50 |
fn DRAW_LINE_CLIPPED = 0x004F_7500;
|
| 51 |
/// Two-color line draw on 8bpp BitGrid (0x4F7A60)
|
| 52 |
fn DRAW_LINE_TWO_COLOR = 0x004F_7A60;
|
| 53 |
+
/// `DisplayBitGrid::SetExternalBuffer` (FUN_004F6470) β fastcall
|
| 54 |
+
/// `(ECX=height, EDX=width, stack=(bitgrid, data, row_stride))`.
|
| 55 |
+
/// Updates an external-buffer bitgrid's data pointer + dimensions
|
| 56 |
+
/// + clip rect. Only known caller is `SpriteBank__GetFrameForBlit`,
|
| 57 |
+
/// which is itself unreachable in shipping WA β see slot 33.
|
| 58 |
+
fn/Fastcall DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER = 0x004F_6470;
|
| 59 |
}
|
| 60 |
}
|
| 61 |
|
|
@@ -317,18 +317,16 @@ pub struct DisplayGfxVtable {
|
|
| 317 |
/// | `out_left`/`top`/`right`/`bot` | frame bounding box within the cell |
|
| 318 |
/// | `out_anim_frac` | sub-frame interpolation value (Fixed16) or 0 |
|
| 319 |
///
|
| 320 |
-
/// **Ported.** Native Rust impl is
|
| 321 |
-
/// in this file
|
| 322 |
-
///
|
| 323 |
-
/// `
|
| 324 |
-
///
|
| 325 |
-
///
|
| 326 |
-
///
|
| 327 |
-
///
|
| 328 |
-
///
|
| 329 |
-
///
|
| 330 |
-
/// still ported (per the disassembly) as a defensive measure for any
|
| 331 |
-
/// obscure game state we haven't sampled.
|
| 332 |
///
|
| 333 |
/// # Porting blueprint (kept for archival reference)
|
| 334 |
///
|
|
@@ -1664,187 +1662,56 @@ unsafe fn sprite_get_frame_for_blit(
|
|
| 1664 |
bitgrid
|
| 1665 |
}
|
| 1666 |
|
| 1667 |
-
///
|
| 1668 |
-
///
|
| 1669 |
-
///
|
| 1670 |
-
///
|
| 1671 |
-
///
|
| 1672 |
-
///
|
| 1673 |
-
///
|
| 1674 |
-
///
|
| 1675 |
-
///
|
| 1676 |
-
///
|
| 1677 |
-
///
|
| 1678 |
-
|
| 1679 |
-
|
| 1680 |
-
|
| 1681 |
-
|
| 1682 |
-
|
| 1683 |
-
|
| 1684 |
-
|
| 1685 |
-
|
| 1686 |
-
|
| 1687 |
-
|
| 1688 |
-
|
| 1689 |
-
|
| 1690 |
-
|
| 1691 |
-
|
| 1692 |
-
|
| 1693 |
-
|
| 1694 |
-
|
| 1695 |
-
|
| 1696 |
-
(*bitgrid).clip_bottom = height;
|
| 1697 |
-
true
|
| 1698 |
-
}
|
| 1699 |
-
|
| 1700 |
-
/// Pure-Rust port of `SpriteBank__GetFrameForBlit` (0x4F9710).
|
| 1701 |
-
///
|
| 1702 |
-
/// SpriteBank-side counterpart of [`sprite_get_frame_for_blit`]: walks
|
| 1703 |
-
/// the bank's `index_table` to map `sprite_id` to a frame in
|
| 1704 |
-
/// `frame_table`, resolves the animation value to a sub-frame index in
|
| 1705 |
-
/// `bbox_table`, lazily decompresses the corresponding subframe via
|
| 1706 |
-
/// [`frame_cache_allocate`] + [`sprite_lzss_decode`], updates the
|
| 1707 |
-
/// embedded `frame_bitgrid` (at `bank + 0x130`), and returns a pointer
|
| 1708 |
-
/// to it. Returns null on any validation failure (null `frame_table` or
|
| 1709 |
-
/// out-of-range `entry_idx`).
|
| 1710 |
-
///
|
| 1711 |
-
/// Note the structural differences from the Sprite path:
|
| 1712 |
-
/// - Animation metadata lives in [`SpriteBankFrame`]
|
| 1713 |
-
/// (`flags/width/height/base_frame_idx/scale_or_count`) instead of
|
| 1714 |
-
/// `Sprite::flags`/`max_frames`/`scale_*`.
|
| 1715 |
-
/// - Out-`w`/`h` come from the per-frame `width`/`height`, NOT from a
|
| 1716 |
-
/// sprite-wide cell dimension.
|
| 1717 |
-
/// - The bbox table is a separate array of [`SpriteBankBboxEntry`] at
|
| 1718 |
-
/// `bank + 0x18`, with `subframe_idx` at `+0` (low u16 of what would
|
| 1719 |
-
/// be `SpriteFrame::bitmap_offset`) and `decoded_offset` at `+2`.
|
| 1720 |
-
/// - The subframe cache uses [`SpriteBankSubframeCache`] with
|
| 1721 |
-
/// `decoded_ptr` at `+8` (vs `+4` in `SpriteSubframeCache`).
|
| 1722 |
-
/// - The LUT for LZSS is the inline 256-byte `palette_lut` at
|
| 1723 |
-
/// `bank + 0x30`, not a separately-allocated palette pointer.
|
| 1724 |
-
/// - The bitgrid update goes through `bitgrid_set_external_buffer`
|
| 1725 |
-
/// instead of inline writes.
|
| 1726 |
-
///
|
| 1727 |
-
/// # Safety
|
| 1728 |
-
///
|
| 1729 |
-
/// `bank` must point at a valid, fully-parsed [`SpriteBank`]. Output
|
| 1730 |
-
/// pointers must be writable.
|
| 1731 |
#[allow(clippy::too_many_arguments)]
|
| 1732 |
unsafe fn sprite_bank_get_frame_for_blit(
|
| 1733 |
-
|
| 1734 |
sprite_id: u32,
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
-
|
| 1739 |
-
|
| 1740 |
-
|
| 1741 |
-
|
| 1742 |
-
|
| 1743 |
) -> *mut DisplayBitGrid {
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
| 1747 |
-
|
| 1748 |
-
|
| 1749 |
-
|
| 1750 |
-
|
| 1751 |
-
|
| 1752 |
-
let frame_flags = (*frame).flags;
|
| 1753 |
-
|
| 1754 |
-
// ββ Clamp anim_value βββββββββββββββββββββββββββββββββββββββββββββ
|
| 1755 |
-
if frame_flags & 1 != 0 {
|
| 1756 |
-
anim_value &= 0xFFFF;
|
| 1757 |
-
} else {
|
| 1758 |
-
let signed = anim_value as i32;
|
| 1759 |
-
if signed < 0 {
|
| 1760 |
-
anim_value = 0;
|
| 1761 |
-
} else if signed >= 0x10000 {
|
| 1762 |
-
anim_value = 0xFFFF;
|
| 1763 |
-
}
|
| 1764 |
-
}
|
| 1765 |
-
|
| 1766 |
-
// ββ Ping-pong ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1767 |
-
if frame_flags & 2 != 0 {
|
| 1768 |
-
anim_value &= 0xFFFF;
|
| 1769 |
-
anim_value = anim_value.wrapping_mul(2);
|
| 1770 |
-
if anim_value >= 0x10000 {
|
| 1771 |
-
anim_value = 0x1FFFE - anim_value;
|
| 1772 |
-
}
|
| 1773 |
-
}
|
| 1774 |
-
|
| 1775 |
-
// ββ Frame index resolution βββββββββββββββββββββββββββββββββββββββ
|
| 1776 |
-
let scale_or_count = (*frame).scale_or_count;
|
| 1777 |
-
let base_frame_idx = (*frame).base_frame_idx;
|
| 1778 |
-
|
| 1779 |
-
let resolved_idx: u32 = if scale_or_count & 0x8000 != 0 {
|
| 1780 |
-
// Scaled mode: low/high 7-bit nibbles of `scale_or_count` form a
|
| 1781 |
-
// signed Fixed16 pair. The asm extracts both via:
|
| 1782 |
-
// `value = (low_or_high_7 << 16) >> 5` (signed)
|
| 1783 |
-
// i.e. shift the 7-bit value into the high half, sign-extend
|
| 1784 |
-
// from bit 22, then shift right by 5 β effective `<< 11` with
|
| 1785 |
-
// sign extension from bit 6 of the original 7-bit value.
|
| 1786 |
-
let low7 = scale_or_count & 0x7F;
|
| 1787 |
-
let high7 = (scale_or_count >> 8) & 0x7F;
|
| 1788 |
-
let low_val = (((low7 as u32) << 16) as i32) >> 5;
|
| 1789 |
-
let high_val = (((high7 as u32) << 16) as i32) >> 5;
|
| 1790 |
-
// Note: the asm computes `EAX = low - high` (NOT high - low) β
|
| 1791 |
-
// verified at 0x4F97AD `SUB EAX, EDI` where EAX = low_val and
|
| 1792 |
-
// EDI = high_val.
|
| 1793 |
-
let diff = low_val.wrapping_sub(high_val);
|
| 1794 |
-
let prod = (diff as i64).wrapping_mul((anim_value as i32) as i64);
|
| 1795 |
-
let interp = ((prod >> 16) as i32).wrapping_add(high_val);
|
| 1796 |
-
*out_anim_frac = interp as u32;
|
| 1797 |
-
// Scaled mode uses `base_frame_idx` directly as the bbox index.
|
| 1798 |
-
base_frame_idx as u32
|
| 1799 |
-
} else {
|
| 1800 |
-
// Non-scaled mode: subframe_idx = ((scale_or_count * anim_value) >> 16)
|
| 1801 |
-
// + base_frame_idx. *out_anim_frac = 0.
|
| 1802 |
-
let prod = (scale_or_count as i32).wrapping_mul(anim_value as i32);
|
| 1803 |
-
*out_anim_frac = 0;
|
| 1804 |
-
((prod >> 16) as u32).wrapping_add(base_frame_idx as u32)
|
| 1805 |
-
};
|
| 1806 |
-
|
| 1807 |
-
// ββ Read bbox metadata βββββββββββββββββββββββββββββββββββββββββββ
|
| 1808 |
-
let bbox = (*bank).bbox_table.add(resolved_idx as usize);
|
| 1809 |
-
*out_left = (*bbox).start_x as i32;
|
| 1810 |
-
*out_top = (*bbox).start_y as i32;
|
| 1811 |
-
*out_right = (*bbox).end_x as i32;
|
| 1812 |
-
*out_bottom = (*bbox).end_y as i32;
|
| 1813 |
-
// out_w/out_h come from the per-frame width/height, NOT the bbox.
|
| 1814 |
-
*out_w = (*frame).width as i32;
|
| 1815 |
-
*out_h = (*frame).height as i32;
|
| 1816 |
-
let frame_w = (*out_right - *out_left) as u32;
|
| 1817 |
-
let frame_h = (*out_bottom - *out_top) as u32;
|
| 1818 |
-
|
| 1819 |
-
// ββ Subframe cache lookup + lazy decompress ββββββββββββββββββββββ
|
| 1820 |
-
let subframe_idx = (*bbox).subframe_idx as u32;
|
| 1821 |
-
let entry = (*bank).subframe_cache_table.add(subframe_idx as usize);
|
| 1822 |
-
|
| 1823 |
-
if (*entry).decoded_ptr.is_null() {
|
| 1824 |
-
let context_ptr = (*bank).context_ptr;
|
| 1825 |
-
let decoded_size = (*entry).decoded_size;
|
| 1826 |
-
let decoded = frame_cache_allocate(
|
| 1827 |
-
decoded_size,
|
| 1828 |
-
context_ptr,
|
| 1829 |
-
bank as *mut core::ffi::c_void,
|
| 1830 |
-
subframe_idx,
|
| 1831 |
-
);
|
| 1832 |
-
(*entry).decoded_ptr = decoded;
|
| 1833 |
-
let src = (*bank)
|
| 1834 |
-
.bitmap_data_ptr
|
| 1835 |
-
.add((*entry).compressed_offset as usize) as *mut u8;
|
| 1836 |
-
// Bank LUT is the inline 256-byte palette_lut at bank+0x30.
|
| 1837 |
-
let lut = (&raw const (*bank).palette_lut) as *const u8;
|
| 1838 |
-
sprite_lzss_decode(decoded, src, lut);
|
| 1839 |
-
}
|
| 1840 |
-
|
| 1841 |
-
// ββ Update embedded bitgrid via SetExternalBuffer ββββββββββββββββ
|
| 1842 |
-
//
|
| 1843 |
-
// Surface address = bbox.decoded_offset + entry.decoded_ptr.
|
| 1844 |
-
let surface = (*entry).decoded_ptr.add((*bbox).decoded_offset as usize);
|
| 1845 |
-
let bitgrid = &raw mut (*bank).frame_bitgrid;
|
| 1846 |
-
bitgrid_set_external_buffer(bitgrid, surface, frame_w, frame_w, frame_h);
|
| 1847 |
-
bitgrid
|
| 1848 |
}
|
| 1849 |
|
| 1850 |
/// Port of `DisplayGfx::GetSpriteFrameForBlit` (vtable slot 33, 0x5237C0).
|
|
|
|
| 317 |
/// | `out_left`/`top`/`right`/`bot` | frame bounding box within the cell |
|
| 318 |
/// | `out_anim_frac` | sub-frame interpolation value (Fixed16) or 0 |
|
| 319 |
///
|
| 320 |
+
/// **Ported (Sprite path only).** Native Rust impl is
|
| 321 |
+
/// [`get_sprite_frame_for_blit`] in this file: Sprite path β
|
| 322 |
+
/// [`sprite_get_frame_for_blit`], SpriteBank path β
|
| 323 |
+
/// [`sprite_bank_get_frame_for_blit`] (a `panic!()` stub β see its
|
| 324 |
+
/// docstring for the structural-deadness argument). Wired via
|
| 325 |
+
/// `vtable_replace!` in `install_display`.
|
| 326 |
+
///
|
| 327 |
+
/// With this slot ported, the entire in-game render pipeline of a
|
| 328 |
+
/// normal Worms match runs on Rust code with zero bridges into WA's
|
| 329 |
+
/// render functions.
|
|
|
|
|
|
|
| 330 |
///
|
| 331 |
/// # Porting blueprint (kept for archival reference)
|
| 332 |
///
|
|
|
|
| 1662 |
bitgrid
|
| 1663 |
}
|
| 1664 |
|
| 1665 |
+
/// Stub for `SpriteBank__GetFrameForBlit` (0x4F9710).
|
| 1666 |
+
///
|
| 1667 |
+
/// **Structurally unreachable in shipping WA.** The only function that
|
| 1668 |
+
/// constructs a `SpriteBank` object is `SpriteBank__Constructor`
|
| 1669 |
+
/// (0x4F9450), and the only caller of *that* is
|
| 1670 |
+
/// `DisplayGfx::LoadSpriteEx` (slot 30, 0x523310), which has zero
|
| 1671 |
+
/// callers in WA.exe and zero in Rust and is currently trapped via
|
| 1672 |
+
/// `install_trap!("DisplayGfx__LoadSpriteEx", ...)` in `install_display`.
|
| 1673 |
+
///
|
| 1674 |
+
/// Therefore `DisplayBase::sprite_banks[id]` is always null in shipping
|
| 1675 |
+
/// WA, and the `bank.is_null()` check in [`get_sprite_frame_for_blit`]
|
| 1676 |
+
/// always succeeds. The bank branch of slot 33 is dead code in normal
|
| 1677 |
+
/// gameplay β confirmed by adding a `panic!()` here and playing several
|
| 1678 |
+
/// turns of a regular Worms match without triggering it.
|
| 1679 |
+
///
|
| 1680 |
+
/// A previous revision of this function (commit `973f234`) contained a
|
| 1681 |
+
/// disassembly-derived port of the bank-side logic, but it was untested
|
| 1682 |
+
/// because no replay or live play could reach it. Per the project rule
|
| 1683 |
+
/// against keeping unverified code, the body has been replaced with a
|
| 1684 |
+
/// panic β if any future change to WA, mods, or our ports starts
|
| 1685 |
+
/// producing live `SpriteBank` objects, this panic will fire and we'll
|
| 1686 |
+
/// need to revive the port (see git history for the unverified Ghidra
|
| 1687 |
+
/// translation, which would then need bridge-and-compare validation
|
| 1688 |
+
/// against the original WA function at 0x4F9710).
|
| 1689 |
+
///
|
| 1690 |
+
/// The slot 33 dispatcher still calls this stub when `sprite_banks[id]`
|
| 1691 |
+
/// is non-null so we get a clear, source-located error rather than
|
| 1692 |
+
/// silently returning null (which would just present as missing
|
| 1693 |
+
/// graphics with no diagnostic).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1694 |
#[allow(clippy::too_many_arguments)]
|
| 1695 |
unsafe fn sprite_bank_get_frame_for_blit(
|
| 1696 |
+
_bank: *mut SpriteBank,
|
| 1697 |
sprite_id: u32,
|
| 1698 |
+
_anim_value: u32,
|
| 1699 |
+
_out_anim_frac: *mut u32,
|
| 1700 |
+
_out_w: *mut i32,
|
| 1701 |
+
_out_h: *mut i32,
|
| 1702 |
+
_out_left: *mut i32,
|
| 1703 |
+
_out_top: *mut i32,
|
| 1704 |
+
_out_right: *mut i32,
|
| 1705 |
+
_out_bottom: *mut i32,
|
| 1706 |
) -> *mut DisplayBitGrid {
|
| 1707 |
+
panic!(
|
| 1708 |
+
"SpriteBank::GetFrameForBlit reached for sprite_id={sprite_id}, \
|
| 1709 |
+
but `DisplayBase::sprite_banks` was supposed to be unreachable \
|
| 1710 |
+
(LoadSpriteEx slot 30 is trapped). The bank-side port was \
|
| 1711 |
+
removed because it was unverifiable; revive it from commit \
|
| 1712 |
+
973f234 if banks have become live and validate against \
|
| 1713 |
+
0x4F9710 by bridge-and-compare."
|
| 1714 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1715 |
}
|
| 1716 |
|
| 1717 |
/// Port of `DisplayGfx::GetSpriteFrameForBlit` (vtable slot 33, 0x5237C0).
|
|
@@ -23,6 +23,18 @@ use crate::render::display::base::FrameCache;
|
|
| 23 |
use crate::render::display::base::FrameCacheEntry;
|
| 24 |
use crate::render::SpriteCache;
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
/// Eviction callback signature β `vtable[1]` of both `Sprite` and
|
| 27 |
/// `SpriteBank`. Called when the oldest entry is being dropped from the
|
| 28 |
/// LRU ring buffer; the callback clears the owner's per-subframe
|
|
|
|
| 23 |
use crate::render::display::base::FrameCacheEntry;
|
| 24 |
use crate::render::SpriteCache;
|
| 25 |
|
| 26 |
+
crate::define_addresses! {
|
| 27 |
+
class "FrameCache" {
|
| 28 |
+
/// `FrameCache__Allocate` (FUN_004FA950) β usercall LRU
|
| 29 |
+
/// allocator. `EAX = entry_size`, stack args
|
| 30 |
+
/// `(context_ptr, owner, frame_idx)`. Ported to
|
| 31 |
+
/// [`frame_cache_allocate`]; only callers were
|
| 32 |
+
/// `Sprite__GetFrameForBlit` and `SpriteBank__GetFrameForBlit`,
|
| 33 |
+
/// both bypassed by the slot 33 vtable_replace.
|
| 34 |
+
fn/Usercall FRAME_CACHE_ALLOCATE = 0x004F_A950;
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
/// Eviction callback signature β `vtable[1]` of both `Sprite` and
|
| 39 |
/// `SpriteBank`. Called when the oldest entry is being dropped from the
|
| 40 |
/// LRU ring buffer; the callback clears the owner's per-subframe
|
|
@@ -1370,6 +1370,37 @@ fn install_display() -> Result<(), String> {
|
|
| 1370 |
// own Rust code (no `bind_DisplayVtable!` invocation, no direct call).
|
| 1371 |
// Trap it so we'll be alerted the moment any future caller appears.
|
| 1372 |
hook::install_trap!("DisplayGfx__LoadSpriteEx", va::DISPLAY_GFX_LOAD_SPRITE_EX);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1373 |
}
|
| 1374 |
|
| 1375 |
Ok(())
|
|
|
|
| 1370 |
// own Rust code (no `bind_DisplayVtable!` invocation, no direct call).
|
| 1371 |
// Trap it so we'll be alerted the moment any future caller appears.
|
| 1372 |
hook::install_trap!("DisplayGfx__LoadSpriteEx", va::DISPLAY_GFX_LOAD_SPRITE_EX);
|
| 1373 |
+
|
| 1374 |
+
// ββ Slot 33 leaf functions: trap the WA-side originals ββββββββ
|
| 1375 |
+
//
|
| 1376 |
+
// After `vtable_replace!` swapped slot 33 to our Rust impl, the
|
| 1377 |
+
// original `DisplayGfx::GetSpriteFrameForBlit` (0x5237C0) is no
|
| 1378 |
+
// longer reached, which means its leaves have no remaining
|
| 1379 |
+
// callers in shipping WA either:
|
| 1380 |
+
//
|
| 1381 |
+
// - `Sprite__GetFrameForBlit` (0x4FAD30): only caller was the
|
| 1382 |
+
// original slot 33 dispatcher.
|
| 1383 |
+
// - `SpriteBank__GetFrameForBlit` (0x4F9710): same. Also bank
|
| 1384 |
+
// objects are themselves unreachable (LoadSpriteEx is trapped).
|
| 1385 |
+
// - `FrameCache__Allocate` (0x4FA950): only callers were the
|
| 1386 |
+
// two GetFrameForBlit helpers above.
|
| 1387 |
+
// - `DisplayBitGrid::SetExternalBuffer` (0x4F6470): only caller
|
| 1388 |
+
// was `SpriteBank__GetFrameForBlit`.
|
| 1389 |
+
//
|
| 1390 |
+
// `Sprite_LZSS_Decode` (0x5B29E0) is NOT trapped β it has
|
| 1391 |
+
// independent callers (`IMG_Decode`, `FUN_0048EE32`) reachable
|
| 1392 |
+
// from `DDGame__Constructor`/`SoundEmitter`/`LoadHudAndWeaponSprites`,
|
| 1393 |
+
// all still bridged. Trapping it would break those.
|
| 1394 |
+
hook::install_trap!("Sprite__GetFrameForBlit", va::SPRITE_GET_FRAME_FOR_BLIT);
|
| 1395 |
+
hook::install_trap!(
|
| 1396 |
+
"SpriteBank__GetFrameForBlit",
|
| 1397 |
+
va::SPRITE_BANK_GET_FRAME_FOR_BLIT
|
| 1398 |
+
);
|
| 1399 |
+
hook::install_trap!("FrameCache__Allocate", va::FRAME_CACHE_ALLOCATE);
|
| 1400 |
+
hook::install_trap!(
|
| 1401 |
+
"DisplayBitGrid__SetExternalBuffer",
|
| 1402 |
+
va::DISPLAY_BIT_GRID_SET_EXTERNAL_BUFFER
|
| 1403 |
+
);
|
| 1404 |
}
|
| 1405 |
|
| 1406 |
Ok(())
|