paavohtl Claude Opus 4.6 (1M context) commited on
Commit
2eaf41f
·
1 Parent(s): 6c987d4

refactor: add TaskStateMachine and GfxDirEntry structs, eliminate pointer arithmetic

Browse files

New 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 at offset +0x18
121
- let gradient_height = *(gradient_sprite.add(0x18) as *const i32);
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 DisplayGfx-compatible object
243
- let gfx_obj = wa_malloc(0x2C);
 
 
244
  if gfx_obj.is_null() {
245
  wa_free(data);
246
  return;
247
  }
248
- core::ptr::write_bytes(gfx_obj, 0, 0x2C);
249
- let g = gfx_obj as *mut u32;
250
- *g = rb(0x6640EC); // vtable
251
- *g.add(2) = data as u32; // pixel data
252
- *g.add(4) = stride; // bytes per row
253
- *g.add(5) = 0; // x offset
254
- *g.add(6) = total_height as u32; // height (CTaskLand checks `if (0 < [6])`)
255
- (*ddgame).gradient_image = gfx_obj;
 
 
 
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 = wa_malloc(0x2C);
1283
- core::ptr::write_bytes(obj, 0, 0x2C);
1284
  if !obj.is_null() {
1285
- *(obj as *mut u32) = rb(0x6640EC); // vtable (DisplayGfx vtable, vtable[4]=ProcessFrame_stub)
1286
- // [6] = height/width = 0 → CTaskLand loop: `if (0 < 0)` → skip
1287
- (*ddgame).gradient_image = obj;
 
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 u8;
94
 
95
  while !entry.is_null() {
96
- // Compare entry name at entry+0x0C with our buffer
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
- // Next in linked list: entry+0x00
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.add(4) as *const u32);
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.add(4) as *const u32));
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
- /// Convention: usercall(ESI=object, ECX=param1, EDI=height) + 1 stack(width), RET 0x4.
9
- ///
10
- /// Allocates a bit-per-cell grid buffer. `param1` is typically 1 (cells per unit).
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(object: *mut u8, param1: u32, width: u32, height: u32) {
17
- // Row stride: bits-to-bytes rounded up, then aligned to 4
18
- let bits = param1.wrapping_mul(width).wrapping_add(7) as i32;
 
 
 
 
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 obj = object as *mut u32;
39
- *obj.add(0) = rb(0x6640EC); // vtable
40
- *obj.add(1) = 0;
41
- *obj.add(2) = buffer as u32; // data pointer
42
- *obj.add(3) = param1;
43
- *obj.add(4) = row_stride as u32;
44
- *obj.add(5) = width;
45
- *obj.add(6) = height;
46
- *obj.add(7) = 0;
47
- *obj.add(8) = 0;
48
- *obj.add(9) = width; // duplicate
49
- *obj.add(10) = height; // duplicate
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
  }