Skip to main content

msft_typelib/
func.rs

1//! [`FuncRecord`] -- zero-copy view of a variable-length `MSFT_FuncRecord`.
2//!
3//! Each function record describes one method or property accessor within a
4//! TypeInfo.  The record layout is:
5//!
6//! ```text
7//! [Info: u32] [DataType: i32] [Flags: u32] [VtableOff: i16] [FuncDescSize: i16]
8//! [FKCCIC: u32] [NrArgs: i16] [NrOArgs: i16]
9//! [optional attrs 0..N as i32]
10//! [per-arg custom data offsets, if FKCCIC bit 12 set]
11//! [ParameterInfo entries, nrargs * 12 bytes]
12//! ```
13//!
14//! The optional attributes (in order) are: help context, help string offset,
15//! oEntry/DISPID, name offset, help string context, and custom data offset.
16
17use crate::{
18    ParameterInfo,
19    util::{read_i16_le, read_i32_le, read_u32_le},
20};
21
22/// Zero-copy view of a variable-length `MSFT_FuncRecord`.
23///
24/// The base fixed portion is [`BASE_SIZE`](Self::BASE_SIZE) (0x18) bytes.
25/// Optional attribute DWORDs and per-parameter data follow.
26///
27/// Constructed by [`FuncIter`](crate::FuncIter); the backing slice length
28/// equals the record size encoded in the `Info` field.
29#[derive(Clone, Copy, Debug)]
30pub struct FuncRecord<'a> {
31    bytes: &'a [u8],
32}
33
34impl<'a> FuncRecord<'a> {
35    /// Wraps raw record bytes as a `FuncRecord`.  The caller is responsible
36    /// for slicing `bytes` to exactly the record's encoded size.
37    pub(crate) fn new(bytes: &'a [u8]) -> Self {
38        Self { bytes }
39    }
40
41    /// Minimum record size (the fixed fields before optional attributes).
42    pub const BASE_SIZE: usize = 0x18;
43
44    /// Returns the raw backing bytes for this record.
45    #[inline]
46    pub fn as_bytes(&self) -> &'a [u8] {
47        self.bytes
48    }
49
50    /// Raw `Info` field at offset 0x00.
51    ///
52    /// Low 16 bits = record size in bytes; high 16 bits = member index.
53    #[inline]
54    pub fn info(&self) -> u32 {
55        read_u32_le(self.bytes, 0x00).unwrap_or(0)
56    }
57
58    /// Record size in bytes (low 16 bits of [`info`](Self::info)).
59    #[inline]
60    pub fn record_size(&self) -> usize {
61        (self.info() & 0xFFFF) as usize
62    }
63
64    /// Return type (encoded `DataType` at offset 0x04).
65    ///
66    /// Negative values encode simple `VT_*` types inline.
67    /// Non-negative values are offsets into the type descriptor table.
68    #[inline]
69    pub fn datatype(&self) -> i32 {
70        read_i32_le(self.bytes, 0x04).unwrap_or(-1)
71    }
72
73    /// `FUNCFLAG_*` flags at offset 0x08.
74    #[inline]
75    pub fn flags(&self) -> u32 {
76        read_u32_le(self.bytes, 0x08).unwrap_or(0)
77    }
78
79    /// VTable slot offset at offset 0x0C.
80    #[inline]
81    pub fn vtable_offset(&self) -> i16 {
82        read_i16_le(self.bytes, 0x0C).unwrap_or(0)
83    }
84
85    /// `FUNCDESC` size at offset 0x0E.
86    #[inline]
87    pub fn funcdesc_size(&self) -> i16 {
88        read_i16_le(self.bytes, 0x0E).unwrap_or(0)
89    }
90
91    /// `FKCCIC` field at offset 0x10.
92    ///
93    /// Encodes function kind (bits 0-2), invoke kind (bits 3-6),
94    /// calling convention (bits 8-11), and flags (bits 7, 12).
95    #[inline]
96    pub fn fkccic(&self) -> u32 {
97        read_u32_le(self.bytes, 0x10).unwrap_or(0)
98    }
99
100    /// `FUNC_*` kind (bits 0-2 of [`fkccic`](Self::fkccic)).
101    ///
102    /// 0 = Virtual, 1 = PureVirtual, 2 = NonVirtual, 3 = Static, 4 = Dispatch.
103    #[inline]
104    pub fn func_kind(&self) -> u8 {
105        (self.fkccic() & 0x07) as u8
106    }
107
108    /// `INVOKE_*` kind (bits 3-6 of [`fkccic`](Self::fkccic)).
109    ///
110    /// 1 = Func, 2 = PropertyGet, 4 = PropertyPut, 8 = PropertyPutRef.
111    #[inline]
112    pub fn invoke_kind(&self) -> u8 {
113        ((self.fkccic() >> 3) & 0x0F) as u8
114    }
115
116    /// Calling convention (bits 8-11 of [`fkccic`](Self::fkccic)).
117    ///
118    /// 0 = FastCall, 1 = CDecl, 2 = Pascal, 4 = StdCall.
119    #[inline]
120    pub fn callconv(&self) -> u8 {
121        ((self.fkccic() >> 8) & 0x0F) as u8
122    }
123
124    /// Number of parameters at offset 0x14.
125    #[inline]
126    pub fn nrargs(&self) -> i16 {
127        read_i16_le(self.bytes, 0x14).unwrap_or(0)
128    }
129
130    /// Number of optional parameters at offset 0x16.
131    #[inline]
132    pub fn nroargs(&self) -> i16 {
133        read_i16_le(self.bytes, 0x16).unwrap_or(0)
134    }
135
136    /// Number of standard optional-attribute DWORDs between the base
137    /// fixed fields and the parameter array.
138    ///
139    /// The record layout after `BASE_SIZE` is:
140    ///
141    /// ```text
142    /// [standard attrs: 0..6 DWORDs] [arg cust data: nrargs DWORDs if bit 12]
143    /// [ParameterInfo: nrargs * 12 bytes]
144    /// ```
145    ///
146    /// This method returns only the count of standard attribute DWORDs
147    /// (up to 6), excluding per-argument custom data.
148    fn nrattribs(&self) -> usize {
149        let size = self.record_size();
150        let nrargs = self.nrargs().max(0) as usize;
151        let params_size = nrargs * ParameterInfo::SIZE;
152        let arg_cust_size = if self.has_arg_cust_data() {
153            nrargs * 4
154        } else {
155            0
156        };
157        size.saturating_sub(Self::BASE_SIZE)
158            .saturating_sub(params_size)
159            .saturating_sub(arg_cust_size)
160            / 4
161    }
162
163    /// Whether this function has custom data (FKCCIC bit 7).
164    #[inline]
165    pub fn has_cust_data(&self) -> bool {
166        self.fkccic() & 0x80 != 0
167    }
168
169    /// Whether this function has per-argument custom data (FKCCIC bit 12).
170    #[inline]
171    pub fn has_arg_cust_data(&self) -> bool {
172        self.fkccic() & 0x1000 != 0
173    }
174
175    /// Help context (attribute 0, offset 0x18).
176    ///
177    /// Returns `None` if the record is too short to contain this field.
178    pub fn help_context(&self) -> Option<i32> {
179        if self.nrattribs() > 0 {
180            read_i32_le(self.bytes, 0x18)
181        } else {
182            None
183        }
184    }
185
186    /// Help string offset in the string table (attribute 1, offset 0x1C).
187    ///
188    /// Returns `None` if the record is too short to contain this field.
189    pub fn help_string_offset(&self) -> Option<i32> {
190        if self.nrattribs() > 1 {
191            read_i32_le(self.bytes, 0x1C)
192        } else {
193            None
194        }
195    }
196
197    /// `oEntry` / DISPID (attribute 2, offset 0x20).
198    ///
199    /// For `DISPATCH` interfaces this is the DISPID.
200    /// For `MODULE` types this is a name offset or ordinal.
201    ///
202    /// Returns `None` if the record is too short to contain this field.
203    pub fn oentry(&self) -> Option<i32> {
204        if self.nrattribs() > 2 {
205            read_i32_le(self.bytes, 0x20)
206        } else {
207            None
208        }
209    }
210
211    /// Function name offset in the name table (attribute 3, offset 0x24).
212    ///
213    /// Note: in practice, function names are read from the auxiliary
214    /// arrays via [`TypeLib::func_name`](crate::TypeLib::func_name)
215    /// rather than from this record attribute.
216    ///
217    /// Returns `None` if the record is too short to contain this field.
218    pub fn name_offset(&self) -> Option<i32> {
219        if self.nrattribs() > 3 {
220            read_i32_le(self.bytes, 0x24)
221        } else {
222            None
223        }
224    }
225
226    /// Help string context (attribute 4, offset 0x28).
227    ///
228    /// Returns `None` if the record is too short to contain this field.
229    pub fn helpstringcontext(&self) -> Option<i32> {
230        if self.nrattribs() > 4 {
231            read_i32_le(self.bytes, 0x28)
232        } else {
233            None
234        }
235    }
236
237    /// Offset into the CDGuids directory for this function's custom data
238    /// (attribute 5, offset 0x2C).
239    ///
240    /// Only meaningful when [`has_cust_data`](Self::has_cust_data) returns `true`.
241    ///
242    /// Returns `None` if the record is too short to contain this field.
243    pub fn cust_data_offset(&self) -> Option<i32> {
244        if self.nrattribs() > 5 {
245            read_i32_le(self.bytes, 0x2C)
246        } else {
247            None
248        }
249    }
250
251    /// Returns the custom data offset for argument `arg_index`.
252    ///
253    /// The per-argument custom data array follows all optional attributes
254    /// (when FKCCIC bit 12 is set). Returns `None` if the function has
255    /// no per-argument custom data or the index is out of bounds.
256    pub fn arg_cust_data_offset(&self, arg_index: usize) -> Option<i32> {
257        if !self.has_arg_cust_data() {
258            return None;
259        }
260        let nrargs = self.nrargs().max(0) as usize;
261        if arg_index >= nrargs {
262            return None;
263        }
264        let attrs = self.nrattribs();
265        let offset = Self::BASE_SIZE + attrs * 4 + arg_index * 4;
266        read_i32_le(self.bytes, offset)
267    }
268}