#![allow(clippy::needless_doctest_main)]
#![allow(static_mut_refs)]
#![deny(missing_docs)]
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
pub use imgui;
use imgui::{Context, Io, TextureId, Ui};
use once_cell::sync::OnceCell;
pub use tracing;
use tracing::{error, trace, warn};
pub use windows;
use windows::core::Error;
use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, WPARAM};
use windows::Win32::System::Console::{
AllocConsole, FreeConsole, GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE,
ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE,
};
use windows::Win32::System::LibraryLoader::FreeLibraryAndExitThread;
use crate::mh::{MH_ApplyQueued, MH_Initialize, MH_Uninitialize, MhHook, MH_STATUS};
use crate::util::HookEjectionBarrier;
pub mod hooks;
#[cfg(feature = "inject")]
pub mod inject;
pub mod mh;
pub(crate) mod renderer;
pub use renderer::msg_filter::MessageFilter;
pub mod util;
static mut MODULE: OnceCell<HINSTANCE> = OnceCell::new();
static mut HUDHOOK: OnceCell<Hudhook> = OnceCell::new();
static CONSOLE_ALLOCATED: AtomicBool = AtomicBool::new(false);
static EJECT_REQUESTED: AtomicBool = AtomicBool::new(false);
static HOOK_EJECTION_BARRIER: HookEjectionBarrier = HookEjectionBarrier::new();
pub trait RenderContext {
fn load_texture(&mut self, data: &[u8], width: u32, height: u32) -> Result<TextureId, Error>;
fn replace_texture(
&mut self,
texture_id: TextureId,
data: &[u8],
width: u32,
height: u32,
) -> Result<(), Error>;
}
#[derive(PartialEq, Eq)]
pub enum BeforeWndProc {
Continue,
Break,
}
pub fn alloc_console() -> Result<(), Error> {
if !CONSOLE_ALLOCATED.swap(true, Ordering::SeqCst) {
unsafe { AllocConsole()? };
}
Ok(())
}
pub fn enable_console_colors() {
if CONSOLE_ALLOCATED.load(Ordering::SeqCst) {
unsafe {
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
let mut current_console_mode = CONSOLE_MODE(0);
GetConsoleMode(stdout_handle, &mut current_console_mode).unwrap();
current_console_mode.0 |= ENABLE_VIRTUAL_TERMINAL_PROCESSING.0;
SetConsoleMode(stdout_handle, current_console_mode).unwrap();
}
}
}
pub fn free_console() -> Result<(), Error> {
if CONSOLE_ALLOCATED.swap(false, Ordering::SeqCst) {
unsafe { FreeConsole()? };
}
Ok(())
}
pub fn eject() {
trace!("Requesting eject");
EJECT_REQUESTED.store(true, Ordering::SeqCst);
}
unsafe fn perform_eject() {
trace!("Performing eject");
if let Err(e) = free_console() {
error!("{e:?}");
}
if let Some(mut hudhook) = HUDHOOK.take() {
if let Err(e) = hudhook.unapply() {
error!("Couldn't unapply hooks: {e:?}");
}
}
thread::spawn(|| unsafe {
HOOK_EJECTION_BARRIER.wait_for_all_guards();
if let Some(module) = MODULE.take() {
FreeLibraryAndExitThread(module.into(), 0);
}
trace!("Finished ejecting!");
});
}
pub trait ImguiRenderLoop {
fn initialize<'a>(
&'a mut self,
_ctx: &mut Context,
_render_context: &'a mut dyn RenderContext,
) {
}
fn before_render<'a>(
&'a mut self,
_ctx: &mut Context,
_render_context: &'a mut dyn RenderContext,
) {
}
fn render(&mut self, ui: &mut Ui);
fn before_wnd_proc(
&self,
_hwnd: HWND,
_umsg: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> BeforeWndProc {
BeforeWndProc::Continue
}
fn after_wnd_proc(&self, _hwnd: HWND, _umsg: u32, _wparam: WPARAM, _lparam: LPARAM) {}
fn message_filter(&self, _io: &Io) -> MessageFilter {
MessageFilter::empty()
}
}
pub trait Hooks {
fn from_render_loop<T>(t: T) -> Box<Self>
where
Self: Sized,
T: ImguiRenderLoop + Send + Sync + 'static;
fn hooks(&self) -> &[MhHook];
unsafe fn unhook(&mut self);
}
pub struct Hudhook(Vec<Box<dyn Hooks>>);
unsafe impl Send for Hudhook {}
unsafe impl Sync for Hudhook {}
impl Hudhook {
pub fn builder() -> HudhookBuilder {
HudhookBuilder(Hudhook::new())
}
fn new() -> Self {
match unsafe { MH_Initialize() } {
MH_STATUS::MH_OK => {},
MH_STATUS::MH_ERROR_ALREADY_INITIALIZED => {
warn!("Minhook already initialized");
},
status @ MH_STATUS::MH_ERROR_MEMORY_ALLOC => panic!("MH_Initialize: {status:?}"),
_ => unreachable!(),
}
Hudhook(Vec::new())
}
fn hooks(&self) -> impl IntoIterator<Item = &MhHook> {
self.0.iter().flat_map(|h| h.hooks())
}
pub fn apply(self) -> Result<(), MH_STATUS> {
for hook in self.hooks() {
unsafe { hook.queue_enable()? };
}
unsafe { MH_ApplyQueued().ok_context("MH_ApplyQueued")? };
unsafe { HUDHOOK.set(self).ok() };
Ok(())
}
pub fn unapply(&mut self) -> Result<(), MH_STATUS> {
trace!("Unapply hook");
for hook in self.hooks() {
unsafe { hook.queue_disable()? };
}
unsafe { MH_ApplyQueued().ok_context("MH_ApplyQueued")? };
unsafe { MH_Uninitialize().ok_context("MH_Uninitialize")? };
for hook in &mut self.0 {
unsafe { hook.unhook() };
}
trace!("Finished removing hook");
Ok(())
}
}
pub struct HudhookBuilder(Hudhook);
impl HudhookBuilder {
pub fn with<T: Hooks + 'static>(
mut self,
render_loop: impl ImguiRenderLoop + Send + Sync + 'static,
) -> Self {
self.0 .0.push(T::from_render_loop(render_loop));
self
}
pub fn with_hmodule(self, module: HINSTANCE) -> Self {
unsafe { MODULE.set(module).unwrap() };
self
}
pub fn build(self) -> Hudhook {
self.0
}
}
#[macro_export]
macro_rules! hudhook {
($t:ty, $hooks:expr) => {
#[no_mangle]
pub unsafe extern "system" fn DllMain(
hmodule: ::hudhook::windows::Win32::Foundation::HINSTANCE,
reason: u32,
_: *mut ::std::ffi::c_void,
) {
use ::hudhook::*;
if reason == ::hudhook::windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH {
::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::<$t>({ $hooks })
.with_hmodule(hmodule)
.build()
.apply()
{
::hudhook::tracing::error!("Couldn't apply hooks: {e:?}");
::hudhook::eject();
}
});
}
}
};
}