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}
68
69impl SoftpalScript {
70    /// Create a new Softpal script
71    pub fn new(
72        buf: Vec<u8>,
73        filename: &str,
74        encoding: Encoding,
75        _config: &ExtraConfig,
76        archive: Option<&Box<dyn Script>>,
77    ) -> Result<Self> {
78        let texts = Self::load_texts_data(Self::load_file(filename, archive, "TEXT.DAT")?)?;
79        let points_data = MemReader::new(Self::load_file(filename, archive, "POINT.DAT")?);
80        let label_offsets = Self::load_point_data(points_data)?;
81        let strs = Disasm::new(&buf, &label_offsets)?.disassemble::<MemWriter>(None)?;
82        Ok(Self {
83            data: MemReader::new(buf),
84            strs,
85            encoding,
86            texts,
87            label_offsets,
88        })
89    }
90
91    fn load_file(filename: &str, archive: Option<&Box<dyn Script>>, name: &str) -> Result<Vec<u8>> {
92        if let Some(archive) = archive {
93            Ok(archive
94                .open_file_by_name(name, true)
95                .map_err(|e| anyhow::anyhow!("Failed to open file {} in archive: {}", name, e))?
96                .data()?)
97        } else {
98            let mut path = std::path::PathBuf::from(filename);
99            path.set_file_name(name);
100            std::fs::read(path).map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", name, e))
101        }
102    }
103
104    fn load_texts_data(data: Vec<u8>) -> Result<MemReader> {
105        let mut writer = MemWriter::from_vec(data);
106        if writer.data.len() >= 0x14 {
107            let ind = writer.cpeek_u32_at(0x10)?;
108            writer.pos = 0x10;
109            if ind != 0 {
110                let mut shift = 4;
111                for _ in 0..(writer.data.len() / 4 - 4) {
112                    let mut data = writer.cpeek_u32()?;
113                    let mut add = data.to_le_bytes();
114                    add[0] = add[0].rotate_left(shift);
115                    shift = (shift + 1) % 8;
116                    data = u32::from_le_bytes(add);
117                    data ^= 0x084DF873 ^ 0xFF987DEE;
118                    writer.write_u32(data)?;
119                }
120            }
121        }
122        Ok(MemReader::new(writer.into_inner()))
123    }
124
125    fn load_point_data(mut data: MemReader) -> Result<Vec<u32>> {
126        let mut magic = [0u8; 16];
127        data.read_exact(&mut magic)?;
128        if magic != *b"$POINT_LIST_****" {
129            return Err(anyhow::anyhow!("Invalid point list magic: {:?}", magic));
130        }
131        let mut label_offsets = Vec::new();
132        while !data.is_eof() {
133            label_offsets.push(data.read_u32()? + CODE_OFFSET);
134        }
135        label_offsets.reverse();
136        Ok(label_offsets)
137    }
138}
139
140impl Script for SoftpalScript {
141    fn default_output_script_type(&self) -> OutputScriptType {
142        OutputScriptType::Json
143    }
144
145    fn default_format_type(&self) -> FormatOptions {
146        FormatOptions::None
147    }
148
149    fn is_output_supported(&self, _: OutputScriptType) -> bool {
150        true
151    }
152
153    fn extract_messages(&self) -> Result<Vec<Message>> {
154        let mut messages = Vec::new();
155        let mut name = None;
156        for str in &self.strs {
157            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
158            let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
159            let text =
160                decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
161            match str.typ {
162                StringType::Name => {
163                    if text.is_empty() {
164                        continue; // Skip empty names
165                    }
166                    name = Some(text);
167                }
168                StringType::Message => messages.push(Message {
169                    name: name.take(),
170                    message: text,
171                }),
172            }
173        }
174        Ok(messages)
175    }
176
177    fn import_messages<'a>(
178        &'a self,
179        messages: Vec<Message>,
180        mut file: Box<dyn WriteSeek + 'a>,
181        filename: &str,
182        encoding: Encoding,
183        replacement: Option<&'a ReplacementTable>,
184    ) -> Result<()> {
185        let mut texts_filename = std::path::PathBuf::from(filename);
186        texts_filename.set_file_name("TEXT.DAT");
187        let mut texts = Vec::new();
188        let mut reader = self.texts.to_ref();
189        reader.pos = 0x10;
190        while !reader.is_eof() {
191            reader.pos += 4; // Skip index
192            texts.push(reader.read_cstring()?)
193        }
194        let mut texts_file = std::fs::File::create(&texts_filename)
195            .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
196        file.write_all(&self.data.data)?;
197        let mut mes = messages.iter();
198        let mut mess = mes.next();
199        let texts_data_len = self.texts.data.len() as u32;
200        let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
201        for str in &self.strs {
202            let addr = self.data.cpeek_u32_at(str.offset as u64)?;
203            if addr + 4 > texts_data_len {
204                continue;
205            }
206            let m = match mess {
207                Some(m) => m,
208                None => return Err(anyhow::anyhow!("Not enough messages.")),
209            };
210            let mut text = match str.typ {
211                StringType::Name => match &m.name {
212                    Some(name) => name.clone(),
213                    None => return Err(anyhow::anyhow!("Missing name for message.")),
214                },
215                StringType::Message => {
216                    let m = m.message.clone();
217                    mess = mes.next();
218                    m
219                }
220            };
221            if let Some(repl) = replacement {
222                for (from, to) in repl.map.iter() {
223                    text = text.replace(from, to);
224                }
225            }
226            text = text.replace("\n", "<br>");
227            let encoded = encode_string(encoding, &text, false)?;
228            let s = std::ffi::CString::new(encoded)?;
229            let num = texts.len() as u32;
230            num_offset_map.insert(num, str.offset);
231            texts.push(s);
232        }
233        if mess.is_some() || mes.next().is_some() {
234            return Err(anyhow::anyhow!("Some messages were not processed."));
235        }
236        texts_file.write_all(b"$TEXT_LIST__")?;
237        texts_file.write_u32(texts.len() as u32)?;
238        let mut nf = MemWriter::new();
239        for (num, text) in texts.into_iter().enumerate() {
240            let num = num as u32;
241            let newaddr = nf.pos as u32 + 0x10;
242            if let Some(offset) = num_offset_map.get(&num) {
243                file.write_u32_at(*offset as u64, newaddr)?;
244            }
245            nf.write_u32(num)?;
246            nf.write_cstring(&text)?;
247        }
248        nf.pos = 0;
249        let mut shift = 4;
250        for _ in 0..(nf.data.len() / 4) {
251            let mut data = nf.cpeek_u32()?;
252            data ^= 0x084DF873 ^ 0xFF987DEE;
253            let mut add = data.to_le_bytes();
254            add[0] = add[0].rotate_right(shift);
255            shift = (shift + 1) % 8;
256            data = u32::from_le_bytes(add);
257            nf.write_u32(data)?;
258        }
259        texts_file.write_all(&nf.data)?;
260        Ok(())
261    }
262
263    fn custom_output_extension<'a>(&'a self) -> &'a str {
264        "txt"
265    }
266
267    fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
268        let mut file = std::fs::File::create(filename)
269            .map_err(|e| anyhow::anyhow!("Failed to create file {}: {}", filename.display(), e))?;
270        Disasm::new(&self.data.data, &self.label_offsets)?.disassemble(Some(&mut file))?;
271        Ok(())
272    }
273}