feat: port DDDisplay::FlushRender to Rust
Browse filesPort FlushRender (vtable slot 26) with two simplifications:
- unlock_surface_write call eliminated (it's a no-op that writes a
success code to a discarded result buffer)
- get_renderer_surface called via DDDisplayWrapper::call_method()
Add call_method/call_method_1 helpers on DDDisplayWrapper for calling
its vtable methods which use an MFC-style result-return convention
(ECX=this, EDX=result_buf_ptr). These are temporary bridges — the
proper fix is to investigate whether this is MSVC's hidden return
pointer for struct-returning thiscall methods and type accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/display/dd_display.rs
CHANGED
|
@@ -294,9 +294,33 @@ pub unsafe extern "thiscall" fn get_dimensions(
|
|
| 294 |
*out_h = (*gfx).base.display_height;
|
| 295 |
}
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
//
|
| 302 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
*out_h = (*gfx).base.display_height;
|
| 295 |
}
|
| 296 |
|
| 297 |
+
use super::display_wrapper::DDDisplayWrapper;
|
| 298 |
+
use crate::address::va;
|
| 299 |
+
use crate::rebase::rb;
|
| 300 |
+
|
| 301 |
+
/// Port of DDDisplay::FlushRender (vtable slot 26, 0x56A580).
|
| 302 |
+
///
|
| 303 |
+
/// If the render lock is held, clears it (the original also calls
|
| 304 |
+
/// DDDisplayWrapper::unlock_surface_write, but that's a no-op that
|
| 305 |
+
/// writes a success code to a discarded result buffer).
|
| 306 |
+
///
|
| 307 |
+
/// Then calls DDDisplayWrapper::get_renderer_surface (slot 13),
|
| 308 |
+
/// which dispatches to the renderer backend's Flip.
|
| 309 |
+
///
|
| 310 |
+
/// # Safety
|
| 311 |
+
/// `this` must be a valid `*mut DDDisplay` (actually a `*mut DisplayGfx`).
|
| 312 |
+
/// `g_DDDisplayWrapper` (0x79D6D4) must be initialized.
|
| 313 |
+
pub unsafe extern "thiscall" fn flush_render(this: *mut DDDisplay) {
|
| 314 |
+
let gfx = this as *mut DisplayGfx;
|
| 315 |
+
let wrapper = *(rb(va::G_DD_DISPLAY_WRAPPER) as *const *mut DDDisplayWrapper);
|
| 316 |
+
|
| 317 |
+
if (*gfx).render_lock != 0 {
|
| 318 |
+
// Original calls wrapper->vtable[18] (unlock_surface_write) here,
|
| 319 |
+
// but that function ignores its data parameter and just writes a
|
| 320 |
+
// success code to a result buffer that FlushRender never reads.
|
| 321 |
+
(*gfx).render_lock = 0;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
// get_renderer_surface → renderer Flip
|
| 325 |
+
DDDisplayWrapper::call_method(wrapper, 13);
|
| 326 |
+
}
|
crates/openwa-core/src/display/display_wrapper.rs
CHANGED
|
@@ -140,4 +140,56 @@ pub struct DDDisplayWrapper {
|
|
| 140 |
const _: () = assert!(core::mem::size_of::<DDDisplayWrapper>() == 0x1C);
|
| 141 |
|
| 142 |
// Generate calling wrappers: DDDisplayWrapper::flip_surface(), etc.
|
|
|
|
|
|
|
|
|
|
| 143 |
bind_DDDisplayWrapperVtable!(DDDisplayWrapper, vtable);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
const _: () = assert!(core::mem::size_of::<DDDisplayWrapper>() == 0x1C);
|
| 141 |
|
| 142 |
// Generate calling wrappers: DDDisplayWrapper::flip_surface(), etc.
|
| 143 |
+
// NOTE: The generated bind wrappers assume standard thiscall, but
|
| 144 |
+
// DDDisplayWrapper methods actually use MFC result-return convention
|
| 145 |
+
// (ECX=this, EDX=result_buf_ptr). Use call_wrapper_method() instead.
|
| 146 |
bind_DDDisplayWrapperVtable!(DDDisplayWrapper, vtable);
|
| 147 |
+
|
| 148 |
+
impl DDDisplayWrapper {
|
| 149 |
+
/// Call a DDDisplayWrapper vtable method by slot index.
|
| 150 |
+
///
|
| 151 |
+
/// DDDisplayWrapper methods use an MFC result-return convention:
|
| 152 |
+
/// ECX = this, EDX = pointer to caller-allocated result buffer.
|
| 153 |
+
/// The result is discarded by most callers.
|
| 154 |
+
///
|
| 155 |
+
/// # Safety
|
| 156 |
+
/// `this` must be a valid DDDisplayWrapper pointer with an initialized vtable.
|
| 157 |
+
#[inline]
|
| 158 |
+
pub unsafe fn call_method(this: *mut DDDisplayWrapper, slot: usize) {
|
| 159 |
+
let vtable = (*this).vtable as *const u32;
|
| 160 |
+
let func: u32 = *vtable.add(slot);
|
| 161 |
+
let mut result_buf: [u32; 2] = [0; 2];
|
| 162 |
+
core::arch::asm!(
|
| 163 |
+
"call {func}",
|
| 164 |
+
func = in(reg) func,
|
| 165 |
+
in("ecx") this,
|
| 166 |
+
inout("edx") result_buf.as_mut_ptr() => _,
|
| 167 |
+
out("eax") _,
|
| 168 |
+
clobber_abi("C"),
|
| 169 |
+
);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/// Call a DDDisplayWrapper vtable method with one stack parameter.
|
| 173 |
+
///
|
| 174 |
+
/// Same MFC convention as `call_method`, with an additional stack argument
|
| 175 |
+
/// pushed before the call (callee cleans via RET N).
|
| 176 |
+
///
|
| 177 |
+
/// # Safety
|
| 178 |
+
/// `this` must be a valid DDDisplayWrapper pointer with an initialized vtable.
|
| 179 |
+
#[inline]
|
| 180 |
+
pub unsafe fn call_method_1(this: *mut DDDisplayWrapper, slot: usize, param: u32) {
|
| 181 |
+
let vtable = (*this).vtable as *const u32;
|
| 182 |
+
let func: u32 = *vtable.add(slot);
|
| 183 |
+
let mut result_buf: [u32; 2] = [0; 2];
|
| 184 |
+
core::arch::asm!(
|
| 185 |
+
"push {param}",
|
| 186 |
+
"call {func}",
|
| 187 |
+
param = in(reg) param,
|
| 188 |
+
func = in(reg) func,
|
| 189 |
+
in("ecx") this,
|
| 190 |
+
inout("edx") result_buf.as_mut_ptr() => _,
|
| 191 |
+
out("eax") _,
|
| 192 |
+
clobber_abi("C"),
|
| 193 |
+
);
|
| 194 |
+
}
|
| 195 |
+
}
|
crates/openwa-dll/src/replacements/display.rs
CHANGED
|
@@ -82,8 +82,9 @@ pub fn install() -> Result<(), String> {
|
|
| 82 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 83 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 84 |
get_dimensions => dd_display::get_dimensions,
|
|
|
|
| 85 |
})?;
|
| 86 |
-
let _ = log_line("[Display] DDDisplay: patched get_dimensions → Rust");
|
| 87 |
}
|
| 88 |
|
| 89 |
Ok(())
|
|
|
|
| 82 |
// Patch DDDisplay vtable (0x66A218): replace ported methods with Rust.
|
| 83 |
vtable_replace!(DDDisplayVtable, va::DD_DISPLAY_VTABLE, {
|
| 84 |
get_dimensions => dd_display::get_dimensions,
|
| 85 |
+
flush_render => dd_display::flush_render,
|
| 86 |
})?;
|
| 87 |
+
let _ = log_line("[Display] DDDisplay: patched get_dimensions + flush_render → Rust");
|
| 88 |
}
|
| 89 |
|
| 90 |
Ok(())
|