mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 12:58:45 +08:00
Add import support for catsystem2 script
This commit is contained in:
@@ -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 = []
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user