fromsoftware_shared/
rtti.rs

1use std::ptr::NonNull;
2
3use pelite::pe64::{Pe, Rva, Va, msvc::RTTICompleteObjectLocator};
4use undname::Flags;
5
6use crate::program::Program;
7
8// TODO: this cast to u32 is probably not going to cause panics but can be prettier.
9const VA_SIZE: u32 = size_of::<Va>() as u32;
10
11/// Builds an iterator that walks over the entire .rdata section looking for
12/// recoverable classes.
13pub fn find_rtti_classes<'a, T: Pe<'a>>(program: &'a T) -> impl Iterator<Item = Class<'a, T>> + 'a {
14    let text = program
15        .section_headers()
16        .by_name(".text")
17        .expect("no .text section found");
18
19    let rdata = program
20        .section_headers()
21        .by_name(".rdata")
22        .expect("no .rdata section found");
23
24    rdata
25        .virtual_range()
26        .step_by(size_of::<Va>())
27        .filter_map(move |candidate_rva| {
28            let vftable_meta_rva = candidate_rva;
29            let vftable_rva = candidate_rva + VA_SIZE;
30
31            let vftable_meta_rva = program
32                .derva(vftable_meta_rva)
33                .and_then(|va| program.va_to_rva(*va))
34                .ok()?;
35
36            let vftable_entry_rva = program
37                .derva(vftable_rva)
38                .and_then(|va| program.va_to_rva(*va))
39                .ok()?;
40
41            if rdata.virtual_range().contains(&vftable_meta_rva)
42                && text.virtual_range().contains(&vftable_entry_rva)
43            {
44                let _: &RTTICompleteObjectLocator = program.derva(vftable_meta_rva).ok()?;
45
46                Some((vftable_meta_rva, vftable_rva))
47            } else {
48                None
49            }
50        })
51        .filter_map(|(meta, vftable)| {
52            let col: &RTTICompleteObjectLocator = program.derva(meta).ok()?;
53
54            let ty_name = program
55                .derva_c_str(col.type_descriptor + 16)
56                .ok()?
57                .to_string();
58            if !ty_name
59                .chars()
60                .all(|ch| (0x20..=0x7e).contains(&(ch as u8)))
61            {
62                return None;
63            }
64
65            let demangled = undname::demangle(ty_name.as_str(), Flags::NAME_ONLY)
66                .map(|s| s.to_string())
67                .ok()?;
68
69            Some(Class {
70                program,
71                name: demangled,
72                vftable,
73            })
74        })
75}
76
77// TODO: use better than usizes.
78/// Attempts to extract the class name for a given vftable.
79pub fn vftable_classname(program: &Program, vftable_va: usize) -> Option<String> {
80    let vftable_rva = program.va_to_rva(vftable_va as u64).ok()?;
81    let vftable_meta_rva = vftable_rva - VA_SIZE;
82
83    let rdata = program
84        .section_headers()
85        .by_name(".rdata")
86        .expect("no .rdata section found");
87
88    let vftable_meta_rva = program
89        .derva(vftable_meta_rva)
90        .and_then(|va| program.va_to_rva(*va))
91        .ok()?;
92
93    if !rdata.virtual_range().contains(&vftable_meta_rva) {
94        return None;
95    }
96
97    let col: &RTTICompleteObjectLocator = program.derva(vftable_meta_rva).ok()?;
98    let ty_name = program
99        .derva_c_str(col.type_descriptor + 16)
100        .ok()?
101        .to_string();
102    if !ty_name
103        .chars()
104        .all(|ch| (0x20..=0x7e).contains(&(ch as u8)))
105    {
106        return None;
107    }
108
109    let demangled = undname::demangle(ty_name.as_str(), Flags::NAME_ONLY)
110        .map(|s| s.to_string())
111        .ok()?;
112
113    Some(demangled)
114}
115
116#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
117#[allow(dead_code)]
118struct RttiCandidate {
119    vftable_meta_rva: Rva,
120    vftable_rva: Rva,
121}
122
123pub struct Class<'a, T: Pe<'a>> {
124    program: &'a T,
125    pub name: String,
126    pub vftable: Rva,
127}
128
129impl<'a, T: Pe<'a>> Class<'a, T> {
130    /// Retrieves the function pointer from the VMT.
131    ///
132    /// # Safety
133    /// Does not validate whether or not the index is actually contained within the VMT.
134    pub unsafe fn vmt_fn(&self, index: u32) -> Option<Va> {
135        Some(*self.program.derva(self.vftable + VA_SIZE * index).ok()?)
136    }
137
138    /// Retrieves a mutable function pointer from the VMT.
139    ///
140    /// # Safety
141    /// Does not validate whether or not the index is actually contained within the VMT.
142    pub unsafe fn vmt_index(&self, index: u32) -> Option<NonNull<Va>> {
143        let ptr = self
144            .program
145            .rva_to_va(self.vftable + VA_SIZE * index)
146            .ok()? as *const u64 as *mut u64;
147
148        NonNull::new(ptr)
149    }
150}