oc_hook_macros/
lib.rs

1#![allow(non_snake_case)]
2
3use objc2::encode::Encode;
4use objc2::encode::EncodeArguments;
5use objc2::encode::EncodeReturn;
6use objc2::ffi::class_addMethod;
7use objc2::runtime::AnyClass;
8use objc2::runtime::AnyObject;
9use objc2::runtime::MethodImplementation;
10use objc2::runtime::Sel;
11use objc2::Encoding;
12use objc2::Message;
13
14pub use objc2;
15pub use ctor;
16
17// https://github.com/MustangYM/WeChatExtension-ForMac/blob/develope/WeChatExtension/WeChatExtension/Sources/Helper/YMSwizzledHelper.m
18pub fn exchange_instance_method(
19    originalClass: &AnyClass,
20    originalSelector: Sel,
21    swizzledClass: &AnyClass,
22    swizzledSelector: Sel,
23) {
24    let original_method = originalClass.instance_method(originalSelector);
25    let swizzled_method = swizzledClass.instance_method(swizzledSelector);
26    if original_method.is_some() && swizzled_method.is_some() {
27        let ori = original_method.unwrap();
28        let swi = swizzled_method.unwrap();
29        unsafe { ori.exchange_implementation(swi) };
30    }
31}
32
33pub fn exchange_class_method(
34    originalClass: &AnyClass,
35    originalSelector: Sel,
36    swizzledClass: &AnyClass,
37    swizzledSelector: Sel,
38) {
39    let original_method = originalClass.class_method(originalSelector);
40    let swizzled_method = swizzledClass.class_method(swizzledSelector);
41    if original_method.is_some() && swizzled_method.is_some() {
42        let ori = original_method.unwrap();
43        let swi = swizzled_method.unwrap();
44        unsafe { ori.exchange_implementation(swi) };
45    }
46}
47
48pub fn hook_instance_method<T, F>(cls: &AnyClass, sel: Sel, func: F)
49where
50    T: Message + ?Sized,
51    F: MethodImplementation<Callee = T>,
52{
53    if cls.instance_method(sel).is_none() {
54        return;
55    }
56
57    let s = format!("hook_{}", sel.name());
58    let hook_sel = Sel::register(s.as_str());
59    insert_method(cls, hook_sel, func);
60
61    exchange_instance_method(cls, sel, cls, hook_sel);
62}
63
64pub fn hook_class_method<T, F>(cls: &AnyClass, sel: Sel, func: F)
65where
66    T: Message + ?Sized,
67    F: MethodImplementation<Callee = T>,
68{
69    let s = format!("hook_{}", sel.name());
70    let hook_sel = Sel::register(s.as_str());
71    insert_method(cls.metaclass(), hook_sel, func);
72
73    exchange_class_method(cls, sel, cls, hook_sel);
74}
75
76pub fn insert_method<T, F>(cls: &AnyClass, sel: Sel, func: F) -> bool
77where
78    T: Message + ?Sized,
79    F: MethodImplementation<Callee = T>,
80{
81    fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> std::ffi::CString {
82        // First two arguments are always self and the selector
83        let mut types = format!("{ret}{}{}", <*mut AnyObject>::ENCODING, Sel::ENCODING);
84        for enc in args {
85            use core::fmt::Write;
86            write!(&mut types, "{enc}").unwrap();
87        }
88        std::ffi::CString::new(types).unwrap()
89    }
90
91    let enc_args = F::Arguments::ENCODINGS;
92    let enc_ret = &F::Return::ENCODING_RETURN;
93
94    let sel_args = sel.name().as_bytes().iter().filter(|&&b| b == b':').count();
95
96    assert_eq!(
97        sel_args,
98        enc_args.len(),
99        "selector {sel} accepts {sel_args} arguments, but function accepts {}",
100        enc_args.len(),
101    );
102
103    let types = method_type_encoding(enc_ret, enc_args);
104
105    unsafe {
106        let success = class_addMethod(
107            cls as *const AnyClass as _,
108            sel.as_ptr(),
109            Some(func.__imp()),
110            types.as_ptr(),
111        );
112        // success is a `bool` in aarch64, or is an `i8`
113        success as i8 != false as i8
114    }
115}
116
117#[macro_export]
118macro_rules! dummy_args {
119    (
120        $arg:ident $(,$tail:ident)* $(,)?
121    ) => {
122        $crate::dummy_args! {
123            [_]
124            $($tail)*
125        }
126    };
127    (
128        unsafe $arg:ident $(,$tail:ident)* $(,)?
129    ) => {
130        $crate::dummy_args! {
131            unsafe
132            [_]
133            $($tail)*
134        }
135    };
136
137    (
138        $($unsafe:ident)?
139        [$($t:tt)*]
140        $arg:ident $($tail:ident)*
141    ) => {
142        $crate::dummy_args! {
143            $($unsafe)?
144            [$($t)*, _]
145            $($tail)*
146        }
147    };
148
149    (
150        $($unsafe:ident)?
151        [$($t:tt)*]
152    ) => {
153        $($unsafe)? extern "C" fn ($($t)*) -> _
154    };
155}
156
157#[macro_export]
158macro_rules! arg_count {
159    // for function arguments, Example: a,b,c
160    (
161        $arg:ident $(,$tail:ident)* $(,)?
162    ) => {
163        $crate::arg_count! {
164            [1] $($tail)*
165        }
166    };
167
168    (
169        [$($t:tt)*]
170        $arg:ident $($tail:ident)*
171    ) => {
172        $crate::arg_count! {
173            [$($t)* + 1] $($tail)*
174        }
175    };
176
177    () => {0};
178
179    (
180        [$($t:tt)*]
181    ) => {
182        $($t)*
183    };
184}
185
186#[macro_export]
187macro_rules! sel_count {
188    // for selector, Example: WeChat:hello:world:
189    (
190        $arg:ident
191    ) => {
192        $crate::sel_count! {
193            [0]
194        }
195    };
196
197    (
198        $arg:ident : $($tail:ident :)*
199    ) => {
200        $crate::sel_count! {
201            [1]
202            $($tail :)*
203        }
204    };
205
206    (
207        [$($t:tt)*]
208        $arg:ident : $($tail:ident :)*
209    ) => {
210        $crate::sel_count! {
211            [$($t)* + 1] $($tail :)*
212        }
213    };
214
215    (
216        [$($t:tt)*]
217    ) => {
218        $($t)*
219    };
220}
221
222#[macro_export]
223macro_rules! param_count_match {
224    (
225        [$($arg:ident),*]
226        []
227    ) => {};
228
229    (
230        [$($arg:ident),*]
231        [$($sel:tt)*]
232    ) => {
233        $crate::param_count_match!{
234            [$($arg),*]
235            [$($sel)*]
236            [$($sel)*]
237        }
238    };
239
240    (
241        [$arg1:ident $(,$tail1:ident)* $(,)?]
242        [$sel:ident : $($tail2:ident :)*]
243        [$($t:tt)*]
244    ) => {
245        $crate::param_count_match! {
246            [$($tail1),*]
247            [$($tail2 :)*]
248            [$($t)*]
249        }
250    };
251
252    (
253        [$arg1:ident, $arg2:ident]
254        [$($sel:ident)?]
255        [$($t:tt)*]
256    ) => {
257        // count match
258    };
259
260    (
261        [$($arg:tt)*]
262        [$($sel:tt)*]
263        [$($t:tt)*]
264    ) => {
265        compile_error!(stringify!(the param count of selector($($t)*) and function does not equal));
266    };
267}
268
269/// Example:
270///
271/// ```
272/// hook_helper! {
273///     // hook a class method
274///     +(void)[ClassA hello]
275///     unsafe extern "C" fn(this: &NSObject, _cmd: Sel) {
276///         // call the original implementation by the `hook_*` selector`
277///        let _:() = msg_send![this, hook_hello];
278///     }
279///
280///     // hokk a instance method
281///     -(void)[ClassB world:]
282///     unsafe extern "C" fn(this: &NSObject, _cmd: Sel, s: &NSString) {
283///         // call the original implementation by the `hook_*` selector`
284///         let _:() = msg_send![this, hook_world:s];
285///     }
286/// }
287/// ```
288#[macro_export]
289macro_rules! hook_helper {
290    (
291        $(
292            $(#[$meta: meta])*
293            $(-($($r:tt)*)[$class1: ident $($sel1:tt)*])?
294            $(+($($r2:tt)*)[$class2: ident $($sel2:tt)*])?
295            unsafe extern "C" fn $name: ident ($($arg:ident: $ty: ty),* $(,)?) $(->$ret:ty)? $body: block
296        )*
297    ) => {
298        $(
299            #[allow(non_snake_case)]
300            $(#[$meta])*
301            unsafe extern "C" fn $name($($arg: $ty),*) $(->$ret)? $body
302
303            #[allow(unused_imports)]
304            #[allow(non_snake_case)]
305            mod $name {
306                use $crate::objc2::class;
307                use $crate::objc2::sel;
308                use super::$name;
309
310                #[$crate::ctor::ctor]
311                fn __dymmy() {
312                    $crate::param_count_match!{
313                        [$($arg),*]
314                        [$($($sel1)*)?]
315                    };
316
317                    $crate::param_count_match!{
318                        [$($arg),*]
319                        [$($($sel2)*)?]
320                    };
321
322                    #[allow(unused_macros)]
323                    macro_rules! func {
324                        () => {
325                            $crate::dummy_args!(unsafe $($arg),*)
326                        };
327                    }
328                    $(
329                        $crate::hook_instance_method(class!($class1), sel!($($sel1)*), $name as func!());
330                    )?
331                    $(
332                        $crate::hook_class_method(class!($class2), sel!($($sel2)*), $name as func!());
333                    )?
334                }
335            }
336        )*
337    };
338}
339
340#[macro_export]
341macro_rules! new_selector {
342    (
343        $(
344            $(#[$meta: meta])*
345            $(-($($r:tt)*)[$class1: ident $($sel1:tt)*])?
346            $(+($($r2:tt)*)[$class2: ident $($sel2:tt)*])?
347            unsafe extern "C" fn $name: ident ($($arg:ident: $ty: ty),* $(,)?) $(->$ret:ty)? $body: block
348        )*
349    ) => {
350        $(
351            #[allow(non_snake_case)]
352            $(#[$meta])*
353            unsafe extern "C" fn $name($($arg: $ty),*) $(->$ret)? $body
354
355            #[allow(unused_imports)]
356            #[allow(non_snake_case)]
357            mod $name {
358                use $crate::objc2::class;
359                use $crate::objc2::sel;
360                use super::$name;
361
362                #[$crate::ctor::ctor]
363                fn __dymmy() {
364                    $crate::param_count_match!{
365                        [$($arg),*]
366                        [$($($sel1)*)?]
367                    };
368
369                    $crate::param_count_match!{
370                        [$($arg),*]
371                        [$($($sel2)*)?]
372                    };
373
374                    #[allow(unused_macros)]
375                    macro_rules! func {
376                        () => {
377                            $crate::dummy_args!(unsafe $($arg),*)
378                        };
379                    }
380                    $(
381                        $crate::insert_method(class!($class1), sel!($($sel1)*), $name as func!());
382                    )?
383                    $(
384                        $crate::insert_method(class!($class2).metaclass(), sel!($($sel2)*), $name as func!());
385                    )?
386                }
387            }
388        )*
389    };
390}