diff --git a/Cargo.toml b/Cargo.toml index 94f9e9e..b1263ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,6 @@ default = ["websockets"] [dependencies] sigscan = { git = "https://github.com/super-continent/sigscan" } -imgui-impl-win32-rs = { git = "https://github.com/super-continent/imgui-impl-win32-rs" } -imgui-dx9-renderer = "0" winapi = { version = "0", features = [ "minwindef", "consoleapi", @@ -26,8 +24,9 @@ winapi = { version = "0", features = [ "d3d9types", "ntdef", "psapi", + "winuser", + "processthreadsapi", ] } -imgui = "0.8" retour = { version = "0.3", features = ["static-detour", "thiscall-abi"] } parking_lot = "0" log = { version = "0", features = ["serde"] } @@ -47,3 +46,4 @@ tokio = { version = "1.39.1", features = [ tokio-tungstenite = { version = "0.24.0", optional = true } steamworks-sys = "0.11.0" futures-util = "0.3.31" +hudhook = "0.8.0" diff --git a/src/lib.rs b/src/lib.rs index a2b668d..71fcc45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,12 +18,13 @@ use std::thread; #[macro_use] extern crate log; +use hudhook::windows::Win32::Foundation::{BOOL, HINSTANCE, MAX_PATH, TRUE}; use once_cell::sync::OnceCell; use simplelog::*; +use winapi::shared::minwindef::{DWORD, HINSTANCE__, LPVOID}; use winapi::{ ctypes::c_void, shared::guiddef::REFIID, - shared::minwindef::*, shared::ntdef::HRESULT, shared::winerror, um::libloaderapi, @@ -35,18 +36,16 @@ use winapi::{ #[allow(non_snake_case)] pub unsafe extern "stdcall" fn DllMain( hinst_dll: HINSTANCE, - attach_reason: DWORD, - _: c_void, + attach_reason: u32, + _: *mut (), ) -> BOOL { - libloaderapi::DisableThreadLibraryCalls(hinst_dll); - if attach_reason == DLL_PROCESS_ATTACH { // if websockets are enabled we set up the message passing state #[cfg(feature = "websockets")] { let (tx, rx) = tokio::sync::mpsc::channel(8); global::MESSAGE_SENDER.get_or_init(move || tx); - thread::spawn(|| unsafe { initialize() }); + thread::spawn(move || unsafe { initialize(hinst_dll) }); thread::spawn(move || { let runtime = tokio::runtime::Builder::new_current_thread() @@ -60,14 +59,14 @@ pub unsafe extern "stdcall" fn DllMain( } #[cfg(not(feature = "websockets"))] { - thread::spawn(|| unsafe { initialize() }); + thread::spawn(|| unsafe { initialize(hinst_dll) }); } }; TRUE } -unsafe fn initialize() { +unsafe fn initialize(module: HINSTANCE) { let log_level = global::CONFIG.lock().log_level; // let log_level = LevelFilter::Trace; if let Ok(logfile) = File::create("rev2mod.log") { @@ -111,13 +110,12 @@ unsafe fn initialize() { debug!("UI hooks initializing..."); - let mut ui_result = ui::ui_hooks::init_ui(); - while let Err(e) = ui_result { - error!("Initializing UI failed: {}", e); - thread::sleep(std::time::Duration::from_secs(5)); - ui_result = ui::ui_hooks::init_ui(); + let ui_result = ui::ui_hooks::init_ui(module); + if let Err(e) = ui_result { + info!("Failed to hook UI: {:?}", e); + } else { + info!("UI hook success!"); } - info!("UI hook success!"); info!("Initializing game hooks..."); let game_result = game::hooks::init_game_hooks(); @@ -145,7 +143,7 @@ pub unsafe extern "stdcall" fn DirectInput8Create( // Load real dinput8.dll if not already loaded - let real_dinput8 = *REAL_DINPUT8_HANDLE.get_or_init(|| get_dinput8_handle()) as HINSTANCE; + let real_dinput8 = *REAL_DINPUT8_HANDLE.get_or_init(|| get_dinput8_handle()) as *mut HINSTANCE__; let dinput8create_fn_name = CString::new("DirectInput8Create").expect("CString::new(`DirectInput8Create`) failed"); @@ -161,7 +159,7 @@ pub unsafe extern "stdcall" fn DirectInput8Create( } unsafe fn get_dinput8_handle() -> u32 { - let mut buffer = [0u16; MAX_PATH]; + let mut buffer = [0u16; MAX_PATH as usize]; let written_wchars = GetSystemDirectoryW(buffer.as_mut_ptr(), MAX_PATH as u32); let system_directory = if written_wchars == 0 { diff --git a/src/ui/gui.rs b/src/ui/gui.rs index ae513d2..c31e524 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -9,10 +9,10 @@ use std::borrow::Cow; use std::path::PathBuf; use std::sync::atomic::Ordering; -use imgui::*; +use hudhook::imgui::*; +use hudhook::ImguiRenderLoop; use once_cell::sync::Lazy; use std::sync::atomic::{AtomicBool, AtomicUsize}; -use winapi::um::winuser::*; // this should work because we initialize the config // before ever accessing DISPLAY_UI through the UI loop @@ -30,21 +30,29 @@ fn save_config(config: global::ModConfig) { .unwrap_or_else(|e| error!("writing config: {}", e)); } -pub fn ui_loop(ui: Ui) -> Ui { +pub struct XrdModUi; + +impl ImguiRenderLoop for XrdModUi { + fn render(&mut self, ui: &mut Ui) { + ui_loop(ui); + } +} + +pub fn ui_loop(ui: &mut Ui) { let display_ui = DISPLAY_UI.load(Ordering::SeqCst); let mut config = global::CONFIG.lock(); - if ui.is_key_index_pressed(VK_F1) { + if ui.is_key_pressed(Key::F1) { DISPLAY_UI.store(!display_ui, Ordering::SeqCst); } if !display_ui { - return ui; + return; } - Window::new("Pangaea's Rev2 Mod") + ui.window("Pangaea's Rev2 Mod") .size([300., 400.], Condition::FirstUseEver) - .build(&ui, || { + .build(|| { TabBar::new("Mods and Config").build(&ui, || { TabItem::new("Config").build(&ui, || { let mut mods_on = config.mods_enabled; @@ -94,7 +102,7 @@ pub fn ui_loop(ui: Ui) -> Ui { config.dump_scripts = dump_scripts }; - Slider::new("Online delay", 0, 4).build(&ui, &mut config.online_input_delay); + ui.slider("Online delay", 0, 4, &mut config.online_input_delay); unsafe { let rollback_manager = *(ROLLBACK_MANAGER.get_address() as *mut *mut u8); @@ -133,12 +141,12 @@ pub fn ui_loop(ui: Ui) -> Ui { let gamestate = *(GAMESTATE_PTR.get_address() as *mut *mut u8); if gamestate.is_null() { - return ui; + return; } let gamestate = GameState(gamestate); - Window::new("Game State").build(&ui, || { + ui.window("Game State").build(|| { ui.text("P1 Tension Pulse"); ProgressBar::new( (gamestate.player_1().tension_pulse() as f32 + 25000.0) / (25000.0 + 25000.0), @@ -154,6 +162,4 @@ pub fn ui_loop(ui: Ui) -> Ui { .build(&ui); }); } - - ui } diff --git a/src/ui/ui_hooks.rs b/src/ui/ui_hooks.rs index 4e2729a..8d814cf 100644 --- a/src/ui/ui_hooks.rs +++ b/src/ui/ui_hooks.rs @@ -1,292 +1,17 @@ use super::gui; -use crate::global::GlobalMut; -use crate::helpers::*; -use crate::{error::ModError, make_fn}; +use hudhook::hooks::dx9::ImguiDx9Hooks; +use hudhook::mh::MH_STATUS; +use hudhook::windows::Win32::Foundation::HINSTANCE; +use hudhook::Hudhook; -use std::ffi::CString; -use std::mem; -use std::{error::Error, ptr}; - -use imgui_dx9_renderer::Renderer; -use imgui_impl_win32_rs::*; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use retour::static_detour; -use winapi::um::winuser::LPMSG; -use winapi::{ - shared::{d3d9::*, d3d9types::*, minwindef::*, windef::HWND, winerror::FAILED}, - um::{ - errhandlingapi::GetLastError, - libloaderapi::{GetModuleHandleW, GetProcAddress}, - winuser::{ - CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassExW, UnregisterClassW, - CS_HREDRAW, CS_VREDRAW, WNDCLASSEXW, WS_EX_OVERLAPPEDWINDOW, - }, - }, -}; - -static IMHOOK_STATE: GlobalMut> = Lazy::new(|| Mutex::new(None)); -// static ORIG_WNDPROC: GlobalMut = Lazy::new(|| Mutex::new(None)); -// static GAME_WINDOW_HWND: AtomicUsize = AtomicUsize::new(0); - -// Static Detour for EndScene and Reset -static_detour! { - static PeekMessageWHook: unsafe extern "system" fn(LPMSG, HWND, u32, u32, u32) -> i32; - static SetWindowLongWHook: unsafe extern "system" fn(HWND, i32, i32) -> i32; - static EndSceneHook: unsafe extern "system" fn(*mut IDirect3DDevice9) -> i32; - static ResetHook: unsafe extern "system" fn(*mut IDirect3DDevice9, *mut D3DPRESENT_PARAMETERS) -> i32; -} - -unsafe fn get_d3d9_device_wndclass() -> Result<*mut IDirect3DDevice9, ModError> { - let mut class_name = win32_wstring("d3d_hook_class"); - let mut window_name = win32_wstring("HookWindow"); - let mut wndclass: WNDCLASSEXW = mem::zeroed(); - wndclass.cbSize = mem::size_of::() as u32; - wndclass.style = CS_HREDRAW | CS_VREDRAW; - wndclass.lpfnWndProc = Some(DefWindowProcW); - wndclass.hInstance = GetModuleHandleW(ptr::null()); - wndclass.lpszClassName = class_name.as_mut_ptr(); - - if RegisterClassExW(&wndclass) == 0 { - let err_code = GetLastError(); - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - return Err(ModError::GetDeviceFailed(format!( - "RegisterClassExW failed with error {err_code:#08X}" - ))); - } - - let window = CreateWindowExW( - WS_EX_OVERLAPPEDWINDOW, - wndclass.lpszClassName, - window_name.as_mut_ptr(), - wndclass.style, - 10, - 10, - 10, - 10, - ptr::null_mut(), - ptr::null_mut(), - wndclass.hInstance, - ptr::null_mut(), - ); - if window.is_null() { - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - return Err(ModError::GetDeviceFailed("CreateWindowExW failed".into())); - } - - let d3d9 = Direct3DCreate9(winapi::shared::d3d9::D3D_SDK_VERSION); - if d3d9.is_null() { - DestroyWindow(window); - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - return Err(ModError::GetDeviceFailed("Direct3DCreate9 failed".into())); - } - - let mut display_mode: D3DDISPLAYMODE = mem::zeroed(); - if FAILED((*d3d9).GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mut display_mode)) { - (*d3d9).Release(); - DestroyWindow(window); - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - return Err(ModError::GetDeviceFailed( - "GetAdapterDisplayMode failed".into(), - )); - } - - let mut present_params: D3DPRESENT_PARAMETERS = mem::zeroed(); - present_params.Windowed = TRUE; - present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; - present_params.BackBufferFormat = display_mode.Format; - present_params.hDeviceWindow = window; - - let mut d3d9_device = ptr::null_mut(); - - let hr = (*d3d9).CreateDevice( - D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - window, - D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_DISABLE_DRIVER_MANAGEMENT, - &mut present_params, - &mut d3d9_device, - ); - if FAILED(hr) { - (*d3d9).Release(); - DestroyWindow(window); - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - return Err(ModError::GetDeviceFailed("CreateDevice failed".into())); - } - - (*d3d9).Release(); - DestroyWindow(window); - UnregisterClassW(wndclass.lpszClassName, wndclass.hInstance); - Ok(d3d9_device) -} - -pub unsafe fn init_ui() -> Result<(), Box> { +pub unsafe fn init_ui(module: HINSTANCE) -> Result<(), MH_STATUS> { info!("Initializing UI"); - let device = get_d3d9_device_wndclass()?; - - debug!("Got device VTable"); - - debug!("Hooking PeekMessageW"); - - let user32_module = GetModuleHandleW(win32_wstring("User32.dll").as_ptr()); - let name = CString::new("PeekMessageW").unwrap(); - - let peekmessagew_addr = GetProcAddress(user32_module, name.as_ptr()); - - let peekmessagew_addr = - make_fn!(peekmessagew_addr => unsafe extern "system" fn(LPMSG, HWND, u32, u32, u32) -> i32); - - PeekMessageWHook - .initialize(peekmessagew_addr, peek_message_w_hook)? - .enable()?; - let endscene = (*(*device).lpVtbl).EndScene; - let reset = (*(*device).lpVtbl).Reset; - - let mut im_ctx = imgui::Context::create(); - im_ctx.style_mut().use_dark_colors(); - - debug!("Set up imgui context"); - - let program_state = ImState { - renderer: None, - window: None, - im_ctx, - }; - - { - // initialize global ui hooks state - *IMHOOK_STATE.lock() = Some(program_state); - } - - ResetHook.initialize(reset, reset_hook)?.enable()?; - EndSceneHook.initialize(endscene, endscene_hook)?.enable()?; + Hudhook::builder() + .with::(gui::XrdModUi) + .with_hmodule(module) + .build() + .apply()?; Ok(()) } - -fn peek_message_w_hook( - msg: LPMSG, - hwnd: HWND, - msg_filter_min: u32, - msg_filter_max: u32, - remove_msg: u32, -) -> i32 { - unsafe { - let trampoline_ret = - PeekMessageWHook.call(msg, hwnd, msg_filter_min, msg_filter_max, remove_msg); - - if trampoline_ret == 0 { - return FALSE; - } - - if let Err(e) = handle_lpmsg_input(msg) { - error!("Error processing message: {}", e); - } - } - TRUE -} - -fn endscene_hook(device: *mut IDirect3DDevice9) -> i32 { - unsafe { - // trace!("endscene called"); - let mut state_lock = IMHOOK_STATE.lock(); - //trace!("acquired state lock"); - let state: &mut ImState = match *state_lock { - Some(ref mut s) => s, - None => return EndSceneHook.call(device), - }; - - if state.renderer.is_none() { - let new_renderer = match Renderer::new_raw(&mut state.im_ctx, device) { - Ok(r) => r, - Err(e) => { - error!("Error creating new renderer: {:#X}", e); - return EndSceneHook.call(device); - } - }; - - state.renderer = Some(new_renderer); - } - - if state.window.is_none() { - let mut creation_params: D3DDEVICE_CREATION_PARAMETERS = mem::zeroed(); - - if (*device).GetCreationParameters(&mut creation_params) != 0 { - return EndSceneHook.call(device); - }; - - let new_window = match Win32Impl::init(&mut state.im_ctx, creation_params.hFocusWindow) - { - Ok(r) => r, - Err(e) => { - error!("Error creating new Win32Impl: {}", e); - return EndSceneHook.call(device); - } - }; - - state.window = Some(new_window); - } - - // Should always be Some - let wind = state.window.as_mut().unwrap(); - if let Err(e) = wind.prepare_frame(&mut state.im_ctx) { - // Handles error of possibly setting wndproc for wrong window, should never happen. - error!("Error calling Win32Impl::prepare_frame: {}", e); - - state.window = None; - return EndSceneHook.call(device); - } - - let ui = gui::ui_loop(state.im_ctx.frame()); - let draw_data = ui.render(); - - let renderer = match state.renderer.as_mut() { - Some(r) => r, - None => { - error!("no renderer in state"); - return EndSceneHook.call(device); - } - }; - if let Err(e) = renderer.render(draw_data) { - error!("could not render draw data: {}", e); - }; - - EndSceneHook.call(device) - } -} - -fn reset_hook(device: *mut IDirect3DDevice9, present_params: *mut D3DPRESENT_PARAMETERS) -> i32 { - unsafe { - trace!("Reset called"); - let mut state_lock = IMHOOK_STATE.lock(); - //trace!("acquired state lock"); - let state: &mut ImState = match *state_lock { - Some(ref mut s) => s, - None => { - return ResetHook.call(device, present_params); - } - }; - - drop(state.renderer.take()); - - ResetHook.call(device, present_params) - } -} - -unsafe fn handle_lpmsg_input(msg: LPMSG) -> Result<(), Win32ImplError> { - let hwnd = (*msg).hwnd; - let msg_type = (*msg).message; - let wparam = (*msg).wParam; - let lparam = (*msg).lParam; - - imgui_win32_window_proc(hwnd, msg_type, wparam, lparam)?; - Ok(()) -} - -struct ImState { - renderer: Option, - im_ctx: imgui::Context, - window: Option, -} -unsafe impl Send for ImState {}