Skip to main content

siglus_scene_vm/
scene_stream.rs

1use anyhow::{anyhow, bail, Result};
2
3use siglus_assets::scene_pck::CIndex;
4
5/// All fields are little-endian i32 and all offsets are relative to the start of the chunk.
6#[derive(Debug, Clone, Copy)]
7pub struct ScnHeader {
8    pub header_size: i32,
9    pub scn_ofs: i32,
10    pub scn_size: i32,
11    pub str_index_list_ofs: i32,
12    pub str_index_cnt: i32,
13    pub str_list_ofs: i32,
14    pub str_cnt: i32,
15    pub label_list_ofs: i32,
16    pub label_cnt: i32,
17    pub z_label_list_ofs: i32,
18    pub z_label_cnt: i32,
19    pub cmd_label_list_ofs: i32,
20    pub cmd_label_cnt: i32,
21    pub scn_prop_list_ofs: i32,
22    pub scn_prop_cnt: i32,
23    pub scn_prop_name_index_list_ofs: i32,
24    pub scn_prop_name_index_cnt: i32,
25    pub scn_prop_name_list_ofs: i32,
26    pub scn_prop_name_cnt: i32,
27    pub scn_cmd_list_ofs: i32,
28    pub scn_cmd_cnt: i32,
29    pub scn_cmd_name_index_list_ofs: i32,
30    pub scn_cmd_name_index_cnt: i32,
31    pub scn_cmd_name_list_ofs: i32,
32    pub scn_cmd_name_cnt: i32,
33    pub call_prop_name_index_list_ofs: i32,
34    pub call_prop_name_index_cnt: i32,
35    pub call_prop_name_list_ofs: i32,
36    pub call_prop_name_cnt: i32,
37    pub namae_list_ofs: i32,
38    pub namae_cnt: i32,
39    pub read_flag_list_ofs: i32,
40    pub read_flag_cnt: i32,
41}
42
43impl ScnHeader {
44    pub fn read(chunk: &[u8]) -> Result<Self> {
45        // `S_tnm_scn_header` has 33 i32 fields. We only read the early subset we need.
46        let need = 33 * 4;
47        if chunk.len() < need {
48            bail!("scn: chunk too short for header");
49        }
50        let mut p = 0usize;
51        let mut rd = || {
52            let v = i32::from_le_bytes(chunk[p..p + 4].try_into().unwrap());
53            p += 4;
54            v
55        };
56
57        let header_size = rd();
58        let scn_ofs = rd();
59        let scn_size = rd();
60        let str_index_list_ofs = rd();
61        let str_index_cnt = rd();
62        let str_list_ofs = rd();
63        let str_cnt = rd();
64        let label_list_ofs = rd();
65        let label_cnt = rd();
66        let z_label_list_ofs = rd();
67        let z_label_cnt = rd();
68        let cmd_label_list_ofs = rd();
69        let cmd_label_cnt = rd();
70        let scn_prop_list_ofs = rd();
71        let scn_prop_cnt = rd();
72        let scn_prop_name_index_list_ofs = rd();
73        let scn_prop_name_index_cnt = rd();
74        let scn_prop_name_list_ofs = rd();
75        let scn_prop_name_cnt = rd();
76        let scn_cmd_list_ofs = rd();
77        let scn_cmd_cnt = rd();
78        let scn_cmd_name_index_list_ofs = rd();
79        let scn_cmd_name_index_cnt = rd();
80        let scn_cmd_name_list_ofs = rd();
81        let scn_cmd_name_cnt = rd();
82        let call_prop_name_index_list_ofs = rd();
83        let call_prop_name_index_cnt = rd();
84        let call_prop_name_list_ofs = rd();
85        let call_prop_name_cnt = rd();
86        let namae_list_ofs = rd();
87        let namae_cnt = rd();
88        let read_flag_list_ofs = rd();
89        let read_flag_cnt = rd();
90
91        Ok(Self {
92            header_size,
93            scn_ofs,
94            scn_size,
95            str_index_list_ofs,
96            str_index_cnt,
97            str_cnt,
98            str_list_ofs,
99            label_list_ofs,
100            label_cnt,
101            z_label_list_ofs,
102            z_label_cnt,
103            cmd_label_list_ofs,
104            cmd_label_cnt,
105            scn_prop_list_ofs,
106            scn_prop_cnt,
107            scn_prop_name_index_list_ofs,
108            scn_prop_name_index_cnt,
109            scn_prop_name_list_ofs,
110            scn_prop_name_cnt,
111            scn_cmd_list_ofs,
112            scn_cmd_cnt,
113            scn_cmd_name_index_list_ofs,
114            scn_cmd_name_index_cnt,
115            scn_cmd_name_list_ofs,
116            scn_cmd_name_cnt,
117            call_prop_name_index_list_ofs,
118            call_prop_name_index_cnt,
119            call_prop_name_list_ofs,
120            call_prop_name_cnt,
121            namae_list_ofs,
122            namae_cnt,
123            read_flag_list_ofs,
124            read_flag_cnt,
125        })
126    }
127}
128
129fn read_indexed_utf16_name_map(
130    chunk: &[u8],
131    index_list_ofs: usize,
132    count: usize,
133    list_ofs: usize,
134) -> Result<std::collections::HashMap<u32, String>> {
135    let mut out = std::collections::HashMap::new();
136    if index_list_ofs + count * 8 > chunk.len() || list_ofs > chunk.len() {
137        return Ok(out);
138    }
139    for i in 0..count {
140        let idx = CIndex::read(chunk, index_list_ofs + i * 8)?;
141        let o = idx.offset.max(0) as usize;
142        let n = idx.size.max(0) as usize;
143        let byte_off = list_ofs
144            .checked_add(o * 2)
145            .ok_or_else(|| anyhow!("scn: name offset overflow"))?;
146        let byte_end = byte_off
147            .checked_add(n * 2)
148            .ok_or_else(|| anyhow!("scn: name size overflow"))?;
149        if byte_end > chunk.len() {
150            continue;
151        }
152        let mut u16s = Vec::with_capacity(n);
153        for j in 0..n {
154            let p = byte_off + j * 2;
155            let w = u16::from_le_bytes([chunk[p], chunk[p + 1]]);
156            if w == 0 {
157                break;
158            }
159            u16s.push(w);
160        }
161        let s = String::from_utf16_lossy(&u16s);
162        if !s.is_empty() {
163            out.insert(i as u32, s);
164        }
165    }
166    Ok(out)
167}
168
169#[derive(Debug, Clone)]
170pub struct SceneStream<'a> {
171    pub chunk: &'a [u8],
172    pub header: ScnHeader,
173    pub scn: &'a [u8],
174    pub str_index_list: &'a [u8],
175    pub str_list: &'a [u8],
176    pub label_list: &'a [u8],
177    pub z_label_list: &'a [u8],
178    pub scn_prop_name_map: std::collections::HashMap<u32, String>,
179    pub scn_cmd_name_map: std::collections::HashMap<u32, String>,
180    pub call_prop_name_map: std::collections::HashMap<u32, String>,
181    pub pc: usize,
182}
183
184impl<'a> SceneStream<'a> {
185    pub fn new(chunk: &'a [u8]) -> Result<Self> {
186        let header = ScnHeader::read(chunk)?;
187        let scn_ofs = header.scn_ofs.max(0) as usize;
188        let scn_size = header.scn_size.max(0) as usize;
189        let scn_end = scn_ofs
190            .checked_add(scn_size)
191            .ok_or_else(|| anyhow!("scn: scn_size overflow"))?;
192        if scn_end > chunk.len() {
193            bail!("scn: scn stream out of bounds");
194        }
195        let scn = &chunk[scn_ofs..scn_end];
196
197        let str_index_list_ofs = header.str_index_list_ofs.max(0) as usize;
198        let str_index_cnt = header.str_index_cnt.max(0) as usize;
199        let str_index_list_end = str_index_list_ofs
200            .checked_add(str_index_cnt * 8)
201            .ok_or_else(|| anyhow!("scn: str_index_list overflow"))?;
202        if str_index_list_end > chunk.len() {
203            bail!("scn: str_index_list out of bounds");
204        }
205        let str_index_list = &chunk[str_index_list_ofs..str_index_list_end];
206
207        let str_list_ofs = header.str_list_ofs.max(0) as usize;
208        if str_list_ofs > chunk.len() {
209            bail!("scn: str_list_ofs out of bounds");
210        }
211        let str_list = &chunk[str_list_ofs..];
212
213        let label_list_ofs = header.label_list_ofs.max(0) as usize;
214        let label_cnt = header.label_cnt.max(0) as usize;
215        let label_list_end = label_list_ofs
216            .checked_add(label_cnt * 4)
217            .ok_or_else(|| anyhow!("scn: label_list overflow"))?;
218        if label_list_end > chunk.len() {
219            bail!("scn: label_list out of bounds");
220        }
221        let label_list = &chunk[label_list_ofs..label_list_end];
222
223        let z_label_list_ofs = header.z_label_list_ofs.max(0) as usize;
224        let z_label_cnt = header.z_label_cnt.max(0) as usize;
225        let z_label_list_end = z_label_list_ofs
226            .checked_add(z_label_cnt * 4)
227            .ok_or_else(|| anyhow!("scn: z_label_list overflow"))?;
228        if z_label_list_end > chunk.len() {
229            bail!("scn: z_label_list out of bounds");
230        }
231        let z_label_list = &chunk[z_label_list_ofs..z_label_list_end];
232
233        let scn_prop_name_map = read_indexed_utf16_name_map(
234            chunk,
235            header.scn_prop_name_index_list_ofs.max(0) as usize,
236            header.scn_prop_name_cnt.max(0) as usize,
237            header.scn_prop_name_list_ofs.max(0) as usize,
238        )?;
239        let scn_cmd_name_map = read_indexed_utf16_name_map(
240            chunk,
241            header.scn_cmd_name_index_list_ofs.max(0) as usize,
242            header.scn_cmd_name_cnt.max(0) as usize,
243            header.scn_cmd_name_list_ofs.max(0) as usize,
244        )?;
245        let call_prop_name_map = read_indexed_utf16_name_map(
246            chunk,
247            header.call_prop_name_index_list_ofs.max(0) as usize,
248            header.call_prop_name_cnt.max(0) as usize,
249            header.call_prop_name_list_ofs.max(0) as usize,
250        )?;
251
252        Ok(Self {
253            chunk,
254            header,
255            scn,
256            str_index_list,
257            str_list,
258            label_list,
259            z_label_list,
260            scn_prop_name_map,
261            scn_cmd_name_map,
262            call_prop_name_map,
263            pc: 0,
264        })
265    }
266
267    pub fn eof(&self) -> bool {
268        self.pc >= self.scn.len()
269    }
270
271    pub fn get_prg_cntr(&self) -> usize {
272        self.pc
273    }
274
275    pub fn set_prg_cntr(&mut self, prg_cntr: usize) -> Result<()> {
276        if prg_cntr > self.scn.len() {
277            bail!("scn: prg_cntr out of bounds");
278        }
279        self.pc = prg_cntr;
280        Ok(())
281    }
282
283    pub fn jump_to_label(&mut self, label_no: usize) -> Result<()> {
284        let cnt = self.header.label_cnt.max(0) as usize;
285        if label_no >= cnt {
286            bail!("scn: label_no out of range");
287        }
288        let off = label_no * 4;
289        let label_offset = i32::from_le_bytes(self.label_list[off..off + 4].try_into().unwrap());
290        self.set_prg_cntr(label_offset.max(0) as usize)
291    }
292
293    pub fn jump_to_z_label(&mut self, z_no: usize) -> Result<()> {
294        let cnt = self.header.z_label_cnt.max(0) as usize;
295        if z_no >= cnt {
296            bail!("scn: z_label out of range");
297        }
298        let off = z_no * 4;
299        let z_offset = i32::from_le_bytes(self.z_label_list[off..off + 4].try_into().unwrap());
300        self.set_prg_cntr(z_offset.max(0) as usize)
301    }
302
303    pub fn scn_cmd_offset(&self, cmd_no: usize) -> Result<usize> {
304        let cnt = self.header.scn_cmd_cnt.max(0) as usize;
305        if cmd_no >= cnt {
306            bail!("scn: scn_cmd_no out of range");
307        }
308        let ofs = self.header.scn_cmd_list_ofs.max(0) as usize;
309        let byte_ofs = ofs
310            .checked_add(cmd_no * 4)
311            .ok_or_else(|| anyhow!("scn: scn_cmd_list overflow"))?;
312        let byte_end = byte_ofs
313            .checked_add(4)
314            .ok_or_else(|| anyhow!("scn: scn_cmd entry overflow"))?;
315        if byte_end > self.chunk.len() {
316            bail!("scn: scn_cmd_list out of bounds");
317        }
318        let cmd_offset = i32::from_le_bytes(self.chunk[byte_ofs..byte_end].try_into().unwrap());
319        let prg = cmd_offset.max(0) as usize;
320        if prg > self.scn.len() {
321            bail!("scn: scn_cmd offset out of bounds");
322        }
323        Ok(prg)
324    }
325
326    pub fn next_scn_cmd_offset_after(&self, start: usize) -> Result<Option<usize>> {
327        let cnt = self.header.scn_cmd_cnt.max(0) as usize;
328        let mut next: Option<usize> = None;
329        for cmd_no in 0..cnt {
330            let off = self.scn_cmd_offset(cmd_no)?;
331            if off > start {
332                next = Some(match next {
333                    Some(cur) => cur.min(off),
334                    None => off,
335                });
336            }
337        }
338        Ok(next)
339    }
340
341    pub fn pop_u8(&mut self) -> Result<u8> {
342        if self.pc + 1 > self.scn.len() {
343            bail!("scn: pop_u8 past end");
344        }
345        let v = self.scn[self.pc];
346        self.pc += 1;
347        Ok(v)
348    }
349
350    pub fn pop_u16(&mut self) -> Result<u16> {
351        if self.pc + 2 > self.scn.len() {
352            bail!("scn: pop_u16 past end");
353        }
354        let v = u16::from_le_bytes(self.scn[self.pc..self.pc + 2].try_into().unwrap());
355        self.pc += 2;
356        Ok(v)
357    }
358
359    pub fn pop_i32(&mut self) -> Result<i32> {
360        if self.pc + 4 > self.scn.len() {
361            bail!("scn: pop_i32 past end");
362        }
363        let v = i32::from_le_bytes(self.scn[self.pc..self.pc + 4].try_into().unwrap());
364        self.pc += 4;
365        Ok(v)
366    }
367
368    pub fn pop_str(&mut self) -> Result<String> {
369        let str_id = self.pop_i32()?;
370        self.get_string(str_id as usize)
371    }
372
373    pub fn get_string(&self, str_id: usize) -> Result<String> {
374        let str_cnt = self.header.str_cnt.max(0) as usize;
375        if str_id >= str_cnt {
376            bail!("scn: str_id out of range");
377        }
378        let idx = CIndex::read(self.str_index_list, str_id * 8)?;
379        let o = idx.offset.max(0) as usize;
380        let n = idx.size.max(0) as usize;
381
382        let byte_off = o
383            .checked_mul(2)
384            .ok_or_else(|| anyhow!("scn: str offset overflow"))?;
385        let byte_end = byte_off
386            .checked_add(n * 2)
387            .ok_or_else(|| anyhow!("scn: str size overflow"))?;
388        if byte_end > self.str_list.len() {
389            bail!("scn: str data out of bounds");
390        }
391
392        // XOR per-u16: wchar ^= (28807 * str_index)
393        let key = (28807u32).wrapping_mul(str_id as u32) as u16;
394
395        let mut u16s = Vec::with_capacity(n);
396        for j in 0..n {
397            let p = byte_off + j * 2;
398            let mut w = u16::from_le_bytes([self.str_list[p], self.str_list[p + 1]]);
399            w ^= key;
400            if w == 0 {
401                break;
402            }
403            u16s.push(w);
404        }
405        Ok(String::from_utf16_lossy(&u16s))
406    }
407}