use binread::io::{Read, Seek, SeekFrom}; use binread::{BinRead, BinResult, FilePtr, ReadOptions}; use binwrite::BinWrite; use one::{OneArchive, OneArchiveEntry, WriteSeek}; use std::fmt::Formatter; 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 && matches!(magic.as_str(), self::MAGIC_60 | self::MAGIC_50)))] #[binwrite(little)] pub struct ShadOne { /// 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 "pre-header" of these first three u32's pub archive_size_minus_twelve: u32, /// 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)))] pub magic: String, /// number of files stored in the .one pub count: u32, #[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, 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, 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, // 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: FilePtr>, // TODO: compute size by finding PRS length? pub is_compressed: u32, } impl std::fmt::Debug for ShadOne { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "ShadOne{{version: 0x{:x}, comment: {:?}, count: {}, ..}}", self.version, self.comment, self.count, ) } } impl std::fmt::Debug for ShadOneEntry { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "ShadOneEntry{{is_compressed: {}, name: {:?}, size_dec: {}, _size_cmp: {}, ..}}", self.is_compressed != 0, self.name, self.size_dec, self._size_cmp, ) } } impl OneArchiveEntry for ShadOneEntry {} impl OneArchive for ShadOne { fn write(&self, mut _writer: &mut dyn WriteSeek) -> std::io::Result<()> { todo!() } fn pack(_contents: impl IntoIterator)>) -> Self where Self: Sized, { todo!() } fn unpack(self) -> Box)>> { 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> { self.entries .iter() .map(|x| { let y: &dyn OneArchiveEntry = x; y }) .collect() } }