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