use super::signature::{HookReturn, Signature, nothing};
use super::site::{Site, on_main_thread};
use super::{CallBackend, HookCall, HookError, HookTiming, Level};
use crate::MetamodApi;
use crate::sys::khook::{Action, IKHook, IKHookVtable, INVALID_HOOK};
use std::ffi::{c_int, c_uint, c_void};
use std::mem::size_of;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicPtr, Ordering};
static KHOOK: AtomicPtr<IKHook> = AtomicPtr::new(ptr::null_mut());
static KHOOK_VTABLE: AtomicPtr<IKHookVtable> = AtomicPtr::new(ptr::null_mut());
pub(super) fn bind(api: MetamodApi<'_>, plugin: c_int) -> Result<(), HookError> {
let khook = api
.detour_interface(plugin)
.map_err(|_| HookError::Unsupported)?
.ok_or(HookError::NotBound)?
.cast::<IKHook>();
let vtable = unsafe { (*khook.as_ptr()).vtable };
if vtable.is_null() {
return Err(HookError::NotBound);
}
KHOOK_VTABLE.store(vtable.cast_mut(), Ordering::Release);
KHOOK.store(khook.as_ptr(), Ordering::Release);
Ok(())
}
pub(super) unsafe fn call_original<S: Signature>(this: *mut S::This, args: S::Args) -> S::Output {
let (khook, vtable) = interface();
let original = unsafe { (vtable.get_original_function)(khook) };
let value = match NonNull::new(original) {
Some(original) => unsafe { S::invoke(S::from_address(original), this, args) },
None => nothing(),
};
unsafe { save(Action::IGNORE, Some(value), true) };
value
}
unsafe fn callback<S: Signature>(
timing: HookTiming,
this: *mut S::This,
args: S::Args,
) -> S::Output {
if !on_main_thread() {
return nothing();
}
let (khook, vtable) = interface();
let site = unsafe { &*(vtable.get_context_ptr)(khook).cast::<Site<S>>() };
if !site.active() {
return nothing();
}
let call = HookCall::<S>::new(this, args, timing, CallBackend::KHook);
site.dispatch(&call);
let outcome = call.outcome();
let action = match outcome.level {
Level::Ignore | Level::Handled => return nothing(),
Level::Override => Action::OVERRIDE,
Level::Supersede => Action::SUPERSEDE,
};
match (size_of::<S::Output>(), outcome.value) {
(0, _) => unsafe { save::<S::Output>(action, None, false) },
(_, Some(value)) => unsafe { save(action, Some(value), false) },
(_, None) => {}
}
nothing()
}
unsafe extern "C" fn copy<R: HookReturn>(destination: *mut R, value: *const R) {
unsafe { destination.write_unaligned(value.read_unaligned()) };
}
unsafe extern "C" fn destroy<R: HookReturn>(_value: *mut R) {}
pub(super) unsafe fn install<S: Signature>(
site: &'static Site<S>,
timing: HookTiming,
) -> Result<(), HookError> {
let index = c_int::try_from(site.index()).map_err(|_| HookError::InvalidArgument)?;
let (khook, vtable) = interface();
let thunks = S::THUNKS;
let (pre, post) = match timing {
HookTiming::Pre => (thunks.khook_pre.address().as_ptr(), ptr::null_mut()),
HookTiming::Post => (ptr::null_mut(), thunks.khook_post.address().as_ptr()),
};
let id = unsafe {
(vtable.setup_virtual_hook)(
khook,
site.vtable().as_ptr(),
index,
ptr::from_ref(site).cast_mut().cast(),
ptr::null_mut(),
pre,
post,
thunks.khook_make_return.address().as_ptr(),
thunks.khook_call_original.address().as_ptr(),
stack_size::<S>(),
true,
)
};
match id {
INVALID_HOOK => Err(HookError::Refused),
_ => Ok(()),
}
}
fn interface() -> (*mut IKHook, &'static IKHookVtable) {
let vtable = KHOOK_VTABLE.load(Ordering::Acquire);
(KHOOK.load(Ordering::Acquire), unsafe { &*vtable })
}
pub(super) unsafe fn make_return<R: HookReturn>() -> R {
let (khook, vtable) = interface();
let value = match size_of::<R>() {
0 => nothing(),
_ => unsafe {
let value = (vtable.get_current_value_ptr)(khook, true).cast::<R>();
match value.is_null() {
true => nothing(),
false => value.read_unaligned(),
}
},
};
unsafe { (vtable.destroy_return_value)(khook) };
value
}
pub(super) unsafe fn original(vtable: NonNull<*mut c_void>, index: c_int) -> *mut c_void {
let (khook, functions) = interface();
unsafe { (functions.find_original_virtual)(khook, vtable.as_ptr(), index) }
}
pub(super) unsafe fn post<S: Signature>(this: *mut S::This, args: S::Args) -> S::Output {
unsafe { callback::<S>(HookTiming::Post, this, args) }
}
pub(super) unsafe fn pre<S: Signature>(this: *mut S::This, args: S::Args) -> S::Output {
unsafe { callback::<S>(HookTiming::Pre, this, args) }
}
pub(super) unsafe fn return_value() -> *const c_void {
let (khook, vtable) = interface();
unsafe { (vtable.get_current_value_ptr)(khook, false) }.cast_const()
}
unsafe fn save<R: HookReturn>(action: Action, value: Option<R>, original: bool) {
let (khook, vtable) = interface();
let mut value = value.filter(|_| size_of::<R>() != 0);
let (pointer, size, copy, destroy) = match &mut value {
Some(value) => (
ptr::from_mut(value).cast(),
size_of::<R>(),
copy::<R> as unsafe extern "C" fn(*mut R, *const R) as *mut c_void,
destroy::<R> as unsafe extern "C" fn(*mut R) as *mut c_void,
),
None => (ptr::null_mut(), 0, ptr::null_mut(), ptr::null_mut()),
};
unsafe { (vtable.save_return_value)(khook, action, pointer, size, copy, destroy, original) };
}
fn stack_size<S: Signature>() -> c_uint {
let returned = size_of::<S::Output>();
let size = if cfg!(windows) {
let arguments = 1 + S::PARAMETER_SIZES.len() + usize::from(returned != 0);
32 + 8 * arguments.saturating_sub(4)
} else {
let slots = |size: usize| size.next_multiple_of(8).max(8);
let returned = if returned != 0 { slots(returned) } else { 0 };
returned
+ 8
+ S::PARAMETER_SIZES
.iter()
.map(|&size| slots(size))
.sum::<usize>()
};
c_uint::try_from(size).unwrap_or(c_uint::MAX)
}