use crate::{
ArrayDesc, ConstValue, CustDataEntry, CustDataIter, Error, FuncIter, FuncRecord, Guid,
GuidEntryIter, ImpFile, ImpFileIter, ImpInfo, ImpInfoIter, ImplTypeIter, NameIter, ParamIter,
ParameterInfo, TypeInfoEntry, TypeInfoIter, VarIter,
util::{
read_f32_le, read_f64_le, read_i16_le, read_i32_le, read_i64_le, read_u16_le, read_u32_le,
read_u64_le,
},
};
#[derive(Debug)]
pub enum ResolvedHreftype<'a> {
Internal(usize),
External {
imp_info: ImpInfo<'a>,
imp_file: Option<ImpFile<'a>>,
},
}
const SEG_TYPEINFO: usize = 0;
const SEG_IMPINFO: usize = 1;
const SEG_IMPFILES: usize = 2;
const SEG_REFTAB: usize = 3;
const SEG_GUIDHASH: usize = 4;
const SEG_GUIDTAB: usize = 5;
const SEG_NAMEHASH: usize = 6;
const SEG_NAMETAB: usize = 7;
const SEG_STRINGTAB: usize = 8;
const SEG_TYPDESC: usize = 9;
const SEG_ARRAYDESC: usize = 10;
const SEG_CUSTDATA: usize = 11;
const SEG_CDGUIDS: usize = 12;
const NUM_SEGMENTS: usize = 15;
pub struct TypeLib<'a> {
data: &'a [u8],
nrtypeinfos: usize,
offset_table_start: usize,
seg_offsets: [(i32, i32); NUM_SEGMENTS],
}
impl<'a> TypeLib<'a> {
pub const HEADER_SIZE: usize = 0x54;
pub const MAGIC: u32 = 0x5446534D;
pub const DISPATCH_HREF_OFFSET: u32 = 0x0100_0000;
const SEGDIR_ENTRY_SIZE: usize = 16;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::HEADER_SIZE {
return Err(Error::TooShort {
expected: Self::HEADER_SIZE,
actual: data.len(),
context: "MSFT header",
});
}
let magic = read_u32_le(data, 0x00).ok_or(Error::InvalidData {
context: "cannot read magic",
})?;
if magic != Self::MAGIC {
return Err(Error::BadMagic {
expected: Self::MAGIC,
got: magic,
});
}
let nrtypeinfos = read_i32_le(data, 0x20).ok_or(Error::InvalidData {
context: "cannot read nrtypeinfos",
})? as usize;
let offset_table_start = Self::HEADER_SIZE;
let segdir_start = offset_table_start
.checked_add(nrtypeinfos.checked_mul(4).ok_or(Error::InvalidData {
context: "typeinfo count overflow",
})?)
.and_then(|v| v.checked_add(4))
.ok_or(Error::InvalidData {
context: "segdir offset overflow",
})?;
let segdir_end = segdir_start
.checked_add(NUM_SEGMENTS * Self::SEGDIR_ENTRY_SIZE)
.ok_or(Error::InvalidData {
context: "segdir size overflow",
})?;
if data.len() < segdir_end {
return Err(Error::TooShort {
expected: segdir_end,
actual: data.len(),
context: "segment directory",
});
}
let mut seg_offsets = [(0i32, 0i32); NUM_SEGMENTS];
for (i, entry) in seg_offsets.iter_mut().enumerate() {
let pos = segdir_start + i * Self::SEGDIR_ENTRY_SIZE;
*entry = (
read_i32_le(data, pos).unwrap_or(-1),
read_i32_le(data, pos + 4).unwrap_or(0),
);
}
Ok(TypeLib {
data,
nrtypeinfos,
offset_table_start,
seg_offsets,
})
}
#[inline]
pub fn data(&self) -> &'a [u8] {
self.data
}
#[inline]
pub fn format_version(&self) -> u32 {
read_u32_le(self.data, 0x04).unwrap_or(0)
}
#[inline]
pub fn lib_guid_offset(&self) -> i32 {
read_i32_le(self.data, 0x08).unwrap_or(-1)
}
#[inline]
pub fn lcid(&self) -> u32 {
read_u32_le(self.data, 0x0C).unwrap_or(0)
}
#[inline]
pub fn lcid2(&self) -> u32 {
read_u32_le(self.data, 0x10).unwrap_or(0)
}
#[inline]
pub fn varflags(&self) -> u32 {
read_u32_le(self.data, 0x14).unwrap_or(0)
}
#[inline]
pub fn syskind(&self) -> u8 {
(self.varflags() & 0x0F) as u8
}
#[inline]
pub fn has_help_dll(&self) -> bool {
self.varflags() & 0x100 != 0
}
#[inline]
pub fn version(&self) -> u32 {
read_u32_le(self.data, 0x18).unwrap_or(0)
}
#[inline]
pub fn version_major(&self) -> u16 {
(self.version() & 0xFFFF) as u16
}
#[inline]
pub fn version_minor(&self) -> u16 {
(self.version() >> 16) as u16
}
#[inline]
pub fn flags(&self) -> u32 {
read_u32_le(self.data, 0x1C).unwrap_or(0)
}
#[inline]
pub fn typeinfo_count(&self) -> usize {
self.nrtypeinfos
}
#[inline]
pub fn helpstring_offset(&self) -> i32 {
read_i32_le(self.data, 0x24).unwrap_or(-1)
}
#[inline]
pub fn helpstringcontext(&self) -> i32 {
read_i32_le(self.data, 0x28).unwrap_or(0)
}
#[inline]
pub fn helpcontext(&self) -> i32 {
read_i32_le(self.data, 0x2C).unwrap_or(0)
}
#[inline]
pub fn nametablecount(&self) -> i32 {
read_i32_le(self.data, 0x30).unwrap_or(0)
}
#[inline]
pub fn nametablechars(&self) -> i32 {
read_i32_le(self.data, 0x34).unwrap_or(0)
}
#[inline]
pub fn name_offset(&self) -> i32 {
read_i32_le(self.data, 0x38).unwrap_or(-1)
}
#[inline]
pub fn helpfile_offset(&self) -> i32 {
read_i32_le(self.data, 0x3C).unwrap_or(-1)
}
#[inline]
pub fn custom_data_offset(&self) -> i32 {
read_i32_le(self.data, 0x40).unwrap_or(-1)
}
#[inline]
pub fn dispatchpos(&self) -> i32 {
read_i32_le(self.data, 0x4C).unwrap_or(-1)
}
#[inline]
pub fn nimpinfos(&self) -> i32 {
read_i32_le(self.data, 0x50).unwrap_or(0)
}
pub fn segment(&self, index: usize) -> Option<(usize, usize)> {
let &(offset, length) = self.seg_offsets.get(index)?;
if offset < 0 || length <= 0 {
return None;
}
Some((offset as usize, length as usize))
}
pub fn segment_data(&self, index: usize) -> Option<&'a [u8]> {
let (offset, length) = self.segment(index)?;
let end = offset.checked_add(length)?;
self.data.get(offset..end)
}
pub fn guid_hash_data(&self) -> Option<&'a [u8]> {
self.segment_data(SEG_GUIDHASH)
}
pub fn name_hash_data(&self) -> Option<&'a [u8]> {
self.segment_data(SEG_NAMEHASH)
}
pub fn name(&self, offset: i32) -> Option<&'a str> {
if offset < 0 {
return None;
}
let (seg_offset, _) = self.segment(SEG_NAMETAB)?;
let abs = seg_offset.checked_add(offset as usize)?;
let namelen = *self.data.get(abs.checked_add(8)?)? as usize;
let name_start = abs.checked_add(12)?;
let name_end = name_start.checked_add(namelen)?;
std::str::from_utf8(self.data.get(name_start..name_end)?).ok()
}
pub fn guid(&self, offset: i32) -> Option<Guid<'a>> {
if offset < 0 {
return None;
}
let (seg_offset, _) = self.segment(SEG_GUIDTAB)?;
let abs = seg_offset.checked_add(offset as usize)?;
let end = abs.checked_add(16)?;
Some(Guid::new(self.data.get(abs..end)?))
}
pub fn string(&self, offset: i32) -> Option<&'a str> {
if offset < 0 {
return None;
}
let (seg_offset, _) = self.segment(SEG_STRINGTAB)?;
let abs = seg_offset.checked_add(offset as usize)?;
let len = read_u16_le(self.data, abs)? as usize;
let start = abs.checked_add(2)?;
let end = start.checked_add(len)?;
std::str::from_utf8(self.data.get(start..end)?).ok()
}
pub fn lib_name(&self) -> Option<&'a str> {
self.name(self.name_offset())
}
pub fn lib_guid(&self) -> Option<Guid<'a>> {
self.guid(self.lib_guid_offset())
}
pub fn lib_helpstring(&self) -> Option<&'a str> {
self.string(self.helpstring_offset())
}
pub fn typeinfo_offset(&self, index: usize) -> Option<i32> {
if index >= self.nrtypeinfos {
return None;
}
let pos = self.offset_table_start.checked_add(index.checked_mul(4)?)?;
read_i32_le(self.data, pos)
}
pub fn member_names(&self, index: usize) -> Vec<&'a str> {
let ti_offset = match self.typeinfo_offset(index) {
Some(o) => o,
None => return Vec::new(),
};
let mut names: Vec<&'a str> = Vec::new();
for entry in self.names() {
if entry.hreftype() == ti_offset {
names.push(entry.name());
}
}
if !names.is_empty() {
names.remove(0);
}
names
}
pub fn typeinfo(&self, index: usize) -> Result<TypeInfoEntry<'a>, Error> {
if index >= self.nrtypeinfos {
return Err(Error::TypeInfoOutOfRange {
index,
count: self.nrtypeinfos,
});
}
let offset_pos =
self.offset_table_start
.checked_add(index * 4)
.ok_or(Error::InvalidData {
context: "offset table position overflow",
})?;
let ti_offset = read_i32_le(self.data, offset_pos).ok_or(Error::InvalidData {
context: "cannot read typeinfo offset",
})?;
if ti_offset < 0 {
return Err(Error::TypeInfoOutOfRange {
index,
count: self.nrtypeinfos,
});
}
let (seg_offset, _) = self.segment(SEG_TYPEINFO).ok_or(Error::InvalidData {
context: "typeinfo segment missing",
})?;
let abs = seg_offset
.checked_add(ti_offset as usize)
.ok_or(Error::InvalidData {
context: "typeinfo absolute offset overflow",
})?;
if abs + TypeInfoEntry::SIZE > self.data.len() {
return Err(Error::TooShort {
expected: TypeInfoEntry::SIZE,
actual: self.data.len().saturating_sub(abs),
context: "TypeInfoBase",
});
}
Ok(TypeInfoEntry::new(
&self.data[abs..abs + TypeInfoEntry::SIZE],
))
}
pub fn typeinfos(&'a self) -> TypeInfoIter<'a> {
TypeInfoIter::new(self)
}
pub fn funcs(&self, ti: &TypeInfoEntry<'_>) -> FuncIter<'a> {
let empty = FuncIter::new(self.data, 0, 0, 0);
let memoffset = ti.memoffset();
let func_count = ti.func_count();
if memoffset < 0 || func_count == 0 {
return empty;
}
let block_start = memoffset as usize;
let block_size = match read_u32_le(self.data, block_start) {
Some(v) => v as usize,
None => return empty,
};
let funcs_start = match block_start.checked_add(4) {
Some(v) => v,
None => return empty,
};
let end = match funcs_start.checked_add(block_size) {
Some(v) => v,
None => return empty,
};
FuncIter::new(self.data, funcs_start, end, func_count as usize)
}
pub fn vars(&self, ti: &TypeInfoEntry<'_>) -> VarIter<'a> {
let empty = VarIter::new(self.data, 0, 0, 0);
let memoffset = ti.memoffset();
let var_count = ti.var_count();
if memoffset < 0 || var_count == 0 {
return empty;
}
let block_start = memoffset as usize;
let block_size = match read_u32_le(self.data, block_start) {
Some(v) => v as usize,
None => return empty,
};
let mut pos = match block_start.checked_add(4) {
Some(v) => v,
None => return empty,
};
let end = match pos.checked_add(block_size) {
Some(v) => v,
None => return empty,
};
for _ in 0..ti.func_count() {
let size = match read_u32_le(self.data, pos) {
Some(v) => (v & 0xFFFF) as usize,
None => return empty,
};
if size == 0 {
break;
}
pos = match pos.checked_add(size) {
Some(v) => v,
None => return empty,
};
}
VarIter::new(self.data, pos, end, var_count as usize)
}
pub fn params(&self, func: &FuncRecord<'a>) -> ParamIter<'a> {
let nrargs = func.nrargs().max(0) as usize;
if nrargs == 0 {
return ParamIter::new(&[], 0, 0);
}
let record_len = func.as_bytes().len();
let params_size = match nrargs.checked_mul(ParameterInfo::SIZE) {
Some(v) => v,
None => return ParamIter::new(&[], 0, 0),
};
if params_size > record_len {
return ParamIter::new(&[], 0, 0);
}
ParamIter::new(func.as_bytes(), record_len - params_size, nrargs)
}
fn aux_start(&self, ti: &TypeInfoEntry<'_>) -> Option<usize> {
let memoffset = ti.memoffset();
if memoffset < 0 {
return None;
}
let block_start = memoffset as usize;
let block_size = read_u32_le(self.data, block_start)? as usize;
block_start.checked_add(4)?.checked_add(block_size)
}
fn read_aux_i32(&self, ti: &TypeInfoEntry<'_>, array_index: usize) -> Option<i32> {
let aux = self.aux_start(ti)?;
let pos = aux.checked_add(array_index.checked_mul(4)?)?;
read_i32_le(self.data, pos)
}
pub fn func_memid(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<i32> {
self.read_aux_i32(ti, index)
}
pub fn var_memid(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<i32> {
let fc = ti.func_count() as usize;
self.read_aux_i32(ti, fc.checked_add(index)?)
}
pub fn func_name(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<&'a str> {
let fc = ti.func_count() as usize;
let vc = ti.var_count() as usize;
let array_idx = fc.checked_add(vc)?.checked_add(index)?;
let name_offset = self.read_aux_i32(ti, array_idx)?;
self.name(name_offset)
}
pub fn var_name(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<&'a str> {
let fc = ti.func_count() as usize;
let vc = ti.var_count() as usize;
let array_idx = fc.checked_mul(2)?.checked_add(vc)?.checked_add(index)?;
let name_offset = self.read_aux_i32(ti, array_idx)?;
self.name(name_offset)
}
pub fn const_value(&self, offs_value: i32) -> Option<ConstValue<'a>> {
if offs_value < 0 {
let raw = offs_value as u32;
let vt = ((raw & 0x7C00_0000) >> 26) as u16;
let val = (raw & 0x03FF_FFFF) as i32;
return Some(match vt {
0 => ConstValue::Empty,
1 => ConstValue::Null,
2 => ConstValue::I2(val as i16),
3 => ConstValue::I4(val),
10 => ConstValue::Error(val),
11 => ConstValue::Bool(val != 0),
16 => ConstValue::I1(val as i8),
17 => ConstValue::UI1(val as u8),
18 => ConstValue::UI2(val as u16),
19 => ConstValue::UI4(val as u32),
22 => ConstValue::Int(val),
23 => ConstValue::UInt(val as u32),
24 => ConstValue::Void,
25 => ConstValue::Hresult(val),
_ => ConstValue::I4(val),
});
}
let (seg_offset, _) = self.segment(SEG_CUSTDATA)?;
let abs = seg_offset.checked_add(offs_value as usize)?;
let vt = read_u16_le(self.data, abs)?;
Some(match vt {
0 => ConstValue::Empty,
1 => ConstValue::Null,
2 => ConstValue::I2(read_i32_le(self.data, abs + 2)? as i16),
3 => ConstValue::I4(read_i32_le(self.data, abs + 2)?),
4 => ConstValue::R4(read_f32_le(self.data, abs + 2)?),
5 => ConstValue::R8(read_f64_le(self.data, abs + 2)?),
6 => ConstValue::Cy(read_i64_le(self.data, abs + 2)?),
7 => ConstValue::Date(read_f64_le(self.data, abs + 2)?),
8 => {
let len_raw = read_i32_le(self.data, abs + 2)?;
if len_raw < 0 {
return None;
}
let len = len_raw as usize;
let start = abs.checked_add(6)?;
let end = start.checked_add(len)?;
ConstValue::Bstr(self.data.get(start..end)?)
}
10 => ConstValue::Error(read_i32_le(self.data, abs + 2)?),
11 => ConstValue::Bool(read_i16_le(self.data, abs + 2)? != 0),
14 => {
let start = abs.checked_add(2)?;
let end = start.checked_add(16)?;
ConstValue::Decimal(self.data.get(start..end)?)
}
16 => ConstValue::I1(read_i32_le(self.data, abs + 2)? as i8),
17 => ConstValue::UI1(read_i32_le(self.data, abs + 2)? as u8),
18 => ConstValue::UI2(read_i32_le(self.data, abs + 2)? as u16),
19 => ConstValue::UI4(read_u32_le(self.data, abs + 2)?),
20 => ConstValue::I8(read_i64_le(self.data, abs + 2)?),
21 => ConstValue::UI8(read_u64_le(self.data, abs + 2)?),
22 => ConstValue::Int(read_i32_le(self.data, abs + 2)?),
23 => ConstValue::UInt(read_u32_le(self.data, abs + 2)?),
24 => ConstValue::Void,
25 => ConstValue::Hresult(read_i32_le(self.data, abs + 2)?),
64 => ConstValue::Filetime(read_u64_le(self.data, abs + 2)?),
_ => ConstValue::Raw {
vt,
bits: read_i32_le(self.data, abs + 2)?,
},
})
}
pub fn type_desc(&self, offset: i32) -> Option<(i32, i32)> {
if offset < 0 {
return None;
}
let (seg_offset, _) = self.segment(SEG_TYPDESC)?;
let abs = seg_offset.checked_add(offset as usize)?;
Some((
read_i32_le(self.data, abs)?,
read_i32_le(self.data, abs + 4)?,
))
}
pub fn resolve_hreftype(&self, hreftype: i32) -> Option<usize> {
if hreftype < 0 {
return None;
}
let index = hreftype as usize / TypeInfoEntry::SIZE;
if index < self.nrtypeinfos {
Some(index)
} else {
None
}
}
pub fn resolve_hreftype_full(&self, hreftype: i32) -> Option<ResolvedHreftype<'a>> {
if hreftype < 0 {
return None;
}
let effective = if (hreftype as u32) & Self::DISPATCH_HREF_OFFSET != 0 {
(hreftype as u32 & !Self::DISPATCH_HREF_OFFSET) as i32
} else {
hreftype
};
let index = effective as usize / TypeInfoEntry::SIZE;
if index < self.nrtypeinfos {
return Some(ResolvedHreftype::Internal(index));
}
let imp_byte_offset = (effective & !0x03) as usize;
let (seg_offset, seg_len) = self.segment(SEG_IMPINFO)?;
if imp_byte_offset + ImpInfo::SIZE > seg_len {
return None;
}
let abs = seg_offset + imp_byte_offset;
let imp_info = ImpInfo::new(&self.data[abs..abs + ImpInfo::SIZE]);
let imp_file = self.imp_file(imp_info.imp_file_offset());
Some(ResolvedHreftype::External { imp_info, imp_file })
}
pub fn guids(&self) -> GuidEntryIter<'a> {
let (offset, length) = self.segment(SEG_GUIDTAB).unwrap_or((0, 0));
GuidEntryIter::new(self.data, offset, offset.saturating_add(length))
}
pub fn names(&self) -> NameIter<'a> {
let (offset, length) = self.segment(SEG_NAMETAB).unwrap_or((0, 0));
NameIter::new(self.data, offset, offset.saturating_add(length), offset)
}
pub fn impl_types(&self, ti: &TypeInfoEntry<'_>) -> ImplTypeIter<'a> {
let ref_offset = ti.res2();
let (seg_offset, _) = self.segment(SEG_REFTAB).unwrap_or((0, 0));
ImplTypeIter::new(self.data, seg_offset, ref_offset)
}
pub fn imports(&self) -> ImpInfoIter<'a> {
let (offset, length) = self.segment(SEG_IMPINFO).unwrap_or((0, 0));
ImpInfoIter::new(self.data, offset, offset.saturating_add(length))
}
pub fn imp_files(&self) -> ImpFileIter<'a> {
let (offset, length) = self.segment(SEG_IMPFILES).unwrap_or((0, 0));
ImpFileIter::new(self.data, offset, offset.saturating_add(length))
}
pub fn imp_file(&self, offset: i32) -> Option<ImpFile<'a>> {
if offset < 0 {
return None;
}
let (seg_offset, seg_len) = self.segment(SEG_IMPFILES)?;
let abs = seg_offset.checked_add(offset as usize)?;
if abs.checked_add(ImpFile::HEADER_SIZE)? > seg_offset.checked_add(seg_len)? {
return None;
}
let size_field = read_u16_le(self.data, abs + 0x0C)?;
let name_len = (size_field >> 2) as usize;
let entry_size = (ImpFile::HEADER_SIZE + name_len + 3) & !3;
let end = abs.checked_add(entry_size)?;
if end > self.data.len() {
return None;
}
Some(ImpFile::new(&self.data[abs..end]))
}
pub fn array_desc(&self, offset: i32) -> Option<ArrayDesc<'a>> {
if offset < 0 {
return None;
}
let (seg_offset, _) = self.segment(SEG_ARRAYDESC)?;
let abs = seg_offset.checked_add(offset as usize)?;
if abs.checked_add(8)? > self.data.len() {
return None;
}
let num_dims = read_u16_le(self.data, abs + 4)? as usize;
let size = 8 + num_dims * 8;
let end = abs.checked_add(size)?;
if end > self.data.len() {
return None;
}
Some(ArrayDesc::new(&self.data[abs..end]))
}
pub fn cust_data(&self, offset: i32) -> CustDataIter<'a> {
let (seg_offset, _) = self.segment(SEG_CDGUIDS).unwrap_or((0, 0));
CustDataIter::new(self.data, seg_offset, offset)
}
pub fn lib_cust_data(&self) -> CustDataIter<'a> {
self.cust_data(self.custom_data_offset())
}
pub fn resolve_cust_data_entry(
&self,
entry: &CustDataEntry<'_>,
) -> Option<(Guid<'a>, ConstValue<'a>)> {
let guid = self.guid(entry.guid_offset())?;
let value = self.const_value(entry.data_offset())?;
Some((guid, value))
}
}