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}
68
69impl SoftpalScript {
70 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; }
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; 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}