refactor: introduce Layer newtype, type layer_contexts and palette_ctx
Browse filesAdds `Layer(NonZeroU8)` covering the 1..=3 display-layer index used by
`set_layer_color`, `set_active_layer`, `set_layer_visibility`,
`load_sprite`, `load_font`, etc. Vtable signatures still take i32/u32
to match WA's binary contract; conversion happens at function entry,
which both deletes the open-coded `(layer as u32).wrapping_sub(1) >= 3`
checks and statically guarantees array indices are in range.
Bundled with the matching pointer-typing pass:
- `DisplayBase::layer_contexts: [u32; 4]` -> `[*mut PaletteContext; 4]`
- `LayerSprite::palette_ctx: u32` -> `*mut PaletteContext`
- `wa_load_sprite_from_vfs` and the `load_sprite_from_vfs` callback
param retyped from `u32` to `*mut PaletteContext`
Both types are 4 bytes on i686-pc-windows-msvc, so the struct layouts
and bridge calling conventions are unchanged. Replaces every
`if layer_ctx == 0` with `if layer_ctx.is_null()` and deletes the dead
`is_valid_layer` helper.
Verification: cargo test (4 new Layer tests pass), 15/15 headless
replay tests pass. Pure type refactor with no logic changes, so no
headful verification needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/render/display/base.rs +5 -3
- crates/openwa-core/src/render/display/layer.rs +124 -0
- crates/openwa-core/src/render/display/mod.rs +2 -0
- crates/openwa-core/src/render/display/vtable.rs +42 -45
- crates/openwa-core/src/render/sprite/sprite.rs +5 -3
- crates/openwa-dll/src/replacements/render.rs +1 -1
|
@@ -25,6 +25,7 @@
|
|
| 25 |
use crate::{
|
| 26 |
render::{
|
| 27 |
display::font::Font,
|
|
|
|
| 28 |
sprite::{Sprite, SpriteBank},
|
| 29 |
},
|
| 30 |
task::base::Vtable,
|
|
@@ -65,9 +66,10 @@ pub struct DisplayBase<V: Vtable = *const DisplayBaseVtable> {
|
|
| 65 |
// Indexed by font_id (valid range 1-31). Used by GetFontInfo, GetFontMetric, SetFontParam.
|
| 66 |
pub font_table: [*mut Font; 32],
|
| 67 |
// +0x311C..0x312C: layer context pointers (4 entries), zeroed by ctor.
|
| 68 |
-
// Indexed by
|
| 69 |
-
// Used as palette data input for
|
| 70 |
-
|
|
|
|
| 71 |
// +0x312C: Palette slot table guard. Always 0 — prevents palette_slot_alloc
|
| 72 |
// from allocating starting at index 0. Part of the contiguous scan area:
|
| 73 |
// [slot_table_guard(0)] [slot_table(1s)...] [slot_table_sentinel(-1)].
|
|
|
|
| 25 |
use crate::{
|
| 26 |
render::{
|
| 27 |
display::font::Font,
|
| 28 |
+
palette::PaletteContext,
|
| 29 |
sprite::{Sprite, SpriteBank},
|
| 30 |
},
|
| 31 |
task::base::Vtable,
|
|
|
|
| 66 |
// Indexed by font_id (valid range 1-31). Used by GetFontInfo, GetFontMetric, SetFontParam.
|
| 67 |
pub font_table: [*mut Font; 32],
|
| 68 |
// +0x311C..0x312C: layer context pointers (4 entries), zeroed by ctor.
|
| 69 |
+
// Indexed by `Layer` (valid range 1-3); index 0 unused. Returned by
|
| 70 |
+
// set_active_layer (vtable slot 5). Used as palette data input for
|
| 71 |
+
// update_palette (vtable slot 24). `null_mut()` = no context allocated.
|
| 72 |
+
pub layer_contexts: [*mut PaletteContext; 4],
|
| 73 |
// +0x312C: Palette slot table guard. Always 0 — prevents palette_slot_alloc
|
| 74 |
// from allocating starting at index 0. Part of the contiguous scan area:
|
| 75 |
// [slot_table_guard(0)] [slot_table(1s)...] [slot_table_sentinel(-1)].
|
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Display layer index newtype.
|
| 2 |
+
|
| 3 |
+
use core::num::NonZeroU8;
|
| 4 |
+
|
| 5 |
+
/// Display layer index — valid values are 1, 2, 3.
|
| 6 |
+
///
|
| 7 |
+
/// Used as the index for `DisplayBase::layer_contexts`,
|
| 8 |
+
/// `DisplayBase::layer_visibility`, and as the value space stored in
|
| 9 |
+
/// `DisplayBase::font_layers`. Surfaces in `DisplayGfx` vtable slots:
|
| 10 |
+
///
|
| 11 |
+
/// - slot 4 `set_layer_color`
|
| 12 |
+
/// - slot 5 `set_active_layer`
|
| 13 |
+
/// - slot 23 `set_layer_visibility`
|
| 14 |
+
/// - slot 34 `load_font` (the WA "mode" parameter — same value space)
|
| 15 |
+
///
|
| 16 |
+
/// Stored as `NonZeroU8` so `Option<Layer>` is one byte and round-trips
|
| 17 |
+
/// through 0 = "no layer" sentinels in adjacent code.
|
| 18 |
+
///
|
| 19 |
+
/// The vtable signatures themselves still take `i32`/`u32` because they
|
| 20 |
+
/// match WA's binary contract; conversion happens at the function entry
|
| 21 |
+
/// via [`Layer::try_from_i32`] / [`Layer::try_from_u32`].
|
| 22 |
+
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
| 23 |
+
#[repr(transparent)]
|
| 24 |
+
pub struct Layer(NonZeroU8);
|
| 25 |
+
|
| 26 |
+
impl Layer {
|
| 27 |
+
/// Layer 1 — backdrop (gradient/landscape).
|
| 28 |
+
pub const ONE: Layer = match NonZeroU8::new(1) {
|
| 29 |
+
Some(n) => Layer(n),
|
| 30 |
+
None => unreachable!(),
|
| 31 |
+
};
|
| 32 |
+
/// Layer 2 — water/foreground sprites.
|
| 33 |
+
pub const TWO: Layer = match NonZeroU8::new(2) {
|
| 34 |
+
Some(n) => Layer(n),
|
| 35 |
+
None => unreachable!(),
|
| 36 |
+
};
|
| 37 |
+
/// Layer 3 — main game layer.
|
| 38 |
+
pub const THREE: Layer = match NonZeroU8::new(3) {
|
| 39 |
+
Some(n) => Layer(n),
|
| 40 |
+
None => unreachable!(),
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
/// Construct from a raw `u8`. Returns `None` outside `1..=3`.
|
| 44 |
+
pub const fn new(value: u8) -> Option<Self> {
|
| 45 |
+
match value {
|
| 46 |
+
1 | 2 | 3 => match NonZeroU8::new(value) {
|
| 47 |
+
Some(n) => Some(Layer(n)),
|
| 48 |
+
None => None,
|
| 49 |
+
},
|
| 50 |
+
_ => None,
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/// Construct from a signed integer (vtable param). `None` outside `1..=3`.
|
| 55 |
+
#[inline]
|
| 56 |
+
pub const fn try_from_i32(value: i32) -> Option<Self> {
|
| 57 |
+
if value >= 1 && value <= 3 {
|
| 58 |
+
Self::new(value as u8)
|
| 59 |
+
} else {
|
| 60 |
+
None
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
/// Construct from an unsigned integer (vtable param). `None` outside `1..=3`.
|
| 65 |
+
#[inline]
|
| 66 |
+
pub const fn try_from_u32(value: u32) -> Option<Self> {
|
| 67 |
+
if value >= 1 && value <= 3 {
|
| 68 |
+
Self::new(value as u8)
|
| 69 |
+
} else {
|
| 70 |
+
None
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/// Index into `[T; 4]` arrays keyed by layer (slot 0 unused).
|
| 75 |
+
#[inline]
|
| 76 |
+
pub const fn idx(self) -> usize {
|
| 77 |
+
self.0.get() as usize
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/// Raw value as `u32`.
|
| 81 |
+
#[inline]
|
| 82 |
+
pub const fn as_u32(self) -> u32 {
|
| 83 |
+
self.0.get() as u32
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/// Raw value as `i32`.
|
| 87 |
+
#[inline]
|
| 88 |
+
pub const fn as_i32(self) -> i32 {
|
| 89 |
+
self.0.get() as i32
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
#[cfg(test)]
|
| 94 |
+
mod tests {
|
| 95 |
+
use super::*;
|
| 96 |
+
|
| 97 |
+
#[test]
|
| 98 |
+
fn rejects_out_of_range() {
|
| 99 |
+
assert!(Layer::try_from_i32(0).is_none());
|
| 100 |
+
assert!(Layer::try_from_i32(-1).is_none());
|
| 101 |
+
assert!(Layer::try_from_i32(4).is_none());
|
| 102 |
+
assert!(Layer::try_from_u32(0).is_none());
|
| 103 |
+
assert!(Layer::try_from_u32(4).is_none());
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
#[test]
|
| 107 |
+
fn accepts_in_range() {
|
| 108 |
+
assert_eq!(Layer::try_from_i32(1), Some(Layer::ONE));
|
| 109 |
+
assert_eq!(Layer::try_from_i32(2), Some(Layer::TWO));
|
| 110 |
+
assert_eq!(Layer::try_from_i32(3), Some(Layer::THREE));
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
#[test]
|
| 114 |
+
fn idx_round_trip() {
|
| 115 |
+
assert_eq!(Layer::ONE.idx(), 1);
|
| 116 |
+
assert_eq!(Layer::TWO.idx(), 2);
|
| 117 |
+
assert_eq!(Layer::THREE.idx(), 3);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
#[test]
|
| 121 |
+
fn option_layer_is_one_byte() {
|
| 122 |
+
assert_eq!(core::mem::size_of::<Option<Layer>>(), 1);
|
| 123 |
+
}
|
| 124 |
+
}
|
|
@@ -3,6 +3,7 @@ pub mod context;
|
|
| 3 |
pub mod font;
|
| 4 |
pub mod gfx;
|
| 5 |
pub mod gradient;
|
|
|
|
| 6 |
pub mod line_draw;
|
| 7 |
pub mod palette;
|
| 8 |
pub mod sprite_blit;
|
|
@@ -11,5 +12,6 @@ pub mod vtable;
|
|
| 11 |
pub use base::{DisplayBase, DisplayBaseVtable, SpriteBufferCtrl, SpriteCache};
|
| 12 |
pub use context::{FastcallResult, RenderContext};
|
| 13 |
pub use gfx::DisplayGfx;
|
|
|
|
| 14 |
pub use palette::{Palette, PaletteVtable};
|
| 15 |
pub use vtable::{DisplayGfxVtable, DrawScaledSpriteResult};
|
|
|
|
| 3 |
pub mod font;
|
| 4 |
pub mod gfx;
|
| 5 |
pub mod gradient;
|
| 6 |
+
pub mod layer;
|
| 7 |
pub mod line_draw;
|
| 8 |
pub mod palette;
|
| 9 |
pub mod sprite_blit;
|
|
|
|
| 12 |
pub use base::{DisplayBase, DisplayBaseVtable, SpriteBufferCtrl, SpriteCache};
|
| 13 |
pub use context::{FastcallResult, RenderContext};
|
| 14 |
pub use gfx::DisplayGfx;
|
| 15 |
+
pub use layer::Layer;
|
| 16 |
pub use palette::{Palette, PaletteVtable};
|
| 17 |
pub use vtable::{DisplayGfxVtable, DrawScaledSpriteResult};
|
|
@@ -7,6 +7,7 @@ use crate::render::display::font::{
|
|
| 7 |
font_extend, font_get_info_impl, font_get_metric_impl, font_load_from_gfx,
|
| 8 |
font_set_palette_impl, font_set_param_impl, Font,
|
| 9 |
};
|
|
|
|
| 10 |
use crate::render::display::line_draw::Vertex;
|
| 11 |
use crate::render::sprite::gfx_dir::GfxDir;
|
| 12 |
use crate::render::sprite::sprite::{LayerSprite, LayerSpriteFrame};
|
|
@@ -904,12 +905,6 @@ fn is_valid_sprite_id(id: i32) -> bool {
|
|
| 904 |
(1..=0x3FF).contains(&id)
|
| 905 |
}
|
| 906 |
|
| 907 |
-
/// Check if a layer ID is in the valid range [1, 3].
|
| 908 |
-
#[inline]
|
| 909 |
-
fn is_valid_layer(layer: u32) -> bool {
|
| 910 |
-
(1..=3).contains(&layer)
|
| 911 |
-
}
|
| 912 |
-
|
| 913 |
/// Port of DisplayGfx::GetSpriteInfo (vtable slot 6, 0x523500).
|
| 914 |
///
|
| 915 |
/// Looks up sprite metadata for `layer` (valid: 1..=0x3FF). Checks
|
|
@@ -1386,13 +1381,12 @@ pub unsafe extern "thiscall" fn draw_pixel_strip(
|
|
| 1386 |
/// # Safety
|
| 1387 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1388 |
pub unsafe extern "thiscall" fn set_layer_color(this: *mut DisplayGfx, layer: i32, color: i32) {
|
| 1389 |
-
|
| 1390 |
-
if (layer as u32).wrapping_sub(1) >= 3 {
|
| 1391 |
return;
|
| 1392 |
-
}
|
| 1393 |
|
| 1394 |
// Only allocate if no context exists for this layer
|
| 1395 |
-
if (*this).base.layer_contexts[layer
|
| 1396 |
return;
|
| 1397 |
}
|
| 1398 |
|
|
@@ -1405,15 +1399,15 @@ pub unsafe extern "thiscall" fn set_layer_color(this: *mut DisplayGfx, layer: i3
|
|
| 1405 |
core::ptr::write_bytes(ctx, 0, 0x70C);
|
| 1406 |
|
| 1407 |
let result = if ctx.is_null() {
|
| 1408 |
-
|
| 1409 |
} else {
|
| 1410 |
let ctx = ctx as *mut PaletteContext;
|
| 1411 |
palette_context_init(ctx, start as i16, (start as i32 + color - 1) as i16);
|
| 1412 |
-
ctx
|
| 1413 |
};
|
| 1414 |
|
| 1415 |
-
(*this).base.layer_contexts[layer
|
| 1416 |
-
(*this).base.layer_visibility[layer
|
| 1417 |
}
|
| 1418 |
|
| 1419 |
/// Palette slot allocator — port of FUN_00523190.
|
|
@@ -1493,10 +1487,9 @@ unsafe fn palette_context_init(ctx: *mut PaletteContext, range_min: i16, range_m
|
|
| 1493 |
/// # Safety
|
| 1494 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1495 |
pub unsafe extern "thiscall" fn set_active_layer(this: *mut DisplayGfx, layer: i32) -> *mut u8 {
|
| 1496 |
-
|
| 1497 |
-
(*this).base.layer_contexts[layer
|
| 1498 |
-
|
| 1499 |
-
core::ptr::null_mut()
|
| 1500 |
}
|
| 1501 |
}
|
| 1502 |
|
|
@@ -1616,13 +1609,17 @@ pub unsafe extern "thiscall" fn set_layer_visibility(
|
|
| 1616 |
layer: i32,
|
| 1617 |
visible: i32,
|
| 1618 |
) {
|
| 1619 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1620 |
if !layer_ctx.is_null() {
|
| 1621 |
update_palette(this, layer_ctx, visible);
|
| 1622 |
}
|
| 1623 |
|
| 1624 |
if visible < 0 {
|
| 1625 |
-
(*this).base.layer_visibility[layer
|
| 1626 |
}
|
| 1627 |
}
|
| 1628 |
|
|
@@ -1672,7 +1669,7 @@ pub unsafe fn load_sprite(
|
|
| 1672 |
sprite: *mut Sprite,
|
| 1673 |
gfx_dir: *mut GfxDir,
|
| 1674 |
name: *const c_char,
|
| 1675 |
-
layer_ctx:
|
| 1676 |
) -> i32,
|
| 1677 |
) -> i32 {
|
| 1678 |
// Bit 23 set = already loaded sentinel
|
|
@@ -1680,11 +1677,12 @@ pub unsafe fn load_sprite(
|
|
| 1680 |
return 1;
|
| 1681 |
}
|
| 1682 |
|
| 1683 |
-
|
| 1684 |
return 0;
|
| 1685 |
-
}
|
| 1686 |
let base = &mut (*this).base;
|
| 1687 |
-
|
|
|
|
| 1688 |
return 0;
|
| 1689 |
}
|
| 1690 |
if !is_valid_sprite_id(id as i32) {
|
|
@@ -1703,7 +1701,6 @@ pub unsafe fn load_sprite(
|
|
| 1703 |
construct_sprite(sprite, base.sprite_cache);
|
| 1704 |
|
| 1705 |
// Load from VFS
|
| 1706 |
-
let layer_ctx = base.layer_contexts[layer as usize];
|
| 1707 |
let result = load_sprite_from_vfs(sprite, gfx_dir, _name, layer_ctx);
|
| 1708 |
if result == 0 {
|
| 1709 |
// Load failed — destroy sprite via vtable[0]
|
|
@@ -1717,8 +1714,8 @@ pub unsafe fn load_sprite(
|
|
| 1717 |
// Store in sprite table
|
| 1718 |
let base = &mut (*this).base;
|
| 1719 |
base.sprite_ptrs[id as usize] = sprite;
|
| 1720 |
-
base.sprite_layers[id as usize] = layer;
|
| 1721 |
-
base.layer_visibility[layer
|
| 1722 |
|
| 1723 |
// Update max_frames on the sprite if flag is set
|
| 1724 |
if flag != 0 {
|
|
@@ -1774,7 +1771,7 @@ pub unsafe fn load_sprite_by_name(
|
|
| 1774 |
|
| 1775 |
// 2. Store gfx_dir and palette_ctx in sprite
|
| 1776 |
(*sprite).gfx_dir = gfx_dir;
|
| 1777 |
-
(*sprite).palette_ctx = palette_ctx
|
| 1778 |
|
| 1779 |
// 3. Load image stream from GfxDir
|
| 1780 |
let stream = call_gfx_load_image(gfx_dir, name) as *mut GfxDirStream;
|
|
@@ -2057,7 +2054,8 @@ pub unsafe fn load_sprite_by_layer(
|
|
| 2057 |
return 1;
|
| 2058 |
}
|
| 2059 |
|
| 2060 |
-
// Call set_active_layer (vtable slot 5) — returns
|
|
|
|
| 2061 |
let palette_ctx = set_active_layer(this, layer as i32) as *mut PaletteContext;
|
| 2062 |
|
| 2063 |
if !is_valid_sprite_id(id as i32) {
|
|
@@ -2130,13 +2128,13 @@ pub unsafe fn load_font(
|
|
| 2130 |
) -> u32 {
|
| 2131 |
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 2132 |
|
| 2133 |
-
|
| 2134 |
return 0;
|
| 2135 |
-
}
|
| 2136 |
|
| 2137 |
let base = &mut (*this).base;
|
| 2138 |
-
let layer_ctx = base.layer_contexts[layer
|
| 2139 |
-
if layer_ctx
|
| 2140 |
return 0;
|
| 2141 |
}
|
| 2142 |
|
|
@@ -2156,12 +2154,7 @@ pub unsafe fn load_font(
|
|
| 2156 |
}
|
| 2157 |
|
| 2158 |
// Load and parse the font data.
|
| 2159 |
-
let result = font_load_from_gfx(
|
| 2160 |
-
font_obj,
|
| 2161 |
-
gfx_dir,
|
| 2162 |
-
layer_ctx as *mut PaletteContext,
|
| 2163 |
-
filename,
|
| 2164 |
-
);
|
| 2165 |
if result == 0 {
|
| 2166 |
// Original leaks here on failure (via an unported cleanup helper).
|
| 2167 |
// We match that behavior rather than introducing a free path that
|
|
@@ -2173,8 +2166,8 @@ pub unsafe fn load_font(
|
|
| 2173 |
// this font slot at DisplayBase + 0x301C + font_id*4 (the gap region
|
| 2174 |
// next to font_table), then bumps the layer visibility counter.
|
| 2175 |
base.font_table[font_id as usize] = font_obj;
|
| 2176 |
-
base.font_layers[font_id as usize] = layer;
|
| 2177 |
-
base.layer_visibility[layer
|
| 2178 |
|
| 2179 |
1
|
| 2180 |
}
|
|
@@ -2213,13 +2206,17 @@ pub unsafe fn load_font_extension(
|
|
| 2213 |
// The original always reads layer_contexts[1] here, regardless of which
|
| 2214 |
// mode owns the font. This matches the disassembly at 0x52364d:
|
| 2215 |
// `MOV ECX, [EDI+0x3120]` (= layer_contexts[1])
|
| 2216 |
-
let layer1_ctx = base.layer_contexts[
|
| 2217 |
let mut resolved_rgb: u32 = 0;
|
| 2218 |
let _ = palette_context_lookup_entry(layer1_ctx, palette_value as i32, &mut resolved_rgb);
|
| 2219 |
|
| 2220 |
-
// The actual font extension call uses the font's owning layer's palette
|
| 2221 |
-
|
| 2222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2223 |
|
| 2224 |
font_extend(font_obj, layer_ctx, path, char_map, resolved_rgb);
|
| 2225 |
|
|
|
|
| 7 |
font_extend, font_get_info_impl, font_get_metric_impl, font_load_from_gfx,
|
| 8 |
font_set_palette_impl, font_set_param_impl, Font,
|
| 9 |
};
|
| 10 |
+
use crate::render::display::layer::Layer;
|
| 11 |
use crate::render::display::line_draw::Vertex;
|
| 12 |
use crate::render::sprite::gfx_dir::GfxDir;
|
| 13 |
use crate::render::sprite::sprite::{LayerSprite, LayerSpriteFrame};
|
|
|
|
| 905 |
(1..=0x3FF).contains(&id)
|
| 906 |
}
|
| 907 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 908 |
/// Port of DisplayGfx::GetSpriteInfo (vtable slot 6, 0x523500).
|
| 909 |
///
|
| 910 |
/// Looks up sprite metadata for `layer` (valid: 1..=0x3FF). Checks
|
|
|
|
| 1381 |
/// # Safety
|
| 1382 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1383 |
pub unsafe extern "thiscall" fn set_layer_color(this: *mut DisplayGfx, layer: i32, color: i32) {
|
| 1384 |
+
let Some(layer) = Layer::try_from_i32(layer) else {
|
|
|
|
| 1385 |
return;
|
| 1386 |
+
};
|
| 1387 |
|
| 1388 |
// Only allocate if no context exists for this layer
|
| 1389 |
+
if !(*this).base.layer_contexts[layer.idx()].is_null() {
|
| 1390 |
return;
|
| 1391 |
}
|
| 1392 |
|
|
|
|
| 1399 |
core::ptr::write_bytes(ctx, 0, 0x70C);
|
| 1400 |
|
| 1401 |
let result = if ctx.is_null() {
|
| 1402 |
+
core::ptr::null_mut()
|
| 1403 |
} else {
|
| 1404 |
let ctx = ctx as *mut PaletteContext;
|
| 1405 |
palette_context_init(ctx, start as i16, (start as i32 + color - 1) as i16);
|
| 1406 |
+
ctx
|
| 1407 |
};
|
| 1408 |
|
| 1409 |
+
(*this).base.layer_contexts[layer.idx()] = result;
|
| 1410 |
+
(*this).base.layer_visibility[layer.idx()] = 0;
|
| 1411 |
}
|
| 1412 |
|
| 1413 |
/// Palette slot allocator — port of FUN_00523190.
|
|
|
|
| 1487 |
/// # Safety
|
| 1488 |
/// `this` must be a valid `*mut DisplayGfx`.
|
| 1489 |
pub unsafe extern "thiscall" fn set_active_layer(this: *mut DisplayGfx, layer: i32) -> *mut u8 {
|
| 1490 |
+
match Layer::try_from_i32(layer) {
|
| 1491 |
+
Some(layer) => (*this).base.layer_contexts[layer.idx()] as *mut u8,
|
| 1492 |
+
None => core::ptr::null_mut(),
|
|
|
|
| 1493 |
}
|
| 1494 |
}
|
| 1495 |
|
|
|
|
| 1609 |
layer: i32,
|
| 1610 |
visible: i32,
|
| 1611 |
) {
|
| 1612 |
+
let Some(layer) = Layer::try_from_i32(layer) else {
|
| 1613 |
+
return;
|
| 1614 |
+
};
|
| 1615 |
+
|
| 1616 |
+
let layer_ctx = (*this).base.layer_contexts[layer.idx()];
|
| 1617 |
if !layer_ctx.is_null() {
|
| 1618 |
update_palette(this, layer_ctx, visible);
|
| 1619 |
}
|
| 1620 |
|
| 1621 |
if visible < 0 {
|
| 1622 |
+
(*this).base.layer_visibility[layer.idx()] = 0;
|
| 1623 |
}
|
| 1624 |
}
|
| 1625 |
|
|
|
|
| 1669 |
sprite: *mut Sprite,
|
| 1670 |
gfx_dir: *mut GfxDir,
|
| 1671 |
name: *const c_char,
|
| 1672 |
+
layer_ctx: *mut PaletteContext,
|
| 1673 |
) -> i32,
|
| 1674 |
) -> i32 {
|
| 1675 |
// Bit 23 set = already loaded sentinel
|
|
|
|
| 1677 |
return 1;
|
| 1678 |
}
|
| 1679 |
|
| 1680 |
+
let Some(layer) = Layer::try_from_u32(layer) else {
|
| 1681 |
return 0;
|
| 1682 |
+
};
|
| 1683 |
let base = &mut (*this).base;
|
| 1684 |
+
let layer_ctx = base.layer_contexts[layer.idx()];
|
| 1685 |
+
if layer_ctx.is_null() {
|
| 1686 |
return 0;
|
| 1687 |
}
|
| 1688 |
if !is_valid_sprite_id(id as i32) {
|
|
|
|
| 1701 |
construct_sprite(sprite, base.sprite_cache);
|
| 1702 |
|
| 1703 |
// Load from VFS
|
|
|
|
| 1704 |
let result = load_sprite_from_vfs(sprite, gfx_dir, _name, layer_ctx);
|
| 1705 |
if result == 0 {
|
| 1706 |
// Load failed — destroy sprite via vtable[0]
|
|
|
|
| 1714 |
// Store in sprite table
|
| 1715 |
let base = &mut (*this).base;
|
| 1716 |
base.sprite_ptrs[id as usize] = sprite;
|
| 1717 |
+
base.sprite_layers[id as usize] = layer.as_u32();
|
| 1718 |
+
base.layer_visibility[layer.idx()] += 1;
|
| 1719 |
|
| 1720 |
// Update max_frames on the sprite if flag is set
|
| 1721 |
if flag != 0 {
|
|
|
|
| 1771 |
|
| 1772 |
// 2. Store gfx_dir and palette_ctx in sprite
|
| 1773 |
(*sprite).gfx_dir = gfx_dir;
|
| 1774 |
+
(*sprite).palette_ctx = palette_ctx;
|
| 1775 |
|
| 1776 |
// 3. Load image stream from GfxDir
|
| 1777 |
let stream = call_gfx_load_image(gfx_dir, name) as *mut GfxDirStream;
|
|
|
|
| 2054 |
return 1;
|
| 2055 |
}
|
| 2056 |
|
| 2057 |
+
// Call set_active_layer (vtable slot 5) — returns the layer's
|
| 2058 |
+
// PaletteContext, or null if `layer` is out of range.
|
| 2059 |
let palette_ctx = set_active_layer(this, layer as i32) as *mut PaletteContext;
|
| 2060 |
|
| 2061 |
if !is_valid_sprite_id(id as i32) {
|
|
|
|
| 2128 |
) -> u32 {
|
| 2129 |
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 2130 |
|
| 2131 |
+
let Some(layer) = Layer::try_from_u32(layer) else {
|
| 2132 |
return 0;
|
| 2133 |
+
};
|
| 2134 |
|
| 2135 |
let base = &mut (*this).base;
|
| 2136 |
+
let layer_ctx = base.layer_contexts[layer.idx()];
|
| 2137 |
+
if layer_ctx.is_null() {
|
| 2138 |
return 0;
|
| 2139 |
}
|
| 2140 |
|
|
|
|
| 2154 |
}
|
| 2155 |
|
| 2156 |
// Load and parse the font data.
|
| 2157 |
+
let result = font_load_from_gfx(font_obj, gfx_dir, layer_ctx, filename);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2158 |
if result == 0 {
|
| 2159 |
// Original leaks here on failure (via an unported cleanup helper).
|
| 2160 |
// We match that behavior rather than introducing a free path that
|
|
|
|
| 2166 |
// this font slot at DisplayBase + 0x301C + font_id*4 (the gap region
|
| 2167 |
// next to font_table), then bumps the layer visibility counter.
|
| 2168 |
base.font_table[font_id as usize] = font_obj;
|
| 2169 |
+
base.font_layers[font_id as usize] = layer.as_u32();
|
| 2170 |
+
base.layer_visibility[layer.idx()] += 1;
|
| 2171 |
|
| 2172 |
1
|
| 2173 |
}
|
|
|
|
| 2206 |
// The original always reads layer_contexts[1] here, regardless of which
|
| 2207 |
// mode owns the font. This matches the disassembly at 0x52364d:
|
| 2208 |
// `MOV ECX, [EDI+0x3120]` (= layer_contexts[1])
|
| 2209 |
+
let layer1_ctx = base.layer_contexts[Layer::ONE.idx()];
|
| 2210 |
let mut resolved_rgb: u32 = 0;
|
| 2211 |
let _ = palette_context_lookup_entry(layer1_ctx, palette_value as i32, &mut resolved_rgb);
|
| 2212 |
|
| 2213 |
+
// The actual font extension call uses the font's owning layer's palette
|
| 2214 |
+
// ctx. The original reads `layer_contexts[font_layers[font_id]]` directly
|
| 2215 |
+
// without validation; if `font_layers` is 0 it reads index 0 (always
|
| 2216 |
+
// null on a properly-initialized DisplayBase). We preserve that exact
|
| 2217 |
+
// index lookup rather than going through `Layer` here.
|
| 2218 |
+
let layer_idx = base.font_layers[font_id as usize] as usize;
|
| 2219 |
+
let layer_ctx = base.layer_contexts[layer_idx];
|
| 2220 |
|
| 2221 |
font_extend(font_obj, layer_ctx, path, char_map, resolved_rgb);
|
| 2222 |
|
|
@@ -1,4 +1,6 @@
|
|
| 1 |
-
use crate::render::{
|
|
|
|
|
|
|
| 2 |
|
| 3 |
// Re-export SpriteId from its own module to keep this file focused on struct layouts.
|
| 4 |
pub use super::sprite_id::SpriteId;
|
|
@@ -194,8 +196,8 @@ pub struct LayerSprite {
|
|
| 194 |
pub gfx_dir: *mut GfxDir,
|
| 195 |
/// 0x58: DisplayGfx pointer (set by caller before load).
|
| 196 |
pub display_gfx: *mut DisplayGfx,
|
| 197 |
-
/// 0x5C: PaletteContext pointer (
|
| 198 |
-
pub palette_ctx:
|
| 199 |
/// 0x60: Header field from stream (4 bytes).
|
| 200 |
pub field_60: u32,
|
| 201 |
/// 0x64: Header field from stream (2 bytes).
|
|
|
|
| 1 |
+
use crate::render::{
|
| 2 |
+
display::gfx::DisplayGfx, palette::PaletteContext, sprite::gfx_dir::GfxDir, SpriteCache,
|
| 3 |
+
};
|
| 4 |
|
| 5 |
// Re-export SpriteId from its own module to keep this file focused on struct layouts.
|
| 6 |
pub use super::sprite_id::SpriteId;
|
|
|
|
| 196 |
pub gfx_dir: *mut GfxDir,
|
| 197 |
/// 0x58: DisplayGfx pointer (set by caller before load).
|
| 198 |
pub display_gfx: *mut DisplayGfx,
|
| 199 |
+
/// 0x5C: PaletteContext pointer (set by load_sprite_by_name).
|
| 200 |
+
pub palette_ctx: *mut PaletteContext,
|
| 201 |
/// 0x60: Header field from stream (4 bytes).
|
| 202 |
pub field_60: u32,
|
| 203 |
/// 0x64: Header field from stream (2 bytes).
|
|
@@ -1341,7 +1341,7 @@ unsafe extern "cdecl" fn wa_load_sprite_from_vfs(
|
|
| 1341 |
_sprite: *mut openwa_core::render::sprite::Sprite,
|
| 1342 |
_gfx_dir: *mut GfxDir,
|
| 1343 |
_name: *const c_char,
|
| 1344 |
-
_layer_ctx:
|
| 1345 |
) -> i32 {
|
| 1346 |
core::arch::naked_asm!(
|
| 1347 |
// cdecl: +4=sprite, +8=gfx, +12=name, +16=layer_ctx
|
|
|
|
| 1341 |
_sprite: *mut openwa_core::render::sprite::Sprite,
|
| 1342 |
_gfx_dir: *mut GfxDir,
|
| 1343 |
_name: *const c_char,
|
| 1344 |
+
_layer_ctx: *mut openwa_core::render::palette::PaletteContext,
|
| 1345 |
) -> i32 {
|
| 1346 |
core::arch::naked_asm!(
|
| 1347 |
// cdecl: +4=sprite, +8=gfx, +12=name, +16=layer_ctx
|