172 lines
5.7 KiB
Rust
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()
|
|
}
|
|
}
|