feat: port DDDisplay blit dispatch — slots 19 and 20
Browse filesPort 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>
|
@@ -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 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
#[slot(20)]
|
| 156 |
pub draw_scaled_sprite: fn(
|
| 157 |
this: *mut DDDisplay,
|
| 158 |
x: Fixed,
|
| 159 |
y: Fixed,
|
| 160 |
-
sprite:
|
| 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,
|
|
@@ -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,
|
|
@@ -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
|
| 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(())
|