asdf_overlay_hook/
lib.rs

1//! Hooking library for Windows using Detours.
2//!
3//! This crate is intended to be used only as `asdf-overlay`'s internal dependency.
4//! It provides a safe abstraction over the Detours library for function hooking.
5
6#[cfg(all(not(doc), not(docsrs)))]
7#[allow(non_camel_case_types, non_snake_case, unused, clippy::all)]
8mod detours {
9    include!(concat!(env!("OUT_DIR"), "/detours_bindings.rs"));
10}
11
12use tracing::debug;
13
14use core::{
15    error::Error,
16    ffi::c_long,
17    fmt::{self, Debug, Display, Formatter},
18};
19
20/// A detour function hook.
21#[derive(Debug)]
22pub struct DetourHook<F> {
23    func: F,
24}
25
26impl<F: Copy> DetourHook<F> {
27    /// Attach a hook to the target function.
28    ///
29    /// # Safety
30    /// func and detour should be valid function pointers with same signature.
31    #[tracing::instrument]
32    pub unsafe fn attach(mut func: F, mut detour: F) -> DetourResult<Self>
33    where
34        F: Debug,
35    {
36        #[cfg(all(not(doc), not(docsrs)))]
37        unsafe {
38            wrap_detour_call(|| detours::DetourTransactionBegin())?;
39            wrap_detour_call(|| {
40                use core::ffi::c_void;
41
42                detours::DetourAttach(
43                    (&raw mut func).cast(),
44                    *(&raw mut detour).cast::<*mut c_void>(),
45                )
46            })?;
47            wrap_detour_call(|| detours::DetourTransactionCommit())?;
48        }
49        debug!("hook attached");
50
51        Ok(DetourHook { func })
52    }
53
54    /// Get the original function pointer.
55    #[inline(always)]
56    pub fn original_fn(&self) -> F {
57        self.func
58    }
59}
60
61type DetourResult<T> = Result<T, DetourError>;
62
63/// Detour error code.
64#[derive(Debug, Clone, Copy)]
65pub struct DetourError(c_long);
66
67impl Display for DetourError {
68    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69        write!(f, "Detour call error: {:?}", self.0)
70    }
71}
72
73impl Error for DetourError {}
74
75/// Wrap a detour call and convert its errors to `DetourError`.
76#[inline]
77fn wrap_detour_call(f: impl FnOnce() -> c_long) -> Result<(), DetourError> {
78    let code = f();
79    if code == 0 {
80        Ok(())
81    } else {
82        Err(DetourError(code))
83    }
84}