paavohtl Claude Opus 4.6 (1M context) commited on
Commit
43b6a0b
·
1 Parent(s): 3fc929d

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 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, LOAD_SPRITE_FROM_VFS, PROCESS_SPRITE,
96
- SPRITE_BANK_GET_INFO, SPRITE_GET_INFO, SPRITE_VTABLE,
 
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
- let id_u = id as u32;
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 u8,
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 BitGrid vtable pointer (0x664144, set by ConstructSprite).
78
- /// Part of a BitGrid sub-object within the Sprite.
79
- pub bitgrid_vtable: *mut u8,
80
- /// 0x38-0x5F: Unknown fields
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 26 methods -> Rust");
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 u8) {
23
- // Zero the entire 0x70-byte struct first
 
 
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
- // BitGrid sub-object fields (within _unknown_38)
31
- let p = sprite as *mut u8;
32
- *(p.add(0x38) as *mut u32) = 1;
33
- *(p.add(0x40) as *mut u32) = 8;
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
  // ---------------------------------------------------------------------------