226 lines
6.2 KiB
Rust
226 lines
6.2 KiB
Rust
#[macro_use]
|
|
extern crate argh;
|
|
extern crate binread;
|
|
extern crate one_rust;
|
|
|
|
use one_rust::prelude::*;
|
|
use std::error::Error;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::path::PathBuf;
|
|
use std::str::FromStr;
|
|
|
|
/// Unpack .one archives from NiGHTS: Journey of Dreams
|
|
#[derive(FromArgs)]
|
|
struct Args {
|
|
#[argh(subcommand)]
|
|
action: Action,
|
|
}
|
|
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand)]
|
|
enum Action {
|
|
DescribeFile(DescribeFile),
|
|
ExtractFile(ExtractFile),
|
|
CreateFile(CreateFile),
|
|
CreateJodFile(CreateJodFile),
|
|
CreateSsrFile(CreateSsrFile),
|
|
CreateShadFile(CreateShadFile),
|
|
CreateHerFile(CreateHerFile),
|
|
}
|
|
|
|
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.
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "xf")]
|
|
struct ExtractFile {
|
|
/// path to the .one file
|
|
#[argh(positional)]
|
|
file: PathBuf,
|
|
}
|
|
|
|
/// Describe the metadata of a .one archive.
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "tf")]
|
|
struct DescribeFile {
|
|
/// path to the .one file
|
|
#[argh(positional)]
|
|
file: PathBuf,
|
|
}
|
|
|
|
/// Create a .one archive from the given files.
|
|
#[derive(FromArgs)]
|
|
#[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,
|
|
/// paths of the files to pack into the archive
|
|
#[argh(positional)]
|
|
contents: Vec<PathBuf>,
|
|
}
|
|
|
|
/// 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>,
|
|
}
|
|
|
|
/// Create a _S_hadow the Hedgehog .one archive.
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "cSf")]
|
|
struct CreateShadFile {
|
|
/// 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>,
|
|
}
|
|
|
|
/// Create a Sonic _H_eroes .one archive.
|
|
#[derive(FromArgs)]
|
|
#[argh(subcommand, name = "cHf")]
|
|
struct CreateHerFile {
|
|
/// 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 => Box::new(ShadOne::pack(contents)),
|
|
GameType::Heroes => Box::new(HerOne::pack(contents)),
|
|
};
|
|
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 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 {
|
|
let full_path = dir.join(name);
|
|
if let Some(path) = full_path.parent() {
|
|
std::fs::create_dir_all(path)?;
|
|
}
|
|
File::create(full_path)?.write_all(&data)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Action::DescribeFile(subcmd) => {
|
|
let infile = File::open(&subcmd.file)?;
|
|
let archive = read_one_archive(infile)?;
|
|
println!("{:?}", archive);
|
|
for entry in archive.entries() {
|
|
println!("{:?}", entry);
|
|
}
|
|
Ok(())
|
|
}
|
|
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)
|
|
}
|
|
Action::CreateShadFile(subcmd) => {
|
|
let subcmd = CreateFile {
|
|
game: GameType::Shadow,
|
|
file: subcmd.file,
|
|
contents: subcmd.contents,
|
|
};
|
|
create_one(subcmd)
|
|
}
|
|
Action::CreateHerFile(subcmd) => {
|
|
let subcmd = CreateFile {
|
|
game: GameType::Heroes,
|
|
file: subcmd.file,
|
|
contents: subcmd.contents,
|
|
};
|
|
create_one(subcmd)
|
|
}
|
|
}
|
|
}
|