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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
use std::marker::PhantomData;
use std::{iter, ptr};

use super::{CName, Function, IScriptable, Instr, Type, ValueContainer, OPCODE_SIZE};
use crate::raw::root::RED4ext as red;
use crate::repr::NativeRepr;
use crate::systems::RttiSystem;
use crate::{FromRepr, VoidPtr};

/// A script stack frame.
#[derive(Debug)]
#[repr(transparent)]
pub struct StackFrame(red::CStackFrame);

impl StackFrame {
    /// Returns the current function of the stack frame.
    #[inline]
    pub fn func(&self) -> &Function {
        unsafe { &*(self.0.func as *const Function) }
    }

    /// Returns the parent stack frame.
    #[inline]
    pub fn parent(&self) -> Option<&StackFrame> {
        unsafe { (self.0.parent as *const StackFrame).as_ref() }
    }

    /// Returns an iterator over all parent stack frames.
    #[inline]
    pub fn parent_iter(&self) -> impl Iterator<Item = &StackFrame> {
        iter::successors(self.parent(), |frame| frame.parent())
    }

    /// Returns the context of the stack frame, the `this` pointer.
    #[inline]
    pub fn context(&self) -> Option<&IScriptable> {
        unsafe { (self.0.context as *const IScriptable).as_ref() }
    }

    /// Returns `true` if the stack frame has a code block.
    #[inline]
    pub fn has_code(&self) -> bool {
        !self.0.code.is_null()
    }

    /// Returns the memory address where local variables are stored.
    #[inline]
    pub fn locals(&self) -> ValueContainer {
        ValueContainer::new(self.0.localVars)
    }

    /// Returns the memory address where parameters are stored.
    #[inline]
    pub fn params(&self) -> ValueContainer {
        ValueContainer::new(self.0.params)
    }

    /// Interprets the code at specified offset as an instruction of type `I`.
    pub unsafe fn instr_at<I: Instr>(&self, offset: isize) -> Option<&I> {
        if self.0.code.is_null() {
            return None;
        }
        let ptr = self.0.code.offset(offset);
        (ptr.read() as u8 == I::OPCODE).then(|| &*(ptr.offset(OPCODE_SIZE) as *const I))
    }

    /// Steps over a single opcode (1 byte).
    #[inline]
    pub unsafe fn step(&mut self) {
        self.0.code = unsafe { self.0.code.offset(OPCODE_SIZE) };
    }

    /// Retrieves the next argument from the stack frame.
    ///
    /// # Safety
    /// The type `T` must be the correct type of the next argument.
    #[inline]
    pub unsafe fn get_arg<T>(&mut self) -> T
    where
        T: FromRepr,
        T::Repr: Default,
    {
        let mut repr = T::Repr::default();
        self.read_arg(&mut repr as *mut T::Repr as VoidPtr);
        T::from_repr(repr)
    }

    unsafe fn read_arg(&mut self, ptr: VoidPtr) {
        self.0.data = ptr::null_mut();
        self.0.dataType = ptr::null_mut();
        self.0.currentParam += 1;
        unsafe {
            let opcode = *self.0.code as u8;
            self.step();
            red::OpcodeHandlers::Run(opcode, self.0.context, &mut self.0, ptr, ptr::null_mut());
        }
    }

    /// Captures the state of stack arguments.
    ///
    /// Use its returned value
    /// with [restore_args](Self::restore_args) to restore the state of arguments.
    pub fn args_state(&self) -> StackArgsState {
        StackArgsState {
            code: self.0.code,
            data: self.0.data,
            data_type: self.0.dataType,
        }
    }

    /// Allows to reset the state of function arguments.
    ///
    /// # Safety
    /// The state must be saved **before** reading arguments.
    ///
    /// The state must be restored **before** passing it back to game code.
    ///
    /// Stack arguments should **neither** be _partially_ read,
    /// **nor** _partially_ restored.
    ///
    /// # Example
    /// ```rust
    /// # use red4ext_rs::{hooks, SdkEnv, types::{CName, EntityId, StackFrame, IScriptable}, VoidPtr};
    /// # hooks! {
    /// #    static ADD_HOOK: fn(i: *mut IScriptable, f: *mut StackFrame, a3: VoidPtr, a4: VoidPtr) -> ();
    /// # }
    /// # fn attach_my_hook(env: &SdkEnv, addr: unsafe extern "C" fn(i: *mut IScriptable, f: *mut StackFrame, a3: VoidPtr, a4: VoidPtr)) {
    /// #     unsafe { env.attach_hook(ADD_HOOK, addr, detour) };
    /// # }
    /// # fn should_detour(event_name: CName) -> bool { false }
    ///
    /// unsafe extern "C" fn detour(
    ///     i: *mut IScriptable,
    ///     f: *mut StackFrame,
    ///     a3: VoidPtr,
    ///     a4: VoidPtr,
    ///     cb: unsafe extern "C" fn(i: *mut IScriptable, f: *mut StackFrame, a3: VoidPtr, a4: VoidPtr),
    /// ) {
    ///     let frame = &mut *f;
    ///
    ///     // stack must be saved before reading stack function parameters
    ///     let state = frame.args_state();
    ///
    ///     // assuming our function accepts these 3 parameters
    ///     let event_name: CName = StackFrame::get_arg(frame);
    ///     let entity_id: EntityId = StackFrame::get_arg(frame);
    ///     let emitter_name: CName = StackFrame::get_arg(frame);
    ///
    ///     if should_detour(event_name) {
    ///         // do something else...
    ///     } else {
    ///         // since we've read stack function arguments,
    ///         // stack arguments must be restored before callback.
    ///         frame.restore_args(state);
    ///         cb(i, f, a3, a4);
    ///     }
    /// }
    /// ```
    pub unsafe fn restore_args(&mut self, state: StackArgsState) {
        self.0.code = state.code;
        self.0.data = state.data;
        self.0.dataType = state.data_type;
        self.0.currentParam = 0;
    }
}

/// A stack argument to be passed to a function.
#[derive(Debug)]
#[repr(transparent)]
pub struct StackArg<'a>(red::CStackType, PhantomData<&'a mut ()>);

impl<'a> StackArg<'a> {
    /// Creates a new stack argument from a reference to a value.
    pub fn new<A: NativeRepr>(val: &'a mut A) -> Option<Self> {
        let type_ = if A::NAME == "Void" {
            ptr::null_mut()
        } else {
            let rtti = RttiSystem::get();
            rtti.get_type(CName::new(A::NAME))?.as_raw() as *const _ as *mut red::CBaseRTTIType
        };
        let inner = red::CStackType {
            type_,
            value: val as *const A as VoidPtr,
        };
        Some(Self(inner, PhantomData))
    }

    /// Returns the type of the stack argument.
    #[inline]
    pub fn type_(&self) -> Option<&Type> {
        unsafe { self.0.type_.cast::<Type>().as_ref() }
    }

    #[inline]
    pub(super) fn as_raw_mut(&mut self) -> &mut red::CStackType {
        &mut self.0
    }
}

/// Snapshot of the state of stack arguments.
pub struct StackArgsState {
    code: *mut i8,
    data: VoidPtr,
    data_type: *mut red::CBaseRTTIType,
}