use core::{
ffi::c_void,
fmt::Debug,
mem::{self, transmute_copy},
};
use slim_detours_sys::SlimDetoursInlineHook;
use windows::core::HRESULT;
use crate::{FnPtr, log::*};
#[cfg(feature = "std")]
mod map;
#[cfg(feature = "std")]
pub use map::InlineHookMap;
#[derive(Debug)]
pub struct InlineHook<F: FnPtr> {
target: F,
trampoline: F,
detour: F,
}
impl<F: FnPtr> InlineHook<F> {
pub fn with_enabled(target: F, detour: F, enable: bool) -> Result<Self, HRESULT> {
let target_ptr: *mut c_void = unsafe { transmute_copy(&target) };
let detour_ptr: *mut c_void = unsafe { transmute_copy(&detour) };
let mut trampoline_ptr: *mut c_void = target_ptr;
let res = unsafe { SlimDetoursInlineHook(enable as _, &mut trampoline_ptr, detour_ptr) };
let hr = HRESULT(res);
if hr.is_ok() {
let trampoline: F = unsafe { transmute_copy(&trampoline_ptr) };
let guard = Self {
target,
trampoline,
detour,
};
debug!(?target, ?detour, ?trampoline, ?enable, "InlineHook");
Ok(guard)
} else {
Err(hr)
}
}
#[doc(alias = "new_disabled")]
pub const fn new(target: F, detour: F) -> Self {
Self {
target,
trampoline: target,
detour,
}
}
pub fn new_enabled(target: F, detour: F) -> Result<Self, HRESULT> {
Self::with_enabled(target, detour, true)
}
pub fn set_enabled(&mut self, enable: bool) -> HRESULT {
let detour_ptr: *mut c_void = unsafe { transmute_copy(&self.detour) };
let mut trampoline_ptr: *mut c_void = unsafe { transmute_copy(&self.trampoline) };
let res = unsafe { SlimDetoursInlineHook(enable as _, &mut trampoline_ptr, detour_ptr) };
let hr = HRESULT(res);
if hr.is_ok() {
self.trampoline = unsafe { transmute_copy(&trampoline_ptr) };
}
hr
}
pub fn enable(&mut self) -> HRESULT {
if self.is_enabled() {
return HRESULT(0);
}
self.set_enabled(true)
}
pub fn disable(&mut self) -> HRESULT {
if !self.is_enabled() {
return HRESULT(0);
}
self.set_enabled(false)
}
pub fn toggle(&mut self) -> HRESULT {
if self.is_enabled() {
self.disable()
} else {
self.enable()
}
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.target != self.trampoline
}
#[inline]
pub const fn target(&self) -> F {
self.target
}
#[inline]
pub fn is_target(&self, other: F) -> bool {
self.target == other
}
#[inline]
pub const fn detour(&self) -> F {
self.detour
}
#[inline]
pub const fn trampoline(&self) -> F {
self.trampoline
}
pub unsafe fn cast<F2: FnPtr>(&self) -> &InlineHook<F2> {
unsafe { transmute_copy(&self) }
}
pub unsafe fn cast_mut<F2: FnPtr>(&mut self) -> &mut InlineHook<F2> {
unsafe { transmute_copy(&self) }
}
pub unsafe fn cast_into<F2: FnPtr>(self) -> InlineHook<F2> {
let hook = InlineHook {
target: unsafe { transmute_copy(&self.target) },
trampoline: unsafe { transmute_copy(&self.trampoline) },
detour: unsafe { transmute_copy(&self.detour) },
};
mem::forget(self);
hook
}
pub unsafe fn into_type_erased(self) -> InlineHook<fn()> {
unsafe { self.cast_into::<fn()>() }
}
}
impl<F: FnPtr> Drop for InlineHook<F> {
fn drop(&mut self) {
let hr = self.disable();
if !hr.is_ok() {
debug!(?hr, "Failed to disable hook on drop");
}
}
}
#[cfg(test)]
pub mod tests {
use std::sync::Mutex;
use super::*;
pub static TEST_MUTEX: Mutex<()> = Mutex::new(());
#[inline(never)]
extern "system" fn inc_target(x: u32) -> u32 {
x + 1
}
#[inline(never)]
extern "system" fn dec_detour(x: u32) -> u32 {
x - 1
}
#[test]
fn assert_send_sync() {
fn assert_send<F: FnPtr>(_: &InlineHook<F>) {}
fn assert_sync<F: FnPtr>(_: &InlineHook<F>) {}
type MyFn = extern "system" fn(u32) -> u32;
extern "system" fn dummy(_x: u32) -> u32 {
0
}
let hook = InlineHook::<MyFn>::new(dummy, dummy);
assert_send(&hook);
assert_sync(&hook);
{
type MyFn = unsafe extern "system" fn(*mut c_void) -> u32;
unsafe extern "system" fn dummy(_x: *mut c_void) -> u32 {
0
}
let hook = InlineHook::<MyFn>::new(dummy, dummy);
assert_send(&hook);
assert_sync(&hook);
}
}
#[test]
fn is_target() {
let _guard = TEST_MUTEX.lock().unwrap();
type MyFn = extern "system" fn(u32) -> u32;
let target: MyFn = inc_target;
let detour: MyFn = dec_detour;
let hook = InlineHook::<MyFn>::new(target, detour);
assert!(hook.is_target(target));
assert!(!hook.is_target(detour));
}
#[test]
fn inline_hook_creation() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
assert_eq!(target(5), 6); assert_eq!(detour(5), 4);
let hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert!(hook.is_enabled());
assert_eq!(hook.target() as *const c_void, target as *const c_void);
assert_eq!(hook.detour() as *const c_void, detour as *const c_void);
assert_eq!(hook.target()(5), 4); assert_eq!(inc_target(5), 4); assert_eq!(hook.trampoline()(5), 6); assert_eq!(hook.detour()(5), 4); assert_eq!(dec_detour(5), 4); }
#[test]
fn inline_hook_disabled_by_default() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
let hook = InlineHook::<FnType>::new(target, detour);
assert!(!hook.is_enabled());
assert_eq!(hook.target() as *const c_void, target as *const c_void);
assert_eq!(hook.detour() as *const c_void, detour as *const c_void);
assert_eq!(target(10), 11); }
#[test]
fn trampoline_is_true_original() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
let hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert_eq!(hook.trampoline()(5), 6); }
#[test]
fn enable_disable() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
let mut hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert!(hook.is_enabled());
hook.disable().unwrap();
assert!(!hook.is_enabled());
hook.enable().unwrap();
assert!(hook.is_enabled());
}
#[test]
fn toggle() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
let mut hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert!(hook.is_enabled());
hook.toggle().unwrap();
assert!(!hook.is_enabled());
hook.toggle().unwrap();
assert!(hook.is_enabled());
}
#[test]
fn typed_function_pointers() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "system" fn(u32) -> u32;
let target = inc_target;
let detour = dec_detour;
let hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert_eq!(hook.target() as *const c_void, target as *const c_void);
assert_eq!(hook.detour() as *const c_void, detour as *const c_void);
}
#[test]
fn doc() {
let _guard = TEST_MUTEX.lock().unwrap();
extern "system" fn original(x: u32) -> u32 {
x + 1
}
extern "system" fn hooked(x: u32) -> u32 {
x + 0o721
}
let mut hook =
InlineHook::<extern "system" fn(u32) -> u32>::new_enabled(original, hooked).unwrap();
assert!(hook.is_enabled());
assert_eq!(original(0x100), 721);
assert_eq!(hook.trampoline()(0x100), 0x101);
hook.disable().unwrap();
assert!(!hook.is_enabled());
assert_eq!(original(0x100), 0x101); }
#[test]
#[cfg(target_arch = "x86_64")]
fn abi_sysv64() {
let _guard = TEST_MUTEX.lock().unwrap();
type FnType = extern "sysv64" fn(u32) -> u32;
#[inline(never)]
extern "sysv64" fn target_inc(x: u32) -> u32 {
x + 1
}
#[inline(never)]
extern "sysv64" fn detour_dec(x: u32) -> u32 {
x - 1
}
let target = target_inc;
let detour = detour_dec;
assert_eq!(target(5), 6); assert_eq!(detour(5), 4);
let hook = InlineHook::<FnType>::new_enabled(target, detour).unwrap();
assert!(hook.is_enabled());
assert_eq!(hook.target()(5), 4); assert_eq!(hook.trampoline()(5), 6); assert_eq!(hook.detour()(5), 4); }
}