Add import support for catsystem2 script

This commit is contained in:
2025-07-21 23:41:50 +08:00
parent 8d32d2ec4b
commit ec9e947ee1
3 changed files with 166 additions and 6 deletions

View File

@@ -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 = []

View File

@@ -1145,8 +1145,8 @@ impl<'a> CPeek for MemReaderRef<'a> {
}
pub struct MemWriter {
data: Vec<u8>,
pos: usize,
pub data: Vec<u8>,
pub pos: usize,
}
impl MemWriter {

View File

@@ -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<usize>;
}
impl CustomFn for MemWriter {
fn write_patched_string(&mut self, s: &CstString, data: &[u8]) -> Result<usize> {
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<Message>,
mut file: Box<dyn WriteSeek + 'a>,
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(())
}
}