Skip to content

Commit 828918a

Browse files
committed
feat: 添加 d3d9 proxy(workspace 改造 + FPS OSD + mod API)
- 改造为 workspace: loader/ (version.dll) + d3d9-proxy/ (d3d9.dll) - d3d9 proxy: 转发系统 d3d9.dll,拦截 CreateDevice/Present/EndScene - retour inline hook Present/EndScene,兼容 segatools com_proxy - FPS OSD: GDI 绘制到游戏窗口(后续改为纯 D3D9 渲染) - mod API: d3d9proxy_get_api() 导出函数表 - 配置文件: d3d9.toml(分辨率/FPS 显示/帧锁) - 头文件: include/d3d9proxy.h
1 parent bbabf0f commit 828918a

34 files changed

Lines changed: 447 additions & 29 deletions

Cargo.toml

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,6 @@
1-
[package]
2-
name = "chumod-loader"
3-
version = "1.0.0"
4-
edition = "2021"
5-
6-
[lib]
7-
name = "version"
8-
crate-type = ["cdylib"]
9-
10-
[dependencies]
11-
windows-sys = { version = "0.59", features = [
12-
"Win32_Foundation",
13-
"Win32_System_LibraryLoader",
14-
"Win32_System_Threading",
15-
"Win32_System_Console",
16-
"Win32_System_Memory",
17-
"Win32_System_SystemInformation",
18-
"Win32_System_IO",
19-
"Win32_System_Diagnostics_Debug",
20-
"Win32_System_Kernel",
21-
"Win32_System_ProcessStatus",
22-
"Win32_Storage_FileSystem",
23-
] }
24-
retour = "0.4.0-alpha.4"
25-
once_cell = "1"
26-
toml = "0.8"
27-
28-
[build-dependencies]
29-
forward-dll = "0.1.11"
1+
[workspace]
2+
members = ["loader", "d3d9-proxy"]
3+
resolver = "2"
304

315
[profile.release]
326
lto = true

d3d9-proxy/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "d3d9-proxy"
3+
version = "1.0.0"
4+
edition = "2021"
5+
6+
[lib]
7+
name = "d3d9"
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
windows-sys = { version = "0.59", features = [
12+
"Win32_Foundation",
13+
"Win32_System_LibraryLoader",
14+
] }
15+
retour = "0.4.0-alpha.4"

d3d9-proxy/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() {
2+
// d3d9.dll 的关键导出只有 Direct3DCreate9,由 lib.rs 手动实现
3+
// 不使用 forward-dll,避免跟手动导出冲突
4+
}

d3d9-proxy/src/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#[derive(Clone)]
2+
pub struct Config {
3+
pub frame_lock: Option<u32>,
4+
}
5+
6+
impl Default for Config {
7+
fn default() -> Self {
8+
Self {
9+
frame_lock: None,
10+
}
11+
}
12+
}
13+
14+
pub fn load() -> Config {
15+
Config::default()
16+
}

d3d9-proxy/src/d3d9_wrapper.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::ffi::c_void;
2+
3+
use crate::config::Config;
4+
use crate::device_wrapper;
5+
6+
// IDirect3D9 vtable index
7+
const CREATE_DEVICE_INDEX: usize = 16;
8+
9+
static mut ORIG_CREATE_DEVICE: usize = 0;
10+
static mut D3D9_CONFIG: Option<Config> = None;
11+
12+
pub unsafe fn create(real: *mut c_void, config: Config) -> *mut c_void {
13+
D3D9_CONFIG = Some(config);
14+
patch_vtable(real);
15+
OutputDebugStringA(b"[d3d9proxy] create() called, vtable patched\0".as_ptr());
16+
real
17+
}
18+
19+
unsafe fn patch_vtable(obj: *mut c_void) {
20+
let vtable_ptr = *(obj as *mut *mut usize);
21+
let slot = vtable_ptr.add(CREATE_DEVICE_INDEX);
22+
23+
ORIG_CREATE_DEVICE = *slot;
24+
25+
let mut old_protect = 0u32;
26+
VirtualProtect(
27+
slot.cast(),
28+
std::mem::size_of::<usize>(),
29+
0x40, // PAGE_EXECUTE_READWRITE
30+
&mut old_protect,
31+
);
32+
*slot = hooked_create_device as usize;
33+
let mut ignored = 0u32;
34+
VirtualProtect(
35+
slot.cast(),
36+
std::mem::size_of::<usize>(),
37+
old_protect,
38+
&mut ignored,
39+
);
40+
}
41+
42+
// IDirect3D9::CreateDevice(UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS*, IDirect3DDevice9**)
43+
unsafe extern "system" fn hooked_create_device(
44+
this: *mut c_void,
45+
adapter: u32,
46+
device_type: u32,
47+
focus_window: usize,
48+
behavior_flags: u32,
49+
present_params: *mut c_void,
50+
returned_device: *mut *mut c_void,
51+
) -> i32 {
52+
OutputDebugStringA(b"[d3d9proxy] hooked_create_device ENTERED\0".as_ptr());
53+
54+
let orig: unsafe extern "system" fn(
55+
*mut c_void, u32, u32, usize, u32, *mut c_void, *mut *mut c_void,
56+
) -> i32 = std::mem::transmute(ORIG_CREATE_DEVICE);
57+
58+
let hr = orig(this, adapter, device_type, focus_window, behavior_flags, present_params, returned_device);
59+
60+
if hr >= 0 && !returned_device.is_null() && !(*returned_device).is_null() {
61+
OutputDebugStringA(b"[d3d9proxy] CreateDevice OK, patching device\0".as_ptr());
62+
device_wrapper::GAME_HWND = focus_window;
63+
if let Some(cfg) = &D3D9_CONFIG {
64+
device_wrapper::patch(*returned_device, cfg.clone());
65+
}
66+
} else {
67+
OutputDebugStringA(b"[d3d9proxy] CreateDevice FAILED\0".as_ptr());
68+
}
69+
70+
hr
71+
}
72+
73+
#[link(name = "kernel32")]
74+
extern "system" {
75+
fn VirtualProtect(addr: *mut c_void, size: usize, new_protect: u32, old_protect: *mut u32) -> i32;
76+
fn OutputDebugStringA(s: *const u8);
77+
}

d3d9-proxy/src/device_wrapper.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::ffi::c_void;
2+
3+
use retour::GenericDetour;
4+
5+
use crate::config::Config;
6+
use crate::mod_api;
7+
use crate::overlay;
8+
9+
// IDirect3DDevice9 vtable index
10+
const PRESENT_INDEX: usize = 17;
11+
const END_SCENE_INDEX: usize = 42;
12+
13+
type PresentFn = unsafe extern "system" fn(
14+
*mut c_void, *const c_void, *const c_void, usize, *const c_void,
15+
) -> i32;
16+
type EndSceneFn = unsafe extern "system" fn(*mut c_void) -> i32;
17+
18+
pub static mut DEVICE_CONFIG: Option<Config> = None;
19+
pub static mut FPS_STATE: Option<overlay::FpsState> = None;
20+
pub static mut GAME_HWND: usize = 0;
21+
22+
static mut PRESENT_HOOK: Option<GenericDetour<PresentFn>> = None;
23+
static mut END_SCENE_HOOK: Option<GenericDetour<EndSceneFn>> = None;
24+
25+
pub unsafe fn patch(device: *mut c_void, mut config: Config) {
26+
mod_api::apply_pending(&mut config);
27+
let frame_lock = config.frame_lock;
28+
DEVICE_CONFIG = Some(config);
29+
if frame_lock.is_some() {
30+
FPS_STATE = Some(overlay::FpsState::new());
31+
}
32+
mod_api::set_device(device);
33+
34+
let vtable = *(device as *const *const usize);
35+
let present_addr = *vtable.add(PRESENT_INDEX);
36+
let end_scene_addr = *vtable.add(END_SCENE_INDEX);
37+
38+
let present_fn: PresentFn = std::mem::transmute(present_addr);
39+
if let Ok(hook) = GenericDetour::<PresentFn>::new(present_fn, hooked_present) {
40+
let _ = hook.enable();
41+
PRESENT_HOOK = Some(hook);
42+
}
43+
44+
let end_scene_fn: EndSceneFn = std::mem::transmute(end_scene_addr);
45+
if let Ok(hook) = GenericDetour::<EndSceneFn>::new(end_scene_fn, hooked_end_scene) {
46+
let _ = hook.enable();
47+
END_SCENE_HOOK = Some(hook);
48+
}
49+
}
50+
51+
// IDirect3DDevice9::Present(RECT*, RECT*, HWND, RGNDATA*)
52+
unsafe extern "system" fn hooked_present(
53+
this: *mut c_void,
54+
source_rect: *const c_void,
55+
dest_rect: *const c_void,
56+
dest_window_override: usize,
57+
dirty_region: *const c_void,
58+
) -> i32 {
59+
if let Some(state) = &mut FPS_STATE {
60+
if let Some(cfg) = &DEVICE_CONFIG {
61+
if let Some(target_fps) = cfg.frame_lock {
62+
state.frame_lock(target_fps);
63+
}
64+
}
65+
}
66+
67+
PRESENT_HOOK.as_ref().unwrap().call(this, source_rect, dest_rect, dest_window_override, dirty_region)
68+
}
69+
70+
// IDirect3DDevice9::EndScene()
71+
unsafe extern "system" fn hooked_end_scene(this: *mut c_void) -> i32 {
72+
mod_api::run_present_callbacks(this);
73+
74+
END_SCENE_HOOK.as_ref().unwrap().call(this)
75+
}

d3d9-proxy/src/lib.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#![allow(non_snake_case, non_camel_case_types)]
2+
3+
mod config;
4+
mod d3d9_wrapper;
5+
mod device_wrapper;
6+
mod mod_api;
7+
mod overlay;
8+
9+
use std::ffi::c_void;
10+
use std::ptr;
11+
use windows_sys::Win32::Foundation::{BOOL, HMODULE, TRUE};
12+
use windows_sys::Win32::System::LibraryLoader::{DisableThreadLibraryCalls, GetProcAddress, LoadLibraryA};
13+
14+
const DLL_PROCESS_ATTACH: u32 = 1;
15+
16+
static mut REAL_D3D9: HMODULE = ptr::null_mut();
17+
static mut REAL_DIRECT3D_CREATE9: usize = 0;
18+
static mut REAL_DIRECT3D_CREATE9_EX: usize = 0;
19+
20+
type Direct3DCreate9Fn = unsafe extern "system" fn(u32) -> *mut c_void;
21+
type Direct3DCreate9ExFn = unsafe extern "system" fn(u32, *mut *mut c_void) -> i32;
22+
23+
unsafe fn load_real_d3d9() -> bool {
24+
let sys_dir = std::env::var("SYSTEMDRIVE").unwrap_or("C:".to_string());
25+
let path = format!("{}\\Windows\\SysWOW64\\d3d9.dll\0", sys_dir);
26+
REAL_D3D9 = LoadLibraryA(path.as_ptr());
27+
if REAL_D3D9.is_null() {
28+
return false;
29+
}
30+
let proc = GetProcAddress(REAL_D3D9, b"Direct3DCreate9\0".as_ptr());
31+
REAL_DIRECT3D_CREATE9 = proc.map_or(0, |f| f as usize);
32+
33+
let proc_ex = GetProcAddress(REAL_D3D9, b"Direct3DCreate9Ex\0".as_ptr());
34+
REAL_DIRECT3D_CREATE9_EX = proc_ex.map_or(0, |f| f as usize);
35+
36+
REAL_DIRECT3D_CREATE9 != 0
37+
}
38+
39+
#[no_mangle]
40+
pub unsafe extern "system" fn Direct3DCreate9(sdk_version: u32) -> *mut c_void {
41+
if REAL_DIRECT3D_CREATE9 == 0 {
42+
return ptr::null_mut();
43+
}
44+
45+
let real_create: Direct3DCreate9Fn = std::mem::transmute(REAL_DIRECT3D_CREATE9);
46+
let real_d3d9 = real_create(sdk_version);
47+
if real_d3d9.is_null() {
48+
return ptr::null_mut();
49+
}
50+
51+
let cfg = config::load();
52+
d3d9_wrapper::create(real_d3d9, cfg)
53+
}
54+
55+
#[no_mangle]
56+
pub unsafe extern "system" fn Direct3DCreate9Ex(sdk_version: u32, ppd3d: *mut *mut c_void) -> i32 {
57+
if REAL_DIRECT3D_CREATE9_EX == 0 {
58+
return -1; // D3DERR_NOTAVAILABLE
59+
}
60+
61+
let real_create_ex: Direct3DCreate9ExFn = std::mem::transmute(REAL_DIRECT3D_CREATE9_EX);
62+
let hr = real_create_ex(sdk_version, ppd3d);
63+
if hr >= 0 && !ppd3d.is_null() && !(*ppd3d).is_null() {
64+
let cfg = config::load();
65+
*ppd3d = d3d9_wrapper::create(*ppd3d, cfg);
66+
}
67+
hr
68+
}
69+
70+
#[no_mangle]
71+
unsafe extern "system" fn DllMain(h_module: HMODULE, reason: u32, _reserved: *mut c_void) -> BOOL {
72+
if reason == DLL_PROCESS_ATTACH {
73+
DisableThreadLibraryCalls(h_module);
74+
if !load_real_d3d9() {
75+
return 0;
76+
}
77+
}
78+
TRUE
79+
}

d3d9-proxy/src/mod_api.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::ffi::c_void;
2+
3+
use crate::config::Config;
4+
5+
pub type PresentCallbackFn = unsafe extern "C" fn(*mut c_void);
6+
7+
#[repr(C)]
8+
pub struct D3D9ProxyAPI {
9+
pub set_frame_lock: unsafe extern "C" fn(u32),
10+
pub get_device: unsafe extern "C" fn() -> *mut c_void,
11+
pub get_hwnd: unsafe extern "C" fn() -> usize,
12+
pub register_present_callback: unsafe extern "C" fn(PresentCallbackFn),
13+
}
14+
15+
static API_TABLE: D3D9ProxyAPI = D3D9ProxyAPI {
16+
set_frame_lock: api_set_frame_lock,
17+
get_device: api_get_device,
18+
get_hwnd: api_get_hwnd,
19+
register_present_callback: api_register_present_callback,
20+
};
21+
22+
static mut DEVICE_PTR: *mut c_void = std::ptr::null_mut();
23+
static mut PRESENT_CALLBACKS: [Option<PresentCallbackFn>; 8] = [None; 8];
24+
static mut PENDING: PendingConfig = PendingConfig::new();
25+
26+
struct PendingConfig {
27+
frame_lock: Option<u32>,
28+
}
29+
30+
impl PendingConfig {
31+
const fn new() -> Self {
32+
Self { frame_lock: None }
33+
}
34+
}
35+
36+
pub fn set_device(device: *mut c_void) {
37+
unsafe { DEVICE_PTR = device; }
38+
}
39+
40+
pub fn apply_pending(config: &mut Config) {
41+
unsafe {
42+
if let Some(fps) = PENDING.frame_lock {
43+
config.frame_lock = Some(fps);
44+
}
45+
}
46+
}
47+
48+
pub fn run_present_callbacks(device: *mut c_void) {
49+
unsafe {
50+
for cb in &PRESENT_CALLBACKS {
51+
if let Some(f) = cb {
52+
f(device);
53+
}
54+
}
55+
}
56+
}
57+
58+
#[no_mangle]
59+
pub extern "C" fn d3d9proxy_get_api() -> *const D3D9ProxyAPI {
60+
&API_TABLE
61+
}
62+
63+
unsafe extern "C" fn api_set_frame_lock(fps: u32) {
64+
PENDING.frame_lock = if fps > 0 { Some(fps) } else { None };
65+
if let Some(cfg) = &mut crate::device_wrapper::DEVICE_CONFIG {
66+
cfg.frame_lock = if fps > 0 { Some(fps) } else { None };
67+
}
68+
}
69+
70+
unsafe extern "C" fn api_get_device() -> *mut c_void {
71+
DEVICE_PTR
72+
}
73+
74+
unsafe extern "C" fn api_get_hwnd() -> usize {
75+
crate::device_wrapper::GAME_HWND
76+
}
77+
78+
unsafe extern "C" fn api_register_present_callback(cb: PresentCallbackFn) {
79+
for slot in &mut PRESENT_CALLBACKS {
80+
if slot.is_none() {
81+
*slot = Some(cb);
82+
return;
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)