paavohtl Claude Opus 4.6 (1M context) commited on
Commit
5c0eb9d
·
1 Parent(s): 8a23377

feat: port DDDisplay slots 5, 23, 24 — layer management and palette update

Browse files

Port set_active_layer (trivial field read), update_palette (iterates
PaletteContext cache to copy RGBX entries into DisplayGfx palette, calls
PaletteCommit via usercall bridge), and set_layer_visibility (dispatches
to set_active_layer + update_palette).

Improve type safety: use typed PaletteContext struct instead of raw pointer
arithmetic, rename DisplayBase fields (font_table, layer_contexts,
layer_visibility) and DisplayGfx palette dirty range fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/display/base.rs CHANGED
@@ -45,18 +45,22 @@ pub struct DisplayBase {
45
  pub _field_3018: u32,
46
  // +0x301C..0x309C: gap (0x80 bytes, 32 u32s: indices 0xC07..0xC26)
47
  pub _gap_301c: [u32; 32],
48
- // +0x309C..0x311C: indices 0xC27..0xC46 (32 entries), zeroed by ctor
49
- pub _fields_309c: [u32; 32],
50
- // +0x311C..0x312C: indices 0xC47..0xC4A (4 entries), zeroed by ctor
51
- pub _fields_311c: [u32; 4],
 
 
 
52
  // +0x312C: index 0xC4B, zeroed by ctor
53
  pub _field_312c: u32,
54
  // +0x3130..0x352C: slot_table — 0xFF entries, all initialized to 1 by ctor
55
  pub slot_table: [u32; 0xFF],
56
  // +0x352C: index 0xD4B = 0xFFFFFFFF
57
  pub _field_352c: u32,
58
- // +0x3530..0x3540: indices 0xD4C..0xD4F (4 entries), zeroed by ctor
59
- pub _fields_3530: [u32; 4],
 
60
  // +0x3540: display initialized flag (set to 1 by DDDisplay__Init)
61
  pub display_initialized: u32,
62
  // +0x3544: unknown
 
45
  pub _field_3018: u32,
46
  // +0x301C..0x309C: gap (0x80 bytes, 32 u32s: indices 0xC07..0xC26)
47
  pub _gap_301c: [u32; 32],
48
+ // +0x309C..0x311C: font object pointers (32 entries), zeroed by ctor.
49
+ // Indexed by font_id (valid range 1-31). Used by GetFontInfo, GetFontMetric, SetFontParam.
50
+ pub font_table: [u32; 32],
51
+ // +0x311C..0x312C: layer context pointers (4 entries), zeroed by ctor.
52
+ // Indexed by layer (valid range 1-3). Returned by set_active_layer (vtable slot 5).
53
+ // Used as palette data input for update_palette (vtable slot 24).
54
+ pub layer_contexts: [u32; 4],
55
  // +0x312C: index 0xC4B, zeroed by ctor
56
  pub _field_312c: u32,
57
  // +0x3130..0x352C: slot_table — 0xFF entries, all initialized to 1 by ctor
58
  pub slot_table: [u32; 0xFF],
59
  // +0x352C: index 0xD4B = 0xFFFFFFFF
60
  pub _field_352c: u32,
61
+ // +0x3530..0x3540: layer visibility flags (4 entries), zeroed by ctor.
62
+ // Indexed by layer. Cleared to 0 by set_layer_visibility when visible < 0.
63
+ pub layer_visibility: [u32; 4],
64
  // +0x3540: display initialized flag (set to 1 by DDDisplay__Init)
65
  pub display_initialized: u32,
66
  // +0x3544: unknown
crates/openwa-core/src/display/dd_display.rs CHANGED
@@ -184,9 +184,13 @@ pub struct DDDisplayVtable {
184
  /// set layer visibility (0x56A5D0, RET 0x8)
185
  #[slot(23)]
186
  pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32, visible: i32),
187
- /// update palette from palette data (0x56A610, RET 0x8)
188
  #[slot(24)]
189
- pub update_palette: fn(this: *mut DDDisplay, palette_data: *mut i16, p3: i32),
 
 
 
 
190
  // Slot 25: stub (CGameTask__vt19)
191
  /// flush pending render state (0x56A580, plain RET)
192
  ///
@@ -285,6 +289,7 @@ bind_DDDisplayVtable!(DDDisplay, vtable);
285
  use super::gfx::DisplayGfx;
286
  use super::line_draw;
287
  use crate::bitgrid::DisplayBitGrid;
 
288
 
289
  /// Port of DDDisplay::GetDimensions (vtable slot 1, 0x56A460).
290
  ///
@@ -1020,3 +1025,149 @@ pub unsafe extern "thiscall" fn draw_pixel_strip(
1020
  }
1021
  }
1022
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  /// set layer visibility (0x56A5D0, RET 0x8)
185
  #[slot(23)]
186
  pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32, visible: i32),
187
+ /// update palette from PaletteContext (0x56A610, RET 0x8)
188
  #[slot(24)]
189
+ pub update_palette: fn(
190
+ this: *mut DDDisplay,
191
+ palette_ctx: *mut crate::render::palette::PaletteContext,
192
+ commit: i32,
193
+ ),
194
  // Slot 25: stub (CGameTask__vt19)
195
  /// flush pending render state (0x56A580, plain RET)
196
  ///
 
289
  use super::gfx::DisplayGfx;
290
  use super::line_draw;
291
  use crate::bitgrid::DisplayBitGrid;
292
+ use crate::render::palette::PaletteContext;
293
 
294
  /// Port of DDDisplay::GetDimensions (vtable slot 1, 0x56A460).
295
  ///
 
1025
  }
1026
  }
1027
  }
1028
+
1029
+ /// Port of DDDisplay::SetActiveLayer (vtable slot 5, 0x523270).
1030
+ ///
1031
+ /// Returns the layer context pointer for `layer` (valid: 1, 2, 3), or null
1032
+ /// if the layer index is out of range. The returned pointer is used as
1033
+ /// palette data input for `update_palette`.
1034
+ ///
1035
+ /// # Safety
1036
+ /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
1037
+ pub unsafe extern "thiscall" fn set_active_layer(this: *mut DDDisplay, layer: i32) -> *mut u8 {
1038
+ let gfx = this as *mut DisplayGfx;
1039
+ if (layer as u32).wrapping_sub(1) < 3 {
1040
+ (*gfx).base.layer_contexts[layer as usize] as *mut u8
1041
+ } else {
1042
+ core::ptr::null_mut()
1043
+ }
1044
+ }
1045
+
1046
+ /// Port of DDDisplay::UpdatePalette (vtable slot 24, 0x56A610).
1047
+ ///
1048
+ /// Updates DisplayGfx palette entries from a `PaletteContext`. The context's
1049
+ /// `cache` array lists which palette indices to copy, and `cache_count` says
1050
+ /// how many. Each index's RGB is read from `rgb_table` and written to the
1051
+ /// DisplayGfx `palette_entries` table.
1052
+ ///
1053
+ /// If `commit != 0`, calls the palette commit function (0x56CD20) to push
1054
+ /// the updated entries to the DDraw surface palette.
1055
+ ///
1056
+ /// # Safety
1057
+ /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
1058
+ /// `palette_ctx` must point to a valid `PaletteContext`.
1059
+ pub unsafe extern "thiscall" fn update_palette(
1060
+ this: *mut DDDisplay,
1061
+ palette_ctx: *mut PaletteContext,
1062
+ commit: i32,
1063
+ ) {
1064
+ let gfx = this as *mut DisplayGfx;
1065
+ let ctx = &mut *palette_ctx;
1066
+
1067
+ // Reset iteration counter
1068
+ ctx.cache_iter = 0;
1069
+
1070
+ if ctx.cache_count <= 0 {
1071
+ return;
1072
+ }
1073
+
1074
+ let dirty_min = ctx.dirty_range_min as i32;
1075
+ let dirty_max = ctx.dirty_range_max as i32;
1076
+
1077
+ // Mark iteration started
1078
+ ctx.cache_iter = 1;
1079
+
1080
+ // First index to update
1081
+ let mut idx = ctx.cache[0] as usize;
1082
+
1083
+ loop {
1084
+ // Read RGB entry from PaletteContext rgb_table (stored as u32: low 3 bytes = R, G, B)
1085
+ let rgb = ctx.rgb_table[idx].to_le_bytes();
1086
+
1087
+ // Write to DisplayGfx palette_entries: [R, G, B, flags=0]
1088
+ (*gfx).palette_entries[idx * 4] = rgb[0];
1089
+ (*gfx).palette_entries[idx * 4 + 1] = rgb[1];
1090
+ (*gfx).palette_entries[idx * 4 + 2] = rgb[2];
1091
+ (*gfx).palette_entries[idx * 4 + 3] = 0;
1092
+
1093
+ // Check if we've processed all entries
1094
+ if ctx.cache_iter >= ctx.cache_count {
1095
+ break;
1096
+ }
1097
+
1098
+ // Advance to next index
1099
+ idx = ctx.cache[ctx.cache_iter as usize] as usize;
1100
+ ctx.cache_iter += 1;
1101
+ }
1102
+
1103
+ // Track dirty palette range (expand to cover this update)
1104
+ if ((*gfx).palette_dirty_min as i32) > dirty_min {
1105
+ (*gfx).palette_dirty_min = dirty_min as u32;
1106
+ }
1107
+ if ((*gfx).palette_dirty_max as i32) < dirty_max {
1108
+ (*gfx).palette_dirty_max = dirty_max as u32;
1109
+ }
1110
+
1111
+ // Commit palette to DDraw surface if requested
1112
+ if commit != 0 {
1113
+ palette_commit(gfx);
1114
+ (*gfx).palette_dirty_min = 0x100;
1115
+ (*gfx).palette_dirty_max = 0xFFFF_FFFF;
1116
+ }
1117
+ }
1118
+
1119
+ /// Call WA's palette commit function (0x56CD20).
1120
+ ///
1121
+ /// Usercall: EAX = dirty_min, EDX = dirty_max, stack param = this (DisplayGfx*).
1122
+ /// Pushes updated palette entries to the DDraw surface palette.
1123
+ unsafe fn palette_commit(gfx: *mut DisplayGfx) {
1124
+ let dirty_min = (*gfx).palette_dirty_min;
1125
+ let dirty_max = (*gfx).palette_dirty_max;
1126
+ palette_commit_bridge(
1127
+ gfx as *mut u8,
1128
+ dirty_min,
1129
+ dirty_max,
1130
+ crate::rebase::rb(0x0056_CD20),
1131
+ );
1132
+ }
1133
+
1134
+ #[unsafe(naked)]
1135
+ unsafe extern "cdecl" fn palette_commit_bridge(
1136
+ _gfx: *mut u8,
1137
+ _dirty_min: u32,
1138
+ _dirty_max: u32,
1139
+ _target: u32,
1140
+ ) {
1141
+ core::arch::naked_asm!(
1142
+ "mov eax, [esp + 8]", // dirty_min
1143
+ "mov edx, [esp + 12]", // dirty_max
1144
+ "push dword ptr [esp + 4]", // gfx (this)
1145
+ "call dword ptr [esp + 20]", // target (+4 from our push, +16 from original)
1146
+ "ret",
1147
+ );
1148
+ }
1149
+
1150
+ /// Port of DDDisplay::SetLayerVisibility (vtable slot 23, 0x56A5D0).
1151
+ ///
1152
+ /// Gets the layer context via `set_active_layer`, then updates the palette
1153
+ /// from that context if it exists. If `visible < 0`, clears the layer's
1154
+ /// visibility flag.
1155
+ ///
1156
+ /// # Safety
1157
+ /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
1158
+ pub unsafe extern "thiscall" fn set_layer_visibility(
1159
+ this: *mut DDDisplay,
1160
+ layer: i32,
1161
+ visible: i32,
1162
+ ) {
1163
+ let gfx = this as *mut DisplayGfx;
1164
+
1165
+ let layer_ctx = set_active_layer(this, layer) as *mut PaletteContext;
1166
+ if !layer_ctx.is_null() {
1167
+ update_palette(this, layer_ctx, visible);
1168
+ }
1169
+
1170
+ if visible < 0 {
1171
+ (*gfx).base.layer_visibility[layer as usize] = 0;
1172
+ }
1173
+ }
crates/openwa-core/src/display/gfx.rs CHANGED
@@ -87,10 +87,12 @@ pub struct DisplayGfx {
87
  // =========================================================================
88
  // Palette metadata and render state (0x3D90 - 0x3D97)
89
  // =========================================================================
90
- /// 0x3D90: Palette entry count (init 0x100 = 256)
91
- pub palette_count: u32,
92
- /// 0x3D94: Unknown (init 0xFFFFFFFF)
93
- pub _unknown_3d94: u32,
 
 
94
 
95
  // =========================================================================
96
  // Render state and layer pointers (0x3D98 - 0x3DD3)
 
87
  // =========================================================================
88
  // Palette metadata and render state (0x3D90 - 0x3D97)
89
  // =========================================================================
90
+ /// 0x3D90: Lowest dirty palette index (init 0x100 = none dirty).
91
+ /// Updated by `update_palette`, reset to 0x100 after palette commit.
92
+ pub palette_dirty_min: u32,
93
+ /// 0x3D94: Highest dirty palette index (init 0xFFFFFFFF = none dirty).
94
+ /// Updated by `update_palette`, reset to 0xFFFFFFFF after palette commit.
95
+ pub palette_dirty_max: u32,
96
 
97
  // =========================================================================
98
  // Render state and layer pointers (0x3D98 - 0x3DD3)
crates/openwa-core/src/render/palette.rs CHANGED
@@ -9,8 +9,11 @@
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).
@@ -25,8 +28,13 @@ pub struct PaletteContext {
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);
@@ -34,6 +42,7 @@ 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
  ///
 
9
  /// May be embedded in a larger composite object (e.g. GfxDir + palette data).
10
  #[repr(C)]
11
  pub struct PaletteContext {
12
+ /// +0x00: Lowest palette index in the current update batch.
13
+ /// Set by the caller before `update_palette`; used to expand `palette_dirty_min`.
14
+ pub dirty_range_min: i16,
15
+ /// +0x02: Highest palette index in the current update batch.
16
+ pub dirty_range_max: i16,
17
  /// +0x04: RGB values stored per palette index (24-bit RGB in low 3 bytes).
18
  pub rgb_table: [u32; 256],
19
  /// +0x404: In-use flag per palette index (1 = allocated).
 
28
  pub cache: [u8; 256],
29
  /// +0x708: Dirty flag (set to 1 when a new slot is allocated).
30
  pub dirty: u16,
31
+ /// +0x70A: Iteration position in cache[] during `update_palette`.
32
+ /// Read/written by `update_palette` to track progress through the cache.
33
+ pub cache_iter: i16,
34
  }
35
 
36
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, dirty_range_min) == 0x00);
37
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, dirty_range_max) == 0x02);
38
  const _: () = assert!(core::mem::offset_of!(PaletteContext, rgb_table) == 0x04);
39
  const _: () = assert!(core::mem::offset_of!(PaletteContext, in_use) == 0x404);
40
  const _: () = assert!(core::mem::offset_of!(PaletteContext, free_count) == 0x504);
 
42
  const _: () = assert!(core::mem::offset_of!(PaletteContext, cache_count) == 0x606);
43
  const _: () = assert!(core::mem::offset_of!(PaletteContext, cache) == 0x608);
44
  const _: () = assert!(core::mem::offset_of!(PaletteContext, dirty) == 0x708);
45
+ const _: () = assert!(core::mem::offset_of!(PaletteContext, cache_iter) == 0x70A);
46
 
47
  /// Map an RGB color to the nearest display palette index.
48
  ///
crates/openwa-dll/src/replacements/display.rs CHANGED
@@ -557,10 +557,13 @@ pub fn install() -> Result<(), String> {
557
  set_camera_offset => dd_display::set_camera_offset,
558
  set_clip_rect => dd_display::set_clip_rect,
559
  is_sprite_loaded => dd_display::is_sprite_loaded,
560
- draw_scaled_sprite => draw_scaled_sprite,
 
 
 
561
  slot 19 [originals::BLIT_SPRITE] => blit_sprite,
562
  })?;
563
- let _ = log_line("[Display] DDDisplay: patched 15 methods → Rust");
564
  }
565
 
566
  Ok(())
 
557
  set_camera_offset => dd_display::set_camera_offset,
558
  set_clip_rect => dd_display::set_clip_rect,
559
  is_sprite_loaded => dd_display::is_sprite_loaded,
560
+ draw_scaled_sprite => draw_scaled_sprite,
561
+ set_active_layer => dd_display::set_active_layer,
562
+ set_layer_visibility => dd_display::set_layer_visibility,
563
+ update_palette => dd_display::update_palette,
564
  slot 19 [originals::BLIT_SPRITE] => blit_sprite,
565
  })?;
566
+ let _ = log_line("[Display] DDDisplay: patched 18 methods → Rust");
567
  }
568
 
569
  Ok(())