retour/
macros.rs

1/// A macro for defining static, type-safe detours.
2///
3/// This macro defines one or more [StaticDetour](./struct.StaticDetour.html)s.
4///
5///
6/// # Syntax
7///
8/// ```ignore
9/// static_detour! {
10///   [pub] static NAME_1: [unsafe] [extern "cc"] fn([argument]...) [-> ret];
11///   [pub] static NAME_2: [unsafe] [extern "cc"] fn([argument]...) [-> ret];
12///   ...
13///   [pub] static NAME_N: [unsafe] [extern "cc"] fn([argument]...) [-> ret];
14/// }
15/// ```
16///
17/// # Example
18///
19/// ```rust
20/// # use retour::static_detour;
21/// static_detour! {
22///   // The simplest detour
23///   static Foo: fn();
24///
25///   // An unsafe public detour with a different calling convention
26///   pub static PubFoo: unsafe extern "C" fn(i32) -> i32;
27///
28///   // A specific visibility modifier
29///   pub(crate) static PubSelf: unsafe extern "C" fn();
30/// }
31/// # fn main() { }
32/// ```
33#[cfg(feature = "static-detour")]
34#[cfg_attr(docsrs, doc(cfg(feature = "static-detour")))]
35#[macro_export]
36// Inspired by: https://github.com/Jascha-N/minhook-rs
37macro_rules! static_detour {
38  // 1 — meta attributes
39  (@parse_attributes ($($input:tt)*) | #[$attribute:meta] $($rest:tt)*) => {
40    $crate::static_detour!(@parse_attributes ($($input)* $attribute) | $($rest)*);
41  };
42  (@parse_attributes ($($input:tt)*) | $($rest:tt)+) => {
43    $crate::static_detour!(@parse_access_modifier (($($input)*)) | $($rest)*);
44  };
45
46  // 2 — pub modifier (path/scope/yes/no)
47  (@parse_access_modifier ($($input:tt)*) | pub(in $vis:path) static $($rest:tt)*) => {
48    $crate::static_detour!(@parse_name ($($input)* (pub(in $vis))) | $($rest)*);
49  };
50  (@parse_access_modifier ($($input:tt)*) | pub($vis:tt) static $($rest:tt)*) => {
51    $crate::static_detour!(@parse_name ($($input)* (pub($vis))) | $($rest)*);
52  };
53  (@parse_access_modifier ($($input:tt)*) | pub static $($rest:tt)*) => {
54    $crate::static_detour!(@parse_name ($($input)* (pub)) | $($rest)*);
55  };
56  (@parse_access_modifier ($($input:tt)*) | static $($rest:tt)*) => {
57    $crate::static_detour!(@parse_name ($($input)* ()) | $($rest)*);
58  };
59
60  // 3 — detour name
61  (@parse_name ($($input:tt)*) | $name:ident : $($rest:tt)*) => {
62    $crate::static_detour!(@parse_unsafe ($($input)* ($name)) | $($rest)*);
63  };
64
65  // 4 — unsafe modifier (yes/no)
66  (@parse_unsafe ($($input:tt)*) | unsafe $($rest:tt)*) => {
67    $crate::static_detour!(@parse_calling_convention ($($input)*) (unsafe) | $($rest)*);
68  };
69  (@parse_unsafe ($($input:tt)*) | $($rest:tt)*) => {
70    $crate::static_detour!(@parse_calling_convention ($($input)*) () | $($rest)*);
71  };
72
73  // 5 — calling convention (extern "XXX"/extern/-)
74  (@parse_calling_convention
75      ($($input:tt)*) ($($modifier:tt)*) | extern $cc:tt fn $($rest:tt)*) => {
76    $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)* extern $cc)) | $($rest)*);
77  };
78  (@parse_calling_convention
79      ($($input:tt)*) ($($modifier:tt)*) | extern fn $($rest:tt)*) => {
80    $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)* extern)) | $($rest)*);
81  };
82  (@parse_calling_convention ($($input:tt)*) ($($modifier:tt)*) | fn $($rest:tt)*) => {
83    $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)*)) | $($rest)*);
84  };
85
86  // 6 — argument and return type (return/void)
87  (@parse_prototype
88      ($($input:tt)*) | ($($argument_type:ty),*) -> $return_type:ty ; $($rest:tt)*) => {
89    $crate::static_detour!(
90      @parse_terminator ($($input)* ($($argument_type)*) ($return_type)) | ; $($rest)*);
91  };
92  (@parse_prototype ($($input:tt)*) | ($($argument_type:ty),*) $($rest:tt)*) => {
93    $crate::static_detour!(@parse_terminator ($($input)* ($($argument_type)*) (())) | $($rest)*);
94  };
95
96  // 7 — semicolon terminator
97  (@parse_terminator ($($input:tt)*) | ; $($rest:tt)*) => {
98    $crate::static_detour!(@parse_entries ($($input)*) | $($rest)*);
99  };
100
101  // 8 - additional detours (multiple/single)
102  (@parse_entries ($($input:tt)*) | $($rest:tt)+) => {
103    $crate::static_detour!(@aggregate $($input)*);
104    $crate::static_detour!($($rest)*);
105  };
106  (@parse_entries ($($input:tt)*) | ) => {
107    $crate::static_detour!(@aggregate $($input)*);
108  };
109
110  // 9 - aggregate data for the generate function
111  (@aggregate ($($attribute:meta)*) ($($visibility:tt)*) ($name:ident)
112              ($($modifier:tt)*) ($($argument_type:ty)*) ($return_type:ty)) => {
113    $crate::static_detour!(@argument_names (create_detour)(
114      ($($attribute)*) ($($visibility)*) ($name)
115      ($($modifier)*) ($($argument_type)*) ($return_type)
116      ($($modifier)* fn ($($argument_type),*) -> $return_type)
117    )($($argument_type)*));
118  };
119
120  // 10 - detour type implementation
121  (@create_detour ($($argument_name:ident)*) ($($attribute:meta)*) ($($visibility:tt)*)
122                  ($name:ident) ($($modifier:tt)*) ($($argument_type:ty)*)
123                  ($return_type:ty) ($fn_type:ty)) => {
124    $crate::static_detour!(@generate
125      #[allow(non_upper_case_globals)]
126      $(#[$attribute])*
127      $($visibility)* static $name: $crate::StaticDetour<$fn_type> = {
128        #[inline(never)]
129        #[allow(unused_unsafe)]
130        $($modifier) * fn __ffi_detour(
131            $($argument_name: $argument_type),*) -> $return_type {
132          #[allow(unused_unsafe)]
133          ($name.__detour())($($argument_name),*)
134        }
135
136        $crate::StaticDetour::__new(__ffi_detour)
137      };
138    );
139  };
140
141  // Associates each argument type with a dummy name.
142  (@argument_names ($label:ident) ($($input:tt)*) ($($token:tt)*)) => {
143    $crate::static_detour!(@argument_names ($label) ($($input)*)(
144      __arg_0  __arg_1  __arg_2  __arg_3  __arg_4  __arg_5  __arg_6
145      __arg_7  __arg_8  __arg_9  __arg_10 __arg_11 __arg_12 __arg_13
146    )($($token)*)());
147  };
148  (@argument_names
149      ($label:ident)
150      ($($input:tt)*)
151      ($hd_name:tt $($tl_name:tt)*)
152      ($hd:tt $($tl:tt)*) ($($acc:tt)*)) => {
153    $crate::static_detour!(
154      @argument_names ($label) ($($input)*) ($($tl_name)*) ($($tl)*) ($($acc)* $hd_name));
155  };
156  (@argument_names ($label:ident) ($($input:tt)*) ($($name:tt)*) () ($($acc:tt)*)) => {
157    $crate::static_detour!(@$label ($($acc)*) $($input)*);
158  };
159
160  (@generate $item:item) => { $item };
161
162  // Bootstrapper
163  ($($t:tt)+) => {
164    $crate::static_detour!(@parse_attributes () | $($t)+);
165  };
166}
167
168macro_rules! impl_hookable {
169  (@recurse () ($($nm:ident : $ty:ident),*)) => {
170    impl_hookable!(@impl_all ($($nm : $ty),*));
171  };
172  (@recurse
173      ($hd_nm:ident : $hd_ty:ident $(, $tl_nm:ident : $tl_ty:ident)*)
174      ($($nm:ident : $ty:ident),*)) => {
175    impl_hookable!(@impl_all ($($nm : $ty),*));
176    impl_hookable!(@recurse ($($tl_nm : $tl_ty),*) ($($nm : $ty,)* $hd_nm : $hd_ty));
177  };
178
179  (@impl_all ($($nm:ident : $ty:ident),*)) => {
180    impl_hookable!(@impl_pair ($($nm : $ty),*) (                  fn($($ty),*) -> Ret));
181    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "cdecl"    fn($($ty),*) -> Ret));
182    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "stdcall"  fn($($ty),*) -> Ret));
183    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "fastcall" fn($($ty),*) -> Ret));
184    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "win64"    fn($($ty),*) -> Ret));
185    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "C"        fn($($ty),*) -> Ret));
186    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "system"   fn($($ty),*) -> Ret));
187
188    #[cfg(feature = "thiscall-abi")]
189    #[cfg_attr(docsrs, doc(cfg(feature = "thiscall-abi")))]
190    impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "thiscall" fn($($ty),*) -> Ret));
191  };
192
193  (@impl_pair ($($nm:ident : $ty:ident),*) ($($fn_t:tt)*)) => {
194    impl_hookable!(@impl_fun ($($nm : $ty),*) ($($fn_t)*) (unsafe $($fn_t)*));
195  };
196
197  (@impl_fun ($($nm:ident : $ty:ident),*) ($safe_type:ty) ($unsafe_type:ty)) => {
198    impl_hookable!(@impl_core ($($nm : $ty),*) ($safe_type));
199    impl_hookable!(@impl_core ($($nm : $ty),*) ($unsafe_type));
200
201    impl_hookable!(@impl_unsafe ($($nm : $ty),*) ($unsafe_type) ($safe_type));
202    impl_hookable!(@impl_safe ($($nm : $ty),*) ($safe_type));
203  };
204
205  (@impl_unsafe ($($nm:ident : $ty:ident),*) ($target:ty) ($detour:ty)) => {
206    #[cfg(feature = "static-detour")]
207    impl<Ret: 'static, $($ty: 'static),*> $crate::StaticDetour<$target> {
208      #[doc(hidden)]
209      pub unsafe fn call(&self, $($nm : $ty),*) -> Ret {
210        let original: $target = ::std::mem::transmute(self.trampoline().expect("calling detour trampoline"));
211        original($($nm),*)
212      }
213    }
214
215    impl<Ret: 'static, $($ty: 'static),*> $crate::GenericDetour<$target> {
216      #[doc(hidden)]
217      pub unsafe fn call(&self, $($nm : $ty),*) -> Ret {
218        let original: $target = ::std::mem::transmute(self.trampoline());
219        original($($nm),*)
220      }
221    }
222  };
223
224  (@impl_safe ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => {
225    #[cfg(feature = "static-detour")]
226    impl<Ret: 'static, $($ty: 'static),*> $crate::StaticDetour<$fn_type> {
227      #[doc(hidden)]
228      pub fn call(&self, $($nm : $ty),*) -> Ret {
229        unsafe {
230          let original: $fn_type = ::std::mem::transmute(self.trampoline().expect("calling detour trampoline"));
231          original($($nm),*)
232        }
233      }
234    }
235
236    impl<Ret: 'static, $($ty: 'static),*> $crate::GenericDetour<$fn_type> {
237      #[doc(hidden)]
238      pub fn call(&self, $($nm : $ty),*) -> Ret {
239        unsafe {
240          let original: $fn_type = ::std::mem::transmute(self.trampoline());
241          original($($nm),*)
242        }
243      }
244    }
245  };
246
247  (@impl_core ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => {
248    unsafe impl<Ret: 'static, $($ty: 'static),*> Function for $fn_type {
249      type Arguments = ($($ty,)*);
250      type Output = Ret;
251
252      unsafe fn from_ptr(ptr: *const ()) -> Self {
253        ::std::mem::transmute(ptr)
254      }
255
256      fn to_ptr(&self) -> *const () {
257        *self as *const ()
258      }
259    }
260  };
261
262  ($($nm:ident : $ty:ident),*) => {
263    impl_hookable!(@recurse ($($nm : $ty),*) ());
264  };
265}