macro_rules! hook_managers {
($($function:tt)*) => {
[
$($function)*::<0>, $($function)*::<1>, $($function)*::<2>, $($function)*::<3>,
$($function)*::<4>, $($function)*::<5>, $($function)*::<6>, $($function)*::<7>,
$($function)*::<8>, $($function)*::<9>, $($function)*::<10>, $($function)*::<11>,
$($function)*::<12>, $($function)*::<13>, $($function)*::<14>, $($function)*::<15>,
$($function)*::<16>, $($function)*::<17>, $($function)*::<18>, $($function)*::<19>,
$($function)*::<20>, $($function)*::<21>, $($function)*::<22>, $($function)*::<23>,
$($function)*::<24>, $($function)*::<25>, $($function)*::<26>, $($function)*::<27>,
$($function)*::<28>, $($function)*::<29>, $($function)*::<30>, $($function)*::<31>,
$($function)*::<32>, $($function)*::<33>, $($function)*::<34>, $($function)*::<35>,
$($function)*::<36>, $($function)*::<37>, $($function)*::<38>, $($function)*::<39>,
$($function)*::<40>, $($function)*::<41>, $($function)*::<42>, $($function)*::<43>,
$($function)*::<44>, $($function)*::<45>, $($function)*::<46>, $($function)*::<47>,
$($function)*::<48>, $($function)*::<49>, $($function)*::<50>, $($function)*::<51>,
$($function)*::<52>, $($function)*::<53>, $($function)*::<54>, $($function)*::<55>,
$($function)*::<56>, $($function)*::<57>, $($function)*::<58>, $($function)*::<59>,
$($function)*::<60>, $($function)*::<61>, $($function)*::<62>, $($function)*::<63>,
]
};
}
mod khook;
mod signature;
mod site;
mod sourcehook;
#[cfg(test)]
#[path = "../tests/hook.rs"]
mod tests;
use crate::MetamodApi;
use crate::sys::sourcehook::{ISourceHook, MetaRes};
use site::Registry;
use std::cell::Cell;
use std::ffi::{c_int, c_void};
use std::marker::PhantomData;
use std::mem::size_of;
use std::num::NonZeroU64;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, Ordering};
pub use signature::{HookArg, HookReturn, Signature};
#[derive(Debug, Clone, Copy)]
enum CallBackend {
SourceHook(NonNull<ISourceHook>),
KHook,
}
pub trait Handler<S: Signature>: 'static {
fn call(&self, call: &HookCall<'_, S>) -> HookAction<S::Output>;
}
impl<S, F> Handler<S> for F
where
S: Signature,
F: Fn(&HookCall<'_, S>) -> HookAction<S::Output> + 'static,
{
fn call(&self, call: &HookCall<'_, S>) -> HookAction<S::Output> {
self(call)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HookAction<R> {
Ignore,
Handled,
Override(R),
Supersede(R),
}
impl<R> HookAction<R> {
fn into_parts(self) -> (Level, Option<R>) {
match self {
Self::Ignore => (Level::Ignore, None),
Self::Handled => (Level::Handled, None),
Self::Override(value) => (Level::Override, Some(value)),
Self::Supersede(value) => (Level::Supersede, Some(value)),
}
}
}
pub struct HookCall<'call, S: Signature> {
this: *mut S::This,
args: S::Args,
timing: HookTiming,
backend: CallBackend,
outcome: Cell<Outcome<S::Output>>,
_call: PhantomData<&'call ()>,
}
impl<S: Signature> HookCall<'_, S> {
fn new(this: *mut S::This, args: S::Args, timing: HookTiming, backend: CallBackend) -> Self {
Self {
this,
args,
timing,
backend,
outcome: Cell::new(Outcome::IGNORED),
_call: PhantomData,
}
}
pub fn args(&self) -> S::Args {
self.args
}
fn outcome(&self) -> Outcome<S::Output> {
self.outcome.get()
}
fn record(&self, action: HookAction<S::Output>) {
let mut outcome = self.outcome.get();
let (level, value) = action.into_parts();
outcome.level = outcome.level.max(level);
if value.is_some() {
outcome.value = value;
}
self.outcome.set(outcome);
}
pub fn return_value(&self) -> Option<S::Output> {
let outcome = self.outcome.get();
if outcome.level >= Level::Override {
return outcome.value;
}
if size_of::<S::Output>() == 0 {
return (self.timing == HookTiming::Post).then(signature::nothing);
}
let value = match self.backend {
CallBackend::SourceHook(sourcehook) => unsafe {
sourcehook::return_value(sourcehook, self.timing)
},
CallBackend::KHook => unsafe { khook::return_value() },
};
NonNull::new(value.cast_mut())
.map(|value| unsafe { value.cast::<S::Output>().read_unaligned() })
}
pub fn superseded(&self) -> Option<bool> {
if self.timing == HookTiming::Post {
return None;
}
if self.outcome.get().level == Level::Supersede {
return Some(true);
}
match self.backend {
CallBackend::SourceHook(sourcehook) => {
Some(unsafe { sourcehook::status(sourcehook) } >= MetaRes::SUPERCEDE)
}
CallBackend::KHook => None,
}
}
pub fn this(&self) -> *mut S::This {
self.this
}
pub fn timing(&self) -> HookTiming {
self.timing
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum HookError {
#[error(
"hooks can only be installed while Metamod runs the plugin, and with a hooking library"
)]
NotBound,
#[error("the hook is already installed")]
AlreadyInstalled,
#[error("Metamod's hooking library refused the hook")]
Refused,
#[error("the hook was given invalid arguments")]
InvalidArgument,
#[error("this Metamod version is not supported")]
Unsupported,
#[error("the function is already hooked with another signature")]
SignatureMismatch,
#[error("too many virtual functions are hooked")]
TooManyFunctions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HookId(NonZeroU64);
impl HookId {
fn next() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(1);
Self(NonZeroU64::new(NEXT.fetch_add(1, Ordering::Relaxed)).expect("hook IDs ran out"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HookTarget(Target);
impl HookTarget {
pub fn class_of<T>(object: NonNull<T>) -> Self {
Self(Target::ClassOf(object.cast()))
}
pub fn instance<T>(object: NonNull<T>) -> Self {
Self(Target::Instance(object.cast()))
}
pub fn vtable(vtable: NonNull<*mut c_void>) -> Self {
Self(Target::Vtable(vtable))
}
unsafe fn resolve(self) -> Option<(NonNull<*mut c_void>, Option<NonNull<c_void>>)> {
let vtable_of = |object: NonNull<c_void>| unsafe {
NonNull::new(object.cast::<*mut *mut c_void>().read())
};
match self.0 {
Target::ClassOf(object) => Some((vtable_of(object)?, None)),
Target::Instance(object) => Some((vtable_of(object)?, Some(object))),
Target::Vtable(vtable) => Some((vtable, None)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HookTiming {
Pre,
Post,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Level {
Ignore,
Handled,
Override,
Supersede,
}
#[derive(Debug, Clone, Copy)]
struct Outcome<R> {
level: Level,
value: Option<R>,
}
impl<R> Outcome<R> {
const IGNORED: Self = Self {
level: Level::Ignore,
value: None,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Target {
ClassOf(NonNull<c_void>),
Instance(NonNull<c_void>),
Vtable(NonNull<*mut c_void>),
}
#[derive(Debug, Clone, Copy)]
pub struct VirtualFunction<S: Signature> {
index: usize,
_signature: PhantomData<fn() -> S>,
}
impl<S: Signature> VirtualFunction<S> {
pub const fn new(index: usize) -> Self {
Self {
index,
_signature: PhantomData,
}
}
pub const fn index(self) -> usize {
self.index
}
}
impl MetamodApi<'_> {
pub unsafe fn add_hook<S: Signature>(
self,
function: VirtualFunction<S>,
target: HookTarget,
timing: HookTiming,
handler: &'static dyn Handler<S>,
) -> Result<HookId, HookError> {
let (vtable, instance) = unsafe { target.resolve() }.ok_or(HookError::InvalidArgument)?;
let mut registry = Registry::bind(self)?;
let site = registry.site::<S>(vtable, function.index())?;
if !site.installed(timing) {
unsafe { registry.install(site, timing) }?;
site.set_installed(timing);
}
let id = HookId::next();
site.push(id, timing, instance, handler);
Ok(id)
}
pub fn has_hook(self, id: HookId) -> bool {
Registry::current(self).is_some_and(|registry| registry.contains(id))
}
pub unsafe fn original_function<S: Signature>(
self,
function: VirtualFunction<S>,
target: HookTarget,
) -> Result<S, HookError> {
let (vtable, _) = unsafe { target.resolve() }.ok_or(HookError::InvalidArgument)?;
let index = c_int::try_from(function.index()).map_err(|_| HookError::InvalidArgument)?;
let registry = Registry::bind(self)?;
let original = unsafe { registry.original(vtable, index) };
NonNull::new(original)
.map(S::from_address)
.ok_or(HookError::InvalidArgument)
}
pub fn remove_hook(self, id: HookId) -> bool {
Registry::current(self).is_some_and(|registry| registry.remove(id))
}
}