finish adding SSR support
This commit is contained in:
parent
8472af4c21
commit
2658e076d5
|
@ -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<Self, Self::Err> {
|
||||
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<PathBuf>,
|
||||
}
|
||||
|
||||
// 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<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// 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<PathBuf>,
|
||||
}
|
||||
|
||||
fn create_one(subcmd: CreateFile) -> Result<(), Box<dyn Error>> {
|
||||
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<dyn OneArchive> = 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<dyn Error>> {
|
||||
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<dyn std::error::Error>> {
|
|||
}
|
||||
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<dyn std::error::Error>> {
|
|||
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(())
|
||||
}
|
||||
|
|
|
@ -93,9 +93,9 @@ impl OneArchive for JodOne {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn pack(inputs: impl IntoIterator<Item=(PathBuf, Vec<u8>)>) -> Self where Self: Sized {
|
||||
fn pack(contents: impl IntoIterator<Item=(PathBuf, Vec<u8>)>) -> 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() {
|
||||
|
|
|
@ -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<T> 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<Item=(PathBuf, Vec<u8>)>) -> Self where Self: Sized;
|
||||
fn pack(contents: impl IntoIterator<Item=(PathBuf, Vec<u8>)>) -> Self where Self: Sized;
|
||||
fn unpack(self) -> Box<dyn Iterator<Item=(PathBuf, Vec<u8>)>>;
|
||||
fn entries(&self) -> Vec<&dyn OneArchiveEntry>;
|
||||
}
|
||||
|
||||
pub trait OneArchiveEntry: Debug {}
|
||||
|
||||
pub fn read_one_archive(mut reader: impl Read + Seek) -> Result<Box<dyn OneArchive>, Box<dyn Error>> {
|
||||
// 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<Item=(PathBuf, Vec<u8>)>) -> Self where Self: Sized {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unpack(self) -> Box<dyn Iterator<Item=(PathBuf, Vec<u8>)>> {
|
||||
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<DynOneArchive, Box<dyn Error>> {
|
||||
let rewind = reader.seek(SeekFrom::Current(0))?;
|
||||
match reader.read_le::<dreams::JodOne>() {
|
||||
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::<rings::SsrOne>() {
|
||||
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())
|
||||
|
|
|
@ -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<u32, Vec<u8>>| x.ptr))]
|
||||
pub data: FilePtr<u32, Vec<u8>>,
|
||||
#[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<Item=(PathBuf, Vec<u8>)>) -> Self where Self: Sized {
|
||||
fn pack(contents: impl IntoIterator<Item=(PathBuf, Vec<u8>)>) -> 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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue