Skip to main content

compatdb_core/runtime/
version.rs

1//! The reader of a `VS_VERSIONINFO` blob.
2
3use super::process::to_lower_u16;
4use crate::VersionInfo;
5
6/// The standard fields of a `VS_VERSIONINFO` blob.
7///
8/// The blob is the bytes of an `RT_VERSION` resource, as UTF-16 units. It is
9/// walked as the tree it is: the root, its `StringFileInfo` children, their
10/// string tables and the `String` entries in them. A field takes the value
11/// of the first entry with its key, in table order; an entry whose value is
12/// empty, or a key no table has, leaves the field empty, so a rule that pins
13/// it cannot match.
14#[must_use]
15pub fn version_info(blob: &[u16]) -> VersionInfo {
16    VersionInfo {
17        company: string_value(blob, "CompanyName"),
18        product: string_value(blob, "ProductName"),
19        original_filename: string_value(blob, "OriginalFilename"),
20    }
21}
22
23/// One node of a `VS_VERSIONINFO` tree. Every node, the root, the containers
24/// and the `String` entries alike, is `wLength` (bytes, children included),
25/// `wValueLength`, `wType`, a NUL-terminated UTF-16 key, padding to a 32-bit
26/// boundary, the value, padding again, and then the children.
27struct VersionNode<'a> {
28    key: &'a [u16],
29    /// From the start of the value to the end of the node.
30    value: &'a [u16],
31    /// The children, as the units that hold them.
32    children: &'a [u16],
33}
34
35impl<'a> VersionNode<'a> {
36    /// Parse the node at the start of `units` and return it with the units
37    /// after it, where its next sibling starts. `units` has to start on a
38    /// 32-bit boundary, which every node does. A node whose length is too
39    /// short for its header or whose key is not NUL-terminated ends the walk;
40    /// one that claims more than `units` holds is cut off at the end.
41    fn parse(units: &'a [u16]) -> Option<(Self, &'a [u16])> {
42        let length = usize::from(*units.first()?).div_ceil(2);
43        let value_length = usize::from(*units.get(1)?);
44        // Any nonzero wType is text, as in Wine's VerQueryValue.
45        let text = *units.get(2)? != 0;
46        if length < 3 {
47            return None;
48        }
49        let node = units.get(..length).unwrap_or(units);
50        let key_text = node.get(3..)?;
51        let key_end = key_text.iter().position(|&u| u == 0)?;
52        let key = key_text.get(..key_end)?;
53        // The key starts 3 units in, and its NUL is one more.
54        let value_at = key_end.checked_add(4)?.checked_next_multiple_of(2)?;
55        let value = node.get(value_at..).unwrap_or_default();
56        // wValueLength counts characters for a text value and bytes for a
57        // binary one; the children start at the next 32-bit boundary after it.
58        let value_units = if text {
59            value_length
60        } else {
61            value_length.div_ceil(2)
62        };
63        let children_at = value_at
64            .checked_add(value_units)?
65            .checked_next_multiple_of(2)?;
66        let children = node.get(children_at..).unwrap_or_default();
67        let next = length.checked_next_multiple_of(2)?;
68        let rest = units.get(next..).unwrap_or_default();
69        Some((
70            Self {
71                key,
72                value,
73                children,
74            },
75            rest,
76        ))
77    }
78
79    /// The nodes in a run of sibling units, in order.
80    fn siblings(mut units: &'a [u16]) -> impl Iterator<Item = Self> {
81        std::iter::from_fn(move || {
82            let (node, rest) = Self::parse(units)?;
83            units = rest;
84            Some(node)
85        })
86    }
87
88    /// Whether the key is `want`, compared ASCII-case-insensitively as
89    /// `VerQueryValue` does.
90    fn key_is(&self, want: &str) -> bool {
91        self.key
92            .iter()
93            .map(|&u| to_lower_u16(u))
94            .eq(want.encode_utf16().map(to_lower_u16))
95    }
96}
97
98/// The value of the first `String` entry with this key in any string table of
99/// the blob, empty when there is none or its value is empty.
100///
101/// The value is read up to its NUL within the entry rather than by
102/// `wValueLength`, which resource compilers do not all fill in the same way.
103fn string_value(blob: &[u16], key: &str) -> String {
104    let Some((root, _)) = VersionNode::parse(blob) else {
105        return String::new();
106    };
107    if !root.key_is("VS_VERSION_INFO") {
108        return String::new();
109    }
110    VersionNode::siblings(root.children)
111        .filter(|info| info.key_is("StringFileInfo"))
112        .flat_map(|info| VersionNode::siblings(info.children))
113        .flat_map(|table| VersionNode::siblings(table.children))
114        .find(|entry| entry.key_is(key))
115        .and_then(|entry| entry.value.split(|&u| u == 0).next())
116        .map(String::from_utf16_lossy)
117        .unwrap_or_default()
118}
119
120#[cfg(test)]
121mod tests;