use std::fmt;
use crate::{addressmap::AddressMap, error::Error, util::read_u32_le, vb::control::Guid};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GuiObjectType {
Form,
MdiForm,
UserControl,
PropertyPage,
Other(u8),
Unknown(u8),
}
impl GuiObjectType {
pub fn from_raw(raw: u8) -> Self {
match raw & 0x0F {
0..=2 => Self::Form,
3 => Self::MdiForm,
4 => Self::UserControl,
5 => Self::PropertyPage,
6 | 7 => Self::Other(raw & 0x0F),
n => Self::Unknown(n),
}
}
}
impl fmt::Display for GuiObjectType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Form => write!(f, "Form"),
Self::MdiForm => write!(f, "MDIForm"),
Self::UserControl => write!(f, "UserControl"),
Self::PropertyPage => write!(f, "PropertyPage"),
Self::Other(n) => write!(f, "GuiType{n}"),
Self::Unknown(n) => write!(f, "Unknown({n})"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct GuiTypeFlags(pub u32);
impl GuiTypeFlags {
pub const DISPATCH_FLAG: u32 = 0x0010;
pub const RUNTIME_FLAG: u32 = 0x0020;
pub const WRAPPER_FLAG: u32 = 0x0080;
pub const MDI_FLAG: u32 = 0x8000;
pub const EXTENDED_1: u32 = 0x10000;
pub const EXTENDED_2: u32 = 0x20000;
#[inline]
pub fn object_type(self) -> GuiObjectType {
GuiObjectType::from_raw((self.0 & 0xF) as u8)
}
#[inline]
pub fn has(self, flag: u32) -> bool {
self.0 & flag != 0
}
#[inline]
pub fn flag_bits(self) -> u32 {
self.0 & !0xF
}
}
impl fmt::Debug for GuiTypeFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GuiTypeFlags(0x{:05X} {})", self.0, self.object_type())?;
let flags: &[(&str, u32)] = &[
("DISPATCH", Self::DISPATCH_FLAG),
("RUNTIME", Self::RUNTIME_FLAG),
("WRAPPER", Self::WRAPPER_FLAG),
("MDI", Self::MDI_FLAG),
("EXT1", Self::EXTENDED_1),
("EXT2", Self::EXTENDED_2),
];
for &(name, val) in flags {
if self.has(val) {
write!(f, " | {name}")?;
}
}
Ok(())
}
}
impl fmt::Display for GuiTypeFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[derive(Clone, Copy, Debug)]
pub struct GuiTableEntry<'a> {
bytes: &'a [u8],
va: u32,
}
impl<'a> GuiTableEntry<'a> {
pub const MIN_SIZE: usize = 0x50;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::MIN_SIZE {
return Err(Error::TooShort {
expected: Self::MIN_SIZE,
actual: data.len(),
context: "GuiTableEntry",
});
}
let bytes = data.get(..Self::MIN_SIZE).ok_or(Error::TooShort {
expected: Self::MIN_SIZE,
actual: data.len(),
context: "GuiTableEntry",
})?;
Ok(Self { bytes, va: 0 })
}
pub fn parse_at(data: &'a [u8], va: u32) -> Result<Self, Error> {
let mut entry = Self::parse(data)?;
entry.va = va;
Ok(entry)
}
#[inline]
pub fn va(&self) -> u32 {
self.va
}
#[inline]
pub fn entry_size(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
pub fn guid(&self) -> Option<Guid> {
Guid::from_bytes(self.bytes.get(0x04..0x14)?)
}
pub fn secondary_guid(&self) -> Option<Guid> {
let data = self.bytes.get(0x14..0x24)?;
if data.iter().all(|&b| b == 0) {
return None;
}
Guid::from_bytes(data)
}
#[inline]
pub fn field_24(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x24)
}
#[inline]
pub fn object_type_raw(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x28)
}
pub fn object_type(&self) -> GuiObjectType {
GuiObjectType::from_raw((self.object_type_raw().unwrap_or(0) & 0xF) as u8)
}
pub fn gui_type_flags(&self) -> GuiTypeFlags {
GuiTypeFlags(self.object_type_raw().unwrap_or(0))
}
#[inline]
pub fn type_data_dword(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x2C)
}
pub fn type_data_iid(&self) -> Option<Guid> {
let data = self.bytes.get(0x30..0x40)?;
if data.iter().all(|&b| b == 0) {
return None;
}
Guid::from_bytes(data)
}
#[inline]
pub fn form_data_size(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x40)
}
#[inline]
pub fn form_data_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x48)
}
#[inline]
pub fn form_data_size2(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x4C)
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct GuiTableIter<'a> {
map: &'a AddressMap<'a>,
current_va: u32,
remaining: u16,
}
impl<'a> GuiTableIter<'a> {
pub fn new(map: &'a AddressMap<'a>, gui_table_va: u32, form_count: u16) -> Self {
Self {
map,
current_va: gui_table_va,
remaining: form_count,
}
}
}
impl<'a> Iterator for GuiTableIter<'a> {
type Item = GuiTableEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 || self.current_va == 0 {
return None;
}
let data = self
.map
.slice_from_va(self.current_va, GuiTableEntry::MIN_SIZE)
.ok()?;
let entry = GuiTableEntry::parse_at(data, self.current_va).ok()?;
let size = entry.entry_size().ok()?;
if size == 0 {
return None; }
self.current_va = self.current_va.wrapping_add(size);
self.remaining = self.remaining.saturating_sub(1);
Some(entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
const LOADER_FORM: [u8; 0x50] = [
0x50, 0x00, 0x00, 0x00, 0x6b, 0x5a, 0x0f, 0x22, 0x7d, 0xc6, 0x82, 0x4f, 0x8d, 0x47, 0x9d, 0x24, 0x3f, 0x9b, 0x39, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0x1C, 0x40, 0x00, 0x4C, 0x00, 0x00, 0x00, ];
#[test]
fn test_parse_loader_form() {
let entry = GuiTableEntry::parse(&LOADER_FORM).unwrap();
assert_eq!(entry.entry_size().unwrap(), 0x50);
assert!(entry.guid().is_some());
assert_eq!(entry.object_type(), GuiObjectType::Form);
assert_eq!(entry.object_type_raw().unwrap(), 0x90);
assert_eq!(entry.form_data_size().unwrap(), 0xC5);
assert_eq!(entry.form_data_va().unwrap(), 0x00401C94);
assert_eq!(entry.form_data_size2().unwrap(), 0x4C);
}
#[test]
fn test_gui_object_types() {
assert_eq!(GuiObjectType::from_raw(0), GuiObjectType::Form);
assert_eq!(GuiObjectType::from_raw(1), GuiObjectType::Form);
assert_eq!(GuiObjectType::from_raw(2), GuiObjectType::Form);
assert_eq!(GuiObjectType::from_raw(3), GuiObjectType::MdiForm);
assert_eq!(GuiObjectType::from_raw(4), GuiObjectType::UserControl);
assert_eq!(GuiObjectType::from_raw(5), GuiObjectType::PropertyPage);
assert_eq!(format!("{}", GuiObjectType::Form), "Form");
assert_eq!(format!("{}", GuiObjectType::MdiForm), "MDIForm");
}
#[test]
fn test_parse_too_short() {
let short = [0u8; 0x4F];
assert!(GuiTableEntry::parse(&short).is_err());
}
}