diff --git a/luca_pak/src/lib.rs b/luca_pak/src/lib.rs index b2b076f..7d789f6 100644 --- a/luca_pak/src/lib.rs +++ b/luca_pak/src/lib.rs @@ -257,7 +257,7 @@ impl Pak { debug!("remainder {}", remainder); debug!("block_offset {} - expected offset {}", block_offset, entry.offset); output.write_all(&entry.data)?; - output.write_all(&vec![0u8; remainder as usize])?; + output.write_all(&vec![0u8; remainder])?; block_offset += block_size as u32; } @@ -331,6 +331,23 @@ impl Pak { Ok(()) } + pub fn replace_by_id( + &mut self, + id: u32, + replacement_bytes: &[u8], + ) -> Result<(), PakError> { + let entry = self.get_entry_by_id(id); + let index = if let Some(entry) = entry { + entry.index + } else { + return Err(PakError::IndexError) + }; + + self.replace(index, replacement_bytes)?; + + Ok(()) + } + /// Get the header information from the PAK pub fn header(&self) -> &Header { &self.header @@ -350,7 +367,7 @@ impl Pak { self.entries .iter_mut() .find(|e| - e.name.as_ref().is_some_and(|n| n == &name) + e.name.as_ref().is_some_and(|n| n == name) ) } @@ -364,6 +381,6 @@ impl Pak { self.entries .iter() .any(|e| e.name.as_ref() - .is_some_and(|n| n == &name)) + .is_some_and(|n| n == name)) } } diff --git a/luca_pak/src/main.rs b/luca_pak/src/main.rs index 85e16f9..44a0473 100644 --- a/luca_pak/src/main.rs +++ b/luca_pak/src/main.rs @@ -1,5 +1,5 @@ use std::{fs, path::PathBuf}; -use clap::{error::ErrorKind, Error, Parser, Subcommand}; +use clap::{error::{Error, ErrorKind}, Parser, Subcommand}; use luca_pak::Pak; #[derive(Parser)] @@ -31,10 +31,16 @@ enum Commands { /// The name of the file within the PAK you wish to replace. /// If not provided, the filename will be used. - /// Icompatible with batch mode. + /// Incompatible with batch mode, and ID. #[arg(short, long)] name: Option, + /// The ID of the file within the PAK you wish to replace. + /// If not provided, the filename will be used. + /// Incompatible with batch mode, and name. + #[arg(short, long)] + id: Option, + /// File or folder to use as a replacement #[arg(value_name = "REPLACEMENT")] replacement: PathBuf, @@ -48,44 +54,34 @@ enum Commands { fn main() { let cli = Cli::parse(); - if !cli.input.is_file() { - Error::raw(ErrorKind::ValueValidation, - "The input file/folder provided is not a file\n", - ).exit() - } - - let mut pak = Pak::open(&cli.input).unwrap(); + let mut pak = match Pak::open(&cli.input) { + Ok(pak) => pak, + Err(err) => fmt_error(&format!("Could not open PAK file: {}", err)).exit() + }; match cli.command { Commands::Extract { output } => { - if !output.is_dir() { - Error::raw(ErrorKind::ValueValidation, - "The output given was not a directory\n", - ).exit() + if output.exists() && !output.is_dir() { + fmt_error("The output given was not a directory").exit() + } else if !output.exists() { + fs::create_dir(&output).unwrap(); } for entry in pak.entries() { entry.save(&output).unwrap(); } }, - Commands::Replace { batch, name, replacement, output } => { - if !output.is_file() { - Error::raw(ErrorKind::ValueValidation, - "Replacement output must be a file\n", - ).exit() + Commands::Replace { batch, name, id, replacement, output } => { + if id.is_some() && name.is_some() { + fmt_error("Cannot use ID and name together").exit() } - if batch { - if name.is_some() { - Error::raw(ErrorKind::ValueValidation, - "Cannot use name with batch\n", - ).exit() + if name.is_some() || id.is_some() { + fmt_error("Cannot use name or ID with batch").exit() } if !replacement.is_dir() { - Error::raw(ErrorKind::ValueValidation, - "Batch replacement must be a directory\n", - ).exit() + fmt_error("Batch replacement must be a directory").exit() } for entry in fs::read_dir(replacement).unwrap() { @@ -97,23 +93,21 @@ fn main() { .to_string_lossy() .into(); - dbg!(&search_name); + let parsed_id: Option = search_name.parse().ok(); // Read in the replacement file to a vec let rep_data: Vec = std::fs::read(entry.path()).unwrap(); - if let Err(err) = pak.replace_by_name(search_name, &rep_data) { - Error::raw(ErrorKind::ValueValidation, - format!("Failed to replace file in PAK: {}\n", err), - ).exit() + + // Try replacing by name, if that fails, replace by parsed ID + if pak.replace_by_name(search_name, &rep_data).is_err() { + fmt_error("Could not replace entry in PAK: Could not find name").print().unwrap() + } else if parsed_id.is_some() && pak.replace_by_id(parsed_id.unwrap(), &rep_data).is_err() { + fmt_error("Could not replace entry in PAK: ID is invalid").print().unwrap() } } - - pak.save(&output).unwrap(); } else { if !replacement.is_file() { - Error::raw(ErrorKind::ValueValidation, - "Replacement input must be a file\n", - ).exit() + fmt_error("Replacement input must be a file").exit() } let search_name = if let Some(name) = name { @@ -126,24 +120,36 @@ fn main() { .into() }; + let search_id = if id.is_some() { + id + } else if let Ok(id) = search_name.parse::() { + Some(id) + } else { + None + }; + // Read in the replacement file to a vec let rep_data: Vec = std::fs::read(replacement).unwrap(); - if let Err(err) = pak.replace_by_name(search_name, &rep_data) { - Error::raw(ErrorKind::ValueValidation, - format!("Failed to replace file in PAK: {}\n", err), - ).exit() + if id.is_some() { + if pak.replace_by_id(search_id.unwrap(), &rep_data).is_err() { + fmt_error("Could not replace entry in PAK: ID is invalid").exit() + } + } else if pak.replace_by_name(search_name, &rep_data).is_err() { + fmt_error("Could not replace entry in PAK: Could not find name").exit() } pak.save(&output).unwrap(); } - }, + + pak.save(&output).unwrap(); + } } - - /* - let rep_cz_data: Vec = std::fs::read("en_manual01_Linkto_2_6.cz1").unwrap(); - pak.replace(4, &rep_cz_data).unwrap(); - - let mut output = BufWriter::new(File::create("MANUAL-modified.PAK").unwrap()); - pak.encode(&mut output).unwrap(); - */ +} + +#[inline(always)] +fn fmt_error(message: &str) -> Error { + Error::raw( + ErrorKind::ValueValidation, + format!("{}\n", message), + ) }