start adding Shadow the Hedgehog support

This commit is contained in:
lifning 2021-01-17 01:13:01 -08:00
parent 3e4fd54402
commit 2f887b4924
5 changed files with 157 additions and 30 deletions

View File

@ -12,6 +12,7 @@ pub mod prelude {
pub use one::dreams::{JodOne, JodOneEntry};
pub use one::read_one_archive;
pub use one::rings::{SsrOne, SsrOneEntry};
pub use one::shadow::{ShadOne, ShadOneEntry};
pub use one::{OneArchive, OneArchiveEntry};
}

View File

@ -18,26 +18,36 @@ use crate::string_binwrite_helper;
// 988 JodOne{version: 0xcc, comment: " ", category: Default, ..}
// 1579 JodOne{version: 0xcc, comment: " ", category: landData, ..}
const JOD_ONE_MAGIC: &'static [u8; 16] = b"ThisIsOneFile\0\0\0";
const MAGIC: &'static str = "ThisIsOneFile";
const SIZE_OF_JOD_ONE_HEADER: usize = JOD_ONE_MAGIC.len() + 2 * size_of::<u32>() + 32 + 132;
const SIZE_OF_JOD_ONE_ENTRY: usize = 6 * size_of::<u32>() + 192;
const SIZE_OF_MAGIC: usize = 16;
const SIZE_OF_CATEGORY: usize = 32;
const SIZE_OF_COMMENT: usize = 132;
const SIZE_OF_HEADER: u32 =
(2 * size_of::<u32>() + SIZE_OF_MAGIC + SIZE_OF_CATEGORY + SIZE_OF_COMMENT) as u32;
const SIZE_OF_FILENAME: usize = 192;
const SIZE_OF_ENTRY: u32 = (6 * size_of::<u32>() + SIZE_OF_FILENAME) as u32;
// not using binread's built-in [br(magic=...)] because it doesn't let me reference self::MAGIC
#[derive(BinRead, BinWrite)]
#[br(little, magic = b"ThisIsOneFile\0\0\0")]
#[br(little, assert(magic == self::MAGIC))]
#[binwrite(little)]
pub struct JodOne {
// usually 0xCC... archive creator version maybe?
// but i've seen 0xCA in Test04/disp/Test04.one and 0xCB in Test05/disp/stg2010_sun.one
#[br(pad_size_to = self::SIZE_OF_MAGIC, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_MAGIC)))]
pub magic: String,
/// usually 0xCC... archive creator version?
/// i've seen 0xCA in Test04/disp/Test04.one and 0xCB in Test05/disp/stg2010_sun.one
pub version: u32,
// "Default" or "landData"
#[br(pad_size_to = 32, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(32)))]
/// always "Default" or "landData"
#[br(pad_size_to = self::SIZE_OF_CATEGORY, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_CATEGORY)))]
pub category: String,
pub count: u32,
// always " "
#[br(pad_size_to = 132, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(132)))]
/// always " "
#[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)]
pub entries: Vec<JodOneEntry>,
@ -53,10 +63,10 @@ pub struct JodOneEntry {
pub size_dec: u32,
pub id_again: u32,
#[br(count = size_cmp)]
#[binwrite(preprocessor(|x: &FilePtr<u32, Vec<u8>>| x.ptr))]
#[binwrite(preprocessor(|x: &FilePtr<u32, _>| x.ptr))]
pub data: FilePtr<u32, Vec<u8>>,
#[br(pad_size_to = 192, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(192)))]
#[br(pad_size_to = self::SIZE_OF_FILENAME, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(self::SIZE_OF_FILENAME)))]
pub name: String,
}
@ -88,7 +98,6 @@ impl OneArchiveEntry for JodOneEntry {}
impl OneArchive for JodOne {
fn write(&self, mut writer: &mut dyn WriteSeek) -> std::io::Result<()> {
writer.write(JOD_ONE_MAGIC)?;
BinWrite::write(&self, &mut writer)?;
for entry in &self.entries {
let loc = writer.seek(std::io::SeekFrom::Current(0)).unwrap() as usize;
@ -138,7 +147,7 @@ impl OneArchive for JodOne {
}
}
let mut loc = (SIZE_OF_JOD_ONE_HEADER + SIZE_OF_JOD_ONE_ENTRY * entries.len()) as u32;
let mut loc = SIZE_OF_HEADER + SIZE_OF_ENTRY * entries.len() as u32;
for entry in &mut entries {
loc = ((loc + 15) / 16) * 16; // round up to 16 byte alignment
entry.data.ptr = loc;
@ -147,6 +156,7 @@ impl OneArchive for JodOne {
// user can overwrite string/version fields afterward
JodOne {
magic: MAGIC.to_string(),
version: 0xCC,
category: "Default".to_string(),
count: entries.len() as u32,

View File

@ -2,6 +2,7 @@ use binread::io::SeekFrom;
use binread::BinReaderExt;
use one::dreams::JodOne;
use one::rings::SsrOne;
use one::shadow::ShadOne;
use std::error::Error;
use std::fmt::{Debug, Formatter};
use std::io::{Read, Seek, Write};
@ -9,6 +10,7 @@ use std::path::PathBuf;
pub(crate) mod dreams;
pub(crate) mod rings;
pub(crate) mod shadow;
pub trait WriteSeek: Write + Seek {}
impl<T> WriteSeek for T where T: Write + Seek {}
@ -22,13 +24,14 @@ pub trait OneArchive: Debug {
fn entries(&self) -> Vec<&dyn OneArchiveEntry>;
}
// TODO: find out the least-common-denominator of things we know about these files
// TODO: find out the least-common-denominator of things we know about these files to expose here
pub trait OneArchiveEntry: Debug {}
// hack to work around inability to consume self via boxed trait object
pub enum DynOneArchive {
JodOne(JodOne),
SsrOne(SsrOne),
ShadOne(ShadOne),
}
impl Debug for DynOneArchive {
@ -36,6 +39,7 @@ impl Debug for DynOneArchive {
match self {
DynOneArchive::JodOne(one) => one.fmt(f),
DynOneArchive::SsrOne(one) => one.fmt(f),
DynOneArchive::ShadOne(one) => one.fmt(f),
}
}
}
@ -45,6 +49,7 @@ impl OneArchive for DynOneArchive {
match self {
DynOneArchive::JodOne(one) => one.write(writer),
DynOneArchive::SsrOne(one) => one.write(writer),
DynOneArchive::ShadOne(one) => one.write(writer),
}
}
@ -59,6 +64,7 @@ impl OneArchive for DynOneArchive {
match self {
DynOneArchive::JodOne(one) => one.unpack(),
DynOneArchive::SsrOne(one) => one.unpack(),
DynOneArchive::ShadOne(one) => one.unpack(),
}
}
@ -66,6 +72,7 @@ impl OneArchive for DynOneArchive {
match self {
DynOneArchive::JodOne(one) => one.entries(),
DynOneArchive::SsrOne(one) => one.entries(),
DynOneArchive::ShadOne(one) => one.entries(),
}
}
}
@ -81,5 +88,10 @@ pub fn read_one_archive(mut reader: impl Read + Seek) -> Result<DynOneArchive, B
Ok(one) => return Ok(DynOneArchive::SsrOne(one)),
Err(e) => info!("Not a Sonic and the Secret Rings archive: {}", e),
}
reader.seek(SeekFrom::Start(rewind))?;
match reader.read_le::<shadow::ShadOne>() {
Ok(one) => return Ok(DynOneArchive::ShadOne(one)),
Err(e) => info!("Not a Shadow the Hedgehog archive: {}", e),
}
Err("No valid .one archive found".into())
}

View File

@ -10,13 +10,14 @@ use std::path::PathBuf;
use crate::string_binread_helper;
use crate::string_binwrite_helper;
const SIZE_OF_SSR_ONE_HEADER: usize = 4 * size_of::<u32>(); // 0x10
const SIZE_OF_SSR_ONE_ENTRY: usize = 4 * size_of::<u32>() + 32; // 0x30
const SIZE_OF_HEADER: u32 = 4 * size_of::<u32>() as u32;
const SIZE_OF_FILENAME: usize = 32;
const SIZE_OF_ENTRY: u32 = (4 * size_of::<u32>() + SIZE_OF_FILENAME) as u32;
// stats on padding and size_dec:
// $ find . -name \*.one -exec bash -c "onear tf "{}" > "{}".txt" \;
// $ fd one.txt | xargs cat | sed 's/count: [0-9]\+, //' | grep '^SsrOne{' | sort | uniq -c
// 284 SsrOne{_unknown: 0x0, ..}
// 284 SsrOne{_padding: 0x0, ..}
// $ fd one.txt | xargs cat | grep '^SsrOneEntry{' | sed \
// > -e 's/ [1-9][0-9]*, / 1+, /g' \
// > -e 's/id: [01]+\?, name: "[^"]\+", //' | sort | uniq -c
@ -24,14 +25,17 @@ const SIZE_OF_SSR_ONE_ENTRY: usize = 4 * size_of::<u32>() + 32; // 0x30
// 5105 SsrOneEntry{size_cmp: 1+, size_dec: 1+, ..}
#[derive(BinRead, BinWrite)]
#[br(big, assert(entries.ptr == 0x10 && data_offset == entries.ptr + count * 0x30))]
#[br(big, assert(
entries.ptr == self::SIZE_OF_HEADER &&
data_offset == entries.ptr + count * self::SIZE_OF_ENTRY
))]
#[binwrite(big)]
pub struct SsrOne {
/// number of files stored in the .one
pub count: u32,
/// the location of the table of contents. usually 0x10, just after this header
/// the location of the table of contents. always 0x10, just after this header
#[br(count = count)]
#[binwrite(preprocessor(|x: &FilePtr<u32, Vec<_>>| x.ptr))]
#[binwrite(preprocessor(|x: &FilePtr<u32, _>| x.ptr))]
pub entries: FilePtr<u32, Vec<SsrOneEntry>>,
/// location of the end of the toc/beginning of data. TODO: assertion
pub data_offset: u32,
@ -43,8 +47,8 @@ pub struct SsrOne {
#[br(big)]
#[binwrite(big)]
pub struct SsrOneEntry {
#[br(pad_size_to = 32, map = string_binread_helper)]
#[binwrite(preprocessor(string_binwrite_helper(32)))]
#[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 id: u32,
// note: seek_before/restore_position shenanigans because we need _size_cmp for data's count
@ -52,7 +56,7 @@ pub struct SsrOneEntry {
#[binwrite(ignore)]
pub _size_cmp: u32,
#[br(count = _size_cmp)]
#[binwrite(preprocessor(|x: &FilePtr<u32, Vec<u8>>| x.ptr))]
#[binwrite(preprocessor(|x: &FilePtr<u32, _>| x.ptr))]
pub data: FilePtr<u32, Vec<u8>>,
pub size_cmp: u32,
pub size_dec: u32,
@ -62,7 +66,7 @@ impl std::fmt::Debug for SsrOne {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SsrOne{{count: {}, _unknown: 0x{:x}, ..}}",
"SsrOne{{count: {}, _padding: 0x{:x}, ..}}",
self.count, self._padding
)
}
@ -129,7 +133,7 @@ impl OneArchive for SsrOne {
}
}
let data_offset = (SIZE_OF_SSR_ONE_HEADER + SIZE_OF_SSR_ONE_ENTRY * entries.len()) as u32;
let data_offset = SIZE_OF_HEADER + SIZE_OF_ENTRY * entries.len() as u32;
let mut loc = data_offset;
for entry in &mut entries {
loc = ((loc + 3) / 4) * 4; // round up to 4 byte alignment
@ -141,7 +145,7 @@ impl OneArchive for SsrOne {
SsrOne {
count: entries.len() as u32,
entries: FilePtr {
ptr: SIZE_OF_SSR_ONE_HEADER as u32,
ptr: SIZE_OF_HEADER as u32,
value: Some(entries),
},
data_offset,

100
one-rust/src/one/shadow.rs Normal file
View File

@ -0,0 +1,100 @@
use binread::FilePtr;
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;
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_FILENAME: usize = 0x2c;
#[derive(BinRead, BinWrite)]
#[br(little, assert(start == 0 && (magic == self::MAGIC_60 || magic == self::MAGIC_50)))]
#[binwrite(little)]
pub struct ShadOne {
/// 0. presumably where data starts, not counting the "mini-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
pub archive_size_minus_twelve: u32,
/// presumably the archiver version. known values: 0x1c020037 and 0x1c020020 from gamecube 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)]
pub entries: Vec<ShadOneEntry>,
}
#[derive(BinRead, BinWrite)]
#[br(little)]
#[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)]
#[binwrite(preprocessor(|x: &FilePtr<u32, _>| x.ptr))]
pub data_offset: FilePtr<u32, ()>,
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: {}, ..}}",
self.is_compressed, self.name, self.size_dec,
)
}
}
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>)>> {
todo!()
}
fn entries(&self) -> Vec<&dyn OneArchiveEntry> {
self.entries
.iter()
.map(|x| {
let y: &dyn OneArchiveEntry = x;
y
})
.collect()
}
}