paavohtl Claude Opus 4.6 (1M context) commited on
Commit
f8a02c3
·
1 Parent(s): 0c779ff

feat: port DDDisplay::FlushRender to Rust

Browse files

Port 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
- // FlushRender (vtable slot 26, 0x56A580) — NOT YET PORTED.
298
- //
299
- // Calls DDDisplayWrapper::unlock_surface_write and ::get_renderer_surface,
300
- // which use a non-standard fastcall convention (EDX = result pointer, not
301
- // standard thiscall). Needs a naked asm bridge or corrected vtable typing
302
- // for the DDDisplayWrapper methods before this can be ported.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(())