feat: port DisplayGfx::LoadSprite (slot 31) and improve Sprite struct
Browse files- Port load_sprite vtable method: Rust validation/bookkeeping with
ConstructSprite in pure Rust, delegating VFS loading to WA bridge
- Embed DisplayBitGrid as a proper struct field in Sprite instead of
raw _unknown_38 bytes, with offset assertions
- Type Sprite::context_ptr as *mut SpriteCache instead of *mut u8
- Add construct_sprite() in openwa-core (pure Rust port of 0x4FAA30)
- Update ConstructSprite hook in DLL to use typed bitgrid fields
- Re-export SpriteVtable from sprite module
- Add address constants for sprite loading helper functions
DisplayGfx now at 27/34 non-stub vtable slots ported.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +3 -2
- crates/openwa-core/src/render/display/vtable.rs +194 -3
- crates/openwa-core/src/render/sprite/mod.rs +1 -1
- crates/openwa-core/src/render/sprite/sprite.rs +22 -6
- crates/openwa-dll/src/replacements/render.rs +58 -1
- crates/openwa-dll/src/replacements/sprite.rs +9 -8
crates/openwa-core/src/address.rs
CHANGED
|
@@ -92,8 +92,9 @@ pub mod va {
|
|
| 92 |
};
|
| 93 |
pub use crate::render::sprite::sprite::SPRITE_BANK_VTABLE;
|
| 94 |
pub use crate::render::sprite::sprite::{
|
| 95 |
-
CONSTRUCT_SPRITE, DESTROY_SPRITE,
|
| 96 |
-
|
|
|
|
| 97 |
};
|
| 98 |
|
| 99 |
crate::define_addresses! {
|
|
|
|
| 92 |
};
|
| 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! {
|
crates/openwa-core/src/render/display/vtable.rs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
use crate::fixed::Fixed;
|
| 2 |
use crate::render::display::line_draw::Vertex;
|
|
|
|
|
|
|
| 3 |
|
| 4 |
/// DisplayVtable — vtable for the display/rendering subsystem (DisplayGfx).
|
| 5 |
///
|
|
@@ -306,7 +308,7 @@ use super::gfx::DisplayGfx;
|
|
| 306 |
use super::line_draw;
|
| 307 |
use crate::bitgrid::DisplayBitGrid;
|
| 308 |
use crate::render::palette::PaletteContext;
|
| 309 |
-
use crate::render::sprite::{Sprite, SpriteBank};
|
| 310 |
|
| 311 |
/// Port of DisplayGfx::GetDimensions (vtable slot 1, 0x56A460).
|
| 312 |
///
|
|
@@ -807,8 +809,7 @@ pub unsafe extern "thiscall" fn draw_polyline(
|
|
| 807 |
/// # Safety
|
| 808 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 809 |
pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32) -> u32 {
|
| 810 |
-
|
| 811 |
-
if id_u.wrapping_sub(1) >= 0x3FF {
|
| 812 |
return 0;
|
| 813 |
}
|
| 814 |
|
|
@@ -823,6 +824,18 @@ pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32)
|
|
| 823 |
}
|
| 824 |
}
|
| 825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
/// Port of DisplayGfx::GetSpriteInfo (vtable slot 6, 0x523500).
|
| 827 |
///
|
| 828 |
/// Looks up sprite metadata for `layer` (valid: 1..=0x3FF). Checks
|
|
@@ -1538,3 +1551,181 @@ pub unsafe extern "thiscall" fn set_layer_visibility(
|
|
| 1538 |
(*this).base.layer_visibility[layer as usize] = 0;
|
| 1539 |
}
|
| 1540 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
use crate::fixed::Fixed;
|
| 2 |
use crate::render::display::line_draw::Vertex;
|
| 3 |
+
use crate::render::SpriteCache;
|
| 4 |
+
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 5 |
|
| 6 |
/// DisplayVtable — vtable for the display/rendering subsystem (DisplayGfx).
|
| 7 |
///
|
|
|
|
| 308 |
use super::line_draw;
|
| 309 |
use crate::bitgrid::DisplayBitGrid;
|
| 310 |
use crate::render::palette::PaletteContext;
|
| 311 |
+
use crate::render::sprite::{Sprite, SpriteBank, SpriteVtable};
|
| 312 |
|
| 313 |
/// Port of DisplayGfx::GetDimensions (vtable slot 1, 0x56A460).
|
| 314 |
///
|
|
|
|
| 809 |
/// # Safety
|
| 810 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 811 |
pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32) -> u32 {
|
| 812 |
+
if !is_valid_sprite_id(id) {
|
|
|
|
| 813 |
return 0;
|
| 814 |
}
|
| 815 |
|
|
|
|
| 824 |
}
|
| 825 |
}
|
| 826 |
|
| 827 |
+
/// Check if a sprite ID is in the valid range [1, 0x3FF].
|
| 828 |
+
#[inline]
|
| 829 |
+
fn is_valid_sprite_id(id: i32) -> bool {
|
| 830 |
+
(1..=0x3FF).contains(&id)
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
/// Check if a layer ID is in the valid range [1, 3].
|
| 834 |
+
#[inline]
|
| 835 |
+
fn is_valid_layer(layer: u32) -> bool {
|
| 836 |
+
(1..=3).contains(&layer)
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
/// Port of DisplayGfx::GetSpriteInfo (vtable slot 6, 0x523500).
|
| 840 |
///
|
| 841 |
/// Looks up sprite metadata for `layer` (valid: 1..=0x3FF). Checks
|
|
|
|
| 1551 |
(*this).base.layer_visibility[layer as usize] = 0;
|
| 1552 |
}
|
| 1553 |
}
|
| 1554 |
+
|
| 1555 |
+
// =========================================================================
|
| 1556 |
+
// Sprite loading methods
|
| 1557 |
+
// =========================================================================
|
| 1558 |
+
|
| 1559 |
+
/// Construct a Sprite in-place — pure Rust port of ConstructSprite (0x4FAA30).
|
| 1560 |
+
///
|
| 1561 |
+
/// Sets vtable, embedded BitGrid sub-object, and context pointer.
|
| 1562 |
+
/// All other fields are expected to be zeroed by the caller's allocation.
|
| 1563 |
+
///
|
| 1564 |
+
/// # Safety
|
| 1565 |
+
/// `sprite` must point to a zeroed `Sprite`-sized allocation.
|
| 1566 |
+
pub unsafe fn construct_sprite(sprite: *mut Sprite, sprite_cache: *mut SpriteCache) {
|
| 1567 |
+
use crate::bitgrid::{BitGridDisplayVtable, BIT_GRID_DISPLAY_VTABLE};
|
| 1568 |
+
use crate::rebase::rb;
|
| 1569 |
+
|
| 1570 |
+
(*sprite).vtable = rb(va::SPRITE_VTABLE) as *const SpriteVtable;
|
| 1571 |
+
(*sprite).context_ptr = sprite_cache;
|
| 1572 |
+
|
| 1573 |
+
// Initialize embedded DisplayBitGrid sub-object
|
| 1574 |
+
(*sprite).bitgrid.vtable = rb(BIT_GRID_DISPLAY_VTABLE) as *const BitGridDisplayVtable;
|
| 1575 |
+
(*sprite).bitgrid.external_buffer = 1; // sprite doesn't own pixel data
|
| 1576 |
+
(*sprite).bitgrid.cells_per_unit = 8; // 8bpp pixel buffer
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
/// Port of DisplayGfx::LoadSprite (vtable slot 31, 0x523400).
|
| 1580 |
+
///
|
| 1581 |
+
/// Loads a Sprite from VFS into the sprite table. Allocates a Sprite object,
|
| 1582 |
+
/// constructs it, and loads data via LoadSpriteFromVfs. On success, stores
|
| 1583 |
+
/// in sprite_ptrs[id] and updates layer metadata.
|
| 1584 |
+
///
|
| 1585 |
+
/// The `flag` parameter controls max_frames clamping on the loaded sprite.
|
| 1586 |
+
///
|
| 1587 |
+
/// # Safety
|
| 1588 |
+
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1589 |
+
/// `load_sprite_from_vfs` must be a valid function pointer to 0x4FAAF0.
|
| 1590 |
+
pub unsafe fn load_sprite(
|
| 1591 |
+
this: *mut DisplayGfx,
|
| 1592 |
+
layer: u32,
|
| 1593 |
+
id: u32,
|
| 1594 |
+
flag: u32,
|
| 1595 |
+
_gfx: *mut u8,
|
| 1596 |
+
_name: *const core::ffi::c_char,
|
| 1597 |
+
load_sprite_from_vfs: unsafe extern "cdecl" fn(
|
| 1598 |
+
sprite: *mut Sprite,
|
| 1599 |
+
gfx: *mut u8,
|
| 1600 |
+
name: *const core::ffi::c_char,
|
| 1601 |
+
layer_ctx: u32,
|
| 1602 |
+
) -> i32,
|
| 1603 |
+
) -> i32 {
|
| 1604 |
+
// Bit 23 set = already loaded sentinel
|
| 1605 |
+
if id & 0x80_0000 != 0 {
|
| 1606 |
+
return 1;
|
| 1607 |
+
}
|
| 1608 |
+
|
| 1609 |
+
if !is_valid_layer(layer) {
|
| 1610 |
+
return 0;
|
| 1611 |
+
}
|
| 1612 |
+
let base = &mut (*this).base;
|
| 1613 |
+
if base.layer_contexts[layer as usize] == 0 {
|
| 1614 |
+
return 0;
|
| 1615 |
+
}
|
| 1616 |
+
if !is_valid_sprite_id(id as i32) {
|
| 1617 |
+
return 0;
|
| 1618 |
+
}
|
| 1619 |
+
|
| 1620 |
+
// Already loaded?
|
| 1621 |
+
if is_sprite_loaded(this, id as i32) != 0 {
|
| 1622 |
+
return 0;
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
let sprite = wa_malloc_struct_zeroed::<Sprite>();
|
| 1626 |
+
if sprite.is_null() {
|
| 1627 |
+
return 0;
|
| 1628 |
+
}
|
| 1629 |
+
construct_sprite(sprite, base.sprite_cache);
|
| 1630 |
+
|
| 1631 |
+
// Load from VFS
|
| 1632 |
+
let layer_ctx = base.layer_contexts[layer as usize];
|
| 1633 |
+
let result = load_sprite_from_vfs(sprite, _gfx, _name, layer_ctx);
|
| 1634 |
+
if result == 0 {
|
| 1635 |
+
// Load failed — destroy sprite via vtable[0]
|
| 1636 |
+
if !sprite.is_null() {
|
| 1637 |
+
let dtor = (*(*sprite).vtable).destructor;
|
| 1638 |
+
dtor(sprite, 1);
|
| 1639 |
+
}
|
| 1640 |
+
return 0;
|
| 1641 |
+
}
|
| 1642 |
+
|
| 1643 |
+
// Store in sprite table
|
| 1644 |
+
let base = &mut (*this).base;
|
| 1645 |
+
base.sprite_ptrs[id as usize] = sprite;
|
| 1646 |
+
base.sprite_layers[id as usize] = layer;
|
| 1647 |
+
base.layer_visibility[layer as usize] += 1;
|
| 1648 |
+
|
| 1649 |
+
// Update max_frames on the sprite if flag is set
|
| 1650 |
+
if flag != 0 {
|
| 1651 |
+
let sprite = base.sprite_ptrs[id as usize];
|
| 1652 |
+
let id_u16 = id as u16;
|
| 1653 |
+
if id_u16 != 0 && id_u16 < (*sprite).max_frames {
|
| 1654 |
+
(*sprite).max_frames = id_u16;
|
| 1655 |
+
}
|
| 1656 |
+
(*sprite)._unknown_18 = (id >> 16) as u16;
|
| 1657 |
+
}
|
| 1658 |
+
|
| 1659 |
+
1
|
| 1660 |
+
}
|
| 1661 |
+
|
| 1662 |
+
/// Port of DisplayGfx::LoadSpriteByLayer (vtable slot 37, 0x56A4C0).
|
| 1663 |
+
///
|
| 1664 |
+
/// Simplified sprite loading that stores into DisplayGfx::sprite_table
|
| 1665 |
+
/// (offset 0x3DD4) instead of DisplayBase::sprite_ptrs. Allocates a raw
|
| 1666 |
+
/// 0x70-byte object, partially initializes it (NOT via ConstructSprite),
|
| 1667 |
+
/// then loads via FUN_005733b0.
|
| 1668 |
+
///
|
| 1669 |
+
/// # Safety
|
| 1670 |
+
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1671 |
+
/// `load_fn` must be a valid function pointer to FUN_005733b0.
|
| 1672 |
+
/// `free_fn` must be a valid function pointer to FUN_0056a2f0.
|
| 1673 |
+
pub unsafe fn load_sprite_by_layer(
|
| 1674 |
+
this: *mut DisplayGfx,
|
| 1675 |
+
layer: u32,
|
| 1676 |
+
id: u32,
|
| 1677 |
+
gfx: *mut u8,
|
| 1678 |
+
name: *const core::ffi::c_char,
|
| 1679 |
+
load_fn: unsafe extern "cdecl" fn(
|
| 1680 |
+
sprite: *mut u8,
|
| 1681 |
+
gfx: *mut u8,
|
| 1682 |
+
id: u32,
|
| 1683 |
+
name: *const core::ffi::c_char,
|
| 1684 |
+
) -> i32,
|
| 1685 |
+
free_fn: unsafe extern "cdecl" fn(sprite: *mut u8),
|
| 1686 |
+
) -> i32 {
|
| 1687 |
+
// Bit 23 set = already loaded sentinel
|
| 1688 |
+
if id & 0x80_0000 != 0 {
|
| 1689 |
+
return 1;
|
| 1690 |
+
}
|
| 1691 |
+
|
| 1692 |
+
// Call set_active_layer (vtable slot 5) — already ported Rust
|
| 1693 |
+
set_active_layer(this, layer as i32);
|
| 1694 |
+
|
| 1695 |
+
if !is_valid_sprite_id(id as i32) {
|
| 1696 |
+
return 0;
|
| 1697 |
+
}
|
| 1698 |
+
|
| 1699 |
+
// Already loaded?
|
| 1700 |
+
if is_sprite_loaded(this, id as i32) != 0 {
|
| 1701 |
+
return 1;
|
| 1702 |
+
}
|
| 1703 |
+
|
| 1704 |
+
// Allocate 0x70 bytes + 0x20 guard (matching WA_MallocMemset behavior)
|
| 1705 |
+
use crate::wa_alloc::wa_malloc_zeroed;
|
| 1706 |
+
let sprite = wa_malloc_zeroed(0x90);
|
| 1707 |
+
if sprite.is_null() {
|
| 1708 |
+
return 0;
|
| 1709 |
+
}
|
| 1710 |
+
|
| 1711 |
+
// Partial init (NOT ConstructSprite — different from load_sprite)
|
| 1712 |
+
*(sprite.add(0x58) as *mut *mut DisplayGfx) = this;
|
| 1713 |
+
*(sprite.add(0x66) as *mut u16) = 0;
|
| 1714 |
+
*(sprite.add(0x6C) as *mut u32) = 0;
|
| 1715 |
+
*(sprite.add(0x54) as *mut u32) = 0;
|
| 1716 |
+
|
| 1717 |
+
// Load via FUN_005733b0
|
| 1718 |
+
let result = load_fn(sprite, gfx, id, name);
|
| 1719 |
+
if result == 0 {
|
| 1720 |
+
free_fn(sprite);
|
| 1721 |
+
return 0;
|
| 1722 |
+
}
|
| 1723 |
+
|
| 1724 |
+
// Store in sprite_table (DisplayGfx offset 0x3DD4)
|
| 1725 |
+
(*this).sprite_table[id as usize] = sprite as u32;
|
| 1726 |
+
1
|
| 1727 |
+
}
|
| 1728 |
+
|
| 1729 |
+
// LoadSpriteComplex (vtable slot 33, 0x5237C0) is NOT ported — the internal
|
| 1730 |
+
// dispatch functions (0x4FAD30, 0x4F9710) use ESI for sprite/bank pointers
|
| 1731 |
+
// in complex usercall conventions that are impractical to bridge.
|
crates/openwa-core/src/render/sprite/mod.rs
CHANGED
|
@@ -4,4 +4,4 @@ pub mod sprite;
|
|
| 4 |
pub mod sprite_id;
|
| 5 |
|
| 6 |
pub use spr::{ParsedSprite, SprError, SprHeader};
|
| 7 |
-
pub use sprite::{Sprite, SpriteBank, SpriteFrame, SpriteId};
|
|
|
|
| 4 |
pub mod sprite_id;
|
| 5 |
|
| 6 |
pub use spr::{ParsedSprite, SprError, SprHeader};
|
| 7 |
+
pub use sprite::{Sprite, SpriteBank, SpriteFrame, SpriteId, SpriteVtable};
|
crates/openwa-core/src/render/sprite/sprite.rs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
// Re-export SpriteId from its own module to keep this file focused on struct layouts.
|
| 2 |
pub use super::sprite_id::SpriteId;
|
| 3 |
|
|
@@ -39,7 +41,7 @@ pub struct Sprite {
|
|
| 39 |
/// 0x00: Vtable pointer (0x66418C, 8 slots)
|
| 40 |
pub vtable: *const SpriteVtable,
|
| 41 |
/// 0x04: Context/parent pointer (ECX from ConstructSprite)
|
| 42 |
-
pub context_ptr: *mut
|
| 43 |
/// 0x08: Unknown
|
| 44 |
pub _unknown_08: u16,
|
| 45 |
/// 0x0A: Animation frames per second
|
|
@@ -74,11 +76,10 @@ pub struct Sprite {
|
|
| 74 |
pub secondary_frame_count: u16,
|
| 75 |
/// 0x32: Padding
|
| 76 |
pub _pad_32: u16,
|
| 77 |
-
/// 0x34: Embedded
|
| 78 |
-
///
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
pub _unknown_38: [u8; 0x28],
|
| 82 |
/// 0x60: Pointer to raw frame header data
|
| 83 |
pub raw_frame_header_ptr: *mut u8,
|
| 84 |
/// 0x64: Pointer to bitmap pixel data
|
|
@@ -90,6 +91,8 @@ pub struct Sprite {
|
|
| 90 |
}
|
| 91 |
|
| 92 |
const _: () = assert!(core::mem::size_of::<Sprite>() == 0x70);
|
|
|
|
|
|
|
| 93 |
|
| 94 |
/// SpriteBank — indexed sprite container (0x17C bytes).
|
| 95 |
///
|
|
@@ -194,5 +197,18 @@ crate::define_addresses! {
|
|
| 194 |
class "SpriteBank" {
|
| 195 |
/// SpriteBank__GetInfo — usercall EAX=layer, ECX=bank*, ESI=out_width, 2 stack params
|
| 196 |
fn/Usercall SPRITE_BANK_GET_INFO = 0x004F_98C0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}
|
| 198 |
}
|
|
|
|
| 1 |
+
use crate::render::SpriteCache;
|
| 2 |
+
|
| 3 |
// Re-export SpriteId from its own module to keep this file focused on struct layouts.
|
| 4 |
pub use super::sprite_id::SpriteId;
|
| 5 |
|
|
|
|
| 41 |
/// 0x00: Vtable pointer (0x66418C, 8 slots)
|
| 42 |
pub vtable: *const SpriteVtable,
|
| 43 |
/// 0x04: Context/parent pointer (ECX from ConstructSprite)
|
| 44 |
+
pub context_ptr: *mut SpriteCache,
|
| 45 |
/// 0x08: Unknown
|
| 46 |
pub _unknown_08: u16,
|
| 47 |
/// 0x0A: Animation frames per second
|
|
|
|
| 76 |
pub secondary_frame_count: u16,
|
| 77 |
/// 0x32: Padding
|
| 78 |
pub _pad_32: u16,
|
| 79 |
+
/// 0x34: Embedded DisplayBitGrid sub-object (0x2C bytes).
|
| 80 |
+
/// ConstructSprite sets vtable=0x664144, external_buffer=1, cells_per_unit=8.
|
| 81 |
+
/// Populated further by ProcessSprite with pixel data pointers.
|
| 82 |
+
pub bitgrid: crate::bitgrid::DisplayBitGrid,
|
|
|
|
| 83 |
/// 0x60: Pointer to raw frame header data
|
| 84 |
pub raw_frame_header_ptr: *mut u8,
|
| 85 |
/// 0x64: Pointer to bitmap pixel data
|
|
|
|
| 91 |
}
|
| 92 |
|
| 93 |
const _: () = assert!(core::mem::size_of::<Sprite>() == 0x70);
|
| 94 |
+
const _: () = assert!(core::mem::offset_of!(Sprite, bitgrid) == 0x34);
|
| 95 |
+
const _: () = assert!(core::mem::offset_of!(Sprite, raw_frame_header_ptr) == 0x60);
|
| 96 |
|
| 97 |
/// SpriteBank — indexed sprite container (0x17C bytes).
|
| 98 |
///
|
|
|
|
| 197 |
class "SpriteBank" {
|
| 198 |
/// SpriteBank__GetInfo — usercall EAX=layer, ECX=bank*, ESI=out_width, 2 stack params
|
| 199 |
fn/Usercall SPRITE_BANK_GET_INFO = 0x004F_98C0;
|
| 200 |
+
/// SpriteBank__Init — usercall, initializes from VFS resource
|
| 201 |
+
fn/Usercall SPRITE_BANK_INIT = 0x004F_95A0;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
class "DisplayGfx_Sprite" {
|
| 205 |
+
/// Load sprite from VFS by name — usercall(EDI=sprite, ECX=gfx) + stack(id, name), RET 0x8
|
| 206 |
+
fn/Usercall LOAD_SPRITE_BY_NAME = 0x0057_33B0;
|
| 207 |
+
/// Free sprite object (with sub-object cleanup) — usercall(EDI=sprite), plain RET
|
| 208 |
+
fn/Usercall FREE_SPRITE_OBJECT = 0x0056_A2F0;
|
| 209 |
+
/// Sprite-path dispatch for LoadSpriteComplex — usercall on Sprite*
|
| 210 |
+
fn/Usercall SPRITE_COMPLEX_SPRITE_PATH = 0x004F_AD30;
|
| 211 |
+
/// SpriteBank-path dispatch for LoadSpriteComplex — usercall on SpriteBank*
|
| 212 |
+
fn/Usercall SPRITE_COMPLEX_BANK_PATH = 0x004F_9710;
|
| 213 |
}
|
| 214 |
}
|
crates/openwa-dll/src/replacements/render.rs
CHANGED
|
@@ -1559,6 +1559,59 @@ unsafe extern "cdecl" fn wa_font_draw_text(
|
|
| 1559 |
);
|
| 1560 |
}
|
| 1561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1562 |
// ---------------------------------------------------------------------------
|
| 1563 |
// Display installation
|
| 1564 |
// ---------------------------------------------------------------------------
|
|
@@ -1594,6 +1647,9 @@ fn install_display() -> Result<(), String> {
|
|
| 1594 |
let _ = log_line("[Display] Headless: patched slot 0 (destructor) -> Rust");
|
| 1595 |
})?;
|
| 1596 |
|
|
|
|
|
|
|
|
|
|
| 1597 |
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 1598 |
vtable_replace!(DisplayVtable, va::DISPLAY_GFX_VTABLE, {
|
| 1599 |
get_dimensions => display_vtable::get_dimensions,
|
|
@@ -1617,13 +1673,14 @@ fn install_display() -> Result<(), String> {
|
|
| 1617 |
set_camera_offset => display_vtable::set_camera_offset,
|
| 1618 |
set_clip_rect => display_vtable::set_clip_rect,
|
| 1619 |
is_sprite_loaded => display_vtable::is_sprite_loaded,
|
|
|
|
| 1620 |
draw_scaled_sprite => draw_scaled_sprite,
|
| 1621 |
set_layer_visibility => display_vtable::set_layer_visibility,
|
| 1622 |
update_palette => display_vtable::update_palette,
|
| 1623 |
set_font_palette => set_font_palette,
|
| 1624 |
slot 19 => blit_sprite,
|
| 1625 |
})?;
|
| 1626 |
-
let _ = log_line("[Display] DisplayGfx: patched
|
| 1627 |
}
|
| 1628 |
|
| 1629 |
Ok(())
|
|
|
|
| 1559 |
);
|
| 1560 |
}
|
| 1561 |
|
| 1562 |
+
// =========================================================================
|
| 1563 |
+
// Sprite loading vtable method wrappers
|
| 1564 |
+
// =========================================================================
|
| 1565 |
+
|
| 1566 |
+
/// Thiscall wrapper for DisplayGfx::LoadSprite (vtable slot 31, 0x523400).
|
| 1567 |
+
unsafe extern "thiscall" fn load_sprite(
|
| 1568 |
+
this: *mut DisplayGfx,
|
| 1569 |
+
layer: u32,
|
| 1570 |
+
id: u32,
|
| 1571 |
+
flag: u32,
|
| 1572 |
+
gfx: *mut u8,
|
| 1573 |
+
name: *const core::ffi::c_char,
|
| 1574 |
+
) -> i32 {
|
| 1575 |
+
display_vtable::load_sprite(this, layer, id, flag, gfx, name, wa_load_sprite_from_vfs)
|
| 1576 |
+
}
|
| 1577 |
+
|
| 1578 |
+
/// Bridge to LoadSpriteFromVfs (0x4FAAF0).
|
| 1579 |
+
/// Usercall: ECX=gfx, EAX=name, stack(sprite, layer_ctx), RET 0x8.
|
| 1580 |
+
///
|
| 1581 |
+
/// Verified from caller at 0x523489:
|
| 1582 |
+
/// ECX ← layer_contexts[layer] (gfx/VFS context)... wait, re-checked:
|
| 1583 |
+
/// ECX ← gfx param, EAX ← name param.
|
| 1584 |
+
/// Stack: sprite (EDI from ConstructSprite), layer_ctx.
|
| 1585 |
+
#[unsafe(naked)]
|
| 1586 |
+
unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
| 1587 |
+
_sprite: *mut openwa_core::render::sprite::Sprite,
|
| 1588 |
+
_gfx: *mut u8,
|
| 1589 |
+
_name: *const core::ffi::c_char,
|
| 1590 |
+
_layer_ctx: u32,
|
| 1591 |
+
) -> i32 {
|
| 1592 |
+
core::arch::naked_asm!(
|
| 1593 |
+
// cdecl: +4=sprite, +8=gfx, +12=name, +16=layer_ctx
|
| 1594 |
+
"mov ecx, [esp+8]", // gfx → ECX
|
| 1595 |
+
"mov eax, [esp+12]", // name → EAX
|
| 1596 |
+
"push dword ptr [esp+16]", // layer_ctx
|
| 1597 |
+
"push dword ptr [esp+8]", // sprite (shifted +4 by push)
|
| 1598 |
+
"call [{ADDR}]", // RET 0x8 cleans 2 stack params
|
| 1599 |
+
"ret",
|
| 1600 |
+
ADDR = sym LOAD_SPRITE_FROM_VFS_ADDR,
|
| 1601 |
+
);
|
| 1602 |
+
}
|
| 1603 |
+
|
| 1604 |
+
static mut LOAD_SPRITE_FROM_VFS_ADDR: u32 = 0;
|
| 1605 |
+
|
| 1606 |
+
// LoadSpriteByLayer (vtable slot 37, 0x56A4C0) is NOT ported — the internal
|
| 1607 |
+
// loader FUN_005733b0 is a 379-instruction DirectDraw surface creator with
|
| 1608 |
+
// complex usercall conventions and caller-frame reads. Left as original WA code.
|
| 1609 |
+
|
| 1610 |
+
// LoadSpriteComplex (slot 33) and LoadSpriteByLayer (slot 37) are NOT ported —
|
| 1611 |
+
// their internal functions (FUN_005733b0, FUN_004FAD30, FUN_004F9710) have
|
| 1612 |
+
// complex usercall conventions with caller-frame reads and DirectDraw surface
|
| 1613 |
+
// creation. Left as original WA code.
|
| 1614 |
+
|
| 1615 |
// ---------------------------------------------------------------------------
|
| 1616 |
// Display installation
|
| 1617 |
// ---------------------------------------------------------------------------
|
|
|
|
| 1647 |
let _ = log_line("[Display] Headless: patched slot 0 (destructor) -> Rust");
|
| 1648 |
})?;
|
| 1649 |
|
| 1650 |
+
// Initialize bridge address statics for sprite loading
|
| 1651 |
+
LOAD_SPRITE_FROM_VFS_ADDR = rb(va::LOAD_SPRITE_FROM_VFS);
|
| 1652 |
+
|
| 1653 |
// Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
|
| 1654 |
vtable_replace!(DisplayVtable, va::DISPLAY_GFX_VTABLE, {
|
| 1655 |
get_dimensions => display_vtable::get_dimensions,
|
|
|
|
| 1673 |
set_camera_offset => display_vtable::set_camera_offset,
|
| 1674 |
set_clip_rect => display_vtable::set_clip_rect,
|
| 1675 |
is_sprite_loaded => display_vtable::is_sprite_loaded,
|
| 1676 |
+
load_sprite => load_sprite,
|
| 1677 |
draw_scaled_sprite => draw_scaled_sprite,
|
| 1678 |
set_layer_visibility => display_vtable::set_layer_visibility,
|
| 1679 |
update_palette => display_vtable::update_palette,
|
| 1680 |
set_font_palette => set_font_palette,
|
| 1681 |
slot 19 => blit_sprite,
|
| 1682 |
})?;
|
| 1683 |
+
let _ = log_line("[Display] DisplayGfx: patched 28 methods -> Rust");
|
| 1684 |
}
|
| 1685 |
|
| 1686 |
Ok(())
|
crates/openwa-dll/src/replacements/sprite.rs
CHANGED
|
@@ -7,8 +7,8 @@
|
|
| 7 |
use openwa_core::address::va;
|
| 8 |
use openwa_core::rebase::rb;
|
| 9 |
use openwa_core::render::palette::PaletteContext;
|
| 10 |
-
use openwa_core::render::sprite::sprite::SpriteVtable;
|
| 11 |
use openwa_core::render::sprite::{Sprite, SpriteFrame};
|
|
|
|
| 12 |
|
| 13 |
use crate::hook::{self, usercall_trampoline};
|
| 14 |
|
|
@@ -19,18 +19,19 @@ use crate::hook::{self, usercall_trampoline};
|
|
| 19 |
usercall_trampoline!(fn trampoline_construct_sprite; impl_fn = construct_sprite_impl;
|
| 20 |
regs = [eax, ecx]);
|
| 21 |
|
| 22 |
-
unsafe extern "cdecl" fn construct_sprite_impl(sprite: *mut Sprite, context: *mut
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
core::ptr::write_bytes(sprite as *mut u8, 0, core::mem::size_of::<Sprite>());
|
| 25 |
|
| 26 |
(*sprite).vtable = rb(va::SPRITE_VTABLE) as *const SpriteVtable;
|
| 27 |
(*sprite).context_ptr = context;
|
| 28 |
-
(*sprite).bitgrid_vtable = rb(va::BIT_GRID_DISPLAY_VTABLE) as *mut u8;
|
| 29 |
|
| 30 |
-
//
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
}
|
| 35 |
|
| 36 |
// ---------------------------------------------------------------------------
|
|
|
|
| 7 |
use openwa_core::address::va;
|
| 8 |
use openwa_core::rebase::rb;
|
| 9 |
use openwa_core::render::palette::PaletteContext;
|
|
|
|
| 10 |
use openwa_core::render::sprite::{Sprite, SpriteFrame};
|
| 11 |
+
use openwa_core::render::SpriteCache;
|
| 12 |
|
| 13 |
use crate::hook::{self, usercall_trampoline};
|
| 14 |
|
|
|
|
| 19 |
usercall_trampoline!(fn trampoline_construct_sprite; impl_fn = construct_sprite_impl;
|
| 20 |
regs = [eax, ecx]);
|
| 21 |
|
| 22 |
+
unsafe extern "cdecl" fn construct_sprite_impl(sprite: *mut Sprite, context: *mut SpriteCache) {
|
| 23 |
+
use openwa_core::bitgrid::{BitGridDisplayVtable, BIT_GRID_DISPLAY_VTABLE};
|
| 24 |
+
use openwa_core::render::sprite::SpriteVtable;
|
| 25 |
+
|
| 26 |
core::ptr::write_bytes(sprite as *mut u8, 0, core::mem::size_of::<Sprite>());
|
| 27 |
|
| 28 |
(*sprite).vtable = rb(va::SPRITE_VTABLE) as *const SpriteVtable;
|
| 29 |
(*sprite).context_ptr = context;
|
|
|
|
| 30 |
|
| 31 |
+
// Embedded DisplayBitGrid sub-object at +0x34
|
| 32 |
+
(*sprite).bitgrid.vtable = rb(BIT_GRID_DISPLAY_VTABLE) as *const BitGridDisplayVtable;
|
| 33 |
+
(*sprite).bitgrid.external_buffer = 1;
|
| 34 |
+
(*sprite).bitgrid.cells_per_unit = 8;
|
| 35 |
}
|
| 36 |
|
| 37 |
// ---------------------------------------------------------------------------
|