Skip to main content

msft_typelib/
typelib.rs

1//! [`TypeLib`] -- the main entry point for parsing MSFT type libraries.
2//!
3//! [`TypeLib::parse`] validates the header and caches the segment
4//! directory.  All subsequent lookups (`name`, `guid`, `string`,
5//! `typeinfo`, `type_desc`, `const_value`, etc.) are `O(1)` offset
6//! calculations into the backing `&[u8]`, except for iterators and
7//! chain-walking methods which are `O(n)` in the number of entries.
8
9use crate::{
10    ArrayDesc, ConstValue, CustDataEntry, CustDataIter, Error, FuncIter, FuncRecord, Guid,
11    GuidEntryIter, ImpFile, ImpFileIter, ImpInfo, ImpInfoIter, ImplTypeIter, NameIter, ParamIter,
12    ParameterInfo, TypeInfoEntry, TypeInfoIter, VarIter,
13    util::{
14        read_f32_le, read_f64_le, read_i16_le, read_i32_le, read_i64_le, read_u16_le, read_u32_le,
15        read_u64_le,
16    },
17};
18
19/// The result of resolving an `hreftype` via
20/// [`TypeLib::resolve_hreftype_full`].
21#[derive(Debug)]
22pub enum ResolvedHreftype<'a> {
23    /// An internal TypeInfo, identified by its index.
24    Internal(usize),
25    /// An external type from an imported library.
26    External {
27        /// The import-info entry describing this reference.
28        imp_info: ImpInfo<'a>,
29        /// The import file entry (library name, version, etc.), if resolvable.
30        imp_file: Option<ImpFile<'a>>,
31    },
32}
33
34/// Segment 0: TypeInfo table -- fixed 0x64-byte `MSFT_TypeInfoBase` entries.
35const SEG_TYPEINFO: usize = 0;
36/// Segment 1: Import-info table -- 12-byte `MSFT_ImpInfo` entries.
37const SEG_IMPINFO: usize = 1;
38/// Segment 2: Import-files table -- variable-length entries with filename, GUID, version.
39const SEG_IMPFILES: usize = 2;
40/// Segment 3: Reference table -- 16-byte linked-list entries for coclass impl-types.
41const SEG_REFTAB: usize = 3;
42/// Segment 4: GUID hash table -- bucket array for O(1) GUID lookup.
43const SEG_GUIDHASH: usize = 4;
44/// Segment 5: GUID table -- 24-byte entries (GUID + hreftype + hash chain).
45const SEG_GUIDTAB: usize = 5;
46/// Segment 6: Name hash table -- bucket array for O(1) name lookup.
47const SEG_NAMEHASH: usize = 6;
48/// Segment 7: Name table -- variable-length entries (12-byte header + name string).
49const SEG_NAMETAB: usize = 7;
50/// Segment 8: String table -- variable-length entries (2-byte length + string bytes).
51const SEG_STRINGTAB: usize = 8;
52/// Segment 9: Type-descriptor table -- 8-byte `(vt, extra)` pairs encoding `TYPEDESC` trees.
53const SEG_TYPDESC: usize = 9;
54/// Segment 10: Array-descriptor table -- variable-length `ARRAYDESC` entries.
55const SEG_ARRAYDESC: usize = 10;
56/// Segment 11: Custom-data table -- variant-tagged values (2-byte VT tag + payload).
57const SEG_CUSTDATA: usize = 11;
58/// Segment 12: Custom-data GUID directory -- 12-byte linked-list entries mapping GUIDs to values.
59const SEG_CDGUIDS: usize = 12;
60/// Total number of segments in the segment directory (indices 0..14; 13 and 14 are reserved).
61const NUM_SEGMENTS: usize = 15;
62
63/// Zero-copy view over an MSFT-format type library file.
64///
65/// Created via [`TypeLib::parse`], which validates the header and segment
66/// directory. All subsequent accessors borrow from the original byte slice
67/// without copying.
68///
69/// # Example
70///
71/// ```no_run
72/// # fn main() -> Result<(), msft_typelib::Error> {
73/// let data = std::fs::read("library.tlb").unwrap();
74/// let lib = msft_typelib::TypeLib::parse(&data)?;
75/// println!("{} typeinfos", lib.typeinfo_count());
76/// # Ok(())
77/// # }
78/// ```
79pub struct TypeLib<'a> {
80    /// Raw file data backing all zero-copy views.
81    data: &'a [u8],
82    /// Number of TypeInfo entries (from header offset 0x20).
83    nrtypeinfos: usize,
84    /// Byte offset of the TypeInfo offset table (immediately after the header).
85    offset_table_start: usize,
86    /// Cached segment directory: `(file_offset, length)` for each of the 15 segments.
87    seg_offsets: [(i32, i32); NUM_SEGMENTS],
88}
89
90impl<'a> TypeLib<'a> {
91    /// Size of the fixed MSFT header in bytes.
92    pub const HEADER_SIZE: usize = 0x54;
93    /// Expected magic value at offset 0 (`"MSFT"` in little-endian).
94    pub const MAGIC: u32 = 0x5446534D;
95    /// Offset added to hreftypes for dispatch-side references in dual interfaces.
96    pub const DISPATCH_HREF_OFFSET: u32 = 0x0100_0000;
97
98    /// Size of one segment-directory entry: `offset(i32) + length(i32) + res08(i32) + res0c(i32)`.
99    const SEGDIR_ENTRY_SIZE: usize = 16;
100
101    /// Parses an MSFT type library from raw file bytes.
102    ///
103    /// Validates the magic signature, reads the segment directory,
104    /// and returns a zero-copy view over the data.
105    ///
106    /// # Errors
107    ///
108    /// Returns [`Error::TooShort`] if the buffer is smaller than the
109    /// header or segment directory requires.
110    ///
111    /// Returns [`Error::BadMagic`] if the first four bytes are not `"MSFT"`.
112    ///
113    /// Returns [`Error::InvalidData`] if required fields cannot be read
114    /// or computed offsets overflow.
115    pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
116        if data.len() < Self::HEADER_SIZE {
117            return Err(Error::TooShort {
118                expected: Self::HEADER_SIZE,
119                actual: data.len(),
120                context: "MSFT header",
121            });
122        }
123
124        let magic = read_u32_le(data, 0x00).ok_or(Error::InvalidData {
125            context: "cannot read magic",
126        })?;
127        if magic != Self::MAGIC {
128            return Err(Error::BadMagic {
129                expected: Self::MAGIC,
130                got: magic,
131            });
132        }
133
134        let nrtypeinfos = read_i32_le(data, 0x20).ok_or(Error::InvalidData {
135            context: "cannot read nrtypeinfos",
136        })? as usize;
137
138        // Layout: Header(0x54) + OffsetTable(n*4) + ExtraField(4) + SegDir(15*16)
139        // Note: Wine conditionally includes the ExtraField based on
140        // `varflags & HELPDLLFLAG (0x100)`, but empirical testing shows
141        // it is always present.  See `has_help_dll()` for the flag accessor.
142        let offset_table_start = Self::HEADER_SIZE;
143        let segdir_start = offset_table_start
144            .checked_add(nrtypeinfos.checked_mul(4).ok_or(Error::InvalidData {
145                context: "typeinfo count overflow",
146            })?)
147            .and_then(|v| v.checked_add(4))
148            .ok_or(Error::InvalidData {
149                context: "segdir offset overflow",
150            })?;
151        let segdir_end = segdir_start
152            .checked_add(NUM_SEGMENTS * Self::SEGDIR_ENTRY_SIZE)
153            .ok_or(Error::InvalidData {
154                context: "segdir size overflow",
155            })?;
156
157        if data.len() < segdir_end {
158            return Err(Error::TooShort {
159                expected: segdir_end,
160                actual: data.len(),
161                context: "segment directory",
162            });
163        }
164
165        let mut seg_offsets = [(0i32, 0i32); NUM_SEGMENTS];
166        for (i, entry) in seg_offsets.iter_mut().enumerate() {
167            let pos = segdir_start + i * Self::SEGDIR_ENTRY_SIZE;
168            *entry = (
169                read_i32_le(data, pos).unwrap_or(-1),
170                read_i32_le(data, pos + 4).unwrap_or(0),
171            );
172        }
173
174        Ok(TypeLib {
175            data,
176            nrtypeinfos,
177            offset_table_start,
178            seg_offsets,
179        })
180    }
181
182    /// Returns the complete raw file data backing this type library.
183    #[inline]
184    pub fn data(&self) -> &'a [u8] {
185        self.data
186    }
187
188    /// Format version word at offset 0x04 (typically `0x00010002`).
189    #[inline]
190    pub fn format_version(&self) -> u32 {
191        read_u32_le(self.data, 0x04).unwrap_or(0)
192    }
193
194    /// Offset of the library GUID in the GUID table segment.
195    #[inline]
196    pub fn lib_guid_offset(&self) -> i32 {
197        read_i32_le(self.data, 0x08).unwrap_or(-1)
198    }
199
200    /// Locale identifier (LCID) of this type library.
201    #[inline]
202    pub fn lcid(&self) -> u32 {
203        read_u32_le(self.data, 0x0C).unwrap_or(0)
204    }
205
206    /// Secondary locale identifier at offset 0x10.
207    #[inline]
208    pub fn lcid2(&self) -> u32 {
209        read_u32_le(self.data, 0x10).unwrap_or(0)
210    }
211
212    /// Variable flags at offset 0x14.
213    ///
214    /// Bits 0-3 encode `SYSKIND` (target platform).
215    /// Bit 8 (`HELPDLLFLAG`) indicates a help-string DLL is present.
216    #[inline]
217    pub fn varflags(&self) -> u32 {
218        read_u32_le(self.data, 0x14).unwrap_or(0)
219    }
220
221    /// Target platform (`SYSKIND`, bits 0-3 of [`varflags`](Self::varflags)).
222    ///
223    /// 0 = Win16, 1 = Win32, 2 = Mac, 3 = Win64.
224    #[inline]
225    pub fn syskind(&self) -> u8 {
226        (self.varflags() & 0x0F) as u8
227    }
228
229    /// Whether a help-string DLL offset is present (`HELPDLLFLAG`, bit 8 of
230    /// [`varflags`](Self::varflags)).
231    #[inline]
232    pub fn has_help_dll(&self) -> bool {
233        self.varflags() & 0x100 != 0
234    }
235
236    /// Library version (set with `SetVersion`).
237    ///
238    /// Low 16 bits = major, high 16 bits = minor.
239    #[inline]
240    pub fn version(&self) -> u32 {
241        read_u32_le(self.data, 0x18).unwrap_or(0)
242    }
243
244    /// Major version number (low 16 bits of [`version`](Self::version)).
245    #[inline]
246    pub fn version_major(&self) -> u16 {
247        (self.version() & 0xFFFF) as u16
248    }
249
250    /// Minor version number (high 16 bits of [`version`](Self::version)).
251    #[inline]
252    pub fn version_minor(&self) -> u16 {
253        (self.version() >> 16) as u16
254    }
255
256    /// Library flags at offset 0x1C.
257    #[inline]
258    pub fn flags(&self) -> u32 {
259        read_u32_le(self.data, 0x1C).unwrap_or(0)
260    }
261
262    /// Number of TypeInfo entries in this library.
263    #[inline]
264    pub fn typeinfo_count(&self) -> usize {
265        self.nrtypeinfos
266    }
267
268    /// Offset of the help string in the string table, or `-1`.
269    #[inline]
270    pub fn helpstring_offset(&self) -> i32 {
271        read_i32_le(self.data, 0x24).unwrap_or(-1)
272    }
273
274    /// Library-level help string context at offset 0x28.
275    #[inline]
276    pub fn helpstringcontext(&self) -> i32 {
277        read_i32_le(self.data, 0x28).unwrap_or(0)
278    }
279
280    /// Library-level help context at offset 0x2C.
281    #[inline]
282    pub fn helpcontext(&self) -> i32 {
283        read_i32_le(self.data, 0x2C).unwrap_or(0)
284    }
285
286    /// Number of entries in the name table at offset 0x30.
287    #[inline]
288    pub fn nametablecount(&self) -> i32 {
289        read_i32_le(self.data, 0x30).unwrap_or(0)
290    }
291
292    /// Number of characters in the name table at offset 0x34.
293    #[inline]
294    pub fn nametablechars(&self) -> i32 {
295        read_i32_le(self.data, 0x34).unwrap_or(0)
296    }
297
298    /// Offset of the library name in the name table.
299    #[inline]
300    pub fn name_offset(&self) -> i32 {
301        read_i32_le(self.data, 0x38).unwrap_or(-1)
302    }
303
304    /// Offset of the help-file name in the string table, or `-1`.
305    #[inline]
306    pub fn helpfile_offset(&self) -> i32 {
307        read_i32_le(self.data, 0x3C).unwrap_or(-1)
308    }
309
310    /// Offset into the CDGuids directory for library-level custom data, or `-1`.
311    #[inline]
312    pub fn custom_data_offset(&self) -> i32 {
313        read_i32_le(self.data, 0x40).unwrap_or(-1)
314    }
315
316    /// Dispatch hreftype at offset 0x4C.
317    ///
318    /// For dual interfaces, this is the hreftype of the `IDispatch` base.
319    /// Used with [`DISPATCH_HREF_OFFSET`](Self::DISPATCH_HREF_OFFSET).
320    #[inline]
321    pub fn dispatchpos(&self) -> i32 {
322        read_i32_le(self.data, 0x4C).unwrap_or(-1)
323    }
324
325    /// Number of import-info entries.
326    #[inline]
327    pub fn nimpinfos(&self) -> i32 {
328        read_i32_le(self.data, 0x50).unwrap_or(0)
329    }
330
331    /// Returns `(file_offset, length)` for the segment at `index`.
332    ///
333    /// Returns `None` if the segment is empty (offset < 0 or length <= 0).
334    pub fn segment(&self, index: usize) -> Option<(usize, usize)> {
335        let &(offset, length) = self.seg_offsets.get(index)?;
336        if offset < 0 || length <= 0 {
337            return None;
338        }
339        Some((offset as usize, length as usize))
340    }
341
342    /// Returns the raw bytes for the segment at `index`.
343    ///
344    /// Returns `None` if the segment is empty or extends past the end of file.
345    pub fn segment_data(&self, index: usize) -> Option<&'a [u8]> {
346        let (offset, length) = self.segment(index)?;
347        let end = offset.checked_add(length)?;
348        self.data.get(offset..end)
349    }
350
351    /// Returns the raw GUID hash table data (segment 4).
352    pub fn guid_hash_data(&self) -> Option<&'a [u8]> {
353        self.segment_data(SEG_GUIDHASH)
354    }
355
356    /// Returns the raw name hash table data (segment 6).
357    pub fn name_hash_data(&self) -> Option<&'a [u8]> {
358        self.segment_data(SEG_NAMEHASH)
359    }
360
361    /// Looks up a name by byte offset into the name table segment.
362    ///
363    /// Returns `None` if `offset` is negative, the segment is missing,
364    /// the entry is out of bounds, or the bytes are not valid UTF-8.
365    pub fn name(&self, offset: i32) -> Option<&'a str> {
366        if offset < 0 {
367            return None;
368        }
369        let (seg_offset, _) = self.segment(SEG_NAMETAB)?;
370        let abs = seg_offset.checked_add(offset as usize)?;
371        // Header: hreftype(4) + next_hash(4) + namelen(1) + flags(1) + hashcode(2)
372        let namelen = *self.data.get(abs.checked_add(8)?)? as usize;
373        let name_start = abs.checked_add(12)?;
374        let name_end = name_start.checked_add(namelen)?;
375        std::str::from_utf8(self.data.get(name_start..name_end)?).ok()
376    }
377
378    /// Looks up a GUID by byte offset into the GUID table segment.
379    ///
380    /// Returns `None` if `offset` is negative, the segment is missing,
381    /// or the entry is out of bounds.
382    pub fn guid(&self, offset: i32) -> Option<Guid<'a>> {
383        if offset < 0 {
384            return None;
385        }
386        let (seg_offset, _) = self.segment(SEG_GUIDTAB)?;
387        let abs = seg_offset.checked_add(offset as usize)?;
388        let end = abs.checked_add(16)?;
389        Some(Guid::new(self.data.get(abs..end)?))
390    }
391
392    /// Looks up a string by byte offset into the string table segment.
393    ///
394    /// Returns `None` if `offset` is negative, the segment is missing,
395    /// out of bounds, or not valid UTF-8.
396    pub fn string(&self, offset: i32) -> Option<&'a str> {
397        if offset < 0 {
398            return None;
399        }
400        let (seg_offset, _) = self.segment(SEG_STRINGTAB)?;
401        let abs = seg_offset.checked_add(offset as usize)?;
402        let len = read_u16_le(self.data, abs)? as usize;
403        let start = abs.checked_add(2)?;
404        let end = start.checked_add(len)?;
405        std::str::from_utf8(self.data.get(start..end)?).ok()
406    }
407
408    /// Returns the library name from the name table.
409    pub fn lib_name(&self) -> Option<&'a str> {
410        self.name(self.name_offset())
411    }
412
413    /// Returns the library GUID from the GUID table.
414    pub fn lib_guid(&self) -> Option<Guid<'a>> {
415        self.guid(self.lib_guid_offset())
416    }
417
418    /// Returns the library help string from the string table.
419    pub fn lib_helpstring(&self) -> Option<&'a str> {
420        self.string(self.helpstring_offset())
421    }
422
423    /// Returns the byte offset of `TypeInfo[index]` within the TypeInfo segment.
424    ///
425    /// Returns `None` if `index >= typeinfo_count`.
426    pub fn typeinfo_offset(&self, index: usize) -> Option<i32> {
427        if index >= self.nrtypeinfos {
428            return None;
429        }
430        let pos = self.offset_table_start.checked_add(index.checked_mul(4)?)?;
431        read_i32_le(self.data, pos)
432    }
433
434    /// Collects member names for `TypeInfo[index]` by scanning the name table.
435    ///
436    /// Returns the ordered list of member names (excluding the TypeInfo's
437    /// own name). Names are matched by `hreftype == typeinfo_offset`.
438    pub fn member_names(&self, index: usize) -> Vec<&'a str> {
439        let ti_offset = match self.typeinfo_offset(index) {
440            Some(o) => o,
441            None => return Vec::new(),
442        };
443        let mut names: Vec<&'a str> = Vec::new();
444        for entry in self.names() {
445            if entry.hreftype() == ti_offset {
446                names.push(entry.name());
447            }
448        }
449        if !names.is_empty() {
450            names.remove(0);
451        }
452        names
453    }
454
455    /// Returns the `TypeInfo` entry at the given index.
456    ///
457    /// # Errors
458    ///
459    /// Returns [`Error::TypeInfoOutOfRange`] if `index >= typeinfo_count`.
460    ///
461    /// Returns [`Error::TooShort`] or [`Error::InvalidData`] if the
462    /// entry cannot be read from the file data.
463    pub fn typeinfo(&self, index: usize) -> Result<TypeInfoEntry<'a>, Error> {
464        if index >= self.nrtypeinfos {
465            return Err(Error::TypeInfoOutOfRange {
466                index,
467                count: self.nrtypeinfos,
468            });
469        }
470        let offset_pos =
471            self.offset_table_start
472                .checked_add(index * 4)
473                .ok_or(Error::InvalidData {
474                    context: "offset table position overflow",
475                })?;
476        let ti_offset = read_i32_le(self.data, offset_pos).ok_or(Error::InvalidData {
477            context: "cannot read typeinfo offset",
478        })?;
479        if ti_offset < 0 {
480            return Err(Error::TypeInfoOutOfRange {
481                index,
482                count: self.nrtypeinfos,
483            });
484        }
485        let (seg_offset, _) = self.segment(SEG_TYPEINFO).ok_or(Error::InvalidData {
486            context: "typeinfo segment missing",
487        })?;
488        let abs = seg_offset
489            .checked_add(ti_offset as usize)
490            .ok_or(Error::InvalidData {
491                context: "typeinfo absolute offset overflow",
492            })?;
493        if abs + TypeInfoEntry::SIZE > self.data.len() {
494            return Err(Error::TooShort {
495                expected: TypeInfoEntry::SIZE,
496                actual: self.data.len().saturating_sub(abs),
497                context: "TypeInfoBase",
498            });
499        }
500        Ok(TypeInfoEntry::new(
501            &self.data[abs..abs + TypeInfoEntry::SIZE],
502        ))
503    }
504
505    /// Returns an iterator over all TypeInfo entries.
506    pub fn typeinfos(&'a self) -> TypeInfoIter<'a> {
507        TypeInfoIter::new(self)
508    }
509
510    /// Returns an iterator over [`FuncRecord`] values for the given TypeInfo.
511    ///
512    /// Returns an empty iterator if the TypeInfo has no functions or its
513    /// `memoffset` is invalid.
514    pub fn funcs(&self, ti: &TypeInfoEntry<'_>) -> FuncIter<'a> {
515        let empty = FuncIter::new(self.data, 0, 0, 0);
516        let memoffset = ti.memoffset();
517        let func_count = ti.func_count();
518        if memoffset < 0 || func_count == 0 {
519            return empty;
520        }
521        let block_start = memoffset as usize;
522        let block_size = match read_u32_le(self.data, block_start) {
523            Some(v) => v as usize,
524            None => return empty,
525        };
526        let funcs_start = match block_start.checked_add(4) {
527            Some(v) => v,
528            None => return empty,
529        };
530        let end = match funcs_start.checked_add(block_size) {
531            Some(v) => v,
532            None => return empty,
533        };
534        FuncIter::new(self.data, funcs_start, end, func_count as usize)
535    }
536
537    /// Returns an iterator over [`VarRecord`](crate::VarRecord) values for the given TypeInfo.
538    ///
539    /// Internally skips past all function records first, since variable
540    /// records follow functions in the data block.
541    ///
542    /// Returns an empty iterator if the TypeInfo has no variables or its
543    /// `memoffset` is invalid.
544    pub fn vars(&self, ti: &TypeInfoEntry<'_>) -> VarIter<'a> {
545        let empty = VarIter::new(self.data, 0, 0, 0);
546        let memoffset = ti.memoffset();
547        let var_count = ti.var_count();
548        if memoffset < 0 || var_count == 0 {
549            return empty;
550        }
551        let block_start = memoffset as usize;
552        let block_size = match read_u32_le(self.data, block_start) {
553            Some(v) => v as usize,
554            None => return empty,
555        };
556        let mut pos = match block_start.checked_add(4) {
557            Some(v) => v,
558            None => return empty,
559        };
560        let end = match pos.checked_add(block_size) {
561            Some(v) => v,
562            None => return empty,
563        };
564        // Skip all func records
565        for _ in 0..ti.func_count() {
566            let size = match read_u32_le(self.data, pos) {
567                Some(v) => (v & 0xFFFF) as usize,
568                None => return empty,
569            };
570            if size == 0 {
571                break;
572            }
573            pos = match pos.checked_add(size) {
574                Some(v) => v,
575                None => return empty,
576            };
577        }
578        VarIter::new(self.data, pos, end, var_count as usize)
579    }
580
581    /// Returns an iterator over [`ParameterInfo`] entries for a function.
582    ///
583    /// Parameters occupy the last `nrargs * 12` bytes of the record.
584    /// Returns an empty iterator if the function has no parameters.
585    pub fn params(&self, func: &FuncRecord<'a>) -> ParamIter<'a> {
586        let nrargs = func.nrargs().max(0) as usize;
587        if nrargs == 0 {
588            return ParamIter::new(&[], 0, 0);
589        }
590        let record_len = func.as_bytes().len();
591        let params_size = match nrargs.checked_mul(ParameterInfo::SIZE) {
592            Some(v) => v,
593            None => return ParamIter::new(&[], 0, 0),
594        };
595        if params_size > record_len {
596            return ParamIter::new(&[], 0, 0);
597        }
598        ParamIter::new(func.as_bytes(), record_len - params_size, nrargs)
599    }
600
601    /// Computes the file offset where auxiliary arrays begin for a TypeInfo.
602    ///
603    /// Each TypeInfo's func/var data block has the layout:
604    ///
605    /// ```text
606    /// [block_size: u32] [func records...] [var records...] [aux arrays...]
607    /// ```
608    ///
609    /// The auxiliary arrays immediately follow the last record and contain
610    /// five parallel arrays of `i32` values:
611    ///
612    /// 1. `func_memids[func_count]` -- MEMBERID / DISPID for each function
613    /// 2. `var_memids[var_count]` -- MEMBERID for each variable
614    /// 3. `func_names[func_count]` -- name-table offset for each function
615    /// 4. `var_names[var_count]` -- name-table offset for each variable
616    /// 5. `record_offsets[func_count + var_count]` -- byte offsets of records
617    ///
618    /// Returns `None` if the TypeInfo has no data block (`memoffset < 0`).
619    fn aux_start(&self, ti: &TypeInfoEntry<'_>) -> Option<usize> {
620        let memoffset = ti.memoffset();
621        if memoffset < 0 {
622            return None;
623        }
624        let block_start = memoffset as usize;
625        let block_size = read_u32_le(self.data, block_start)? as usize;
626        block_start.checked_add(4)?.checked_add(block_size)
627    }
628
629    /// Reads one `i32` from the auxiliary arrays at the given flat index.
630    ///
631    /// The flat index spans all five sub-arrays contiguously.  Callers
632    /// (e.g. [`func_memid`](Self::func_memid), [`var_name`](Self::var_name))
633    /// compute the correct index from the func/var counts.
634    fn read_aux_i32(&self, ti: &TypeInfoEntry<'_>, array_index: usize) -> Option<i32> {
635        let aux = self.aux_start(ti)?;
636        let pos = aux.checked_add(array_index.checked_mul(4)?)?;
637        read_i32_le(self.data, pos)
638    }
639
640    /// Returns the MEMBERID (DISPID) for `function[index]`.
641    ///
642    /// Read from the auxiliary array that follows all func/var records.
643    /// Returns `None` if the TypeInfo has no data block or `index` is
644    /// out of bounds.
645    pub fn func_memid(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<i32> {
646        self.read_aux_i32(ti, index)
647    }
648
649    /// Returns the MEMBERID for `variable[index]`.
650    ///
651    /// Read from the auxiliary array that follows all func/var records.
652    /// Returns `None` if the TypeInfo has no data block or `index` is
653    /// out of bounds.
654    pub fn var_memid(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<i32> {
655        let fc = ti.func_count() as usize;
656        self.read_aux_i32(ti, fc.checked_add(index)?)
657    }
658
659    /// Returns the name of `function[index]` from the auxiliary name array.
660    ///
661    /// Returns `None` if the TypeInfo has no data block, `index` is out of
662    /// bounds, or the name-table offset is invalid.
663    pub fn func_name(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<&'a str> {
664        let fc = ti.func_count() as usize;
665        let vc = ti.var_count() as usize;
666        let array_idx = fc.checked_add(vc)?.checked_add(index)?;
667        let name_offset = self.read_aux_i32(ti, array_idx)?;
668        self.name(name_offset)
669    }
670
671    /// Returns the name of `variable[index]` from the auxiliary name array.
672    ///
673    /// Returns `None` if the TypeInfo has no data block, `index` is out of
674    /// bounds, or the name-table offset is invalid.
675    pub fn var_name(&self, ti: &TypeInfoEntry<'_>, index: usize) -> Option<&'a str> {
676        let fc = ti.func_count() as usize;
677        let vc = ti.var_count() as usize;
678        let array_idx = fc.checked_mul(2)?.checked_add(vc)?.checked_add(index)?;
679        let name_offset = self.read_aux_i32(ti, array_idx)?;
680        self.name(name_offset)
681    }
682
683    /// Decodes a constant value from an `OffsValue` field.
684    ///
685    /// If `offs_value < 0` the value is packed inline:
686    /// - VT code = `(offs_value >> 26) & 0x1F`
687    /// - unsigned 26-bit value = `offs_value & 0x03FF_FFFF`
688    ///
689    /// If `offs_value >= 0` the value is stored in the custom-data segment
690    /// at the given byte offset (2-byte VT tag + value bytes).
691    ///
692    /// Returns `None` if the offset is non-negative but the custom-data
693    /// segment is missing or the entry is out of bounds.
694    pub fn const_value(&self, offs_value: i32) -> Option<ConstValue<'a>> {
695        if offs_value < 0 {
696            let raw = offs_value as u32;
697            let vt = ((raw & 0x7C00_0000) >> 26) as u16;
698            let val = (raw & 0x03FF_FFFF) as i32;
699            return Some(match vt {
700                0 => ConstValue::Empty,
701                1 => ConstValue::Null,
702                2 => ConstValue::I2(val as i16),
703                3 => ConstValue::I4(val),
704                10 => ConstValue::Error(val),
705                11 => ConstValue::Bool(val != 0),
706                16 => ConstValue::I1(val as i8),
707                17 => ConstValue::UI1(val as u8),
708                18 => ConstValue::UI2(val as u16),
709                19 => ConstValue::UI4(val as u32),
710                22 => ConstValue::Int(val),
711                23 => ConstValue::UInt(val as u32),
712                24 => ConstValue::Void,
713                25 => ConstValue::Hresult(val),
714                _ => ConstValue::I4(val),
715            });
716        }
717        let (seg_offset, _) = self.segment(SEG_CUSTDATA)?;
718        let abs = seg_offset.checked_add(offs_value as usize)?;
719        let vt = read_u16_le(self.data, abs)?;
720        Some(match vt {
721            0 => ConstValue::Empty,
722            1 => ConstValue::Null,
723            2 => ConstValue::I2(read_i32_le(self.data, abs + 2)? as i16),
724            3 => ConstValue::I4(read_i32_le(self.data, abs + 2)?),
725            4 => ConstValue::R4(read_f32_le(self.data, abs + 2)?),
726            5 => ConstValue::R8(read_f64_le(self.data, abs + 2)?),
727            6 => ConstValue::Cy(read_i64_le(self.data, abs + 2)?),
728            7 => ConstValue::Date(read_f64_le(self.data, abs + 2)?),
729            8 => {
730                let len_raw = read_i32_le(self.data, abs + 2)?;
731                if len_raw < 0 {
732                    return None;
733                }
734                let len = len_raw as usize;
735                let start = abs.checked_add(6)?;
736                let end = start.checked_add(len)?;
737                ConstValue::Bstr(self.data.get(start..end)?)
738            }
739            10 => ConstValue::Error(read_i32_le(self.data, abs + 2)?),
740            11 => ConstValue::Bool(read_i16_le(self.data, abs + 2)? != 0),
741            14 => {
742                let start = abs.checked_add(2)?;
743                let end = start.checked_add(16)?;
744                ConstValue::Decimal(self.data.get(start..end)?)
745            }
746            16 => ConstValue::I1(read_i32_le(self.data, abs + 2)? as i8),
747            17 => ConstValue::UI1(read_i32_le(self.data, abs + 2)? as u8),
748            18 => ConstValue::UI2(read_i32_le(self.data, abs + 2)? as u16),
749            19 => ConstValue::UI4(read_u32_le(self.data, abs + 2)?),
750            20 => ConstValue::I8(read_i64_le(self.data, abs + 2)?),
751            21 => ConstValue::UI8(read_u64_le(self.data, abs + 2)?),
752            22 => ConstValue::Int(read_i32_le(self.data, abs + 2)?),
753            23 => ConstValue::UInt(read_u32_le(self.data, abs + 2)?),
754            24 => ConstValue::Void,
755            25 => ConstValue::Hresult(read_i32_le(self.data, abs + 2)?),
756            64 => ConstValue::Filetime(read_u64_le(self.data, abs + 2)?),
757            _ => ConstValue::Raw {
758                vt,
759                bits: read_i32_le(self.data, abs + 2)?,
760            },
761        })
762    }
763
764    /// Reads a type-descriptor table entry at the given byte offset.
765    ///
766    /// Returns `(vt, extra)` where:
767    /// - For `VT_PTR` (26): `extra` is the pointed-to type descriptor.
768    /// - For `VT_USERDEFINED` (29): `extra` is the hreftype.
769    /// - For `VT_SAFEARRAY` (27): `extra` is the element type descriptor.
770    ///
771    /// Returns `None` if `offset` is negative, the segment is missing,
772    /// or the entry is out of bounds.
773    pub fn type_desc(&self, offset: i32) -> Option<(i32, i32)> {
774        if offset < 0 {
775            return None;
776        }
777        let (seg_offset, _) = self.segment(SEG_TYPDESC)?;
778        let abs = seg_offset.checked_add(offset as usize)?;
779        Some((
780            read_i32_le(self.data, abs)?,
781            read_i32_le(self.data, abs + 4)?,
782        ))
783    }
784
785    /// Resolves an `hreftype` to a TypeInfo index (internal references only).
786    ///
787    /// For internal references the hreftype is the byte offset of the
788    /// TypeInfo within the TypeInfo segment; dividing by
789    /// [`TypeInfoEntry::SIZE`] gives the index.
790    ///
791    /// Returns `None` if `hreftype` is negative or the computed index
792    /// is out of range. For full resolution including external and
793    /// dispatch references, use [`resolve_hreftype_full`](Self::resolve_hreftype_full).
794    pub fn resolve_hreftype(&self, hreftype: i32) -> Option<usize> {
795        if hreftype < 0 {
796            return None;
797        }
798        let index = hreftype as usize / TypeInfoEntry::SIZE;
799        if index < self.nrtypeinfos {
800            Some(index)
801        } else {
802            None
803        }
804    }
805
806    /// Resolves an `hreftype` to either an internal TypeInfo index or an
807    /// external import reference.
808    ///
809    /// Handles [`DISPATCH_HREF_OFFSET`](Self::DISPATCH_HREF_OFFSET) by
810    /// stripping the flag before resolution. Returns `None` if `hreftype`
811    /// is negative.
812    pub fn resolve_hreftype_full(&self, hreftype: i32) -> Option<ResolvedHreftype<'a>> {
813        if hreftype < 0 {
814            return None;
815        }
816        // Strip DISPATCH_HREF_OFFSET if present
817        let effective = if (hreftype as u32) & Self::DISPATCH_HREF_OFFSET != 0 {
818            (hreftype as u32 & !Self::DISPATCH_HREF_OFFSET) as i32
819        } else {
820            hreftype
821        };
822
823        // Try internal first
824        let index = effective as usize / TypeInfoEntry::SIZE;
825        if index < self.nrtypeinfos {
826            return Some(ResolvedHreftype::Internal(index));
827        }
828
829        // External: hreftype (with low 2 bits masked) is a byte offset into ImpInfo segment
830        let imp_byte_offset = (effective & !0x03) as usize;
831        let (seg_offset, seg_len) = self.segment(SEG_IMPINFO)?;
832        if imp_byte_offset + ImpInfo::SIZE > seg_len {
833            return None;
834        }
835        let abs = seg_offset + imp_byte_offset;
836        let imp_info = ImpInfo::new(&self.data[abs..abs + ImpInfo::SIZE]);
837        let imp_file = self.imp_file(imp_info.imp_file_offset());
838        Some(ResolvedHreftype::External { imp_info, imp_file })
839    }
840
841    /// Returns an iterator over all [`GuidEntry`](crate::GuidEntry) values in the GUID table.
842    pub fn guids(&self) -> GuidEntryIter<'a> {
843        let (offset, length) = self.segment(SEG_GUIDTAB).unwrap_or((0, 0));
844        GuidEntryIter::new(self.data, offset, offset.saturating_add(length))
845    }
846
847    /// Returns an iterator over all entries in the name table.
848    pub fn names(&self) -> NameIter<'a> {
849        let (offset, length) = self.segment(SEG_NAMETAB).unwrap_or((0, 0));
850        NameIter::new(self.data, offset, offset.saturating_add(length), offset)
851    }
852
853    /// Returns an iterator over implemented interfaces for a coclass.
854    ///
855    /// Traverses the linked list in the reference table starting from
856    /// the TypeInfo's `res2` field.
857    pub fn impl_types(&self, ti: &TypeInfoEntry<'_>) -> ImplTypeIter<'a> {
858        let ref_offset = ti.res2();
859        let (seg_offset, _) = self.segment(SEG_REFTAB).unwrap_or((0, 0));
860        ImplTypeIter::new(self.data, seg_offset, ref_offset)
861    }
862
863    /// Returns an iterator over [`ImpInfo`](crate::ImpInfo) entries in the import table.
864    pub fn imports(&self) -> ImpInfoIter<'a> {
865        let (offset, length) = self.segment(SEG_IMPINFO).unwrap_or((0, 0));
866        ImpInfoIter::new(self.data, offset, offset.saturating_add(length))
867    }
868
869    /// Returns an iterator over [`ImpFile`](crate::ImpFile) entries in the import-files
870    /// segment (segment 2).
871    pub fn imp_files(&self) -> ImpFileIter<'a> {
872        let (offset, length) = self.segment(SEG_IMPFILES).unwrap_or((0, 0));
873        ImpFileIter::new(self.data, offset, offset.saturating_add(length))
874    }
875
876    /// Looks up an import file by byte offset into the import-files segment.
877    ///
878    /// Use this to resolve [`ImpInfo::imp_file_offset`](crate::ImpInfo::imp_file_offset)
879    /// to an [`ImpFile`](crate::ImpFile).
880    ///
881    /// Returns `None` if `offset` is negative, the segment is missing,
882    /// or the entry is out of bounds.
883    pub fn imp_file(&self, offset: i32) -> Option<ImpFile<'a>> {
884        if offset < 0 {
885            return None;
886        }
887        let (seg_offset, seg_len) = self.segment(SEG_IMPFILES)?;
888        let abs = seg_offset.checked_add(offset as usize)?;
889        if abs.checked_add(ImpFile::HEADER_SIZE)? > seg_offset.checked_add(seg_len)? {
890            return None;
891        }
892        let size_field = read_u16_le(self.data, abs + 0x0C)?;
893        let name_len = (size_field >> 2) as usize;
894        let entry_size = (ImpFile::HEADER_SIZE + name_len + 3) & !3;
895        let end = abs.checked_add(entry_size)?;
896        if end > self.data.len() {
897            return None;
898        }
899        Some(ImpFile::new(&self.data[abs..end]))
900    }
901
902    /// Returns an array descriptor at the given byte offset in segment 10.
903    ///
904    /// Returns `None` if `offset` is negative, the segment is missing,
905    /// or the entry is out of bounds.
906    pub fn array_desc(&self, offset: i32) -> Option<ArrayDesc<'a>> {
907        if offset < 0 {
908            return None;
909        }
910        let (seg_offset, _) = self.segment(SEG_ARRAYDESC)?;
911        let abs = seg_offset.checked_add(offset as usize)?;
912        // Need at least 8 bytes for the header (element_type + num_dims + flags)
913        if abs.checked_add(8)? > self.data.len() {
914            return None;
915        }
916        let num_dims = read_u16_le(self.data, abs + 4)? as usize;
917        let size = 8 + num_dims * 8;
918        let end = abs.checked_add(size)?;
919        if end > self.data.len() {
920            return None;
921        }
922        Some(ArrayDesc::new(&self.data[abs..end]))
923    }
924
925    /// Returns an iterator over the custom data chain starting at `offset`
926    /// in the CDGuids directory (segment 12).
927    ///
928    /// The chain is a linked list of [`CustDataEntry`](crate::CustDataEntry)
929    /// values, ending when `next` is `-1`.
930    pub fn cust_data(&self, offset: i32) -> CustDataIter<'a> {
931        let (seg_offset, _) = self.segment(SEG_CDGUIDS).unwrap_or((0, 0));
932        CustDataIter::new(self.data, seg_offset, offset)
933    }
934
935    /// Returns the library-level custom data chain.
936    pub fn lib_cust_data(&self) -> CustDataIter<'a> {
937        self.cust_data(self.custom_data_offset())
938    }
939
940    /// Resolves one custom data entry into its GUID and value.
941    ///
942    /// Returns `None` if the GUID or data offset cannot be resolved.
943    pub fn resolve_cust_data_entry(
944        &self,
945        entry: &CustDataEntry<'_>,
946    ) -> Option<(Guid<'a>, ConstValue<'a>)> {
947        let guid = self.guid(entry.guid_offset())?;
948        let value = self.const_value(entry.data_offset())?;
949        Some((guid, value))
950    }
951}