1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, HashMap};
3use std::fs::File;
4use std::io::Read;
5use std::mem::size_of;
6use std::path::Path;
7use std::rc::Rc;
8use std::str::FromStr;
9
10use anyhow::{anyhow, Result};
11
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub enum Nls {
14 #[default]
15 ShiftJIS = 0,
16 GBK = 1,
17 UTF8 = 2,
18}
19
20impl FromStr for Nls {
21 type Err = anyhow::Error;
22
23 fn from_str(s: &str) -> Result<Self> {
24 let lower = s.to_ascii_lowercase();
25 match lower.as_str() {
26 "sjis" => Ok(Nls::ShiftJIS),
27 "gbk" => Ok(Nls::GBK),
28 "utf8" => Ok(Nls::UTF8),
29 _ => Err(anyhow!("unknown NLS")),
30 }
31 }
32}
33
34#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35pub struct Syscall {
36 pub args: u8,
38 pub name: String,
40}
41
42#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43struct YamlSyscall {
44 pub args: u8,
45 pub name: String,
46}
47
48#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49struct YamlExport {
50 pub nls: Nls,
51 pub custom_syscall_count: u16,
52 pub game_mode: u8,
53 pub game_mode_reserved: u8,
54 pub game_title: String,
55 pub syscall_count: u16,
56 pub syscalls: BTreeMap<usize, YamlSyscall>,
57}
58
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct Parser {
61 #[serde(skip)]
62 pub buffer: Rc<Vec<u8>>,
63 pub nls: Nls,
64 pub sys_desc_offset: u32,
65 pub entry_point: u32,
67 pub non_volatile_global_count: u16,
68 pub volatile_global_count: u16,
69 pub custom_syscall_count: u16,
71 pub game_mode: u8,
73 pub game_mode_reserved: u8,
74 pub game_title: String,
75 pub syscall_count: u16,
76 pub syscalls: HashMap<usize, Syscall>,
77}
78
79impl Parser {
80 pub fn new(path: impl AsRef<Path>, nls: Nls) -> Result<Self> {
81 let mut rdr = File::open(path)?;
82 let mut buffer = Vec::new();
83 rdr.read_to_end(&mut buffer)?;
84
85 let mut parser = Parser {
86 buffer: Rc::new(buffer),
87 nls,
88 sys_desc_offset: 0,
89 entry_point: 0,
90 non_volatile_global_count: 0,
91 volatile_global_count: 0,
92 custom_syscall_count: 0,
93 game_mode: 0,
94 game_mode_reserved: 0,
95 game_title: String::new(),
96 syscall_count: 0,
97 syscalls: HashMap::new(),
98 };
99
100 parser.parse_header()?;
101 Ok(parser)
102 }
103
104 pub fn len(&self) -> usize {
105 self.buffer.len()
106 }
107
108 pub fn read_u8(&self, offset: usize) -> Result<u8> {
109 self.buffer
110 .get(offset)
111 .copied()
112 .ok_or_else(|| anyhow!("offset out of bounds"))
113 }
114
115 pub fn read_i8(&self, offset: usize) -> Result<i8> {
116 Ok(self.read_u8(offset)? as i8)
117 }
118
119 pub fn read_u16(&self, offset: usize) -> Result<u16> {
120 if offset + 1 >= self.buffer.len() {
121 return Err(anyhow!("offset out of bounds"));
122 }
123 Ok(u16::from_le_bytes([
124 self.buffer[offset],
125 self.buffer[offset + 1],
126 ]))
127 }
128
129 pub fn read_i16(&self, offset: usize) -> Result<i16> {
130 Ok(self.read_u16(offset)? as i16)
131 }
132
133 pub fn read_u32(&self, offset: usize) -> Result<u32> {
134 if offset + 3 >= self.buffer.len() {
135 return Err(anyhow!("offset out of bounds"));
136 }
137 Ok(u32::from_le_bytes([
138 self.buffer[offset],
139 self.buffer[offset + 1],
140 self.buffer[offset + 2],
141 self.buffer[offset + 3],
142 ]))
143 }
144
145 pub fn read_i32(&self, offset: usize) -> Result<i32> {
146 Ok(self.read_u32(offset)? as i32)
147 }
148
149 pub fn read_f32(&self, offset: usize) -> Result<f32> {
150 if offset + 3 >= self.buffer.len() {
151 return Err(anyhow!("offset out of bounds"));
152 }
153 Ok(f32::from_le_bytes([
154 self.buffer[offset],
155 self.buffer[offset + 1],
156 self.buffer[offset + 2],
157 self.buffer[offset + 3],
158 ]))
159 }
160
161 pub fn read_cstring(&self, offset: usize, len: usize) -> Result<String> {
164 if offset + len > self.buffer.len() {
165 return Err(anyhow!("offset out of bounds"));
166 }
167 let mut raw = Vec::new();
168 for i in 0..len {
169 let b = self.buffer[offset + i];
170 if b == 0 {
171 break;
172 }
173 raw.push(b);
174 }
175
176 let decoded = match self.nls {
177 Nls::ShiftJIS => {
178 let (s, _, had_err) = encoding_rs::SHIFT_JIS.decode(&raw);
179 if had_err {
180 log::warn!("ShiftJIS decode error");
181 }
182 s
183 }
184 Nls::GBK => {
185 let (s, _, had_err) = encoding_rs::GBK.decode(&raw);
186 if had_err {
187 log::warn!("GBK decode error");
188 }
189 s
190 }
191 Nls::UTF8 => {
192 let (s, _, had_err) = encoding_rs::UTF_8.decode(&raw);
193 if had_err {
194 log::warn!("UTF-8 decode error");
195 }
196 s
197 }
198 };
199
200 Ok(decoded.to_string())
201 }
202
203 fn parse_header(&mut self) -> Result<()> {
204 let mut off: usize = 0;
205 self.sys_desc_offset = self.read_u32(off)?;
206
207 off = self.sys_desc_offset as usize;
208 self.entry_point = self.read_u32(off)?;
209 off += size_of::<u32>();
210
211 self.non_volatile_global_count = self.read_u16(off)?;
212 off += size_of::<u16>();
213
214 self.volatile_global_count = self.read_u16(off)?;
215 off += size_of::<u16>();
216
217 self.game_mode = self.read_u8(off)? as u8;
218 off += size_of::<u8>();
219
220 self.game_mode_reserved = self.read_u8(off)? as u8;
221 off += size_of::<u8>();
222
223 let title_len = self.read_u8(off)? as usize;
224 off += size_of::<u8>();
225
226 self.game_title = self.read_cstring(off, title_len)?;
227 off += title_len;
228
229 self.syscall_count = self.read_u16(off)?;
230 off += size_of::<u16>();
231
232 for i in 0..self.syscall_count {
233 let args = self.read_u8(off)?;
234 off += size_of::<u8>();
235
236 let name_len = self.read_u8(off)? as usize;
237 off += size_of::<u8>();
238
239 let name = self.read_cstring(off, name_len)?;
240 off += name_len;
241
242 self.syscalls.insert(i as usize, Syscall { args, name });
243 }
244
245 self.custom_syscall_count = self.read_u16(off)?;
246 if self.custom_syscall_count > 0 {
247 log::warn!("custom syscall count: {}", self.custom_syscall_count);
248 }
249
250 Ok(())
251 }
252
253 pub fn is_code_area(&self, addr: u32) -> bool {
254 addr >= 4 && addr < self.sys_desc_offset
255 }
256
257 pub fn get_syscall(&self, id: u16) -> Option<&Syscall> {
258 self.syscalls.get(&(id as usize))
259 }
260
261 pub fn get_all_syscalls(&self) -> &HashMap<usize, Syscall> {
262 &self.syscalls
263 }
264
265 pub fn export_yaml(&self, path: impl AsRef<Path>) -> Result<()> {
266 let mut syscalls = BTreeMap::new();
267 for (id, sc) in &self.syscalls {
268 syscalls.insert(
269 *id,
270 YamlSyscall {
271 args: sc.args,
272 name: sc.name.clone(),
273 },
274 );
275 }
276
277 let export = YamlExport {
278 nls: self.nls.clone(),
279 custom_syscall_count: self.custom_syscall_count,
280 game_mode: self.game_mode,
281 game_mode_reserved: self.game_mode_reserved,
282 game_title: self.game_title.clone(),
283 syscall_count: self.syscall_count,
284 syscalls,
285 };
286
287 let s = serde_yml::to_string(&export)?;
288 std::fs::write(path, s)?;
289 Ok(())
290 }
291}
292
293impl Parser {
294 pub fn get_title(&self) -> &str {
295 &self.game_title
296 }
297
298 pub fn get_non_volatile_global_count(&self) -> u16 {
299 self.non_volatile_global_count
300 }
301
302 pub fn get_volatile_global_count(&self) -> u16 {
303 self.volatile_global_count
304 }
305
306 pub fn get_screen_size(&self) -> (u32, u32) {
307 match self.game_mode {
308 0 => (640, 480),
309 1 => (800, 600),
310 2 => (1024, 768),
311 3 => (1280, 960),
312 4 => (1600, 1200),
313 5 => (640, 480),
314 6 => (1024, 576),
315 7 => (1024, 640),
316 8 => (1280, 720),
317 9 => (1280, 800),
318 10 => (1440, 810),
319 11 => (1440, 900),
320 12 => (1680, 945),
321 13 => (1680, 1050),
322 14 => (1920, 1080),
323 15 => (1920, 1200),
324 _ => {
325 log::warn!(
326 "unknown resolution: {}, defaulting to 640x480",
327 self.game_mode
328 );
329 (640, 480)
330 }
331 }
332 }
333
334 pub fn get_game_mode(&self) -> u8 {
335 self.game_mode
336 }
337
338 pub fn get_game_mode_reserved(&self) -> u8 {
339 self.game_mode_reserved
340 }
341
342 pub fn get_entry_point(&self) -> u32 {
343 self.entry_point
344 }
345
346 pub fn get_custom_syscall_count(&self) -> u16 {
347 self.custom_syscall_count
348 }
349
350 pub fn get_sys_desc_offset(&self) -> u32 {
351 self.sys_desc_offset
352 }
353}