feat: port DDDisplay slots 5, 23, 24 — layer management and palette update
Browse filesPort 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:
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 59 |
-
|
|
|
|
| 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
|
| 188 |
#[slot(24)]
|
| 189 |
-
pub update_palette: fn(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
| 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:
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
| 561 |
slot 19 [originals::BLIT_SPRITE] => blit_sprite,
|
| 562 |
})?;
|
| 563 |
-
let _ = log_line("[Display] DDDisplay: patched
|
| 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(())
|