use alloc::string::String;
use alloc::vec::Vec;
use zerocopy::little_endian::U32;
use crate::bytes::{Bytes, utf16_len};
use crate::error::Result;
fn align(offset: usize) -> usize {
offset.next_multiple_of(4)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionHeader {
pub w_length: u16,
pub w_value_length: u16,
pub w_type: u16,
pub sz_key: String,
}
impl VersionHeader {
fn parse(bytes: Bytes, offset: usize) -> Result<Self> {
Ok(Self {
w_length: bytes.u16(offset)?,
w_value_length: bytes.u16(offset + 2)?,
w_type: bytes.u16(offset + 4)?,
sz_key: bytes.utf16_string(offset + 6)?,
})
}
fn value_offset(&self, offset: usize) -> usize {
align(offset + 6 + utf16_len(&self.sz_key) * 2 + 2)
}
}
trait Node: Sized {
fn header(&self) -> &VersionHeader;
fn with_header(bytes: Bytes, offset: usize, header: VersionHeader) -> Result<Self>;
fn parse(bytes: Bytes, offset: usize) -> Result<Self> {
let header = VersionHeader::parse(bytes, offset)?;
Self::with_header(bytes, offset, header)
}
}
macro_rules! parent_node {
($name:ident, $children:ident) => {
impl Node for $name {
fn header(&self) -> &VersionHeader {
&self.header
}
fn with_header(bytes: Bytes, offset: usize, header: VersionHeader) -> Result<Self> {
let end = offset + header.w_length as usize;
let $children = parse_children(bytes, header.value_offset(offset), end)?;
Ok(Self { header, $children })
}
}
};
}
fn parse_children<T: Node>(bytes: Bytes, first_offset: usize, end: usize) -> Result<Vec<T>> {
let mut children = Vec::new();
let mut current = align(first_offset);
while current < end {
let child = T::parse(bytes, current)?;
let length = child.header().w_length as usize;
if length == 0 {
break;
}
current = align(current + length);
children.push(child);
}
Ok(children)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct VsVersionInfo<'a> {
pub header: VersionHeader,
pub fixed_file_info: Option<&'a VsFixedFileInfo>,
pub string_file_info: Option<StringFileInfo>,
pub var_file_info: Option<VarFileInfo>,
}
impl<'a> VsVersionInfo<'a> {
pub fn parse(data: &'a [u8], offset: usize) -> Result<Self> {
let bytes = Bytes(data);
let header = VersionHeader::parse(bytes, offset)?;
let fixed_file_info_offset = header.value_offset(offset);
let end = offset + header.w_length as usize;
let first_child = align(fixed_file_info_offset + header.w_value_length as usize);
let mut string_file_info = None;
let mut var_file_info = None;
let mut current = first_child;
while current < end {
let Ok(child) = VersionHeader::parse(bytes, current) else {
break;
};
match child.sz_key.as_str() {
"StringFileInfo" if string_file_info.is_none() => {
string_file_info = StringFileInfo::parse(bytes, current).ok();
}
"VarFileInfo" if var_file_info.is_none() => {
var_file_info = VarFileInfo::parse(bytes, current).ok()
}
_ => {}
}
if child.w_length == 0 {
break;
}
current = align(current + child.w_length as usize);
}
Ok(Self {
fixed_file_info: VsFixedFileInfo::parse(data, fixed_file_info_offset).ok(),
header,
string_file_info,
var_file_info,
})
}
}
pe_struct! {
pub struct VsFixedFileInfo {
dw_signature: U32 => u32,
dw_struc_version: U32 => u32,
dw_file_version_ms: U32 => u32,
dw_file_version_ls: U32 => u32,
dw_product_version_ms: U32 => u32,
dw_product_version_ls: U32 => u32,
dw_file_flags_mask: U32 => u32,
dw_file_flags: U32 => u32,
dw_file_os: U32 => u32,
dw_file_type: U32 => u32,
dw_file_sub_type: U32 => u32,
dw_file_date_ms: U32 => u32,
dw_file_date_ls: U32 => u32,
}
}
impl VsFixedFileInfo {
pub fn file_version(&self) -> [u16; 4] {
version(self.dw_file_version_ms(), self.dw_file_version_ls())
}
pub fn product_version(&self) -> [u16; 4] {
version(self.dw_product_version_ms(), self.dw_product_version_ls())
}
}
fn version(ms: u32, ls: u32) -> [u16; 4] {
[(ms >> 16) as u16, ms as u16, (ls >> 16) as u16, ls as u16]
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct StringFileInfo {
pub header: VersionHeader,
pub string_tables: Vec<StringTable>,
}
parent_node!(StringFileInfo, string_tables);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct StringTable {
pub header: VersionHeader,
pub strings: Vec<VersionString>,
}
impl StringTable {
pub fn get(&self, key: VersionKey) -> Option<&str> {
self.value(key.as_str())
}
pub fn value(&self, key: &str) -> Option<&str> {
self.strings
.iter()
.find(|s| s.header.sz_key == key)
.map(|s| s.value.as_str())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.strings
.iter()
.map(|s| (s.header.sz_key.as_str(), s.value.as_str()))
}
}
parent_node!(StringTable, strings);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct VersionString {
pub header: VersionHeader,
pub value: String,
}
impl Node for VersionString {
fn with_header(bytes: Bytes, offset: usize, header: VersionHeader) -> Result<Self> {
let value = bytes.utf16_string(header.value_offset(offset))?;
Ok(Self { header, value })
}
fn header(&self) -> &VersionHeader {
&self.header
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VersionKey {
Comments,
CompanyName,
FileDescription,
FileVersion,
InternalName,
LegalCopyright,
LegalTrademarks,
OriginalFilename,
PrivateBuild,
ProductName,
ProductVersion,
SpecialBuild,
}
impl VersionKey {
pub const fn as_str(self) -> &'static str {
match self {
Self::Comments => "Comments",
Self::CompanyName => "CompanyName",
Self::FileDescription => "FileDescription",
Self::FileVersion => "FileVersion",
Self::InternalName => "InternalName",
Self::LegalCopyright => "LegalCopyright",
Self::LegalTrademarks => "LegalTrademarks",
Self::OriginalFilename => "OriginalFilename",
Self::PrivateBuild => "PrivateBuild",
Self::ProductName => "ProductName",
Self::ProductVersion => "ProductVersion",
Self::SpecialBuild => "SpecialBuild",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct VarFileInfo {
pub header: VersionHeader,
pub children: Vec<Var>,
}
parent_node!(VarFileInfo, children);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Var {
pub header: VersionHeader,
pub value: Vec<u32>,
}
impl Node for Var {
fn with_header(bytes: Bytes, offset: usize, header: VersionHeader) -> Result<Self> {
let start = header.value_offset(offset);
let values: &[U32] = bytes.read_slice(start, header.w_value_length as usize / 4)?;
let value = values.iter().map(|v| v.get()).collect();
Ok(Self { header, value })
}
fn header(&self) -> &VersionHeader {
&self.header
}
}