paavohtl Claude Opus 4.6 (1M context) commited on
Commit
368f9b1
Β·
1 Parent(s): f8a02c3

refactor: DDDisplayWrapper vtable uses extern "fastcall" instead of inline asm

Browse files

The "MFC result-return convention" (ECX=this, EDX=result_buf_ptr) is
actually __fastcall with an explicit FastcallResult parameter. This
eliminates all inline asm (call_method/call_method_1) and lets the
compiler generate correct calling code via the bind macro.

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

crates/openwa-core/src/display/dd_display.rs CHANGED
@@ -294,7 +294,7 @@ pub unsafe extern "thiscall" fn get_dimensions(
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
 
@@ -322,5 +322,6 @@ pub unsafe extern "thiscall" fn flush_render(this: *mut DDDisplay) {
322
  }
323
 
324
  // get_renderer_surface β†’ renderer Flip
325
- DDDisplayWrapper::call_method(wrapper, 13);
 
326
  }
 
294
  *out_h = (*gfx).base.display_height;
295
  }
296
 
297
+ use super::display_wrapper::{DDDisplayWrapper, FastcallResult};
298
  use crate::address::va;
299
  use crate::rebase::rb;
300
 
 
322
  }
323
 
324
  // get_renderer_surface β†’ renderer Flip
325
+ let mut buf = FastcallResult::default();
326
+ DDDisplayWrapper::get_renderer_surface_raw(wrapper, &mut buf);
327
  }
crates/openwa-core/src/display/display_wrapper.rs CHANGED
@@ -4,28 +4,46 @@
4
  crate::define_addresses! {
5
  class "CWormsApp" {
6
  /// CWormsApp__FlipSurface β€” dispatches to renderer backend vtable[1]
7
- fn/Thiscall CWORMSAPP_FLIP_SURFACE = 0x005A_2700;
8
  /// ConstructFrameBuffer β€” allocates software framebuffer, inits renderer
9
- fn/Thiscall CONSTRUCT_FRAME_BUFFER_THISCALL = 0x005A_2430;
10
  /// CWormsApp__ReleaseFrameBuffer
11
- fn/Thiscall CWORMSAPP_RELEASE_FRAME_BUFFER = 0x005A_24A0;
12
  /// CWormsApp__FillRect β€” lock surface, memset rows, unlock
13
- fn/Thiscall CWORMSAPP_FILL_RECT = 0x005A_25C0;
14
  /// CWormsApp__BlitToFrameBuffer β€” optimized blit from surface to framebuffer
15
- fn/Thiscall CWORMSAPP_BLIT_TO_FRAME_BUFFER = 0x005A_2A40;
16
  /// CWormsApp__DrawLandscape β€” blit with clipping and transparency
17
- fn/Thiscall CWORMSAPP_DRAW_LANDSCAPE = 0x005A_2790;
18
  /// ClearFrameBuffer β€” memset(framebuffer, 0, w*h)
19
  fn CWORMSAPP_CLEAR_FRAME_BUFFER = 0x005A_23F0;
20
  }
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  /// DDDisplayWrapper vtable (0x662EC8, 25 slots).
24
  ///
25
  /// Many slots are thin thunks that forward to the renderer backend
26
  /// via `*(this+0x18)->vtable[N]`. Others manage the software framebuffer
27
  /// or perform blitting directly.
28
  ///
 
 
 
 
 
 
29
  /// ## Slot categories
30
  ///
31
  /// - **Renderer thunks** (0, 2, 4, 5, 8, 10, 11): Forward to backend
@@ -37,90 +55,191 @@ crate::define_addresses! {
37
  pub struct DDDisplayWrapperVtable {
38
  /// renderer thunk -> backend vtable[0] (init/create) (0x4E3420, RET 0x8)
39
  #[slot(0)]
40
- pub renderer_init: fn(this: *mut DDDisplayWrapper, p2: u32, p3: u32),
 
 
 
 
 
41
  /// flip/present β€” dispatches to renderer backend vtable[1] (0x5A2700)
42
  #[slot(1)]
43
- pub flip_surface: fn(this: *mut DDDisplayWrapper),
 
 
 
44
  /// renderer thunk -> backend vtable[2] (reset state) (0x4E3440)
45
  #[slot(2)]
46
- pub renderer_reset: fn(this: *mut DDDisplayWrapper),
 
 
 
47
  /// get framebuffer dimensions β€” reads G_FRAME_BUFFER_WIDTH/HEIGHT (0x5A2660, RET 0x4)
48
  #[slot(3)]
49
- pub get_framebuffer_dims: fn(this: *mut DDDisplayWrapper, out: *mut u32) -> u32,
 
 
 
 
50
  /// renderer thunk -> backend vtable[4] (enum display modes) (0x4E3460, RET 0x8)
51
  #[slot(4)]
52
- pub renderer_enum_modes: fn(this: *mut DDDisplayWrapper, p2: u32, p3: u32),
 
 
 
 
 
53
  /// renderer thunk -> backend vtable[5] (tail jump) (0x4E3480)
54
  #[slot(5)]
55
- pub renderer_slot5: fn(this: *mut DDDisplayWrapper),
 
 
 
56
  /// construct framebuffer β€” wa_malloc(w*h), init renderer (0x5A2430, RET 0x8)
57
  #[slot(6)]
58
- pub construct_frame_buffer: fn(this: *mut DDDisplayWrapper, width: i32, height: i32) -> i32,
 
 
 
 
 
59
  /// release framebuffer β€” calls renderer teardown, frees buffer (0x5A24A0)
60
  #[slot(7)]
61
- pub release_frame_buffer: fn(this: *mut DDDisplayWrapper, p2: i32),
 
 
 
 
62
  /// renderer thunk -> backend vtable[8] (0x5A24F0)
63
  #[slot(8)]
64
- pub renderer_slot8: fn(this: *mut DDDisplayWrapper),
 
 
 
65
  /// lock framebuffer for pixel access (0x5A2530)
66
  ///
67
  /// Returns framebuffer pointer and stride for direct pixel manipulation.
68
  #[slot(9)]
69
- pub lock_framebuffer:
70
- fn(this: *mut DDDisplayWrapper, p2: u32, p3: u32, p4: u32, p5: u32) -> u32,
 
 
 
 
 
 
71
  /// renderer thunk -> backend vtable[10] (0x5A2510)
72
  #[slot(10)]
73
- pub renderer_restore_dims: fn(this: *mut DDDisplayWrapper),
 
 
 
74
  /// renderer thunk -> backend vtable[11] (restore surface) (0x5A25A0)
75
  #[slot(11)]
76
- pub renderer_restore_surface: fn(this: *mut DDDisplayWrapper),
 
 
 
77
  /// allocate error/result object (0x5A2720, RET 0x4)
78
  #[slot(12)]
79
- pub alloc_error_result: fn(this: *mut DDDisplayWrapper, p2: u32) -> u32,
 
 
 
 
80
  /// get renderer surface pointer (0x5A26C0)
81
  ///
82
  /// Queries `renderer->vtable[12]` with G_FRAME_BUFFER_PTR.
83
  #[slot(13)]
84
- pub get_renderer_surface: fn(this: *mut DDDisplayWrapper),
 
 
 
85
  /// stub β€” returns 0 (CGameTask__vt18, 0x545780)
86
  #[slot(14)]
87
- pub stub_ret_zero: fn(this: *mut DDDisplayWrapper) -> u32,
 
 
 
88
  /// lock surface for reading β€” returns framebuffer ptr + width (0x5A2690, RET 0x8)
89
  #[slot(15)]
90
- pub lock_surface_read: fn(this: *mut DDDisplayWrapper, p2: u32, p3: u32),
 
 
 
 
 
91
  /// unlock surface (0x5A2C10, RET 0x4)
92
  ///
93
  /// No-op for software framebuffer β€” just returns success code.
94
  #[slot(16)]
95
- pub unlock_surface_read: fn(this: *mut DDDisplayWrapper, p2: u32),
 
 
 
 
96
  /// lock surface for writing (0x5A2690 β€” same fn as slot 15, RET 0x8)
97
  #[slot(17)]
98
- pub lock_surface_write: fn(this: *mut DDDisplayWrapper, p2: u32, p3: u32),
 
 
 
 
 
99
  /// unlock surface after writing (0x5A2C10 β€” same fn as slot 16, RET 0x4)
100
  #[slot(18)]
101
- pub unlock_surface_write: fn(this: *mut DDDisplayWrapper, p2: u32),
 
 
 
 
102
  /// fill rectangle β€” lock, memset rows, unlock (0x5A25C0)
103
  ///
104
  /// Params: x, y, width, height, fill_value.
105
  #[slot(19)]
106
- pub fill_rect:
107
- fn(this: *mut DDDisplayWrapper, x: i32, y: i32, width: i32, height: i32, fill_value: u32),
 
 
 
 
 
 
 
108
  /// clear framebuffer β€” memset(ptr, 0, w*h) (0x5A23F0)
109
  #[slot(20)]
110
- pub clear_frame_buffer: fn(this: *mut DDDisplayWrapper),
 
 
 
111
  /// trampoline to slot 20 via vtable dispatch (0x5A2420)
112
  #[slot(21)]
113
- pub clear_frame_buffer_indirect: fn(this: *mut DDDisplayWrapper),
 
 
 
114
  /// allocate surface object β€” wa_malloc(0x14) (0x5A2760)
115
  #[slot(22)]
116
- pub alloc_surface: fn(this: *mut DDDisplayWrapper) -> *mut u8,
 
 
 
117
  /// draw landscape β€” blit with clipping and optional color-key transparency (0x5A2790)
118
  #[slot(23)]
119
- pub draw_landscape:
120
- fn(this: *mut DDDisplayWrapper, surface: *mut u8, y_offset: i32, flags: u32),
 
 
 
 
 
121
  /// blit surface to framebuffer β€” optimized copy with stride alignment (0x5A2A40)
122
  #[slot(24)]
123
- pub blit_to_frame_buffer: fn(this: *mut DDDisplayWrapper, surface: *mut u8, y_offset: i32),
 
 
 
 
 
124
  }
125
 
126
  /// DDDisplayWrapper struct β€” CWormsApp rendering dispatch subobject.
@@ -139,57 +258,7 @@ pub struct DDDisplayWrapper {
139
 
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
- }
 
4
  crate::define_addresses! {
5
  class "CWormsApp" {
6
  /// CWormsApp__FlipSurface β€” dispatches to renderer backend vtable[1]
7
+ fn/Fastcall CWORMSAPP_FLIP_SURFACE = 0x005A_2700;
8
  /// ConstructFrameBuffer β€” allocates software framebuffer, inits renderer
9
+ fn/Fastcall CONSTRUCT_FRAME_BUFFER_THISCALL = 0x005A_2430;
10
  /// CWormsApp__ReleaseFrameBuffer
11
+ fn/Fastcall CWORMSAPP_RELEASE_FRAME_BUFFER = 0x005A_24A0;
12
  /// CWormsApp__FillRect β€” lock surface, memset rows, unlock
13
+ fn/Fastcall CWORMSAPP_FILL_RECT = 0x005A_25C0;
14
  /// CWormsApp__BlitToFrameBuffer β€” optimized blit from surface to framebuffer
15
+ fn/Fastcall CWORMSAPP_BLIT_TO_FRAME_BUFFER = 0x005A_2A40;
16
  /// CWormsApp__DrawLandscape β€” blit with clipping and transparency
17
+ fn/Fastcall CWORMSAPP_DRAW_LANDSCAPE = 0x005A_2790;
18
  /// ClearFrameBuffer β€” memset(framebuffer, 0, w*h)
19
  fn CWORMSAPP_CLEAR_FRAME_BUFFER = 0x005A_23F0;
20
  }
21
  }
22
 
23
+ /// Result buffer for DDDisplayWrapper and CompatRenderer vtable calls.
24
+ ///
25
+ /// These classes use `__fastcall` convention: ECX = this, EDX = result buffer pointer.
26
+ /// The callee writes a result (typically an HRESULT or pointer) into `value` and
27
+ /// returns `EDX` in `EAX`. Most callers allocate this on the stack and ignore the result.
28
+ #[repr(C)]
29
+ #[derive(Clone, Copy, Default)]
30
+ pub struct FastcallResult {
31
+ pub value: u32,
32
+ _pad: u32,
33
+ }
34
+
35
  /// DDDisplayWrapper vtable (0x662EC8, 25 slots).
36
  ///
37
  /// Many slots are thin thunks that forward to the renderer backend
38
  /// via `*(this+0x18)->vtable[N]`. Others manage the software framebuffer
39
  /// or perform blitting directly.
40
  ///
41
+ /// ## Calling convention
42
+ ///
43
+ /// All methods use `__fastcall`: ECX = this, EDX = `*mut FastcallResult` (caller-allocated
44
+ /// 8-byte result buffer). Remaining parameters are on the stack, callee-cleaned.
45
+ /// Most callers ignore the result value.
46
+ ///
47
  /// ## Slot categories
48
  ///
49
  /// - **Renderer thunks** (0, 2, 4, 5, 8, 10, 11): Forward to backend
 
55
  pub struct DDDisplayWrapperVtable {
56
  /// renderer thunk -> backend vtable[0] (init/create) (0x4E3420, RET 0x8)
57
  #[slot(0)]
58
+ pub renderer_init: unsafe extern "fastcall" fn(
59
+ this: *mut DDDisplayWrapper,
60
+ result: *mut FastcallResult,
61
+ p2: u32,
62
+ p3: u32,
63
+ ) -> *mut FastcallResult,
64
  /// flip/present β€” dispatches to renderer backend vtable[1] (0x5A2700)
65
  #[slot(1)]
66
+ pub flip_surface: unsafe extern "fastcall" fn(
67
+ this: *mut DDDisplayWrapper,
68
+ result: *mut FastcallResult,
69
+ ) -> *mut FastcallResult,
70
  /// renderer thunk -> backend vtable[2] (reset state) (0x4E3440)
71
  #[slot(2)]
72
+ pub renderer_reset: unsafe extern "fastcall" fn(
73
+ this: *mut DDDisplayWrapper,
74
+ result: *mut FastcallResult,
75
+ ) -> *mut FastcallResult,
76
  /// get framebuffer dimensions β€” reads G_FRAME_BUFFER_WIDTH/HEIGHT (0x5A2660, RET 0x4)
77
  #[slot(3)]
78
+ pub get_framebuffer_dims: unsafe extern "fastcall" fn(
79
+ this: *mut DDDisplayWrapper,
80
+ result: *mut FastcallResult,
81
+ out: *mut u32,
82
+ ) -> *mut FastcallResult,
83
  /// renderer thunk -> backend vtable[4] (enum display modes) (0x4E3460, RET 0x8)
84
  #[slot(4)]
85
+ pub renderer_enum_modes: unsafe extern "fastcall" fn(
86
+ this: *mut DDDisplayWrapper,
87
+ result: *mut FastcallResult,
88
+ p2: u32,
89
+ p3: u32,
90
+ ) -> *mut FastcallResult,
91
  /// renderer thunk -> backend vtable[5] (tail jump) (0x4E3480)
92
  #[slot(5)]
93
+ pub renderer_slot5: unsafe extern "fastcall" fn(
94
+ this: *mut DDDisplayWrapper,
95
+ result: *mut FastcallResult,
96
+ ) -> *mut FastcallResult,
97
  /// construct framebuffer β€” wa_malloc(w*h), init renderer (0x5A2430, RET 0x8)
98
  #[slot(6)]
99
+ pub construct_frame_buffer: unsafe extern "fastcall" fn(
100
+ this: *mut DDDisplayWrapper,
101
+ result: *mut FastcallResult,
102
+ width: i32,
103
+ height: i32,
104
+ ) -> *mut FastcallResult,
105
  /// release framebuffer β€” calls renderer teardown, frees buffer (0x5A24A0)
106
  #[slot(7)]
107
+ pub release_frame_buffer: unsafe extern "fastcall" fn(
108
+ this: *mut DDDisplayWrapper,
109
+ result: *mut FastcallResult,
110
+ p2: i32,
111
+ ) -> *mut FastcallResult,
112
  /// renderer thunk -> backend vtable[8] (0x5A24F0)
113
  #[slot(8)]
114
+ pub renderer_slot8: unsafe extern "fastcall" fn(
115
+ this: *mut DDDisplayWrapper,
116
+ result: *mut FastcallResult,
117
+ ) -> *mut FastcallResult,
118
  /// lock framebuffer for pixel access (0x5A2530)
119
  ///
120
  /// Returns framebuffer pointer and stride for direct pixel manipulation.
121
  #[slot(9)]
122
+ pub lock_framebuffer: unsafe extern "fastcall" fn(
123
+ this: *mut DDDisplayWrapper,
124
+ result: *mut FastcallResult,
125
+ p2: u32,
126
+ p3: u32,
127
+ p4: u32,
128
+ p5: u32,
129
+ ) -> *mut FastcallResult,
130
  /// renderer thunk -> backend vtable[10] (0x5A2510)
131
  #[slot(10)]
132
+ pub renderer_restore_dims: unsafe extern "fastcall" fn(
133
+ this: *mut DDDisplayWrapper,
134
+ result: *mut FastcallResult,
135
+ ) -> *mut FastcallResult,
136
  /// renderer thunk -> backend vtable[11] (restore surface) (0x5A25A0)
137
  #[slot(11)]
138
+ pub renderer_restore_surface: unsafe extern "fastcall" fn(
139
+ this: *mut DDDisplayWrapper,
140
+ result: *mut FastcallResult,
141
+ ) -> *mut FastcallResult,
142
  /// allocate error/result object (0x5A2720, RET 0x4)
143
  #[slot(12)]
144
+ pub alloc_error_result: unsafe extern "fastcall" fn(
145
+ this: *mut DDDisplayWrapper,
146
+ result: *mut FastcallResult,
147
+ p2: u32,
148
+ ) -> *mut FastcallResult,
149
  /// get renderer surface pointer (0x5A26C0)
150
  ///
151
  /// Queries `renderer->vtable[12]` with G_FRAME_BUFFER_PTR.
152
  #[slot(13)]
153
+ pub get_renderer_surface: unsafe extern "fastcall" fn(
154
+ this: *mut DDDisplayWrapper,
155
+ result: *mut FastcallResult,
156
+ ) -> *mut FastcallResult,
157
  /// stub β€” returns 0 (CGameTask__vt18, 0x545780)
158
  #[slot(14)]
159
+ pub stub_ret_zero: unsafe extern "fastcall" fn(
160
+ this: *mut DDDisplayWrapper,
161
+ result: *mut FastcallResult,
162
+ ) -> *mut FastcallResult,
163
  /// lock surface for reading β€” returns framebuffer ptr + width (0x5A2690, RET 0x8)
164
  #[slot(15)]
165
+ pub lock_surface_read: unsafe extern "fastcall" fn(
166
+ this: *mut DDDisplayWrapper,
167
+ result: *mut FastcallResult,
168
+ p2: u32,
169
+ p3: u32,
170
+ ) -> *mut FastcallResult,
171
  /// unlock surface (0x5A2C10, RET 0x4)
172
  ///
173
  /// No-op for software framebuffer β€” just returns success code.
174
  #[slot(16)]
175
+ pub unlock_surface_read: unsafe extern "fastcall" fn(
176
+ this: *mut DDDisplayWrapper,
177
+ result: *mut FastcallResult,
178
+ p2: u32,
179
+ ) -> *mut FastcallResult,
180
  /// lock surface for writing (0x5A2690 β€” same fn as slot 15, RET 0x8)
181
  #[slot(17)]
182
+ pub lock_surface_write: unsafe extern "fastcall" fn(
183
+ this: *mut DDDisplayWrapper,
184
+ result: *mut FastcallResult,
185
+ p2: u32,
186
+ p3: u32,
187
+ ) -> *mut FastcallResult,
188
  /// unlock surface after writing (0x5A2C10 β€” same fn as slot 16, RET 0x4)
189
  #[slot(18)]
190
+ pub unlock_surface_write: unsafe extern "fastcall" fn(
191
+ this: *mut DDDisplayWrapper,
192
+ result: *mut FastcallResult,
193
+ p2: u32,
194
+ ) -> *mut FastcallResult,
195
  /// fill rectangle β€” lock, memset rows, unlock (0x5A25C0)
196
  ///
197
  /// Params: x, y, width, height, fill_value.
198
  #[slot(19)]
199
+ pub fill_rect: unsafe extern "fastcall" fn(
200
+ this: *mut DDDisplayWrapper,
201
+ result: *mut FastcallResult,
202
+ x: i32,
203
+ y: i32,
204
+ width: i32,
205
+ height: i32,
206
+ fill_value: u32,
207
+ ) -> *mut FastcallResult,
208
  /// clear framebuffer β€” memset(ptr, 0, w*h) (0x5A23F0)
209
  #[slot(20)]
210
+ pub clear_frame_buffer: unsafe extern "fastcall" fn(
211
+ this: *mut DDDisplayWrapper,
212
+ result: *mut FastcallResult,
213
+ ) -> *mut FastcallResult,
214
  /// trampoline to slot 20 via vtable dispatch (0x5A2420)
215
  #[slot(21)]
216
+ pub clear_frame_buffer_indirect: unsafe extern "fastcall" fn(
217
+ this: *mut DDDisplayWrapper,
218
+ result: *mut FastcallResult,
219
+ ) -> *mut FastcallResult,
220
  /// allocate surface object β€” wa_malloc(0x14) (0x5A2760)
221
  #[slot(22)]
222
+ pub alloc_surface: unsafe extern "fastcall" fn(
223
+ this: *mut DDDisplayWrapper,
224
+ result: *mut FastcallResult,
225
+ ) -> *mut FastcallResult,
226
  /// draw landscape β€” blit with clipping and optional color-key transparency (0x5A2790)
227
  #[slot(23)]
228
+ pub draw_landscape: unsafe extern "fastcall" fn(
229
+ this: *mut DDDisplayWrapper,
230
+ result: *mut FastcallResult,
231
+ surface: *mut u8,
232
+ y_offset: i32,
233
+ flags: u32,
234
+ ) -> *mut FastcallResult,
235
  /// blit surface to framebuffer β€” optimized copy with stride alignment (0x5A2A40)
236
  #[slot(24)]
237
+ pub blit_to_frame_buffer: unsafe extern "fastcall" fn(
238
+ this: *mut DDDisplayWrapper,
239
+ result: *mut FastcallResult,
240
+ surface: *mut u8,
241
+ y_offset: i32,
242
+ ) -> *mut FastcallResult,
243
  }
244
 
245
  /// DDDisplayWrapper struct β€” CWormsApp rendering dispatch subobject.
 
258
 
259
  const _: () = assert!(core::mem::size_of::<DDDisplayWrapper>() == 0x1C);
260
 
261
+ // Generate calling wrappers: DDDisplayWrapper::get_renderer_surface_raw(), etc.
262
+ // Use the `_raw` variants to avoid LLVM noalias miscompilation.
263
+ // Callers must pass a `&mut FastcallResult` buffer (allocated on the stack).
 
264
  bind_DDDisplayWrapperVtable!(DDDisplayWrapper, vtable);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crates/openwa-core/src/display/mod.rs CHANGED
@@ -15,7 +15,7 @@ pub use bitgrid::{
15
  };
16
  pub use compat_renderer::CompatRenderer;
17
  pub use dd_display::DDDisplay;
18
- pub use display_wrapper::DDDisplayWrapper;
19
  pub use gfx::DisplayGfx;
20
  pub use opengl::OpenGLState;
21
  pub use palette::{Palette, PaletteVtable};
 
15
  };
16
  pub use compat_renderer::CompatRenderer;
17
  pub use dd_display::DDDisplay;
18
+ pub use display_wrapper::{DDDisplayWrapper, FastcallResult};
19
  pub use gfx::DisplayGfx;
20
  pub use opengl::OpenGLState;
21
  pub use palette::{Palette, PaletteVtable};