From b7c670b37b4547063bac3052b0cf842915bd9b7f Mon Sep 17 00:00:00 2001 From: lifning <> Date: Sun, 17 Jan 2021 03:16:36 -0800 Subject: [PATCH] implement extraction for Shadow --- one-rust/src/one/shadow.rs | 84 ++++++++++++++++++++++++++++++++------ prs-rust | 2 +- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/one-rust/src/one/shadow.rs b/one-rust/src/one/shadow.rs index fe2b066..d2345c2 100644 --- a/one-rust/src/one/shadow.rs +++ b/one-rust/src/one/shadow.rs @@ -1,4 +1,5 @@ -use binread::FilePtr; +use binread::io::{Read, Seek, SeekFrom}; +use binread::{BinRead, BinResult, FilePtr, ReadOptions}; use binwrite::BinWrite; use one::{OneArchive, OneArchiveEntry, WriteSeek}; use std::fmt::Formatter; @@ -6,23 +7,58 @@ use std::path::PathBuf; use crate::string_binread_helper; use crate::string_binwrite_helper; +use prs_rust::prs; +use std::mem::size_of; + +// stats on version, comment, and count +// $ find . -name \*.one -exec bash -c "onear tf "{}" > "{}".txt" \; +// $ fd one.txt | xargs cat | grep '^ShadOne{' | sed \ +// > -e 's/count: [1-9][0-9]*, /count: 1+, /' \ +// > -e 's/comment: ".\+"/comment: ".+"/' | sort | uniq -c +// 11 ShadOne{version: 0x1c020020, comment: "", count: 1+, ..} +// 1 ShadOne{version: 0x1c020037, comment: "", count: 0, ..} +// 445 ShadOne{version: 0x1c020037, comment: "", count: 1+, ..} +// 787 ShadOne{version: 0x1c020037, comment: ".+", count: 1+, ..} const MAGIC_50: &'static str = "One Ver 0.50"; const MAGIC_60: &'static str = "One Ver 0.60"; const SIZE_OF_MAGIC: usize = 16; const SIZE_OF_COMMENT: usize = 0x90; +const SIZE_OF_HEADER: u32 = (4 * size_of::() + SIZE_OF_MAGIC + SIZE_OF_COMMENT) as u32; + const SIZE_OF_FILENAME: usize = 0x2c; +const SIZE_OF_ENTRY: u32 = (3 * size_of::() + SIZE_OF_FILENAME) as u32; + +fn compute_size_cmp( + reader: &mut R, + ro: &ReadOptions, + (count, arc_size): (u32, u32), +) -> BinResult { + let saved_pos = reader.seek(SeekFrom::Current(0))?; + let cur_offset = u32::read_options(reader, ro, ())?; + let next_pos = reader.seek(SeekFrom::Current( + SIZE_OF_ENTRY as i64 - size_of::() as i64, + ))?; + // last entry, use whole-archive end offset instead of next-file begin offset + let next_offset = if next_pos as u32 > SIZE_OF_HEADER + count * self::SIZE_OF_ENTRY { + arc_size + } else { + u32::read_options(reader, ro, ())? + }; + reader.seek(SeekFrom::Start(saved_pos))?; + Ok(next_offset - cur_offset) +} #[derive(BinRead, BinWrite)] -#[br(little, assert(start == 0 && (magic == self::MAGIC_60 || magic == self::MAGIC_50)))] +#[br(little, assert(start == 0 && matches!(magic.as_str(), self::MAGIC_60 | self::MAGIC_50)))] #[binwrite(little)] pub struct ShadOne { - /// 0. presumably where data starts, not counting the "mini-header" of these first three u32's + /// 0. presumably where data starts, not counting the "pre-header" of these first three u32's pub start: u32, - /// the size of the archive, not counting the "mini-header" of these first three u32's + /// the size of the archive, not counting the "pre-header" of these first three u32's pub archive_size_minus_twelve: u32, - /// presumably the archiver version. known values: 0x1c020037 and 0x1c020020 from gamecube version + /// presumably the archiver version. known values: 0x1c020037 and 0x1c020020 from GCN version pub version: u32, #[br(pad_size_to = self::SIZE_OF_MAGIC, map = string_binread_helper)] #[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_MAGIC)))] @@ -32,21 +68,32 @@ pub struct ShadOne { #[br(pad_size_to = self::SIZE_OF_COMMENT, map = string_binread_helper)] #[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_COMMENT)))] pub comment: String, - #[br(count = count)] + #[br(count = count, args(count, archive_size_minus_twelve))] pub entries: Vec, } +// there is one file for which the assertion can't be (size_dec == 0) == (is_compressed == 0). +// event/event8006_sceneE.one: EVENT8006.CEN, whose size_dec is 0x20, its in-archive size. #[derive(BinRead, BinWrite)] -#[br(little)] +#[br( + little, + assert(if size_dec == 0 { is_compressed == 0 } else { true }), + import(count: u32, arc_size: u32) +)] #[binwrite(little)] pub struct ShadOneEntry { #[br(pad_size_to = self::SIZE_OF_FILENAME, map = string_binread_helper)] #[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_FILENAME)))] pub name: String, pub size_dec: u32, - #[br(offset = 0xC)] + // we have to compute this via shenanigans because it's not otherwise represented. + // we can't just use prs::Decompressor::get_sizes because of event8006_sceneE.one, noted above. + #[br(restore_position, parse_with = compute_size_cmp, args(count, arc_size))] + #[binwrite(ignore)] + pub _size_cmp: u32, + #[br(offset = 12, count = _size_cmp)] #[binwrite(preprocessor(|x: &FilePtr| x.ptr))] - pub data_offset: FilePtr, + pub data: FilePtr>, // TODO: compute size by finding PRS length? pub is_compressed: u32, } @@ -64,8 +111,11 @@ impl std::fmt::Debug for ShadOneEntry { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "ShadOneEntry{{is_compressed: {}, name: {:?}, size_dec: {}, ..}}", - self.is_compressed, self.name, self.size_dec, + "ShadOneEntry{{is_compressed: {}, name: {:?}, size_dec: {}, _size_cmp: {}, ..}}", + self.is_compressed != 0, + self.name, + self.size_dec, + self._size_cmp, ) } } @@ -85,7 +135,17 @@ impl OneArchive for ShadOne { } fn unpack(self) -> Box)>> { - todo!() + Box::new(self.entries.into_iter().map(|entry| { + let key = PathBuf::from(entry.name); + if entry.is_compressed == 0 { + (key, entry.data.into_inner()) + } else { + let dec_buf = + prs::Decompressor::new(entry.data.into_inner(), Some(entry.size_dec as usize)) + .decompress(); + (key, dec_buf) + } + })) } fn entries(&self) -> Vec<&dyn OneArchiveEntry> { diff --git a/prs-rust b/prs-rust index 8ee6b27..c5a4269 160000 --- a/prs-rust +++ b/prs-rust @@ -1 +1 @@ -Subproject commit 8ee6b274c0180d5d58ffa00f815a84cc0780603a +Subproject commit c5a4269779ca926114065745f0691f4669c7d2bd