use crate::errors::FileParseError;
use crate::field::Field;
use crate::utils::{extract_u16, extract_u32};
pub struct ResourceDirectory {
pub characteristics: Field<u32>,
pub time_date_stamp: Field<u32>,
pub major_version: Field<u16>,
pub minor_version: Field<u16>,
pub number_of_named_entries: Field<u16>,
pub number_of_id_entries: Field<u16>,
}
pub struct ResourceDirectoryEntry {
pub name_or_id: Field<u32>,
pub offset_to_data: Field<u32>,
}
pub struct ResourceDataEntry {
pub offset_to_data: Field<u32>,
pub size: Field<u32>,
pub code_page: Field<u32>,
pub reserved: Field<u32>,
}
pub enum ResourceEntry {
Directory {
name: Option<String>,
id: Option<u16>,
entry: ResourceDirectoryEntry,
directory: ResourceDirectoryNode,
},
Data {
name: Option<String>,
id: Option<u16>,
entry: ResourceDirectoryEntry,
data: ResourceDataEntry,
},
}
pub struct ResourceDirectoryNode {
pub offset: usize,
pub header: ResourceDirectory,
pub entries: Vec<ResourceEntry>,
}
pub struct ResourceTree {
pub offset: usize,
pub root: ResourceDirectoryNode,
}
impl ResourceDirectory {
pub const SIZE: usize = 16;
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
if buffer.len() < offset + Self::SIZE {
return Err(FileParseError::BufferOverflow);
}
Ok(ResourceDirectory {
characteristics: Field::new(extract_u32(buffer, offset)?, offset, 4),
time_date_stamp: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
major_version: Field::new(extract_u16(buffer, offset + 8)?, offset + 8, 2),
minor_version: Field::new(extract_u16(buffer, offset + 10)?, offset + 10, 2),
number_of_named_entries: Field::new(extract_u16(buffer, offset + 12)?, offset + 12, 2),
number_of_id_entries: Field::new(extract_u16(buffer, offset + 14)?, offset + 14, 2),
})
}
}
impl ResourceDirectoryEntry {
pub const SIZE: usize = 8;
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
if buffer.len() < offset + Self::SIZE {
return Err(FileParseError::BufferOverflow);
}
Ok(ResourceDirectoryEntry {
name_or_id: Field::new(extract_u32(buffer, offset)?, offset, 4),
offset_to_data: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
})
}
pub fn is_directory(&self) -> bool {
self.offset_to_data.value & 0x8000_0000 != 0
}
pub fn data_offset(&self) -> u32 {
self.offset_to_data.value & 0x7FFF_FFFF
}
}
impl ResourceDataEntry {
pub const SIZE: usize = 16;
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
if buffer.len() < offset + Self::SIZE {
return Err(FileParseError::BufferOverflow);
}
Ok(ResourceDataEntry {
offset_to_data: Field::new(extract_u32(buffer, offset)?, offset, 4),
size: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
code_page: Field::new(extract_u32(buffer, offset + 8)?, offset + 8, 4),
reserved: Field::new(extract_u32(buffer, offset + 12)?, offset + 12, 4),
})
}
}
impl ResourceTree {
pub fn parse(
buffer: &[u8],
offset: usize,
rva_to_offset: impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<Self, FileParseError> {
let root = parse_node(buffer, offset, offset, &rva_to_offset)?;
Ok(ResourceTree { offset, root })
}
}
#[allow(clippy::only_used_in_recursion)]
fn parse_node(
buffer: &[u8],
resource_base: usize,
offset: usize,
rva_to_offset: &impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<ResourceDirectoryNode, FileParseError> {
let header = ResourceDirectory::parse(buffer, offset)?;
let named_count = header.number_of_named_entries.value as usize;
let id_count = header.number_of_id_entries.value as usize;
let entries_offset = offset + ResourceDirectory::SIZE;
let total = named_count
.checked_add(id_count)
.ok_or(FileParseError::ValueTooLarge)?;
let entries_end = entries_offset
.checked_add(total * ResourceDirectoryEntry::SIZE)
.ok_or(FileParseError::BufferOverflow)?;
if buffer.len() < entries_end {
return Err(FileParseError::BufferOverflow);
}
let mut entries = Vec::with_capacity(total);
for i in 0..total {
let entry_off = entries_offset + i * ResourceDirectoryEntry::SIZE;
let entry = ResourceDirectoryEntry::parse(buffer, entry_off)?;
let is_named = i < named_count;
let (name, id) = if is_named {
let name = read_resource_name(buffer, resource_base, entry.name_or_id.value)?;
(Some(name), None)
} else {
(None, Some(entry.name_or_id.value as u16))
};
let child_off = resource_base
.checked_add(entry.data_offset() as usize)
.ok_or(FileParseError::BufferOverflow)?;
if entry.is_directory() {
let directory = parse_node(buffer, resource_base, child_off, rva_to_offset)?;
entries.push(ResourceEntry::Directory {
name,
id,
entry,
directory,
});
} else {
let data = ResourceDataEntry::parse(buffer, child_off)?;
entries.push(ResourceEntry::Data {
name,
id,
entry,
data,
});
}
}
Ok(ResourceDirectoryNode {
offset,
header,
entries,
})
}
fn read_resource_name(
buffer: &[u8],
resource_base: usize,
name_offset: u32,
) -> Result<String, FileParseError> {
let off = resource_base
.checked_add(name_offset as usize)
.ok_or(FileParseError::BufferOverflow)?;
if buffer.len() < off + 2 {
return Err(FileParseError::BufferOverflow);
}
let length = u16::from_le_bytes([buffer[off], buffer[off + 1]]) as usize;
let start = off + 2;
let end = start
.checked_add(length * 2)
.ok_or(FileParseError::BufferOverflow)?;
let bytes = buffer
.get(start..end)
.ok_or(FileParseError::BufferOverflow)?;
let units: Vec<u16> = bytes
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes(*chunk))
.collect();
Ok(String::from_utf16_lossy(&units))
}