mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 12:58:45 +08:00
Add support to import escude archive
This commit is contained in:
107
Cargo.lock
generated
107
Cargo.lock
generated
@@ -58,6 +58,12 @@ version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -140,6 +146,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -164,6 +182,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@@ -180,6 +204,7 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"lazy_static",
|
||||
"msg_tool_macro",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unicode-segmentation",
|
||||
@@ -200,6 +225,15 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -218,6 +252,41 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
@@ -291,6 +360,15 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -363,3 +441,32 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -10,6 +10,7 @@ csv = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
lazy_static = "1.5.0"
|
||||
msg_tool_macro = { path = "./msg_tool_macro" }
|
||||
rand = { version = "0.9", optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
unicode-segmentation = "1.12"
|
||||
@@ -18,7 +19,7 @@ unicode-segmentation = "1.12"
|
||||
default = ["bgi", "circus", "escude"]
|
||||
bgi = []
|
||||
circus = []
|
||||
escude = []
|
||||
escude = ["rand"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0", features = ["Win32_Globalization", "Win32_System_Diagnostics_Debug"] }
|
||||
|
||||
204
src/main.rs
204
src/main.rs
@@ -299,8 +299,10 @@ pub fn export_script(
|
||||
Some(output) => {
|
||||
let mut pb = std::path::PathBuf::from(output);
|
||||
let filename = std::path::PathBuf::from(filename);
|
||||
if let Some(fname) = filename.file_name() {
|
||||
pb.push(fname);
|
||||
if is_dir {
|
||||
if let Some(fname) = filename.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
}
|
||||
pb.to_string_lossy().into_owned()
|
||||
}
|
||||
@@ -313,7 +315,7 @@ pub fn export_script(
|
||||
if !std::fs::exists(&odir)? {
|
||||
std::fs::create_dir_all(&odir)?;
|
||||
}
|
||||
for f in script.iter_archive()? {
|
||||
for f in script.iter_archive_mut()? {
|
||||
let f = f?;
|
||||
if f.is_script() {
|
||||
let (script_file, _) = parse_script_from_archive(&f, arg, config)?;
|
||||
@@ -547,7 +549,201 @@ pub fn import_script(
|
||||
repl: Option<&types::ReplacementTable>,
|
||||
) -> anyhow::Result<types::ScriptResult> {
|
||||
eprintln!("Importing {}", filename);
|
||||
let (script, builder) = parse_script(filename, arg, config)?;
|
||||
let (mut script, builder) = parse_script(filename, arg, config)?;
|
||||
if script.is_archive() {
|
||||
let odir = {
|
||||
let mut pb = std::path::PathBuf::from(&imp_cfg.output);
|
||||
let filename = std::path::PathBuf::from(filename);
|
||||
if is_dir {
|
||||
if let Some(fname) = filename.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
}
|
||||
pb.to_string_lossy().into_owned()
|
||||
};
|
||||
let files: Vec<_> = script.iter_archive()?.collect();
|
||||
let files = files.into_iter().filter_map(|f| f.ok()).collect::<Vec<_>>();
|
||||
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()
|
||||
};
|
||||
let files: Vec<_> = files.iter().map(|s| s.as_str()).collect();
|
||||
let encoding = get_encoding(arg, builder);
|
||||
let enc = get_archived_encoding(arg, builder, encoding);
|
||||
let mut arch = builder.create_archive(&patched_f, &files, enc)?;
|
||||
for f in script.iter_archive_mut()? {
|
||||
let f = f?;
|
||||
let mut writer = arch.new_file(f.name())?;
|
||||
if f.is_script() {
|
||||
let (script_file, _) = parse_script_from_archive(&f, arg, config)?;
|
||||
let mut of = match &arg.output_type {
|
||||
Some(t) => t.clone(),
|
||||
None => script_file.default_output_script_type(),
|
||||
};
|
||||
if !script_file.is_output_supported(of) {
|
||||
of = script_file.default_output_script_type();
|
||||
}
|
||||
let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
|
||||
let ext = if of.is_custom() {
|
||||
script_file.custom_output_extension()
|
||||
} else {
|
||||
of.as_ref()
|
||||
};
|
||||
out_path.set_extension(ext);
|
||||
let mut mes = match of {
|
||||
types::OutputScriptType::Json => {
|
||||
let enc = get_output_encoding(arg);
|
||||
let b = match utils::files::read_file(&out_path) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading file {}: {}", out_path.display(), e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let s = match utils::encoding::decode_to_string(enc, &b) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error decoding string: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match serde_json::from_str::<Vec<types::Message>>(&s) {
|
||||
Ok(mes) => mes,
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing JSON: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
types::OutputScriptType::M3t => {
|
||||
let enc = get_output_encoding(arg);
|
||||
let b = match utils::files::read_file(&out_path) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading file {}: {}", out_path.display(), e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let s = match utils::encoding::decode_to_string(enc, &b) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error decoding string: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut parser = output_scripts::m3t::M3tParser::new(&s);
|
||||
match parser.parse() {
|
||||
Ok(mes) => mes,
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing M3T: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
types::OutputScriptType::Custom => {
|
||||
Vec::new() // Custom scripts handle their own messages
|
||||
}
|
||||
};
|
||||
if !of.is_custom() && mes.is_empty() {
|
||||
eprintln!("No messages found in {}", f.name());
|
||||
COUNTER.inc(types::ScriptResult::Ignored);
|
||||
continue;
|
||||
}
|
||||
let encoding = get_patched_encoding(imp_cfg, builder);
|
||||
if of.is_custom() {
|
||||
let enc = get_output_encoding(arg);
|
||||
match script_file.custom_import(
|
||||
&out_path.to_string_lossy(),
|
||||
writer,
|
||||
encoding,
|
||||
enc,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Error importing custom script: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
COUNTER.inc(types::ScriptResult::Ok);
|
||||
continue;
|
||||
}
|
||||
let fmt = match imp_cfg.patched_format {
|
||||
Some(fmt) => match fmt {
|
||||
types::FormatType::Fixed => types::FormatOptions::Fixed {
|
||||
length: imp_cfg.patched_fixed_length.unwrap_or(32),
|
||||
keep_original: imp_cfg.patched_keep_original,
|
||||
},
|
||||
types::FormatType::None => types::FormatOptions::None,
|
||||
},
|
||||
None => script_file.default_format_type(),
|
||||
};
|
||||
match name_csv {
|
||||
Some(name_table) => {
|
||||
utils::name_replacement::replace_message(&mut mes, name_table);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
format::fmt_message(&mut mes, fmt, *builder.script_type());
|
||||
if let Err(e) = script_file.import_messages(mes, writer, encoding, repl) {
|
||||
eprintln!("Error importing messages: {}", e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let out_path = std::path::PathBuf::from(&odir).join(f.name());
|
||||
if out_path.is_file() {
|
||||
let f = match std::fs::File::open(&out_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("Error opening file {}: {}", out_path.display(), e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut f = std::io::BufReader::new(f);
|
||||
match std::io::copy(&mut f, &mut writer) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Error writing to file {}: {}", out_path.display(), e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"Warning: File {} does not exist, use file from original archive.",
|
||||
out_path.display()
|
||||
);
|
||||
COUNTER.inc_warning();
|
||||
match writer.write_all(f.data()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Error writing to file {}: {}", out_path.display(), e);
|
||||
COUNTER.inc_error();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
COUNTER.inc(types::ScriptResult::Ok);
|
||||
}
|
||||
arch.write_header()?;
|
||||
return Ok(types::ScriptResult::Ok);
|
||||
}
|
||||
let mut of = match &arg.output_type {
|
||||
Some(t) => t.clone(),
|
||||
None => script.default_output_script_type(),
|
||||
|
||||
@@ -67,6 +67,17 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
||||
fn is_archive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn create_archive(
|
||||
&self,
|
||||
_filename: &str,
|
||||
_files: &[&str],
|
||||
_encoding: Encoding,
|
||||
) -> Result<Box<dyn Archive>> {
|
||||
Err(anyhow::anyhow!(
|
||||
"This script type does not support creating an archive."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArchiveContent {
|
||||
@@ -97,12 +108,12 @@ pub trait Script: std::fmt::Debug {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn import_messages(
|
||||
&self,
|
||||
fn import_messages<'a>(
|
||||
&'a self,
|
||||
_messages: Vec<Message>,
|
||||
_file: Box<dyn WriteSeek>,
|
||||
_file: Box<dyn WriteSeek + 'a>,
|
||||
_encoding: Encoding,
|
||||
_replacement: Option<&ReplacementTable>,
|
||||
_replacement: Option<&'a ReplacementTable>,
|
||||
) -> Result<()> {
|
||||
if !self.is_archive() {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -130,10 +141,10 @@ pub trait Script: std::fmt::Debug {
|
||||
))
|
||||
}
|
||||
|
||||
fn custom_import(
|
||||
&self,
|
||||
_custom_filename: &str,
|
||||
_file: Box<dyn WriteSeek>,
|
||||
fn custom_import<'a>(
|
||||
&'a self,
|
||||
_custom_filename: &'a str,
|
||||
_file: Box<dyn WriteSeek + 'a>,
|
||||
_encoding: Encoding,
|
||||
_output_encoding: Encoding,
|
||||
) -> Result<()> {
|
||||
@@ -158,9 +169,20 @@ pub trait Script: std::fmt::Debug {
|
||||
false
|
||||
}
|
||||
|
||||
fn iter_archive<'a>(
|
||||
fn iter_archive<'a>(&'a mut self) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
|
||||
Err(anyhow::anyhow!(
|
||||
"This script type does not support iterating over archive contents."
|
||||
))
|
||||
}
|
||||
|
||||
fn iter_archive_mut<'a>(
|
||||
&'a mut self,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<Box<dyn ArchiveContent>>> + 'a>> {
|
||||
Ok(Box::new(std::iter::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Archive {
|
||||
fn new_file<'a>(&'a mut self, name: &str) -> Result<Box<dyn WriteSeek + 'a>>;
|
||||
fn write_header(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -141,12 +141,12 @@ impl Script for BGIScript {
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn import_messages(
|
||||
&self,
|
||||
fn import_messages<'a>(
|
||||
&'a self,
|
||||
_messages: Vec<Message>,
|
||||
_filename: Box<dyn WriteSeek>,
|
||||
_filename: Box<dyn WriteSeek + 'a>,
|
||||
_encoding: Encoding,
|
||||
_replacement: Option<&ReplacementTable>,
|
||||
_replacement: Option<&'a ReplacementTable>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -221,12 +221,12 @@ impl Script for CircusMesScript {
|
||||
Ok(mes)
|
||||
}
|
||||
|
||||
fn import_messages(
|
||||
&self,
|
||||
fn import_messages<'a>(
|
||||
&'a self,
|
||||
messages: Vec<Message>,
|
||||
mut writer: Box<dyn WriteSeek>,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
replacement: Option<&ReplacementTable>,
|
||||
replacement: Option<&'a ReplacementTable>,
|
||||
) -> Result<()> {
|
||||
let mut repls = Vec::new();
|
||||
if !encoding.is_jis() {
|
||||
@@ -244,7 +244,7 @@ impl Script for CircusMesScript {
|
||||
let _ = insert_repl(&mut repls, "/", encoding);
|
||||
let _ = insert_repl(&mut repls, "}", encoding);
|
||||
if repls.len() < 3 {
|
||||
println!(
|
||||
eprintln!(
|
||||
"Warning: Some replacements cannot used in current encoding. Ruby text may be broken."
|
||||
);
|
||||
crate::COUNTER.inc_warning();
|
||||
|
||||
@@ -2,9 +2,11 @@ use super::crypto::*;
|
||||
use crate::ext::io::*;
|
||||
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;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinArchiveBuilder {}
|
||||
@@ -97,6 +99,18 @@ impl ScriptBuilder for EscudeBinArchiveBuilder {
|
||||
fn is_archive(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn create_archive(
|
||||
&self,
|
||||
filename: &str,
|
||||
files: &[&str],
|
||||
encoding: Encoding,
|
||||
) -> Result<Box<dyn Archive>> {
|
||||
let f = std::fs::File::create(filename)?;
|
||||
let writer = std::io::BufWriter::new(f);
|
||||
let archive = EscudeBinArchiveWriter::new(writer, files, encoding)?;
|
||||
Ok(Box::new(archive))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -129,7 +143,6 @@ impl ArchiveContent for Entry {
|
||||
pub struct EscudeBinArchive<T: Read + Seek + std::fmt::Debug> {
|
||||
reader: T,
|
||||
file_count: u32,
|
||||
name_tbl_len: u32,
|
||||
entries: Vec<BinEntry>,
|
||||
archive_encoding: Encoding,
|
||||
}
|
||||
@@ -144,7 +157,7 @@ impl<T: Read + Seek + std::fmt::Debug> EscudeBinArchive<T> {
|
||||
reader.seek(SeekFrom::Start(0xC))?;
|
||||
let mut crypto_reader = CryptoReader::new(&mut reader)?;
|
||||
let file_count = crypto_reader.read_u32()?;
|
||||
let name_tbl_len = crypto_reader.read_u32()?;
|
||||
let _name_tbl_len = crypto_reader.read_u32()?;
|
||||
let mut entries = Vec::with_capacity(file_count as usize);
|
||||
for _ in 0..file_count {
|
||||
let name_offset = crypto_reader.read_u32()?;
|
||||
@@ -159,7 +172,6 @@ impl<T: Read + Seek + std::fmt::Debug> EscudeBinArchive<T> {
|
||||
Ok(EscudeBinArchive {
|
||||
reader,
|
||||
file_count,
|
||||
name_tbl_len,
|
||||
entries,
|
||||
archive_encoding,
|
||||
})
|
||||
@@ -179,7 +191,16 @@ impl<T: Read + Seek + std::fmt::Debug> Script for EscudeBinArchive<T> {
|
||||
true
|
||||
}
|
||||
|
||||
fn iter_archive<'a>(
|
||||
fn iter_archive<'a>(&'a mut self) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
|
||||
Ok(Box::new(EscudeBinArchiveIter {
|
||||
entries: self.entries.iter(),
|
||||
reader: &mut self.reader,
|
||||
file_count: self.file_count,
|
||||
archive_encoding: self.archive_encoding,
|
||||
}))
|
||||
}
|
||||
|
||||
fn iter_archive_mut<'a>(
|
||||
&'a mut self,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<Box<dyn ArchiveContent>>> + 'a>> {
|
||||
Ok(Box::new(EscudeBinArchiveIterator {
|
||||
@@ -191,6 +212,36 @@ impl<T: Read + Seek + std::fmt::Debug> Script for EscudeBinArchive<T> {
|
||||
}
|
||||
}
|
||||
|
||||
struct EscudeBinArchiveIter<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> {
|
||||
entries: T,
|
||||
reader: &'a mut R,
|
||||
file_count: u32,
|
||||
archive_encoding: Encoding,
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> Iterator
|
||||
for EscudeBinArchiveIter<'a, T, R>
|
||||
{
|
||||
type Item = Result<String>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let entry = match self.entries.next() {
|
||||
Some(entry) => entry,
|
||||
None => return None,
|
||||
};
|
||||
let name_offset = entry.name_offset as usize + self.file_count as usize * 12 + 0x14;
|
||||
let name = match self.reader.peek_cstring_at(name_offset) {
|
||||
Ok(name) => name,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
let name = match decode_to_string(self.archive_encoding, name.as_bytes()) {
|
||||
Ok(name) => name,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
Some(Ok(name))
|
||||
}
|
||||
}
|
||||
|
||||
struct EscudeBinArchiveIterator<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> {
|
||||
entries: T,
|
||||
reader: &'a mut R,
|
||||
@@ -239,3 +290,131 @@ impl<'a, T: Iterator<Item = &'a BinEntry>, R: Read + Seek> Iterator
|
||||
Some(Ok(Box::new(Entry { name, data })))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EscudeBinArchiveWriter<T: Write + Seek> {
|
||||
writer: T,
|
||||
headers: HashMap<String, BinEntry>,
|
||||
name_tbl_len: u32,
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> EscudeBinArchiveWriter<T> {
|
||||
pub fn new(mut writer: T, files: &[&str], encoding: Encoding) -> Result<Self> {
|
||||
writer.write_all(b"ESC-ARC2")?;
|
||||
let header_len = 0xC + 0xC * files.len();
|
||||
let header = vec![0u8; header_len];
|
||||
writer.write_all(&header)?;
|
||||
let mut headers = HashMap::new();
|
||||
for file in files {
|
||||
let f = file.to_string();
|
||||
let encoded = encode_string(encoding, file, true)?;
|
||||
let encoded = CString::new(encoded)?;
|
||||
let name_offset = writer.stream_position()? as u32;
|
||||
writer.write_all(encoded.as_bytes_with_nul())?;
|
||||
headers.insert(
|
||||
f,
|
||||
BinEntry {
|
||||
name_offset,
|
||||
data_offset: 0,
|
||||
length: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
let name_tbl_len = writer.stream_position()? as u32 - header_len as u32 - 0x8;
|
||||
Ok(EscudeBinArchiveWriter {
|
||||
writer,
|
||||
headers,
|
||||
name_tbl_len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> Archive for EscudeBinArchiveWriter<T> {
|
||||
fn new_file<'a>(&'a mut self, name: &str) -> Result<Box<dyn WriteSeek + 'a>> {
|
||||
let entry = self
|
||||
.headers
|
||||
.get_mut(name)
|
||||
.ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?;
|
||||
if entry.data_offset != 0 {
|
||||
return Err(anyhow::anyhow!("File '{}' already exists in archive", name));
|
||||
}
|
||||
entry.data_offset = self.writer.stream_position()? as u32;
|
||||
Ok(Box::new(EscudeBinArchiveFile {
|
||||
header: entry,
|
||||
writer: &mut self.writer,
|
||||
pos: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
fn write_header(&mut self) -> Result<()> {
|
||||
self.writer.seek(SeekFrom::Start(0x8))?;
|
||||
let mut crypto = CryptoWriter::new(&mut self.writer)?;
|
||||
let file_count = self.headers.len() as u32;
|
||||
crypto.write_u32(file_count)?;
|
||||
crypto.write_u32(self.name_tbl_len)?;
|
||||
for entry in self.headers.values() {
|
||||
let name_offset = entry.name_offset - file_count * 12 - 0x14;
|
||||
crypto.write_u32(name_offset)?;
|
||||
crypto.write_u32(entry.data_offset)?;
|
||||
crypto.write_u32(entry.length)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EscudeBinArchiveFile<'a, T: Write + Seek> {
|
||||
header: &'a mut BinEntry,
|
||||
writer: &'a mut T,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Write for EscudeBinArchiveFile<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.writer.seek(SeekFrom::Start(
|
||||
self.header.data_offset as u64 + self.pos as u64,
|
||||
))?;
|
||||
let written = self.writer.write(buf)?;
|
||||
self.pos += written;
|
||||
self.header.length = self.header.length.max(self.pos as u32);
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Seek for EscudeBinArchiveFile<'a, T> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
let new_pos = match pos {
|
||||
SeekFrom::Start(offset) => offset as usize,
|
||||
SeekFrom::End(offset) => {
|
||||
if offset < 0 {
|
||||
if (-offset) as usize > self.header.length as usize {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"Seek from end exceeds file length",
|
||||
));
|
||||
}
|
||||
self.header.length as usize - (-offset) as usize
|
||||
} else {
|
||||
self.header.length as usize + offset as usize
|
||||
}
|
||||
}
|
||||
SeekFrom::Current(offset) => {
|
||||
if offset < 0 {
|
||||
if (-offset) as usize > self.pos {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"Seek from current exceeds current position",
|
||||
));
|
||||
}
|
||||
self.pos.saturating_sub((-offset) as usize)
|
||||
} else {
|
||||
self.pos + offset as usize
|
||||
}
|
||||
}
|
||||
};
|
||||
self.pos = new_pos;
|
||||
Ok(self.pos as u64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::ext::io::*;
|
||||
use anyhow::Result;
|
||||
use std::io::{Read, Seek};
|
||||
use rand::Rng;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
pub struct CryptoReader<T: Read + Seek> {
|
||||
reader: T,
|
||||
@@ -52,3 +53,45 @@ impl<T: Read + Seek> Read for CryptoReader<T> {
|
||||
Ok(readed)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CryptoWriter<T: Write + Seek> {
|
||||
writer: T,
|
||||
key: u32,
|
||||
in_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> CryptoWriter<T> {
|
||||
pub fn new(mut writer: T) -> Result<Self> {
|
||||
let mut rng = rand::rng();
|
||||
let key = rng.random();
|
||||
writer.write_u32(key)?;
|
||||
Ok(Self {
|
||||
writer,
|
||||
key,
|
||||
in_buffer: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn key(&mut self) -> u32 {
|
||||
self.key ^= 0x65AC9365;
|
||||
self.key ^= (((self.key >> 1) ^ self.key) >> 3) ^ (((self.key << 1) ^ self.key) << 3);
|
||||
return self.key;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> Write for CryptoWriter<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.in_buffer.extend_from_slice(buf);
|
||||
while self.in_buffer.len() >= 4 {
|
||||
let mut val = self.in_buffer.as_slice().read_u32()?;
|
||||
val ^= self.key();
|
||||
self.writer.write_u32(val)?;
|
||||
self.in_buffer.drain(0..4);
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,10 +284,10 @@ impl Script for EscudeBinList {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn custom_import(
|
||||
&self,
|
||||
custom_filename: &str,
|
||||
mut writer: Box<dyn WriteSeek>,
|
||||
fn custom_import<'a>(
|
||||
&'a self,
|
||||
custom_filename: &'a str,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
output_encoding: Encoding,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -116,12 +116,12 @@ impl Script for EscudeBinScript {
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn import_messages(
|
||||
&self,
|
||||
fn import_messages<'a>(
|
||||
&'a self,
|
||||
messages: Vec<Message>,
|
||||
mut writer: Box<dyn WriteSeek>,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
replacement: Option<&ReplacementTable>,
|
||||
replacement: Option<&'a ReplacementTable>,
|
||||
) -> Result<()> {
|
||||
writer.write_all(b"ESCR1_00")?;
|
||||
let mut offsets = Vec::with_capacity(messages.len());
|
||||
|
||||
Reference in New Issue
Block a user