finish adding SSR support

This commit is contained in:
lifning 2021-01-16 23:40:35 -08:00
parent 8472af4c21
commit 2658e076d5
4 changed files with 170 additions and 37 deletions

View File

@ -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(())
}

View File

@ -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() {

View File

@ -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())

View File

@ -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
});
}
}