1use std::collections::{BTreeMap, HashMap};
2use std::fs::File;
3use std::io::Read;
4use std::mem::size_of;
5use std::path::Path;
6use std::rc::Rc;
7use std::str::FromStr;
8use serde::{Serialize, Deserialize};
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.get(offset).copied().ok_or_else(|| anyhow!("offset out of bounds"))
110 }
111
112 pub fn read_i8(&self, offset: usize) -> Result<i8> {
113 Ok(self.read_u8(offset)? as i8)
114 }
115
116 pub fn read_u16(&self, offset: usize) -> Result<u16> {
117 if offset + 1 >= self.buffer.len() {
118 return Err(anyhow!("offset out of bounds"));
119 }
120 Ok(u16::from_le_bytes([self.buffer[offset], self.buffer[offset + 1]]))
121 }
122
123 pub fn read_i16(&self, offset: usize) -> Result<i16> {
124 Ok(self.read_u16(offset)? as i16)
125 }
126
127 pub fn read_u32(&self, offset: usize) -> Result<u32> {
128 if offset + 3 >= self.buffer.len() {
129 return Err(anyhow!("offset out of bounds"));
130 }
131 Ok(u32::from_le_bytes([
132 self.buffer[offset],
133 self.buffer[offset + 1],
134 self.buffer[offset + 2],
135 self.buffer[offset + 3],
136 ]))
137 }
138
139 pub fn read_i32(&self, offset: usize) -> Result<i32> {
140 Ok(self.read_u32(offset)? as i32)
141 }
142
143 pub fn read_f32(&self, offset: usize) -> Result<f32> {
144 if offset + 3 >= self.buffer.len() {
145 return Err(anyhow!("offset out of bounds"));
146 }
147 Ok(f32::from_le_bytes([
148 self.buffer[offset],
149 self.buffer[offset + 1],
150 self.buffer[offset + 2],
151 self.buffer[offset + 3],
152 ]))
153 }
154
155 pub fn read_cstring(&self, offset: usize, len: usize) -> Result<String> {
158 if offset + len > self.buffer.len() {
159 return Err(anyhow!("offset out of bounds"));
160 }
161 let mut raw = Vec::new();
162 for i in 0..len {
163 let b = self.buffer[offset + i];
164 if b == 0 {
165 break;
166 }
167 raw.push(b);
168 }
169
170 let decoded = match self.nls {
171 Nls::ShiftJIS => {
172 let (s, _, had_err) = encoding_rs::SHIFT_JIS.decode(&raw);
173 if had_err {
174 log::warn!("ShiftJIS decode error");
175 }
176 s
177 }
178 Nls::GBK => {
179 let (s, _, had_err) = encoding_rs::GBK.decode(&raw);
180 if had_err {
181 log::warn!("GBK decode error");
182 }
183 s
184 }
185 Nls::UTF8 => {
186 let (s, _, had_err) = encoding_rs::UTF_8.decode(&raw);
187 if had_err {
188 log::warn!("UTF-8 decode error");
189 }
190 s
191 }
192 };
193
194 Ok(decoded.to_string())
195 }
196
197 fn parse_header(&mut self) -> Result<()> {
198 let mut off: usize = 0;
199 self.sys_desc_offset = self.read_u32(off)?;
200
201 off = self.sys_desc_offset as usize;
202 self.entry_point = self.read_u32(off)?;
203 off += size_of::<u32>();
204
205 self.non_volatile_global_count = self.read_u16(off)?;
206 off += size_of::<u16>();
207
208 self.volatile_global_count = self.read_u16(off)?;
209 off += size_of::<u16>();
210
211 self.game_mode = self.read_u8(off)? as u8;
212 off += size_of::<u8>();
213
214 self.game_mode_reserved = self.read_u8(off)? as u8;
215 off += size_of::<u8>();
216
217 let title_len = self.read_u8(off)? as usize;
218 off += size_of::<u8>();
219
220 self.game_title = self.read_cstring(off, title_len)?;
221 off += title_len;
222
223 self.syscall_count = self.read_u16(off)?;
224 off += size_of::<u16>();
225
226 for i in 0..self.syscall_count {
227 let args = self.read_u8(off)?;
228 off += size_of::<u8>();
229
230 let name_len = self.read_u8(off)? as usize;
231 off += size_of::<u8>();
232
233 let name = self.read_cstring(off, name_len)?;
234 off += name_len;
235
236 self.syscalls.insert(i as usize, Syscall { args, name });
237 }
238
239 self.custom_syscall_count = self.read_u16(off)?;
240 if self.custom_syscall_count > 0 {
241 log::warn!("custom syscall count: {}", self.custom_syscall_count);
242 }
243
244 Ok(())
245 }
246
247 pub fn is_code_area(&self, addr: u32) -> bool {
248 addr >= 4 && addr < self.sys_desc_offset
249 }
250
251 pub fn get_syscall(&self, id: u16) -> Option<&Syscall> {
252 self.syscalls.get(&(id as usize))
253 }
254
255 pub fn get_all_syscalls(&self) -> &HashMap<usize, Syscall> {
256 &self.syscalls
257 }
258
259 pub fn export_yaml(&self, path: impl AsRef<Path>) -> Result<()> {
260 let mut syscalls = BTreeMap::new();
261 for (id, sc) in &self.syscalls {
262 syscalls.insert(*id, YamlSyscall { args: sc.args, name: sc.name.clone() });
263 }
264
265 let export = YamlExport {
266 nls: self.nls.clone(),
267 custom_syscall_count: self.custom_syscall_count,
268 game_mode: self.game_mode,
269 game_mode_reserved: self.game_mode_reserved,
270 game_title: self.game_title.clone(),
271 syscall_count: self.syscall_count,
272 syscalls,
273 };
274
275 let s = serde_yml::to_string(&export)?;
276 std::fs::write(path, s)?;
277 Ok(())
278 }
279}
280
281impl Parser {
282 pub fn get_title(&self) -> &str {
283 &self.game_title
284 }
285
286 pub fn get_non_volatile_global_count(&self) -> u16 {
287 self.non_volatile_global_count
288 }
289
290 pub fn get_volatile_global_count(&self) -> u16 {
291 self.volatile_global_count
292 }
293
294 pub fn get_screen_size(&self) -> (u32, u32) {
295 match self.game_mode {
296 0 => (640, 480),
297 1 => (800, 600),
298 2 => (1024, 768),
299 3 => (1280, 960),
300 4 => (1600, 1200),
301 5 => (640, 480),
302 6 => (1024, 576),
303 7 => (1024, 640),
304 8 => (1280, 720),
305 9 => (1280, 800),
306 10 => (1440, 810),
307 11 => (1440, 900),
308 12 => (1680, 945),
309 13 => (1680, 1050),
310 14 => (1920, 1080),
311 15 => (1920, 1200),
312 _ => {
313 log::warn!("unknown resolution: {}, defaulting to 640x480", self.game_mode);
314 (640, 480)
315 }
316 }
317 }
318
319 pub fn get_game_mode(&self) -> u8 {
320 self.game_mode
321 }
322
323 pub fn get_game_mode_reserved(&self) -> u8 {
324 self.game_mode_reserved
325 }
326
327 pub fn get_entry_point(&self) -> u32 {
328 self.entry_point
329 }
330
331 pub fn get_custom_syscall_count(&self) -> u16 {
332 self.custom_syscall_count
333 }
334
335 pub fn get_sys_desc_offset(&self) -> u32 {
336 self.sys_desc_offset
337 }
338}