msg_tool\scripts\softpal\scr/
mod.rs

1//! Softpal script (.src)
2mod disasm;
3
4use crate::ext::io::*;
5use crate::scripts::base::*;
6use crate::types::*;
7use crate::utils::encoding::*;
8use anyhow::Result;
9use disasm::*;
10use std::collections::HashMap;
11use std::io::{Read, Write};
12
13#[derive(Debug)]
14/// Softpal script builder
15pub struct SoftpalScriptBuilder {}
16
17impl SoftpalScriptBuilder {
18    /// Create a new Softpal script builder
19    pub fn new() -> Self {
20        Self {}
21    }
22}
23
24impl ScriptBuilder for SoftpalScriptBuilder {
25    fn default_encoding(&self) -> Encoding {
26        Encoding::Cp932
27    }
28
29    fn build_script(
30        &self,
31        buf: Vec<u8>,
32        filename: &str,
33        encoding: Encoding,
34        _archive_encoding: Encoding,
35        config: &ExtraConfig,
36        archive: Option<&Box<dyn Script>>,
37    ) -> Result<Box<dyn Script>> {
38        Ok(Box::new(SoftpalScript::new(
39            buf, filename, encoding, config, archive,
40        )?))
41    }
42
43    fn extensions(&self) -> &'static [&'static str] {
44        &["src"]
45    }
46
47    fn script_type(&self) -> &'static ScriptType {
48        &ScriptType::Softpal
49    }
50
51    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
52        if buf_len >= 4 && buf.starts_with(b"Sv20") {
53            return Some(10);
54        }
55        None
56    }
57}
58
59#[derive(Debug)]
60/// Softpal SRC Script
61pub struct SoftpalScript {
62    data: MemReader,
63    strs: Vec<PalString>,
64    texts: MemReader,
65    encoding: Encoding,
66    label_offsets: Vec<u32>,
67    add_message_index: bool,
68}
69
70impl SoftpalScript {
71    /// Create a new Softpal script
72    pub fn new(
73        buf: Vec<u8>,
74        filename: &str,
75        encoding: Encoding,
76        config: &ExtraConfig,
77        archive: Option<&Box<dyn Script>>,
78    ) -> Result<Self> {
79        let texts = Self::load_texts_data(Self::load_file(filename, archive, "TEXT.DAT")?)?;
80        let points_data = MemReader::new(Self::load_file(filename, archive, "POINT.DAT")?);
81        let label_offsets = Self::load_point_data(points_data)?;
82        let strs = Disasm::new(&buf, &label_offsets)?.disassemble::<MemWriter>(None)?;
83        Ok(Self {
84            data: MemReader::new(buf),
85            strs,
86            encoding,
87            texts,
88            label_offsets,
89            add_message_index: config.softpal_add_message_index,
90        })
91    }
92
93    fn load_file(filename: &str, archive: Option<&Box<dyn Script>>, name: &str) -> Result<Vec<u8>> {
94        if let Some(archive) = archive {
95            Ok(archive
96                .open_file_by_name(name, true)
97                .map_err(|e| anyhow::anyhow!("Failed to open file {} in archive: {}", name, e))?
98                .data()?)
99        } else {
100            let mut path = std::path::PathBuf::from(filename);
101            path.set_file_name(name);
102            path = crate::utils::files::get_ignorecase_path(&path)?;
103            std::fs::read(path).map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", name, e))
104        }
105    }
106
107    fn load_texts_data(data: Vec<u8>) -> Result<MemReader> {
108        let mut writer = MemWriter::from_vec(data);
109        if writer.data.len() >= 0x14 {
110            let ind = writer.cpeek_u32_at(0x10)?;
111            writer.pos = 0x10;
112            if ind != 0 {
113                let mut shift = 4;
114                for _ in 0..(writer.data.len() / 4 - 4) {
115                    let mut data = writer.cpeek_u32()?;
116                    let mut add = data.to_le_bytes();
117                    add[0] = add[0].rotate_left(shift);
118                    shift = (shift + 1) % 8;
119                    data = u32::from_le_bytes(add);
120                    data ^= 0x084DF873 ^ 0xFF987DEE;
121                    writer.write_u32(data)?;
122                }
123            }
124        }
125        Ok(MemReader::new(writer.into_inner()))
126    }
127
128    fn load_point_data(mut data: MemReader) -> Result<Vec<u32>> {
129        let mut magic = [0u8; 16];
130        data.read_exact(&mut magic)?;
131        if magic != *b"$POINT_LIST_****" {
132            return Err(anyhow::anyhow!("Invalid point list magic: {:?}", magic));
133        }
134        let mut label_offsets = Vec::new();
135        while !data.is_eof() {
136            label_offsets.push(data.read_u32()? + CODE_OFFSET);
137        }
138        label_offsets.reverse();
139        Ok(label_offsets)
140    }
141}
142
143impl Script for SoftpalScript {
144    fn default_output_script_type(&self) -> OutputScriptType {
145        OutputScriptType::Json
146    }
147
148    fn default_format_type(&self) -> FormatOptions {
149        FormatOptions::None
150    }
151
152    fn is_output_supported(&self, _: OutputScriptType) -> bool {
153        true
154    }
155
156    fn multiple_message_files(&self) -> bool {
157        true
158    }
159
160    fn extract_messages(&self) -> Result<Vec<Message>> {
161        let mut messages = Vec::new();
162        let mut name = None;
163        let max_len = self.texts.data.len() as u32;
164        for str in &self.strs {
165            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
166            if addr - 4 > max_len {
167                continue;
168            }
169            let idx = self.texts.cpeek_u32_at(addr as u64)?;
170            let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
171            let text =
172                decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
173            let text = if self.add_message_index {
174                format!("[{}]{}", idx, text)
175            } else {
176                text
177            };
178            match str.typ {
179                StringType::Name => {
180                    if text.is_empty() {
181                        continue; // Skip empty names
182                    }
183                    name = Some(text);
184                }
185                StringType::Message => messages.push(Message {
186                    name: name.take(),
187                    message: text,
188                }),
189                StringType::Hover => messages.push(Message::new(text, None)),
190                StringType::Label => {} // Ignore labels
191            }
192        }
193        Ok(messages)
194    }
195
196    fn extract_multiple_messages(&self) -> Result<HashMap<String, Vec<Message>>> {
197        let mut hovers = Vec::new();
198        let mut messages = Vec::new();
199        let mut label = None;
200        let mut name = None;
201        let mut result = HashMap::new();
202        let max_len = self.texts.data.len() as u32;
203        for str in &self.strs {
204            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
205            if addr - 4 > max_len {
206                continue;
207            }
208            let idx = self.texts.cpeek_u32_at(addr as u64)?;
209            let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
210            let ptext =
211                decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
212            let text = if self.add_message_index {
213                format!("[{}]{}", idx, ptext)
214            } else {
215                ptext.clone()
216            };
217            match str.typ {
218                StringType::Name => {
219                    if text.is_empty() {
220                        continue; // Skip empty names
221                    }
222                    name = Some(text);
223                }
224                StringType::Message => messages.push(Message::new(text, name.take())),
225                StringType::Hover => hovers.push(Message::new(text, None)),
226                StringType::Label => {
227                    if !messages.is_empty() {
228                        let key = label.take().unwrap_or_else(|| "default".to_string());
229                        if result.contains_key(&key) {
230                            eprintln!(
231                                "Warning: Duplicate label '{}', overwriting previous messages.",
232                                key
233                            );
234                            crate::COUNTER.inc_warning();
235                        }
236                        result.insert(key, messages);
237                        messages = Vec::new();
238                    }
239                    label = Some(ptext);
240                }
241            }
242        }
243        if !messages.is_empty() {
244            let key = label.take().unwrap_or_else(|| "default".to_string());
245            result.insert(key, messages);
246        }
247        if !hovers.is_empty() {
248            result.insert("hover".to_string(), hovers);
249        }
250        Ok(result)
251    }
252
253    fn import_messages<'a>(
254        &'a self,
255        messages: Vec<Message>,
256        mut file: Box<dyn WriteSeek + 'a>,
257        filename: &str,
258        encoding: Encoding,
259        replacement: Option<&'a ReplacementTable>,
260    ) -> Result<()> {
261        let mut texts_filename = std::path::PathBuf::from(filename);
262        texts_filename.set_file_name("TEXT.DAT");
263        let mut texts = Vec::new();
264        let mut reader = self.texts.to_ref();
265        reader.pos = 0x10;
266        while !reader.is_eof() {
267            reader.pos += 4; // Skip index
268            texts.push(reader.read_cstring()?)
269        }
270        let mut texts_file = std::fs::File::create(&texts_filename)
271            .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
272        file.write_all(&self.data.data)?;
273        let mut mes = messages.iter();
274        let mut mess = mes.next();
275        let texts_data_len = self.texts.data.len() as u32;
276        let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
277        for str in &self.strs {
278            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
279            if addr + 4 > texts_data_len {
280                continue;
281            }
282            if str.typ.is_label() {
283                continue; // Ignore labels
284            }
285            let m = match mess {
286                Some(m) => m,
287                None => return Err(anyhow::anyhow!("Not enough messages.")),
288            };
289            let mut text = match str.typ {
290                StringType::Name => match &m.name {
291                    Some(name) => name.clone(),
292                    None => return Err(anyhow::anyhow!("Missing name for message.")),
293                },
294                StringType::Message => {
295                    let m = m.message.clone();
296                    mess = mes.next();
297                    m
298                }
299                StringType::Hover => {
300                    let m = m.message.clone();
301                    mess = mes.next();
302                    m
303                }
304                StringType::Label => continue, // Ignore labels
305            };
306            if let Some(repl) = replacement {
307                for (from, to) in repl.map.iter() {
308                    text = text.replace(from, to);
309                }
310            }
311            text = text.replace("\n", "<br>");
312            let encoded = encode_string(encoding, &text, false)?;
313            let s = std::ffi::CString::new(encoded)?;
314            let num = texts.len() as u32;
315            num_offset_map.insert(num, str.offset);
316            texts.push(s);
317        }
318        if mess.is_some() || mes.next().is_some() {
319            return Err(anyhow::anyhow!("Some messages were not processed."));
320        }
321        texts_file.write_all(b"$TEXT_LIST__")?;
322        texts_file.write_u32(texts.len() as u32)?;
323        let mut nf = MemWriter::new();
324        for (num, text) in texts.into_iter().enumerate() {
325            let num = num as u32;
326            let newaddr = nf.pos as u32 + 0x10;
327            if let Some(offset) = num_offset_map.get(&num) {
328                file.write_u32_at(*offset as u64, newaddr)?;
329            }
330            nf.write_u32(num)?;
331            nf.write_cstring(&text)?;
332        }
333        nf.pos = 0;
334        let mut shift = 4;
335        for _ in 0..(nf.data.len() / 4) {
336            let mut data = nf.cpeek_u32()?;
337            data ^= 0x084DF873 ^ 0xFF987DEE;
338            let mut add = data.to_le_bytes();
339            add[0] = add[0].rotate_right(shift);
340            shift = (shift + 1) % 8;
341            data = u32::from_le_bytes(add);
342            nf.write_u32(data)?;
343        }
344        texts_file.write_all(&nf.data)?;
345        Ok(())
346    }
347
348    fn import_multiple_messages<'a>(
349        &'a self,
350        messages: HashMap<String, Vec<Message>>,
351        mut file: Box<dyn WriteSeek + 'a>,
352        filename: &str,
353        encoding: Encoding,
354        replacement: Option<&'a ReplacementTable>,
355    ) -> Result<()> {
356        let mut texts_filename = std::path::PathBuf::from(filename);
357        texts_filename.set_file_name("TEXT.DAT");
358        let mut texts = Vec::new();
359        let mut reader = self.texts.to_ref();
360        reader.pos = 0x10;
361        while !reader.is_eof() {
362            reader.pos += 4; // Skip index
363            texts.push(reader.read_cstring()?)
364        }
365        let mut texts_file = std::fs::File::create(&texts_filename)
366            .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
367        file.write_all(&self.data.data)?;
368        let hover_messages = messages.get("hover").cloned().unwrap_or_default();
369        let mut hover_iter = hover_messages.iter();
370        let mut hover_mes = hover_iter.next();
371        let mut cur_label: Option<String> = None;
372        let mut cur_messages = messages
373            .get(cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default"))
374            .cloned()
375            .unwrap_or_default();
376        let mut cur_iter = cur_messages.iter();
377        let mut cur_mes = cur_iter.next();
378        let texts_data_len = self.texts.data.len() as u32;
379        let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
380        for str in &self.strs {
381            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
382            if addr + 4 > texts_data_len {
383                continue;
384            }
385            let mut text = match str.typ {
386                StringType::Label => {
387                    if cur_mes.is_some() || cur_iter.next().is_some() {
388                        return Err(anyhow::anyhow!(
389                            "Not all messages were used for label {}.",
390                            cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
391                        ));
392                    }
393                    let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
394                    let text = decode_to_string(self.encoding, text.as_bytes(), false)?
395                        .replace("<br>", "\n");
396                    cur_messages = messages.get(text.as_str()).cloned().unwrap_or_default();
397                    cur_iter = cur_messages.iter();
398                    cur_mes = cur_iter.next();
399                    cur_label = Some(text);
400                    // We don't need update labels
401                    continue;
402                }
403                StringType::Hover => {
404                    let m = match hover_mes {
405                        Some(m) => m,
406                        None => return Err(anyhow::anyhow!("Not enough hover messages.")),
407                    };
408                    let m = m.message.clone();
409                    hover_mes = hover_iter.next();
410                    m
411                }
412                StringType::Name => {
413                    let m = match cur_mes {
414                        Some(m) => m,
415                        None => {
416                            return Err(anyhow::anyhow!(
417                                "Not enough messages for label {}.",
418                                cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
419                            ));
420                        }
421                    };
422                    let name = match &m.name {
423                        Some(name) => name.clone(),
424                        None => return Err(anyhow::anyhow!("Missing name for message.")),
425                    };
426                    name
427                }
428                StringType::Message => {
429                    let m = match cur_mes {
430                        Some(m) => m,
431                        None => {
432                            return Err(anyhow::anyhow!(
433                                "Not enough messages for label {}.",
434                                cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
435                            ));
436                        }
437                    };
438                    let m = m.message.clone();
439                    cur_mes = cur_iter.next();
440                    m
441                }
442            };
443            if let Some(repl) = replacement {
444                for (from, to) in repl.map.iter() {
445                    text = text.replace(from, to);
446                }
447            }
448            text = text.replace("\n", "<br>");
449            let encoded = encode_string(encoding, &text, false)?;
450            let s = std::ffi::CString::new(encoded)?;
451            let num = texts.len() as u32;
452            num_offset_map.insert(num, str.offset);
453            texts.push(s);
454        }
455        if cur_mes.is_some() || cur_iter.next().is_some() {
456            return Err(anyhow::anyhow!(
457                "Some messages were not processed for label {}.",
458                cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
459            ));
460        }
461        if hover_mes.is_some() || hover_iter.next().is_some() {
462            return Err(anyhow::anyhow!("Some hover messages were not processed."));
463        }
464        texts_file.write_all(b"$TEXT_LIST__")?;
465        texts_file.write_u32(texts.len() as u32)?;
466        let mut nf = MemWriter::new();
467        for (num, text) in texts.into_iter().enumerate() {
468            let num = num as u32;
469            let newaddr = nf.pos as u32 + 0x10;
470            if let Some(offset) = num_offset_map.get(&num) {
471                file.write_u32_at(*offset as u64, newaddr)?;
472            }
473            nf.write_u32(num)?;
474            nf.write_cstring(&text)?;
475        }
476        nf.pos = 0;
477        let mut shift = 4;
478        for _ in 0..(nf.data.len() / 4) {
479            let mut data = nf.cpeek_u32()?;
480            data ^= 0x084DF873 ^ 0xFF987DEE;
481            let mut add = data.to_le_bytes();
482            add[0] = add[0].rotate_right(shift);
483            shift = (shift + 1) % 8;
484            data = u32::from_le_bytes(add);
485            nf.write_u32(data)?;
486        }
487        texts_file.write_all(&nf.data)?;
488        Ok(())
489    }
490
491    fn custom_output_extension<'a>(&'a self) -> &'a str {
492        "txt"
493    }
494
495    fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
496        let file = std::fs::File::create(filename)
497            .map_err(|e| anyhow::anyhow!("Failed to create file {}: {}", filename.display(), e))?;
498        let mut file = std::io::BufWriter::new(file);
499        Disasm::new(&self.data.data, &self.label_offsets)?.disassemble(Some(&mut file))?;
500        Ok(())
501    }
502}