From 15d438f294f9f70f31ba799ad5bf709449905a4b Mon Sep 17 00:00:00 2001 From: modeco80 Date: Wed, 28 Jun 2023 19:25:18 -0400 Subject: [PATCH] jmrenamer: rewrite to use package.toc The package.toc file contains all the file names, except for itself (but we know it always so this isn't a problem). Therefore, we don't need to store a hardcoded string table! Yay! --- crates/jmmt/src/format/package_toc.rs | 10 +-- crates/jmmt/src/hash/filename.rs | 6 ++ crates/jmmt/src/read/mod.rs | 4 ++ crates/jmmt/src/read/package_toc.rs | 47 ++++++++++++++ crates/jmrenamer/src/main.rs | 91 ++++++++++++--------------- 5 files changed, 101 insertions(+), 57 deletions(-) create mode 100644 crates/jmmt/src/read/package_toc.rs diff --git a/crates/jmmt/src/format/package_toc.rs b/crates/jmmt/src/format/package_toc.rs index cbc238b..61b8321 100644 --- a/crates/jmmt/src/format/package_toc.rs +++ b/crates/jmmt/src/format/package_toc.rs @@ -16,23 +16,23 @@ pub struct PackageTocEntry { } impl PackageTocEntry { - fn file_name(&self) -> Option { + pub fn file_name(&self) -> Option { make_c_string(&self.file_name) } - fn file_name_hash(&self) -> u32 { + pub fn file_name_hash(&self) -> u32 { self.file_name_hash } - fn toc_start_offset(&self) -> u32 { + pub fn toc_start_offset(&self) -> u32 { self.toc_start_offset } - fn toc_size(&self) -> u32 { + pub fn toc_size(&self) -> u32 { self.toc_size } - fn toc_file_count(&self) -> u32 { + pub fn toc_file_count(&self) -> u32 { self.toc_file_count } } diff --git a/crates/jmmt/src/hash/filename.rs b/crates/jmmt/src/hash/filename.rs index 749e6b0..25b57cf 100644 --- a/crates/jmmt/src/hash/filename.rs +++ b/crates/jmmt/src/hash/filename.rs @@ -16,3 +16,9 @@ pub fn dat_filename(filename: &str) -> String { pub fn met_filename(filename: &str) -> String { format!("{:X}.MET", hash_string(String::from(filename))) } + +/// Make a .DAT filename from a pre-created hash. +/// This is notably used to re-use hashes from `package.toc`. +pub fn dat_filename_from_hash(hash: u32) -> String { + format!("{:X}.DAT", hash) +} diff --git a/crates/jmmt/src/read/mod.rs b/crates/jmmt/src/read/mod.rs index 0daf88a..1c71a47 100644 --- a/crates/jmmt/src/read/mod.rs +++ b/crates/jmmt/src/read/mod.rs @@ -1 +1,5 @@ +//! High-level readers + +pub mod package_toc; + pub mod texture; diff --git a/crates/jmmt/src/read/package_toc.rs b/crates/jmmt/src/read/package_toc.rs new file mode 100644 index 0000000..f41acca --- /dev/null +++ b/crates/jmmt/src/read/package_toc.rs @@ -0,0 +1,47 @@ +use std::fs::File; +use std::io::{Seek, SeekFrom}; +use std::path::{PathBuf}; + +use crate::format::{ + package_toc::* +}; + +use binext::BinaryRead; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("underlying I/O error: {0}")] + IoError(#[from] std::io::Error), + + #[error("file too small to hold package toc entry")] + FileTooSmall, + + /// Under-read of data + #[error("underread")] + ReadTooSmall, +} + +pub type Result = std::result::Result; + +pub fn read_package_toc(path: PathBuf) -> Result> { + let mut toc_file = File::open(path.clone())?; + + let file_size = toc_file.seek(SeekFrom::End(0))?; + toc_file.seek(SeekFrom::Start(0))?; + + let vec_size : usize = file_size as usize / std::mem::size_of::(); + + if vec_size == 0 { + return Err(Error::FileTooSmall); + } + + let mut vec : Vec = Vec::with_capacity(vec_size); + + for _ in 0..vec_size { + vec.push(toc_file.read_binary::()?); + } + + drop(toc_file); + Ok(vec) +} diff --git a/crates/jmrenamer/src/main.rs b/crates/jmrenamer/src/main.rs index 5ee58fa..a85866d 100644 --- a/crates/jmrenamer/src/main.rs +++ b/crates/jmrenamer/src/main.rs @@ -1,68 +1,55 @@ -//! A reimplemntation of jmmt_renamer in Rust. +//! A reimplementation of jmmt_renamer in Rust. //! This program should be run in the root directory //! of an extracted (from image) copy of the game. -use std::{fs, io, path::Path}; +use std::{fs, path::Path}; use jmmt::hash::filename::*; +use jmmt::read::package_toc::{read_package_toc}; -const FILENAME_TABLE : [&str; 20] = [ - // First loaded by the game - "package.toc", +// TODO: A mode that will re-name everything back? This wouldn't be too hard to implement - // General packs - "config.pak", - - // This file is referenced in the game files, - // but doesn't seem to exist anymore in the final build. - //"shell.pak", - - "shell_character_select.pak", - "shell_main.pak", - "shell_title.pak", - "shell_venue.pak", - "shell_event.pak", - "shell_option.pak", - "win_screens.pak", - - // Game levels - "SF_san_fran.pak", - "DC_washington.pak", - "MK_MT_KILI.pak", - "MP_MACHU_PIHU.pak", - "LV_Las_Vegas.pak", - "AN_ANTARTICA.pak", - "NP_Nepal.pak", - "TH_TAHOE.pak", - "VA_Valdez_alaska.pak", - "RV_Rome.pak", - "TR_training.pak" -]; - -fn main() -> io::Result<()> { - +fn main() { // A relatively simple idiot-check. Later on utilities might have a shared library // of code which validates game root stuff and can open it up/etc. if !Path::new("DATA").is_dir() { println!("This program should be run in the root of an extracted copy."); - std::process::exit(1); + return (); } - - // Go through the clearname table and rename files in the DATA directory - for clearname in FILENAME_TABLE.iter() { - let dat_filename = dat_filename(clearname); - let dat_src = format!("DATA/{}", dat_filename); - let dat_clearname = format!("DATA/{}", String::from(*clearname)); - let src_path = Path::new(dat_src.as_str()); - let dest_path = Path::new(dat_clearname.as_str()); + let package_toc_filename = format!("DATA/{}", dat_filename("package.toc")); - if src_path.exists() { - fs::rename(src_path, dest_path)?; - println!("moved {} -> {}", src_path.display(), dest_path.display()); - } else if dest_path.exists() { - println!("{} already exists", dest_path.display()); + match read_package_toc(Path::new(package_toc_filename.as_str()).to_path_buf()) { + Ok(toc) => { + for toc_entry in toc { + let dat_src = format!("DATA/{}", dat_filename_from_hash(toc_entry.file_name_hash())); + let src_path = Path::new(dat_src.as_str()); + let dat_clearname = format!("DATA/{}", toc_entry.file_name().expect("How did invalid ASCII get here?")); + let dest_path = Path::new(dat_clearname.as_str()); + + if src_path.exists() { + match fs::rename(src_path, dest_path) { + Ok(_) => {}, + Err(error) => { + println!("Error renaming {}: {}", src_path.display(), error); + return (); + } + }; + println!("moved {} -> {}", src_path.display(), dest_path.display()); + } + } + + match fs::rename(Path::new(package_toc_filename.as_str()), Path::new("DATA/package.toc")) { + Ok(_) => {}, + Err(error) => { + println!("Error renaming TOC file: {}", error); + return (); + } + }; + } + + Err(error) => { + println!("Error reading package.toc file: {}", error); + return (); } } - - Ok(()) }