commonlibsse_ng\re\b/
BSExtraData.rs

1use crate::re::CxxVirtClass;
2use crate::re::ExtraDataType::ExtraDataType;
3use crate::re::offsets_rtti::RTTI_BSExtraData;
4use crate::re::offsets_vtable::VTABLE_BSExtraData;
5use crate::rel::ResolvableAddress as _;
6use crate::rel::id::VariantID;
7use core::ffi::c_void;
8use core::marker::PhantomData;
9use core::ptr::{self, NonNull};
10
11/// Represents the base structure for extra data nodes in a singly linked list.
12#[repr(C)]
13#[derive(Debug, PartialEq)]
14pub struct BSExtraData {
15    /// Pointer to the virtual function table (vtable).
16    pub vtbl: *const BSExtraDataVtbl,
17    /// Pointer to the next `BSExtraData` node.
18    ///
19    /// This is a raw pointer to ensure FFI compatibility. `Option<NonNull<T>>`
20    /// would be safer, but raw pointers are required for interoperability.
21    /// # Note
22    /// This is assumed to be a heap-derived **unique pointer** created from `Box::into_raw`, etc.
23    ///
24    /// If this process is broken, UB happens.
25    pub next: *mut BSExtraData,
26    /// Marker indicating that the pointer to next is owned and unique.
27    marker: PhantomData<BSExtraData>,
28}
29
30const _: () = assert!(core::mem::size_of::<BSExtraData>() == 0x10);
31
32impl BSExtraData {
33    /// Address & offset of RTTI for `BSExtraData`.
34    pub const RTTI: VariantID = RTTI_BSExtraData;
35
36    /// Address & offset of Virtual function table.
37    pub const VTABLE: [VariantID; 1] = VTABLE_BSExtraData;
38
39    /// Default extra data type (None).
40    pub const EXTRA_DATA_TYPE: ExtraDataType = ExtraDataType::None;
41
42    /// Creates a new instance of `BSExtraData` with default values.
43    #[inline]
44    pub const fn new() -> Self {
45        Self { vtbl: &BS_EXTRA_DATA_VTBL, next: ptr::null_mut(), marker: PhantomData }
46    }
47
48    /// Get the extra data type by invoking the `GetType` virtual function.
49    #[inline]
50    pub fn get_type(&self) -> ExtraDataType {
51        let vtable = match Self::VTABLE[0].address() {
52            Ok(addr) => addr.cast::<BSExtraDataVtbl>(),
53            Err(_err) => {
54                #[cfg(feature = "tracing")]
55                tracing::error!("Failed to get address of vtable: {_err}");
56                return (BS_EXTRA_DATA_VTBL.GetType)(self);
57            }
58        };
59        unsafe { (vtable.as_ref().GetType)(self) }
60    }
61
62    /// Compares two `BSExtraData` instances for inequality.
63    #[inline]
64    pub fn is_not_equal(&self, rhs: &Self) -> bool {
65        let vtable = match Self::VTABLE[0].address() {
66            Ok(addr) => addr.cast::<BSExtraDataVtbl>(),
67            Err(_err) => {
68                #[cfg(feature = "tracing")]
69                tracing::error!("Failed to get address of vtable: {_err}");
70                return false;
71            }
72        };
73        unsafe { (vtable.as_ref().IsNotEqual)(self, rhs) }
74    }
75
76    /// Creates a new instance of `T` that implements `CxxVirtClass`.
77    ///
78    /// Returns an `Option<NonNull<T>>` containing a pointer to the allocated instance, or `None` on failure.
79    pub fn create<T: CxxVirtClass>() -> Option<NonNull<T>> {
80        let t = &T::vtable()[0];
81        Self::create_with(core::mem::size_of::<T>(), t.address().ok()?).map(|void| void.cast())
82    }
83
84    /// Allocates and initializes a new `BSExtraData` instance with a given size and vtable.
85    ///
86    /// # Returns
87    /// The pointer to the allocated instance.
88    #[inline]
89    pub fn create_with(size: usize, vtbl: NonNull<c_void>) -> Option<NonNull<c_void>> {
90        use core::alloc::Layout;
91        use core::mem::align_of;
92        use std::alloc::alloc_zeroed;
93
94        unsafe {
95            let memory = {
96                let layout = Layout::from_size_align(size, align_of::<Self>()).ok()?;
97                NonNull::new(alloc_zeroed(layout))?
98            };
99
100            let vtable_ptr = memory.cast();
101            vtable_ptr.write(vtbl);
102
103            Some(memory.cast())
104        }
105    }
106}
107
108#[inline]
109pub fn downcast_as<T>(extra_data: *mut BSExtraData) -> Option<NonNull<T>> {
110    if extra_data.is_null() || !extra_data.is_aligned() {
111        return None;
112    }
113
114    if crate::rex::win32::is_valid_range(extra_data.cast(), core::mem::size_of::<T>()) {
115        return Some(unsafe { NonNull::new_unchecked(extra_data.cast::<T>()) });
116    };
117    None
118}
119
120/// Trait indicating whether or not `BSExtraData` is inherited in the C++ sense.
121///
122/// Used for downcast availability and linked list traversal.
123pub trait DerivedBSExtraData {
124    /// Type used for downcast-ing availability and linked list search.
125    fn get_extra_data(&self) -> &BSExtraData;
126    /// Function for testing whether `BSExtraData` is really inherited. It will not be called in practice.
127    fn get_extra_data_type() -> ExtraDataType;
128}
129
130impl DerivedBSExtraData for BSExtraData {
131    #[inline]
132    fn get_extra_data(&self) -> &BSExtraData {
133        self
134    }
135
136    #[inline]
137    fn get_extra_data_type() -> ExtraDataType {
138        Self::EXTRA_DATA_TYPE
139    }
140}
141
142impl CxxVirtClass for BSExtraData {
143    #[inline]
144    fn rtti() -> &'static VariantID {
145        &Self::RTTI
146    }
147
148    #[inline]
149    fn vtable() -> &'static [VariantID] {
150        &Self::VTABLE
151    }
152}
153
154impl Default for BSExtraData {
155    #[inline]
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161/// Default implemented vtable.
162static BS_EXTRA_DATA_VTBL: BSExtraDataVtbl = BSExtraDataVtbl::new();
163
164/// Virtual function table for `BSExtraData`.
165#[repr(C)]
166pub struct BSExtraDataVtbl {
167    /// Destructor (`~BSExtraData` in C++).
168    pub CxxDrop: fn(this: &mut BSExtraData),
169
170    /// Gets the type of extra data.
171    pub GetType: fn(this: &BSExtraData) -> ExtraDataType,
172
173    /// Checks inequality between two `BSExtraData` instances.
174    pub IsNotEqual: fn(this: &BSExtraData, rhs: &BSExtraData) -> bool,
175}
176
177impl BSExtraDataVtbl {
178    /// Creates a new default virtual function table with no-op functions.
179    #[inline]
180    const fn new() -> Self {
181        const fn CxxDrop(_this: &mut BSExtraData) {}
182
183        const fn GetType(_this: &BSExtraData) -> ExtraDataType {
184            BSExtraData::EXTRA_DATA_TYPE
185        }
186
187        const fn IsNotEqual(_this: &BSExtraData, _rhs: &BSExtraData) -> bool {
188            false
189        }
190
191        Self { CxxDrop, GetType, IsNotEqual }
192    }
193}
194
195/// Iterator over `BSExtraData` nodes.
196pub struct BSExtraDataIter<'a> {
197    /// Pointer to the current node.
198    pub(crate) current: *mut BSExtraData,
199
200    /// Pointer to the previous node.
201    pub(crate) prev: *mut BSExtraData,
202
203    /// Lifetime marker for Rust's borrow checker.
204    marker: PhantomData<&'a BSExtraData>,
205}
206
207impl BSExtraDataIter<'_> {
208    /// Creates a new iterator starting at `start`.
209    #[inline]
210    pub const fn new(start: *mut BSExtraData) -> Self {
211        Self { current: start, prev: std::ptr::null_mut(), marker: PhantomData }
212    }
213
214    /// Removes the current node and concatenates the previous and next nodes.
215    ///
216    /// # Returns
217    /// Pointer to the removed node.
218    pub fn remove_current(&mut self) -> Option<*mut BSExtraData> {
219        if self.current.is_null() {
220            return None;
221        }
222
223        let removed = self.current;
224        unsafe {
225            let next = (*removed).next;
226
227            if !self.prev.is_null() {
228                (*self.prev).next = next;
229            }
230
231            self.current = next;
232        }
233
234        Some(removed)
235    }
236}
237
238impl Iterator for BSExtraDataIter<'_> {
239    type Item = *mut BSExtraData;
240
241    fn next(&mut self) -> Option<Self::Item> {
242        if self.current.is_null() {
243            return None;
244        }
245
246        let current = self.current;
247        unsafe {
248            self.prev = current;
249            self.current = (*current).next;
250        }
251        Some(current)
252    }
253}
254
255/// Mutable iterator over `BSExtraData` nodes.
256pub struct BSExtraDataIterMut<'a> {
257    cur: *mut BSExtraData,
258    prev: *mut BSExtraData,
259    marker: PhantomData<&'a BSExtraData>,
260}
261
262impl BSExtraDataIterMut<'_> {
263    /// Creates a new iterator starting at `start`.
264    #[inline]
265    pub const fn new(start: *mut BSExtraData) -> Self {
266        Self { cur: start, prev: std::ptr::null_mut(), marker: PhantomData }
267    }
268
269    /// Removes the current node and concatenates the previous and next nodes.
270    pub fn remove_current(&mut self) {
271        if self.cur.is_null() {
272            return;
273        }
274
275        let next = (unsafe { &*self.cur }).next;
276
277        if !self.prev.is_null() {
278            (unsafe { &mut *self.prev }).next = next;
279        }
280
281        let to_delete = self.cur;
282        self.cur = next;
283
284        // Drop the node, assuming it was heap-allocated.
285        drop(unsafe { Box::from_raw(to_delete) });
286    }
287}
288
289impl Iterator for BSExtraDataIterMut<'_> {
290    type Item = *mut BSExtraData;
291
292    fn next(&mut self) -> Option<Self::Item> {
293        if self.cur.is_null() {
294            return None;
295        }
296
297        let current = self.cur;
298        unsafe {
299            self.prev = current;
300            self.cur = (*current).next;
301        }
302        Some(current)
303    }
304}
305
306// #[cfg(test)]
307// mod tests {
308//     use super::*;
309
310//     #[test]
311//     fn test_bs_extra_data_creation() {
312//         let data = BSExtraData::new();
313//         assert_eq!(data.get_type(), ExtraDataType::None);
314//     }
315// }