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>> {
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_****" {
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; }
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 => {} }
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; }
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; 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; }
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, };
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; 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 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}