msg_tool\scripts\silky/
mes.rs

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)]
12/// Sliky mes script builder
13pub struct MesBuilder {}
14
15impl MesBuilder {
16    /// Create a new Sliky mes script builder
17    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)?; // ruby start
103                            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)?; // new line
123                        }
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                        // new line
243                        0 => result.push('\n'),
244                        // ruby
245                        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                // Ignore internal strings
311                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}