diff --git a/Cargo.toml b/Cargo.toml index 9156d0c..3d0be02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ default = ["bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-sys bgi = [] bgi-arc = ["bgi", "rand", "utils-bit-stream"] bgi-img = ["bgi", "image", "utils-bit-stream"] -cat-system = ["flate2", "int-enum"] +cat-system = ["fancy-regex", "flate2", "int-enum"] cat-system-arc = ["cat-system", "blowfish", "utils-crc32"] cat-system-img = ["cat-system", "flate2", "image", "utils-bit-stream"] circus = [] diff --git a/src/ext/io.rs b/src/ext/io.rs index fe96999..322a37a 100644 --- a/src/ext/io.rs +++ b/src/ext/io.rs @@ -1145,8 +1145,8 @@ impl<'a> CPeek for MemReaderRef<'a> { } pub struct MemWriter { - data: Vec, - pos: usize, + pub data: Vec, + pub pos: usize, } impl MemWriter { diff --git a/src/scripts/cat_system/cst.rs b/src/scripts/cat_system/cst.rs index c614d33..f3f664f 100644 --- a/src/scripts/cat_system/cst.rs +++ b/src/scripts/cat_system/cst.rs @@ -3,8 +3,9 @@ use crate::scripts::base::*; use crate::types::*; use crate::utils::encoding::*; use anyhow::Result; +use fancy_regex::Regex; use int_enum::IntEnum; -use std::io::Read; +use std::io::{Read, Write}; #[derive(Debug)] pub struct CstScriptBuilder {} @@ -47,6 +48,31 @@ impl ScriptBuilder for CstScriptBuilder { } } +trait CustomFn { + fn write_patched_string(&mut self, s: &CstString, data: &[u8]) -> Result; +} + +impl CustomFn for MemWriter { + fn write_patched_string(&mut self, s: &CstString, data: &[u8]) -> Result { + if data.len() + 1 > s.len { + let pos = self.data.len(); + self.pos = pos; + self.write_u8(1)?; // Start marker + self.write_u8(u8::from(s.typ))?; + self.write_all(data)?; + self.write_u8(0)?; // Null terminator + Ok(pos) + } else { + self.pos = s.address; + self.write_u8(1)?; // Start marker + self.write_u8(u8::from(s.typ))?; + self.write_all(data)?; + self.write_u8(0)?; // Null terminator + Ok(s.address) + } + } +} + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntEnum)] enum CstStringType { @@ -146,6 +172,10 @@ impl CstScript { } } +lazy_static::lazy_static! { + static ref CST_COMMAND_REGEX: Regex = Regex::new(r"^\d+\s+\w+\s+(.+)").unwrap(); +} + impl Script for CstScript { fn default_output_script_type(&self) -> OutputScriptType { OutputScriptType::Json @@ -165,17 +195,147 @@ impl Script for CstScript { continue; // Skip empty messages } messages.push(Message { - message: s.text.to_string(), + message: s.text.replace("\\n", "\n"), name: name.take(), }); } CstStringType::Character => { name = Some(s.text.clone()); } - // #TODO: Command + CstStringType::Command => { + if let Some(caps) = CST_COMMAND_REGEX.captures(&s.text)? { + if let Some(text) = caps.get(1) { + messages.push(Message { + message: text.as_str().to_string(), + name: None, + }); + } + } + } _ => {} } } Ok(messages) } + + fn import_messages<'a>( + &'a self, + messages: Vec, + mut file: Box, + encoding: Encoding, + replacement: Option<&'a ReplacementTable>, + ) -> Result<()> { + let mut writer = MemWriter::from_vec(self.data.data.clone()); + let mut mess = messages.iter(); + let mut mes = mess.next(); + let strings_address_offset = 0x10 + self.data.cpeek_u32_at(0x8)? as usize; + let strings_offset = 0x10 + self.data.cpeek_u32_at(0xC)? as usize; + for (i, s) in self.strings.iter().enumerate() { + match s.typ { + CstStringType::Message => { + if s.text.is_empty() { + continue; // Skip empty messages + } + let m = match mes { + Some(m) => m, + None => { + return Err(anyhow::anyhow!("No enough messages.")); + } + }; + let mut message = m.message.clone(); + if let Some(replacement) = replacement { + for (k, v) in &replacement.map { + message = message.replace(k, v); + } + } + message = message.replace("\n", "\\n"); + let data = encode_string(encoding, &message, true)?; + let pos = writer.write_patched_string(s, &data)?; + if pos != s.address { + writer.write_u32_at( + strings_address_offset + i * 4, + (pos - strings_offset) as u32, + )?; + } + mes = mess.next(); + } + CstStringType::Character => { + let m = match mes { + Some(m) => m, + None => { + return Err(anyhow::anyhow!("No enough messages.")); + } + }; + let mut name = match &m.name { + Some(name) => name.to_owned(), + None => return Err(anyhow::anyhow!("Message without name.")), + }; + if let Some(replacement) = replacement { + for (k, v) in &replacement.map { + name = name.replace(k, v); + } + } + let data = encode_string(encoding, &name, true)?; + let pos = writer.write_patched_string(s, &data)?; + if pos != s.address { + writer.write_u32_at( + strings_address_offset + i * 4, + (pos - strings_offset) as u32, + )?; + } + } + CstStringType::Command => { + if let Some(caps) = CST_COMMAND_REGEX.captures(&s.text)? { + if let Some(mat) = caps.get(1) { + let m = match mes { + Some(m) => m, + None => { + return Err(anyhow::anyhow!("No enough messages.")); + } + }; + let mut text = m.message.clone(); + if let Some(replacement) = replacement { + for (k, v) in &replacement.map { + text = text.replace(k, v); + } + } + let mut command_text = s.text.clone(); + command_text.replace_range(mat.range(), &text); + let data = encode_string(encoding, &command_text, true)?; + let pos = writer.write_patched_string(s, &data)?; + if pos != s.address { + writer.write_u32_at( + strings_address_offset + i * 4, + (pos - strings_offset) as u32, + )?; + } + mes = mess.next(); + } + } + } + _ => {} + } + } + if mes.is_some() || mess.next().is_some() { + return Err(anyhow::anyhow!("Not all messages were processed.")); + } + let data_len = writer.data.len() as u32 - 0x10; + writer.write_u32_at(0, data_len)?; + let data = writer.into_inner(); + file.write_all(b"CatScene")?; + file.write_u32(0)?; // Compressed size + file.write_u32(data.len() as u32)?; // Uncompressed size + if self.compressed { + let mut encoder = + flate2::write::ZlibEncoder::new(&mut file, flate2::Compression::default()); + encoder.write_all(&data)?; + encoder.finish()?; + let file_len = file.stream_position()?; + let compressed_size = (file_len as u32) - 0x10; + file.write_u32_at(8, compressed_size)?; + } else { + file.write_all(&data)?; + } + Ok(()) + } }