msg_tool\scripts\softpal\scr/
mod.rs1mod 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)]
14pub struct SoftpalScriptBuilder {}
16
17impl SoftpalScriptBuilder {
18 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 + Send + Sync>> {
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)]
60pub 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 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_****" && magic != *b"_POINT_LIST_****" {
132 return Err(anyhow::anyhow!("Invalid point list magic: {:?}", magic));
133 }
134 let is_crypted = magic[0] == b'$' && {
135 let first_offset = data.cpeek_u32()?;
136 first_offset & 0xFF000000 != 0
137 };
138 let mut label_offsets = Vec::new();
139 let mut shift = 4;
140 while !data.is_eof() {
141 let mut val = data.read_u32()?;
142 if is_crypted {
143 let mut add = val.to_le_bytes();
144 add[0] = add[0].rotate_left(shift);
145 shift = (shift + 1) % 8;
146 val = u32::from_le_bytes(add);
147 val ^= 0x084DF873 ^ 0xFF987DEE;
148 }
149 label_offsets.push(val + CODE_OFFSET);
150 }
151 label_offsets.reverse();
152 Ok(label_offsets)
153 }
154}
155
156impl Script for SoftpalScript {
157 fn default_output_script_type(&self) -> OutputScriptType {
158 OutputScriptType::Json
159 }
160
161 fn default_format_type(&self) -> FormatOptions {
162 FormatOptions::None
163 }
164
165 fn is_output_supported(&self, _: OutputScriptType) -> bool {
166 true
167 }
168
169 fn multiple_message_files(&self) -> bool {
170 true
171 }
172
173 fn extract_messages(&self) -> Result<Vec<Message>> {
174 let mut messages = Vec::new();
175 let mut name = None;
176 let max_len = self.texts.data.len() as u32;
177 for str in &self.strs {
178 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
179 if addr - 4 > max_len {
180 continue;
181 }
182 let idx = self.texts.cpeek_u32_at(addr as u64)?;
183 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
184 let text =
185 decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
186 let text = if self.add_message_index {
187 format!("[{}]{}", idx, text)
188 } else {
189 text
190 };
191 match str.typ {
192 StringType::Name => {
193 if text.is_empty() {
194 continue; }
196 name = Some(text);
197 }
198 StringType::Message => messages.push(Message {
199 name: name.take(),
200 message: text,
201 }),
202 StringType::Hover => messages.push(Message::new(text, None)),
203 StringType::Label => {} }
205 }
206 Ok(messages)
207 }
208
209 fn extract_multiple_messages(&self) -> Result<HashMap<String, Vec<Message>>> {
210 let mut hovers = Vec::new();
211 let mut messages = Vec::new();
212 let mut label = None;
213 let mut name = None;
214 let mut result = HashMap::new();
215 let max_len = self.texts.data.len() as u32;
216 for str in &self.strs {
217 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
218 if addr - 4 > max_len {
219 continue;
220 }
221 let idx = self.texts.cpeek_u32_at(addr as u64)?;
222 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
223 let ptext =
224 decode_to_string(self.encoding, text.as_bytes(), false)?.replace("<br>", "\n");
225 let text = if self.add_message_index {
226 format!("[{}]{}", idx, ptext)
227 } else {
228 ptext.clone()
229 };
230 match str.typ {
231 StringType::Name => {
232 if text.is_empty() {
233 continue; }
235 name = Some(text);
236 }
237 StringType::Message => messages.push(Message::new(text, name.take())),
238 StringType::Hover => hovers.push(Message::new(text, None)),
239 StringType::Label => {
240 if !messages.is_empty() {
241 let key = label.take().unwrap_or_else(|| "default".to_string());
242 if result.contains_key(&key) {
243 eprintln!(
244 "Warning: Duplicate label '{}', overwriting previous messages.",
245 key
246 );
247 crate::COUNTER.inc_warning();
248 }
249 result.insert(key, messages);
250 messages = Vec::new();
251 }
252 label = Some(ptext);
253 }
254 }
255 }
256 if !messages.is_empty() {
257 let key = label.take().unwrap_or_else(|| "default".to_string());
258 result.insert(key, messages);
259 }
260 if !hovers.is_empty() {
261 result.insert("hover".to_string(), hovers);
262 }
263 Ok(result)
264 }
265
266 fn import_messages<'a>(
267 &'a self,
268 messages: Vec<Message>,
269 mut file: Box<dyn WriteSeek + 'a>,
270 filename: &str,
271 encoding: Encoding,
272 replacement: Option<&'a ReplacementTable>,
273 ) -> Result<()> {
274 let mut texts_filename = std::path::PathBuf::from(filename);
275 texts_filename.set_file_name("TEXT.DAT");
276 let mut texts = Vec::new();
277 let mut reader = self.texts.to_ref();
278 reader.pos = 0x10;
279 while !reader.is_eof() {
280 reader.pos += 4; texts.push(reader.read_cstring()?)
282 }
283 let mut texts_file = std::fs::File::create(&texts_filename)
284 .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
285 file.write_all(&self.data.data)?;
286 let mut mes = messages.iter();
287 let mut mess = mes.next();
288 let texts_data_len = self.texts.data.len() as u32;
289 let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
290 for str in &self.strs {
291 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
292 if addr + 4 > texts_data_len {
293 continue;
294 }
295 if str.typ.is_label() {
296 continue; }
298 let m = match mess {
299 Some(m) => m,
300 None => return Err(anyhow::anyhow!("Not enough messages.")),
301 };
302 let mut text = match str.typ {
303 StringType::Name => match &m.name {
304 Some(name) => name.clone(),
305 None => return Err(anyhow::anyhow!("Missing name for message.")),
306 },
307 StringType::Message => {
308 let m = m.message.clone();
309 mess = mes.next();
310 m
311 }
312 StringType::Hover => {
313 let m = m.message.clone();
314 mess = mes.next();
315 m
316 }
317 StringType::Label => continue, };
319 if let Some(repl) = replacement {
320 for (from, to) in repl.map.iter() {
321 text = text.replace(from, to);
322 }
323 }
324 text = text.replace("\n", "<br>");
325 let encoded = encode_string(encoding, &text, false)?;
326 let s = std::ffi::CString::new(encoded)?;
327 let num = texts.len() as u32;
328 num_offset_map.insert(num, str.offset);
329 texts.push(s);
330 }
331 if mess.is_some() || mes.next().is_some() {
332 return Err(anyhow::anyhow!("Some messages were not processed."));
333 }
334 texts_file.write_all(b"$TEXT_LIST__")?;
335 texts_file.write_u32(texts.len() as u32)?;
336 let mut nf = MemWriter::new();
337 for (num, text) in texts.into_iter().enumerate() {
338 let num = num as u32;
339 let newaddr = nf.pos as u32 + 0x10;
340 if let Some(offset) = num_offset_map.get(&num) {
341 file.write_u32_at(*offset as u64, newaddr)?;
342 }
343 nf.write_u32(num)?;
344 nf.write_cstring(&text)?;
345 }
346 nf.pos = 0;
347 let mut shift = 4;
348 for _ in 0..(nf.data.len() / 4) {
349 let mut data = nf.cpeek_u32()?;
350 data ^= 0x084DF873 ^ 0xFF987DEE;
351 let mut add = data.to_le_bytes();
352 add[0] = add[0].rotate_right(shift);
353 shift = (shift + 1) % 8;
354 data = u32::from_le_bytes(add);
355 nf.write_u32(data)?;
356 }
357 texts_file.write_all(&nf.data)?;
358 Ok(())
359 }
360
361 fn import_multiple_messages<'a>(
362 &'a self,
363 messages: HashMap<String, Vec<Message>>,
364 mut file: Box<dyn WriteSeek + 'a>,
365 filename: &str,
366 encoding: Encoding,
367 replacement: Option<&'a ReplacementTable>,
368 ) -> Result<()> {
369 let mut texts_filename = std::path::PathBuf::from(filename);
370 texts_filename.set_file_name("TEXT.DAT");
371 let mut texts = Vec::new();
372 let mut reader = self.texts.to_ref();
373 reader.pos = 0x10;
374 while !reader.is_eof() {
375 reader.pos += 4; texts.push(reader.read_cstring()?)
377 }
378 let mut texts_file = std::fs::File::create(&texts_filename)
379 .map_err(|e| anyhow::anyhow!("Failed to create TEXT.DAT file: {}", e))?;
380 file.write_all(&self.data.data)?;
381 let hover_messages = messages.get("hover").cloned().unwrap_or_default();
382 let mut hover_iter = hover_messages.iter();
383 let mut hover_mes = hover_iter.next();
384 let mut cur_label: Option<String> = None;
385 let mut cur_messages = messages
386 .get(cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default"))
387 .cloned()
388 .unwrap_or_default();
389 let mut cur_iter = cur_messages.iter();
390 let mut cur_mes = cur_iter.next();
391 let texts_data_len = self.texts.data.len() as u32;
392 let mut num_offset_map: HashMap<u32, u32> = HashMap::new();
393 for str in &self.strs {
394 let addr = self.data.cpeek_u32_at(str.offset as u64)?;
395 if addr + 4 > texts_data_len {
396 continue;
397 }
398 let mut text = match str.typ {
399 StringType::Label => {
400 if cur_mes.is_some() || cur_iter.next().is_some() {
401 return Err(anyhow::anyhow!(
402 "Not all messages were used for label {}.",
403 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
404 ));
405 }
406 let text = self.texts.cpeek_cstring_at(addr as u64 + 4)?;
407 let text = decode_to_string(self.encoding, text.as_bytes(), false)?
408 .replace("<br>", "\n");
409 cur_messages = messages.get(text.as_str()).cloned().unwrap_or_default();
410 cur_iter = cur_messages.iter();
411 cur_mes = cur_iter.next();
412 cur_label = Some(text);
413 continue;
415 }
416 StringType::Hover => {
417 let m = match hover_mes {
418 Some(m) => m,
419 None => return Err(anyhow::anyhow!("Not enough hover messages.")),
420 };
421 let m = m.message.clone();
422 hover_mes = hover_iter.next();
423 m
424 }
425 StringType::Name => {
426 let m = match cur_mes {
427 Some(m) => m,
428 None => {
429 return Err(anyhow::anyhow!(
430 "Not enough messages for label {}.",
431 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
432 ));
433 }
434 };
435 let name = match &m.name {
436 Some(name) => name.clone(),
437 None => return Err(anyhow::anyhow!("Missing name for message.")),
438 };
439 name
440 }
441 StringType::Message => {
442 let m = match cur_mes {
443 Some(m) => m,
444 None => {
445 return Err(anyhow::anyhow!(
446 "Not enough messages for label {}.",
447 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
448 ));
449 }
450 };
451 let m = m.message.clone();
452 cur_mes = cur_iter.next();
453 m
454 }
455 };
456 if let Some(repl) = replacement {
457 for (from, to) in repl.map.iter() {
458 text = text.replace(from, to);
459 }
460 }
461 text = text.replace("\n", "<br>");
462 let encoded = encode_string(encoding, &text, false)?;
463 let s = std::ffi::CString::new(encoded)?;
464 let num = texts.len() as u32;
465 num_offset_map.insert(num, str.offset);
466 texts.push(s);
467 }
468 if cur_mes.is_some() || cur_iter.next().is_some() {
469 return Err(anyhow::anyhow!(
470 "Some messages were not processed for label {}.",
471 cur_label.as_ref().map(|s| s.as_str()).unwrap_or("default")
472 ));
473 }
474 if hover_mes.is_some() || hover_iter.next().is_some() {
475 return Err(anyhow::anyhow!("Some hover messages were not processed."));
476 }
477 texts_file.write_all(b"$TEXT_LIST__")?;
478 texts_file.write_u32(texts.len() as u32)?;
479 let mut nf = MemWriter::new();
480 for (num, text) in texts.into_iter().enumerate() {
481 let num = num as u32;
482 let newaddr = nf.pos as u32 + 0x10;
483 if let Some(offset) = num_offset_map.get(&num) {
484 file.write_u32_at(*offset as u64, newaddr)?;
485 }
486 nf.write_u32(num)?;
487 nf.write_cstring(&text)?;
488 }
489 nf.pos = 0;
490 let mut shift = 4;
491 for _ in 0..(nf.data.len() / 4) {
492 let mut data = nf.cpeek_u32()?;
493 data ^= 0x084DF873 ^ 0xFF987DEE;
494 let mut add = data.to_le_bytes();
495 add[0] = add[0].rotate_right(shift);
496 shift = (shift + 1) % 8;
497 data = u32::from_le_bytes(add);
498 nf.write_u32(data)?;
499 }
500 texts_file.write_all(&nf.data)?;
501 Ok(())
502 }
503
504 fn custom_output_extension<'a>(&'a self) -> &'a str {
505 "txt"
506 }
507
508 fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
509 let file = std::fs::File::create(filename)
510 .map_err(|e| anyhow::anyhow!("Failed to create file {}: {}", filename.display(), e))?;
511 let mut file = std::io::BufWriter::new(file);
512 Disasm::new(&self.data.data, &self.label_offsets)?.disassemble(Some(&mut file))?;
513 Ok(())
514 }
515}