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}