paavohtl Claude Opus 4.6 commited on
Commit
c60058c
·
1 Parent(s): 7cc32c4

feat: port PaletteContext__MapColor to Rust with global hook

Browse files

Pure Rust reimplementation of the palette color mapping function
(0x5412B0). All 10 callers now use the Rust version via MinHook detour.

Uses raw pointer ops to match WA's cache overflow behavior (cache_count
can reach 256, writing past the cache array into the dirty field) and
to avoid noalias miscompilation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

crates/openwa-core/src/render/mod.rs CHANGED
@@ -1,5 +1,6 @@
1
  pub mod gfx_dir;
2
  pub mod landscape;
 
3
  pub mod queue;
4
  pub mod spr;
5
  pub mod sprite;
 
1
  pub mod gfx_dir;
2
  pub mod landscape;
3
+ pub mod palette;
4
  pub mod queue;
5
  pub mod spr;
6
  pub mod sprite;
crates/openwa-core/src/render/palette.rs ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Palette context — maps RGB colors to display palette indices.
2
+ //!
3
+ //! WA's `PaletteContext` maintains a 256-entry palette with a free-slot
4
+ //! allocator and a recently-mapped cache for fast repeated lookups.
5
+
6
+ /// PaletteContext struct layout.
7
+ ///
8
+ /// Used by `PaletteContext__MapColor` (0x5412B0) and related functions.
9
+ /// May be embedded in a larger composite object (e.g. GfxDir + palette data).
10
+ #[repr(C)]
11
+ pub struct PaletteContext {
12
+ /// +0x00: Unknown (possibly vtable or parent object field).
13
+ pub _unknown_00: u32,
14
+ /// +0x04: RGB values stored per palette index (24-bit RGB in low 3 bytes).
15
+ pub rgb_table: [u32; 256],
16
+ /// +0x404: In-use flag per palette index (1 = allocated).
17
+ pub in_use: [u8; 256],
18
+ /// +0x504: Number of free slots remaining in the free stack.
19
+ pub free_count: i16,
20
+ /// +0x506: Free slot index stack (LIFO). Entries are palette indices.
21
+ pub free_stack: [u8; 256],
22
+ /// +0x606: Number of entries in the recently-mapped cache.
23
+ pub cache_count: i16,
24
+ /// +0x608: Recently-mapped palette indices (cache for fast lookup).
25
+ pub cache: [u8; 256],
26
+ /// +0x708: Dirty flag (set to 1 when a new slot is allocated).
27
+ pub dirty: u16,
28
+ }
29
+
30
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, rgb_table) == 0x04);
31
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, in_use) == 0x404);
32
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, free_count) == 0x504);
33
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, free_stack) == 0x506);
34
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, cache_count) == 0x606);
35
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, cache) == 0x608);
36
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, dirty) == 0x708);
37
+
38
+ /// Map an RGB color to the nearest display palette index.
39
+ ///
40
+ /// Rust port of `PaletteContext__MapColor` (0x5412B0). Operates on a raw
41
+ /// pointer to WA's PaletteContext struct.
42
+ ///
43
+ /// Algorithm:
44
+ /// 1. Search the recently-mapped cache for an exact 24-bit RGB match
45
+ /// 2. On miss: pop a free slot, store the RGB, add to cache, return new index
46
+ /// 3. If no free slots: return 0xFFFFFFFF
47
+ ///
48
+ /// # Safety
49
+ ///
50
+ /// `ctx` must point to a valid WA PaletteContext struct.
51
+ pub unsafe fn palette_map_color(ctx: *mut PaletteContext, rgb: u32) -> u32 {
52
+ // Raw pointer ops throughout — no &mut to avoid noalias miscompilation,
53
+ // and to match WA's overflow behavior when cache_count reaches 256
54
+ // (writes past cache[] into dirty field, which is then set to 1 anyway).
55
+ let p = ctx as *mut u8;
56
+
57
+ let cache_count = *(p.add(0x606) as *const i16);
58
+
59
+ // Search cache for matching RGB (24-bit compare)
60
+ for i in 0..cache_count as usize {
61
+ let idx = *p.add(0x608 + i) as usize;
62
+ if idx != 0 && (*(p.add(0x04 + idx * 4) as *const u32) ^ rgb) & 0xFF_FFFF == 0 {
63
+ return idx as u32;
64
+ }
65
+ }
66
+
67
+ // Cache miss — allocate a free slot
68
+ let free_count = *(p.add(0x504) as *const i16);
69
+ if free_count <= 0 {
70
+ return 0xFFFF_FFFF;
71
+ }
72
+
73
+ let new_free_count = free_count - 1;
74
+ *(p.add(0x504) as *mut i16) = new_free_count;
75
+ let slot = *p.add(0x506 + new_free_count as usize);
76
+ let slot_idx = slot as usize;
77
+
78
+ // Add to cache (may overflow into dirty field at +0x708 when cache is full —
79
+ // matches WA behavior, and dirty is set to 1 immediately after anyway)
80
+ *p.add(0x608 + cache_count as usize) = slot;
81
+ *(p.add(0x606) as *mut i16) = cache_count + 1;
82
+
83
+ // Store RGB and mark in-use
84
+ *(p.add(0x04 + slot_idx * 4) as *mut u32) = rgb;
85
+ *p.add(0x404 + slot_idx) = 1;
86
+ *(p.add(0x708) as *mut u16) = 1;
87
+
88
+ slot_idx as u32
89
+ }
crates/openwa-wormkit/src/replacements/sprite.rs CHANGED
@@ -42,14 +42,17 @@ unsafe extern "cdecl" fn construct_sprite_impl(sprite: u32, context: u32) {
42
  usercall_trampoline!(fn trampoline_process_sprite; impl_fn = process_sprite_impl;
43
  regs = [eax, ecx]; stack_params = 1; ret_bytes = "0x4");
44
 
45
- static mut PALETTE_MAP_COLOR_ADDR: u32 = 0;
46
-
47
- /// Call WA's PaletteContext__MapColor to find the nearest display palette index.
48
- #[cfg(target_arch = "x86")]
49
- unsafe fn palette_map_color(palette_ctx: u32, rgb: u32) -> u8 {
50
- let f: unsafe extern "thiscall" fn(u32, u32) -> u32 =
51
- core::mem::transmute(PALETTE_MAP_COLOR_ADDR as usize);
52
- f(palette_ctx, rgb) as u8
 
 
 
53
  }
54
 
55
  unsafe extern "cdecl" fn process_sprite_impl(sprite: u32, palette_ctx: u32, raw_data: u32) -> u32 {
@@ -100,11 +103,12 @@ unsafe extern "cdecl" fn process_sprite_impl(sprite: u32, palette_ctx: u32, raw_
100
 
101
  // Map each RGB entry to display palette index
102
  let rgb_start = data_ptr.add(hdr.palette_offset);
 
103
  for i in 0..hdr.palette_count as usize {
104
  // Read 4 bytes (3 RGB + 1 from next entry, matching WA behavior)
105
  let rgb_val = *(rgb_start.add(i * 3) as *const u32);
106
- let mapped = palette_map_color(palette_ctx, rgb_val);
107
- *palette_base.add(1 + i) = mapped;
108
  }
109
 
110
  // --- Secondary frame table (if header_flags & 0x4000) ---
@@ -188,8 +192,6 @@ unsafe extern "cdecl" fn process_sprite_impl(sprite: u32, palette_ctx: u32, raw_
188
 
189
  pub fn install() -> Result<(), String> {
190
  unsafe {
191
- PALETTE_MAP_COLOR_ADDR = rb(va::PALETTE_CONTEXT_MAP_COLOR);
192
-
193
  let _ = hook::install(
194
  "ConstructSprite",
195
  va::CONSTRUCT_SPRITE,
@@ -201,6 +203,12 @@ pub fn install() -> Result<(), String> {
201
  va::PROCESS_SPRITE,
202
  trampoline_process_sprite as *const (),
203
  )?;
 
 
 
 
 
 
204
  }
205
  Ok(())
206
  }
 
42
  usercall_trampoline!(fn trampoline_process_sprite; impl_fn = process_sprite_impl;
43
  regs = [eax, ecx]; stack_params = 1; ret_bytes = "0x4");
44
 
45
+ // ---------------------------------------------------------------------------
46
+ // PaletteContext__MapColor (0x5412B0)
47
+ // thiscall(ECX=palette_ctx, stack=rgb_u32), RET 0x4
48
+ // ---------------------------------------------------------------------------
49
+
50
+ usercall_trampoline!(fn trampoline_palette_map_color; impl_fn = palette_map_color_impl;
51
+ reg = ecx; stack_params = 1; ret_bytes = "0x4");
52
+
53
+ unsafe extern "cdecl" fn palette_map_color_impl(palette_ctx: u32, rgb: u32) -> u32 {
54
+ let ctx = palette_ctx as *mut openwa_core::render::palette::PaletteContext;
55
+ openwa_core::render::palette::palette_map_color(ctx, rgb)
56
  }
57
 
58
  unsafe extern "cdecl" fn process_sprite_impl(sprite: u32, palette_ctx: u32, raw_data: u32) -> u32 {
 
103
 
104
  // Map each RGB entry to display palette index
105
  let rgb_start = data_ptr.add(hdr.palette_offset);
106
+ let ctx = palette_ctx as *mut openwa_core::render::palette::PaletteContext;
107
  for i in 0..hdr.palette_count as usize {
108
  // Read 4 bytes (3 RGB + 1 from next entry, matching WA behavior)
109
  let rgb_val = *(rgb_start.add(i * 3) as *const u32);
110
+ let mapped = openwa_core::render::palette::palette_map_color(ctx, rgb_val);
111
+ *palette_base.add(1 + i) = mapped as u8;
112
  }
113
 
114
  // --- Secondary frame table (if header_flags & 0x4000) ---
 
192
 
193
  pub fn install() -> Result<(), String> {
194
  unsafe {
 
 
195
  let _ = hook::install(
196
  "ConstructSprite",
197
  va::CONSTRUCT_SPRITE,
 
203
  va::PROCESS_SPRITE,
204
  trampoline_process_sprite as *const (),
205
  )?;
206
+
207
+ let _ = hook::install(
208
+ "PaletteContext__MapColor",
209
+ va::PALETTE_CONTEXT_MAP_COLOR,
210
+ trampoline_palette_map_color as *const (),
211
+ )?;
212
  }
213
  Ok(())
214
  }