diff --git a/one-rust/src/bin/onear.rs b/one-rust/src/bin/onear.rs index 85b449f..442be74 100644 --- a/one-rust/src/bin/onear.rs +++ b/one-rust/src/bin/onear.rs @@ -5,8 +5,9 @@ extern crate one_rust; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; -use binread::prelude::*; use one_rust::prelude::*; +use std::error::Error; +use std::str::FromStr; /// Unpack .one archives from NiGHTS: Journey of Dreams #[derive(FromArgs)] @@ -18,9 +19,37 @@ struct Args { #[derive(FromArgs)] #[argh(subcommand)] enum Action { + DescribeFile(DescribeFile), ExtractFile(ExtractFile), CreateFile(CreateFile), - DescribeFile(DescribeFile), + CreateJodFile(CreateJodFile), + CreateSsrFile(CreateSsrFile), +} + +enum GameType { + /// NiGHTS: Journey of Dreams + Dreams, + /// Sonic and the Secret Rings + Rings, + /// Shadow the Hedgehog + Shadow, + /// Sonic Heroes + Heroes, +} + +impl FromStr for GameType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.chars().next() { + None => Err("no GameType found".to_string()), + Some('D') | Some('d') => Ok(GameType::Dreams), + Some('R') | Some('r') => Ok(GameType::Rings), + Some('S') | Some('s') => Ok(GameType::Shadow), + Some('H') | Some('h') => Ok(GameType::Heroes), + Some(_) => Err(format!("Unsupported game: {}", s)), + } + } } /// Unpack and decompress the files inside a .one archive. @@ -43,8 +72,25 @@ struct DescribeFile { /// Create a .one archive from the given files. #[derive(FromArgs)] -#[argh(subcommand, name = "cf")] +#[argh(subcommand, name = "c")] struct CreateFile { + /// (D|R|S|H) game of the .one file to create + #[argh(option, short='g')] + game: GameType, + /// path of the .one file to create + #[argh(option, short='f')] + file: PathBuf, + /// paths of the files to pack into the archive + #[argh(positional)] + contents: Vec, +} + +// sugar: + +/// Create a NiGHTS: Journey of _D_reams .one archive. +#[derive(FromArgs)] +#[argh(subcommand, name = "cDf")] +struct CreateJodFile { /// path of the .one file to create #[argh(positional)] file: PathBuf, @@ -53,12 +99,48 @@ struct CreateFile { contents: Vec, } -fn main() -> Result<(), Box> { +/// Create a Sonic and the Secret _R_ings .one archive. +#[derive(FromArgs)] +#[argh(subcommand, name = "cRf")] +struct CreateSsrFile { + /// path of the .one file to create + #[argh(positional)] + file: PathBuf, + /// paths of the files to pack into the archive + #[argh(positional)] + contents: Vec, +} + +fn create_one(subcmd: CreateFile) -> Result<(), Box> { + if subcmd.file.exists() { + return Err(format!("{:?} already exists, refusing to overwrite", subcmd.file).into()); + } + + let mut contents = Vec::new(); + for path in subcmd.contents { + let mut data = Vec::new(); + File::open(&path)?.read_to_end(&mut data)?; + contents.push((path, data)); + } + + let archive: Box = match subcmd.game { + GameType::Dreams => Box::new(JodOne::pack(contents)), + GameType::Rings => Box::new(SsrOne::pack(contents)), + GameType::Shadow => todo!(), + GameType::Heroes => todo!(), + }; + let mut outfile = File::create(&subcmd.file)?; + archive.write(&mut outfile)?; + + Ok(()) +} + +fn main() -> Result<(), Box> { let args: Args = argh::from_env(); match args.action { Action::ExtractFile(subcmd) => { - let mut infile = File::open(&subcmd.file)?; - let archive: JodOne = infile.read_le()?; + let infile = File::open(&subcmd.file)?; + let archive = read_one_archive(infile)?; let entries = archive.unpack(); let dir = PathBuf::from(format!("{}.d", subcmd.file.to_string_lossy())); for (name, data) in entries { @@ -68,6 +150,7 @@ fn main() -> Result<(), Box> { } File::create(full_path)?.write_all(&data)?; } + Ok(()) } Action::DescribeFile(subcmd) => { let infile = File::open(&subcmd.file)?; @@ -76,23 +159,24 @@ fn main() -> Result<(), Box> { for entry in archive.entries() { println!("{:?}", entry); } + Ok(()) } - Action::CreateFile(subcmd) => { - if subcmd.file.exists() { - return Err(format!("{:?} already exists, refusing to overwrite", subcmd.file).into()); - } - - let mut entries = Vec::new(); - for path in subcmd.contents { - let mut data = Vec::new(); - File::open(&path)?.read_to_end(&mut data)?; - entries.push((path, data)); - } - - let archive = JodOne::pack(entries); - let mut outfile = File::create(&subcmd.file)?; - archive.write(&mut outfile)?; + Action::CreateFile(subcmd) => create_one(subcmd), + Action::CreateJodFile(subcmd) => { + let subcmd = CreateFile { + game: GameType::Dreams, + file: subcmd.file, + contents: subcmd.contents, + }; + create_one(subcmd) + } + Action::CreateSsrFile(subcmd) => { + let subcmd = CreateFile { + game: GameType::Rings, + file: subcmd.file, + contents: subcmd.contents, + }; + create_one(subcmd) } } - Ok(()) } diff --git a/one-rust/src/one/dreams.rs b/one-rust/src/one/dreams.rs index 52237e0..f7e5803 100644 --- a/one-rust/src/one/dreams.rs +++ b/one-rust/src/one/dreams.rs @@ -93,9 +93,9 @@ impl OneArchive for JodOne { Ok(()) } - fn pack(inputs: impl IntoIterator)>) -> Self where Self: Sized { + fn pack(contents: impl IntoIterator)>) -> Self where Self: Sized { let mut entries = Vec::new(); - for (id, (path, data)) in inputs.into_iter().enumerate() { + for (id, (path, data)) in contents.into_iter().enumerate() { let cmp = prs::Compressor::new(&data, None).compress(); let name = path.to_string_lossy().replace(std::path::MAIN_SEPARATOR, "\\"); if cmp.len() < data.len() { diff --git a/one-rust/src/one/mod.rs b/one-rust/src/one/mod.rs index f6c4817..4967c4a 100644 --- a/one-rust/src/one/mod.rs +++ b/one-rust/src/one/mod.rs @@ -1,9 +1,11 @@ use std::path::PathBuf; use std::io::{Seek, Write, Read}; -use binread::BinReaderExt; use std::error::Error; +use std::fmt::{Debug, Formatter}; +use binread::BinReaderExt; use binread::io::SeekFrom; -use std::fmt::Debug; +use one::dreams::JodOne; +use one::rings::SsrOne; pub(crate) mod dreams; pub(crate) mod rings; @@ -13,22 +15,64 @@ impl WriteSeek for T where T: Write + Seek {} pub trait OneArchive: Debug { fn write(&self, writer: &mut dyn WriteSeek) -> std::io::Result<()>; - fn pack(inputs: impl IntoIterator)>) -> Self where Self: Sized; + fn pack(contents: impl IntoIterator)>) -> Self where Self: Sized; fn unpack(self) -> Box)>>; fn entries(&self) -> Vec<&dyn OneArchiveEntry>; } pub trait OneArchiveEntry: Debug {} -pub fn read_one_archive(mut reader: impl Read + Seek) -> Result, Box> { +// hack to work around inability to consume self via boxed trait object +pub enum DynOneArchive { + JodOne(JodOne), + SsrOne(SsrOne), +} + +impl Debug for DynOneArchive { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + DynOneArchive::JodOne(one) => one.fmt(f), + DynOneArchive::SsrOne(one) => one.fmt(f), + } + } +} + +impl OneArchive for DynOneArchive { + fn write(&self, writer: &mut dyn WriteSeek) -> std::io::Result<()> { + match self { + DynOneArchive::JodOne(one) => one.write(writer), + DynOneArchive::SsrOne(one) => one.write(writer), + } + } + + fn pack(_: impl IntoIterator)>) -> Self where Self: Sized { + unimplemented!() + } + + fn unpack(self) -> Box)>> { + match self { + DynOneArchive::JodOne(one) => one.unpack(), + DynOneArchive::SsrOne(one) => one.unpack(), + } + } + + fn entries(&self) -> Vec<&dyn OneArchiveEntry> { + match self { + DynOneArchive::JodOne(one) => one.entries(), + DynOneArchive::SsrOne(one) => one.entries(), + } + } +} + +pub fn read_one_archive(mut reader: impl Read + Seek) -> Result> { let rewind = reader.seek(SeekFrom::Current(0))?; match reader.read_le::() { - Ok(one) => return Ok(Box::new(one)), + Ok(one) => return Ok(DynOneArchive::JodOne(one)), Err(e) => info!("Not a NiGHTS Journey of Dreams archive: {}", e), } reader.seek(SeekFrom::Start(rewind))?; match reader.read_be::() { - Ok(one) => return Ok(Box::new(one)), + Ok(one) => return Ok(DynOneArchive::SsrOne(one)), Err(e) => info!("Not a Sonic and the Secret Rings archive: {}", e), } Err("No valid .one archive found".into()) diff --git a/one-rust/src/one/rings.rs b/one-rust/src/one/rings.rs index 4b569fd..d52cf9c 100644 --- a/one-rust/src/one/rings.rs +++ b/one-rust/src/one/rings.rs @@ -49,13 +49,14 @@ pub struct SsrOneEntry { #[binwrite(preprocessor(string_binwrite_helper(32)))] pub name: String, pub id: u32, - // note: seek_before/restore_position shenanigans because we need size_cmp for data's count + // note: seek_before/restore_position shenanigans because we need _size_cmp for data's count #[br(seek_before = SeekFrom::Current(4), restore_position)] - pub size_cmp: u32, - #[br(count = size_cmp)] + #[binwrite(ignore)] + pub _size_cmp: u32, + #[br(count = _size_cmp)] #[binwrite(preprocessor(|x: &FilePtr>| x.ptr))] pub data: FilePtr>, - #[br(seek_before = SeekFrom::Current(4))] + pub size_cmp: u32, pub size_dec: u32, } @@ -79,8 +80,10 @@ impl OneArchiveEntry for SsrOneEntry {} impl OneArchive for SsrOne { fn write(&self, mut writer: &mut dyn WriteSeek) -> std::io::Result<()> { + let entries = self.entries.value.as_ref().unwrap(); BinWrite::write(&self, &mut writer)?; - for entry in self.entries.value.as_ref().unwrap() { + BinWrite::write(entries, &mut writer)?; + for entry in entries { let loc = writer.seek(std::io::SeekFrom::Current(0)).unwrap() as usize; let padding = vec![0; entry.data.ptr as usize - loc]; writer.write(&padding)?; @@ -89,9 +92,9 @@ impl OneArchive for SsrOne { Ok(()) } - fn pack(inputs: impl IntoIterator)>) -> Self where Self: Sized { + fn pack(contents: impl IntoIterator)>) -> Self where Self: Sized { let mut entries = Vec::new(); - for (id, (path, data)) in inputs.into_iter().enumerate() { + for (id, (path, data)) in contents.into_iter().enumerate() { let cmp = prs::Compressor::new(&data, None).compress(); let name = path.to_string_lossy().to_uppercase(); if cmp.len() < data.len() { @@ -101,6 +104,7 @@ impl OneArchive for SsrOne { size_dec: data.len() as u32, data: FilePtr { ptr: 0, value: Some(cmp) }, name, + _size_cmp: 0 }); } else { entries.push(SsrOneEntry { @@ -109,6 +113,7 @@ impl OneArchive for SsrOne { size_dec: 0, data: FilePtr { ptr: 0, value: Some(data) }, name, + _size_cmp: 0 }); } }