From 90a9a7f9b1c695c8e8c7a8e19a9690e80dab4b94 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 1 Nov 2025 10:14:42 +0800 Subject: [PATCH] Add pack-v2 command which support multiple input files/folders --- src/args.rs | 14 ++++++ src/main.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/src/args.rs b/src/args.rs index 0a5a5ef..b73cef0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -638,6 +638,20 @@ pub enum Command { /// Output script file output: Option, }, + /// Pack files to archive (version 2) + PackV2 { + #[arg(short = 'o', long)] + /// Output archive file + output: Option, + /// Path to input files/directories + input: Vec, + #[arg(long)] + /// Use \ as path separator instead of / in archive + backslash: bool, + #[arg(long)] + /// Do not create directory entries in archive. This means all files are stored in a flat structure. + no_dir: bool, + }, } pub fn parse_args() -> Arg { diff --git a/src/main.rs b/src/main.rs index 83706b1..802f66c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2380,6 +2380,121 @@ pub fn pack_archive( Ok(()) } +pub fn pack_archive_v2( + input: &[&str], + output: Option<&str>, + arg: &args::Arg, + config: std::sync::Arc, + backslash: bool, + no_dir: bool, +) -> anyhow::Result<()> { + let typ = match &arg.script_type { + Some(t) => t, + None => { + return Err(anyhow::anyhow!("No script type specified")); + } + }; + // File List in real path + let mut files = Vec::new(); + // File list in archive path + let mut re_files = Vec::new(); + for i in input { + let (fs, is_dir) = utils::files::collect_files(i, arg.recursive, true)?; + if is_dir { + files.extend_from_slice(&fs); + for n in fs.iter() { + if no_dir { + if let Some(p) = std::path::PathBuf::from(n).file_name() { + re_files.push(p.to_string_lossy().into_owned()); + } else { + return Err(anyhow::anyhow!("Failed to get filename from {}", n)); + } + } else { + if let Some(p) = { + std::path::PathBuf::from(n) + .strip_prefix(i) + .ok() + .and_then(|p| { + p.to_str().map(|s| { + if backslash { + s.replace("/", "\\").trim_start_matches("\\").to_owned() + } else { + s.replace("\\", "/").trim_start_matches("/").to_owned() + } + }) + }) + } { + re_files.push(p); + } else { + return Err(anyhow::anyhow!("Failed to get relative path from {}", n)); + } + } + } + } else { + files.push(i.to_string()); + let p = std::path::PathBuf::from(i); + if let Some(fname) = p.file_name() { + re_files.push(fname.to_string_lossy().into_owned()); + } else { + return Err(anyhow::anyhow!("Failed to get filename from {}", i)); + } + } + } + let reff = re_files.iter().map(|s| s.as_str()).collect::>(); + let builder = scripts::BUILDER + .iter() + .find(|b| b.script_type() == typ) + .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?; + let output = match output { + Some(output) => output.to_string(), + None => { + let mut pb = std::path::PathBuf::from(input[0]); + let ext = builder.extensions().first().unwrap_or(&"unk"); + pb.set_extension(ext); + if pb.to_string_lossy() == input[0] { + pb.set_extension(format!("{}.{}", ext, ext)); + } + pb.to_string_lossy().into_owned() + } + }; + let mut archive = builder.create_archive( + &output, + &reff, + get_archived_encoding(arg, builder, get_encoding(arg, builder)), + &config, + )?; + for (file, name) in files.iter().zip(reff) { + let mut f = match std::fs::File::open(file) { + Ok(f) => f, + Err(e) => { + eprintln!("Error opening file {}: {}", file, e); + COUNTER.inc_error(); + continue; + } + }; + let mut wf = match archive.new_file_non_seek(name) { + Ok(f) => f, + Err(e) => { + eprintln!("Error creating file {} in archive: {}", name, e); + COUNTER.inc_error(); + continue; + } + }; + match std::io::copy(&mut f, &mut wf) { + Ok(_) => { + COUNTER.inc(types::ScriptResult::Ok); + } + Err(e) => { + eprintln!("Error writing to file {} in archive: {}", name, e); + COUNTER.inc_error(); + continue; + } + } + } + archive.write_header()?; + Ok(()) +} + pub fn unpack_archive( filename: &str, arg: &args::Arg, @@ -3046,6 +3161,30 @@ fn main() { } } } + args::Command::PackV2 { + output, + input, + backslash, + no_dir, + } => { + if !input.is_empty() { + let input = input.iter().map(|s| s.as_str()).collect::>(); + let re = pack_archive_v2( + &input, + output.as_ref().map(|s| s.as_str()), + &arg, + cfg.clone(), + *backslash, + *no_dir, + ); + if let Err(e) = re { + COUNTER.inc_error(); + eprintln!("Error packing archive: {}", e); + } + } else { + eprintln!("No input files specified for packing."); + } + } } eprintln!("{}", std::ops::Deref::deref(&COUNTER)); }