use std::fmt;
use crate::{
error::Error,
util::{read_u16_le, read_u32_le},
vb::controlprop::ControlPropertyIter,
};
#[derive(Clone, Copy, Debug)]
pub struct CleanupTable<'a> {
bytes: &'a [u8],
}
impl<'a> CleanupTable<'a> {
pub const HEADER_SIZE: usize = 0x0C;
pub fn parse(data: &'a [u8]) -> Option<Self> {
if data.len() < Self::HEADER_SIZE {
return None;
}
let size = read_u16_le(data, 0x00).ok()? as usize;
if size < Self::HEADER_SIZE || size > data.len() {
return None;
}
Some(Self {
bytes: data.get(..size)?,
})
}
#[inline]
pub fn size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x00)
}
#[inline]
pub fn count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x04)
}
#[inline]
pub fn total(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x06)
}
#[inline]
pub fn flags(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
#[inline]
pub fn has_entries(&self) -> bool {
self.total().unwrap_or(0) > 0
}
pub fn entries(&self) -> ControlPropertyIter<'a> {
if self.bytes.len() > Self::HEADER_SIZE {
let total = self.total().unwrap_or(0);
match self.bytes.get(Self::HEADER_SIZE..) {
Some(rest) => ControlPropertyIter::new(rest, total),
None => ControlPropertyIter::new(&[], 0),
}
} else {
ControlPropertyIter::new(&[], 0)
}
}
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
}
#[derive(Clone, Copy, Debug)]
pub struct ProcDscInfo<'a> {
bytes: &'a [u8],
}
impl<'a> ProcDscInfo<'a> {
pub const MIN_SIZE: usize = 0x1E;
pub const HEADER_SIZE: usize = 0x18;
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: "ProcDscInfo",
});
}
Ok(Self { bytes: data })
}
#[inline]
pub fn object_info_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x00)
}
#[inline]
pub fn arg_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x04)
}
#[inline]
pub fn frame_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x06)
}
#[inline]
pub fn pcode_back_offset(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x08)
}
#[inline]
pub fn proc_size(&self) -> Result<u16, Error> {
self.pcode_back_offset()
}
#[inline]
pub fn total_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x0A)
}
#[inline]
pub fn proc_opt_flags(&self) -> Result<ProcOptFlags, Error> {
Ok(ProcOptFlags(read_u16_le(self.bytes, 0x0C)?))
}
#[inline]
pub fn proc_opt_flags_raw(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x0C)
}
#[inline]
pub fn has_error_handler(&self) -> bool {
self.proc_opt_flags()
.map(|f| f.has_error_handler())
.unwrap_or(false)
}
#[inline]
pub fn has_resume_next(&self) -> bool {
self.proc_opt_flags()
.map(|f| f.has_resume_next())
.unwrap_or(false)
}
#[inline]
pub fn reserved_0e(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x0E)
}
#[inline]
pub fn bos_skip_table_offset(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x10)
}
#[inline]
pub fn base_iface_slot_count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x12)
}
#[inline]
pub fn reserved_14(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x14)
}
#[inline]
pub fn reserved_16(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x16)
}
#[inline]
pub fn cleanup_table_size(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x18)
}
#[inline]
pub fn reserved_1a(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x1A)
}
#[inline]
pub fn cleanup_count(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x1C)
}
#[inline]
pub fn cleanup_total(&self) -> u16 {
if self.bytes.len() > 0x1F {
read_u16_le(self.bytes, 0x1E).unwrap_or(0)
} else {
0
}
}
#[inline]
pub fn has_cleanup(&self) -> bool {
self.cleanup_count().unwrap_or(0) > 0 || self.cleanup_total() > 0
}
pub fn cleanup_table(&self) -> Option<CleanupTable<'a>> {
let offset = Self::HEADER_SIZE; let min_len = offset.checked_add(CleanupTable::HEADER_SIZE)?;
if self.bytes.len() > min_len {
CleanupTable::parse(self.bytes.get(offset..)?)
} else {
None
}
}
pub fn secondary_table(&self) -> Option<CleanupTable<'a>> {
let offset = self.total_size().ok()? as usize;
let min_len = offset.checked_add(CleanupTable::HEADER_SIZE)?;
if offset >= Self::HEADER_SIZE && self.bytes.len() > min_len {
CleanupTable::parse(self.bytes.get(offset..)?)
} else {
None
}
}
pub fn actual_size(&self) -> Result<usize, Error> {
let base = self.total_size()? as usize;
if let Some(secondary) = self.secondary_table() {
let sec_size = secondary.size()? as usize;
base.checked_add(sec_size).ok_or(Error::ArithmeticOverflow {
context: "ProcDscInfo::actual_size base + secondary",
})
} else {
Ok(base)
}
}
pub fn cleanup_entries(&self) -> ControlPropertyIter<'a> {
match self.cleanup_table() {
Some(table) => table.entries(),
None => ControlPropertyIter::new(&[], 0),
}
}
#[inline]
pub fn arg_count(&self) -> Result<u16, Error> {
Ok(self.arg_size()? / 4)
}
}
pub const OBJECT_INFO_CONSTANTS_OFFSET: usize = 0x34;
pub const OBJECT_INFO_MIN_SIZE: usize = OBJECT_INFO_CONSTANTS_OFFSET + 4;
#[inline]
pub fn read_constants_va(object_info_data: &[u8]) -> Result<u32, Error> {
read_u32_le(object_info_data, OBJECT_INFO_CONSTANTS_OFFSET)
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ProcOptFlags(pub u16);
impl ProcOptFlags {
pub const HAS_ERROR_HANDLER: u16 = 0x10;
pub const HAS_RESUME_NEXT: u16 = 0x20;
#[inline]
pub fn has(self, flag: u16) -> bool {
self.0 & flag != 0
}
#[inline]
pub fn has_error_handler(self) -> bool {
self.has(Self::HAS_ERROR_HANDLER)
}
#[inline]
pub fn has_resume_next(self) -> bool {
self.has(Self::HAS_RESUME_NEXT)
}
}
impl fmt::Debug for ProcOptFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ProcOptFlags(0x{:02X}", self.0)?;
if self.has_error_handler() {
write!(f, " HAS_ERROR_HANDLER")?;
}
if self.has_resume_next() {
write!(f, " | HAS_RESUME_NEXT")?;
}
write!(f, ")")
}
}
impl fmt::Display for ProcOptFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
pub mod pcode_frame {
pub const PCODE_IP: i32 = -0x5C;
pub const CONST_POOL_VA: i32 = -0x58;
pub const PROC_DSC_INFO: i32 = -0x50;
pub const ERROR_HANDLER_IP: i32 = -0x3C;
pub const ERROR_TARGET: i32 = -0x40;
pub const ENGINE_CONTEXT: i32 = -0x44;
pub const ENGINE_TLS: i32 = -0x48;
pub const PROC_FLAGS: i32 = -0x4C;
pub const OBJECT_PTR: i32 = -0x30;
pub const ERROR_STATE: i32 = -0x38;
pub const SAVED_PCODE_IP: i32 = -0x18;
pub const HANDLER_FN: i32 = -0x70;
pub const HOUSEKEEPING_SIZE: u32 = 0x88;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proc_dsc_info_parse() {
let mut data = vec![0u8; ProcDscInfo::MIN_SIZE];
data[0x04..0x06].copy_from_slice(&0x0010u16.to_le_bytes()); data[0x06..0x08].copy_from_slice(&0x0100u16.to_le_bytes()); data[0x08..0x0A].copy_from_slice(&0x0050u16.to_le_bytes()); data[0x0A..0x0C].copy_from_slice(&0x0024u16.to_le_bytes()); data[0x18..0x1A].copy_from_slice(&0x000Cu16.to_le_bytes()); data[0x1C..0x1E].copy_from_slice(&0x0000u16.to_le_bytes()); let pdi = ProcDscInfo::parse(&data).unwrap();
assert_eq!(pdi.arg_size().unwrap(), 0x0010);
assert_eq!(pdi.arg_count().unwrap(), 4);
assert_eq!(pdi.frame_size().unwrap(), 0x0100);
assert_eq!(pdi.pcode_back_offset().unwrap(), 0x0050);
assert_eq!(pdi.proc_size().unwrap(), 0x0050); assert_eq!(pdi.total_size().unwrap(), 0x0024);
assert_eq!(pdi.cleanup_table_size().unwrap(), 0x000C);
assert_eq!(pdi.cleanup_count().unwrap(), 0);
assert!(!pdi.has_cleanup());
}
#[test]
fn test_proc_dsc_info_with_error_handler() {
let mut data = vec![0u8; ProcDscInfo::MIN_SIZE];
data[0x0A..0x0C].copy_from_slice(&0x0054u16.to_le_bytes()); data[0x18..0x1A].copy_from_slice(&0x003Cu16.to_le_bytes()); data[0x1C..0x1E].copy_from_slice(&0x0001u16.to_le_bytes()); let pdi = ProcDscInfo::parse(&data).unwrap();
assert_eq!(pdi.total_size().unwrap(), 0x0054);
assert_eq!(pdi.cleanup_table_size().unwrap(), 0x003C);
assert_eq!(pdi.cleanup_count().unwrap(), 1);
assert!(pdi.has_cleanup());
assert_eq!(
ProcDscInfo::HEADER_SIZE as u16 + pdi.cleanup_table_size().unwrap(),
pdi.total_size().unwrap()
);
}
#[test]
fn test_proc_dsc_info_too_short() {
let data = vec![0u8; ProcDscInfo::MIN_SIZE - 1];
assert!(matches!(
ProcDscInfo::parse(&data),
Err(Error::TooShort { .. })
));
}
#[test]
fn test_proc_dsc_info_all_fields() {
let data = vec![0u8; ProcDscInfo::MIN_SIZE];
let pdi = ProcDscInfo::parse(&data).unwrap();
let _ = pdi.object_info_va().unwrap();
let _ = pdi.arg_size().unwrap();
let _ = pdi.arg_count().unwrap();
let _ = pdi.pcode_back_offset().unwrap();
let _ = pdi.total_size().unwrap();
let _ = pdi.proc_opt_flags().unwrap();
let _ = pdi.reserved_0e().unwrap();
let _ = pdi.bos_skip_table_offset().unwrap();
let _ = pdi.base_iface_slot_count().unwrap();
let _ = pdi.reserved_14().unwrap();
let _ = pdi.reserved_16().unwrap();
let _ = pdi.cleanup_table_size().unwrap();
let _ = pdi.reserved_1a().unwrap();
let _ = pdi.cleanup_count().unwrap();
let _ = pdi.has_cleanup();
}
#[test]
fn test_read_constants_va() {
let mut data = vec![0u8; OBJECT_INFO_MIN_SIZE];
data[0x34..0x38].copy_from_slice(&0x00405000u32.to_le_bytes());
assert_eq!(read_constants_va(&data).unwrap(), 0x00405000);
}
#[test]
fn test_real_method_2b() {
let data: [u8; 0x1E] = [
0xD8, 0x21, 0x41, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let pdi = ProcDscInfo::parse(&data).unwrap();
assert_eq!(pdi.object_info_va().unwrap(), 0x004121D8);
assert_eq!(pdi.arg_size().unwrap(), 16);
assert_eq!(pdi.arg_count().unwrap(), 4);
assert_eq!(pdi.frame_size().unwrap(), 8);
assert_eq!(pdi.pcode_back_offset().unwrap(), 8);
assert_eq!(pdi.total_size().unwrap(), 0x24);
assert!(!pdi.has_cleanup());
assert_eq!(
0x18 + pdi.cleanup_table_size().unwrap(),
pdi.total_size().unwrap()
);
}
}