use crate::md4::block_checksum;
use crate::Archive;
impl Archive {
pub fn pak_checksum(&self) -> u32 {
let feed: Vec<u8> =
self.content_crc_feed().iter().flat_map(|crc| crc.to_le_bytes()).collect();
block_checksum(&feed)
}
pub fn is_addon(&self) -> bool {
self.contains("addon.conf")
}
pub fn is_binary_pak(&self) -> bool {
self.contains("binary.conf")
}
}
const RESOURCES_MAGIC: u32 = 0xd000_000d;
#[derive(Debug, Clone)]
pub struct ResourceEntry {
pub name: String,
pub offset: u32,
pub length: u32,
}
#[derive(Debug)]
pub enum ResourcesError {
TooShort,
BadMagic(u32),
BadTable,
}
impl std::fmt::Display for ResourcesError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourcesError::TooShort => write!(formatter, "resources file too short"),
ResourcesError::BadMagic(magic) => write!(formatter, "bad resources magic {magic:#010x}"),
ResourcesError::BadTable => write!(formatter, "resources table out of range"),
}
}
}
impl std::error::Error for ResourcesError {}
fn read_be_u32(data: &[u8], offset: usize) -> Option<u32> {
data.get(offset..offset + 4).map(|slice| u32::from_be_bytes(slice.try_into().unwrap()))
}
pub struct ResourcesContainer {
data: Vec<u8>,
entries: Vec<ResourceEntry>,
}
impl ResourcesContainer {
pub fn from_bytes(data: Vec<u8>) -> Result<Self, ResourcesError> {
if data.len() < 12 {
return Err(ResourcesError::TooShort);
}
let magic = read_be_u32(&data, 0).unwrap();
if magic != RESOURCES_MAGIC {
return Err(ResourcesError::BadMagic(magic));
}
let table_offset = read_be_u32(&data, 4).ok_or(ResourcesError::BadTable)? as usize;
let table_length = read_be_u32(&data, 8).ok_or(ResourcesError::BadTable)? as usize;
if table_offset + table_length > data.len() {
return Err(ResourcesError::BadTable);
}
let mut cursor = table_offset;
let count = read_be_u32(&data, cursor).ok_or(ResourcesError::BadTable)? as usize;
cursor += 4;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let name_length = read_be_u32(&data, cursor).ok_or(ResourcesError::BadTable)? as usize;
cursor += 4;
let name_bytes =
data.get(cursor..cursor + name_length).ok_or(ResourcesError::BadTable)?;
let name = crate::normalize_name(&name_bytes.iter().map(|&b| b as char).collect::<String>());
cursor += name_length;
let offset = read_be_u32(&data, cursor).ok_or(ResourcesError::BadTable)?;
cursor += 4;
let length = read_be_u32(&data, cursor).ok_or(ResourcesError::BadTable)?;
cursor += 4;
entries.push(ResourceEntry { name, offset, length });
}
Ok(Self { data, entries })
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn entry(&self, index: usize) -> Option<&ResourceEntry> {
self.entries.get(index)
}
pub fn entries(&self) -> impl Iterator<Item = &ResourceEntry> {
self.entries.iter()
}
pub fn find(&self, name: &str) -> Option<usize> {
let query = crate::normalize_name(name);
self.entries.iter().position(|entry| entry.name == query)
}
pub fn read(&self, index: usize) -> Option<&[u8]> {
let entry = self.entries.get(index)?;
let start = entry.offset as usize;
self.data.get(start..start + entry.length as usize)
}
pub fn read_by_name(&self, name: &str) -> Option<&[u8]> {
self.read(self.find(name)?)
}
}