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