use std::str;
use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u16_le, read_u32_le},
vb::control::Guid,
};
#[derive(Clone, Copy, Debug)]
pub struct ComRegData<'a> {
bytes: &'a [u8],
base_va: u32,
}
impl<'a> ComRegData<'a> {
pub const HEADER_SIZE: usize = 0x2A;
pub const MIN_BUFFER_SIZE: usize = Self::HEADER_SIZE;
pub fn parse(data: &'a [u8], base_va: u32) -> Result<Self, Error> {
if data.len() < Self::HEADER_SIZE {
return Err(Error::TooShort {
expected: Self::HEADER_SIZE,
actual: data.len(),
context: "ComRegData",
});
}
Ok(Self {
bytes: data,
base_va,
})
}
#[inline]
pub fn first_object_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn project_name_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
#[inline]
pub fn help_dir_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
#[inline]
pub fn description_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x0C)
}
pub fn project_guid(&self) -> Option<Guid> {
Guid::from_bytes(self.bytes.get(0x10..0x20)?)
}
#[inline]
pub fn lcid(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x20)
}
#[inline]
pub fn reg_flags(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x24)
}
#[inline]
pub fn major_version(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x26)
}
#[inline]
pub fn minor_version(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x28)
}
pub fn project_name(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let off = self.project_name_offset().ok()?;
if off == 0 {
return None;
}
let data = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()?;
let name = read_cstr(data, 0).ok()?;
if name.is_empty() {
return None;
}
str::from_utf8(name).ok()
}
pub fn help_dir(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let off = self.help_dir_offset().ok()?;
if off == 0 {
return None;
}
let data = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()?;
let name = read_cstr(data, 0).ok()?;
if name.is_empty() {
return None;
}
str::from_utf8(name).ok()
}
#[inline]
pub fn base_va(&self) -> u32 {
self.base_va
}
pub fn total_size(&self, map: &AddressMap<'a>) -> Result<usize, Error> {
let mut max_end = Self::HEADER_SIZE as u32;
for off in [
self.project_name_offset()?,
self.help_dir_offset()?,
self.description_offset()?,
] {
if off != 0 {
let str_end = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()
.map(|d| {
let len = d.iter().position(|&b| b == 0).unwrap_or(0) as u32;
off.wrapping_add(len).wrapping_add(1)
})
.unwrap_or(off.wrapping_add(1));
max_end = max_end.max(str_end);
}
}
for obj in self.objects(map)? {
let obj_end = obj
.va()
.wrapping_sub(self.base_va)
.wrapping_add(ComRegObject::SIZE as u32);
max_end = max_end.max(obj_end);
for off in [obj.object_name_offset()?, obj.description_offset()?] {
if off != 0 {
let str_end = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()
.map(|d| {
let len = d.iter().position(|&b| b == 0).unwrap_or(0) as u32;
off.wrapping_add(len).wrapping_add(1)
})
.unwrap_or(off.wrapping_add(1));
max_end = max_end.max(str_end);
}
}
let di_off = obj.default_iface_guids_offset()?;
if di_off != 0 {
max_end =
max_end.max(di_off.wrapping_add(obj.default_iface_count()?.wrapping_mul(16)));
}
let si_off = obj.source_iface_guids_offset()?;
if si_off != 0 {
max_end =
max_end.max(si_off.wrapping_add(obj.source_iface_count()?.wrapping_mul(16)));
}
}
Ok(max_end as usize)
}
#[inline]
pub fn has_objects(&self) -> Result<bool, Error> {
Ok(self.first_object_offset()? != 0)
}
pub fn objects(&self, map: &'a AddressMap<'a>) -> Result<ComRegObjectIter<'a>, Error> {
Ok(ComRegObjectIter {
map,
base_va: self.base_va,
next_offset: self.first_object_offset()?,
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct ComRegObject<'a> {
bytes: &'a [u8],
base_va: u32,
va: u32,
}
impl<'a> ComRegObject<'a> {
pub const SIZE: usize = 0x40;
pub fn parse(data: &'a [u8], base_va: u32, va: u32) -> Result<Self, Error> {
if data.len() < Self::SIZE {
return Err(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "ComRegObject",
});
}
Ok(Self {
bytes: data,
base_va,
va,
})
}
#[inline]
pub fn va(&self) -> u32 {
self.va
}
#[inline]
pub fn next_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn object_name_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
#[inline]
pub fn description_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
pub fn object_name(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let off = self.object_name_offset().ok()?;
if off == 0 {
return None;
}
let data = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()?;
let name = read_cstr(data, 0).ok()?;
if name.is_empty() {
return None;
}
str::from_utf8(name).ok()
}
pub fn description(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let off = self.description_offset().ok()?;
if off == 0 {
return None;
}
let data = map
.slice_from_va(self.base_va.wrapping_add(off), 256)
.ok()?;
let name = read_cstr(data, 0).ok()?;
if name.is_empty() {
return None;
}
str::from_utf8(name).ok()
}
#[inline]
pub fn reg_flag(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x0C)
}
pub fn clsid(&self) -> Option<Guid> {
Guid::from_bytes(self.bytes.get(0x14..0x24)?)
}
#[inline]
pub fn default_iface_count(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x24)
}
#[inline]
pub fn default_iface_guids_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x28)
}
#[inline]
pub fn source_iface_guids_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x2C)
}
#[inline]
pub fn source_iface_count(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x30)
}
#[inline]
pub fn misc_status(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x34)
}
#[inline]
pub fn object_flags(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x38)
}
#[inline]
pub fn toolbox_bitmap_id(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x3A)
}
#[inline]
pub fn default_icon_id(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x3C)
}
#[inline]
pub fn extended_flags(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x3E)
}
#[inline]
pub fn is_control(&self) -> Result<bool, Error> {
Ok(self.object_flags()? & 0x0020 != 0)
}
#[inline]
pub fn is_doc_object(&self) -> Result<bool, Error> {
Ok(self.object_flags()? & 0x0080 != 0)
}
#[inline]
pub fn is_automatable(&self) -> Result<bool, Error> {
Ok(self.object_flags()? & 0x00B2 != 0)
}
pub fn default_iface_guids(&self, map: &AddressMap<'a>) -> Result<Vec<Guid>, Error> {
Ok(self.read_guid_array(
map,
self.default_iface_guids_offset()?,
self.default_iface_count()?,
))
}
pub fn source_iface_guids(&self, map: &AddressMap<'a>) -> Result<Vec<Guid>, Error> {
Ok(self.read_guid_array(
map,
self.source_iface_guids_offset()?,
self.source_iface_count()?,
))
}
fn read_guid_array(&self, map: &AddressMap<'a>, offset: u32, count: u32) -> Vec<Guid> {
if offset == 0 || count == 0 {
return Vec::new();
}
let va = self.base_va.wrapping_add(offset);
let size = (count as usize).saturating_mul(16);
let Ok(data) = map.slice_from_va(va, size) else {
return Vec::new();
};
(0..count as usize)
.filter_map(|i| {
let start = i.saturating_mul(16);
let end = start.saturating_add(16);
Guid::from_bytes(data.get(start..end)?)
})
.collect()
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ComRegObjectIter<'a> {
map: &'a AddressMap<'a>,
base_va: u32,
next_offset: u32,
}
impl<'a> Iterator for ComRegObjectIter<'a> {
type Item = ComRegObject<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.next_offset == 0 {
return None;
}
let va = self.base_va.wrapping_add(self.next_offset);
let data = self.map.slice_from_va(va, ComRegObject::SIZE).ok()?;
let obj = ComRegObject::parse(data, self.base_va, va).ok()?;
self.next_offset = obj.next_offset().ok()?;
Some(obj)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_header() {
let mut data = vec![0u8; 0x30];
data[0x04..0x08].copy_from_slice(&0x30u32.to_le_bytes());
data[0x10..0x20].copy_from_slice(&[
0x98, 0xF0, 0xDD, 0x2C, 0xC0, 0x58, 0xA4, 0x43, 0xBE, 0xB7, 0x64, 0xB3, 0x61, 0x53,
0x57, 0x36,
]);
data[0x26..0x28].copy_from_slice(&1u16.to_le_bytes());
let reg = ComRegData::parse(&data, 0x00401000).unwrap();
assert!(!reg.has_objects().unwrap());
assert_eq!(reg.major_version().unwrap(), 1);
assert_eq!(reg.minor_version().unwrap(), 0);
assert!(reg.project_guid().is_some());
}
#[test]
fn test_parse_too_short() {
let data = vec![0u8; ComRegData::HEADER_SIZE - 1];
assert!(matches!(
ComRegData::parse(&data, 0),
Err(Error::TooShort { .. })
));
}
}