refactor: add TaskStateMachine and GfxDirEntry structs, eliminate pointer arithmetic
Browse filesNew structs:
- TaskStateMachine (#[repr(C)], 0x2C bytes): replaces raw u32 offset
writes in task_state_machine_init and gradient stub creation
- GfxDirEntry: typed linked-list entry with .next, .value, .name_ptr()
replaces .add(0x00), .add(0x04), .add(0x0C) in gfx_dir_find_entry
Updated files:
- task/state_machine.rs: struct definition + typed init
- render/gfx_handler.rs: GfxDirEntry struct, typed entry traversal
- display/gradient.rs: use TaskStateMachine for gradient image wrapper
and sprite height read
- engine/ddgame.rs: use TaskStateMachine for gradient stub
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/display/gradient.rs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
use crate::engine::ddgame::DDGame;
|
| 9 |
use crate::rebase::rb;
|
| 10 |
use crate::render::gfx_handler::call_gfx_find_and_load;
|
|
|
|
| 11 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 12 |
|
| 13 |
/// Palette context for gradient color mapping.
|
|
@@ -117,8 +118,8 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 117 |
return;
|
| 118 |
}
|
| 119 |
|
| 120 |
-
// Sprite height
|
| 121 |
-
let gradient_height = *(gradient_sprite
|
| 122 |
if gradient_height <= 0 {
|
| 123 |
return;
|
| 124 |
}
|
|
@@ -239,18 +240,23 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 239 |
}
|
| 240 |
}
|
| 241 |
|
| 242 |
-
// Step 8: Wrap pixel data in a
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
if gfx_obj.is_null() {
|
| 245 |
wa_free(data);
|
| 246 |
return;
|
| 247 |
}
|
| 248 |
-
core::ptr::write_bytes(
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
(*
|
|
|
|
|
|
|
|
|
|
| 256 |
}
|
|
|
|
| 8 |
use crate::engine::ddgame::DDGame;
|
| 9 |
use crate::rebase::rb;
|
| 10 |
use crate::render::gfx_handler::call_gfx_find_and_load;
|
| 11 |
+
use crate::task::state_machine::TaskStateMachine;
|
| 12 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 13 |
|
| 14 |
/// Palette context for gradient color mapping.
|
|
|
|
| 118 |
return;
|
| 119 |
}
|
| 120 |
|
| 121 |
+
// Sprite height from TaskStateMachine layout (shared vtable 0x6640EC)
|
| 122 |
+
let gradient_height = (*(gradient_sprite as *const TaskStateMachine)).height as i32;
|
| 123 |
if gradient_height <= 0 {
|
| 124 |
return;
|
| 125 |
}
|
|
|
|
| 240 |
}
|
| 241 |
}
|
| 242 |
|
| 243 |
+
// Step 8: Wrap pixel data in a TaskStateMachine-compatible object
|
| 244 |
+
// (shares vtable 0x6640EC; CTaskLand reads .height to decide whether to render)
|
| 245 |
+
let gfx_obj =
|
| 246 |
+
wa_malloc(core::mem::size_of::<TaskStateMachine>() as u32) as *mut TaskStateMachine;
|
| 247 |
if gfx_obj.is_null() {
|
| 248 |
wa_free(data);
|
| 249 |
return;
|
| 250 |
}
|
| 251 |
+
core::ptr::write_bytes(
|
| 252 |
+
gfx_obj as *mut u8,
|
| 253 |
+
0,
|
| 254 |
+
core::mem::size_of::<TaskStateMachine>(),
|
| 255 |
+
);
|
| 256 |
+
(*gfx_obj).vtable = rb(0x6640EC);
|
| 257 |
+
(*gfx_obj).data = data;
|
| 258 |
+
(*gfx_obj).row_stride = stride;
|
| 259 |
+
(*gfx_obj).width = 0;
|
| 260 |
+
(*gfx_obj).height = total_height as u32;
|
| 261 |
+
(*ddgame).gradient_image = gfx_obj as *mut u8;
|
| 262 |
}
|
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -20,6 +20,7 @@ pub use crate::render::gfx_handler::{
|
|
| 20 |
use crate::render::landscape::PCLandscape;
|
| 21 |
use crate::render::queue::RenderQueue;
|
| 22 |
use crate::render::turn_order::TurnOrderWidget;
|
|
|
|
| 23 |
use crate::wa_alloc::wa_malloc;
|
| 24 |
|
| 25 |
/// DDGame — the main game engine object.
|
|
@@ -1279,12 +1280,13 @@ unsafe fn init_graphics_and_resources(
|
|
| 1279 |
// ── Gradient image stub (DDGame+0x30) ──
|
| 1280 |
// Minimal stub: [6]=0 (zero-width) so CTaskLand skips the gradient column loop.
|
| 1281 |
if (*ddgame).gradient_image.is_null() {
|
| 1282 |
-
let obj =
|
| 1283 |
-
|
| 1284 |
if !obj.is_null() {
|
| 1285 |
-
|
| 1286 |
-
|
| 1287 |
-
|
|
|
|
| 1288 |
}
|
| 1289 |
}
|
| 1290 |
|
|
|
|
| 20 |
use crate::render::landscape::PCLandscape;
|
| 21 |
use crate::render::queue::RenderQueue;
|
| 22 |
use crate::render::turn_order::TurnOrderWidget;
|
| 23 |
+
use crate::task::state_machine::TaskStateMachine;
|
| 24 |
use crate::wa_alloc::wa_malloc;
|
| 25 |
|
| 26 |
/// DDGame — the main game engine object.
|
|
|
|
| 1280 |
// ── Gradient image stub (DDGame+0x30) ──
|
| 1281 |
// Minimal stub: [6]=0 (zero-width) so CTaskLand skips the gradient column loop.
|
| 1282 |
if (*ddgame).gradient_image.is_null() {
|
| 1283 |
+
let obj =
|
| 1284 |
+
wa_malloc(core::mem::size_of::<TaskStateMachine>() as u32) as *mut TaskStateMachine;
|
| 1285 |
if !obj.is_null() {
|
| 1286 |
+
core::ptr::write_bytes(obj as *mut u8, 0, core::mem::size_of::<TaskStateMachine>());
|
| 1287 |
+
(*obj).vtable = rb(0x6640EC);
|
| 1288 |
+
// height = 0 → CTaskLand skips the gradient column loop
|
| 1289 |
+
(*ddgame).gradient_image = obj as *mut u8;
|
| 1290 |
}
|
| 1291 |
}
|
| 1292 |
|
crates/openwa-core/src/render/gfx_handler.rs
CHANGED
|
@@ -4,6 +4,24 @@ use crate::address::va;
|
|
| 4 |
use crate::rebase::rb;
|
| 5 |
use crate::wa_alloc::wa_malloc;
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
/// Hash function for GfxDir entry lookup (FUN_566390).
|
| 8 |
///
|
| 9 |
/// 10-bit CRC-like hash over the global name buffer at 0x8ACF58.
|
|
@@ -90,11 +108,10 @@ pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_handler: *mut u8) -> *mut
|
|
| 90 |
// Walk linked list in hash bucket
|
| 91 |
// gfx_handler+4 = pointer to bucket array (1024 entries)
|
| 92 |
let bucket_array = *(gfx_handler.add(4) as *const *const u32);
|
| 93 |
-
let mut entry = *bucket_array.add(bucket as usize) as *mut
|
| 94 |
|
| 95 |
while !entry.is_null() {
|
| 96 |
-
|
| 97 |
-
let entry_name = entry.add(0x0C);
|
| 98 |
let mut match_found = true;
|
| 99 |
let mut k = 0usize;
|
| 100 |
loop {
|
|
@@ -111,11 +128,10 @@ pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_handler: *mut u8) -> *mut
|
|
| 111 |
}
|
| 112 |
|
| 113 |
if match_found {
|
| 114 |
-
return entry;
|
| 115 |
}
|
| 116 |
|
| 117 |
-
|
| 118 |
-
entry = *(entry as *const *mut u8);
|
| 119 |
}
|
| 120 |
|
| 121 |
// Not found — try fallback name after '|'
|
|
@@ -287,7 +303,7 @@ pub unsafe fn gfx_resource_create(
|
|
| 287 |
let vt = *(gfx_handler as *const *const u32);
|
| 288 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 289 |
core::mem::transmute(*vt.add(2));
|
| 290 |
-
let entry_val = *(entry
|
| 291 |
let cached = load_cached(gfx_handler, entry_val);
|
| 292 |
if !cached.is_null() {
|
| 293 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
|
@@ -332,7 +348,7 @@ pub(crate) unsafe fn call_gfx_find_and_load(
|
|
| 332 |
let dir_vt = *(gfx_dir as *const *const u32);
|
| 333 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 334 |
core::mem::transmute(*dir_vt.add(2));
|
| 335 |
-
let cached = load_cached(gfx_dir, *(entry
|
| 336 |
if !cached.is_null() {
|
| 337 |
// Wrap with DisplayGfx__Constructor_Maybe (0x4F5E80)
|
| 338 |
// This is stdcall(1 param), RET 0x4
|
|
|
|
| 4 |
use crate::rebase::rb;
|
| 5 |
use crate::wa_alloc::wa_malloc;
|
| 6 |
|
| 7 |
+
/// Entry in a GfxDir hash bucket linked list.
|
| 8 |
+
/// Each entry maps a name string to a cached resource value.
|
| 9 |
+
#[repr(C)]
|
| 10 |
+
pub struct GfxDirEntry {
|
| 11 |
+
pub next: *mut GfxDirEntry,
|
| 12 |
+
/// Passed to GfxHandler vtable[2] for cached resource lookup.
|
| 13 |
+
pub value: u32,
|
| 14 |
+
pub _unknown_08: u32,
|
| 15 |
+
// +0x0C: null-terminated name (variable-length, not in struct)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
impl GfxDirEntry {
|
| 19 |
+
/// Name string at +0x0C (immediately after the fixed fields).
|
| 20 |
+
pub unsafe fn name_ptr(&self) -> *const u8 {
|
| 21 |
+
(self as *const Self as *const u8).add(0x0C)
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
/// Hash function for GfxDir entry lookup (FUN_566390).
|
| 26 |
///
|
| 27 |
/// 10-bit CRC-like hash over the global name buffer at 0x8ACF58.
|
|
|
|
| 108 |
// Walk linked list in hash bucket
|
| 109 |
// gfx_handler+4 = pointer to bucket array (1024 entries)
|
| 110 |
let bucket_array = *(gfx_handler.add(4) as *const *const u32);
|
| 111 |
+
let mut entry = *bucket_array.add(bucket as usize) as *mut GfxDirEntry;
|
| 112 |
|
| 113 |
while !entry.is_null() {
|
| 114 |
+
let entry_name = (*entry).name_ptr();
|
|
|
|
| 115 |
let mut match_found = true;
|
| 116 |
let mut k = 0usize;
|
| 117 |
loop {
|
|
|
|
| 128 |
}
|
| 129 |
|
| 130 |
if match_found {
|
| 131 |
+
return entry as *mut u8;
|
| 132 |
}
|
| 133 |
|
| 134 |
+
entry = (*entry).next;
|
|
|
|
| 135 |
}
|
| 136 |
|
| 137 |
// Not found — try fallback name after '|'
|
|
|
|
| 303 |
let vt = *(gfx_handler as *const *const u32);
|
| 304 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 305 |
core::mem::transmute(*vt.add(2));
|
| 306 |
+
let entry_val = (*(entry as *const GfxDirEntry)).value;
|
| 307 |
let cached = load_cached(gfx_handler, entry_val);
|
| 308 |
if !cached.is_null() {
|
| 309 |
// DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
|
|
|
|
| 348 |
let dir_vt = *(gfx_dir as *const *const u32);
|
| 349 |
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 350 |
core::mem::transmute(*dir_vt.add(2));
|
| 351 |
+
let cached = load_cached(gfx_dir, (*(entry as *const GfxDirEntry)).value);
|
| 352 |
if !cached.is_null() {
|
| 353 |
// Wrap with DisplayGfx__Constructor_Maybe (0x4F5E80)
|
| 354 |
// This is stdcall(1 param), RET 0x4
|
crates/openwa-core/src/task/state_machine.rs
CHANGED
|
@@ -3,30 +3,50 @@
|
|
| 3 |
use crate::rebase::rb;
|
| 4 |
use crate::wa_alloc::wa_malloc;
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
/// Pure Rust implementation of TaskStateMachine__Init (0x4F6370).
|
| 7 |
///
|
| 8 |
-
///
|
| 9 |
-
///
|
| 10 |
-
///
|
| 11 |
-
/// `width` and `height` are pixel dimensions. The buffer is a row-major bitfield
|
| 12 |
-
/// with rows aligned to 4 bytes.
|
| 13 |
///
|
| 14 |
/// # Safety
|
| 15 |
/// `object` must point to a zero-filled allocation of at least 0x2C bytes.
|
| 16 |
-
pub unsafe fn task_state_machine_init(
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
let row_stride = ((bits >> 3) + 3) & !3;
|
| 20 |
let total_size = row_stride as u32 * height;
|
| 21 |
|
| 22 |
-
// Allocate data buffer with 0x20-byte header
|
| 23 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 24 |
let buffer = wa_malloc(alloc_size);
|
| 25 |
|
| 26 |
if buffer.is_null() {
|
| 27 |
return;
|
| 28 |
}
|
| 29 |
-
// Guard against integer overflow producing tiny alloc_size with huge total_size
|
| 30 |
if total_size as usize > alloc_size as usize {
|
| 31 |
return;
|
| 32 |
}
|
|
@@ -35,16 +55,16 @@ pub unsafe fn task_state_machine_init(object: *mut u8, param1: u32, width: u32,
|
|
| 35 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 36 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 37 |
|
| 38 |
-
let
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
}
|
|
|
|
| 3 |
use crate::rebase::rb;
|
| 4 |
use crate::wa_alloc::wa_malloc;
|
| 5 |
|
| 6 |
+
/// Bitfield grid buffer used for spatial collision/visibility queries.
|
| 7 |
+
/// Vtable at 0x6640EC (shared with DisplayGfx base).
|
| 8 |
+
/// Size: 0x2C bytes (11 u32 fields).
|
| 9 |
+
#[repr(C)]
|
| 10 |
+
pub struct TaskStateMachine {
|
| 11 |
+
pub vtable: u32,
|
| 12 |
+
pub _unused_04: u32,
|
| 13 |
+
pub data: *mut u8,
|
| 14 |
+
pub cells_per_unit: u32,
|
| 15 |
+
pub row_stride: u32,
|
| 16 |
+
pub width: u32,
|
| 17 |
+
pub height: u32,
|
| 18 |
+
pub _unused_1c: u32,
|
| 19 |
+
pub _unused_20: u32,
|
| 20 |
+
pub width_dup: u32,
|
| 21 |
+
pub height_dup: u32,
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const _: () = assert!(core::mem::size_of::<TaskStateMachine>() == 0x2C);
|
| 25 |
+
|
| 26 |
/// Pure Rust implementation of TaskStateMachine__Init (0x4F6370).
|
| 27 |
///
|
| 28 |
+
/// Allocates a bit-per-cell grid buffer. `cells_per_unit` is typically 1.
|
| 29 |
+
/// `width` and `height` are pixel dimensions. The buffer is a row-major
|
| 30 |
+
/// bitfield with rows aligned to 4 bytes.
|
|
|
|
|
|
|
| 31 |
///
|
| 32 |
/// # Safety
|
| 33 |
/// `object` must point to a zero-filled allocation of at least 0x2C bytes.
|
| 34 |
+
pub unsafe fn task_state_machine_init(
|
| 35 |
+
object: *mut u8,
|
| 36 |
+
cells_per_unit: u32,
|
| 37 |
+
width: u32,
|
| 38 |
+
height: u32,
|
| 39 |
+
) {
|
| 40 |
+
let bits = cells_per_unit.wrapping_mul(width).wrapping_add(7) as i32;
|
| 41 |
let row_stride = ((bits >> 3) + 3) & !3;
|
| 42 |
let total_size = row_stride as u32 * height;
|
| 43 |
|
|
|
|
| 44 |
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 45 |
let buffer = wa_malloc(alloc_size);
|
| 46 |
|
| 47 |
if buffer.is_null() {
|
| 48 |
return;
|
| 49 |
}
|
|
|
|
| 50 |
if total_size as usize > alloc_size as usize {
|
| 51 |
return;
|
| 52 |
}
|
|
|
|
| 55 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 56 |
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 57 |
|
| 58 |
+
let tsm = &mut *(object as *mut TaskStateMachine);
|
| 59 |
+
tsm.vtable = rb(0x6640EC);
|
| 60 |
+
tsm._unused_04 = 0;
|
| 61 |
+
tsm.data = buffer;
|
| 62 |
+
tsm.cells_per_unit = cells_per_unit;
|
| 63 |
+
tsm.row_stride = row_stride as u32;
|
| 64 |
+
tsm.width = width;
|
| 65 |
+
tsm.height = height;
|
| 66 |
+
tsm._unused_1c = 0;
|
| 67 |
+
tsm._unused_20 = 0;
|
| 68 |
+
tsm.width_dup = width;
|
| 69 |
+
tsm.height_dup = height;
|
| 70 |
}
|