use crate::{
addressmap::AddressMap,
project::{VbObject, VbProject},
vb::external::ExternalTableEntry,
};
#[derive(Debug, Clone)]
pub enum CallTarget {
Api {
library: String,
function: String,
},
VTableMethod {
control_name: Option<String>,
vtable_offset: u16,
},
ImportRef {
name: Option<String>,
},
Unknown,
}
pub struct ImportResolver<'a> {
map: &'a AddressMap<'a>,
externals: Vec<ExternalTableEntry<'a>>,
}
impl<'a> ImportResolver<'a> {
pub fn new(map: &'a AddressMap<'a>, external_table_va: u32, external_count: u32) -> Self {
let mut externals = Vec::new();
if external_table_va != 0 {
let entry_size = ExternalTableEntry::SIZE as u32;
for i in 0..external_count {
let offset = i.saturating_mul(entry_size);
let entry_va = external_table_va.wrapping_add(offset);
if let Ok(data) = map.slice_from_va(entry_va, ExternalTableEntry::SIZE)
&& let Ok(entry) = ExternalTableEntry::parse(data)
{
externals.push(entry);
}
}
}
Self { map, externals }
}
pub fn from_project(project: &'a VbProject<'a>) -> Self {
let externals: Vec<_> = project
.externals()
.into_iter()
.flatten()
.filter_map(|r| match r {
Ok(e) => Some(e),
Err(e) => {
crate::trace::warn_drop!("import_resolver.externals", error = ?e);
None
}
})
.collect();
Self {
map: project.address_map(),
externals,
}
}
pub fn resolve_external(&self, import: u16) -> CallTarget {
if let Some(entry) = self.externals.get(import as usize)
&& let Some(decl) = entry.as_declare(self.map)
{
let library = decl.library_name(self.map).unwrap_or("").to_string();
let function = decl.function_name(self.map).unwrap_or("").to_string();
if !library.is_empty() || !function.is_empty() {
return CallTarget::Api { library, function };
}
}
CallTarget::Unknown
}
pub fn resolve_import_index(&self, index: u16) -> CallTarget {
let target = self.resolve_external(index);
match target {
CallTarget::Unknown => CallTarget::ImportRef { name: None },
other => other,
}
}
pub fn address_map(&self) -> &AddressMap<'a> {
self.map
}
}
pub struct CallResolver<'a> {
project: &'a VbProject<'a>,
imports: ImportResolver<'a>,
}
impl<'a> CallResolver<'a> {
pub fn new(project: &'a VbProject<'a>) -> Self {
let imports = ImportResolver::from_project(project);
Self { project, imports }
}
pub fn import_resolver(&self) -> &ImportResolver<'a> {
&self.imports
}
pub fn resolve_external(&self, import: u16) -> CallTarget {
self.imports.resolve_external(import)
}
pub fn resolve_vtable(
&self,
vtable_offset: u16,
control_index: u16,
object: &VbObject<'a, '_>,
) -> CallTarget {
let controls: Vec<_> = object
.controls()
.into_iter()
.flatten()
.filter_map(|r| match r {
Ok(c) => Some(c),
Err(e) => {
crate::trace::warn_drop!("call_resolver.vtable_controls", error = ?e);
None
}
})
.collect();
let control_name = controls.get(control_index as usize).and_then(|ctrl| {
let name = ctrl.name();
if name.is_empty() {
None
} else {
Some(name.into_owned())
}
});
CallTarget::VTableMethod {
control_name,
vtable_offset,
}
}
pub fn resolve_import_index(&self, index: u16) -> CallTarget {
self.imports.resolve_import_index(index)
}
pub fn address_map(&self) -> &AddressMap<'a> {
self.project.address_map()
}
}
impl core::fmt::Display for CallTarget {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Api { library, function } => write!(f, "{library}!{function}"),
Self::VTableMethod {
control_name,
vtable_offset,
} => {
if let Some(name) = control_name {
write!(f, "{name}.vtbl+0x{vtable_offset:04X}")
} else {
write!(f, "vtbl+0x{vtable_offset:04X}")
}
}
Self::ImportRef { name: Some(n) } => write!(f, "{n}"),
Self::ImportRef { name: None } => write!(f, "<import>"),
Self::Unknown => write!(f, "<unknown>"),
}
}
}