one-rust/one-rust/src/one/shadow.rs

161 lines
5.7 KiB
Rust

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::<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 && 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<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,
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<u32, _>| x.ptr))]
pub data: FilePtr<u32, Vec<u8>>, // 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<Item = (PathBuf, Vec<u8>)>) -> Self
where
Self: Sized,
{
todo!()
}
fn unpack(self) -> Box<dyn Iterator<Item = (PathBuf, Vec<u8>)>> {
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()
}
}