use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use core::{borrow::Borrow, cmp::Ordering, iter, mem::size_of};
use debug_ignore::DebugIgnore;
use foldhash::fast::RandomState;
use indexmap::{IndexMap, IndexSet};
use log::{error, trace, warn};
use zerocopy::IntoBytes;
#[cfg(feature = "images")]
pub use image::DynamicImage;
use crate::util::u16_string_len;
pub trait ToIcon {
fn icons(&self) -> Result<Vec<Vec<u8>>, ResourceError>;
}
impl ToIcon for &[u8] {
fn icons(&self) -> Result<Vec<Vec<u8>>, ResourceError> {
if self.len() < 22 {
return Err(ResourceError::InvalidBytes("icon data is too small".into()));
}
let directory = read::<IconDirectory>(&self[0..6])?;
if directory.type_ != 1 {
return Err(ResourceError::InvalidBytes("icon data is not an icon".into()));
}
if directory.count < 1 {
return Err(ResourceError::InvalidBytes("icon data has no images".into()));
}
let mut icons = Vec::with_capacity(directory.count as usize);
for i in 0..directory.count as usize {
if self.len() < 6 + i * 16 + 16 {
return Err(ResourceError::InvalidBytes("icon data is too small".into()));
}
let size = read::<u32>(&self[6..][i * 16 + 8..])? as usize;
let offset = read::<u32>(&self[6..][i * 16 + 12..])? as usize;
let end = match offset.checked_add(size) {
Some(end) if end <= self.len() => end,
_ => return Err(ResourceError::InvalidBytes("icon data is truncated".into())),
};
let mut data = Vec::with_capacity(14 + size);
data.extend_from_slice(&self[6..][i * 16..i * 16 + 12]);
data.extend_from_slice(&[0u8; 2]);
data.extend_from_slice(&self[offset..end]);
icons.push(data);
}
Ok(icons)
}
}
impl ToIcon for Vec<u8> {
fn icons(&self) -> Result<Vec<Vec<u8>>, ResourceError> { self.as_slice().icons() }
}
#[cfg(feature = "images")]
impl ToIcon for &DynamicImage {
fn icons(&self) -> Result<Vec<Vec<u8>>, ResourceError> {
use image::{ImageFormat, imageops::FilterType::Lanczos3};
use std::io::Cursor;
const RESOLUTIONS: &[u32] = &[256, 128, 48, 32, 24, 16];
RESOLUTIONS
.iter()
.map(|&size| {
let mut data = Vec::new();
self.resize_exact(size, size, Lanczos3)
.to_rgba8()
.write_to(&mut Cursor::new(&mut data), ImageFormat::Ico)?;
let mut result = Vec::with_capacity(14 + data.len() - 22);
result.extend_from_slice(&data[6..18]);
result.extend_from_slice(&[0u8; 2]);
result.extend_from_slice(&data[22..]);
Ok(result)
})
.collect::<Result<Vec<Vec<u8>>, ResourceError>>()
}
}
#[cfg(feature = "images")]
impl ToIcon for DynamicImage {
fn icons(&self) -> Result<Vec<Vec<u8>>, ResourceError> { (&self).icons() }
}
use crate::{constants::*, errors::*, types::*, util::*};
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct ResourceDirectory {
pub(crate) virtual_address: u32,
pub(crate) root: ResourceTable,
}
impl ResourceDirectory {
pub fn parse(
image: &[u8], base_address: u32, virtual_address: u32,
) -> Result<Self, ImageReadError> {
let root =
ResourceTable::parse(image, base_address, virtual_address, 0, 0, &mut Vec::new())?;
Ok(Self {
virtual_address,
root,
})
}
pub fn get_main_icon(&self) -> Result<Option<&[u8]>, ResourceError> {
if self.root.entries.is_empty() {
return Ok(None);
}
let group_table = self.root.get(ResourceEntryName::ID(RT_GROUP_ICON as u32));
let group_table = match group_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"group icon table is not a table".to_string(),
));
}
_ => return Ok(None),
};
if group_table.entries.is_empty() {
return Ok(None);
}
let icon_directory_table = group_table
.entries
.get(&ResourceEntryName::from_string("MAINICON"))
.or_else(|| group_table.entries.first().map(|(_, v)| v));
let icon_directory_table = match icon_directory_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner group icon table is not a table".to_string(),
));
}
None => return Ok(None),
};
if icon_directory_table.entries.is_empty() {
return Ok(None);
}
let icon_directory_entry = match icon_directory_table.entries.first().map(|(_, v)| v) {
Some(ResourceEntry::Data(data)) => data,
Some(ResourceEntry::Table(_)) => {
return Err(ResourceError::InvalidTable(
"group icon table entry is not data".to_string(),
));
}
None => return Ok(None),
};
let icon_directory = read::<IconDirectory>(&icon_directory_entry.data)?;
if icon_directory.count == 0 {
return Ok(None);
}
let icon_directory_entry = read::<IconDirectoryEntry>(&icon_directory_entry.data[6..])?;
let icon_id = icon_directory_entry.id as u32;
let icon_table = match self.root.get(ResourceEntryName::ID(RT_ICON as u32)) {
Some(ResourceEntry::Table(table)) => table,
Some(ResourceEntry::Data(_)) => {
return Err(ResourceError::InvalidTable("icon table is not a table".to_string()));
}
None => return Ok(None),
};
let inner_table = icon_table.get(ResourceEntryName::ID(icon_id));
let inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner icon table is not a table".to_string(),
));
}
None => return Ok(None),
};
if inner_table.entries.is_empty() {
return Ok(None);
}
let icon = match inner_table.entries.first().map(|(_, v)| v) {
Some(ResourceEntry::Table(_)) => {
return Err(ResourceError::InvalidTable(
"icon table entry is not data".to_string(),
));
}
Some(ResourceEntry::Data(data)) => data,
None => return Ok(None),
};
Ok(Some(icon.data()))
}
pub fn set_main_icon<T: ToIcon>(&mut self, icon: T) -> Result<(), ResourceError> {
let icons = icon.icons()?;
let icon_count = u16::try_from(icons.len()).map_err(|_| {
ResourceError::InvalidBytes(ReadError("icon count exceeds 65535".into()))
})?;
if icon_count == 0 {
return Err(ResourceError::InvalidBytes(ReadError("icon has no images".into())));
}
for icon in &icons {
if icon.len() < 14 {
return Err(ResourceError::InvalidBytes(ReadError(
"icon directory entry is too small".into(),
)));
}
u32::try_from(icon.len() - 14).map_err(|_| {
ResourceError::InvalidBytes(ReadError("icon image exceeds 4GiB".into()))
})?;
}
let icon_table_name = ResourceEntryName::ID(RT_ICON as u32);
let mut icon_table = self
.root
.cloned_table_or_default(&icon_table_name, "icon table is not a table")?;
let group_table_name = ResourceEntryName::ID(RT_GROUP_ICON as u32);
let mut group_table = self
.root
.cloned_table_or_default(&group_table_name, "group icon table is not a table")?;
let first_free_icon_id = icon_table
.entries
.keys()
.filter_map(|key| match key {
ResourceEntryName::ID(id) => Some(*id),
ResourceEntryName::Name(_) => None,
})
.max()
.unwrap_or(0)
.checked_add(1)
.ok_or_else(|| {
ResourceError::InvalidBytes(ReadError("icon identifier overflows".into()))
})?;
let last_icon_id =
first_free_icon_id.checked_add(u32::from(icon_count) - 1).ok_or_else(|| {
ResourceError::InvalidBytes(ReadError("icon identifier overflows".into()))
})?;
if last_icon_id > u16::MAX as u32 {
return Err(ResourceError::InvalidBytes(ReadError(
"icon identifier exceeds 65535".into(),
)));
}
let mut icon_directory_entries = Vec::new();
for (i, icon) in icons.iter().enumerate() {
let id = first_free_icon_id
.checked_add(u32::try_from(i).map_err(|_| {
ResourceError::InvalidBytes(ReadError("icon index exceeds u32".into()))
})?)
.ok_or_else(|| {
ResourceError::InvalidBytes(ReadError("icon identifier overflows".into()))
})?;
let mut inner_table = ResourceTable::default();
inner_table.insert(
ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32),
ResourceEntry::Data(ResourceData {
data: {
let mut entry = read::<IconDirectoryEntry>(&icon[..14])?;
entry.id = u16::try_from(id).map_err(|_| {
ResourceError::InvalidBytes(ReadError(
"icon identifier exceeds 65535".into(),
))
})?;
entry.bytes = u32::try_from(icon.len() - 14).map_err(|_| {
ResourceError::InvalidBytes(ReadError("icon image exceeds 4GiB".into()))
})?;
icon_directory_entries.push(entry);
icon[14..].to_vec().into()
},
codepage: CODE_PAGE_ID_EN_US as u32,
reserved: 0,
}),
);
icon_table.insert(ResourceEntryName::ID(id), ResourceEntry::Table(inner_table));
}
let mut inner_table = ResourceTable::default();
inner_table.insert(
ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32),
ResourceEntry::Data(ResourceData {
data: {
let mut data = Vec::new();
let icon_directory = IconDirectory {
reserved: 0,
type_: 1,
count: icon_count,
};
data.extend(icon_directory.as_bytes());
for entry in icon_directory_entries {
data.extend(&entry.as_bytes()[..14]);
}
data.into()
},
codepage: CODE_PAGE_ID_EN_US as u32,
reserved: 0,
}),
);
group_table.insert_at(
ResourceEntryName::from_string("MAINICON"),
ResourceEntry::Table(inner_table),
0,
);
self.root.insert(icon_table_name, ResourceEntry::Table(icon_table));
self.root.insert(group_table_name, ResourceEntry::Table(group_table));
Ok(())
}
#[cfg(feature = "std")]
pub fn set_main_icon_file(&mut self, path: &str) -> Result<(), ResourceError> {
#[cfg(feature = "images")]
let icon = image::ImageReader::open(path)?.decode()?;
#[cfg(not(feature = "images"))]
let icon = std::fs::read(path)?;
self.set_main_icon(icon)
}
#[cfg(feature = "std")]
pub fn set_main_icon_reader<R: std::io::Read>(
&mut self, reader: &mut R,
) -> Result<(), ResourceError> {
let mut icon = Vec::new();
reader.read_to_end(&mut icon)?;
#[cfg(feature = "images")]
let icon = image::load_from_memory(&icon)?;
self.set_main_icon(icon)
}
pub fn remove_main_icon(&mut self) -> Result<(), ResourceError> {
if self.root.entries.is_empty() {
return Ok(());
}
let group_table = self.root.get_mut(ResourceEntryName::ID(RT_GROUP_ICON as u32));
let group_table = match group_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"group icon table is not a table".to_string(),
));
}
_ => return Ok(()),
};
if group_table.entries.is_empty() {
return Ok(());
}
let mut icon_directory_name = ResourceEntryName::from_string("MAINICON");
let mut icon_directory_table = group_table.get(&icon_directory_name);
if icon_directory_table.is_none() {
icon_directory_table = group_table.entries.first().map(|(name, v)| {
icon_directory_name = name.clone();
v
});
}
let icon_directory_table = match icon_directory_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner group icon table is not a table".to_string(),
));
}
_ => return Ok(()),
};
if icon_directory_table.entries.is_empty() {
return Ok(());
}
let icon_directory_entry = match icon_directory_table.entries.first().map(|(_, v)| v) {
Some(ResourceEntry::Data(data)) => data,
Some(ResourceEntry::Table(_)) => {
return Err(ResourceError::InvalidTable(
"group icon table entry is not data".to_string(),
));
}
None => return Ok(()),
};
let icon_directory = read::<IconDirectory>(&icon_directory_entry.data)?;
let mut icons_to_remove = IndexSet::with_hasher(RandomState::default());
for i in 0..icon_directory.count {
let icon_directory_entry = read_at::<IconDirectoryEntry>(
&icon_directory_entry.data,
6 + i as usize * size_of::<IconDirectoryEntry>(),
)?;
let icon_id = icon_directory_entry.id;
icons_to_remove.insert(icon_id);
}
for (other_icon_directory_name, other_icon_directory_table) in group_table.entries.iter() {
if other_icon_directory_name == &icon_directory_name {
continue;
}
let other_icon_directory_table = match other_icon_directory_table {
ResourceEntry::Table(table) => table,
ResourceEntry::Data(_) => continue,
};
if other_icon_directory_table.entries.is_empty() {
continue;
}
let other_icon_directory_entry =
match other_icon_directory_table.entries.first().map(|(_, v)| v) {
Some(ResourceEntry::Data(data)) => data,
Some(ResourceEntry::Table(_)) | None => continue,
};
let other_icon_directory = read::<IconDirectory>(&other_icon_directory_entry.data)?;
for i in 0..other_icon_directory.count {
let icon_directory_entry = read_at::<IconDirectoryEntry>(
&other_icon_directory_entry.data,
6 + i as usize * size_of::<IconDirectoryEntry>(),
)?;
let icon_id = icon_directory_entry.id;
icons_to_remove.swap_remove(&icon_id);
}
}
group_table.remove(&icon_directory_name);
if group_table.entries.is_empty() {
self.root.remove(ResourceEntryName::ID(RT_GROUP_ICON as u32));
}
let icon_table = match self.root.get_mut(ResourceEntryName::ID(RT_ICON as u32)) {
Some(ResourceEntry::Table(table)) => table,
Some(ResourceEntry::Data(_)) | None => return Ok(()),
};
for icon_id in icons_to_remove {
icon_table.remove(ResourceEntryName::ID(icon_id as u32));
}
Ok(())
}
pub fn get_version_info(&self) -> Result<Option<VersionInfo>, ResourceError> {
if self.root.entries.is_empty() {
return Ok(None);
}
let version_table = self.root.get(ResourceEntryName::ID(RT_VERSION as u32));
let version_table = match version_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"version table is not a table".to_string(),
));
}
_ => return Ok(None),
};
if version_table.entries.is_empty() {
return Ok(None);
}
let inner_table = version_table.entries.first().map(|(_, v)| v);
let inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner version table is not a table".to_string(),
));
}
None => return Ok(None),
};
if inner_table.entries.is_empty() {
return Ok(None);
}
let version_directory_entry = inner_table
.entries
.iter()
.find(|(name, _)| **name == ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32))
.or_else(|| inner_table.entries.first())
.map(|(_, v)| v)
.unwrap();
let version_directory_entry = match version_directory_entry {
ResourceEntry::Data(data) => data,
ResourceEntry::Table(_) => {
return Err(ResourceError::InvalidTable(
"version table entry is not data".to_string(),
));
}
};
Ok(Some(VersionInfo::parse(&version_directory_entry.data)?))
}
pub fn set_version_info(&mut self, version_info: &VersionInfo) -> Result<(), ResourceError> {
let data = version_info.try_build()?;
let version_table_name = ResourceEntryName::ID(RT_VERSION as u32);
let mut version_table = self
.root
.cloned_table_or_default(&version_table_name, "version table is not a table")?;
let inner_table = version_table.entries.first().map(|(_, v)| v);
let mut inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t.clone(),
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner version table is not a table".to_string(),
));
}
None => ResourceTable::default(),
};
inner_table.insert_at(
ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32),
ResourceEntry::Data(ResourceData {
data: data.into(),
codepage: CODE_PAGE_ID_EN_US as u32,
reserved: 0,
}),
0,
);
version_table.insert_at(ResourceEntryName::ID(1), ResourceEntry::Table(inner_table), 0);
self.root.insert(version_table_name, ResourceEntry::Table(version_table));
Ok(())
}
pub fn remove_version_info(&mut self) -> Result<(), ResourceError> {
if self.root.entries.is_empty() {
return Ok(());
}
let version_table = self.root.get_mut(ResourceEntryName::ID(RT_VERSION as u32));
let version_table = match version_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"version table is not a table".to_string(),
));
}
_ => return Ok(()),
};
if version_table.entries.is_empty() {
return Ok(());
}
let inner_table = version_table.entries.first_mut().map(|(_, v)| v);
let inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner version table is not a table".to_string(),
));
}
None => return Ok(()),
};
if inner_table.entries.is_empty() {
return Ok(());
}
inner_table.remove(inner_table.entries.keys().next().unwrap().clone());
if inner_table.entries.is_empty() {
version_table.remove(version_table.entries.keys().next().unwrap().clone());
}
if version_table.entries.is_empty() {
self.root.remove(ResourceEntryName::ID(RT_VERSION as u32));
}
Ok(())
}
pub fn get_manifest(&self) -> Result<Option<String>, ResourceError> {
if self.root.entries.is_empty() {
return Ok(None);
}
let manifest_table = self.root.get(ResourceEntryName::ID(RT_MANIFEST as u32));
let manifest_table = match manifest_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"manifest table is not a table".to_string(),
));
}
_ => return Ok(None),
};
if manifest_table.entries.is_empty() {
return Ok(None);
}
let inner_table = manifest_table.entries.first().map(|(_, v)| v);
let inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner manifest table is not a table".to_string(),
));
}
None => return Ok(None),
};
if inner_table.entries.is_empty() {
return Ok(None);
}
let manifest_directory_entry = inner_table
.entries
.iter()
.find(|(name, _)| **name == ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32))
.or_else(|| inner_table.entries.first())
.map(|(_, v)| v)
.unwrap();
let manifest_directory_entry = match manifest_directory_entry {
ResourceEntry::Data(data) => data,
ResourceEntry::Table(_) => {
return Err(ResourceError::InvalidTable(
"manifest table entry is not data".to_string(),
));
}
};
Ok(Some(String::from_utf8_lossy(&manifest_directory_entry.data).to_string()))
}
pub fn set_manifest(&mut self, manifest: &str) -> Result<(), ResourceError> {
let manifest_table_name = ResourceEntryName::ID(RT_MANIFEST as u32);
let mut manifest_table = self
.root
.cloned_table_or_default(&manifest_table_name, "manifest table is not a table")?;
let inner_table = manifest_table.entries.first().map(|(_, v)| v);
let mut inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t.clone(),
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner manifest table is not a table".to_string(),
));
}
None => ResourceTable::default(),
};
inner_table.insert_at(
ResourceEntryName::ID(LANGUAGE_ID_EN_US as u32),
ResourceEntry::Data(ResourceData {
data: manifest.as_bytes().to_vec().into(),
codepage: CODE_PAGE_ID_EN_US as u32,
reserved: 0,
}),
0,
);
manifest_table.insert_at(ResourceEntryName::ID(1), ResourceEntry::Table(inner_table), 0);
self.root.insert(manifest_table_name, ResourceEntry::Table(manifest_table));
Ok(())
}
pub fn remove_manifest(&mut self) -> Result<(), ResourceError> {
if self.root.entries.is_empty() {
return Ok(());
}
let manifest_table = self.root.get_mut(ResourceEntryName::ID(RT_MANIFEST as u32));
let manifest_table = match manifest_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"manifest table is not a table".to_string(),
));
}
_ => return Ok(()),
};
if manifest_table.entries.is_empty() {
return Ok(());
}
let inner_table = manifest_table.entries.first_mut().map(|(_, v)| v);
let inner_table = match inner_table {
Some(ResourceEntry::Table(t)) => t,
Some(_) => {
return Err(ResourceError::InvalidTable(
"inner manifest table is not a table".to_string(),
));
}
None => return Ok(()),
};
if inner_table.entries.is_empty() {
return Ok(());
}
inner_table.remove(inner_table.entries.keys().next().unwrap().clone());
if inner_table.entries.is_empty() {
manifest_table.remove(manifest_table.entries.keys().next().unwrap().clone());
}
if manifest_table.entries.is_empty() {
self.root.remove(ResourceEntryName::ID(RT_MANIFEST as u32));
}
Ok(())
}
pub fn virtual_address(&self) -> u32 { self.virtual_address }
pub fn root(&self) -> &ResourceTable { &self.root }
pub fn root_mut(&mut self) -> &mut ResourceTable { &mut self.root }
pub fn size(&self) -> u32 { self.root.size() }
pub fn build(&self, virtual_address: u32) -> Vec<u8> { self.root.build(virtual_address) }
}
enum TableData {
Table(ResourceDirectoryTable),
Entry(ResourceDirectoryEntry),
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct ResourceTable {
pub(crate) data: ResourceDirectoryTable,
pub(crate) entries: IndexMap<ResourceEntryName, ResourceEntry, RandomState>,
}
impl ResourceTable {
fn cloned_table_or_default<N: Borrow<ResourceEntryName>>(
&self, name: N, error: &str,
) -> Result<Self, ResourceError> {
match self.get(name) {
Some(ResourceEntry::Table(table)) => Ok(table.clone()),
Some(ResourceEntry::Data(_)) => Err(ResourceError::InvalidTable(error.to_string())),
None => Ok(Self::default()),
}
}
fn parse(
image: &[u8], base_address: u32, virtual_address: u32, directory_offset: u32, level: usize,
ancestors: &mut Vec<u32>,
) -> Result<Self, ImageReadError> {
let table_offset = base_address.checked_add(directory_offset).ok_or_else(|| {
ReadError(format!(
"resource table offset {base_address:#x} + {directory_offset:#x} overflows"
))
})?;
if ancestors.contains(&table_offset) {
return Err(ReadError(format!(
"resource table cycle detected at offset {table_offset:#x}"
))
.into());
}
ancestors.push(table_offset);
let result = (|| {
let resource_table = read_at::<ResourceDirectoryTable>(image, table_offset as usize)?;
trace!("{} {:#x?}", "--".repeat(level + 1), resource_table);
let mut entries = IndexMap::default();
let num_entries = resource_table.number_of_name_entries as u32
+ resource_table.number_of_id_entries as u32;
let mut entry_offset = table_offset.checked_add(16).ok_or_else(|| {
ReadError(format!("resource entry offset at {table_offset:#x} overflows"))
})?;
for _ in 0..num_entries {
let entry = read_at::<ResourceDirectoryEntry>(image, entry_offset as usize)?;
trace!("{} {:#x?}", "--".repeat(level + 1), entry);
entry_offset = entry_offset
.checked_add(8)
.ok_or_else(|| ReadError("resource entry offset overflows".into()))?;
let name = match ResourceEntryName::parse(
image,
base_address,
entry.name_offset_or_integer_id,
) {
Ok(name) => name,
Err(error) => {
warn!(
"{} skipping invalid resource name: {error:?}",
"--".repeat(level + 1)
);
continue;
}
};
if entry.data_entry_or_subdirectory_offset & 0x80000000 != 0 {
match ResourceTable::parse(
image,
base_address,
virtual_address,
entry.data_entry_or_subdirectory_offset ^ 0x80000000,
level + 1,
ancestors,
) {
Ok(table) => {
entries.insert(name, ResourceEntry::Table(table));
}
Err(error) => warn!(
"{} skipping invalid resource subdirectory: {error:?}",
"--".repeat(level + 1)
),
}
} else {
let data_entry_offset =
match base_address.checked_add(entry.data_entry_or_subdirectory_offset) {
Some(offset) => offset,
None => {
warn!(
"{} skipping resource data entry with overflowing offset",
"--".repeat(level + 1)
);
continue;
}
};
trace!(
"reading {} bytes at {} (image size {})",
size_of::<ResourceDataEntry>(),
data_entry_offset,
image.len()
);
let data = match read_at::<ResourceDataEntry>(image, data_entry_offset as usize)
{
Ok(data) => data,
Err(error) => {
warn!(
"{} skipping invalid resource data entry: {error:?}",
"--".repeat(level + 1)
);
continue;
}
};
let address =
base_address as i64 + data.data_rva as i64 - virtual_address as i64;
let mut address = address as u64;
if address & 0xffffffffff000000 == 0xffffffffff000000 {
warn!(
"{} resource data entry address {:#x?} seems to be packed, ignoring padding",
"--".repeat(level + 1),
address
);
address ^= 0xffffffffff000000;
}
trace!("{} {:#x?} {:#x?}", "--".repeat(level + 1), address, data);
let resource_data = usize::try_from(address)
.ok()
.and_then(|address| checked_slice(image, address, data.size as usize).ok());
let Some(resource_data) = resource_data else {
warn!(
"{} skipping resource data outside the image",
"--".repeat(level + 1)
);
continue;
};
entries.insert(
name,
ResourceEntry::Data(ResourceData {
codepage: data.codepage,
reserved: data.reserved,
data: resource_data.to_vec().into(),
}),
);
}
}
let mut normalized_table = resource_table;
normalized_table.number_of_name_entries =
entries.keys().filter(|name| matches!(name, ResourceEntryName::Name(_))).count()
as u16;
normalized_table.number_of_id_entries =
entries.keys().filter(|name| matches!(name, ResourceEntryName::ID(_))).count()
as u16;
Ok(Self {
data: normalized_table,
entries,
})
})();
ancestors.pop();
result
}
fn build(&self, virtual_address: u32) -> Vec<u8> {
let mut tables_offset = 0;
let mut strings_offset = 0;
let mut descriptions_offset = 0;
let mut data_offset = 0;
let (mut tables_data, strings_data, mut descriptions_data, data_data) = self.build_table(
virtual_address,
&mut tables_offset,
&mut strings_offset,
&mut descriptions_offset,
&mut data_offset,
);
let mut data = Vec::new();
data.extend(tables_data.iter_mut().flat_map(|data| match data {
TableData::Table(table) => table.as_bytes(),
TableData::Entry(entry) => {
if entry.data_entry_or_subdirectory_offset & 0x80000000 == 0 {
entry.data_entry_or_subdirectory_offset += tables_offset + strings_offset;
}
if entry.name_offset_or_integer_id & 0x80000000 != 0 {
entry.name_offset_or_integer_id += tables_offset;
}
entry.as_bytes()
}
}));
data.extend(strings_data.iter());
data.extend(descriptions_data.iter_mut().flat_map(|data| {
data.data_rva += tables_offset + strings_offset + descriptions_offset;
data.as_bytes()
}));
data.extend(data_data);
data
}
fn build_table(
&self, virtual_address: u32, tables_offset: &mut u32, strings_offset: &mut u32,
descriptions_offset: &mut u32, data_offset: &mut u32,
) -> (Vec<TableData>, Vec<u8>, Vec<ResourceDataEntry>, Vec<u8>) {
let mut tables_data = Vec::<TableData>::new();
let mut strings_data = Vec::<u8>::new();
let mut descriptions_data = Vec::<ResourceDataEntry>::new();
let mut data_data = Vec::<u8>::new();
tables_data.push(TableData::Table(self.data));
*tables_offset += 16;
let mut sorted_keys: Vec<&ResourceEntryName> = self.entries.keys().collect();
sorted_keys.sort_by(|a, b| match (a, b) {
(ResourceEntryName::Name(_), ResourceEntryName::ID(_)) => Ordering::Less,
(ResourceEntryName::ID(_), ResourceEntryName::Name(_)) => Ordering::Greater,
(ResourceEntryName::Name(_), ResourceEntryName::Name(_)) => {
match (a.to_string(), b.to_string()) {
(Some(a), Some(b)) => a.to_uppercase().cmp(&b.to_uppercase()),
_ => a.string_data().cmp(b.string_data()),
}
}
(ResourceEntryName::ID(a), ResourceEntryName::ID(b)) => a.cmp(b),
});
let mut next_table_offset = 0u32;
let mut next_table_sizes = 0u32;
for name in &sorted_keys {
let entry = &self.entries[*name];
strings_data.extend(name.string_data());
let name_offset_or_integer_id = if name.string_size() > 0 {
*strings_offset | 0x80000000
} else {
name.id()
};
*strings_offset += name.string_size();
match entry {
ResourceEntry::Table(table) => {
let entry_data = ResourceDirectoryEntry {
name_offset_or_integer_id,
data_entry_or_subdirectory_offset: (*tables_offset
+ self.entries.len() as u32 * 8
+ next_table_sizes)
| 0x80000000,
};
tables_data.push(TableData::Entry(entry_data));
next_table_offset += 8;
next_table_sizes += table.tables_size();
}
ResourceEntry::Data(data) => {
let entry_data = ResourceDirectoryEntry {
name_offset_or_integer_id,
data_entry_or_subdirectory_offset: *descriptions_offset,
};
tables_data.push(TableData::Entry(entry_data));
next_table_offset += 8;
let mut data_len = data.data.len();
if data_len > u32::MAX as _ {
error!(
"resource entry data is larger than {}B and will be truncated (is {}B)",
u32::MAX,
data_len
);
data_len = u32::MAX as _;
}
let data_len = data_len as u32;
data_data.extend(&data.data[..data_len as usize]);
let description_data = ResourceDataEntry {
data_rva: *data_offset + virtual_address,
size: data_len,
codepage: data.codepage,
reserved: data.reserved,
};
descriptions_data.push(description_data);
*descriptions_offset += 16;
*data_offset += data_len;
}
}
}
*tables_offset += next_table_offset;
for name in &sorted_keys {
let entry = &self.entries[*name];
match entry {
ResourceEntry::Table(table) => {
let (t_tables_data, t_strings_data, t_descriptions_data, t_data_data) = table
.build_table(
virtual_address,
tables_offset,
strings_offset,
descriptions_offset,
data_offset,
);
tables_data.extend(t_tables_data);
strings_data.extend(t_strings_data);
descriptions_data.extend(t_descriptions_data);
data_data.extend(t_data_data);
}
ResourceEntry::Data(_) => {}
}
}
(tables_data, strings_data, descriptions_data, data_data)
}
pub fn get<N: Borrow<ResourceEntryName>>(&self, name: N) -> Option<&ResourceEntry> {
self.entries.get(name.borrow())
}
pub fn get_mut<N: Borrow<ResourceEntryName>>(&mut self, name: N) -> Option<&mut ResourceEntry> {
self.entries.get_mut(name.borrow())
}
pub fn insert<N: Borrow<ResourceEntryName>>(
&mut self, name: N, entry: ResourceEntry,
) -> Option<ResourceEntry> {
let name = name.borrow();
let entry = self.entries.insert(name.clone(), entry);
if entry.is_none() {
if name.string_size() > 0 {
self.data.number_of_name_entries += 1;
} else {
self.data.number_of_id_entries += 1;
}
}
entry
}
pub fn insert_at<N: Borrow<ResourceEntryName>>(
&mut self, name: N, entry: ResourceEntry, position: usize,
) -> Option<ResourceEntry> {
let name = name.borrow();
let len = self.entries.len();
let old_entry = self.entries.get(name).cloned();
let new_entry = self
.entries
.entry(name.clone())
.and_modify(|old_entry| *old_entry = entry.clone());
let index = new_entry.index();
new_entry.or_insert(entry);
self.entries.move_index(index, position);
if index >= len {
if name.string_size() > 0 {
self.data.number_of_name_entries += 1;
} else {
self.data.number_of_id_entries += 1;
}
}
old_entry
}
pub fn remove<N: Borrow<ResourceEntryName>>(&mut self, name: N) -> Option<ResourceEntry> {
let name = name.borrow();
if let Some(entry) = self.entries.swap_remove(name) {
if name.string_size() > 0 {
self.data.number_of_name_entries -= 1;
} else {
self.data.number_of_id_entries -= 1;
}
Some(entry)
} else {
None
}
}
pub fn entries(&self) -> Vec<&ResourceEntryName> { self.entries.keys().collect() }
pub fn size(&self) -> u32 {
self.tables_size() + self.strings_size() + self.descriptions_size() + self.data_size()
}
pub fn tables_size(&self) -> u32 {
self.entries.iter().map(|(_, entry)| entry.table_size()).sum::<u32>() + 16
}
pub fn strings_size(&self) -> u32 {
self.entries
.iter()
.map(|(name, entry)| name.string_size() + entry.strings_size())
.sum::<u32>()
}
pub fn descriptions_size(&self) -> u32 {
self.entries.iter().map(|(_, entry)| entry.description_size()).sum::<u32>()
}
pub fn data_size(&self) -> u32 {
self.entries.iter().map(|(_, entry)| entry.data_size()).sum::<u32>()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ResourceData {
data: DebugIgnore<Vec<u8>>,
codepage: u32,
reserved: u32,
}
impl Default for ResourceData {
fn default() -> Self {
Self {
data: Vec::new().into(),
codepage: CODE_PAGE_ID_EN_US as u32,
reserved: 0,
}
}
}
impl ResourceData {
pub fn data(&self) -> &[u8] { &self.data }
pub fn codepage(&self) -> u32 { self.codepage }
pub fn set_data(&mut self, data: Vec<u8>) { self.data = data.into(); }
pub fn set_codepage(&mut self, codepage: u32) { self.codepage = codepage; }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ResourceEntry {
Table(ResourceTable),
Data(ResourceData),
}
impl Default for ResourceEntry {
fn default() -> Self { Self::Data(ResourceData::default()) }
}
impl ResourceEntry {
pub fn is_table(&self) -> bool {
match self {
ResourceEntry::Table(_) => true,
ResourceEntry::Data(_) => false,
}
}
pub fn is_data(&self) -> bool {
match self {
ResourceEntry::Table(_) => false,
ResourceEntry::Data(_) => true,
}
}
pub fn as_table(&self) -> Option<&ResourceTable> {
match self {
ResourceEntry::Table(table) => Some(table),
ResourceEntry::Data(_) => None,
}
}
pub fn as_table_mut(&mut self) -> Option<&mut ResourceTable> {
match self {
ResourceEntry::Table(table) => Some(table),
ResourceEntry::Data(_) => None,
}
}
pub fn as_data(&self) -> Option<&ResourceData> {
match self {
ResourceEntry::Table(_) => None,
ResourceEntry::Data(entry) => Some(entry),
}
}
pub fn as_data_mut(&mut self) -> Option<&mut ResourceData> {
match self {
ResourceEntry::Table(_) => None,
ResourceEntry::Data(entry) => Some(entry),
}
}
pub fn table_size(&self) -> u32 {
match self {
ResourceEntry::Table(table) => table.tables_size() + 8,
ResourceEntry::Data(_) => 8,
}
}
pub fn strings_size(&self) -> u32 {
match self {
ResourceEntry::Table(table) => table.strings_size(),
ResourceEntry::Data(_) => 0,
}
}
pub fn description_size(&self) -> u32 {
match self {
ResourceEntry::Table(table) => table.descriptions_size(),
ResourceEntry::Data(_) => 16,
}
}
pub fn data_size(&self) -> u32 {
match self {
ResourceEntry::Table(table) => table.data_size(),
ResourceEntry::Data(data) => data.data.len() as u32,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ResourceEntryName {
ID(u32),
Name(Vec<u8>),
}
impl Default for ResourceEntryName {
fn default() -> Self { Self::ID(LANGUAGE_ID_EN_US as u32) }
}
impl ResourceEntryName {
fn parse(image: &[u8], offset: u32, id: u32) -> Result<Self, ReadError> {
if id & 0x80000000 != 0 {
trace!("reading resource name {:#x?}", id);
let address = offset
.checked_add(id ^ 0x80000000)
.ok_or_else(|| ReadError("resource name offset overflows".into()))?;
let length = read_at::<u16>(image, address as usize)? as usize;
trace!("resource name length: {}", length);
let byte_length = length
.checked_mul(2)
.and_then(|length| length.checked_add(2))
.ok_or_else(|| ReadError("resource name length overflows".into()))?;
let data = checked_slice(image, address as usize, byte_length)?;
trace!("resource name: {:x?}", data);
Ok(Self::Name(data.to_vec()))
} else {
trace!("reading resource id {:#x?}", id);
Ok(Self::ID(id))
}
}
pub fn from_string<S: AsRef<str>>(string: S) -> Self {
Self::try_from_string(string).expect("resource name exceeds 65535 UTF-16 units")
}
pub fn try_from_string<S: AsRef<str>>(string: S) -> Result<Self, ResourceError> {
let string = string.as_ref();
let string = string.encode_utf16().collect::<Vec<_>>();
let length = u16::try_from(string.len()).map_err(|_| {
ResourceError::InvalidBytes(ReadError(
"resource name exceeds 65535 UTF-16 units".into(),
))
})?;
let mut data = Vec::with_capacity(string.len() * 2 + 2);
data.extend_from_slice(&length.to_le_bytes());
data.extend(string.into_iter().flat_map(|c| c.to_le_bytes()));
Ok(Self::Name(data))
}
pub fn to_string(&self) -> Option<String> {
match self {
Self::ID(_) => None,
Self::Name(data) => {
let length = read_at::<u16>(data, 0).ok()? as usize;
let data = checked_slice(data, 2, length.checked_mul(2)?).ok()?;
let string = (0..length)
.map(|index| u16::from_le_bytes([data[index * 2], data[index * 2 + 1]]))
.collect::<Vec<_>>();
Some(String::from_utf16_lossy(&string))
}
}
}
fn string_size(&self) -> u32 {
match self {
Self::ID(_) => 0,
Self::Name(name) => name.len() as u32,
}
}
fn id(&self) -> u32 {
match self {
Self::ID(id) => *id,
Self::Name(_) => unreachable!(),
}
}
fn string_data(&self) -> &[u8] {
match self {
Self::ID(_) => &[],
Self::Name(data) => data.as_bytes(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct VersionStringTable {
pub key: String,
pub strings: IndexMap<String, String, RandomState>,
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct VersionInfo {
pub info: FixedFileInfo,
pub strings: Vec<VersionStringTable>,
pub vars: Vec<VersionU16>,
}
fn version_block_end(
start: usize, length: u16, parent_end: usize, name: &str,
) -> Result<usize, ReadError> {
if (length as usize) < size_of::<VersionHeader>() {
return Err(ReadError(format!("{name} length is smaller than its header")));
}
let end = start
.checked_add(length as usize)
.ok_or_else(|| ReadError(format!("{name} length overflows")))?;
if end > parent_end {
return Err(ReadError(format!("{name} end {end:#x} exceeds parent end {parent_end:#x}")));
}
Ok(end)
}
fn version_length(value: usize, name: &str) -> Result<u16, ResourceError> {
u16::try_from(value).map_err(|_| {
ResourceError::InvalidBytes(ReadError(format!("{name} exceeds the 65535-byte limit")))
})
}
impl VersionInfo {
pub fn parse(data: &[u8]) -> Result<Self, ReadError> {
let header = read::<VersionHeader>(data)?;
let root_end = version_block_end(0, header.length, data.len(), "version root")?;
let data = checked_slice(data, 0, root_end)?;
let version_root_key =
read_u16_string(checked_slice(data, size_of::<VersionHeader>(), 32)?)?;
if version_root_key != "VS_VERSION_INFO" {
return Err(ReadError(format!("invalid version root key: {:?}", version_root_key)));
}
let value_offset =
aligned_to(size_of::<VersionHeader>() + u16_string_len(&version_root_key) * 2 + 2, 4);
if header.value_length != size_of::<FixedFileInfo>() as u16 {
return Err(ReadError(format!(
"invalid file info length: {:#x?}",
header.value_length
)));
}
let info = read_at::<FixedFileInfo>(data, value_offset)?;
if info.signature != 0xfeef04bd {
return Err(ReadError(format!(
"invalid fixed file info signature: {:#x?}",
info.signature
)));
}
let file_info_header_offset = aligned_to(value_offset + size_of::<FixedFileInfo>(), 4);
let mut child_offset = file_info_header_offset;
let mut strings = Vec::new();
let mut vars = Vec::new();
while child_offset < data.len() {
let file_info_header = read_at::<VersionHeader>(data, child_offset)?;
let file_info_end = version_block_end(
child_offset,
file_info_header.length,
data.len(),
"version child",
)?;
let key_offset = child_offset + size_of::<VersionHeader>();
let file_info_key =
read_u16_string(checked_slice(data, key_offset, file_info_end - key_offset)?)?;
let mut tables_offset = aligned_to(
child_offset + size_of::<VersionHeader>() + u16_string_len(&file_info_key) * 2 + 2,
4,
);
if tables_offset > file_info_end {
return Err(ReadError("version child key exceeds its block".into()));
}
match file_info_key.as_str() {
"VarFileInfo" => {
let var_header = read_at::<VersionHeader>(data, tables_offset)?;
let var_end = version_block_end(
tables_offset,
var_header.length,
file_info_end,
"translation table",
)?;
let key_offset = tables_offset + size_of::<VersionHeader>();
let table_key =
read_u16_string(checked_slice(data, key_offset, var_end - key_offset)?)?;
if &table_key != "Translation" {
return Err(ReadError(format!("invalid var table key: {:?}", table_key)));
}
let vars_offset = aligned_to(
tables_offset
+ size_of::<VersionHeader>()
+ u16_string_len(&table_key) * 2
+ 2,
4,
);
let vars_end = vars_offset
.checked_add(var_header.value_length as usize)
.ok_or_else(|| ReadError("translation value length overflows".into()))?;
if vars_end > var_end
|| var_header.value_length as usize % size_of::<u32>() != 0
{
return Err(ReadError("invalid translation value length".into()));
}
let mut var_offset = vars_offset;
while var_offset < vars_end {
vars.push(read_at::<VersionU16>(data, var_offset)?);
var_offset += size_of::<u32>();
}
}
"StringFileInfo" => {
while tables_offset < file_info_end {
let string_table_header = read_at::<VersionHeader>(data, tables_offset)?;
let string_table_end = version_block_end(
tables_offset,
string_table_header.length,
file_info_end,
"version string table",
)?;
let key_offset = tables_offset + size_of::<VersionHeader>();
let string_table_key = read_u16_string(checked_slice(
data,
key_offset,
string_table_end - key_offset,
)?)?;
let strings_offset = aligned_to(
tables_offset
+ size_of::<VersionHeader>()
+ u16_string_len(&string_table_key) * 2
+ 2,
4,
);
if strings_offset > string_table_end {
return Err(ReadError(
"version string table key exceeds its block".into(),
));
}
let mut string_offset = strings_offset;
let mut string_table = VersionStringTable {
key: string_table_key,
strings: IndexMap::default(),
};
while string_offset < string_table_end {
let string_start = string_offset;
let string_header = read_at::<VersionHeader>(data, string_start)?;
let string_end = version_block_end(
string_start,
string_header.length,
string_table_end,
"version string",
)?;
let key_offset = string_start + size_of::<VersionHeader>();
let string_key = read_u16_string(checked_slice(
data,
key_offset,
string_end - key_offset,
)?)?;
let value_offset =
aligned_to(key_offset + u16_string_len(&string_key) * 2 + 2, 4);
let value_length =
(string_header.value_length as usize).checked_mul(2).ok_or_else(
|| ReadError("version string length overflows".into()),
)?;
let value_end = value_offset
.checked_add(value_length)
.ok_or_else(|| ReadError("version string end overflows".into()))?;
if value_end > string_end {
return Err(ReadError(
"version string value exceeds its block".into(),
));
}
if string_header.value_length > 0 && string_header.type_ == 1 {
let string_value = read_u16_string(checked_slice(
data,
value_offset,
value_length,
)?)?;
string_table.strings.insert(string_key, string_value);
} else if string_header.value_length > 0 {
error!(
"invalid string value type: {:#x?} (expected 0x1)",
string_header.type_
);
};
string_offset = aligned_to(string_end, 4);
}
tables_offset = aligned_to(string_table_end, 4);
strings.push(string_table);
}
}
_ => {
return Err(ReadError(format!(
"invalid version string key: {:?}",
file_info_key
)));
}
}
child_offset = aligned_to(file_info_end, 4);
}
Ok(Self {
info,
strings,
vars,
})
}
pub fn try_build(&self) -> Result<Vec<u8>, ResourceError> {
let mut data = Vec::new();
let mut string_tables = Vec::new();
for string_table_data in &self.strings {
let mut string_table_children = Vec::new();
for (key, value) in &string_table_data.strings {
let key_units = u16_string_len(key);
let value_units = u16_string_len(value);
let mut string = Vec::new();
string.extend(
VersionHeader {
length: version_length(
aligned_to(6 + key_units * 2 + 2, 4) + value_units * 2 + 2,
"version string",
)?,
value_length: version_length(value_units + 1, "version string value")?,
type_: 1,
}
.as_bytes(),
);
string.extend(string_to_u16(key));
string.extend(iter::repeat(0).take(aligned_to(string.len(), 4) - string.len()));
string.extend(string_to_u16(value));
string.extend(iter::repeat(0).take(aligned_to(string.len(), 4) - string.len()));
string_table_children.extend(string);
}
let mut string_table = Vec::new();
let key_units = u16_string_len(&string_table_data.key);
string_table.extend(
VersionHeader {
length: version_length(
aligned_to(6 + key_units * 2 + 2, 4) + string_table_children.len(),
"version string table",
)?,
value_length: 0,
type_: 1,
}
.as_bytes(),
);
string_table.extend(string_to_u16(&string_table_data.key));
string_table.extend(
iter::repeat(0).take(aligned_to(string_table.len(), 4) - string_table.len()),
);
string_table.extend(string_table_children);
string_tables.extend(string_table);
}
let mut string_info = Vec::new();
let string_info_key_units = u16_string_len("StringFileInfo");
string_info.extend(
VersionHeader {
length: version_length(
aligned_to(6 + string_info_key_units * 2 + 2, 4) + string_tables.len(),
"string file info",
)?,
value_length: 0,
type_: 1,
}
.as_bytes(),
);
string_info.extend(string_to_u16("StringFileInfo"));
string_info
.extend(iter::repeat(0).take(aligned_to(string_info.len(), 4) - string_info.len()));
string_info.extend(string_tables);
let mut var = Vec::new();
let var_data_length = self
.vars
.len()
.checked_mul(size_of::<VersionU16>())
.expect("translation data length overflows usize");
let translation_key_units = u16_string_len("Translation");
var.extend(
VersionHeader {
length: version_length(
aligned_to(6 + translation_key_units * 2 + 2, 4) + var_data_length,
"translation table",
)?,
value_length: version_length(var_data_length, "translation data")?,
type_: 0,
}
.as_bytes(),
);
var.extend(string_to_u16("Translation"));
var.extend(iter::repeat(0).take(aligned_to(var.len(), 4) - var.len()));
var.extend(self.vars.iter().flat_map(|var| var.as_bytes()));
var.extend(iter::repeat(0).take(aligned_to(var.len(), 4) - var.len()));
let mut var_info = Vec::new();
let var_info_key_units = u16_string_len("VarFileInfo");
var_info.extend(
VersionHeader {
length: version_length(
aligned_to(6 + var_info_key_units * 2 + 2, 4) + var.len(),
"var file info",
)?,
value_length: 0,
type_: 1,
}
.as_bytes(),
);
var_info.extend(string_to_u16("VarFileInfo"));
var_info.extend(iter::repeat(0).take(aligned_to(var_info.len(), 4) - var_info.len()));
var_info.extend(var);
let root_key_units = u16_string_len("VS_VERSION_INFO");
data.extend(
VersionHeader {
length: version_length(
aligned_to(
aligned_to(6 + root_key_units * 2 + 2, 4) + size_of::<FixedFileInfo>(),
4,
) + string_info.len()
+ var_info.len(),
"version root",
)?,
value_length: version_length(size_of::<FixedFileInfo>(), "fixed file info")?,
type_: 0,
}
.as_bytes(),
);
data.extend(string_to_u16("VS_VERSION_INFO"));
data.extend(iter::repeat(0).take(aligned_to(data.len(), 4) - data.len()));
data.extend(self.info.as_bytes());
data.extend(iter::repeat(0).take(aligned_to(data.len(), 4) - data.len()));
data.extend(string_info);
data.extend(var_info);
Ok(data)
}
pub fn build(&self) -> Vec<u8> {
self.try_build().expect("version information exceeds format limits")
}
}