msg_tool\scripts\circus/
script.rs

1//! Circus Script File (.mes)
2use super::info::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::{decode_to_string, encode_string};
7use anyhow::Result;
8use overf::overflowing;
9
10#[derive(Debug)]
11/// Circus MES Script Builder
12pub struct CircusMesScriptBuilder {}
13
14impl CircusMesScriptBuilder {
15    /// Creates a new instance of `CircusMesScriptBuilder`.
16    pub const fn new() -> Self {
17        CircusMesScriptBuilder {}
18    }
19}
20
21impl ScriptBuilder for CircusMesScriptBuilder {
22    fn default_encoding(&self) -> Encoding {
23        Encoding::Cp932
24    }
25
26    fn build_script(
27        &self,
28        buf: Vec<u8>,
29        _filename: &str,
30        encoding: Encoding,
31        _archive_encoding: Encoding,
32        config: &ExtraConfig,
33        _archive: Option<&Box<dyn Script>>,
34    ) -> Result<Box<dyn Script + Send + Sync>> {
35        Ok(Box::new(CircusMesScript::new(buf, encoding, config)?))
36    }
37
38    fn extensions(&self) -> &'static [&'static str] {
39        &["mes"]
40    }
41
42    fn script_type(&self) -> &'static ScriptType {
43        &ScriptType::Circus
44    }
45
46    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
47        try_parse_header(MemReaderRef::new(&buf[..buf_len])).ok()
48    }
49}
50
51fn try_parse_header(mut data: MemReaderRef<'_>) -> Result<u8> {
52    let head0 = data.read_i32()?;
53    let head1 = data.read_i32()?;
54    if head1 == 0x3 {
55        let offset = overflowing!(head0 as u64 * 0x6 + 0x4);
56        let version = data.peek_u16_at(offset)?;
57        if ScriptInfo::query_by_version(version).is_some() {
58            return Ok(10);
59        }
60    } else {
61        let offset = overflowing!(head0 as u64 * 0x4 + 0x4);
62        let version = data.peek_u16_at(offset)?;
63        if ScriptInfo::query_by_version(version).is_some() {
64            return Ok(10);
65        }
66    }
67    Err(anyhow::anyhow!("Not a Circus MES script"))
68}
69
70#[derive(Debug)]
71struct Token {
72    offset: usize,
73    length: usize,
74    value: u8,
75}
76
77/// Circus MES Script
78pub struct CircusMesScript {
79    data: Vec<u8>,
80    encoding: Encoding,
81    is_new_ver: bool,
82    version: u16,
83    info: &'static ScriptInfo,
84    asm_bin_offset: usize,
85    blocks_offset: usize,
86    tokens: Vec<Token>,
87}
88
89impl CircusMesScript {
90    /// Creates a new `CircusMesScript` from the given data and configuration.
91    ///
92    /// * `data` - The data to read the MES script from.
93    /// * `encoding` - The encoding to use for string fields.
94    /// * `config` - Extra configuration options.
95    pub fn new(data: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
96        let head0 = i32::from_le_bytes(data[0..4].try_into()?);
97        let head1 = i32::from_le_bytes(data[4..8].try_into()?);
98        let mut is_new_ver = false;
99        let mut version = 0;
100        let mut info = config
101            .circus_mes_type
102            .as_ref()
103            .and_then(|name| ScriptInfo::query(name.as_ref()));
104        let mut asm_bin_offset = 0;
105        let mut blocks_offset = 0;
106        if head1 == 0x3 {
107            let offset = head0 * 0x6 + 0x4;
108            if data.len() > offset as usize {
109                if data.len() > offset as usize + 3 {
110                    version =
111                        u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
112                    if info.is_none() {
113                        info = ScriptInfo::query_by_version(version);
114                    }
115                    asm_bin_offset = offset as usize + 3;
116                }
117                blocks_offset = 8;
118            }
119            is_new_ver = true;
120        } else {
121            let offset = head0 * 0x4 + 0x4;
122            if data.len() > offset as usize {
123                if data.len() > offset as usize + 2 {
124                    version =
125                        u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
126                    if info.is_none() {
127                        info = ScriptInfo::query_by_version(version);
128                    }
129                    asm_bin_offset = offset as usize + 2;
130                }
131                blocks_offset = 4;
132            }
133        }
134        let info = info.ok_or(anyhow::anyhow!("Failed to detect version."))?;
135        let mut tokens = Vec::new();
136        let mut offset = 0;
137        let asm_bin_size = if asm_bin_offset == 0 {
138            0
139        } else {
140            data.len() - asm_bin_offset
141        };
142        while offset < asm_bin_size {
143            let value = data[asm_bin_offset + offset];
144            let length = if info.uint8x2.its(value) {
145                0x03
146            } else if info.uint8str.its(value) {
147                let mut len = 0x3;
148                let mut temp = data[asm_bin_offset + offset + len - 1];
149                while temp != 0x00 {
150                    len += 0x1;
151                    if asm_bin_offset + offset + len >= data.len() {
152                        break;
153                    }
154                    temp = data[asm_bin_offset + offset + len - 1];
155                }
156                len
157            } else if info.string.its(value) || info.encstr.its(value) {
158                let mut len = 1;
159                let mut temp = data[asm_bin_offset + offset + len - 1];
160                while temp != 0x00 {
161                    len += 0x1;
162                    if asm_bin_offset + offset + len >= data.len() {
163                        break;
164                    }
165                    temp = data[asm_bin_offset + offset + len - 1];
166                }
167                len
168            } else if info.uint16x4.its(value) {
169                0x09
170            } else {
171                return Err(anyhow::anyhow!(format!(
172                    "Unknown token type: 0x{:02X} at offset {}",
173                    value,
174                    asm_bin_offset + offset
175                )));
176            };
177            let token = Token {
178                offset,
179                length,
180                value,
181            };
182            offset += length;
183            tokens.push(token);
184        }
185        Ok(CircusMesScript {
186            data,
187            encoding,
188            is_new_ver,
189            version,
190            info,
191            asm_bin_offset,
192            blocks_offset,
193            tokens,
194        })
195    }
196}
197
198impl std::fmt::Debug for CircusMesScript {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        f.debug_struct("CircusMesScript")
201            .field("encoding", &self.encoding)
202            .field("is_new_ver", &self.is_new_ver)
203            .field("version", &self.version)
204            .field("info", &self.info)
205            .field("asm_bin_offset", &self.asm_bin_offset)
206            .field("blocks_offset", &self.blocks_offset)
207            .field("tokens", &self.tokens)
208            .finish_non_exhaustive()
209    }
210}
211
212impl Script for CircusMesScript {
213    fn default_output_script_type(&self) -> OutputScriptType {
214        OutputScriptType::Json
215    }
216
217    fn default_format_type(&self) -> FormatOptions {
218        FormatOptions::Fixed {
219            length: 32,
220            keep_original: false,
221            break_words: false,
222            insert_fullwidth_space_at_line_start: true,
223            break_with_sentence: true,
224            #[cfg(feature = "jieba")]
225            break_chinese_words: true,
226            #[cfg(feature = "jieba")]
227            jieba_dict: None,
228            no_remove_space_at_line_start: false,
229        }
230    }
231
232    fn extract_messages(&self) -> Result<Vec<Message>> {
233        let mut mes = vec![];
234        let mut name = None;
235        for token in self.tokens.iter() {
236            let mut t = None;
237            if self.info.encstr.its(token.value) {
238                let mut text = self.data[self.asm_bin_offset + token.offset + 1
239                    ..self.asm_bin_offset + token.offset + token.length - 1]
240                    .to_vec();
241                for t in text.iter_mut() {
242                    *t = (*t).overflowing_add(self.info.deckey).0;
243                }
244                t = Some(decode_to_string(self.encoding, &text, true)?);
245                // println!("Token(enc): {:?}, {}", token, t.as_ref().unwrap());
246            } else if token.value == self.info.optunenc {
247                let text = &self.data[self.asm_bin_offset + token.offset + 1
248                    ..self.asm_bin_offset + token.offset + token.length - 1];
249                t = Some(decode_to_string(self.encoding, text, true)?);
250                // println!("Token: {:?}, {}", token, t.as_ref().unwrap());
251            }
252            match t {
253                Some(t) => {
254                    if token.value == self.info.nameopcode {
255                        name = Some(t);
256                    } else {
257                        let message = Message::new(t, name.take());
258                        mes.push(message);
259                    }
260                }
261                None => {}
262            }
263        }
264        Ok(mes)
265    }
266
267    fn import_messages<'a>(
268        &'a self,
269        messages: Vec<Message>,
270        writer: Box<dyn WriteSeek + 'a>,
271        _filename: &str,
272        encoding: Encoding,
273        replacement: Option<&'a ReplacementTable>,
274    ) -> Result<()> {
275        let mut repls = Vec::new();
276        if !encoding.is_jis() {
277            fn insert_repl(
278                repls: &mut Vec<(String, String)>,
279                s: &'static str,
280                encoding: Encoding,
281            ) -> Result<()> {
282                let jis = encode_string(Encoding::Cp932, s, true)?;
283                let out = decode_to_string(encoding, &jis, true)?;
284                repls.push((s.to_string(), out));
285                Ok(())
286            }
287            let _ = insert_repl(&mut repls, "{", encoding);
288            let _ = insert_repl(&mut repls, "/", encoding);
289            let _ = insert_repl(&mut repls, "}", encoding);
290            if repls.len() < 3 {
291                eprintln!(
292                    "Warning: Some replacements cannot used in current encoding. Ruby text may be broken."
293                );
294                crate::COUNTER.inc_warning();
295            }
296        }
297        if let Some(repl) = replacement {
298            for (k, v) in repl.map.iter() {
299                repls.push((k.to_string(), v.to_string()));
300            }
301        }
302
303        let source = MemReaderRef::new(&self.data);
304        let mut patcher = BinaryPatcher::new(source, writer, |pos| Ok(pos), |pos| Ok(pos))?;
305
306        let mut pending_messages: Vec<Message> = messages.into_iter().rev().collect();
307        let mut current_message = pending_messages.pop();
308        let mut block_updates: Vec<(u64, u32)> = Vec::new();
309        let mut block_index = 0usize;
310
311        for token in &self.tokens {
312            let token_start = (self.asm_bin_offset + token.offset) as u64;
313            patcher.copy_up_to(token_start)?;
314
315            if !self.is_new_ver {
316                let block_offset = (self.blocks_offset + block_index * 4) as u64;
317                let new_offset = patcher.map_offset(token_start)?;
318                let offset_value = (new_offset - self.asm_bin_offset as u64 + 2) as u32;
319                block_updates.push((block_offset, offset_value));
320                block_index += 1;
321            }
322
323            if self.info.is_message_opcode(token.value) {
324                if current_message.is_none() {
325                    current_message = pending_messages.pop();
326                    if current_message.is_none() {
327                        return Err(anyhow::anyhow!("No more messages to import"));
328                    }
329                }
330
331                let mut text = {
332                    let message = current_message.as_mut().unwrap();
333                    if self.info.is_name_opcode(token.value) {
334                        match message.name.take() {
335                            Some(name) => name,
336                            None => {
337                                let msg = message.message.clone();
338                                current_message = None;
339                                msg
340                            }
341                        }
342                    } else {
343                        let msg = message.message.clone();
344                        current_message = None;
345                        msg
346                    }
347                };
348
349                for (from, to) in &repls {
350                    text = text.replace(from, to);
351                }
352
353                let mut token_bytes = Vec::with_capacity(text.len() + 2);
354                token_bytes.push(token.value);
355                let mut encoded = encode_string(encoding, &text, false)?;
356                if self.info.is_encrypted_message(token.value) {
357                    if encoded.contains(&self.info.deckey) {
358                        eprintln!(
359                            "Warning: text contains deckey 0x{:02X}, text may be truncated: {}",
360                            self.info.deckey, text,
361                        );
362                        crate::COUNTER.inc_warning();
363                    }
364                    for b in &mut encoded {
365                        *b = (*b).overflowing_sub(self.info.deckey).0;
366                    }
367                }
368                token_bytes.extend_from_slice(&encoded);
369                token_bytes.push(0x00);
370                patcher.replace_bytes(token.length as u64, &token_bytes)?;
371                continue;
372            }
373
374            if self.is_new_ver && (token.value == 0x03 || token.value == 0x04) {
375                let block_offset = (self.blocks_offset + block_index * 4) as u64;
376                let original_block = patcher.input.cpeek_u32_at(block_offset)?;
377                let new_offset = patcher.map_offset(token_start)?;
378                let offset = (new_offset - self.asm_bin_offset as u64 + token.length as u64) as u32;
379                let value = (original_block & (0xFF << 0x18)) | offset;
380                block_updates.push((block_offset, value));
381                block_index += 1;
382            }
383
384            let token_end = token_start + token.length as u64;
385            patcher.copy_up_to(token_end)?;
386        }
387
388        patcher.copy_up_to(self.data.len() as u64)?;
389
390        for (offset, value) in block_updates {
391            patcher.patch_u32(offset, value)?;
392        }
393
394        Ok(())
395    }
396}