use std::fmt;
use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u16_le, read_u32_le},
vb::external::{VarType, VbType},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropertyKind {
None,
Get,
Let,
Set,
Unknown(u8),
}
impl fmt::Display for PropertyKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => Ok(()),
Self::Get => write!(f, "Get "),
Self::Let => write!(f, "Let "),
Self::Set => write!(f, "Set "),
Self::Unknown(v) => write!(f, "Prop{v} "),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct FuncTypDesc<'a> {
bytes: &'a [u8],
}
impl<'a> FuncTypDesc<'a> {
pub const MIN_SIZE: usize = 0x14;
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: "FuncTypDesc",
});
}
Ok(Self {
bytes: data.get(..Self::MIN_SIZE).ok_or(Error::Truncated {
needed: Self::MIN_SIZE,
available: data.len(),
})?,
})
}
#[inline]
pub fn raw_arg_size(&self) -> u8 {
self.bytes.first().copied().unwrap_or(0)
}
#[inline]
pub fn arg_count(&self) -> u8 {
self.raw_arg_size() >> 3
}
pub fn property_kind(&self) -> PropertyKind {
match self.raw_arg_size() & 0x07 {
0 => PropertyKind::None,
1 | 5 => PropertyKind::Get,
2 => PropertyKind::Let,
7 => PropertyKind::Set,
other => PropertyKind::Unknown(other),
}
}
#[inline]
pub fn is_property(&self) -> bool {
self.raw_arg_size() & 0x07 != 0
}
#[inline]
pub fn flags(&self) -> u8 {
self.bytes.get(1).copied().unwrap_or(0)
}
#[inline]
pub fn has_return_type(&self) -> bool {
self.flags() & 0x01 != 0
}
#[inline]
pub fn has_param_array(&self) -> bool {
self.flags() & 0x02 != 0
}
#[inline]
pub fn vtable_offset(&self) -> Result<u16, Error> {
Ok(read_u16_le(self.bytes, 0x02)? & 0xFFFE)
}
#[inline]
pub fn object_index(&self) -> Result<i16, Error> {
Ok(read_u16_le(self.bytes, 0x04)? as i16)
}
#[inline]
pub fn optional_defaults_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
#[inline]
pub fn dispid(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x0C)
}
pub fn return_type(&self) -> Option<VbType> {
if self.has_return_type() {
self.bytes.get(0x0E).copied().map(VbType)
} else {
None
}
}
#[inline]
pub fn func_flags(&self) -> u8 {
self.bytes.get(0x0F).copied().unwrap_or(0)
}
#[inline]
pub fn func_flags_is_property(&self) -> bool {
self.func_flags() & 0x08 != 0
}
#[inline]
pub fn param_names_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x10)
}
pub fn param_names<'b>(&self, map: &AddressMap<'b>) -> Vec<&'b [u8]> {
let Ok(base) = self.param_names_va() else {
return Vec::new();
};
if base == 0 || self.arg_count() == 0 {
return Vec::new();
}
let count = self.arg_count() as usize;
let mut names = Vec::with_capacity(count);
for i in 0..count {
let ptr_va = base.wrapping_add((i as u32).wrapping_mul(4));
let name: &'b [u8] = map
.slice_from_va(ptr_va, 4)
.ok()
.and_then(|d| {
let name_va = read_u32_le(d, 0).ok()?;
if name_va == 0 {
return None;
}
let off = map.va_to_offset(name_va).ok()?;
read_cstr(map.file(), off).ok()
})
.unwrap_or(b"");
names.push(name);
}
names
}
pub fn kind_keyword(&self) -> &'static str {
if self.is_property() {
match self.property_kind() {
PropertyKind::Get => "Property Get",
PropertyKind::Let => "Property Let",
PropertyKind::Set => "Property Set",
_ => "Property",
}
} else if self.has_return_type() {
"Function"
} else {
"Sub"
}
}
pub fn arg_types(&self) -> Vec<ArgType> {
let count = self.arg_count() as usize;
if count == 0 {
return Vec::new();
}
let Some(data) = self.bytes.get(0x20..) else {
return Vec::new();
};
if data.is_empty() {
return Vec::new();
}
let mut types = Vec::with_capacity(count);
let mut pos = 0;
for _ in 0..count {
let Some(&type_byte) = data.get(pos) else {
break;
};
types.push(ArgType(type_byte));
pos = pos.saturating_add(calc_arg_type_entry_size(data, pos));
}
types
}
pub fn parse_extended(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::MIN_SIZE {
return Err(Error::TooShort {
expected: Self::MIN_SIZE,
actual: data.len(),
context: "FuncTypDesc",
});
}
let usable = data.len().min(0x40);
Ok(Self {
bytes: data.get(..usable).ok_or(Error::Truncated {
needed: usable,
available: data.len(),
})?,
})
}
pub fn optional_defaults(&self, map: &AddressMap<'_>) -> Vec<OptionalDefault> {
let Ok(header_va) = self.optional_defaults_va() else {
return Vec::new();
};
if header_va == 0 {
return Vec::new();
}
let Ok(hdr) = map.slice_from_va(header_va, 8) else {
return Vec::new();
};
let Ok(total_size) = read_u32_le(hdr, 0) else {
return Vec::new();
};
let total_size = total_size as usize;
let Ok(defaults_va) = read_u32_le(hdr, 4) else {
return Vec::new();
};
if defaults_va == 0 || total_size == 0 {
return Vec::new();
}
let Ok(data) = map.slice_from_va(defaults_va, total_size) else {
return Vec::new();
};
let mut defaults = Vec::new();
let mut pos: usize = 0;
while pos.checked_add(2).is_some_and(|p| p <= data.len()) {
let Ok(vt_raw) = read_u16_le(data, pos) else {
break;
};
let vt = VarType::from_raw(vt_raw).unwrap_or(VarType::Empty);
let data_size = vt.data_size();
let Some(value_start) = pos.checked_add(2) else {
break;
};
if vt == VarType::Bstr {
let Some(after_len) = value_start.checked_add(2) else {
break;
};
if after_len > data.len() {
break;
}
let Ok(byte_len_raw) = read_u16_le(data, value_start) else {
break;
};
let byte_len = byte_len_raw as usize;
let str_start = after_len;
let str_end = str_start.saturating_add(byte_len).min(data.len());
let Some(str_bytes) = data.get(str_start..str_end) else {
break;
};
let text = String::from_utf16_lossy(
&str_bytes
.chunks_exact(2)
.filter_map(|c| <[u8; 2]>::try_from(c).ok())
.map(u16::from_le_bytes)
.collect::<Vec<_>>(),
);
defaults.push(OptionalDefault {
vt,
vt_raw,
value: DefaultValue::String(text),
});
let aligned_len = byte_len.saturating_add(1) & !1;
let Some(next) = after_len.checked_add(aligned_len) else {
break;
};
pos = next;
} else if data_size > 0 {
let val_end = value_start.saturating_add(data_size).min(data.len());
let Some(val_bytes) = data.get(value_start..val_end) else {
break;
};
let value = match vt {
VarType::I2 | VarType::Bool | VarType::I1 | VarType::Ui1 | VarType::Ui2 => {
if val_bytes.len() >= 2 {
match read_u16_le(val_bytes, 0) {
Ok(v) => DefaultValue::Integer(i64::from(v as i16)),
Err(_) => DefaultValue::Raw(val_bytes.to_vec()),
}
} else {
DefaultValue::Raw(val_bytes.to_vec())
}
}
VarType::I4
| VarType::Dispatch
| VarType::Unknown
| VarType::R4
| VarType::Record
| VarType::Int
| VarType::Uint => {
if val_bytes.len() >= 4 {
match read_u32_le(val_bytes, 0) {
Ok(v) => DefaultValue::Integer(i64::from(v as i32)),
Err(_) => DefaultValue::Raw(val_bytes.to_vec()),
}
} else {
DefaultValue::Raw(val_bytes.to_vec())
}
}
_ => DefaultValue::Raw(val_bytes.to_vec()),
};
defaults.push(OptionalDefault { vt, vt_raw, value });
let Some(next) = value_start.checked_add(data_size) else {
break;
};
pos = next;
} else {
defaults.push(OptionalDefault {
vt,
vt_raw,
value: DefaultValue::Empty,
});
pos = value_start;
}
}
defaults
}
}
#[derive(Debug, Clone)]
pub struct OptionalDefault {
pub vt: VarType,
pub vt_raw: u16,
pub value: DefaultValue,
}
#[derive(Debug, Clone)]
pub enum DefaultValue {
Empty,
Integer(i64),
String(String),
Raw(Vec<u8>),
}
impl fmt::Display for OptionalDefault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.value {
DefaultValue::Empty => write!(f, "Empty"),
DefaultValue::Integer(v) => {
if self.vt == VarType::Bool {
write!(f, "{}", if *v != 0 { "True" } else { "False" })
} else {
write!(f, "{v}")
}
}
DefaultValue::String(s) => write!(f, "\"{s}\""),
DefaultValue::Raw(b) => {
write!(
f,
"0x{}",
b.iter().map(|x| format!("{x:02X}")).collect::<String>()
)
}
}
}
}
fn calc_arg_type_entry_size(data: &[u8], pos: usize) -> usize {
let Some(&type_byte) = data.get(pos) else {
return 1;
};
let base = type_byte & 0x1F;
let base_size: usize = 1;
match base {
0x11 | 0x13 | 0x14 | 0x1C | 0x1D => {
let after_type = pos.saturating_add(base_size);
let aligned = after_type.saturating_add(3) & !3;
aligned.saturating_sub(pos).saturating_add(4)
}
_ => base_size,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArgType(pub u8);
impl ArgType {
pub const VOID: u8 = 0x00;
pub const BOOLEAN: u8 = 0x03;
pub const SBYTE: u8 = 0x04;
pub const BYTE: u8 = 0x05;
pub const INTEGER: u8 = 0x06;
pub const USHORT: u8 = 0x07;
pub const LONG: u8 = 0x08;
pub const ULONG: u8 = 0x09;
pub const SINGLE: u8 = 0x0A;
pub const DOUBLE: u8 = 0x0B;
pub const DATE: u8 = 0x0C;
pub const CURRENCY: u8 = 0x0D;
pub const DECIMAL: u8 = 0x0E;
pub const VARIANT: u8 = 0x0F;
pub const STRING: u8 = 0x10;
pub const UDT: u8 = 0x11;
pub const OBJECT: u8 = 0x13;
pub const RECORD: u8 = 0x14;
pub const DISPATCH_PTR: u8 = 0x1E;
pub const BYREF: u8 = 0x20;
pub const ARRAY: u8 = 0x40;
pub const OPTIONAL: u8 = 0x80;
#[inline]
pub fn base_type(self) -> u8 {
self.0 & 0x1F
}
#[inline]
pub fn is_byref(self) -> bool {
self.0 & Self::BYREF != 0
}
#[inline]
pub fn is_array(self) -> bool {
self.0 & Self::ARRAY != 0
}
#[inline]
pub fn is_optional(self) -> bool {
self.0 & Self::OPTIONAL != 0
}
pub fn type_name(self) -> &'static str {
match self.base_type() {
0x00 | 0x01 => "Void",
0x02 => "void",
Self::BOOLEAN => "Boolean",
Self::SBYTE => "SByte",
Self::BYTE => "Byte",
Self::INTEGER => "Integer",
Self::USHORT => "UShort",
Self::LONG | 0x1A => "Long",
Self::ULONG => "ULong",
Self::SINGLE => "Single",
Self::DOUBLE => "Double",
Self::DATE => "Date",
Self::CURRENCY => "Currency",
Self::DECIMAL => "Decimal",
Self::VARIANT => "Variant",
Self::STRING => "String",
Self::UDT => "UDT",
Self::OBJECT | 0x1B | 0x1D => "Object",
Self::RECORD => "Record",
0x16 => "IDispatch",
0x1C => "IUnknown",
Self::DISPATCH_PTR => "DispPtr",
_ => "Unknown",
}
}
}
impl fmt::Display for ArgType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_optional() {
write!(f, "Optional ")?;
}
if self.is_byref() {
write!(f, "ByRef ")?;
}
write!(f, "{}", self.type_name())?;
if self.is_array() {
write!(f, "()")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
const ZIP_FUNC0: [u8; 0x14] = [
0x08, 0x01, 0x1D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03,
0x60, 0x74, 0x55, 0x40, 0x00,
];
const ZIP_FUNC2: [u8; 0x14] = [
0x18, 0x01, 0x25, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x18, 0x56, 0x40, 0x00, 0x12, 0x00, 0x03,
0x60, 0x6C, 0x56, 0x40, 0x00,
];
const ZIP_PROP_GET: [u8; 0x14] = [
0x09, 0x01, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x03,
0x68, 0x7C, 0x55, 0x40, 0x00,
];
const CRC32_SUB: [u8; 0x14] = [
0x00, 0x00, 0x1D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03,
0x60, 0x44, 0x55, 0x40, 0x00,
];
#[test]
fn test_function_with_one_arg() {
let ftd = FuncTypDesc::parse(&ZIP_FUNC0).unwrap();
assert_eq!(ftd.arg_count(), 1);
assert_eq!(ftd.property_kind(), PropertyKind::None);
assert!(ftd.has_return_type());
assert_eq!(ftd.return_type(), Some(VbType(0x03))); assert_eq!(ftd.vtable_offset().unwrap(), 0x001C);
assert_eq!(ftd.object_index().unwrap(), -1);
assert_eq!(ftd.optional_defaults_va().unwrap(), 0);
assert_eq!(ftd.param_names_va().unwrap(), 0x00405574);
assert_eq!(ftd.kind_keyword(), "Function");
}
#[test]
fn test_function_with_three_args() {
let ftd = FuncTypDesc::parse(&ZIP_FUNC2).unwrap();
assert_eq!(ftd.arg_count(), 3);
assert_eq!(ftd.property_kind(), PropertyKind::None);
assert!(ftd.has_return_type());
assert_eq!(ftd.return_type(), Some(VbType(0x03)));
assert_eq!(ftd.vtable_offset().unwrap(), 0x0024);
assert!(ftd.optional_defaults_va().unwrap() != 0); assert_eq!(ftd.kind_keyword(), "Function");
}
#[test]
fn test_property_get() {
let ftd = FuncTypDesc::parse(&ZIP_PROP_GET).unwrap();
assert_eq!(ftd.arg_count(), 1);
assert_eq!(ftd.property_kind(), PropertyKind::Get);
assert!(ftd.is_property());
assert!(ftd.has_return_type());
assert_eq!(ftd.return_type(), Some(VbType(0x03)));
assert_eq!(ftd.vtable_offset().unwrap(), 0x0030);
assert_eq!(ftd.func_flags(), 0x68); assert_eq!(ftd.kind_keyword(), "Property Get");
}
#[test]
fn test_sub_no_args() {
let ftd = FuncTypDesc::parse(&CRC32_SUB).unwrap();
assert_eq!(ftd.arg_count(), 0);
assert_eq!(ftd.property_kind(), PropertyKind::None);
assert!(!ftd.has_return_type());
assert_eq!(ftd.return_type(), None);
assert_eq!(ftd.kind_keyword(), "Sub");
}
#[test]
fn test_parse_too_short() {
let short = [0u8; 0x13];
assert!(FuncTypDesc::parse(&short).is_err());
}
#[test]
fn test_vtable_offset_masks_runtime_bit() {
let ftd = FuncTypDesc::parse(&ZIP_FUNC0).unwrap();
assert_eq!(ftd.vtable_offset().unwrap(), 0x001C);
}
#[test]
fn test_arg_type_names() {
assert_eq!(ArgType(0x10).type_name(), "String"); assert_eq!(ArgType(0x08).type_name(), "Long"); assert_eq!(ArgType(0x06).type_name(), "Integer");
assert_eq!(ArgType(0x03).type_name(), "Boolean");
assert_eq!(ArgType(0x0A).type_name(), "Single");
assert_eq!(ArgType(0x0B).type_name(), "Double");
assert_eq!(ArgType(0x0F).type_name(), "Variant");
assert_eq!(ArgType(0x13).type_name(), "Object");
assert_eq!(ArgType(0x1E).type_name(), "DispPtr");
}
#[test]
fn test_arg_type_display() {
assert_eq!(format!("{}", ArgType(0x1E)), "DispPtr");
assert_eq!(format!("{}", ArgType(0x10)), "String");
assert_eq!(format!("{}", ArgType(0x30)), "ByRef String");
assert_eq!(format!("{}", ArgType(0x50)), "String()");
assert_eq!(format!("{}", ArgType(0x70)), "ByRef String()");
assert_eq!(format!("{}", ArgType(0x90)), "Optional String");
}
#[test]
fn test_arg_type_modifiers() {
let t = ArgType(0x70); assert!(t.is_byref());
assert!(t.is_array());
assert!(!t.is_optional());
assert_eq!(t.base_type(), ArgType::STRING);
let t = ArgType(0x90); assert!(t.is_optional());
assert!(!t.is_byref());
assert!(!t.is_array());
}
}