From 8aaf2da6267759ba8ba25cd89262589208e80788 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 20 May 2025 22:43:45 +0800 Subject: [PATCH] Add import support --- .gitignore | 1 + src/args.rs | 20 ++++ src/main.rs | 174 ++++++++++++++++++++++++++--------- src/scripts/base.rs | 11 +++ src/scripts/circus/script.rs | 111 +++++++++++++++++++++- src/types.rs | 1 - src/utils/encoding.rs | 4 +- src/utils/encoding_win.rs | 4 +- src/utils/files.rs | 2 +- 9 files changed, 278 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index a92ca28..e280971 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /testscripts /output +/patched diff --git a/src/args.rs b/src/args.rs index f978890..ccf930b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -52,6 +52,24 @@ pub struct Arg { pub command: Command, } +#[derive(Parser, Debug)] +#[clap(group = ArgGroup::new("patched_encodingg").multiple(false))] +pub struct ImportArgs { + /// Input script file or directory + pub input: String, + /// Text file or directory + pub output: String, + /// Patched script file or directory + pub patched: String, + #[arg(short = 'p', long, group = "patched_encodingg")] + /// Patched script encoding + pub patched_encoding: Option, + #[cfg(windows)] + #[arg(short = 'P', long, group = "patched_encodingg")] + /// Patched script code page + pub patched_code_page: Option, +} + #[derive(Subcommand, Debug)] /// Commands pub enum Command { @@ -62,6 +80,8 @@ pub enum Command { /// Output file or directory output: Option, }, + /// Import to script + Import(ImportArgs), } pub fn parse_args() -> Arg { diff --git a/src/main.rs b/src/main.rs index 076b30f..ec2651a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,25 +3,18 @@ pub mod scripts; pub mod types; pub mod utils; -fn get_encoding(arg: &args::Arg, builder: &Box) -> types::Encoding { +fn get_encoding( + arg: &args::Arg, + builder: &Box, +) -> types::Encoding { match &arg.encoding { Some(enc) => { return match enc { - &types::TextEncoding::Default => { - builder.default_encoding() - } - &types::TextEncoding::Auto => { - types::Encoding::Auto - } - &types::TextEncoding::Cp932 => { - types::Encoding::Cp932 - } - &types::TextEncoding::Utf8 => { - types::Encoding::Utf8 - } - &types::TextEncoding::Gb2312 => { - types::Encoding::Gb2312 - } + &types::TextEncoding::Default => builder.default_encoding(), + &types::TextEncoding::Auto => types::Encoding::Auto, + &types::TextEncoding::Cp932 => types::Encoding::Cp932, + &types::TextEncoding::Utf8 => types::Encoding::Utf8, + &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} @@ -40,27 +33,17 @@ fn get_output_encoding(arg: &args::Arg) -> types::Encoding { match &arg.output_encoding { Some(enc) => { return match enc { - &types::TextEncoding::Default => { - types::Encoding::Utf8 - } - &types::TextEncoding::Auto => { - types::Encoding::Utf8 - } - &types::TextEncoding::Cp932 => { - types::Encoding::Cp932 - } - &types::TextEncoding::Utf8 => { - types::Encoding::Utf8 - } - &types::TextEncoding::Gb2312 => { - types::Encoding::Gb2312 - } + &types::TextEncoding::Default => types::Encoding::Utf8, + &types::TextEncoding::Auto => types::Encoding::Utf8, + &types::TextEncoding::Cp932 => types::Encoding::Cp932, + &types::TextEncoding::Utf8 => types::Encoding::Utf8, + &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] - match &arg.code_page { + match &arg.output_code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } @@ -69,13 +52,43 @@ fn get_output_encoding(arg: &args::Arg) -> types::Encoding { types::Encoding::Utf8 } -pub fn parse_script(filename: &str, arg: &args::Arg, config: &types::ExtraConfig) -> anyhow::Result> { +fn get_patched_encoding( + arg: &args::ImportArgs, + builder: &Box, +) -> types::Encoding { + match &arg.patched_encoding { + Some(enc) => { + return match enc { + &types::TextEncoding::Default => builder.default_patched_encoding(), + &types::TextEncoding::Auto => types::Encoding::Utf8, + &types::TextEncoding::Cp932 => types::Encoding::Cp932, + &types::TextEncoding::Utf8 => types::Encoding::Utf8, + &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, + }; + } + None => {} + } + #[cfg(windows)] + match &arg.patched_code_page { + Some(code_page) => { + return types::Encoding::CodePage(*code_page); + } + None => {} + } + builder.default_patched_encoding() +} + +pub fn parse_script( + filename: &str, + arg: &args::Arg, + config: &types::ExtraConfig, +) -> anyhow::Result<(Box, &'static Box)> { match &arg.script_type { Some(typ) => { for builder in scripts::BUILDER.iter() { if typ == builder.script_type() { let encoding = get_encoding(arg, builder); - return Ok(builder.build_script(filename, encoding, config)?); + return Ok((builder.build_script(filename, encoding, config)?, builder)); } } } @@ -86,7 +99,7 @@ pub fn parse_script(filename: &str, arg: &args::Arg, config: &types::ExtraConfig for ext in exts { if filename.to_lowercase().ends_with(ext) { let encoding = get_encoding(arg, builder); - return Ok(builder.build_script(filename, encoding, config)?); + return Ok((builder.build_script(filename, encoding, config)?, builder)); } } } @@ -101,7 +114,7 @@ pub fn export_script( is_dir: bool, ) -> anyhow::Result<()> { eprintln!("Exporting {}", filename); - let script = parse_script(filename, arg, config)?; + let script = parse_script(filename, arg, config)?.0; // println!("{:?}", script); let mes = script.extract_messages()?; // for m in mes.iter() { @@ -152,6 +165,61 @@ pub fn export_script( Ok(()) } +pub fn import_script( + filename: &str, + arg: &args::Arg, + config: &types::ExtraConfig, + imp_cfg: &args::ImportArgs, + is_dir: bool, +) -> anyhow::Result<()> { + eprintln!("Importing {}", filename); + let (script, builder) = parse_script(filename, arg, config)?; + let of = match &arg.output_type { + Some(t) => t.clone(), + None => script.default_output_script_type(), + }; + let out_f = if is_dir { + let f = std::path::PathBuf::from(filename); + let mut pb = std::path::PathBuf::from(&imp_cfg.output); + if let Some(fname) = f.file_name() { + pb.push(fname); + } + pb.set_extension(of.as_ref()); + pb.to_string_lossy().into_owned() + } else { + imp_cfg.output.clone() + }; + let mes = match of { + types::OutputScriptType::Json => { + let enc = get_output_encoding(arg); + let b = utils::files::read_file(&out_f)?; + let s = utils::encoding::decode_to_string(enc, &b)?; + serde_json::from_str::>(&s)? + } + _ => { + return Err(anyhow::anyhow!("Unsupported script type")); + } + }; + if mes.is_empty() { + eprintln!("No messages found"); + return Ok(()); + } + let encoding = get_patched_encoding(imp_cfg, builder); + let patched_f = if is_dir { + let f = std::path::PathBuf::from(filename); + let mut pb = std::path::PathBuf::from(&imp_cfg.patched); + if let Some(fname) = f.file_name() { + pb.push(fname); + } + pb.set_extension(builder.extensions().first().unwrap_or(&"")); + pb.to_string_lossy().into_owned() + } else { + imp_cfg.patched.clone() + }; + script.import_messages(mes, &patched_f, encoding)?; + Ok(()) +} + fn main() { let arg = args::parse_args(); if arg.backtrace { @@ -177,20 +245,44 @@ fn main() { } } None => { - eprintln!("Output path is not specified"); - return; } } } for script in scripts.iter() { let re = export_script(&script, &arg, &cfg, output, is_dir); match re { - Ok(_) => { - } + Ok(_) => {} Err(e) => { eprintln!("Error exporting {}: {}", script, e); if arg.backtrace { - eprintln!("Backtrace: {:?}", e.backtrace()); + eprintln!("Backtrace: {}", e.backtrace()); + } + } + } + } + } + args::Command::Import(args) => { + let (scripts, is_dir) = + utils::files::collect_files(&args.input, arg.recursive).unwrap(); + if is_dir { + let pb = std::path::Path::new(&args.patched); + if pb.exists() { + if !pb.is_dir() { + eprintln!("Patched path is not a directory"); + return; + } + } else { + std::fs::create_dir_all(pb).unwrap(); + } + } + for script in scripts.iter() { + let re = import_script(&script, &arg, &cfg, args, is_dir); + match re { + Ok(_) => {} + Err(e) => { + eprintln!("Error exporting {}: {}", script, e); + if arg.backtrace { + eprintln!("Backtrace: {}", e.backtrace()); } } } diff --git a/src/scripts/base.rs b/src/scripts/base.rs index d0e21b6..712a199 100644 --- a/src/scripts/base.rs +++ b/src/scripts/base.rs @@ -4,6 +4,10 @@ use anyhow::Result; pub trait ScriptBuilder { fn default_encoding(&self) -> Encoding; + fn default_patched_encoding(&self) -> Encoding { + self.default_encoding() + } + fn build_script( &self, filename: &str, @@ -20,4 +24,11 @@ pub trait Script: std::fmt::Debug { fn default_output_script_type(&self) -> OutputScriptType; fn extract_messages(&self) -> Result>; + + fn import_messages( + &self, + messages: Vec, + filename: &str, + encoding: Encoding, + ) -> Result<()>; } diff --git a/src/scripts/circus/script.rs b/src/scripts/circus/script.rs index c986ed8..64f4619 100644 --- a/src/scripts/circus/script.rs +++ b/src/scripts/circus/script.rs @@ -1,7 +1,7 @@ use super::info::*; use crate::scripts::base::*; use crate::types::*; -use crate::utils::encoding::decode_to_string; +use crate::utils::encoding::{decode_to_string, encode_string}; use anyhow::Result; pub struct CircusMesScriptBuilder {} @@ -188,7 +188,7 @@ impl Script for CircusMesScript { let mut t = None; if self.info.encstr.its(token.value) { let mut text = self.data[self.asm_bin_offset + token.offset + 1 - ..self.asm_bin_offset + token.offset + token.length] + ..self.asm_bin_offset + token.offset + token.length - 1] .to_vec(); for t in text.iter_mut() { *t = (*t).overflowing_add(self.info.deckey).0; @@ -197,7 +197,7 @@ impl Script for CircusMesScript { // println!("Token(enc): {:?}, {}", token, t.as_ref().unwrap()); } else if token.value == self.info.optunenc { let text = &self.data[self.asm_bin_offset + token.offset + 1 - ..self.asm_bin_offset + token.offset + token.length]; + ..self.asm_bin_offset + token.offset + token.length - 1]; t = Some(decode_to_string(self.encoding, text)?); // println!("Token: {:?}, {}", token, t.as_ref().unwrap()); } @@ -215,4 +215,109 @@ impl Script for CircusMesScript { } Ok(mes) } + + fn import_messages( + &self, + messages: Vec, + filename: &str, + encoding: Encoding, + ) -> Result<()> { + let mut buffer = Vec::with_capacity(self.data.len()); + buffer.extend_from_slice(&self.data[..self.asm_bin_offset]); + let mut nmes = Vec::with_capacity(messages.len()); + for m in messages { + nmes.insert(0, m); + } + let mut mes = nmes.pop(); + let mut s = None; + let mut block_count = 0; + for token in self.tokens.iter() { + if !self.is_new_ver { + let count = buffer.len() as u32; + let offset = count - self.asm_bin_offset as u32 + 2; + buffer[self.blocks_offset + block_count * 4 + ..self.blocks_offset + block_count * 4 + 4] + .copy_from_slice(&offset.to_le_bytes()); + block_count += 1; + } + if self.info.encstr.its(token.value) { + if mes.is_none() { + mes = nmes.pop(); + if mes.is_none() { + return Err(anyhow::anyhow!("No more messages to import")); + } + } + if token.value == self.info.nameopcode { + if mes.as_ref().unwrap().name.is_none() { + s = Some(mes.as_ref().unwrap().message.clone()); + mes = None; + } else { + s = mes.as_mut().unwrap().name.take(); + } + } else { + s = Some(mes.as_ref().unwrap().message.clone()); + mes = None; + } + let s = s.take().ok_or(anyhow::anyhow!("No string found"))?; + let mut text = encode_string(encoding, &s)?; + buffer.push(token.value); + for t in text.iter_mut() { + *t = (*t).overflowing_sub(self.info.deckey).0; + } + buffer.extend_from_slice(&text); + buffer.push(0x00); + continue; + } + if token.value == self.info.optunenc { + if mes.is_none() { + mes = nmes.pop(); + if mes.is_none() { + return Err(anyhow::anyhow!("No more messages to import")); + } + } + if token.value == self.info.nameopcode { + if mes.as_ref().unwrap().name.is_none() { + s = Some(mes.as_ref().unwrap().message.clone()); + mes = None; + } else { + s = mes.as_mut().unwrap().name.take(); + } + } else { + s = Some(mes.as_ref().unwrap().message.clone()); + mes = None; + } + buffer.push(token.value); + let s = s.take().ok_or(anyhow::anyhow!("No string found"))?; + let text = encode_string(encoding, &s)?; + buffer.extend_from_slice(&text); + buffer.push(0x00); + continue; + } + if self.is_new_ver && (token.value == 0x3 || token.value == 0x4) { + let count = buffer.len() as u32; + let offset = count - self.asm_bin_offset as u32 + token.length as u32; + let block = u32::from_le_bytes( + buffer[self.blocks_offset + block_count * 4 + ..self.blocks_offset + block_count * 4 + 4] + .try_into()?, + ); + let block = (block & (0xFF << 0x18)) | offset; + buffer[self.blocks_offset + block_count * 4 + ..self.blocks_offset + block_count * 4 + 4] + .copy_from_slice(&block.to_le_bytes()); + block_count += 1; + } + let len = std::cmp::min( + self.asm_bin_offset + token.offset + token.length, + self.data.len(), + ); + buffer.extend_from_slice( + &self.data[self.asm_bin_offset + token.offset + ..len] + ); + } + let mut f = crate::utils::files::write_file(filename)?; + f.write_all(&buffer)?; + Ok(()) + } } diff --git a/src/types.rs b/src/types.rs index 66b664f..7c1d285 100644 --- a/src/types.rs +++ b/src/types.rs @@ -184,4 +184,3 @@ impl Message { Message { message, name } } } - diff --git a/src/utils/encoding.rs b/src/utils/encoding.rs index 1470aa9..1a19758 100644 --- a/src/utils/encoding.rs +++ b/src/utils/encoding.rs @@ -50,9 +50,7 @@ pub fn encode_string(encoding: Encoding, data: &str) -> Result, anyhow:: } } #[cfg(windows)] - Encoding::CodePage(code_page) => { - Ok(super::encoding_win::encode_string(code_page, data)?) - } + Encoding::CodePage(code_page) => Ok(super::encoding_win::encode_string(code_page, data)?), } } diff --git a/src/utils/encoding_win.rs b/src/utils/encoding_win.rs index eace9d8..094f076 100644 --- a/src/utils/encoding_win.rs +++ b/src/utils/encoding_win.rs @@ -1,5 +1,7 @@ use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Globalization::{MB_ERR_INVALID_CHARS, MultiByteToWideChar, WideCharToMultiByte}; +use windows_sys::Win32::Globalization::{ + MB_ERR_INVALID_CHARS, MultiByteToWideChar, WideCharToMultiByte, +}; #[derive(Debug)] pub struct WinError { diff --git a/src/utils/files.rs b/src/utils/files.rs index 4e766be..311cfed 100644 --- a/src/utils/files.rs +++ b/src/utils/files.rs @@ -1,7 +1,7 @@ use std::fs; use std::io; -use std::path::Path; use std::io::{Read, Write}; +use std::path::Path; use crate::scripts::ALL_EXTS;