paavohtl Claude Opus 4.6 (1M context) commited on
Commit
735f0bc
·
1 Parent(s): cd82e9c

feat: port DDDisplay blit dispatch — slots 19 and 20

Browse files

Port the two DDDisplay sprite blit vtable methods:

Slot 20 (draw_scaled_sprite): thiscall with camera offset + centering,
dispatches to core blit via blit_impl. Falls through for stippled modes.

Slot 19 (blit_sprite): the main sprite rendering function. Handles palette
manipulation, sprite array lookup, vtable[33] call for sprite surface data,
16-case orientation switch for camera coordinate mapping, shadow clear via
layer_2, and blit dispatch (normal/additive/color-blend). Falls through for
bitmap path and stippled/tiled modes.

Key discovery: Ghidra's decompiler incorrectly showed EBX/ESI/EDI as
implicit entry params ("unaff_" prefix). Full ASM analysis revealed they
are standard callee-saved registers loaded from vtable[33] output locals
within the function body — the function is plain thiscall.

Also: make acquire_render_lock and blit_impl pub(crate) for cross-module use.

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

crates/openwa-core/src/display/dd_display.rs CHANGED
@@ -151,13 +151,19 @@ pub struct DDDisplayVtable {
151
  /// draw scaled/rotated sprite (0x56B660, RET 0x20)
152
  ///
153
  /// x/y are fixed-point world coordinates.
154
- /// Dispatches by flags: bit 21 mirror, bit 26 additive, bit 27/28 blend modes.
 
 
 
 
 
 
155
  #[slot(20)]
156
  pub draw_scaled_sprite: fn(
157
  this: *mut DDDisplay,
158
  x: Fixed,
159
  y: Fixed,
160
- sprite: u32,
161
  src_x: i32,
162
  src_y: i32,
163
  src_w: i32,
@@ -441,7 +447,7 @@ unsafe fn flush_render_lock(gfx: *mut DisplayGfx) {
441
  /// # Safety
442
  /// `gfx` must be a valid `*mut DisplayGfx` with `layer_0` initialized.
443
  /// `g_DDDisplayWrapper` must be initialized.
444
- unsafe fn acquire_render_lock(gfx: *mut DisplayGfx) {
445
  if (*gfx).render_lock != 0 {
446
  return; // already locked
447
  }
@@ -844,6 +850,130 @@ pub unsafe extern "thiscall" fn draw_via_callback(
844
  ///
845
  /// # Safety
846
  /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  pub unsafe extern "thiscall" fn draw_pixel_strip(
848
  this: *mut DDDisplay,
849
  x: Fixed,
 
151
  /// draw scaled/rotated sprite (0x56B660, RET 0x20)
152
  ///
153
  /// x/y are fixed-point world coordinates.
154
+ /// `sprite` is a source DisplayBitGrid pointer.
155
+ /// Dispatches by flags:
156
+ /// - bit 20: blend mode toggle (0 = ColorTable/transparency, 1 = Copy/opaque)
157
+ /// - bit 21 (0x200000): additive blend (uses color_add_table LUT)
158
+ /// - bit 26 (0x4000000): color blend (uses color_blend_table LUT)
159
+ /// - bit 27 (0x8000000): stippled mode 0
160
+ /// - bit 28 (0x10000000): stippled mode 1
161
  #[slot(20)]
162
  pub draw_scaled_sprite: fn(
163
  this: *mut DDDisplay,
164
  x: Fixed,
165
  y: Fixed,
166
+ sprite: *mut crate::bitgrid::DisplayBitGrid,
167
  src_x: i32,
168
  src_y: i32,
169
  src_w: i32,
 
447
  /// # Safety
448
  /// `gfx` must be a valid `*mut DisplayGfx` with `layer_0` initialized.
449
  /// `g_DDDisplayWrapper` must be initialized.
450
+ pub unsafe fn acquire_render_lock(gfx: *mut DisplayGfx) {
451
  if (*gfx).render_lock != 0 {
452
  return; // already locked
453
  }
 
850
  ///
851
  /// # Safety
852
  /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
853
+ /// Port of DDDisplay::DrawScaledSprite (vtable slot 20, 0x56B660).
854
+ ///
855
+ /// Blits a source BitGrid to the display layer with camera offset and centering.
856
+ /// The source rectangle is `(src_x, src_y)` to `(src_w, src_h)` — width and
857
+ /// height are computed as `src_w - src_x` and `src_h - src_y`.
858
+ ///
859
+ /// Dispatches to different blit modes based on flag bits:
860
+ /// - Default: normal blit (color table mode for transparency)
861
+ /// - 0x200000: additive blend via color_add_table LUT
862
+ /// - 0x4000000: color blend via color_blend_table LUT
863
+ /// - 0x8000000 / 0x10000000: stippled (checkerboard) blit — NOT YET PORTED, falls through
864
+ ///
865
+ /// Bit 20 controls the blend mode: clear = ColorTable (transparency), set = Copy (opaque).
866
+ ///
867
+ /// # Safety
868
+ /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
869
+ /// `sprite` must be a valid `*mut DisplayBitGrid`.
870
+ pub unsafe fn draw_scaled_sprite(
871
+ this: *mut DDDisplay,
872
+ x: Fixed,
873
+ y: Fixed,
874
+ sprite: *mut crate::bitgrid::DisplayBitGrid,
875
+ src_x: i32,
876
+ src_y: i32,
877
+ src_w: i32,
878
+ src_h: i32,
879
+ flags: u32,
880
+ ) -> DrawScaledSpriteResult {
881
+ let gfx = this as *mut DisplayGfx;
882
+
883
+ let width = src_w - src_x;
884
+ let height = src_h - src_y;
885
+
886
+ // Signed division rounding toward zero: (n + (n >> 31)) >> 1
887
+ let half_w = if width < 0 {
888
+ (width + 1) / 2
889
+ } else {
890
+ width / 2
891
+ };
892
+ let half_h = if height < 0 {
893
+ (height + 1) / 2
894
+ } else {
895
+ height / 2
896
+ };
897
+
898
+ // Camera offset + centering + fixed-point to pixel conversion
899
+ let dst_x = (*gfx).camera_x - half_w + (x.0 >> 16);
900
+ let dst_y = (*gfx).camera_y - half_h + (y.0 >> 16);
901
+
902
+ // Blend mode from flag bit 20: clear = 1 (ColorTable), set = 0 (Copy)
903
+ let blend_mode = (!(flags >> 20)) & 1;
904
+
905
+ // Stippled modes — not yet ported
906
+ if (flags & 0x8000000) != 0 || (flags & 0x10000000) != 0 {
907
+ return DrawScaledSpriteResult::Unhandled;
908
+ }
909
+
910
+ // Determine color table pointer from flags
911
+ let color_table: *const u8 = if (flags & 0x200000) != 0 {
912
+ // Additive: use color_add_table (offset 0x4DF4 in DisplayGfx)
913
+ (*gfx).color_add_table.as_ptr()
914
+ } else if (flags & 0x4000000) != 0 {
915
+ // Color blend: use color_blend_table (offset 0x14DF4 in DisplayGfx)
916
+ (*gfx).color_blend_table.as_ptr()
917
+ } else {
918
+ // Normal: no color table (transparency handled by blend mode 1)
919
+ core::ptr::null()
920
+ };
921
+
922
+ // Early out if zero-size
923
+ if width <= 0 || height <= 0 {
924
+ return DrawScaledSpriteResult::Handled;
925
+ }
926
+
927
+ acquire_render_lock(gfx);
928
+
929
+ let layer = (*gfx).layer_0;
930
+
931
+ // Build flags for core blit: blend_mode in low 16 bits
932
+ // The core blit interprets: 0 = Copy, 1 = ColorTable
933
+ let blit_flags = blend_mode;
934
+
935
+ DrawScaledSpriteResult::Blit {
936
+ layer,
937
+ dst_x,
938
+ dst_y,
939
+ width,
940
+ height,
941
+ sprite,
942
+ src_x,
943
+ src_y,
944
+ color_table,
945
+ blit_flags,
946
+ }
947
+ }
948
+
949
+ /// Result of draw_scaled_sprite coordinate computation.
950
+ ///
951
+ /// The actual blit call is performed by the DLL hook layer, since it needs
952
+ /// access to `blit_impl` which bridges DisplayBitGrid → PixelGrid.
953
+ pub enum DrawScaledSpriteResult {
954
+ /// Blit should be performed with these parameters.
955
+ Blit {
956
+ layer: *mut crate::bitgrid::DisplayBitGrid,
957
+ dst_x: i32,
958
+ dst_y: i32,
959
+ width: i32,
960
+ height: i32,
961
+ sprite: *mut crate::bitgrid::DisplayBitGrid,
962
+ src_x: i32,
963
+ src_y: i32,
964
+ color_table: *const u8,
965
+ blit_flags: u32,
966
+ },
967
+ /// Already handled (e.g. zero-size, early out).
968
+ Handled,
969
+ /// Unhandled mode — caller should fall through to original WA function.
970
+ Unhandled,
971
+ }
972
+
973
+ /// Port of DDDisplay::DrawPixelStrip (vtable slot 15, 0x56BE10).
974
+ ///
975
+ /// # Safety
976
+ /// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
977
  pub unsafe extern "thiscall" fn draw_pixel_strip(
978
  this: *mut DDDisplay,
979
  x: Fixed,
crates/openwa-dll/src/replacements/bitgrid.rs CHANGED
@@ -553,7 +553,7 @@ unsafe extern "C" fn blit_hook_trampoline() {
553
  ///
554
  /// Handles all 8bpp blend modes: 0 (copy), 1 (color table), 2 (additive mask), 3 (subtractive mask).
555
  /// Falls through to the original WA function for non-8bpp surfaces.
556
- unsafe extern "cdecl" fn blit_impl(
557
  dst: *mut openwa_core::bitgrid::DisplayBitGrid,
558
  dst_x: i32,
559
  dst_y: i32,
 
553
  ///
554
  /// Handles all 8bpp blend modes: 0 (copy), 1 (color table), 2 (additive mask), 3 (subtractive mask).
555
  /// Falls through to the original WA function for non-8bpp surfaces.
556
+ pub(crate) unsafe extern "cdecl" fn blit_impl(
557
  dst: *mut openwa_core::bitgrid::DisplayBitGrid,
558
  dst_x: i32,
559
  dst_y: i32,
crates/openwa-dll/src/replacements/display.rs CHANGED
@@ -48,6 +48,430 @@ unsafe extern "thiscall" fn headless_destructor(
48
  this
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  pub fn install() -> Result<(), String> {
52
  let _ = log_line("[Display] Patching DisplayBase vtables");
53
 
@@ -94,8 +518,10 @@ pub fn install() -> Result<(), String> {
94
  set_camera_offset => dd_display::set_camera_offset,
95
  set_clip_rect => dd_display::set_clip_rect,
96
  is_sprite_loaded => dd_display::is_sprite_loaded,
 
 
97
  })?;
98
- let _ = log_line("[Display] DDDisplay: patched 13 methods → Rust");
99
  }
100
 
101
  Ok(())
 
48
  this
49
  }
50
 
51
+ mod originals {
52
+ use std::sync::atomic::AtomicU32;
53
+ pub static BLIT_SPRITE: AtomicU32 = AtomicU32::new(0);
54
+ pub static DRAW_SCALED_SPRITE: AtomicU32 = AtomicU32::new(0);
55
+ }
56
+
57
+ /// Rust port of DDDisplay::BlitSprite (slot 19, 0x56B080).
58
+ ///
59
+ /// Standard thiscall: ECX=this, stack params: x, y, sprite_flags, palette (RET 0x10).
60
+ ///
61
+ /// sprite_flags layout:
62
+ /// low 16 bits = sprite ID (0 = no sprite)
63
+ /// high 16 bits = orientation/flags:
64
+ /// bit 16 (0x10000): tiled mode
65
+ /// bit 17: additional orientation
66
+ /// bit 18 (0x40000): extra mirror X
67
+ /// bit 19 (0x80000): extra mirror Y
68
+ /// bit 20 (0x100000): stippled palette adjust
69
+ /// bit 21 (0x200000): additive blend
70
+ /// bit 22 (0x400000): shadow clear
71
+ /// bit 23 (0x800000): invert palette
72
+ /// bit 24 (0x1000000): palette ×4 adjust
73
+ /// bit 25 (0x2000000): palette transform
74
+ /// bit 26 (0x4000000): color blend
75
+ /// bit 27 (0x8000000): stippled mode 0
76
+ /// bit 28 (0x10000000): stippled mode 1
77
+ unsafe extern "thiscall" fn blit_sprite(
78
+ this: *mut openwa_core::display::dd_display::DDDisplay,
79
+ x: openwa_core::fixed::Fixed,
80
+ y: openwa_core::fixed::Fixed,
81
+ sprite_flags: u32,
82
+ palette: u32,
83
+ ) {
84
+ use openwa_core::bitgrid::DisplayBitGrid;
85
+ use openwa_core::display::dd_display;
86
+ use openwa_core::display::gfx::DisplayGfx;
87
+
88
+ let gfx = this as *mut DisplayGfx;
89
+ let base = this as *const u8;
90
+
91
+ // ---------------------------------------------------------------
92
+ // Extract sprite ID and high flags
93
+ // ---------------------------------------------------------------
94
+ let high_flags = sprite_flags & 0xFFFF_0000;
95
+ let sprite_id = sprite_flags & 0xFFFF;
96
+
97
+ if sprite_id == 0 {
98
+ return;
99
+ }
100
+
101
+ // ---------------------------------------------------------------
102
+ // Palette manipulation
103
+ // ---------------------------------------------------------------
104
+ let mut pal: u32 = palette;
105
+ if (high_flags & 0x0080_0000) != 0 {
106
+ // Bit 23: invert palette
107
+ pal = 0x10000u32.wrapping_sub(palette);
108
+ if sprite_id.wrapping_sub(0x1D5) < 3 {
109
+ // Special sprite IDs: scale by 8/18
110
+ pal = (0x10000u32.wrapping_sub(palette).wrapping_mul(8)) / 0x12;
111
+ }
112
+ }
113
+ if (high_flags & 0x0200_0000) != 0 {
114
+ // Bit 25: palette transform (modular arithmetic for color cycling)
115
+ let tmp = ((pal.wrapping_mul(0x1F) as i32)
116
+ .wrapping_add(((pal.wrapping_mul(0x1F) as i32) >> 31) & 0x1F)
117
+ >> 5) as u32;
118
+ let tmp = tmp.wrapping_add(0x400) & 0xFFFF;
119
+ pal = (tmp.wrapping_rem(0xF800)) / 2;
120
+ if (pal & 0x400) != 0 {
121
+ pal = (pal & !0x400) | 0x8000;
122
+ }
123
+ }
124
+
125
+ // ---------------------------------------------------------------
126
+ // Check sprite arrays — bitmap path if not in primary arrays
127
+ // ---------------------------------------------------------------
128
+ let arr1 = *(base.add(sprite_id as usize * 4 + 0x1008) as *const u32);
129
+ let arr2 = *(base.add(sprite_id as usize * 4 + 0x2008) as *const u32);
130
+
131
+ if arr1 == 0 && arr2 == 0 {
132
+ // Bitmap fallback — not yet ported, call original
133
+ call_original_blit_sprite(this, x, y, sprite_flags, palette);
134
+ return;
135
+ }
136
+
137
+ // ---------------------------------------------------------------
138
+ // Bit 24: palette ×4 adjust with orientation-dependent high bits
139
+ // ---------------------------------------------------------------
140
+ // The original ASM at 0x56B145 does a complex palette×4 + orientation mapping
141
+ // that writes extra orientation bits into the local orient variable.
142
+ // For now, handle the simple case:
143
+ let mut orient_local: u32 = 0x0000_0001; // blend=1 (ColorTable/transparency), orientation=0 (Normal)
144
+ if (high_flags & 0x0100_0000) != 0 {
145
+ // The ASM computes: pal = pal * 4 + 0x8000, then maps (pal >> 16) & 3
146
+ // to set specific orient values (0x80001, 0xC0001, 0x40001)
147
+ let scaled = pal.wrapping_mul(4).wrapping_add(0x8000);
148
+ pal = scaled & 0xFFFF;
149
+ let quad = ((scaled as i32) >> 16) & 3;
150
+ orient_local = match quad {
151
+ 0 => 0x0008_0001,
152
+ 1 => 0x000C_0001,
153
+ 2 => 0x0004_0001,
154
+ _ => 0x0000_0001, // shouldn't happen, keep default blend=1
155
+ };
156
+ }
157
+
158
+ // ---------------------------------------------------------------
159
+ // Sprite data lookup via vtable[33]
160
+ // ---------------------------------------------------------------
161
+ let vtable_ptr = *(this as *const *const u32);
162
+ let slot33_addr = *vtable_ptr.add(33);
163
+
164
+ // vtable[33] is thiscall with 9 stack params (RET 0x24).
165
+ // Output semantics (traced from ASM ESP offsets through LEA/PUSH sequence):
166
+ // param 3 → sprite full width (for centering)
167
+ // param 4 → sprite full height (for centering)
168
+ // param 5 → render rect LEFT
169
+ // param 6 → render rect TOP (overwrites palette on original stack!)
170
+ // param 7 → render rect RIGHT
171
+ // param 8 → render rect BOTTOM
172
+ // param 9 → unknown (unused)
173
+ let mut out_sprite_w: i32 = 0;
174
+ let mut out_sprite_h: i32 = 0;
175
+ let mut out_rect_left: i32 = 0;
176
+ let mut out_rect_top: i32 = 0;
177
+ let mut out_rect_right: i32 = 0;
178
+ let mut out_rect_bottom: i32 = 0;
179
+ let mut out_unknown: u32 = 0;
180
+
181
+ let fn33: unsafe extern "thiscall" fn(
182
+ *mut openwa_core::display::dd_display::DDDisplay,
183
+ u32,
184
+ u32,
185
+ *mut i32,
186
+ *mut i32,
187
+ *mut i32,
188
+ *mut i32,
189
+ *mut i32,
190
+ *mut i32,
191
+ *mut u32,
192
+ ) -> *mut DisplayBitGrid = core::mem::transmute(slot33_addr as usize);
193
+
194
+ let mut sprite_surface = fn33(
195
+ this,
196
+ sprite_id,
197
+ pal,
198
+ &mut out_sprite_w,
199
+ &mut out_sprite_h,
200
+ &mut out_rect_left,
201
+ &mut out_rect_top,
202
+ &mut out_rect_right,
203
+ &mut out_rect_bottom,
204
+ &mut out_unknown,
205
+ );
206
+
207
+ if sprite_surface.is_null() {
208
+ return;
209
+ }
210
+
211
+ let sprite_w = out_sprite_w;
212
+ let sprite_h = out_sprite_h;
213
+ let rect_left = out_rect_left;
214
+ let rect_top = out_rect_top;
215
+ let rect_right = out_rect_right;
216
+ let rect_bottom = out_rect_bottom;
217
+
218
+ // Size checks
219
+ if rect_left >= rect_right || rect_top >= rect_bottom {
220
+ return;
221
+ }
222
+
223
+ let mut blit_w = rect_right - rect_left;
224
+ let mut blit_h = rect_bottom - rect_top;
225
+
226
+ // ---------------------------------------------------------------
227
+ // Shadow clear (high_flags bit 22)
228
+ // ---------------------------------------------------------------
229
+ if (high_flags & 0x0040_0000) != 0 {
230
+ // Blit sprite to layer_2 as shadow base
231
+ let layer2 = (*gfx).layer_2;
232
+ super::bitgrid::blit_impl(
233
+ layer2,
234
+ 0,
235
+ 0,
236
+ blit_w,
237
+ blit_h,
238
+ sprite_surface,
239
+ 0,
240
+ 0,
241
+ core::ptr::null(),
242
+ 0, // mode 0 = copy
243
+ );
244
+ // Manipulate color_add_table entry for shadow
245
+ let color_idx = ((*gfx)._unknown_356c as usize) * 0x100;
246
+ let table_byte = &mut (*gfx).color_add_table[color_idx];
247
+ let saved = *table_byte;
248
+ *table_byte = 0;
249
+
250
+ // Call BitGrid__ClearColumn_Maybe (0x4F6590) — clears shadow channel
251
+ let clear_fn: unsafe extern "cdecl" fn(*mut u8) =
252
+ core::mem::transmute(openwa_core::rebase::rb(0x004F6590) as usize);
253
+ clear_fn(table_byte as *mut u8);
254
+
255
+ *table_byte = saved;
256
+
257
+ // Replace sprite surface with layer_2 (shadow-processed)
258
+ sprite_surface = layer2;
259
+ }
260
+
261
+ // ---------------------------------------------------------------
262
+ // Extra orientation flags from high_flags
263
+ // ---------------------------------------------------------------
264
+ if (high_flags & 0x0004_0000) != 0 {
265
+ orient_local |= 0x0001_0000;
266
+ }
267
+ if (high_flags & 0x0008_0000) != 0 {
268
+ orient_local |= 0x0002_0000;
269
+ }
270
+
271
+ // ---------------------------------------------------------------
272
+ // 16-case orientation switch for camera coordinate mapping
273
+ // ---------------------------------------------------------------
274
+ let camera_x = (*gfx).camera_x;
275
+ let camera_y = (*gfx).camera_y;
276
+
277
+ // Signed divide toward zero (matches MSVC CDQ+SUB+SAR pattern)
278
+ let half_w = if sprite_w < 0 {
279
+ (sprite_w + 1) / 2
280
+ } else {
281
+ sprite_w / 2
282
+ };
283
+ let half_h = if sprite_h < 0 {
284
+ (sprite_h + 1) / 2
285
+ } else {
286
+ sprite_h / 2
287
+ };
288
+
289
+ let x_px = x.0 >> 16;
290
+ let y_px = y.0 >> 16;
291
+
292
+ let (dst_x, dst_y);
293
+ let orientation_key = (orient_local >> 16) as i32;
294
+
295
+ match orientation_key {
296
+ 1 | 10 => {
297
+ // MirrorX
298
+ dst_x = camera_x + half_w + x_px - rect_right;
299
+ dst_y = camera_y - half_h + rect_top + y_px;
300
+ }
301
+ 2 | 9 => {
302
+ // MirrorY — X same as Normal, Y mirrored
303
+ dst_x = camera_x - half_w + rect_left + x_px;
304
+ dst_y = camera_y + half_h + y_px - rect_bottom;
305
+ }
306
+ 3 | 8 => {
307
+ // MirrorXY
308
+ dst_x = camera_x + half_w + x_px - rect_right;
309
+ dst_y = camera_y + half_h + y_px - rect_bottom;
310
+ }
311
+ 4 | 15 => {
312
+ // Rotate90 — swap axes
313
+ dst_x = camera_x - half_h + rect_top + x_px;
314
+ dst_y = camera_y + half_w + y_px - rect_right;
315
+ blit_w = rect_bottom - rect_top;
316
+ blit_h = rect_right - rect_left;
317
+ }
318
+ 5 | 14 => {
319
+ // Rotate90MirrorX
320
+ dst_x = camera_x + half_h + x_px - rect_bottom;
321
+ dst_y = camera_y + half_w + y_px - rect_right;
322
+ blit_w = rect_bottom - rect_top;
323
+ blit_h = rect_right - rect_left;
324
+ }
325
+ 6 | 13 => {
326
+ // Rotate90MirrorY
327
+ dst_x = camera_x - half_h + rect_top + x_px;
328
+ dst_y = camera_y - half_w + rect_left + y_px;
329
+ blit_w = rect_bottom - rect_top;
330
+ blit_h = rect_right - rect_left;
331
+ }
332
+ 7 | 12 => {
333
+ // Rotate90MirrorXY
334
+ dst_x = camera_x + half_h + x_px - rect_bottom;
335
+ dst_y = camera_y - half_w + rect_left + y_px;
336
+ blit_w = rect_bottom - rect_top;
337
+ blit_h = rect_right - rect_left;
338
+ }
339
+ _ => {
340
+ // Normal (0, 11, and any other value)
341
+ dst_x = camera_x - half_w + rect_left + x_px;
342
+ dst_y = camera_y - half_h + rect_top + y_px;
343
+ }
344
+ }
345
+
346
+ // ---------------------------------------------------------------
347
+ // Blit dispatch based on high_flags
348
+ // ---------------------------------------------------------------
349
+
350
+ // Stippled/tiled modes — fall through to original
351
+ if (high_flags & 0x0800_0000) != 0
352
+ || (high_flags & 0x1000_0000) != 0
353
+ || (high_flags & 0x0001_0000) != 0
354
+ {
355
+ call_original_blit_sprite(this, x, y, sprite_flags, palette);
356
+ return;
357
+ }
358
+
359
+ // Determine color table pointer
360
+ let color_table: *const u8 = if (high_flags & 0x0020_0000) != 0 {
361
+ (*gfx).color_add_table.as_ptr()
362
+ } else if (high_flags & 0x0400_0000) != 0 {
363
+ (*gfx).color_blend_table.as_ptr()
364
+ } else {
365
+ core::ptr::null()
366
+ };
367
+
368
+ if blit_w <= 0 || blit_h <= 0 {
369
+ return;
370
+ }
371
+
372
+ dd_display::acquire_render_lock(gfx);
373
+
374
+ // src_x=0, src_y=0 always — vtable[33] already set up the sprite surface
375
+ super::bitgrid::blit_impl(
376
+ (*gfx).layer_0,
377
+ dst_x,
378
+ dst_y,
379
+ blit_w,
380
+ blit_h,
381
+ sprite_surface,
382
+ 0,
383
+ 0,
384
+ color_table,
385
+ orient_local,
386
+ );
387
+ }
388
+
389
+ /// Call the original DDDisplay::BlitSprite via saved vtable pointer.
390
+ unsafe fn call_original_blit_sprite(
391
+ this: *mut openwa_core::display::dd_display::DDDisplay,
392
+ x: openwa_core::fixed::Fixed,
393
+ y: openwa_core::fixed::Fixed,
394
+ sprite_flags: u32,
395
+ palette: u32,
396
+ ) {
397
+ let orig = originals::BLIT_SPRITE.load(core::sync::atomic::Ordering::Relaxed);
398
+ if orig != 0 {
399
+ let f: unsafe extern "thiscall" fn(
400
+ *mut openwa_core::display::dd_display::DDDisplay,
401
+ openwa_core::fixed::Fixed,
402
+ openwa_core::fixed::Fixed,
403
+ u32,
404
+ u32,
405
+ ) = core::mem::transmute(orig as usize);
406
+ f(this, x, y, sprite_flags, palette);
407
+ }
408
+ }
409
+
410
+ /// Thiscall wrapper for DDDisplay::DrawScaledSprite (slot 20).
411
+ ///
412
+ /// Computes coordinates in core, then dispatches the blit via blit_impl.
413
+ /// Falls through to the original WA function for unhandled modes (stippled).
414
+ unsafe extern "thiscall" fn draw_scaled_sprite(
415
+ this: *mut openwa_core::display::dd_display::DDDisplay,
416
+ x: openwa_core::fixed::Fixed,
417
+ y: openwa_core::fixed::Fixed,
418
+ sprite: *mut openwa_core::bitgrid::DisplayBitGrid,
419
+ src_x: i32,
420
+ src_y: i32,
421
+ src_w: i32,
422
+ src_h: i32,
423
+ flags: u32,
424
+ ) {
425
+ use openwa_core::display::dd_display::{self, DrawScaledSpriteResult};
426
+
427
+ match dd_display::draw_scaled_sprite(this, x, y, sprite, src_x, src_y, src_w, src_h, flags) {
428
+ DrawScaledSpriteResult::Blit {
429
+ layer,
430
+ dst_x,
431
+ dst_y,
432
+ width,
433
+ height,
434
+ sprite,
435
+ src_x,
436
+ src_y,
437
+ color_table,
438
+ blit_flags,
439
+ } => {
440
+ super::bitgrid::blit_impl(
441
+ layer,
442
+ dst_x,
443
+ dst_y,
444
+ width,
445
+ height,
446
+ sprite,
447
+ src_x,
448
+ src_y,
449
+ color_table,
450
+ blit_flags,
451
+ );
452
+ }
453
+ DrawScaledSpriteResult::Handled => {}
454
+ DrawScaledSpriteResult::Unhandled => {
455
+ // Fall through to original WA function for stippled modes
456
+ let orig = originals::DRAW_SCALED_SPRITE.load(core::sync::atomic::Ordering::Relaxed);
457
+ if orig != 0 {
458
+ let f: unsafe extern "thiscall" fn(
459
+ *mut openwa_core::display::dd_display::DDDisplay,
460
+ openwa_core::fixed::Fixed,
461
+ openwa_core::fixed::Fixed,
462
+ *mut openwa_core::bitgrid::DisplayBitGrid,
463
+ i32,
464
+ i32,
465
+ i32,
466
+ i32,
467
+ u32,
468
+ ) = core::mem::transmute(orig as usize);
469
+ f(this, x, y, sprite, src_x, src_y, src_w, src_h, flags);
470
+ }
471
+ }
472
+ }
473
+ }
474
+
475
  pub fn install() -> Result<(), String> {
476
  let _ = log_line("[Display] Patching DisplayBase vtables");
477
 
 
518
  set_camera_offset => dd_display::set_camera_offset,
519
  set_clip_rect => dd_display::set_clip_rect,
520
  is_sprite_loaded => dd_display::is_sprite_loaded,
521
+ draw_scaled_sprite [originals::DRAW_SCALED_SPRITE] => draw_scaled_sprite,
522
+ slot 19 [originals::BLIT_SPRITE] => blit_sprite,
523
  })?;
524
+ let _ = log_line("[Display] DDDisplay: patched 15 methods → Rust");
525
  }
526
 
527
  Ok(())