From 7120f88452fc6e41b7685f1d883442fe2211835f Mon Sep 17 00:00:00 2001 From: super-continent <46540468+super-continent@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:53:01 -0700 Subject: [PATCH 1/2] Add DirectX9Ex support --- Cargo.toml | 9 +- README.md | 8 +- examples/demo_hook_dx9ex.rs | 33 ++++ src/hooks/dx9ex.rs | 350 ++++++++++++++++++++++++++++++++++++ src/hooks/mod.rs | 2 + src/lib.rs | 10 +- src/renderer/backend/mod.rs | 2 +- src/renderer/mod.rs | 2 +- tests/dx9ex.rs | 25 +++ tests/harness/dx9ex.rs | 161 +++++++++++++++++ tests/harness/mod.rs | 1 + 11 files changed, 597 insertions(+), 6 deletions(-) create mode 100644 examples/demo_hook_dx9ex.rs create mode 100644 src/hooks/dx9ex.rs create mode 100644 tests/dx9ex.rs create mode 100644 tests/harness/dx9ex.rs diff --git a/Cargo.toml b/Cargo.toml index 99bcdd80..91926163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "hudhook" version = "0.9.2" edition = "2021" rust-version = "1.85" -description = "A graphics API hook with dear imgui render loop. Supports DirectX 9, 11, 12, and OpenGL 3." +description = "A graphics API hook with dear imgui render loop. Supports DirectX 9, 9Ex, 11, 12, and OpenGL 3." homepage = "https://github.com/veeenu/hudhook" repository = "https://github.com/veeenu/hudhook" documentation = "https://veeenu.github.io/hudhook" @@ -20,8 +20,9 @@ targets = [ ] [features] -default = ["dx9", "dx11", "dx12", "opengl3", "inject"] +default = ["dx9", "dx9ex", "dx11", "dx12", "opengl3", "inject"] dx9 = [] +dx9ex = [] dx11 = [] dx12 = [] opengl3 = ["dep:gl_generator"] @@ -54,6 +55,10 @@ crate-type = ["cdylib"] name = "demo_hook_dx9" crate-type = ["cdylib"] +[[example]] +name = "demo_hook_dx9ex" +crate-type = ["cdylib"] + [[example]] name = "demo_hook_opengl3" crate-type = ["cdylib"] diff --git a/README.md b/README.md index 59bee5d4..3f9f4ab7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Rust renderer hook library for building [Dear ImGui](https://github.com/ocornut/imgui) overlays. -Currently supports DirectX 9, DirectX 11, DirectX 12 and OpenGL 3. Runs on Windows and Wine/Proton. +Currently supports DirectX 9, DirectX 9Ex, DirectX 11, DirectX 12 and OpenGL 3. Runs on Windows and Wine/Proton. ![hello](tests/hello.jpg) @@ -52,6 +52,12 @@ impl ImguiRenderLoop for MyRenderLoop { hudhook!(ImguiDx9Hooks, MyRenderLoop); } +{ + // Use this if hooking into a DirectX 9Ex application. + use hudhook::hooks::dx9ex::ImguiDx9ExHooks; + hudhook!(ImguiDx9ExHooks, MyRenderLoop); +} + { // Use this if hooking into a DirectX 11 application. use hudhook::hooks::dx11::ImguiDx11Hooks; diff --git a/examples/demo_hook_dx9ex.rs b/examples/demo_hook_dx9ex.rs new file mode 100644 index 00000000..ad458bb1 --- /dev/null +++ b/examples/demo_hook_dx9ex.rs @@ -0,0 +1,33 @@ +use hudhook::*; + +mod support; + +/// Entry point created by the `hudhook` library. +/// +/// # Safety +/// +/// haha +#[no_mangle] +pub unsafe extern "system" fn DllMain( + hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE, + reason: u32, + _: *mut ::std::ffi::c_void, +) { + if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH { + support::setup_tracing(); + ::hudhook::tracing::trace!("DllMain()"); + let hmodule_raw = hmodule.0 as usize; + ::std::thread::spawn(move || { + let hmodule = ::hudhook::windows::Win32::Foundation::HINSTANCE(hmodule_raw as _); + if let Err(e) = ::hudhook::Hudhook::builder() + .with::(support::HookExample::new()) + .with_hmodule(hmodule) + .build() + .apply() + { + ::hudhook::tracing::error!("Couldn't apply hooks: {e:?}"); + ::hudhook::eject(); + } + }); + } +} diff --git a/src/hooks/dx9ex.rs b/src/hooks/dx9ex.rs new file mode 100644 index 00000000..687ae650 --- /dev/null +++ b/src/hooks/dx9ex.rs @@ -0,0 +1,350 @@ +//! Hooks for DirectX 9Ex. + +use std::ffi::c_void; +use std::sync::atomic::Ordering; +use std::sync::OnceLock; +use std::{mem, ptr}; + +use imgui::Context; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use tracing::{error, trace}; +use windows::core::{Error, Interface, Result, BOOL, HRESULT}; +use windows::Win32::Foundation::{HWND, RECT}; +use windows::Win32::Graphics::Direct3D9::{ + Direct3DCreate9Ex, IDirect3DDevice9Ex, D3DADAPTER_DEFAULT, D3DBACKBUFFER_TYPE_MONO, + D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DDEVTYPE_NULLREF, D3DDISPLAYMODE, D3DDISPLAYMODEEX, + D3DFORMAT, D3DPRESENT_PARAMETERS, D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, +}; +use windows::Win32::Graphics::Gdi::RGNDATA; + +use super::DummyHwnd; +use crate::mh::MhHook; +use crate::renderer::{D3D9RenderEngine, Pipeline}; +use crate::{perform_eject, util, Hooks, ImguiRenderLoop, EJECT_REQUESTED, HOOK_EJECTION_BARRIER}; + +type Dx9ExPresentType = unsafe extern "system" fn( + this: IDirect3DDevice9Ex, + psourcerect: *const RECT, + pdestrect: *const RECT, + hdestwindowoverride: HWND, + pdirtyregion: *const RGNDATA, +) -> HRESULT; + +type Dx9ExPresentExType = unsafe extern "system" fn( + this: IDirect3DDevice9Ex, + psourcerect: *const RECT, + pdestrect: *const RECT, + hdestwindowoverride: HWND, + pdirtyregion: *const RGNDATA, + dwflags: u32, +) -> HRESULT; + +type Dx9ExResetType = + unsafe extern "system" fn(this: IDirect3DDevice9Ex, *const D3DPRESENT_PARAMETERS) -> HRESULT; + +type Dx9ExResetExType = unsafe extern "system" fn( + this: IDirect3DDevice9Ex, + *const D3DPRESENT_PARAMETERS, + *const D3DDISPLAYMODEEX, +) -> HRESULT; + +struct Trampolines { + dx9ex_present: Dx9ExPresentType, + dx9ex_present_ex: Dx9ExPresentExType, + dx9ex_reset: Dx9ExResetType, + dx9ex_reset_ex: Dx9ExResetExType, +} + +static mut TRAMPOLINES: OnceLock = OnceLock::new(); +static mut PIPELINE: OnceCell>> = OnceCell::new(); +static mut RENDER_LOOP: OnceCell> = OnceCell::new(); + +unsafe fn init_pipeline(device: &IDirect3DDevice9Ex) -> Result>> { + trace!("initializing pipeline"); + let mut creation_parameters = Default::default(); + device.GetCreationParameters(&mut creation_parameters)?; + + let hwnd = creation_parameters.hFocusWindow; + + let mut ctx = Context::create(); + trace!("creating engine"); + let engine = D3D9RenderEngine::new(device, &mut ctx)?; + + let Some(render_loop) = RENDER_LOOP.take() else { + error!("Render loop not yet initialized"); + return Err(Error::from_hresult(HRESULT(-1))); + }; + + trace!("creating pipeline"); + let pipeline = Pipeline::new(hwnd, ctx, engine, render_loop).map_err(|(e, render_loop)| { + RENDER_LOOP.get_or_init(move || render_loop); + e + })?; + Ok(Mutex::new(pipeline)) +} + +fn render(device: &IDirect3DDevice9Ex) -> Result<()> { + let pipeline = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(device)) }?; + + let Some(mut pipeline) = pipeline.try_lock() else { + error!("Could not lock pipeline"); + return Err(Error::from_hresult(HRESULT(-1))); + }; + + pipeline.prepare_render()?; + + let surface = unsafe { device.GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO)? }; + + unsafe { device.BeginScene() }?; + let render_result = pipeline.render(surface); + unsafe { device.EndScene() }?; + + render_result +} + +unsafe fn reset_pipeline() { + trace!("Resetting pipeline"); + if let Some(pipeline) = PIPELINE.take() { + let render_loop = pipeline.into_inner().take(); + + RENDER_LOOP.set(render_loop).map_err(|_| ()).expect("Render loop cell should be empty"); + } +} + +unsafe extern "system" fn dx9ex_present_impl( + device: IDirect3DDevice9Ex, + psourcerect: *const RECT, + pdestrect: *const RECT, + hdestwindowoverride: HWND, + pdirtyregion: *const RGNDATA, +) -> HRESULT { + let _hook_ejection_guard = HOOK_EJECTION_BARRIER.acquire_ejection_guard(); + + let Trampolines { dx9ex_present, .. } = + TRAMPOLINES.get().expect("DirectX 9Ex trampolines uninitialized"); + + if let Err(e) = render(&device) { + error!("Render error: {e:?}"); + } + + trace!("Call IDirect3DDevice9Ex::Present trampoline"); + let result = dx9ex_present(device, psourcerect, pdestrect, hdestwindowoverride, pdirtyregion); + if EJECT_REQUESTED.load(Ordering::SeqCst) { + perform_eject(); + } + result +} + +unsafe extern "system" fn dx9ex_present_ex_impl( + device: IDirect3DDevice9Ex, + psourcerect: *const RECT, + pdestrect: *const RECT, + hdestwindowoverride: HWND, + pdirtyregion: *const RGNDATA, + dwflags: u32, +) -> HRESULT { + let _hook_ejection_guard = HOOK_EJECTION_BARRIER.acquire_ejection_guard(); + + let Trampolines { dx9ex_present_ex, .. } = + TRAMPOLINES.get().expect("DirectX 9Ex trampolines uninitialized"); + + if let Err(e) = render(&device) { + error!("Render error: {e:?}"); + } + + trace!("Call IDirect3DDevice9Ex::PresentEx trampoline"); + let result = dx9ex_present_ex( + device, + psourcerect, + pdestrect, + hdestwindowoverride, + pdirtyregion, + dwflags, + ); + if EJECT_REQUESTED.load(Ordering::SeqCst) { + perform_eject(); + } + result +} + +unsafe extern "system" fn dx9ex_reset_impl( + this: IDirect3DDevice9Ex, + present_params: *const D3DPRESENT_PARAMETERS, +) -> HRESULT { + let _hook_ejection_guard = HOOK_EJECTION_BARRIER.acquire_ejection_guard(); + + let Trampolines { dx9ex_reset, .. } = + TRAMPOLINES.get().expect("DirectX 9Ex trampolines uninitialized"); + + reset_pipeline(); + + dx9ex_reset(this, present_params) +} + +unsafe extern "system" fn dx9ex_reset_ex_impl( + this: IDirect3DDevice9Ex, + present_params: *const D3DPRESENT_PARAMETERS, + fullscreen_display_mode: *const D3DDISPLAYMODEEX, +) -> HRESULT { + let _hook_ejection_guard = HOOK_EJECTION_BARRIER.acquire_ejection_guard(); + + let Trampolines { dx9ex_reset_ex, .. } = + TRAMPOLINES.get().expect("DirectX 9Ex trampolines uninitialized"); + + reset_pipeline(); + + dx9ex_reset_ex(this, present_params, fullscreen_display_mode) +} + +fn get_target_addrs() -> (Dx9ExPresentType, Dx9ExPresentExType, Dx9ExResetType, Dx9ExResetExType) { + let d9 = unsafe { Direct3DCreate9Ex(D3D_SDK_VERSION).unwrap() }; + + let mut d3d_display_mode = + D3DDISPLAYMODE { Width: 0, Height: 0, RefreshRate: 0, Format: D3DFORMAT(0) }; + unsafe { d9.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mut d3d_display_mode).unwrap() }; + + let mut present_params = D3DPRESENT_PARAMETERS { + Windowed: BOOL(1), + SwapEffect: D3DSWAPEFFECT_DISCARD, + BackBufferFormat: d3d_display_mode.Format, + ..Default::default() + }; + + let dummy_hwnd = DummyHwnd::new(); + let device: IDirect3DDevice9Ex = util::try_out_ptr(|v| unsafe { + d9.CreateDeviceEx( + D3DADAPTER_DEFAULT, + D3DDEVTYPE_NULLREF, + dummy_hwnd.hwnd(), + D3DCREATE_SOFTWARE_VERTEXPROCESSING as u32, + &mut present_params, + ptr::null_mut(), + v, + ) + }) + .expect("IDirect3D9Ex::CreateDeviceEx: failed to create device"); + + let present_ptr = device.vtable().base__.Present; + let present_ex_ptr = device.vtable().PresentEx; + let reset_ptr = device.vtable().base__.Reset; + let reset_ex_ptr = device.vtable().ResetEx; + + unsafe { + ( + mem::transmute::< + unsafe extern "system" fn( + *mut c_void, + *const RECT, + *const RECT, + HWND, + *const RGNDATA, + ) -> HRESULT, + Dx9ExPresentType, + >(present_ptr), + mem::transmute::< + unsafe extern "system" fn( + *mut c_void, + *const RECT, + *const RECT, + HWND, + *const RGNDATA, + u32, + ) -> HRESULT, + Dx9ExPresentExType, + >(present_ex_ptr), + mem::transmute::< + unsafe extern "system" fn(*mut c_void, *mut D3DPRESENT_PARAMETERS) -> HRESULT, + Dx9ExResetType, + >(reset_ptr), + mem::transmute::< + unsafe extern "system" fn( + *mut c_void, + *mut D3DPRESENT_PARAMETERS, + *mut D3DDISPLAYMODEEX, + ) -> HRESULT, + Dx9ExResetExType, + >(reset_ex_ptr), + ) + } +} + +/// Hooks for DirectX 9Ex. +pub struct ImguiDx9ExHooks([MhHook; 4]); + +impl ImguiDx9ExHooks { + /// Construct a set of [`MhHook`]s that will render UI via the + /// provided [`ImguiRenderLoop`]. + /// + /// The following functions are hooked: + /// - `IDirect3DDevice9Ex::Present` + /// - `IDirect3DDevice9Ex::PresentEx` + /// - `IDirect3DDevice9Ex::Reset` + /// - `IDirect3DDevice9Ex::ResetEx` + /// + /// An application using DirectX9Ex may render through either the + /// `Ex` functions or the ones inherited from `IDirect3DDevice9`, so both + /// pairs are hooked. + /// + /// # Safety + /// + /// yolo + pub unsafe fn new(t: T) -> Self + where + T: ImguiRenderLoop + Send + Sync + 'static, + { + let (dx9ex_present_addr, dx9ex_present_ex_addr, dx9ex_reset_addr, dx9ex_reset_ex_addr) = + get_target_addrs(); + + trace!("IDirect3DDevice9Ex::Present = {:p}", dx9ex_present_addr as *const c_void); + trace!("IDirect3DDevice9Ex::PresentEx = {:p}", dx9ex_present_ex_addr as *const c_void); + let hook_present = + MhHook::new(dx9ex_present_addr as *mut c_void, dx9ex_present_impl as *mut c_void) + .expect("couldn't create IDirect3DDevice9Ex::Present hook"); + let hook_present_ex = + MhHook::new(dx9ex_present_ex_addr as *mut c_void, dx9ex_present_ex_impl as *mut c_void) + .expect("couldn't create IDirect3DDevice9Ex::PresentEx hook"); + let hook_reset = + MhHook::new(dx9ex_reset_addr as *mut c_void, dx9ex_reset_impl as *mut c_void) + .expect("couldn't create IDirect3DDevice9Ex::Reset hook"); + let hook_reset_ex = + MhHook::new(dx9ex_reset_ex_addr as *mut c_void, dx9ex_reset_ex_impl as *mut c_void) + .expect("couldn't create IDirect3DDevice9Ex::ResetEx hook"); + + RENDER_LOOP.get_or_init(|| Box::new(t)); + TRAMPOLINES.get_or_init(|| Trampolines { + dx9ex_present: mem::transmute::<*mut c_void, Dx9ExPresentType>( + hook_present.trampoline(), + ), + dx9ex_present_ex: mem::transmute::<*mut c_void, Dx9ExPresentExType>( + hook_present_ex.trampoline(), + ), + dx9ex_reset: mem::transmute::<*mut c_void, Dx9ExResetType>(hook_reset.trampoline()), + dx9ex_reset_ex: mem::transmute::<*mut c_void, Dx9ExResetExType>( + hook_reset_ex.trampoline(), + ), + }); + + Self([hook_present, hook_present_ex, hook_reset, hook_reset_ex]) + } +} + +impl Hooks for ImguiDx9ExHooks { + fn from_render_loop(t: T) -> Box + where + Self: Sized, + T: ImguiRenderLoop + Send + Sync + 'static, + { + Box::new(unsafe { Self::new(t) }) + } + + fn hooks(&self) -> &[MhHook] { + &self.0 + } + + unsafe fn unhook(&mut self) { + TRAMPOLINES.take(); + PIPELINE.take().map(|p| p.into_inner().take()); + RENDER_LOOP.take(); + } +} diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 3020638f..5b68609b 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -20,6 +20,8 @@ pub mod dx11; pub mod dx12; #[cfg(feature = "dx9")] pub mod dx9; +#[cfg(feature = "dx9ex")] +pub mod dx9ex; #[cfg(feature = "opengl3")] pub mod opengl3; diff --git a/src/lib.rs b/src/lib.rs index 2f3f73b9..17003b8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,8 @@ //! render loop of applications and drawing things on screen via //! [`dear imgui`](https://docs.rs/imgui/0.11.0/imgui/). //! -//! Currently, DirectX9, DirectX 11, DirectX 12 and OpenGL 3 are supported. +//! Currently, DirectX9, DirectX9Ex, DirectX 11, DirectX 12 and OpenGL 3 are +//! supported. //! //! For complete, fully fledged examples of usage, check out the following //! projects: @@ -73,6 +74,12 @@ //! } //! //! { +//! // Use this if hooking into a DirectX 9Ex application. +//! use hudhook::hooks::dx9ex::ImguiDx9ExHooks; +//! hudhook!(ImguiDx9ExHooks, MyRenderLoop); +//! } +//! +//! { //! // Use this if hooking into a DirectX 11 application. //! use hudhook::hooks::dx11::ImguiDx11Hooks; //! hudhook!(ImguiDx11Hooks, MyRenderLoop); @@ -321,6 +328,7 @@ pub trait ImguiRenderLoop { /// Check out first party implementations for guidance on how to implement the /// methods: /// - [`ImguiDx9Hooks`](crate::hooks::dx9::ImguiDx9Hooks) +/// - [`ImguiDx9ExHooks`](crate::hooks::dx9ex::ImguiDx9ExHooks) /// - [`ImguiDx11Hooks`](crate::hooks::dx11::ImguiDx11Hooks) /// - [`ImguiDx12Hooks`](crate::hooks::dx12::ImguiDx12Hooks) /// - [`ImguiOpenGl3Hooks`](crate::hooks::opengl3::ImguiOpenGl3Hooks) diff --git a/src/renderer/backend/mod.rs b/src/renderer/backend/mod.rs index b7c235de..a0b7cf8d 100644 --- a/src/renderer/backend/mod.rs +++ b/src/renderer/backend/mod.rs @@ -2,7 +2,7 @@ pub mod dx11; #[cfg(feature = "dx12")] pub mod dx12; -#[cfg(feature = "dx9")] +#[cfg(any(feature = "dx9", feature = "dx9ex"))] pub mod dx9; #[cfg(feature = "opengl3")] pub mod opengl3; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 84b9dfdd..18f1541d 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -24,7 +24,7 @@ pub(crate) trait RenderEngine: RenderContext { pub(crate) use backend::dx11::D3D11RenderEngine; #[cfg(feature = "dx12")] pub(crate) use backend::dx12::D3D12RenderEngine; -#[cfg(feature = "dx9")] +#[cfg(any(feature = "dx9", feature = "dx9ex"))] pub(crate) use backend::dx9::D3D9RenderEngine; #[cfg(feature = "opengl3")] pub(crate) use backend::opengl3::OpenGl3RenderEngine; diff --git a/tests/dx9ex.rs b/tests/dx9ex.rs new file mode 100644 index 00000000..910ec1d8 --- /dev/null +++ b/tests/dx9ex.rs @@ -0,0 +1,25 @@ +mod harness; +mod hook; + +use std::thread; +use std::time::Duration; + +use harness::dx9ex::Dx9ExHarness; +use hook::HookExample; +use hudhook::hooks::dx9ex::ImguiDx9ExHooks; +use hudhook::*; + +#[test] +fn test_imgui_dx9ex() { + hook::setup_tracing(); + + let dx9ex_harness = Dx9ExHarness::new("DX9Ex hook example"); + thread::sleep(Duration::from_millis(500)); + + if let Err(e) = Hudhook::builder().with::(HookExample::new()).build().apply() { + eprintln!("Couldn't apply hooks: {e:?}"); + } + + thread::sleep(Duration::from_millis(7000)); + drop(dx9ex_harness); +} diff --git a/tests/harness/dx9ex.rs b/tests/harness/dx9ex.rs new file mode 100644 index 00000000..c3e93e37 --- /dev/null +++ b/tests/harness/dx9ex.rs @@ -0,0 +1,161 @@ +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::ptr::{null, null_mut}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; + +use windows::core::PCSTR; +use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM}; +use windows::Win32::Graphics::Direct3D9::{ + Direct3DCreate9Ex, D3DADAPTER_DEFAULT, D3DCLEAR_TARGET, D3DCREATE_SOFTWARE_VERTEXPROCESSING, + D3DDEVTYPE_HAL, D3DPRESENT_PARAMETERS, D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, +}; +use windows::Win32::Graphics::Gdi::HBRUSH; +use windows::Win32::System::LibraryLoader::GetModuleHandleA; +use windows::Win32::UI::WindowsAndMessaging::{ + AdjustWindowRect, CreateWindowExA, DefWindowProcA, DispatchMessageA, PeekMessageA, + PostQuitMessage, RegisterClassA, SetTimer, TranslateMessage, CS_HREDRAW, CS_OWNDC, CS_VREDRAW, + HCURSOR, HICON, PM_REMOVE, WINDOW_EX_STYLE, WM_DESTROY, WNDCLASSA, WS_OVERLAPPEDWINDOW, + WS_VISIBLE, +}; + +pub struct Dx9ExHarness { + child: Option>, + done: Arc, + _caption: Arc, +} + +impl Dx9ExHarness { + #[allow(unused)] + pub fn new(caption: &str) -> Self { + let done = Arc::new(AtomicBool::new(false)); + let caption = Arc::new(CString::new(caption).unwrap()); + let child = Some(thread::spawn({ + let done = Arc::clone(&done); + let caption = Arc::clone(&caption); + + move || { + let hinstance = unsafe { GetModuleHandleA(None).unwrap() }; + let wnd_class = WNDCLASSA { + style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(window_proc), + hInstance: hinstance.into(), + lpszClassName: PCSTR(c"MyClass".as_ptr().cast()), + cbClsExtra: 0, + cbWndExtra: 0, + hIcon: HICON::default(), + hCursor: HCURSOR::default(), + hbrBackground: HBRUSH::default(), + lpszMenuName: PCSTR(null_mut()), + }; + unsafe { RegisterClassA(&wnd_class) }; + let mut rect = RECT { left: 0, top: 0, right: 800, bottom: 600 }; + unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW | WS_VISIBLE, false) } + .unwrap(); + let handle = unsafe { + CreateWindowExA( + WINDOW_EX_STYLE(0), + PCSTR(c"MyClass".as_ptr().cast()), + PCSTR(caption.as_ptr().cast()), + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + // size and position + 100, + 100, + rect.right - rect.left, + rect.bottom - rect.top, + None, + None, + Some(hinstance.into()), + None, + ) + } + .unwrap(); + + let direct3d = unsafe { Direct3DCreate9Ex(D3D_SDK_VERSION).unwrap() }; + let mut device = None; + unsafe { + direct3d.CreateDeviceEx( + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + handle, + D3DCREATE_SOFTWARE_VERTEXPROCESSING as _, + &mut D3DPRESENT_PARAMETERS { + Windowed: true.into(), + SwapEffect: D3DSWAPEFFECT_DISCARD, + ..Default::default() + }, + null_mut(), + &mut device, + ) + } + .unwrap(); + let device = device.unwrap(); + + unsafe { SetTimer(Some(handle), 0, 100, None) }; + + let mut frame = 0u64; + + loop { + eprintln!("Present..."); + unsafe { + device.Clear(0, null(), D3DCLEAR_TARGET as _, 0x0022cc22, 1.0, 0).unwrap(); + // Alternate rendering functions so that both the inherited and + // the `Ex` hooks get executed. + if frame % 2 == 0 { + device.Present(null(), null(), handle, null()).unwrap(); + } else { + device.PresentEx(null(), null(), handle, null(), 0).unwrap(); + } + } + frame += 1; + + if done.load(Ordering::SeqCst) { + break; + } + + if !handle_message(handle) { + break; + } + } + } + })); + + Self { child, done, _caption: caption } + } +} + +impl Drop for Dx9ExHarness { + fn drop(&mut self) { + self.done.store(true, Ordering::SeqCst); + self.child.take().unwrap().join().unwrap(); + } +} + +fn handle_message(window: HWND) -> bool { + let mut msg = MaybeUninit::uninit(); + unsafe { + if PeekMessageA(msg.as_mut_ptr(), Some(window), 0, 0, PM_REMOVE).0 > 0 { + let msg = msg.assume_init(); + let _ = TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } + + true +} + +unsafe extern "system" fn window_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + match msg { + WM_DESTROY => { + PostQuitMessage(0); + LRESULT(0) + }, + _ => DefWindowProcA(hwnd, msg, wparam, lparam), + } +} diff --git a/tests/harness/mod.rs b/tests/harness/mod.rs index 60e57749..36e22f4d 100644 --- a/tests/harness/mod.rs +++ b/tests/harness/mod.rs @@ -1,4 +1,5 @@ pub mod dx11; pub mod dx12; pub mod dx9; +pub mod dx9ex; pub mod opengl3; From 84bc9334960bfa3a8beb7ee4676eba32aa2f916a Mon Sep 17 00:00:00 2001 From: super-continent <46540468+super-continent@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:53:04 -0700 Subject: [PATCH 2/2] refactor to reduce duplication --- Cargo.toml | 2 +- README.md | 2 +- examples/demo_hook_dx9ex.rs | 2 +- src/hooks/{dx9ex.rs => dx9/ex.rs} | 69 +++---------------------------- src/hooks/{dx9.rs => dx9/mod.rs} | 19 ++++++--- src/hooks/mod.rs | 2 - src/lib.rs | 4 +- src/renderer/backend/mod.rs | 2 +- src/renderer/mod.rs | 2 +- tests/dx9ex.rs | 2 +- 10 files changed, 27 insertions(+), 79 deletions(-) rename src/hooks/{dx9ex.rs => dx9/ex.rs} (80%) rename src/hooks/{dx9.rs => dx9/mod.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index 91926163..9ed68fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ targets = [ [features] default = ["dx9", "dx9ex", "dx11", "dx12", "opengl3", "inject"] dx9 = [] -dx9ex = [] +dx9ex = ["dx9"] dx11 = [] dx12 = [] opengl3 = ["dep:gl_generator"] diff --git a/README.md b/README.md index 3f9f4ab7..c55d96c9 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ impl ImguiRenderLoop for MyRenderLoop { { // Use this if hooking into a DirectX 9Ex application. - use hudhook::hooks::dx9ex::ImguiDx9ExHooks; + use hudhook::hooks::dx9::ex::ImguiDx9ExHooks; hudhook!(ImguiDx9ExHooks, MyRenderLoop); } diff --git a/examples/demo_hook_dx9ex.rs b/examples/demo_hook_dx9ex.rs index ad458bb1..b0763896 100644 --- a/examples/demo_hook_dx9ex.rs +++ b/examples/demo_hook_dx9ex.rs @@ -20,7 +20,7 @@ pub unsafe extern "system" fn DllMain( ::std::thread::spawn(move || { let hmodule = ::hudhook::windows::Win32::Foundation::HINSTANCE(hmodule_raw as _); if let Err(e) = ::hudhook::Hudhook::builder() - .with::(support::HookExample::new()) + .with::(support::HookExample::new()) .with_hmodule(hmodule) .build() .apply() diff --git a/src/hooks/dx9ex.rs b/src/hooks/dx9/ex.rs similarity index 80% rename from src/hooks/dx9ex.rs rename to src/hooks/dx9/ex.rs index 687ae650..80f9bb58 100644 --- a/src/hooks/dx9ex.rs +++ b/src/hooks/dx9/ex.rs @@ -5,22 +5,19 @@ use std::sync::atomic::Ordering; use std::sync::OnceLock; use std::{mem, ptr}; -use imgui::Context; -use once_cell::sync::OnceCell; -use parking_lot::Mutex; use tracing::{error, trace}; -use windows::core::{Error, Interface, Result, BOOL, HRESULT}; +use windows::core::{Interface, BOOL, HRESULT}; use windows::Win32::Foundation::{HWND, RECT}; use windows::Win32::Graphics::Direct3D9::{ - Direct3DCreate9Ex, IDirect3DDevice9Ex, D3DADAPTER_DEFAULT, D3DBACKBUFFER_TYPE_MONO, - D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DDEVTYPE_NULLREF, D3DDISPLAYMODE, D3DDISPLAYMODEEX, - D3DFORMAT, D3DPRESENT_PARAMETERS, D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, + Direct3DCreate9Ex, IDirect3DDevice9Ex, D3DADAPTER_DEFAULT, D3DCREATE_SOFTWARE_VERTEXPROCESSING, + D3DDEVTYPE_NULLREF, D3DDISPLAYMODE, D3DDISPLAYMODEEX, D3DFORMAT, D3DPRESENT_PARAMETERS, + D3DSWAPEFFECT_DISCARD, D3D_SDK_VERSION, }; use windows::Win32::Graphics::Gdi::RGNDATA; -use super::DummyHwnd; +use super::{render, reset_pipeline, PIPELINE, RENDER_LOOP}; +use crate::hooks::DummyHwnd; use crate::mh::MhHook; -use crate::renderer::{D3D9RenderEngine, Pipeline}; use crate::{perform_eject, util, Hooks, ImguiRenderLoop, EJECT_REQUESTED, HOOK_EJECTION_BARRIER}; type Dx9ExPresentType = unsafe extern "system" fn( @@ -57,60 +54,6 @@ struct Trampolines { } static mut TRAMPOLINES: OnceLock = OnceLock::new(); -static mut PIPELINE: OnceCell>> = OnceCell::new(); -static mut RENDER_LOOP: OnceCell> = OnceCell::new(); - -unsafe fn init_pipeline(device: &IDirect3DDevice9Ex) -> Result>> { - trace!("initializing pipeline"); - let mut creation_parameters = Default::default(); - device.GetCreationParameters(&mut creation_parameters)?; - - let hwnd = creation_parameters.hFocusWindow; - - let mut ctx = Context::create(); - trace!("creating engine"); - let engine = D3D9RenderEngine::new(device, &mut ctx)?; - - let Some(render_loop) = RENDER_LOOP.take() else { - error!("Render loop not yet initialized"); - return Err(Error::from_hresult(HRESULT(-1))); - }; - - trace!("creating pipeline"); - let pipeline = Pipeline::new(hwnd, ctx, engine, render_loop).map_err(|(e, render_loop)| { - RENDER_LOOP.get_or_init(move || render_loop); - e - })?; - Ok(Mutex::new(pipeline)) -} - -fn render(device: &IDirect3DDevice9Ex) -> Result<()> { - let pipeline = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(device)) }?; - - let Some(mut pipeline) = pipeline.try_lock() else { - error!("Could not lock pipeline"); - return Err(Error::from_hresult(HRESULT(-1))); - }; - - pipeline.prepare_render()?; - - let surface = unsafe { device.GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO)? }; - - unsafe { device.BeginScene() }?; - let render_result = pipeline.render(surface); - unsafe { device.EndScene() }?; - - render_result -} - -unsafe fn reset_pipeline() { - trace!("Resetting pipeline"); - if let Some(pipeline) = PIPELINE.take() { - let render_loop = pipeline.into_inner().take(); - - RENDER_LOOP.set(render_loop).map_err(|_| ()).expect("Render loop cell should be empty"); - } -} unsafe extern "system" fn dx9ex_present_impl( device: IDirect3DDevice9Ex, diff --git a/src/hooks/dx9.rs b/src/hooks/dx9/mod.rs similarity index 98% rename from src/hooks/dx9.rs rename to src/hooks/dx9/mod.rs index 4b238267..17742f25 100644 --- a/src/hooks/dx9.rs +++ b/src/hooks/dx9/mod.rs @@ -1,5 +1,8 @@ //! Hooks for DirectX 9. +#[cfg(feature = "dx9ex")] +pub mod ex; + use std::ffi::c_void; use std::mem; use std::sync::atomic::Ordering; @@ -67,6 +70,15 @@ unsafe fn init_pipeline(device: &IDirect3DDevice9) -> Result Result<()> { let pipeline = unsafe { PIPELINE.get_or_try_init(|| init_pipeline(device)) }?; @@ -118,12 +130,7 @@ unsafe extern "system" fn dx9_reset_impl( let Trampolines { dx9_reset, .. } = TRAMPOLINES.get().expect("DirectX 9 trampolines uninitialized"); - trace!("Resetting pipeline"); - if let Some(pipeline) = PIPELINE.take() { - let render_loop = pipeline.into_inner().take(); - - RENDER_LOOP.set(render_loop).map_err(|_| ()).expect("Render loop cell should be empty"); - } + reset_pipeline(); dx9_reset(this, present_params) } diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 5b68609b..3020638f 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -20,8 +20,6 @@ pub mod dx11; pub mod dx12; #[cfg(feature = "dx9")] pub mod dx9; -#[cfg(feature = "dx9ex")] -pub mod dx9ex; #[cfg(feature = "opengl3")] pub mod opengl3; diff --git a/src/lib.rs b/src/lib.rs index 17003b8f..9ddecba5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ //! //! { //! // Use this if hooking into a DirectX 9Ex application. -//! use hudhook::hooks::dx9ex::ImguiDx9ExHooks; +//! use hudhook::hooks::dx9::ex::ImguiDx9ExHooks; //! hudhook!(ImguiDx9ExHooks, MyRenderLoop); //! } //! @@ -328,7 +328,7 @@ pub trait ImguiRenderLoop { /// Check out first party implementations for guidance on how to implement the /// methods: /// - [`ImguiDx9Hooks`](crate::hooks::dx9::ImguiDx9Hooks) -/// - [`ImguiDx9ExHooks`](crate::hooks::dx9ex::ImguiDx9ExHooks) +/// - [`ImguiDx9ExHooks`](crate::hooks::dx9::ex::ImguiDx9ExHooks) /// - [`ImguiDx11Hooks`](crate::hooks::dx11::ImguiDx11Hooks) /// - [`ImguiDx12Hooks`](crate::hooks::dx12::ImguiDx12Hooks) /// - [`ImguiOpenGl3Hooks`](crate::hooks::opengl3::ImguiOpenGl3Hooks) diff --git a/src/renderer/backend/mod.rs b/src/renderer/backend/mod.rs index a0b7cf8d..b7c235de 100644 --- a/src/renderer/backend/mod.rs +++ b/src/renderer/backend/mod.rs @@ -2,7 +2,7 @@ pub mod dx11; #[cfg(feature = "dx12")] pub mod dx12; -#[cfg(any(feature = "dx9", feature = "dx9ex"))] +#[cfg(feature = "dx9")] pub mod dx9; #[cfg(feature = "opengl3")] pub mod opengl3; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 18f1541d..84b9dfdd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -24,7 +24,7 @@ pub(crate) trait RenderEngine: RenderContext { pub(crate) use backend::dx11::D3D11RenderEngine; #[cfg(feature = "dx12")] pub(crate) use backend::dx12::D3D12RenderEngine; -#[cfg(any(feature = "dx9", feature = "dx9ex"))] +#[cfg(feature = "dx9")] pub(crate) use backend::dx9::D3D9RenderEngine; #[cfg(feature = "opengl3")] pub(crate) use backend::opengl3::OpenGl3RenderEngine; diff --git a/tests/dx9ex.rs b/tests/dx9ex.rs index 910ec1d8..9cc619ed 100644 --- a/tests/dx9ex.rs +++ b/tests/dx9ex.rs @@ -6,7 +6,7 @@ use std::time::Duration; use harness::dx9ex::Dx9ExHarness; use hook::HookExample; -use hudhook::hooks::dx9ex::ImguiDx9ExHooks; +use hudhook::hooks::dx9::ex::ImguiDx9ExHooks; use hudhook::*; #[test]