mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 21:08:48 +08:00
Add PACK support
This commit is contained in:
@@ -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 = <len>` attribute can be used to specify a fixed string length for String fields.
|
||||
/// * `fstring_pad = <u8>` attribute can be used to specify a padding byte for fixed strings. (Default is 0)
|
||||
/// * `fvec = <len>` 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<usize> = None;
|
||||
let mut fixed_vec: Option<usize> = 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<usize> = None;
|
||||
let mut fixed_vec: Option<usize> = 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])?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
79
src/main.rs
79
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<String> = 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::<Vec<_>>();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::Path;
|
||||
|
||||
use crate::scripts::ALL_EXTS;
|
||||
|
||||
pub fn find_files(path: &String, recursive: bool) -> io::Result<Vec<String>> {
|
||||
pub fn find_files(path: &str, recursive: bool, no_ext_filter: bool) -> io::Result<Vec<String>> {
|
||||
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<Vec<String>> {
|
||||
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<Vec<String>> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn collect_files(path: &String, recursive: bool) -> io::Result<(Vec<String>, bool)> {
|
||||
pub fn collect_files(
|
||||
path: &str,
|
||||
recursive: bool,
|
||||
no_ext_filter: bool,
|
||||
) -> io::Result<(Vec<String>, 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,
|
||||
|
||||
Reference in New Issue
Block a user