@@ -66,13 +66,19 @@ unsafe extern "system" fn hooked_create_device(
6666 device_type : u32 ,
6767 focus_window : usize ,
6868 behavior_flags : u32 ,
69- presentation_parameters : * mut c_void ,
69+ presentation_parameters : * mut D3dPresentParameters ,
7070 returned_device_interface : * mut * mut c_void ,
7171) -> i32 {
7272 let addr = ORIGINAL_CREATE_DEVICE . load ( Ordering :: SeqCst ) ;
7373 if addr == 0 {
7474 return -1 ;
7575 }
76+ if WINDOWED_MODE . load ( Ordering :: SeqCst ) {
77+ // SAFETY: [类别 8:FFI 边界] Direct3D 保证非空参数在调用期间指向可写且正确对齐的展示参数。
78+ if let Some ( parameters) = unsafe { presentation_parameters. as_mut ( ) } {
79+ parameters. force_windowed ( ) ;
80+ }
81+ }
7682 let original: CreateDeviceFn = std:: mem:: transmute ( addr) ;
7783 let result = original (
7884 this,
@@ -94,7 +100,7 @@ unsafe extern "system" fn hooked_create_device(
94100 result
95101}
96102
97- unsafe fn hook_device ( device : * mut c_void , presentation_parameters : * mut c_void ) {
103+ unsafe fn hook_device ( device : * mut c_void , presentation_parameters : * mut D3dPresentParameters ) {
98104 if DEVICE_HOOKED . swap ( true , Ordering :: SeqCst ) {
99105 return ;
100106 }
@@ -164,3 +170,52 @@ unsafe fn hook_slot(
164170 log_warn ( & format ! ( "d3d9: {name} vtable write failed" ) ) ;
165171 }
166172}
173+
174+ #[ cfg( test) ]
175+ mod tests {
176+ use super :: * ;
177+
178+ unsafe extern "system" fn fake_create_device (
179+ _this : * mut c_void ,
180+ _adapter : u32 ,
181+ _device_type : u32 ,
182+ _focus_window : usize ,
183+ _behavior_flags : u32 ,
184+ _presentation_parameters : * mut D3dPresentParameters ,
185+ _returned_device_interface : * mut * mut c_void ,
186+ ) -> i32 {
187+ 0
188+ }
189+
190+ #[ test]
191+ #[ cfg_attr( miri, ignore = "Win32 vtable 函数地址通过 usize 保存" ) ]
192+ fn create_device_forces_windowed_without_d3d9ex ( ) {
193+ // Given: loader 直接代理普通 D3D9,游戏仍传入全屏参数。
194+ let previous_create_device =
195+ ORIGINAL_CREATE_DEVICE . swap ( fake_create_device as * const ( ) as usize , Ordering :: SeqCst ) ;
196+ let previous_windowed = WINDOWED_MODE . swap ( true , Ordering :: SeqCst ) ;
197+ let mut parameters = D3dPresentParameters :: default ( ) ;
198+ parameters. full_screen_refresh_rate_in_hz = 60 ;
199+
200+ // When: 参数经过 loader 的通用 CreateDevice hook。
201+ // SAFETY: [类别 8:FFI 边界] 假函数签名与 CreateDeviceFn 完全一致,参数在本次调用期间有效。
202+ let result = unsafe {
203+ hooked_create_device (
204+ std:: ptr:: null_mut ( ) ,
205+ 0 ,
206+ 0 ,
207+ 0 ,
208+ 0 ,
209+ & mut parameters,
210+ std:: ptr:: null_mut ( ) ,
211+ )
212+ } ;
213+ ORIGINAL_CREATE_DEVICE . store ( previous_create_device, Ordering :: SeqCst ) ;
214+ WINDOWED_MODE . store ( previous_windowed, Ordering :: SeqCst ) ;
215+
216+ // Then: 普通 D3D9 后端也收到窗口展示参数。
217+ assert_eq ! ( result, 0 ) ;
218+ assert_eq ! ( parameters. windowed, 1 ) ;
219+ assert_eq ! ( parameters. full_screen_refresh_rate_in_hz, 0 ) ;
220+ }
221+ }
0 commit comments