diff --git a/Cargo.lock b/Cargo.lock index 40753d3..900b851 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml index d2bf1fc..1a786eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/main.rs b/src/main.rs index 858446f..42f7982 100644 --- a/src/main.rs +++ b/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 { 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::>(); + 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::>(&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(), diff --git a/src/scripts/base.rs b/src/scripts/base.rs index 77cb02d..21fbbef 100644 --- a/src/scripts/base.rs +++ b/src/scripts/base.rs @@ -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> { + 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, - _file: Box, + _file: Box, _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, + fn custom_import<'a>( + &'a self, + _custom_filename: &'a str, + _file: Box, _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> + 'a>> { + Err(anyhow::anyhow!( + "This script type does not support iterating over archive contents." + )) + } + + fn iter_archive_mut<'a>( &'a mut self, ) -> Result>> + 'a>> { Ok(Box::new(std::iter::empty())) } } + +pub trait Archive { + fn new_file<'a>(&'a mut self, name: &str) -> Result>; + fn write_header(&mut self) -> Result<()>; +} diff --git a/src/scripts/bgi/script.rs b/src/scripts/bgi/script.rs index 4447786..fba9722 100644 --- a/src/scripts/bgi/script.rs +++ b/src/scripts/bgi/script.rs @@ -141,12 +141,12 @@ impl Script for BGIScript { Ok(messages) } - fn import_messages( - &self, + fn import_messages<'a>( + &'a self, _messages: Vec, - _filename: Box, + _filename: Box, _encoding: Encoding, - _replacement: Option<&ReplacementTable>, + _replacement: Option<&'a ReplacementTable>, ) -> Result<()> { Ok(()) } diff --git a/src/scripts/circus/script.rs b/src/scripts/circus/script.rs index 7feb104..f417b6f 100644 --- a/src/scripts/circus/script.rs +++ b/src/scripts/circus/script.rs @@ -221,12 +221,12 @@ impl Script for CircusMesScript { Ok(mes) } - fn import_messages( - &self, + fn import_messages<'a>( + &'a self, messages: Vec, - mut writer: Box, + mut writer: Box, 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(); diff --git a/src/scripts/escude/archive.rs b/src/scripts/escude/archive.rs index af0a74a..fbc61c1 100644 --- a/src/scripts/escude/archive.rs +++ b/src/scripts/escude/archive.rs @@ -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> { + 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 { reader: T, file_count: u32, - name_tbl_len: u32, entries: Vec, archive_encoding: Encoding, } @@ -144,7 +157,7 @@ impl EscudeBinArchive { 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 EscudeBinArchive { Ok(EscudeBinArchive { reader, file_count, - name_tbl_len, entries, archive_encoding, }) @@ -179,7 +191,16 @@ impl Script for EscudeBinArchive { true } - fn iter_archive<'a>( + fn iter_archive<'a>(&'a mut self) -> Result> + '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>> + 'a>> { Ok(Box::new(EscudeBinArchiveIterator { @@ -191,6 +212,36 @@ impl Script for EscudeBinArchive { } } +struct EscudeBinArchiveIter<'a, T: Iterator, R: Read + Seek> { + entries: T, + reader: &'a mut R, + file_count: u32, + archive_encoding: Encoding, +} + +impl<'a, T: Iterator, R: Read + Seek> Iterator + for EscudeBinArchiveIter<'a, T, R> +{ + type Item = Result; + + fn next(&mut self) -> Option { + 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, R: Read + Seek> { entries: T, reader: &'a mut R, @@ -239,3 +290,131 @@ impl<'a, T: Iterator, R: Read + Seek> Iterator Some(Ok(Box::new(Entry { name, data }))) } } + +pub struct EscudeBinArchiveWriter { + writer: T, + headers: HashMap, + name_tbl_len: u32, +} + +impl EscudeBinArchiveWriter { + pub fn new(mut writer: T, files: &[&str], encoding: Encoding) -> Result { + 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 Archive for EscudeBinArchiveWriter { + fn new_file<'a>(&'a mut self, name: &str) -> Result> { + 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 { + 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 { + 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) + } +} diff --git a/src/scripts/escude/crypto.rs b/src/scripts/escude/crypto.rs index d275467..06b6abd 100644 --- a/src/scripts/escude/crypto.rs +++ b/src/scripts/escude/crypto.rs @@ -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 { reader: T, @@ -52,3 +53,45 @@ impl Read for CryptoReader { Ok(readed) } } + +pub struct CryptoWriter { + writer: T, + key: u32, + in_buffer: Vec, +} + +impl CryptoWriter { + pub fn new(mut writer: T) -> Result { + 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 Write for CryptoWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + 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() + } +} diff --git a/src/scripts/escude/list.rs b/src/scripts/escude/list.rs index 656a18f..c37b669 100644 --- a/src/scripts/escude/list.rs +++ b/src/scripts/escude/list.rs @@ -284,10 +284,10 @@ impl Script for EscudeBinList { Ok(()) } - fn custom_import( - &self, - custom_filename: &str, - mut writer: Box, + fn custom_import<'a>( + &'a self, + custom_filename: &'a str, + mut writer: Box, encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { diff --git a/src/scripts/escude/script.rs b/src/scripts/escude/script.rs index 41e52d9..d01a1c9 100644 --- a/src/scripts/escude/script.rs +++ b/src/scripts/escude/script.rs @@ -116,12 +116,12 @@ impl Script for EscudeBinScript { .collect()) } - fn import_messages( - &self, + fn import_messages<'a>( + &'a self, messages: Vec, - mut writer: Box, + mut writer: Box, encoding: Encoding, - replacement: Option<&ReplacementTable>, + replacement: Option<&'a ReplacementTable>, ) -> Result<()> { writer.write_all(b"ESCR1_00")?; let mut offsets = Vec::with_capacity(messages.len());