Skip to content

Commit 13f367f

Browse files
committed
chore: 让窗口化不依赖 d3d9
1 parent 684bf68 commit 13f367f

6 files changed

Lines changed: 119 additions & 27 deletions

File tree

packages/applechu/src/d3d9/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ use crate::util::api::Api;
99

1010
static FPS_ENABLED: AtomicBool = AtomicBool::new(false);
1111

12+
#[repr(C)]
13+
#[derive(Clone, Copy, Default)]
14+
pub(crate) struct D3dPresentParameters {
15+
back_buffer_width: u32,
16+
back_buffer_height: u32,
17+
back_buffer_format: u32,
18+
back_buffer_count: u32,
19+
multi_sample_type: u32,
20+
multi_sample_quality: u32,
21+
swap_effect: u32,
22+
device_window: usize,
23+
pub(crate) windowed: i32,
24+
enable_auto_depth_stencil: i32,
25+
auto_depth_stencil_format: u32,
26+
flags: u32,
27+
pub(crate) full_screen_refresh_rate_in_hz: u32,
28+
presentation_interval: u32,
29+
}
30+
31+
impl D3dPresentParameters {
32+
pub(crate) fn force_windowed(&mut self) {
33+
self.windowed = 1;
34+
self.full_screen_refresh_rate_in_hz = 0;
35+
}
36+
}
37+
1238
crate::config_section! {
1339
pub(crate) struct FpsDisplayConfig => FPS_DISPLAY_CONFIG_SECTION {
1440
section: "FpsDisplay",
@@ -72,3 +98,25 @@ unsafe extern "C" fn on_present(device: *mut c_void) {
7298
}
7399
osd::render(device);
74100
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::D3dPresentParameters;
105+
106+
#[test]
107+
fn force_windowed_sets_required_d3d_fields() {
108+
// Given: 全屏展示参数带有非零刷新率。
109+
let mut parameters = D3dPresentParameters {
110+
windowed: 0,
111+
full_screen_refresh_rate_in_hz: 60,
112+
..Default::default()
113+
};
114+
115+
// When: 窗口模式在进入 D3D9 前被强制应用。
116+
parameters.force_windowed();
117+
118+
// Then: D3D9 同时收到窗口标志与窗口模式要求的刷新率。
119+
assert_eq!(parameters.windowed, 1);
120+
assert_eq!(parameters.full_screen_refresh_rate_in_hz, 0);
121+
}
122+
}

packages/applechu/src/gfx/d3d9.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
55
use once_cell::sync::OnceCell;
66

77
use crate::config::Config;
8+
use crate::d3d9::D3dPresentParameters;
89
use crate::gfx::WindowConfig;
910
use crate::util::api::Api;
1011
use crate::util::iat_hook::hook_iat;
@@ -55,25 +56,6 @@ static DEVICE_LOST_RECOVER: AtomicBool = AtomicBool::new(true);
5556
static D3D9_VTABLE: OnceCell<Box<[usize; 17]>> = OnceCell::new();
5657
static DEVICE_VTABLE: OnceCell<Box<[usize; 119]>> = OnceCell::new();
5758

58-
#[repr(C)]
59-
#[derive(Clone, Copy)]
60-
struct D3dPresentParameters {
61-
back_buffer_width: u32,
62-
back_buffer_height: u32,
63-
back_buffer_format: u32,
64-
back_buffer_count: u32,
65-
multi_sample_type: u32,
66-
multi_sample_quality: u32,
67-
swap_effect: u32,
68-
device_window: usize,
69-
windowed: i32,
70-
enable_auto_depth_stencil: i32,
71-
auto_depth_stencil_format: u32,
72-
flags: u32,
73-
full_screen_refresh_rate_in_hz: u32,
74-
presentation_interval: u32,
75-
}
76-
7759
#[repr(C)]
7860
struct D3d9ExProxy {
7961
vtable: *const usize,
@@ -510,8 +492,7 @@ unsafe extern "system" fn d3d9_create_device(
510492

511493
let selected_adapter = MONITOR_INDEX.load(Ordering::SeqCst);
512494
if WINDOWED.load(Ordering::SeqCst) && !presentation_parameters.is_null() {
513-
(*presentation_parameters).windowed = 1;
514-
(*presentation_parameters).full_screen_refresh_rate_in_hz = 0;
495+
(*presentation_parameters).force_windowed();
515496
}
516497

517498
let real = (*this).real;

packages/applechu/src/gfx/windowed.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::ffi::c_void;
22
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
33

44
use crate::gfx::WindowConfig;
5+
use crate::proxy::set_windowed_mode;
56
use crate::util::api::Api;
67
use crate::util::iat_hook::hook_iat_all_modules;
78

@@ -50,6 +51,7 @@ extern "system" {
5051

5152
#[applechu_macros::config_section(stage = Graphics, order = 10)]
5253
pub fn init(api: &Api, config: &WindowConfig) {
54+
set_windowed_mode(config.windowed);
5355
if !config.windowed {
5456
return;
5557
}

packages/applechu/src/proxy/d3d9/hooks.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

packages/applechu/src/proxy/d3d9/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
1313

1414
use chu_abi::{ChuModPresentCallback, ChuModResetCallback};
1515

16+
use crate::d3d9::D3dPresentParameters;
1617
use crate::proxy::loader::log::{log_info, log_warn};
1718

1819
use self::iat_hook::{hook_iat, patch_slot, vtable_slot};
@@ -42,7 +43,7 @@ type CreateDeviceFn = unsafe extern "system" fn(
4243
u32,
4344
usize,
4445
u32,
45-
*mut c_void,
46+
*mut D3dPresentParameters,
4647
*mut *mut c_void,
4748
) -> i32;
4849
type PresentFn = unsafe extern "system" fn(
@@ -60,6 +61,7 @@ static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
6061
static ORIGINAL_DIRECT3D_CREATE9: AtomicUsize = AtomicUsize::new(0);
6162
static ORIGINAL_CREATE_DEVICE: AtomicUsize = AtomicUsize::new(0);
6263
static DEVICE_HOOKED: AtomicBool = AtomicBool::new(false);
64+
static WINDOWED_MODE: AtomicBool = AtomicBool::new(false);
6365

6466
static ORIGINAL_PRESENT: AtomicUsize = AtomicUsize::new(0);
6567
static ORIGINAL_RESET: AtomicUsize = AtomicUsize::new(0);
@@ -96,6 +98,10 @@ static RESET_CALLBACKS: [AtomicUsize; MAX_RESET_CALLBACKS] = [
9698
AtomicUsize::new(0),
9799
];
98100

101+
pub(crate) fn set_windowed_mode(enabled: bool) {
102+
WINDOWED_MODE.store(enabled, Ordering::SeqCst);
103+
}
104+
99105
#[link(name = "kernel32")]
100106
extern "system" {
101107
fn QueryPerformanceCounter(count: *mut i64) -> i32;

packages/applechu/src/proxy/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ use std::ptr;
1515

1616
use windows_sys::Win32::Foundation::{BOOL, HMODULE, TRUE};
1717
use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache;
18-
use windows_sys::Win32::System::LibraryLoader::{
19-
DisableThreadLibraryCalls, GetModuleHandleA,
20-
};
18+
use windows_sys::Win32::System::LibraryLoader::{DisableThreadLibraryCalls, GetModuleHandleA};
2119
use windows_sys::Win32::System::Memory::{
2220
VirtualAlloc, VirtualProtect, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
2321
};
2422
use windows_sys::Win32::System::Threading::GetCurrentProcess;
2523

24+
pub(crate) use d3d9::set_windowed_mode;
25+
2626
const DLL_PROCESS_ATTACH: u32 = 1;
2727
const DLL_PROCESS_DETACH: u32 = 0;
2828
const JMP_REL32_LEN: usize = 5;

0 commit comments

Comments
 (0)