1use anyhow::{anyhow, bail, Result};
2
3use siglus_assets::scene_pck::CIndex;
4
5#[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 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 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}