Skip to main content

fromsoftware_shared/
rtti.rs

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