1use anyhow::{Result, anyhow, bail};
2use std::cmp::Reverse;
3use std::collections::{BinaryHeap, HashMap};
4use std::ops::Range;
5use std::sync::Arc;
6
7use siglus_assets::scene_pck::{CIndex, SceneStringCodec};
8
9#[derive(Debug, Clone, Copy)]
11pub struct ScnHeader {
12 pub header_size: i32,
13 pub scn_ofs: i32,
14 pub scn_size: i32,
15 pub str_index_list_ofs: i32,
16 pub str_index_cnt: i32,
17 pub str_list_ofs: i32,
18 pub str_cnt: i32,
19 pub label_list_ofs: i32,
20 pub label_cnt: i32,
21 pub z_label_list_ofs: i32,
22 pub z_label_cnt: i32,
23 pub cmd_label_list_ofs: i32,
24 pub cmd_label_cnt: i32,
25 pub scn_prop_list_ofs: i32,
26 pub scn_prop_cnt: i32,
27 pub scn_prop_name_index_list_ofs: i32,
28 pub scn_prop_name_index_cnt: i32,
29 pub scn_prop_name_list_ofs: i32,
30 pub scn_prop_name_cnt: i32,
31 pub scn_cmd_list_ofs: i32,
32 pub scn_cmd_cnt: i32,
33 pub scn_cmd_name_index_list_ofs: i32,
34 pub scn_cmd_name_index_cnt: i32,
35 pub scn_cmd_name_list_ofs: i32,
36 pub scn_cmd_name_cnt: i32,
37 pub call_prop_name_index_list_ofs: i32,
38 pub call_prop_name_index_cnt: i32,
39 pub call_prop_name_list_ofs: i32,
40 pub call_prop_name_cnt: i32,
41 pub namae_list_ofs: i32,
42 pub namae_cnt: i32,
43 pub read_flag_list_ofs: i32,
44 pub read_flag_cnt: i32,
45}
46
47impl ScnHeader {
48 pub fn read(chunk: &[u8]) -> Result<Self> {
49 let need = 33 * 4;
51 if chunk.len() < need {
52 bail!("scn: chunk too short for header");
53 }
54 let mut p = 0usize;
55 let mut rd = || {
56 let v = i32::from_le_bytes(chunk[p..p + 4].try_into().unwrap());
57 p += 4;
58 v
59 };
60
61 let header_size = rd();
62 let scn_ofs = rd();
63 let scn_size = rd();
64 let str_index_list_ofs = rd();
65 let str_index_cnt = rd();
66 let str_list_ofs = rd();
67 let str_cnt = rd();
68 let label_list_ofs = rd();
69 let label_cnt = rd();
70 let z_label_list_ofs = rd();
71 let z_label_cnt = rd();
72 let cmd_label_list_ofs = rd();
73 let cmd_label_cnt = rd();
74 let scn_prop_list_ofs = rd();
75 let scn_prop_cnt = rd();
76 let scn_prop_name_index_list_ofs = rd();
77 let scn_prop_name_index_cnt = rd();
78 let scn_prop_name_list_ofs = rd();
79 let scn_prop_name_cnt = rd();
80 let scn_cmd_list_ofs = rd();
81 let scn_cmd_cnt = rd();
82 let scn_cmd_name_index_list_ofs = rd();
83 let scn_cmd_name_index_cnt = rd();
84 let scn_cmd_name_list_ofs = rd();
85 let scn_cmd_name_cnt = rd();
86 let call_prop_name_index_list_ofs = rd();
87 let call_prop_name_index_cnt = rd();
88 let call_prop_name_list_ofs = rd();
89 let call_prop_name_cnt = rd();
90 let namae_list_ofs = rd();
91 let namae_cnt = rd();
92 let read_flag_list_ofs = rd();
93 let read_flag_cnt = rd();
94
95 Ok(Self {
96 header_size,
97 scn_ofs,
98 scn_size,
99 str_index_list_ofs,
100 str_index_cnt,
101 str_cnt,
102 str_list_ofs,
103 label_list_ofs,
104 label_cnt,
105 z_label_list_ofs,
106 z_label_cnt,
107 cmd_label_list_ofs,
108 cmd_label_cnt,
109 scn_prop_list_ofs,
110 scn_prop_cnt,
111 scn_prop_name_index_list_ofs,
112 scn_prop_name_index_cnt,
113 scn_prop_name_list_ofs,
114 scn_prop_name_cnt,
115 scn_cmd_list_ofs,
116 scn_cmd_cnt,
117 scn_cmd_name_index_list_ofs,
118 scn_cmd_name_index_cnt,
119 scn_cmd_name_list_ofs,
120 scn_cmd_name_cnt,
121 call_prop_name_index_list_ofs,
122 call_prop_name_index_cnt,
123 call_prop_name_list_ofs,
124 call_prop_name_cnt,
125 namae_list_ofs,
126 namae_cnt,
127 read_flag_list_ofs,
128 read_flag_cnt,
129 })
130 }
131}
132
133fn read_indexed_utf16_name_map(
134 chunk: &[u8],
135 index_list_ofs: usize,
136 count: usize,
137 list_ofs: usize,
138) -> Result<std::collections::HashMap<u32, String>> {
139 let mut out = std::collections::HashMap::new();
140 if index_list_ofs + count * 8 > chunk.len() || list_ofs > chunk.len() {
141 return Ok(out);
142 }
143 for i in 0..count {
144 let idx = CIndex::read(chunk, index_list_ofs + i * 8)?;
145 let o = idx.offset.max(0) as usize;
146 let n = idx.size.max(0) as usize;
147 let byte_off = list_ofs
148 .checked_add(o * 2)
149 .ok_or_else(|| anyhow!("scn: name offset overflow"))?;
150 let byte_end = byte_off
151 .checked_add(n * 2)
152 .ok_or_else(|| anyhow!("scn: name size overflow"))?;
153 if byte_end > chunk.len() {
154 continue;
155 }
156 let mut u16s = Vec::with_capacity(n);
157 for j in 0..n {
158 let p = byte_off + j * 2;
159 let w = u16::from_le_bytes([chunk[p], chunk[p + 1]]);
160 if w == 0 {
161 break;
162 }
163 u16s.push(w);
164 }
165 let s = String::from_utf16_lossy(&u16s);
166 if !s.is_empty() {
167 out.insert(i as u32, s);
168 }
169 }
170 Ok(out)
171}
172
173fn read_i32_at(buf: &[u8], pos: &mut usize) -> Option<i32> {
174 let end = pos.checked_add(4)?;
175 let bytes = buf.get(*pos..end)?;
176 *pos = end;
177 Some(i32::from_le_bytes(bytes.try_into().ok()?))
178}
179
180fn skip_arg_form(buf: &[u8], pos: &mut usize, depth: usize) -> bool {
181 if depth > 64 {
184 return false;
185 }
186 let Some(form) = read_i32_at(buf, pos) else {
187 return false;
188 };
189 if form != crate::runtime::constants::fm::LIST {
190 return true;
191 }
192 skip_arg_form_list(buf, pos, depth + 1)
193}
194
195fn skip_arg_form_list(buf: &[u8], pos: &mut usize, depth: usize) -> bool {
196 let Some(count) = read_i32_at(buf, pos) else {
197 return false;
198 };
199 if count < 0 {
200 return false;
201 }
202 for _ in 0..count as usize {
203 if !skip_arg_form(buf, pos, depth) {
204 return false;
205 }
206 }
207 true
208}
209
210fn read_flag_line_no_from_chunk(
211 chunk: &[u8],
212 header: &ScnHeader,
213 read_flag_no: usize,
214) -> Option<i32> {
215 let count = usize::try_from(header.read_flag_cnt).ok()?;
216 if read_flag_no >= count {
217 return None;
218 }
219 let base = usize::try_from(header.read_flag_list_ofs).ok()?;
220 let start = base.checked_add(read_flag_no.checked_mul(4)?)?;
221 let end = start.checked_add(4)?;
222 let bytes = chunk.get(start..end)?;
223 Some(i32::from_le_bytes(bytes.try_into().ok()?))
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
227struct ReadFlagScanState {
228 pc: usize,
229 line_no: i32,
230 next_read_flag: usize,
231}
232
233#[derive(Debug, Clone, Copy)]
234struct ReadFlagScanPred {
235 prev: ReadFlagScanState,
236 command_read_flag: Option<(usize, i32)>,
239}
240
241#[derive(Debug, Clone, Copy)]
242struct ReadFlagScanNode {
243 path_count: u8,
246 pred: Option<ReadFlagScanPred>,
247}
248
249#[derive(Debug, Clone, Copy)]
250enum ReadFlagScanKind {
251 Other,
252 Text(i32),
253 Command,
254}
255
256fn scan_instruction(scn: &[u8], pc: usize, line_no: i32) -> Option<(usize, i32, ReadFlagScanKind)> {
257 use crate::runtime::constants::cd;
258
259 let opcode = *scn.get(pc)?;
260 let mut pos = pc.checked_add(1)?;
261 let mut next_line = line_no;
262 let kind = match opcode {
263 cd::NONE => return None,
264 cd::NL => {
265 next_line = read_i32_at(scn, &mut pos)?;
266 ReadFlagScanKind::Other
267 }
268 cd::PUSH => {
269 read_i32_at(scn, &mut pos)?;
270 read_i32_at(scn, &mut pos)?;
271 ReadFlagScanKind::Other
272 }
273 cd::POP | cd::COPY => {
274 read_i32_at(scn, &mut pos)?;
275 ReadFlagScanKind::Other
276 }
277 cd::PROPERTY
278 | cd::COPY_ELM
279 | cd::ELM_POINT
280 | cd::ARG
281 | cd::EOF
282 | cd::NAME
283 | cd::SEL_BLOCK_START
284 | cd::SEL_BLOCK_END => ReadFlagScanKind::Other,
285 cd::DEC_PROP => {
286 read_i32_at(scn, &mut pos)?;
287 read_i32_at(scn, &mut pos)?;
288 ReadFlagScanKind::Other
289 }
290 cd::GOTO | cd::GOTO_TRUE | cd::GOTO_FALSE => {
291 read_i32_at(scn, &mut pos)?;
292 ReadFlagScanKind::Other
293 }
294 cd::GOSUB | cd::GOSUBSTR => {
295 read_i32_at(scn, &mut pos)?;
296 if !skip_arg_form_list(scn, &mut pos, 0) {
297 return None;
298 }
299 ReadFlagScanKind::Other
300 }
301 cd::RETURN => {
302 if !skip_arg_form_list(scn, &mut pos, 0) {
303 return None;
304 }
305 ReadFlagScanKind::Other
306 }
307 cd::ASSIGN => {
308 read_i32_at(scn, &mut pos)?;
309 read_i32_at(scn, &mut pos)?;
310 read_i32_at(scn, &mut pos)?;
311 ReadFlagScanKind::Other
312 }
313 cd::OPERATE_1 => {
314 read_i32_at(scn, &mut pos)?;
315 scn.get(pos)?;
316 pos += 1;
317 ReadFlagScanKind::Other
318 }
319 cd::OPERATE_2 => {
320 read_i32_at(scn, &mut pos)?;
321 read_i32_at(scn, &mut pos)?;
322 scn.get(pos)?;
323 pos += 1;
324 ReadFlagScanKind::Other
325 }
326 cd::COMMAND => {
327 read_i32_at(scn, &mut pos)?;
331 if !skip_arg_form_list(scn, &mut pos, 0) {
332 return None;
333 }
334 let named_count = read_i32_at(scn, &mut pos)?;
335 if named_count < 0 {
336 return None;
337 }
338 let named_bytes = (named_count as usize).checked_mul(4)?;
339 pos = pos.checked_add(named_bytes)?;
340 if pos > scn.len() {
341 return None;
342 }
343 read_i32_at(scn, &mut pos)?;
345 ReadFlagScanKind::Command
346 }
347 cd::TEXT => {
348 let flag_no = read_i32_at(scn, &mut pos)?;
349 ReadFlagScanKind::Text(flag_no)
350 }
351 _ => return None,
352 };
353 Some((pos, next_line, kind))
354}
355
356fn add_read_flag_scan_state(
357 nodes: &mut HashMap<ReadFlagScanState, ReadFlagScanNode>,
358 queue: &mut BinaryHeap<Reverse<ReadFlagScanState>>,
359 next: ReadFlagScanState,
360 incoming_paths: u8,
361 pred: ReadFlagScanPred,
362) {
363 use std::collections::hash_map::Entry;
364
365 let incoming_paths = incoming_paths.min(2);
366 match nodes.entry(next) {
367 Entry::Vacant(entry) => {
368 entry.insert(ReadFlagScanNode {
369 path_count: incoming_paths,
370 pred: (incoming_paths == 1).then_some(pred),
371 });
372 queue.push(Reverse(next));
373 }
374 Entry::Occupied(mut entry) => {
375 let node = entry.get_mut();
379 let merged = node.path_count.saturating_add(incoming_paths).min(2);
380 if merged != node.path_count {
381 node.path_count = merged;
382 if merged != 1 {
383 node.pred = None;
384 }
385 }
386 }
387 }
388}
389
390fn resolve_command_read_flags(
409 scn: &[u8],
410 chunk: &[u8],
411 header: &ScnHeader,
412) -> Option<HashMap<usize, i32>> {
413 let read_flag_cnt = usize::try_from(header.read_flag_cnt).ok()?;
414 let mut read_flag_lines = Vec::with_capacity(read_flag_cnt);
415 for flag_no in 0..read_flag_cnt {
416 read_flag_lines.push(read_flag_line_no_from_chunk(chunk, header, flag_no)?);
417 }
418
419 let start = ReadFlagScanState {
420 pc: 0,
421 line_no: 0,
422 next_read_flag: 0,
423 };
424 let mut nodes = HashMap::new();
425 nodes.insert(
426 start,
427 ReadFlagScanNode {
428 path_count: 1,
429 pred: None,
430 },
431 );
432 let mut queue = BinaryHeap::new();
433 queue.push(Reverse(start));
434
435 let mut complete_paths = 0u8;
436 let mut terminal = None;
437
438 while let Some(Reverse(state)) = queue.pop() {
439 let node = *nodes.get(&state)?;
440 if state.pc == scn.len() {
441 if state.next_read_flag == read_flag_cnt {
442 complete_paths = complete_paths.saturating_add(node.path_count).min(2);
443 if complete_paths == 1 && node.path_count == 1 {
444 terminal = Some(state);
445 } else {
446 terminal = None;
447 }
448 }
449 continue;
450 }
451
452 let (next_pc, next_line, kind) = match scan_instruction(scn, state.pc, state.line_no) {
453 Some(v) => v,
454 None => continue,
455 };
456
457 match kind {
458 ReadFlagScanKind::Text(flag_no) => {
459 if flag_no < 0
460 || flag_no as usize != state.next_read_flag
461 || state.next_read_flag >= read_flag_cnt
462 || read_flag_lines[state.next_read_flag] != state.line_no
463 {
464 continue;
465 }
466 let next = ReadFlagScanState {
467 pc: next_pc,
468 line_no: next_line,
469 next_read_flag: state.next_read_flag + 1,
470 };
471 add_read_flag_scan_state(
472 &mut nodes,
473 &mut queue,
474 next,
475 node.path_count,
476 ReadFlagScanPred {
477 prev: state,
478 command_read_flag: None,
479 },
480 );
481 }
482 ReadFlagScanKind::Command => {
483 add_read_flag_scan_state(
485 &mut nodes,
486 &mut queue,
487 ReadFlagScanState {
488 pc: next_pc,
489 line_no: next_line,
490 next_read_flag: state.next_read_flag,
491 },
492 node.path_count,
493 ReadFlagScanPred {
494 prev: state,
495 command_read_flag: None,
496 },
497 );
498
499 if state.next_read_flag < read_flag_cnt
502 && read_flag_lines[state.next_read_flag] == state.line_no
503 {
504 let mut after_flag = next_pc;
505 if read_i32_at(scn, &mut after_flag) == i32::try_from(state.next_read_flag).ok()
506 {
507 add_read_flag_scan_state(
508 &mut nodes,
509 &mut queue,
510 ReadFlagScanState {
511 pc: after_flag,
512 line_no: next_line,
513 next_read_flag: state.next_read_flag + 1,
514 },
515 node.path_count,
516 ReadFlagScanPred {
517 prev: state,
518 command_read_flag: Some((next_pc, state.next_read_flag as i32)),
519 },
520 );
521 }
522 }
523 }
524 ReadFlagScanKind::Other => {
525 add_read_flag_scan_state(
526 &mut nodes,
527 &mut queue,
528 ReadFlagScanState {
529 pc: next_pc,
530 line_no: next_line,
531 next_read_flag: state.next_read_flag,
532 },
533 node.path_count,
534 ReadFlagScanPred {
535 prev: state,
536 command_read_flag: None,
537 },
538 );
539 }
540 }
541 }
542
543 if complete_paths != 1 {
544 return None;
545 }
546
547 let mut command_read_flags = HashMap::new();
548 let mut state = terminal?;
549 while state != start {
550 let pred = nodes.get(&state)?.pred?;
551 if let Some((pc, flag_no)) = pred.command_read_flag {
552 command_read_flags.insert(pc, flag_no);
553 }
554 state = pred.prev;
555 }
556 Some(command_read_flags)
557}
558
559#[derive(Debug, Clone)]
560pub struct SceneStream<'a> {
561 owned_chunk: Option<Arc<[u8]>>,
564 owned_pack: Option<Arc<Vec<u8>>>,
565 pub chunk: &'a [u8],
566 pub header: ScnHeader,
567 pub scn: &'a [u8],
568 pub str_index_list: &'a [u8],
569 pub str_list: &'a [u8],
570 pub label_list: &'a [u8],
571 pub z_label_list: &'a [u8],
572 pub scn_prop_name_map: Arc<std::collections::HashMap<u32, String>>,
573 pub scn_cmd_name_map: Arc<std::collections::HashMap<u32, String>>,
574 pub call_prop_name_map: Arc<std::collections::HashMap<u32, String>>,
575 command_read_flags: Option<Arc<HashMap<usize, i32>>>,
576 pub string_codec: SceneStringCodec,
577 pub pc: usize,
578}
579
580impl<'a> SceneStream<'a> {
581 pub(crate) fn instruction_end(&self, pc: usize) -> Option<usize> {
582 let (mut end, _, kind) = scan_instruction(self.scn, pc, -1)?;
583 if matches!(kind, ReadFlagScanKind::Command)
584 && self.command_read_flags.as_ref()?.contains_key(&end)
585 {
586 end = end.checked_add(4)?;
587 }
588 (end <= self.scn.len()).then_some(end)
589 }
590
591 pub fn new(chunk: &'a [u8]) -> Result<Self> {
592 Self::new_with_string_codec(chunk, SceneStringCodec::Xor)
593 }
594
595 pub fn new_with_string_codec(chunk: &'a [u8], string_codec: SceneStringCodec) -> Result<Self> {
596 let header = ScnHeader::read(chunk)?;
597 let scn_ofs = header.scn_ofs.max(0) as usize;
598 let scn_size = header.scn_size.max(0) as usize;
599 let scn_end = scn_ofs
600 .checked_add(scn_size)
601 .ok_or_else(|| anyhow!("scn: scn_size overflow"))?;
602 if scn_end > chunk.len() {
603 bail!("scn: scn stream out of bounds");
604 }
605 let scn = &chunk[scn_ofs..scn_end];
606
607 let str_index_list_ofs = header.str_index_list_ofs.max(0) as usize;
608 let str_index_cnt = header.str_index_cnt.max(0) as usize;
609 let str_index_list_end = str_index_list_ofs
610 .checked_add(str_index_cnt * 8)
611 .ok_or_else(|| anyhow!("scn: str_index_list overflow"))?;
612 if str_index_list_end > chunk.len() {
613 bail!("scn: str_index_list out of bounds");
614 }
615 let str_index_list = &chunk[str_index_list_ofs..str_index_list_end];
616
617 let str_list_ofs = header.str_list_ofs.max(0) as usize;
618 if str_list_ofs > chunk.len() {
619 bail!("scn: str_list_ofs out of bounds");
620 }
621 let str_list = &chunk[str_list_ofs..];
622
623 let label_list_ofs = header.label_list_ofs.max(0) as usize;
624 let label_cnt = header.label_cnt.max(0) as usize;
625 let label_list_end = label_list_ofs
626 .checked_add(label_cnt * 4)
627 .ok_or_else(|| anyhow!("scn: label_list overflow"))?;
628 if label_list_end > chunk.len() {
629 bail!("scn: label_list out of bounds");
630 }
631 let label_list = &chunk[label_list_ofs..label_list_end];
632
633 let z_label_list_ofs = header.z_label_list_ofs.max(0) as usize;
634 let z_label_cnt = header.z_label_cnt.max(0) as usize;
635 let z_label_list_end = z_label_list_ofs
636 .checked_add(z_label_cnt * 4)
637 .ok_or_else(|| anyhow!("scn: z_label_list overflow"))?;
638 if z_label_list_end > chunk.len() {
639 bail!("scn: z_label_list out of bounds");
640 }
641 let z_label_list = &chunk[z_label_list_ofs..z_label_list_end];
642
643 let scn_prop_name_map = read_indexed_utf16_name_map(
644 chunk,
645 header.scn_prop_name_index_list_ofs.max(0) as usize,
646 header.scn_prop_name_cnt.max(0) as usize,
647 header.scn_prop_name_list_ofs.max(0) as usize,
648 )?;
649 let scn_cmd_name_map = read_indexed_utf16_name_map(
650 chunk,
651 header.scn_cmd_name_index_list_ofs.max(0) as usize,
652 header.scn_cmd_name_cnt.max(0) as usize,
653 header.scn_cmd_name_list_ofs.max(0) as usize,
654 )?;
655 let call_prop_name_map = read_indexed_utf16_name_map(
656 chunk,
657 header.call_prop_name_index_list_ofs.max(0) as usize,
658 header.call_prop_name_cnt.max(0) as usize,
659 header.call_prop_name_list_ofs.max(0) as usize,
660 )?;
661 let command_read_flags = resolve_command_read_flags(scn, chunk, &header).map(Arc::new);
662
663 Ok(Self {
664 owned_chunk: None,
665 owned_pack: None,
666 chunk,
667 header,
668 scn,
669 str_index_list,
670 str_list,
671 label_list,
672 z_label_list,
673 scn_prop_name_map: Arc::new(scn_prop_name_map),
674 scn_cmd_name_map: Arc::new(scn_cmd_name_map),
675 call_prop_name_map: Arc::new(call_prop_name_map),
676 command_read_flags,
677 string_codec,
678 pc: 0,
679 })
680 }
681
682 pub fn new_owned_with_string_codec(
684 owner: Arc<[u8]>,
685 string_codec: SceneStringCodec,
686 ) -> Result<SceneStream<'static>> {
687 let len = owner.len();
688 let chunk: &'static [u8] = unsafe { std::slice::from_raw_parts(owner.as_ptr(), len) };
689 let mut stream = SceneStream::new_with_string_codec(chunk, string_codec)?;
690 stream.owned_chunk = Some(owner);
691 Ok(stream)
692 }
693
694 pub fn new_shared_range_with_string_codec(
701 owner: Arc<Vec<u8>>,
702 range: Range<usize>,
703 string_codec: SceneStringCodec,
704 ) -> Result<SceneStream<'static>> {
705 if range.start > range.end || range.end > owner.len() {
706 bail!("scn: shared scene range out of bounds");
707 }
708 let ptr = owner.as_ptr();
709 let len = range.end - range.start;
710 let chunk: &'static [u8] = unsafe { std::slice::from_raw_parts(ptr.add(range.start), len) };
713 let mut stream = SceneStream::new_with_string_codec(chunk, string_codec)?;
714 stream.owned_pack = Some(owner);
715 Ok(stream)
716 }
717
718 pub fn eof(&self) -> bool {
719 self.pc >= self.scn.len()
720 }
721
722 pub fn get_prg_cntr(&self) -> usize {
723 self.pc
724 }
725
726 pub fn debug_bytes_around(&self, pc: usize, before: usize, after: usize) -> (usize, Vec<u8>) {
733 let start = pc.saturating_sub(before);
734 let end = (pc + after).min(self.scn.len());
735 if start >= end {
736 return (start, Vec::new());
737 }
738 (start, self.scn[start..end].to_vec())
739 }
740
741 pub fn debug_len(&self) -> usize {
743 self.scn.len()
744 }
745
746 pub fn set_prg_cntr(&mut self, prg_cntr: usize) -> Result<()> {
747 if prg_cntr > self.scn.len() {
748 bail!("scn: prg_cntr out of bounds");
749 }
750 self.pc = prg_cntr;
751 Ok(())
752 }
753
754 pub fn jump_to_label(&mut self, label_no: usize) -> Result<()> {
755 let cnt = self.header.label_cnt.max(0) as usize;
756 if label_no >= cnt {
757 bail!("scn: label_no out of range");
758 }
759 let off = label_no * 4;
760 let label_offset = i32::from_le_bytes(self.label_list[off..off + 4].try_into().unwrap());
761 self.set_prg_cntr(label_offset.max(0) as usize)
762 }
763
764 pub fn jump_to_z_label(&mut self, z_no: usize) -> Result<()> {
765 let cnt = self.header.z_label_cnt.max(0) as usize;
766 if z_no >= cnt {
767 bail!("scn: z_label out of range");
768 }
769 let off = z_no * 4;
770 let z_offset = i32::from_le_bytes(self.z_label_list[off..off + 4].try_into().unwrap());
771 self.set_prg_cntr(z_offset.max(0) as usize)
772 }
773
774 pub fn scn_cmd_offset(&self, cmd_no: usize) -> Result<usize> {
775 let cnt = self.header.scn_cmd_cnt.max(0) as usize;
776 if cmd_no >= cnt {
777 bail!("scn: scn_cmd_no out of range");
778 }
779 let ofs = self.header.scn_cmd_list_ofs.max(0) as usize;
780 let byte_ofs = ofs
781 .checked_add(cmd_no * 4)
782 .ok_or_else(|| anyhow!("scn: scn_cmd_list overflow"))?;
783 let byte_end = byte_ofs
784 .checked_add(4)
785 .ok_or_else(|| anyhow!("scn: scn_cmd entry overflow"))?;
786 if byte_end > self.chunk.len() {
787 bail!("scn: scn_cmd_list out of bounds");
788 }
789 let cmd_offset = i32::from_le_bytes(self.chunk[byte_ofs..byte_end].try_into().unwrap());
790 let prg = cmd_offset.max(0) as usize;
791 if prg > self.scn.len() {
792 bail!("scn: scn_cmd offset out of bounds");
793 }
794 Ok(prg)
795 }
796
797 pub fn next_scn_cmd_offset_after(&self, start: usize) -> Result<Option<usize>> {
798 let cnt = self.header.scn_cmd_cnt.max(0) as usize;
799 let mut next: Option<usize> = None;
800 for cmd_no in 0..cnt {
801 let off = self.scn_cmd_offset(cmd_no)?;
802 if off > start {
803 next = Some(match next {
804 Some(cur) => cur.min(off),
805 None => off,
806 });
807 }
808 }
809 Ok(next)
810 }
811
812 pub fn pop_u8(&mut self) -> Result<u8> {
813 if self.pc + 1 > self.scn.len() {
814 bail!("scn: pop_u8 past end");
815 }
816 let v = self.scn[self.pc];
817 self.pc += 1;
818 Ok(v)
819 }
820
821 pub fn pop_u16(&mut self) -> Result<u16> {
822 if self.pc + 2 > self.scn.len() {
823 bail!("scn: pop_u16 past end");
824 }
825 let v = u16::from_le_bytes(self.scn[self.pc..self.pc + 2].try_into().unwrap());
826 self.pc += 2;
827 Ok(v)
828 }
829
830 pub fn pop_i32(&mut self) -> Result<i32> {
831 if self.pc + 4 > self.scn.len() {
832 bail!("scn: pop_i32 past end");
833 }
834 let v = i32::from_le_bytes(self.scn[self.pc..self.pc + 4].try_into().unwrap());
835 self.pc += 4;
836 Ok(v)
837 }
838
839 pub fn has_resolved_command_read_flag_layout(&self) -> bool {
843 self.command_read_flags.is_some()
844 }
845
846 pub fn command_read_flag_no_at_current_pc(&self) -> Option<i32> {
849 self.command_read_flags
850 .as_ref()
851 .and_then(|flags| flags.get(&self.pc).copied())
852 }
853
854 pub fn pop_str(&mut self) -> Result<String> {
855 let str_id = self.pop_i32()?;
856 self.get_string(str_id as usize)
857 }
858
859 pub fn get_string(&self, str_id: usize) -> Result<String> {
860 let str_cnt = self.header.str_cnt.max(0) as usize;
861 if str_id >= str_cnt {
862 bail!("scn: str_id out of range");
863 }
864 let idx = CIndex::read(self.str_index_list, str_id * 8)?;
865 let o = idx.offset.max(0) as usize;
866 let n = idx.size.max(0) as usize;
867
868 let byte_off = o
869 .checked_mul(2)
870 .ok_or_else(|| anyhow!("scn: str offset overflow"))?;
871 let byte_end = byte_off
872 .checked_add(n * 2)
873 .ok_or_else(|| anyhow!("scn: str size overflow"))?;
874 if byte_end > self.str_list.len() {
875 bail!("scn: str data out of bounds");
876 }
877
878 let key = (28807u32).wrapping_mul(str_id as u32) as u16;
879
880 let mut u16s = Vec::with_capacity(n);
881 for j in 0..n {
882 let p = byte_off + j * 2;
883 let raw = u16::from_le_bytes([self.str_list[p], self.str_list[p + 1]]);
884 let w = match self.string_codec {
885 SceneStringCodec::Plain => raw,
886 SceneStringCodec::Xor => raw ^ key,
887 };
888 if w == 0 {
889 break;
890 }
891 u16s.push(w);
892 }
893 Ok(String::from_utf16_lossy(&u16s))
894 }
895}
896
897#[cfg(test)]
898mod read_flag_compat_tests {
899 use super::{SceneStream, SceneStringCodec};
900 use crate::runtime::constants::{cd, fm};
901 use std::sync::Arc;
902
903 fn push_i32(out: &mut Vec<u8>, value: i32) {
904 out.extend_from_slice(&value.to_le_bytes());
905 }
906
907 fn nl(out: &mut Vec<u8>, line: i32) {
908 out.push(cd::NL);
909 push_i32(out, line);
910 }
911
912 fn text(out: &mut Vec<u8>, flag_no: i32) {
913 out.push(cd::TEXT);
914 push_i32(out, flag_no);
915 }
916
917 fn empty_command(out: &mut Vec<u8>) -> usize {
918 out.push(cd::COMMAND);
919 push_i32(out, 0); push_i32(out, 0); push_i32(out, 0); push_i32(out, fm::VOID); out.len()
924 }
925
926 fn scene_with_code(code: &[u8], read_flag_lines: &[i32]) -> Vec<u8> {
927 const HEADER_WORDS: usize = 33;
928 const HEADER_SIZE: usize = HEADER_WORDS * 4;
929 let read_flag_ofs = HEADER_SIZE + code.len();
930
931 let mut words = [0i32; HEADER_WORDS];
932 words[0] = HEADER_SIZE as i32;
933 words[1] = HEADER_SIZE as i32;
934 words[2] = code.len() as i32;
935 for idx in [3usize, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29] {
936 words[idx] = read_flag_ofs as i32;
937 }
938 words[31] = read_flag_ofs as i32;
939 words[32] = read_flag_lines.len() as i32;
940
941 let mut chunk = Vec::with_capacity(read_flag_ofs + read_flag_lines.len() * 4);
942 for word in words {
943 chunk.extend_from_slice(&word.to_le_bytes());
944 }
945 chunk.extend_from_slice(code);
946 for line in read_flag_lines {
947 chunk.extend_from_slice(&line.to_le_bytes());
948 }
949 chunk
950 }
951
952 #[test]
953 fn legacy_pop_collision_does_not_become_command_read_flag() {
954 let mut code = Vec::new();
955 for (line, flag) in [(10, 0), (20, 1), (30, 2)] {
956 nl(&mut code, line);
957 text(&mut code, flag);
958 }
959 nl(&mut code, 66);
960 let command_end = empty_command(&mut code);
961 code.push(cd::POP);
965 push_i32(&mut code, fm::VOID);
966 text(&mut code, 3);
967
968 let chunk = scene_with_code(&code, &[10, 20, 30, 66]);
969 let mut stream = SceneStream::new(&chunk).expect("scene stream");
970 assert!(stream.has_resolved_command_read_flag_layout());
971 stream.set_prg_cntr(command_end).expect("seek command end");
972 assert_eq!(stream.command_read_flag_no_at_current_pc(), None);
973 }
974
975 #[test]
976 fn modern_command_read_flag_is_resolved_at_exact_boundary() {
977 let mut code = Vec::new();
978 for (line, flag) in [(10, 0), (20, 1), (30, 2)] {
979 nl(&mut code, line);
980 text(&mut code, flag);
981 }
982 nl(&mut code, 66);
983 let command_end = empty_command(&mut code);
984 push_i32(&mut code, 3);
985 code.push(cd::POP);
986 push_i32(&mut code, fm::VOID);
987 nl(&mut code, 67);
988 text(&mut code, 4);
989
990 let chunk = scene_with_code(&code, &[10, 20, 30, 66, 67]);
991 let mut stream = SceneStream::new(&chunk).expect("scene stream");
992 assert!(stream.has_resolved_command_read_flag_layout());
993 stream.set_prg_cntr(command_end).expect("seek command end");
994 assert_eq!(stream.command_read_flag_no_at_current_pc(), Some(3));
995 }
996
997 #[test]
998 fn mixed_scene_resolves_each_command_independently() {
999 let mut code = Vec::new();
1000 for (line, flag) in [(10, 0), (20, 1), (30, 2)] {
1001 nl(&mut code, line);
1002 text(&mut code, flag);
1003 }
1004
1005 nl(&mut code, 66);
1006 let legacy_end = empty_command(&mut code);
1007 code.push(cd::POP);
1008 push_i32(&mut code, fm::VOID);
1009 text(&mut code, 3);
1010
1011 nl(&mut code, 67);
1012 let modern_end = empty_command(&mut code);
1013 push_i32(&mut code, 4);
1014 code.push(cd::POP);
1015 push_i32(&mut code, fm::VOID);
1016 nl(&mut code, 68);
1017 text(&mut code, 5);
1018
1019 let chunk = scene_with_code(&code, &[10, 20, 30, 66, 67, 68]);
1020 let mut stream = SceneStream::new(&chunk).expect("scene stream");
1021 assert!(stream.has_resolved_command_read_flag_layout());
1022
1023 stream
1024 .set_prg_cntr(legacy_end)
1025 .expect("seek legacy command end");
1026 assert_eq!(stream.command_read_flag_no_at_current_pc(), None);
1027
1028 stream
1029 .set_prg_cntr(modern_end)
1030 .expect("seek modern command end");
1031 assert_eq!(stream.command_read_flag_no_at_current_pc(), Some(4));
1032 }
1033
1034 #[test]
1035 fn text_only_scene_resolves_with_no_command_flags() {
1036 let mut code = Vec::new();
1037 nl(&mut code, 7);
1038 text(&mut code, 0);
1039 let chunk = scene_with_code(&code, &[7]);
1040 let stream = SceneStream::new(&chunk).expect("scene stream");
1041 assert!(stream.has_resolved_command_read_flag_layout());
1042 assert_eq!(stream.command_read_flag_no_at_current_pc(), None);
1043 }
1044 #[test]
1045 fn owned_stream_releases_backing_after_last_clone_drops() {
1046 let chunk = scene_with_code(&[], &[]);
1047 let owner: Arc<[u8]> = Arc::from(chunk.into_boxed_slice());
1048 assert_eq!(Arc::strong_count(&owner), 1);
1049 let stream = SceneStream::new_owned_with_string_codec(owner.clone(), SceneStringCodec::Xor)
1050 .expect("owned scene stream");
1051 assert_eq!(Arc::strong_count(&owner), 2);
1052 let cloned = stream.clone();
1053 assert_eq!(Arc::strong_count(&owner), 3);
1054 drop(stream);
1055 assert_eq!(Arc::strong_count(&owner), 2);
1056 drop(cloned);
1057 assert_eq!(Arc::strong_count(&owner), 1);
1058 }
1059}