commonlibsse_ng\re/rtti.rs
1//! # RTTI Structure Overview
2//!
3//! ASCII art representing the RTTI (Run-Time Type Information) structure in C++.
4//!
5//! - C++ src: https://godbolt.org/z/xn5f96E9s
6//! - AA ref: https://www.openrce.org/articles/full_view/23
7//!
8//! ```text
9//! +------------------+
10//! +-------------+ | A::RTTI Complete |
11//! +---------+ | A::vftable | | Object Locator |
12//! | class A | |-------------| |------------------|
13//! |---------| | &A_meta |->| signature (0) | +-------------------------+ +-------------------------------+
14//! | _vfptr |->| &A::A_virt1 | | offset (0) | | A::RTTI Type Descriptor | | A::RTTI Base Class Descriptor |
15//! | a1 | | &A::A_virt2 | | cdOffset (0) | |-------------------------| |-------------------------------|
16//! +---------+ +-------------+ | pTypeDescriptor |-------->| pVFTable |-->| pTypeDescriptor |<-------+
17//! | pClassDescriptor |---+ | spare | | numContainedBases (0) | |
18//! +------------------+ | | name (?AVA@@) | | PMD where (0, -1, 0) | |
19//! | +-------------------------+ | attributes (0) | |
20//! | +-------------------------------+ |
21//! | +------------------------------------+ |
22//! | | A::RTTI Class Hierarchy Descriptor | |
23//! | |------------------------------------| |
24//! +---->| signature (0) | +--------------------------+ |
25//! | attributes (0) | | A::RTTI Base Class Array | |
26//! | numBaseClasses (1) | |--------------------------| |
27//! | pBaseClassArray |-->| &A_BCD |--+
28//! +------------------------------------+ +--------------------------+
29//!
30//! +------------------+
31//! +-------------+ | B::RTTI Complete |
32//! +---------+ | B::vftable | | Object Locator |
33//! | class B | |-------------| |------------------|
34//! |---------| | &B_meta |->| signature (0) | +-------------------------+ +-------------------------------+
35//! | _vfptr |->| &B::B_virt1 | | offset (0) | | B::RTTI Type Descriptor | | B::RTTI Base Class Descriptor |
36//! | b1 | | &B::B_virt2 | | cdOffset (0) | |-------------------------| |-------------------------------|
37//! | b2 | +-------------+ | pTypeDescriptor |-------->| pVFTable |-->| pTypeDescriptor |<-------+
38//! +---------+ | pClassDescriptor |---+ | spare | | numContainedBases (0) | |
39//! +------------------+ | | name (?AVA@@) | | PMD where (0, -1, 0) | |
40//! | +-------------------------+ | attributes (0) | |
41//! | +-------------------------------+ |
42//! | +------------------------------------+ |
43//! | | B::RTTI Class Hierarchy Descriptor | |
44//! | |------------------------------------| |
45//! +---->| signature (0) | +--------------------------+ |
46//! | attributes (0) | | B::RTTI Base Class Array | |
47//! | numBaseClasses (1) | |--------------------------| |
48//! | pBaseClassArray |-->| &B_BCD |--+
49//! +------------------------------------+ +--------------------------+
50//!
51//!
52//! +----------------------+
53//! +------------------+ | C::RTTI Complete |
54//! +----------+ | C::vftable for A | | Object Locator for A |
55//! | class C | |------------------| |----------------------|
56//! |----------| | &C_meta_A |--->| signature (0) | +-------------------------+ +-------------------------------+
57//! | _vfptr_A |--> | &C::A_virt1 | | offset (0) | | C::RTTI Type Descriptor | | C::RTTI Base Class Descriptor |
58//! | a1 | | &C::A_virt2 | | cdOffset (0) | |-------------------------| |-------------------------------|
59//! | _vfptr_B |-+ +------------------+ | pTypeDescriptor |----->| pVFTable |-->| pTypeDescriptor |
60//! | b1 | | | pClassDescriptor |--+ | spare | | numContainedBases (2) |
61//! | b2 | | +----------------------+ | | name (?AVC@@) | | PMD where (8, -1, 0) |
62//! | c1 | | +----------------------+ | +-------------------------+ | attributes (0) |
63//! +----------+ | +------------------+ | C::RTTI Complete | | +-------------------------------+
64//! | | C::vftable for B | | Object Locator for B | |
65//! | |------------------| |----------------------| |
66//! +-> | &C_meta_A |-->| signature (0) | |
67//! | &C::A_virt1 | | offset (8) | | +----------------------+
68//! | &C::A_virt2 | | cdOffset (0) | | | C::RTTI Class |
69//! +------------------+ | pTypeDescriptor | | | Hierarchy Descriptor |
70//! | pClassDescriptor | | |----------------------|
71//! +----------------------+ +->| signature (0) | +------------------------------------+
72//! | attributes (1) | | C::RTTI Base Class Array |
73//! | numBaseClasses (3) | |------------------------------------|
74//! | pBaseClassArray |->| &C_BCD -> C::RTTI Base Class Desc. |
75//! +----------------------+ | &A_BCD -> A::RTTI Base Class Desc. |
76//! | &B_BCD -> B::RTTI Base Class Desc. |
77//! +------------------------------------+
78//! ```
79
80use crate::re::CxxVirtClass;
81use crate::rel::ResolvableAddress as _;
82use crate::rel::id::DataBaseError;
83use core::ffi::c_void;
84use core::marker::PhantomData;
85use core::ptr::NonNull;
86
87pub mod msvc {
88 use core::ffi::c_char;
89
90 #[repr(C)]
91 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92 pub struct TypeInfo {
93 // This field is hidden ptr to virtual function table.
94 __vtbl: &'static TypeInfoVTable, // 0
95
96 _data: *mut std::ffi::c_void, // 08
97 _name: [c_char; 1], // 10
98 }
99
100 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101 struct TypeInfoVTable {
102 /// C++ Destructor
103 delete: fn(), // 00
104 }
105
106 const _: () = {
107 assert!(0x08 == core::mem::offset_of!(TypeInfo, _data));
108 assert!(0x10 == core::mem::offset_of!(TypeInfo, _name));
109 assert!(core::mem::size_of::<TypeInfo>() == 0x18);
110 };
111
112 impl TypeInfo {
113 /// Get mangled name of this class.
114 pub const fn mangled_name(&self) -> *const c_char {
115 self._name.as_ptr()
116 }
117 }
118}
119
120#[repr(C)]
121#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
122pub struct RVA<T> {
123 _rva: u32, // 00
124 _maker: PhantomData<T>,
125}
126
127const _: () = {
128 assert!(core::mem::size_of::<RVA<*const c_void>>() == 0x4);
129};
130
131impl<T> RVA<T> {
132 pub const fn new(rva: u32) -> Self {
133 Self { _rva: rva, _maker: PhantomData::<T> }
134 }
135
136 pub fn get(&self) -> Option<NonNull<T>> {
137 use crate::rel::offset::Offset;
138
139 if self.is_good() {
140 Some(Offset::new(self._rva as usize).address().ok()?.cast())
141 } else {
142 None
143 }
144 }
145
146 pub const fn offset(&self) -> u32 {
147 self._rva
148 }
149
150 const fn is_good(&self) -> bool {
151 self._rva != 0
152 }
153}
154
155#[repr(C)]
156#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
157pub struct PMD {
158 m_disp: i32, // 0
159 p_disp: i32, // 4
160 v_disp: i32, // 8
161}
162
163const _: () = {
164 assert!(0x00 == core::mem::offset_of!(PMD, m_disp));
165 assert!(0x04 == core::mem::offset_of!(PMD, p_disp));
166 assert!(0x08 == core::mem::offset_of!(PMD, v_disp));
167
168 assert!(core::mem::size_of::<PMD>() == 0xc);
169};
170
171bitflags::bitflags! {
172 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
173 struct BaseClassDescriptorAttribute: u32 {
174 const NONE = 0;
175 const NOT_VISIBLE = 1 << 0;
176 const Ambiguous = 1 << 1;
177 const PRIVATE = 1 << 2;
178 const PRIVATE_OR_PROTECTED_BASE = 1 << 3;
179 const VIRTUAL = 1 << 4;
180 const NON_POLYMORPHIC = 1 << 5;
181 const HAS_HIERARCHY_DESCRIPTOR = 1 << 6;
182 }
183}
184
185#[repr(C)]
186#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187pub struct BaseClassDescriptor {
188 type_descriptor: RVA<msvc::TypeInfo>, // 00
189 num_contained_bases: u32, // 04
190 pmd: PMD, // 08
191 attributes: BaseClassDescriptorAttribute, // 14
192}
193
194const _: () = {
195 assert!(0x00 == core::mem::offset_of!(BaseClassDescriptor, type_descriptor));
196 assert!(0x04 == core::mem::offset_of!(BaseClassDescriptor, num_contained_bases));
197 assert!(0x08 == core::mem::offset_of!(BaseClassDescriptor, pmd));
198 assert!(0x14 == core::mem::offset_of!(BaseClassDescriptor, attributes));
199
200 assert!(core::mem::size_of::<BaseClassDescriptor>() == 0x18);
201};
202
203bitflags::bitflags! {
204 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
205 struct ClassHierarchyDescriptorAttribute: u32 {
206 const NO_INHERITANCE = 0;
207 const MULTIPLE_INHERITANCE = 1 << 0;
208 const VIRTUAL_INHERITANCE = 1 << 1;
209 const AMBIGUOUS_INHERITANCE = 1 << 2;
210 }
211}
212
213#[repr(C)]
214#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215pub struct ClassHierarchyDescriptor {
216 signature: u32, // 00
217 attributes: ClassHierarchyDescriptorAttribute, // 04
218 num_base_classes: u32, // 08
219 base_class_array: RVA<BaseClassDescriptor>, // 0C
220}
221
222const _: () = {
223 assert!(0x00 == core::mem::offset_of!(ClassHierarchyDescriptor, signature));
224 assert!(0x04 == core::mem::offset_of!(ClassHierarchyDescriptor, attributes));
225 assert!(0x08 == core::mem::offset_of!(ClassHierarchyDescriptor, num_base_classes));
226 assert!(0x0C == core::mem::offset_of!(ClassHierarchyDescriptor, base_class_array));
227
228 assert!(core::mem::size_of::<ClassHierarchyDescriptor>() == 0x10);
229};
230
231#[repr(C)]
232#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
233pub struct CompleteObjectLocator {
234 signature: Signature, // 00
235 offset: u32, // 04
236 ctor_disp_offset: u32, // 08
237 type_descriptor: RVA<msvc::TypeInfo>, // 0C
238 class_descriptor: RVA<ClassHierarchyDescriptor>, // 10
239}
240
241#[repr(u32)]
242#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
243pub enum Signature {
244 X86 = 0,
245 X64 = 1,
246}
247
248const _: () = {
249 assert!(0x00 == core::mem::offset_of!(CompleteObjectLocator, signature));
250 assert!(0x04 == core::mem::offset_of!(CompleteObjectLocator, offset));
251 assert!(0x08 == core::mem::offset_of!(CompleteObjectLocator, ctor_disp_offset));
252 assert!(0x0C == core::mem::offset_of!(CompleteObjectLocator, type_descriptor));
253 assert!(0x10 == core::mem::offset_of!(CompleteObjectLocator, class_descriptor));
254
255 assert!(core::mem::size_of::<CompleteObjectLocator>() == 0x14);
256};
257
258/// RTTI Dynamic Cast function
259///
260/// # Safety
261/// # Errors
262#[commonlibsse_ng_derive_internal::relocate_fn(se_id = 102238, ae_id = 109689, vr_id = 102238)]
263pub unsafe fn rt_dynamic_cast(
264 in_ptr: *mut c_void,
265 vf_delta: i32,
266 src_type: *mut c_void,
267 target_type: *mut c_void,
268 is_reference: i32,
269) -> *mut c_void {
270}
271
272// TODO: Write tests
273// FIXME: remove unwrap
274/// # Safety
275/// # Errors
276/// # Panics
277pub unsafe fn skyrim_cast<To, From>(from: *mut From) -> Result<*mut To, DataBaseError>
278where
279 From: 'static + CxxVirtClass,
280 To: 'static + CxxVirtClass,
281{
282 use crate::rel::relocation::Relocation;
283 let from_rtti = Relocation::new(From::rtti().address()?).cast::<c_void>();
284 let to_rtti = Relocation::new(To::rtti().address()?).cast::<c_void>();
285
286 Ok(unsafe {
287 rt_dynamic_cast(from.cast::<c_void>(), 0, from_rtti.as_ptr(), to_rtti.as_ptr(), 0)
288 }
289 .cast())
290}