1use super::disasm::*;
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use anyhow::Result;
7use std::io::Write;
8use std::sync::Mutex;
9use unicode_segmentation::UnicodeSegmentation;
10
11#[derive(Debug)]
12pub struct MesBuilder {}
14
15impl MesBuilder {
16 pub fn new() -> Self {
18 Self {}
19 }
20}
21
22impl ScriptBuilder for MesBuilder {
23 fn default_encoding(&self) -> Encoding {
24 Encoding::Cp932
25 }
26
27 fn build_script(
28 &self,
29 buf: Vec<u8>,
30 _filename: &str,
31 encoding: Encoding,
32 _archive_encoding: Encoding,
33 config: &ExtraConfig,
34 _archive: Option<&Box<dyn Script>>,
35 ) -> Result<Box<dyn Script + Send + Sync>> {
36 Ok(Box::new(Mes::new(buf, encoding, config)?))
37 }
38
39 fn extensions(&self) -> &'static [&'static str] {
40 &["mes"]
41 }
42
43 fn script_type(&self) -> &'static ScriptType {
44 &ScriptType::Silky
45 }
46}
47
48struct TextParser<'a> {
49 data: Vec<&'a str>,
50 typ: SlikyStringType,
51 opcodes: &'static Opcodes,
52 encoding: Encoding,
53 pos: usize,
54}
55
56impl<'a> TextParser<'a> {
57 fn new(
58 s: &'a str,
59 typ: SlikyStringType,
60 opcodes: &'static Opcodes,
61 encoding: Encoding,
62 ) -> Self {
63 let data = s.graphemes(true).collect();
64 Self {
65 data,
66 typ,
67 opcodes,
68 encoding,
69 pos: 0,
70 }
71 }
72
73 fn parse(mut self) -> Result<Vec<u8>> {
74 match self.typ {
75 SlikyStringType::Internal => Err(anyhow::anyhow!(
76 "Internal strings cannot be parsed from text."
77 )),
78 SlikyStringType::Name => {
79 let mut m = MemWriter::new();
80 m.write_u8(self.opcodes.push_string)?;
81 let s = encode_string(self.encoding, &self.data.join(""), false)?;
82 m.write_all(&s)?;
83 m.write_u8(0)?;
84 Ok(m.into_inner())
85 }
86 SlikyStringType::Message => {
87 let mut m = MemWriter::new();
88 let mut in_ruby = false;
89 let mut in_normal_text = false;
90 while let Some(c) = self.next() {
91 match c {
92 "[" => {
93 if in_ruby {
94 return Err(anyhow::anyhow!("Nested ruby tags are not allowed."));
95 }
96 if in_normal_text {
97 m.write_u8(0)?;
98 in_normal_text = false;
99 }
100 in_ruby = true;
101 m.write_u8(self.opcodes.escape_sequence)?;
102 m.write_u8(1)?; m.write_u8(self.opcodes.message2)?;
104 }
105 "]" => {
106 if !in_ruby {
107 return Err(anyhow::anyhow!("Unmatched closing ruby tag."));
108 }
109 in_ruby = false;
110 m.write_u8(0)?;
111 m.write_u8(self.opcodes.r#yield)?;
112 }
113 "\n" => {
114 if in_ruby {
115 return Err(anyhow::anyhow!("Newline inside ruby is not allowed."));
116 }
117 if in_normal_text {
118 m.write_u8(0)?;
119 in_normal_text = false;
120 }
121 m.write_u8(self.opcodes.escape_sequence)?;
122 m.write_u8(0)?; }
124 _ => {
125 if !in_ruby && !in_normal_text {
126 in_normal_text = true;
127 m.write_u8(self.opcodes.message2)?;
128 }
129 let s = encode_string(self.encoding, c, false)?;
130 m.write_all(&s)?;
131 }
132 }
133 }
134 if in_ruby {
135 m.write_u8(0)?;
136 m.write_u8(self.opcodes.r#yield)?;
137 }
138 if in_normal_text {
139 m.write_u8(0)?;
140 }
141 Ok(m.into_inner())
142 }
143 }
144 }
145
146 fn next(&mut self) -> Option<&'a str> {
147 if self.pos < self.data.len() {
148 let c = self.data[self.pos];
149 self.pos += 1;
150 Some(c)
151 } else {
152 None
153 }
154 }
155}
156
157#[derive(Debug)]
158pub struct Mes {
159 disasm: Mutex<Box<dyn Disasm + Send + Sync>>,
160 encoding: Encoding,
161 texts: Vec<SlikyString>,
162}
163
164impl Mes {
165 pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
166 let reader = MemReader::new(buf);
167 let num_message = reader.cpeek_u32()?;
168 let code_offset = 4 + num_message as u64 * 4;
169 let first_line_offset = reader.cpeek_u32_at(4)? as u64 + code_offset;
170 let mut disasm: Box<dyn Disasm + Send + Sync> = if reader.cpeek_u8_at(first_line_offset)?
171 == 0x19
172 && reader.cpeek_u32_at(first_line_offset + 1)? == 0
173 {
174 Box::new(Ai6WinDisasm::new(reader)?)
175 } else {
176 Box::new(PlusDisasm::new(reader)?)
177 };
178 disasm.read_header()?;
179 let texts = disasm.read_code()?;
180 Ok(Self {
181 disasm: Mutex::new(disasm),
182 encoding,
183 texts,
184 })
185 }
186
187 fn code_to_text(&self, str: &SlikyString) -> Result<String> {
188 let mut disasm = self
189 .disasm
190 .lock()
191 .map_err(|_| anyhow::anyhow!("Failed to acquire disassembler lock"))?;
192 let mut result = String::new();
193 disasm.stream_mut().pos = str.start as usize;
194 let end = str.start as usize + str.len as usize;
195 let opcodes = disasm.opcodes();
196 while disasm.stream().pos < end {
197 let (opcode, operands) = disasm.read_instruction()?;
198 if opcode == opcodes.push_string
199 || (opcode == opcodes.message1 && !opcodes.is_message1_obfuscated)
200 || opcode == opcodes.message2
201 {
202 if let Some(Obj::Str(s)) = operands.get(0) {
203 let s = disasm.stream().cpeek_fstring_at(
204 s.start,
205 s.len as usize,
206 self.encoding,
207 true,
208 )?;
209 result.push_str(&s);
210 }
211 } else if opcode == opcodes.message1 && opcodes.is_message1_obfuscated {
212 if let Some(Obj::Str(s)) = operands.get(0) {
213 let mut deobfuscated = vec![0u8; (s.len as usize - 1) * 2];
214 let mut input_idx = 0;
215 let mut output_idx = 0;
216 let tlen = s.len - 1;
217 while input_idx < tlen {
218 let b = disasm.stream().cpeek_u8_at(s.start + input_idx)?;
219 input_idx += 1;
220 if matches!(b, 0x81..0xA0 | 0xE0..0xF0) {
221 deobfuscated[output_idx] = b;
222 output_idx += 1;
223 deobfuscated[output_idx] =
224 disasm.stream().cpeek_u8_at(s.start + input_idx)?;
225 input_idx += 1;
226 output_idx += 1;
227 } else {
228 let c = b as i32 - 0x7D62;
229 deobfuscated[output_idx] = (c >> 8) as u8;
230 output_idx += 1;
231 deobfuscated[output_idx] = (c & 0xFF) as u8;
232 output_idx += 1;
233 }
234 }
235 deobfuscated.truncate(output_idx);
236 let s = decode_to_string(self.encoding, &deobfuscated, true)?;
237 result.push_str(&s);
238 }
239 } else if opcode == opcodes.escape_sequence {
240 if let Some(Obj::Byte(e)) = operands.get(0) {
241 match e {
242 0 => result.push('\n'),
244 1 => result.push_str("["),
246 _ => {
247 return Err(anyhow::anyhow!("Unknown escape sequence: {}", e));
248 }
249 }
250 }
251 } else if opcode == opcodes.r#yield {
252 result.push_str("]");
253 }
254 }
255 Ok(result)
256 }
257}
258
259impl Script for Mes {
260 fn default_output_script_type(&self) -> OutputScriptType {
261 OutputScriptType::Json
262 }
263
264 fn default_format_type(&self) -> FormatOptions {
265 FormatOptions::None
266 }
267
268 fn extract_messages(&self) -> Result<Vec<Message>> {
269 let mut messages = Vec::new();
270 let mut name = None;
271 for t in self.texts.iter() {
272 match t.typ {
273 SlikyStringType::Internal => {}
274 SlikyStringType::Name => {
275 name = Some(self.code_to_text(t)?);
276 }
277 SlikyStringType::Message => {
278 let message = self.code_to_text(t)?;
279 messages.push(Message {
280 name: name.take(),
281 message,
282 });
283 }
284 }
285 }
286 Ok(messages)
287 }
288
289 fn import_messages<'a>(
290 &'a self,
291 messages: Vec<Message>,
292 file: Box<dyn WriteSeek + 'a>,
293 _filename: &str,
294 encoding: Encoding,
295 replacement: Option<&'a ReplacementTable>,
296 ) -> Result<()> {
297 let disasm = self
298 .disasm
299 .lock()
300 .map_err(|_| anyhow::anyhow!("Failed to acquire disassembler lock"))?;
301 let opcodes = disasm.opcodes();
302 let mut inp = disasm.stream().clone();
303 inp.pos = 0;
304 let mut patcher = BinaryPatcher::new(inp.to_ref(), file, |add| Ok(add), |add| Ok(add))?;
305 let mut mess = messages.iter();
306 let mut mes = mess.next();
307 for text in &self.texts {
308 patcher.copy_up_to(text.start)?;
309 match text.typ {
310 SlikyStringType::Internal => {}
312 SlikyStringType::Name => {
313 let m = match mes {
314 Some(m) => m,
315 None => {
316 return Err(anyhow::anyhow!("Not enough messages"));
317 }
318 };
319 let mut name = match &m.name {
320 Some(n) => n.to_string(),
321 None => {
322 return Err(anyhow::anyhow!("Message name is missing"));
323 }
324 };
325 if let Some(repl) = replacement {
326 for (k, v) in &repl.map {
327 name = name.replace(k, v);
328 }
329 }
330 let data =
331 TextParser::new(&name, SlikyStringType::Name, opcodes, encoding).parse()?;
332 patcher.replace_bytes(text.len, &data)?;
333 }
334 SlikyStringType::Message => {
335 let m = match mes {
336 Some(m) => m,
337 None => {
338 return Err(anyhow::anyhow!("Not enough messages"));
339 }
340 };
341 let mut message = m.message.to_string();
342 if let Some(repl) = replacement {
343 for (k, v) in &repl.map {
344 message = message.replace(k, v);
345 }
346 }
347 let data =
348 TextParser::new(&message, SlikyStringType::Message, opcodes, encoding)
349 .parse()?;
350 patcher.replace_bytes(text.len, &data)?;
351 mes = mess.next();
352 }
353 }
354 }
355 if mes.is_some() || mess.next().is_some() {
356 return Err(anyhow::anyhow!("Too many messages"));
357 }
358 patcher.copy_up_to(inp.data.len() as u64)?;
359 let code_offset = disasm.code_offset();
360 for &address_offset in disasm.little_endian_addresses() {
361 let orig_address = inp.cpeek_u32_at(address_offset as u64)? as u64;
362 let orig_offset = orig_address + code_offset as u64;
363 let new_offset = patcher.map_offset(orig_offset)?;
364 let new_address = new_offset - code_offset as u64;
365 patcher.patch_u32(address_offset as u64, new_address as u32)?;
366 }
367 for &address_offset in disasm.big_endian_addresses() {
368 let orig_address = inp.cpeek_u32_be_at(address_offset as u64)? as u64;
369 let orig_offset = orig_address + code_offset as u64;
370 let new_offset = patcher.map_offset(orig_offset)?;
371 let new_address = new_offset - code_offset as u64;
372 patcher.patch_u32_be(address_offset as u64, new_address as u32)?;
373 }
374 Ok(())
375 }
376}
377
378#[test]
379fn test_text_parser() {
380 let opcodes = &PLUS_OPCODES;
381 let parser = TextParser::new(
382 "Hello, [world]s\nThis is a test.",
383 SlikyStringType::Message,
384 opcodes,
385 Encoding::Utf8,
386 );
387 let data = parser.parse().unwrap();
388 assert_eq!(
389 data,
390 vec![
391 opcodes.message2,
392 b'H',
393 b'e',
394 b'l',
395 b'l',
396 b'o',
397 b',',
398 b' ',
399 0,
400 opcodes.escape_sequence,
401 1,
402 opcodes.message2,
403 b'w',
404 b'o',
405 b'r',
406 b'l',
407 b'd',
408 0,
409 opcodes.r#yield,
410 opcodes.message2,
411 b's',
412 0,
413 opcodes.escape_sequence,
414 0,
415 opcodes.message2,
416 b'T',
417 b'h',
418 b'i',
419 b's',
420 b' ',
421 b'i',
422 b's',
423 b' ',
424 b'a',
425 b' ',
426 b't',
427 b'e',
428 b's',
429 b't',
430 b'.',
431 0
432 ]
433 );
434}