commonlibsse_ng\rel/
relocation.rs

1// C++ Original code
2// - ref: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/Relocation.h
3// - ref(`safe_write`, `safe_fill`): https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/Relocation.cpp
4//
5// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
6// SPDX-License-Identifier: MIT
7mod phantom_member;
8
9pub use phantom_member::PhantomMember;
10
11use crate::rel::ResolvableAddress;
12use crate::rel::id::{DataBaseError, ID, RelocationID};
13use crate::rel::module::{ModuleState, ModuleStateError};
14use crate::rel::offset::{Offset, VariantOffset};
15use crate::rel::version::Version;
16use core::ffi::c_void;
17use core::marker::PhantomData;
18use core::mem;
19use core::num::NonZeroUsize;
20use core::ptr::NonNull;
21
22pub const NOP: u8 = 0x90;
23pub const NOP2: [u8; 2] = [0x66, 0x90];
24pub const NOP3: [u8; 3] = [0x0F, 0x1F, 0x00];
25pub const NOP4: [u8; 4] = [0x0F, 0x1F, 0x40, 0x00];
26pub const NOP5: [u8; 5] = [0x0F, 0x1F, 0x44, 0x00, 0x00];
27pub const NOP6: [u8; 6] = [0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00];
28pub const NOP7: [u8; 7] = [0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00];
29pub const NOP8: [u8; 8] = [0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
30pub const NOP9: [u8; 9] = [0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
31pub const JMP8: u8 = 0xEB;
32pub const JMP32: u8 = 0xE9;
33pub const RET: u8 = 0xC3;
34pub const INT3: u8 = 0xCC;
35
36#[inline]
37unsafe fn enable_write_permission(
38    addr: *const c_void,
39    len: usize,
40) -> windows::core::Result<windows::Win32::System::Memory::PAGE_PROTECTION_FLAGS> {
41    unsafe {
42        use windows::Win32::System::Memory::{
43            PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, VirtualProtect,
44        };
45        let mut old_protection = PAGE_PROTECTION_FLAGS(0);
46
47        // VirtualProtect: https://learn.microsoft.com/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
48        VirtualProtect(addr, len, PAGE_EXECUTE_READWRITE, &mut old_protection)?;
49        Ok(old_protection)
50    }
51}
52
53#[inline]
54unsafe fn restore_memory_protection(
55    addr: *const c_void,
56    len: usize,
57    old_protection: windows::Win32::System::Memory::PAGE_PROTECTION_FLAGS,
58) -> windows::core::Result<()> {
59    unsafe {
60        use windows::Win32::System::Memory::{PAGE_PROTECTION_FLAGS, VirtualProtect};
61        let mut temp = PAGE_PROTECTION_FLAGS(0);
62
63        // VirtualProtect: https://learn.microsoft.com/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
64        VirtualProtect(addr, len, old_protection, &mut temp)
65    }
66}
67
68#[inline]
69unsafe fn safe_write<T>(dst: *mut T, src: *const T, len: usize) -> windows::core::Result<()> {
70    unsafe {
71        let old_protection = enable_write_permission(dst as _, len)?;
72        core::ptr::copy_nonoverlapping(src, dst, len);
73        restore_memory_protection(dst.cast(), len, old_protection)
74    }
75}
76
77#[inline]
78pub(crate) unsafe fn safe_write_value<T>(dst: *mut T, src: &T) -> windows::core::Result<()> {
79    unsafe { safe_write(dst, src, core::mem::size_of::<T>()) }
80}
81
82#[inline]
83unsafe fn safe_fill(dst: *mut c_void, value: u8, len: usize) -> windows::core::Result<()> {
84    unsafe {
85        let old_protection = enable_write_permission(dst, len)?;
86        core::ptr::write_bytes(dst, value, len);
87        restore_memory_protection(dst, len, old_protection)
88    }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct Relocation {
93    _impl: NonNull<c_void>,
94    // owned
95    _cast_target: PhantomData<c_void>,
96}
97
98impl Relocation {
99    #[inline]
100    pub const fn new(address: NonNull<c_void>) -> Self {
101        Self { _impl: address, _cast_target: PhantomData }
102    }
103
104    /// Creates an instance from two resolvable addresses.
105    ///
106    /// # Errors
107    /// Returns an error if either of the addresses cannot be resolved.
108    #[inline]
109    pub fn from_id_offset<A1, A2>(id: A1, offset: A2) -> Result<Self, DataBaseError>
110    where
111        A1: ResolvableAddress,
112        A2: ResolvableAddress,
113    {
114        Ok(Self {
115            _impl: unsafe { id.address()?.byte_add(offset.offset()?.get()) },
116            _cast_target: PhantomData,
117        })
118    }
119
120    /// Cast to any type.
121    ///
122    /// Equivalent to C++'s `REL::Relocation::get`.
123    #[inline]
124    pub const fn cast<U>(&self) -> NonNull<U> {
125        self._impl.cast()
126    }
127
128    #[inline]
129    pub fn write<U>(&self, data: &U) {
130        let _ = unsafe { safe_write_value(self._impl.cast::<U>().as_mut(), data) };
131    }
132
133    #[inline]
134    pub fn write_bytes(&self, data: &[u8]) {
135        let _ = unsafe { safe_write(self._impl.cast::<u8>().as_mut(), data.as_ptr(), data.len()) };
136    }
137
138    #[inline]
139    pub fn swap_as_vfn<T>(&mut self, idx: usize, new_fn: *const ()) -> NonNull<c_void> {
140        const PTR_SIZE: usize = mem::size_of::<usize>();
141
142        let mut old_fn = unsafe { self._impl.byte_add(PTR_SIZE * idx) };
143        let _ = unsafe { safe_write(old_fn.as_mut(), new_fn.cast(), PTR_SIZE) };
144        old_fn
145    }
146
147    #[inline]
148    pub fn write_fill(&mut self, value: u8, count: usize) {
149        unsafe {
150            let _ = safe_fill(self._impl.as_mut(), value, count);
151        }
152    }
153}
154
155impl ResolvableAddress for Relocation {
156    /// Get the address.(No error returned)
157    #[inline]
158    fn address(&self) -> Result<NonNull<c_void>, DataBaseError> {
159        Ok(self._impl)
160    }
161
162    #[inline]
163    fn offset(&self) -> Result<NonZeroUsize, DataBaseError> {
164        let offset = unsafe { self._impl.byte_offset_from(Self::base()?) as usize };
165        NonZeroUsize::new(offset).ok_or(DataBaseError::SpecifiedZeroOffset)
166    }
167}
168
169/// # Errors
170#[inline]
171pub fn relocate<T>(se_and_vr: T, ae: T) -> Result<T, ModuleStateError> {
172    let is_ae = ModuleState::map_or_init(|module| module.runtime.is_ae())?;
173    Ok(if is_ae { ae } else { se_and_vr })
174}
175
176impl TryFrom<Offset> for Relocation {
177    type Error = DataBaseError;
178
179    #[inline]
180    fn try_from(offset: Offset) -> Result<Self, Self::Error> {
181        Ok(Self { _impl: offset.address()?, _cast_target: PhantomData })
182    }
183}
184
185impl TryFrom<VariantOffset> for Relocation {
186    type Error = DataBaseError;
187
188    #[inline]
189    fn try_from(offset: VariantOffset) -> Result<Self, Self::Error> {
190        Ok(Self { _impl: offset.address()?, _cast_target: PhantomData })
191    }
192}
193
194impl TryFrom<ID> for Relocation {
195    type Error = DataBaseError;
196
197    #[inline]
198    fn try_from(id: ID) -> Result<Self, Self::Error> {
199        Ok(Self { _impl: id.address()?, _cast_target: PhantomData })
200    }
201}
202
203impl TryFrom<RelocationID> for Relocation {
204    type Error = DataBaseError;
205
206    #[inline]
207    fn try_from(id: RelocationID) -> Result<Self, Self::Error> {
208        Ok(Self { _impl: id.address()?, _cast_target: PhantomData })
209    }
210}
211
212/// # Safety
213/// # Errors
214#[inline]
215pub unsafe fn relocate_virtual<F>(
216    se_ae_vtable_offset: isize,
217    vr_vtable_offset: isize,
218    se_ae_vtable_index: isize,
219    vr_vtable_index: isize,
220    this: NonNull<u8>,
221) -> Result<F, ModuleStateError>
222where
223    F: Copy,
224{
225    let is_vr = ModuleState::map_active(|module| module.runtime.is_vr())?;
226
227    unsafe {
228        let vtable_ptr = *(this.as_ptr() as *const *const F).offset(if is_vr {
229            vr_vtable_offset
230        } else {
231            se_ae_vtable_offset
232        });
233        let func_ptr = *vtable_ptr.offset(if is_vr { vr_vtable_index } else { se_ae_vtable_index });
234
235        Ok(func_ptr)
236    }
237}
238
239/// Relocates a member based on the runtime state, returning a pointer to the new location.
240///
241/// # Safety
242/// This function requires that the caller ensure the provided pointer `this` is valid, meaning it should point to a valid memory location.
243/// The `se_ae_offset` and `vr_offset` must be safe offsets for the given pointer type.
244///
245/// # Errors
246/// This function may return an error if the module's runtime is not available or if any error occurs while fetching the runtime state.
247/// Specifically, it calls `ModuleState::map_active`, which could result in an error.
248#[inline]
249pub fn relocate_member<THIS, T>(
250    this: &THIS,
251    se_ae_offset: isize,
252    vr_offset: isize,
253) -> Result<&T, RelocationError> {
254    let member_ptr = {
255        let is_vr = ModuleState::map_active(|module| module.runtime.is_vr())?;
256        let this = this as *const THIS;
257        let offset = if is_vr { vr_offset } else { se_ae_offset };
258        this.wrapping_offset(offset).cast::<T>()
259    };
260
261    Ok(unsafe { raw_pointer_as_ref(member_ptr) }?)
262}
263
264/// Relocates a member based on the runtime state, returning a pointer to the new location.
265///
266/// # Safety
267/// This function requires that the caller ensure the provided pointer `this` is valid, meaning it should point to a valid memory location.
268/// The `se_ae_offset` and `vr_offset` must be safe offsets for the given pointer type.
269///
270/// # Errors
271/// This function may return an error if the module's runtime is not available or if any error occurs while fetching the runtime state.
272/// Specifically, it calls `ModuleState::map_active`, which could result in an error.
273pub fn relocate_member_mut<THIS, T>(
274    this: &mut THIS,
275    se_ae_offset: isize,
276    vr_offset: isize,
277) -> Result<&mut T, RelocationError> {
278    let member_ptr = {
279        let is_vr = ModuleState::map_active(|module| module.runtime.is_vr())?;
280        let this = this as *mut THIS;
281        let offset = if is_vr { vr_offset } else { se_ae_offset };
282        this.wrapping_offset(offset).cast::<T>()
283    };
284
285    Ok(unsafe { raw_pointer_as_mut(member_ptr) }?)
286}
287
288/// Relocates a member based on a condition, using either offset `a` or `b` depending on the condition.
289///
290/// # Safety
291/// It is safe as long as the following are observed.
292/// - ptr of added offset never exceeds `isize::MAX`
293/// - All memory from this to offset is valid.
294pub const unsafe fn relocate_member_if<T>(
295    condition: bool,
296    this: *mut u8,
297    a: isize,
298    b: isize,
299) -> *mut T {
300    unsafe { this.offset(if condition { a } else { b }).cast::<T>() }
301}
302
303/// Relocates a member based on the version comparison, using either `older` or `newer` offset depending on the current version.
304///
305/// # Safety
306/// It is safe as long as the following are observed.
307/// - ptr of offset added is valid
308/// - ptr of added offset never exceeds `isize::MAX`
309/// - All memory from this to offset is valid.
310///
311/// # Errors
312/// - This function may return an error if the module's state cannot be accessed, or if the `map_active` call fails when fetching the current version.
313/// - If the pointer is null
314/// - If the pointer is unaligned
315#[inline]
316pub unsafe fn relocate_member_if_newer<THIS, T>(
317    version: Version,
318    this: &THIS,
319    older: isize,
320    newer: isize,
321) -> Result<&T, RelocationError> {
322    let is_old = ModuleState::map_active(|module| module.version < version)?;
323    let this = this as *const THIS;
324    let offset = if is_old { older } else { newer };
325    let member_ptr = unsafe { this.offset(offset).cast::<T>() };
326    Ok(unsafe { raw_pointer_as_ref(member_ptr) }?)
327}
328
329/// Relocates a member based on the version comparison, using either `older` or `newer` offset depending on the current version.
330///
331/// # Safety
332/// It is safe as long as the following are observed.
333/// - ptr of offset added is valid
334/// - ptr of added offset never exceeds `isize::MAX`
335/// - All memory from this to offset is valid.
336///
337/// # Errors
338/// This function may return an error if the module's state cannot be accessed, or if the `map_active` call fails when fetching the current version.
339pub unsafe fn relocate_member_if_newer_mut<THIS, T>(
340    version: Version,
341    this: &mut THIS,
342    older: isize,
343    newer: isize,
344) -> Result<&mut T, RelocationError> {
345    let is_old = ModuleState::map_active(|module| module.version < version)?;
346    let this = this as *mut THIS;
347    let offset = if is_old { older } else { newer };
348    let member_ptr = unsafe { this.offset(offset).cast::<T>() };
349    Ok(unsafe { raw_pointer_as_mut(member_ptr) }?)
350}
351
352/// Converts a raw pointer to an immutable reference.
353///
354/// # Safety
355/// - The caller must guarantee that the pointer is valid.
356/// - The lifetime of the reference must not outlive the pointer's validity.
357///
358/// # Errors
359/// - If the pointer is null
360/// - If the pointer is unaligned
361#[inline]
362pub unsafe fn raw_pointer_as_ref<'a, T>(ptr: *const T) -> Result<&'a T, RawPointerError> {
363    if ptr.is_null() {
364        return Err(RawPointerError::NullPointer);
365    }
366
367    if !ptr.is_aligned() {
368        return Err(RawPointerError::MisalignedPointer {
369            addr: ptr.addr(),
370            expected_align: core::mem::align_of::<T>(),
371            misaligned_type: core::any::type_name::<T>(),
372        });
373    };
374
375    Ok(unsafe { &*ptr })
376}
377
378/// Converts a raw pointer to a mutable reference.
379///
380/// # Safety
381/// - The caller must guarantee that the pointer is valid.
382/// - The lifetime of the reference must not outlive the pointer's validity.
383///
384/// # Errors
385/// - If the pointer is null
386/// - If the pointer is unaligned
387#[inline]
388pub unsafe fn raw_pointer_as_mut<'a, T>(ptr: *mut T) -> Result<&'a mut T, RawPointerError> {
389    if ptr.is_null() {
390        return Err(RawPointerError::NullPointer);
391    }
392
393    if !ptr.is_aligned() {
394        return Err(RawPointerError::MisalignedPointer {
395            addr: ptr.addr(),
396            expected_align: core::mem::align_of::<T>(),
397            misaligned_type: core::any::type_name::<T>(),
398        });
399    }
400
401    Ok(unsafe { &mut *ptr })
402}
403
404/// Represents errors that may occur during member relocation.
405#[derive(Debug, snafu::Snafu)]
406pub enum RelocationError {
407    /// Error indicating issues with raw pointer conversion.
408    #[snafu(transparent)]
409    PointerConversion { source: RawPointerError },
410
411    /// Error indicating that the module state could not be accessed.
412    #[snafu(transparent)]
413    ModuleState { source: ModuleStateError },
414}
415
416/// Represents errors that may occur when converting raw pointers to references.
417#[derive(Debug, snafu::Snafu)]
418pub enum RawPointerError {
419    /// Null pointer encountered during conversion.
420    NullPointer,
421
422    /// Misaligned pointer.
423    #[snafu(display(
424        "This pointer of `{misaligned_type}` was expected to have an alignment of {expected_align}, but the actual address was {addr:X}."
425    ))]
426    MisalignedPointer { addr: usize, expected_align: usize, misaligned_type: &'static str },
427}