Skip to main content

msft_typelib/
var.rs

1//! [`VarRecord`] -- zero-copy view of a variable-length `MSFT_VarRecord`.
2//!
3//! Each variable record describes one constant, field, or dispatch property
4//! within a TypeInfo.  The record layout is:
5//!
6//! ```text
7//! [Info: u32] [DataType: i32] [Flags: u32] [VarKind: i16] [VarDescSize: i16]
8//! [OffsValue: i32]
9//! [optional attrs 0..N as i32]
10//! ```
11//!
12//! The optional attributes (in order) are: help context, help string offset,
13//! reserved (always -1), custom data offset, and help string context.
14
15use crate::{
16    Error,
17    util::{read_i16_le, read_i32_le, read_u32_le},
18};
19
20/// Zero-copy view of a variable-length `MSFT_VarRecord`.
21///
22/// The base fixed portion is [`BASE_SIZE`](Self::BASE_SIZE) (0x14) bytes.
23/// Optional attribute DWORDs follow.
24///
25/// Constructed by [`VarIter`](crate::VarIter) or [`VarRecord::from_raw`].
26#[derive(Clone, Copy, Debug)]
27pub struct VarRecord<'a> {
28    bytes: &'a [u8],
29}
30
31impl<'a> VarRecord<'a> {
32    /// Wraps raw record bytes as a `VarRecord`.  The caller is responsible
33    /// for slicing `bytes` to exactly the record's encoded size.
34    pub(crate) fn new(bytes: &'a [u8]) -> Self {
35        Self { bytes }
36    }
37
38    /// Minimum record size (the fixed fields).
39    pub const BASE_SIZE: usize = 0x14;
40
41    /// Creates a [`VarRecord`] from an already-sliced byte buffer.
42    ///
43    /// # Errors
44    ///
45    /// Returns [`Error::TooShort`] if `bytes.len() < BASE_SIZE`.
46    pub fn from_raw(bytes: &'a [u8]) -> Result<Self, Error> {
47        if bytes.len() < Self::BASE_SIZE {
48            return Err(Error::TooShort {
49                expected: Self::BASE_SIZE,
50                actual: bytes.len(),
51                context: "VarRecord",
52            });
53        }
54        Ok(VarRecord { bytes })
55    }
56
57    /// Returns the raw backing bytes.
58    #[inline]
59    pub fn as_bytes(&self) -> &'a [u8] {
60        self.bytes
61    }
62
63    /// Raw `Info` field.
64    ///
65    /// Low 8 bits = record size in bytes; upper bits = member index.
66    #[inline]
67    pub fn info(&self) -> u32 {
68        read_u32_le(self.bytes, 0x00).unwrap_or(0)
69    }
70
71    /// Record size in bytes (low 16 bits of [`info`](Self::info)).
72    #[inline]
73    pub fn record_size(&self) -> usize {
74        (self.info() & 0xFFFF) as usize
75    }
76
77    /// Variable type (encoded `DataType`).
78    ///
79    /// Negative values encode simple `VT_*` types inline.
80    /// Non-negative values are offsets into the type descriptor table.
81    #[inline]
82    pub fn datatype(&self) -> i32 {
83        read_i32_le(self.bytes, 0x04).unwrap_or(-1)
84    }
85
86    /// `VARFLAG_*` flags.
87    #[inline]
88    pub fn flags(&self) -> u32 {
89        read_u32_le(self.bytes, 0x08).unwrap_or(0)
90    }
91
92    /// `VAR_*` kind.
93    ///
94    /// 0 = `VAR_PERINSTANCE`, 2 = `VAR_CONST`, 3 = `VAR_DISPATCH`.
95    #[inline]
96    pub fn varkind(&self) -> i16 {
97        read_i16_le(self.bytes, 0x0C).unwrap_or(0)
98    }
99
100    /// `VARDESC` size.
101    #[inline]
102    pub fn vardesc_size(&self) -> i16 {
103        read_i16_le(self.bytes, 0x0E).unwrap_or(0)
104    }
105
106    /// For `VAR_CONST`: inline value or offset into the custom data segment.
107    /// For `VAR_PERINSTANCE`: byte offset within the instance.
108    #[inline]
109    pub fn offs_value(&self) -> i32 {
110        read_i32_le(self.bytes, 0x10).unwrap_or(0)
111    }
112
113    /// Number of optional attribute DWORDs after the base fixed fields.
114    ///
115    /// The record layout after `BASE_SIZE` is simply `[attrs: 0..5 DWORDs]`.
116    /// Attributes (in order): help context, help string offset, reserved,
117    /// custom data offset, help string context.
118    fn nrattribs(&self) -> usize {
119        self.record_size().saturating_sub(Self::BASE_SIZE) / 4
120    }
121
122    /// Help context (attribute 0, offset 0x14).
123    ///
124    /// Returns `None` if the record is too short to contain this field.
125    pub fn help_context(&self) -> Option<i32> {
126        if self.nrattribs() > 0 {
127            read_i32_le(self.bytes, 0x14)
128        } else {
129            None
130        }
131    }
132
133    /// Help string offset (attribute 1, offset 0x18).
134    ///
135    /// Returns `None` if the record is too short to contain this field.
136    pub fn help_string_offset(&self) -> Option<i32> {
137        if self.nrattribs() > 1 {
138            read_i32_le(self.bytes, 0x18)
139        } else {
140            None
141        }
142    }
143
144    /// Reserved field (attribute 2, offset 0x1C).
145    ///
146    /// Always `-1` in practice. Wine identifies this as `res9`.
147    ///
148    /// Returns `None` if the record is too short to contain this field.
149    pub fn res9(&self) -> Option<i32> {
150        if self.nrattribs() > 2 {
151            read_i32_le(self.bytes, 0x1C)
152        } else {
153            None
154        }
155    }
156
157    /// Offset into the CDGuids directory for this variable's custom data
158    /// (attribute 3, offset 0x20).
159    ///
160    /// Returns `None` if the record is too short to contain this field.
161    pub fn cust_data_offset(&self) -> Option<i32> {
162        if self.nrattribs() > 3 {
163            read_i32_le(self.bytes, 0x20)
164        } else {
165            None
166        }
167    }
168
169    /// Help string context (attribute 4, offset 0x24).
170    ///
171    /// Returns `None` if the record is too short to contain this field.
172    pub fn helpstringcontext(&self) -> Option<i32> {
173        if self.nrattribs() > 4 {
174            read_i32_le(self.bytes, 0x24)
175        } else {
176            None
177        }
178    }
179}