From 6e8d248ed27028caac4955b19b66077b32ffb276 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 4 Jun 2025 12:33:56 +0800 Subject: [PATCH] Add PACK support --- msg_tool_macro/src/lib.rs | 37 +++++++++++++++--- src/args.rs | 6 +++ src/main.rs | 79 +++++++++++++++++++++++++++++++++++++- src/scripts/escude/list.rs | 27 +++++++++++++ src/utils/files.rs | 22 +++++++---- 5 files changed, 156 insertions(+), 15 deletions(-) diff --git a/msg_tool_macro/src/lib.rs b/msg_tool_macro/src/lib.rs index f359c52..c8e86d4 100644 --- a/msg_tool_macro/src/lib.rs +++ b/msg_tool_macro/src/lib.rs @@ -63,8 +63,9 @@ pub fn struct_unpack_impl_for_num(item: TokenStream) -> TokenStream { /// /// * `skip_pack` attribute can be used to skip fields from packing. /// * `fstring = ` attribute can be used to specify a fixed string length for String fields. +/// * `fstring_pad = ` attribute can be used to specify a padding byte for fixed strings. (Default is 0) /// * `fvec = ` attribute can be used to specify a fixed vector length for Vec<_> fields. -#[proc_macro_derive(StructPack, attributes(skip_pack, fstring, fvec))] +#[proc_macro_derive(StructPack, attributes(skip_pack, fstring, fstring_pad, fvec))] pub fn struct_pack_derive(input: TokenStream) -> TokenStream { let a = syn::parse_macro_input!(input as PackStruct); match a { @@ -75,6 +76,7 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { let mut skipped = false; let mut fixed_string: Option = None; let mut fixed_vec: Option = None; + let mut fstring_pad = 0u8; // Default padding byte for attr in &field.attrs { let path = attr.path(); if path.is_ident("skip_pack") { @@ -95,6 +97,14 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { } } } + } else if path.is_ident("fstring_pad") { + if let syn::Meta::NameValue(nv) = &attr.meta { + if let syn::Expr::Lit(lit) = &nv.value { + if let syn::Lit::Int(s) = &lit.lit { + fstring_pad = s.base10_parse().unwrap(); + } + } + } } } if skipped { @@ -115,13 +125,17 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { if let Some(fixed_string) = fixed_string { return quote::quote! { let s = encode_string(encoding, &self.#field_name, true)?; - let slen = s.len(); + let mut slen = s.len(); if slen > #fixed_string { return Err(anyhow::anyhow!("String length was too long for field '{}'", stringify!(#field_name))); } writer.write_all(&s)?; - for _ in slen..#fixed_string { + if slen < #fixed_string { writer.write_all(&[0])?; + slen += 1; + } + for _ in slen..#fixed_string { + writer.write_all(&[#fstring_pad])?; } }; } @@ -176,6 +190,7 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { let mut skipped = false; let mut fixed_string: Option = None; let mut fixed_vec: Option = None; + let mut fstring_pad = 0u8; // Default padding byte for attr in &field.attrs { let path = attr.path(); if path.is_ident("skip_pack") { @@ -196,6 +211,14 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { } } } + } else if path.is_ident("fstring_pad") { + if let syn::Meta::NameValue(nv) = &attr.meta { + if let syn::Expr::Lit(lit) = &nv.value { + if let syn::Lit::Int(s) = &lit.lit { + fstring_pad = s.base10_parse().unwrap(); + } + } + } } } if skipped { @@ -217,13 +240,17 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream { if let Some(fixed_string) = fixed_string { return quote::quote! { let s = encode_string(encoding, &#field_name, true)?; - let slen = s.len(); + let mut slen = s.len(); if slen > #fixed_string { return Err(anyhow::anyhow!("String length was too long for field '{}'", stringify!(#field_name))); } writer.write_all(&s)?; - for _ in slen..#fixed_string { + if slen < #fixed_string { writer.write_all(&[0])?; + slen += 1; + } + for _ in slen..#fixed_string { + writer.write_all(&[#fstring_pad])?; } }; } diff --git a/src/args.rs b/src/args.rs index c803333..b02ee03 100644 --- a/src/args.rs +++ b/src/args.rs @@ -126,6 +126,12 @@ pub enum Command { }, /// Import to script Import(ImportArgs), + Pack { + /// Input directory + input: String, + /// Output archive file + output: String, + }, } pub fn parse_args() -> Arg { diff --git a/src/main.rs b/src/main.rs index 9e8d5c2..0c02fc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -896,6 +896,73 @@ pub fn import_script( Ok(types::ScriptResult::Ok) } +pub fn pack_archive( + input: &str, + output: &str, + arg: &args::Arg, + config: &types::ExtraConfig, +) -> anyhow::Result<()> { + let typ = match &arg.script_type { + Some(t) => t, + None => { + return Err(anyhow::anyhow!("No script type specified")); + } + }; + let (files, isdir) = utils::files::collect_files(input, arg.recursive, true) + .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?; + if !isdir { + return Err(anyhow::anyhow!("Input must be a directory for packing")); + } + let re_files: Vec = files + .iter() + .filter_map(|f| { + std::path::PathBuf::from(f) + .strip_prefix(input) + .ok() + .and_then(|p| { + p.to_str() + .map(|s| s.replace("\\", "/").trim_start_matches("/").to_owned()) + }) + }) + .collect(); + let reff = re_files.iter().map(|s| s.as_str()).collect::>(); + let mut archive = scripts::BUILDER + .iter() + .find(|b| b.script_type() == typ) + .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))? + .create_archive(output, &reff, get_output_encoding(arg), 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(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(()) +} + lazy_static::lazy_static! { static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new(); } @@ -911,7 +978,8 @@ fn main() { }; match &arg.command { args::Command::Export { input, output } => { - let (scripts, is_dir) = utils::files::collect_files(input, arg.recursive).unwrap(); + let (scripts, is_dir) = + utils::files::collect_files(input, arg.recursive, false).unwrap(); if is_dir { match &output { Some(output) => { @@ -962,7 +1030,7 @@ fn main() { None => None, }; let (scripts, is_dir) = - utils::files::collect_files(&args.input, arg.recursive).unwrap(); + utils::files::collect_files(&args.input, arg.recursive, false).unwrap(); if is_dir { let pb = std::path::Path::new(&args.patched); if pb.exists() { @@ -998,6 +1066,13 @@ fn main() { } } } + args::Command::Pack { input, output } => { + let re = pack_archive(input, output, &arg, &cfg); + if let Err(e) = re { + COUNTER.inc_error(); + eprintln!("Error packing archive: {}", e); + } + } } eprintln!("{}", std::ops::Deref::deref(&COUNTER)); } diff --git a/src/scripts/escude/list.rs b/src/scripts/escude/list.rs index c37b669..adfcb70 100644 --- a/src/scripts/escude/list.rs +++ b/src/scripts/escude/list.rs @@ -320,21 +320,25 @@ impl Script for EscudeBinList { #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct ScriptT { #[fstring = 64] + #[fstring_pad = 0x20] /// File name pub file: String, pub source: u32, #[fstring = 64] + #[fstring_pad = 0x20] pub title: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct NameT { #[fstring = 64] + #[fstring_pad = 0x20] /// Name of the character pub text: String, /// Text color pub color: u32, #[fstring = 32] + #[fstring_pad = 0x20] /// Face image file name pub face: String, } @@ -343,6 +347,7 @@ struct NameT { struct VarT { /// Variable name #[fstring = 32] + #[fstring_pad = 0x20] pub name: String, /// Variable value pub value: u16, @@ -356,9 +361,11 @@ struct SceneT { pub script: u32, /// The scene name #[fstring = 64] + #[fstring_pad = 0x20] pub name: String, /// The scene thumbail image file name #[fstring = 32] + #[fstring_pad = 0x20] pub thumbnail: String, /// The scene order in the scene (Extra) pub order: i32, @@ -377,11 +384,14 @@ enum EnumScr { struct BgT { /// Background image name #[fstring = 32] + #[fstring_pad = 0x20] name: String, /// Background image file name #[fstring = 64] + #[fstring_pad = 0x20] file: String, #[fstring = 128] + #[fstring_pad = 0x20] option: String, coverd: u32, color: u32, @@ -395,11 +405,14 @@ struct BgT { struct EvT { /// Event image name #[fstring = 32] + #[fstring_pad = 0x20] name: String, /// Event image file name #[fstring = 64] + #[fstring_pad = 0x20] file: String, #[fstring = 128] + #[fstring_pad = 0x20] option: String, coverd: u32, color: u32, @@ -412,10 +425,13 @@ struct EvT { #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct StT { #[fstring = 32] + #[fstring_pad = 0x20] name: String, #[fstring = 64] + #[fstring_pad = 0x20] file: String, #[fstring = 128] + #[fstring_pad = 0x20] option: String, coverd: u32, color: u32, @@ -429,9 +445,11 @@ struct StT { struct EfxT { /// Effect image name #[fstring = 32] + #[fstring_pad = 0x20] name: String, /// Effect image file name #[fstring = 64] + #[fstring_pad = 0x20] file: String, spot: i32, dx: i32, @@ -471,10 +489,13 @@ enum EnumGfx { #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct BgmT { #[fstring = 64] + #[fstring_pad = 0x20] pub name: String, #[fstring = 64] + #[fstring_pad = 0x20] pub file: String, #[fstring = 64] + #[fstring_pad = 0x20] pub title: String, pub order: i32, } @@ -482,24 +503,30 @@ struct BgmT { #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct AmbT { #[fstring = 64] + #[fstring_pad = 0x20] pub name: String, #[fstring = 64] + #[fstring_pad = 0x20] pub file: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct SeT { #[fstring = 64] + #[fstring_pad = 0x20] pub name: String, #[fstring = 64] + #[fstring_pad = 0x20] pub file: String, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] struct SfxT { #[fstring = 64] + #[fstring_pad = 0x20] pub name: String, #[fstring = 64] + #[fstring_pad = 0x20] pub file: String, } diff --git a/src/utils/files.rs b/src/utils/files.rs index cf69b1d..c6d7545 100644 --- a/src/utils/files.rs +++ b/src/utils/files.rs @@ -5,7 +5,7 @@ use std::path::Path; use crate::scripts::ALL_EXTS; -pub fn find_files(path: &String, recursive: bool) -> io::Result> { +pub fn find_files(path: &str, recursive: bool, no_ext_filter: bool) -> io::Result> { let mut result = Vec::new(); let dir_path = Path::new(&path); @@ -15,16 +15,18 @@ pub fn find_files(path: &String, recursive: bool) -> io::Result> { let path = entry.path(); if path.is_file() - && path.extension().map_or(true, |ext| { - ALL_EXTS.contains(&ext.to_string_lossy().to_lowercase()) - }) + && (no_ext_filter + || path.extension().map_or(true, |ext| { + ALL_EXTS.contains(&ext.to_string_lossy().to_lowercase()) + })) { if let Some(path_str) = path.to_str() { result.push(path_str.to_string()); } } else if recursive && path.is_dir() { if let Some(path_str) = path.to_str() { - let mut sub_files = find_files(&path_str.to_string(), recursive)?; + let mut sub_files = + find_files(&path_str.to_string(), recursive, no_ext_filter)?; result.append(&mut sub_files); } } @@ -34,13 +36,17 @@ pub fn find_files(path: &String, recursive: bool) -> io::Result> { Ok(result) } -pub fn collect_files(path: &String, recursive: bool) -> io::Result<(Vec, bool)> { +pub fn collect_files( + path: &str, + recursive: bool, + no_ext_filter: bool, +) -> io::Result<(Vec, bool)> { let pa = Path::new(path); if pa.is_dir() { - return Ok((find_files(path, recursive)?, true)); + return Ok((find_files(path, recursive, no_ext_filter)?, true)); } if pa.is_file() { - return Ok((vec![path.clone()], false)); + return Ok((vec![path.to_string()], false)); } Err(io::Error::new( io::ErrorKind::NotFound,