use ffi::{
MH_ApplyQueued, MH_CreateHook, MH_CreateHookApi, MH_CreateHookApiEx, MH_DisableHook,
MH_EnableHook, MH_Initialize, MH_QueueDisableHook, MH_QueueEnableHook, MH_RemoveHook,
MH_Uninitialize,
};
use std::{
ffi::{CString, c_void},
fmt,
ptr::null_mut,
sync::Once,
};
use tracing::debug;
mod ffi;
const MH_ALL_HOOKS: *const i32 = std::ptr::null();
static MINHOOK_INIT: Once = Once::new();
static MINHOOK_UNINIT: Once = Once::new();
pub struct MinHook {}
impl MinHook {
fn initialize() {
MINHOOK_INIT.call_once(|| {
let status = unsafe { MH_Initialize() };
debug!("MH_Initialize: {:?}", status);
match status.ok() {
Ok(_) => (), Err(MH_STATUS::MH_ERROR_ALREADY_INITIALIZED) => (), Err(e) => panic!("Could not initialize MinHook, error: {e:?}"),
}
});
}
pub fn uninitialize() {
Self::initialize();
MINHOOK_UNINIT.call_once(|| {
let status = unsafe { MH_Uninitialize() };
debug!("MH_Uninitialize: {:?}", status);
status.ok().expect("Could not uninitialize MinHook");
});
}
pub unsafe fn create_hook(
target: *mut c_void,
detour: *mut c_void,
) -> Result<*mut c_void, MH_STATUS> {
Self::initialize();
let mut pp_original: *mut c_void = null_mut();
let status = unsafe { MH_CreateHook(target, detour, &mut pp_original) };
debug!("MH_CreateHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(pp_original),
_ => Err(status),
}
}
pub unsafe fn create_hook_api<T: AsRef<str>>(
module_name: T,
proc_name: T,
detour: *mut c_void,
) -> Result<*mut c_void, MH_STATUS> {
Self::initialize();
let mut module_name = module_name.as_ref().encode_utf16().collect::<Vec<_>>();
module_name.push(0);
let proc_name = CString::new(proc_name.as_ref()).unwrap();
let mut pp_original: *mut c_void = null_mut();
let status = unsafe {
MH_CreateHookApi(
module_name.as_ptr() as *const _,
proc_name.as_ptr() as *const _,
detour,
&mut pp_original,
)
};
debug!("MH_CreateHookApi: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(pp_original),
_ => Err(status),
}
}
pub unsafe fn create_hook_api_ex<T: AsRef<str>>(
module_name: T,
proc_name: T,
detour: *mut c_void,
) -> Result<(*mut c_void, *mut *mut c_void), MH_STATUS> {
Self::initialize();
let mut module_name = module_name.as_ref().encode_utf16().collect::<Vec<_>>();
module_name.push(0);
let proc_name = CString::new(proc_name.as_ref()).unwrap();
let mut pp_original: *mut c_void = null_mut();
let pp_target: *mut *mut c_void = null_mut();
let status = unsafe {
MH_CreateHookApiEx(
module_name.as_ptr() as *const _,
proc_name.as_ptr() as *const _,
detour,
&mut pp_original,
pp_target,
)
};
debug!("MH_CreateHookApiEx: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok((pp_original, pp_target)),
_ => Err(status),
}
}
pub unsafe fn enable_hook(target: *mut c_void) -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_EnableHook(target) };
debug!("MH_EnableHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
pub unsafe fn enable_all_hooks() -> Result<(), MH_STATUS> {
unsafe { Self::enable_hook(MH_ALL_HOOKS as *mut _) }
}
pub unsafe fn disable_hook(target: *mut c_void) -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_DisableHook(target) };
debug!("MH_DisableHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
pub unsafe fn disable_all_hooks() -> Result<(), MH_STATUS> {
unsafe { Self::disable_hook(MH_ALL_HOOKS as *mut _) }
}
pub unsafe fn remove_hook(target: *mut c_void) -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_RemoveHook(target) };
debug!("MH_RemoveHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
pub unsafe fn queue_enable_hook(target: *mut c_void) -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_QueueEnableHook(target) };
debug!("MH_QueueEnableHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
pub unsafe fn queue_disable_hook(target: *mut c_void) -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_QueueDisableHook(target) };
debug!("MH_QueueDisableHook: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
pub unsafe fn apply_queued() -> Result<(), MH_STATUS> {
Self::initialize();
let status = unsafe { MH_ApplyQueued() };
debug!("MH_ApplyQueued: {:?}", status);
match status {
MH_STATUS::MH_OK => Ok(()),
_ => Err(status),
}
}
}
#[allow(non_camel_case_types)]
#[must_use]
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MH_STATUS {
MH_UNKNOWN = -1,
MH_OK = 0,
MH_ERROR_ALREADY_INITIALIZED,
MH_ERROR_NOT_INITIALIZED,
MH_ERROR_ALREADY_CREATED,
MH_ERROR_NOT_CREATED,
MH_ERROR_ENABLED,
MH_ERROR_DISABLED,
MH_ERROR_NOT_EXECUTABLE,
MH_ERROR_UNSUPPORTED_FUNCTION,
MH_ERROR_MEMORY_ALLOC,
MH_ERROR_MEMORY_PROTECT,
MH_ERROR_MODULE_NOT_FOUND,
MH_ERROR_FUNCTION_NOT_FOUND,
}
impl MH_STATUS {
pub fn ok(self) -> Result<(), MH_STATUS> {
if self == MH_STATUS::MH_OK {
Ok(())
} else {
Err(self)
}
}
}
impl fmt::Display for MH_STATUS {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let message = match self {
MH_STATUS::MH_UNKNOWN => "Unknown error. Should not be returned.",
MH_STATUS::MH_OK => "Successful.",
MH_STATUS::MH_ERROR_ALREADY_INITIALIZED => "MinHook is already initialized.",
MH_STATUS::MH_ERROR_NOT_INITIALIZED => {
"MinHook is not initialized yet, or already uninitialized."
}
MH_STATUS::MH_ERROR_ALREADY_CREATED => {
"The hook for the specified target function is already created."
}
MH_STATUS::MH_ERROR_NOT_CREATED => {
"The hook for the specified target function is not created yet."
}
MH_STATUS::MH_ERROR_ENABLED => {
"The hook for the specified target function is already enabled."
}
MH_STATUS::MH_ERROR_DISABLED => {
"The hook for the specified target function is not enabled yet, or already disabled."
}
MH_STATUS::MH_ERROR_NOT_EXECUTABLE => {
"The specified pointer is invalid. It points the address of non-allocated and/or non-executable region."
}
MH_STATUS::MH_ERROR_UNSUPPORTED_FUNCTION => {
"The specified target function cannot be hooked."
}
MH_STATUS::MH_ERROR_MEMORY_ALLOC => "Failed to allocate memory.",
MH_STATUS::MH_ERROR_MEMORY_PROTECT => "Failed to change the memory protection.",
MH_STATUS::MH_ERROR_MODULE_NOT_FOUND => "The specified module is not loaded.",
MH_STATUS::MH_ERROR_FUNCTION_NOT_FOUND => "The specified function is not found.",
};
write!(f, "{message}")
}
}
impl std::error::Error for MH_STATUS {}