use std::{borrow::Cow, fmt, 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 CallApiStub<'a> {
bytes: &'a [u8],
}
impl<'a> CallApiStub<'a> {
pub const SIZE: usize = 8;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "CallApiStub",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn library_name_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn function_name_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
pub fn library_name(&self, map: &AddressMap<'a>) -> Result<Cow<'a, str>, Error> {
Ok(String::from_utf8_lossy(self.library_name_bytes(map)?))
}
pub fn library_name_bytes(&self, map: &AddressMap<'a>) -> Result<&'a [u8], Error> {
let va = self.library_name_va()?;
if va == 0 {
return Ok(b"");
}
let offset = map.va_to_offset(va)?;
read_cstr(map.file(), offset)
}
pub fn function_name(&self, map: &AddressMap<'a>) -> Result<Cow<'a, str>, Error> {
Ok(String::from_utf8_lossy(self.function_name_bytes(map)?))
}
pub fn function_name_bytes(&self, map: &AddressMap<'a>) -> Result<&'a [u8], Error> {
let va = self.function_name_va()?;
if va == 0 {
return Ok(b"");
}
let offset = map.va_to_offset(va)?;
read_cstr(map.file(), offset)
}
}
pub fn resolve_api_stub<'a>(map: &AddressMap<'a>, stub_va: u32) -> Result<CallApiStub<'a>, Error> {
let stub_data = map.slice_from_va(stub_va, 5)?;
let first = *stub_data.first().ok_or(Error::TooShort {
expected: 5,
actual: stub_data.len(),
context: "resolve_api_stub",
})?;
if first != 0x68 {
return Err(Error::EntryPointNotPush { byte: first });
}
let call_api_va = read_u32_le(stub_data, 1)?;
let call_api_data = map.slice_from_va(call_api_va, CallApiStub::SIZE)?;
CallApiStub::parse(call_api_data)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VbType(
).
pub u8,
);
impl VbType {
pub const EMPTY: u8 = 0x00;
pub const NULL: u8 = 0x01;
pub const INTEGER: u8 = 0x02;
pub const LONG: u8 = 0x03;
pub const SINGLE: u8 = 0x04;
pub const DOUBLE: u8 = 0x05;
pub const CURRENCY: u8 = 0x06;
pub const DATE: u8 = 0x07;
pub const STRING: u8 = 0x08;
pub const OBJECT: u8 = 0x0A;
pub const ERROR: u8 = 0x0B;
pub const BOOLEAN: u8 = 0x0C;
pub const VARIANT: u8 = 0x0D;
pub const DECIMAL: u8 = 0x0E;
pub const BYTE: u8 = 0x10;
pub const UDT: u8 = 0x11;
pub const TYPED_OBJECT: u8 = 0x13;
pub const TYPED_ARRAY: u8 = 0x14;
pub const LONG_PTR: u8 = 0x1B;
pub const EXTENDED_DECIMAL: u8 = 0x1C;
pub const EXTERNAL_COM: u8 = 0x1D;
pub const DISPATCH_PTR: u8 = 0x1E;
pub const ARRAY: u8 = 0x20;
pub const BYREF: u8 = 0x40;
pub const OPTIONAL: u8 = 0x80;
#[inline]
pub fn base_type(self) -> u8 {
self.0 & 0x1F
}
#[inline]
pub fn base_type_enum(self) -> VbBaseType {
VbBaseType::from_raw(self.base_type())
}
#[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() {
Self::EMPTY => "Void",
Self::NULL => "Null",
Self::INTEGER => "Integer",
Self::LONG => "Long",
Self::SINGLE => "Single",
Self::DOUBLE => "Double",
Self::CURRENCY => "Currency",
Self::DATE => "Date",
Self::STRING => "String",
Self::OBJECT => "Object",
Self::ERROR => "Error",
Self::BOOLEAN => "Boolean",
Self::VARIANT => "Variant",
Self::DECIMAL => "Decimal",
Self::BYTE => "Byte",
Self::UDT => "UDT",
Self::TYPED_OBJECT => "TypedObject",
Self::TYPED_ARRAY => "TypedArray",
Self::LONG_PTR => "LongPtr",
Self::EXTENDED_DECIMAL => "ExtDecimal",
Self::EXTERNAL_COM => "ExternalCOM",
Self::DISPATCH_PTR => "DispatchPtr",
_ => "Unknown",
}
}
}
impl fmt::Display for VbType {
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(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VbBaseType {
Void,
Null,
Integer,
Long,
Single,
Double,
Currency,
Date,
String,
Object,
Error,
Boolean,
Variant,
Decimal,
Byte,
Udt,
TypedObject,
TypedArray,
LongPtr,
ExtDecimal,
ExternalCom,
DispatchPtr,
Unknown(u8),
}
impl VbBaseType {
pub fn from_raw(raw: u8) -> Self {
match raw & 0x1F {
0x00 => Self::Void,
0x01 => Self::Null,
0x02 => Self::Integer,
0x03 => Self::Long,
0x04 => Self::Single,
0x05 => Self::Double,
0x06 => Self::Currency,
0x07 => Self::Date,
0x08 => Self::String,
0x0A => Self::Object,
0x0B => Self::Error,
0x0C => Self::Boolean,
0x0D => Self::Variant,
0x0E => Self::Decimal,
0x10 => Self::Byte,
0x11 => Self::Udt,
0x13 => Self::TypedObject,
0x14 => Self::TypedArray,
0x1B => Self::LongPtr,
0x1C => Self::ExtDecimal,
0x1D => Self::ExternalCom,
0x1E => Self::DispatchPtr,
n => Self::Unknown(n),
}
}
pub fn name(self) -> &'static str {
match self {
Self::Void => "Void",
Self::Null => "Null",
Self::Integer => "Integer",
Self::Long => "Long",
Self::Single => "Single",
Self::Double => "Double",
Self::Currency => "Currency",
Self::Date => "Date",
Self::String => "String",
Self::Object => "Object",
Self::Error => "Error",
Self::Boolean => "Boolean",
Self::Variant => "Variant",
Self::Decimal => "Decimal",
Self::Byte => "Byte",
Self::Udt => "UDT",
Self::TypedObject => "TypedObject",
Self::TypedArray => "TypedArray",
Self::LongPtr => "LongPtr",
Self::ExtDecimal => "ExtDecimal",
Self::ExternalCom => "ExternalCOM",
Self::DispatchPtr => "DispatchPtr",
Self::Unknown(_) => "Unknown",
}
}
}
impl fmt::Display for VbBaseType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum VarType {
Empty = 0,
Null = 1,
I2 = 2,
I4 = 3,
R4 = 4,
R8 = 5,
Cy = 6,
Date = 7,
Bstr = 8,
Dispatch = 9,
Error = 10,
Bool = 11,
Variant = 12,
Unknown = 13,
Decimal = 14,
I1 = 16,
Ui1 = 17,
Ui2 = 18,
Record = 19,
Int = 22,
Uint = 23,
}
impl VarType {
pub fn from_raw(v: u16) -> Option<Self> {
match v {
0 => Some(Self::Empty),
1 => Some(Self::Null),
2 => Some(Self::I2),
3 => Some(Self::I4),
4 => Some(Self::R4),
5 => Some(Self::R8),
6 => Some(Self::Cy),
7 => Some(Self::Date),
8 => Some(Self::Bstr),
9 => Some(Self::Dispatch),
10 => Some(Self::Error),
11 => Some(Self::Bool),
12 => Some(Self::Variant),
13 => Some(Self::Unknown),
14 => Some(Self::Decimal),
16 => Some(Self::I1),
17 => Some(Self::Ui1),
18 => Some(Self::Ui2),
19 => Some(Self::Record),
22 => Some(Self::Int),
23 => Some(Self::Uint),
_ => None,
}
}
pub fn data_size(self) -> usize {
match self {
Self::Empty | Self::Null => 0,
Self::I2 => 2,
Self::I4 | Self::R4 => 4,
Self::R8 | Self::Cy | Self::Date => 8,
Self::Bstr => 0, Self::Dispatch | Self::Error => 4,
Self::Bool => 2,
Self::Variant => 0,
Self::Unknown => 4,
Self::Decimal => 16,
Self::I1 | Self::Ui1 | Self::Ui2 => 2,
Self::Record => 4,
Self::Int | Self::Uint => 4,
}
}
}
impl fmt::Display for VarType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "Empty"),
Self::Null => write!(f, "Null"),
Self::I2 => write!(f, "Integer"),
Self::I4 => write!(f, "Long"),
Self::R4 => write!(f, "Single"),
Self::R8 => write!(f, "Double"),
Self::Cy => write!(f, "Currency"),
Self::Date => write!(f, "Date"),
Self::Bstr => write!(f, "String"),
Self::Dispatch => write!(f, "Object"),
Self::Error => write!(f, "Error"),
Self::Bool => write!(f, "Boolean"),
Self::Variant => write!(f, "Variant"),
Self::Unknown => write!(f, "Unknown"),
Self::Decimal => write!(f, "Decimal"),
Self::I1 => write!(f, "SByte"),
Self::Ui1 => write!(f, "Byte"),
Self::Ui2 => write!(f, "UShort"),
Self::Record => write!(f, "UDT"),
Self::Int => write!(f, "Int"),
Self::Uint => write!(f, "UInt"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExternalKind {
TypeLib,
DeclareFunction,
Unknown(u32),
}
impl ExternalKind {
pub fn from_raw(value: u32) -> Self {
match value {
0x06 => Self::TypeLib,
0x07 => Self::DeclareFunction,
other => Self::Unknown(other),
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::DeclareFunction => "DeclareFunction",
Self::TypeLib => "TypeLib",
Self::Unknown(_) => "Unknown",
}
}
}
impl fmt::Display for ExternalKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug)]
pub struct ExternalTableEntry<'a> {
bytes: &'a [u8],
}
impl<'a> ExternalTableEntry<'a> {
pub const SIZE: usize = 8;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "ExternalTableEntry",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn external_type(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn kind(&self) -> Result<ExternalKind, Error> {
Ok(ExternalKind::from_raw(self.external_type()?))
}
#[inline]
pub fn external_object_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
pub fn as_declare(&self, map: &AddressMap<'a>) -> Option<ExternalDeclareInfo<'a>> {
if !matches!(self.kind().ok()?, ExternalKind::DeclareFunction) {
return None;
}
let va = self.external_object_va().ok()?;
let data = map.slice_from_va(va, ExternalDeclareInfo::SIZE).ok()?;
ExternalDeclareInfo::parse(data).ok()
}
pub fn as_typelib(&self, map: &AddressMap<'a>) -> Option<ExternalTypelibInfo<'a>> {
let va = self.external_object_va().ok()?;
let data = map.slice_from_va(va, ExternalTypelibInfo::SIZE).ok()?;
ExternalTypelibInfo::parse(data).ok()
}
}
#[derive(Clone, Copy, Debug)]
pub struct ExternalDeclareInfo<'a> {
bytes: &'a [u8],
}
impl<'a> ExternalDeclareInfo<'a> {
pub const SIZE: usize = 0x10;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "ExternalDeclareInfo",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn library_name_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn function_name_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
#[inline]
pub fn flags(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
#[inline]
pub fn native_stub_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x0C)
}
pub fn library_name(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let va = self.library_name_va().ok()?;
if va == 0 {
return None;
}
let off = map.va_to_offset(va).ok()?;
let name = read_cstr(map.file(), off).ok()?;
str::from_utf8(name).ok()
}
pub fn function_name(&self, map: &AddressMap<'a>) -> Option<&'a str> {
let va = self.function_name_va().ok()?;
if va == 0 {
return None;
}
let off = map.va_to_offset(va).ok()?;
let name = read_cstr(map.file(), off).ok()?;
str::from_utf8(name).ok()
}
}
#[derive(Clone, Copy, Debug)]
pub struct ExternalTypelibInfo<'a> {
bytes: &'a [u8],
}
impl<'a> ExternalTypelibInfo<'a> {
pub const SIZE: usize = 0x08;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "ExternalTypelibInfo",
})?;
Ok(Self { bytes })
}
#[inline]
pub fn typelib_guid_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
pub fn typelib_guid(&self, map: &AddressMap<'a>) -> Option<Guid> {
let va = self.typelib_guid_va().ok()?;
if va == 0 {
return None;
}
let data = map.slice_from_va(va, 16).ok()?;
Guid::from_bytes(data)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ExternalComponentEntry<'a> {
bytes: &'a [u8],
}
impl<'a> ExternalComponentEntry<'a> {
pub const HEADER_SIZE: usize = 0x34;
pub const EVENT_ENTRY_SIZE: usize = 0x18;
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: "ExternalComponentEntry",
});
}
let size = read_u32_le(data, 0x00)? as usize;
let bytes = data.get(..size).ok_or(Error::TooShort {
expected: size,
actual: data.len(),
context: "ExternalComponentEntry (entry_size)",
})?;
if size < Self::HEADER_SIZE {
return Err(Error::TooShort {
expected: Self::HEADER_SIZE,
actual: size,
context: "ExternalComponentEntry (entry_size)",
});
}
Ok(Self { bytes })
}
#[inline]
pub fn entry_size(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
pub fn ocx_filename(&self) -> Cow<'a, str> {
String::from_utf8_lossy(self.ocx_filename_bytes())
}
pub fn ocx_filename_bytes(&self) -> &'a [u8] {
self.resolve_string(0x28)
}
pub fn prog_id(&self) -> Cow<'a, str> {
String::from_utf8_lossy(self.prog_id_bytes())
}
pub fn prog_id_bytes(&self) -> &'a [u8] {
self.resolve_string(0x2C)
}
pub fn class_name(&self) -> Cow<'a, str> {
String::from_utf8_lossy(self.class_name_bytes())
}
pub fn class_name_bytes(&self) -> &'a [u8] {
self.resolve_string(0x30)
}
pub fn component_flags(&self) -> Option<u8> {
let off = read_u32_le(self.bytes, 0x04).ok()? as usize;
let end = off.checked_add(0x87)?;
if off == 0 || end > self.bytes.len() {
return None;
}
let flags_off = off.checked_add(0x86)?;
self.bytes.get(flags_off).copied()
}
pub fn event_count(&self) -> u16 {
let Ok(off_raw) = read_u32_le(self.bytes, 0x04) else {
return 0;
};
let off = off_raw as usize;
let Some(end) = off.checked_add(0x94) else {
return 0;
};
if off == 0 || end > self.bytes.len() {
return 0;
}
let Some(field_off) = off.checked_add(0x92) else {
return 0;
};
read_u16_le(self.bytes, field_off).unwrap_or(0)
}
#[inline]
pub fn info_block_size(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x20)
}
pub fn event_names(&self) -> Vec<&'a str> {
let Ok(evt_off_raw) = read_u32_le(self.bytes, 0x18) else {
return Vec::new();
};
let evt_off = evt_off_raw as usize;
if evt_off == 0 {
return Vec::new();
}
let count = self.event_count() as usize;
if count == 0 {
return Vec::new();
}
let Some(array_bytes) = count.checked_mul(Self::EVENT_ENTRY_SIZE) else {
return Vec::new();
};
let Some(names_start) = evt_off.checked_add(array_bytes) else {
return Vec::new();
};
if names_start >= self.bytes.len() {
return Vec::new();
}
let mut names = Vec::with_capacity(count);
let mut pos = names_start;
for _ in 0..count {
let Ok(name) = read_cstr(self.bytes, pos) else {
break;
};
let s = str::from_utf8(name).unwrap_or("?");
names.push(s);
let Some(next) = pos.checked_add(name.len()).and_then(|p| p.checked_add(1)) else {
break;
};
pos = next;
if pos >= self.bytes.len() {
break;
}
}
names
}
fn resolve_string(&self, header_offset: usize) -> &'a [u8] {
let Ok(off_raw) = read_u32_le(self.bytes, header_offset) else {
return &[];
};
let off = off_raw as usize;
if off == 0 || off >= self.bytes.len() {
return &[];
}
read_cstr(self.bytes, off).unwrap_or(&[])
}
}
impl fmt::Display for ExternalComponentEntry<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let filename = self.ocx_filename();
let class = self.class_name();
write!(f, "{filename}!{class}")?;
let ec = self.event_count();
if ec > 0 {
write!(f, " ({ec} events)")?;
}
Ok(())
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ExternalComponentIter<'a> {
data: &'a [u8],
pos: usize,
remaining: u16,
}
impl<'a> ExternalComponentIter<'a> {
pub fn new(data: &'a [u8], count: u16) -> Self {
Self {
data,
pos: 0,
remaining: count,
}
}
}
impl<'a> Iterator for ExternalComponentIter<'a> {
type Item = ExternalComponentEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 || self.pos >= self.data.len() {
return None;
}
self.remaining = self.remaining.saturating_sub(1);
let rest = self.data.get(self.pos..)?;
let entry = ExternalComponentEntry::parse(rest).ok()?;
let size = entry.entry_size().ok()? as usize;
if size == 0 {
return None;
}
self.pos = self.pos.checked_add(size)?;
Some(entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::addressmap::SectionEntry;
#[test]
fn test_call_api_stub_parse() {
let mut data = vec![0u8; CallApiStub::SIZE];
data[0x00..0x04].copy_from_slice(&0x00401000u32.to_le_bytes());
data[0x04..0x08].copy_from_slice(&0x00402000u32.to_le_bytes());
let stub = CallApiStub::parse(&data).unwrap();
assert_eq!(stub.library_name_va().unwrap(), 0x00401000);
assert_eq!(stub.function_name_va().unwrap(), 0x00402000);
}
#[test]
fn test_call_api_stub_too_short() {
let data = vec![0u8; CallApiStub::SIZE - 1];
assert!(matches!(
CallApiStub::parse(&data),
Err(Error::TooShort { .. })
));
}
#[test]
fn test_call_api_stub_zero_va() {
let data = vec![0u8; CallApiStub::SIZE];
let stub = CallApiStub::parse(&data).unwrap();
assert_eq!(stub.library_name_va().unwrap(), 0);
assert_eq!(stub.function_name_va().unwrap(), 0);
}
#[test]
fn test_resolve_api_stub_valid() {
let mut file = vec![0u8; 0x500];
file[0x200] = 0x68; file[0x201..0x205].copy_from_slice(&0x00401100u32.to_le_bytes());
file[0x300..0x304].copy_from_slice(&0x00401200u32.to_le_bytes()); file[0x304..0x308].copy_from_slice(&0x00401210u32.to_le_bytes());
file[0x400..0x40C].copy_from_slice(b"kernel32.dll");
file[0x410..0x41C].copy_from_slice(b"GetTickCount");
let map = AddressMap::from_parts(
&file,
0x00400000,
vec![SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x1000,
raw_data_offset: 0x200,
raw_data_size: 0x1000,
}],
);
let stub = resolve_api_stub(&map, 0x00401000).unwrap();
assert_eq!(stub.library_name_bytes(&map).unwrap(), b"kernel32.dll");
assert_eq!(stub.function_name_bytes(&map).unwrap(), b"GetTickCount");
assert_eq!(stub.library_name(&map).unwrap(), "kernel32.dll");
assert_eq!(stub.function_name(&map).unwrap(), "GetTickCount");
}
#[test]
fn test_resolve_api_stub_not_push() {
let mut file = vec![0u8; 0x500];
file[0x200] = 0xCC;
let map = AddressMap::from_parts(
&file,
0x00400000,
vec![SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x1000,
raw_data_offset: 0x200,
raw_data_size: 0x1000,
}],
);
assert!(matches!(
resolve_api_stub(&map, 0x00401000),
Err(Error::EntryPointNotPush { byte: 0xCC })
));
}
#[test]
fn test_vb_type_base() {
let t = VbType(0x03);
assert_eq!(t.base_type(), VbType::LONG);
assert_eq!(t.type_name(), "Long");
assert!(!t.is_byref());
assert!(!t.is_array());
assert!(!t.is_optional());
}
#[test]
fn test_vb_type_byref_long() {
let t = VbType(0x43);
assert_eq!(t.base_type(), VbType::LONG);
assert_eq!(t.type_name(), "Long");
assert!(t.is_byref());
assert!(!t.is_array());
}
#[test]
fn test_vb_type_optional_array_string() {
let t = VbType(0xA8);
assert_eq!(t.base_type(), VbType::STRING);
assert!(t.is_optional());
assert!(t.is_array());
assert!(!t.is_byref());
}
#[test]
fn test_vb_type_byref_array_byte() {
let t = VbType(0x70);
assert_eq!(t.base_type(), VbType::BYTE);
assert!(t.is_byref());
assert!(t.is_array());
assert!(!t.is_optional());
assert_eq!(format!("{t}"), "ByRef Byte()");
}
#[test]
fn test_vb_type_array_byte() {
let t = VbType(0x30);
assert_eq!(t.base_type(), VbType::BYTE);
assert!(t.is_array());
assert!(!t.is_byref());
assert_eq!(format!("{t}"), "Byte()");
}
#[test]
fn test_vb_type_all_base_types() {
assert_eq!(VbType(0x00).type_name(), "Void");
assert_eq!(VbType(0x01).type_name(), "Null");
assert_eq!(VbType(0x02).type_name(), "Integer");
assert_eq!(VbType(0x04).type_name(), "Single");
assert_eq!(VbType(0x05).type_name(), "Double");
assert_eq!(VbType(0x06).type_name(), "Currency");
assert_eq!(VbType(0x07).type_name(), "Date");
assert_eq!(VbType(0x0A).type_name(), "Object");
assert_eq!(VbType(0x0B).type_name(), "Error");
assert_eq!(VbType(0x0C).type_name(), "Boolean");
assert_eq!(VbType(0x0D).type_name(), "Variant");
assert_eq!(VbType(0x0E).type_name(), "Decimal");
assert_eq!(VbType(0x10).type_name(), "Byte");
assert_eq!(VbType(0x1D).type_name(), "ExternalCOM");
assert_eq!(VbType(0x1E).type_name(), "DispatchPtr");
assert_eq!(VbType(0x1F).type_name(), "Unknown");
}
}