use crate::{
error::Error,
util::{read_fixed_cstr, read_u16_le, read_u32_le},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VbHeader<'a> {
bytes: &'a [u8],
}
impl<'a> VbHeader<'a> {
pub const SIZE: usize = 0x68;
pub const MAGIC: &'static [u8; 4] = b"VB5!";
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: "VbHeader",
})?;
let magic_slice = bytes.get(0..4).ok_or(Error::TooShort {
expected: 4,
actual: bytes.len(),
context: "VbHeader magic",
})?;
let magic = <[u8; 4]>::try_from(magic_slice).map_err(|_| Error::TooShort {
expected: 4,
actual: magic_slice.len(),
context: "VbHeader magic",
})?;
if &magic != Self::MAGIC {
return Err(Error::BadMagic {
expected: "VB5!",
got: magic,
});
}
Ok(Self { bytes })
}
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
#[inline]
pub fn magic(&self) -> &'a [u8] {
self.bytes.get(0x00..0x04).unwrap_or(&[])
}
#[inline]
pub fn runtime_build(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x04)
}
#[inline]
pub fn lang_dll(&self) -> Result<&'a [u8], Error> {
read_fixed_cstr(self.bytes, 0x06, 14)
}
#[inline]
pub fn sec_lang_dll(&self) -> Result<&'a [u8], Error> {
read_fixed_cstr(self.bytes, 0x14, 14)
}
#[inline]
pub fn runtime_revision(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x22)
}
#[inline]
pub fn lcid(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x24)
}
#[inline]
pub fn sec_lcid(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x28)
}
#[inline]
pub fn sub_main_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x2C)
}
#[inline]
pub fn project_data_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x30)
}
#[inline]
pub fn mdl_int_ctls(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x34)
}
#[inline]
pub fn mdl_int_ctls2(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x38)
}
#[inline]
pub fn thread_flags(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x3C)
}
#[inline]
pub fn thread_count(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x40)
}
#[inline]
pub fn form_count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x44)
}
#[inline]
pub fn external_count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x46)
}
#[inline]
pub fn thunk_count(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x48)
}
#[inline]
pub fn gui_table_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x4C)
}
#[inline]
pub fn external_table_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x50)
}
#[inline]
pub fn com_register_data_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x54)
}
#[inline]
pub fn project_description_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x58)
}
#[inline]
pub fn project_exe_name_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x5C)
}
#[inline]
pub fn project_help_file_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x60)
}
#[inline]
pub fn project_name_offset(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x64)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_vb_header() -> Vec<u8> {
let mut buf = vec![0u8; VbHeader::SIZE];
buf[0x00..0x04].copy_from_slice(b"VB5!");
buf[0x04..0x06].copy_from_slice(&9848u16.to_le_bytes());
buf[0x06..0x0F].copy_from_slice(b"VB6EN.DLL");
buf[0x22..0x24].copy_from_slice(&9u16.to_le_bytes());
buf[0x24..0x28].copy_from_slice(&0x0409u32.to_le_bytes());
buf[0x30..0x34].copy_from_slice(&0x00401234u32.to_le_bytes());
buf[0x3C..0x40].copy_from_slice(&0x01u32.to_le_bytes());
buf[0x44..0x46].copy_from_slice(&3u16.to_le_bytes());
buf[0x46..0x48].copy_from_slice(&2u16.to_le_bytes());
buf[0x64..0x68].copy_from_slice(&0x00405678u32.to_le_bytes());
buf
}
#[test]
fn test_parse_valid() {
let data = make_vb_header();
let hdr = VbHeader::parse(&data).unwrap();
assert_eq!(hdr.magic(), b"VB5!");
assert_eq!(hdr.runtime_build().unwrap(), 9848);
assert_eq!(hdr.lang_dll().unwrap(), b"VB6EN.DLL");
assert_eq!(hdr.runtime_revision().unwrap(), 9);
assert_eq!(hdr.lcid().unwrap(), 0x0409);
assert_eq!(hdr.project_data_va().unwrap(), 0x00401234);
assert_eq!(hdr.thread_flags().unwrap(), 0x01);
assert_eq!(hdr.form_count().unwrap(), 3);
assert_eq!(hdr.external_count().unwrap(), 2);
assert_eq!(hdr.project_name_offset().unwrap(), 0x00405678);
}
#[test]
fn test_parse_too_short() {
let data = vec![0u8; VbHeader::SIZE - 1];
assert_eq!(
VbHeader::parse(&data),
Err(Error::TooShort {
expected: VbHeader::SIZE,
actual: VbHeader::SIZE - 1,
context: "VbHeader",
})
);
}
#[test]
fn test_parse_bad_magic() {
let mut data = make_vb_header();
data[0..4].copy_from_slice(b"MZ\x90\x00");
assert_eq!(
VbHeader::parse(&data),
Err(Error::BadMagic {
expected: "VB5!",
got: [0x4D, 0x5A, 0x90, 0x00],
})
);
}
#[test]
fn test_parse_extra_bytes_ignored() {
let mut data = make_vb_header();
data.extend_from_slice(&[0xFF; 100]);
let hdr = VbHeader::parse(&data).unwrap();
assert_eq!(hdr.as_bytes().len(), VbHeader::SIZE);
}
#[test]
fn test_all_zero_fields() {
let mut data = vec![0u8; VbHeader::SIZE];
data[0..4].copy_from_slice(b"VB5!");
let hdr = VbHeader::parse(&data).unwrap();
assert_eq!(hdr.sub_main_va().unwrap(), 0);
assert_eq!(hdr.sec_lcid().unwrap(), 0);
assert_eq!(hdr.sec_lang_dll().unwrap(), b"");
assert_eq!(hdr.thread_count().unwrap(), 0);
assert_eq!(hdr.thunk_count().unwrap(), 0);
assert_eq!(hdr.gui_table_va().unwrap(), 0);
assert_eq!(hdr.external_table_va().unwrap(), 0);
assert_eq!(hdr.com_register_data_va().unwrap(), 0);
assert_eq!(hdr.project_description_offset().unwrap(), 0);
assert_eq!(hdr.project_exe_name_offset().unwrap(), 0);
assert_eq!(hdr.project_help_file_offset().unwrap(), 0);
assert_eq!(hdr.mdl_int_ctls().unwrap(), 0);
assert_eq!(hdr.mdl_int_ctls2().unwrap(), 0);
}
#[test]
fn test_copy_semantics() {
let data = make_vb_header();
let hdr1 = VbHeader::parse(&data).unwrap();
let hdr2 = hdr1; assert_eq!(hdr1.runtime_build().unwrap(), hdr2.runtime_build().unwrap());
}
}