use std::str;
use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u16_le, read_u32_le},
};
#[derive(Clone, Copy, Debug)]
pub struct VarStubDesc<'a> {
bytes: &'a [u8],
}
impl<'a> VarStubDesc<'a> {
pub const MIN_SIZE: usize = 0x0C;
pub fn parse(data: &'a [u8]) -> Option<Self> {
if data.len() < Self::MIN_SIZE {
return None;
}
Some(Self { bytes: data })
}
#[inline]
pub fn header_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x00)
}
#[inline]
pub fn data_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x02)
}
#[inline]
pub fn param_count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x06)
}
#[inline]
pub fn flags1(&self) -> u8 {
self.bytes.get(0x0A).copied().unwrap_or(0)
}
#[inline]
pub fn flags2(&self) -> u8 {
self.bytes.get(0x0B).copied().unwrap_or(0)
}
#[inline]
pub fn total_size(&self) -> usize {
let hdr = self.header_size().unwrap_or(0) as usize;
let data = self.data_size().unwrap_or(0) as usize;
hdr.saturating_add(data)
}
pub fn has_va_data(&self) -> bool {
let Ok(hdr) = self.header_size() else {
return false;
};
let Some(&first) = self.bytes.get(hdr as usize) else {
return false;
};
first != 0 && !(0x20..=0x7E).contains(&first)
}
pub fn resolve_api_names<'b>(&self, map: &AddressMap<'b>) -> Vec<&'b str> {
if !self.has_va_data() {
return Vec::new();
}
let Ok(hdr) = self.header_size() else {
return Vec::new();
};
let Some(data) = self.bytes.get(hdr as usize..) else {
return Vec::new();
};
let mut result = Vec::new();
let mut pos: usize = 0;
while pos.saturating_add(4) <= data.len() {
let chunk = match data.get(pos..pos.saturating_add(4)) {
Some(c) => c,
None => break,
};
let arr: [u8; 4] = match chunk.try_into() {
Ok(a) => a,
Err(_) => break,
};
let va = u32::from_le_bytes(arr);
pos = pos.saturating_add(4);
if va == 0 {
continue;
}
if let Ok(off) = map.va_to_offset(va)
&& let Ok(name) = read_cstr(map.file(), off)
&& let Ok(s) = str::from_utf8(name)
&& !s.is_empty()
{
result.push(s);
}
}
result
}
pub fn name(&self) -> &'a str {
let Ok(hdr) = self.header_size() else {
return "";
};
let Some(data) = self.bytes.get(hdr as usize..) else {
return "";
};
let start = data
.iter()
.take(4)
.position(|&b| b != 0)
.unwrap_or(4)
.min(data.len());
let Some(data) = data.get(start..) else {
return "";
};
let Some(&first) = data.first() else {
return "";
};
if !(0x20..=0x7E).contains(&first) {
return "";
}
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
match data.get(..end) {
Some(s) => str::from_utf8(s).unwrap_or(""),
None => "",
}
}
pub fn names(&self) -> Vec<&'a str> {
let Ok(hdr) = self.header_size() else {
return Vec::new();
};
let Some(data) = self.bytes.get(hdr as usize..) else {
return Vec::new();
};
let mut result = Vec::new();
let mut pos: usize = 0;
while pos < data.len() {
while data.get(pos).copied() == Some(0) {
pos = pos.saturating_add(1);
}
if pos >= data.len() {
break;
}
let start = pos;
while let Some(&b) = data.get(pos) {
if b == 0 {
break;
}
pos = pos.saturating_add(1);
}
if let Some(slice) = data.get(start..pos)
&& let Ok(s) = str::from_utf8(slice)
&& !s.is_empty()
{
result.push(s);
}
}
result
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct VarStubIter<'a> {
map: &'a AddressMap<'a>,
ptr_array_va: u32,
index: usize,
count: usize,
}
impl<'a> VarStubIter<'a> {
pub fn new(map: &'a AddressMap<'a>, va: u32, count: u16) -> Self {
Self {
map,
ptr_array_va: va,
index: 0,
count: count as usize,
}
}
}
impl<'a> Iterator for VarStubIter<'a> {
type Item = VarStubDesc<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
return None;
}
let offset = (self.index as u32).wrapping_mul(4);
let ptr_va = self.ptr_array_va.wrapping_add(offset);
self.index = self.index.saturating_add(1);
let ptr_data = self.map.slice_from_va(ptr_va, 4).ok()?;
let stub_va = read_u32_le(ptr_data, 0).ok()?;
if stub_va == 0 {
return None;
}
let header_data = self
.map
.slice_from_va(stub_va, VarStubDesc::MIN_SIZE)
.ok()?;
let hdr_size = read_u16_le(header_data, 0x00).ok()? as usize;
let data_size = read_u16_le(header_data, 0x02).ok()? as usize;
let total = hdr_size.saturating_add(data_size);
let full_data = self
.map
.slice_from_va(stub_va, total.max(VarStubDesc::MIN_SIZE))
.ok()?;
VarStubDesc::parse(full_data)
}
}
#[cfg(test)]
mod tests {
use super::*;
const STUB_DATEVAR: [u8; 28] = [
0x0C, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x04,
0x5F, 0x5F, 0x76, 0x62, 0x61, 0x44, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x00, 0x00, 0x00,
0x00,
];
const STUB_PACK: [u8; 20] = [
0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x04, 0x04, 0x50, 0x61, 0x63,
0x6B, 0x00, 0x00, 0x00, 0x00, ];
#[test]
fn test_simple_stub() {
let stub = VarStubDesc::parse(&STUB_DATEVAR).unwrap();
assert_eq!(stub.header_size().unwrap(), 0x0C);
assert_eq!(stub.data_size().unwrap(), 0x1C);
assert_eq!(stub.param_count().unwrap(), 0);
assert_eq!(stub.name(), "__vbaDateVar");
}
#[test]
fn test_method_stub() {
let stub = VarStubDesc::parse(&STUB_PACK).unwrap();
assert_eq!(stub.header_size().unwrap(), 0x0C);
assert_eq!(stub.data_size().unwrap(), 0x0C);
assert_eq!(stub.param_count().unwrap(), 0);
assert_eq!(stub.name(), "Pack");
}
#[test]
fn test_names() {
let stub = VarStubDesc::parse(&STUB_DATEVAR).unwrap();
let names = stub.names();
assert_eq!(names.len(), 1);
assert_eq!(names[0], "__vbaDateVar");
}
}