one-rust/one-rust/src/one/heroes.rs

172 lines
5.7 KiB
Rust

use crate::string_binread_helper;
use crate::string_binwrite_helper;
use binread::BinRead;
use binwrite::BinWrite;
use one::{OneArchive, OneArchiveEntry, WriteSeek};
use prs_rust::prs;
use std::fmt::Formatter;
use std::mem::size_of;
use std::path::PathBuf;
const SIZE_OF_ENTRY: u32 = 3 * size_of::<u32>() as u32;
const SIZE_OF_FILENAME: usize = 0x40;
const TYPICAL_STRING_TABLE_COUNT: usize = 0x100;
// stats on version
// $ find . -name \*.one -exec bash -c "onear tf "{}" > "{}".txt" \;
// $ fd one.txt | xargs cat | grep '^HerOne{' | sort | uniq -c
// 3 HerOne{version: 0x1003ffff, ..}
// 725 HerOne{version: 0x1400ffff, ..}
// FIXME: coverOpen*_gc.one are missing the first entry (they start with the string table entry)
// solve this by factoring out a struct that's just { id, size, version } and Option<> it
#[derive(BinRead, BinWrite)]
#[br(little, assert(
entries.iter().enumerate().all(|(i, ent)| ent.id as usize == i + 2 && ent.version == version) &&
archive_size_minus_12 == (
entries.iter().map(|e| self::SIZE_OF_ENTRY + e.size_cmp).sum::<u32>() +
SIZE_OF_ENTRY + (string_table.len() * SIZE_OF_FILENAME) as u32
)
))]
#[binwrite(little)]
pub struct HerOne {
/// 0. conceptually the ID of the "entry" containing the entire archive
#[br(assert(id_archive == 0))]
id_archive: u32,
/// the size of the archive, not counting the "first entry" of these first three u32's
archive_size_minus_12: u32,
/// presumably the archiver version. known values: 0x1400ffff and 0x1003ffff from GCN version
version: u32,
#[br(assert(id_string_table == 1))]
/// 1. conceptually the ID of the (uncompressed) array of filenames, indexed by ID.
id_string_table: u32,
/// usually 0x4000. size of string table in bytes.
string_table_size: u32,
#[br(assert(version == version_again))]
version_again: u32,
#[br(count = string_table_size as usize / self::SIZE_OF_FILENAME)]
pub string_table: Vec<HerOneFilename>,
#[br(
count = string_table.iter().skip(2).position(|x| x.inner.is_empty()).unwrap_or(0),
args(version, string_table.as_ptr())
)]
pub entries: Vec<HerOneEntry>,
}
#[derive(BinRead, BinWrite, Clone, Default)]
pub struct HerOneFilename {
#[br(pad_size_to = self::SIZE_OF_FILENAME, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_FILENAME)))]
pub inner: String,
}
#[derive(BinRead, BinWrite)]
#[br(
little,
import(version_hdr: u32, string_table: *const HerOneFilename),
assert(version == version_hdr)
)]
#[binwrite(little)]
pub struct HerOneEntry {
pub id: u32,
pub size_cmp: u32,
version: u32,
#[br(count = size_cmp)]
pub data: Vec<u8>,
#[br(calc = unsafe { (*string_table.add(id as usize)).inner.clone() })]
#[binwrite(ignore)]
name: String,
}
impl std::fmt::Debug for HerOne {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "HerOne{{version: 0x{:x}, ..}}", self.version)
}
}
impl std::fmt::Debug for HerOneEntry {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"HerOneEntry{{id: {}, name: {:?}, size_cmp: {}, ..}}",
self.id, self.name, self.size_cmp,
)
}
}
impl OneArchiveEntry for HerOneEntry {}
impl OneArchive for HerOne {
fn write(&self, mut writer: &mut dyn WriteSeek) -> std::io::Result<()> {
BinWrite::write(&self, &mut writer)
}
fn pack(contents: impl IntoIterator<Item = (PathBuf, Vec<u8>)>) -> Self
where
Self: Sized,
{
// TODO: set_version that updates both copies here as well as all the entries
let version = 0x1400ffff;
let mut entries = Vec::new();
let mut string_table = vec![HerOneFilename::default(); 2];
let mut archive_size_minus_12 = 0;
for (id, (path, data)) in contents.into_iter().enumerate() {
let mut cmp = prs::Compressor::new(&data, None).compress();
let name = path.to_string_lossy().to_uppercase();
string_table.push(HerOneFilename {
inner: name.clone(),
});
// round up to 4-byte alignment
while (cmp.len() & 3) != 0 {
cmp.push(0);
}
archive_size_minus_12 += SIZE_OF_ENTRY + cmp.len() as u32;
entries.push(HerOneEntry {
id: id as u32 + 2,
size_cmp: cmp.len() as u32,
version,
data: cmp,
name,
});
}
if string_table.len() < TYPICAL_STRING_TABLE_COUNT {
string_table.resize_with(TYPICAL_STRING_TABLE_COUNT, HerOneFilename::default);
}
// we're counting 'entry 1' for the string table, but not 'entry 0' for the entire file
let string_table_size = (string_table.len() * SIZE_OF_FILENAME) as u32;
archive_size_minus_12 += SIZE_OF_ENTRY + string_table_size;
HerOne {
id_archive: 0,
archive_size_minus_12,
version,
id_string_table: 1,
string_table_size,
version_again: version,
string_table,
entries,
}
}
fn unpack(self) -> Box<dyn Iterator<Item = (PathBuf, Vec<u8>)>> {
Box::new(self.entries.into_iter().map(|entry| {
let key = PathBuf::from(entry.name);
let dec_buf = prs::Decompressor::new(entry.data, None).decompress();
(key, dec_buf)
}))
}
fn entries(&self) -> Vec<&dyn OneArchiveEntry> {
self.entries
.iter()
.map(|x| {
let y: &dyn OneArchiveEntry = x;
y
})
.collect()
}
}