1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/// Creates an trait that emulates virtual table behavior from C++.
/// You can use `'this` lifetime for arguments that requires `self` lifetime.
/// ```
/// # use faithe::interface;
/// struct CPlayer;
/// interface! {
///     trait IEntity(CPlayer) {
///         // 1 - is an index of this function in the table.
///         extern "C" fn print() = 1;
///         // Lifetimes works too
///         extern "C" fn other<'a>(num: &'a i32) -> &'a i32 = 2;
///     }
/// }
/// // You can use `IInterface::virt_by_address(obj, "func_name")` to get the address of the function by its name or
/// // `IInterface::virt_by_index(obj, <function_index>)` to get the address by the function's index.
/// ```
#[macro_export]
macro_rules! interface {
    (
        $(
            $vs:vis trait $name:ident$(($($target:ident$(<$($tlf:tt),*>)?),*))? {
                $(
                    $(extern $($cc:literal)?)? fn $fn_id:ident$(<$($gen:tt),*>)?($($arg_id:ident: $arg_ty:ty),*) $(-> $ret_ty:ty)? = $idx:expr;
                )*
            }
        )*
    ) => {
        $(
            $vs unsafe trait $name: ::core::marker::Sized {
                $(
                    #[inline(always)]
                    #[allow(non_snake_case)]
                    $(extern $($cc)?)? fn $fn_id<'this$(,$($gen),*)?>(&'this self, $($arg_id: $arg_ty),*) $(-> $ret_ty)? {
                        unsafe {
                            let slot = *(self as *const Self as *const usize) + $idx * core::mem::size_of::<usize>();
                            (*core::mem::transmute::<_, *const $(extern $($cc)?)? fn(&Self, $($arg_ty),*) $(-> $ret_ty)?>(slot))
                            (self, $($arg_id),*)
                        }
                    }
                )*

                /// Returns the address of the virtual function by its name.
                #[inline]
                fn virt_by_name(&self, name: &'static str) -> usize {
                    unsafe {
                        match name {
                            $(
                                stringify!($fn_id) => {
                                    (*(self as *const Self as *const &'static [usize; $idx + 1]))[$idx]
                                }
                            )*
                            _ => panic!("Unknown function")
                        }
                    }
                }

                /// Returns the address of the virtual function by its index.
                #[inline]
                fn virt_by_index(&self, idx: usize) -> usize {
                    unsafe {
                        *(*(self as *const Self as *const *const usize)).add(idx)
                    }
                }

                /// Dumps the virtual function table by walking vmt pointer until reaches zero slot.
                #[inline]
                fn walk_vmt(&self, mut callback: impl core::ops::FnMut(usize, *const ())) {
                    unsafe {
                        let mut i = 0;
                        let mut slot = *(self as *const Self as *const *const usize);
                        while *slot.add(i) != 0 {
                            callback(i, *slot.add(i) as _);
                            i += 1;
                        }
                    }
                }

                /// Same as `walk_vmt` but calls `callback` only on defined methods.
                #[inline]
                fn dump_vmt(&self, mut callback: impl core::ops::FnMut(usize, &'static str, *const ())) {
                    unsafe {
                        let mut i = 0;
                        let mut slot = *(self as *const Self as *const *const usize);
                        while *slot.add(i) != 0 {
                            match i {
                                $(
                                    $idx => { callback(i, stringify!($fn_id), *slot.add(i) as _) }
                                )*
                                _ => {}
                            }

                            i += 1;
                        }
                    }
                }
            }
            $(
                $(
                    unsafe impl$(<$($tlf),*>)? $name for $target$(<$($tlf),*>)? { }
                )*
            )?
        )*
    };
}