use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u32_le},
vb::{
bstr::BStr,
external::{CallApiStub, resolve_api_stub},
},
};
#[derive(Debug)]
pub enum ConstPoolEntry<'a> {
BStr(BStr<'a>),
Null,
RawVa(u32),
}
#[derive(Debug, Clone)]
pub struct ConstantPool<'a> {
map: &'a AddressMap<'a>,
data_const_va: u32,
}
impl<'a> ConstantPool<'a> {
pub fn new(map: &'a AddressMap<'a>, data_const_va: u32) -> Self {
Self { map, data_const_va }
}
#[inline]
pub fn data_const_va(&self) -> u32 {
self.data_const_va
}
pub fn read_u32(&self, offset: u16) -> Result<u32, Error> {
let va = self.data_const_va.wrapping_add(u32::from(offset));
let data = self.map.slice_from_va(va, 4)?;
read_u32_le(data, 0)
}
pub fn read_bstr(&self, offset: u16) -> Result<BStr<'a>, Error> {
let bstr_va = self.read_u32(offset)?;
self.resolve_bstr_at_va(bstr_va)
}
pub fn read_bstr_as_string(&self, offset: u16) -> Result<String, Error> {
Ok(self.read_bstr(offset)?.to_string_lossy())
}
pub fn string_at(&self, index: u16) -> Result<Option<BStr<'a>>, Error> {
let offset = index_to_offset(index, "ConstantPool::string_at")?;
let va = self.read_u32(offset)?;
if va == 0 {
return Ok(None);
}
Ok(self.try_parse_bstr(va))
}
pub fn api_stub_at(&self, index: u16) -> Result<Option<CallApiStub<'a>>, Error> {
let offset = index_to_offset(index, "ConstantPool::api_stub_at")?;
let va = self.read_u32(offset)?;
if va == 0 {
return Ok(None);
}
Ok(resolve_api_stub(self.map, va).ok())
}
pub fn resolve(&self, offset: u16) -> Result<ConstPoolEntry<'a>, Error> {
let va = self.read_u32(offset)?;
if va == 0 {
return Ok(ConstPoolEntry::Null);
}
match self.try_parse_bstr(va) {
Some(bstr) => Ok(ConstPoolEntry::BStr(bstr)),
None => Ok(ConstPoolEntry::RawVa(va)),
}
}
pub fn resolve_string(&self, offset: u16) -> Result<Option<String>, Error> {
match self.resolve(offset)? {
ConstPoolEntry::BStr(bstr) => Ok(Some(bstr.to_string_lossy())),
ConstPoolEntry::Null => Ok(Some(String::new())),
ConstPoolEntry::RawVa(_) => Ok(None),
}
}
pub fn entries(&self, count: u16) -> ConstPoolIter<'a> {
ConstPoolIter {
pool: self.clone(),
index: 0,
count,
}
}
#[inline]
pub fn entries_with_hints(&self, count: u16) -> ConstPoolIter<'a> {
self.entries(count)
}
pub fn bstr_entries(&self, count: u16) -> impl Iterator<Item = BStr<'a>> {
self.entries(count).filter_map(|(_, r)| match r {
Ok(ConstPoolEntry::BStr(b)) if b.va() != 0 && !b.is_empty() => Some(b),
_ => None,
})
}
fn try_parse_bstr(&self, va: u32) -> Option<BStr<'a>> {
let len_va = va.wrapping_sub(4);
let len_data = self.map.slice_from_va(len_va, 4).ok()?;
let byte_len = read_u32_le(len_data, 0).ok()?;
if byte_len == 0 {
return Some(BStr::new(va, 0, &[]));
}
if byte_len >= 0x10000 || byte_len % 2 != 0 {
return None;
}
let str_data = self.map.slice_from_va(va, byte_len as usize).ok()?;
let bytes = str_data.get(..byte_len as usize)?;
Some(BStr::new(va, byte_len, bytes))
}
pub fn resolve_bstr_at_va(&self, bstr_va: u32) -> Result<BStr<'a>, Error> {
if bstr_va == 0 {
return Ok(BStr::empty());
}
let len_va = bstr_va.wrapping_sub(4);
let len_data = self.map.slice_from_va(len_va, 4)?;
let byte_len = read_u32_le(len_data, 0)?;
if byte_len == 0 {
return Ok(BStr::new(bstr_va, 0, &[]));
}
let str_data = self.map.slice_from_va(bstr_va, byte_len as usize)?;
let bytes = str_data.get(..byte_len as usize).ok_or(Error::TooShort {
expected: byte_len as usize,
actual: str_data.len(),
context: "BSTR data",
})?;
Ok(BStr::new(bstr_va, byte_len, bytes))
}
pub fn read_ansi_string(&self, offset: u16) -> Result<&'a [u8], Error> {
let str_va = self.read_u32(offset)?;
if str_va == 0 {
return Ok(&[]);
}
let offset = self.map.va_to_offset(str_va)?;
read_cstr(self.map.file(), offset)
}
}
#[inline]
fn index_to_offset(index: u16, context: &'static str) -> Result<u16, Error> {
index
.checked_mul(4)
.ok_or(Error::ArithmeticOverflow { context })
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ConstPoolIter<'a> {
pool: ConstantPool<'a>,
index: u16,
count: u16,
}
impl<'a> Iterator for ConstPoolIter<'a> {
type Item = (u16, Result<ConstPoolEntry<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
return None;
}
let offset = self.index.saturating_mul(4);
self.index = self.index.saturating_add(1);
Some((offset, self.pool.resolve(offset)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::addressmap::SectionEntry;
fn make_test_map(file: &[u8]) -> AddressMap<'_> {
AddressMap::from_parts(
file,
0x00400000,
vec![SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x2000,
raw_data_offset: 0x200,
raw_data_size: 0x2000,
}],
)
}
#[test]
fn test_read_u32() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert_eq!(pool.read_u32(0).unwrap(), 0xDEADBEEF);
}
#[test]
fn test_read_u32_with_offset() {
let mut file = vec![0u8; 0x3000];
file[0x208..0x20C].copy_from_slice(&0x12345678u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert_eq!(pool.read_u32(8).unwrap(), 0x12345678);
}
#[test]
fn test_read_bstr() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0x00401104u32.to_le_bytes());
file[0x300..0x304].copy_from_slice(&10u32.to_le_bytes()); file[0x304] = b'H';
file[0x305] = 0;
file[0x306] = b'e';
file[0x307] = 0;
file[0x308] = b'l';
file[0x309] = 0;
file[0x30A] = b'l';
file[0x30B] = 0;
file[0x30C] = b'o';
file[0x30D] = 0;
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let bstr = pool.read_bstr(0).unwrap();
assert_eq!(bstr.byte_length(), 10);
assert_eq!(bstr.char_count(), 5);
assert_eq!(bstr.va(), 0x00401104);
assert_eq!(bstr.as_bytes().len(), 10);
let s = pool.read_bstr_as_string(0).unwrap();
assert_eq!(s, "Hello");
}
#[test]
fn test_read_bstr_null_pointer() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let bstr = pool.read_bstr(0).unwrap();
assert!(bstr.is_empty());
let s = pool.read_bstr_as_string(0).unwrap();
assert!(s.is_empty());
}
#[test]
fn test_read_bstr_zero_length() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0x00401104u32.to_le_bytes());
file[0x300..0x304].copy_from_slice(&0u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let bstr = pool.read_bstr(0).unwrap();
assert!(bstr.is_empty());
}
#[test]
fn test_read_ansi_string() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0x00401100u32.to_le_bytes());
file[0x300..0x306].copy_from_slice(b"Hello\0");
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let s = pool.read_ansi_string(0).unwrap();
assert_eq!(s, b"Hello");
}
#[test]
fn test_read_ansi_string_null_va() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let s = pool.read_ansi_string(0).unwrap();
assert!(s.is_empty());
}
#[test]
fn test_data_const_va_accessor() {
let file = vec![0u8; 0x3000];
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert_eq!(pool.data_const_va(), 0x00401000);
}
#[test]
fn test_resolve_null() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert!(
matches!(pool.resolve(0).unwrap(), ConstPoolEntry::Null),
"expected Null, got {:?}",
pool.resolve(0)
);
}
#[test]
fn test_resolve_bstr() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0x00401104u32.to_le_bytes());
file[0x300..0x304].copy_from_slice(&6u32.to_le_bytes());
file[0x304] = b'H';
file[0x305] = 0;
file[0x306] = b'i';
file[0x307] = 0;
file[0x308] = b'!';
file[0x309] = 0;
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let entry = pool.resolve(0).unwrap();
let ConstPoolEntry::BStr(bstr) = entry else {
panic!("expected BStr, got {entry:?}");
};
assert_eq!(bstr.byte_length(), 6);
assert_eq!(bstr.va(), 0x00401104);
let s = pool.resolve_string(0).unwrap();
assert_eq!(s, Some("Hi!".to_string()));
}
#[test]
fn test_resolve_raw_va() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0x00401104u32.to_le_bytes());
file[0x300..0x304].copy_from_slice(&7u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
let entry = pool.resolve(0).unwrap();
let ConstPoolEntry::RawVa(va) = entry else {
panic!("expected RawVa, got {entry:?}");
};
assert_eq!(va, 0x00401104);
assert_eq!(pool.resolve_string(0).unwrap(), None);
}
#[test]
fn test_resolve_string_null() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert_eq!(pool.resolve_string(0).unwrap(), Some(String::new()));
}
#[test]
fn test_string_at_indexed() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
file[0x204..0x208].copy_from_slice(&0x00401204u32.to_le_bytes());
file[0x400..0x404].copy_from_slice(&4u32.to_le_bytes());
file[0x404] = b'H';
file[0x405] = 0;
file[0x406] = b'i';
file[0x407] = 0;
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert!(pool.string_at(0).unwrap().is_none());
let bstr = pool
.string_at(1)
.unwrap()
.expect("entry 1 should be a BSTR");
assert_eq!(bstr.byte_length(), 4);
assert_eq!(bstr.to_string_lossy(), "Hi");
}
#[test]
fn test_api_stub_at_classification() {
let mut file = vec![0u8; 0x3000];
file[0x200..0x204].copy_from_slice(&0u32.to_le_bytes());
file[0x204..0x208].copy_from_slice(&0x00401300u32.to_le_bytes());
file[0x500] = 0x68;
file[0x501..0x505].copy_from_slice(&0x00401400u32.to_le_bytes());
file[0x600..0x604].copy_from_slice(&0x00401500u32.to_le_bytes());
file[0x604..0x608].copy_from_slice(&0x00401510u32.to_le_bytes());
file[0x700..0x709].copy_from_slice(b"kernel32\0");
file[0x710..0x71d].copy_from_slice(b"GetLastError\0");
file[0x208..0x20C].copy_from_slice(&0x00401204u32.to_le_bytes());
file[0x400..0x404].copy_from_slice(&4u32.to_le_bytes());
let map = make_test_map(&file);
let pool = ConstantPool::new(&map, 0x00401000);
assert!(pool.api_stub_at(0).unwrap().is_none());
let stub = pool
.api_stub_at(1)
.unwrap()
.expect("entry 1 should be an API stub");
assert_eq!(stub.library_name_bytes(&map).unwrap(), b"kernel32");
assert_eq!(stub.function_name_bytes(&map).unwrap(), b"GetLastError");
assert!(pool.api_stub_at(2).unwrap().is_none());
}
}