use std::fmt;
use crate::{addressmap::AddressMap, error::Error, util::read_u32_le};
#[derive(Clone, Copy, Debug)]
pub struct EventHandlerThunk {
pub event_dispatch_id: u32,
pub proc_dsc_info_va: u32,
pub return_handler_va: u32,
pub method_entry_va: u32,
}
impl EventHandlerThunk {
pub const SIZE: usize = 0x14;
pub const METHOD_ENTRY_OFFSET: usize = 0x07;
pub fn parse_from_event_entry(data: &[u8], event_entry_va: u32) -> Option<Self> {
let bytes: &[u8; Self::SIZE] = data.get(..Self::SIZE)?.try_into().ok()?;
if bytes[0] != 0xB8 {
return None;
}
let event_dispatch_id = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
if bytes[5] != 0x66 || bytes[6] != 0x3D {
return None;
}
if bytes[7] != 0x33 || bytes[8] != 0xC0 {
return None;
}
if bytes[9] != 0xBA {
return None;
}
let proc_dsc_info_va = u32::from_le_bytes([bytes[10], bytes[11], bytes[12], bytes[13]]);
if bytes[14] != 0x68 {
return None;
}
let return_handler_va = u32::from_le_bytes([bytes[15], bytes[16], bytes[17], bytes[18]]);
if bytes[19] != 0xC3 {
return None;
}
Some(Self {
event_dispatch_id,
proc_dsc_info_va,
return_handler_va,
method_entry_va: event_entry_va.wrapping_add(Self::METHOD_ENTRY_OFFSET as u32),
})
}
pub fn parse_from_method_entry(data: &[u8], method_entry_va: u32) -> Option<Self> {
if data.len() < Self::SIZE {
return None;
}
Self::parse_from_event_entry(
data,
method_entry_va.wrapping_sub(Self::METHOD_ENTRY_OFFSET as u32),
)
}
}
impl fmt::Display for EventHandlerThunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"event_id={} rtmi=0x{:08X} method=0x{:08X}",
self.event_dispatch_id, self.proc_dsc_info_va, self.method_entry_va
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct NativeEventThunk {
pub this_adjust: u32,
pub handler_va: u32,
}
impl NativeEventThunk {
pub const SIZE: usize = 13;
pub fn parse(data: &[u8], thunk_va: u32) -> Option<Self> {
let bytes: &[u8; Self::SIZE] = data.get(..Self::SIZE)?.try_into().ok()?;
if bytes[0..4] != [0x81, 0x6C, 0x24, 0x04] {
return None;
}
let this_adjust = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
if bytes[8] != 0xE9 {
return None;
}
let rel32 = i32::from_le_bytes([bytes[9], bytes[10], bytes[11], bytes[12]]);
let handler_va = thunk_va
.wrapping_add(Self::SIZE as u32)
.wrapping_add(rel32 as u32);
Some(Self {
this_adjust,
handler_va,
})
}
}
impl fmt::Display for NativeEventThunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"this_adjust=0x{:X} -> 0x{:08X}",
self.this_adjust, self.handler_va
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct IUnknownThunk {
pub iat_va: u32,
}
impl IUnknownThunk {
pub const SIZE: usize = 6;
pub fn parse(data: &[u8]) -> Option<Self> {
let bytes: &[u8; Self::SIZE] = data.get(..Self::SIZE)?.try_into().ok()?;
if bytes[0] != 0xFF || bytes[1] != 0x25 {
return None;
}
let iat_va = u32::from_le_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]);
Some(Self { iat_va })
}
}
impl fmt::Display for IUnknownThunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "jmp [0x{:08X}]", self.iat_va)
}
}
#[derive(Clone, Copy, Debug)]
pub struct EventSinkVtable<'a> {
bytes: &'a [u8],
handler_count: u16,
}
impl<'a> EventSinkVtable<'a> {
pub const HEADER_SIZE: usize = 0x18;
pub fn parse(data: &'a [u8], handler_count: u16) -> Result<Self, Error> {
let entries_size = (handler_count as usize).saturating_mul(4);
let total = Self::HEADER_SIZE.saturating_add(entries_size);
let bytes = data.get(..total).ok_or(Error::TooShort {
expected: total,
actual: data.len(),
context: "EventSinkVtable",
})?;
Ok(Self {
bytes,
handler_count,
})
}
#[inline]
pub fn control_info_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x04)
}
#[inline]
pub fn object_info_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x08)
}
#[inline]
pub fn query_interface_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x0C)
}
#[inline]
pub fn add_ref_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x10)
}
#[inline]
pub fn release_va(&self) -> Result<u32, Error> {
read_u32_le(self.bytes, 0x14)
}
#[inline]
pub fn handler_count(&self) -> u16 {
self.handler_count
}
pub fn handler_va(&self, slot: u16) -> Option<u32> {
if slot >= self.handler_count {
return None;
}
let offset = Self::HEADER_SIZE.checked_add((slot as usize).checked_mul(4)?)?;
read_u32_le(self.bytes, offset).ok()
}
pub fn resolve_handler_thunk(
&self,
slot: u16,
map: &AddressMap<'_>,
) -> Option<EventHandlerThunk> {
let va = self.handler_va(slot)?;
if va == 0 {
return None;
}
let data = map.slice_from_va(va, EventHandlerThunk::SIZE).ok()?;
EventHandlerThunk::parse_from_event_entry(data, va)
}
pub fn resolve_native_thunk(
&self,
slot: u16,
map: &AddressMap<'_>,
) -> Option<NativeEventThunk> {
let va = self.handler_va(slot)?;
if va == 0 {
return None;
}
let data = map.slice_from_va(va, NativeEventThunk::SIZE).ok()?;
NativeEventThunk::parse(data, va)
}
pub fn resolve_iunknown_thunk(&self, va: u32, map: &AddressMap<'_>) -> Option<IUnknownThunk> {
if va == 0 {
return None;
}
let data = map.slice_from_va(va, IUnknownThunk::SIZE).ok()?;
IUnknownThunk::parse(data)
}
pub fn connected_count(&self) -> u16 {
(0..self.handler_count)
.filter(|&i| self.handler_va(i).is_some_and(|va| va != 0))
.count() as u16
}
pub fn connected_handlers(&self) -> impl Iterator<Item = (u16, u32)> + '_ {
(0..self.handler_count).filter_map(|i| {
let va = self.handler_va(i)?;
if va != 0 { Some((i, va)) } else { None }
})
}
}