use std::str;
use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u32_le},
vb::control::Guid,
};
#[derive(Clone, Copy, Debug)]
pub struct ProjectInfo2<'a> {
bytes: &'a [u8],
}
impl<'a> ProjectInfo2<'a> {
pub const HEADER_SIZE: usize = 0x28;
pub const ENTRY_SIZE: usize = 0x0C;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::HEADER_SIZE {
return Err(Error::TooShort {
expected: Self::HEADER_SIZE,
actual: data.len(),
context: "ProjectInfo2",
});
}
let bytes = data.get(..Self::HEADER_SIZE).ok_or(Error::TooShort {
expected: Self::HEADER_SIZE,
actual: data.len(),
context: "ProjectInfo2",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn object_table_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
#[inline]
pub fn object_descs_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x10)
}
}
#[derive(Clone, Copy, Debug)]
pub struct InterfaceMetadata<'a> {
bytes: &'a [u8],
}
impl<'a> InterfaceMetadata<'a> {
pub const SIZE: usize = 0x24;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::SIZE {
return Err(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "InterfaceMetadata",
});
}
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "InterfaceMetadata",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn name_table_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x14)
}
#[inline]
pub fn typelib_guid_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn typelib_path_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x10)
}
#[inline]
pub fn data_slot_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x18)
}
pub fn dispatch_names(&self, map: &AddressMap<'a>) -> Vec<&'a str> {
let Ok(va) = self.name_table_va() else {
return Vec::new();
};
if va == 0 {
return Vec::new();
}
let Ok(data) = map.slice_from_va(va, 512) else {
return Vec::new();
};
extract_name_block(data, 0).0
}
pub fn all_dispatch_names(&self, map: &AddressMap<'a>) -> Vec<Vec<&'a str>> {
let Ok(va) = self.name_table_va() else {
return Vec::new();
};
if va == 0 {
return Vec::new();
}
let Ok(data) = map.slice_from_va(va, 4096) else {
return Vec::new();
};
extract_all_name_blocks(data)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ControlTypeEntry {
pub interface_metadata_va: u32,
pub guid_data_va: u32,
pub dispatch_slot_va: u32,
}
impl ControlTypeEntry {
pub fn control_guid<'a>(&self, map: &AddressMap<'a>) -> Option<Guid> {
let data = map.slice_from_va(self.guid_data_va, 16).ok()?;
Guid::from_bytes(data)
}
pub fn control_name<'a>(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let data = map
.slice_from_va(self.guid_data_va.wrapping_add(16), 64)
.ok()?;
let name = read_cstr(data, 0).ok()?;
if name.is_empty() {
return None;
}
str::from_utf8(name).ok()
}
pub fn interface_metadata<'a>(&self, map: &'a AddressMap<'a>) -> Option<InterfaceMetadata<'a>> {
let data = map
.slice_from_va(self.interface_metadata_va, InterfaceMetadata::SIZE)
.ok()?;
InterfaceMetadata::parse(data).ok()
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ControlTypeIter<'a> {
map: &'a AddressMap<'a>,
base_va: u32,
index: u32,
}
impl<'a> ControlTypeIter<'a> {
pub fn new(map: &'a AddressMap<'a>, pi2_va: u32) -> Self {
Self {
map,
base_va: pi2_va.wrapping_add(ProjectInfo2::HEADER_SIZE as u32),
index: 0,
}
}
}
impl<'a> Iterator for ControlTypeIter<'a> {
type Item = ControlTypeEntry;
fn next(&mut self) -> Option<Self::Item> {
let entry_offset = self.index.checked_mul(ProjectInfo2::ENTRY_SIZE as u32)?;
let entry_va = self.base_va.checked_add(entry_offset)?;
let data = self
.map
.slice_from_va(entry_va, ProjectInfo2::ENTRY_SIZE)
.ok()?;
let a = read_u32_le(data, 0).ok()?;
let b = read_u32_le(data, 4).ok()?;
if !self.map.is_va_in_image(a) || !self.map.is_va_in_image(b) {
return None;
}
let c = read_u32_le(data, 8).ok()?;
self.index = self.index.checked_add(1)?;
Some(ControlTypeEntry {
interface_metadata_va: a,
guid_data_va: b,
dispatch_slot_va: c,
})
}
}
pub fn read_name_strings<'a>(
map: &'a AddressMap<'a>,
pi2_va: u32,
entry_count: u32,
) -> Vec<&'a str> {
let entries_size = entry_count.wrapping_mul(ProjectInfo2::ENTRY_SIZE as u32);
let names_va = pi2_va
.wrapping_add(ProjectInfo2::HEADER_SIZE as u32)
.wrapping_add(entries_size);
let Ok(data) = map.slice_from_va(names_va, 1024) else {
return Vec::new();
};
let mut names = Vec::new();
let mut pos = 0usize;
while pos < data.len() {
let Some(&b) = data.get(pos) else { break };
if b == 0 {
pos = pos.saturating_add(1);
continue;
}
if !(0x20..=0x7E).contains(&b) {
break;
}
let Ok(name) = read_cstr(data, pos) else {
break;
};
if name.is_empty() {
break;
}
if let Ok(s) = str::from_utf8(name) {
names.push(s);
}
pos = pos.saturating_add(name.len()).saturating_add(1);
while !pos.is_multiple_of(4) && pos < data.len() {
pos = pos.saturating_add(1);
}
}
names
}
fn is_vb_identifier(name: &[u8]) -> bool {
name.len() >= 2
&& name
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'_' || b == b'.')
}
fn extract_name_block(data: &[u8], start: usize) -> (Vec<&str>, usize) {
let mut names = Vec::new();
let mut pos = start;
while pos < data.len() {
let Some(&b) = data.get(pos) else { break };
if b == 0 {
let tail = data.get(pos..).unwrap_or(&[]);
let nulls = tail.iter().take_while(|&&b| b == 0).count();
if nulls >= 4 && !names.is_empty() {
return (names, pos.saturating_add(nulls));
}
pos = pos.saturating_add(nulls);
continue;
}
let Ok(name) = read_cstr(data, pos) else {
break;
};
if !is_vb_identifier(name) {
break;
}
if let Ok(s) = str::from_utf8(name) {
names.push(s);
}
pos = pos.saturating_add(name.len()).saturating_add(1);
}
(names, pos)
}
fn extract_all_name_blocks(data: &[u8]) -> Vec<Vec<&str>> {
let mut blocks = Vec::new();
let mut pos = 0usize;
while pos < data.len() {
let Some(&b) = data.get(pos) else { break };
if b == 0 {
pos = pos.saturating_add(1);
continue;
}
if !(0x20..=0x7E).contains(&b) {
pos = pos.saturating_add(1);
continue;
}
let Ok(name) = read_cstr(data, pos) else {
pos = pos.saturating_add(1);
continue;
};
if !is_vb_identifier(name) {
pos = pos.saturating_add(1);
continue;
}
let (block, end) = extract_name_block(data, pos);
if !block.is_empty() {
blocks.push(block);
}
pos = end;
}
blocks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_header() {
let mut data = vec![0u8; ProjectInfo2::HEADER_SIZE];
data[0x04..0x08].copy_from_slice(&0x00402000u32.to_le_bytes());
data[0x08..0x0C].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
data[0x10..0x14].copy_from_slice(&0x00405000u32.to_le_bytes());
let pi2 = ProjectInfo2::parse(&data).unwrap();
assert_eq!(pi2.object_table_va().unwrap(), 0x00402000);
assert_eq!(pi2.object_descs_va().unwrap(), 0x00405000);
}
#[test]
fn test_parse_too_short() {
let data = vec![0u8; ProjectInfo2::HEADER_SIZE - 1];
assert!(matches!(
ProjectInfo2::parse(&data),
Err(Error::TooShort { .. })
));
}
}