nexus\api/
hook.rs

1//! Hooking via [MinHook](https://github.com/TsudaKageyu/minhook).
2//!
3//! Enable the `"hook"` feature for bindings using trait interfaces from the [detour](https://github.com/darfink/detour-rs) crate.
4
5use crate::{AddonApi, MinHookApi};
6use std::{ffi::c_void, ptr};
7
8/// MinHook error codes.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(
12    feature = "strum",
13    derive(
14        strum::AsRefStr,
15        strum::Display,
16        strum::EnumCount,
17        strum::EnumIter,
18        strum::IntoStaticStr,
19        strum::VariantArray,
20        strum::VariantNames
21    )
22)]
23#[repr(C)]
24pub enum HookStatus {
25    /// Unknown error. Should not be returned.
26    Unknown = -1,
27
28    /// Successful.
29    Ok = 0,
30
31    /// MinHook is already initialized.
32    ErrorAlreadyInitialized,
33
34    /// MinHook is not initialized yet, or already uninitialized.
35    ErrorNotInitialized,
36
37    /// The hook for the specified target function is already created.
38    ErrorAlreadyCreated,
39
40    /// The hook for the specified target function is not created yet.
41    ErrorNotCreated,
42
43    /// The hook for the specified target function is already enabled.
44    ErrorEnabled,
45
46    /// The hook for the specified target function is not enabled yet, or already disabled.
47    ErrorDisabled,
48
49    /// The specified pointer is invalid.
50    /// It points the address of non-allocated and/or non-executable region.
51    ErrorNotExecutable,
52
53    /// The specified target function cannot be hooked.
54    ErrorUnsupportedFunction,
55
56    /// Failed to allocate memory.
57    ErrorMemoryAlloc,
58
59    /// Failed to change the memory protection.
60    ErrorMemoryProtect,
61
62    /// The specified module is not loaded.
63    ErrorModuleNotFound,
64
65    /// The specified function is not found.
66    ErrorFunctionNotFound,
67}
68
69impl HookStatus {
70    /// Checks if the status is [`HookStatus::Ok`].
71    #[inline]
72    pub fn is_ok(&self) -> bool {
73        matches!(self, Self::Ok)
74    }
75
76    /// Converts the status to a [`Result`].
77    #[inline]
78    pub fn ok(self) -> Result<(), Self> {
79        self.ok_then(())
80    }
81
82    /// Converts the status to a [`Result`] with a value.
83    pub fn ok_then<T>(self, value: T) -> Result<T, Self> {
84        match self {
85            Self::Ok => Ok(value),
86            _ => Err(self),
87        }
88    }
89}
90
91pub type RawHookCreate = unsafe extern "system-unwind" fn(
92    target: *const c_void,
93    detour: *const c_void,
94    trampoline: *mut *const c_void,
95) -> HookStatus;
96
97pub type RawHookRemove = unsafe extern "system-unwind" fn(target: *const c_void) -> HookStatus;
98
99pub type RawHookEnable = unsafe extern "system-unwind" fn(target: *const c_void) -> HookStatus;
100
101pub type RawHookDisable = unsafe extern "system-unwind" fn(target: *const c_void) -> HookStatus;
102
103/// Creates a hook for the specified target function in **disabled** state.
104///
105/// Returns a pointer to the trampoline function, which will be used to call the original target function.
106///
107/// # Safety
108/// Target and detour must point to valid functions and have the same or a compatible function signature.
109#[inline]
110pub unsafe fn create_hook_raw(
111    target: *const (),
112    detour: *const (),
113) -> Result<*const (), HookStatus> {
114    let mut original = ptr::null();
115    let MinHookApi { create, .. } = AddonApi::get().min_hook;
116    let result = unsafe { create(target.cast(), detour.cast(), &mut original) };
117    result.ok_then(original.cast())
118}
119
120/// Removes an already created hook.
121#[inline]
122pub fn remove_hook_raw(target: *const ()) -> Result<(), HookStatus> {
123    let MinHookApi { remove, .. } = AddonApi::get().min_hook;
124    let result = unsafe { remove(target.cast()) };
125    result.ok()
126}
127
128/// Enables an already created hook.
129#[inline]
130pub fn enable_hook_raw(target: *const ()) -> Result<(), HookStatus> {
131    let MinHookApi { enable, .. } = AddonApi::get().min_hook;
132    let result = unsafe { enable(target.cast()) };
133    result.ok()
134}
135
136/// Dsiables an already created hook.
137#[inline]
138pub fn disable_hook_raw(target: *const ()) -> Result<(), HookStatus> {
139    let MinHookApi { disable, .. } = AddonApi::get().min_hook;
140    let result = unsafe { disable(target.cast()) };
141    result.ok()
142}
143
144#[cfg(feature = "hook")]
145mod bindings {
146    use super::*;
147    use retour::{Function, HookableWith};
148
149    /// Creates a hook for the specified target function in **disabled** state.
150    ///
151    /// Returns a pointer to the trampoline function, which will be used to call the original target function.
152    pub fn create_hook<F, D>(target: F, detour: D) -> Result<*const (), HookStatus>
153    where
154        F: Function + HookableWith<D>,
155        D: Function,
156    {
157        unsafe { create_hook_raw(target.to_ptr(), detour.to_ptr()) }
158    }
159
160    /// Creates a hook for the specified target function, and enables it if successful.
161    ///
162    /// Returns a pointer to the trampoline function, which will be used to call the original target function.
163    pub fn create_hook_enabled<F, D>(target: F, detour: D) -> Result<*const (), HookStatus>
164    where
165        F: Function + HookableWith<D>,
166        D: Function,
167    {
168        let trampoline = create_hook(target, detour)?;
169        enable_hook(target)?;
170        Ok(trampoline)
171    }
172
173    /// Removes an already created hook.
174    pub fn remove_hook(target: impl Function) -> Result<(), HookStatus> {
175        remove_hook_raw(target.to_ptr())
176    }
177
178    /// Enables an already created hook.
179    pub fn enable_hook(target: impl Function) -> Result<(), HookStatus> {
180        enable_hook_raw(target.to_ptr())
181    }
182
183    /// Disables an already created hook.
184    pub fn disable_hook(target: impl Function) -> Result<(), HookStatus> {
185        disable_hook_raw(target.to_ptr())
186    }
187}
188
189#[cfg(feature = "hook")]
190pub use bindings::*;