hcb2lua_decompiler/
parser.rs

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    /// How many arguments the syscall takes from the stack.
37    pub args: u8,
38    /// Name of the syscall.
39    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    /// Entry point (offset) of the script.
66    pub entry_point: u32,
67    pub non_volatile_global_count: u16,
68    pub volatile_global_count: u16,
69    /// Register a script function as syscall (usually unused).
70    pub custom_syscall_count: u16,
71    /// Game resolution id.
72    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    /// Read a C-style string with a maximum length `len` (may contain an early NUL).
156    /// Then decode it into UTF-8 according to the configured NLS.
157    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}