mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-10 00:05:07 +08:00
Add import support
This commit is contained in:
20
src/args.rs
20
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<TextEncoding>,
|
||||
#[cfg(windows)]
|
||||
#[arg(short = 'P', long, group = "patched_encodingg")]
|
||||
/// Patched script code page
|
||||
pub patched_code_page: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
/// Commands
|
||||
pub enum Command {
|
||||
@@ -62,6 +80,8 @@ pub enum Command {
|
||||
/// Output file or directory
|
||||
output: Option<String>,
|
||||
},
|
||||
/// Import to script
|
||||
Import(ImportArgs),
|
||||
}
|
||||
|
||||
pub fn parse_args() -> Arg {
|
||||
|
||||
174
src/main.rs
174
src/main.rs
@@ -3,25 +3,18 @@ pub mod scripts;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
fn get_encoding(arg: &args::Arg, builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>) -> types::Encoding {
|
||||
fn get_encoding(
|
||||
arg: &args::Arg,
|
||||
builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
|
||||
) -> 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<Box<dyn scripts::Script>> {
|
||||
fn get_patched_encoding(
|
||||
arg: &args::ImportArgs,
|
||||
builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
|
||||
) -> 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<dyn scripts::Script>, &'static Box<dyn scripts::ScriptBuilder + Send + Sync>)> {
|
||||
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::<Vec<types::Message>>(&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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Vec<Message>>;
|
||||
|
||||
fn import_messages(
|
||||
&self,
|
||||
messages: Vec<Message>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -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<Message>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,4 +184,3 @@ impl Message {
|
||||
Message { message, name }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,7 @@ pub fn encode_string(encoding: Encoding, data: &str) -> Result<Vec<u8>, 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)?),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user