implement extraction for Shadow

This commit is contained in:
lifning 2021-01-17 03:16:36 -08:00
parent 2f887b4924
commit b7c670b37b
2 changed files with 73 additions and 13 deletions

View File

@ -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::<u32>() + SIZE_OF_MAGIC + SIZE_OF_COMMENT) as u32;
const SIZE_OF_FILENAME: usize = 0x2c;
const SIZE_OF_ENTRY: u32 = (3 * size_of::<u32>() + SIZE_OF_FILENAME) as u32;
fn compute_size_cmp<R: Read + Seek>(
reader: &mut R,
ro: &ReadOptions,
(count, arc_size): (u32, u32),
) -> BinResult<u32> {
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::<u32>() 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<ShadOneEntry>,
}
// 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<u32, _>| x.ptr))]
pub data_offset: FilePtr<u32, ()>,
pub data: FilePtr<u32, Vec<u8>>, // 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<dyn Iterator<Item = (PathBuf, Vec<u8>)>> {
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> {

@ -1 +1 @@
Subproject commit 8ee6b274c0180d5d58ffa00f815a84cc0780603a
Subproject commit c5a4269779ca926114065745f0691f4669c7d2bd