feat: port slot 19 bitmap-sprite path to pure Rust
Browse filesThe slot-19 (BlitSprite) bitmap-sprite branch was the last in-game
caller of three usercall bridges to WA: GetBitmapSpriteInfo (0x573C50),
BlitBitmapClipped (0x56A700), and BlitBitmapTiled (0x56A7D0). After
this change, the only remaining in-game render bridge is slot 33
(GetSpriteFrameForBlit).
Type cleanups (lessons 14, 18: small typing wins are always welcome):
- LayerSprite renames `field_64`/`field_68`/`field_6a` to `flags`,
`cell_width: i16`, and `cell_height: i16`. Field semantics verified
against the GetBitmapSpriteInfo disassembly's reads of [EAX+0x64..0x6A].
- DisplayGfx::sprite_table retyped from `[u32; 0x400]` to
`[*mut LayerSprite; 0x400]`. Each non-null entry is the bitmap-sprite
object allocated by `load_sprite_by_layer` (slot 37).
- LayerSpriteFrame::bitmap_ptr() helper returns the trailing 12-byte
CBitmap block as `*mut CBitmap`. Lets us avoid duplicating the
vtable+surface+pad triple as a nested struct while keeping the
call-site type-safe.
- blit_bitmap_clipped_native promoted from `unsafe fn` to `pub` so
the DLL hook can call it directly.
New ports in `vtable.rs`:
- `blit_bitmap_tiled_native` — pure-Rust port of BlitBitmapTiled. The
two-loop alignment idiom (walk x by ±tile_width until x ≤ clip_x1)
was verified against the disassembly at 0x56A7DD..0x56A7F4. Then
loops `blit_bitmap_clipped_native` across `[clip_x1, clip_x2)`.
- `get_bitmap_sprite_info` — pure-Rust port of GetBitmapSpriteInfo.
Forward-mode and ping-pong frame index calculation, returns the
resolved frame's `*mut CBitmap` via `LayerSpriteFrame::bitmap_ptr`.
DLL `blit_sprite` now calls these three native functions directly
in the bitmap-sprite branch. Deletes ~95 lines of asm bridges
(`wa_get_bitmap_sprite_info`, `wa_blit_bitmap_clipped`,
`wa_blit_bitmap_tiled`) plus their three address constants
(`DISPLAY_GFX_GET_BITMAP_SPRITE_INFO`,
`DISPLAY_GFX_BLIT_BITMAP_CLIPPED`, `DISPLAY_GFX_BLIT_BITMAP_TILED`).
Verification: cargo build clean, 15/15 headless replay tests pass,
headful gameplay (HUD, weapon icons, scoreboard) confirmed visually
by user — graphics look perfect, no visible issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -462,12 +462,6 @@ pub mod va {
|
|
| 462 |
fn/Usercall DISPLAY_GFX_INIT = 0x0056_9D00;
|
| 463 |
/// DisplayGfx vtable slot 19 — blit sprite
|
| 464 |
fn/Thiscall DISPLAY_GFX_BLIT_SPRITE = 0x0056_B080;
|
| 465 |
-
/// DisplayGfx bitmap sprite info lookup — usercall(EAX=bitmap_obj, EDX=palette), RET 0x18
|
| 466 |
-
fn/Usercall DISPLAY_GFX_GET_BITMAP_SPRITE_INFO = 0x0057_3C50;
|
| 467 |
-
/// DisplayGfx bitmap blit (clipped) — usercall(EAX=this, EDX=width), RET 0x14
|
| 468 |
-
fn/Usercall DISPLAY_GFX_BLIT_BITMAP_CLIPPED = 0x0056_A700;
|
| 469 |
-
/// DisplayGfx bitmap blit (tiled) — usercall(EAX=initial_x, EDI=tile_width), RET 0x10
|
| 470 |
-
fn/Usercall DISPLAY_GFX_BLIT_BITMAP_TILED = 0x0056_A7D0;
|
| 471 |
/// DisplayGfx flush render lock — releases lock, plain RET
|
| 472 |
fn DISPLAY_GFX_FLUSH_RENDER_LOCK = 0x0056_A330;
|
| 473 |
/// Streaming audio constructor
|
|
|
|
| 462 |
fn/Usercall DISPLAY_GFX_INIT = 0x0056_9D00;
|
| 463 |
/// DisplayGfx vtable slot 19 — blit sprite
|
| 464 |
fn/Thiscall DISPLAY_GFX_BLIT_SPRITE = 0x0056_B080;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
/// DisplayGfx flush render lock — releases lock, plain RET
|
| 466 |
fn DISPLAY_GFX_FLUSH_RENDER_LOCK = 0x0056_A330;
|
| 467 |
/// Streaming audio constructor
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
use super::base::DisplayBase;
|
| 2 |
use super::vtable::DisplayGfxVtable;
|
| 3 |
use crate::bitgrid::DisplayBitGrid;
|
| 4 |
-
use crate::render::sprite::sprite::CBitmap;
|
| 5 |
use core::ffi::c_void;
|
| 6 |
|
| 7 |
/// DisplayGfx — full display/graphics subsystem (derived from DisplayBase).
|
|
@@ -133,9 +133,11 @@ pub struct DisplayGfx {
|
|
| 133 |
// =========================================================================
|
| 134 |
// Sprite/bitmap table (0x3DD4 - 0x4DD7)
|
| 135 |
// =========================================================================
|
| 136 |
-
/// 0x3DD4:
|
| 137 |
-
///
|
| 138 |
-
|
|
|
|
|
|
|
| 139 |
// =========================================================================
|
| 140 |
// Tile stream config (0x4DD4 - 0x4DF3)
|
| 141 |
// =========================================================================
|
|
|
|
| 1 |
use super::base::DisplayBase;
|
| 2 |
use super::vtable::DisplayGfxVtable;
|
| 3 |
use crate::bitgrid::DisplayBitGrid;
|
| 4 |
+
use crate::render::sprite::sprite::{CBitmap, LayerSprite};
|
| 5 |
use core::ffi::c_void;
|
| 6 |
|
| 7 |
/// DisplayGfx — full display/graphics subsystem (derived from DisplayBase).
|
|
|
|
| 133 |
// =========================================================================
|
| 134 |
// Sprite/bitmap table (0x3DD4 - 0x4DD7)
|
| 135 |
// =========================================================================
|
| 136 |
+
/// 0x3DD4: Bitmap-sprite table — 1024 entries, zeroed in DisplayGfx__Init.
|
| 137 |
+
/// Each non-null entry is a `LayerSprite` allocated by `load_sprite_by_layer`
|
| 138 |
+
/// (vtable slot 37). Read by `GetBitmapSpriteInfo` (slot's helper, used
|
| 139 |
+
/// by the bitmap-sprite branch of `BlitSprite` / slot 19).
|
| 140 |
+
pub sprite_table: [*mut LayerSprite; 0x400],
|
| 141 |
// =========================================================================
|
| 142 |
// Tile stream config (0x4DD4 - 0x4DF3)
|
| 143 |
// =========================================================================
|
|
@@ -891,7 +891,7 @@ pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32)
|
|
| 891 |
let base = &(*this).base;
|
| 892 |
if !base.sprite_ptrs[id as usize].is_null()
|
| 893 |
|| !base.sprite_banks[id as usize].is_null()
|
| 894 |
-
|| (*this).sprite_table[id as usize]
|
| 895 |
{
|
| 896 |
1
|
| 897 |
} else {
|
|
@@ -1772,11 +1772,12 @@ pub unsafe fn load_sprite_by_name(
|
|
| 1772 |
}
|
| 1773 |
}
|
| 1774 |
|
| 1775 |
-
// Read sprite metadata fields
|
|
|
|
| 1776 |
GfxDirStream::read_raw(stream, sp.add(0x60), 4); // field_60
|
| 1777 |
-
GfxDirStream::read_raw(stream, sp.add(0x64), 2); //
|
| 1778 |
-
GfxDirStream::read_raw(stream, sp.add(0x68), 2); //
|
| 1779 |
-
GfxDirStream::read_raw(stream, sp.add(0x6A), 2); //
|
| 1780 |
|
| 1781 |
// frame_count: zero first, then read
|
| 1782 |
(*sprite).frame_count = 0;
|
|
@@ -2034,7 +2035,7 @@ pub unsafe fn load_sprite_by_layer(
|
|
| 2034 |
}
|
| 2035 |
|
| 2036 |
// Store in sprite_table (DisplayGfx offset 0x3DD4)
|
| 2037 |
-
(*this).sprite_table[id as usize] = sprite
|
| 2038 |
|
| 2039 |
1
|
| 2040 |
}
|
|
@@ -2711,16 +2712,14 @@ unsafe fn cbitmap_blit_via_wrapper(
|
|
| 2711 |
/// render lock if held, and dispatches the actual blit through
|
| 2712 |
/// `cbitmap_blit_via_wrapper`.
|
| 2713 |
///
|
| 2714 |
-
/// Used by ported slots 11 (`DrawTiledBitmap`)
|
| 2715 |
-
///
|
| 2716 |
-
///
|
| 2717 |
-
/// see follow-up plan item 4. The address constant
|
| 2718 |
-
/// `DISPLAY_GFX_BLIT_BITMAP_CLIPPED` is kept until that path is ported.
|
| 2719 |
///
|
| 2720 |
/// # Safety
|
| 2721 |
/// `this` must be a valid `*mut DisplayGfx`. `surface` must be a valid
|
| 2722 |
/// `*mut CBitmap`. `g_RenderContext` must be initialized.
|
| 2723 |
-
unsafe fn blit_bitmap_clipped_native(
|
| 2724 |
this: *mut DisplayGfx,
|
| 2725 |
dst_x: i32,
|
| 2726 |
dst_y: i32,
|
|
@@ -2768,6 +2767,122 @@ unsafe fn blit_bitmap_clipped_native(
|
|
| 2768 |
);
|
| 2769 |
}
|
| 2770 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2771 |
/// Pure-Rust port of `DisplayGfx::DrawTiledBitmap` (vtable slot 11,
|
| 2772 |
/// `0x56B8C0`).
|
| 2773 |
///
|
|
|
|
| 891 |
let base = &(*this).base;
|
| 892 |
if !base.sprite_ptrs[id as usize].is_null()
|
| 893 |
|| !base.sprite_banks[id as usize].is_null()
|
| 894 |
+
|| !(*this).sprite_table[id as usize].is_null()
|
| 895 |
{
|
| 896 |
1
|
| 897 |
} else {
|
|
|
|
| 1772 |
}
|
| 1773 |
}
|
| 1774 |
|
| 1775 |
+
// Read sprite metadata fields (in original WA's read order:
|
| 1776 |
+
// field_60 first, then flags / cell_width / cell_height).
|
| 1777 |
GfxDirStream::read_raw(stream, sp.add(0x60), 4); // field_60
|
| 1778 |
+
GfxDirStream::read_raw(stream, sp.add(0x64), 2); // flags
|
| 1779 |
+
GfxDirStream::read_raw(stream, sp.add(0x68), 2); // cell_width
|
| 1780 |
+
GfxDirStream::read_raw(stream, sp.add(0x6A), 2); // cell_height
|
| 1781 |
|
| 1782 |
// frame_count: zero first, then read
|
| 1783 |
(*sprite).frame_count = 0;
|
|
|
|
| 2035 |
}
|
| 2036 |
|
| 2037 |
// Store in sprite_table (DisplayGfx offset 0x3DD4)
|
| 2038 |
+
(*this).sprite_table[id as usize] = sprite;
|
| 2039 |
|
| 2040 |
1
|
| 2041 |
}
|
|
|
|
| 2712 |
/// render lock if held, and dispatches the actual blit through
|
| 2713 |
/// `cbitmap_blit_via_wrapper`.
|
| 2714 |
///
|
| 2715 |
+
/// Used by ported slots 11 (`DrawTiledBitmap`), 22 (`DrawTiledTerrain`),
|
| 2716 |
+
/// and the bitmap-sprite branch of slot 19 (`BlitSprite`) via the DLL
|
| 2717 |
+
/// `blit_sprite` hook.
|
|
|
|
|
|
|
| 2718 |
///
|
| 2719 |
/// # Safety
|
| 2720 |
/// `this` must be a valid `*mut DisplayGfx`. `surface` must be a valid
|
| 2721 |
/// `*mut CBitmap`. `g_RenderContext` must be initialized.
|
| 2722 |
+
pub unsafe fn blit_bitmap_clipped_native(
|
| 2723 |
this: *mut DisplayGfx,
|
| 2724 |
dst_x: i32,
|
| 2725 |
dst_y: i32,
|
|
|
|
| 2767 |
);
|
| 2768 |
}
|
| 2769 |
|
| 2770 |
+
/// Pure-Rust port of `DisplayGfx::BlitBitmapTiled` (`0x56A7D0`,
|
| 2771 |
+
/// usercall EAX=initial_x, EDI=tile_width).
|
| 2772 |
+
///
|
| 2773 |
+
/// Tiles `surface` horizontally across the visible clip range
|
| 2774 |
+
/// `[clip_x1, clip_x2)`. The function first walks `initial_x` by
|
| 2775 |
+
/// `±tile_width` until `x ≤ clip_x1`, then blits at `(x, dst_y),
|
| 2776 |
+
/// (x + tile_width, dst_y), …` until past `clip_x2`. Each blit goes
|
| 2777 |
+
/// through `blit_bitmap_clipped_native` with the same fixed `flags = 2`
|
| 2778 |
+
/// the original WA function uses.
|
| 2779 |
+
///
|
| 2780 |
+
/// Used by the bitmap-sprite branch of slot 19 (`BlitSprite`) when the
|
| 2781 |
+
/// `tiled` mode bit (high_flags bit 16) is set on the sprite.
|
| 2782 |
+
///
|
| 2783 |
+
/// # Safety
|
| 2784 |
+
/// `this` must be a valid `*mut DisplayGfx` with an initialized clip
|
| 2785 |
+
/// rect. `surface` must be a valid `*mut CBitmap`. `tile_width` must be
|
| 2786 |
+
/// positive (zero or negative would loop forever; matches the original).
|
| 2787 |
+
pub unsafe fn blit_bitmap_tiled_native(
|
| 2788 |
+
this: *mut DisplayGfx,
|
| 2789 |
+
initial_x: i32,
|
| 2790 |
+
tile_width: i32,
|
| 2791 |
+
dst_y: i32,
|
| 2792 |
+
height: i32,
|
| 2793 |
+
surface: *mut CBitmap,
|
| 2794 |
+
) {
|
| 2795 |
+
let clip_x1 = (*this).base.clip_x1;
|
| 2796 |
+
let clip_x2 = (*this).base.clip_x2;
|
| 2797 |
+
|
| 2798 |
+
// Walk to the largest x ≤ clip_x1 in the arithmetic sequence
|
| 2799 |
+
// {initial_x ± k*tile_width} (matches the original's two-loop pattern
|
| 2800 |
+
// at 0x56A7DD..0x56A7F4).
|
| 2801 |
+
let mut x = initial_x;
|
| 2802 |
+
while x < clip_x1 {
|
| 2803 |
+
x += tile_width;
|
| 2804 |
+
}
|
| 2805 |
+
while x > clip_x1 {
|
| 2806 |
+
x -= tile_width;
|
| 2807 |
+
}
|
| 2808 |
+
|
| 2809 |
+
while x < clip_x2 {
|
| 2810 |
+
blit_bitmap_clipped_native(this, x, dst_y, tile_width, height, surface, 2);
|
| 2811 |
+
x += tile_width;
|
| 2812 |
+
}
|
| 2813 |
+
}
|
| 2814 |
+
|
| 2815 |
+
/// Pure-Rust port of `DisplayGfx::GetBitmapSpriteInfo` (`0x573C50`,
|
| 2816 |
+
/// usercall EAX=bitmap_obj, EDX=palette_or_anim).
|
| 2817 |
+
///
|
| 2818 |
+
/// Resolves an animation/palette value into a frame index for a
|
| 2819 |
+
/// `LayerSprite`, then writes the selected frame's bounding box and the
|
| 2820 |
+
/// sprite's full cell width/height to the output pointers and returns a
|
| 2821 |
+
/// pointer to the frame's `CBitmap` (the trailing 12 bytes of the
|
| 2822 |
+
/// `LayerSpriteFrame` entry).
|
| 2823 |
+
///
|
| 2824 |
+
/// `bitmap_obj.flags` (`LayerSprite +0x64`) controls interpretation of
|
| 2825 |
+
/// `palette_or_anim`:
|
| 2826 |
+
/// - bit 0 set: low 16 bits taken as-is (no clamp)
|
| 2827 |
+
/// - bit 0 clear: signed clamp to `[0, 0xFFFF]`
|
| 2828 |
+
/// - bit 1 set: ping-pong (bounce) iteration over `[0, frame_count)`
|
| 2829 |
+
/// - bit 1 clear: forward iteration
|
| 2830 |
+
///
|
| 2831 |
+
/// Used by the bitmap-sprite branch of slot 19 (`BlitSprite`).
|
| 2832 |
+
///
|
| 2833 |
+
/// # Safety
|
| 2834 |
+
/// `bitmap_obj` must be a valid `LayerSprite` with a populated
|
| 2835 |
+
/// `frame_array`. The output pointers must be writable.
|
| 2836 |
+
pub unsafe fn get_bitmap_sprite_info(
|
| 2837 |
+
bitmap_obj: *mut LayerSprite,
|
| 2838 |
+
palette_or_anim: u32,
|
| 2839 |
+
out_w: *mut i32,
|
| 2840 |
+
out_h: *mut i32,
|
| 2841 |
+
out_left: *mut i32,
|
| 2842 |
+
out_top: *mut i32,
|
| 2843 |
+
out_right: *mut i32,
|
| 2844 |
+
out_bottom: *mut i32,
|
| 2845 |
+
) -> *mut CBitmap {
|
| 2846 |
+
let flags = (*bitmap_obj).flags as i32;
|
| 2847 |
+
|
| 2848 |
+
// Step 1: clamp / mask the palette_or_anim value into [0, 0xFFFF].
|
| 2849 |
+
let pal: i32 = if flags & 1 != 0 {
|
| 2850 |
+
(palette_or_anim & 0xFFFF) as i32
|
| 2851 |
+
} else {
|
| 2852 |
+
let p = palette_or_anim as i32;
|
| 2853 |
+
p.max(0).min(0xFFFF)
|
| 2854 |
+
};
|
| 2855 |
+
|
| 2856 |
+
// Step 2: compute frame index from frame_count and pal.
|
| 2857 |
+
let frame_count = (*bitmap_obj).frame_count as i16 as i32;
|
| 2858 |
+
let frame_idx = if flags & 2 != 0 {
|
| 2859 |
+
// Ping-pong: scaled = ((2*frame_count - 1) * pal) >> 16
|
| 2860 |
+
// If scaled >= frame_count, fold back: (2*frame_count - scaled) - 1.
|
| 2861 |
+
let scaled = ((frame_count * 2 - 1) * pal) >> 16;
|
| 2862 |
+
if scaled >= frame_count {
|
| 2863 |
+
(frame_count * 2 - scaled) - 1
|
| 2864 |
+
} else {
|
| 2865 |
+
scaled
|
| 2866 |
+
}
|
| 2867 |
+
} else {
|
| 2868 |
+
// Forward: idx = (frame_count * pal) >> 16
|
| 2869 |
+
(frame_count * pal) >> 16
|
| 2870 |
+
};
|
| 2871 |
+
|
| 2872 |
+
// Step 3: read the selected frame entry.
|
| 2873 |
+
let frame = (*bitmap_obj).frame_array.offset(frame_idx as isize);
|
| 2874 |
+
|
| 2875 |
+
*out_left = (*frame).start_x as i32;
|
| 2876 |
+
*out_top = (*frame).start_y as i32;
|
| 2877 |
+
*out_right = (*frame).end_x as i32;
|
| 2878 |
+
*out_bottom = (*frame).end_y as i32;
|
| 2879 |
+
|
| 2880 |
+
*out_w = (*bitmap_obj).cell_width as i32;
|
| 2881 |
+
*out_h = (*bitmap_obj).cell_height as i32;
|
| 2882 |
+
|
| 2883 |
+
LayerSpriteFrame::bitmap_ptr(frame)
|
| 2884 |
+
}
|
| 2885 |
+
|
| 2886 |
/// Pure-Rust port of `DisplayGfx::DrawTiledBitmap` (vtable slot 11,
|
| 2887 |
/// `0x56B8C0`).
|
| 2888 |
///
|
|
@@ -184,6 +184,12 @@ pub struct SpriteBankVtable {
|
|
| 184 |
|
| 185 |
/// Layer sprite — 0x70-byte object used by `load_sprite_by_layer` (vtable slot 37).
|
| 186 |
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
/// Unlike `Sprite` (which has a vtable and is created via ConstructSprite), this
|
| 188 |
/// is a raw allocation with partial init by the caller. Used for background layers
|
| 189 |
/// (back.spr, layer.spr) which load DirectDraw surfaces directly from `.dir` files.
|
|
@@ -200,14 +206,18 @@ pub struct LayerSprite {
|
|
| 200 |
pub palette_ctx: *mut PaletteContext,
|
| 201 |
/// 0x60: Header field from stream (4 bytes).
|
| 202 |
pub field_60: u32,
|
| 203 |
-
/// 0x64:
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
/// 0x66: Frame count (zeroed, then read from stream).
|
| 206 |
pub frame_count: u16,
|
| 207 |
-
/// 0x68:
|
| 208 |
-
pub
|
| 209 |
-
/// 0x6A:
|
| 210 |
-
pub
|
| 211 |
/// 0x6C: Pointer to LayerSpriteFrame array (count stored at ptr[-4]).
|
| 212 |
pub frame_array: *mut LayerSpriteFrame,
|
| 213 |
}
|
|
@@ -272,6 +282,18 @@ pub struct LayerSpriteFrame {
|
|
| 272 |
|
| 273 |
const _: () = assert!(core::mem::size_of::<LayerSpriteFrame>() == 0x14);
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
crate::define_addresses! {
|
| 276 |
class "CBitmap" {
|
| 277 |
/// Vtable shared by [`CBitmap`] (12 bytes) and [`LayerSpriteFrame`]
|
|
|
|
| 184 |
|
| 185 |
/// Layer sprite — 0x70-byte object used by `load_sprite_by_layer` (vtable slot 37).
|
| 186 |
///
|
| 187 |
+
/// Also used for the "bitmap sprite" path of `BlitSprite` (slot 19): the
|
| 188 |
+
/// per-id pointers in `DisplayGfx::sprite_table` (`+0x3DD4`) point at
|
| 189 |
+
/// `LayerSprite` instances. `DisplayGfx::GetBitmapSpriteInfo` reads
|
| 190 |
+
/// `flags`, `frame_count`, `cell_width`, `cell_height`, and `frame_array`
|
| 191 |
+
/// to resolve a frame index → frame metadata + `CBitmap` pointer.
|
| 192 |
+
///
|
| 193 |
/// Unlike `Sprite` (which has a vtable and is created via ConstructSprite), this
|
| 194 |
/// is a raw allocation with partial init by the caller. Used for background layers
|
| 195 |
/// (back.spr, layer.spr) which load DirectDraw surfaces directly from `.dir` files.
|
|
|
|
| 206 |
pub palette_ctx: *mut PaletteContext,
|
| 207 |
/// 0x60: Header field from stream (4 bytes).
|
| 208 |
pub field_60: u32,
|
| 209 |
+
/// 0x64: Animation/playback mode flags. Read by
|
| 210 |
+
/// `GetBitmapSpriteInfo` to decide how to interpret the
|
| 211 |
+
/// palette/anim parameter:
|
| 212 |
+
/// - bit 0: low 16 bits used as-is (no clamp)
|
| 213 |
+
/// - bit 1: ping-pong (bounce) frame iteration
|
| 214 |
+
pub flags: u16,
|
| 215 |
/// 0x66: Frame count (zeroed, then read from stream).
|
| 216 |
pub frame_count: u16,
|
| 217 |
+
/// 0x68: Sprite cell width in pixels (used for centering during blit).
|
| 218 |
+
pub cell_width: i16,
|
| 219 |
+
/// 0x6A: Sprite cell height in pixels (used for centering during blit).
|
| 220 |
+
pub cell_height: i16,
|
| 221 |
/// 0x6C: Pointer to LayerSpriteFrame array (count stored at ptr[-4]).
|
| 222 |
pub frame_array: *mut LayerSpriteFrame,
|
| 223 |
}
|
|
|
|
| 282 |
|
| 283 |
const _: () = assert!(core::mem::size_of::<LayerSpriteFrame>() == 0x14);
|
| 284 |
|
| 285 |
+
impl LayerSpriteFrame {
|
| 286 |
+
/// Returns a pointer to the trailing 12-byte `CBitmap` block
|
| 287 |
+
/// (`bitmap_vtable`/`surface`/`_pad_10`) inside this entry. The
|
| 288 |
+
/// `CBitmap` layout is bit-identical to the trailing 12 bytes of
|
| 289 |
+
/// `LayerSpriteFrame`, so the value can be passed to any function
|
| 290 |
+
/// that takes `*mut CBitmap` (e.g., `blit_bitmap_clipped_native`).
|
| 291 |
+
#[inline]
|
| 292 |
+
pub fn bitmap_ptr(this: *mut Self) -> *mut CBitmap {
|
| 293 |
+
unsafe { (this as *mut u8).add(0x08) as *mut CBitmap }
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
crate::define_addresses! {
|
| 298 |
class "CBitmap" {
|
| 299 |
/// Vtable shared by [`CBitmap`] (12 bytes) and [`LayerSpriteFrame`]
|
|
@@ -808,19 +808,19 @@ unsafe extern "thiscall" fn blit_sprite(
|
|
| 808 |
if arr1 == 0 && arr2 == 0 {
|
| 809 |
// Bitmap sprite path — sprite is in the bitmap table at 0x3DD4.
|
| 810 |
let bitmap_obj = (*gfx).sprite_table[sprite_id as usize];
|
| 811 |
-
if bitmap_obj
|
| 812 |
return;
|
| 813 |
}
|
| 814 |
|
| 815 |
-
// Get frame data and dimensions from bitmap sprite object
|
| 816 |
let mut sprite_w: i32 = 0;
|
| 817 |
let mut sprite_h: i32 = 0;
|
| 818 |
let mut rect_left: i32 = 0;
|
| 819 |
let mut rect_top: i32 = 0;
|
| 820 |
let mut rect_right: i32 = 0;
|
| 821 |
let mut rect_bottom: i32 = 0;
|
| 822 |
-
let frame_data =
|
| 823 |
-
bitmap_obj
|
| 824 |
pal,
|
| 825 |
&mut sprite_w,
|
| 826 |
&mut sprite_h,
|
|
@@ -828,7 +828,6 @@ unsafe extern "thiscall" fn blit_sprite(
|
|
| 828 |
&mut rect_top,
|
| 829 |
&mut rect_right,
|
| 830 |
&mut rect_bottom,
|
| 831 |
-
rb(openwa_core::address::va::DISPLAY_GFX_GET_BITMAP_SPRITE_INFO),
|
| 832 |
);
|
| 833 |
if frame_data.is_null() {
|
| 834 |
return;
|
|
@@ -839,33 +838,18 @@ unsafe extern "thiscall" fn blit_sprite(
|
|
| 839 |
let half_w = sprite_w / 2;
|
| 840 |
let half_h = sprite_h / 2;
|
| 841 |
let blit_h = rect_bottom - rect_top;
|
| 842 |
-
|
| 843 |
let dst_y = (y.0 >> 16) + (camera_y - half_h) + rect_top;
|
| 844 |
|
| 845 |
if (high_flags & 0x0001_0000) == 0 {
|
| 846 |
-
// Non-tiled:
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
this as *mut u8,
|
| 850 |
-
sprite_w as u32,
|
| 851 |
-
dst_x,
|
| 852 |
-
dst_y,
|
| 853 |
-
blit_h,
|
| 854 |
-
frame_data,
|
| 855 |
-
2,
|
| 856 |
-
rb(openwa_core::address::va::DISPLAY_GFX_BLIT_BITMAP_CLIPPED),
|
| 857 |
);
|
| 858 |
} else {
|
| 859 |
-
// Tiled:
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
dst_x,
|
| 863 |
-
sprite_w,
|
| 864 |
-
this as *mut u8,
|
| 865 |
-
dst_y,
|
| 866 |
-
blit_h,
|
| 867 |
-
frame_data,
|
| 868 |
-
rb(openwa_core::address::va::DISPLAY_GFX_BLIT_BITMAP_TILED),
|
| 869 |
);
|
| 870 |
}
|
| 871 |
return;
|
|
@@ -1136,93 +1120,6 @@ unsafe extern "thiscall" fn blit_sprite(
|
|
| 1136 |
);
|
| 1137 |
}
|
| 1138 |
|
| 1139 |
-
// =========================================================================
|
| 1140 |
-
// Bitmap sprite bridges (naked asm for usercall conventions)
|
| 1141 |
-
// =========================================================================
|
| 1142 |
-
|
| 1143 |
-
/// Call DisplayGfx__GetBitmapSpriteInfo (0x573C50).
|
| 1144 |
-
/// Usercall: EAX=bitmap_obj, EDX=palette, 6 stack params (output ptrs), RET 0x18.
|
| 1145 |
-
#[unsafe(naked)]
|
| 1146 |
-
unsafe extern "cdecl" fn wa_get_bitmap_sprite_info(
|
| 1147 |
-
_bitmap_obj: *mut u8,
|
| 1148 |
-
_palette: u32,
|
| 1149 |
-
_out_w: *mut i32,
|
| 1150 |
-
_out_h: *mut i32,
|
| 1151 |
-
_out_left: *mut i32,
|
| 1152 |
-
_out_top: *mut i32,
|
| 1153 |
-
_out_right: *mut i32,
|
| 1154 |
-
_out_bottom: *mut i32,
|
| 1155 |
-
_target: u32,
|
| 1156 |
-
) -> *const u8 {
|
| 1157 |
-
core::arch::naked_asm!(
|
| 1158 |
-
"mov eax, [esp + 4]", // bitmap_obj
|
| 1159 |
-
"mov edx, [esp + 8]", // palette
|
| 1160 |
-
"mov ecx, [esp + 36]", // target
|
| 1161 |
-
"push dword ptr [esp + 32]", // out_bottom
|
| 1162 |
-
"push dword ptr [esp + 32]", // out_right
|
| 1163 |
-
"push dword ptr [esp + 32]", // out_top
|
| 1164 |
-
"push dword ptr [esp + 32]", // out_left
|
| 1165 |
-
"push dword ptr [esp + 32]", // out_h
|
| 1166 |
-
"push dword ptr [esp + 32]", // out_w
|
| 1167 |
-
"call ecx", // RET 0x18 cleans 6 params
|
| 1168 |
-
"ret",
|
| 1169 |
-
);
|
| 1170 |
-
}
|
| 1171 |
-
|
| 1172 |
-
/// Call DisplayGfx__BlitBitmapClipped (0x56A700).
|
| 1173 |
-
/// Usercall: EAX=this, EDX=width, 5 stack params (dst_x, dst_y, height, frame_data, flags), RET 0x14.
|
| 1174 |
-
#[unsafe(naked)]
|
| 1175 |
-
unsafe extern "cdecl" fn wa_blit_bitmap_clipped(
|
| 1176 |
-
_this: *mut u8,
|
| 1177 |
-
_width: u32,
|
| 1178 |
-
_dst_x: i32,
|
| 1179 |
-
_dst_y: i32,
|
| 1180 |
-
_height: i32,
|
| 1181 |
-
_frame_data: *const u8,
|
| 1182 |
-
_flags: u32,
|
| 1183 |
-
_target: u32,
|
| 1184 |
-
) {
|
| 1185 |
-
core::arch::naked_asm!(
|
| 1186 |
-
"mov eax, [esp + 4]", // this
|
| 1187 |
-
"mov edx, [esp + 8]", // width
|
| 1188 |
-
"mov ecx, [esp + 32]", // target
|
| 1189 |
-
"push dword ptr [esp + 28]", // flags
|
| 1190 |
-
"push dword ptr [esp + 28]", // frame_data
|
| 1191 |
-
"push dword ptr [esp + 28]", // height
|
| 1192 |
-
"push dword ptr [esp + 28]", // dst_y
|
| 1193 |
-
"push dword ptr [esp + 28]", // dst_x
|
| 1194 |
-
"call ecx", // RET 0x14 cleans 5 params
|
| 1195 |
-
"ret",
|
| 1196 |
-
);
|
| 1197 |
-
}
|
| 1198 |
-
|
| 1199 |
-
/// Call DisplayGfx__BlitBitmapTiled (0x56A7D0).
|
| 1200 |
-
/// Usercall: EAX=initial_x, EDI=tile_width, 4 stack params (this, dst_y, height, frame_data), RET 0x10.
|
| 1201 |
-
#[unsafe(naked)]
|
| 1202 |
-
unsafe extern "cdecl" fn wa_blit_bitmap_tiled(
|
| 1203 |
-
_initial_x: i32,
|
| 1204 |
-
_tile_width: i32,
|
| 1205 |
-
_this: *mut u8,
|
| 1206 |
-
_dst_y: i32,
|
| 1207 |
-
_height: i32,
|
| 1208 |
-
_frame_data: *const u8,
|
| 1209 |
-
_target: u32,
|
| 1210 |
-
) {
|
| 1211 |
-
core::arch::naked_asm!(
|
| 1212 |
-
"push edi",
|
| 1213 |
-
"mov eax, [esp + 8]", // initial_x
|
| 1214 |
-
"mov edi, [esp + 12]", // tile_width
|
| 1215 |
-
"mov ecx, [esp + 32]", // target (offset +4 from push edi)
|
| 1216 |
-
"push dword ptr [esp + 28]", // frame_data
|
| 1217 |
-
"push dword ptr [esp + 28]", // height
|
| 1218 |
-
"push dword ptr [esp + 28]", // dst_y
|
| 1219 |
-
"push dword ptr [esp + 28]", // this
|
| 1220 |
-
"call ecx", // RET 0x10 cleans 4 params
|
| 1221 |
-
"pop edi",
|
| 1222 |
-
"ret",
|
| 1223 |
-
);
|
| 1224 |
-
}
|
| 1225 |
-
|
| 1226 |
/// Thiscall wrapper for DisplayGfx::DrawScaledSprite (slot 20).
|
| 1227 |
///
|
| 1228 |
/// Computes coordinates in core, then dispatches the blit via blit_impl.
|
|
|
|
| 808 |
if arr1 == 0 && arr2 == 0 {
|
| 809 |
// Bitmap sprite path — sprite is in the bitmap table at 0x3DD4.
|
| 810 |
let bitmap_obj = (*gfx).sprite_table[sprite_id as usize];
|
| 811 |
+
if bitmap_obj.is_null() {
|
| 812 |
return;
|
| 813 |
}
|
| 814 |
|
| 815 |
+
// Get frame data and dimensions from bitmap sprite object.
|
| 816 |
let mut sprite_w: i32 = 0;
|
| 817 |
let mut sprite_h: i32 = 0;
|
| 818 |
let mut rect_left: i32 = 0;
|
| 819 |
let mut rect_top: i32 = 0;
|
| 820 |
let mut rect_right: i32 = 0;
|
| 821 |
let mut rect_bottom: i32 = 0;
|
| 822 |
+
let frame_data = display_vtable::get_bitmap_sprite_info(
|
| 823 |
+
bitmap_obj,
|
| 824 |
pal,
|
| 825 |
&mut sprite_w,
|
| 826 |
&mut sprite_h,
|
|
|
|
| 828 |
&mut rect_top,
|
| 829 |
&mut rect_right,
|
| 830 |
&mut rect_bottom,
|
|
|
|
| 831 |
);
|
| 832 |
if frame_data.is_null() {
|
| 833 |
return;
|
|
|
|
| 838 |
let half_w = sprite_w / 2;
|
| 839 |
let half_h = sprite_h / 2;
|
| 840 |
let blit_h = rect_bottom - rect_top;
|
| 841 |
+
let dst_x = (x.0 >> 16) + (camera_x - half_w) + rect_left;
|
| 842 |
let dst_y = (y.0 >> 16) + (camera_y - half_h) + rect_top;
|
| 843 |
|
| 844 |
if (high_flags & 0x0001_0000) == 0 {
|
| 845 |
+
// Non-tiled: clipped blit at (dst_x, dst_y).
|
| 846 |
+
display_vtable::blit_bitmap_clipped_native(
|
| 847 |
+
gfx, dst_x, dst_y, sprite_w, blit_h, frame_data, 2,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 848 |
);
|
| 849 |
} else {
|
| 850 |
+
// Tiled: horizontal tile across the clip rect.
|
| 851 |
+
display_vtable::blit_bitmap_tiled_native(
|
| 852 |
+
gfx, dst_x, sprite_w, dst_y, blit_h, frame_data,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 853 |
);
|
| 854 |
}
|
| 855 |
return;
|
|
|
|
| 1120 |
);
|
| 1121 |
}
|
| 1122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1123 |
/// Thiscall wrapper for DisplayGfx::DrawScaledSprite (slot 20).
|
| 1124 |
///
|
| 1125 |
/// Computes coordinates in core, then dispatches the blit via blit_impl.
|