use crate::error;
use crate::options::Permissive;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::ops::Not;
use log::debug;
use scroll::ctx;
use scroll::{Pread, Pwrite, SizeWith};
use crate::pe::data_directories;
use crate::pe::options;
use crate::pe::section_table;
use crate::pe::utils;
pub(super) const SIZE_OF_WCHAR: usize = core::mem::size_of::<u16>();
pub(super) fn to_utf16_string(bytes: &[u8]) -> Option<String> {
if bytes.len() % 2 != 0 {
return None;
}
let u16_chars = bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.take_while(|&wchar| wchar != 0)
.collect::<Vec<_>>();
String::from_utf16(&u16_chars).ok()
}
#[derive(Debug, Copy, Clone, Default, PartialEq)]
pub(crate) struct Utf16String<'a>(&'a [u8]);
impl<'a> Utf16String<'a> {
pub(crate) fn to_string(&self) -> Option<String> {
to_utf16_string(self.0)
}
pub(crate) fn len(&self) -> usize {
self.0.len()
}
}
impl<'a> ctx::TryFromCtx<'a, scroll::Endian> for Utf16String<'a> {
type Error = crate::error::Error;
fn try_from_ctx(bytes: &'a [u8], _ctx: scroll::Endian) -> error::Result<(Self, usize)> {
let len = bytes
.chunks_exact(2)
.take_while(|x| u16::from_le_bytes([x[0], x[1]]) != 0u16)
.count()
* SIZE_OF_WCHAR;
if len > bytes.len() {
return Err(error::Error::Malformed(format!(
"Invalid UTF-16 string length: {len}"
)));
}
Ok((Self(&bytes[..len]), len + SIZE_OF_WCHAR)) }
}
pub const RT_CURSOR: u16 = 1;
pub const RT_BITMAP: u16 = 2;
pub const RT_ICON: u16 = 3;
pub const RT_MENU: u16 = 4;
pub const RT_DIALOG: u16 = 5;
pub const RT_STRING: u16 = 6;
pub const RT_FONTDIR: u16 = 7;
pub const RT_FONT: u16 = 8;
pub const RT_ACCELERATOR: u16 = 9;
pub const RT_RCDATA: u16 = 10;
pub const RT_MESSAGETABLE: u16 = 11;
pub const RT_GROUP_CURSOR: u16 = 12;
pub const RT_GROUP_ICON: u16 = 14;
pub const RT_VERSION: u16 = 16;
pub const RT_DLGINCLUDE: u16 = 17;
pub const RT_PLUGPLAY: u16 = 19;
pub const RT_VXD: u16 = 20;
pub const RT_ANICURSOR: u16 = 21;
pub const RT_ANIICON: u16 = 22;
pub const RT_HTML: u16 = 23;
pub const RT_MANIFEST: u16 = 24;
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
pub struct ImageResourceDirectory {
pub characteristics: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub number_of_named_entries: u16,
pub number_of_id_entries: u16,
}
pub const IMAGE_RESOURCE_NAME_IS_STRING: u32 = 0x80000000;
pub const IMAGE_RESOURCE_DATA_IS_DIRECTORY: u32 = 0x80000000;
pub const IMAGE_RESOURCE_MASK: u32 = 0x7FFFFFFF;
impl<'a> ImageResourceDirectory {
pub fn parse(
bytes: &'a [u8],
dd: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
) -> error::Result<Self> {
Self::parse_with_opts(
bytes,
dd,
sections,
file_alignment,
&options::ParseOptions::default(),
)
}
pub fn parse_with_opts(
bytes: &'a [u8],
dd: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let rva = dd.virtual_address as usize;
let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"Cannot map ImageResourceDirectory rva {:#x} into offset",
rva
))
})?;
let resource_dir = bytes.pread_with(offset, scroll::LE)?;
Ok(resource_dir)
}
pub fn count(&self) -> u16 {
self.number_of_id_entries
.saturating_add(self.number_of_named_entries)
}
pub fn entries_size(&self) -> usize {
self.count() as usize * RESOURCE_ENTRY_SIZE
}
pub fn next_iter(
&self,
offset: usize,
bytes: &'a [u8],
) -> error::Result<ResourceEntryIterator<'a>> {
let bytes = bytes.pread_with::<&[u8]>(offset, self.entries_size())?;
Ok(ResourceEntryIterator { data: bytes })
}
}
#[derive(Debug, Copy, Clone)]
pub struct ResourceEntryIterator<'a> {
data: &'a [u8],
}
impl Iterator for ResourceEntryIterator<'_> {
type Item = error::Result<ResourceEntry>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
Some(match self.data.pread_with(0, scroll::LE) {
Ok(func) => {
self.data = &self.data[RESOURCE_ENTRY_SIZE..];
Ok(func)
}
Err(error) => {
self.data = &[];
Err(error.into())
}
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.data.len() / RESOURCE_ENTRY_SIZE;
(len, Some(len))
}
}
impl<'a> ResourceEntryIterator<'a> {
pub fn find_by_id(&self, id: u16) -> error::Result<Option<ResourceEntry>> {
self.map(|x| {
x.and_then(|x| {
if x.id() == Some(id) {
Ok(Some(x))
} else {
Ok(None)
}
})
})
.find_map(Result::transpose)
.transpose()
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
pub struct ResourceDataEntry {
pub offset_to_data: u32,
pub size: u32,
pub code_page: u32,
pub reserved: u32,
}
#[derive(PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
pub struct ResourceEntry {
pub name_or_id: u32,
pub offset_to_data_or_directory: u32,
}
pub const RESOURCE_ENTRY_SIZE: usize = core::mem::size_of::<u64>();
impl fmt::Debug for ResourceEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ResourceEntry")
.field("value", &format_args!("{:#x}", self.value()))
.field("name_is_string", &self.name_is_string())
.field("name_offset", &format_args!("{:#x}", self.name_offset()))
.field("id", &self.id())
.field("data_is_directory", &self.data_is_directory())
.field(
"offset_to_directory",
&format_args!("{:#x}", self.offset_to_directory()),
)
.field(
"offset_to_data",
&format_args!("{:#x?}", self.offset_to_data()),
)
.finish()
}
}
impl ResourceEntry {
pub fn value(&self) -> u64 {
((self.name_or_id) as u64) << 32 | self.offset_to_data_or_directory as u64
}
pub fn name_is_string(&self) -> bool {
self.name_or_id & IMAGE_RESOURCE_NAME_IS_STRING != 0
}
pub fn name_offset(&self) -> u32 {
self.name_or_id & IMAGE_RESOURCE_MASK
}
pub fn id(&self) -> Option<u16> {
self.name_is_string().not().then(|| self.name_or_id as u16)
}
pub fn data_is_directory(&self) -> bool {
self.offset_to_data_or_directory & IMAGE_RESOURCE_DATA_IS_DIRECTORY != 0
}
pub fn offset_to_directory(&self) -> u32 {
self.offset_to_data_or_directory & IMAGE_RESOURCE_MASK
}
pub fn offset_to_data(&self) -> Option<u32> {
self.data_is_directory()
.not()
.then(|| self.offset_to_data_or_directory)
}
pub fn next_depth<'a>(&self, bytes: &'a [u8]) -> error::Result<Option<ResourceEntry>> {
let mut offset = self.offset_to_directory() as usize;
let dir = bytes.gread_with::<ImageResourceDirectory>(&mut offset, scroll::LE)?;
let iterator = dir.next_iter(offset, bytes)?;
let entries = iterator.collect::<Result<Vec<_>, _>>()?;
Ok(entries.first().map(|x| *x))
}
pub fn recursive_next_depth<'a, P>(
&self,
bytes: &'a [u8],
predicate: P,
) -> error::Result<Option<ResourceEntry>>
where
P: Fn(&Self) -> bool,
{
self.iterative_next_depth(bytes, predicate)
}
fn iterative_next_depth<'a, P>(
&self,
bytes: &'a [u8],
predicate: P,
) -> error::Result<Option<ResourceEntry>>
where
P: Fn(&Self) -> bool,
{
let mut current = *self;
let mut visited = alloc::collections::BTreeSet::new();
visited.insert(current.offset_to_data_or_directory);
while let Some(next) = current.next_depth(bytes)? {
if !predicate(&next) {
return Ok(Some(next));
}
if !visited.insert(next.offset_to_data_or_directory) {
return Err(error::Error::Malformed(format!(
"Cycle detected in resource directory at offset {:#x}",
next.offset_to_data_or_directory
)));
}
current = next;
}
Ok(Some(current))
}
}
impl From<u64> for ResourceEntry {
fn from(value: u64) -> Self {
Self {
name_or_id: (value >> 32) as u32,
offset_to_data_or_directory: value as u32,
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub struct ResourceData<'a> {
pub image_resource_directory: ImageResourceDirectory,
pub version_info: Option<VersionInfo<'a>>,
pub manifest_data: Option<ManifestData<'a>>,
data: &'a [u8],
}
impl<'a> ResourceData<'a> {
pub fn parse(
bytes: &'a [u8],
dd: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
) -> error::Result<Self> {
Self::parse_with_opts(
bytes,
dd,
sections,
file_alignment,
&options::ParseOptions::default(),
)
}
pub fn parse_with_opts(
bytes: &'a [u8],
dd: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let image_resource_directory =
ImageResourceDirectory::parse_with_opts(bytes, dd, sections, file_alignment, opts)?;
let rva = dd.virtual_address as usize;
let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"Cannot map ImageResourceDirectory rva {:#x} into offset",
rva
))
})?;
let data = bytes
.pread_with::<&[u8]>(offset, dd.size as usize)
.map_err(|_| {
error::Error::Malformed(format!(
"Resource directory offset ({offset:#x}) out of bounds"
))
})?;
let offset = core::mem::size_of::<ImageResourceDirectory>();
let size = image_resource_directory.entries_size();
if offset > data.len() {
return Err(error::Error::Malformed(format!(
"Resource entry offset ({offset:#x}) out of bounds"
)))
.or_permissive_and_default(
opts.parse_mode.is_permissive(),
"Resource entry offset out of bounds; skipping",
);
}
let iterator_data = data
.pread_with::<&[u8]>(offset, size)
.map_err(|_| {
error::Error::Malformed(format!(
"Resource entry offset ({offset:#x}) out of bounds"
))
})
.or_permissive_and_default(
opts.parse_mode.is_permissive(),
"Resource entry offset out of bounds; skipping",
)?;
let iterator = ResourceEntryIterator {
data: iterator_data,
};
let version_info =
VersionInfo::parse(bytes, data, iterator, sections, file_alignment, opts)?;
let manifest_data =
ManifestData::parse(bytes, data, iterator, sections, file_alignment, opts)?;
Ok(ResourceData {
image_resource_directory,
data,
version_info,
manifest_data,
})
}
pub fn count(&self) -> u16 {
self.image_resource_directory.count()
}
pub fn entries(&self) -> ResourceEntryIterator<'a> {
let offset = core::mem::size_of::<ImageResourceDirectory>();
let size = self.image_resource_directory.entries_size();
ResourceEntryIterator {
data: &self.data[offset..offset + size],
}
}
}
pub const VS_FFI_SIGNATURE: u32 = 0xFEEF04BD;
pub const VS_FFI_STRUCVERSION: u32 = 0x00010000;
pub const VS_FFI_FILEFLAGSMASK: u32 = 0x0000003F;
pub const VS_FF_DEBUG: u32 = 0x00000001;
pub const VS_FF_PRERELEASE: u32 = 0x00000002;
pub const VS_FF_PATCHED: u32 = 0x00000004;
pub const VS_FF_PRIVATEBUILD: u32 = 0x00000008;
pub const VS_FF_INFOINFERRED: u32 = 0x00000010;
pub const VS_FF_SPECIALBUILD: u32 = 0x00000020;
pub const VOS_UNKNOWN: u32 = 0x00000000;
pub const VOS_DOS: u32 = 0x00010000;
pub const VOS_OS216: u32 = 0x00020000;
pub const VOS_OS232: u32 = 0x00030000;
pub const VOS_NT: u32 = 0x00040000;
pub const VOS_WINCE: u32 = 0x00050000;
#[doc(alias("VOS__BASE"))]
pub const VOS_BASE: u32 = 0x00000000;
#[doc(alias("VOS__WINDOWS16"))]
pub const VOS_WINDOWS16: u32 = 0x00000001;
#[doc(alias("VOS__PM16"))]
pub const VOS_PM16: u32 = 0x00000002;
#[doc(alias("VOS__PM32"))]
pub const VOS_PM32: u32 = 0x00000003;
#[doc(alias("VOS__WINDOWS32"))]
pub const VOS_WINDOWS32: u32 = 0x00000004;
pub const VOS_DOS_WINDOWS16: u32 = 0x00010001;
pub const VOS_DOS_WINDOWS32: u32 = 0x00010004;
pub const VOS_OS216_PM16: u32 = 0x00020002;
pub const VOS_OS216_PM32: u32 = 0x00030003;
pub const VOS_NT_WINDOWS32: u32 = 0x00040004;
pub const VFT_UNKNOWN: u32 = 0x00000000;
pub const VFT_APP: u32 = 0x00000001;
pub const VFT_DLL: u32 = 0x00000002;
pub const VFT_DRV: u32 = 0x00000003;
pub const VFT_FONT: u32 = 0x00000004;
pub const VFT_VXD: u32 = 0x00000005;
pub const VFT_STATIC_LIB: u32 = 0x00000007;
pub const VFT2_UNKNOWN: u32 = 0x00000000;
pub const VFT2_DRV_PRINTER: u32 = 0x00000001;
pub const VFT2_DRV_KEYBOARD: u32 = 0x00000002;
pub const VFT2_DRV_LANGUAGE: u32 = 0x00000003;
pub const VFT2_DRV_DISPLAY: u32 = 0x00000004;
pub const VFT2_DRV_MOUSE: u32 = 0x00000005;
pub const VFT2_DRV_NETWORK: u32 = 0x00000006;
pub const VFT2_DRV_SYSTEM: u32 = 0x00000007;
pub const VFT2_DRV_INSTALLABLE: u32 = 0x00000008;
pub const VFT2_DRV_SOUND: u32 = 0x00000009;
pub const VFT2_DRV_COMM: u32 = 0x0000000A;
pub const VFT2_DRV_INPUTMETHOD: u32 = 0x0000000B;
pub const VFT2_DRV_VERSIONED_PRINTER: u32 = 0x0000000C;
pub const VFT2_FONT_RASTER: u32 = 0x00000001;
pub const VFT2_FONT_VECTOR: u32 = 0x00000002;
pub const VFT2_FONT_TRUETYPE: u32 = 0x00000003;
#[derive(Debug, Copy, Clone)]
pub struct ResourceStringIterator<'a> {
data: &'a [u8],
}
impl<'a> Iterator for ResourceStringIterator<'a> {
type Item = error::Result<ResourceString<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
let mut offset = 0;
Some(match ResourceString::parse(self.data, &mut offset) {
Ok(next) => {
debug!(
"Parsed next resource string as size {:#x}: {:#x?}",
offset, next?
);
self.data = &self.data[offset..];
Ok(next?)
}
Err(error) => {
self.data = &[];
Err(error.into())
}
})
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct ResourceString<'a> {
pub len: u16,
pub value_len: u16,
pub r#type: u16,
pub(crate) key: Utf16String<'a>,
pub value: &'a [u8],
}
pub const RESOURCE_STRING_FIELD_ALIGNMENT: usize = core::mem::size_of::<u32>();
impl fmt::Debug for ResourceString<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = f.debug_struct("ResourceString");
debug_struct.field("len", &format_args!("{:#x}", self.len));
if self.is_text_data() {
debug_struct.field(
"value_len",
&format_args!(
"{:#x} ({} bytes)",
self.value_len,
self.value_len * SIZE_OF_WCHAR as u16
),
);
} else {
debug_struct.field("value_len", &format_args!("{:#x}", self.value_len));
}
debug_struct
.field(
"type",
&format_args!(
"{} ({})",
self.r#type,
match self.r#type {
0 => "Binary Data",
1 => "String Data",
_ => "Unknown",
}
),
)
.field("key", &self.key_string())
.field(
"key_slice",
&format_args!("{:02x?} ({} bytes)", self.key, self.key.len()),
);
if self.is_text_data() && self.value_len > 0 {
debug_struct.field("value", &self.value_string());
}
debug_struct
.field(
"value_slice",
&format_args!(
"{:02x?} ({} bytes, {})",
self.value,
self.value.len(),
if self.value.len() == self.value_len as usize {
"Correct"
} else {
"Incorrect"
}
),
)
.finish()
}
}
impl<'a> ResourceString<'a> {
pub fn parse(bytes: &'a [u8], offset: &mut usize) -> error::Result<Option<Self>> {
let len = bytes.gread_with::<u16>(offset, scroll::LE)?;
if len == 0 {
return Ok(None);
}
let value_len = bytes.gread_with::<u16>(offset, scroll::LE)?;
let r#type = bytes.gread_with::<u16>(offset, scroll::LE)?;
let key = bytes.gread_with::<Utf16String>(offset, scroll::LE)?;
*offset = utils::align_up(*offset, RESOURCE_STRING_FIELD_ALIGNMENT);
let unaligned_value_len = if r#type == 1 {
value_len.checked_mul(SIZE_OF_WCHAR as u16).ok_or_else(|| {
error::Error::Malformed(format!(
"ResourceString value_len overflow: {} * {}",
value_len, SIZE_OF_WCHAR
))
})? as usize
} else {
value_len as usize
};
let bytes_remaining = bytes.len().saturating_sub(*offset);
if unaligned_value_len > bytes_remaining {
return Err(error::Error::Malformed(format!(
"ResourceString value_len ({}) exceeds available bytes ({})",
unaligned_value_len, bytes_remaining
))
.into());
}
let aligned_value_len = utils::align_up(unaligned_value_len, 4);
let actual_read_len = if aligned_value_len <= bytes_remaining {
aligned_value_len
} else {
unaligned_value_len
};
let value = bytes.pread_with::<&[u8]>(*offset, actual_read_len)?;
*offset += value.len();
Ok(Some(Self {
len,
value_len,
r#type,
key,
value,
}))
}
pub fn is_text_data(&self) -> bool {
self.r#type == 1
}
pub fn is_binary_data(&self) -> bool {
self.r#type == 0
}
pub fn key_string(&self) -> Option<String> {
self.key.to_string()
}
pub fn value_string(&self) -> Option<String> {
to_utf16_string(&self.value)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)]
pub struct VersionField {
pub major: u16,
pub minor: u16,
pub build: u16,
pub revision: u16,
}
impl VersionField {
pub fn from_ms_ls(ms: u32, ls: u32) -> Self {
let major = (ms >> 16) as u16;
let minor = (ms & 0xFFFF) as u16;
let build = (ls >> 16) as u16;
let revision = (ls & 0xFFFF) as u16;
Self {
major,
minor,
build,
revision,
}
}
pub fn to_ms(&self) -> u32 {
((self.major as u32) << 16) | (self.minor as u32)
}
pub fn to_ls(&self) -> u32 {
((self.build as u32) << 16) | (self.revision as u32)
}
pub fn to_string(&self) -> String {
format!(
"{}.{}.{}.{}",
self.major, self.minor, self.build, self.revision
)
}
}
#[derive(PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
pub struct VsFixedFileInfo {
pub signature: u32,
pub struct_version: u32,
pub file_version_ms: u32,
pub file_version_ls: u32,
pub product_version_ms: u32,
pub product_version_ls: u32,
pub file_flags_mask: u32,
pub file_flags: u32,
pub file_os: u32,
pub file_type: u32,
pub file_subtype: u32,
pub file_date_ms: u32,
pub file_date_ls: u32,
}
impl fmt::Debug for VsFixedFileInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VsFixedFileInfo")
.field(
"signature",
&format_args!(
"{:#x} ({})",
&self.signature,
if self.is_valid() { "Valid" } else { "Invalid" }
),
)
.field(
"struct_version",
&format_args!("{:#x}", &self.struct_version),
)
.field(
"file_version_ms",
&format_args!("{:#x}", &self.file_version_ms),
)
.field(
"file_version_ls",
&format_args!("{:#x}", &self.file_version_ls),
)
.field(
"product_version_ms",
&format_args!("{:#x}", &self.product_version_ms),
)
.field(
"product_version_ls",
&format_args!("{:#x}", &self.product_version_ls),
)
.field(
"file_flags_mask",
&format_args!("{:#x}", &self.file_flags_mask),
)
.field("file_flags", &format_args!("{:#x}", &self.file_flags))
.field("file_os", &format_args!("{:#x}", &self.file_os))
.field("file_type", &format_args!("{:#x}", &self.file_type))
.field("file_subtype", &format_args!("{:#x}", &self.file_subtype))
.field("file_date_ms", &format_args!("{:#x}", &self.file_date_ms))
.field("file_date_ls", &format_args!("{:#x}", &self.file_date_ls))
.finish()
}
}
pub const VERSION_INFO_US_ENGLISH_UNICODE: &str = "040904E4";
pub const VS_VERSION_INFO_KEY: &str = "VS_VERSION_INFO";
impl VsFixedFileInfo {
pub fn is_valid(&self) -> bool {
self.signature == VS_FFI_SIGNATURE
}
pub fn file_version(&self) -> VersionField {
VersionField::from_ms_ls(self.file_date_ms, self.file_date_ls)
}
pub fn product_version(&self) -> VersionField {
VersionField::from_ms_ls(self.product_version_ms, self.product_version_ls)
}
}
#[derive(Copy, Clone)]
pub struct StringFileInfo<'a> {
comments: Option<&'a [u8]>,
company_name: Option<&'a [u8]>,
file_description: Option<&'a [u8]>,
file_version: Option<&'a [u8]>,
internal_name: Option<&'a [u8]>,
legal_copyright: Option<&'a [u8]>,
legal_trademarks: Option<&'a [u8]>,
original_filename: Option<&'a [u8]>,
private_build: Option<&'a [u8]>,
product_name: Option<&'a [u8]>,
product_version: Option<&'a [u8]>,
special_build: Option<&'a [u8]>,
}
impl fmt::Debug for StringFileInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("StringFileInfo")
.field("comments", &format_args!("{:?}", self.comments()))
.field("company_name", &format_args!("{:?}", self.company_name()))
.field(
"file_description",
&format_args!("{:?}", self.file_description()),
)
.field("file_version", &format_args!("{:?}", self.file_version()))
.field("internal_name", &format_args!("{:?}", self.internal_name()))
.field(
"legal_copyright",
&format_args!("{:?}", self.legal_copyright()),
)
.field(
"legal_trademarks",
&format_args!("{:?}", self.legal_trademarks()),
)
.field(
"original_filename",
&format_args!("{:?}", self.original_filename()),
)
.field("private_build", &format_args!("{:?}", self.private_build()))
.field("product_name", &format_args!("{:?}", self.product_name()))
.field(
"product_version",
&format_args!("{:?}", self.product_version()),
)
.field("special_build", &format_args!("{:?}", self.special_build()))
.finish()
}
}
impl<'a> StringFileInfo<'a> {
fn from_resource_string_iterator(it: ResourceStringIterator<'a>) -> Self {
let find = |s: &'static str| {
it.filter_map(Result::ok)
.find(|x| x.key_string() == Some(s.to_string()))
.and_then(|x| Some(x.value))
};
Self {
comments: find("Comments"),
company_name: find("CompanyName"),
file_description: find("FileDescription"),
file_version: find("FileVersion"),
internal_name: find("InternalName"),
legal_copyright: find("LegalCopyright"),
legal_trademarks: find("LegalTrademarks"),
original_filename: find("OriginalFilename"),
private_build: find("PrivateBuild"),
product_name: find("ProductName"),
product_version: find("ProductVersion"),
special_build: find("SpecialBuild"),
}
}
pub fn comments(&self) -> Option<String> {
self.comments.and_then(to_utf16_string)
}
pub fn company_name(&self) -> Option<String> {
self.company_name.and_then(to_utf16_string)
}
pub fn file_description(&self) -> Option<String> {
self.file_description.and_then(to_utf16_string)
}
pub fn file_version(&self) -> Option<String> {
self.file_version.and_then(to_utf16_string)
}
pub fn internal_name(&self) -> Option<String> {
self.internal_name.and_then(to_utf16_string)
}
pub fn legal_copyright(&self) -> Option<String> {
self.legal_copyright.and_then(to_utf16_string)
}
pub fn legal_trademarks(&self) -> Option<String> {
self.legal_trademarks.and_then(to_utf16_string)
}
pub fn original_filename(&self) -> Option<String> {
self.original_filename.and_then(to_utf16_string)
}
pub fn private_build(&self) -> Option<String> {
self.private_build.and_then(to_utf16_string)
}
pub fn product_name(&self) -> Option<String> {
self.product_name.and_then(to_utf16_string)
}
pub fn product_version(&self) -> Option<String> {
self.product_version.and_then(to_utf16_string)
}
pub fn special_build(&self) -> Option<String> {
self.special_build.and_then(to_utf16_string)
}
}
#[derive(Copy, Clone)]
pub struct VersionInfo<'a> {
data: &'a [u8],
pub fixed_info: Option<VsFixedFileInfo>,
pub string_info: StringFileInfo<'a>,
}
impl fmt::Debug for VersionInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VersionInfo")
.field(
"data",
&format_args!("{:02x?} ({} bytes)", &self.data, self.data.len()),
)
.field("fixed_info", &self.fixed_info)
.field("string_info", &self.string_info)
.finish()
}
}
impl<'a> VersionInfo<'a> {
pub fn parse(
pe: &'a [u8],
bytes: &'a [u8],
it: ResourceEntryIterator<'a>,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Option<Self>> {
if let Some(entry) = it.find_by_id(RT_VERSION)? {
let offset_to_data =
match entry.recursive_next_depth(bytes, |e| e.offset_to_data().is_none())? {
Some(next) => match next.offset_to_data() {
Some(offset_to_data) => offset_to_data,
None => return Ok(None),
},
None => return Ok(None),
};
let mut offset = offset_to_data as usize;
let data_entry = bytes.gread_with::<ResourceDataEntry>(&mut offset, scroll::LE)?;
let rva = data_entry.offset_to_data as usize;
offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"Cannot map ResourceDataEntry rva {:#x} into offset",
rva
))
})?;
let data = pe
.pread_with::<&[u8]>(offset, data_entry.size as usize)
.map_err(|_| {
error::Error::Malformed(format!("Cannot map RT_VERSION at {offset:#x}"))
})?;
let iterator = ResourceStringIterator { data };
let strings = iterator.collect::<Result<Vec<_>, _>>()?;
let fixed_info = match strings
.iter()
.find(|x| x.key_string() == Some(VS_VERSION_INFO_KEY.to_string()))
{
Some(version_info) => Some(version_info.value.pread_with(0, scroll::LE)?),
None => None,
};
let string_info = StringFileInfo::from_resource_string_iterator(iterator);
Ok(Some(Self {
data,
fixed_info,
string_info,
}))
} else {
Ok(None)
}
}
}
#[derive(Copy, Clone, Default)]
pub struct ManifestData<'a> {
pub data: &'a [u8],
}
impl fmt::Debug for ManifestData<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ManifestData")
.field("value", &format_args!("{:02x?}", self.data))
.finish()
}
}
impl<'a> ManifestData<'a> {
pub fn parse(
pe: &'a [u8],
bytes: &'a [u8],
it: ResourceEntryIterator<'a>,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Option<Self>> {
if let Some(entry) = it.find_by_id(RT_MANIFEST)? {
let offset_to_data =
match entry.recursive_next_depth(bytes, |e| e.offset_to_data().is_none())? {
Some(next) => match next.offset_to_data() {
Some(offset_to_data) => offset_to_data,
None => return Ok(None),
},
None => return Ok(None),
};
let mut offset = offset_to_data as usize;
let data_entry = bytes.gread_with::<ResourceDataEntry>(&mut offset, scroll::LE)?;
let rva = data_entry.offset_to_data as usize;
offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"Cannot map ResourceDataEntry rva {:#x} into offset",
rva
))
})?;
let data = pe
.pread_with::<&[u8]>(offset, data_entry.size as usize)
.map_err(|_| {
error::Error::Malformed(format!("Cannot map RT_MANIFEST at {offset:#x}"))
})?;
Ok(Some(Self { data }))
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const HAS_NO_RES: &[u8] = include_bytes!("../../tests/bins/pe/has_no_res.exe.bin");
const HAS_RES_FULL_VERSION_AND_MANIFEST: &[u8] =
include_bytes!("../../tests/bins/pe/has_res_full_version_and_manifest.exe.bin");
const MALFORMED_RESOURCE_TREE: &[u8] =
include_bytes!("../../tests/bins/pe/malformed_resource_tree.exe.bin");
const EXPECTED_MANIFEST: &[u8; 413] = &[
0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x27,
0x31, 0x2E, 0x30, 0x27, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x3D, 0x27,
0x55, 0x54, 0x46, 0x2D, 0x38, 0x27, 0x20, 0x73, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x6C, 0x6F,
0x6E, 0x65, 0x3D, 0x27, 0x79, 0x65, 0x73, 0x27, 0x3F, 0x3E, 0x0D, 0x0A, 0x3C, 0x61, 0x73,
0x73, 0x65, 0x6D, 0x62, 0x6C, 0x79, 0x20, 0x78, 0x6D, 0x6C, 0x6E, 0x73, 0x3D, 0x27, 0x75,
0x72, 0x6E, 0x3A, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x73, 0x2D, 0x6D, 0x69, 0x63, 0x72,
0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2D, 0x63, 0x6F, 0x6D, 0x3A, 0x61, 0x73, 0x6D, 0x2E, 0x76,
0x31, 0x27, 0x20, 0x6D, 0x61, 0x6E, 0x69, 0x66, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73,
0x69, 0x6F, 0x6E, 0x3D, 0x27, 0x31, 0x2E, 0x30, 0x27, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20,
0x20, 0x3C, 0x74, 0x72, 0x75, 0x73, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x20, 0x78, 0x6D, 0x6C,
0x6E, 0x73, 0x3D, 0x22, 0x75, 0x72, 0x6E, 0x3A, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x73,
0x2D, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2D, 0x63, 0x6F, 0x6D, 0x3A,
0x61, 0x73, 0x6D, 0x2E, 0x76, 0x33, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x3C, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x3E, 0x0D, 0x0A,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6C, 0x65, 0x67,
0x65, 0x73, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65,
0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x65, 0x76, 0x65, 0x6C,
0x20, 0x6C, 0x65, 0x76, 0x65, 0x6C, 0x3D, 0x27, 0x61, 0x73, 0x49, 0x6E, 0x76, 0x6F, 0x6B,
0x65, 0x72, 0x27, 0x20, 0x75, 0x69, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x3D, 0x27, 0x66,
0x61, 0x6C, 0x73, 0x65, 0x27, 0x20, 0x2F, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x65, 0x64, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6C, 0x65, 0x67, 0x65, 0x73, 0x3E, 0x0D,
0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x73, 0x65, 0x63, 0x75,
0x72, 0x69, 0x74, 0x79, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x74, 0x72,
0x75, 0x73, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, 0x61, 0x73, 0x73,
0x65, 0x6D, 0x62, 0x6C, 0x79, 0x3E, 0x0D, 0x0A,
];
const PYTHON_INSTALLER_VERSION_INFO: &[u8; 876] = &[
0x78, 0x03, 0x34, 0x00, 0x00, 0x00, 0x56, 0x00, 0x53, 0x00, 0x5F, 0x00, 0x56, 0x00, 0x45,
0x00, 0x52, 0x00, 0x53, 0x00, 0x49, 0x00, 0x4F, 0x00, 0x4E, 0x00, 0x5F, 0x00, 0x49, 0x00,
0x4E, 0x00, 0x46, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD, 0x04, 0xEF, 0xFE, 0x00,
0x00, 0x01, 0x00, 0x0B, 0x00, 0x03, 0x00, 0x00, 0x00, 0x4E, 0x0C, 0x0B, 0x00, 0x03, 0x00,
0x00, 0x00, 0x4E, 0x0C, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0xD8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x74, 0x00,
0x72, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65,
0x00, 0x49, 0x00, 0x6E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xB4, 0x02, 0x00, 0x00,
0x00, 0x00, 0x30, 0x00, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x30, 0x00, 0x34, 0x00, 0x45,
0x00, 0x34, 0x00, 0x00, 0x00, 0x58, 0x00, 0x36, 0x00, 0x00, 0x00, 0x43, 0x00, 0x6F, 0x00,
0x6D, 0x00, 0x70, 0x00, 0x61, 0x00, 0x6E, 0x00, 0x79, 0x00, 0x4E, 0x00, 0x61, 0x00, 0x6D,
0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00,
0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x53, 0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0x77,
0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x46, 0x00, 0x6F, 0x00, 0x75, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x58, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00,
0x65, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70,
0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00,
0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x33, 0x00, 0x2E,
0x00, 0x31, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x33, 0x00, 0x20, 0x00, 0x28, 0x00, 0x36, 0x00,
0x34, 0x00, 0x2D, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00,
0x00, 0x38, 0x00, 0x18, 0x00, 0x00, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65, 0x00,
0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x33, 0x00,
0x31, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x06,
0x00, 0x01, 0x00, 0x49, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6E, 0x00,
0x61, 0x00, 0x6C, 0x00, 0x4E, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73,
0x00, 0x65, 0x00, 0x74, 0x00, 0x75, 0x00, 0x70, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x7E, 0x00,
0x00, 0x00, 0x4C, 0x00, 0x65, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x43, 0x00, 0x6F,
0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00,
0x00, 0x00, 0x43, 0x00, 0x6F, 0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67,
0x00, 0x68, 0x00, 0x74, 0x00, 0x20, 0x00, 0x28, 0x00, 0x63, 0x00, 0x29, 0x00, 0x20, 0x00,
0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x53,
0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00,
0x20, 0x00, 0x46, 0x00, 0x6F, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x61, 0x00, 0x74,
0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x2E, 0x00, 0x20, 0x00, 0x41, 0x00, 0x6C, 0x00,
0x6C, 0x00, 0x20, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00, 0x73,
0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00,
0x65, 0x00, 0x64, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x30, 0x00, 0x00,
0x00, 0x4F, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x61, 0x00,
0x6C, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x6D,
0x00, 0x65, 0x00, 0x00, 0x00, 0x70, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6F, 0x00,
0x6E, 0x00, 0x2D, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x33,
0x00, 0x2D, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x64, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2E, 0x00,
0x65, 0x00, 0x78, 0x00, 0x65, 0x00, 0x00, 0x00, 0x50, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x50,
0x00, 0x72, 0x00, 0x6F, 0x00, 0x64, 0x00, 0x75, 0x00, 0x63, 0x00, 0x74, 0x00, 0x4E, 0x00,
0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x79, 0x00, 0x74,
0x00, 0x68, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x31, 0x00,
0x31, 0x00, 0x2E, 0x00, 0x33, 0x00, 0x20, 0x00, 0x28, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2D,
0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00,
0x18, 0x00, 0x00, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x64, 0x00, 0x75, 0x00, 0x63,
0x00, 0x74, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00,
0x6E, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x33,
0x00, 0x31, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x00, 0x00, 0x44, 0x00,
0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x61, 0x00, 0x72, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C,
0x00, 0x65, 0x00, 0x49, 0x00, 0x6E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00,
0x24, 0x00, 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6E, 0x00, 0x73,
0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09, 0x04, 0xE4, 0x04,
];
const NTDLL_VERSION_INFO: &[u8; 896] = &[
0x7C, 0x03, 0x34, 0x00, 0x00, 0x00, 0x56, 0x00, 0x53, 0x00, 0x5F, 0x00, 0x56, 0x00, 0x45,
0x00, 0x52, 0x00, 0x53, 0x00, 0x49, 0x00, 0x4F, 0x00, 0x4E, 0x00, 0x5F, 0x00, 0x49, 0x00,
0x4E, 0x00, 0x46, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD, 0x04, 0xEF, 0xFE, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xAA, 0x11, 0x61, 0x4A, 0x00, 0x00, 0x0A, 0x00,
0xAA, 0x11, 0x61, 0x4A, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xDC, 0x02, 0x00, 0x00, 0x01, 0x00, 0x53, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69,
0x00, 0x6E, 0x00, 0x67, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x49, 0x00,
0x6E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x00, 0x00, 0xB8, 0x02, 0x00, 0x00, 0x01, 0x00, 0x30,
0x00, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x30, 0x00, 0x34, 0x00, 0x42, 0x00, 0x30, 0x00,
0x00, 0x00, 0x4C, 0x00, 0x16, 0x00, 0x01, 0x00, 0x43, 0x00, 0x6F, 0x00, 0x6D, 0x00, 0x70,
0x00, 0x61, 0x00, 0x6E, 0x00, 0x79, 0x00, 0x4E, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x73,
0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6F, 0x00, 0x72, 0x00,
0x70, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E,
0x00, 0x00, 0x00, 0x42, 0x00, 0x0D, 0x00, 0x01, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00,
0x65, 0x00, 0x44, 0x00, 0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70,
0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x00,
0x54, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x61, 0x00, 0x79, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20,
0x00, 0x44, 0x00, 0x4C, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x27, 0x00,
0x01, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72,
0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00,
0x30, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x39, 0x00, 0x30, 0x00, 0x34,
0x00, 0x31, 0x00, 0x2E, 0x00, 0x34, 0x00, 0x35, 0x00, 0x32, 0x00, 0x32, 0x00, 0x20, 0x00,
0x28, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x42, 0x00, 0x75, 0x00, 0x69, 0x00, 0x6C,
0x00, 0x64, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x36, 0x00, 0x30, 0x00, 0x31, 0x00, 0x30, 0x00,
0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x29, 0x00, 0x00,
0x00, 0x00, 0x00, 0x34, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x49, 0x00, 0x6E, 0x00, 0x74, 0x00,
0x65, 0x00, 0x72, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x4E, 0x00, 0x61, 0x00, 0x6D,
0x00, 0x65, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x6C, 0x00,
0x2E, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x80, 0x00, 0x2E, 0x00, 0x01,
0x00, 0x4C, 0x00, 0x65, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x43, 0x00, 0x6F, 0x00,
0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00, 0x00,
0x00, 0xA9, 0x00, 0x20, 0x00, 0x4D, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6F, 0x00,
0x73, 0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6F, 0x00, 0x72,
0x00, 0x70, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00,
0x6E, 0x00, 0x2E, 0x00, 0x20, 0x00, 0x41, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x20, 0x00, 0x72,
0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00, 0x74, 0x00, 0x73, 0x00, 0x20, 0x00, 0x72, 0x00,
0x65, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x2E,
0x00, 0x00, 0x00, 0x3C, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x72, 0x00, 0x69, 0x00,
0x67, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6C,
0x00, 0x65, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6E, 0x00,
0x74, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x2E, 0x00, 0x64, 0x00, 0x6C, 0x00, 0x6C,
0x00, 0x00, 0x00, 0x6A, 0x00, 0x25, 0x00, 0x01, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6F, 0x00,
0x64, 0x00, 0x75, 0x00, 0x63, 0x00, 0x74, 0x00, 0x4E, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65,
0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6F, 0x00,
0x73, 0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0xAE, 0x00, 0x20, 0x00, 0x57, 0x00, 0x69,
0x00, 0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0xAE, 0x00, 0x20, 0x00,
0x4F, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6E,
0x00, 0x67, 0x00, 0x20, 0x00, 0x53, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x10, 0x00, 0x01, 0x00, 0x50, 0x00, 0x72,
0x00, 0x6F, 0x00, 0x64, 0x00, 0x75, 0x00, 0x63, 0x00, 0x74, 0x00, 0x56, 0x00, 0x65, 0x00,
0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x31, 0x00, 0x30,
0x00, 0x2E, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x39, 0x00, 0x30, 0x00, 0x34, 0x00,
0x31, 0x00, 0x2E, 0x00, 0x34, 0x00, 0x35, 0x00, 0x32, 0x00, 0x32, 0x00, 0x00, 0x00, 0x44,
0x00, 0x00, 0x00, 0x01, 0x00, 0x56, 0x00, 0x61, 0x00, 0x72, 0x00, 0x46, 0x00, 0x69, 0x00,
0x6C, 0x00, 0x65, 0x00, 0x49, 0x00, 0x6E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x24, 0x00, 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6E, 0x00,
0x73, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x09, 0x04, 0xB0, 0x04, 0x00, 0x00, 0x00, 0x00,
];
#[test]
fn test_resource_entry_unions() {
let entry = ResourceEntry::from(0x8000183880002938);
assert_eq!(entry.name_is_string(), true);
assert_eq!(entry.name_offset(), 0x1838);
assert_eq!(entry.id(), None);
assert_eq!(entry.data_is_directory(), true);
assert_eq!(entry.offset_to_directory(), 0x2938);
assert_eq!(entry.value(), 0x8000183880002938);
let entry = ResourceEntry::from(0x183880002938);
assert_eq!(entry.name_is_string(), false);
assert_eq!(entry.name_offset(), 0x1838); assert_eq!(entry.id(), Some(6200)); assert_eq!(entry.data_is_directory(), true);
assert_eq!(entry.offset_to_directory(), 0x2938);
assert_eq!(entry.value(), 0x183880002938);
let entry = ResourceEntry::from(0x8000183800002938);
assert_eq!(entry.name_is_string(), true);
assert_eq!(entry.name_offset(), 0x1838);
assert_eq!(entry.id(), None);
assert_eq!(entry.data_is_directory(), false);
assert_eq!(entry.offset_to_directory(), 0x2938);
assert_eq!(entry.value(), 0x8000183800002938);
let entry = ResourceEntry::from(0x208800008080);
assert_eq!(entry.name_is_string(), false);
assert_eq!(entry.name_offset(), 0x2088); assert_eq!(entry.id(), Some(8328)); assert_eq!(entry.data_is_directory(), false);
assert_eq!(entry.offset_to_directory(), 0x8080);
assert_eq!(entry.value(), 0x208800008080);
let entry = ResourceEntry::from(0x3880008080);
assert_eq!(entry.name_is_string(), false);
assert_eq!(entry.id(), Some(56));
assert_eq!(entry.data_is_directory(), true);
assert_eq!(entry.offset_to_directory(), 0x8080);
assert_eq!(entry.value(), 0x3880008080);
}
#[test]
fn test_version_field_from_ms_ls() {
const MS: u32 = (4 << 16) | 2; const LS: u32 = (3 << 16) | 1;
let mut version = VersionField::from_ms_ls(MS, LS);
assert_eq!(version.major, 4);
assert_eq!(version.minor, 2);
assert_eq!(version.build, 3);
assert_eq!(version.revision, 1);
assert_eq!(version.to_string(), "4.2.3.1");
assert_eq!(version.to_ms(), MS);
assert_eq!(version.to_ls(), LS);
version.major += 1;
version.minor += 2;
assert_eq!(version.to_ms(), (4 + 1 << 16) | 2 + 2);
version.build += 3;
version.revision += 4;
assert_eq!(version.to_ls(), (3 + 3 << 16) | 1 + 4);
}
#[test]
fn parse_no_resource() {
let binary = crate::pe::PE::parse(HAS_NO_RES).expect("Unable to parse binary");
assert_eq!(binary.resource_data.is_none(), true);
}
#[test]
fn parse_full_version_and_manifest() {
let binary = crate::pe::PE::parse(HAS_RES_FULL_VERSION_AND_MANIFEST)
.expect("Unable to parse binary");
assert_eq!(binary.resource_data.is_some(), true);
let res_data = binary.resource_data.unwrap();
assert_eq!(res_data.version_info.is_some(), true);
let ver_info = res_data.version_info.unwrap();
assert_eq!(ver_info.fixed_info.is_some(), true);
let fixed_info = ver_info.fixed_info.unwrap();
assert_eq!(fixed_info.signature, VS_FFI_SIGNATURE);
assert_eq!(fixed_info.is_valid(), true);
assert_eq!(fixed_info.struct_version, VS_FFI_STRUCVERSION);
assert_eq!(fixed_info.file_version_ms, 0x1);
assert_eq!(fixed_info.file_version_ls, 0x0);
assert_eq!(fixed_info.product_version_ms, 0x1);
assert_eq!(fixed_info.product_version_ls, 0x0);
assert_eq!(fixed_info.file_flags_mask, VS_FFI_FILEFLAGSMASK);
assert_eq!(fixed_info.file_flags, 0x0);
assert_eq!(fixed_info.file_os, VOS_NT_WINDOWS32);
assert_eq!(fixed_info.file_type, VFT_APP);
assert_eq!(fixed_info.file_subtype, 0x0);
assert_eq!(fixed_info.file_date_ms, 0x0);
assert_eq!(fixed_info.file_date_ls, 0x0);
let str_info = ver_info.string_info;
assert_eq!(
str_info.comments(),
Some(String::from("GOBLIN-TEST-BIN-COMMENTS"))
);
assert_eq!(
str_info.company_name(),
Some(String::from("GOBLIN-TEST-BIN-COMPANY-NAME"))
);
assert_eq!(
str_info.file_description(),
Some(String::from("GOBLIN-TEST-BIN-FILE-DESCRIPTION"))
);
assert_eq!(
str_info.file_version(),
Some(String::from("GOBLIN-TEST-BIN-FILE-VERSION"))
);
assert_eq!(
str_info.internal_name(),
Some(String::from("GOBLIN-TEST-BIN-INTERNAL-NAME"))
);
assert_eq!(
str_info.legal_copyright(),
Some(String::from("GOBLIN-TEST-BIN-LEGAL-COPYRIGHT"))
);
assert_eq!(
str_info.legal_trademarks(),
Some(String::from("GOBLIN-TEST-BIN-LEGAL-TRADEMARKS"))
);
assert_eq!(
str_info.original_filename(),
Some(String::from("GOBLIN-TEST-BIN-ORIGINAL-FILENAME"))
);
assert_eq!(
str_info.private_build(),
Some(String::from("GOBLIN-TEST-BIN-PRIVATE-BUILD"))
);
assert_eq!(
str_info.product_name(),
Some(String::from("GOBLIN-TEST-BIN-PRODUCT-NAME"))
);
assert_eq!(
str_info.product_version(),
Some(String::from("GOBLIN-TEST-BIN-PRODUCT-VERSION"))
);
assert_eq!(
str_info.special_build(),
Some(String::from("GOBLIN-TEST-BIN-SPECIAL-BUILD"))
);
assert_eq!(res_data.manifest_data.is_some(), true);
let manifest_info = res_data.manifest_data.unwrap();
assert_eq!(manifest_info.data, EXPECTED_MANIFEST);
}
#[test]
fn test_resource_string_iterator() {
let it = ResourceStringIterator {
data: PYTHON_INSTALLER_VERSION_INFO,
};
let it_vec = it.collect::<Result<Vec<_>, _>>();
assert_eq!(it_vec.is_ok(), true);
let it_vec = it_vec.unwrap();
assert_eq!(it_vec[0].is_binary_data(), true);
assert_eq!(
it_vec[0].key_string(),
Some(VS_VERSION_INFO_KEY.to_string())
);
assert_eq!(
it_vec[0].value,
&[
0xbd, 0x04, 0xef, 0xfe, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x03, 0x00, 0x00, 0x00,
0x4e, 0x0c, 0x0b, 0x00, 0x03, 0x00, 0x00, 0x00, 0x4e, 0x0c, 0x3f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd8, 0x02,
0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x74, 0x00, 0x72, 0x00
]
);
assert_eq!(it_vec[1].r#type, 103); assert_eq!(it_vec[1].key_string(), Some("FileInfo".to_string()));
assert_eq!(
it_vec[1].value,
&[
0xb4, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00,
0x30, 0x00, 0x34, 0x00, 0x45, 0x00, 0x34, 0x00, 0x00, 0x00, 0x58, 0x00, 0x36, 0x00,
0x00, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x61, 0x00, 0x6e, 0x00,
0x79, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x53, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00,
0x65, 0x00, 0x20, 0x00, 0x46, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00,
0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[2].is_binary_data(), true);
assert_eq!(it_vec[2].key_string(), Some("FileDescription".to_string()));
assert_eq!(
it_vec[2].value_string(),
Some("Python 3.11.3 (64-bit)".to_string())
);
assert_eq!(
it_vec[2].value,
&[
0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x33, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x20, 0x00,
0x28, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2d, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00,
0x29, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[3].is_binary_data(), true);
assert_eq!(it_vec[3].key_string(), Some("FileVersion".to_string()));
assert_eq!(it_vec[3].value_string(), Some("3.11.3150.0".to_string()));
assert_eq!(
it_vec[3].value,
&[
0x33, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x31, 0x00,
0x35, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[4].is_text_data(), true);
assert_eq!(it_vec[4].key_string(), Some("InternalName".to_string()));
assert_eq!(it_vec[4].value_string(), Some("setup".to_string()));
assert_eq!(
it_vec[4].value,
&[
0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x75, 0x00, 0x70, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[5].is_binary_data(), true);
assert_eq!(it_vec[5].key_string(), Some("LegalCopyright".to_string()));
assert_eq!(
it_vec[5].value_string(),
Some("Copyright (c) Python Software Foundation. All rights reserved.".to_string())
);
assert_eq!(
it_vec[5].value,
&[
0x43, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x79, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00,
0x68, 0x00, 0x74, 0x00, 0x20, 0x00, 0x28, 0x00, 0x63, 0x00, 0x29, 0x00, 0x20, 0x00,
0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x53, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00,
0x65, 0x00, 0x20, 0x00, 0x46, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00,
0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x20, 0x00,
0x41, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00,
0x68, 0x00, 0x74, 0x00, 0x73, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00,
0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x00, 0x00,
0x00, 0x00,
]
);
assert_eq!(it_vec[6].is_binary_data(), true);
assert_eq!(it_vec[6].key_string(), Some("OriginalFilename".to_string()));
assert_eq!(
it_vec[6].value_string(),
Some("python-3.11.3-amd64.exe".to_string())
);
assert_eq!(
it_vec[6].value,
&[
0x70, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00,
0x33, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x2d, 0x00,
0x61, 0x00, 0x6d, 0x00, 0x64, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x65, 0x00,
0x78, 0x00, 0x65, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[7].is_binary_data(), true);
assert_eq!(it_vec[7].key_string(), Some("ProductName".to_string()));
assert_eq!(
it_vec[7].value_string(),
Some("Python 3.11.3 (64-bit)".to_string())
);
assert_eq!(
it_vec[7].value,
&[
0x50, 0x00, 0x79, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
0x33, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x20, 0x00,
0x28, 0x00, 0x36, 0x00, 0x34, 0x00, 0x2d, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00,
0x29, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[8].is_binary_data(), true);
assert_eq!(it_vec[8].key_string(), Some("ProductVersion".to_string()));
assert_eq!(it_vec[8].value_string(), Some("3.11.3150.0".to_string()));
assert_eq!(
it_vec[8].value,
&[
0x33, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x31, 0x00,
0x35, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00,
]
);
assert_eq!(it_vec[9].is_binary_data(), true);
assert_eq!(it_vec[9].key_string(), Some("VarFileInfo".to_string()));
assert_eq!(it_vec[9].value, &[]);
assert_eq!(it_vec[9].is_binary_data(), true);
assert_eq!(it_vec[10].key_string(), Some("Translation".to_string()));
assert_eq!(it_vec[10].value, &[0x09, 0x04, 0xe4, 0x04]);
assert_eq!(it_vec.get(11), None);
let it = ResourceStringIterator {
data: NTDLL_VERSION_INFO,
};
let it_vec = it.collect::<Result<Vec<_>, _>>();
assert_eq!(it_vec.is_ok(), true);
}
#[test]
fn test_empty_bytes() {
let bytes = &[];
let result = to_utf16_string(bytes);
assert_eq!(result, Some(String::new()));
}
#[test]
fn test_odd_length_bytes() {
let bytes = &[0x48, 0x00, 0x65]; let result = to_utf16_string(bytes);
assert_eq!(result, None);
}
#[test]
fn test_simple_ascii_string() {
let bytes = &[0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("Hello".to_string()));
}
#[test]
fn test_null_terminated_string() {
let bytes = &[0x48, 0x00, 0x69, 0x00, 0x00, 0x00, 0x65, 0x00, 0x78, 0x00];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("Hi".to_string()));
}
#[test]
fn test_unicode_characters() {
let bytes = &[
0x53, 0x30, 0x93, 0x30, 0x6b, 0x30, 0x61, 0x30, 0x6f, 0x30, ];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("こんにちは".to_string()));
}
#[test]
fn test_emoji_characters() {
let bytes = &[0x3E, 0xD8, 0x80, 0xDD];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("🦀".to_string()));
}
#[test]
fn test_mixed_characters() {
let bytes = &[
0x41, 0x00, 0xe5, 0x65, 0x2c, 0x67, 0x42, 0x00, ];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("A日本B".to_string()));
}
#[test]
fn test_only_null_bytes() {
let bytes = &[0x00, 0x00];
let result = to_utf16_string(bytes);
assert_eq!(result, Some(String::new()));
}
#[test]
fn test_multiple_null_terminators() {
let bytes = &[0x41, 0x00, 0x00, 0x00, 0x00, 0x00];
let result = to_utf16_string(bytes);
assert_eq!(result, Some("A".to_string()));
}
#[test]
fn test_long_string() {
let mut bytes = Vec::new();
for i in 0..1000 {
let ch = (0x41 + (i % 26)) as u8; bytes.push(ch);
bytes.push(0x00);
}
let result = to_utf16_string(&bytes);
assert!(result.is_some());
assert_eq!(result.unwrap().len(), 1000);
}
#[test]
fn test_invalid_utf16_sequence() {
let bytes = &[0x3d, 0xd8, 0x41, 0x00]; let result = to_utf16_string(bytes);
assert_eq!(result, None);
}
#[test]
fn test_alignment_edge_cases() {
let test_cases = vec![
vec![0x48, 0x00, 0x69, 0x00], vec![0x00, 0x48, 0x00, 0x69, 0x00], ];
for bytes in test_cases {
if bytes.len() % 2 == 0 {
let result = to_utf16_string(&bytes);
assert!(result.is_some());
}
}
}
#[test]
fn test_high_unicode_codepoints() {
let bytes = &[
0x35, 0xd8, 0xd7, 0xdc, 0x35, 0xd8, 0xee, 0xdc, 0x35, 0xd8, 0xf5, 0xdc, 0x35, 0xd8, 0xf5, 0xdc, 0x35, 0xd8, 0xf8, 0xdc, ];
let result = to_utf16_string(bytes);
assert!(result.is_some());
}
#[test]
#[should_panic = "Cycle detected in resource directory at offset"]
fn malformed_resource_tree() {
let _ = crate::pe::PE::parse(MALFORMED_RESOURCE_TREE).unwrap();
}
#[test]
fn test_parse_dotnet_dll_resources() {
const MONO_DOTNET_DLL: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/bins/pe/System.Xml.XDocument.dll"
));
let pe =
crate::pe::PE::parse(MONO_DOTNET_DLL).expect("Failed to parse Mono-compiled .NET DLL");
let res_data = pe
.resource_data
.as_ref()
.expect("Resource data should be present");
let ver_info = res_data
.version_info
.as_ref()
.expect("Version info should be present");
let fixed = ver_info
.fixed_info
.as_ref()
.expect("Fixed info should be present");
let file_ver = fixed.file_version();
assert_eq!(file_ver.major, 0, "File version major");
assert_eq!(file_ver.minor, 0, "File version minor");
assert_eq!(file_ver.build, 0, "File version build");
assert_eq!(file_ver.revision, 0, "File version revision");
let product_ver = fixed.product_version();
assert_eq!(product_ver.major, 4, "Product version major (Mono 4.x)");
assert_eq!(product_ver.minor, 8, "Product version minor");
assert_eq!(product_ver.build, 3761, "Product version build");
assert_eq!(product_ver.revision, 0, "Product version revision");
assert_eq!(
ver_info.string_info.legal_copyright(),
Some("(c) Various Mono authors".to_string()),
"Copyright should match Mono authors"
);
assert_eq!(
ver_info.string_info.product_name(),
Some("Mono Common Language Infrastructure".to_string()),
"Product name should match Mono CLI"
);
assert_eq!(
ver_info.string_info.file_description(),
Some("System.Xml.XDocument".to_string()),
"File description should match assembly name"
);
}
}