1#[allow(
8 non_camel_case_types,
9 non_upper_case_globals,
10 non_snake_case,
11 unused,
12 clippy::all
13)]
14mod bindings {
15 include!("./bindings.rs");
17}
18
19use fn_ptr::{FnPtr, UntypedFnPtr};
20use scopeguard::defer;
21use tracing::{Level, debug};
22
23use core::{fmt::Debug, ptr};
24use std::sync::LazyLock;
25
26#[derive(Debug)]
30pub struct DetourHook<F> {
31 trampoline: F,
32}
33
34impl<F: FnPtr> DetourHook<F> {
35 #[tracing::instrument(level = Level::TRACE)]
44 pub unsafe fn attach(func: F, detour: F) -> DetourResult<Self> {
45 let mut trampoline: UntypedFnPtr = ptr::null_mut();
46 let code = unsafe {
47 bindings::gum_bindings_interceptor_replace_fast(
48 INTERCEPTER.0,
49 func.as_ptr() as _,
50 detour.as_ptr() as _,
51 (&raw mut trampoline).cast(),
52 )
53 };
54 match code {
55 bindings::GumReplaceReturn_GUM_REPLACE_WRONG_SIGNATURE => {
56 return Err(HookError::BadSignature);
57 }
58 bindings::GumReplaceReturn_GUM_REPLACE_ALREADY_REPLACED => {
59 return Err(HookError::AlreadyReplaced);
60 }
61 bindings::GumReplaceReturn_GUM_REPLACE_POLICY_VIOLATION => {
62 return Err(HookError::PolicyViolation);
63 }
64 bindings::GumReplaceReturn_GUM_REPLACE_WRONG_TYPE => {
65 return Err(HookError::WrongType);
66 }
67
68 _ => {}
69 }
70
71 debug!("hook attached");
72 Ok(DetourHook {
73 trampoline: unsafe { F::from_ptr(trampoline as _) },
74 })
75 }
76
77 #[inline(always)]
82 pub unsafe fn original_fn(&self) -> F {
83 self.trampoline
84 }
85}
86
87pub fn with_transaction<R>(f: impl FnOnce() -> R) -> R {
92 unsafe {
93 bindings::gum_bindings_interceptor_begin_transaction(INTERCEPTER.0);
94 }
95 defer!(unsafe { bindings::gum_bindings_interceptor_end_transaction(INTERCEPTER.0) });
96
97 f()
98}
99
100type DetourResult<T> = Result<T, HookError>;
101
102#[derive(Debug, Clone, Copy, thiserror::Error)]
104pub enum HookError {
105 #[error("Bad interceptor signature")]
106 BadSignature,
107
108 #[error("Function already replaced")]
109 AlreadyReplaced,
110
111 #[error("Policy violation")]
112 PolicyViolation,
113
114 #[error("Wrong type")]
115 WrongType,
116}
117
118static INTERCEPTER: LazyLock<Intercepter> = LazyLock::new(|| {
119 Intercepter(unsafe {
120 bindings::gum_bindings_init();
121 bindings::gum_bindings_interceptor_obtain()
122 })
123});
124
125#[derive(Debug, Clone, Copy)]
126#[repr(transparent)]
127struct Intercepter(*mut bindings::GumInterceptor);
128
129unsafe impl Send for Intercepter {}
130unsafe impl Sync for Intercepter {}