use std::borrow::Cow;
use crate::{
addressmap::AddressMap,
error::Error,
util::{read_cstr, read_u32_le},
vb::{
control::{ControlInfo, Guid},
events::EventSinkVtable,
formdata::{FormControlType, FormDataParser},
},
};
#[derive(Debug)]
pub struct VbControl<'a> {
info: ControlInfo<'a>,
name: &'a [u8],
guid: Option<Guid>,
event_handler_vas: &'a [u8],
form_control_type: Option<FormControlType>,
}
impl<'a> VbControl<'a> {
#[inline]
pub fn info(&self) -> &ControlInfo<'a> {
&self.info
}
#[inline]
pub fn name(&self) -> Cow<'a, str> {
String::from_utf8_lossy(self.name)
}
#[inline]
pub fn name_bytes(&self) -> &'a [u8] {
self.name
}
#[inline]
pub fn control_type(&self) -> Result<u32, Error> {
self.info.control_type()
}
#[inline]
pub fn event_count(&self) -> Result<u16, Error> {
self.info.event_handler_slots()
}
#[inline]
pub fn index(&self) -> Result<u16, Error> {
self.info.index()
}
#[inline]
pub fn guid(&self) -> Option<&Guid> {
self.guid.as_ref()
}
#[inline]
pub fn form_control_type(&self) -> Option<FormControlType> {
self.form_control_type
}
pub fn class_name(&self) -> Option<&'static str> {
if let Some(fct) = self.form_control_type {
return Some(fct.name());
}
self.guid.as_ref().and_then(|g| g.control_class_name())
}
pub fn event_handler_va(&self, event_index: u16) -> Option<u32> {
let offset = (event_index as usize).checked_mul(4)?;
let end = offset.checked_add(4)?;
if end > self.event_handler_vas.len() {
return None;
}
read_u32_le(self.event_handler_vas, offset).ok()
}
pub fn event_sink<'p>(&self, map: &'p AddressMap<'a>) -> Option<EventSinkVtable<'a>> {
let va = self.info.event_sink_vtable_va().ok()?;
if va == 0 {
return None;
}
let slots = self.info.event_handler_slots().ok()?;
let size = EventSinkVtable::HEADER_SIZE.checked_add((slots as usize).checked_mul(4)?)?;
let data = map.slice_from_va(va, size).ok()?;
EventSinkVtable::parse(data, slots).ok()
}
pub fn connected_event_count(&self) -> Result<u16, Error> {
let mut count: u16 = 0;
for i in 0..self.event_count()? {
if self.event_handler_va(i).is_some_and(|va| va != 0) {
count = count.saturating_add(1);
}
}
Ok(count)
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ControlEntryIterator<'a, 'p> {
map: &'p AddressMap<'a>,
controls_va: u32,
index: u32,
total: u32,
form_data: Option<&'p FormDataParser<'a>>,
}
impl<'a, 'p> ControlEntryIterator<'a, 'p> {
pub fn new(map: &'p AddressMap<'a>, controls_va: u32, total: u32) -> Self {
Self {
map,
controls_va,
index: 0,
total,
form_data: None,
}
}
pub fn with_form_data(mut self, form_data: &'p FormDataParser<'a>) -> Self {
self.form_data = Some(form_data);
self
}
}
impl<'a, 'p> Iterator for ControlEntryIterator<'a, 'p> {
type Item = Result<VbControl<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.total || self.controls_va == 0 {
return None;
}
let offset = self.index.saturating_mul(ControlInfo::MIN_SIZE as u32);
let entry_va = self.controls_va.wrapping_add(offset);
self.index = self.index.saturating_add(1);
let data = match self.map.slice_from_va(entry_va, ControlInfo::MIN_SIZE) {
Ok(d) => d,
Err(e) => return Some(Err(e)),
};
let info = match ControlInfo::parse(data) {
Ok(c) => c,
Err(e) => return Some(Err(e)),
};
let name_va = match info.name_va() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let name: &[u8] = if name_va != 0 {
let off = match self.map.va_to_offset(name_va) {
Ok(o) => o,
Err(e) => return Some(Err(e)),
};
match read_cstr(self.map.file(), off) {
Ok(s) => s,
Err(e) => return Some(Err(e)),
}
} else {
b""
};
let guid_va = match info.guid_va() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let guid = if guid_va != 0 {
self.map
.slice_from_va(guid_va, 16)
.ok()
.and_then(Guid::from_bytes)
} else {
None
};
let dispid_va = match info.dispid_count_or_zero() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let slots = match info.event_handler_slots() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let event_handler_vas: &[u8] = if dispid_va != 0 && slots > 0 {
let size = (slots as usize).saturating_mul(4);
self.map.slice_from_va(dispid_va, size).unwrap_or(b"")
} else {
b""
};
let info_index = match info.index() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let form_control_type = self.form_data.and_then(|fd| {
fd.control_by_id(info_index as u8)
.map(|fc| fc.control_type())
});
Some(Ok(VbControl {
info,
name,
guid,
event_handler_vas,
form_control_type,
}))
}
}