1mod close;
4mod early_save;
5mod short_save;
6use crate::original_save::NativeLocalLayout;
7mod indexed_save;
8
9use anyhow::{Result, anyhow, bail};
10use std::collections::BTreeMap;
11use std::fmt::Write as _;
12use std::sync::Arc;
13
14use crate::elm_code;
15use crate::runtime::forms::codes;
16use crate::runtime::globals::{
17 ObjectFrameActionState, PendingButtonAction, PendingButtonActionKind, PendingFrameActionFinish,
18};
19use crate::runtime::{
20 self, CommandContext, RuntimeLoadRequest, RuntimeSaveKind, RuntimeSaveRequest, Value, constants,
21};
22use crate::scene_stream::SceneStream;
23use siglus_assets::scene_pck::{ScenePck, ScenePckDecodeOptions};
24
25macro_rules! vm_trace {
30 ($vm:expr, $pc:expr, $msg:expr $(,)?) => {{
31 if $vm.vm_trace_config.enabled && $vm.vm_trace_matches() {
33 $vm.vm_trace_emit($pc, $msg);
34 }
35 }};
36}
37
38macro_rules! sg_omv_trace {
42 ($vm:expr, $($arg:tt)*) => {{
43 if $vm.sg_debug_enabled() {
44 $vm.sg_omv_trace_emit(format_args!($($arg)*));
45 }
46 }};
47}
48
49const CD_NONE: u8 = constants::cd::NONE;
50const CD_NL: u8 = constants::cd::NL;
51const CD_PUSH: u8 = constants::cd::PUSH;
52const CD_POP: u8 = constants::cd::POP;
53const CD_COPY: u8 = constants::cd::COPY;
54const CD_PROPERTY: u8 = constants::cd::PROPERTY;
55const CD_COPY_ELM: u8 = constants::cd::COPY_ELM;
56const CD_DEC_PROP: u8 = constants::cd::DEC_PROP;
57const CD_ELM_POINT: u8 = constants::cd::ELM_POINT;
58const CD_ARG: u8 = constants::cd::ARG;
59
60const CD_GOTO: u8 = constants::cd::GOTO;
61const CD_GOTO_TRUE: u8 = constants::cd::GOTO_TRUE;
62const CD_GOTO_FALSE: u8 = constants::cd::GOTO_FALSE;
63const CD_GOSUB: u8 = constants::cd::GOSUB;
64const CD_GOSUBSTR: u8 = constants::cd::GOSUBSTR;
65const CD_RETURN: u8 = constants::cd::RETURN;
66const CD_EOF: u8 = constants::cd::EOF;
67
68const CD_ASSIGN: u8 = constants::cd::ASSIGN;
69const CD_OPERATE_1: u8 = constants::cd::OPERATE_1;
70const CD_OPERATE_2: u8 = constants::cd::OPERATE_2;
71
72const CD_COMMAND: u8 = constants::cd::COMMAND;
73const CD_TEXT: u8 = constants::cd::TEXT;
74const CD_NAME: u8 = constants::cd::NAME;
75const CD_SEL_BLOCK_START: u8 = constants::cd::SEL_BLOCK_START;
76const CD_SEL_BLOCK_END: u8 = constants::cd::SEL_BLOCK_END;
77
78const OP_PLUS: u8 = constants::op::PLUS;
79const OP_MINUS: u8 = constants::op::MINUS;
80const OP_MULTIPLE: u8 = constants::op::MULTIPLE;
81const OP_DIVIDE: u8 = constants::op::DIVIDE;
82const OP_AMARI: u8 = constants::op::AMARI;
83
84const OP_EQUAL: u8 = constants::op::EQUAL;
85const OP_NOT_EQUAL: u8 = constants::op::NOT_EQUAL;
86const OP_GREATER: u8 = constants::op::GREATER;
87const OP_GREATER_EQUAL: u8 = constants::op::GREATER_EQUAL;
88const OP_LESS: u8 = constants::op::LESS;
89const OP_LESS_EQUAL: u8 = constants::op::LESS_EQUAL;
90
91const OP_LOGICAL_AND: u8 = constants::op::LOGICAL_AND;
92const OP_LOGICAL_OR: u8 = constants::op::LOGICAL_OR;
93
94const OP_TILDE: u8 = constants::op::TILDE;
95const OP_AND: u8 = constants::op::AND;
96const OP_OR: u8 = constants::op::OR;
97const OP_HAT: u8 = constants::op::HAT;
98const OP_SL: u8 = constants::op::SL;
99const OP_SR: u8 = constants::op::SR;
100const OP_SR3: u8 = constants::op::SR3;
101
102const LEGACY_GLOBAL_WORLD_ELEMENT: i32 = 47;
108
109#[derive(Debug, Clone, Copy)]
116pub struct VmConfig {
117 pub fm_void: i32,
118 pub fm_int: i32,
119 pub fm_str: i32,
120 pub fm_label: i32,
121 pub fm_list: i32,
122 pub fm_intlist: i32,
123 pub fm_strlist: i32,
124 pub max_steps: u64,
125}
126
127impl VmConfig {
128 pub fn from_env() -> Self {
129 fn env_u64(key: &str, default: u64) -> u64 {
130 std::env::var(key)
131 .ok()
132 .and_then(|v| v.parse::<u64>().ok())
133 .unwrap_or(default)
134 }
135
136 Self {
137 fm_void: constants::fm::VOID,
138 fm_int: constants::fm::INT,
139 fm_str: constants::fm::STR,
140 fm_label: constants::fm::LABEL,
141 fm_list: constants::fm::LIST,
142 fm_intlist: constants::fm::INTLIST,
143 fm_strlist: constants::fm::STRLIST,
144 max_steps: env_u64("SIGLUS_VM_MAX_STEPS", 0),
145 }
146 }
147}
148
149#[derive(Debug, Clone)]
150struct VmTraceConfig {
151 enabled: bool,
152 scene: Option<String>,
153 pc_range: Option<(usize, usize)>,
154 commands_enabled: bool,
155}
156
157impl VmTraceConfig {
158 fn from_env() -> Self {
159 let enabled = env_is_set!("SIGLUS_TRACE_VM");
160 let scene = std::env::var("SIGLUS_TRACE_VM_SCENE")
161 .ok()
162 .filter(|value| !value.is_empty());
163 let pc_range = std::env::var("SIGLUS_TRACE_VM_PC").ok().and_then(|range| {
164 let (start, end) = range.split_once("..")?;
165 let parse = |value: &str| {
166 usize::from_str_radix(value.trim_start_matches("0x"), 16)
167 .or_else(|_| value.parse::<usize>())
168 .ok()
169 };
170 Some((parse(start)?, parse(end)?))
171 });
172
173 Self {
174 enabled,
175 scene,
176 pc_range,
177 commands_enabled: env_is_set!("SIGLUS_TRACE_VM_COMMANDS"),
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy)]
183struct VmRuntimeOptions {
184 inline_user_cmd_max_steps: u64,
185 frame_action_max_steps: u64,
186 trace_unknown_forms: bool,
187 proc_flow_trace: bool,
188 sg_debug: bool,
189 syscom_proc_trace: bool,
190 tick_trace: bool,
191 frame_action_trace: bool,
192 title_chain_trace: bool,
193 save_load_trace: bool,
194 trace_call_return_pc: bool,
195 trace_frame_action_call: bool,
196}
197
198impl VmRuntimeOptions {
199 fn from_env() -> Self {
200 fn env_u64(key: &str) -> u64 {
201 std::env::var(key)
202 .ok()
203 .and_then(|value| value.parse::<u64>().ok())
204 .unwrap_or(0)
205 }
206
207 let sg_debug = env_is_set!("SG_DEBUG");
208 Self {
209 inline_user_cmd_max_steps: env_u64("SIGLUS_INLINE_USER_CMD_MAX_STEPS"),
210 frame_action_max_steps: env_u64("SIGLUS_FRAME_ACTION_MAX_STEPS"),
211 trace_unknown_forms: env_is_set!("SIGLUS_TRACE_UNKNOWN_FORMS"),
212 proc_flow_trace: env_is_set!("SG_PROC_FLOW_TRACE"),
213 sg_debug,
214 syscom_proc_trace: sg_debug || env_is_set!("SG_SYSCOM_PROC_TRACE"),
215 tick_trace: env_is_set!("SG_TICK_TRACE"),
216 frame_action_trace: env_is_set!("SG_FRAME_ACTION_TRACE"),
217 title_chain_trace: env_is_set!("SG_TITLE_CHAIN_TRACE"),
218 save_load_trace: env_is_set!("SG_SAVELOAD_TRACE"),
219 trace_call_return_pc: env_is_set!("SIGLUS_TRACE_CALL_RETURN_PC"),
220 trace_frame_action_call: env_is_set!("SIGLUS_TRACE_FRAME_ACTION_CALL"),
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy)]
226struct InlineExecCheckpoint {
227 scene_no: Option<usize>,
228 pc: usize,
229 line_no: i32,
230 ctx_line_no: i64,
231 halted: bool,
232 int_len: usize,
233 str_len: usize,
234 element_point_len: usize,
235 call_depth: usize,
236 gosub_depth: usize,
237 scene_depth: usize,
238 caller_return: Option<(usize, i32)>,
239}
240
241#[derive(Debug, Clone)]
242struct CallProp {
243 scn_no: i32,
244 prop_id: i32,
245 form: i32,
246 decl_size: usize,
247 element: Vec<i32>,
248 value: CallPropValue,
249}
250
251#[derive(Debug, Clone)]
252enum CallPropValue {
253 Int(i32),
254 Str(String),
255 Element(Vec<i32>),
256 IntList(Vec<i32>),
257 StrList(Vec<String>),
258}
259
260#[derive(Debug, Clone)]
261struct CallFrame {
262 call_type: i32,
264 return_pc: usize,
265 return_scene_no: Option<usize>,
269 return_scene_name: Option<String>,
270 return_line_no: i32,
271 ret_form: i32,
272 return_override: Option<(usize, i32)>,
276 excall_proc: bool,
277 frame_action_proc: bool,
278 arg_cnt: usize,
279 delayed_ret_form: Option<i32>,
280 user_props: Vec<CallProp>,
281 int_args: Vec<i32>,
282 str_args: Vec<String>,
283}
284
285#[derive(Debug, Clone)]
286struct UserPropCell {
287 form: i32,
288 int_value: i32,
289 str_value: String,
290 element: Vec<i32>,
291 int_list: Vec<i32>,
292 str_list: Vec<String>,
293 list_items: Vec<UserPropCell>,
294}
295
296impl UserPropCell {
297 fn new(form: i32, element: Vec<i32>) -> Self {
298 Self {
299 form,
300 int_value: 0,
301 str_value: String::new(),
302 element,
303 int_list: Vec::new(),
304 str_list: Vec::new(),
305 list_items: Vec::new(),
306 }
307 }
308}
309
310#[derive(Debug)]
311struct SceneExecFrame<'a> {
312 stream: SceneStream<'a>,
316 user_cmd_names: Arc<std::collections::HashMap<u32, String>>,
317 call_cmd_names: Arc<std::collections::HashMap<u32, String>>,
318 current_scene_no: Option<usize>,
319 current_scene_name: Option<String>,
320 current_line_no: i32,
321 call_depth: usize,
324}
325
326#[derive(Debug, Clone)]
327struct ResolvedUserCommand {
328 encoded_no: usize,
329 name: String,
330 target_scene_no: usize,
331 target_offset: usize,
332 include_command: bool,
333}
334
335fn siglus_name_eq(lhs: &str, rhs: &str) -> bool {
336 lhs.eq_ignore_ascii_case(rhs)
337}
338
339fn find_named_index(names: &std::collections::HashMap<u32, String>, target: &str) -> Option<usize> {
340 names.iter().find_map(|(no, name)| {
341 if siglus_name_eq(name, target) {
342 Some(*no as usize)
343 } else {
344 None
345 }
346 })
347}
348
349fn resolve_named_user_command_number(
350 include_names: &std::collections::HashMap<u32, String>,
351 local_names: &std::collections::HashMap<u32, String>,
352 include_count: usize,
353 target: &str,
354) -> Option<(usize, bool)> {
355 if let Some(no) = find_named_index(include_names, target) {
356 return Some((no, true));
357 }
358 find_named_index(local_names, target).map(|no| (include_count + no, false))
359}
360
361#[derive(Debug, Clone)]
362struct RuntimeDiskSnapshot {
363 scene_name: String,
364 scene_no: i32,
365 line_no: i32,
366 pc: i32,
367 int_stack: Vec<i32>,
368 str_stack: Vec<String>,
369 element_points: Vec<usize>,
370 call_stack: Vec<CallFrame>,
371}
372
373fn resize_i64_vec(mut v: Vec<i64>, n: usize) -> Vec<i64> {
374 v.resize(n, 0);
375 v
376}
377
378fn resize_string_vec(mut v: Vec<String>, n: usize) -> Vec<String> {
379 v.resize_with(n, String::new);
380 v
381}
382
383#[derive(Clone)]
384struct VmResumePoint<'a> {
385 stream: SceneStream<'a>,
386 user_cmd_names: Arc<std::collections::HashMap<u32, String>>,
387 call_cmd_names: Arc<std::collections::HashMap<u32, String>>,
388 int_stack: Vec<i32>,
389 str_stack: Vec<String>,
390 element_points: Vec<usize>,
391 call_stack: Vec<CallFrame>,
392 gosub_return_stack: Vec<(usize, i32)>,
393 user_props: BTreeMap<u16, UserPropCell>,
394 scene_user_props: BTreeMap<usize, BTreeMap<u16, UserPropCell>>,
395 current_scene_no: Option<usize>,
396 current_scene_name: Option<String>,
397 current_line_no: i32,
398 globals: runtime::globals::GlobalState,
399}
400
401pub struct SceneVm<'a> {
402 pub cfg: VmConfig,
403 vm_trace_config: VmTraceConfig,
404 runtime_options: VmRuntimeOptions,
405 call_flag_count: usize,
406 stream: SceneStream<'a>,
407
408 pub ctx: CommandContext,
409
410 int_stack: Vec<i32>,
412 str_stack: Vec<String>,
413 element_points: Vec<usize>,
414
415 call_stack: Vec<CallFrame>,
416 call_frame_pool: Vec<CallFrame>,
417 gosub_return_stack: Vec<(usize, i32)>,
418 user_props: BTreeMap<u16, UserPropCell>,
419 scene_user_props: BTreeMap<usize, BTreeMap<u16, UserPropCell>>,
423 scene_stack: Vec<SceneExecFrame<'a>>,
424 save_point: Option<VmResumePoint<'a>>,
425 sel_point_stack: Vec<VmResumePoint<'a>>,
426 current_scene_no: Option<usize>,
427 current_scene_name: Option<String>,
428 current_line_no: i32,
429
430 pub unknown_opcodes: BTreeMap<u8, u64>,
431 pub unknown_forms: BTreeMap<i32, u64>,
432 steps: u64,
436 halted: bool,
437
438 delayed_ret_form: Option<i32>,
440 script_input_synced_this_frame: bool,
441 yield_safe_after_step: bool,
442
443 user_cmd_names: Arc<std::collections::HashMap<u32, String>>,
444 call_cmd_names: Arc<std::collections::HashMap<u32, String>>,
445
446 scene_pck_cache: Option<ScenePck>,
449 scene_pck_append_dir: Option<String>,
450 scene_stream_cache: BTreeMap<usize, SceneStream<'a>>,
451 scene_name_resolve_cache: std::collections::HashMap<String, Option<usize>>,
452 user_cmd_resolve_cache:
453 std::collections::HashMap<usize, std::collections::HashMap<String, ResolvedUserCommand>>,
454}
455
456#[derive(Debug, Clone, Copy, Default)]
457pub struct SceneMemoryStats {
458 pub scene_pck_bytes: usize,
459 pub cached_scene_streams: usize,
460}
461
462#[derive(Debug, Clone)]
463struct FrameActionWork {
464 stage_idx: i64,
465 obj_idx: usize,
466 ch_idx: Option<usize>,
467 global_form_id: Option<u32>,
468 object_chain: Option<Vec<i32>>,
469 frame_action_chain: Option<Vec<i32>>,
470 scn_name: String,
471 cmd_name: String,
472 args: Vec<Value>,
473 count: i64,
474 end_time: i64,
475}
476
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478enum FrameActionObjectRoot {
479 Stage {
480 obj_idx: usize,
481 child_pos: usize,
482 },
483 Mwnd {
484 mwnd_idx: usize,
485 selector: i32,
486 obj_idx: usize,
487 child_pos: usize,
488 },
489 BtnSelItem {
490 item_idx: usize,
491 obj_idx: usize,
492 child_pos: usize,
493 },
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
497struct FrameActionObjectLocator {
498 raw_form_id: i32,
499 stage_idx: i64,
500 root: FrameActionObjectRoot,
501}
502
503fn frame_action_stage_alias_to_index(code: i32) -> Option<i64> {
504 if code == crate::runtime::forms::codes::ELM_GLOBAL_BACK {
505 Some(0)
506 } else if code == crate::runtime::forms::codes::ELM_GLOBAL_FRONT {
507 Some(1)
508 } else if code == crate::runtime::forms::codes::ELM_GLOBAL_NEXT {
509 Some(2)
510 } else {
511 None
512 }
513}
514
515fn frame_action_array_marker(code: i32, elm_array: i32) -> bool {
516 code == elm_array || code == crate::runtime::forms::codes::ELM_ARRAY || code == -1
517}
518
519fn parse_frame_action_object_locator(
520 chain: &[i32],
521 elm_array: i32,
522) -> Option<FrameActionObjectLocator> {
523 let raw_form_id = *chain.first()?;
524 let (stage_idx, pos) = if let Some(stage_idx) = frame_action_stage_alias_to_index(raw_form_id) {
525 (stage_idx, 1usize)
526 } else {
527 if chain.len() < 4 || !frame_action_array_marker(chain[1], elm_array) {
528 return None;
529 }
530 (chain[2] as i64, 3usize)
531 };
532
533 let list_op = *chain.get(pos)?;
534 if !frame_action_array_marker(*chain.get(pos + 1)?, elm_array) {
535 return None;
536 }
537 let first_idx = (*chain.get(pos + 2)?).max(0) as usize;
538
539 let root = if list_op == crate::runtime::forms::codes::elm_value::STAGE_OBJECT {
540 FrameActionObjectRoot::Stage {
541 obj_idx: first_idx,
542 child_pos: pos + 3,
543 }
544 } else if list_op == crate::runtime::forms::codes::elm_value::STAGE_MWND {
545 let selector = *chain.get(pos + 3)?;
546 if !matches!(
547 selector,
548 crate::runtime::forms::codes::elm_value::MWND_OBJECT
549 | crate::runtime::forms::codes::elm_value::MWND_BUTTON
550 | crate::runtime::forms::codes::elm_value::MWND_FACE
551 ) || !frame_action_array_marker(*chain.get(pos + 4)?, elm_array)
552 {
553 return None;
554 }
555 FrameActionObjectRoot::Mwnd {
556 mwnd_idx: first_idx,
557 selector,
558 obj_idx: (*chain.get(pos + 5)?).max(0) as usize,
559 child_pos: pos + 6,
560 }
561 } else if list_op == crate::runtime::forms::codes::elm_value::STAGE_BTNSELITEM {
562 if *chain.get(pos + 3)? != crate::runtime::forms::codes::elm_value::BTNSELITEM_OBJECT
563 || !frame_action_array_marker(*chain.get(pos + 4)?, elm_array)
564 {
565 return None;
566 }
567 FrameActionObjectRoot::BtnSelItem {
568 item_idx: first_idx,
569 obj_idx: (*chain.get(pos + 5)?).max(0) as usize,
570 child_pos: pos + 6,
571 }
572 } else {
573 return None;
574 };
575
576 Some(FrameActionObjectLocator {
577 raw_form_id,
578 stage_idx,
579 root,
580 })
581}
582
583fn frame_action_locator_object_idx(locator: FrameActionObjectLocator) -> usize {
584 match locator.root {
585 FrameActionObjectRoot::Stage { obj_idx, .. }
586 | FrameActionObjectRoot::Mwnd { obj_idx, .. }
587 | FrameActionObjectRoot::BtnSelItem { obj_idx, .. } => obj_idx,
588 }
589}
590
591#[cfg(test)]
592mod frame_action_locator_tests {
593 use super::*;
594
595 #[test]
596 fn front_object_alias_and_canonical_stage_locate_the_same_object() {
597 let alias = [
598 crate::runtime::forms::codes::ELM_GLOBAL_FRONT,
599 crate::runtime::forms::codes::elm_value::STAGE_OBJECT,
600 -1,
601 115,
602 ];
603 let canonical = [
604 crate::runtime::forms::codes::ELM_GLOBAL_STAGE,
605 -1,
606 1,
607 crate::runtime::forms::codes::elm_value::STAGE_OBJECT,
608 -1,
609 115,
610 ];
611 let a = parse_frame_action_object_locator(&alias, -1).unwrap();
612 let b = parse_frame_action_object_locator(&canonical, -1).unwrap();
613 assert_eq!(a.stage_idx, b.stage_idx);
614 assert_eq!(frame_action_locator_object_idx(a), 115);
615 assert_eq!(frame_action_locator_object_idx(b), 115);
616 }
617
618 #[test]
619 fn front_mwnd_button_alias_keeps_both_array_indices() {
620 let alias = [
621 crate::runtime::forms::codes::ELM_GLOBAL_FRONT,
622 crate::runtime::forms::codes::elm_value::STAGE_MWND,
623 -1,
624 0,
625 crate::runtime::forms::codes::elm_value::MWND_BUTTON,
626 -1,
627 9,
628 ];
629 let canonical = [
630 crate::runtime::forms::codes::ELM_GLOBAL_STAGE,
631 -1,
632 1,
633 crate::runtime::forms::codes::elm_value::STAGE_MWND,
634 -1,
635 0,
636 crate::runtime::forms::codes::elm_value::MWND_BUTTON,
637 -1,
638 9,
639 ];
640 let a = parse_frame_action_object_locator(&alias, -1).unwrap();
641 let b = parse_frame_action_object_locator(&canonical, -1).unwrap();
642 assert_eq!(a.stage_idx, 1);
643 assert_eq!(b.stage_idx, 1);
644 assert_eq!(frame_action_locator_object_idx(a), 9);
645 assert_eq!(frame_action_locator_object_idx(b), 9);
646 assert!(matches!(
647 a.root,
648 FrameActionObjectRoot::Mwnd { mwnd_idx: 0, selector, obj_idx: 9, .. }
649 if selector == crate::runtime::forms::codes::elm_value::MWND_BUTTON
650 ));
651 }
652
653 #[test]
654 fn btnselitem_object_child_locator_starts_children_after_object_index() {
655 let chain = [
656 crate::runtime::forms::codes::ELM_GLOBAL_FRONT,
657 crate::runtime::forms::codes::elm_value::STAGE_BTNSELITEM,
658 -1,
659 2,
660 crate::runtime::forms::codes::elm_value::BTNSELITEM_OBJECT,
661 -1,
662 3,
663 crate::runtime::forms::codes::elm_value::OBJECT_CHILD,
664 -1,
665 4,
666 ];
667 let locator = parse_frame_action_object_locator(&chain, -1).unwrap();
668 assert!(matches!(
669 locator.root,
670 FrameActionObjectRoot::BtnSelItem {
671 item_idx: 2,
672 obj_idx: 3,
673 child_pos: 7
674 }
675 ));
676 }
677}
678
679struct SavedSound {
680 bgm_regist_name: String,
681 bgm_volume: u8,
682 bgm_delay_time: i64,
683 bgm_loop_flag: bool,
684 bgm_pause_flag: bool,
685 koe_volume: u8,
686 pcm_volume: u8,
687 pcmch: Vec<runtime::globals::PcmChPersistentState>,
688 se_volume: u8,
689 mov_file_name: String,
690}
691
692impl<'a> SceneVm<'a> {
693 pub fn debug_scene_memory_stats(&self) -> SceneMemoryStats {
696 SceneMemoryStats {
697 scene_pck_bytes: self.scene_pck_cache.as_ref().map_or(0, |pck| pck.buf.len()),
698 cached_scene_streams: self.scene_stream_cache.len(),
699 }
700 }
701
702 fn trace_unknown_form(&mut self, form_code: i32, site: &str) {
703 *self.unknown_forms.entry(form_code).or_insert(0) += 1;
704 if self.runtime_options.trace_unknown_forms {
705 eprintln!(
706 "[vm unknown form] site={} form={} pc=0x{:x}",
707 site,
708 form_code,
709 self.stream.get_prg_cntr()
710 );
711 }
712 }
713
714 fn configured_call_flag_count(ctx: &CommandContext) -> usize {
715 ctx.tables
716 .gameexe
717 .as_ref()
718 .and_then(|cfg| {
719 cfg.get_usize("#CALL_FLAG.CNT")
720 .or_else(|| cfg.get_usize("CALL_FLAG.CNT"))
721 })
722 .unwrap_or(50)
723 .min(256)
724 }
725
726 fn blank_call_int_args(count: usize) -> Vec<i32> {
727 vec![0; count]
728 }
729
730 fn blank_call_str_args(count: usize) -> Vec<String> {
731 vec![String::new(); count]
732 }
733
734 fn make_call_frame(
735 &self,
736 ret_form: i32,
737 excall_proc: bool,
738 frame_action_proc: bool,
739 arg_cnt: usize,
740 scratch_args: Option<(Vec<i32>, Vec<String>)>,
741 ) -> CallFrame {
742 let (int_args, str_args) = scratch_args.unwrap_or_else(|| {
743 (
744 Self::blank_call_int_args(self.call_flag_count),
745 Self::blank_call_str_args(self.call_flag_count),
746 )
747 });
748 CallFrame {
749 call_type: 0,
750 return_pc: 0,
751 return_scene_no: None,
752 return_scene_name: None,
753 return_line_no: -1,
754 ret_form,
755 return_override: None,
756 excall_proc,
757 frame_action_proc,
758 arg_cnt,
759 delayed_ret_form: None,
760 user_props: Vec::new(),
761 int_args,
762 str_args,
763 }
764 }
765
766 fn take_call_frame(
767 &mut self,
768 ret_form: i32,
769 excall_proc: bool,
770 frame_action_proc: bool,
771 arg_cnt: usize,
772 scratch_args: Option<(Vec<i32>, Vec<String>)>,
773 ) -> CallFrame {
774 let Some(mut frame) = self.call_frame_pool.pop() else {
775 return self.make_call_frame(
776 ret_form,
777 excall_proc,
778 frame_action_proc,
779 arg_cnt,
780 scratch_args,
781 );
782 };
783
784 frame.call_type = 0;
785 frame.return_pc = 0;
786 frame.return_scene_no = None;
787 frame.return_scene_name = None;
788 frame.return_line_no = -1;
789 frame.ret_form = ret_form;
790 frame.return_override = None;
791 frame.excall_proc = excall_proc;
792 frame.frame_action_proc = frame_action_proc;
793 frame.arg_cnt = arg_cnt;
794 frame.delayed_ret_form = None;
795 frame.user_props.clear();
796
797 if let Some((int_args, str_args)) = scratch_args {
798 frame.int_args = int_args;
799 frame.str_args = str_args;
800 } else {
801 frame.int_args.resize(self.call_flag_count, 0);
802 frame.int_args.fill(0);
803 frame
804 .str_args
805 .resize_with(self.call_flag_count, String::new);
806 for value in &mut frame.str_args {
807 value.clear();
808 }
809 }
810 frame
811 }
812
813 fn recycle_call_frame(&mut self, mut frame: CallFrame) {
814 frame.user_props.clear();
815 frame.return_override = None;
816 frame.delayed_ret_form = None;
817 self.call_frame_pool.push(frame);
821 }
822
823 fn shared_user_prop_count(&self) -> usize {
824 self.scene_pck_cache
825 .as_ref()
826 .map(|pck| pck.inc_props.len())
827 .unwrap_or_else(|| self.stream.header.scn_prop_cnt.max(0) as usize)
828 }
829
830 fn stash_current_scene_user_props(&mut self) {
831 let Some(scene_no) = self.current_scene_no else {
832 return;
833 };
834 let shared_count = self.shared_user_prop_count();
835 let locals = if shared_count > u16::MAX as usize {
836 BTreeMap::new()
837 } else {
838 self.user_props.split_off(&(shared_count as u16))
839 };
840 self.scene_user_props.insert(scene_no, locals);
844 }
845
846 fn activate_scene_user_prop_scope(&mut self, scene_no: usize) {
847 let shared_count = self.shared_user_prop_count();
848 if shared_count <= u16::MAX as usize {
852 let _ = self.user_props.split_off(&(shared_count as u16));
853 }
854 if let Some(mut locals) = self.scene_user_props.remove(&scene_no) {
855 self.user_props.append(&mut locals);
856 }
857 }
858
859 fn enter_cross_scene_user_prop_scope(&mut self, target_scene_no: usize) {
860 self.stash_current_scene_user_props();
861 self.activate_scene_user_prop_scope(target_scene_no);
862 }
863
864 fn restore_cross_scene_user_prop_scope(&mut self, caller_scene_no: Option<usize>) {
865 self.stash_current_scene_user_props();
869 if let Some(scene_no) = caller_scene_no {
870 self.activate_scene_user_prop_scope(scene_no);
871 }
872 }
873
874 fn inline_exec_checkpoint(&self) -> InlineExecCheckpoint {
875 InlineExecCheckpoint {
876 scene_no: self.current_scene_no,
877 pc: self.stream.get_prg_cntr(),
878 line_no: self.current_line_no,
879 ctx_line_no: self.ctx.current_line_no,
880 halted: self.halted,
881 int_len: self.int_stack.len(),
882 str_len: self.str_stack.len(),
883 element_point_len: self.element_points.len(),
884 call_depth: self.call_stack.len(),
885 gosub_depth: self.gosub_return_stack.len(),
886 scene_depth: self.scene_stack.len(),
887 caller_return: self
888 .call_stack
889 .last()
890 .map(|frame| (frame.return_pc, frame.ret_form)),
891 }
892 }
893
894 fn restore_inline_exec_checkpoint(&mut self, checkpoint: InlineExecCheckpoint) -> Result<()> {
895 if self.current_scene_no != checkpoint.scene_no {
896 return Ok(());
897 }
898 if self.int_stack.len() < checkpoint.int_len
899 || self.str_stack.len() < checkpoint.str_len
900 || self.element_points.len() < checkpoint.element_point_len
901 || self.call_stack.len() < checkpoint.call_depth
902 || self.gosub_return_stack.len() < checkpoint.gosub_depth
903 || self.scene_stack.len() < checkpoint.scene_depth
904 {
905 bail!(
906 "inline user command corrupted caller execution state: scene={:?} int={}/{} str={}/{} elm={}/{} call={}/{} gosub={}/{} scene_stack={}/{}",
907 checkpoint.scene_no,
908 self.int_stack.len(),
909 checkpoint.int_len,
910 self.str_stack.len(),
911 checkpoint.str_len,
912 self.element_points.len(),
913 checkpoint.element_point_len,
914 self.call_stack.len(),
915 checkpoint.call_depth,
916 self.gosub_return_stack.len(),
917 checkpoint.gosub_depth,
918 self.scene_stack.len(),
919 checkpoint.scene_depth,
920 );
921 }
922
923 self.int_stack.truncate(checkpoint.int_len);
924 self.str_stack.truncate(checkpoint.str_len);
925 self.element_points.truncate(checkpoint.element_point_len);
926 while self.call_stack.len() > checkpoint.call_depth {
927 if let Some(frame) = self.call_stack.pop() {
928 self.recycle_call_frame(frame);
929 }
930 }
931 self.gosub_return_stack.truncate(checkpoint.gosub_depth);
932 self.scene_stack.truncate(checkpoint.scene_depth);
933 if let (Some((return_pc, ret_form)), Some(caller)) =
934 (checkpoint.caller_return, self.call_stack.last_mut())
935 {
936 caller.return_pc = return_pc;
937 caller.ret_form = ret_form;
938 }
939 self.current_line_no = checkpoint.line_no;
940 self.ctx.current_line_no = checkpoint.ctx_line_no;
941 self.halted = checkpoint.halted;
942 self.stream.set_prg_cntr(checkpoint.pc)?;
943 Ok(())
944 }
945
946 pub fn new(stream: SceneStream<'a>, ctx: CommandContext) -> Self {
947 let cfg = VmConfig::from_env();
948 let vm_trace_config = VmTraceConfig::from_env();
949 let runtime_options = VmRuntimeOptions::from_env();
950 let call_flag_count = Self::configured_call_flag_count(&ctx);
951 let user_cmd_names = stream.scn_cmd_name_map.clone();
952 let base_call = CallFrame {
953 call_type: 0,
954 return_pc: 0,
955 return_scene_no: None,
956 return_scene_name: None,
957 return_line_no: -1,
958 ret_form: cfg.fm_void,
959 return_override: None,
960 excall_proc: false,
961 frame_action_proc: false,
962 arg_cnt: 0,
963 delayed_ret_form: None,
964 user_props: Vec::new(),
965 int_args: Self::blank_call_int_args(call_flag_count),
966 str_args: Self::blank_call_str_args(call_flag_count),
967 };
968 Self {
969 cfg,
970 vm_trace_config,
971 runtime_options,
972 call_flag_count,
973 stream,
974 ctx,
975 int_stack: Vec::new(),
976 str_stack: Vec::new(),
977 element_points: Vec::new(),
978 call_stack: vec![base_call],
979 call_frame_pool: Vec::new(),
980 gosub_return_stack: Vec::new(),
981 user_props: BTreeMap::new(),
982 scene_user_props: BTreeMap::new(),
983 scene_stack: Vec::new(),
984 save_point: None,
985 sel_point_stack: Vec::new(),
986 current_scene_no: None,
987 current_scene_name: None,
988 current_line_no: -1,
989 unknown_opcodes: BTreeMap::new(),
990 unknown_forms: BTreeMap::new(),
991
992 steps: 0,
993 halted: false,
994 delayed_ret_form: None,
995 script_input_synced_this_frame: false,
996 yield_safe_after_step: false,
997 user_cmd_names,
998 call_cmd_names: Arc::default(),
999 scene_pck_cache: None,
1000 scene_pck_append_dir: None,
1001 scene_stream_cache: BTreeMap::new(),
1002 scene_name_resolve_cache: std::collections::HashMap::new(),
1003 user_cmd_resolve_cache: std::collections::HashMap::new(),
1004 }
1005 }
1006
1007 pub fn with_config(cfg: VmConfig, stream: SceneStream<'a>, ctx: CommandContext) -> Self {
1008 let vm_trace_config = VmTraceConfig::from_env();
1009 let runtime_options = VmRuntimeOptions::from_env();
1010 let call_flag_count = Self::configured_call_flag_count(&ctx);
1011 let user_cmd_names = stream.scn_cmd_name_map.clone();
1012 let base_call = CallFrame {
1013 call_type: 0,
1014 return_pc: 0,
1015 return_scene_no: None,
1016 return_scene_name: None,
1017 return_line_no: -1,
1018 ret_form: cfg.fm_void,
1019 return_override: None,
1020 excall_proc: false,
1021 frame_action_proc: false,
1022 arg_cnt: 0,
1023 delayed_ret_form: None,
1024 user_props: Vec::new(),
1025 int_args: Self::blank_call_int_args(call_flag_count),
1026 str_args: Self::blank_call_str_args(call_flag_count),
1027 };
1028 Self {
1029 cfg,
1030 vm_trace_config,
1031 runtime_options,
1032 call_flag_count,
1033 stream,
1034 ctx,
1035 int_stack: Vec::new(),
1036 str_stack: Vec::new(),
1037 element_points: Vec::new(),
1038 call_stack: vec![base_call],
1039 call_frame_pool: Vec::new(),
1040 gosub_return_stack: Vec::new(),
1041 user_props: BTreeMap::new(),
1042 scene_user_props: BTreeMap::new(),
1043 scene_stack: Vec::new(),
1044 save_point: None,
1045 sel_point_stack: Vec::new(),
1046 current_scene_no: None,
1047 current_scene_name: None,
1048 current_line_no: -1,
1049 unknown_opcodes: BTreeMap::new(),
1050 unknown_forms: BTreeMap::new(),
1051
1052 steps: 0,
1053 halted: false,
1054 delayed_ret_form: None,
1055 script_input_synced_this_frame: false,
1056 yield_safe_after_step: false,
1057 user_cmd_names,
1058 call_cmd_names: Arc::default(),
1059 scene_pck_cache: None,
1060 scene_pck_append_dir: None,
1061 scene_stream_cache: BTreeMap::new(),
1062 scene_name_resolve_cache: std::collections::HashMap::new(),
1063 user_cmd_resolve_cache: std::collections::HashMap::new(),
1064 }
1065 }
1066
1067 pub fn is_blocked(&mut self) -> bool {
1068 self.ctx.wait_poll()
1069 }
1070
1071 pub fn is_halted(&self) -> bool {
1072 self.halted
1073 }
1074
1075 pub fn proc_generation(&self) -> u64 {
1076 self.ctx.proc_generation()
1077 }
1078
1079 pub fn last_proc_kind(&self) -> runtime::ProcKind {
1080 self.ctx.last_proc_kind()
1081 }
1082
1083 pub fn current_scene_name(&self) -> Option<&str> {
1084 self.current_scene_name.as_deref()
1085 }
1086
1087 pub fn current_line_no(&self) -> i32 {
1088 self.current_line_no
1089 }
1090
1091 pub fn current_scene_no(&self) -> Option<usize> {
1092 self.current_scene_no
1093 }
1094
1095 pub fn take_runtime_load_completed(&mut self) -> bool {
1096 self.ctx.take_runtime_load_completed()
1097 }
1098
1099 pub fn call_syscom_configured_scene(&mut self, key: &str) -> Result<bool> {
1100 let entry = self.ctx.tables.gameexe.as_ref().and_then(|cfg| {
1109 cfg.get_entry(key)
1110 .or_else(|| cfg.get_entry(&format!("#{key}")))
1111 });
1112 let Some(entry) = entry else {
1113 if self.runtime_options.proc_flow_trace {
1114 eprintln!(
1115 "[SG_PROC_FLOW] syscom_config_scene key={} raw=<missing> scene={:?} line={} pending_proc={:?}",
1116 key,
1117 self.current_scene_name.as_deref(),
1118 self.current_line_no,
1119 self.ctx.globals.syscom.pending_proc
1120 );
1121 }
1122 return Ok(false);
1123 };
1124
1125 let scene_name = entry
1126 .item_unquoted(0)
1127 .map(|s| s.trim().trim_matches('\"').trim().to_string())
1128 .unwrap_or_default();
1129 let z_no = entry
1130 .item_unquoted(1)
1131 .and_then(|s| s.trim().parse::<i32>().ok())
1132 .unwrap_or(0);
1133 let raw = format!("{scene_name},{z_no}");
1134
1135 if scene_name.is_empty() {
1136 if self.runtime_options.proc_flow_trace {
1137 eprintln!(
1138 "[SG_PROC_FLOW] syscom_config_scene key={} raw={:?} target=<empty> scene={:?} line={}",
1139 key,
1140 raw,
1141 self.current_scene_name.as_deref(),
1142 self.current_line_no
1143 );
1144 }
1145 return Ok(false);
1146 }
1147
1148 if self.runtime_options.proc_flow_trace {
1149 eprintln!(
1150 "[SG_PROC_FLOW] syscom_config_scene key={} raw={:?} target={} z={} before_scene={:?} line={} scene_stack={} call_depth={}",
1151 key,
1152 raw,
1153 scene_name,
1154 z_no,
1155 self.current_scene_name.as_deref(),
1156 self.current_line_no,
1157 self.scene_stack.len(),
1158 self.call_stack.len()
1159 );
1160 }
1161 self.call_syscom_scene(&scene_name, z_no)?;
1162 if self.runtime_options.proc_flow_trace {
1163 eprintln!(
1164 "[SG_PROC_FLOW] syscom_config_scene entered key={} now_scene={:?} line={} scene_stack={} call_depth={}",
1165 key,
1166 self.current_scene_name.as_deref(),
1167 self.current_line_no,
1168 self.scene_stack.len(),
1169 self.call_stack.len()
1170 );
1171 }
1172 Ok(true)
1173 }
1174
1175 fn call_syscom_scene(&mut self, scene_name: &str, z_no: i32) -> Result<()> {
1178 self.farcall_scene_name_ex(scene_name, z_no, self.cfg.fm_void, true, &[])
1179 }
1180
1181 #[inline(always)]
1182 fn vm_trace_matches(&self) -> bool {
1183 let config = &self.vm_trace_config;
1184 if !config.enabled {
1185 return false;
1186 }
1187 if let Some(filter) = config.scene.as_deref()
1188 && self.current_scene_name.as_deref() != Some(filter)
1189 {
1190 return false;
1191 }
1192 if let Some((start, end)) = config.pc_range {
1193 let pc = self.stream.get_prg_cntr();
1194 if pc < start || pc > end {
1195 return false;
1196 }
1197 }
1198 true
1199 }
1200
1201 fn vm_trace_stack_summary(&self) -> String {
1202 let mut out = String::new();
1203 let int_tail_start = self.int_stack.len().saturating_sub(8);
1204 let int_tail = &self.int_stack[int_tail_start..];
1205 let _ = write!(
1206 &mut out,
1207 "call_depth={} int_len={} str_len={} elm_points={:?} int_tail={:?}",
1208 self.call_stack.len(),
1209 self.int_stack.len(),
1210 self.str_stack.len(),
1211 self.element_points,
1212 int_tail
1213 );
1214 if let Some(last) = self.str_stack.last() {
1215 let preview = if last.chars().count() > 48 {
1216 let mut tmp = last.chars().take(48).collect::<String>();
1217 tmp.push('…');
1218 tmp
1219 } else {
1220 last.clone()
1221 };
1222 let _ = write!(&mut out, " str_top={:?}", preview);
1223 }
1224 out
1225 }
1226
1227 fn vm_trace_emit(&self, pc: Option<usize>, msg: impl std::fmt::Display) {
1228 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1229 let scene_no = self
1230 .current_scene_no
1231 .map(|v| v.to_string())
1232 .unwrap_or_else(|| "-".to_string());
1233 let pc_text = pc
1234 .map(|v| format!("0x{v:x}"))
1235 .unwrap_or_else(|| "-".to_string());
1236 eprintln!(
1237 "[SG_VM_TRACE] scene={} scene_no={} line={} pc={} {} | {}",
1238 scene,
1239 scene_no,
1240 self.current_line_no,
1241 pc_text,
1242 msg,
1243 self.vm_trace_stack_summary()
1244 );
1245 }
1246
1247 fn vm_opcode_name(opcode: u8) -> &'static str {
1248 match opcode {
1249 CD_NONE => "NONE",
1250 CD_NL => "NL",
1251 CD_PUSH => "PUSH",
1252 CD_POP => "POP",
1253 CD_COPY => "COPY",
1254 CD_PROPERTY => "PROPERTY",
1255 CD_COPY_ELM => "COPY_ELM",
1256 CD_DEC_PROP => "DEC_PROP",
1257 CD_ELM_POINT => "ELM_POINT",
1258 CD_ARG => "ARG",
1259 CD_GOTO => "GOTO",
1260 CD_GOTO_TRUE => "GOTO_TRUE",
1261 CD_GOTO_FALSE => "GOTO_FALSE",
1262 CD_GOSUB => "GOSUB",
1263 CD_GOSUBSTR => "GOSUBSTR",
1264 CD_RETURN => "RETURN",
1265 CD_EOF => "EOF",
1266 CD_ASSIGN => "ASSIGN",
1267 CD_OPERATE_1 => "OPERATE_1",
1268 CD_OPERATE_2 => "OPERATE_2",
1269 CD_COMMAND => "COMMAND",
1270 CD_TEXT => "TEXT",
1271 CD_NAME => "NAME",
1272 CD_SEL_BLOCK_START => "SEL_BLOCK_START",
1273 CD_SEL_BLOCK_END => "SEL_BLOCK_END",
1274 _ => "UNKNOWN",
1275 }
1276 }
1277
1278 #[inline(always)]
1279 fn vm_trace_opcode(&self, pc: usize, opcode: u8, phase: &str) {
1280 vm_trace!(
1281 self,
1282 Some(pc),
1283 format!(
1284 "{} opcode={}({:#04x})",
1285 phase,
1286 Self::vm_opcode_name(opcode),
1287 opcode
1288 )
1289 );
1290 }
1291 #[inline(always)]
1292 fn sg_debug_enabled(&self) -> bool {
1293 self.runtime_options.sg_debug
1294 }
1295
1296 fn sg_cgm_coord_trace_emit(&self, msg: impl std::fmt::Display) {
1297 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1298 let scene_no = self
1299 .current_scene_no
1300 .map(|v| v.to_string())
1301 .unwrap_or_else(|| "-".to_string());
1302 eprintln!(
1303 "[SG_DEBUG][CGM_COORD_TRACE][VM] scene={} scene_no={} line={} pc=0x{:x} {}",
1304 scene,
1305 scene_no,
1306 self.current_line_no,
1307 self.stream.get_prg_cntr(),
1308 msg
1309 );
1310 }
1311
1312 fn trace_cgm_coord_assign(&self, elm: &[i32], rhs: &Value) {
1313 if !self.sg_debug_enabled() || elm.len() < 3 {
1314 return;
1315 }
1316 let array_op = if self.ctx.ids.elm_array != 0 {
1317 self.ctx.ids.elm_array
1318 } else {
1319 crate::runtime::forms::codes::ELM_ARRAY
1320 };
1321 if elm[1] != array_op {
1322 return;
1323 }
1324 let head = elm[0] as u32;
1325 let idx = elm[2];
1326 if head == crate::runtime::forms::codes::elm_value::GLOBAL_B as u32 {
1327 let interesting = (100..=129).contains(&idx)
1328 || (140..=169).contains(&idx)
1329 || (180..=209).contains(&idx);
1330 if interesting {
1331 self.sg_cgm_coord_trace_emit(format_args!("global B[{}] <- {:?}", idx, rhs));
1332 }
1333 } else if head == crate::runtime::forms::codes::elm_value::GLOBAL_S as u32
1334 && (1120..=1139).contains(&idx)
1335 {
1336 self.sg_cgm_coord_trace_emit(format_args!("global S[{}] <- {:?}", idx, rhs));
1337 }
1338 }
1339
1340 fn cf_branch_trace_interesting_line(&self) -> bool {
1341 if !self.sg_debug_enabled() || self.current_scene_name.as_deref() != Some("sys10_cf01") {
1342 return false;
1343 }
1344 matches!(self.current_line_no, 700..=730 | 870..=895)
1345 }
1346
1347 fn cf_condition_trace_interesting_line(&self) -> bool {
1348 if !self.sg_debug_enabled() {
1349 return false;
1350 }
1351 matches!(self.current_scene_name.as_deref(), Some("sys10_cf01"))
1352 && matches!(self.current_line_no, 700..=730 | 870..=895)
1353 }
1354
1355 fn cf_condition_trace_prop_name(prop_id: u16) -> Option<&'static str> {
1356 match prop_id {
1357 14 => Some("ip_mx"),
1358 15 => Some("ip_my"),
1359 16 => Some("ip_wheel"),
1360 18 => Some("ip_bl_is"),
1361 19 => Some("ip_br_is"),
1362 20 => Some("ip_bl_on"),
1363 21 => Some("ip_br_on"),
1364 22 => Some("ip_key_enable_enter"),
1365 23 => Some("ip_key_enable_esc"),
1366 24 => Some("ip_key_is_enter"),
1367 25 => Some("ip_key_is_esc"),
1368 26 => Some("ip_key_on_enter"),
1369 27 => Some("ip_key_on_esc"),
1370 39 => Some("cntr_now"),
1371 40 => Some("cntr_exit"),
1372 41 => Some("skip_flag"),
1373 _ => None,
1374 }
1375 }
1376
1377 fn cf_condition_trace_value_summary(
1378 &self,
1379 cell: &UserPropCell,
1380 array_idx: Option<usize>,
1381 ) -> String {
1382 if let Some(idx) = array_idx {
1383 if cell.form == self.cfg.fm_intlist {
1384 return format!(
1385 "intlist[{}]={}",
1386 idx,
1387 cell.int_list.get(idx).copied().unwrap_or(0)
1388 );
1389 }
1390 if cell.form == self.cfg.fm_strlist {
1391 return format!(
1392 "strlist[{}]={:?}",
1393 idx,
1394 cell.str_list.get(idx).cloned().unwrap_or_default()
1395 );
1396 }
1397 if let Some(slot) = cell.list_items.get(idx) {
1398 return format!(
1399 "list[{}] form={} int={} str={:?} int_list_len={} str_list_len={} items={}",
1400 idx,
1401 slot.form,
1402 slot.int_value,
1403 slot.str_value,
1404 slot.int_list.len(),
1405 slot.str_list.len(),
1406 slot.list_items.len()
1407 );
1408 }
1409 return format!(
1410 "array[{}] <missing> form={} int_list_len={} str_list_len={} items={}",
1411 idx,
1412 cell.form,
1413 cell.int_list.len(),
1414 cell.str_list.len(),
1415 cell.list_items.len()
1416 );
1417 }
1418 if cell.form == self.cfg.fm_int {
1419 return format!("int={}", cell.int_value);
1420 }
1421 if cell.form == self.cfg.fm_str {
1422 return format!("str={:?}", cell.str_value);
1423 }
1424 if cell.form == self.cfg.fm_intlist {
1425 let preview = cell.int_list.iter().take(20).copied().collect::<Vec<_>>();
1426 return format!("intlist len={} head={:?}", cell.int_list.len(), preview);
1427 }
1428 if cell.form == self.cfg.fm_strlist {
1429 let preview = cell.str_list.iter().take(6).cloned().collect::<Vec<_>>();
1430 return format!("strlist len={} head={:?}", cell.str_list.len(), preview);
1431 }
1432 format!(
1433 "form={} int={} str={:?} int_list_len={} str_list_len={} items={}",
1434 cell.form,
1435 cell.int_value,
1436 cell.str_value,
1437 cell.int_list.len(),
1438 cell.str_list.len(),
1439 cell.list_items.len()
1440 )
1441 }
1442
1443 fn sg_cf_condition_trace(&self, pc: usize, msg: impl AsRef<str>) {
1444 if !self.sg_debug_enabled() {
1445 return;
1446 }
1447 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1448 let scene_no = self
1449 .current_scene_no
1450 .map(|v| v.to_string())
1451 .unwrap_or_else(|| "-".to_string());
1452 let int_tail_start = self.int_stack.len().saturating_sub(12);
1453 let int_tail = &self.int_stack[int_tail_start..];
1454 eprintln!(
1455 "[SG_DEBUG][CF_CONDITION_TRACE] scene={} scene_no={} line={} pc=0x{:x} {} | int_tail={:?}",
1456 scene,
1457 scene_no,
1458 self.current_line_no,
1459 pc,
1460 msg.as_ref(),
1461 int_tail
1462 );
1463 }
1464
1465 fn trace_cf_condition_user_prop_read(
1466 &self,
1467 pc: usize,
1468 prop_id: u16,
1469 array_idx: Option<usize>,
1470 cell: &UserPropCell,
1471 elm: &[i32],
1472 ) {
1473 if !self.cf_condition_trace_interesting_line() {
1474 return;
1475 }
1476 let Some(name) = Self::cf_condition_trace_prop_name(prop_id) else {
1477 return;
1478 };
1479 self.sg_cf_condition_trace(
1480 pc,
1481 format!(
1482 "kind=USER_PROP_READ prop={}({}) array={:?} value={} elm={:?}",
1483 prop_id,
1484 name,
1485 array_idx,
1486 self.cf_condition_trace_value_summary(cell, array_idx),
1487 elm
1488 ),
1489 );
1490 }
1491
1492 fn trace_cf_condition_user_prop_assign(
1493 &self,
1494 pc: usize,
1495 prop_id: u16,
1496 array_idx: Option<usize>,
1497 old: Option<&UserPropCell>,
1498 new: Option<&UserPropCell>,
1499 rhs: &Value,
1500 elm: &[i32],
1501 ) {
1502 if !self.cf_condition_trace_interesting_line() {
1503 return;
1504 }
1505 let Some(name) = Self::cf_condition_trace_prop_name(prop_id) else {
1506 return;
1507 };
1508 let old_summary = old
1509 .map(|cell| self.cf_condition_trace_value_summary(cell, array_idx))
1510 .unwrap_or_else(|| "<default/missing>".to_string());
1511 let new_summary = new
1512 .map(|cell| self.cf_condition_trace_value_summary(cell, array_idx))
1513 .unwrap_or_else(|| "<missing>".to_string());
1514 self.sg_cf_condition_trace(
1515 pc,
1516 format!(
1517 "kind=USER_PROP_ASSIGN prop={}({}) array={:?} old={} new={} rhs={:?} elm={:?}",
1518 prop_id, name, array_idx, old_summary, new_summary, rhs, elm
1519 ),
1520 );
1521 }
1522
1523 fn cf_condition_op_name(opr: u8) -> &'static str {
1524 match opr {
1525 OP_PLUS => "+",
1526 OP_MINUS => "-",
1527 OP_MULTIPLE => "*",
1528 OP_DIVIDE => "/",
1529 OP_AMARI => "%",
1530 OP_EQUAL => "==",
1531 OP_NOT_EQUAL => "!=",
1532 OP_GREATER => ">",
1533 OP_GREATER_EQUAL => ">=",
1534 OP_LESS => "<",
1535 OP_LESS_EQUAL => "<=",
1536 OP_LOGICAL_AND => "&&",
1537 OP_LOGICAL_OR => "||",
1538 OP_TILDE => "~",
1539 OP_AND => "&",
1540 OP_OR => "|",
1541 OP_HAT => "^",
1542 OP_SL => "<<",
1543 OP_SR => ">>",
1544 OP_SR3 => ">>>",
1545 _ => "?",
1546 }
1547 }
1548
1549 fn cf_branch_trace_stack_snapshot(&self) -> String {
1550 let int_tail_start = self.int_stack.len().saturating_sub(16);
1551 let int_tail = &self.int_stack[int_tail_start..];
1552 let str_tail_start = self.str_stack.len().saturating_sub(4);
1553 let str_tail = &self.str_stack[str_tail_start..];
1554 let (cur_l, cur_s, arg_cnt) = if let Some(frame) = self.call_stack.last() {
1555 let l_take = frame.int_args.len().min(16);
1556 let s_take = frame.str_args.len().min(6);
1557 (
1558 format!("{:?}", &frame.int_args[..l_take]),
1559 format!("{:?}", &frame.str_args[..s_take]),
1560 frame.arg_cnt,
1561 )
1562 } else {
1563 ("[]".to_string(), "[]".to_string(), 0)
1564 };
1565 format!(
1566 "int_len={} int_tail={:?} str_len={} str_tail={:?} elm_points={:?} call_depth={} arg_cnt={} cur_call_l0_15={} cur_call_s0_5={}",
1567 self.int_stack.len(),
1568 int_tail,
1569 self.str_stack.len(),
1570 str_tail,
1571 self.element_points,
1572 self.call_stack.len(),
1573 arg_cnt,
1574 cur_l,
1575 cur_s,
1576 )
1577 }
1578
1579 fn sg_cf_branch_trace_emit(&self, pc: usize, msg: impl std::fmt::Display) {
1580 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1581 let scene_no = self
1582 .current_scene_no
1583 .map(|v| v.to_string())
1584 .unwrap_or_else(|| "-".to_string());
1585 eprintln!(
1586 "[SG_DEBUG][CF_BRANCH_TRACE] scene={} scene_no={} line={} pc=0x{:x} {} | {}",
1587 scene,
1588 scene_no,
1589 self.current_line_no,
1590 pc,
1591 msg,
1592 self.cf_branch_trace_stack_snapshot(),
1593 );
1594 }
1595
1596 fn trace_cf_branch_goto(
1597 &self,
1598 pc: usize,
1599 opcode_name: &str,
1600 label_no: i32,
1601 cond: i32,
1602 taken: bool,
1603 before_tail: &[i32],
1604 ) {
1605 if self.cf_branch_trace_interesting_line() {
1606 self.sg_cf_branch_trace_emit(
1607 pc,
1608 format_args!(
1609 "kind=GOTO opcode={} label={} cond={} taken={} before_int_tail={:?}",
1610 opcode_name, label_no, cond, taken, before_tail
1611 ),
1612 );
1613 }
1614 }
1615
1616 fn trace_cf_branch_farcall(
1617 &self,
1618 pc: usize,
1619 scene_name: &str,
1620 z_no: i32,
1621 ret_form: i32,
1622 ex_call_proc: bool,
1623 scratch_source_args: &[Value],
1624 ) {
1625 if !self.sg_debug_enabled()
1626 || !(self.current_scene_name.as_deref() == Some("sys10_cf01")
1627 && matches!(self.current_line_no, 700..=730 | 870..=895)
1628 && matches!(scene_name, "sys10_sm00" | "sys10_cf00")
1629 && matches!(z_no, 14 | 15))
1630 {
1631 return;
1632 }
1633 let args_dbg = scratch_source_args
1634 .iter()
1635 .map(|v| format!("{v:?}"))
1636 .collect::<Vec<_>>()
1637 .join(", ");
1638 self.sg_cf_branch_trace_emit(
1639 pc,
1640 format_args!(
1641 "kind=FARCALL target={} z={} ret_form={} ex_call_proc={} argc={} args=[{}]",
1642 scene_name,
1643 z_no,
1644 ret_form,
1645 ex_call_proc,
1646 scratch_source_args.len(),
1647 args_dbg
1648 ),
1649 );
1650 }
1651
1652 fn sg_omv_trace_emit(&self, msg: impl std::fmt::Display) {
1653 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1654 let scene_no = self
1655 .current_scene_no
1656 .map(|v| v.to_string())
1657 .unwrap_or_else(|| "-".to_string());
1658 eprintln!(
1659 "[SG_DEBUG][OMV_TRACE] scene={} scene_no={} line={} pc=0x{:x} {}",
1660 scene,
1661 scene_no,
1662 self.current_line_no,
1663 self.stream.get_prg_cntr(),
1664 msg
1665 );
1666 }
1667
1668 fn sg_omv_trace_command(
1669 &self,
1670 phase: &str,
1671 elm: &[i32],
1672 form_id: i32,
1673 op_id: i32,
1674 al_id: i32,
1675 ret_form: i32,
1676 args: &[Value],
1677 ) {
1678 if !self.sg_debug_enabled() {
1679 return;
1680 }
1681
1682 let label = if form_id == crate::runtime::forms::codes::elm_value::GLOBAL_JUMP
1683 || (form_id == crate::runtime::forms::codes::FM_GLOBAL
1684 && op_id == crate::runtime::forms::codes::elm_value::GLOBAL_JUMP)
1685 {
1686 Some("GLOBAL.JUMP")
1687 } else if form_id == crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL
1688 || (form_id == crate::runtime::forms::codes::FM_GLOBAL
1689 && op_id == crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL)
1690 {
1691 Some("GLOBAL.FARCALL")
1692 } else if (form_id as u32 == constants::global_form::SYSCOM
1693 || form_id == constants::fm::SYSCOM)
1694 && op_id == crate::runtime::forms::codes::elm_value::SYSCOM_CALL_EX
1695 {
1696 Some("SYSCOM.CALL_EX")
1697 } else if form_id as u32 == constants::global_form::MOV || form_id == constants::fm::MOV {
1698 Some("MOV")
1699 } else if form_id == constants::fm::OBJECT
1700 && matches!(
1701 op_id,
1702 crate::runtime::forms::codes::object_op::CREATE_MOVIE
1703 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_LOOP
1704 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_WAIT
1705 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_WAIT_KEY
1706 )
1707 {
1708 Some("OBJECT.CREATE_MOVIE")
1709 } else {
1710 None
1711 };
1712 let Some(label) = label else {
1713 return;
1714 };
1715
1716 let args_dbg = args
1717 .iter()
1718 .take(8)
1719 .map(|v| format!("{v:?}"))
1720 .collect::<Vec<_>>()
1721 .join(", ");
1722 self.sg_omv_trace_emit(format_args!(
1723 "{} {} form={} op={} al_id={} ret_form={} elm={:?} argc={} args=[{}]",
1724 phase,
1725 label,
1726 form_id,
1727 op_id,
1728 al_id,
1729 ret_form,
1730 elm,
1731 args.len(),
1732 args_dbg
1733 ));
1734 }
1735
1736 fn vm_scn_cmd_context(&self, pc: usize) -> String {
1737 let cnt = self.stream.header.scn_cmd_cnt.max(0) as usize;
1738 let mut prev: Option<(usize, usize)> = None;
1739 let mut next: Option<(usize, usize)> = None;
1740 for cmd_no in 0..cnt {
1741 let Ok(off) = self.stream.scn_cmd_offset(cmd_no) else {
1742 continue;
1743 };
1744 if off <= pc {
1745 prev = Some(match prev {
1746 Some(cur) if cur.1 > off => cur,
1747 _ => (cmd_no, off),
1748 });
1749 }
1750 if off > pc {
1751 next = Some(match next {
1752 Some(cur) if cur.1 < off => cur,
1753 _ => (cmd_no, off),
1754 });
1755 }
1756 }
1757
1758 let mut out = String::new();
1759 if let Some((cmd_no, off)) = prev {
1760 let name = self
1761 .stream
1762 .scn_cmd_name_map
1763 .get(&(cmd_no as u32))
1764 .map(String::as_str)
1765 .unwrap_or("<unnamed>");
1766 let _ = write!(
1767 &mut out,
1768 "prev_scn_cmd=#{}:{}@0x{:x} delta={} ",
1769 cmd_no,
1770 name,
1771 off,
1772 pc.saturating_sub(off)
1773 );
1774 } else {
1775 let _ = write!(&mut out, "prev_scn_cmd=<none> ");
1776 }
1777 if let Some((cmd_no, off)) = next {
1778 let name = self
1779 .stream
1780 .scn_cmd_name_map
1781 .get(&(cmd_no as u32))
1782 .map(String::as_str)
1783 .unwrap_or("<unnamed>");
1784 let _ = write!(
1785 &mut out,
1786 "next_scn_cmd=#{}:{}@0x{:x} distance={}",
1787 cmd_no,
1788 name,
1789 off,
1790 off.saturating_sub(pc)
1791 );
1792 } else {
1793 let _ = write!(&mut out, "next_scn_cmd=<none>");
1794 }
1795 out
1796 }
1797
1798 pub fn take_script_proc_request(&mut self) -> bool {
1799 let requested = self.ctx.excall_state.script_proc_requested;
1800 self.ctx.excall_state.script_proc_requested = false;
1801 requested
1802 }
1803
1804 pub fn take_script_proc_pop_request(&mut self) -> bool {
1805 let requested = self.ctx.excall_state.script_proc_pop_requested;
1806 self.ctx.excall_state.script_proc_pop_requested = false;
1807 requested
1808 }
1809
1810 fn mark_excall_script_proc_requested(&mut self) {
1811 self.halted = false;
1812 self.ctx.excall_state.ex_call_flag = true;
1813 self.ctx.excall_state.script_proc_requested = true;
1814 self.ctx.request_proc_boundary(runtime::ProcKind::Script);
1818 }
1819
1820 fn mark_excall_script_proc_pop_requested(&mut self) {
1821 self.ctx.excall_state.ex_call_flag = false;
1822 self.ctx.excall_state.script_proc_pop_requested = true;
1823 self.ctx.input.clear_all();
1824 self.ctx.request_proc_boundary(runtime::ProcKind::Script);
1830 }
1831
1832 fn push_call_arg_value(&mut self, arg: &Value) {
1833 match arg {
1834 Value::NamedArg { value, .. } => self.push_call_arg_value(value),
1835 Value::Int(n) => self.push_int(*n as i32),
1836 Value::Str(s) => self.push_str(s.clone()),
1837 Value::Element(elm) => self.push_element(elm.clone()),
1838 Value::List(items) => {
1839 for item in items {
1840 self.push_call_arg_value(item);
1841 }
1842 }
1843 }
1844 }
1845
1846 fn run_user_cmd_inline_at_offset(
1847 &mut self,
1848 cmd_name: &str,
1849 offset: usize,
1850 return_pc: usize,
1851 end_offset: Option<usize>,
1852 _expected_return_pc: Option<usize>,
1853 ret_form: i32,
1854 call_args: &[Value],
1855 frame_action_proc: bool,
1856 ) -> Result<bool> {
1857 let checkpoint = self.inline_exec_checkpoint();
1858 let base_depth = checkpoint.call_depth;
1859
1860 if let Some(caller) = self.call_stack.last_mut() {
1861 if self.runtime_options.trace_call_return_pc {
1862 eprintln!(
1863 "[SG_CALL_PC] inline set cmd={} depth={} saved_pc=0x{:x} return_pc=0x{:x} old=0x{:x}",
1864 cmd_name, base_depth, checkpoint.pc, return_pc, caller.return_pc
1865 );
1866 }
1867 caller.return_pc = return_pc;
1868 caller.return_scene_no = self.current_scene_no;
1869 caller.return_scene_name = self.current_scene_name.clone();
1870 caller.return_line_no = self.current_line_no;
1871 caller.ret_form = ret_form;
1872 }
1873 for arg in call_args {
1874 self.push_call_arg_value(arg);
1875 }
1876 let mut call_frame = self.take_call_frame(
1877 self.cfg.fm_void,
1878 false,
1879 frame_action_proc,
1880 call_args.len(),
1881 None,
1882 );
1883 call_frame.call_type = 3;
1884 call_frame.return_override = Some((return_pc, ret_form));
1885 self.call_stack.push(call_frame);
1886 self.stream.set_prg_cntr(offset)?;
1887
1888 if self.runtime_options.trace_frame_action_call {
1889 eprintln!(
1890 "[SG_FRAME_ACTION_CALL] run cmd={} scene={:?} offset=0x{:x} return_pc=0x{:x} args={:?}",
1891 cmd_name, self.current_scene_no, offset, return_pc, call_args
1892 );
1893 }
1894
1895 let max_steps = self.runtime_options.inline_user_cmd_max_steps;
1896 let mut steps: u64 = 0;
1897 let mut run_error = None;
1898 loop {
1899 if let Some(end) = end_offset
1900 && self.stream.get_prg_cntr() >= end
1901 {
1902 break;
1903 }
1904 let wait_generation_before_step = self.ctx.wait.block_generation();
1905 let proc_generation_before_step = self.ctx.proc_generation();
1906 let running = match self.step_inner(false) {
1907 Ok(v) => v,
1908 Err(e) => {
1909 run_error = Some(e);
1910 break;
1911 }
1912 };
1913 if self.halted || !running {
1914 break;
1915 }
1916 if self.ctx.proc_generation() != proc_generation_before_step {
1917 break;
1918 }
1919 if self.ctx.wait.block_generation() != wait_generation_before_step
1920 && self.ctx.wait_poll()
1921 {
1922 break;
1923 }
1924 if self.call_stack.len() == base_depth {
1925 break;
1926 }
1927 steps = steps.saturating_add(1);
1928 if max_steps > 0 && steps >= max_steps {
1929 run_error = Some(anyhow!(
1930 "inline user command exceeded SIGLUS_INLINE_USER_CMD_MAX_STEPS: cmd={}",
1931 cmd_name
1932 ));
1933 break;
1934 }
1935 }
1936
1937 let captured_inline_return = if ret_form == self.cfg.fm_int || ret_form == self.cfg.fm_label
1938 {
1939 if self.int_stack.len() > checkpoint.int_len {
1940 self.int_stack.last().copied().map(|v| Value::Int(v as i64))
1941 } else {
1942 None
1943 }
1944 } else if ret_form == self.cfg.fm_str {
1945 if self.str_stack.len() > checkpoint.str_len {
1946 self.str_stack.last().cloned().map(Value::Str)
1947 } else {
1948 None
1949 }
1950 } else {
1951 None
1952 };
1953
1954 if self.current_scene_no == checkpoint.scene_no {
1955 self.restore_inline_exec_checkpoint(checkpoint)?;
1956 if self.runtime_options.trace_call_return_pc
1957 && let Some(caller) = self.call_stack.last()
1958 {
1959 eprintln!(
1960 "[SG_CALL_PC] inline restore cmd={} depth={} return_pc=0x{:x}",
1961 cmd_name, base_depth, caller.return_pc
1962 );
1963 }
1964 }
1965 if let Some(v) = captured_inline_return {
1966 self.ctx.stack.push(v);
1967 }
1968
1969 if let Some(e) = run_error {
1970 return Err(e);
1971 }
1972
1973 Ok(true)
1974 }
1975
1976 pub fn install_initial_scene_pck(&mut self, pack: ScenePck, append_dir: String) {
1977 self.scene_pck_cache = Some(pack);
1978 self.scene_pck_append_dir = Some(append_dir);
1979 }
1980
1981 fn ensure_scene_pck_cache(&mut self) -> Result<()> {
1982 let active_append = self.ctx.globals.append_dir.clone();
1983 let append_changed = self
1984 .scene_pck_append_dir
1985 .as_deref()
1986 .map(|cached| !cached.eq_ignore_ascii_case(&active_append))
1987 .unwrap_or(true);
1988 if self.scene_pck_cache.is_some() && !append_changed {
1989 return Ok(());
1990 }
1991
1992 self.scene_pck_cache = None;
1996 self.scene_stream_cache.clear();
1997 self.scene_name_resolve_cache.clear();
1998 self.user_cmd_resolve_cache.clear();
1999
2000 let scene_pck_path =
2001 crate::resource::find_scene_pck_path_for_append(&self.ctx.project_dir, &active_append)?;
2002
2003 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2004 {
2005 let bytes = crate::resource::read_file_bytes(&scene_pck_path)?;
2006 let key_cfg = ["key.toml", "Key.toml"].iter().find_map(|name| {
2007 let p = self.ctx.project_dir.join(name);
2008 if !crate::resource::wasm_path_is_file(&p) {
2009 return None;
2010 }
2011 let text = crate::resource::read_file_to_string(&p).ok()?;
2012 siglus_assets::key_toml::parse_key_toml(&text).ok()
2013 });
2014 let exe = key_cfg
2015 .as_ref()
2016 .and_then(|cfg| cfg.exe_key16)
2017 .map(|v| v.to_vec());
2018 let string_encryption_override = key_cfg
2019 .map(|cfg| cfg.override_string_encryption)
2020 .unwrap_or_default();
2021 let opt = ScenePckDecodeOptions {
2022 exe_angou_element: exe,
2023 easy_angou_code: Some(siglus_assets::keys::SCENE_KEY.to_vec()),
2024 string_encryption_override,
2025 };
2026 self.scene_pck_cache = Some(ScenePck::load_lazy_from_bytes(bytes, &opt)?);
2027 }
2028
2029 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
2030 {
2031 let opt = crate::resource::load_scene_pck_decode_options(&self.ctx.project_dir)?;
2032 self.scene_pck_cache = Some(ScenePck::load_lazy(&scene_pck_path, &opt)?);
2033 }
2034
2035 self.ctx.install_scene_metadata(
2036 &active_append,
2037 self.scene_pck_cache
2038 .as_ref()
2039 .expect("scene pck cache initialized"),
2040 )?;
2041 self.scene_pck_append_dir = Some(active_append);
2042 Ok(())
2043 }
2044
2045 fn ensure_scene_stream_cached(&mut self, scene_no: usize) -> Result<()> {
2046 self.ensure_scene_pck_cache()?;
2047 if !self.scene_stream_cache.contains_key(&scene_no) {
2048 let (owner, range, string_codec) = {
2049 let pck = self
2050 .scene_pck_cache
2051 .as_ref()
2052 .expect("scene pck cache initialized");
2053 let (owner, range) = pck.scn_data_shared(scene_no)?;
2054 (owner, range, pck.string_codec)
2055 };
2056 let stream =
2057 SceneStream::new_shared_range_with_string_codec(owner, range, string_codec)?;
2058 self.scene_stream_cache.insert(scene_no, stream);
2059 }
2060 Ok(())
2061 }
2062
2063 fn cached_scene_stream(&mut self, scene_no: usize) -> Result<SceneStream<'a>> {
2064 self.ensure_scene_stream_cached(scene_no)?;
2065 Ok(self
2066 .scene_stream_cache
2067 .get(&scene_no)
2068 .expect("scene stream cached")
2069 .clone())
2070 }
2071
2072 fn find_scene_no_by_name(pck: &ScenePck, name: &str) -> Option<usize> {
2073 pck.scn_name_map.iter().find_map(|(scene_name, scene_no)| {
2074 if siglus_name_eq(scene_name, name) {
2075 Some(*scene_no)
2076 } else {
2077 None
2078 }
2079 })
2080 }
2081
2082 fn requested_user_command_scene_no(&mut self, scn_name: Option<&str>) -> Result<Option<usize>> {
2083 let Some(name) = scn_name.filter(|name| !name.is_empty()) else {
2084 return Ok(self.current_scene_no);
2085 };
2086 if let Some(scene_no) = self.scene_name_resolve_cache.get(name) {
2087 return Ok(*scene_no);
2088 }
2089 self.ensure_scene_pck_cache()?;
2090 let pck = self
2091 .scene_pck_cache
2092 .as_ref()
2093 .ok_or_else(|| anyhow!("scene pck cache is not initialized"))?;
2094 let scene_no = Self::find_scene_no_by_name(pck, name);
2095 self.scene_name_resolve_cache
2096 .insert(name.to_string(), scene_no);
2097 Ok(scene_no)
2098 }
2099
2100 fn resolve_user_command_by_name(
2101 &mut self,
2102 requested_scene_no: usize,
2103 cmd_name: &str,
2104 ) -> Result<Option<ResolvedUserCommand>> {
2105 if let Some(cached) = self
2106 .user_cmd_resolve_cache
2107 .get(&requested_scene_no)
2108 .and_then(|commands| commands.get(cmd_name))
2109 {
2110 return Ok(Some(cached.clone()));
2111 }
2112
2113 self.ensure_scene_pck_cache()?;
2114 if self.current_scene_no != Some(requested_scene_no) {
2115 self.ensure_scene_stream_cached(requested_scene_no)?;
2116 }
2117
2118 let resolved = {
2119 let pck = self
2120 .scene_pck_cache
2121 .as_ref()
2122 .ok_or_else(|| anyhow!("scene pck cache is not initialized"))?;
2123 let local_names = if self.current_scene_no == Some(requested_scene_no) {
2124 &self.user_cmd_names
2125 } else {
2126 &self
2127 .scene_stream_cache
2128 .get(&requested_scene_no)
2129 .expect("scene stream cached")
2130 .scn_cmd_name_map
2131 };
2132 let inc_cmd_cnt = pck.inc_cmds.len();
2133 let Some((encoded_no, include_command)) = resolve_named_user_command_number(
2134 &pck.inc_cmd_name_map,
2135 local_names,
2136 inc_cmd_cnt,
2137 cmd_name,
2138 ) else {
2139 return Ok(None);
2140 };
2141
2142 if include_command {
2143 let target = pck.inc_cmds.get(encoded_no).copied().ok_or_else(|| {
2144 anyhow!(
2145 "include user command {} is missing from Scene.pck inc_cmds",
2146 encoded_no
2147 )
2148 })?;
2149 let canonical_name = pck
2150 .inc_cmd_name_map
2151 .get(&(encoded_no as u32))
2152 .cloned()
2153 .unwrap_or_else(|| cmd_name.to_string());
2154 if target.scn_no < 0 || target.offset < 0 {
2155 bail!(
2156 "invalid include user command target: cmd_no={} name={} scn_no={} offset={}",
2157 encoded_no,
2158 canonical_name,
2159 target.scn_no,
2160 target.offset
2161 );
2162 }
2163 ResolvedUserCommand {
2164 encoded_no,
2165 name: canonical_name,
2166 target_scene_no: target.scn_no as usize,
2167 target_offset: target.offset as usize,
2168 include_command: true,
2169 }
2170 } else {
2171 let local_cmd_no = encoded_no - inc_cmd_cnt;
2172 let target_offset = if self.current_scene_no == Some(requested_scene_no) {
2173 self.stream.scn_cmd_offset(local_cmd_no)?
2174 } else {
2175 self.scene_stream_cache
2176 .get(&requested_scene_no)
2177 .expect("scene stream cached")
2178 .scn_cmd_offset(local_cmd_no)?
2179 };
2180 let name = local_names
2181 .get(&(local_cmd_no as u32))
2182 .cloned()
2183 .unwrap_or_else(|| cmd_name.to_string());
2184 ResolvedUserCommand {
2185 encoded_no,
2186 name,
2187 target_scene_no: requested_scene_no,
2188 target_offset,
2189 include_command: false,
2190 }
2191 }
2192 };
2193
2194 self.user_cmd_resolve_cache
2195 .entry(requested_scene_no)
2196 .or_default()
2197 .insert(cmd_name.to_string(), resolved.clone());
2198 Ok(Some(resolved))
2199 }
2200
2201 fn resolve_user_command_by_id(
2202 &mut self,
2203 requested_scene_no: usize,
2204 cmd_no: usize,
2205 ) -> Result<ResolvedUserCommand> {
2206 self.ensure_scene_pck_cache()?;
2207 let (inc_cmd_cnt, inc_target, inc_name) = {
2208 let pck = self
2209 .scene_pck_cache
2210 .as_ref()
2211 .ok_or_else(|| anyhow!("scene pck cache is not initialized"))?;
2212 let inc_target = pck.inc_cmds.get(cmd_no).copied();
2213 let inc_name = pck.inc_cmd_name_map.get(&(cmd_no as u32)).cloned();
2214 (pck.inc_cmds.len(), inc_target, inc_name)
2215 };
2216
2217 if cmd_no < inc_cmd_cnt {
2218 let target = inc_target.ok_or_else(|| {
2219 anyhow!(
2220 "include user command {} is missing from Scene.pck inc_cmds",
2221 cmd_no
2222 )
2223 })?;
2224 if target.scn_no < 0 || target.offset < 0 {
2225 bail!(
2226 "invalid include user command target: cmd_no={} name={} scn_no={} offset={}",
2227 cmd_no,
2228 inc_name.as_deref().unwrap_or("<unknown>"),
2229 target.scn_no,
2230 target.offset
2231 );
2232 }
2233 return Ok(ResolvedUserCommand {
2234 encoded_no: cmd_no,
2235 name: inc_name.unwrap_or_else(|| format!("<include-command-{cmd_no}>")),
2236 target_scene_no: target.scn_no as usize,
2237 target_offset: target.offset as usize,
2238 include_command: true,
2239 });
2240 }
2241
2242 let local_cmd_no = cmd_no - inc_cmd_cnt;
2243 let (target_offset, name) = if self.current_scene_no == Some(requested_scene_no) {
2244 let target_offset = self.stream.scn_cmd_offset(local_cmd_no)?;
2245 let name = self
2246 .user_cmd_names
2247 .get(&(local_cmd_no as u32))
2248 .cloned()
2249 .unwrap_or_else(|| format!("<scene-command-{local_cmd_no}>"));
2250 (target_offset, name)
2251 } else {
2252 let target_stream = self.cached_scene_stream(requested_scene_no)?;
2253 let target_offset = target_stream.scn_cmd_offset(local_cmd_no)?;
2254 let name = target_stream
2255 .scn_cmd_name_map
2256 .get(&(local_cmd_no as u32))
2257 .cloned()
2258 .unwrap_or_else(|| format!("<scene-command-{local_cmd_no}>"));
2259 (target_offset, name)
2260 };
2261
2262 Ok(ResolvedUserCommand {
2263 encoded_no: cmd_no,
2264 name,
2265 target_scene_no: requested_scene_no,
2266 target_offset,
2267 include_command: false,
2268 })
2269 }
2270
2271 fn enter_resolved_user_command(
2272 &mut self,
2273 command: &ResolvedUserCommand,
2274 ret_form: i32,
2275 call_args: &[Value],
2276 excall_proc: bool,
2277 frame_action_proc: bool,
2278 ) -> Result<bool> {
2279 if self.current_scene_no == Some(command.target_scene_no) {
2280 self.enter_current_scene_user_cmd_proc_at_offset(
2281 command.target_offset,
2282 ret_form,
2283 call_args,
2284 excall_proc,
2285 frame_action_proc,
2286 )
2287 } else {
2288 self.enter_scene_user_cmd_at_scene_offset_ex(
2289 command.target_scene_no,
2290 command.target_offset,
2291 call_args,
2292 ret_form,
2293 excall_proc,
2294 frame_action_proc,
2295 )
2296 }
2297 }
2298
2299 fn run_scene_user_cmd_inline_at_cached_scene_offset(
2300 &mut self,
2301 target_scene_no: usize,
2302 cmd_name: &str,
2303 target_offset: usize,
2304 call_args: &[Value],
2305 ret_form: i32,
2306 preserve_return_pc: bool,
2307 frame_action_proc: bool,
2308 ) -> Result<bool> {
2309 let target_stream = self.cached_scene_stream(target_scene_no)?;
2310 if target_offset > target_stream.scn.len() {
2311 bail!(
2312 "scene_pck: user command offset out of bounds: cmd={} scn_no={} offset=0x{:x} scn_len=0x{:x}",
2313 cmd_name,
2314 target_scene_no,
2315 target_offset,
2316 target_stream.scn.len()
2317 );
2318 }
2319 let (target_call_cmd_names, target_scene_name) = {
2320 let pck = self
2321 .scene_pck_cache
2322 .as_ref()
2323 .expect("scene pck cache initialized");
2324 (
2325 pck.inc_cmd_name_map.clone(),
2326 pck.find_scene_name(target_scene_no).map(ToOwned::to_owned),
2327 )
2328 };
2329
2330 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
2331 let target_user_cmd_names = self.stream.scn_cmd_name_map.clone();
2332 let saved_user_cmd_names =
2333 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
2334 let saved_call_cmd_names =
2335 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
2336 let saved_current_scene_no = self.current_scene_no;
2337 let saved_current_scene_name = self.current_scene_name.clone();
2338 let saved_current_line_no = self.current_line_no;
2339 let saved_ctx_scene_no = self.ctx.current_scene_no;
2340 let saved_ctx_scene_name = self.ctx.current_scene_name.clone();
2341 let saved_ctx_line_no = self.ctx.current_line_no;
2342 let saved_halted = self.halted;
2343 self.enter_cross_scene_user_prop_scope(target_scene_no);
2344
2345 self.current_scene_no = Some(target_scene_no);
2346 self.current_scene_name = target_scene_name;
2347 self.current_line_no = -1;
2348 self.ctx.current_scene_no = Some(target_scene_no as i64);
2349 self.ctx.current_scene_name = self.current_scene_name.clone();
2350 self.ctx.current_line_no = -1;
2351
2352 let target_return_pc = if preserve_return_pc {
2353 saved_stream.get_prg_cntr()
2354 } else {
2355 self.stream.scn.len()
2356 };
2357 let result = self.run_user_cmd_inline_at_offset(
2358 cmd_name,
2359 target_offset,
2360 target_return_pc,
2361 None,
2362 None,
2363 ret_form,
2364 call_args,
2365 frame_action_proc,
2366 );
2367
2368 self.restore_cross_scene_user_prop_scope(saved_current_scene_no);
2371 self.stream = saved_stream;
2372 self.user_cmd_names = saved_user_cmd_names;
2373 self.call_cmd_names = saved_call_cmd_names;
2374 self.current_scene_no = saved_current_scene_no;
2375 self.current_scene_name = saved_current_scene_name;
2376 self.current_line_no = saved_current_line_no;
2377 self.ctx.current_scene_no = saved_ctx_scene_no;
2378 self.ctx.current_scene_name = saved_ctx_scene_name;
2379 self.ctx.current_line_no = saved_ctx_line_no;
2380 self.halted = saved_halted;
2381 result
2382 }
2383
2384 fn run_scene_user_cmd_inline(
2385 &mut self,
2386 scn_name: Option<&str>,
2387 cmd_name: &str,
2388 call_args: &[Value],
2389 ret_form: i32,
2390 frame_action_proc: bool,
2391 ) -> Result<bool> {
2392 if frame_action_proc {
2393 return self.run_scene_user_cmd_frame_action_proc(scn_name, cmd_name, call_args);
2394 }
2395
2396 let Some(requested_scene_no) = self.requested_user_command_scene_no(scn_name)? else {
2397 return Ok(false);
2398 };
2399 let Some(command) = self.resolve_user_command_by_name(requested_scene_no, cmd_name)? else {
2400 if self.runtime_options.trace_frame_action_call {
2401 eprintln!(
2402 "[SG_FRAME_ACTION_CALL] user command not found: requested_scene={} scn_name={:?} cmd={}",
2403 requested_scene_no, scn_name, cmd_name
2404 );
2405 }
2406 return Ok(false);
2407 };
2408
2409 if self.current_scene_no == Some(command.target_scene_no) {
2410 let return_pc = self.stream.get_prg_cntr();
2411 return self.run_user_cmd_inline_at_offset(
2412 &command.name,
2413 command.target_offset,
2414 return_pc,
2415 None,
2416 Some(return_pc),
2417 ret_form,
2418 call_args,
2419 false,
2420 );
2421 }
2422
2423 self.run_scene_user_cmd_inline_at_cached_scene_offset(
2424 command.target_scene_no,
2425 &command.name,
2426 command.target_offset,
2427 call_args,
2428 ret_form,
2429 false,
2430 false,
2431 )
2432 }
2433
2434 fn run_scene_user_cmd_frame_action_proc(
2435 &mut self,
2436 scn_name: Option<&str>,
2437 cmd_name: &str,
2438 call_args: &[Value],
2439 ) -> Result<bool> {
2440 let checkpoint = self.inline_exec_checkpoint();
2441 let saved_scene_no = checkpoint.scene_no;
2442 let saved_int_stack = self.int_stack.clone();
2448 let saved_str_stack = self.str_stack.clone();
2449 let saved_element_points = self.element_points.clone();
2450 let saved_scene_stack_len = checkpoint.scene_depth;
2451 let saved_call_depth = checkpoint.call_depth;
2452
2453 let Some(requested_scene_no) = self.requested_user_command_scene_no(scn_name)? else {
2454 return Ok(false);
2455 };
2456 let Some(command) = self.resolve_user_command_by_name(requested_scene_no, cmd_name)? else {
2457 if self.runtime_options.trace_frame_action_call {
2458 eprintln!(
2459 "[SG_FRAME_ACTION_CALL] user command not found: requested_scene={} scn_name={:?} cmd={}",
2460 requested_scene_no, scn_name, cmd_name
2461 );
2462 }
2463 return Ok(false);
2464 };
2465 self.halted = false;
2470 self.enter_resolved_user_command(&command, self.cfg.fm_void, call_args, false, true)?;
2471
2472 if self.runtime_options.trace_frame_action_call {
2473 eprintln!(
2474 "[SG_FRAME_ACTION_CALL] proc enter cmd={} scene={:?} depth={} args={:?}",
2475 cmd_name,
2476 self.current_scene_no,
2477 self.call_stack.len(),
2478 call_args
2479 );
2480 }
2481
2482 let mut completed_by_return = false;
2483 let mut stopped_at_proc_boundary = false;
2484 let mut stopped_at_wait_boundary = false;
2485 let mut run_error = None;
2486 let max_steps = self.runtime_options.frame_action_max_steps;
2487 let mut steps: u64 = 0;
2488 loop {
2489 let wait_generation_before_step = self.ctx.wait.block_generation();
2490 let proc_generation_before_step = self.ctx.proc_generation();
2491 let running = match self.step_inner(false) {
2492 Ok(v) => v,
2493 Err(e) => {
2494 run_error = Some(e);
2495 break;
2496 }
2497 };
2498 if self.current_scene_no == saved_scene_no
2499 && self.scene_stack.len() == saved_scene_stack_len
2500 && self.call_stack.len() == saved_call_depth
2501 {
2502 completed_by_return = true;
2503 break;
2504 }
2505 if self.halted || !running {
2506 break;
2507 }
2508 if self.ctx.proc_generation() != proc_generation_before_step {
2509 stopped_at_proc_boundary = true;
2510 break;
2511 }
2512 if self.ctx.wait.block_generation() != wait_generation_before_step
2513 && self.ctx.wait_poll()
2514 {
2515 stopped_at_wait_boundary = true;
2516 break;
2517 }
2518 steps = steps.saturating_add(1);
2519 if max_steps > 0 && steps >= max_steps {
2520 run_error = Some(anyhow!(
2521 "frame_action user command exceeded SIGLUS_FRAME_ACTION_MAX_STEPS: cmd={} scene={:?}",
2522 cmd_name,
2523 scn_name
2524 ));
2525 break;
2526 }
2527 }
2528
2529 let restore_callback_lexer = self.current_scene_no == saved_scene_no;
2537 if restore_callback_lexer {
2538 if self.runtime_options.trace_frame_action_call {
2539 eprintln!(
2540 "[SG_FRAME_ACTION_CALL] proc exit cmd={} scene={:?} completed={} proc_boundary={} wait_boundary={} error={} restoring caller execution checkpoint",
2541 cmd_name,
2542 scn_name,
2543 completed_by_return,
2544 stopped_at_proc_boundary,
2545 stopped_at_wait_boundary,
2546 run_error.is_some()
2547 );
2548 }
2549 if completed_by_return {
2550 self.int_stack = saved_int_stack;
2551 self.str_stack = saved_str_stack;
2552 self.element_points = saved_element_points;
2553 }
2554 self.restore_inline_exec_checkpoint(checkpoint)?;
2555 }
2556
2557 if let Some(e) = run_error {
2558 return Err(e);
2559 }
2560
2561 Ok(true)
2562 }
2563
2564 fn enter_current_scene_user_cmd_proc_at_offset(
2565 &mut self,
2566 offset: usize,
2567 ret_form: i32,
2568 call_args: &[Value],
2569 excall_proc: bool,
2570 frame_action_proc: bool,
2571 ) -> Result<bool> {
2572 let return_pc = self.stream.get_prg_cntr();
2573 let depth = self.call_stack.len();
2574 let Some(caller) = self.call_stack.last_mut() else {
2575 return Ok(false);
2576 };
2577 if self.runtime_options.trace_call_return_pc {
2578 eprintln!(
2579 "[SG_CALL_PC] proc-call set depth={} offset=0x{:x} return_pc=0x{:x} old=0x{:x} frame_action={}",
2580 depth, offset, return_pc, caller.return_pc, frame_action_proc
2581 );
2582 }
2583 caller.return_pc = return_pc;
2584 caller.return_scene_no = self.current_scene_no;
2585 caller.return_scene_name = self.current_scene_name.clone();
2586 caller.return_line_no = self.current_line_no;
2587 caller.ret_form = ret_form;
2588 for arg in call_args {
2589 self.push_call_arg_value(arg);
2590 }
2591 let mut call_frame = self.take_call_frame(
2592 self.cfg.fm_void,
2593 excall_proc,
2594 frame_action_proc,
2595 call_args.len(),
2596 None,
2597 );
2598 call_frame.call_type = 3;
2599 call_frame.return_override = Some((return_pc, ret_form));
2600 self.call_stack.push(call_frame);
2601 self.stream.set_prg_cntr(offset)?;
2602 if excall_proc {
2603 self.mark_excall_script_proc_requested();
2604 }
2605 Ok(true)
2606 }
2607
2608 fn enter_scene_user_cmd_at_scene_offset_ex(
2609 &mut self,
2610 target_scene_no: usize,
2611 target_offset: usize,
2612 call_args: &[Value],
2613 ret_form: i32,
2614 ex_call_proc: bool,
2615 frame_action_proc: bool,
2616 ) -> Result<bool> {
2617 let target_stream = self.cached_scene_stream(target_scene_no)?;
2618 if target_offset > target_stream.scn.len() {
2619 bail!(
2620 "scene_pck: user command offset out of bounds: scn_no={} offset=0x{:x} scn_len=0x{:x}",
2621 target_scene_no,
2622 target_offset,
2623 target_stream.scn.len()
2624 );
2625 }
2626
2627 let return_pc = self.stream.get_prg_cntr();
2631 let depth = self.call_stack.len();
2632 let Some(caller) = self.call_stack.last_mut() else {
2633 return Ok(false);
2634 };
2635 if self.runtime_options.trace_call_return_pc {
2636 eprintln!(
2637 "[SG_CALL_PC] cross-scene user-cmd set depth={} target_scene={} offset=0x{:x} return_pc=0x{:x} old=0x{:x} frame_action={}",
2638 depth,
2639 target_scene_no,
2640 target_offset,
2641 return_pc,
2642 caller.return_pc,
2643 frame_action_proc
2644 );
2645 }
2646 caller.return_pc = return_pc;
2647 caller.return_scene_no = self.current_scene_no;
2648 caller.return_scene_name = self.current_scene_name.clone();
2649 caller.return_line_no = self.current_line_no;
2650 caller.ret_form = ret_form;
2651
2652 let target_user_cmd_names = target_stream.scn_cmd_name_map.clone();
2653 let (target_call_cmd_names, target_scene_name) = {
2654 let pck = self
2655 .scene_pck_cache
2656 .as_ref()
2657 .expect("scene pck cache initialized");
2658 (
2659 pck.inc_cmd_name_map.clone(),
2660 pck.find_scene_name(target_scene_no).map(ToOwned::to_owned),
2661 )
2662 };
2663
2664 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
2665 let saved_user_cmd_names =
2666 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
2667 let saved_call_cmd_names =
2668 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
2669 let saved_current_scene_no = self.current_scene_no;
2670 let saved_current_scene_name = self.current_scene_name.clone();
2671 let saved_current_line_no = self.current_line_no;
2672
2673 self.enter_cross_scene_user_prop_scope(target_scene_no);
2674 self.current_scene_no = Some(target_scene_no);
2675 self.current_scene_name = target_scene_name;
2676 self.current_line_no = -1;
2677 self.ctx.current_scene_no = Some(target_scene_no as i64);
2678 self.ctx.current_scene_name = self.current_scene_name.clone();
2679 self.ctx.current_line_no = -1;
2680
2681 for arg in call_args {
2682 self.push_call_arg_value(arg);
2683 }
2684 let mut call_frame = self.take_call_frame(
2685 self.cfg.fm_void,
2686 ex_call_proc,
2687 frame_action_proc,
2688 call_args.len(),
2689 None,
2690 );
2691 call_frame.call_type = 3;
2692 self.call_stack.push(call_frame);
2693 self.scene_stack.push(SceneExecFrame {
2694 stream: saved_stream,
2695 user_cmd_names: saved_user_cmd_names,
2696 call_cmd_names: saved_call_cmd_names,
2697 current_scene_no: saved_current_scene_no,
2698 current_scene_name: saved_current_scene_name,
2699 current_line_no: saved_current_line_no,
2700 call_depth: self.call_stack.len(),
2701 });
2702 self.stream.set_prg_cntr(target_offset)?;
2703 if ex_call_proc {
2704 self.mark_excall_script_proc_requested();
2705 }
2706 Ok(true)
2707 }
2708
2709 fn enter_scene_user_cmd_call(
2710 &mut self,
2711 scn_name: Option<&str>,
2712 cmd_name: &str,
2713 call_args: &[Value],
2714 ) -> Result<bool> {
2715 let Some(requested_scene_no) = self.requested_user_command_scene_no(scn_name)? else {
2716 return Ok(false);
2717 };
2718 let Some(command) = self.resolve_user_command_by_name(requested_scene_no, cmd_name)? else {
2719 if self.runtime_options.sg_debug {
2720 eprintln!(
2721 "[SG_DEBUG][BUTTON] user command not found for ex-call: requested_scene={} scn_name={:?} cmd={}",
2722 requested_scene_no, scn_name, cmd_name
2723 );
2724 }
2725 return Ok(false);
2726 };
2727
2728 if self.runtime_options.sg_debug {
2729 eprintln!(
2730 "[SG_DEBUG][BUTTON] enter user command requested_scene={} target_scene={} cmd={} encoded_no={} include={} offset=0x{:x}",
2731 requested_scene_no,
2732 command.target_scene_no,
2733 command.name.as_str(),
2734 command.encoded_no,
2735 command.include_command,
2736 command.target_offset
2737 );
2738 }
2739 self.enter_resolved_user_command(&command, self.cfg.fm_void, call_args, true, false)
2740 }
2741
2742 fn enter_current_scene_user_cmd_at_offset(
2743 &mut self,
2744 offset: usize,
2745 call_args: &[Value],
2746 ) -> Result<bool> {
2747 self.enter_current_scene_user_cmd_proc_at_offset(
2748 offset,
2749 self.cfg.fm_void,
2750 call_args,
2751 true,
2752 false,
2753 )
2754 }
2755
2756 fn enter_scene_user_cmd_at_scene_offset(
2757 &mut self,
2758 target_scene_no: usize,
2759 target_offset: usize,
2760 call_args: &[Value],
2761 ) -> Result<bool> {
2762 self.enter_scene_user_cmd_at_scene_offset_ex(
2763 target_scene_no,
2764 target_offset,
2765 call_args,
2766 self.cfg.fm_void,
2767 true,
2768 false,
2769 )
2770 }
2771
2772 fn run_current_scene_user_cmd_inline(
2773 &mut self,
2774 cmd_name: &str,
2775 call_args: &[Value],
2776 ) -> Result<bool> {
2777 self.run_scene_user_cmd_inline(None, cmd_name, call_args, self.cfg.fm_void, false)
2778 }
2779
2780 fn run_scene_user_cmd_inline_at_scene_offset(
2781 &mut self,
2782 pck: &ScenePck,
2783 target_scene_no: usize,
2784 cmd_name: &str,
2785 target_offset: usize,
2786 call_args: &[Value],
2787 preserve_return_pc: bool,
2788 frame_action_proc: bool,
2789 ) -> Result<bool> {
2790 let (owner, range) = pck.scn_data_shared(target_scene_no)?;
2791 let target_stream: SceneStream<'a> =
2792 SceneStream::new_shared_range_with_string_codec(owner, range, pck.string_codec)?;
2793 if target_offset > target_stream.scn.len() {
2794 bail!(
2795 "scene_pck: user command offset out of bounds: cmd={} scn_no={} offset=0x{:x} scn_len=0x{:x}",
2796 cmd_name,
2797 target_scene_no,
2798 target_offset,
2799 target_stream.scn.len()
2800 );
2801 }
2802
2803 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
2804 let target_user_cmd_names = self.stream.scn_cmd_name_map.clone();
2805 let target_call_cmd_names = pck.inc_cmd_name_map.clone();
2806 let saved_user_cmd_names =
2807 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
2808 let saved_call_cmd_names =
2809 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
2810 let saved_current_scene_no = self.current_scene_no;
2811 let saved_current_scene_name = self.current_scene_name.clone();
2812 let saved_current_line_no = self.current_line_no;
2813 let saved_ctx_scene_no = self.ctx.current_scene_no;
2814 let saved_ctx_scene_name = self.ctx.current_scene_name.clone();
2815 let saved_ctx_line_no = self.ctx.current_line_no;
2816 let saved_halted = self.halted;
2817 self.enter_cross_scene_user_prop_scope(target_scene_no);
2818
2819 self.current_scene_no = Some(target_scene_no);
2820 self.current_scene_name = pck.find_scene_name(target_scene_no).map(ToOwned::to_owned);
2821 self.current_line_no = -1;
2822 self.ctx.current_scene_no = Some(target_scene_no as i64);
2823 self.ctx.current_scene_name = self.current_scene_name.clone();
2824 self.ctx.current_line_no = -1;
2825
2826 let target_return_pc = if preserve_return_pc {
2827 saved_stream.get_prg_cntr()
2828 } else {
2829 self.stream.scn.len()
2830 };
2831 let result = self.run_user_cmd_inline_at_offset(
2832 cmd_name,
2833 target_offset,
2834 target_return_pc,
2835 None,
2836 None,
2837 self.cfg.fm_void,
2838 call_args,
2839 frame_action_proc,
2840 );
2841
2842 self.restore_cross_scene_user_prop_scope(saved_current_scene_no);
2845 self.stream = saved_stream;
2846 self.user_cmd_names = saved_user_cmd_names;
2847 self.call_cmd_names = saved_call_cmd_names;
2848 self.current_scene_no = saved_current_scene_no;
2849 self.current_scene_name = saved_current_scene_name;
2850 self.current_line_no = saved_current_line_no;
2851 self.ctx.current_scene_no = saved_ctx_scene_no;
2852 self.ctx.current_scene_name = saved_ctx_scene_name;
2853 self.ctx.current_line_no = saved_ctx_line_no;
2854 self.halted = saved_halted;
2855 result
2856 }
2857
2858 fn collect_object_frame_action_work_recursive(
2859 obj: &crate::runtime::globals::ObjectState,
2860 stage_idx: i64,
2861 obj_idx: usize,
2862 object_chain: &mut Vec<i32>,
2866 out: &mut Vec<FrameActionWork>,
2867 ) {
2868 let fa = &obj.frame_action;
2869 if !fa.cmd_name.is_empty() {
2870 let mut frame_action_chain = object_chain.clone();
2871 frame_action_chain.push(crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION);
2872 out.push(FrameActionWork {
2873 stage_idx,
2874 obj_idx,
2875 ch_idx: None,
2876 global_form_id: None,
2877 object_chain: Some(object_chain.clone()),
2878 frame_action_chain: Some(frame_action_chain),
2879 scn_name: fa.scn_name.clone(),
2880 cmd_name: fa.cmd_name.clone(),
2881 args: fa.args.clone(),
2882 count: fa.counter.get_count(),
2883 end_time: fa.end_time,
2884 });
2885 }
2886 for (ch_idx, ch) in obj.frame_action_ch.iter().enumerate() {
2887 if !ch.cmd_name.is_empty() {
2888 let mut frame_action_chain = object_chain.clone();
2889 frame_action_chain
2890 .push(crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH);
2891 frame_action_chain.push(crate::runtime::forms::codes::ELM_ARRAY);
2892 frame_action_chain.push(ch_idx as i32);
2893 out.push(FrameActionWork {
2894 stage_idx,
2895 obj_idx,
2896 ch_idx: Some(ch_idx),
2897 global_form_id: None,
2898 object_chain: Some(object_chain.clone()),
2899 frame_action_chain: Some(frame_action_chain),
2900 scn_name: ch.scn_name.clone(),
2901 cmd_name: ch.cmd_name.clone(),
2902 args: ch.args.clone(),
2903 count: ch.counter.get_count(),
2904 end_time: ch.end_time,
2905 });
2906 }
2907 }
2908 for (child_idx, child) in obj.runtime.child_objects.iter().enumerate() {
2909 let depth = object_chain.len();
2914 object_chain.extend([
2915 crate::runtime::forms::codes::elm_value::OBJECT_CHILD,
2916 crate::runtime::forms::codes::ELM_ARRAY,
2917 child_idx as i32,
2918 ]);
2919 Self::collect_object_frame_action_work_recursive(
2920 child,
2921 stage_idx,
2922 child_idx,
2923 object_chain,
2924 out,
2925 );
2926 object_chain.truncate(depth);
2927 }
2928 }
2929
2930 fn object_child_from_chain_mut<'b>(
2931 mut obj: &'b mut crate::runtime::globals::ObjectState,
2932 object_chain: &[i32],
2933 mut pos: usize,
2934 elm_array: i32,
2935 ) -> Option<&'b mut crate::runtime::globals::ObjectState> {
2936 while pos + 2 < object_chain.len() {
2937 let op = object_chain[pos];
2938 if op != crate::runtime::forms::codes::elm_value::OBJECT_CHILD {
2939 break;
2940 }
2941 if object_chain[pos + 1] != elm_array
2942 && object_chain[pos + 1] != crate::runtime::forms::codes::ELM_ARRAY
2943 {
2944 return None;
2945 }
2946 let child_idx = object_chain[pos + 2].max(0) as usize;
2947 obj = obj.runtime.child_objects.get_mut(child_idx)?;
2948 pos += 3;
2949 }
2950 Some(obj)
2951 }
2952
2953 fn with_frame_action_mut<R>(
2954 &mut self,
2955 item: &FrameActionWork,
2956 f: impl FnOnce(&mut crate::runtime::globals::ObjectFrameActionState) -> R,
2957 ) -> Option<R> {
2958 if item.object_chain.is_none() {
2959 let form_id = item.global_form_id?;
2960 if let Some(idx) = item.ch_idx {
2961 let list = self.ctx.globals.frame_action_lists.get_mut(&form_id)?;
2962 return list.get_mut(idx).map(f);
2963 }
2964 return self.ctx.globals.frame_actions.get_mut(&form_id).map(f);
2965 }
2966
2967 let chain = item.object_chain.as_deref()?;
2968 let elm_array = if self.ctx.ids.elm_array != 0 {
2969 self.ctx.ids.elm_array
2970 } else {
2971 crate::runtime::forms::codes::ELM_ARRAY
2972 };
2973 let locator = parse_frame_action_object_locator(chain, elm_array)?;
2974 let form_id =
2975 crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, locator.raw_form_id);
2976 let st = self.ctx.globals.stage_forms.get_mut(&form_id)?;
2977 let obj = match locator.root {
2978 FrameActionObjectRoot::Stage { obj_idx, child_pos } => {
2979 let objects = st.object_lists.get_mut(&locator.stage_idx)?;
2980 let obj = objects.get_mut(obj_idx)?;
2981 Self::object_child_from_chain_mut(obj, chain, child_pos, elm_array)?
2982 }
2983 FrameActionObjectRoot::Mwnd {
2984 mwnd_idx,
2985 selector,
2986 obj_idx,
2987 child_pos,
2988 } => {
2989 let mwnds = st.mwnd_lists.get_mut(&locator.stage_idx)?;
2990 let mwnd = mwnds.get_mut(mwnd_idx)?;
2991 let obj = if selector == crate::runtime::forms::codes::elm_value::MWND_BUTTON {
2992 mwnd.button_list.get_mut(obj_idx)?
2993 } else if selector == crate::runtime::forms::codes::elm_value::MWND_FACE {
2994 mwnd.face_list.get_mut(obj_idx)?
2995 } else {
2996 mwnd.object_list.get_mut(obj_idx)?
2997 };
2998 Self::object_child_from_chain_mut(obj, chain, child_pos, elm_array)?
2999 }
3000 FrameActionObjectRoot::BtnSelItem {
3001 item_idx,
3002 obj_idx,
3003 child_pos,
3004 } => {
3005 let items = st.btnselitem_lists.get_mut(&locator.stage_idx)?;
3006 let item = items.get_mut(item_idx)?;
3007 let obj = item.object_list.get_mut(obj_idx)?;
3008 Self::object_child_from_chain_mut(obj, chain, child_pos, elm_array)?
3009 }
3010 };
3011
3012 if let Some(idx) = item.ch_idx {
3013 obj.frame_action_ch.get_mut(idx).map(f)
3014 } else {
3015 Some(f(&mut obj.frame_action))
3016 }
3017 }
3018
3019 fn begin_frame_action_finish(
3020 &mut self,
3021 item: &FrameActionWork,
3022 ) -> Option<(String, String, Vec<Value>)> {
3023 self.with_frame_action_mut(item, |fa| {
3024 if fa.cmd_name.is_empty() || fa.end_time < 0 {
3025 return None;
3026 }
3027 if fa.counter.get_count() < fa.end_time {
3028 return None;
3029 }
3030
3031 let scn_name = fa.scn_name.clone();
3035 let cmd_name = fa.cmd_name.clone();
3036 let args = fa.args.clone();
3037 fa.counter.set_count(fa.end_time);
3038 fa.scn_name.clear();
3039 fa.cmd_name.clear();
3040 fa.end_flag = true;
3041 Some((scn_name, cmd_name, args))
3042 })?
3043 }
3044
3045 fn end_frame_action_finish(&mut self, item: &FrameActionWork) {
3046 let _ = self.with_frame_action_mut(item, |fa| {
3047 fa.reinit_without_finish();
3051 });
3052 }
3053
3054 fn make_frame_action_call_args(
3055 frame_action_chain: Option<&Vec<i32>>,
3056 object_chain: Option<&Vec<i32>>,
3057 args: &[Value],
3058 ) -> Vec<Value> {
3059 let mut call_args = Vec::with_capacity(args.len() + 2);
3060 if let Some(frame_action_chain) = frame_action_chain {
3061 call_args.push(Value::Element(frame_action_chain.clone()));
3062 }
3063 if let Some(object_chain) = object_chain {
3064 call_args.push(Value::Element(object_chain.clone()));
3065 }
3066 call_args.extend(args.iter().cloned());
3067 call_args
3068 }
3069
3070 fn runtime_slot_from_object_children(
3071 mut obj: &mut crate::runtime::globals::ObjectState,
3072 fallback_slot: usize,
3073 chain: &[i32],
3074 mut pos: usize,
3075 elm_array: i32,
3076 next_slot: &mut usize,
3077 ) -> usize {
3078 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
3079 let mut slot = obj.runtime_slot_or(fallback_slot);
3080 while pos + 2 < chain.len() {
3081 if chain[pos] == object_child
3082 && (chain[pos + 1] == elm_array
3083 || chain[pos + 1] == crate::runtime::forms::codes::ELM_ARRAY)
3084 {
3085 let child_idx = chain[pos + 2].max(0) as usize;
3086 if obj.runtime.child_objects.len() <= child_idx {
3087 obj.runtime
3088 .child_objects
3089 .resize_with(child_idx + 1, crate::runtime::globals::ObjectState::default);
3090 }
3091 let child = &mut obj.runtime.child_objects[child_idx];
3092 slot = child.ensure_runtime_slot(next_slot);
3093 obj = child;
3094 pos += 3;
3095 } else {
3096 pos += 1;
3097 }
3098 }
3099 slot
3100 }
3101
3102 fn runtime_slot_from_object_chain(&mut self, fallback_obj_idx: usize, chain: &[i32]) -> usize {
3103 let elm_array = if self.ctx.ids.elm_array != 0 {
3104 self.ctx.ids.elm_array
3105 } else {
3106 crate::runtime::forms::codes::ELM_ARRAY
3107 };
3108 let Some(locator) = parse_frame_action_object_locator(chain, elm_array) else {
3109 return fallback_obj_idx;
3110 };
3111 let stage_form =
3112 crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, locator.raw_form_id);
3113 let Some(st) = self.ctx.globals.stage_forms.get_mut(&stage_form) else {
3114 return fallback_obj_idx;
3115 };
3116 let nested_slot_base = st.backend_slot_base + 100_000;
3117 let next_slot = st
3118 .next_nested_object_slot
3119 .entry(locator.stage_idx)
3120 .or_insert(nested_slot_base);
3121
3122 match locator.root {
3123 FrameActionObjectRoot::Stage { obj_idx, child_pos } => {
3124 let Some(list) = st.object_lists.get_mut(&locator.stage_idx) else {
3125 return obj_idx;
3126 };
3127 let Some(obj) = list.get_mut(obj_idx) else {
3128 return obj_idx;
3129 };
3130 Self::runtime_slot_from_object_children(
3131 obj, obj_idx, chain, child_pos, elm_array, next_slot,
3132 )
3133 }
3134 FrameActionObjectRoot::Mwnd {
3135 mwnd_idx,
3136 selector,
3137 obj_idx,
3138 child_pos,
3139 } => {
3140 let Some(mwnds) = st.mwnd_lists.get_mut(&locator.stage_idx) else {
3141 return obj_idx;
3142 };
3143 let Some(mwnd) = mwnds.get_mut(mwnd_idx) else {
3144 return obj_idx;
3145 };
3146 let obj = if selector == crate::runtime::forms::codes::elm_value::MWND_BUTTON {
3147 mwnd.button_list.get_mut(obj_idx)
3148 } else if selector == crate::runtime::forms::codes::elm_value::MWND_FACE {
3149 mwnd.face_list.get_mut(obj_idx)
3150 } else {
3151 mwnd.object_list.get_mut(obj_idx)
3152 };
3153 let Some(obj) = obj else {
3154 return obj_idx;
3155 };
3156 Self::runtime_slot_from_object_children(
3157 obj, obj_idx, chain, child_pos, elm_array, next_slot,
3158 )
3159 }
3160 FrameActionObjectRoot::BtnSelItem {
3161 item_idx,
3162 obj_idx,
3163 child_pos,
3164 } => {
3165 let Some(items) = st.btnselitem_lists.get_mut(&locator.stage_idx) else {
3166 return obj_idx;
3167 };
3168 let Some(item) = items.get_mut(item_idx) else {
3169 return obj_idx;
3170 };
3171 let Some(obj) = item.object_list.get_mut(obj_idx) else {
3172 return obj_idx;
3173 };
3174 Self::runtime_slot_from_object_children(
3175 obj, obj_idx, chain, child_pos, elm_array, next_slot,
3176 )
3177 }
3178 }
3179 }
3180
3181 fn set_frame_action_current_object(
3182 &mut self,
3183 item: &FrameActionWork,
3184 ) -> (Option<(i64, usize)>, Option<Vec<i32>>) {
3185 let prev_target = self.ctx.globals.current_stage_object;
3186 let prev_chain = self.ctx.globals.current_object_chain.clone();
3187 if let Some(chain) = item.object_chain.clone() {
3188 let elm_array = if self.ctx.ids.elm_array != 0 {
3189 self.ctx.ids.elm_array
3190 } else {
3191 crate::runtime::forms::codes::ELM_ARRAY
3192 };
3193 if let Some(locator) = parse_frame_action_object_locator(&chain, elm_array) {
3194 let top_idx = frame_action_locator_object_idx(locator);
3195 let runtime_slot = self.runtime_slot_from_object_chain(top_idx, &chain);
3196 self.ctx.globals.current_stage_object = Some((locator.stage_idx, runtime_slot));
3197 self.ctx.globals.current_object_chain = Some(chain);
3198 } else {
3199 self.ctx.globals.current_stage_object = None;
3200 self.ctx.globals.current_object_chain = None;
3201 }
3202 } else {
3203 self.ctx.globals.current_stage_object = None;
3204 self.ctx.globals.current_object_chain = None;
3205 }
3206 (prev_target, prev_chain)
3207 }
3208
3209 fn restore_frame_action_current_object(
3210 &mut self,
3211 prev_target: Option<(i64, usize)>,
3212 prev_chain: Option<Vec<i32>>,
3213 ) {
3214 self.ctx.globals.current_stage_object = prev_target;
3215 self.ctx.globals.current_object_chain = prev_chain;
3216 }
3217
3218 fn frame_action_work_from_pending_finish(
3219 &self,
3220 pending: &PendingFrameActionFinish,
3221 ) -> FrameActionWork {
3222 let object_chain = pending.object_chain.clone();
3223 let elm_array = if self.ctx.ids.elm_array != 0 {
3224 self.ctx.ids.elm_array
3225 } else {
3226 crate::runtime::forms::codes::ELM_ARRAY
3227 };
3228 let (stage_idx, obj_idx) = object_chain
3229 .as_deref()
3230 .and_then(|chain| parse_frame_action_object_locator(chain, elm_array))
3231 .map(|locator| (locator.stage_idx, frame_action_locator_object_idx(locator)))
3232 .unwrap_or((-1, usize::MAX));
3233
3234 let ch_idx = if let Some(chain) = object_chain.as_ref() {
3235 let base = chain.len();
3236 if pending.frame_action_chain.len() >= base + 3
3237 && pending.frame_action_chain[base]
3238 == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH
3239 && pending.frame_action_chain[base + 1] == crate::runtime::forms::codes::ELM_ARRAY
3240 {
3241 Some(pending.frame_action_chain[base + 2].max(0) as usize)
3242 } else {
3243 None
3244 }
3245 } else if pending.frame_action_chain.len() >= 3
3246 && pending.frame_action_chain[1] == crate::runtime::forms::codes::ELM_ARRAY
3247 {
3248 Some(pending.frame_action_chain[2].max(0) as usize)
3249 } else {
3250 None
3251 };
3252
3253 let global_form_id = if object_chain.is_none() {
3254 pending
3255 .frame_action_chain
3256 .first()
3257 .copied()
3258 .map(|v| v as u32)
3259 } else {
3260 None
3261 };
3262
3263 FrameActionWork {
3264 stage_idx,
3265 obj_idx,
3266 ch_idx,
3267 global_form_id,
3268 object_chain,
3269 frame_action_chain: Some(pending.frame_action_chain.clone()),
3270 scn_name: pending.scn_name.clone(),
3271 cmd_name: pending.cmd_name.clone(),
3272 args: pending.args.clone(),
3273 count: pending.end_time,
3274 end_time: pending.end_time,
3275 }
3276 }
3277
3278 fn run_pending_frame_action_finish(&mut self, pending: PendingFrameActionFinish) -> Result<()> {
3279 if pending.cmd_name.is_empty() {
3280 return Ok(());
3281 }
3282 let item = self.frame_action_work_from_pending_finish(&pending);
3283 let final_count = if pending.end_time == -1 {
3284 0
3285 } else {
3286 pending.end_time
3287 };
3288
3289 let outer_state = self.with_frame_action_mut(&item, |fa| {
3295 let outer_state = fa.clone();
3296 let mut finishing = pending.snapshot.clone();
3297 finishing.scn_name.clear();
3298 finishing.cmd_name.clear();
3299 finishing.counter.set_count(final_count);
3300 finishing.end_flag = true;
3301 *fa = finishing;
3302 outer_state
3303 });
3304
3305 let call_args = Self::make_frame_action_call_args(
3306 item.frame_action_chain.as_ref(),
3307 item.object_chain.as_ref(),
3308 &pending.args,
3309 );
3310 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
3311 let result = self.run_scene_user_cmd_inline(
3312 Some(&pending.scn_name),
3313 &pending.cmd_name,
3314 &call_args,
3315 self.cfg.fm_void,
3316 true,
3317 );
3318 self.restore_frame_action_current_object(prev_target, prev_chain);
3319
3320 if pending.reinit_after_finish {
3321 let _ = self.with_frame_action_mut(&item, |fa| {
3322 fa.reinit_without_finish();
3323 });
3324 } else if let Some(outer_state) = outer_state {
3325 let _ = self.with_frame_action_mut(&item, |fa| {
3326 *fa = outer_state;
3327 });
3328 }
3329 if let Err(e) = result {
3330 self.ctx.unknown.record_note(&format!(
3331 "frame_action.finish.failed:{}:{}:{e}",
3332 pending.scn_name, pending.cmd_name
3333 ));
3334 }
3335 Ok(())
3336 }
3337
3338 fn run_pending_button_action(&mut self, action: PendingButtonAction) -> Result<()> {
3339 self.ctx.input.use_current();
3346 self.script_input_synced_this_frame = false;
3347
3348 match action.kind {
3349 PendingButtonActionKind::UserCall {
3350 scn_name,
3351 cmd_name,
3352 z_no,
3353 } => {
3354 if scn_name.is_empty() {
3355 return Ok(());
3356 }
3357 if self.runtime_options.sg_debug {
3358 eprintln!(
3359 "[SG_DEBUG][BUTTON] run action scene={} cmd={} z_no={}",
3360 scn_name, cmd_name, z_no
3361 );
3362 }
3363 if !cmd_name.is_empty() {
3364 let _ = self.enter_scene_user_cmd_call(Some(&scn_name), &cmd_name, &[])?;
3365 } else if z_no >= 0 {
3366 self.farcall_scene_name_ex(
3367 &scn_name,
3368 z_no as i32,
3369 self.cfg.fm_void,
3370 true,
3371 &[],
3372 )?;
3373 }
3374 }
3375 PendingButtonActionKind::Syscom {
3376 sys_type,
3377 sys_type_opt,
3378 mode,
3379 } => {
3380 self.run_pending_button_syscom_action(sys_type, sys_type_opt, mode)?;
3381 }
3382 }
3383 Ok(())
3384 }
3385
3386 #[inline(always)]
3387 fn syscom_proc_trace_enabled(&self) -> bool {
3388 self.runtime_options.syscom_proc_trace
3389 }
3390
3391 fn syscom_trace_state(&self) -> String {
3392 let st = &self.ctx.globals.syscom;
3393 let msgbk_form = self.ctx.ids.form_global_msgbk;
3394 let msgbk_count = self
3395 .ctx
3396 .globals
3397 .msgbk_forms
3398 .get(&msgbk_form)
3399 .map(|m| m.history.len())
3400 .unwrap_or(0);
3401 let msgbk_visible_count = self
3402 .ctx
3403 .globals
3404 .msgbk_forms
3405 .get(&msgbk_form)
3406 .map(|m| {
3407 m.history
3408 .iter()
3409 .filter(|entry| {
3410 entry.pct_flag
3411 || !entry.msg_str.is_empty()
3412 || !entry.disp_name.is_empty()
3413 || !entry.original_name.is_empty()
3414 || !entry.koe_no_list.is_empty()
3415 })
3416 .count()
3417 })
3418 .unwrap_or(0);
3419 format!(
3420 "read_skip={} auto_mode={} hide_mwnd={} msg_back_open={} msg_back_enable={} pending_proc={:?} msgbk_form={} msgbk_count={} msgbk_visible_count={} mwnd_waiting={} mwnd_visible_chars={} mwnd_wait_len={} msg_chars={}",
3421 st.read_skip.onoff,
3422 st.auto_mode.onoff,
3423 st.hide_mwnd.onoff,
3424 st.msg_back_open,
3425 st.msg_back.check_enabled(),
3426 st.pending_proc,
3427 msgbk_form,
3428 msgbk_count,
3429 msgbk_visible_count,
3430 self.ctx.ui.message_waiting(),
3431 self.ctx.ui.message_visible_chars(),
3432 self.ctx.ui.message_wait_message_len(),
3433 self.ctx.ui.message_text().unwrap_or("").chars().count()
3434 )
3435 }
3436
3437 fn syscom_button_op(sys_type: i64) -> Option<(i32, &'static str)> {
3438 use crate::runtime::forms::codes::syscom_op;
3439 Some(match sys_type {
3440 1 => (syscom_op::CALL_SAVE_MENU, "CALL_SAVE_MENU"),
3441 2 => (syscom_op::CALL_LOAD_MENU, "CALL_LOAD_MENU"),
3442 3 => (
3443 syscom_op::SET_READ_SKIP_ONOFF_FLAG,
3444 "SET_READ_SKIP_ONOFF_FLAG",
3445 ),
3446 4 => (
3447 syscom_op::SET_AUTO_MODE_ONOFF_FLAG,
3448 "SET_AUTO_MODE_ONOFF_FLAG",
3449 ),
3450 5 => (syscom_op::RETURN_TO_SEL, "RETURN_TO_SEL"),
3451 6 => (
3452 syscom_op::SET_HIDE_MWND_ONOFF_FLAG,
3453 "SET_HIDE_MWND_ONOFF_FLAG",
3454 ),
3455 7 => (syscom_op::OPEN_MSG_BACK, "OPEN_MSG_BACK"),
3456 8 => (syscom_op::REPLAY_KOE, "REPLAY_KOE"),
3457 9 => (syscom_op::QUICK_SAVE, "QUICK_SAVE"),
3458 10 => (syscom_op::QUICK_LOAD, "QUICK_LOAD"),
3459 11 => (syscom_op::CALL_CONFIG_MENU, "CALL_CONFIG_MENU"),
3460 12 => (
3461 syscom_op::SET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG,
3462 "SET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG",
3463 ),
3464 13 => (
3465 syscom_op::SET_LOCAL_EXTRA_MODE_VALUE,
3466 "SET_LOCAL_EXTRA_MODE_VALUE",
3467 ),
3468 14 => (
3469 syscom_op::SET_GLOBAL_EXTRA_SWITCH_ONOFF,
3470 "SET_GLOBAL_EXTRA_SWITCH_ONOFF",
3471 ),
3472 15 => (
3473 syscom_op::SET_GLOBAL_EXTRA_MODE_VALUE,
3474 "SET_GLOBAL_EXTRA_MODE_VALUE",
3475 ),
3476 _ => return None,
3477 })
3478 }
3479
3480 fn dispatch_syscom_button_op(&mut self, op: i32, params: &[Value]) -> Result<bool> {
3481 let form_id = if self.ctx.ids.form_global_syscom != 0 {
3487 self.ctx.ids.form_global_syscom as i32
3488 } else {
3489 constants::global_form::SYSCOM as i32
3490 };
3491
3492 let saved_call = self.ctx.vm_call.take();
3493 let saved_stack_len = self.ctx.stack.len();
3494 self.ctx.vm_call = Some(runtime::VmCallMeta {
3495 element: vec![form_id, op],
3496 al_id: 0,
3497 ret_form: self.cfg.fm_void as i64,
3498 });
3499
3500 let result = runtime::dispatch_form_code(&mut self.ctx, form_id as u32, params);
3501
3502 self.ctx.vm_call = saved_call;
3503 self.ctx.stack.truncate(saved_stack_len);
3504 result
3505 }
3506
3507 fn run_pending_button_syscom_action(
3508 &mut self,
3509 sys_type: i64,
3510 sys_type_opt: i64,
3511 mode: i64,
3512 ) -> Result<()> {
3513 let trace = self.syscom_proc_trace_enabled();
3514 let Some((op, op_name)) = Self::syscom_button_op(sys_type) else {
3515 if trace {
3516 eprintln!(
3517 "[SYSCOM_PROC_TRACE] button sys_type={} sys_opt={} mode={} resolved=UNKNOWN before {}",
3518 sys_type,
3519 sys_type_opt,
3520 mode,
3521 self.syscom_trace_state()
3522 );
3523 }
3524 return Ok(());
3525 };
3526
3527 if matches!(sys_type, 1 | 9) {
3528 crate::runtime::forms::syscom::prepare_runtime_save_thumb_capture(&mut self.ctx);
3529 }
3530
3531 let params: Vec<Value> = match sys_type {
3538 1 | 2 => Vec::new(),
3539 3 | 4 => vec![Value::Int(if mode == 0 { 1 } else { 0 })],
3540 6 => vec![Value::Int(1)],
3541 5 => vec![Value::Int(1), Value::Int(1), Value::Int(1)],
3542 7 | 8 | 11 => Vec::new(),
3543 9 => vec![Value::Int(sys_type_opt), Value::Int(1), Value::Int(1)],
3544 10 => vec![
3545 Value::Int(sys_type_opt),
3546 Value::Int(1),
3547 Value::Int(1),
3548 Value::Int(1),
3549 ],
3550 12 | 14 => vec![
3551 Value::Int(sys_type_opt),
3552 Value::Int(if mode == 0 { 1 } else { 0 }),
3553 ],
3554 13 | 15 => vec![Value::Int(sys_type_opt), Value::Int(mode + 1)],
3555 _ => Vec::new(),
3556 };
3557 if trace {
3558 eprintln!(
3559 "[SYSCOM_PROC_TRACE] button sys_type={} sys_opt={} mode={} resolved={}({}) params={:?} before {}",
3560 sys_type,
3561 sys_type_opt,
3562 mode,
3563 op_name,
3564 op,
3565 params,
3566 self.syscom_trace_state()
3567 );
3568 }
3569 let dispatch_result = self.dispatch_syscom_button_op(op, ¶ms);
3570 if trace {
3571 let status = match dispatch_result.as_ref() {
3572 Ok(handled) => format!("ok handled={}", handled),
3573 Err(err) => format!("err={}", err),
3574 };
3575 eprintln!(
3576 "[SYSCOM_PROC_TRACE] after resolved={}({}) status={} {}",
3577 op_name,
3578 op,
3579 status,
3580 self.syscom_trace_state()
3581 );
3582 }
3583 dispatch_result?;
3584 Ok(())
3585 }
3586
3587 fn drain_pending_button_actions(&mut self) -> Result<()> {
3588 let mut budget = 64usize;
3589 while !self.ctx.globals.pending_button_actions.is_empty() {
3590 if budget == 0 {
3591 bail!("button action queue did not drain");
3592 }
3593 budget -= 1;
3594 let pending = std::mem::take(&mut self.ctx.globals.pending_button_actions);
3595 for action in pending {
3596 self.run_pending_button_action(action)?;
3597 }
3598 }
3599 Ok(())
3600 }
3601
3602 pub fn process_pending_button_actions(&mut self) -> Result<()> {
3603 self.drain_pending_button_actions()
3604 }
3605
3606 fn drain_pending_frame_action_finishes(&mut self) -> Result<()> {
3607 let mut budget = 64usize;
3608 while !self.ctx.globals.pending_frame_action_finishes.is_empty() {
3609 if budget == 0 {
3610 bail!("frame action finish queue did not drain");
3611 }
3612 budget -= 1;
3613 let pending = std::mem::take(&mut self.ctx.globals.pending_frame_action_finishes);
3614 for item in pending {
3615 self.run_pending_frame_action_finish(item)?;
3616 }
3617 }
3618 crate::runtime::forms::excall::finalize_pending_free(&mut self.ctx);
3623 Ok(())
3624 }
3625
3626 pub fn tick_frame(&mut self) -> Result<()> {
3627 let trace = self.runtime_options.tick_trace || self.runtime_options.frame_action_trace;
3628 if trace {
3629 eprintln!(
3630 "[SG_TICK_TRACE] tick_frame start blocked={} halted={} scene={:?}",
3631 self.is_blocked(),
3632 self.halted,
3633 self.current_scene_name()
3634 );
3635 }
3636 self.drain_pending_button_actions()?;
3637 if self.ctx.globals.syscom.pending_proc.is_some() {
3638 if trace || self.runtime_options.sg_debug {
3639 eprintln!(
3640 "[SG_DEBUG][SYSCOM_PROC] stop frame tick before frame actions pending_proc={:?}",
3641 self.ctx.globals.syscom.pending_proc
3642 );
3643 }
3644 return Ok(());
3645 }
3646 if self.ctx.globals.script.frame_action_time_stop_flag && trace {
3647 eprintln!(
3648 "[SG_TICK_TRACE] frame_action_time_stop_flag set; executing callbacks with frozen frame-action time"
3649 );
3650 }
3651
3652 self.ctx.tick_frame();
3658
3659 if let Some(result) = self.ctx.take_pending_sel_point_result() {
3660 self.int_stack.push(result);
3664 let point = self.make_resume_point();
3665 let _ = self.int_stack.pop();
3666 self.sel_point_stack.clear();
3667 self.sel_point_stack.push(point);
3668 }
3669
3670 let mut work: Vec<FrameActionWork> = Vec::new();
3671 let mut global_form_ids: Vec<u32> =
3672 self.ctx.globals.frame_actions.keys().copied().collect();
3673 global_form_ids.sort_unstable();
3674 for form_id in global_form_ids {
3675 let Some(fa) = self.ctx.globals.frame_actions.get(&form_id) else {
3676 continue;
3677 };
3678 if !fa.cmd_name.is_empty() {
3679 work.push(FrameActionWork {
3680 stage_idx: -1,
3681 obj_idx: usize::MAX,
3682 ch_idx: None,
3683 global_form_id: Some(form_id),
3684 object_chain: None,
3685 frame_action_chain: Some(vec![form_id as i32]),
3686 scn_name: fa.scn_name.clone(),
3687 cmd_name: fa.cmd_name.clone(),
3688 args: fa.args.clone(),
3689 count: fa.counter.get_count(),
3690 end_time: fa.end_time,
3691 });
3692 }
3693 }
3694 let mut global_list_form_ids: Vec<u32> = self
3695 .ctx
3696 .globals
3697 .frame_action_lists
3698 .keys()
3699 .copied()
3700 .collect();
3701 global_list_form_ids.sort_unstable();
3702 for form_id in global_list_form_ids {
3703 let Some(list) = self.ctx.globals.frame_action_lists.get(&form_id) else {
3704 continue;
3705 };
3706 for (idx, fa) in list.iter().enumerate() {
3707 if !fa.cmd_name.is_empty() {
3708 work.push(FrameActionWork {
3709 stage_idx: -1,
3710 obj_idx: usize::MAX,
3711 ch_idx: Some(idx),
3712 global_form_id: Some(form_id),
3713 object_chain: None,
3714 frame_action_chain: Some(vec![
3715 form_id as i32,
3716 crate::runtime::forms::codes::ELM_ARRAY,
3717 idx as i32,
3718 ]),
3719 scn_name: fa.scn_name.clone(),
3720 cmd_name: fa.cmd_name.clone(),
3721 args: fa.args.clone(),
3722 count: fa.counter.get_count(),
3723 end_time: fa.end_time,
3724 });
3725 }
3726 }
3727 }
3728 let mut stage_form_ids: Vec<u32> = self.ctx.globals.stage_forms.keys().copied().collect();
3729 stage_form_ids.sort_unstable();
3730 for form_id in stage_form_ids {
3731 let Some(st) = self.ctx.globals.stage_forms.get(&form_id) else {
3732 continue;
3733 };
3734 let mut stage_ids: Vec<i64> = st.object_lists.keys().copied().collect();
3735 stage_ids.sort_unstable();
3736 for stage_idx in stage_ids {
3737 let Some(objs) = st.object_lists.get(&stage_idx) else {
3738 continue;
3739 };
3740 for (obj_idx, obj) in objs.iter().enumerate() {
3741 if st.is_embedded_object_slot(stage_idx, obj_idx)
3742 || !st.object_slot_is_used(stage_idx, obj_idx)
3743 {
3744 continue;
3745 }
3746 let mut object_chain = vec![
3747 form_id as i32,
3748 self.ctx.ids.elm_array,
3749 stage_idx as i32,
3750 self.ctx.ids.stage_elm_object,
3751 self.ctx.ids.elm_array,
3752 obj_idx as i32,
3753 ];
3754 Self::collect_object_frame_action_work_recursive(
3755 obj,
3756 stage_idx,
3757 obj_idx,
3758 &mut object_chain,
3759 &mut work,
3760 );
3761 }
3762 }
3763
3764 let mut mwnd_stage_ids: Vec<i64> = st.mwnd_lists.keys().copied().collect();
3765 mwnd_stage_ids.sort_unstable();
3766 for stage_idx in mwnd_stage_ids {
3767 let Some(mwnds) = st.mwnd_lists.get(&stage_idx) else {
3768 continue;
3769 };
3770 for (mwnd_idx, mwnd) in mwnds.iter().enumerate() {
3771 for (obj_idx, obj) in mwnd.button_list.iter().enumerate() {
3772 let mut object_chain = vec![
3773 form_id as i32,
3774 self.ctx.ids.elm_array,
3775 stage_idx as i32,
3776 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3777 self.ctx.ids.elm_array,
3778 mwnd_idx as i32,
3779 crate::runtime::forms::codes::elm_value::MWND_BUTTON,
3780 self.ctx.ids.elm_array,
3781 obj_idx as i32,
3782 ];
3783 Self::collect_object_frame_action_work_recursive(
3784 obj,
3785 stage_idx,
3786 obj_idx,
3787 &mut object_chain,
3788 &mut work,
3789 );
3790 }
3791 for (obj_idx, obj) in mwnd.face_list.iter().enumerate() {
3792 let mut object_chain = vec![
3793 form_id as i32,
3794 self.ctx.ids.elm_array,
3795 stage_idx as i32,
3796 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3797 self.ctx.ids.elm_array,
3798 mwnd_idx as i32,
3799 crate::runtime::forms::codes::elm_value::MWND_FACE,
3800 self.ctx.ids.elm_array,
3801 obj_idx as i32,
3802 ];
3803 Self::collect_object_frame_action_work_recursive(
3804 obj,
3805 stage_idx,
3806 obj_idx,
3807 &mut object_chain,
3808 &mut work,
3809 );
3810 }
3811 for (obj_idx, obj) in mwnd.object_list.iter().enumerate() {
3812 let mut object_chain = vec![
3813 form_id as i32,
3814 self.ctx.ids.elm_array,
3815 stage_idx as i32,
3816 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3817 self.ctx.ids.elm_array,
3818 mwnd_idx as i32,
3819 crate::runtime::forms::codes::elm_value::MWND_OBJECT,
3820 self.ctx.ids.elm_array,
3821 obj_idx as i32,
3822 ];
3823 Self::collect_object_frame_action_work_recursive(
3824 obj,
3825 stage_idx,
3826 obj_idx,
3827 &mut object_chain,
3828 &mut work,
3829 );
3830 }
3831 }
3832 }
3833 }
3834 if trace {
3835 eprintln!("[SG_TICK_TRACE] frame_action work items={}", work.len());
3836 }
3837 for item in work {
3841 if trace {
3842 eprintln!(
3843 "[SG_TICK_TRACE] invoke stage={} obj={} ch={:?} global={:?} cmd={} count={} end_time={} args={:?}",
3844 item.stage_idx,
3845 item.obj_idx,
3846 item.ch_idx,
3847 item.global_form_id,
3848 item.cmd_name,
3849 item.count,
3850 item.end_time,
3851 item.args
3852 );
3853 }
3854 let call_args = Self::make_frame_action_call_args(
3859 item.frame_action_chain.as_ref(),
3860 item.object_chain.as_ref(),
3861 &item.args,
3862 );
3863 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
3864 if let Err(e) = self.run_scene_user_cmd_inline(
3865 Some(&item.scn_name),
3866 &item.cmd_name,
3867 &call_args,
3868 self.cfg.fm_void,
3869 true,
3870 ) {
3871 self.ctx.unknown.record_note(&format!(
3872 "frame_action.call.failed:{}:{}:{e}",
3873 item.scn_name, item.cmd_name
3874 ));
3875 }
3876 self.restore_frame_action_current_object(prev_target, prev_chain);
3877
3878 if let Some((finish_scn_name, finish_cmd_name, finish_args)) =
3879 self.begin_frame_action_finish(&item)
3880 {
3881 if trace {
3882 eprintln!(
3883 "[SG_TICK_TRACE] finish stage={} obj={} ch={:?} global={:?} cmd={} args={:?}",
3884 item.stage_idx,
3885 item.obj_idx,
3886 item.ch_idx,
3887 item.global_form_id,
3888 finish_cmd_name,
3889 finish_args
3890 );
3891 }
3892 let finish_call_args = Self::make_frame_action_call_args(
3893 item.frame_action_chain.as_ref(),
3894 item.object_chain.as_ref(),
3895 &finish_args,
3896 );
3897 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
3898 if let Err(e) = self.run_scene_user_cmd_inline(
3899 Some(&finish_scn_name),
3900 &finish_cmd_name,
3901 &finish_call_args,
3902 self.cfg.fm_void,
3903 true,
3904 ) {
3905 self.ctx.unknown.record_note(&format!(
3906 "frame_action.finish_call.failed:{}:{}:{e}",
3907 finish_scn_name, finish_cmd_name
3908 ));
3909 }
3910 self.restore_frame_action_current_object(prev_target, prev_chain);
3911 self.end_frame_action_finish(&item);
3912 }
3913 }
3914 if trace {
3915 eprintln!(
3916 "[SG_TICK_TRACE] after frame-action callbacks blocked={} halted={}",
3917 self.is_blocked(),
3918 self.halted
3919 );
3920 }
3921 self.script_input_synced_this_frame = false;
3922 Ok(())
3923 }
3924
3925 pub fn restart_scene_name(&mut self, scene_name: &str, z_no: i32) -> Result<()> {
3926 self.ctx.reset_for_scene_restart();
3930 let (stream, scene_no) = self.load_scene_stream(scene_name, z_no)?;
3931 self.stream = stream;
3932 self.int_stack.clear();
3933 self.str_stack.clear();
3934 self.element_points.clear();
3935 self.call_stack.clear();
3936 self.call_stack.push(self.scene_base_call());
3937 self.gosub_return_stack.clear();
3938 self.user_props.clear();
3939 self.scene_user_props.clear();
3940 self.scene_stack.clear();
3941 self.save_point = None;
3942 self.ctx.local_save_snapshot = None;
3943 self.sel_point_stack.clear();
3944 self.current_scene_no = Some(scene_no);
3945 self.current_scene_name = Some(scene_name.to_string());
3946 self.current_line_no = -1;
3947 self.ctx.current_scene_no = Some(scene_no as i64);
3948 self.ctx.current_scene_name = Some(scene_name.to_string());
3949 self.ctx.current_line_no = -1;
3950 self.halted = false;
3951 self.delayed_ret_form = None;
3952 Ok(())
3953 }
3954
3955 fn make_resume_point(&self) -> VmResumePoint<'a> {
3956 VmResumePoint {
3957 stream: self.stream.clone(),
3958 user_cmd_names: self.user_cmd_names.clone(),
3959 call_cmd_names: self.call_cmd_names.clone(),
3960 int_stack: self.int_stack.clone(),
3961 str_stack: self.str_stack.clone(),
3962 element_points: self.element_points.clone(),
3963 call_stack: self.call_stack.clone(),
3964 gosub_return_stack: self.gosub_return_stack.clone(),
3965 user_props: self.user_props.clone(),
3966 scene_user_props: self.scene_user_props.clone(),
3967 current_scene_no: self.current_scene_no,
3968 current_scene_name: self.current_scene_name.clone(),
3969 current_line_no: self.current_line_no,
3970 globals: self.ctx.globals.clone(),
3971 }
3972 }
3973
3974 fn restore_resume_point(&mut self, point: VmResumePoint<'a>) {
3975 self.stream = point.stream;
3976 self.user_cmd_names = point.user_cmd_names;
3977 self.call_cmd_names = point.call_cmd_names;
3978 self.int_stack = point.int_stack;
3979 self.str_stack = point.str_stack;
3980 self.element_points = point.element_points;
3981 self.call_stack = point.call_stack;
3982 self.gosub_return_stack = point.gosub_return_stack;
3983 self.user_props = point.user_props;
3984 self.scene_user_props = point.scene_user_props;
3985 self.current_scene_no = point.current_scene_no;
3986 self.current_scene_name = point.current_scene_name;
3987 self.current_line_no = point.current_line_no;
3988 let mut restored_globals = point.globals;
3989 restored_globals.syscom.pending_proc = None;
3990 restored_globals.syscom.menu_open = false;
3991 restored_globals.syscom.menu_kind = None;
3992 restored_globals.syscom.fallback_dialog = None;
3993 restored_globals.syscom.fallback_origin = None;
3994 restored_globals.syscom.msg_back_open = false;
3995 self.ctx.globals = restored_globals;
3996 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
3997 self.ctx.current_scene_name = self.current_scene_name.clone();
3998 self.ctx.current_line_no = self.current_line_no as i64;
3999 self.ctx.wait = runtime::wait::VmWait::default();
4000 self.ctx.stack.clear();
4001 self.halted = false;
4002 self.delayed_ret_form = None;
4003 }
4004
4005 pub fn has_sel_point(&self) -> bool {
4006 !self.sel_point_stack.is_empty()
4007 }
4008
4009 pub fn restore_last_sel_point(&mut self) -> bool {
4010 let Some(point) = self.sel_point_stack.last().cloned() else {
4011 return false;
4012 };
4013 self.restore_resume_point(point);
4014 true
4015 }
4016
4017 pub fn step(&mut self) -> Result<bool> {
4018 match self.step_inner(true) {
4019 Ok(running) => Ok(running),
4020 Err(err) => {
4021 self.halted = true;
4028 Err(err)
4029 }
4030 }
4031 }
4032
4033 pub fn begin_script_proc_pump(&mut self) {
4037 self.steps = 0;
4038 self.ctx.begin_frame_main_proc_pass();
4039 }
4040
4041 pub fn run_script_proc(&mut self) -> Result<bool> {
4045 self.begin_script_proc_pump();
4046 self.run_script_proc_continue()
4047 }
4048
4049 pub fn run_script_proc_continue(&mut self) -> Result<bool> {
4054 if self.halted {
4055 return Ok(false);
4056 }
4057 let proc_generation_before_wait = self.ctx.proc_generation();
4061 if self.is_blocked() {
4062 return Ok(true);
4063 }
4064 if self.ctx.proc_generation() != proc_generation_before_wait {
4065 return Ok(true);
4066 }
4067
4068 if !self.script_input_synced_this_frame {
4069 self.ctx.sync_script_input_from_runtime();
4070 self.ctx.begin_input_frame();
4071 self.ctx.input.next_frame();
4072 self.script_input_synced_this_frame = true;
4073 }
4074
4075 loop {
4076 let proc_generation_before = self.ctx.proc_generation();
4077 let running = match self.step_inner(true) {
4078 Ok(running) => running,
4079 Err(err) => {
4080 self.halted = true;
4084 return Err(err);
4085 }
4086 };
4087 if !running || self.halted {
4088 return Ok(running);
4089 }
4090
4091 if self.is_blocked() {
4092 return Ok(true);
4093 }
4094 if self.ctx.proc_generation() != proc_generation_before {
4095 return Ok(true);
4096 }
4097 }
4098 }
4099
4100 #[allow(dead_code)]
4101 fn step_inner(&mut self, respect_wait: bool) -> Result<bool> {
4102 self.yield_safe_after_step = false;
4103 if self.halted {
4104 return Ok(false);
4105 }
4106
4107 if respect_wait {
4111 let blocked = self.ctx.wait_poll();
4112 if blocked {
4113 return Ok(true);
4114 }
4115 }
4116
4117 if respect_wait {
4123 let delayed = self
4124 .call_stack
4125 .last_mut()
4126 .and_then(|frame| frame.delayed_ret_form.take());
4127 if let Some(rf) = delayed {
4128 if self.ctx.stack.is_empty() {
4129 self.push_default_for_ret(rf);
4130 } else {
4131 self.take_ctx_return(rf)?;
4132 }
4133 }
4134 }
4135
4136 if self.cfg.max_steps > 0 && self.steps >= self.cfg.max_steps {
4137 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
4138 let scene_no = self
4139 .current_scene_no
4140 .map(|v| v.to_string())
4141 .unwrap_or_else(|| "-".to_string());
4142 bail!(
4143 "VM reached SIGLUS_VM_MAX_STEPS={} (possible infinite loop) scene={} scene_no={} line={} pc=0x{:x}",
4144 self.cfg.max_steps,
4145 scene,
4146 scene_no,
4147 self.current_line_no,
4148 self.stream.get_prg_cntr()
4149 );
4150 }
4151 self.steps += 1;
4152
4153 let pc_before = self.stream.get_prg_cntr();
4154 let opcode = match self.stream.pop_u8() {
4155 Ok(v) => v,
4156 Err(_) => {
4157 if self.at_cross_scene_return_boundary() && self.return_from_scene(Vec::new())? {
4158 return Ok(true);
4159 }
4160 self.halted = true;
4161 return Ok(false);
4162 }
4163 };
4164
4165 self.vm_trace_opcode(pc_before, opcode, "before");
4166
4167 match opcode {
4168 CD_NL => {
4169 let line_no = self.stream.pop_i32()?;
4170 self.current_line_no = line_no;
4171 self.ctx.current_line_no = line_no as i64;
4172 self.yield_safe_after_step = true;
4173 if self.element_points.is_empty() {
4178 self.ctx.globals.current_object_chain = None;
4179 self.ctx.globals.current_stage_object = None;
4180 }
4181 }
4182
4183 CD_PUSH => {
4184 let form_code = self.stream.pop_i32()?;
4185 self.exec_push(form_code)?;
4186 }
4187 CD_POP => {
4188 let form_code = self.stream.pop_i32()?;
4189 self.exec_pop(form_code)?;
4190 }
4191 CD_COPY => {
4192 let form_code = self.stream.pop_i32()?;
4193 self.exec_copy(form_code)?;
4194 }
4195
4196 CD_ELM_POINT => {
4197 self.element_points.push(self.int_stack.len());
4198 vm_trace!(
4199 self,
4200 None,
4201 format!("ELM_POINT push start={} ", self.int_stack.len()),
4202 );
4203 }
4204 CD_COPY_ELM => {
4205 self.exec_copy_element()?;
4206 }
4207
4208 CD_PROPERTY => {
4209 let elm = self.pop_element()?;
4210 vm_trace!(self, None, format!("CD_PROPERTY elm={:?}", elm));
4211 self.exec_property(elm)?;
4212 }
4213 CD_DEC_PROP => {
4214 let form_code = self.stream.pop_i32()?;
4215 let prop_id = self.stream.pop_i32()?;
4216
4217 let size = if form_code == self.cfg.fm_intlist || form_code == self.cfg.fm_strlist {
4218 self.pop_int()?.max(0) as usize
4219 } else {
4220 0usize
4221 };
4222 let prop_element = vec![constants::elm::create(
4223 constants::elm::OWNER_CALL_PROP,
4224 0,
4225 prop_id,
4226 )];
4227 let value = if form_code == self.cfg.fm_int {
4228 CallPropValue::Int(0)
4229 } else if form_code == self.cfg.fm_str {
4230 CallPropValue::Str(String::new())
4231 } else if form_code == self.cfg.fm_intlist {
4232 CallPropValue::IntList(vec![0; size])
4233 } else if form_code == self.cfg.fm_strlist {
4234 CallPropValue::StrList(vec![String::new(); size])
4235 } else {
4236 CallPropValue::Element(prop_element.clone())
4237 };
4238
4239 let frame = self
4240 .call_stack
4241 .last_mut()
4242 .ok_or_else(|| anyhow!("call stack underflow"))?;
4243 frame.user_props.push(CallProp {
4244 scn_no: self.current_scene_no.unwrap_or(0) as i32,
4245 prop_id,
4246 form: form_code,
4247 decl_size: size,
4248 element: prop_element,
4249 value,
4250 });
4251 }
4252 CD_ARG => {
4253 let (frame_action_proc, actual_arg_cnt, forms): (bool, usize, Vec<i32>) = {
4256 let frame = self
4257 .call_stack
4258 .last()
4259 .ok_or_else(|| anyhow!("call stack underflow"))?;
4260 (
4261 frame.frame_action_proc,
4262 frame.arg_cnt,
4263 frame.user_props.iter().map(|p| p.form).collect(),
4264 )
4265 };
4266
4267 if frame_action_proc {
4268 if forms.first().copied() != Some(crate::runtime::forms::codes::FM_FRAMEACTION)
4269 {
4270 bail!("frame_action CD_ARG requires first argument to be FM_FRAMEACTION");
4271 }
4272 if actual_arg_cnt != forms.len() {
4273 bail!(
4274 "frame_action CD_ARG argument count mismatch: declared={} actual={}",
4275 forms.len(),
4276 actual_arg_cnt
4277 );
4278 }
4279 }
4280
4281 let mut values: Vec<CallPropValue> = Vec::with_capacity(forms.len());
4283 for &form in forms.iter().rev() {
4284 let v = if form == self.cfg.fm_int {
4285 CallPropValue::Int(self.pop_int()?)
4286 } else if form == self.cfg.fm_str {
4287 CallPropValue::Str(self.pop_str()?)
4288 } else {
4289 CallPropValue::Element(self.pop_element()?)
4290 };
4291 values.push(v);
4292 }
4293 values.reverse();
4294
4295 {
4296 let frame = self
4297 .call_stack
4298 .last_mut()
4299 .ok_or_else(|| anyhow!("call stack underflow"))?;
4300 for (prop, v) in frame.user_props.iter_mut().zip(values) {
4301 match (&v, prop.form) {
4302 (CallPropValue::Int(_), f) if f == self.cfg.fm_int => {
4303 prop.value = v;
4304 }
4305 (CallPropValue::Str(_), f) if f == self.cfg.fm_str => {
4306 prop.value = v;
4307 }
4308 (CallPropValue::Element(e), _) => {
4309 prop.element = e.clone();
4312 if matches!(
4313 prop.form,
4314 crate::runtime::forms::codes::FM_INTREF
4315 | crate::runtime::forms::codes::FM_STRREF
4316 | crate::runtime::forms::codes::FM_INTLISTREF
4317 | crate::runtime::forms::codes::FM_STRLISTREF
4318 | crate::runtime::forms::codes::FM_LIST
4319 ) {
4320 prop.value = CallPropValue::Element(e.clone());
4321 }
4322 }
4323 _ => {
4324 prop.value = v;
4325 }
4326 }
4327 }
4328 }
4329 vm_trace!(
4330 self,
4331 Some(pc_before),
4332 format!(
4333 "ARG expanded frame={:?}",
4334 self.call_stack.last().map(|frame| {
4335 format!(
4336 "ret_form={} arg_cnt={} props={:?} L0_8={:?} K0_4={:?}",
4337 frame.ret_form,
4338 frame.arg_cnt,
4339 frame.user_props,
4340 &frame.int_args[..frame.int_args.len().min(8)],
4341 &frame.str_args[..frame.str_args.len().min(4)]
4342 )
4343 })
4344 )
4345 );
4346 }
4347
4348 CD_GOTO => {
4349 let label_no = self.stream.pop_i32()?;
4350 sg_omv_trace!(self, "GOTO label={} taken=true", label_no);
4351 self.stream.jump_to_label(label_no.max(0) as usize)?;
4352 }
4353 CD_GOTO_TRUE => {
4354 let label_no = self.stream.pop_i32()?;
4355 let trace_cf_branch = self.cf_branch_trace_interesting_line();
4356 let before_tail = if trace_cf_branch {
4357 let start = self.int_stack.len().saturating_sub(16);
4358 Some(self.int_stack[start..].to_vec())
4359 } else {
4360 None
4361 };
4362 let cond = self.pop_int()?;
4363 let taken = cond != 0;
4364 sg_omv_trace!(
4365 self,
4366 "GOTO_TRUE label={} cond={} taken={}",
4367 label_no,
4368 cond,
4369 taken
4370 );
4371 if let Some(before_tail) = before_tail.as_deref() {
4372 self.trace_cf_branch_goto(
4373 pc_before,
4374 "GOTO_TRUE",
4375 label_no,
4376 cond,
4377 taken,
4378 before_tail,
4379 );
4380 }
4381 if taken {
4382 self.stream.jump_to_label(label_no.max(0) as usize)?;
4383 }
4384 }
4385 CD_GOTO_FALSE => {
4386 let label_no = self.stream.pop_i32()?;
4387 let trace_cf_branch = self.cf_branch_trace_interesting_line();
4388 let before_tail = if trace_cf_branch {
4389 let start = self.int_stack.len().saturating_sub(16);
4390 Some(self.int_stack[start..].to_vec())
4391 } else {
4392 None
4393 };
4394 let cond = self.pop_int()?;
4395 let taken = cond == 0;
4396 sg_omv_trace!(
4397 self,
4398 "GOTO_FALSE label={} cond={} taken={}",
4399 label_no,
4400 cond,
4401 taken
4402 );
4403 if let Some(before_tail) = before_tail.as_deref() {
4404 self.trace_cf_branch_goto(
4405 pc_before,
4406 "GOTO_FALSE",
4407 label_no,
4408 cond,
4409 taken,
4410 before_tail,
4411 );
4412 }
4413 if taken {
4414 self.stream.jump_to_label(label_no.max(0) as usize)?;
4415 }
4416 }
4417 CD_GOSUB => {
4418 let label_no = self.stream.pop_i32()?;
4419 let _args = self.pop_arg_list()?;
4420 let return_pc = self.stream.get_prg_cntr();
4421 vm_trace!(
4422 self,
4423 Some(pc_before),
4424 format!("GOSUB label={} return_pc=0x{return_pc:x}", label_no),
4425 );
4426
4427 let caller = self
4429 .call_stack
4430 .last_mut()
4431 .ok_or_else(|| anyhow!("call stack underflow"))?;
4432 caller.return_pc = return_pc;
4433 caller.return_scene_no = self.current_scene_no;
4434 caller.return_scene_name = self.current_scene_name.clone();
4435 caller.return_line_no = self.current_line_no;
4436 caller.ret_form = self.cfg.fm_int;
4437 self.gosub_return_stack.push((return_pc, self.cfg.fm_int));
4438
4439 let scratch_args = self.call_scratch_from_args(&_args);
4441 let mut callee = self.take_call_frame(
4442 self.cfg.fm_void,
4443 false,
4444 false,
4445 _args.len(),
4446 Some(scratch_args),
4447 );
4448 callee.call_type = 1;
4449 callee.return_override = Some((return_pc, self.cfg.fm_int));
4450 self.call_stack.push(callee);
4451
4452 self.stream.jump_to_label(label_no.max(0) as usize)?;
4453 }
4454 CD_GOSUBSTR => {
4455 let label_no = self.stream.pop_i32()?;
4456 let _args = self.pop_arg_list()?;
4457 let return_pc = self.stream.get_prg_cntr();
4458 vm_trace!(
4459 self,
4460 Some(pc_before),
4461 format!("GOSUBSTR label={} return_pc=0x{return_pc:x}", label_no),
4462 );
4463
4464 let caller = self
4465 .call_stack
4466 .last_mut()
4467 .ok_or_else(|| anyhow!("call stack underflow"))?;
4468 caller.return_pc = return_pc;
4469 caller.return_scene_no = self.current_scene_no;
4470 caller.return_scene_name = self.current_scene_name.clone();
4471 caller.return_line_no = self.current_line_no;
4472 caller.ret_form = self.cfg.fm_str;
4473 self.gosub_return_stack.push((return_pc, self.cfg.fm_str));
4474
4475 let scratch_args = self.call_scratch_from_args(&_args);
4476 let mut callee = self.take_call_frame(
4477 self.cfg.fm_void,
4478 false,
4479 false,
4480 _args.len(),
4481 Some(scratch_args),
4482 );
4483 callee.call_type = 1;
4484 callee.return_override = Some((return_pc, self.cfg.fm_str));
4485 self.call_stack.push(callee);
4486
4487 self.stream.jump_to_label(label_no.max(0) as usize)?;
4488 }
4489 CD_RETURN => {
4490 let args = self.pop_arg_list()?;
4491 if self.vm_trace_matches() {
4492 if let Some(frame) = self.call_stack.last() {
4493 self.vm_trace_emit(
4494 Some(pc_before),
4495 format_args!(
4496 "RETURN decoded argc={} args={:?} call_depth={} scene_stack={} frame=ret_form={} arg_cnt={} props={:?} L0_8={:?} K0_4={:?}",
4497 args.len(),
4498 args,
4499 self.call_stack.len(),
4500 self.scene_stack.len(),
4501 frame.ret_form,
4502 frame.arg_cnt,
4503 frame.user_props,
4504 &frame.int_args[..frame.int_args.len().min(8)],
4505 &frame.str_args[..frame.str_args.len().min(4)],
4506 ),
4507 );
4508 } else {
4509 self.vm_trace_emit(
4510 Some(pc_before),
4511 format_args!(
4512 "RETURN decoded argc={} args={:?} call_depth={} scene_stack={} frame=<none>",
4513 args.len(),
4514 args,
4515 self.call_stack.len(),
4516 self.scene_stack.len(),
4517 ),
4518 );
4519 }
4520 }
4521 sg_omv_trace!(
4522 self,
4523 "RETURN argc={} args={:?} call_depth={} scene_stack={}",
4524 args.len(),
4525 args,
4526 self.call_stack.len(),
4527 self.scene_stack.len()
4528 );
4529 if self.at_cross_scene_return_boundary() {
4530 if self.return_from_scene(args)? {
4531 return Ok(true);
4532 }
4533 self.halted = true;
4534 return Ok(false);
4535 }
4536 if self.call_stack.len() == 1 {
4537 self.halted = true;
4538 return Ok(false);
4539 }
4540 if self.exec_return(args)? {
4541 return Ok(false);
4542 }
4543 }
4544
4545 CD_ASSIGN => {
4546 let left_form = self.stream.pop_i32()?;
4547 let right_form = self.stream.pop_i32()?;
4548 let al_id = self.stream.pop_i32()?;
4549 let rhs = self.pop_value_for_form(right_form)?;
4550 let elm = self.pop_element()?;
4551 vm_trace!(
4552 self,
4553 Some(pc_before),
4554 format!(
4555 "ASSIGN decoded left_form={} right_form={} al_id={} elm={:?} rhs={:?}",
4556 left_form, right_form, al_id, elm, rhs
4557 ),
4558 );
4559 self.exec_assign(elm, al_id, rhs)?;
4560 if self.vm_trace_matches() {
4561 if let Some(frame) = self.call_stack.last() {
4562 self.vm_trace_emit(
4563 Some(pc_before),
4564 format_args!(
4565 "ASSIGN applied frame=ret_form={} arg_cnt={} props={:?} L0_8={:?} K0_4={:?}",
4566 frame.ret_form,
4567 frame.arg_cnt,
4568 frame.user_props,
4569 &frame.int_args[..frame.int_args.len().min(8)],
4570 &frame.str_args[..frame.str_args.len().min(4)],
4571 ),
4572 );
4573 } else {
4574 self.vm_trace_emit(Some(pc_before), "ASSIGN applied frame=<none>");
4575 }
4576 }
4577 }
4578
4579 CD_OPERATE_1 => {
4580 let form_code = self.stream.pop_i32()?;
4581 let opr = self.stream.pop_u8()?;
4582 self.exec_operate_1(form_code, opr)?;
4583 }
4584 CD_OPERATE_2 => {
4585 let form_l = self.stream.pop_i32()?;
4586 let form_r = self.stream.pop_i32()?;
4587 let opr = self.stream.pop_u8()?;
4588 self.exec_operate_2(form_l, form_r, opr)?;
4589 }
4590 CD_COMMAND => {
4591 let arg_list_id = self.stream.pop_i32()?;
4593 let mut args = self.pop_arg_list()?;
4594 let elm = self.pop_element()?;
4595
4596 let named_arg_cnt = self.stream.pop_i32()?;
4597 if named_arg_cnt < 0 {
4598 bail!("negative named_arg_cnt={named_arg_cnt}");
4599 }
4600
4601 let mut named_ids: Vec<i32> = Vec::with_capacity(named_arg_cnt as usize);
4602 for _ in 0..(named_arg_cnt as usize) {
4603 named_ids.push(self.stream.pop_i32()?);
4604 }
4605
4606 if !named_ids.is_empty() {
4607 let n = named_ids.len().min(args.len());
4608 for a in 0..n {
4609 let idx = args.len() - 1 - a;
4610 let id = named_ids[a];
4611 let v = std::mem::replace(&mut args[idx], crate::runtime::Value::Int(0));
4612 args[idx] = crate::runtime::Value::NamedArg {
4613 id,
4614 value: Box::new(v),
4615 };
4616 }
4617 }
4618
4619 let ret_form = self.stream.pop_i32()?;
4620 if let Some(raw_head) = elm.first().copied() {
4621 let form_id = self.canonical_runtime_form_id(raw_head as u32) as i32;
4622 let op_id = if elm.len() >= 2 { elm[1] } else { arg_list_id };
4623 self.sg_omv_trace_command(
4624 "CD_COMMAND",
4625 &elm,
4626 form_id,
4627 op_id,
4628 arg_list_id,
4629 ret_form,
4630 &args,
4631 );
4632 }
4633 let _ = self.ctx.take_read_flag_no_request();
4634 let block_generation = self.ctx.wait.block_generation();
4635 let proc_generation = self.ctx.proc_generation();
4636 self.exec_command(elm, arg_list_id, ret_form, &mut args)?;
4637 self.drain_runtime_save_load_requests()?;
4638 if self.ctx.take_read_flag_no_request() {
4639 if self.stream.has_resolved_command_read_flag_layout() {
4649 if let Some(expected) = self.stream.command_read_flag_no_at_current_pc() {
4650 let read_flag_no = self.stream.pop_i32()?;
4651 if read_flag_no != expected {
4652 bail!(
4653 "scene read-flag layout mismatch at pc=0x{:x}: expected {}, got {}",
4654 self.stream.get_prg_cntr().saturating_sub(4),
4655 expected,
4656 read_flag_no
4657 );
4658 }
4659 self.ctx.submit_read_flag_no(read_flag_no);
4660 } else {
4661 self.ctx.discard_read_flag_no_request();
4662 }
4663 } else {
4664 let read_flag_no = self.stream.pop_i32()?;
4668 self.ctx.submit_read_flag_no(read_flag_no);
4669 }
4670 }
4671 if self.ctx.proc_generation() != proc_generation {
4672 return Ok(true);
4673 }
4674 if respect_wait
4675 && self.ctx.wait.block_generation() != block_generation
4676 && self.ctx.wait_poll()
4677 {
4678 return Ok(true);
4679 }
4680 }
4681 CD_TEXT => {
4682 let rf_flag_no = self.stream.pop_i32()?;
4683 let text = self.pop_str()?;
4684 if !crate::runtime::forms::stage::cd_text_current_mwnd(
4685 &mut self.ctx,
4686 &text,
4687 rf_flag_no as i64,
4688 ) {
4689 self.ctx.ui.set_message(text);
4690 }
4691 self.drain_runtime_save_load_requests()?;
4692 }
4693 CD_NAME => {
4694 let name = self.pop_str()?;
4695 if !crate::runtime::forms::stage::cd_name_current_mwnd(&mut self.ctx, &name) {
4696 self.ctx.ui.set_name(name);
4697 }
4698 self.drain_runtime_save_load_requests()?;
4699 }
4700 CD_SEL_BLOCK_START => {
4701 }
4704 CD_SEL_BLOCK_END => {
4705 self.push_int(0);
4708 }
4709
4710 CD_EOF => {
4711 if self.at_cross_scene_return_boundary() && self.return_from_scene(Vec::new())? {
4712 return Ok(true);
4713 }
4714 self.halted = true;
4715 return Ok(false);
4716 }
4717
4718 CD_NONE => {
4719 *self.unknown_opcodes.entry(opcode).or_insert(0) += 1;
4722 let scn_cmd_context = self.vm_scn_cmd_context(pc_before);
4723 eprintln!(
4724 "VM hit CD_NONE scene={} line={} pc=0x{:x} {} bytes={:02x?}; stopping",
4725 self.current_scene_name.as_deref().unwrap_or("<none>"),
4726 self.current_line_no,
4727 pc_before,
4728 scn_cmd_context,
4729 &self.stream.scn
4730 [pc_before.saturating_sub(8)..self.stream.scn.len().min(pc_before + 16)]
4731 );
4732 self.halted = true;
4733 return Ok(false);
4734 }
4735
4736 other => {
4737 *self.unknown_opcodes.entry(other).or_insert(0) += 1;
4738 println!(
4739 "VM unknown opcode=0x{other:02x} at pc=0x{:x}; stopping",
4740 pc_before
4741 );
4742 self.halted = true;
4743 return Ok(false);
4744 }
4745 }
4746
4747 Ok(true)
4748 }
4749
4750 pub fn run(&mut self) -> Result<()> {
4751 while self.step()? {}
4752 Ok(())
4753 }
4754
4755 fn push_int(&mut self, v: i32) {
4760 self.int_stack.push(v);
4761 vm_trace!(self, None, format!("push_int {}", v));
4762 }
4763
4764 fn pop_int(&mut self) -> Result<i32> {
4765 match self.int_stack.pop() {
4766 Some(v) => {
4767 vm_trace!(self, None, format!("pop_int -> {}", v));
4768 Ok(v)
4769 }
4770 None => {
4771 vm_trace!(self, None, "pop_int underflow");
4772 Err(anyhow!(
4773 "int stack underflow: scene={} scene_no={} line={} pc=0x{:x}",
4774 self.current_scene_name.as_deref().unwrap_or("<none>"),
4775 self.current_scene_no
4776 .map(|v| v.to_string())
4777 .unwrap_or_else(|| "-".to_string()),
4778 self.current_line_no,
4779 self.stream.get_prg_cntr()
4780 ))
4781 }
4782 }
4783 }
4784
4785 fn peek_int(&self) -> Result<i32> {
4786 self.int_stack
4787 .last()
4788 .copied()
4789 .ok_or_else(|| anyhow!("int stack underflow"))
4790 }
4791
4792 fn push_str(&mut self, s: String) {
4793 self.str_stack.push(s);
4794 vm_trace!(self, None, {
4795 let value = self.str_stack.last().expect("string was just pushed");
4796 let preview = if value.chars().count() > 48 {
4797 let mut preview = value.chars().take(48).collect::<String>();
4798 preview.push('…');
4799 preview
4800 } else {
4801 value.clone()
4802 };
4803 format!("push_str {:?}", preview)
4804 });
4805 }
4806
4807 fn pop_str(&mut self) -> Result<String> {
4808 match self.str_stack.pop() {
4809 Some(v) => {
4810 vm_trace!(
4811 self,
4812 None,
4813 format!(
4814 "pop_str -> {:?}",
4815 if v.chars().count() > 48 {
4816 let mut preview = v.chars().take(48).collect::<String>();
4817 preview.push('…');
4818 preview
4819 } else {
4820 v.clone()
4821 }
4822 )
4823 );
4824 Ok(v)
4825 }
4826 None => {
4827 vm_trace!(self, None, "pop_str underflow");
4828 Err(anyhow!(
4829 "str stack underflow: scene={} scene_no={} line={} pc=0x{:x}",
4830 self.current_scene_name.as_deref().unwrap_or("<none>"),
4831 self.current_scene_no
4832 .map(|v| v.to_string())
4833 .unwrap_or_else(|| "-".to_string()),
4834 self.current_line_no,
4835 self.stream.get_prg_cntr()
4836 ))
4837 }
4838 }
4839 }
4840
4841 fn peek_str(&self) -> Result<String> {
4842 self.str_stack
4843 .last()
4844 .cloned()
4845 .ok_or_else(|| anyhow!("str stack underflow"))
4846 }
4847
4848 fn push_element(&mut self, elm: Vec<i32>) {
4849 self.element_points.push(self.int_stack.len());
4850 self.int_stack.extend_from_slice(&elm);
4851 vm_trace!(self, None, format!("push_element {:?}", elm));
4852 }
4853
4854 fn pop_element(&mut self) -> Result<Vec<i32>> {
4855 let start = match self.element_points.pop() {
4856 Some(v) => v,
4857 None => {
4858 vm_trace!(self, None, "pop_element underflow (missing ELM_POINT)");
4859 return Err(anyhow!("element stack underflow (missing ELM_POINT)"));
4860 }
4861 };
4862 if start > self.int_stack.len() {
4863 vm_trace!(
4864 self,
4865 None,
4866 format!(
4867 "pop_element invalid start={} len={}",
4868 start,
4869 self.int_stack.len()
4870 ),
4871 );
4872 bail!(
4873 "invalid element point start={start} len={}",
4874 self.int_stack.len()
4875 );
4876 }
4877 let elm = self.ctx.int_vec_pool.take_copy(&self.int_stack[start..]);
4878 self.int_stack.truncate(start);
4879 vm_trace!(self, None, format!("pop_element -> {:?}", elm));
4880 Ok(elm)
4881 }
4882
4883 fn extract_array_index(&self, elm: &[i32]) -> Option<usize> {
4884 if elm.len() >= 3 && elm[1] == self.ctx.ids.elm_array {
4885 let idx = elm[2];
4886 if idx >= 0 {
4887 return Some(idx as usize);
4888 }
4889 }
4890 None
4891 }
4892
4893 fn user_prop_decl(&self, prop_id: u16) -> Option<(i32, usize)> {
4894 let prop_idx = prop_id as usize;
4895 if let Some(pck) = self.scene_pck_cache.as_ref() {
4896 if prop_idx < pck.inc_props.len() {
4897 let decl = &pck.inc_props[prop_idx];
4898 return Some((decl.form, decl.size.max(0) as usize));
4899 }
4900 } else if prop_idx < self.stream.header.scn_prop_cnt.max(0) as usize {
4901 let off =
4909 (self.stream.header.scn_prop_list_ofs.max(0) as usize).checked_add(prop_idx * 8)?;
4910 if off + 8 <= self.stream.chunk.len() {
4911 let form = i32::from_le_bytes(self.stream.chunk[off..off + 4].try_into().unwrap());
4912 let size =
4913 i32::from_le_bytes(self.stream.chunk[off + 4..off + 8].try_into().unwrap());
4914 return Some((form, size.max(0) as usize));
4915 }
4916 }
4917
4918 let local_idx = prop_idx.saturating_sub(
4919 self.scene_pck_cache
4920 .as_ref()
4921 .map(|pck| pck.inc_props.len())
4922 .unwrap_or(0),
4923 );
4924 let list_ofs = self.stream.header.scn_prop_list_ofs.max(0) as usize;
4925 let cnt = self.stream.header.scn_prop_cnt.max(0) as usize;
4926 if local_idx < cnt {
4927 let off = list_ofs.checked_add(local_idx * 8)?;
4928 if off + 8 <= self.stream.chunk.len() {
4929 let form = i32::from_le_bytes(self.stream.chunk[off..off + 4].try_into().unwrap());
4930 let size =
4931 i32::from_le_bytes(self.stream.chunk[off + 4..off + 8].try_into().unwrap());
4932 return Some((form, size.max(0) as usize));
4933 }
4934 }
4935 None
4936 }
4937
4938 fn default_user_prop_element(&self, prop_id: u16, _form: i32) -> Vec<i32> {
4939 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
4940 vec![head]
4941 }
4942
4943 fn default_user_prop_slot_element(&self, prop_id: u16, idx: usize) -> Vec<i32> {
4944 vec![
4945 constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32),
4946 self.ctx.ids.elm_array,
4947 idx as i32,
4948 ]
4949 }
4950
4951 fn default_user_prop_cell(&self, prop_id: u16) -> UserPropCell {
4952 let (form, size) = self
4953 .user_prop_decl(prop_id)
4954 .unwrap_or((self.cfg.fm_list, 0));
4955 let mut cell = UserPropCell::new(form, self.default_user_prop_element(prop_id, form));
4956 if form == self.cfg.fm_intlist {
4957 cell.int_list = vec![0; size];
4958 } else if form == self.cfg.fm_strlist {
4959 cell.str_list = vec![String::new(); size];
4960 } else if form == self.cfg.fm_list && size > 0 {
4961 let mut items = Vec::with_capacity(size);
4962 for i in 0..size {
4963 let mut slot = UserPropCell::new(
4964 self.cfg.fm_list,
4965 self.default_user_prop_slot_element(prop_id, i),
4966 );
4967 slot.form = self.cfg.fm_list;
4968 items.push(slot);
4969 }
4970 cell.list_items = items;
4971 }
4972 cell
4973 }
4974
4975 fn user_prop_cell_from_value(
4976 &self,
4977 rhs: Value,
4978 declared_form: i32,
4979 element: Vec<i32>,
4980 prop_id: Option<u16>,
4981 ) -> UserPropCell {
4982 let mut cell = UserPropCell::new(declared_form, element.clone());
4983 match rhs {
4984 Value::NamedArg { value, .. } => {
4985 return self.user_prop_cell_from_value(*value, declared_form, element, prop_id);
4986 }
4987 Value::Int(n) => {
4988 cell.form = self.cfg.fm_int;
4989 cell.int_value = n as i32;
4990 }
4991 Value::Str(s) => {
4992 cell.form = self.cfg.fm_str;
4993 cell.str_value = s;
4994 }
4995 Value::Element(e) => {
4996 cell.form = declared_form;
4997 cell.element = e;
4998 }
4999 Value::List(items) => {
5000 if declared_form == self.cfg.fm_intlist {
5001 cell.form = self.cfg.fm_intlist;
5002 cell.int_list = items
5003 .into_iter()
5004 .map(|item| item.as_i64().unwrap_or(0) as i32)
5005 .collect();
5006 } else if declared_form == self.cfg.fm_strlist {
5007 cell.form = self.cfg.fm_strlist;
5008 cell.str_list = items
5009 .into_iter()
5010 .map(|item| item.as_str().unwrap_or("").to_string())
5011 .collect();
5012 } else {
5013 cell.form = self.cfg.fm_list;
5014 let mut out = Vec::with_capacity(items.len());
5015 for (idx, item) in items.into_iter().enumerate() {
5016 let slot_element = if let Some(pid) = prop_id {
5017 self.default_user_prop_slot_element(pid, idx)
5018 } else {
5019 vec![]
5020 };
5021 out.push(self.user_prop_cell_from_value(
5022 item,
5023 self.cfg.fm_list,
5024 slot_element,
5025 prop_id,
5026 ));
5027 }
5028 cell.list_items = out;
5029 }
5030 }
5031 }
5032 cell
5033 }
5034
5035 fn consume_array_sub_signed<'b>(&self, sub: &'b [i32]) -> Option<(i32, &'b [i32])> {
5036 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5037 Some((sub[1], &sub[2..]))
5038 } else {
5039 None
5040 }
5041 }
5042
5043 fn consume_array_sub<'b>(&self, sub: &'b [i32]) -> Option<(usize, &'b [i32])> {
5044 let (index, rest) = self.consume_array_sub_signed(sub)?;
5045 usize::try_from(index).ok().map(|index| (index, rest))
5046 }
5047
5048 fn intlist_bit_get(values: &[i32], bit: i32, index: usize) -> i32 {
5049 let word = values
5050 .get(index / (32 / bit as usize))
5051 .copied()
5052 .unwrap_or(0) as u32;
5053 let shift = (index % (32 / bit as usize)) * bit as usize;
5054 let mask = ((1u32 << bit) - 1) << shift;
5055 ((word & mask) >> shift) as i32
5056 }
5057
5058 fn intlist_dispatch_read(
5059 &mut self,
5060 values: &[i32],
5061 sub: &[i32],
5062 fallback_element: &[i32],
5063 ) -> Result<()> {
5064 use crate::runtime::forms::codes::{
5065 ELM_ARRAY, ELM_INTLIST_BIT, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4, ELM_INTLIST_BIT8,
5066 ELM_INTLIST_BIT16, ELM_INTLIST_GET_SIZE,
5067 };
5068 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
5069 &[][..]
5070 } else {
5071 sub
5072 };
5073 if sub.is_empty() {
5074 self.push_element(fallback_element.to_vec());
5075 return Ok(());
5076 }
5077 if let Some((idx, rest)) = self.consume_array_sub_signed(sub) {
5078 if !rest.is_empty() {
5079 bail!("unsupported chained intlist array access {:?}", sub);
5080 }
5081 let value = usize::try_from(idx)
5082 .ok()
5083 .and_then(|idx| values.get(idx).copied())
5084 .unwrap_or(0);
5085 self.push_int(value);
5086 return Ok(());
5087 }
5088 match sub[0] {
5089 ELM_INTLIST_GET_SIZE => {
5090 self.push_int(values.len() as i32);
5091 }
5092 ELM_INTLIST_BIT | ELM_INTLIST_BIT2 | ELM_INTLIST_BIT4 | ELM_INTLIST_BIT8
5093 | ELM_INTLIST_BIT16 => {
5094 let bit = match sub[0] {
5095 ELM_INTLIST_BIT => 1,
5096 ELM_INTLIST_BIT2 => 2,
5097 ELM_INTLIST_BIT4 => 4,
5098 ELM_INTLIST_BIT8 => 8,
5099 _ => 16,
5100 };
5101 if let Some((idx, rest)) = self.consume_array_sub_signed(&sub[1..]) {
5102 if !rest.is_empty() {
5103 bail!("unsupported chained intlist bit access {:?}", sub);
5104 }
5105 let value = usize::try_from(idx)
5106 .ok()
5107 .map(|idx| Self::intlist_bit_get(values, bit, idx))
5108 .unwrap_or(0);
5109 self.push_int(value);
5110 } else {
5111 let mut chained = fallback_element.to_vec();
5112 chained.extend_from_slice(sub);
5113 self.push_element(chained);
5114 }
5115 }
5116 _ => self.push_element(fallback_element.to_vec()),
5117 }
5118 Ok(())
5119 }
5120
5121 fn push_user_prop_cell_result(
5122 &mut self,
5123 cell: &UserPropCell,
5124 sub: &[i32],
5125 full_elm: &[i32],
5126 ) -> Result<()> {
5127 use crate::runtime::forms::codes::{
5128 ELM_STRLIST_GET_SIZE, FM_INTLISTREF, FM_INTREF, FM_STRLISTREF, FM_STRREF,
5129 };
5130
5131 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
5132 &[][..]
5133 } else {
5134 sub
5135 };
5136 if cell.form == self.cfg.fm_int && sub.is_empty() {
5137 self.push_int(cell.int_value);
5138 return Ok(());
5139 }
5140 if cell.form == self.cfg.fm_str {
5141 if sub.is_empty() {
5142 self.push_str(cell.str_value.clone());
5143 } else {
5144 self.call_prop_eval_str_op(&cell.str_value, sub[0], &[], 0)?;
5145 }
5146 return Ok(());
5147 }
5148 if cell.form == self.cfg.fm_intlist {
5149 return self.intlist_dispatch_read(&cell.int_list, sub, &cell.element);
5150 }
5151 if cell.form == self.cfg.fm_strlist {
5152 if sub.is_empty() {
5153 self.push_element(cell.element.clone());
5154 } else if let Some((idx, rest)) = self.consume_array_sub_signed(sub) {
5155 let cur = usize::try_from(idx)
5156 .ok()
5157 .and_then(|idx| cell.str_list.get(idx).cloned())
5158 .unwrap_or_default();
5159 if rest.is_empty() {
5160 self.push_str(cur);
5161 } else {
5162 self.call_prop_eval_str_op(&cur, rest[0], &[], 0)?;
5163 }
5164 } else if sub[0] == ELM_STRLIST_GET_SIZE {
5165 self.push_int(cell.str_list.len() as i32);
5166 } else {
5167 self.push_element(cell.element.clone());
5168 }
5169 return Ok(());
5170 }
5171 if matches!(
5172 cell.form,
5173 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF
5174 ) {
5175 self.push_element(cell.element.clone());
5176 return Ok(());
5177 }
5178 if let Some((idx, rest)) = self.consume_array_sub(sub) {
5179 let slot = if let Some(slot) = cell.list_items.get(idx) {
5180 slot.clone()
5181 } else {
5182 let mut tmp = UserPropCell::new(
5183 self.cfg.fm_list,
5184 self.default_user_prop_slot_element(elm_code::code(full_elm[0]), idx),
5185 );
5186 tmp.form = self.cfg.fm_list;
5187 tmp
5188 };
5189 return self.push_user_prop_cell_result(&slot, rest, full_elm);
5190 }
5191 self.push_element(cell.element.clone());
5192 Ok(())
5193 }
5194
5195 fn default_value_like(&self, v: &Value) -> Value {
5196 match v {
5197 Value::NamedArg { value, .. } => self.default_value_like(value),
5198 Value::Int(_) => Value::Int(0),
5199 Value::Str(_) => Value::Str(String::new()),
5200 Value::Element(_) => Value::Element(Vec::new()),
5201 Value::List(_) => Value::List(Vec::new()),
5202 }
5203 }
5204 fn call_scratch_from_args(&self, args: &[Value]) -> (Vec<i32>, Vec<String>) {
5205 let mut int_args = Self::blank_call_int_args(self.call_flag_count);
5206 let mut str_args = Self::blank_call_str_args(self.call_flag_count);
5207 let mut int_pos = 0usize;
5208 let mut str_pos = 0usize;
5209 for v in args {
5210 match v {
5211 Value::NamedArg { value, .. } => match value.as_ref() {
5212 Value::Int(n) => {
5213 if int_pos < int_args.len() {
5214 int_args[int_pos] = *n as i32;
5215 int_pos += 1;
5216 }
5217 }
5218 Value::Str(s) if str_pos < str_args.len() => {
5219 str_args[str_pos] = s.clone();
5220 str_pos += 1;
5221 }
5222 _ => {}
5223 },
5224 Value::Int(n) => {
5225 if int_pos < int_args.len() {
5226 int_args[int_pos] = *n as i32;
5227 int_pos += 1;
5228 }
5229 }
5230 Value::Str(s) if str_pos < str_args.len() => {
5231 str_args[str_pos] = s.clone();
5232 str_pos += 1;
5233 }
5234 _ => {}
5235 }
5236 }
5237 (int_args, str_args)
5238 }
5239
5240 fn call_array_marker(&self, code: i32) -> bool {
5241 let mapped = self.ctx.ids.elm_array;
5242 code == crate::runtime::forms::codes::ELM_ARRAY || (mapped >= 0 && code == mapped)
5243 }
5244
5245 fn resolve_call_frame_index(&self, idx: i32) -> Option<usize> {
5246 if idx < 0 {
5247 return None;
5248 }
5249 let depth = self.call_stack.len();
5250 let rev = idx as usize;
5251 if rev >= depth {
5252 return None;
5253 }
5254 Some(depth - 1 - rev)
5255 }
5256
5257 fn current_call_frame_index(&self) -> Option<usize> {
5258 if self.call_stack.is_empty() {
5259 None
5260 } else {
5261 Some(self.call_stack.len() - 1)
5262 }
5263 }
5264
5265 fn find_call_prop_index_in_frame(&self, frame_idx: usize, call_prop_id: i32) -> Option<usize> {
5266 if call_prop_id < 0 {
5272 return None;
5273 }
5274 let frame = self.call_stack.get(frame_idx)?;
5275 let idx = call_prop_id as usize;
5276 if idx < frame.user_props.len() {
5277 Some(idx)
5278 } else {
5279 None
5280 }
5281 }
5282
5283 fn call_prop_element(prop_id: i32) -> Vec<i32> {
5284 vec![constants::elm::create(
5285 constants::elm::OWNER_CALL_PROP,
5286 0,
5287 prop_id,
5288 )]
5289 }
5290
5291 fn call_prop_value_from_rhs(&self, rhs: &Value) -> (i32, CallPropValue) {
5292 match rhs {
5293 Value::NamedArg { value, .. } => self.call_prop_value_from_rhs(value),
5294 Value::Int(n) => (self.cfg.fm_int, CallPropValue::Int(*n as i32)),
5295 Value::Str(s) => (self.cfg.fm_str, CallPropValue::Str(s.clone())),
5296 Value::Element(e) => (self.cfg.fm_list, CallPropValue::Element(e.clone())),
5297 Value::List(_) => (self.cfg.fm_list, CallPropValue::Element(Vec::new())),
5298 }
5299 }
5300
5301 fn ensure_call_prop_index_for_assign(
5302 &mut self,
5303 frame_idx: usize,
5304 call_prop_id: i32,
5305 rhs: &Value,
5306 ) -> Result<usize> {
5307 if let Some(idx) = self.find_call_prop_index_in_frame(frame_idx, call_prop_id) {
5308 return Ok(idx);
5309 }
5310 if call_prop_id < 0 {
5311 bail!("negative CALL_PROP id {}", call_prop_id);
5312 }
5313
5314 let (form, value) = self.call_prop_value_from_rhs(rhs);
5319 let target_idx = call_prop_id as usize;
5320 let frame = self
5321 .call_stack
5322 .get_mut(frame_idx)
5323 .ok_or_else(|| anyhow!("call stack frame out of range"))?;
5324 while frame.user_props.len() <= target_idx {
5325 let idx = frame.user_props.len() as i32;
5326 frame.user_props.push(CallProp {
5327 scn_no: self.current_scene_no.unwrap_or(0) as i32,
5328 prop_id: idx,
5329 form: self.cfg.fm_list,
5330 decl_size: 0,
5331 element: Self::call_prop_element(idx),
5332 value: CallPropValue::Element(Self::call_prop_element(idx)),
5333 });
5334 }
5335 let prop = frame
5336 .user_props
5337 .get_mut(target_idx)
5338 .ok_or_else(|| anyhow!("CALL_PROP slot allocation failed"))?;
5339 prop.form = form;
5340 prop.element = Self::call_prop_element(call_prop_id);
5341 prop.value = value;
5342 Ok(target_idx)
5343 }
5344
5345 fn is_direct_value_form(&self, form: i32) -> bool {
5346 form == self.cfg.fm_int
5347 || form == self.cfg.fm_str
5348 || form == self.cfg.fm_intlist
5349 || form == self.cfg.fm_strlist
5350 }
5351
5352 fn call_prop_effective_element(&self, prop: &CallProp) -> Vec<i32> {
5353 match &prop.value {
5354 CallPropValue::Element(e) if !e.is_empty() => e.clone(),
5355 _ => prop.element.clone(),
5356 }
5357 }
5358
5359 fn push_call_prop_fast(&mut self, frame: usize, prop_idx: usize, sub: &[i32]) -> Result<bool> {
5364 let prop = &self.call_stack[frame].user_props[prop_idx];
5365 if let Some(composed) = self.compose_call_prop_tail(prop, sub) {
5366 self.exec_property(composed)?;
5367 return Ok(true);
5368 }
5369 use crate::runtime::forms::codes::{
5370 FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST, FM_STRLISTREF,
5371 FM_STRREF,
5372 };
5373 let plain = sub.is_empty() || (sub.len() == 1 && self.call_array_marker(sub[0]));
5374 match (prop.form, &prop.value) {
5375 (FM_INT, CallPropValue::Int(n)) if plain => {
5376 let n = *n;
5377 self.push_int(n);
5378 }
5379 (FM_STR, CallPropValue::Str(text)) if plain => {
5380 let text = text.clone();
5381 self.push_str(text);
5382 }
5383 (
5386 FM_INT | FM_STR | FM_INTLIST | FM_STRLIST | FM_INTREF | FM_STRREF | FM_INTLISTREF
5387 | FM_STRLISTREF,
5388 _,
5389 ) => return Ok(false),
5390 _ => {
5391 self.element_points.push(self.int_stack.len());
5393 self.int_stack
5394 .extend_from_slice(&self.call_stack[frame].user_props[prop_idx].element);
5395 }
5396 }
5397 Ok(true)
5398 }
5399
5400 fn compose_call_prop_tail(&self, prop: &CallProp, sub: &[i32]) -> Option<Vec<i32>> {
5401 if sub.is_empty()
5407 || (sub.len() == 1 && self.call_array_marker(sub[0]))
5408 || self.is_direct_value_form(prop.form)
5409 {
5410 return None;
5411 }
5412 let mut element = self.call_prop_effective_element(prop);
5413 if element.is_empty() {
5414 return None;
5415 }
5416 element.extend_from_slice(sub);
5417 Some(element)
5418 }
5419
5420 fn compose_user_prop_tail(
5421 &self,
5422 prop_id: u16,
5423 cell: &UserPropCell,
5424 sub: &[i32],
5425 ) -> Option<Vec<i32>> {
5426 if sub.is_empty()
5429 || (sub.len() == 1 && self.call_array_marker(sub[0]))
5430 || self.is_direct_value_form(cell.form)
5431 {
5432 return None;
5433 }
5434 if let Some((idx, rest)) = self.consume_array_sub(sub) {
5435 let slot = cell.list_items.get(idx)?;
5436 let default_slot = self.default_user_prop_slot_element(prop_id, idx);
5437 if slot.element.is_empty() || slot.element == default_slot {
5438 return None;
5439 }
5440 if rest.is_empty() || self.is_direct_value_form(slot.form) {
5441 return Some(slot.element.clone());
5442 }
5443 let mut element = slot.element.clone();
5444 element.extend_from_slice(rest);
5445 return Some(element);
5446 }
5447
5448 let default_root = self.default_user_prop_element(prop_id, cell.form);
5449 if cell.element.is_empty() || cell.element == default_root {
5450 return None;
5451 }
5452 let mut element = cell.element.clone();
5453 element.extend_from_slice(sub);
5454 Some(element)
5455 }
5456
5457 fn push_call_prop_result(
5458 &mut self,
5459 prop: &CallProp,
5460 sub: &[i32],
5461 full_elm: &[i32],
5462 ) -> Result<()> {
5463 use crate::runtime::forms::codes::{
5464 ELM_STRLIST_GET_SIZE, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST,
5465 FM_STRLISTREF, FM_STRREF,
5466 };
5467
5468 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
5469 &[][..]
5470 } else {
5471 sub
5472 };
5473 match (prop.form, sub) {
5474 (FM_INT, []) => {
5475 if let CallPropValue::Int(n) = &prop.value {
5476 self.push_int(*n);
5477 } else {
5478 bail!("CALL_PROP int storage mismatch for {:?}", full_elm);
5479 }
5480 }
5481 (FM_STR, []) => {
5482 if let CallPropValue::Str(s) = &prop.value {
5483 self.push_str(s.clone());
5484 } else {
5485 bail!("CALL_PROP str storage mismatch for {:?}", full_elm);
5486 }
5487 }
5488 (FM_STR, _) => {
5489 if let CallPropValue::Str(s) = &prop.value {
5490 self.call_prop_eval_str_op(s, sub[0], &[], 0)?;
5491 } else {
5492 bail!("CALL_PROP str storage mismatch for {:?}", full_elm);
5493 }
5494 }
5495 (FM_INTLIST, _) => {
5496 if let CallPropValue::IntList(v) = &prop.value {
5497 self.intlist_dispatch_read(v, sub, &prop.element)?;
5498 } else {
5499 bail!("CALL_PROP intlist storage mismatch for {:?}", full_elm);
5500 }
5501 }
5502 (FM_STRLIST, _) => {
5503 if let CallPropValue::StrList(v) = &prop.value {
5504 if sub.is_empty() {
5505 self.push_element(prop.element.clone());
5506 } else if let Some((idx, rest)) = self.consume_array_sub_signed(sub) {
5507 let current = usize::try_from(idx)
5508 .ok()
5509 .and_then(|idx| v.get(idx).cloned())
5510 .unwrap_or_default();
5511 if rest.is_empty() {
5512 self.push_str(current);
5513 } else {
5514 self.call_prop_eval_str_op(¤t, rest[0], &[], 0)?;
5515 }
5516 } else if sub[0] == ELM_STRLIST_GET_SIZE {
5517 self.push_int(v.len() as i32);
5518 } else {
5519 self.push_element(prop.element.clone());
5520 }
5521 } else {
5522 bail!("CALL_PROP strlist storage mismatch for {:?}", full_elm);
5523 }
5524 }
5525 (FM_INTREF | FM_STRREF, []) => {
5526 let target = self.call_prop_effective_element(prop);
5533 let default_target = Self::call_prop_element(prop.prop_id);
5534 if target.is_empty() || target == full_elm || target == default_target {
5535 bail!(
5536 "unbound scalar CALL_PROP reference form={} target={:?} access={:?}",
5537 prop.form,
5538 target,
5539 full_elm
5540 );
5541 }
5542 self.push_element(target);
5543 }
5544 (FM_INTLISTREF | FM_STRLISTREF, _) => {
5545 self.push_element(self.call_prop_effective_element(prop));
5548 }
5549 (FM_INTREF | FM_STRREF, _) => {
5550 bail!(
5554 "unsupported scalar CALL_PROP reference suffix form={} sub={:?} access={:?}",
5555 prop.form,
5556 sub,
5557 full_elm
5558 );
5559 }
5560 _ => {
5561 self.push_element(prop.element.clone());
5562 }
5563 }
5564 Ok(())
5565 }
5566
5567 fn call_prop_eval_str_op(
5568 &mut self,
5569 current: &str,
5570 op: i32,
5571 params: &[Value],
5572 al_id: i32,
5573 ) -> Result<()> {
5574 use crate::runtime::forms::codes::str_op;
5575 match op {
5576 str_op::UPPER => self.push_str(crate::runtime::string_semantics::ascii_upper(current)),
5577 str_op::LOWER => self.push_str(crate::runtime::string_semantics::ascii_lower(current)),
5578 str_op::CNT => {
5579 self.push_int(crate::runtime::string_semantics::utf16_len(current) as i32)
5580 }
5581 str_op::LEN => {
5582 self.push_int(crate::runtime::string_semantics::display_width(current) as i32)
5583 }
5584 str_op::LEFT => {
5585 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5586 self.push_str(crate::runtime::string_semantics::utf16_left(current, len));
5587 }
5588 str_op::LEFT_LEN => {
5589 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5590 self.push_str(crate::runtime::string_semantics::left_by_display_width(
5591 current, len,
5592 ));
5593 }
5594 str_op::RIGHT => {
5595 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5596 self.push_str(crate::runtime::string_semantics::utf16_right(current, len));
5597 }
5598 str_op::RIGHT_LEN => {
5599 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5600 self.push_str(crate::runtime::string_semantics::right_by_display_width(
5601 current, len,
5602 ));
5603 }
5604 str_op::MID => {
5605 let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5606 if al_id == 0 || params.len() <= 1 {
5607 self.push_str(crate::runtime::string_semantics::utf16_slice(
5608 current, start, None,
5609 ));
5610 } else {
5611 let len = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5612 self.push_str(crate::runtime::string_semantics::utf16_slice(
5613 current,
5614 start,
5615 Some(len),
5616 ));
5617 }
5618 }
5619 str_op::MID_LEN => {
5620 let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5621 let len = if al_id == 0 || params.len() <= 1 {
5622 None
5623 } else {
5624 Some(params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize)
5625 };
5626 self.push_str(crate::runtime::string_semantics::mid_by_display_width(
5627 current, start, len,
5628 ));
5629 }
5630 str_op::SEARCH => {
5631 let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
5632 self.push_int(
5633 crate::runtime::string_semantics::search_ascii_case_insensitive(
5634 current, needle,
5635 )
5636 .map(|v| v as i32)
5637 .unwrap_or(-1),
5638 );
5639 }
5640 str_op::SEARCH_LAST => {
5641 let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
5642 self.push_int(
5643 crate::runtime::string_semantics::rsearch_ascii_case_insensitive(
5644 current, needle,
5645 )
5646 .map(|v| v as i32)
5647 .unwrap_or(-1),
5648 );
5649 }
5650 str_op::GET_CODE => {
5651 let pos = params.first().and_then(|v| v.as_i64()).unwrap_or(0);
5652 self.push_int(
5653 usize::try_from(pos)
5654 .ok()
5655 .and_then(|pos| {
5656 crate::runtime::string_semantics::utf16_code_unit(current, pos)
5657 })
5658 .map(i32::from)
5659 .unwrap_or(-1),
5660 );
5661 }
5662 str_op::TONUM => self.push_int(current.parse::<i32>().unwrap_or(0)),
5663 _ => bail!("unsupported CALL_PROP string op {}", op),
5664 }
5665 Ok(())
5666 }
5667
5668 fn intlist_assignment_subscript(sub: &[i32]) -> Option<(i32, i32)> {
5669 use crate::runtime::forms::codes::{
5670 ELM_ARRAY, ELM_INTLIST_BIT, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4, ELM_INTLIST_BIT8,
5671 ELM_INTLIST_BIT16,
5672 };
5673
5674 if sub.len() == 2 && sub[0] == ELM_ARRAY {
5675 return Some((32, sub[1]));
5676 }
5677 if sub.len() != 3 || sub[1] != ELM_ARRAY {
5678 return None;
5679 }
5680 let bit = match sub[0] {
5681 ELM_INTLIST_BIT => 1,
5682 ELM_INTLIST_BIT2 => 2,
5683 ELM_INTLIST_BIT4 => 4,
5684 ELM_INTLIST_BIT8 => 8,
5685 ELM_INTLIST_BIT16 => 16,
5686 _ => return None,
5687 };
5688 Some((bit, sub[2]))
5689 }
5690
5691 fn assign_call_prop_result(prop: &mut CallProp, sub: &[i32], rhs: Value) -> Result<()> {
5692 use crate::runtime::forms::codes::{
5693 ELM_ARRAY, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST,
5694 FM_STRLISTREF, FM_STRREF,
5695 };
5696
5697 match (prop.form, sub) {
5698 (FM_INT, []) => match rhs {
5699 Value::Int(n) => {
5700 prop.value = CallPropValue::Int(n as i32);
5701 }
5702 _ => bail!("unsupported CALL_PROP int assign sub={:?}", sub),
5703 },
5704 (FM_STR, []) => match rhs {
5705 Value::Str(s) => {
5706 prop.value = CallPropValue::Str(s);
5707 }
5708 _ => bail!("unsupported CALL_PROP str assign sub={:?}", sub),
5709 },
5710 (FM_INTLIST, _) => {
5711 let Some((bit, index)) = Self::intlist_assignment_subscript(sub) else {
5712 bail!("unsupported CALL_PROP intlist assign sub={:?}", sub);
5713 };
5714 let Value::Int(n) = rhs else {
5715 bail!("unsupported CALL_PROP intlist assign sub={:?}", sub);
5716 };
5717 let mut dst =
5718 match std::mem::replace(&mut prop.value, CallPropValue::IntList(Vec::new())) {
5719 CallPropValue::IntList(v) => v,
5720 other => {
5721 prop.value = other;
5722 bail!("CALL_PROP intlist storage mismatch");
5723 }
5724 };
5725 if bit == 32 && index >= 0 {
5726 let idx = index as usize;
5727 if dst.len() <= idx {
5728 dst.resize(idx + 1, 0);
5729 }
5730 }
5731 Self::set_user_int_list_value(&mut dst, bit, index, n as i32);
5732 prop.value = CallPropValue::IntList(dst);
5733 }
5734 (FM_STRLIST, [ELM_ARRAY, _, ..]) => match rhs {
5735 Value::Str(s) => {
5736 let idx = sub[1].max(0) as usize;
5737 let mut dst = match std::mem::replace(
5738 &mut prop.value,
5739 CallPropValue::StrList(Vec::new()),
5740 ) {
5741 CallPropValue::StrList(v) => v,
5742 other => {
5743 prop.value = other;
5744 bail!("CALL_PROP strlist storage mismatch");
5745 }
5746 };
5747 if dst.len() <= idx {
5748 dst.resize_with(idx + 1, String::new);
5749 }
5750 dst[idx] = s;
5751 prop.value = CallPropValue::StrList(dst);
5752 }
5753 _ => bail!("unsupported CALL_PROP strlist assign sub={:?}", sub),
5754 },
5755 (FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF, _) => match rhs {
5756 Value::Element(e) => {
5757 prop.element = e.clone();
5758 prop.value = CallPropValue::Element(e);
5759 }
5760 _ => bail!("unsupported CALL_PROP ref assign sub={:?}", sub),
5761 },
5762 _ => bail!(
5763 "unsupported call prop assign form={} sub={:?}",
5764 prop.form,
5765 sub
5766 ),
5767 }
5768 Ok(())
5769 }
5770
5771 fn set_user_int_list_value(values: &mut [i32], bit: i32, index: i32, value: i32) {
5772 if index < 0 || !matches!(bit, 1 | 2 | 4 | 8 | 16 | 32) {
5773 return;
5774 }
5775 let index = index as usize;
5776 if bit == 32 {
5777 if let Some(slot) = values.get_mut(index) {
5778 *slot = value;
5779 }
5780 return;
5781 }
5782 let per_word = 32usize / bit as usize;
5783 let Some(word) = values.get_mut(index / per_word) else {
5784 return;
5785 };
5786 let shift = (index % per_word) * bit as usize;
5787 let mask = ((1u32 << bit) - 1) << shift;
5788 let encoded = ((value as u32) << shift) & mask;
5789 *word = (((*word as u32) & !mask) | encoded) as i32;
5790 }
5791
5792 fn get_user_int_list_value(values: &[i32], bit: i32, index: i32) -> Option<i32> {
5793 if index < 0 || !matches!(bit, 1 | 2 | 4 | 8 | 16 | 32) {
5794 return None;
5795 }
5796 let index = index as usize;
5797 if bit == 32 {
5798 return values.get(index).copied();
5799 }
5800 let per_word = 32usize / bit as usize;
5801 let word = *values.get(index / per_word)? as u32;
5802 let shift = (index % per_word) * bit as usize;
5803 Some(((word >> shift) & ((1u32 << bit) - 1)) as i32)
5804 }
5805
5806 fn exec_user_prop_list_command(
5807 &mut self,
5808 prop_id: u16,
5809 sub: &[i32],
5810 al_id: i32,
5811 ret_form: i32,
5812 args: &[Value],
5813 ) -> Result<bool> {
5814 use crate::runtime::forms::codes::{
5815 ELM_INTLIST_BIT, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4, ELM_INTLIST_BIT8,
5816 ELM_INTLIST_BIT16, ELM_INTLIST_CLEAR, ELM_INTLIST_GET_SIZE, ELM_INTLIST_INIT,
5817 ELM_INTLIST_RESIZE, ELM_INTLIST_SETS, ELM_STRLIST_GET_SIZE, ELM_STRLIST_INIT,
5818 ELM_STRLIST_RESIZE,
5819 };
5820
5821 if sub.is_empty() {
5822 return Ok(false);
5823 }
5824
5825 let (decl_form, decl_size) = self
5826 .user_prop_decl(prop_id)
5827 .unwrap_or((self.cfg.fm_list, 0));
5828 let mut cell = self
5829 .user_props
5830 .remove(&prop_id)
5831 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
5832 let form = cell.form;
5833 let mut handled = false;
5834
5835 if form == self.cfg.fm_intlist {
5836 let mut bit = 32;
5837 let mut op = sub;
5838 if let Some(first) = op.first().copied() {
5839 bit = match first {
5840 ELM_INTLIST_BIT => 1,
5841 ELM_INTLIST_BIT2 => 2,
5842 ELM_INTLIST_BIT4 => 4,
5843 ELM_INTLIST_BIT8 => 8,
5844 ELM_INTLIST_BIT16 => 16,
5845 _ => 32,
5846 };
5847 if bit != 32 {
5848 op = &op[1..];
5849 }
5850 }
5851
5852 if op.len() >= 2 && self.call_array_marker(op[0]) {
5853 let index = op[1];
5854 if al_id == 0 {
5855 if let Some(value) = Self::get_user_int_list_value(&cell.int_list, bit, index) {
5856 self.push_int(value);
5857 } else {
5858 self.push_default_for_ret(ret_form);
5859 }
5860 } else {
5861 let value = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
5862 Self::set_user_int_list_value(&mut cell.int_list, bit, index, value);
5863 self.push_default_for_ret(ret_form);
5864 }
5865 handled = true;
5866 } else if op.len() == 1 {
5867 match op[0] {
5868 ELM_INTLIST_INIT => {
5869 cell.form = decl_form;
5870 cell.element = self.default_user_prop_element(prop_id, decl_form);
5871 cell.int_list.clear();
5872 cell.int_list.resize(decl_size, 0);
5873 self.push_default_for_ret(ret_form);
5874 handled = true;
5875 }
5876 ELM_INTLIST_RESIZE => {
5877 let new_len =
5878 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5879 cell.int_list.resize(new_len, 0);
5880 self.push_default_for_ret(ret_form);
5881 handled = true;
5882 }
5883 ELM_INTLIST_GET_SIZE => {
5884 let multiplier = 32 / bit;
5885 self.push_int((cell.int_list.len() * multiplier as usize) as i32);
5886 handled = true;
5887 }
5888 ELM_INTLIST_CLEAR => {
5889 let start = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
5890 let end =
5891 args.get(1).and_then(|v| v.as_i64()).unwrap_or(start as i64) as i32;
5892 let value = if al_id == 0 {
5893 0
5894 } else {
5895 args.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32
5896 };
5897 if start <= end {
5898 for index in start..=end {
5899 Self::set_user_int_list_value(
5900 &mut cell.int_list,
5901 bit,
5902 index,
5903 value,
5904 );
5905 }
5906 }
5907 self.push_default_for_ret(ret_form);
5908 handled = true;
5909 }
5910 ELM_INTLIST_SETS => {
5911 let mut index = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
5912 for value in args.iter().skip(1) {
5913 Self::set_user_int_list_value(
5914 &mut cell.int_list,
5915 bit,
5916 index,
5917 value.as_i64().unwrap_or(0) as i32,
5918 );
5919 index = index.saturating_add(1);
5920 }
5921 self.push_default_for_ret(ret_form);
5922 handled = true;
5923 }
5924 _ => {}
5925 }
5926 }
5927 } else if form == self.cfg.fm_strlist {
5928 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5929 let index = sub[1];
5930 let value = usize::try_from(index)
5931 .ok()
5932 .and_then(|index| cell.str_list.get(index).cloned());
5933 if sub.len() == 2 {
5934 if al_id == 0 {
5935 if let Some(value) = value {
5936 self.push_str(value);
5937 } else {
5938 self.push_default_for_ret(ret_form);
5939 }
5940 } else if let Ok(index) = usize::try_from(index) {
5941 if let Some(slot) = cell.str_list.get_mut(index) {
5942 *slot = args
5943 .first()
5944 .and_then(|v| v.as_str())
5945 .unwrap_or("")
5946 .to_string();
5947 }
5948 self.push_default_for_ret(ret_form);
5949 } else {
5950 self.push_default_for_ret(ret_form);
5951 }
5952 } else if let Some(value) = value {
5953 self.call_prop_eval_str_op(&value, sub[2], args, al_id)?;
5954 } else {
5955 self.push_default_for_ret(ret_form);
5956 }
5957 handled = true;
5958 } else if sub.len() == 1 {
5959 match sub[0] {
5960 ELM_STRLIST_INIT => {
5961 cell.form = decl_form;
5962 cell.element = self.default_user_prop_element(prop_id, decl_form);
5963 cell.str_list.clear();
5964 cell.str_list.resize_with(decl_size, String::new);
5965 self.push_default_for_ret(ret_form);
5966 handled = true;
5967 }
5968 ELM_STRLIST_RESIZE => {
5969 let new_len =
5970 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5971 cell.str_list.resize_with(new_len, String::new);
5972 self.push_default_for_ret(ret_form);
5973 handled = true;
5974 }
5975 ELM_STRLIST_GET_SIZE => {
5976 self.push_int(cell.str_list.len() as i32);
5977 handled = true;
5978 }
5979 _ => {}
5980 }
5981 }
5982 }
5983
5984 self.user_props.insert(prop_id, cell);
5985 Ok(handled)
5986 }
5987
5988 fn exec_call_prop_command(
5989 &mut self,
5990 frame_idx: usize,
5991 prop_idx: usize,
5992 sub: &[i32],
5993 al_id: i32,
5994 ret_form: i32,
5995 args: &[Value],
5996 ) -> Result<()> {
5997 use crate::runtime::forms::codes::{
5998 ELM_ARRAY, ELM_INTLIST_BIT, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4, ELM_INTLIST_BIT8,
5999 ELM_INTLIST_BIT16, ELM_INTLIST_CLEAR, ELM_INTLIST_GET_SIZE, ELM_INTLIST_INIT,
6000 ELM_INTLIST_RESIZE, ELM_INTLIST_SETS, ELM_STRLIST_GET_SIZE, ELM_STRLIST_INIT,
6001 ELM_STRLIST_RESIZE, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST,
6002 FM_STRLISTREF, FM_STRREF,
6003 };
6004
6005 let (form, decl_size, mut value, mut element) = {
6006 let prop = self
6007 .call_stack
6008 .get(frame_idx)
6009 .and_then(|f| f.user_props.get(prop_idx))
6010 .ok_or_else(|| anyhow!("call prop frame/index out of range"))?;
6011 (
6012 prop.form,
6013 prop.decl_size,
6014 prop.value.clone(),
6015 prop.element.clone(),
6016 )
6017 };
6018
6019 let mut write_back = false;
6020
6021 if let Some(composed) =
6028 self.compose_call_prop_tail(&self.call_stack[frame_idx].user_props[prop_idx], sub)
6029 {
6030 let mut owned_args = args.to_vec();
6031 self.exec_command(composed, al_id, ret_form, &mut owned_args)?;
6032 return Ok(());
6033 }
6034
6035 match form {
6036 FM_INT => {
6037 if sub.is_empty() {
6038 if al_id == 0 {
6039 match &value {
6040 CallPropValue::Int(n) => self.push_int(*n),
6041 _ => bail!("CALL_PROP int storage mismatch"),
6042 }
6043 } else {
6044 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
6045 value = CallPropValue::Int(rhs);
6046 write_back = true;
6047 self.push_default_for_ret(ret_form);
6048 }
6049 } else {
6050 self.push_element(element.clone());
6051 }
6052 }
6053 FM_STR => {
6054 let current = match &value {
6055 CallPropValue::Str(s) => s.clone(),
6056 _ => bail!("CALL_PROP str storage mismatch"),
6057 };
6058 if sub.is_empty() {
6059 if al_id == 0 {
6060 self.push_str(current);
6061 } else {
6062 let rhs = args
6063 .first()
6064 .and_then(|v| v.as_str())
6065 .unwrap_or("")
6066 .to_string();
6067 value = CallPropValue::Str(rhs);
6068 write_back = true;
6069 self.push_default_for_ret(ret_form);
6070 }
6071 } else {
6072 self.call_prop_eval_str_op(¤t, sub[0], args, al_id)?;
6073 }
6074 }
6075 FM_INTLIST => {
6076 let mut list = match value {
6077 CallPropValue::IntList(v) => v,
6078 _ => bail!("CALL_PROP intlist storage mismatch"),
6079 };
6080 if sub.is_empty() {
6081 self.push_element(element.clone());
6082 } else if sub.len() >= 2 && sub[0] == ELM_ARRAY {
6083 let idx = sub[1].max(0) as usize;
6084 if al_id == 0 {
6085 self.push_int(list.get(idx).copied().unwrap_or(0));
6086 } else {
6087 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
6088 if list.len() <= idx {
6089 list.resize(idx + 1, 0);
6090 }
6091 list[idx] = rhs;
6092 write_back = true;
6093 self.push_default_for_ret(ret_form);
6094 }
6095 } else {
6096 match sub[0] {
6097 ELM_INTLIST_BIT | ELM_INTLIST_BIT2 | ELM_INTLIST_BIT4
6098 | ELM_INTLIST_BIT8 | ELM_INTLIST_BIT16 => {
6099 self.push_element(element.clone());
6100 }
6101 ELM_INTLIST_INIT => {
6102 list.clear();
6103 list.resize(decl_size, 0);
6104 write_back = true;
6105 self.push_default_for_ret(ret_form);
6106 }
6107 ELM_INTLIST_RESIZE => {
6108 let new_len =
6109 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6110 list.resize(new_len, 0);
6111 write_back = true;
6112 self.push_default_for_ret(ret_form);
6113 }
6114 ELM_INTLIST_GET_SIZE => self.push_int(list.len() as i32),
6115 ELM_INTLIST_CLEAR => {
6116 let start =
6117 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6118 let end =
6119 args.get(1).and_then(|v| v.as_i64()).unwrap_or(-1).max(-1) as isize;
6120 let clear_value = if al_id == 0 {
6121 0
6122 } else {
6123 args.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32
6124 };
6125 if !list.is_empty() && end >= 0 {
6126 let end = usize::min(end as usize, list.len().saturating_sub(1));
6127 for i in start..=end {
6128 if i < list.len() {
6129 list[i] = clear_value;
6130 }
6131 }
6132 }
6133 write_back = true;
6134 self.push_default_for_ret(ret_form);
6135 }
6136 ELM_INTLIST_SETS => {
6137 let start =
6138 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6139 for (off, v) in args.iter().skip(1).enumerate() {
6140 let idx = start + off;
6141 if list.len() <= idx {
6142 list.resize(idx + 1, 0);
6143 }
6144 list[idx] = v.as_i64().unwrap_or(0) as i32;
6145 }
6146 write_back = true;
6147 self.push_default_for_ret(ret_form);
6148 }
6149 _ => bail!("unsupported CALL_PROP intlist op {:?}", sub),
6150 }
6151 }
6152 value = CallPropValue::IntList(list);
6153 }
6154 FM_STRLIST => {
6155 let mut list = match value {
6156 CallPropValue::StrList(v) => v,
6157 _ => bail!("CALL_PROP strlist storage mismatch"),
6158 };
6159 if sub.is_empty() {
6160 self.push_element(element.clone());
6161 } else if sub.len() >= 2 && sub[0] == ELM_ARRAY {
6162 let idx = sub[1].max(0) as usize;
6163 if list.len() <= idx {
6164 list.resize_with(idx + 1, String::new);
6165 }
6166 if sub.len() == 2 {
6167 if al_id == 0 {
6168 self.push_str(list[idx].clone());
6169 } else {
6170 let rhs = args
6171 .first()
6172 .and_then(|v| v.as_str())
6173 .unwrap_or("")
6174 .to_string();
6175 list[idx] = rhs;
6176 write_back = true;
6177 self.push_default_for_ret(ret_form);
6178 }
6179 } else {
6180 let current = list[idx].clone();
6181 self.call_prop_eval_str_op(¤t, sub[2], args, al_id)?;
6182 }
6183 } else {
6184 match sub[0] {
6185 ELM_STRLIST_INIT => {
6186 list.clear();
6187 list.resize_with(decl_size, String::new);
6188 write_back = true;
6189 self.push_default_for_ret(ret_form);
6190 }
6191 ELM_STRLIST_RESIZE => {
6192 let new_len =
6193 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6194 list.resize_with(new_len, String::new);
6195 write_back = true;
6196 self.push_default_for_ret(ret_form);
6197 }
6198 ELM_STRLIST_GET_SIZE => self.push_int(list.len() as i32),
6199 _ => bail!("unsupported CALL_PROP strlist op {:?}", sub),
6200 }
6201 }
6202 value = CallPropValue::StrList(list);
6203 }
6204 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF => {
6205 if sub.is_empty() {
6206 if al_id == 0 {
6207 if let CallPropValue::Element(e) = &value {
6208 if e.is_empty() {
6209 self.push_element(element.clone());
6210 } else {
6211 self.push_element(e.clone());
6212 }
6213 } else {
6214 self.push_element(element.clone());
6215 }
6216 } else {
6217 let rhs = args.first().cloned().unwrap_or(Value::Element(Vec::new()));
6218 match rhs {
6219 Value::Element(e) => {
6220 element = e.clone();
6221 value = CallPropValue::Element(e);
6222 write_back = true;
6223 }
6224 _ => bail!("CALL_PROP ref assign requires element"),
6225 }
6226 self.push_default_for_ret(ret_form);
6227 }
6228 } else if let CallPropValue::Element(e) = &value {
6229 if e.is_empty() {
6230 self.push_element(element.clone());
6231 } else {
6232 self.push_element(e.clone());
6233 }
6234 } else {
6235 self.push_element(element.clone());
6236 }
6237 }
6238 _ => {
6239 if !sub.is_empty() || al_id == 0 {
6240 self.push_element(element.clone());
6241 } else {
6242 bail!("unsupported CALL_PROP form {}", form);
6243 }
6244 }
6245 }
6246
6247 if write_back {
6248 let prop = self
6249 .call_stack
6250 .get_mut(frame_idx)
6251 .and_then(|f| f.user_props.get_mut(prop_idx))
6252 .ok_or_else(|| anyhow!("call prop frame/index out of range"))?;
6253 prop.value = value;
6254 prop.element = element;
6255 }
6256 Ok(())
6257 }
6258
6259 fn exec_call_property(&mut self, elm: &[i32]) -> Result<bool> {
6260 vm_trace!(self, None, format!("exec_call_property elm={:?}", elm));
6261 use crate::runtime::forms::codes::{
6262 ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, ELM_INTLIST_GET_SIZE,
6263 ELM_STRLIST_GET_SIZE, FM_CALL, FM_CALLLIST,
6264 };
6265
6266 if elm.is_empty() {
6267 return Ok(false);
6268 }
6269 let head = elm[0];
6270 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
6271 return Ok(false);
6272 }
6273
6274 let current_idx = self
6275 .current_call_frame_index()
6276 .ok_or_else(|| anyhow!("call stack underflow"))?;
6277
6278 let tail: &[i32] = if head == FM_CALLLIST {
6279 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
6280 bail!("malformed CALLLIST access: {:?}", elm);
6281 }
6282 self.resolve_call_frame_index(elm[2])
6283 .ok_or_else(|| anyhow!("CALLLIST index out of range: {}", elm[2]))?;
6284 &elm[3..]
6285 } else {
6286 &elm[1..]
6287 };
6288
6289 if tail.is_empty() {
6290 self.push_element(elm.to_vec());
6291 return Ok(true);
6292 }
6293
6294 match tail[0] {
6295 ELM_CALL_L => {
6296 let sub = &tail[1..];
6297 if sub.is_empty() {
6298 self.push_element(elm.to_vec());
6299 } else if sub.len() >= 2 && self.call_array_marker(sub[0]) {
6300 let idx = usize::try_from(sub[1]).ok();
6301 let v = idx
6302 .and_then(|idx| self.call_stack[current_idx].int_args.get(idx))
6303 .copied()
6304 .unwrap_or(0);
6305 self.push_int(v);
6306 } else if sub[0] == ELM_INTLIST_GET_SIZE {
6307 self.push_int(self.call_stack[current_idx].int_args.len() as i32);
6308 } else {
6309 self.push_element(elm[..elm.len() - sub.len()].to_vec());
6310 }
6311 return Ok(true);
6312 }
6313 ELM_CALL_K => {
6314 let sub = &tail[1..];
6315 if sub.is_empty() {
6316 self.push_element(elm.to_vec());
6317 } else if sub.len() >= 2 && self.call_array_marker(sub[0]) {
6318 let idx = usize::try_from(sub[1]).ok();
6319 let v = idx
6320 .and_then(|idx| self.call_stack[current_idx].str_args.get(idx))
6321 .cloned()
6322 .unwrap_or_default();
6323 if sub.len() == 2 {
6324 self.push_str(v);
6325 } else {
6326 self.call_prop_eval_str_op(&v, sub[2], &[], 0)?;
6327 }
6328 } else if sub[0] == ELM_STRLIST_GET_SIZE {
6329 self.push_int(self.call_stack[current_idx].str_args.len() as i32);
6330 } else {
6331 self.push_element(elm[..elm.len() - sub.len()].to_vec());
6332 }
6333 return Ok(true);
6334 }
6335 _ => {}
6336 }
6337
6338 if elm_code::owner(tail[0]) != elm_code::ELM_OWNER_CALL_PROP {
6339 bail!("invalid CALL property owner for {:?}", elm);
6340 }
6341
6342 let call_prop_id = elm_code::code(tail[0]) as i32;
6343 let prop_idx = self
6344 .find_call_prop_index_in_frame(current_idx, call_prop_id)
6345 .ok_or_else(|| anyhow!("missing CALL_PROP id={} for {:?}", call_prop_id, elm))?;
6346 let sub = &tail[1..];
6347 if self.push_call_prop_fast(current_idx, prop_idx, sub)? {
6348 return Ok(true);
6349 }
6350 let prop = self.call_stack[current_idx].user_props[prop_idx].clone();
6351 self.push_call_prop_result(&prop, sub, elm)?;
6352 Ok(true)
6353 }
6354
6355 fn exec_call_assign(&mut self, elm: &[i32], al_id: i32, rhs: Value) -> Result<bool> {
6356 use crate::runtime::forms::codes::{
6357 ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, FM_CALL, FM_CALLLIST,
6358 };
6359
6360 if elm.is_empty() {
6361 return Ok(false);
6362 }
6363 let head = elm[0];
6364 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
6365 return Ok(false);
6366 }
6367
6368 let current_idx = match self.current_call_frame_index() {
6369 Some(v) => v,
6370 None => return Ok(true),
6371 };
6372
6373 let tail: &[i32] = if head == FM_CALLLIST {
6374 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
6375 bail!("malformed CALLLIST assign: {:?}", elm);
6376 }
6377 self.resolve_call_frame_index(elm[2])
6378 .ok_or_else(|| anyhow!("CALLLIST index out of range: {}", elm[2]))?;
6379 &elm[3..]
6380 } else {
6381 &elm[1..]
6382 };
6383
6384 if tail.is_empty() {
6385 return Ok(true);
6386 }
6387
6388 match tail[0] {
6389 ELM_CALL_L => {
6390 let sub = &tail[1..];
6391 if sub.len() >= 2
6392 && self.call_array_marker(sub[0])
6393 && let (Ok(idx), Value::Int(n)) = (usize::try_from(sub[1]), rhs)
6394 {
6395 let len = self.call_stack[current_idx].int_args.len();
6396 let old = self.call_stack[current_idx].int_args.get(idx).copied();
6397 vm_trace!(
6398 self,
6399 None,
6400 format!(
6401 "CALL.L assign frame={} idx={} len={} old={:?} new={}",
6402 current_idx, idx, len, old, n
6403 ),
6404 );
6405 if let Some(slot) = self.call_stack[current_idx].int_args.get_mut(idx) {
6406 *slot = n as i32;
6407 }
6408 }
6409 return Ok(true);
6410 }
6411 ELM_CALL_K => {
6412 let sub = &tail[1..];
6413 if sub.len() >= 2
6414 && self.call_array_marker(sub[0])
6415 && let (Ok(idx), Value::Str(s)) = (usize::try_from(sub[1]), rhs)
6416 {
6417 let len = self.call_stack[current_idx].str_args.len();
6418 let old = self.call_stack[current_idx].str_args.get(idx).cloned();
6419 vm_trace!(
6420 self,
6421 None,
6422 format!(
6423 "CALL.K assign frame={} idx={} len={} old={:?} new={:?}",
6424 current_idx, idx, len, old, s
6425 ),
6426 );
6427 if let Some(slot) = self.call_stack[current_idx].str_args.get_mut(idx) {
6428 *slot = s;
6429 }
6430 }
6431 return Ok(true);
6432 }
6433 _ => {}
6434 }
6435
6436 if elm_code::owner(tail[0]) != elm_code::ELM_OWNER_CALL_PROP {
6437 bail!("invalid CALL assign owner for {:?}", elm);
6438 }
6439 let call_prop_id = elm_code::code(tail[0]) as i32;
6440 let sub = &tail[1..];
6441 let prop_idx = self.ensure_call_prop_index_for_assign(current_idx, call_prop_id, &rhs)?;
6442 let prop_for_compose = self.call_stack[current_idx].user_props[prop_idx].clone();
6443 if let Some(composed) = self.compose_call_prop_tail(&prop_for_compose, sub) {
6444 self.exec_assign(composed, al_id, rhs)?;
6445 return Ok(true);
6446 }
6447
6448 if sub.is_empty()
6449 && matches!(
6450 prop_for_compose.form,
6451 crate::runtime::forms::codes::FM_INTREF | crate::runtime::forms::codes::FM_STRREF
6452 )
6453 {
6454 let target = self.call_prop_effective_element(&prop_for_compose);
6459 let default_target = Self::call_prop_element(prop_for_compose.prop_id);
6460 if target.is_empty() || target == elm || target == default_target {
6461 bail!(
6462 "unbound scalar CALL_PROP assignment form={} target={:?} access={:?}",
6463 prop_for_compose.form,
6464 target,
6465 elm
6466 );
6467 }
6468 self.exec_assign(target, al_id, rhs)?;
6469 return Ok(true);
6470 }
6471
6472 let frame = &mut self.call_stack[current_idx];
6473 let prop = frame
6474 .user_props
6475 .get_mut(prop_idx)
6476 .ok_or_else(|| anyhow!("missing CALL_PROP slot assign id={}", call_prop_id))?;
6477 Self::assign_call_prop_result(prop, sub, rhs)?;
6478 Ok(true)
6479 }
6480
6481 fn exec_call_command(
6482 &mut self,
6483 elm: &[i32],
6484 al_id: i32,
6485 ret_form: i32,
6486 args: &[Value],
6487 ) -> Result<bool> {
6488 use crate::runtime::forms::codes::{
6489 ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, ELM_INTLIST_CLEAR, ELM_INTLIST_GET_SIZE,
6490 ELM_INTLIST_INIT, ELM_INTLIST_RESIZE, ELM_INTLIST_SETS, ELM_STRLIST_GET_SIZE,
6491 ELM_STRLIST_INIT, ELM_STRLIST_RESIZE, FM_CALL, FM_CALLLIST,
6492 };
6493
6494 if elm.is_empty() {
6495 return Ok(false);
6496 }
6497 let head = elm[0];
6498 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
6499 return Ok(false);
6500 }
6501
6502 let current_idx = match self.current_call_frame_index() {
6503 Some(v) => v,
6504 None => {
6505 self.push_default_for_ret(ret_form);
6506 return Ok(true);
6507 }
6508 };
6509
6510 let tail: &[i32] = if head == FM_CALLLIST {
6511 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
6512 self.push_default_for_ret(ret_form);
6513 return Ok(true);
6514 }
6515 let Some(_selected_idx) = self.resolve_call_frame_index(elm[2]) else {
6516 self.push_default_for_ret(ret_form);
6517 return Ok(true);
6518 };
6519 &elm[3..]
6520 } else {
6521 &elm[1..]
6522 };
6523
6524 if tail.is_empty() {
6525 self.push_default_for_ret(ret_form);
6526 return Ok(true);
6527 }
6528
6529 match tail[0] {
6530 ELM_CALL_L => {
6531 let sub = &tail[1..];
6532 if sub.is_empty() {
6533 self.push_default_for_ret(ret_form);
6534 return Ok(true);
6535 }
6536 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
6537 let idx = usize::try_from(sub[1]).ok();
6538 if al_id == 1 {
6539 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
6540 if let Some(slot) =
6541 idx.and_then(|idx| self.call_stack[current_idx].int_args.get_mut(idx))
6542 {
6543 *slot = rhs;
6544 }
6545 self.push_default_for_ret(ret_form);
6546 } else {
6547 let value = idx
6548 .and_then(|idx| self.call_stack[current_idx].int_args.get(idx))
6549 .copied()
6550 .unwrap_or(0);
6551 self.push_int(value);
6552 }
6553 return Ok(true);
6554 }
6555 match sub[0] {
6556 ELM_INTLIST_INIT => {
6557 let values = Self::blank_call_int_args(self.call_flag_count);
6558 self.call_stack[current_idx].int_args = values;
6559 }
6560 ELM_INTLIST_RESIZE => {
6561 let new_len =
6562 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6563 self.call_stack[current_idx].int_args.resize(new_len, 0);
6564 }
6565 ELM_INTLIST_GET_SIZE => {
6566 self.push_int(self.call_stack[current_idx].int_args.len() as i32);
6567 }
6568 ELM_INTLIST_CLEAR => {
6569 let start = args.first().and_then(|v| v.as_i64()).unwrap_or(0);
6570 let end = args.get(1).and_then(|v| v.as_i64()).unwrap_or(start);
6571 let value = if al_id == 0 {
6572 0
6573 } else {
6574 args.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32
6575 };
6576 if start <= end {
6577 let frame = &mut self.call_stack[current_idx];
6578 for index in start..=end {
6579 if let Ok(index) = usize::try_from(index)
6580 && let Some(slot) = frame.int_args.get_mut(index)
6581 {
6582 *slot = value;
6583 }
6584 }
6585 }
6586 }
6587 ELM_INTLIST_SETS => {
6588 let start = args.first().and_then(|v| v.as_i64()).unwrap_or(0);
6589 let frame = &mut self.call_stack[current_idx];
6590 for (offset, value) in args.iter().skip(1).enumerate() {
6591 let Some(index) = start.checked_add(offset as i64) else {
6592 break;
6593 };
6594 if let Ok(index) = usize::try_from(index)
6595 && let Some(slot) = frame.int_args.get_mut(index)
6596 {
6597 *slot = value.as_i64().unwrap_or(0) as i32;
6598 }
6599 }
6600 }
6601 _ => self.push_default_for_ret(ret_form),
6602 }
6603 Ok(true)
6604 }
6605 ELM_CALL_K => {
6606 let sub = &tail[1..];
6607 if sub.is_empty() {
6608 self.push_default_for_ret(ret_form);
6609 return Ok(true);
6610 }
6611 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
6612 let idx = usize::try_from(sub[1]).ok();
6613 if sub.len() == 2 {
6614 if al_id == 1 {
6615 let rhs = args
6616 .first()
6617 .and_then(|v| v.as_str())
6618 .unwrap_or("")
6619 .to_string();
6620 if let Some(slot) = idx
6621 .and_then(|idx| self.call_stack[current_idx].str_args.get_mut(idx))
6622 {
6623 *slot = rhs;
6624 }
6625 self.push_default_for_ret(ret_form);
6626 } else {
6627 let value = idx
6628 .and_then(|idx| self.call_stack[current_idx].str_args.get(idx))
6629 .cloned()
6630 .unwrap_or_default();
6631 self.push_str(value);
6632 }
6633 } else {
6634 let value = idx
6635 .and_then(|idx| self.call_stack[current_idx].str_args.get(idx))
6636 .cloned()
6637 .unwrap_or_default();
6638 self.call_prop_eval_str_op(&value, sub[2], args, al_id)?;
6639 }
6640 return Ok(true);
6641 }
6642 match sub[0] {
6643 ELM_STRLIST_INIT => {
6644 let values = Self::blank_call_str_args(self.call_flag_count);
6645 self.call_stack[current_idx].str_args = values;
6646 }
6647 ELM_STRLIST_RESIZE => {
6648 let new_len =
6649 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
6650 self.call_stack[current_idx]
6651 .str_args
6652 .resize_with(new_len, String::new);
6653 }
6654 ELM_STRLIST_GET_SIZE => {
6655 self.push_int(self.call_stack[current_idx].str_args.len() as i32);
6656 }
6657 _ => self.push_default_for_ret(ret_form),
6658 }
6659 Ok(true)
6660 }
6661 _ => {
6662 if elm_code::owner(tail[0]) == elm_code::ELM_OWNER_CALL_PROP {
6663 let call_prop_id = elm_code::code(tail[0]) as i32;
6664 let prop_idx = self
6665 .find_call_prop_index_in_frame(current_idx, call_prop_id)
6666 .ok_or_else(|| {
6667 anyhow!(
6668 "missing CALL_PROP command id={} for {:?}",
6669 call_prop_id,
6670 elm
6671 )
6672 })?;
6673 self.exec_call_prop_command(
6674 current_idx,
6675 prop_idx,
6676 &tail[1..],
6677 al_id,
6678 ret_form,
6679 args,
6680 )?;
6681 return Ok(true);
6682 }
6683 bail!("unsupported CALL command chain {:?}", elm);
6684 }
6685 }
6686 }
6687
6688 fn push_property_value(&mut self, v: Value, array_idx: Option<usize>) {
6689 match v {
6690 Value::NamedArg { value, .. } => self.push_property_value(*value, array_idx),
6691 Value::Int(n) => self.push_int(n as i32),
6692 Value::Str(s) => self.push_str(s),
6693 Value::Element(elm) => self.push_element(elm),
6694 Value::List(items) => {
6695 if let Some(i) = array_idx {
6696 if let Some(item) = items.get(i).cloned() {
6697 self.push_property_value(item, None);
6698 } else {
6699 self.push_int(0);
6700 }
6701 } else {
6702 panic!("raw Value::List used as property result; expected runtime ref");
6703 }
6704 }
6705 }
6706 }
6707
6708 fn assign_user_prop(&mut self, prop_id: u16, array_idx: Option<usize>, rhs: Value) {
6709 let decl = self
6710 .user_prop_decl(prop_id)
6711 .unwrap_or((self.cfg.fm_list, 0));
6712 if let Some(i) = array_idx {
6713 let slot_element = self.default_user_prop_slot_element(prop_id, i);
6714 let default_entry = self.default_user_prop_cell(prop_id);
6715 let existing_form = self
6716 .user_props
6717 .get(&prop_id)
6718 .map(|e| e.form)
6719 .unwrap_or(default_entry.form);
6720 if existing_form == self.cfg.fm_intlist {
6721 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
6722 if let Some(slot) = entry.int_list.get_mut(i) {
6723 *slot = rhs.as_i64().unwrap_or(0) as i32;
6724 }
6725 return;
6726 }
6727 if existing_form == self.cfg.fm_strlist {
6728 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
6729 if let Some(slot) = entry.str_list.get_mut(i) {
6730 *slot = rhs.as_str().unwrap_or("").to_string();
6731 }
6732 return;
6733 }
6734 let list_form = self.cfg.fm_list;
6735 let new_slot =
6736 self.user_prop_cell_from_value(rhs, list_form, slot_element, Some(prop_id));
6737 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
6738 let elm_array = self.ctx.ids.elm_array;
6739 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
6740 if entry.list_items.len() <= i {
6741 let cur = entry.list_items.len();
6742 entry
6743 .list_items
6744 .resize_with(i + 1, || UserPropCell::new(list_form, Vec::new()));
6745 for idx in cur..entry.list_items.len() {
6746 entry.list_items[idx].form = list_form;
6747 entry.list_items[idx].element = vec![head, elm_array, idx as i32];
6748 }
6749 }
6750 entry.list_items[i] = new_slot;
6751 } else {
6752 let element = self.default_user_prop_element(prop_id, decl.0);
6753 let cell = self.user_prop_cell_from_value(rhs, decl.0, element, Some(prop_id));
6754 self.user_props.insert(prop_id, cell);
6755 }
6756 }
6757
6758 fn exec_copy_element(&mut self) -> Result<()> {
6759 let start = match self.element_points.last().copied() {
6760 Some(v) => v,
6761 None => {
6762 vm_trace!(self, None, "COPY_ELM missing prior ELM_POINT");
6763 return Err(anyhow!("COPY_ELM without a prior ELM_POINT"));
6764 }
6765 };
6766 if start > self.int_stack.len() {
6767 vm_trace!(
6768 self,
6769 None,
6770 format!(
6771 "COPY_ELM invalid start={} len={}",
6772 start,
6773 self.int_stack.len()
6774 ),
6775 );
6776 bail!(
6777 "invalid element point start={start} len={}",
6778 self.int_stack.len()
6779 );
6780 }
6781 let end = self.int_stack.len();
6782 if self.sg_mwnd_object_trace_enabled()
6783 && Self::sg_mwnd_chain_interesting(&self.int_stack[start..])
6784 {
6785 self.sg_mwnd_object_trace_emit(format_args!(
6786 "COPY_ELM slice={:?} before_current_chain={:?} before_current_stage_object={:?}",
6787 &self.int_stack[start..],
6788 self.ctx.globals.current_object_chain,
6789 self.ctx.globals.current_stage_object
6790 ));
6791 }
6792 self.element_points.push(end);
6793 self.int_stack.extend_from_within(start..end);
6794 vm_trace!(
6795 self,
6796 None,
6797 format!("COPY_ELM copied {:?}", &self.int_stack[end..])
6798 );
6799 Ok(())
6800 }
6801
6802 fn pop_value_for_form(&mut self, form_code: i32) -> Result<Value> {
6803 if form_code == self.cfg.fm_void {
6804 return Ok(Value::Int(0));
6805 }
6806 if form_code == self.cfg.fm_int {
6807 return Ok(Value::Int(self.pop_int()? as i64));
6808 }
6809 if form_code == self.cfg.fm_str {
6810 return Ok(Value::Str(self.pop_str()?));
6811 }
6812 if form_code == self.cfg.fm_label {
6813 return Ok(Value::Int(self.pop_int()? as i64));
6814 }
6815 if form_code == self.cfg.fm_list {
6816 let nested = self.pop_arg_list()?;
6817 return Ok(Value::List(nested));
6818 }
6819
6820 self.trace_unknown_form(form_code, "pop_value_for_form");
6822 Ok(Value::Element(self.pop_element()?))
6823 }
6824
6825 fn pop_arg_list(&mut self) -> Result<Vec<Value>> {
6826 let arg_cnt_i32 = self.stream.pop_i32()?;
6827 if arg_cnt_i32 < 0 {
6828 bail!("negative arg_cnt={arg_cnt_i32}");
6829 }
6830 let arg_cnt = arg_cnt_i32 as usize;
6831 let mut out: Vec<Value> = vec![Value::Int(0); arg_cnt];
6832
6833 for i in (0..arg_cnt).rev() {
6835 let form_code = self.stream.pop_i32()?;
6836 let v = self.pop_value_for_form(form_code)?;
6837 out[i] = v;
6838 }
6839 Ok(out)
6840 }
6841
6842 fn exec_push(&mut self, form_code: i32) -> Result<()> {
6843 if form_code == self.cfg.fm_void {
6844 return Ok(());
6845 }
6846 if form_code == self.cfg.fm_int {
6847 let v = self.stream.pop_i32()?;
6848 self.push_int(v);
6849 return Ok(());
6850 }
6851 if form_code == self.cfg.fm_str {
6852 let s = self.stream.pop_str()?;
6853 self.push_str(s);
6854 return Ok(());
6855 }
6856
6857 self.trace_unknown_form(form_code, "exec_push");
6859 Ok(())
6860 }
6861
6862 fn exec_pop(&mut self, form_code: i32) -> Result<()> {
6863 if form_code == self.cfg.fm_void {
6864 return Ok(());
6865 }
6866 if form_code == self.cfg.fm_int {
6867 let _ = self.pop_int()?;
6868 return Ok(());
6869 }
6870 if form_code == self.cfg.fm_str {
6871 let _ = self.pop_str()?;
6872 return Ok(());
6873 }
6874
6875 self.trace_unknown_form(form_code, "exec_pop");
6876 Ok(())
6877 }
6878
6879 fn exec_copy(&mut self, form_code: i32) -> Result<()> {
6880 if form_code == self.cfg.fm_void {
6881 return Ok(());
6882 }
6883 if form_code == self.cfg.fm_int {
6884 let v = self.peek_int()?;
6885 self.push_int(v);
6886 return Ok(());
6887 }
6888 if form_code == self.cfg.fm_str {
6889 let s = self.peek_str()?;
6890 self.push_str(s);
6891 return Ok(());
6892 }
6893
6894 self.trace_unknown_form(form_code, "exec_copy");
6896 Ok(())
6897 }
6898
6899 fn legacy_global_world_stage_chain(&self, elm: &[i32], stage_idx: i64) -> Option<Vec<i32>> {
6904 if elm.first().copied() != Some(LEGACY_GLOBAL_WORLD_ELEMENT) || elm.len() < 2 {
6905 return None;
6906 }
6907
6908 let elm_array = if self.ctx.ids.elm_array != 0 {
6909 self.ctx.ids.elm_array
6910 } else {
6911 crate::runtime::forms::codes::ELM_ARRAY
6912 };
6913 let is_array =
6914 |value: i32| value == elm_array || value == crate::runtime::forms::codes::ELM_ARRAY;
6915 let stage_form = if self.ctx.ids.form_global_stage != 0 {
6916 self.ctx.ids.form_global_stage as i32
6917 } else {
6918 crate::runtime::forms::codes::FORM_GLOBAL_STAGE as i32
6919 };
6920 let stage_world = if self.ctx.ids.stage_elm_world != 0 {
6921 self.ctx.ids.stage_elm_world
6922 } else {
6923 crate::runtime::forms::codes::STAGE_ELM_WORLD
6924 };
6925
6926 if is_array(elm[1]) {
6927 let world_idx = *elm.get(2)?;
6928 if world_idx < 0 {
6929 return None;
6930 }
6931 let mut out = vec![
6932 stage_form,
6933 elm_array,
6934 stage_idx as i32,
6935 stage_world,
6936 elm_array,
6937 world_idx,
6938 ];
6939 out.extend_from_slice(&elm[3..]);
6940 return Some(out);
6941 }
6942
6943 if elm.len() == 2
6944 && matches!(
6945 elm[1],
6946 crate::runtime::forms::codes::elm_value::WORLDLIST_CREATE_WORLD
6947 | crate::runtime::forms::codes::elm_value::WORLDLIST_DESTROY_WORLD
6948 )
6949 {
6950 return Some(vec![
6951 stage_form,
6952 elm_array,
6953 stage_idx as i32,
6954 stage_world,
6955 elm[1],
6956 ]);
6957 }
6958
6959 None
6960 }
6961
6962 fn sync_legacy_global_worlds_from_front(&mut self) {
6963 const BACK_STAGE: i64 = 0;
6964 const FRONT_STAGE: i64 = 1;
6965
6966 let stage_form = if self.ctx.ids.form_global_stage != 0 {
6967 self.ctx.ids.form_global_stage
6968 } else {
6969 crate::runtime::forms::codes::FORM_GLOBAL_STAGE
6970 };
6971 let Some(stage) = self.ctx.globals.stage_forms.get_mut(&stage_form) else {
6972 return;
6973 };
6974 let Some(front_worlds) = stage.world_lists.get(&FRONT_STAGE).cloned() else {
6975 return;
6976 };
6977
6978 stage.world_lists.insert(BACK_STAGE, front_worlds);
6984 }
6985
6986 fn canonical_runtime_form_id(&self, form_id: u32) -> u32 {
6987 let ids = &self.ctx.ids;
6988
6989 if crate::runtime::forms::stage::is_stage_form_id(&self.ctx, form_id as i32) {
6994 return constants::global_form::STAGE_ALT;
6995 }
6996 if constants::matches_form_id(form_id, ids.form_global_mov, constants::global_form::MOV) {
6997 return constants::global_form::MOV;
6998 }
6999 if constants::matches_form_id(form_id, ids.form_global_bgm, constants::global_form::BGM) {
7000 return constants::global_form::BGM;
7001 }
7002 if constants::matches_form_id(
7003 form_id,
7004 ids.form_global_bgm_table,
7005 constants::global_form::BGMTABLE,
7006 ) {
7007 return constants::global_form::BGMTABLE;
7008 }
7009 if constants::matches_form_id(form_id, ids.form_global_math, constants::global_form::MATH) {
7010 return constants::global_form::MATH;
7011 }
7012 if constants::matches_form_id(form_id, ids.form_global_pcm, constants::global_form::PCM) {
7013 return constants::global_form::PCM;
7014 }
7015 if constants::matches_form_id(
7016 form_id,
7017 ids.form_global_pcmch,
7018 constants::global_form::PCMCH,
7019 ) {
7020 return constants::global_form::PCMCH;
7021 }
7022 if constants::matches_form_id(form_id, ids.form_global_se, constants::global_form::SE) {
7023 return constants::global_form::SE;
7024 }
7025 if constants::matches_form_id(
7026 form_id,
7027 ids.form_global_pcm_event,
7028 constants::global_form::PCMEVENT,
7029 ) {
7030 return constants::global_form::PCMEVENT;
7031 }
7032 if constants::matches_form_id(
7033 form_id,
7034 ids.form_global_excall,
7035 constants::global_form::EXCALL,
7036 ) {
7037 return constants::global_form::EXCALL;
7038 }
7039 if constants::matches_form_id(
7040 form_id,
7041 ids.form_global_screen,
7042 constants::global_form::SCREEN,
7043 ) {
7044 return constants::global_form::SCREEN;
7045 }
7046 if constants::matches_form_id(
7047 form_id,
7048 ids.form_global_msgbk,
7049 constants::global_form::MSGBK,
7050 ) {
7051 return constants::global_form::MSGBK;
7052 }
7053 if constants::matches_form_id(
7054 form_id,
7055 ids.form_global_koe_st,
7056 constants::global_form::KOE_ST,
7057 ) {
7058 return constants::global_form::KOE_ST;
7059 }
7060 if constants::matches_form_id(form_id, ids.form_global_key, constants::global_form::KEY) {
7061 return constants::global_form::KEY;
7062 }
7063 if form_id == constants::global_form::COUNTER {
7064 return constants::global_form::COUNTER;
7065 }
7066 if constants::matches_form_id(
7067 form_id,
7068 ids.form_global_frame_action,
7069 constants::global_form::FRAME_ACTION,
7070 ) {
7071 return constants::global_form::FRAME_ACTION;
7072 }
7073 if form_id == constants::global_form::TIMEWAIT {
7074 return constants::global_form::TIMEWAIT;
7075 }
7076 if form_id == constants::global_form::TIMEWAIT_KEY {
7077 return constants::global_form::TIMEWAIT_KEY;
7078 }
7079
7080 form_id
7081 }
7082
7083 #[inline(always)]
7084 fn sg_mwnd_object_trace_enabled(&self) -> bool {
7085 self.runtime_options.sg_debug
7086 }
7087
7088 fn sg_mwnd_object_trace_emit(&self, msg: impl std::fmt::Display) {
7089 eprintln!("[SG_DEBUG][MWND_OBJECT_TRACE][VM] {}", msg);
7090 }
7091
7092 fn sg_mwnd_chain_interesting(elm: &[i32]) -> bool {
7093 elm.iter().any(|v| {
7094 *v == crate::runtime::forms::codes::STAGE_ELM_MWND
7095 || *v == crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM
7096 || *v == crate::runtime::forms::codes::elm_value::MWND_OBJECT
7097 || *v == crate::runtime::forms::codes::elm_value::MWND_BUTTON
7098 || *v == crate::runtime::forms::codes::elm_value::MWND_FACE
7099 || *v == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT
7100 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CHILD
7101 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE
7102 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE_RECT
7103 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE_STRING
7104 || *v == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION
7105 || *v == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH
7106 })
7107 }
7108
7109 fn is_global_indexed_list_head(&self, head: i32) -> bool {
7110 if head < 0 {
7111 return false;
7112 }
7113 let head = head as u32;
7114 crate::runtime::constants::global_form::INT_LIST_FORMS.contains(&head)
7115 || crate::runtime::constants::global_form::STR_LIST_FORMS.contains(&head)
7116 }
7117
7118 fn is_global_indexed_list_chain(&self, elm: &[i32]) -> bool {
7119 if elm.len() < 3 || !self.is_global_indexed_list_head(elm[0]) {
7120 return false;
7121 }
7122 elm[1] == self.ctx.ids.elm_array || elm[1] == crate::runtime::forms::codes::ELM_ARRAY
7123 }
7124
7125 fn current_object_chain_has_child_index(&self, child_idx: i32) -> bool {
7126 if child_idx < 0 {
7127 return false;
7128 }
7129 let Some(chain) = self.ctx.globals.current_object_chain.as_ref() else {
7130 return false;
7131 };
7132 let Some(raw_stage_form) = chain.first().copied() else {
7133 return false;
7134 };
7135 let stage_form = raw_stage_form;
7136 if !crate::runtime::forms::stage::is_stage_form_id(&self.ctx, stage_form) {
7137 return false;
7138 }
7139 let stage_form = crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, stage_form);
7140 let elm_array = if self.ctx.ids.elm_array != 0 {
7141 self.ctx.ids.elm_array
7142 } else {
7143 crate::runtime::forms::codes::ELM_ARRAY
7144 };
7145 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
7146 self.ctx.ids.stage_elm_object
7147 } else {
7148 crate::runtime::forms::codes::STAGE_ELM_OBJECT
7149 };
7150 if chain.len() < 6
7151 || crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, chain[0])
7152 != stage_form
7153 || chain[1] != elm_array
7154 || chain[2] < 0
7155 {
7156 return false;
7157 }
7158
7159 let stage_idx = chain[2] as i64;
7160 let Some(stage_state) = self.ctx.globals.stage_forms.get(&stage_form) else {
7161 return false;
7162 };
7163
7164 fn descend_child_chain<'a>(
7165 mut obj: &'a crate::runtime::globals::ObjectState,
7166 chain: &[i32],
7167 mut pos: usize,
7168 elm_array: i32,
7169 ) -> Option<&'a crate::runtime::globals::ObjectState> {
7170 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
7171 while pos + 2 < chain.len() {
7172 if chain[pos] == object_child && chain[pos + 1] == elm_array && chain[pos + 2] >= 0
7173 {
7174 let idx = chain[pos + 2] as usize;
7175 obj = obj.runtime.child_objects.get(idx)?;
7176 pos += 3;
7177 } else {
7178 break;
7179 }
7180 }
7181 Some(obj)
7182 }
7183
7184 let current_obj = (|| -> Option<&crate::runtime::globals::ObjectState> {
7185 if chain[3] == stage_object {
7186 if chain[4] != elm_array || chain[5] < 0 {
7187 return None;
7188 }
7189 let top_idx = chain[5] as usize;
7190 let list = stage_state.object_lists.get(&stage_idx)?;
7191 let obj = list.get(top_idx)?;
7192 descend_child_chain(obj, chain, 6, elm_array)
7193 } else if chain[3] == crate::runtime::forms::codes::STAGE_ELM_MWND {
7194 if chain.len() < 9
7195 || chain[4] != elm_array
7196 || chain[5] < 0
7197 || chain[7] != elm_array
7198 || chain[8] < 0
7199 {
7200 return None;
7201 }
7202 let mwnd_idx = chain[5] as usize;
7203 let selector = chain[6];
7204 let obj_idx = chain[8] as usize;
7205 let mwnds = stage_state.mwnd_lists.get(&stage_idx)?;
7206 let mwnd = mwnds.get(mwnd_idx)?;
7207 let list = if selector == constants::MWND_BUTTON {
7208 &mwnd.button_list
7209 } else if selector == constants::MWND_FACE {
7210 &mwnd.face_list
7211 } else if selector == constants::MWND_OBJECT {
7212 &mwnd.object_list
7213 } else {
7214 return None;
7215 };
7216 let obj = list.get(obj_idx)?;
7217 descend_child_chain(obj, chain, 9, elm_array)
7218 } else if chain[3] == crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM {
7219 if chain.len() < 9
7220 || chain[4] != elm_array
7221 || chain[5] < 0
7222 || chain[7] != elm_array
7223 || chain[8] < 0
7224 {
7225 return None;
7226 }
7227 if chain[6] != crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT {
7228 return None;
7229 }
7230 let item_idx = chain[5] as usize;
7231 let obj_idx = chain[8] as usize;
7232 let items = stage_state.btnselitem_lists.get(&stage_idx)?;
7233 let item = items.get(item_idx)?;
7234 let obj = item.object_list.get(obj_idx)?;
7235 descend_child_chain(obj, chain, 9, elm_array)
7236 } else {
7237 None
7238 }
7239 })();
7240
7241 let Some(current_obj) = current_obj else {
7242 return false;
7243 };
7244 (child_idx as usize) < current_obj.runtime.child_objects.len()
7245 }
7246
7247 fn object_array_property_op(&self, op: i32) -> bool {
7248 let ids = &self.ctx.ids;
7249 op == crate::runtime::forms::codes::elm_value::OBJECT_CHILD
7250 || (ids.obj_x_rep != 0 && op == ids.obj_x_rep)
7251 || (ids.obj_y_rep != 0 && op == ids.obj_y_rep)
7252 || (ids.obj_z_rep != 0 && op == ids.obj_z_rep)
7253 || (ids.obj_tr_rep != 0 && op == ids.obj_tr_rep)
7254 || (ids.obj_f != 0 && op == ids.obj_f)
7255 || (ids.obj_frame_action_ch != 0 && op == ids.obj_frame_action_ch)
7256 }
7257
7258 fn is_current_object_child_tail(&self, elm: &[i32]) -> bool {
7259 if elm.len() < 2 {
7260 return false;
7261 }
7262 if elm[0] < 0 {
7263 return false;
7264 }
7265 if elm[1] != self.ctx.ids.elm_array && elm[1] != crate::runtime::forms::codes::ELM_ARRAY {
7266 return false;
7267 }
7268 if self.object_array_property_op(elm[0]) {
7269 return false;
7270 }
7271 if elm.len() == 2 {
7272 return true;
7273 }
7274 if self.object_array_property_op(elm[2]) {
7275 return elm[2] == crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
7276 }
7277 elm[2] == self.ctx.ids.elm_array
7278 || elm[2] == crate::runtime::forms::codes::ELM_ARRAY
7279 || elm[2] == crate::runtime::forms::codes::ELM_UP
7280 || self.compact_object_op_allowed(elm[2])
7281 }
7282
7283 fn global_indexed_list_must_dispatch_direct(&self, elm: &[i32]) -> bool {
7284 self.is_global_indexed_list_chain(elm)
7288 }
7289
7290 fn dispatch_global_indexed_list_property_direct(&mut self, elm: &[i32]) -> Result<bool> {
7291 if !self.global_indexed_list_must_dispatch_direct(elm) {
7292 return Ok(false);
7293 }
7294 let form_id = elm[0] as u32;
7295 self.ctx.vm_call = Some(runtime::VmCallMeta {
7296 element: elm.to_vec(),
7297 al_id: 0,
7298 ret_form: self.cfg.fm_int as i64,
7299 });
7300 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &[])? {
7301 self.ctx.vm_call = None;
7302 bail!("unhandled global indexed-list property chain {:?}", elm);
7303 }
7304 self.ctx.vm_call = None;
7305 if let Some(v) = self.ctx.pop() {
7306 self.push_return_value_raw(v);
7307 } else {
7308 bail!("global indexed-list property returned no value: {:?}", elm);
7309 }
7310 Ok(true)
7311 }
7312
7313 fn dispatch_global_indexed_list_assign_direct(
7314 &mut self,
7315 elm: &[i32],
7316 al_id: i32,
7317 rhs: Value,
7318 ) -> Result<bool> {
7319 if !self.global_indexed_list_must_dispatch_direct(elm) {
7320 return Ok(false);
7321 }
7322 let form_id = elm[0] as u32;
7323 let args: Vec<Value> = vec![rhs];
7324 self.ctx.vm_call = Some(runtime::VmCallMeta {
7325 element: elm.to_vec(),
7326 al_id: al_id as i64,
7327 ret_form: 0,
7328 });
7329 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
7330 self.ctx.vm_call = None;
7331 bail!("unhandled global indexed-list assignment chain {:?}", elm);
7332 }
7333 self.ctx.vm_call = None;
7334 self.ctx.stack.clear();
7335 self.drain_pending_frame_action_finishes()?;
7336 Ok(true)
7337 }
7338
7339 fn dispatch_global_indexed_list_command_direct(
7340 &mut self,
7341 elm: &[i32],
7342 al_id: i32,
7343 ret_form: i32,
7344 args: &mut [Value],
7345 ) -> Result<bool> {
7346 if !self.global_indexed_list_must_dispatch_direct(elm) {
7347 return Ok(false);
7348 }
7349 let form_id = elm[0] as u32;
7350 self.ctx.vm_call = Some(runtime::VmCallMeta {
7351 element: elm.to_vec(),
7352 al_id: al_id as i64,
7353 ret_form: ret_form as i64,
7354 });
7355 if !runtime::dispatch_form_code(&mut self.ctx, form_id, args)? {
7356 self.ctx.vm_call = None;
7357 bail!("unhandled global indexed-list command chain {:?}", elm);
7358 }
7359 self.ctx.vm_call = None;
7360 self.drain_pending_frame_action_finishes()?;
7361 if ret_form != self.cfg.fm_void {
7362 self.take_ctx_return(ret_form)?;
7363 } else {
7364 self.ctx.stack.clear();
7365 }
7366 Ok(true)
7367 }
7368
7369 fn try_parent_slot_property(&mut self, elm: &[i32]) -> bool {
7370 if elm.len() != 3 || elm[1] != self.ctx.ids.elm_array || elm[2] <= 0 {
7371 return false;
7372 }
7373 if self.ctx.globals.current_object_chain.is_some() && self.compact_object_op_allowed(elm[0])
7379 {
7380 return false;
7381 }
7382 let parent_form = elm[2] as u32;
7383 let slot = elm[0];
7384 let ret_form = self
7385 .ctx
7386 .vm_call
7387 .as_ref()
7388 .map(|m| m.ret_form)
7389 .unwrap_or(self.cfg.fm_int as i64);
7390 if ret_form == self.cfg.fm_str as i64 {
7391 let value = self
7392 .ctx
7393 .globals
7394 .str_props
7395 .get(&parent_form)
7396 .and_then(|m| m.get(&slot))
7397 .cloned()
7398 .unwrap_or_default();
7399 self.push_str(value);
7400 } else if let Some(value) = self
7401 .ctx
7402 .globals
7403 .str_props
7404 .get(&parent_form)
7405 .and_then(|m| m.get(&slot))
7406 .cloned()
7407 {
7408 self.push_str(value);
7409 } else {
7410 let value = self
7411 .ctx
7412 .globals
7413 .int_props
7414 .get(&parent_form)
7415 .and_then(|m| m.get(&slot).copied())
7416 .unwrap_or(0);
7417 self.push_int(value as i32);
7418 }
7419 true
7420 }
7421
7422 fn compact_object_op_allowed(&self, op: i32) -> bool {
7423 (0..=187).contains(&op)
7424 }
7425
7426 fn compact_object_op_allowed_for_element(
7427 &self,
7428 elm: &[i32],
7429 allow_ambiguous_single_token_object_op: bool,
7430 ) -> bool {
7431 let Some(op) = elm.first().copied() else {
7432 return false;
7433 };
7434 if elm.len() == 1 && !allow_ambiguous_single_token_object_op {
7435 return false;
7436 }
7437 self.compact_object_op_allowed(op)
7438 }
7439
7440 fn current_object_has_child_index(&self, child_idx: i32) -> bool {
7441 if self.current_object_chain_has_child_index(child_idx) {
7442 return true;
7443 }
7444 if child_idx < 0 {
7445 return false;
7446 }
7447 let Some((stage_idx, obj_idx)) = self.ctx.globals.current_stage_object else {
7448 return false;
7449 };
7450 let stage_form = self
7451 .ctx
7452 .globals
7453 .current_object_chain
7454 .as_ref()
7455 .and_then(|chain| chain.first().copied())
7456 .filter(|form| crate::runtime::forms::stage::is_stage_form_id(&self.ctx, *form))
7457 .map(|form| crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, form))
7458 .unwrap_or_else(|| crate::runtime::forms::stage::current_stage_form_id(&self.ctx));
7459 let Some(stage_state) = self.ctx.globals.stage_forms.get(&stage_form) else {
7460 return false;
7461 };
7462 let Some(list) = stage_state.object_lists.get(&stage_idx) else {
7463 return false;
7464 };
7465 let Some(obj) = list.get(obj_idx) else {
7466 return false;
7467 };
7468 (child_idx as usize) < obj.runtime.child_objects.len()
7469 }
7470
7471 fn try_compact_object_chain(
7472 &self,
7473 elm: &[i32],
7474 allow_ambiguous_single_token_object_op: bool,
7475 ) -> Option<Vec<i32>> {
7476 if elm.is_empty() {
7477 return None;
7478 }
7479
7480 let op = elm[0];
7481 if !self.compact_object_op_allowed_for_element(elm, allow_ambiguous_single_token_object_op)
7482 {
7483 return None;
7484 }
7485
7486 let elm_array = if self.ctx.ids.elm_array != 0 {
7487 self.ctx.ids.elm_array
7488 } else {
7489 crate::runtime::forms::codes::ELM_ARRAY
7490 };
7491 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
7492 self.ctx.ids.stage_elm_object
7493 } else {
7494 crate::runtime::forms::codes::STAGE_ELM_OBJECT
7495 };
7496
7497 let looks_like_absolute_stage_alias_object = elm.len() >= 4
7498 && constants::is_stage_global_form(elm[0] as u32, self.ctx.ids.form_global_stage)
7499 && elm[1] == stage_object
7500 && (elm[2] == elm_array || elm[2] == crate::runtime::forms::codes::ELM_ARRAY);
7501
7502 if looks_like_absolute_stage_alias_object {
7503 return None;
7504 }
7505
7506 if elm.len() >= 4
7510 && constants::matches_form_id(
7511 elm[0] as u32,
7512 self.ctx.ids.form_global_excall,
7513 constants::global_form::EXCALL,
7514 )
7515 && elm[1] == crate::runtime::forms::codes::ELM_EXCALL_STAGE
7516 && (elm[2] == elm_array || elm[2] == crate::runtime::forms::codes::ELM_ARRAY)
7517 {
7518 return None;
7519 }
7520
7521 if let Some(prefix) = &self.ctx.globals.current_object_chain
7527 && self.is_current_object_child_tail(elm)
7528 && self.current_object_has_child_index(elm[0])
7529 {
7530 let mut synthetic = prefix.clone();
7531 synthetic.push(crate::runtime::forms::codes::elm_value::OBJECT_CHILD);
7532 synthetic.push(elm_array);
7533 synthetic.push(elm[0]);
7534 if elm.len() > 2 {
7535 if elm[2] == crate::runtime::forms::codes::elm_value::OBJECT_CHILD {
7536 synthetic.extend_from_slice(&elm[3..]);
7537 } else {
7538 synthetic.extend_from_slice(&elm[2..]);
7539 }
7540 }
7541 if self.sg_mwnd_object_trace_enabled()
7542 && (Self::sg_mwnd_chain_interesting(elm)
7543 || Self::sg_mwnd_chain_interesting(&synthetic))
7544 {
7545 eprintln!(
7546 "[SG_DEBUG][MWND_OBJECT_TRACE][VM] try_compact child-shorthand elm={:?} prefix={:?} synthetic={:?}",
7547 elm, prefix, synthetic
7548 );
7549 }
7550 return Some(synthetic);
7551 }
7552
7553 if elm.len() >= 4
7557 && elm[1] >= 0
7558 && (elm[2] == elm_array || elm[2] == crate::runtime::forms::codes::ELM_ARRAY)
7559 && elm[3] >= 0
7560 {
7561 let stage_idx = elm[1];
7562 if !(0..3).contains(&stage_idx) {
7563 return None;
7564 }
7565 let stage_form = crate::runtime::forms::stage::current_stage_form_id(&self.ctx) as i32;
7566 let mut synthetic = vec![
7567 stage_form,
7568 elm_array,
7569 stage_idx,
7570 stage_object,
7571 elm_array,
7572 elm[3],
7573 op,
7574 ];
7575 if elm.len() > 4 {
7576 synthetic.extend_from_slice(&elm[4..]);
7577 }
7578 if self.sg_mwnd_object_trace_enabled()
7579 && (Self::sg_mwnd_chain_interesting(elm)
7580 || Self::sg_mwnd_chain_interesting(&synthetic))
7581 {
7582 eprintln!(
7583 "[SG_DEBUG][MWND_OBJECT_TRACE][VM] try_compact absolute elm={:?} synthetic={:?}",
7584 elm, synthetic
7585 );
7586 }
7587 return Some(synthetic);
7588 }
7589
7590 None
7591 }
7592
7593 fn try_parent_slot_assign(&mut self, elm: &[i32], rhs: &Value) -> bool {
7594 if elm.len() != 3 || elm[1] != self.ctx.ids.elm_array || elm[2] <= 0 {
7595 return false;
7596 }
7597 if self.ctx.globals.current_object_chain.is_some() && self.compact_object_op_allowed(elm[0])
7600 {
7601 return false;
7602 }
7603 let parent_form = elm[2] as u32;
7604 let slot = elm[0];
7605 match rhs {
7606 Value::Str(s) => {
7607 self.ctx
7608 .globals
7609 .str_props
7610 .entry(parent_form)
7611 .or_default()
7612 .insert(slot, s.clone());
7613 }
7614 Value::Int(n) => {
7615 self.ctx
7616 .globals
7617 .int_props
7618 .entry(parent_form)
7619 .or_default()
7620 .insert(slot, *n);
7621 }
7622 Value::NamedArg { value, .. } => return self.try_parent_slot_assign(elm, value),
7623 _ => return false,
7624 }
7625 true
7626 }
7627
7628 fn exec_property(&mut self, mut elm: Vec<i32>) -> Result<()> {
7629 if self.runtime_options.title_chain_trace
7630 && self.current_scene_name.as_deref() == Some("sys10_tt01")
7631 && matches!(elm.first().copied(), Some(83 | 84 | 24 | 25))
7632 {
7633 eprintln!(
7634 "[SG_TITLE_CHAIN_TRACE] line={} elm={:?} current_object_chain={:?} current_stage_object={:?}",
7635 self.current_line_no,
7636 elm,
7637 self.ctx.globals.current_object_chain,
7638 self.ctx.globals.current_stage_object
7639 );
7640 }
7641 vm_trace!(self, None, format!("exec_property enter elm={:?}", elm));
7642 if elm.is_empty() {
7643 self.push_int(0);
7644 return Ok(());
7645 }
7646 if self.exec_call_property(&elm)? {
7648 vm_trace!(
7649 self,
7650 None,
7651 format!("exec_property handled by call-property elm={:?}", elm),
7652 );
7653 return Ok(());
7654 }
7655
7656 let head = elm[0];
7657 let head_owner = elm_code::owner(head);
7658 if head_owner == elm_code::ELM_OWNER_CALL_PROP {
7659 let current_idx = self
7660 .current_call_frame_index()
7661 .ok_or_else(|| anyhow!("call stack underflow"))?;
7662 let call_prop_id = elm_code::code(head) as i32;
7663 let prop_idx = self
7664 .find_call_prop_index_in_frame(current_idx, call_prop_id)
7665 .ok_or_else(|| {
7666 anyhow!("missing direct CALL_PROP id={} for {:?}", call_prop_id, elm)
7667 })?;
7668 if self.push_call_prop_fast(current_idx, prop_idx, &elm[1..])? {
7669 vm_trace!(
7670 self,
7671 None,
7672 format!("exec_property direct CALL_PROP fast elm={:?}", elm),
7673 );
7674 return Ok(());
7675 }
7676 let prop = self.call_stack[current_idx].user_props[prop_idx].clone();
7677 self.push_call_prop_result(&prop, &elm[1..], &elm)?;
7678 vm_trace!(
7679 self,
7680 None,
7681 format!("exec_property direct CALL_PROP elm={:?}", elm),
7682 );
7683 return Ok(());
7684 }
7685
7686 if head_owner == elm_code::ELM_OWNER_USER_PROP {
7687 let prop_id = elm_code::code(head);
7688 let cell = self
7689 .user_props
7690 .get(&prop_id)
7691 .cloned()
7692 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
7693 let array_idx = self.extract_array_index(&elm);
7694 self.trace_cf_condition_user_prop_read(
7695 self.stream.get_prg_cntr(),
7696 prop_id,
7697 array_idx,
7698 &cell,
7699 &elm,
7700 );
7701 self.push_user_prop_cell_result(&cell, &elm[1..], &elm)?;
7702 vm_trace!(
7703 self,
7704 None,
7705 format!("exec_property direct USER_PROP elm={:?}", elm),
7706 );
7707 return Ok(());
7708 }
7709
7710 if head_owner != elm_code::ELM_OWNER_FORM {
7711 bail!(
7712 "unsupported property owner {} for element {:?}",
7713 head_owner,
7714 elm
7715 );
7716 }
7717
7718 if let Some(synthetic) = self.legacy_global_world_stage_chain(&elm, 1) {
7719 self.exec_property(synthetic)?;
7720 return Ok(());
7721 }
7722
7723 if self.dispatch_global_indexed_list_property_direct(&elm)? {
7724 vm_trace!(
7725 self,
7726 None,
7727 format!("exec_property handled by global indexed-list elm={:?}", elm)
7728 );
7729 return Ok(());
7730 }
7731
7732 if self.try_parent_slot_property(&elm) {
7733 vm_trace!(
7734 self,
7735 None,
7736 format!("exec_property handled by parent-slot elm={:?}", elm),
7737 );
7738 return Ok(());
7739 }
7740 if let Some(synthetic) = self.try_compact_object_chain(&elm, false) {
7741 vm_trace!(
7742 self,
7743 None,
7744 format!(
7745 "exec_property compact-object elm={:?} synthetic={:?}",
7746 elm, synthetic
7747 ),
7748 );
7749 self.ctx.vm_call = Some(runtime::VmCallMeta {
7750 element: synthetic.clone(),
7751 al_id: 0,
7752 ret_form: self.cfg.fm_int as i64,
7753 });
7754 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32);
7755 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &[])? {
7756 self.ctx.vm_call = None;
7757 bail!(
7758 "unhandled compact object property chain {:?} -> {:?}",
7759 elm,
7760 synthetic
7761 );
7762 }
7763 self.ctx.vm_call = None;
7764 self.update_compact_context_from_object_dispatch_chain(&synthetic);
7765 if let Some(v) = self.ctx.pop() {
7766 self.push_return_value_raw(v);
7767 } else {
7768 bail!("compact object property chain returned no value: {:?}", elm);
7769 }
7770 return Ok(());
7771 }
7772
7773 let form_id = self.canonical_runtime_form_id(head as u32);
7774 let args: Vec<Value> = Vec::new();
7775 vm_trace!(
7776 self,
7777 None,
7778 format!("exec_property dispatch form_id={} elm={:?}", form_id, elm),
7779 );
7780 self.ctx.vm_call = Some(runtime::VmCallMeta {
7783 element: elm,
7784 al_id: 0,
7785 ret_form: self.cfg.fm_int as i64,
7786 });
7787 let handled = runtime::dispatch_form_code(&mut self.ctx, form_id, &args)?;
7788 let elm = self
7789 .ctx
7790 .vm_call
7791 .take()
7792 .map(|meta| meta.element)
7793 .unwrap_or_default();
7794 if !handled {
7795 bail!("unhandled form property chain {:?}", elm);
7796 }
7797
7798 if let Some(v) = self.ctx.pop() {
7799 self.push_return_value_raw(v);
7800 } else {
7801 bail!("property chain returned no value: {:?}", elm);
7802 }
7803 self.ctx.int_vec_pool.give(elm);
7804
7805 Ok(())
7806 }
7807
7808 fn exec_assign(&mut self, elm: Vec<i32>, al_id: i32, rhs: Value) -> Result<()> {
7809 if elm.is_empty() {
7810 return Ok(());
7811 }
7812
7813 self.trace_cgm_coord_assign(&elm, &rhs);
7814
7815 if self.exec_call_assign(&elm, al_id, rhs.clone())? {
7817 return Ok(());
7818 }
7819
7820 let head = elm[0];
7821 let head_owner = elm_code::owner(head);
7822 if head_owner == elm_code::ELM_OWNER_CALL_PROP {
7823 let current_idx = self
7824 .current_call_frame_index()
7825 .ok_or_else(|| anyhow!("call stack underflow"))?;
7826 let call_prop_id = elm_code::code(head) as i32;
7827 let prop_idx = self
7828 .find_call_prop_index_in_frame(current_idx, call_prop_id)
7829 .ok_or_else(|| {
7830 anyhow!(
7831 "missing direct CALL_PROP assign id={} for {:?}",
7832 call_prop_id,
7833 elm
7834 )
7835 })?;
7836 let prop_for_compose = self.call_stack[current_idx].user_props[prop_idx].clone();
7837 if let Some(composed) = self.compose_call_prop_tail(&prop_for_compose, &elm[1..]) {
7838 self.exec_assign(composed, al_id, rhs)?;
7839 return Ok(());
7840 }
7841 let frame = self
7842 .call_stack
7843 .get_mut(current_idx)
7844 .ok_or_else(|| anyhow!("call stack underflow"))?;
7845 let prop = frame.user_props.get_mut(prop_idx).ok_or_else(|| {
7846 anyhow!("missing direct CALL_PROP slot assign id={}", call_prop_id)
7847 })?;
7848 Self::assign_call_prop_result(prop, &elm[1..], rhs)?;
7849 return Ok(());
7850 }
7851
7852 if head_owner == elm_code::ELM_OWNER_USER_PROP {
7853 let prop_id = elm_code::code(head);
7854 if elm.len() >= 3 && self.call_array_marker(elm[1]) && elm[2] < 0 {
7855 return Ok(());
7856 }
7857 let old_cell = self.user_props.get(&prop_id).cloned();
7858
7859 if self.exec_user_prop_list_command(
7866 prop_id,
7867 &elm[1..],
7868 al_id,
7869 self.cfg.fm_void,
7870 std::slice::from_ref(&rhs),
7871 )? {
7872 let new_cell = self.user_props.get(&prop_id);
7873 let array_idx = Self::intlist_assignment_subscript(&elm[1..])
7874 .and_then(|(_, index)| usize::try_from(index).ok());
7875 self.trace_cf_condition_user_prop_assign(
7876 self.stream.get_prg_cntr(),
7877 prop_id,
7878 array_idx,
7879 old_cell.as_ref(),
7880 new_cell,
7881 &rhs,
7882 &elm,
7883 );
7884 return Ok(());
7885 }
7886
7887 let array_idx = self.extract_array_index(&elm);
7888 self.assign_user_prop(prop_id, array_idx, rhs.clone());
7889 let new_cell = self.user_props.get(&prop_id);
7890 self.trace_cf_condition_user_prop_assign(
7891 self.stream.get_prg_cntr(),
7892 prop_id,
7893 array_idx,
7894 old_cell.as_ref(),
7895 new_cell,
7896 &rhs,
7897 &elm,
7898 );
7899 return Ok(());
7900 }
7901
7902 if head_owner != elm_code::ELM_OWNER_FORM {
7903 bail!(
7904 "unsupported assignment owner {} for element {:?}",
7905 head_owner,
7906 elm
7907 );
7908 }
7909
7910 if let Some(synthetic) = self.legacy_global_world_stage_chain(&elm, 1) {
7911 self.exec_assign(synthetic, al_id, rhs)?;
7912 self.sync_legacy_global_worlds_from_front();
7913 return Ok(());
7914 }
7915
7916 if self.dispatch_global_indexed_list_assign_direct(&elm, al_id, rhs.clone())? {
7917 return Ok(());
7918 }
7919
7920 if self.try_parent_slot_assign(&elm, &rhs) {
7921 return Ok(());
7922 }
7923 if let Some(synthetic) = self.try_compact_object_chain(&elm, true) {
7924 vm_trace!(
7925 self,
7926 None,
7927 format!(
7928 "exec_assign compact-object elm={:?} synthetic={:?} al_id={} rhs={:?}",
7929 elm, synthetic, al_id, rhs
7930 ),
7931 );
7932 let args: Vec<Value> = vec![rhs];
7933 self.ctx.vm_call = Some(runtime::VmCallMeta {
7934 element: synthetic.clone(),
7935 al_id: al_id as i64,
7936 ret_form: 0,
7937 });
7938 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32);
7939 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
7940 self.ctx.vm_call = None;
7941 bail!(
7942 "unhandled compact object assignment chain {:?} -> {:?}",
7943 elm,
7944 synthetic
7945 );
7946 }
7947 self.ctx.vm_call = None;
7948 self.update_compact_context_from_object_dispatch_chain(&synthetic);
7949 self.ctx.stack.clear();
7950 self.drain_pending_frame_action_finishes()?;
7951 return Ok(());
7952 }
7953
7954 let form_id = self.canonical_runtime_form_id(head as u32);
7955 vm_trace!(
7956 self,
7957 None,
7958 format!(
7959 "exec_assign dispatch form_id={} elm={:?} al_id={} rhs={:?}",
7960 form_id, elm, al_id, rhs
7961 ),
7962 );
7963 let args: Vec<Value> = vec![rhs];
7964 if self.vm_trace_config.commands_enabled {
7965 eprintln!(
7966 "[vm form assign] form={} al_id={} elm={:?} rhs={:?}",
7967 form_id,
7968 al_id,
7969 elm,
7970 args.first()
7971 );
7972 }
7973 self.ctx.vm_call = Some(runtime::VmCallMeta {
7974 element: elm,
7975 al_id: al_id as i64,
7976 ret_form: 0,
7977 });
7978
7979 let handled = runtime::dispatch_form_code(&mut self.ctx, form_id, &args)?;
7980 let elm = self
7981 .ctx
7982 .vm_call
7983 .take()
7984 .map(|meta| meta.element)
7985 .unwrap_or_default();
7986 if !handled {
7987 bail!("unhandled form assignment chain {:?}", elm);
7988 }
7989 self.ctx.int_vec_pool.give(elm);
7990 self.ctx.stack.clear();
7991 self.drain_pending_frame_action_finishes()?;
7992 Ok(())
7993 }
7994
7995 fn dispatch_owner_command(
7996 &mut self,
7997 owner: u8,
7998 raw_head: i32,
7999 ret_form: i32,
8000 args: &[Value],
8001 ) -> Result<bool> {
8002 let cmd_no = elm_code::code(raw_head) as usize;
8003
8004 if owner == elm_code::ELM_OWNER_CALL_CMD {
8005 let name = self
8010 .call_cmd_names
8011 .get(&(cmd_no as u32))
8012 .map(String::as_str)
8013 .unwrap_or("<unknown>");
8014 bail!(
8015 "invalid CALL_CMD owner: raw_head={} cmd_no={} name={}",
8016 raw_head,
8017 cmd_no,
8018 name
8019 );
8020 }
8021
8022 if owner != elm_code::ELM_OWNER_USER_CMD {
8023 return Ok(false);
8024 }
8025
8026 let requested_scene_no = self
8031 .current_scene_no
8032 .ok_or_else(|| anyhow!("USER_CMD executed without a current scene"))?;
8033 let command = self.resolve_user_command_by_id(requested_scene_no, cmd_no)?;
8034 vm_trace!(
8035 self,
8036 None,
8037 format!(
8038 "USER_CMD decoded name={} target_scene={} offset=0x{:x} include={} ret_form={} args={:?}",
8039 command.name.as_str(),
8040 command.target_scene_no,
8041 command.target_offset,
8042 command.include_command,
8043 ret_form,
8044 args
8045 ),
8046 );
8047 sg_omv_trace!(
8048 self,
8049 "USER_CMD enter name={} raw_head={} cmd_no={} target_scene={} offset=0x{:x} include={} ret_form={} argc={} current_scene={:?} current_pc=0x{:x}",
8050 command.name.as_str(),
8051 raw_head,
8052 command.encoded_no,
8053 command.target_scene_no,
8054 command.target_offset,
8055 command.include_command,
8056 ret_form,
8057 args.len(),
8058 self.current_scene_no,
8059 self.stream.get_prg_cntr()
8060 );
8061 self.enter_resolved_user_command(&command, ret_form, args, false, false)
8062 }
8063
8064 fn command_consumes_read_flag_no(&self, elm: &[i32]) -> bool {
8065 fn global_consumes(op: i32) -> bool {
8066 matches!(
8067 op,
8068 crate::runtime::forms::codes::elm_value::GLOBAL_PRINT
8069 | crate::runtime::forms::codes::elm_value::GLOBAL_SEL
8070 | crate::runtime::forms::codes::elm_value::GLOBAL_SEL_CANCEL
8071 | crate::runtime::forms::codes::elm_value::GLOBAL_SELMSG
8072 | crate::runtime::forms::codes::elm_value::GLOBAL_SELMSG_CANCEL
8073 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN
8074 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN_CANCEL
8075 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN_START
8076 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE
8077 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE_PLAY_WAIT
8078 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE_PLAY_WAIT_KEY
8079 )
8080 }
8081
8082 fn mwnd_consumes(op: i32) -> bool {
8083 matches!(
8084 op,
8085 crate::runtime::forms::codes::elm_value::MWND_PRINT
8086 | crate::runtime::forms::codes::elm_value::MWND_SEL
8087 | crate::runtime::forms::codes::elm_value::MWND_SEL_CANCEL
8088 | crate::runtime::forms::codes::elm_value::MWND_SELMSG
8089 | crate::runtime::forms::codes::elm_value::MWND_SELMSG_CANCEL
8090 | crate::runtime::forms::codes::elm_value::MWND_KOE
8091 | crate::runtime::forms::codes::elm_value::MWND_KOE_PLAY_WAIT
8092 | crate::runtime::forms::codes::elm_value::MWND_KOE_PLAY_WAIT_KEY
8093 )
8094 }
8095
8096 for pair in elm.windows(2) {
8102 let form_id = self.canonical_runtime_form_id(pair[0] as u32) as i32;
8103 let op = pair[1];
8104 if form_id == crate::runtime::forms::codes::FM_GLOBAL && global_consumes(op) {
8105 return true;
8106 }
8107 if form_id == crate::runtime::forms::codes::FM_MWND && mwnd_consumes(op) {
8108 return true;
8109 }
8110 }
8111
8112 false
8113 }
8114
8115 fn exec_command(
8116 &mut self,
8117 elm: Vec<i32>,
8118 al_id: i32,
8119 ret_form: i32,
8120 args: &mut Vec<Value>,
8121 ) -> Result<()> {
8122 let result = self.exec_command_element(&elm, al_id, ret_form, args);
8123 self.ctx.int_vec_pool.give(elm);
8124 result
8125 }
8126
8127 fn exec_command_element(
8128 &mut self,
8129 elm: &[i32],
8130 al_id: i32,
8131 ret_form: i32,
8132 args: &mut Vec<Value>,
8133 ) -> Result<()> {
8134 if elm.is_empty() {
8135 self.push_default_for_ret(ret_form);
8136 return Ok(());
8137 }
8138
8139 if self.exec_call_command(&elm, al_id, ret_form, args)? {
8140 return Ok(());
8141 }
8142
8143 let raw_head = elm[0];
8144 let owner = elm_code::owner(raw_head);
8145
8146 if owner == elm_code::ELM_OWNER_CALL_PROP {
8147 let current_idx = self
8148 .current_call_frame_index()
8149 .ok_or_else(|| anyhow!("call stack underflow"))?;
8150 let call_prop_id = elm_code::code(raw_head) as i32;
8151 let prop_idx = self
8152 .find_call_prop_index_in_frame(current_idx, call_prop_id)
8153 .ok_or_else(|| {
8154 anyhow!(
8155 "missing direct CALL_PROP command id={} for {:?}",
8156 call_prop_id,
8157 elm
8158 )
8159 })?;
8160 self.exec_call_prop_command(current_idx, prop_idx, &elm[1..], al_id, ret_form, args)?;
8161 return Ok(());
8162 }
8163
8164 if owner == elm_code::ELM_OWNER_USER_PROP {
8165 let prop_id = elm_code::code(raw_head);
8166 if self.exec_user_prop_list_command(prop_id, &elm[1..], al_id, ret_form, args)? {
8167 return Ok(());
8168 }
8169 let cell = self
8170 .user_props
8171 .get(&prop_id)
8172 .cloned()
8173 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
8174 if let Some(composed) = self.compose_user_prop_tail(prop_id, &cell, &elm[1..]) {
8175 self.exec_command(composed, al_id, ret_form, args)?;
8176 return Ok(());
8177 }
8178 if cell.form == self.cfg.fm_str && elm.len() == 2 {
8179 self.call_prop_eval_str_op(&cell.str_value, elm[1], args, al_id)?;
8180 return Ok(());
8181 }
8182 self.push_default_for_ret(ret_form);
8183 return Ok(());
8184 }
8185
8186 match owner {
8187 elm_code::ELM_OWNER_FORM => {
8188 if let Some(synthetic) = self.legacy_global_world_stage_chain(&elm, 1) {
8189 self.exec_command(synthetic, al_id, ret_form, args)?;
8190 self.sync_legacy_global_worlds_from_front();
8191 return Ok(());
8192 }
8193 if self.dispatch_global_indexed_list_command_direct(&elm, al_id, ret_form, args)? {
8194 return Ok(());
8195 }
8196 if let Some(synthetic) = self.try_compact_object_chain(&elm, true) {
8197 vm_trace!(
8198 self,
8199 None,
8200 format!(
8201 "exec_command compact-object elm={:?} synthetic={:?} al_id={} ret_form={} args={:?}",
8202 elm, synthetic, al_id, ret_form, args
8203 ),
8204 );
8205 if self.sg_mwnd_object_trace_enabled()
8206 && (Self::sg_mwnd_chain_interesting(&elm)
8207 || Self::sg_mwnd_chain_interesting(&synthetic))
8208 {
8209 self.sg_mwnd_object_trace_emit(format_args!(
8210 "exec_command compact elm={:?} synthetic={:?} al_id={} ret_form={} args={:?} current_chain={:?} current_stage_object={:?}",
8211 elm,
8212 synthetic,
8213 al_id,
8214 ret_form,
8215 args,
8216 self.ctx.globals.current_object_chain,
8217 self.ctx.globals.current_stage_object
8218 ));
8219 }
8220 self.ctx.vm_call = Some(runtime::VmCallMeta {
8221 element: synthetic.clone(),
8222 al_id: al_id as i64,
8223 ret_form: ret_form as i64,
8224 });
8225 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32) as i32;
8226 let op_id = if synthetic.len() >= 2 {
8227 synthetic[1]
8228 } else {
8229 al_id
8230 };
8231 self.sg_omv_trace_command(
8232 "compact", &synthetic, form_id, op_id, al_id, ret_form, args,
8233 );
8234 if !runtime::dispatch_form_code(&mut self.ctx, form_id as u32, args)? {
8235 self.ctx.vm_call = None;
8236 bail!(
8237 "unhandled compact object command chain {:?} -> {:?}",
8238 elm,
8239 synthetic
8240 );
8241 }
8242 self.ctx.vm_call = None;
8243 self.update_compact_context_from_object_dispatch_chain(&synthetic);
8244 self.drain_pending_frame_action_finishes()?;
8245 if ret_form != self.cfg.fm_void {
8246 if !self.ctx.stack.is_empty() {
8247 self.take_ctx_return(ret_form)?;
8248 return Ok(());
8249 }
8250 if self.ctx.wait_poll() {
8251 if let Some(frame) = self.call_stack.last_mut() {
8252 frame.delayed_ret_form = Some(ret_form);
8253 } else {
8254 self.delayed_ret_form = Some(ret_form);
8255 }
8256 return Ok(());
8257 }
8258 }
8259 self.take_ctx_return(ret_form)?;
8260 return Ok(());
8261 }
8262
8263 let form_id = self.canonical_runtime_form_id(raw_head as u32) as i32;
8264 if self.exec_builtin_global_control(form_id, ret_form, args)? {
8265 if ret_form != self.cfg.fm_void {
8266 self.take_ctx_return(ret_form)?;
8267 } else {
8268 self.ctx.stack.clear();
8269 }
8270 return Ok(());
8271 }
8272 if self.exec_syscom_save_value_intlistref(&elm, form_id, ret_form, args)? {
8273 return Ok(());
8274 }
8275 if self.exec_mouse_get_pos(&elm, form_id, args)? {
8276 return Ok(());
8277 }
8278 if self.exec_builtin_scene_form(&elm, form_id, al_id, ret_form, args)? {
8279 return Ok(());
8280 }
8281
8282 let op_id = if elm.len() >= 2 { elm[1] } else { al_id };
8283 self.ctx.vm_call = Some(runtime::VmCallMeta {
8284 element: self.ctx.int_vec_pool.take_copy(elm),
8285 al_id: al_id as i64,
8286 ret_form: ret_form as i64,
8287 });
8288
8289 if self.vm_trace_config.commands_enabled {
8290 let elm_tail = elm
8291 .iter()
8292 .map(|v| v.to_string())
8293 .collect::<Vec<_>>()
8294 .join(",");
8295 let args_dbg = args
8296 .iter()
8297 .map(|v| format!("{v:?}"))
8298 .collect::<Vec<_>>()
8299 .join(", ");
8300 eprintln!(
8301 "[vm form cmd] form={} op={} argc={} ret_form={} al_id={} elm=[{}] args=[{}]",
8302 form_id,
8303 op_id,
8304 args.len(),
8305 ret_form,
8306 al_id,
8307 elm_tail,
8308 args_dbg
8309 );
8310 }
8311
8312 self.sg_omv_trace_command("dispatch", &elm, form_id, op_id, al_id, ret_form, args);
8313
8314 let handled = runtime::dispatch_form_code(&mut self.ctx, form_id as u32, args);
8315 if let Some(call) = self.ctx.vm_call.take() {
8316 self.ctx.int_vec_pool.give(call.element);
8317 }
8318 if !handled? {
8319 bail!("unhandled form command chain {:?}", elm);
8320 }
8321 self.drain_pending_frame_action_finishes()?;
8322 }
8323 elm_code::ELM_OWNER_USER_CMD | elm_code::ELM_OWNER_CALL_CMD => {
8324 if self.vm_trace_config.commands_enabled {
8325 let cmd_no = elm_code::code(raw_head);
8326 let elm_tail = elm
8327 .iter()
8328 .map(|v| v.to_string())
8329 .collect::<Vec<_>>()
8330 .join(",");
8331 let args_dbg = args
8332 .iter()
8333 .map(|v| format!("{v:?}"))
8334 .collect::<Vec<_>>()
8335 .join(", ");
8336 eprintln!(
8337 "[vm owner cmd] owner={} cmd_no={} argc={} ret_form={} al_id={} elm=[{}] args=[{}]",
8338 owner,
8339 cmd_no,
8340 args.len(),
8341 ret_form,
8342 al_id,
8343 elm_tail,
8344 args_dbg
8345 );
8346 }
8347
8348 if !self.dispatch_owner_command(owner, raw_head, ret_form, args)? {
8349 bail!("unhandled owner command chain {:?}", elm);
8350 }
8351 if owner == elm_code::ELM_OWNER_USER_CMD {
8352 return Ok(());
8356 }
8357 }
8358 _ => {
8359 bail!("unsupported command owner {} for element {:?}", owner, elm);
8360 }
8361 }
8362
8363 if ret_form != self.cfg.fm_void {
8364 if !self.ctx.stack.is_empty() {
8365 self.take_ctx_return(ret_form)?;
8366 return Ok(());
8367 }
8368 if self.ctx.wait_poll() {
8369 if let Some(frame) = self.call_stack.last_mut() {
8370 frame.delayed_ret_form = Some(ret_form);
8371 } else {
8372 self.delayed_ret_form = Some(ret_form);
8373 }
8374 return Ok(());
8375 }
8376 }
8377
8378 self.take_ctx_return(ret_form)?;
8379 Ok(())
8380 }
8381
8382 fn save_kind_to_original(kind: RuntimeSaveKind) -> Option<crate::original_save::SaveKind> {
8383 match kind {
8384 RuntimeSaveKind::Normal => Some(crate::original_save::SaveKind::Normal),
8385 RuntimeSaveKind::Quick => Some(crate::original_save::SaveKind::Quick),
8386 RuntimeSaveKind::End => Some(crate::original_save::SaveKind::End),
8387 RuntimeSaveKind::Inner => None,
8388 }
8389 }
8390
8391 fn configured_runtime_save_count(&self, quick: bool) -> usize {
8392 let keys: [&str; 2] = if quick {
8393 ["#QUICK_SAVE.CNT", "QUICK_SAVE.CNT"]
8394 } else {
8395 ["#SAVE.CNT", "SAVE.CNT"]
8396 };
8397 let default_count = if quick { 3 } else { 10 };
8398 self.ctx
8399 .tables
8400 .gameexe
8401 .as_ref()
8402 .and_then(|cfg| keys.iter().find_map(|key| cfg.get_usize(key)))
8403 .unwrap_or(default_count)
8404 .min(10000)
8405 }
8406
8407 fn runtime_save_file_path(
8408 &self,
8409 kind: RuntimeSaveKind,
8410 index: usize,
8411 ) -> Option<std::path::PathBuf> {
8412 let save_kind = Self::save_kind_to_original(kind)?;
8413 let save_cnt = self.configured_runtime_save_count(false);
8414 let quick_cnt = self.configured_runtime_save_count(true);
8415 Some(crate::original_save::save_file_path_with_counts(
8416 &self.ctx.project_dir,
8417 save_cnt,
8418 quick_cnt,
8419 save_kind,
8420 index,
8421 ))
8422 }
8423
8424 fn stamp_slot_with_local_time(slot: &mut crate::runtime::globals::SaveSlotState) {
8425 let now = crate::platform_time::local_time_fields();
8426 slot.exist = true;
8427 slot.year = now.year as i64;
8428 slot.month = now.month as i64;
8429 slot.day = now.day as i64;
8430 slot.weekday = now.weekday_sunday0 as i64;
8432 slot.hour = now.hour as i64;
8433 slot.minute = now.minute as i64;
8434 slot.second = now.second as i64;
8435 slot.millisecond = now.millisecond as i64;
8436 }
8437
8438 fn ensure_runtime_slot_for_save(
8447 &mut self,
8448 req: RuntimeSaveRequest,
8449 ) -> crate::runtime::globals::SaveSlotState {
8450 let mut slot = crate::runtime::globals::SaveSlotState::default();
8451 Self::stamp_slot_with_local_time(&mut slot);
8452 if let Some(snapshot) = self.ctx.local_save_snapshot.as_ref() {
8453 slot.title = snapshot.save_scene_title.clone();
8454 slot.message = snapshot.save_msg.clone();
8455 slot.full_message = if snapshot.save_full_msg.is_empty() {
8456 snapshot.save_msg.clone()
8457 } else {
8458 snapshot.save_full_msg.clone()
8459 };
8460 slot.append_dir = snapshot.append_dir.clone();
8461 slot.append_name = snapshot.append_name.clone();
8462 } else {
8463 slot.title = self.ctx.globals.syscom.current_save_scene_title.clone();
8466 slot.message = self.ctx.globals.syscom.current_save_message.clone();
8467 slot.full_message = if self.ctx.globals.syscom.current_save_full_message.is_empty() {
8468 self.ctx.globals.syscom.current_save_message.clone()
8469 } else {
8470 self.ctx.globals.syscom.current_save_full_message.clone()
8471 };
8472 slot.append_dir = self.ctx.globals.append_dir.clone();
8473 slot.append_name = self.ctx.globals.append_name.clone();
8474 }
8475
8476 match req.kind {
8477 RuntimeSaveKind::Normal => {
8478 if self.ctx.globals.syscom.save_slots.len() <= req.index {
8479 self.ctx
8480 .globals
8481 .syscom
8482 .save_slots
8483 .resize_with(req.index + 1, Default::default);
8484 }
8485 self.ctx.globals.syscom.save_slots[req.index] = slot.clone();
8486 }
8487 RuntimeSaveKind::Quick => {
8488 if self.ctx.globals.syscom.quick_save_slots.len() <= req.index {
8489 self.ctx
8490 .globals
8491 .syscom
8492 .quick_save_slots
8493 .resize_with(req.index + 1, Default::default);
8494 }
8495 self.ctx.globals.syscom.quick_save_slots[req.index] = slot.clone();
8496 }
8497 RuntimeSaveKind::End | RuntimeSaveKind::Inner => {}
8498 }
8499 slot
8500 }
8501
8502 fn local_flag_count(&self) -> usize {
8503 if let Some(configured) = self.ctx.tables.gameexe.as_ref().and_then(|cfg| {
8504 cfg.get_usize("#FLAG.CNT")
8505 .or_else(|| cfg.get_usize("FLAG.CNT"))
8506 }) {
8507 return configured.min(10000);
8508 }
8509
8510 [
8511 codes::ELM_GLOBAL_A,
8512 codes::ELM_GLOBAL_B,
8513 codes::ELM_GLOBAL_C,
8514 codes::ELM_GLOBAL_D,
8515 codes::ELM_GLOBAL_E,
8516 codes::ELM_GLOBAL_F,
8517 codes::ELM_GLOBAL_X,
8518 ]
8519 .into_iter()
8520 .map(|elm| self.int_list_by_element(elm).len())
8521 .chain(std::iter::once(
8522 self.str_list_by_element(codes::ELM_GLOBAL_S).len(),
8523 ))
8524 .max()
8525 .unwrap_or(1000)
8526 .clamp(1000, 10000)
8527 }
8528
8529 fn mwnd_waku_btn_count(&self) -> usize {
8530 self.ctx
8531 .tables
8532 .gameexe
8533 .as_ref()
8534 .and_then(|cfg| {
8535 cfg.get_usize("#WAKU.BTN.CNT")
8536 .or_else(|| cfg.get_usize("WAKU.BTN.CNT"))
8537 })
8538 .unwrap_or(8)
8539 .min(256)
8540 }
8541
8542 fn int_list_by_element(&self, elm: i32) -> &[i64] {
8543 self.ctx
8544 .globals
8545 .int_lists
8546 .get(&(elm as u32))
8547 .map(Vec::as_slice)
8548 .unwrap_or(&[])
8549 }
8550
8551 fn str_list_by_element(&self, elm: i32) -> &[String] {
8552 self.ctx
8553 .globals
8554 .str_lists
8555 .get(&(elm as u32))
8556 .map(Vec::as_slice)
8557 .unwrap_or(&[])
8558 }
8559
8560 fn build_cpp_local_data_pod(&self) -> Vec<u8> {
8561 let mut out = Vec::with_capacity(356);
8562 let script = &self.ctx.globals.script;
8563 let syscom = &self.ctx.globals.syscom;
8564 let push_i32 = |out: &mut Vec<u8>, v: i64| out.extend_from_slice(&(v as i32).to_le_bytes());
8565 let push_bool = |out: &mut Vec<u8>, v: bool| out.push(if v { 1 } else { 0 });
8566
8567 push_i32(&mut out, script.cur_koe_no);
8568 push_i32(&mut out, script.cur_chr_no);
8569 push_i32(&mut out, script.cur_read_flag_scn_no);
8570 push_i32(&mut out, script.cur_read_flag_flag_no);
8571 push_i32(&mut out, script.cursor_no);
8572
8573 push_bool(&mut out, syscom.syscom_menu_disable);
8574 push_bool(&mut out, script.hide_mwnd_disable);
8575 push_bool(&mut out, script.msg_back_disable);
8576 push_bool(&mut out, script.shortcut_disable);
8577
8578 push_bool(&mut out, script.skip_disable);
8579 push_bool(&mut out, script.ctrl_disable);
8580 push_bool(&mut out, script.not_stop_skip_by_click);
8581 push_bool(&mut out, script.not_skip_msg_by_click);
8582 push_bool(&mut out, script.skip_unread_message);
8583 push_bool(&mut out, script.auto_mode_flag);
8584 while out.len() % 4 != 0 {
8585 out.push(0);
8586 }
8587 push_i32(&mut out, script.auto_mode_moji_wait);
8588 push_i32(&mut out, script.auto_mode_min_wait);
8589 push_i32(&mut out, script.auto_mode_moji_cnt);
8590 push_i32(&mut out, script.mouse_cursor_hide_onoff);
8591 push_i32(&mut out, script.mouse_cursor_hide_time);
8592 push_i32(&mut out, script.msg_back_save_cntr);
8593
8594 push_i32(&mut out, script.msg_speed);
8595 push_bool(&mut out, script.msg_nowait);
8596 push_bool(&mut out, script.async_msg_mode);
8597 push_bool(&mut out, script.async_msg_mode_once);
8598 push_bool(&mut out, script.multi_msg_mode);
8599 push_bool(&mut out, script.skip_trigger);
8600 push_bool(&mut out, script.koe_dont_stop_on_flag);
8601 push_bool(&mut out, script.koe_dont_stop_off_flag);
8602
8603 push_bool(&mut out, syscom.mwnd_btn_disable_all);
8604 push_bool(&mut out, syscom.mwnd_btn_touch_disable);
8605 push_bool(&mut out, script.mwnd_anime_on_flag);
8606 push_bool(&mut out, script.mwnd_anime_off_flag);
8607 push_bool(&mut out, script.mwnd_disp_off_flag);
8608
8609 push_bool(&mut out, script.msg_back_off);
8610 push_bool(&mut out, script.msg_back_disp_off);
8611 while out.len() % 4 != 0 {
8612 out.push(0);
8613 }
8614 push_i32(&mut out, script.font_bold);
8615 push_i32(&mut out, script.font_shadow);
8616
8617 push_bool(&mut out, script.cursor_disp_off);
8618 push_bool(&mut out, script.cursor_move_by_key_disable);
8619 for key in 0u16..=255u16 {
8620 push_bool(&mut out, script.key_disable.contains(&(key as u8)));
8621 }
8622
8623 push_bool(&mut out, script.quake_stop_flag);
8624 push_bool(&mut out, script.emote_mouth_stop_flag);
8625 push_bool(&mut out, self.ctx.globals.cg_table_off);
8626 push_bool(&mut out, script.bgmfade_flag);
8627 push_bool(&mut out, script.dont_set_save_point);
8628 push_bool(&mut out, script.ignore_r_flag);
8629 push_bool(&mut out, script.wait_display_vsync_off_flag);
8630
8631 push_bool(&mut out, script.time_stop_flag);
8632 push_bool(&mut out, script.counter_time_stop_flag);
8633 push_bool(&mut out, script.frame_action_time_stop_flag);
8634 push_bool(&mut out, script.stage_time_stop_flag);
8635 while out.len() % 4 != 0 {
8636 out.push(0);
8637 }
8638 debug_assert_eq!(out.len(), 356);
8639 out
8640 }
8641
8642 fn read_cpp_local_data_pod(
8643 &mut self,
8644 rd: &mut crate::original_save::OriginalStreamReader<'_>,
8645 has_font: bool,
8646 ) -> Result<()> {
8647 if rd.layout.uses_early_records() {
8648 return self.read_early_local_data_pod(rd);
8649 }
8650 let script = &mut self.ctx.globals.script;
8651
8652 script.cur_koe_no = rd.i32()? as i64;
8653 script.cur_chr_no = rd.i32()? as i64;
8654 script.cur_read_flag_scn_no = rd.i32()? as i64;
8655 script.cur_read_flag_flag_no = rd.i32()? as i64;
8656 script.cursor_no = rd.i32()? as i64;
8657
8658 self.ctx.globals.syscom.syscom_menu_disable = rd.bool()?;
8659 script.hide_mwnd_disable = rd.bool()?;
8660 script.msg_back_disable = rd.bool()?;
8661 script.shortcut_disable = rd.bool()?;
8662
8663 script.skip_disable = rd.bool()?;
8664 script.ctrl_disable = rd.bool()?;
8665 script.not_stop_skip_by_click = rd.bool()?;
8666 script.not_skip_msg_by_click = rd.bool()?;
8667 script.skip_unread_message = rd.bool()?;
8668 script.auto_mode_flag = rd.bool()?;
8669 rd.skip(2)?;
8670 script.auto_mode_moji_wait = rd.i32()? as i64;
8671 script.auto_mode_min_wait = rd.i32()? as i64;
8672 script.auto_mode_moji_cnt = rd.i32()? as i64;
8673 script.mouse_cursor_hide_onoff = rd.i32()? as i64;
8674 script.mouse_cursor_hide_time = rd.i32()? as i64;
8675 script.msg_back_save_cntr = rd.i32()? as i64;
8676
8677 script.msg_speed = rd.i32()? as i64;
8678 script.msg_nowait = rd.bool()?;
8679 script.async_msg_mode = rd.bool()?;
8680 script.async_msg_mode_once = rd.bool()?;
8681 script.multi_msg_mode = rd.bool()?;
8682 script.skip_trigger = rd.bool()?;
8683 script.koe_dont_stop_on_flag = rd.bool()?;
8684 script.koe_dont_stop_off_flag = rd.bool()?;
8685
8686 self.ctx.globals.syscom.mwnd_btn_disable_all = rd.bool()?;
8687 self.ctx.globals.syscom.mwnd_btn_touch_disable = rd.bool()?;
8688 script.mwnd_anime_on_flag = rd.bool()?;
8689 script.mwnd_anime_off_flag = rd.bool()?;
8690 script.mwnd_disp_off_flag = rd.bool()?;
8691
8692 script.msg_back_off = rd.bool()?;
8693 script.msg_back_disp_off = rd.bool()?;
8694 if has_font {
8695 rd.skip(2)?;
8696 script.font_bold = rd.i32()? as i64;
8697 script.font_shadow = rd.i32()? as i64;
8698 } else {
8699 script.font_bold = -1;
8700 script.font_shadow = -1;
8701 }
8702
8703 script.cursor_disp_off = rd.bool()?;
8704 script.cursor_runtime_visible = !script.cursor_disp_off;
8705 script.cursor_move_by_key_disable = rd.bool()?;
8706 script.key_disable.clear();
8707 for key in 0u16..=255u16 {
8708 if rd.bool()? {
8709 script.key_disable.insert(key as u8);
8710 }
8711 }
8712
8713 script.quake_stop_flag = rd.bool()?;
8714 script.emote_mouth_stop_flag = rd.bool()?;
8715 self.ctx.globals.cg_table_off = rd.bool()?;
8716 script.bgmfade_flag = rd.bool()?;
8717 script.dont_set_save_point = rd.bool()?;
8718 script.ignore_r_flag = rd.bool()?;
8719 script.wait_display_vsync_off_flag = rd.bool()?;
8720
8721 script.time_stop_flag = rd.bool()?;
8722 script.counter_time_stop_flag = rd.bool()?;
8723 script.frame_action_time_stop_flag = rd.bool()?;
8724 script.stage_time_stop_flag = rd.bool()?;
8725 rd.skip(if has_font { 3 } else { 1 })?;
8726
8727 self.ctx.globals.syscom.replay_koe = if script.cur_koe_no >= 0 {
8728 Some((script.cur_koe_no, script.cur_chr_no))
8729 } else {
8730 None
8731 };
8732 Ok(())
8733 }
8734
8735 fn write_cpp_syscom_menu(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8736 let base = w.position();
8737 let s = &self.ctx.globals.syscom;
8738 let push_ex =
8739 |w: &mut crate::original_save::OriginalStreamWriter, exist: bool, enable: bool| {
8740 w.push_bool(exist);
8741 w.push_bool(enable);
8742 };
8743 push_ex(w, s.read_skip.exist, s.read_skip.enable);
8744 push_ex(w, s.unread_skip.exist, s.unread_skip.enable);
8745 push_ex(w, s.auto_skip.exist, s.auto_skip.enable);
8746 push_ex(w, s.auto_mode.exist, s.auto_mode.enable);
8747 push_ex(w, s.hide_mwnd.exist, s.hide_mwnd.enable);
8748 push_ex(w, s.msg_back.exist, s.msg_back.enable);
8749 push_ex(w, s.save_feature.exist, s.save_feature.enable);
8750 push_ex(w, s.load_feature.exist, s.load_feature.enable);
8751 push_ex(w, s.return_to_sel.exist, s.return_to_sel.enable);
8752 push_ex(w, s.config_feature.exist, s.config_feature.enable);
8753 push_ex(w, s.manual_feature.exist, s.manual_feature.enable);
8754 push_ex(w, s.version_feature.exist, s.version_feature.enable);
8755 push_ex(w, s.return_to_menu.exist, s.return_to_menu.enable);
8756 push_ex(w, s.end_game.exist, s.end_game.enable);
8757 push_ex(w, s.cancel_feature.exist, s.cancel_feature.enable);
8758 for i in 0..4 {
8759 let sw = s.local_extra_switches.get(i).copied().unwrap_or(if i == 0 {
8760 s.local_extra_switch
8761 } else {
8762 runtime::globals::ToggleFeatureState::default()
8763 });
8764 w.push_bool(sw.exist);
8765 w.push_bool(sw.enable);
8766 w.push_bool(sw.onoff);
8767 }
8768 while !(w.position() - base).is_multiple_of(4) {
8769 w.push_bool(false);
8770 }
8771 for i in 0..4 {
8772 let mode = s.local_extra_modes.get(i).copied().unwrap_or(if i == 0 {
8773 s.local_extra_mode
8774 } else {
8775 runtime::globals::ValueFeatureState::default()
8776 });
8777 w.push_bool(mode.exist);
8778 w.push_bool(mode.enable);
8779 w.push_padding(2);
8780 w.push_i32(mode.value as i32);
8781 }
8782 debug_assert_eq!(w.position() - base, 76);
8783 }
8784
8785 fn read_cpp_syscom_menu(
8786 &mut self,
8787 rd: &mut crate::original_save::OriginalStreamReader<'_>,
8788 ) -> Result<()> {
8789 fn read_ex(
8790 rd: &mut crate::original_save::OriginalStreamReader<'_>,
8791 state: &mut runtime::globals::ToggleFeatureState,
8792 ) -> Result<()> {
8793 state.exist = rd.bool()?;
8794 state.enable = rd.bool()?;
8795 Ok(())
8796 }
8797
8798 let s = &mut self.ctx.globals.syscom;
8799 read_ex(rd, &mut s.read_skip)?;
8800 read_ex(rd, &mut s.unread_skip)?;
8801 read_ex(rd, &mut s.auto_skip)?;
8802 read_ex(rd, &mut s.auto_mode)?;
8803 read_ex(rd, &mut s.hide_mwnd)?;
8804 read_ex(rd, &mut s.msg_back)?;
8805 read_ex(rd, &mut s.save_feature)?;
8806 read_ex(rd, &mut s.load_feature)?;
8807 read_ex(rd, &mut s.return_to_sel)?;
8808 read_ex(rd, &mut s.config_feature)?;
8809 read_ex(rd, &mut s.manual_feature)?;
8810 read_ex(rd, &mut s.version_feature)?;
8811 read_ex(rd, &mut s.return_to_menu)?;
8812 read_ex(rd, &mut s.end_game)?;
8813 read_ex(rd, &mut s.cancel_feature)?;
8814
8815 for sw in &mut s.local_extra_switches {
8816 sw.exist = rd.bool()?;
8817 sw.enable = rd.bool()?;
8818 sw.onoff = rd.bool()?;
8819 }
8820 rd.skip(2)?;
8821 for mode in &mut s.local_extra_modes {
8822 mode.exist = rd.bool()?;
8823 mode.enable = rd.bool()?;
8824 rd.skip(2)?;
8825 mode.value = rd.i32()? as i64;
8826 }
8827 s.local_extra_switch = s.local_extra_switches[0];
8828 s.local_extra_mode = s.local_extra_modes[0];
8829 Ok(())
8830 }
8831
8832 fn write_empty_counter_param(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8833 w.push_bool(false);
8834 w.push_bool(false);
8835 w.push_bool(false);
8836 w.push_bool(false);
8837 w.push_i32(0);
8838 w.push_i32(0);
8839 w.push_i32(0);
8840 w.push_i32(0);
8841 }
8842
8843 fn write_empty_frame_action(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8844 w.push_i32(0);
8845 w.push_str("");
8846 w.push_str("");
8847 w.push_i32(0);
8848 self.write_empty_counter_param(w);
8849 }
8850
8851 fn write_empty_btn_select(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8852 w.push_i32(0);
8853 w.push_padding(112);
8854 w.push_bool(false);
8855 w.push_bool(false);
8856 w.push_bool(false);
8857 w.push_bool(false);
8858 w.push_bool(false);
8859 w.push_bool(false);
8860 w.push_str("");
8861 w.push_i32(0);
8862 w.push_i32(0);
8863 }
8864
8865 fn write_empty_stage(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8866 w.push_empty_fixed_array();
8867 w.push_empty_fixed_array();
8868 w.push_empty_fixed_array();
8869 self.write_empty_btn_select(w);
8870 w.push_empty_fixed_array();
8871 w.push_empty_fixed_array();
8872 w.push_empty_fixed_array();
8873 }
8874
8875 fn write_empty_screen(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8876 w.push_empty_fixed_array();
8877 w.push_padding(16);
8878 w.push_empty_fixed_array();
8879 }
8880
8881 fn write_empty_sound(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8882 w.push_str("");
8883 w.push_i32(0);
8884 w.push_i32(0);
8885 w.push_bool(false);
8886 w.push_bool(false);
8887 w.push_i32(0);
8888 w.push_i32(0);
8889 w.push_empty_fixed_array();
8890 w.push_i32(0);
8891 w.push_str("");
8892 }
8893
8894 fn write_empty_msg_back(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8895 w.push_i32(0);
8896 w.push_i32(0);
8897 w.push_i32(0);
8898 w.push_i32(0);
8899 w.push_bool(false);
8900 }
8901
8902 fn write_cpp_prop(
8903 &self,
8904 w: &mut crate::original_save::OriginalStreamWriter,
8905 prop_id: i32,
8906 cell: &UserPropCell,
8907 ) {
8908 w.push_i32(prop_id);
8909 w.push_i32(cell.form);
8910 w.push_i32(cell.int_value);
8911 w.push_str(&cell.str_value);
8912 w.push_element(&cell.element);
8913 w.push_extend_items(&cell.list_items, |w, item| self.write_cpp_prop(w, 0, item));
8914 w.push_i32(cell.list_items.len() as i32);
8915 if cell.form == self.cfg.fm_intlist {
8916 let vals: Vec<i64> = cell.int_list.iter().map(|v| *v as i64).collect();
8917 w.push_extend_i32_list(&vals);
8918 } else if cell.form == self.cfg.fm_strlist {
8919 w.push_extend_str_list(&cell.str_list);
8920 }
8921 }
8922
8923 fn read_cpp_prop(
8924 &self,
8925 rd: &mut crate::original_save::OriginalStreamReader<'_>,
8926 ) -> Result<(i32, UserPropCell)> {
8927 let prop_id = rd.i32()?;
8928 let form = rd.i32()?;
8929 let int_value = rd.i32()?;
8930 let str_value = rd.string()?;
8931 let element = rd.element()?;
8932 let list_items = rd.extend_items(|rd| {
8933 let (_id, cell) = self.read_cpp_prop(rd)?;
8934 Ok(cell)
8935 })?;
8936 let _exp_cnt = rd.i32()?;
8937 let mut cell = UserPropCell::new(form, element);
8938 cell.int_value = int_value;
8939 cell.str_value = str_value;
8940 cell.list_items = list_items;
8941 if form == self.cfg.fm_intlist {
8942 cell.int_list = rd
8943 .extend_i32_list()?
8944 .into_iter()
8945 .map(|v| v as i32)
8946 .collect();
8947 } else if form == self.cfg.fm_strlist {
8948 cell.str_list = rd.extend_items(|rd| rd.string())?;
8949 }
8950 Ok((prop_id, cell))
8951 }
8952
8953 fn write_cpp_inc_prop_list(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8954 let shared = self.shared_user_prop_count();
8955 let props: Vec<(i32, UserPropCell)> = (0..shared)
8956 .map(|idx| {
8957 let prop_id = idx as u16;
8958 let cell = self
8959 .user_props
8960 .get(&prop_id)
8961 .cloned()
8962 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
8963 (idx as i32, cell)
8964 })
8965 .collect();
8966 w.push_fixed_items(&props, |w, (id, cell)| self.write_cpp_prop(w, *id, cell));
8967 }
8968
8969 fn read_cpp_inc_prop_list(
8970 &mut self,
8971 rd: &mut crate::original_save::OriginalStreamReader<'_>,
8972 ) -> Result<()> {
8973 let props = rd.fixed_items(|rd| self.read_cpp_prop(rd))?;
8974 for (idx, (_stored_id, cell)) in props.into_iter().enumerate() {
8975 self.user_props.insert(idx as u16, cell);
8976 }
8977 Ok(())
8978 }
8979
8980 fn write_cpp_current_scene_prop_lists(
8981 &self,
8982 w: &mut crate::original_save::OriginalStreamWriter,
8983 ) {
8984 let shared = self.shared_user_prop_count();
8985 let mut props: Vec<(i32, UserPropCell)> = Vec::new();
8986 let scene_prop_cnt = self.stream.header.scn_prop_cnt.max(0) as usize;
8987 for idx in 0..scene_prop_cnt {
8988 let prop_id = (shared + idx) as u16;
8989 if let Some(cell) = self.user_props.get(&prop_id).cloned() {
8990 props.push((idx as i32, cell));
8991 } else if let Some((_, _)) = self.user_prop_decl(prop_id) {
8992 props.push((idx as i32, self.default_user_prop_cell(prop_id)));
8993 }
8994 }
8995 if props.is_empty() {
8996 w.push_i32(0);
8997 return;
8998 }
8999 w.push_i32(1);
9000 w.push_str(self.current_scene_name.as_deref().unwrap_or(""));
9001 w.push_fixed_items(&props, |w, (id, cell)| self.write_cpp_prop(w, *id, cell));
9002 }
9003
9004 fn read_cpp_scene_prop_lists(
9005 &mut self,
9006 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9007 current_scene_name: &str,
9008 ) -> Result<()> {
9009 let shared = self.shared_user_prop_count();
9010 if rd.layout.is_indexed() {
9011 let lists = rd.fixed_items(|rd| rd.fixed_items(|rd| self.read_cpp_prop(rd)))?;
9012 let scene_no = self.ctx.lookup_scene_no(current_scene_name).unwrap_or(-1);
9013 if let Some(props) = lists.into_iter().nth(scene_no as usize) {
9014 for (idx, (_, cell)) in props.into_iter().enumerate() {
9015 self.user_props.insert((shared + idx) as u16, cell);
9016 }
9017 }
9018 return Ok(());
9019 }
9020 let scene_prop_cnt = rd.i32()?.max(0) as usize;
9021 for _ in 0..scene_prop_cnt {
9022 let scene_name = rd.string()?;
9023 let props = rd.fixed_items(|rd| self.read_cpp_prop(rd))?;
9024 if scene_name == current_scene_name {
9025 for (idx, (_stored_id, cell)) in props.into_iter().enumerate() {
9026 self.user_props.insert((shared + idx) as u16, cell);
9027 }
9028 }
9029 }
9030 Ok(())
9031 }
9032
9033 fn write_cpp_call_prop(
9034 &self,
9035 w: &mut crate::original_save::OriginalStreamWriter,
9036 prop: &CallProp,
9037 ) {
9038 w.push_i32(prop.scn_no);
9039 w.push_i32(prop.prop_id);
9040 let mut cell = UserPropCell::new(prop.form, prop.element.clone());
9041 match &prop.value {
9042 CallPropValue::Int(v) => cell.int_value = *v,
9043 CallPropValue::Str(v) => cell.str_value = v.clone(),
9044 CallPropValue::Element(v) => cell.element = v.clone(),
9045 CallPropValue::IntList(v) => cell.int_list = v.clone(),
9046 CallPropValue::StrList(v) => cell.str_list = v.clone(),
9047 }
9048 self.write_cpp_prop(w, prop.prop_id, &cell);
9049 }
9050
9051 fn read_cpp_call_prop(
9052 &self,
9053 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9054 ) -> Result<CallProp> {
9055 let scn_no = rd.i32()?;
9056 let declared_prop_id = rd.i32()?;
9057 let (_stored_id, cell) = self.read_cpp_prop(rd)?;
9058 let value = if cell.form == self.cfg.fm_int {
9059 CallPropValue::Int(cell.int_value)
9060 } else if cell.form == self.cfg.fm_str {
9061 CallPropValue::Str(cell.str_value.clone())
9062 } else if cell.form == self.cfg.fm_intlist {
9063 CallPropValue::IntList(cell.int_list.clone())
9064 } else if cell.form == self.cfg.fm_strlist {
9065 CallPropValue::StrList(cell.str_list.clone())
9066 } else {
9067 CallPropValue::Element(cell.element.clone())
9068 };
9069 Ok(CallProp {
9070 scn_no,
9071 prop_id: declared_prop_id,
9072 form: cell.form,
9073 decl_size: cell
9074 .int_list
9075 .len()
9076 .max(cell.str_list.len())
9077 .max(cell.list_items.len()),
9078 element: cell.element,
9079 value,
9080 })
9081 }
9082
9083 fn write_cpp_call_frame(
9084 &self,
9085 w: &mut crate::original_save::OriginalStreamWriter,
9086 frame: &CallFrame,
9087 ) {
9088 let l: Vec<i64> = frame.int_args.iter().map(|v| *v as i64).collect();
9089 w.push_extend_i32_list(&l);
9090 w.push_extend_str_list(&frame.str_args);
9091 w.push_extend_items(&frame.user_props, |w, prop| {
9092 self.write_cpp_call_prop(w, prop)
9093 });
9094 w.push_i32(frame.call_type);
9095 w.push_i32(frame.ret_form);
9096 w.push_str(frame.return_scene_name.as_deref().unwrap_or(""));
9097 w.push_i32(frame.return_line_no);
9098 w.push_i32(frame.return_pc as i32);
9099 }
9100
9101 fn flattened_call_stack_for_save(&self) -> Vec<CallFrame> {
9102 self.call_stack.clone()
9106 }
9107
9108 fn read_cpp_call_frame(
9109 &self,
9110 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9111 ) -> Result<CallFrame> {
9112 let int_args: Vec<i32> = rd
9113 .extend_i32_list()?
9114 .into_iter()
9115 .map(|v| v as i32)
9116 .collect();
9117 let str_args: Vec<String> = rd.extend_items(|rd| rd.string())?;
9118 let user_props = rd.extend_items(|rd| self.read_cpp_call_prop(rd))?;
9119 let call_type = rd.i32()?;
9120 let ret_form = rd.i32()?;
9121 let (scene_name, line_no, return_pc) = if rd.layout.is_indexed() {
9122 let data = rd.len_bytes()?;
9123 anyhow::ensure!(
9124 data.len() == 12,
9125 "invalid indexed call save length {}",
9126 data.len()
9127 );
9128 let mut saved = crate::original_save::OriginalStreamReader::new(&data);
9129 let scene_no = saved.i32()?;
9130 let scene_name = self.indexed_scene_name(scene_no)?;
9131 (scene_name, saved.i32()?, saved.i32()?.max(0) as usize)
9132 } else {
9133 (rd.string()?, rd.i32()?, rd.i32()?.max(0) as usize)
9134 };
9135 Ok(CallFrame {
9136 call_type,
9137 return_pc,
9138 return_scene_no: None,
9139 return_scene_name: (!scene_name.is_empty()).then_some(scene_name),
9140 return_line_no: line_no,
9141 ret_form,
9142 return_override: None,
9143 excall_proc: false,
9144 frame_action_proc: false,
9147 arg_cnt: 0,
9148 delayed_ret_form: None,
9149 user_props,
9150 int_args,
9151 str_args,
9152 })
9153 }
9154
9155 fn save_i32(v: i64) -> i32 {
9156 v.clamp(i32::MIN as i64, i32::MAX as i64) as i32
9157 }
9158
9159 fn write_cpp_counter_param(
9160 &self,
9161 w: &mut crate::original_save::OriginalStreamWriter,
9162 c: &runtime::globals::Counter,
9163 ) {
9164 let (
9165 is_running,
9166 real_flag,
9167 frame_mode,
9168 frame_loop_flag,
9169 frame_start_value,
9170 frame_end_value,
9171 frame_time,
9172 cur_time,
9173 ) = c.save_parts();
9174 w.push_bool(is_running);
9175 w.push_bool(real_flag);
9176 w.push_bool(frame_mode);
9177 w.push_bool(frame_loop_flag);
9178 w.push_i32(Self::save_i32(frame_start_value));
9179 w.push_i32(Self::save_i32(frame_end_value));
9180 w.push_i32(Self::save_i32(frame_time));
9181 w.push_i32(Self::save_i32(cur_time));
9182 }
9183
9184 fn read_cpp_counter_param(
9185 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9186 ) -> Result<runtime::globals::Counter> {
9187 let is_running = rd.bool()?;
9188 let real_flag = rd.bool()?;
9189 let frame_mode = rd.bool()?;
9190 let frame_loop_flag = rd.bool()?;
9191 let frame_start_value = rd.i32()? as i64;
9192 let frame_end_value = rd.i32()? as i64;
9193 let frame_time = rd.i32()? as i64;
9194 let cur_time = rd.i32()? as i64;
9195 Ok(runtime::globals::Counter::from_save_parts(
9196 is_running,
9197 real_flag,
9198 frame_mode,
9199 frame_loop_flag,
9200 frame_start_value,
9201 frame_end_value,
9202 frame_time,
9203 cur_time,
9204 ))
9205 }
9206
9207 fn write_cpp_value_prop(w: &mut crate::original_save::OriginalStreamWriter, value: &Value) {
9208 use crate::runtime::forms::codes;
9209 w.push_i32(0);
9210 match value {
9211 Value::Str(s) => {
9212 w.push_i32(codes::FM_STR);
9213 w.push_i32(0);
9214 w.push_str(s);
9215 }
9216 Value::Int(v) => {
9217 w.push_i32(codes::FM_INT);
9218 w.push_i32(Self::save_i32(*v));
9219 w.push_str("");
9220 }
9221 _ => {
9222 w.push_i32(codes::FM_INT);
9223 w.push_i32(0);
9224 w.push_str("");
9225 }
9226 }
9227 w.push_empty_element();
9228 w.push_i32(0);
9229 w.push_i32(0);
9230 }
9231
9232 fn read_cpp_value_prop(
9233 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9234 ) -> Result<Value> {
9235 use crate::runtime::forms::codes;
9236 let _id = rd.i32()?;
9237 let form = rd.i32()?;
9238 let int_value = rd.i32()?;
9239 let str_value = rd.string()?;
9240 let _element = rd.element()?;
9241 let _exp_list: Vec<Value> = rd.extend_items(|rd| Self::read_cpp_value_prop(rd))?;
9242 let _exp_cnt = rd.i32()?;
9243 if form == codes::FM_INTLIST {
9244 let _ = rd.extend_i32_list()?;
9245 } else if form == codes::FM_STRLIST {
9246 let _ = rd.extend_items(|rd| rd.string())?;
9247 }
9248 if form == codes::FM_STR {
9249 Ok(Value::Str(str_value))
9250 } else {
9251 Ok(Value::Int(int_value as i64))
9252 }
9253 }
9254
9255 fn write_cpp_frame_action(
9256 &self,
9257 w: &mut crate::original_save::OriginalStreamWriter,
9258 fa: &runtime::globals::ObjectFrameActionState,
9259 ) {
9260 w.push_i32(Self::save_i32(fa.end_time));
9261 w.push_str(&fa.scn_name);
9262 w.push_str(&fa.cmd_name);
9263 w.push_extend_items(&fa.args, Self::write_cpp_value_prop);
9264 self.write_cpp_counter_param(w, &fa.counter);
9265 }
9266
9267 fn read_cpp_frame_action(
9268 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9269 ) -> Result<runtime::globals::ObjectFrameActionState> {
9270 let end_time = rd.i32()? as i64;
9271 let scn_name = rd.string()?;
9272 let cmd_name = rd.string()?;
9273 let args = rd.extend_items(|rd| Self::read_cpp_value_prop(rd))?;
9274 let counter = Self::read_cpp_counter_param(rd)?;
9275 Ok(runtime::globals::ObjectFrameActionState {
9276 scn_name,
9277 cmd_name,
9278 counter,
9279 end_time,
9280 real_time_flag: false,
9281 end_flag: false,
9282 args,
9283 })
9284 }
9285
9286 fn write_cpp_int_event_raw(
9287 w: &mut crate::original_save::OriginalStreamWriter,
9288 e: &runtime::int_event::IntEvent,
9289 ) {
9290 w.push_i32(e.def_value);
9291 w.push_i32(e.value);
9292 w.push_i32(e.cur_time);
9293 w.push_i32(e.end_time);
9294 w.push_i32(e.delay_time);
9295 w.push_i32(e.start_value);
9296 w.push_i32(e.cur_value);
9297 w.push_i32(e.end_value);
9298 w.push_i32(e.loop_type);
9299 w.push_i32(e.speed_type);
9300 w.push_i32(e.real_flag);
9301 }
9302
9303 fn read_cpp_int_event_raw(
9304 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9305 ) -> Result<runtime::int_event::IntEvent> {
9306 let def_value = rd.i32()?;
9307 Ok(runtime::int_event::IntEvent {
9308 def_value,
9309 value: rd.i32()?,
9310 cur_time: rd.i32()?,
9311 end_time: rd.i32()?,
9312 delay_time: rd.i32()?,
9313 start_value: rd.i32()?,
9314 cur_value: rd.i32()?,
9315 end_value: rd.i32()?,
9316 loop_type: rd.i32()?,
9317 speed_type: rd.i32()?,
9318 real_flag: rd.i32()?,
9319 })
9320 }
9321
9322 fn write_cpp_save_event(
9323 w: &mut crate::original_save::OriginalStreamWriter,
9324 e: &runtime::int_event::IntEvent,
9325 ) {
9326 w.push_i32(e.loop_type);
9327 if e.loop_type != -1 {
9328 Self::write_cpp_int_event_raw(w, e);
9329 } else {
9330 w.push_i32(e.value);
9331 }
9332 }
9333
9334 fn read_cpp_save_event(
9335 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9336 def_value: i32,
9337 ) -> Result<runtime::int_event::IntEvent> {
9338 let loop_type = rd.i32()?;
9339 if loop_type != -1 {
9340 let mut e = Self::read_cpp_int_event_raw(rd)?;
9341 e.loop_type = loop_type;
9342 Ok(e)
9343 } else {
9344 let mut e = runtime::int_event::IntEvent::new(def_value);
9345 e.loop_type = -1;
9346 e.value = rd.i32()?;
9347 e.cur_value = e.value;
9348 Ok(e)
9349 }
9350 }
9351
9352 fn write_cpp_int_event_extend_list(
9353 &self,
9354 w: &mut crate::original_save::OriginalStreamWriter,
9355 values: &[runtime::int_event::IntEvent],
9356 ) {
9357 w.push_extend_items(values, Self::write_cpp_int_event_raw);
9358 }
9359
9360 fn read_cpp_int_event_extend_list(
9361 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9362 ) -> Result<Vec<runtime::int_event::IntEvent>> {
9363 rd.extend_items(|rd| Self::read_cpp_int_event_raw(rd))
9364 }
9365
9366 fn write_cpp_group(
9367 &self,
9368 w: &mut crate::original_save::OriginalStreamWriter,
9369 g: &runtime::globals::GroupState,
9370 ) {
9371 w.push_i32(Self::save_i32(g.order));
9372 w.push_i32(Self::save_i32(g.layer));
9373 w.push_i32(Self::save_i32(g.cancel_priority));
9374 w.push_i32(Self::save_i32(g.cancel_se_no));
9375 w.push_i32(Self::save_i32(g.decided_button_no));
9376 w.push_i32(Self::save_i32(g.result));
9377 w.push_i32(Self::save_i32(g.result_button_no));
9378 w.push_bool(g.started);
9379 w.push_bool(g.pause_flag);
9380 w.push_bool(g.wait_flag);
9381 w.push_bool(g.cancel_flag);
9382 w.push_element(&g.target_object);
9383 }
9384
9385 fn read_cpp_group(
9386 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9387 ) -> Result<runtime::globals::GroupState> {
9388 let mut g = runtime::globals::GroupState {
9389 order: rd.i32()? as i64,
9390 layer: rd.i32()? as i64,
9391 cancel_priority: rd.i32()? as i64,
9392 cancel_se_no: rd.i32()? as i64,
9393 decided_button_no: rd.i32()? as i64,
9394 ..Default::default()
9395 };
9396
9397 if !rd.layout.is_indexed() {
9398 g.result = rd.i32()? as i64;
9399 g.result_button_no = rd.i32()? as i64;
9400 }
9401 g.started = rd.bool()?;
9402 g.pause_flag = rd.bool()?;
9403 g.wait_flag = rd.bool()?;
9404 g.cancel_flag = rd.bool()?;
9405 g.target_object = rd.element()?;
9406 Ok(g)
9407 }
9408
9409 fn write_cpp_object(
9410 &self,
9411 w: &mut crate::original_save::OriginalStreamWriter,
9412 obj: &runtime::globals::ObjectState,
9413 ) {
9414 let b = &obj.base;
9415 let ev = &obj.runtime.prop_events;
9416 w.push_i32(Self::save_i32(obj.object_type));
9417 w.push_i32(Self::save_i32(b.wipe_copy));
9418 w.push_i32(Self::save_i32(b.wipe_erase));
9419 w.push_i32(Self::save_i32(b.click_disable));
9420 w.push_i32(Self::save_i32(obj.rect_param.left));
9422 w.push_i32(Self::save_i32(obj.rect_param.top));
9423 w.push_i32(Self::save_i32(obj.rect_param.right));
9424 w.push_i32(Self::save_i32(obj.rect_param.bottom));
9425 w.push_i32(Self::save_i32(obj.rect_param.color_argb));
9426 w.push_i32(Self::save_i32(obj.string_param.moji_size));
9428 w.push_i32(Self::save_i32(obj.string_param.moji_space_x));
9429 w.push_i32(Self::save_i32(obj.string_param.moji_space_y));
9430 w.push_i32(Self::save_i32(obj.string_param.moji_cnt));
9431 w.push_i32(Self::save_i32(obj.string_param.moji_color));
9432 w.push_i32(Self::save_i32(obj.string_param.shadow_color));
9433 w.push_i32(Self::save_i32(obj.string_param.fuchi_color));
9434 w.push_i32(Self::save_i32(obj.string_param.shadow_mode));
9435 w.push_i32(Self::save_i32(obj.number_value));
9437 w.push_i32(Self::save_i32(obj.number_param.keta_max));
9438 w.push_i32(Self::save_i32(obj.number_param.disp_zero));
9439 w.push_i32(Self::save_i32(obj.number_param.disp_sign));
9440 w.push_i32(Self::save_i32(obj.number_param.tumeru_sign));
9441 w.push_i32(Self::save_i32(obj.number_param.space_mod));
9442 w.push_i32(Self::save_i32(obj.number_param.space));
9443 if obj.object_type == 4 {
9444 let wp = &obj.weather_param;
9448 for v in [
9449 wp.weather_type,
9450 wp.cnt,
9451 wp.pat_mode,
9452 wp.pat_no_00,
9453 wp.pat_no_01,
9454 wp.pat_time,
9455 wp.move_time_x,
9456 wp.move_time_y,
9457 wp.sin_time_x,
9458 wp.sin_time_y,
9459 wp.sin_power_x,
9460 wp.sin_power_y,
9461 wp.center_x,
9462 wp.center_y,
9463 wp.center_rotate,
9464 wp.appear_range,
9465 wp.zoom_min,
9466 wp.zoom_max,
9467 wp.scale_x,
9468 wp.scale_y,
9469 wp.active_time,
9470 ] {
9471 w.push_i32(Self::save_i32(v));
9472 }
9473 w.push_bool(wp.real_time_flag);
9474 w.push_padding(3);
9475 }
9476 w.push_i32(Self::save_i32(obj.thumb_save_no));
9477 w.push_bool(obj.movie.loop_flag);
9478 w.push_bool(obj.movie.auto_free_flag);
9479 w.push_bool(obj.movie.real_time_flag);
9480 w.push_bool(obj.movie.pause_flag);
9481 if obj.object_type == 12 {
9482 w.push_i32(Self::save_i32(obj.emote.width));
9483 w.push_i32(Self::save_i32(obj.emote.height));
9484 for option in obj.emote.timeline_options {
9485 w.push_i32(Self::save_i32(option));
9486 }
9487 w.push_i32(Self::save_i32(obj.emote.koe_chara_no));
9488 w.push_i32(Self::save_i32(obj.emote.koe_mouth_volume));
9489 w.push_i32(Self::save_i32(obj.emote.rep_x));
9490 w.push_i32(Self::save_i32(obj.emote.rep_y));
9491 }
9492 if obj.button.enabled {
9493 w.push_i32(1);
9494 w.push_i32(Self::save_i32(obj.button.sys_type));
9495 w.push_i32(Self::save_i32(obj.button.sys_type_opt));
9496 w.push_i32(Self::save_i32(obj.button.action_no));
9497 w.push_i32(Self::save_i32(obj.button.se_no));
9498 w.push_i32(Self::save_i32(obj.button.button_no));
9499 w.push_empty_element();
9500 w.push_i32(if obj.button.push_keep { 1 } else { 0 });
9501 w.push_i32(Self::save_i32(obj.button.state));
9502 w.push_i32(Self::save_i32(obj.button.mode));
9503 w.push_i32(Self::save_i32(obj.button.cut_no));
9504 w.push_i32(-1);
9505 w.push_i32(-1);
9506 w.push_i32(Self::save_i32(obj.button.decided_action_z_no));
9507 w.push_i32(0);
9508 w.push_i32(if obj.button.alpha_test { 1 } else { 0 });
9509 } else {
9510 w.push_i32(0);
9511 }
9512 w.push_i32(Self::save_i32(b.disp));
9513 {
9518 let mut pat_ev = ev.patno.clone();
9519 if pat_ev.loop_type == -1 {
9520 pat_ev.def_value = b.patno as i32;
9521 pat_ev.value = b.patno as i32;
9522 pat_ev.cur_value = b.patno as i32;
9523 }
9524 Self::write_cpp_int_event_raw(w, &pat_ev);
9525 }
9526 w.push_i32(Self::save_i32(b.order));
9527 w.push_i32(Self::save_i32(b.layer));
9528 w.push_i32(Self::save_i32(b.world));
9529 w.push_i32(Self::save_i32(b.child_sort_type));
9530 for e in [
9531 &ev.x,
9532 &ev.y,
9533 &ev.z,
9534 &ev.center_x,
9535 &ev.center_y,
9536 &ev.center_z,
9537 &ev.center_rep_x,
9538 &ev.center_rep_y,
9539 &ev.center_rep_z,
9540 &ev.scale_x,
9541 &ev.scale_y,
9542 &ev.scale_z,
9543 &ev.rotate_x,
9544 &ev.rotate_y,
9545 &ev.rotate_z,
9546 ] {
9547 Self::write_cpp_save_event(w, e);
9548 }
9549 w.push_i32(Self::save_i32(b.clip_use));
9550 for e in [&ev.clip_left, &ev.clip_top, &ev.clip_right, &ev.clip_bottom] {
9551 Self::write_cpp_save_event(w, e);
9552 }
9553 w.push_i32(Self::save_i32(b.src_clip_use));
9554 for e in [
9555 &ev.src_clip_left,
9556 &ev.src_clip_top,
9557 &ev.src_clip_right,
9558 &ev.src_clip_bottom,
9559 &ev.tr,
9560 &ev.mono,
9561 &ev.reverse,
9562 &ev.bright,
9563 &ev.dark,
9564 &ev.color_r,
9565 &ev.color_g,
9566 &ev.color_b,
9567 &ev.color_rate,
9568 &ev.color_add_r,
9569 &ev.color_add_g,
9570 &ev.color_add_b,
9571 ] {
9572 Self::write_cpp_save_event(w, e);
9573 }
9574 for v in [
9575 b.mask_no,
9576 b.tonecurve_no,
9577 b.light_no,
9578 b.fog_use,
9579 b.culling,
9580 b.alpha_test,
9581 b.alpha_blend,
9582 b.blend,
9583 0,
9584 ] {
9585 w.push_i32(Self::save_i32(v));
9586 }
9587 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.x_rep);
9588 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.y_rep);
9589 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.z_rep);
9590 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.tr_rep);
9591 w.push_extend_i32_list(&obj.runtime.prop_lists.f);
9592 w.push_str(obj.file_name.as_deref().unwrap_or(""));
9593 w.push_str(obj.string_value.as_deref().unwrap_or(""));
9594 w.push_str(&obj.button.decided_action_scn_name);
9595 w.push_str(&obj.button.decided_action_cmd_name);
9596 if obj.object_type == 12 {
9597 for timeline in &obj.emote.timeline_names {
9598 w.push_str(timeline);
9599 }
9600 }
9601 self.write_cpp_frame_action(w, &obj.frame_action);
9602 w.push_extend_items(&obj.frame_action_ch, |w, fa| {
9603 self.write_cpp_frame_action(w, fa)
9604 });
9605 w.push_str(obj.gan_file.as_deref().unwrap_or(""));
9606 w.push_extend_items(&obj.runtime.child_objects, |w, child| {
9607 self.write_cpp_object(w, child)
9608 });
9609 }
9610
9611 fn read_cpp_object(
9612 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9613 ) -> Result<runtime::globals::ObjectState> {
9614 match rd.layout {
9615 NativeLocalLayout::ShortElements => return Self::read_short_object(rd),
9616 NativeLocalLayout::Early | NativeLocalLayout::Indexed => {
9617 return Self::read_early_object(rd);
9618 }
9619 NativeLocalLayout::Legacy | NativeLocalLayout::Current => {}
9620 }
9621 let mut obj = runtime::globals::ObjectState {
9622 object_type: rd.i32()? as i64,
9623 ..Default::default()
9624 };
9625
9626 obj.base.wipe_copy = rd.i32()? as i64;
9627 obj.base.wipe_erase = rd.i32()? as i64;
9628 obj.base.click_disable = rd.i32()? as i64;
9629 obj.rect_param.left = rd.i32()? as i64;
9630 obj.rect_param.top = rd.i32()? as i64;
9631 obj.rect_param.right = rd.i32()? as i64;
9632 obj.rect_param.bottom = rd.i32()? as i64;
9633 obj.rect_param.color_argb = rd.i32()? as i64;
9634 obj.string_param.moji_size = rd.i32()? as i64;
9635 obj.string_param.moji_space_x = rd.i32()? as i64;
9636 obj.string_param.moji_space_y = rd.i32()? as i64;
9637 obj.string_param.moji_cnt = rd.i32()? as i64;
9638 obj.string_param.moji_color = rd.i32()? as i64;
9639 obj.string_param.shadow_color = rd.i32()? as i64;
9640 obj.string_param.fuchi_color = rd.i32()? as i64;
9641 obj.string_param.shadow_mode = rd.i32()? as i64;
9642 obj.number_value = rd.i32()? as i64;
9643 obj.number_param.keta_max = rd.i32()? as i64;
9644 obj.number_param.disp_zero = rd.i32()? as i64;
9645 obj.number_param.disp_sign = rd.i32()? as i64;
9646 obj.number_param.tumeru_sign = rd.i32()? as i64;
9647 obj.number_param.space_mod = rd.i32()? as i64;
9648 obj.number_param.space = rd.i32()? as i64;
9649 if obj.object_type == 4 {
9650 obj.weather_param.weather_type = rd.i32()? as i64;
9651 obj.weather_param.cnt = rd.i32()? as i64;
9652 obj.weather_param.pat_mode = rd.i32()? as i64;
9653 obj.weather_param.pat_no_00 = rd.i32()? as i64;
9654 obj.weather_param.pat_no_01 = rd.i32()? as i64;
9655 obj.weather_param.pat_time = rd.i32()? as i64;
9656 obj.weather_param.move_time_x = rd.i32()? as i64;
9657 obj.weather_param.move_time_y = rd.i32()? as i64;
9658 obj.weather_param.sin_time_x = rd.i32()? as i64;
9659 obj.weather_param.sin_time_y = rd.i32()? as i64;
9660 obj.weather_param.sin_power_x = rd.i32()? as i64;
9661 obj.weather_param.sin_power_y = rd.i32()? as i64;
9662 obj.weather_param.center_x = rd.i32()? as i64;
9663 obj.weather_param.center_y = rd.i32()? as i64;
9664 obj.weather_param.center_rotate = rd.i32()? as i64;
9665 obj.weather_param.appear_range = rd.i32()? as i64;
9666 obj.weather_param.zoom_min = rd.i32()? as i64;
9667 obj.weather_param.zoom_max = rd.i32()? as i64;
9668 obj.weather_param.scale_x = rd.i32()? as i64;
9669 obj.weather_param.scale_y = rd.i32()? as i64;
9670 obj.weather_param.active_time = rd.i32()? as i64;
9671 obj.weather_param.real_time_flag = rd.bool()?;
9672 rd.skip(3)?;
9674 obj.weather_param.move_time = obj.weather_param.move_time_x;
9677 }
9678 obj.thumb_save_no = rd.i32()? as i64;
9679 obj.movie.loop_flag = rd.bool()?;
9680 obj.movie.auto_free_flag = rd.bool()?;
9681 obj.movie.real_time_flag = rd.bool()?;
9682 obj.movie.pause_flag = rd.bool()?;
9683 if obj.object_type == 12 {
9684 obj.emote.width = rd.i32()? as i64;
9685 obj.emote.height = rd.i32()? as i64;
9686 for option in &mut obj.emote.timeline_options {
9687 *option = rd.i32()? as i64;
9688 }
9689 obj.emote.koe_chara_no = rd.i32()? as i64;
9690 obj.emote.koe_mouth_volume = rd.i32()? as i64;
9691 obj.emote.rep_x = rd.i32()? as i64;
9692 obj.emote.rep_y = rd.i32()? as i64;
9693 }
9694 let button_exist = rd.i32()? != 0;
9695 if button_exist {
9696 obj.button.enabled = true;
9697 obj.button.sys_type = rd.i32()? as i64;
9698 obj.button.sys_type_opt = rd.i32()? as i64;
9699 obj.button.action_no = rd.i32()? as i64;
9700 obj.button.se_no = rd.i32()? as i64;
9701 obj.button.button_no = rd.i32()? as i64;
9702 rd.skip_element()?;
9703 obj.button.push_keep = rd.i32()? != 0;
9704 obj.button.state = rd.i32()? as i64;
9705 obj.button.mode = rd.i32()? as i64;
9706 obj.button.cut_no = rd.i32()? as i64;
9707 let _ = rd.i32()?;
9708 let _ = rd.i32()?;
9709 obj.button.decided_action_z_no = rd.i32()? as i64;
9710 let _ = rd.i32()?;
9711 obj.button.alpha_test = rd.i32()? != 0;
9712 }
9713 obj.base.disp = rd.i32()? as i64;
9714 obj.runtime.prop_events.patno = Self::read_cpp_int_event_raw(rd)?;
9716 if obj.runtime.prop_events.patno.loop_type == -1 {
9717 obj.base.patno = obj.runtime.prop_events.patno.value as i64;
9718 }
9719 obj.base.order = rd.i32()? as i64;
9720 obj.base.layer = rd.i32()? as i64;
9721 obj.base.world = rd.i32()? as i64;
9722 obj.base.child_sort_type = rd.i32()? as i64;
9723 obj.runtime.prop_events.x = Self::read_cpp_save_event(rd, 0)?;
9724 obj.runtime.prop_events.y = Self::read_cpp_save_event(rd, 0)?;
9725 obj.runtime.prop_events.z = Self::read_cpp_save_event(rd, 0)?;
9726 obj.runtime.prop_events.center_x = Self::read_cpp_save_event(rd, 0)?;
9727 obj.runtime.prop_events.center_y = Self::read_cpp_save_event(rd, 0)?;
9728 obj.runtime.prop_events.center_z = Self::read_cpp_save_event(rd, 0)?;
9729 obj.runtime.prop_events.center_rep_x = Self::read_cpp_save_event(rd, 0)?;
9730 obj.runtime.prop_events.center_rep_y = Self::read_cpp_save_event(rd, 0)?;
9731 obj.runtime.prop_events.center_rep_z = Self::read_cpp_save_event(rd, 0)?;
9732 obj.runtime.prop_events.scale_x = Self::read_cpp_save_event(rd, 1000)?;
9733 obj.runtime.prop_events.scale_y = Self::read_cpp_save_event(rd, 1000)?;
9734 obj.runtime.prop_events.scale_z = Self::read_cpp_save_event(rd, 1000)?;
9735 obj.runtime.prop_events.rotate_x = Self::read_cpp_save_event(rd, 0)?;
9736 obj.runtime.prop_events.rotate_y = Self::read_cpp_save_event(rd, 0)?;
9737 obj.runtime.prop_events.rotate_z = Self::read_cpp_save_event(rd, 0)?;
9738 obj.base.clip_use = rd.i32()? as i64;
9739 obj.runtime.prop_events.clip_left = Self::read_cpp_save_event(rd, 0)?;
9740 obj.runtime.prop_events.clip_top = Self::read_cpp_save_event(rd, 0)?;
9741 obj.runtime.prop_events.clip_right = Self::read_cpp_save_event(rd, 0)?;
9742 obj.runtime.prop_events.clip_bottom = Self::read_cpp_save_event(rd, 0)?;
9743 obj.base.src_clip_use = rd.i32()? as i64;
9744 obj.runtime.prop_events.src_clip_left = Self::read_cpp_save_event(rd, 0)?;
9745 obj.runtime.prop_events.src_clip_top = Self::read_cpp_save_event(rd, 0)?;
9746 obj.runtime.prop_events.src_clip_right = Self::read_cpp_save_event(rd, 0)?;
9747 obj.runtime.prop_events.src_clip_bottom = Self::read_cpp_save_event(rd, 0)?;
9748 obj.runtime.prop_events.tr = Self::read_cpp_save_event(rd, 255)?;
9749 obj.runtime.prop_events.mono = Self::read_cpp_save_event(rd, 0)?;
9750 obj.runtime.prop_events.reverse = Self::read_cpp_save_event(rd, 0)?;
9751 obj.runtime.prop_events.bright = Self::read_cpp_save_event(rd, 0)?;
9752 obj.runtime.prop_events.dark = Self::read_cpp_save_event(rd, 0)?;
9753 obj.runtime.prop_events.color_r = Self::read_cpp_save_event(rd, 0)?;
9754 obj.runtime.prop_events.color_g = Self::read_cpp_save_event(rd, 0)?;
9755 obj.runtime.prop_events.color_b = Self::read_cpp_save_event(rd, 0)?;
9756 obj.runtime.prop_events.color_rate = Self::read_cpp_save_event(rd, 0)?;
9757 obj.runtime.prop_events.color_add_r = Self::read_cpp_save_event(rd, 0)?;
9758 obj.runtime.prop_events.color_add_g = Self::read_cpp_save_event(rd, 0)?;
9759 obj.runtime.prop_events.color_add_b = Self::read_cpp_save_event(rd, 0)?;
9760 obj.base.mask_no = rd.i32()? as i64;
9761 obj.base.tonecurve_no = rd.i32()? as i64;
9762 obj.base.light_no = rd.i32()? as i64;
9763 obj.base.fog_use = rd.i32()? as i64;
9764 obj.base.culling = rd.i32()? as i64;
9765 obj.base.alpha_test = rd.i32()? as i64;
9766 obj.base.alpha_blend = rd.i32()? as i64;
9767 obj.base.blend = rd.i32()? as i64;
9768 let _flags = rd.i32()?;
9769 obj.runtime.prop_event_lists.x_rep = Self::read_cpp_int_event_extend_list(rd)?;
9770 obj.runtime.prop_event_lists.y_rep = Self::read_cpp_int_event_extend_list(rd)?;
9771 obj.runtime.prop_event_lists.z_rep = Self::read_cpp_int_event_extend_list(rd)?;
9772 obj.runtime.prop_event_lists.tr_rep = Self::read_cpp_int_event_extend_list(rd)?;
9773 obj.runtime.prop_lists.f = rd.extend_i32_list()?;
9774 let file_name = rd.string()?;
9775 obj.file_name = if file_name.is_empty() {
9776 None
9777 } else {
9778 Some(file_name)
9779 };
9780 let string_value = rd.string()?;
9781 obj.string_value = if string_value.is_empty() {
9782 None
9783 } else {
9784 Some(string_value)
9785 };
9786 obj.button.decided_action_scn_name = rd.string()?;
9787 obj.button.decided_action_cmd_name = rd.string()?;
9788 if obj.object_type == 12 {
9789 for timeline in &mut obj.emote.timeline_names {
9790 *timeline = rd.string()?;
9791 }
9792 }
9793 obj.frame_action = Self::read_cpp_frame_action(rd)?;
9794 obj.frame_action_ch = rd.extend_items(|rd| Self::read_cpp_frame_action(rd))?;
9795 let gan_file = rd.string()?;
9796 obj.gan_file = if gan_file.is_empty() {
9797 None
9798 } else {
9799 Some(gan_file)
9800 };
9801 if rd.layout != NativeLocalLayout::Current {
9802 obj.gan.read_original_work(rd)?;
9805 }
9806 obj.runtime.child_objects = rd.extend_items(|rd| Self::read_cpp_object(rd))?;
9807 obj.used = obj.object_type != 0 || obj.file_name.is_some() || obj.string_value.is_some();
9808 Ok(obj)
9809 }
9810
9811 fn write_cpp_mwnd_glyph(
9812 w: &mut crate::original_save::OriginalStreamWriter,
9813 glyph: &runtime::globals::MwndGlyphState,
9814 ) {
9815 w.push_i32(glyph.moji_type);
9817 w.push_i32(glyph.code);
9818 w.push_i32(Self::save_i32(glyph.size));
9819 w.push_i32(Self::save_i32(glyph.moji_color_no));
9820 w.push_i32(Self::save_i32(glyph.shadow_color_no));
9821 w.push_i32(Self::save_i32(glyph.fuchi_color_no));
9822 w.push_i32(Self::save_i32(glyph.x));
9823 w.push_i32(Self::save_i32(glyph.y));
9824 w.push_bool(glyph.appeared);
9825 w.push_bool(glyph.ruby);
9826 }
9827
9828 fn read_cpp_mwnd_glyph(
9829 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9830 ) -> Result<runtime::globals::MwndGlyphState> {
9831 if rd.layout.uses_early_records() {
9832 return Self::read_early_mwnd_glyph(rd);
9833 }
9834 let moji_type = rd.i32()?;
9835 let code = rd.i32()?;
9836 let size = rd.i32()? as i64;
9837 let moji_color_no = rd.i32()? as i64;
9838 let shadow_color_no = rd.i32()? as i64;
9839 let fuchi_color_no = rd.i32()? as i64;
9840 let x = rd.i32()? as i64;
9841 let y = rd.i32()? as i64;
9842 let appeared = rd.bool()?;
9843 let ruby = rd.bool()?;
9844 let ch = if moji_type == 0 {
9845 char::from_u32(code as u32).unwrap_or('\u{fffd}')
9846 } else {
9847 '\0'
9848 };
9849 Ok(runtime::globals::MwndGlyphState {
9850 moji_type,
9851 code,
9852 ch,
9853 x,
9854 y,
9855 size,
9856 moji_color_no,
9857 shadow_color_no,
9858 fuchi_color_no,
9859 shadow: shadow_color_no >= 0,
9860 fuchi: fuchi_color_no >= 0,
9861 bold: false,
9862 reveal_index: 1,
9863 ruby,
9864 appeared,
9865 message_button: None,
9866 })
9867 }
9868
9869 fn active_mwnd_message_page(
9870 m: &runtime::globals::MwndState,
9871 ) -> runtime::globals::MwndMessagePageState {
9872 runtime::globals::MwndMessagePageState {
9873 msg_text: m.msg_text.clone(),
9874 glyphs: m.glyphs.clone(),
9875 disp_moji_cnt: m.disp_moji_cnt,
9876 hide_moji_cnt: m.hide_moji_cnt,
9877 cur_msg_type: m.cur_msg_type,
9878 cur_msg_type_decided: m.cur_msg_type_decided,
9879 ruby_start_pos: m.ruby_start_pos,
9880 ruby_start_ready: m.ruby_start_ready,
9881 cursor_pos: m.cursor_pos,
9882 moji_rep_pos: m.moji_rep_pos,
9883 indent_pos: m.indent_pos,
9884 indent_moji: m.indent_moji,
9885 indent_count: m.indent_count,
9886 line_head: m.line_head,
9887 ruby_pending: m.ruby_pending.clone(),
9888 moji_size: m.moji_size,
9889 moji_color: m.moji_color,
9890 shadow_color: m.shadow_color,
9891 fuchi_color: m.fuchi_color,
9892 chara_moji_color: m.chara_moji_color,
9893 chara_shadow_color: m.chara_shadow_color,
9894 chara_fuchi_color: m.chara_fuchi_color,
9895 msgbtn: m.msgbtn,
9896 }
9897 }
9898
9899 fn write_cpp_mwnd_message(
9900 w: &mut crate::original_save::OriginalStreamWriter,
9901 m: &runtime::globals::MwndState,
9902 page: &runtime::globals::MwndMessagePageState,
9903 ) {
9904 let (cnt_x, cnt_y) = m.window_moji_cnt.unwrap_or((0, 0));
9906 let (pos_x, pos_y) = m.message_pos.unwrap_or((0, 0));
9907 let (space_x, space_y) = m.moji_space.unwrap_or((-1, 10));
9908 let (talk_l, talk_t, talk_r, talk_b) = m.message_margin.unwrap_or((0, 0, 0, 0));
9909 for value in [
9910 cnt_x,
9911 cnt_y,
9912 pos_x,
9913 pos_y,
9914 page.moji_rep_pos.0,
9915 page.moji_rep_pos.1,
9916 page.moji_size.unwrap_or(m.default_moji_size),
9917 space_x,
9918 space_y,
9919 page.moji_color.unwrap_or(-1),
9920 page.shadow_color.unwrap_or(-1),
9921 page.fuchi_color.unwrap_or(-1),
9922 m.ruby_size,
9923 m.ruby_space,
9924 m.default_moji_size,
9925 0,
9926 m.name_bracket,
9927 talk_l,
9928 talk_t,
9929 talk_r,
9930 talk_b,
9931 ] {
9932 w.push_i32(Self::save_i32(value));
9933 }
9934 w.push_i32(Self::save_i32(page.chara_moji_color.unwrap_or(-1)));
9935 w.push_i32(Self::save_i32(page.chara_shadow_color.unwrap_or(-1)));
9936 w.push_i32(Self::save_i32(page.chara_fuchi_color.unwrap_or(-1)));
9937 w.push_i32(Self::save_i32(page.indent_pos));
9938 w.push_u16(page.indent_moji.unwrap_or('\0') as u32 as u16);
9939 w.push_i32(Self::save_i32(page.indent_count));
9940 w.push_i32(Self::save_i32(page.cur_msg_type));
9941 w.push_i32(Self::save_i32(page.ruby_start_pos.0));
9942 w.push_i32(Self::save_i32(page.ruby_start_pos.1));
9943 w.push_i32(Self::save_i32(page.disp_moji_cnt));
9944 w.push_i32(Self::save_i32(page.hide_moji_cnt));
9945 w.push_str(&page.msg_text);
9946 w.push_str(
9947 page.ruby_pending
9948 .as_ref()
9949 .map(|ruby| ruby.text.as_str())
9950 .unwrap_or(""),
9951 );
9952 let (btn_no, group_no, action_no, se_no) = page.msgbtn.unwrap_or((0, 0, 0, 0));
9953 w.push_i32(Self::save_i32(btn_no));
9954 w.push_i32(Self::save_i32(group_no));
9955 w.push_i32(Self::save_i32(action_no));
9956 w.push_i32(Self::save_i32(se_no));
9957 w.push_bool(page.cur_msg_type_decided);
9958 w.push_bool(page.line_head);
9959 w.push_bool(page.ruby_start_ready);
9960 w.push_bool(page.msgbtn.is_some());
9961 w.push_extend_items(&page.glyphs, |w, glyph| {
9962 Self::write_cpp_mwnd_glyph(w, glyph)
9963 });
9964 }
9965
9966 fn read_cpp_mwnd_message(
9967 rd: &mut crate::original_save::OriginalStreamReader<'_>,
9968 m: &mut runtime::globals::MwndState,
9969 ) -> Result<runtime::globals::MwndMessagePageState> {
9970 if rd.layout.uses_early_records() {
9971 return Self::read_early_mwnd_message(rd, m);
9972 }
9973 let cnt_x = rd.i32()? as i64;
9974 let cnt_y = rd.i32()? as i64;
9975 let pos_x = rd.i32()? as i64;
9976 let pos_y = rd.i32()? as i64;
9977 let rep_x = rd.i32()? as i64;
9978 let rep_y = rd.i32()? as i64;
9979 let moji_size = rd.i32()? as i64;
9980 let space_x = rd.i32()? as i64;
9981 let space_y = rd.i32()? as i64;
9982 let moji_color = rd.i32()? as i64;
9983 let shadow_color = rd.i32()? as i64;
9984 let fuchi_color = rd.i32()? as i64;
9985 let ruby_size = rd.i32()? as i64;
9986 let ruby_space = rd.i32()? as i64;
9987 let _name_moji_size = rd.i32()?;
9988 let _name_newline = rd.i32()?;
9989 let _name_bracket = rd.i32()?;
9990 let talk_l = rd.i32()? as i64;
9991 let talk_t = rd.i32()? as i64;
9992 let talk_r = rd.i32()? as i64;
9993 let talk_b = rd.i32()? as i64;
9994 m.window_moji_cnt = Some((cnt_x, cnt_y));
9995 m.message_pos = Some((pos_x, pos_y));
9996 m.moji_space = Some((space_x, space_y));
9997 m.message_margin = Some((talk_l, talk_t, talk_r, talk_b));
9998 m.ruby_size = ruby_size;
9999 m.ruby_space = ruby_space;
10000
10001 let chara_moji = rd.i32()? as i64;
10002 let chara_shadow = rd.i32()? as i64;
10003 let chara_fuchi = rd.i32()? as i64;
10004 let indent_pos = rd.i32()? as i64;
10005 let indent_u16 = rd.u16()?;
10006 let indent_count = rd.i32()? as i64;
10007 let cur_msg_type = rd.i32()? as i64;
10008 let ruby_x = rd.i32()? as i64;
10009 let ruby_y = rd.i32()? as i64;
10010 let disp_moji_cnt = rd.i32()? as i64;
10011 let hide_moji_cnt = rd.i32()? as i64;
10012 let debug_msg = rd.string()?;
10013 let ruby = rd.string()?;
10014 let btn_no = rd.i32()? as i64;
10015 let group_no = rd.i32()? as i64;
10016 let action_no = rd.i32()? as i64;
10017 let se_no = rd.i32()? as i64;
10018 let cur_msg_type_decided = rd.bool()?;
10019 let line_head = rd.bool()?;
10020 let ruby_start_ready = rd.bool()?;
10021 let button_flag = rd.bool()?;
10022 let mut glyphs = rd.extend_items(Self::read_cpp_mwnd_glyph)?;
10023 let mut body_index = 0usize;
10024 for glyph in &mut glyphs {
10025 if !glyph.ruby {
10026 body_index += 1;
10027 }
10028 glyph.reveal_index = body_index.max(1);
10029 if button_flag {
10030 glyph.message_button = Some(runtime::globals::MwndMessageButtonState {
10031 btn_no,
10032 group_no,
10033 action_no,
10034 se_no,
10035 });
10036 }
10037 }
10038 let msg_text = if !debug_msg.is_empty() {
10039 debug_msg
10040 } else {
10041 let units: Vec<u16> = glyphs
10042 .iter()
10043 .filter(|glyph| glyph.moji_type == 0 && !glyph.ruby)
10044 .map(|glyph| glyph.code as u16)
10045 .collect();
10046 String::from_utf16_lossy(&units)
10047 };
10048 Ok(runtime::globals::MwndMessagePageState {
10049 msg_text,
10050 glyphs,
10051 disp_moji_cnt,
10052 hide_moji_cnt,
10053 cur_msg_type,
10054 cur_msg_type_decided,
10055 ruby_start_pos: (ruby_x, ruby_y),
10056 ruby_start_ready,
10057 cursor_pos: (pos_x, pos_y),
10058 moji_rep_pos: (rep_x, rep_y),
10059 indent_pos,
10060 indent_moji: (indent_u16 != 0)
10061 .then(|| char::from_u32(indent_u16 as u32).unwrap_or('\u{fffd}')),
10062 indent_count,
10063 line_head,
10064 ruby_pending: (!ruby.is_empty()).then_some(runtime::globals::MwndRubyPendingState {
10065 text: ruby,
10066 start_pos: Some((ruby_x, ruby_y)),
10067 }),
10068 moji_size: Some(moji_size),
10069 moji_color: (moji_color >= 0).then_some(moji_color),
10070 shadow_color: (shadow_color >= 0).then_some(shadow_color),
10071 fuchi_color: (fuchi_color >= 0).then_some(fuchi_color),
10072 chara_moji_color: (chara_moji >= 0).then_some(chara_moji),
10073 chara_shadow_color: (chara_shadow >= 0).then_some(chara_shadow),
10074 chara_fuchi_color: (chara_fuchi >= 0).then_some(chara_fuchi),
10075 msgbtn: button_flag.then_some((btn_no, group_no, action_no, se_no)),
10076 })
10077 }
10078
10079 fn write_cpp_mwnd_waku(
10080 &self,
10081 w: &mut crate::original_save::OriginalStreamWriter,
10082 m: &runtime::globals::MwndState,
10083 name_waku: bool,
10084 ) {
10085 w.push_i32(Self::save_i32(m.msg_waku_no.unwrap_or(0)));
10086 w.push_str(if name_waku { "" } else { &m.waku_file });
10087 w.push_str(if name_waku { "" } else { &m.filter_file });
10088 let margin = if name_waku {
10089 (0, 0, 0, 0)
10090 } else {
10091 m.filter_margin.unwrap_or((0, 0, 0, 0))
10092 };
10093 for value in [margin.0, margin.1, margin.2, margin.3] {
10094 w.push_i32(Self::save_i32(value));
10095 }
10096 let (a, r, g, b) = if name_waku {
10097 (0, 0, 0, 0)
10098 } else {
10099 m.filter_color.unwrap_or((0, 0, 0, 0))
10100 };
10101 w.push_raw(&[b, g, r, a]);
10102 w.push_bool(!name_waku && m.filter_config_color);
10103 w.push_bool(!name_waku && m.filter_config_tr);
10104 w.push_padding(2);
10106 w.push_i32(Self::save_i32(m.msg_waku_no.unwrap_or(0)));
10107 w.push_i32(Self::save_i32(if name_waku { 0 } else { m.key_icon_mode }));
10108 let icon_pos = if name_waku { None } else { m.key_icon_pos };
10109 w.push_i32(Self::save_i32(icon_pos.map(|p| p.0).unwrap_or(0)));
10110 w.push_i32(Self::save_i32(icon_pos.map(|p| p.1).unwrap_or(0)));
10111 let faces: &[runtime::globals::ObjectState] = if name_waku { &[] } else { &m.face_list };
10112 let objects: &[runtime::globals::ObjectState] =
10113 if name_waku { &[] } else { &m.object_list };
10114 w.push_fixed_items(faces, |w, object| self.write_cpp_object(w, object));
10115 w.push_fixed_items(objects, |w, object| self.write_cpp_object(w, object));
10116 }
10117
10118 fn read_cpp_mwnd_waku(
10119 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10120 m: &mut runtime::globals::MwndState,
10121 name_waku: bool,
10122 ) -> Result<()> {
10123 let template_no = rd.i32()? as i64;
10124 let waku_file = rd.string()?;
10125 let filter_file = rd.string()?;
10126 let margin = (
10127 rd.i32()? as i64,
10128 rd.i32()? as i64,
10129 rd.i32()? as i64,
10130 rd.i32()? as i64,
10131 );
10132 let color = rd.take_raw(4)?;
10133 let filter_config_color = rd.bool()?;
10134 let filter_config_tr = rd.bool()?;
10135 rd.skip(2)?;
10136 let _key_template = rd.i32()?;
10137 let key_mode = rd.i32()? as i64;
10138 let key_x = rd.i32()? as i64;
10139 let key_y = rd.i32()? as i64;
10140 let faces = rd.fixed_items(Self::read_cpp_object)?;
10141 let objects = rd.fixed_items(Self::read_cpp_object)?;
10142 if !name_waku {
10143 m.msg_waku_no = Some(template_no);
10144 m.waku_file = waku_file;
10145 m.filter_file = filter_file;
10146 m.filter_margin = Some(margin);
10147 m.filter_color = Some((color[3], color[2], color[1], color[0]));
10148 m.filter_config_color = filter_config_color;
10149 m.filter_config_tr = filter_config_tr;
10150 m.key_icon_mode = key_mode;
10151 m.key_icon_pos = Some((key_x, key_y));
10152 m.face_list = faces;
10153 m.object_list = objects;
10154 }
10155 Ok(())
10156 }
10157
10158 fn write_cpp_mwnd_name(
10159 w: &mut crate::original_save::OriginalStreamWriter,
10160 m: &runtime::globals::MwndState,
10161 ) {
10162 w.push_i32(0);
10163 let (space_x, space_y) = m.moji_space.unwrap_or((-1, 10));
10164 let (pos_x, pos_y) = m.name_message_pos;
10165 for value in [
10166 pos_x,
10167 pos_y,
10168 m.default_moji_size,
10169 space_x,
10170 space_y,
10171 m.name_text.chars().count() as i64,
10172 m.name_window_align,
10173 m.name_moji_color.unwrap_or(-1),
10174 m.name_shadow_color.unwrap_or(-1),
10175 m.name_fuchi_color.unwrap_or(-1),
10176 ] {
10177 w.push_i32(Self::save_i32(value));
10178 }
10179 w.push_str(&m.name_text);
10180 for value in [
10181 m.name_window_rect.0,
10182 m.name_window_rect.1,
10183 m.name_window_rect.2,
10184 m.name_window_rect.3,
10185 ] {
10186 w.push_i32(Self::save_i32(value));
10187 }
10188 w.push_extend_items(&m.name_glyphs, |w, glyph| {
10189 Self::write_cpp_mwnd_glyph(w, glyph)
10190 });
10191 }
10192
10193 fn read_cpp_mwnd_name(
10194 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10195 m: &mut runtime::globals::MwndState,
10196 ) -> Result<()> {
10197 if rd.layout.uses_early_records() {
10198 return Self::read_early_mwnd_name(rd, m);
10199 }
10200 let _template_no = rd.i32()?;
10201 let pos_x = rd.i32()? as i64;
10202 let pos_y = rd.i32()? as i64;
10203 let _size = rd.i32()?;
10204 let _space_x = rd.i32()?;
10205 let _space_y = rd.i32()?;
10206 let _cnt = rd.i32()?;
10207 m.name_window_align = rd.i32()? as i64;
10208 let name_moji_color = rd.i32()? as i64;
10209 let name_shadow_color = rd.i32()? as i64;
10210 let name_fuchi_color = rd.i32()? as i64;
10211 m.name_moji_color = (name_moji_color >= 0).then_some(name_moji_color);
10212 m.name_shadow_color = (name_shadow_color >= 0).then_some(name_shadow_color);
10213 m.name_fuchi_color = (name_fuchi_color >= 0).then_some(name_fuchi_color);
10214 m.name_text = rd.string()?;
10215 m.name_message_pos = (pos_x, pos_y);
10216 m.name_window_rect = (
10217 rd.i32()? as i64,
10218 rd.i32()? as i64,
10219 rd.i32()? as i64,
10220 rd.i32()? as i64,
10221 );
10222 m.name_glyphs = rd.extend_items(Self::read_cpp_mwnd_glyph)?;
10223 Ok(())
10224 }
10225
10226 fn write_cpp_mwnd_selection(
10227 w: &mut crate::original_save::OriginalStreamWriter,
10228 m: &runtime::globals::MwndState,
10229 ) {
10230 w.push_i32(0);
10231 let (cnt_x, cnt_y) = m.window_moji_cnt.unwrap_or((0, 0));
10232 let (pos_x, pos_y) = m.message_pos.unwrap_or((0, 0));
10233 let (space_x, space_y) = m.moji_space.unwrap_or((-1, 10));
10234 for value in [
10235 cnt_x,
10236 cnt_y,
10237 pos_x,
10238 pos_y,
10239 m.default_moji_size,
10240 space_x,
10241 space_y,
10242 m.default_moji_color,
10243 m.default_shadow_color,
10244 m.default_fuchi_color,
10245 ] {
10246 w.push_i32(Self::save_i32(value));
10247 }
10248 let selection = m.selection.as_ref();
10249 w.push_i32(
10250 selection
10251 .map(|sel| sel.disp_item_count.min(i32::MAX as usize) as i32)
10252 .unwrap_or(0),
10253 );
10254 w.push_bool(selection.is_some_and(|sel| sel.cancel_enable));
10255 let choices: &[runtime::globals::MwndSelectionChoice] =
10256 selection.map(|sel| sel.choices.as_slice()).unwrap_or(&[]);
10257 w.push_u32(choices.len().min(u32::MAX as usize) as u32);
10258 for choice in choices {
10259 w.push_i32(Self::save_i32(choice.kind));
10260 w.push_i32(Self::save_i32(choice.pos.0));
10261 w.push_i32(Self::save_i32(choice.pos.1));
10262 w.push_str(&choice.text);
10263 let fallback_width = choice.text.chars().count() as i64 * m.default_moji_size;
10264 w.push_i32(Self::save_i32(if choice.size.0 != 0 {
10265 choice.size.0
10266 } else {
10267 fallback_width
10268 }));
10269 w.push_i32(Self::save_i32(if choice.size.1 != 0 {
10270 choice.size.1
10271 } else {
10272 m.default_moji_size
10273 }));
10274 w.push_extend_items(&choice.glyphs, |w, glyph| {
10275 Self::write_cpp_mwnd_glyph(w, glyph)
10276 });
10277 }
10278 }
10279
10280 fn read_cpp_mwnd_selection(
10281 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10282 m: &mut runtime::globals::MwndState,
10283 ) -> Result<()> {
10284 if rd.layout.uses_early_records() {
10285 return Self::read_early_mwnd_selection(rd, m);
10286 }
10287 let _template = rd.i32()?;
10288 for _ in 0..10 {
10289 let _ = rd.i32()?;
10290 }
10291 let disp_item_count = rd.i32()?.max(0) as usize;
10292 let cancel_enable = rd.bool()?;
10293 let count = rd.i32()?.max(0) as usize;
10294 let mut choices = Vec::with_capacity(count);
10295 for _ in 0..count {
10296 let kind = rd.i32()? as i64;
10297 let x = rd.i32()? as i64;
10298 let y = rd.i32()? as i64;
10299 let text = rd.string()?;
10300 let sx = rd.i32()? as i64;
10301 let sy = rd.i32()? as i64;
10302 let glyphs = rd.extend_items(Self::read_cpp_mwnd_glyph)?;
10303 let color = glyphs
10304 .first()
10305 .map(|glyph| glyph.moji_color_no)
10306 .unwrap_or(m.default_moji_color);
10307 choices.push(runtime::globals::MwndSelectionChoice {
10308 text,
10309 kind,
10310 color,
10311 pos: (x, y),
10312 size: (sx, sy),
10313 glyphs,
10314 });
10315 }
10316 if !choices.is_empty() || cancel_enable {
10317 m.selection = Some(runtime::globals::MwndSelectionState {
10318 choices,
10319 disp_item_count,
10320 cursor: 0,
10321 cancel_enable,
10322 close_mwnd: false,
10323 result: 0,
10324 });
10325 }
10326 Ok(())
10327 }
10328
10329 fn write_cpp_mwnd(
10330 &self,
10331 w: &mut crate::original_save::OriginalStreamWriter,
10332 m: &runtime::globals::MwndState,
10333 ) {
10334 let (window_x, window_y) = m.window_pos.unwrap_or((0, 0));
10336 let (window_w, window_h) = m.window_size.unwrap_or((0, 0));
10337 let (msg_x, msg_y) = m.message_pos.unwrap_or((0, 0));
10338 let (margin_l, margin_t, margin_r, margin_b) = m.message_margin.unwrap_or((0, 0, 0, 0));
10339 let (cnt_x, cnt_y) = m.window_moji_cnt.unwrap_or((0, 0));
10340 for value in [
10341 m.order,
10342 m.layer,
10343 m.world,
10344 m.novel_mode,
10345 m.mwnd_extend_type,
10346 window_x,
10347 window_y,
10348 window_w,
10349 window_h,
10350 msg_x,
10351 msg_y,
10352 margin_l,
10353 margin_t,
10354 margin_r,
10355 margin_b,
10356 cnt_x,
10357 cnt_y,
10358 m.name_disp_mode,
10359 m.name_bracket,
10360 m.name_extend_type,
10361 m.name_window_align,
10362 m.name_window_pos.0,
10363 m.name_window_pos.1,
10364 m.name_window_size.0,
10365 m.name_window_size.1,
10366 m.name_window_rect.0,
10367 m.name_window_rect.1,
10368 m.name_window_rect.2,
10369 m.name_window_rect.3,
10370 m.name_message_pos.0,
10371 m.name_message_pos.1,
10372 m.name_message_pos_rep.0,
10373 m.name_message_pos_rep.1,
10374 m.name_message_margin.0,
10375 m.name_message_margin.1,
10376 m.name_message_margin.2,
10377 m.name_message_margin.3,
10378 m.overflow_check_size,
10379 m.face_hide_name,
10380 m.open_anime_type,
10381 m.open_anime_time,
10382 m.close_anime_type,
10383 m.close_anime_time,
10384 ] {
10385 w.push_i32(Self::save_i32(value));
10386 }
10387 w.push_i32(Self::save_i32(m.time));
10388 w.push_bool(m.msg_block_started);
10389 w.push_bool(m.auto_proc_ready);
10390 w.push_bool(m.window_appear || m.open);
10391 w.push_bool(m.name_appear || !m.name_text.is_empty());
10392 w.push_bool(m.clear_ready);
10393 w.push_i32(Self::save_i32(m.auto_mode_end_moji_cnt));
10394 w.push_i32(Self::save_i32(m.target_msg_no));
10395 w.push_bool(m.slide_msg);
10396 w.push_i32(Self::save_i32(m.slide_time));
10397 w.push_i32(m.koe.map(|value| Self::save_i32(value.0)).unwrap_or(-1));
10398 w.push_bool(m.koe_play_flag || m.koe.is_some());
10399 w.push_i32(Self::save_i32(m.open_anime_type));
10400 w.push_i32(Self::save_i32(m.open_anime_time));
10401 w.push_i32(Self::save_i32(m.open_anime_start_time));
10402 w.push_i32(Self::save_i32(m.close_anime_type));
10403 w.push_i32(Self::save_i32(m.close_anime_time));
10404 w.push_i32(Self::save_i32(m.close_anime_start_time));
10405
10406 w.push_i32((m.message_pages.len() + 1).min(i32::MAX as usize) as i32);
10407 for page in &m.message_pages {
10408 Self::write_cpp_mwnd_message(w, m, page);
10409 }
10410 let active = Self::active_mwnd_message_page(m);
10411 Self::write_cpp_mwnd_message(w, m, &active);
10412 self.write_cpp_mwnd_waku(w, m, false);
10413 Self::write_cpp_mwnd_name(w, m);
10414 self.write_cpp_mwnd_waku(w, m, true);
10415 Self::write_cpp_mwnd_selection(w, m);
10416 }
10417
10418 fn read_cpp_mwnd(
10419 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10420 ) -> Result<runtime::globals::MwndState> {
10421 let mut m = runtime::globals::MwndState::default();
10422 m.order = rd.i32()? as i64;
10423 m.layer = rd.i32()? as i64;
10424 m.world = rd.i32()? as i64;
10425 m.novel_mode = rd.i32()? as i64;
10426 m.mwnd_extend_type = rd.i32()? as i64;
10427 m.window_pos = Some((rd.i32()? as i64, rd.i32()? as i64));
10428 m.window_size = Some((rd.i32()? as i64, rd.i32()? as i64));
10429 m.message_pos = Some((rd.i32()? as i64, rd.i32()? as i64));
10430 m.message_margin = Some((
10431 rd.i32()? as i64,
10432 rd.i32()? as i64,
10433 rd.i32()? as i64,
10434 rd.i32()? as i64,
10435 ));
10436 m.window_moji_cnt = Some((rd.i32()? as i64, rd.i32()? as i64));
10437 m.name_disp_mode = rd.i32()? as i64;
10438 m.name_bracket = rd.i32()? as i64;
10439 m.name_extend_type = rd.i32()? as i64;
10440 m.name_window_align = rd.i32()? as i64;
10441 m.name_window_pos = (rd.i32()? as i64, rd.i32()? as i64);
10442 m.name_window_size = (rd.i32()? as i64, rd.i32()? as i64);
10443 m.name_window_rect = (
10444 rd.i32()? as i64,
10445 rd.i32()? as i64,
10446 rd.i32()? as i64,
10447 rd.i32()? as i64,
10448 );
10449 m.name_message_pos = (rd.i32()? as i64, rd.i32()? as i64);
10450 m.name_message_pos_rep = (rd.i32()? as i64, rd.i32()? as i64);
10451 m.name_message_margin = (
10452 rd.i32()? as i64,
10453 rd.i32()? as i64,
10454 rd.i32()? as i64,
10455 rd.i32()? as i64,
10456 );
10457 m.overflow_check_size = rd.i32()? as i64;
10458 m.face_hide_name = rd.i32()? as i64;
10459 m.open_anime_type = rd.i32()? as i64;
10460 m.open_anime_time = rd.i32()? as i64;
10461 m.close_anime_type = rd.i32()? as i64;
10462 m.close_anime_time = rd.i32()? as i64;
10463 m.time = rd.i32()? as i64;
10464 m.msg_block_started = rd.bool()?;
10465 m.auto_proc_ready = rd.bool()?;
10466 m.window_appear = rd.bool()?;
10467 m.open = m.window_appear;
10468 m.name_appear = rd.bool()?;
10469 m.clear_ready = rd.bool()?;
10470 m.auto_mode_end_moji_cnt = rd.i32()? as i64;
10471 m.target_msg_no = rd.i32()? as i64;
10472 m.slide_msg = rd.bool()?;
10473 m.slide_time = rd.i32()? as i64;
10474 let koe_no = rd.i32()? as i64;
10475 m.koe_play_flag = rd.bool()?;
10476 if m.koe_play_flag {
10477 m.koe = Some((koe_no, 0));
10478 }
10479 m.open_anime_type = rd.i32()? as i64;
10480 m.open_anime_time = rd.i32()? as i64;
10481 m.open_anime_start_time = rd.i32()? as i64;
10482 m.close_anime_type = rd.i32()? as i64;
10483 m.close_anime_time = rd.i32()? as i64;
10484 m.close_anime_start_time = rd.i32()? as i64;
10485
10486 let message_count = rd.i32()?.max(0) as usize;
10487 let mut pages = Vec::with_capacity(message_count.max(1));
10488 for _ in 0..message_count {
10489 pages.push(Self::read_cpp_mwnd_message(rd, &mut m)?);
10490 }
10491 let active_index = m
10492 .target_msg_no
10493 .clamp(0, pages.len().saturating_sub(1) as i64) as usize;
10494 if let Some(active) = pages.get(active_index).cloned() {
10495 m.message_pages = pages[..active_index].to_vec();
10496 m.msg_text = active.msg_text;
10497 m.glyphs = active.glyphs;
10498 m.disp_moji_cnt = active.disp_moji_cnt;
10499 m.hide_moji_cnt = active.hide_moji_cnt;
10500 m.cur_msg_type = active.cur_msg_type;
10501 m.cur_msg_type_decided = active.cur_msg_type_decided;
10502 m.ruby_start_pos = active.ruby_start_pos;
10503 m.ruby_start_ready = active.ruby_start_ready;
10504 m.cursor_pos = active.cursor_pos;
10505 m.moji_rep_pos = active.moji_rep_pos;
10506 m.indent_pos = active.indent_pos;
10507 m.indent_moji = active.indent_moji;
10508 m.indent_count = active.indent_count;
10509 m.line_head = active.line_head;
10510 m.ruby_pending = active.ruby_pending;
10511 m.moji_size = active.moji_size;
10512 m.moji_color = active.moji_color;
10513 m.shadow_color = active.shadow_color;
10514 m.fuchi_color = active.fuchi_color;
10515 m.chara_moji_color = active.chara_moji_color;
10516 m.chara_shadow_color = active.chara_shadow_color;
10517 m.chara_fuchi_color = active.chara_fuchi_color;
10518 m.msgbtn = active.msgbtn;
10519 }
10520 let mut reveal_index = 0usize;
10521 for page in &mut m.message_pages {
10522 for glyph in &mut page.glyphs {
10523 if !glyph.ruby {
10524 reveal_index += 1;
10525 }
10526 glyph.reveal_index = reveal_index.max(1);
10527 }
10528 }
10529 for glyph in &mut m.glyphs {
10530 if !glyph.ruby {
10531 reveal_index += 1;
10532 }
10533 glyph.reveal_index = reveal_index.max(1);
10534 }
10535 Self::read_cpp_mwnd_waku(rd, &mut m, false)?;
10536 Self::read_cpp_mwnd_name(rd, &mut m)?;
10537 Self::read_cpp_mwnd_waku(rd, &mut m, true)?;
10538 Self::read_cpp_mwnd_selection(rd, &mut m)?;
10539 Ok(m)
10540 }
10541
10542 fn write_cpp_world(
10543 &self,
10544 w: &mut crate::original_save::OriginalStreamWriter,
10545 world: &runtime::globals::WorldState,
10546 ) {
10547 w.push_i32(world.mode);
10548 for e in [
10549 &world.camera_eye_x,
10550 &world.camera_eye_y,
10551 &world.camera_eye_z,
10552 &world.camera_pint_x,
10553 &world.camera_pint_y,
10554 &world.camera_pint_z,
10555 &world.camera_up_x,
10556 &world.camera_up_y,
10557 &world.camera_up_z,
10558 ] {
10559 Self::write_cpp_int_event_raw(w, e);
10560 }
10561 for v in [
10562 world.camera_view_angle,
10563 world.mono,
10564 world.order,
10565 world.layer,
10566 world.wipe_copy,
10567 world.wipe_erase,
10568 ] {
10569 w.push_i32(v);
10570 }
10571 }
10572
10573 fn read_cpp_world(
10574 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10575 world_no: i32,
10576 ) -> Result<runtime::globals::WorldState> {
10577 match rd.layout {
10578 NativeLocalLayout::ShortElements => return Self::read_short_world(rd, world_no),
10579 NativeLocalLayout::Early | NativeLocalLayout::Indexed => {
10580 return Self::read_early_world(rd, world_no);
10581 }
10582 NativeLocalLayout::Legacy | NativeLocalLayout::Current => {}
10583 }
10584 let mut world = runtime::globals::WorldState::new(world_no);
10585 world.mode = rd.i32()?;
10586 world.camera_eye_x = Self::read_cpp_int_event_raw(rd)?;
10587 world.camera_eye_y = Self::read_cpp_int_event_raw(rd)?;
10588 world.camera_eye_z = Self::read_cpp_int_event_raw(rd)?;
10589 world.camera_pint_x = Self::read_cpp_int_event_raw(rd)?;
10590 world.camera_pint_y = Self::read_cpp_int_event_raw(rd)?;
10591 world.camera_pint_z = Self::read_cpp_int_event_raw(rd)?;
10592 world.camera_up_x = Self::read_cpp_int_event_raw(rd)?;
10593 world.camera_up_y = Self::read_cpp_int_event_raw(rd)?;
10594 world.camera_up_z = Self::read_cpp_int_event_raw(rd)?;
10595 world.camera_view_angle = rd.i32()?;
10596 world.mono = rd.i32()?;
10597 world.order = rd.i32()?;
10598 world.layer = rd.i32()?;
10599 world.wipe_copy = rd.i32()?;
10600 world.wipe_erase = rd.i32()?;
10601 Ok(world)
10602 }
10603
10604 fn write_cpp_effect(
10605 &self,
10606 w: &mut crate::original_save::OriginalStreamWriter,
10607 e: &runtime::globals::ScreenEffectState,
10608 ) {
10609 for ev in [
10610 &e.x,
10611 &e.y,
10612 &e.z,
10613 &e.mono,
10614 &e.reverse,
10615 &e.bright,
10616 &e.dark,
10617 &e.color_r,
10618 &e.color_g,
10619 &e.color_b,
10620 &e.color_rate,
10621 &e.color_add_r,
10622 &e.color_add_g,
10623 &e.color_add_b,
10624 ] {
10625 Self::write_cpp_int_event_raw(w, ev);
10626 }
10627 for v in [
10628 e.begin_order,
10629 e.end_order,
10630 e.begin_layer,
10631 e.end_layer,
10632 e.wipe_copy,
10633 e.wipe_erase,
10634 ] {
10635 w.push_i32(v);
10636 }
10637 }
10638
10639 fn read_cpp_effect(
10640 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10641 ) -> Result<runtime::globals::ScreenEffectState> {
10642 if rd.layout == NativeLocalLayout::ShortElements {
10643 return Self::read_short_effect(rd);
10644 }
10645 let mut e = runtime::globals::ScreenEffectState {
10646 x: Self::read_cpp_int_event_raw(rd)?,
10647 y: Self::read_cpp_int_event_raw(rd)?,
10648 z: Self::read_cpp_int_event_raw(rd)?,
10649 mono: Self::read_cpp_int_event_raw(rd)?,
10650 reverse: Self::read_cpp_int_event_raw(rd)?,
10651 bright: Self::read_cpp_int_event_raw(rd)?,
10652 dark: Self::read_cpp_int_event_raw(rd)?,
10653 color_r: Self::read_cpp_int_event_raw(rd)?,
10654 color_g: Self::read_cpp_int_event_raw(rd)?,
10655 color_b: Self::read_cpp_int_event_raw(rd)?,
10656 color_rate: Self::read_cpp_int_event_raw(rd)?,
10657 color_add_r: Self::read_cpp_int_event_raw(rd)?,
10658 color_add_g: Self::read_cpp_int_event_raw(rd)?,
10659 color_add_b: Self::read_cpp_int_event_raw(rd)?,
10660 begin_order: rd.i32()?,
10661 end_order: rd.i32()?,
10662 begin_layer: rd.i32()?,
10663 end_layer: rd.i32()?,
10664 wipe_copy: rd.i32()?,
10665 wipe_erase: rd.i32()?,
10666 };
10667
10668 Ok(e)
10669 }
10670
10671 fn write_cpp_quake(
10672 &self,
10673 w: &mut crate::original_save::OriginalStreamWriter,
10674 q: &runtime::globals::ScreenQuakeState,
10675 ) {
10676 w.push_i32(q.quake_type);
10677 w.push_i32(q.vec);
10678 w.push_i32(q.power);
10679 w.push_i32(q.cur_time);
10680 w.push_i32(q.total_time);
10681 w.push_i32(if q.ending { 1 } else { 0 });
10682 w.push_i32(q.end_cur_time);
10683 w.push_i32(q.end_total_time);
10684 w.push_i32(q.cnt);
10685 w.push_i32(q.end_cnt);
10686 w.push_i32(q.center_x);
10687 w.push_i32(q.center_y);
10688 w.push_i32(q.begin_order);
10689 w.push_i32(q.end_order);
10690 }
10691
10692 fn read_cpp_quake(
10693 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10694 ) -> Result<runtime::globals::ScreenQuakeState> {
10695 let mut q = runtime::globals::ScreenQuakeState {
10696 quake_type: rd.i32()?,
10697 vec: rd.i32()?,
10698 power: rd.i32()?,
10699 cur_time: rd.i32()?,
10700 total_time: rd.i32()?,
10701 ending: rd.i32()? != 0,
10702 end_cur_time: rd.i32()?,
10703 end_total_time: rd.i32()?,
10704 cnt: rd.i32()?,
10705 end_cnt: rd.i32()?,
10706 center_x: rd.i32()?,
10707 center_y: rd.i32()?,
10708 begin_order: rd.i32()?,
10709 end_order: rd.i32()?,
10710 };
10711
10712 Ok(q)
10713 }
10714
10715 fn cpp_btn_select_param(&self, state: &runtime::globals::BtnSelectRuntimeState) -> [i64; 28] {
10716 if let Some(saved) = state.saved_cur_param {
10717 return saved;
10718 }
10719
10720 let mut param = [0i64; 28];
10724 param[20] = -1;
10725 let Some(tmpl) = (state.template_no >= 0)
10726 .then(|| {
10727 self.ctx
10728 .tables
10729 .sel_btn_templates
10730 .get(state.template_no as usize)
10731 })
10732 .flatten()
10733 else {
10734 return param;
10735 };
10736 param = [
10737 tmpl.base_pos.0,
10738 tmpl.base_pos.1,
10739 tmpl.rep_pos.0,
10740 tmpl.rep_pos.1,
10741 tmpl.x_align,
10742 tmpl.y_align,
10743 tmpl.max_y_cnt,
10744 tmpl.line_width,
10745 tmpl.moji_cnt,
10746 tmpl.moji_pos.0,
10747 tmpl.moji_pos.1,
10748 tmpl.moji_size,
10749 tmpl.moji_space.0,
10750 tmpl.moji_space.1,
10751 tmpl.moji_x_align,
10752 tmpl.moji_y_align,
10753 tmpl.moji_color,
10754 tmpl.moji_hit_color,
10755 self.ctx.tables.mwnd_render.shadow_color,
10756 self.ctx.tables.mwnd_render.fuchi_color,
10757 tmpl.btn_action_no,
10758 tmpl.open_anime_type,
10759 tmpl.open_anime_time,
10760 tmpl.close_anime_type,
10761 tmpl.close_anime_time,
10762 tmpl.decide_anime_type,
10763 tmpl.decide_anime_time,
10764 state.sync_type,
10765 ];
10766 param
10767 }
10768
10769 fn synthesize_cpp_btn_select_glyphs(
10770 text: &str,
10771 param: &[i64; 28],
10772 color: i64,
10773 appeared: bool,
10774 ) -> Vec<runtime::globals::MwndGlyphState> {
10775 let size = param[11];
10776 let space_x = param[12];
10777 let mut x = 0i64;
10778 let mut glyphs = Vec::with_capacity(text.chars().count());
10779 for ch in text.chars() {
10780 glyphs.push(runtime::globals::MwndGlyphState {
10781 moji_type: 0,
10782 code: ch as u32 as i32,
10783 ch,
10784 x: param[9].saturating_add(x),
10785 y: param[10],
10786 size,
10787 moji_color_no: color,
10788 shadow_color_no: param[18],
10789 fuchi_color_no: param[19],
10790 shadow: param[18] >= 0,
10791 fuchi: param[19] >= 0,
10792 bold: false,
10793 reveal_index: glyphs.len().saturating_add(1),
10794 ruby: false,
10795 appeared,
10796 message_button: None,
10797 });
10798 let full_advance = size.saturating_add(space_x);
10799 let half_width = ch.is_ascii() || matches!(ch as u32, 0xff61..=0xff9f);
10800 x = x.saturating_add(if half_width {
10801 full_advance / 2
10802 } else {
10803 full_advance
10804 });
10805 }
10806
10807 let total_x = if glyphs.is_empty() {
10810 0
10811 } else {
10812 x.saturating_sub(space_x)
10813 };
10814 let rep_x = match param[14] {
10815 1 => -total_x / 2,
10816 2 => -total_x,
10817 _ => 0,
10818 };
10819 let rep_y = match param[15] {
10820 1 => -size / 2,
10821 2 => -size,
10822 _ => 0,
10823 };
10824 for glyph in &mut glyphs {
10825 glyph.x = glyph.x.saturating_add(rep_x);
10826 glyph.y = glyph.y.saturating_add(rep_y);
10827 }
10828 glyphs
10829 }
10830
10831 fn write_cpp_btn_select(
10832 &self,
10833 w: &mut crate::original_save::OriginalStreamWriter,
10834 state: Option<&runtime::globals::BtnSelectRuntimeState>,
10835 ) {
10836 let empty = runtime::globals::BtnSelectRuntimeState::default();
10837 let state = state.unwrap_or(&empty);
10838 let param = self.cpp_btn_select_param(state);
10839 w.push_i32(Self::save_i32(state.template_no));
10840 for value in param {
10841 w.push_i32(Self::save_i32(value));
10842 }
10843 w.push_bool(state.appear_flag);
10844 w.push_bool(state.processing_flag_0);
10845 w.push_bool(state.processing_flag_1);
10846 w.push_bool(state.processing_flag_2);
10847 w.push_bool(state.cancel_enable);
10848 w.push_bool(state.capture_flag);
10849 w.push_str(&state.sel_start_call_scn);
10850 w.push_i32(Self::save_i32(state.sel_start_call_z_no));
10851
10852 let loaded = state.saved_cur_param.is_some();
10853 w.push_extend_items(&state.choices, |w, choice| {
10854 let item_template_no = if choice.template_no >= 0 {
10855 choice.template_no
10856 } else {
10857 state.template_no
10858 };
10859 let item_template = (item_template_no >= 0)
10860 .then(|| {
10861 self.ctx
10862 .tables
10863 .sel_btn_templates
10864 .get(item_template_no as usize)
10865 })
10866 .flatten();
10867 let base_file = if loaded || !choice.base_file.is_empty() {
10868 choice.base_file.as_str()
10869 } else {
10870 item_template.map(|t| t.base_file.as_str()).unwrap_or("")
10871 };
10872 let filter_file = if loaded || !choice.filter_file.is_empty() {
10873 choice.filter_file.as_str()
10874 } else {
10875 item_template.map(|t| t.filter_file.as_str()).unwrap_or("")
10876 };
10877 let color = if choice.color >= 0 {
10878 choice.color
10879 } else {
10880 param[16]
10881 };
10882 let relative_x = choice.pos.0.saturating_sub(param[0]);
10883 let relative_y = choice.pos.1.saturating_sub(param[1]);
10884 let generated;
10885 let glyphs = if choice.glyphs.is_empty() {
10886 generated = Self::synthesize_cpp_btn_select_glyphs(
10887 &choice.text,
10888 ¶m,
10889 color,
10890 state.appear_flag
10891 || state.processing_flag_0
10892 || state.processing_flag_1
10893 || state.processing_flag_2,
10894 );
10895 generated.as_slice()
10896 } else {
10897 choice.glyphs.as_slice()
10898 };
10899
10900 w.push_i32(Self::save_i32(item_template_no));
10901 w.push_str(base_file);
10902 w.push_str(filter_file);
10903 w.push_str(&choice.text);
10904 w.push_i32(Self::save_i32(choice.item_type));
10905 w.push_i32(Self::save_i32(color));
10906 w.push_i32(Self::save_i32(relative_x));
10907 w.push_i32(Self::save_i32(relative_y));
10908 w.push_extend_items(glyphs, Self::write_cpp_mwnd_glyph);
10909 });
10910 }
10911
10912 fn read_cpp_btn_select(
10913 rd: &mut crate::original_save::OriginalStreamReader<'_>,
10914 ) -> Result<runtime::globals::BtnSelectRuntimeState> {
10915 if rd.layout.uses_early_records() {
10916 return Self::read_early_btn_select(rd);
10917 }
10918 let template_no = rd.i32()? as i64;
10919 let mut param = [0i64; 28];
10920 for value in &mut param {
10921 *value = rd.i32()? as i64;
10922 }
10923 let appear_flag = rd.bool()?;
10924 let processing_flag_0 = rd.bool()?;
10925 let processing_flag_1 = rd.bool()?;
10926 let processing_flag_2 = rd.bool()?;
10927 let cancel_enable = rd.bool()?;
10928 let capture_flag = rd.bool()?;
10929 let sel_start_call_scn = rd.string()?;
10930 let sel_start_call_z_no = rd.i32()? as i64;
10931 let choices = rd.extend_items(|rd| {
10932 let item_template_no = rd.i32()? as i64;
10933 let base_file = rd.string()?;
10934 let filter_file = rd.string()?;
10935 let text = rd.string()?;
10936 let item_type = rd.i32()? as i64;
10937 let color = rd.i32()? as i64;
10938 let relative_x = rd.i32()? as i64;
10939 let relative_y = rd.i32()? as i64;
10940 let glyphs = rd.extend_items(Self::read_cpp_mwnd_glyph)?;
10941 Ok(runtime::globals::BtnSelectChoiceState {
10942 template_no: item_template_no,
10943 base_file,
10944 filter_file,
10945 text,
10946 item_type,
10947 color,
10948 pos: (
10949 param[0].saturating_add(relative_x),
10950 param[1].saturating_add(relative_y),
10951 ),
10952 size: (0, 0),
10953 glyphs,
10954 })
10955 })?;
10956 let cursor = choices
10957 .iter()
10958 .position(|choice| choice.item_type == 1)
10959 .unwrap_or(0);
10960
10961 Ok(runtime::globals::BtnSelectRuntimeState {
10962 template_no,
10963 saved_cur_param: Some(param),
10964 choices,
10965 cursor,
10966 pressed_index: None,
10967 pressed_inside: false,
10968 cancel_enable,
10969 capture_flag,
10970 started: processing_flag_0,
10974 result: 0,
10975 sync_type: 0,
10976 read_flag_scene_no: -1,
10977 read_flag_flag_no: -1,
10978 sel_start_call_scn,
10979 sel_start_call_z_no,
10980 appear_flag,
10981 open_anime_type: 0,
10982 open_anime_time: 0,
10983 open_anime_cur_time: 0,
10984 close_anime_type: 0,
10985 close_anime_time: 0,
10986 close_anime_cur_time: 0,
10987 decide_anime_type: 0,
10988 decide_anime_time: 0,
10989 decide_anime_cur_time: 0,
10990 decide_sel_no: -1,
10991 processing_flag_0,
10992 processing_flag_1,
10993 processing_flag_2,
10994 capture_now_flag: false,
10995 result_delivered: false,
10996 })
10997 }
10998
10999 fn write_cpp_stage(&self, w: &mut crate::original_save::OriginalStreamWriter, stage_idx: i64) {
11000 let form_id = if self.ctx.ids.form_global_stage != 0 {
11001 self.ctx.ids.form_global_stage
11002 } else {
11003 runtime::forms::codes::FORM_GLOBAL_STAGE
11004 };
11005 let st = self.ctx.globals.stage_forms.get(&form_id);
11006 let empty_groups: Vec<runtime::globals::GroupState> = Vec::new();
11007 let empty_objects: Vec<runtime::globals::ObjectState> = Vec::new();
11008 let empty_mwnds: Vec<runtime::globals::MwndState> = Vec::new();
11009 let empty_worlds: Vec<runtime::globals::WorldState> = Vec::new();
11010 let empty_effects: Vec<runtime::globals::ScreenEffectState> = Vec::new();
11011 let empty_quakes: Vec<runtime::globals::ScreenQuakeState> = Vec::new();
11012 let groups = st
11013 .and_then(|s| s.group_lists.get(&stage_idx))
11014 .unwrap_or(&empty_groups);
11015 let objects = st
11016 .and_then(|s| s.object_lists.get(&stage_idx))
11017 .unwrap_or(&empty_objects);
11018 let mwnds = st
11019 .and_then(|s| s.mwnd_lists.get(&stage_idx))
11020 .unwrap_or(&empty_mwnds);
11021 let worlds = st
11022 .and_then(|s| s.world_lists.get(&stage_idx))
11023 .unwrap_or(&empty_worlds);
11024 let effects = st
11025 .and_then(|s| s.effect_lists.get(&stage_idx))
11026 .unwrap_or(&empty_effects);
11027 let quakes = st
11028 .and_then(|s| s.quake_lists.get(&stage_idx))
11029 .unwrap_or(&empty_quakes);
11030 w.push_fixed_items(groups, |w, g| self.write_cpp_group(w, g));
11031 w.push_fixed_items(objects, |w, obj| self.write_cpp_object(w, obj));
11032 w.push_fixed_items(mwnds, |w, m| self.write_cpp_mwnd(w, m));
11033 let btn_select = if stage_idx == 1 {
11034 Some(&self.ctx.globals.selbtn)
11035 } else {
11036 st.and_then(|stage| stage.btn_select_states.get(&stage_idx))
11037 };
11038 self.write_cpp_btn_select(w, btn_select);
11039 w.push_fixed_items(worlds, |w, world| self.write_cpp_world(w, world));
11040 w.push_fixed_items(effects, |w, e| self.write_cpp_effect(w, e));
11041 w.push_fixed_items(quakes, |w, q| self.write_cpp_quake(w, q));
11042 }
11043
11044 fn read_cpp_stage(
11045 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11046 stage_idx: i64,
11047 ) -> Result<(
11048 runtime::globals::StageFormState,
11049 runtime::globals::BtnSelectRuntimeState,
11050 )> {
11051 if rd.layout.uses_early_records() {
11052 return Self::read_early_stage(rd, stage_idx);
11053 }
11054 let mut st = runtime::globals::StageFormState {
11055 initialized_from_gameexe: true,
11056 ..Default::default()
11057 };
11058
11059 st.group_lists
11060 .insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_group(rd))?);
11061 st.object_lists
11062 .insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_object(rd))?);
11063 st.mwnd_lists
11064 .insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_mwnd(rd))?);
11065 let btn_select = Self::read_cpp_btn_select(rd)?;
11066 st.btn_select_states.insert(stage_idx, btn_select.clone());
11067 let mut world_no = 0i32;
11068 let worlds = rd.fixed_items(|rd| {
11069 let w = Self::read_cpp_world(rd, world_no);
11070 world_no += 1;
11071 w
11072 })?;
11073 st.world_lists.insert(stage_idx, worlds);
11074 st.effect_lists
11075 .insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_effect(rd))?);
11076 st.quake_lists
11077 .insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_quake(rd))?);
11078 Ok((st, btn_select))
11079 }
11080
11081 fn write_cpp_screen(&self, w: &mut crate::original_save::OriginalStreamWriter) {
11082 let form_id = self.ctx.ids.form_global_screen;
11083 let screen = self
11084 .ctx
11085 .globals
11086 .screen_forms
11087 .get(&form_id)
11088 .cloned()
11089 .unwrap_or_default();
11090 w.push_fixed_items(&screen.effect_list, |w, e| self.write_cpp_effect(w, e));
11091 w.push_i32(screen.shake.shake_no);
11092 w.push_i32(screen.shake.cur_time);
11093 w.push_i32(screen.shake.cur_x);
11094 w.push_i32(screen.shake.cur_y);
11095 w.push_fixed_items(&screen.quake_list, |w, q| self.write_cpp_quake(w, q));
11096 }
11097
11098 fn read_cpp_screen(
11099 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11100 ) -> Result<runtime::globals::ScreenFormState> {
11101 let effect_list = rd.fixed_items(|rd| Self::read_cpp_effect(rd))?;
11102 let mut shake = runtime::globals::ScreenShakeState {
11103 shake_no: rd.i32()?,
11104 cur_time: rd.i32()?,
11105 cur_x: rd.i32()?,
11106 cur_y: rd.i32()?,
11107 };
11108
11109 let quake_list = rd.fixed_items(|rd| Self::read_cpp_quake(rd))?;
11110 Ok(runtime::globals::ScreenFormState {
11111 effect_list,
11112 quake_list,
11113 shake,
11114 })
11115 }
11116
11117 const ORIGINAL_PCMCH_DEFAULT_CNT: usize = 16;
11118 const ORIGINAL_PCMCH_MAX_CNT: usize = 256;
11119
11120 fn original_pcmch_count(&self) -> usize {
11121 self.ctx
11122 .tables
11123 .gameexe
11124 .as_ref()
11125 .and_then(|cfg| {
11126 cfg.get_usize("#PCMCH.CNT")
11127 .or_else(|| cfg.get_usize("PCMCH.CNT"))
11128 })
11129 .unwrap_or(Self::ORIGINAL_PCMCH_DEFAULT_CNT)
11130 .min(Self::ORIGINAL_PCMCH_MAX_CNT)
11131 }
11132
11133 fn write_cpp_pcmch(&self, w: &mut crate::original_save::OriginalStreamWriter, ch: usize) {
11134 let state = self
11135 .ctx
11136 .globals
11137 .pcmch_persistent
11138 .get(ch)
11139 .cloned()
11140 .unwrap_or_default();
11141 w.push_str(&state.pcm_name);
11142 w.push_str(&state.bgm_name);
11143 w.push_i32(Self::save_i32(state.koe_no));
11144 w.push_i32(Self::save_i32(state.se_no));
11145 w.push_i32(Self::save_i32(state.volume_type));
11146 w.push_i32(Self::save_i32(state.chara_no));
11147 w.push_i32(self.ctx.pcm.slot_volume_raw(ch) as i32);
11148 w.push_i32(Self::save_i32(self.ctx.pcm.slot_resume_delay_ms(ch)));
11149 w.push_bool(state.loop_flag);
11150 w.push_bool(state.bgm_fade_target_flag);
11151 w.push_bool(state.bgm_fade2_target_flag);
11152 w.push_bool(state.bgm_fade_source_flag);
11153 w.push_bool(state.ready_flag);
11154 }
11155
11156 fn read_cpp_pcmch(
11157 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11158 ) -> Result<runtime::globals::PcmChPersistentState> {
11159 Ok(runtime::globals::PcmChPersistentState {
11160 pcm_name: rd.string()?,
11161 bgm_name: rd.string()?,
11162 koe_no: rd.i32()? as i64,
11163 se_no: rd.i32()? as i64,
11164 volume_type: rd.i32()? as i64,
11165 chara_no: rd.i32()? as i64,
11166 volume: rd.i32()? as i64,
11167 delay_time: rd.i32()? as i64,
11168 fade_in_time: 0,
11169 loop_flag: rd.bool()?,
11170 bgm_fade_target_flag: rd.bool()?,
11171 bgm_fade2_target_flag: rd.bool()?,
11172 bgm_fade_source_flag: rd.bool()?,
11173 ready_flag: rd.bool()?,
11174 })
11175 }
11176
11177 fn write_cpp_sound(&self, w: &mut crate::original_save::OriginalStreamWriter) {
11178 w.push_str(self.ctx.bgm.current_name().unwrap_or(""));
11180 w.push_i32(self.ctx.bgm.volume_raw() as i32);
11181 w.push_i32(Self::save_i32(self.ctx.bgm.save_delay_time_ms()));
11182 w.push_bool(self.ctx.bgm.save_loop_flag());
11183 w.push_bool(self.ctx.bgm.save_pause_flag());
11184 w.push_i32(self.ctx.koe.volume_raw() as i32);
11185 w.push_i32(self.ctx.pcm.volume_raw() as i32);
11186 let pcmch_count = self.original_pcmch_count();
11187 let channels: Vec<usize> = (0..pcmch_count).collect();
11188 w.push_fixed_items(&channels, |w, ch| self.write_cpp_pcmch(w, *ch));
11189 w.push_i32(self.ctx.se.volume_raw() as i32);
11190 w.push_str(self.ctx.globals.mov.file_name.as_deref().unwrap_or(""));
11191 }
11192
11193 fn read_current_sound(
11194 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11195 ) -> Result<SavedSound> {
11196 let bgm_regist_name = rd.string()?;
11197 let bgm_volume = rd.i32()?.clamp(0, 255) as u8;
11198 let bgm_delay_time = rd.i32()?.max(0) as i64;
11199 let bgm_loop_flag = rd.bool()?;
11200 let bgm_pause_flag = rd.bool()?;
11201 let koe_volume = rd.i32()?.clamp(0, 255) as u8;
11202 let pcm_volume = rd.i32()?.clamp(0, 255) as u8;
11203 let pcmch = rd.fixed_items(|rd| Self::read_cpp_pcmch(rd))?;
11204 let se_volume = rd.i32()?.clamp(0, 255) as u8;
11205 let mov_file_name = rd.string()?;
11206 Ok(SavedSound {
11207 bgm_regist_name,
11208 bgm_volume,
11209 bgm_delay_time,
11210 bgm_loop_flag,
11211 bgm_pause_flag,
11212 koe_volume,
11213 pcm_volume,
11214 pcmch,
11215 se_volume,
11216 mov_file_name,
11217 })
11218 }
11219
11220 fn read_cpp_sound(
11221 &mut self,
11222 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11223 ) -> Result<()> {
11224 let _ = self.ctx.bgm.stop();
11228 let _ = self.ctx.koe.stop(None);
11229 let _ = self.ctx.pcm.stop_all(None);
11230 let _ = self.ctx.se.stop(None);
11231 self.ctx.movie.stop();
11232
11233 let SavedSound {
11234 bgm_regist_name,
11235 bgm_volume,
11236 bgm_delay_time,
11237 bgm_loop_flag,
11238 bgm_pause_flag,
11239 koe_volume,
11240 pcm_volume,
11241 pcmch,
11242 se_volume,
11243 mov_file_name,
11244 } = match rd.layout {
11245 NativeLocalLayout::ShortElements => Self::read_short_sound(rd)?,
11246 _ => Self::read_current_sound(rd)?,
11247 };
11248
11249 {
11250 let (bgm, audio) = (&mut self.ctx.bgm, &mut self.ctx.audio);
11251 bgm.set_volume_raw(audio, bgm_volume)?;
11252 if !bgm_regist_name.is_empty()
11253 && (bgm_loop_flag || bgm_pause_flag || bgm_delay_time > 0)
11254 {
11255 if let Err(err) = bgm.play_name_script(
11258 audio,
11259 &bgm_regist_name,
11260 bgm_loop_flag,
11261 0,
11262 0,
11263 -1,
11264 bgm_pause_flag,
11265 0,
11266 ) {
11267 log::error!("failed to restore BGM {:?}: {err:#}", bgm_regist_name);
11268 }
11269 }
11270 }
11271 {
11272 let (koe, audio) = (&mut self.ctx.koe, &mut self.ctx.audio);
11273 koe.set_volume_raw(audio, koe_volume)?;
11274 }
11275 {
11276 let (pcm, audio) = (&mut self.ctx.pcm, &mut self.ctx.audio);
11277 pcm.set_volume_raw(audio, pcm_volume)?;
11278 }
11279 {
11280 let (se, audio) = (&mut self.ctx.se, &mut self.ctx.audio);
11281 se.set_volume_raw(audio, se_volume)?;
11282 }
11283
11284 self.ctx.globals.pcmch_persistent.clear();
11285 for (ch, state) in pcmch.into_iter().enumerate() {
11286 if let Err(err) =
11287 crate::runtime::forms::pcmch::restore_persistent_channel(&mut self.ctx, ch, state)
11288 {
11289 log::error!("failed to restore PCMCH[{ch}]: {err:#}");
11290 }
11291 }
11292
11293 if mov_file_name.is_empty() {
11294 self.ctx.globals.mov.file_name = None;
11295 self.ctx.movie.stop();
11296 } else {
11297 self.ctx.globals.mov.file_name = Some(mov_file_name.clone());
11298 self.ctx.globals.mov.playing = false;
11299 if let Err(err) = self.ctx.movie.prepare(&mov_file_name) {
11300 log::error!(
11301 "failed to restructure saved MOV {:?}: {err:#}",
11302 mov_file_name
11303 );
11304 }
11305 }
11306 Ok(())
11307 }
11308
11309 fn write_cpp_pcm_event(
11310 &self,
11311 w: &mut crate::original_save::OriginalStreamWriter,
11312 ev: &runtime::globals::PcmEventState,
11313 ) {
11314 let ty = ev.event_type;
11315 w.push_i32(ty);
11316 if ty == runtime::globals::PCM_EVENT_TYPE_LOOP
11317 || ty == runtime::globals::PCM_EVENT_TYPE_RANDOM
11318 {
11319 w.push_i32(ev.pcm_buf_no);
11320 w.push_i32(ev.volume_type);
11321 w.push_i32(ev.chara_no);
11322 w.push_bool(ev.bgm_fade_target_flag);
11323 w.push_bool(ev.bgm_fade2_target_flag);
11324 w.push_bool(ev.bgm_fade_source_flag);
11325 w.push_bool(ev.real_flag);
11326 w.push_bool(ev.time_type);
11327 w.push_extend_items(&ev.lines, |w, line| {
11328 w.push_str(&line.file_name);
11329 w.push_i32(line.min_time);
11330 w.push_i32(line.max_time);
11331 w.push_i32(line.probability);
11332 });
11333 }
11334 }
11335
11336 fn read_cpp_pcm_event(
11337 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11338 ) -> Result<runtime::globals::PcmEventState> {
11339 let ty = rd.i32()?;
11340 let mut ev = runtime::globals::PcmEventState::default();
11341 if ty == runtime::globals::PCM_EVENT_TYPE_LOOP
11342 || ty == runtime::globals::PCM_EVENT_TYPE_RANDOM
11343 {
11344 let pcm_buf_no = rd.i32()?;
11345 let volume_type = rd.i32()?;
11346 let chara_no = rd.i32()?;
11347 let bgm_fade_target_flag = rd.bool()?;
11348 let bgm_fade2_target_flag = rd.bool()?;
11349 let bgm_fade_source_flag = rd.bool()?;
11350 let real_flag = rd.bool()?;
11351 let time_type = rd.bool()?;
11352 ev.lines = rd.extend_items(|rd| {
11353 Ok(runtime::globals::PcmEventLine {
11354 file_name: rd.string()?,
11355 min_time: rd.i32()?,
11356 max_time: rd.i32()?,
11357 probability: rd.i32()?,
11358 })
11359 })?;
11360 ev.start(
11363 ty,
11364 pcm_buf_no,
11365 volume_type,
11366 chara_no,
11367 bgm_fade_target_flag,
11368 bgm_fade2_target_flag,
11369 bgm_fade_source_flag,
11370 real_flag,
11371 time_type,
11372 );
11373 }
11374 Ok(ev)
11375 }
11376
11377 fn write_cpp_editbox(
11378 &self,
11379 w: &mut crate::original_save::OriginalStreamWriter,
11380 e: &runtime::globals::EditBoxState,
11381 ) {
11382 w.push_bool(e.created);
11383 w.push_i32(e.rect_x);
11384 w.push_i32(e.rect_y);
11385 w.push_i32(e.rect_w);
11386 w.push_i32(e.rect_h);
11387 w.push_i32(e.moji_size);
11388 }
11389
11390 fn read_cpp_editbox(
11391 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11392 ) -> Result<runtime::globals::EditBoxState> {
11393 let mut e = runtime::globals::EditBoxState::default();
11394 e.created = rd.bool()?;
11395 e.rect_x = rd.i32()?;
11396 e.rect_y = rd.i32()?;
11397 e.rect_w = rd.i32()?;
11398 e.rect_h = rd.i32()?;
11399 e.moji_size = rd.i32()?;
11400 e.text.clear();
11401 Ok(e)
11402 }
11403
11404 fn write_cpp_msg_back(&self, w: &mut crate::original_save::OriginalStreamWriter) {
11405 let msgbk = self
11406 .ctx
11407 .globals
11408 .msgbk_forms
11409 .values()
11410 .next()
11411 .cloned()
11412 .unwrap_or_default();
11413 w.push_i32(msgbk.history_cnt as i32);
11414 let count = msgbk.history_cnt.min(msgbk.history.len());
11415 for entry in msgbk.history.iter().take(count) {
11416 w.push_bool(entry.pct_flag);
11417 w.push_str(&entry.msg_str);
11418 w.push_str(&entry.original_name);
11419 w.push_str(&entry.disp_name);
11420 w.push_i32(entry.pct_pos_x);
11421 w.push_i32(entry.pct_pos_y);
11422 w.push_extend_i32_list(&entry.koe_no_list);
11423 w.push_extend_i32_list(&entry.chr_no_list);
11424 w.push_i32(Self::save_i32(entry.koe_play_no));
11425 w.push_str(&entry.debug_msg);
11426 w.push_i32(Self::save_i32(entry.scn_no));
11427 w.push_i32(Self::save_i32(entry.line_no));
11428 w.push_tid_zero();
11429 w.push_bool(entry.save_id_check_flag);
11430 }
11431 w.push_i32(msgbk.history_start_pos as i32);
11432 w.push_i32(msgbk.history_last_pos as i32);
11433 w.push_i32(msgbk.history_insert_pos as i32);
11434 w.push_i32(if msgbk.new_msg_flag { 1 } else { 0 });
11435 }
11436
11437 fn read_cpp_msg_back(
11438 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11439 ) -> Result<runtime::globals::MsgBackState> {
11440 if rd.layout == NativeLocalLayout::ShortElements {
11441 return Self::read_short_msg_back(rd);
11442 }
11443 if rd.layout.uses_early_records() {
11444 return Self::read_early_msg_back(rd);
11445 }
11446 let cnt = rd.i32()?.max(0) as usize;
11447 let mut st = runtime::globals::MsgBackState::default();
11448 st.history.clear();
11449 for _ in 0..cnt {
11450 let mut entry = runtime::globals::MsgBackEntry {
11451 pct_flag: rd.bool()?,
11452 msg_str: rd.string()?,
11453 original_name: rd.string()?,
11454 disp_name: rd.string()?,
11455 pct_pos_x: rd.i32()?,
11456 pct_pos_y: rd.i32()?,
11457 koe_no_list: rd.extend_i32_list()?,
11458 chr_no_list: rd.extend_i32_list()?,
11459 koe_play_no: rd.i32()? as i64,
11460 debug_msg: rd.string()?,
11461 scn_no: rd.i32()? as i64,
11462 line_no: rd.i32()? as i64,
11463 ..Default::default()
11464 };
11465
11466 rd.skip(14)?;
11467 if rd.layout == NativeLocalLayout::Current {
11468 entry.save_id_check_flag = rd.bool()?;
11469 }
11470 st.history.push(entry);
11471 }
11472 st.history_cnt = cnt;
11473 st.history_cnt_max = cnt.max(256);
11474 st.history_start_pos = rd.i32()?.max(0) as usize;
11475 st.history_last_pos = rd.i32()?.max(0) as usize;
11476 st.history_insert_pos = rd.i32()?.max(0) as usize;
11477 st.new_msg_flag = rd.i32()? != 0;
11478 if st.history.len() < st.history_cnt_max {
11479 st.history
11480 .resize_with(st.history_cnt_max, runtime::globals::MsgBackEntry::default);
11481 }
11482 Ok(st)
11483 }
11484
11485 fn read_saved_stage(
11486 &mut self,
11487 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11488 stage_idx: i64,
11489 ) -> Result<(
11490 runtime::globals::StageFormState,
11491 runtime::globals::BtnSelectRuntimeState,
11492 )> {
11493 match rd.layout {
11494 NativeLocalLayout::ShortElements => self.read_short_stage(rd, stage_idx),
11495 _ => Self::read_cpp_stage(rd, stage_idx),
11496 }
11497 }
11498
11499 fn parse_cpp_tail_state(
11500 &mut self,
11501 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11502 current_scene_name: &str,
11503 ) -> Result<Vec<CallFrame>> {
11504 self.read_cpp_inc_prop_list(rd)?;
11505 self.read_cpp_scene_prop_lists(rd, current_scene_name)?;
11506
11507 let counter_list = rd.fixed_items(|rd| Self::read_cpp_counter_param(rd))?;
11508 if !counter_list.is_empty() {
11509 self.ctx.globals.counter_lists.insert(
11510 crate::runtime::forms::codes::FORM_GLOBAL_COUNTER,
11511 counter_list,
11512 );
11513 }
11514
11515 let frame_action = Self::read_cpp_frame_action(rd)?;
11516 self.ctx
11517 .globals
11518 .frame_actions
11519 .insert(self.ctx.ids.form_global_frame_action, frame_action);
11520
11521 let frame_action_ch = rd.fixed_items(|rd| Self::read_cpp_frame_action(rd))?;
11522 if !frame_action_ch.is_empty() {
11523 self.ctx
11524 .globals
11525 .frame_action_lists
11526 .insert(self.ctx.ids.form_global_frame_action_ch, frame_action_ch);
11527 }
11528
11529 let g00buf_files = rd.fixed_items(|rd| rd.string())?;
11530 self.ctx.globals.g00buf.clear();
11531 self.ctx.globals.g00buf_names.clear();
11532 self.ctx.globals.g00buf.resize(g00buf_files.len(), None);
11533 self.ctx
11534 .globals
11535 .g00buf_names
11536 .resize(g00buf_files.len(), None);
11537 for (idx, name) in g00buf_files.into_iter().enumerate() {
11538 if !name.is_empty() {
11539 self.ctx.globals.g00buf_names[idx] = Some(name.clone());
11540 if let Ok(img_id) = self.ctx.images.load_g00(&name, 0) {
11541 self.ctx.globals.g00buf[idx] = Some(img_id);
11542 }
11543 }
11544 }
11545
11546 let masks = if rd.layout == NativeLocalLayout::ShortElements {
11547 Vec::new()
11548 } else {
11549 rd.fixed_items(|rd| {
11550 let x_event = Self::read_cpp_int_event_raw(rd)?;
11551 let y_event = Self::read_cpp_int_event_raw(rd)?;
11552 let name = rd.string()?;
11553 Ok(runtime::globals::MaskState {
11554 name: if name.is_empty() { None } else { Some(name) },
11555 x_event,
11556 y_event,
11557 extra_int: Default::default(),
11558 script_events: Default::default(),
11559 })
11560 })?
11561 };
11562 if !masks.is_empty() {
11563 self.ctx.globals.mask_lists.insert(
11564 self.ctx.ids.form_global_mask,
11565 runtime::globals::MaskListState { masks },
11566 );
11567 }
11568
11569 let mut st = runtime::globals::StageFormState::default();
11570 if rd.layout.uses_early_records() {
11572 let mut world_no = 0;
11573 let worlds = rd.fixed_items(|rd| {
11574 let world = Self::read_cpp_world(rd, world_no);
11575 world_no += 1;
11576 world
11577 })?;
11578 st.world_lists.insert(0, worlds.clone());
11579 st.world_lists.insert(1, worlds);
11580 }
11581 let (back, back_btn_select) = self.read_saved_stage(rd, 0)?;
11582 let (front, front_btn_select) = self.read_saved_stage(rd, 1)?;
11583 st.initialized_from_gameexe = true;
11584 st.group_lists.extend(back.group_lists);
11585 st.object_lists.extend(back.object_lists);
11586 st.mwnd_lists.extend(back.mwnd_lists);
11587 st.btn_select_states.extend(back.btn_select_states);
11588 st.world_lists.extend(back.world_lists);
11589 st.effect_lists.extend(back.effect_lists);
11590 st.quake_lists.extend(back.quake_lists);
11591 st.group_lists.extend(front.group_lists);
11592 st.object_lists.extend(front.object_lists);
11593 st.mwnd_lists.extend(front.mwnd_lists);
11594 st.btn_select_states.extend(front.btn_select_states);
11595 st.world_lists.extend(front.world_lists);
11596 st.effect_lists.extend(front.effect_lists);
11597 st.quake_lists.extend(front.quake_lists);
11598 let normal_stage_form_id = if self.ctx.ids.form_global_stage != 0 {
11599 self.ctx.ids.form_global_stage
11600 } else {
11601 runtime::forms::codes::FORM_GLOBAL_STAGE
11602 };
11603 self.ctx
11604 .globals
11605 .stage_forms
11606 .insert(normal_stage_form_id, Box::new(st));
11607 if !back_btn_select.choices.is_empty() {
11608 runtime::forms::global::prepare_saved_stage_btnselitems(
11609 &mut self.ctx,
11610 0,
11611 back_btn_select,
11612 );
11613 }
11614 self.ctx.globals.selbtn = front_btn_select;
11615 if !self.ctx.globals.selbtn.choices.is_empty() {
11616 runtime::forms::global::prepare_stage_btnselitems(&mut self.ctx);
11617 }
11618
11619 let screen = Self::read_cpp_screen(rd)?;
11620 self.ctx
11621 .globals
11622 .screen_forms
11623 .insert(self.ctx.ids.form_global_screen, screen);
11624
11625 self.read_cpp_sound(rd)?;
11626
11627 let pcm_events = rd.fixed_items(|rd| Self::read_cpp_pcm_event(rd))?;
11628 if !pcm_events.is_empty() {
11629 self.ctx
11630 .globals
11631 .pcm_event_lists
11632 .insert(self.ctx.ids.form_global_pcm_event, pcm_events);
11633 }
11634
11635 let mut editboxes = rd.fixed_items(|rd| Self::read_cpp_editbox(rd))?;
11636 if !editboxes.is_empty() {
11637 let screen_w = self.ctx.screen_w as i32;
11638 let screen_h = self.ctx.screen_h as i32;
11639 let display_mode_change_proc_cnt = self.ctx.globals.change_display_mode_proc_cnt;
11640 for eb in &mut editboxes {
11641 eb.design_screen_w = screen_w.max(1);
11642 eb.design_screen_h = screen_h.max(1);
11643 eb.update_rect(screen_w, screen_h);
11644 eb.frame(display_mode_change_proc_cnt);
11645 }
11646 let focused_idx = editboxes.iter().rposition(|eb| eb.created);
11647 let form_id = self.ctx.ids.form_global_editbox;
11648 self.ctx.globals.editbox_lists.insert(
11649 form_id,
11650 runtime::globals::EditBoxListState { boxes: editboxes },
11651 );
11652 self.ctx
11653 .set_focused_editbox(focused_idx.map(|idx| (form_id, idx)));
11654 }
11655
11656 let call_cnt = rd.i32()?.max(0) as usize;
11657 let mut call_stack = Vec::with_capacity(call_cnt.max(1));
11658 for _ in 0..call_cnt {
11659 call_stack.push(self.read_cpp_call_frame(rd)?);
11660 }
11661 if rd.layout == NativeLocalLayout::ShortElements {
11662 self.normalize_short_call_returns(&mut call_stack);
11663 }
11664 if call_stack.is_empty() {
11665 call_stack.push(self.scene_base_call());
11666 }
11667
11668 let msg_back = Self::read_cpp_msg_back(rd)?;
11669 self.ctx
11670 .globals
11671 .msgbk_forms
11672 .insert(self.ctx.ids.form_global_msgbk, msg_back);
11673
11674 if rd.layout == NativeLocalLayout::ShortElements {
11677 anyhow::ensure!(
11678 rd.remaining().is_empty(),
11679 "unexpected trailing short-element save data"
11680 );
11681 self.ctx.globals.syscom.sel_save_stock_stream.clear();
11682 self.ctx.globals.syscom.inner_save_streams.clear();
11683 self.ctx.globals.syscom.inner_save_exists = false;
11684 self.ctx.globals.syscom.sel_save_ids.clear();
11685 return Ok(call_stack);
11686 }
11687
11688 self.ctx.globals.syscom.sel_save_stock_stream = rd.len_bytes()?;
11689 let inner_cnt = rd.i32()?.max(0) as usize;
11690 self.ctx.globals.syscom.inner_save_streams.clear();
11691 for _ in 0..inner_cnt {
11692 self.ctx
11693 .globals
11694 .syscom
11695 .inner_save_streams
11696 .push(rd.len_bytes()?);
11697 }
11698 self.ctx.globals.syscom.inner_save_exists = self
11699 .ctx
11700 .globals
11701 .syscom
11702 .inner_save_streams
11703 .iter()
11704 .any(|s| !s.is_empty());
11705 let sel_save_cnt = if rd.layout.is_indexed() {
11706 0
11707 } else {
11708 rd.i32()?.max(0) as usize
11709 };
11710 self.ctx.globals.syscom.sel_save_ids.clear();
11711 for _ in 0..sel_save_cnt {
11712 self.ctx.globals.syscom.sel_save_ids.push(rd.tid()?);
11713 }
11714 Ok(call_stack)
11715 }
11716
11717 fn restore_saved_scene_stack(
11718 &mut self,
11719 mut frames: Vec<CallFrame>,
11720 current_scene_name: &str,
11721 ) -> Result<Vec<CallFrame>> {
11722 self.scene_stack.clear();
11728 if self.save_load_trace_enabled() {
11729 for (index, frame) in frames.iter().enumerate() {
11730 eprintln!(
11731 "[SG_SAVELOAD_TRACE][CALL] index={index} type={} ret_form={} scene={:?} line={} pc=0x{:x}",
11732 frame.call_type,
11733 frame.ret_form,
11734 frame.return_scene_name,
11735 frame.return_line_no,
11736 frame.return_pc,
11737 );
11738 }
11739 }
11740 if frames.is_empty() {
11741 return Ok(vec![self.scene_base_call()]);
11742 }
11743
11744 for frame in &mut frames {
11748 frame.return_scene_no = frame.return_scene_name.as_deref().and_then(|name| {
11749 self.scene_pck_cache
11750 .as_ref()
11751 .and_then(|cache| cache.find_scene_no(name))
11752 });
11753 }
11754
11755 for callee_idx in 1..frames.len() {
11756 let call_type = frames[callee_idx].call_type;
11757 if call_type != 2 && call_type != 3 {
11758 continue;
11759 }
11760
11761 let Some(caller_scene_name) = frames[callee_idx - 1]
11762 .return_scene_name
11763 .as_deref()
11764 .filter(|name| !name.is_empty())
11765 else {
11766 continue;
11769 };
11770
11771 let callee_scene_name = if callee_idx + 1 == frames.len() {
11772 Some(current_scene_name)
11773 } else {
11774 frames[callee_idx]
11775 .return_scene_name
11776 .as_deref()
11777 .filter(|name| !name.is_empty())
11778 };
11779 let Some(callee_scene_name) = callee_scene_name else {
11780 continue;
11781 };
11782
11783 if caller_scene_name.eq_ignore_ascii_case(callee_scene_name) {
11787 continue;
11788 }
11789
11790 let Some(caller_scene_no) = self
11791 .scene_pck_cache
11792 .as_ref()
11793 .and_then(|cache| cache.find_scene_no(caller_scene_name))
11794 else {
11795 log::warn!(
11796 "[SG_SAVELOAD] saved caller scene not found in active Scene.pck: {}",
11797 caller_scene_name
11798 );
11799 continue;
11800 };
11801
11802 let mut stream = self.cached_scene_stream(caller_scene_no)?;
11803 stream.set_prg_cntr(frames[callee_idx - 1].return_pc)?;
11804 let user_cmd_names = stream.scn_cmd_name_map.clone();
11805 let call_cmd_names = self
11806 .scene_pck_cache
11807 .as_ref()
11808 .expect("scene pck cache initialized")
11809 .inc_cmd_name_map
11810 .clone();
11811
11812 self.scene_stack.push(SceneExecFrame {
11813 stream,
11814 user_cmd_names,
11815 call_cmd_names,
11816 current_scene_no: Some(caller_scene_no),
11817 current_scene_name: Some(caller_scene_name.to_string()),
11818 current_line_no: frames[callee_idx - 1].return_line_no,
11819 call_depth: callee_idx + 1,
11823 });
11824 }
11825
11826 Ok(frames)
11827 }
11828
11829 fn write_cpp_proc_record(
11830 w: &mut crate::original_save::OriginalStreamWriter,
11831 proc_type: i32,
11832 element: &[i32],
11833 option: i32,
11834 ) {
11835 w.push_i32(proc_type);
11836 w.push_element(element);
11837 w.push_i32(0);
11838 w.push_i32(0);
11839 w.push_bool(false);
11840 w.push_bool(false);
11841 w.push_bool(false);
11842 w.push_i32(option);
11843 }
11844
11845 fn write_cpp_runtime_proc_stack(&self, w: &mut crate::original_save::OriginalStreamWriter) {
11846 let proc_type = if matches!(self.ctx.last_proc_kind(), runtime::ProcKind::Script) {
11851 1
11852 } else {
11853 0
11854 };
11855 Self::write_cpp_proc_record(w, proc_type, &[], 0);
11856 w.push_i32(0);
11857 }
11858
11859 fn read_cpp_proc_record(
11860 &self,
11861 rd: &mut crate::original_save::OriginalStreamReader<'_>,
11862 ) -> Result<()> {
11863 let _proc_type = rd.i32()?;
11864 let _element = rd.element()?;
11865 let _arg_list_id = rd.i32()?;
11866 let _arg_list: Vec<()> = rd.extend_items(|rd| {
11867 let _ = self.read_cpp_prop(rd)?;
11868 Ok(())
11869 })?;
11870 rd.skip(rd.layout.proc_trailer_bytes())?;
11871 Ok(())
11872 }
11873
11874 fn decode_cpp_mwnd_element(elm: &[i32]) -> Option<(i64, usize)> {
11875 let is_array = |v: i32| v == crate::runtime::forms::codes::ELM_ARRAY || v == -1;
11876 if elm.len() >= 4 {
11877 let stage_idx = if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_BACK {
11878 Some(0)
11879 } else if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_FRONT {
11880 Some(1)
11881 } else if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_NEXT {
11882 Some(2)
11883 } else {
11884 None
11885 };
11886 if let Some(stage_idx) = stage_idx
11887 && elm[1] == crate::runtime::forms::codes::ELM_STAGE_MWND
11888 && is_array(elm[2])
11889 && elm[3] >= 0
11890 {
11891 return Some((stage_idx, elm[3] as usize));
11892 }
11893 }
11894 if elm.len() >= 6
11895 && elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_STAGE
11896 && is_array(elm[1])
11897 && elm[2] >= 0
11898 && elm[3] == crate::runtime::forms::codes::ELM_STAGE_MWND
11899 && is_array(elm[4])
11900 && elm[5] >= 0
11901 {
11902 return Some((elm[2] as i64, elm[5] as usize));
11903 }
11904 None
11905 }
11906
11907 fn apply_saved_current_mwnd_elements(
11908 &mut self,
11909 cur_mwnd: &[i32],
11910 cur_sel_mwnd: &[i32],
11911 last_mwnd: &[i32],
11912 ) {
11913 self.ctx.globals.current_mwnd_element = cur_mwnd.to_vec();
11914 self.ctx.globals.current_sel_mwnd_element = cur_sel_mwnd.to_vec();
11915 self.ctx.globals.last_mwnd_element = last_mwnd.to_vec();
11916 self.ctx.globals.current_mwnd_no = None;
11917 self.ctx.globals.current_sel_mwnd_no = None;
11918 self.ctx.globals.last_mwnd_no = None;
11919
11920 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(cur_mwnd) {
11921 self.ctx.globals.current_mwnd_stage_idx = stage;
11922 self.ctx.globals.current_mwnd_no = Some(no);
11923 }
11924 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(cur_sel_mwnd) {
11925 self.ctx.globals.current_sel_mwnd_stage_idx = stage;
11926 self.ctx.globals.current_sel_mwnd_no = Some(no);
11927 }
11928 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(last_mwnd) {
11929 self.ctx.globals.last_mwnd_stage_idx = stage;
11930 self.ctx.globals.last_mwnd_no = Some(no);
11931 }
11932 }
11933
11934 fn current_local_save_id(&self) -> [u16; 7] {
11935 let now = crate::platform_time::local_time_fields();
11936 [
11937 now.year.clamp(0, u16::MAX as i32) as u16,
11938 now.month as u16,
11939 now.day as u16,
11940 now.hour as u16,
11941 now.minute as u16,
11942 now.second as u16,
11943 now.millisecond as u16,
11944 ]
11945 }
11946
11947 fn build_local_save_snapshot(&mut self) {
11952 let local_stream = self.build_original_local_stream();
11953 let local_ex_stream = self.build_original_local_ex_stream();
11954 let snapshot = crate::runtime::LocalSaveSnapshot {
11955 save_id: self.current_local_save_id(),
11956 append_dir: self.ctx.globals.append_dir.clone(),
11957 append_name: self.ctx.globals.append_name.clone(),
11958 save_scene_title: self.ctx.globals.syscom.current_save_scene_title.clone(),
11959 save_msg: String::new(),
11960 save_full_msg: self.ctx.globals.syscom.current_save_full_message.clone(),
11961 local_stream,
11962 local_ex_stream,
11963 sel_saves: self
11964 .ctx
11965 .local_save_snapshot
11966 .as_ref()
11967 .map(|s| s.sel_saves.clone())
11968 .unwrap_or_default(),
11969 };
11970 self.ctx.local_save_snapshot = Some(snapshot);
11971 }
11972
11973 fn build_original_local_stream(&self) -> Vec<u8> {
11974 let mut w = crate::original_save::OriginalStreamWriter::new();
11975 let scene_name = self.current_scene_name.as_deref().unwrap_or("");
11976 let flag_cnt = self.local_flag_count();
11977 use crate::runtime::forms::codes;
11978
11979 w.push_str(scene_name);
11980 w.push_i32(self.current_line_no);
11981 w.push_i32(self.stream.get_prg_cntr() as i32);
11982
11983 self.write_cpp_runtime_proc_stack(&mut w);
11984 w.push_element(&self.ctx.globals.current_mwnd_element);
11985 w.push_element(&self.ctx.globals.current_sel_mwnd_element);
11986 w.push_element(&self.ctx.globals.last_mwnd_element);
11987 w.push_str(&self.ctx.globals.syscom.current_save_scene_title);
11988 let current_full_message = if self.ctx.globals.syscom.current_save_full_message.is_empty() {
11989 self.ctx.globals.syscom.current_save_message.as_str()
11990 } else {
11991 self.ctx.globals.syscom.current_save_full_message.as_str()
11992 };
11993 w.push_str(current_full_message);
11994
11995 let btn_cnt = self.mwnd_waku_btn_count();
11996 for idx in 0..btn_cnt {
11997 w.push_bool(
11998 self.ctx
11999 .globals
12000 .syscom
12001 .mwnd_btn_disable
12002 .get(&(idx as i64))
12003 .copied()
12004 .unwrap_or(false),
12005 );
12006 }
12007 w.push_str(&self.ctx.globals.script.font_name);
12008 w.push_raw(&self.build_cpp_local_data_pod());
12009
12010 w.push_i32(self.int_stack.len() as i32);
12011 for v in &self.int_stack {
12012 w.push_i32(*v);
12013 }
12014 w.push_i32(self.str_stack.len() as i32);
12015 for s in &self.str_stack {
12016 w.push_str(s);
12017 }
12018 w.push_i32(self.element_points.len() as i32);
12019 for p in &self.element_points {
12020 w.push_i32(*p as i32);
12021 }
12022
12023 w.push_i32(
12024 self.ctx
12025 .globals
12026 .local_real_time
12027 .clamp(i32::MIN as i64, i32::MAX as i64) as i32,
12028 );
12029 w.push_i32(
12030 self.ctx
12031 .globals
12032 .local_game_time
12033 .clamp(i32::MIN as i64, i32::MAX as i64) as i32,
12034 );
12035 w.push_i32(
12036 self.ctx
12037 .globals
12038 .local_wipe_time
12039 .clamp(i32::MIN as i64, i32::MAX as i64) as i32,
12040 );
12041 self.write_cpp_syscom_menu(&mut w);
12042
12043 let fog = &self.ctx.globals.fog_global;
12044 w.push_str(&fog.name);
12045 Self::write_cpp_int_event_raw(&mut w, &fog.x_event);
12046 w.push_i32(fog.near as i32);
12047 w.push_i32(fog.far as i32);
12048
12049 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_A), flag_cnt);
12050 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_B), flag_cnt);
12051 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_C), flag_cnt);
12052 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_D), flag_cnt);
12053 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_E), flag_cnt);
12054 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_F), flag_cnt);
12055 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_X), flag_cnt);
12056 w.push_fixed_str_list(self.str_list_by_element(codes::ELM_GLOBAL_S), flag_cnt);
12057 w.push_extend_i32_list(&self.ctx.globals.local_flag_h);
12058 w.push_extend_i32_list(&self.ctx.globals.local_flag_i);
12059 w.push_extend_i32_list(&self.ctx.globals.local_flag_j);
12060 w.push_fixed_str_list(
12061 self.str_list_by_element(codes::ELM_GLOBAL_NAMAE_LOCAL),
12062 26 + 26 * 26,
12063 );
12064
12065 self.write_cpp_inc_prop_list(&mut w);
12066 self.write_cpp_current_scene_prop_lists(&mut w);
12067
12068 let counter_list = self
12069 .ctx
12070 .globals
12071 .counter_lists
12072 .values()
12073 .next()
12074 .cloned()
12075 .unwrap_or_default();
12076 w.push_fixed_items(&counter_list, |w, c| self.write_cpp_counter_param(w, c));
12077
12078 let frame_action = self
12079 .ctx
12080 .globals
12081 .frame_actions
12082 .values()
12083 .next()
12084 .cloned()
12085 .unwrap_or_default();
12086 self.write_cpp_frame_action(&mut w, &frame_action);
12087
12088 let frame_action_ch = self
12089 .ctx
12090 .globals
12091 .frame_action_lists
12092 .values()
12093 .next()
12094 .cloned()
12095 .unwrap_or_default();
12096 w.push_fixed_items(&frame_action_ch, |w, fa| self.write_cpp_frame_action(w, fa));
12097
12098 w.push_fixed_items(&self.ctx.globals.g00buf_names, |w, name| {
12100 w.push_str(name.as_deref().unwrap_or(""))
12101 });
12102
12103 let mask_list = self
12104 .ctx
12105 .globals
12106 .mask_lists
12107 .values()
12108 .next()
12109 .map(|m| m.masks.clone())
12110 .unwrap_or_default();
12111 w.push_fixed_items(&mask_list, |w, m| {
12112 Self::write_cpp_int_event_raw(w, &m.x_event);
12113 Self::write_cpp_int_event_raw(w, &m.y_event);
12114 w.push_str(m.name.as_deref().unwrap_or(""));
12115 });
12116
12117 self.write_cpp_stage(&mut w, 0);
12118 self.write_cpp_stage(&mut w, 1);
12119 self.write_cpp_screen(&mut w);
12120 self.write_cpp_sound(&mut w);
12121
12122 let pcm_events = self
12123 .ctx
12124 .globals
12125 .pcm_event_lists
12126 .values()
12127 .next()
12128 .cloned()
12129 .unwrap_or_default();
12130 w.push_fixed_items(&pcm_events, |w, ev| self.write_cpp_pcm_event(w, ev));
12131
12132 let editboxes = self
12133 .ctx
12134 .globals
12135 .editbox_lists
12136 .values()
12137 .next()
12138 .map(|e| e.boxes.clone())
12139 .unwrap_or_default();
12140 w.push_fixed_items(&editboxes, |w, e| self.write_cpp_editbox(w, e));
12141
12142 let saved_call_stack = self.flattened_call_stack_for_save();
12143 w.push_i32(saved_call_stack.len() as i32);
12144 for frame in &saved_call_stack {
12145 self.write_cpp_call_frame(&mut w, frame);
12146 }
12147 self.write_cpp_msg_back(&mut w);
12148
12149 w.push_len_bytes(&self.ctx.globals.syscom.sel_save_stock_stream);
12150 w.push_i32(self.ctx.globals.syscom.inner_save_streams.len() as i32);
12151 for stream in &self.ctx.globals.syscom.inner_save_streams {
12152 w.push_len_bytes(stream);
12153 }
12154 w.push_i32(self.ctx.globals.syscom.sel_save_ids.len() as i32);
12155 for tid in &self.ctx.globals.syscom.sel_save_ids {
12156 w.push_tid(tid);
12157 }
12158 w.into_inner()
12159 }
12160
12161 fn build_original_local_ex_stream(&self) -> Vec<u8> {
12162 let mut w = crate::original_save::OriginalStreamWriter::new();
12163 let s = &self.ctx.globals.syscom;
12164 for i in 0..4 {
12165 let sw = s.local_extra_switches.get(i).copied().unwrap_or(if i == 0 {
12166 s.local_extra_switch
12167 } else {
12168 runtime::globals::ToggleFeatureState::default()
12169 });
12170 w.push_bool(sw.exist);
12171 w.push_bool(sw.enable);
12172 w.push_bool(sw.onoff);
12173 }
12174 for i in 0..4 {
12175 let mode = s.local_extra_modes.get(i).copied().unwrap_or(if i == 0 {
12176 s.local_extra_mode
12177 } else {
12178 runtime::globals::ValueFeatureState::default()
12179 });
12180 w.push_bool(mode.exist);
12181 w.push_bool(mode.enable);
12182 w.push_padding(2);
12183 w.push_i32(mode.value as i32);
12184 }
12185 let out = w.into_inner();
12186 debug_assert_eq!(out.len(), 44);
12187 out
12188 }
12189
12190 fn parse_original_local_ex_stream(&mut self, local_ex_stream: &[u8]) -> Result<()> {
12191 if local_ex_stream.len() < 44 {
12192 return Ok(());
12193 }
12194 let mut rd = crate::original_save::OriginalStreamReader::new(local_ex_stream);
12195 for i in 0..4 {
12196 self.ctx.globals.syscom.local_extra_switches[i].exist = rd.bool()?;
12197 self.ctx.globals.syscom.local_extra_switches[i].enable = rd.bool()?;
12198 self.ctx.globals.syscom.local_extra_switches[i].onoff = rd.bool()?;
12199 }
12200 for i in 0..4 {
12201 self.ctx.globals.syscom.local_extra_modes[i].exist = rd.bool()?;
12202 self.ctx.globals.syscom.local_extra_modes[i].enable = rd.bool()?;
12203 rd.skip(2)?;
12204 self.ctx.globals.syscom.local_extra_modes[i].value = rd.i32()? as i64;
12205 }
12206 self.ctx.globals.syscom.local_extra_switch =
12207 self.ctx.globals.syscom.local_extra_switches[0];
12208 self.ctx.globals.syscom.local_extra_mode = self.ctx.globals.syscom.local_extra_modes[0];
12209 Ok(())
12210 }
12211
12212 fn parse_original_local_stream(&mut self, local_stream: &[u8]) -> Result<RuntimeDiskSnapshot> {
12213 let mut rd = crate::original_save::OriginalStreamReader::new(local_stream);
12214 let flag_cnt = self.local_flag_count();
12215 use crate::runtime::forms::codes;
12216
12217 rd.layout = self
12218 .detect_indexed_local_layout(local_stream)
12219 .unwrap_or_default();
12220 let scene_name = if rd.layout.is_indexed() {
12221 self.indexed_scene_name(rd.i32()?)?
12222 } else {
12223 rd.string()?
12224 };
12225 let line_no = rd.i32()?;
12226 let pc = rd.i32()?;
12227
12228 self.read_cpp_proc_record(&mut rd)?;
12229 let proc_stack_cnt = rd.i32()?.max(0) as usize;
12230 for _ in 0..proc_stack_cnt {
12231 self.read_cpp_proc_record(&mut rd)?;
12232 }
12233 let cur_mwnd = rd.element()?;
12234 let cur_sel_mwnd = rd.element()?;
12235 let last_mwnd = rd.element()?;
12236 self.apply_saved_current_mwnd_elements(&cur_mwnd, &cur_sel_mwnd, &last_mwnd);
12237 self.ctx.globals.syscom.current_save_message.clear();
12238 if rd.layout.is_indexed() {
12239 match rd.layout {
12240 NativeLocalLayout::ShortElements => self.read_short_local_settings(&mut rd)?,
12241 NativeLocalLayout::Indexed => self.read_indexed_local_settings(&mut rd)?,
12242 _ => unreachable!(),
12243 }
12244 } else {
12245 self.ctx.globals.syscom.current_save_scene_title = rd.string()?;
12246 let btn_cnt = self.mwnd_waku_btn_count();
12247 rd.detect_early_local_layout(btn_cnt);
12248 self.ctx.globals.syscom.current_save_full_message = if rd.layout.uses_early_records() {
12249 String::new()
12250 } else {
12251 rd.string()?
12252 };
12253 self.ctx.globals.syscom.mwnd_btn_disable.clear();
12254 for idx in 0..btn_cnt {
12255 if rd.bool()? {
12256 self.ctx
12257 .globals
12258 .syscom
12259 .mwnd_btn_disable
12260 .insert(idx as i64, true);
12261 }
12262 }
12263 rd.detect_local_layout()?;
12264 let has_font = rd.layout == NativeLocalLayout::Current;
12265 self.ctx.globals.script.font_name = if has_font {
12266 rd.string()?
12267 } else {
12268 String::new()
12269 };
12270 self.read_cpp_local_data_pod(&mut rd, has_font)?;
12271 }
12272
12273 let int_cnt = rd.i32()?.max(0) as usize;
12274 let mut int_stack = Vec::with_capacity(int_cnt);
12275 for _ in 0..int_cnt {
12276 int_stack.push(rd.i32()?);
12277 }
12278 let str_cnt = rd.i32()?.max(0) as usize;
12279 let mut str_stack = Vec::with_capacity(str_cnt);
12280 for _ in 0..str_cnt {
12281 str_stack.push(rd.string()?);
12282 }
12283 let ep_cnt = rd.i32()?.max(0) as usize;
12284 let mut element_points = Vec::with_capacity(ep_cnt);
12285 for _ in 0..ep_cnt {
12286 element_points.push(rd.i32()?.max(0) as usize);
12287 }
12288
12289 self.ctx.globals.local_real_time = rd.i32()? as i64;
12290 self.ctx.globals.local_game_time = rd.i32()? as i64;
12291 self.ctx.globals.local_wipe_time = rd.i32()? as i64;
12292 self.read_cpp_syscom_menu(&mut rd)?;
12293
12294 if rd.layout != NativeLocalLayout::ShortElements {
12295 let fog_name = rd.string()?;
12296 let fog_x = Self::read_cpp_int_event_raw(&mut rd)?;
12297 let fog_near = rd.i32()?;
12298 let fog_far = rd.i32()?;
12299 self.ctx.globals.fog_global.name = fog_name;
12300 self.ctx.globals.fog_global.enabled = !self.ctx.globals.fog_global.name.is_empty();
12301 self.ctx.globals.fog_global.texture_image_id = None;
12302 if self.ctx.globals.fog_global.enabled {
12303 match self
12304 .ctx
12305 .images
12306 .load_g00(&self.ctx.globals.fog_global.name, 0)
12307 {
12308 Ok(id) => self.ctx.globals.fog_global.texture_image_id = Some(id),
12309 Err(e) => log::error!(
12310 "load_local fog texture '{}' failed: {e}",
12311 self.ctx.globals.fog_global.name
12312 ),
12313 }
12314 }
12315 self.ctx.globals.fog_global.x_event = fog_x;
12316 self.ctx.globals.fog_global.scroll_x =
12317 self.ctx.globals.fog_global.x_event.get_total_value() as f32;
12318 self.ctx.globals.fog_global.near = fog_near as f32;
12319 self.ctx.globals.fog_global.far = fog_far as f32;
12320 } else {
12321 self.ctx.globals.fog_global = Default::default();
12322 }
12323
12324 let a = rd.fixed_i32_list()?;
12325 let b = rd.fixed_i32_list()?;
12326 let c = rd.fixed_i32_list()?;
12327 let d = rd.fixed_i32_list()?;
12328 let e = rd.fixed_i32_list()?;
12329 let f = rd.fixed_i32_list()?;
12330 let x = if rd.layout == NativeLocalLayout::ShortElements {
12331 Vec::new()
12332 } else {
12333 rd.fixed_i32_list()?
12334 };
12335 let s = rd.fixed_str_list()?;
12336 let h = rd.extend_i32_list()?;
12337 let i = rd.extend_i32_list()?;
12338 let j = rd.extend_i32_list()?;
12339 let namae_local = rd.fixed_str_list()?;
12340 self.ctx
12341 .globals
12342 .int_lists
12343 .insert(codes::ELM_GLOBAL_A as u32, resize_i64_vec(a, flag_cnt));
12344 self.ctx
12345 .globals
12346 .int_lists
12347 .insert(codes::ELM_GLOBAL_B as u32, resize_i64_vec(b, flag_cnt));
12348 self.ctx
12349 .globals
12350 .int_lists
12351 .insert(codes::ELM_GLOBAL_C as u32, resize_i64_vec(c, flag_cnt));
12352 self.ctx
12353 .globals
12354 .int_lists
12355 .insert(codes::ELM_GLOBAL_D as u32, resize_i64_vec(d, flag_cnt));
12356 self.ctx
12357 .globals
12358 .int_lists
12359 .insert(codes::ELM_GLOBAL_E as u32, resize_i64_vec(e, flag_cnt));
12360 self.ctx
12361 .globals
12362 .int_lists
12363 .insert(codes::ELM_GLOBAL_F as u32, resize_i64_vec(f, flag_cnt));
12364 self.ctx
12365 .globals
12366 .int_lists
12367 .insert(codes::ELM_GLOBAL_X as u32, resize_i64_vec(x, flag_cnt));
12368 self.ctx
12369 .globals
12370 .str_lists
12371 .insert(codes::ELM_GLOBAL_S as u32, resize_string_vec(s, flag_cnt));
12372 self.ctx.globals.local_flag_h = h;
12373 self.ctx.globals.local_flag_i = i;
12374 self.ctx.globals.local_flag_j = j;
12375 self.ctx.globals.str_lists.insert(
12376 codes::ELM_GLOBAL_NAMAE_LOCAL as u32,
12377 resize_string_vec(namae_local, 26 + 26 * 26),
12378 );
12379
12380 let call_stack = self.parse_cpp_tail_state(&mut rd, &scene_name)?;
12381
12382 Ok(RuntimeDiskSnapshot {
12383 scene_name,
12384 scene_no: -1,
12385 line_no,
12386 pc,
12387 int_stack,
12388 str_stack,
12389 element_points,
12390 call_stack,
12391 })
12392 }
12393
12394 #[inline(always)]
12395 fn save_load_trace_enabled(&self) -> bool {
12396 self.runtime_options.save_load_trace
12397 }
12398
12399 fn perform_runtime_save_request(&mut self, req: RuntimeSaveRequest) -> Result<()> {
12400 if req.kind == RuntimeSaveKind::Inner {
12401 let Some(snapshot) = self.ctx.local_save_snapshot.as_ref() else {
12405 log::error!(
12406 "[SG_SAVELOAD] inner save dropped idx={}: no local_save snapshot",
12407 req.index
12408 );
12409 return Ok(());
12410 };
12411 if self.save_load_trace_enabled() {
12412 eprintln!("[SG_SAVELOAD_TRACE][VM] save inner idx={}", req.index);
12413 }
12414 if self.ctx.globals.syscom.inner_save_streams.len() <= req.index {
12415 self.ctx
12416 .globals
12417 .syscom
12418 .inner_save_streams
12419 .resize_with(req.index + 1, Vec::new);
12420 }
12421 self.ctx.globals.syscom.inner_save_streams[req.index] = snapshot.local_stream.clone();
12422 self.ctx.globals.syscom.inner_save_exists = true;
12423 return Ok(());
12424 }
12425
12426 if self.ctx.local_save_snapshot.is_none() {
12431 log::error!(
12432 "[SG_SAVELOAD] save dropped (kind={:?} idx={}): no local_save snapshot. \
12433 SAVEPOINT has not fired in the current message block - either the script \
12434 set dont_set_save_point or auto-SAVEPOINT wasn't reached yet. No file written.",
12435 req.kind,
12436 req.index
12437 );
12438 if req.kind == RuntimeSaveKind::Normal {
12439 crate::runtime::forms::syscom::free_runtime_save_thumb_capture(
12440 &mut self.ctx,
12441 crate::runtime::forms::syscom::CAPTURE_PRIOR_SAVE,
12442 );
12443 }
12444 return Ok(());
12445 }
12446
12447 let refreshed_ex = self.build_original_local_ex_stream();
12450 if let Some(snapshot) = self.ctx.local_save_snapshot.as_mut() {
12451 snapshot.local_ex_stream = refreshed_ex;
12452 }
12453
12454 let slot = self.ensure_runtime_slot_for_save(req);
12455 let Some(path) = self.runtime_save_file_path(req.kind, req.index) else {
12456 if req.kind == RuntimeSaveKind::Normal {
12457 crate::runtime::forms::syscom::free_runtime_save_thumb_capture(
12458 &mut self.ctx,
12459 crate::runtime::forms::syscom::CAPTURE_PRIOR_SAVE,
12460 );
12461 }
12462 return Ok(());
12463 };
12464 if self.save_load_trace_enabled() {
12465 eprintln!(
12466 "[SG_SAVELOAD_TRACE][VM] save begin kind={:?} idx={} path={} file_exists_before={}",
12467 req.kind,
12468 req.index,
12469 path.display(),
12470 crate::resource::game_file_exists(&path)
12471 );
12472 }
12473 let snapshot = self
12474 .ctx
12475 .local_save_snapshot
12476 .as_ref()
12477 .expect("snapshot presence checked above");
12478 let env = crate::original_save::OriginalLocalSaveEnvelope {
12479 save_id: snapshot.save_id,
12480 append_dir: snapshot.append_dir.clone(),
12481 append_name: snapshot.append_name.clone(),
12482 title: snapshot.save_scene_title.clone(),
12483 message: snapshot.save_msg.clone(),
12484 full_message: snapshot.save_full_msg.clone(),
12485 local_stream: snapshot.local_stream.clone(),
12486 local_ex_stream: snapshot.local_ex_stream.clone(),
12487 sel_saves: snapshot.sel_saves.clone(),
12488 };
12489 if let Err(err) = crate::original_save::write_local_save_file(&path, &slot, &env) {
12490 if req.kind == RuntimeSaveKind::Normal {
12491 crate::runtime::forms::syscom::free_runtime_save_thumb_capture(
12492 &mut self.ctx,
12493 crate::runtime::forms::syscom::CAPTURE_PRIOR_SAVE,
12494 );
12495 }
12496 return Err(err);
12497 }
12498 crate::runtime::forms::syscom::write_global_save(&self.ctx);
12499 if self.save_load_trace_enabled() {
12500 eprintln!(
12501 "[SG_SAVELOAD_TRACE][VM] save written kind={:?} idx={} path={} bytes={}",
12502 req.kind,
12503 req.index,
12504 path.display(),
12505 crate::resource::game_file_len(&path).unwrap_or(0)
12506 );
12507 }
12508 match req.kind {
12512 RuntimeSaveKind::Normal => {
12513 if self.ctx.globals.syscom.save_slots.len() <= req.index {
12514 self.ctx
12515 .globals
12516 .syscom
12517 .save_slots
12518 .resize_with(req.index + 1, Default::default);
12519 }
12520 self.ctx.globals.syscom.save_slots[req.index].header_cache_valid = false;
12521 }
12522 RuntimeSaveKind::Quick => {
12523 if self.ctx.globals.syscom.quick_save_slots.len() <= req.index {
12524 self.ctx
12525 .globals
12526 .syscom
12527 .quick_save_slots
12528 .resize_with(req.index + 1, Default::default);
12529 }
12530 self.ctx.globals.syscom.quick_save_slots[req.index].header_cache_valid = false;
12531 }
12532 RuntimeSaveKind::End => {
12533 self.ctx.globals.syscom.end_save_exists = true;
12534 }
12535 RuntimeSaveKind::Inner => {}
12536 }
12537 if let Some(save_kind) = Self::save_kind_to_original(req.kind) {
12538 let save_no = crate::original_save::original_save_no(
12539 self.configured_runtime_save_count(false),
12540 self.configured_runtime_save_count(true),
12541 save_kind,
12542 req.index,
12543 );
12544 if self.save_load_trace_enabled() {
12545 eprintln!(
12546 "[SG_SAVELOAD_TRACE][VM] save thumb write kind={:?} idx={} original_save_no={}",
12547 req.kind, req.index, save_no
12548 );
12549 }
12550 crate::runtime::forms::syscom::write_runtime_slot_thumb(&mut self.ctx, save_no);
12551 }
12552 if req.kind == RuntimeSaveKind::Normal {
12553 crate::runtime::forms::syscom::free_runtime_save_thumb_capture(
12554 &mut self.ctx,
12555 crate::runtime::forms::syscom::CAPTURE_PRIOR_SAVE,
12556 );
12557 }
12558 Ok(())
12559 }
12560
12561 fn perform_runtime_load_request(&mut self, req: RuntimeLoadRequest) -> Result<()> {
12562 if self.save_load_trace_enabled() {
12563 eprintln!(
12564 "[SG_SAVELOAD_TRACE][VM] load begin kind={:?} idx={}",
12565 req.kind, req.index
12566 );
12567 }
12568 struct LoadedEnvelopeMeta {
12569 save_id: [u16; 7],
12570 append_dir: String,
12571 append_name: String,
12572 title: String,
12573 message: String,
12574 full_message: String,
12575 sel_saves: Vec<crate::original_save::OriginalLocalSaveEnvelope>,
12576 }
12577 let (local_stream, local_ex_stream, loaded_meta) = if req.kind == RuntimeSaveKind::Inner {
12578 let Some(stream) = self
12579 .ctx
12580 .globals
12581 .syscom
12582 .inner_save_streams
12583 .get(req.index)
12584 .cloned()
12585 else {
12586 return Ok(());
12587 };
12588 (stream, Vec::new(), None)
12589 } else {
12590 let Some(path) = self.runtime_save_file_path(req.kind, req.index) else {
12591 return Ok(());
12592 };
12593 if self.save_load_trace_enabled() {
12594 eprintln!(
12595 "[SG_SAVELOAD_TRACE][VM] load read kind={:?} idx={} path={} file_exists={}",
12596 req.kind,
12597 req.index,
12598 path.display(),
12599 crate::resource::game_file_exists(&path)
12600 );
12601 }
12602 let (_header, env) = crate::original_save::read_local_save_file(&path)?;
12603 let meta = LoadedEnvelopeMeta {
12604 save_id: env.save_id,
12605 append_dir: env.append_dir.clone(),
12606 append_name: env.append_name.clone(),
12607 title: env.title.clone(),
12608 message: env.message.clone(),
12609 full_message: env.full_message.clone(),
12610 sel_saves: env.sel_saves.clone(),
12611 };
12612 (env.local_stream, env.local_ex_stream, Some(meta))
12613 };
12614 if let Some(meta) = loaded_meta.as_ref() {
12615 self.ctx
12616 .set_active_append(meta.append_dir.clone(), meta.append_name.clone());
12617 }
12618 self.scene_stack.clear();
12623 self.scene_user_props.clear();
12624 self.sel_point_stack.clear();
12625 self.save_point = None;
12626 self.ctx.local_save_snapshot = None;
12627 self.ctx.begin_runtime_load_apply();
12628 let snapshot = self.parse_original_local_stream(&local_stream)?;
12629 self.parse_original_local_ex_stream(&local_ex_stream)?;
12630 if let Some(meta) = loaded_meta {
12635 self.ctx.local_save_snapshot = Some(crate::runtime::LocalSaveSnapshot {
12636 save_id: meta.save_id,
12637 append_dir: meta.append_dir,
12638 append_name: meta.append_name,
12639 save_scene_title: meta.title,
12640 save_msg: String::new(),
12641 save_full_msg: self.ctx.globals.syscom.current_save_full_message.clone(),
12642 local_stream: local_stream.clone(),
12643 local_ex_stream: local_ex_stream.clone(),
12644 sel_saves: meta.sel_saves,
12645 });
12646 let snap = self.ctx.local_save_snapshot.as_ref().unwrap();
12651 self.ctx.globals.syscom.current_save_scene_title = snap.save_scene_title.clone();
12652 }
12653 if snapshot.scene_name.is_empty() {
12654 log::error!(
12655 "[SG_SAVELOAD] aborting load (kind={:?} idx={}): saved snapshot has empty scene_name. \
12656 This save file is unusable; please delete it.",
12657 req.kind,
12658 req.index
12659 );
12660 return Ok(());
12661 }
12662 let (mut stream, scene_no) = self.load_scene_stream(&snapshot.scene_name, 0)?;
12663 stream.set_prg_cntr(snapshot.pc.max(0) as usize)?;
12664 let active_call_stack =
12665 self.restore_saved_scene_stack(snapshot.call_stack, &snapshot.scene_name)?;
12666 self.stream = stream;
12667 self.int_stack = snapshot.int_stack;
12668 self.str_stack = snapshot.str_stack;
12669 self.element_points = snapshot.element_points;
12670 self.call_stack = active_call_stack;
12671 if self.call_stack.is_empty() {
12672 self.call_stack.push(self.scene_base_call());
12673 }
12674 self.gosub_return_stack.clear();
12675 self.current_scene_no = if snapshot.scene_no >= 0 {
12676 Some(snapshot.scene_no as usize)
12677 } else {
12678 Some(scene_no)
12679 };
12680 self.current_scene_name = Some(snapshot.scene_name);
12681 self.current_line_no = snapshot.line_no;
12682 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
12683 self.ctx.current_scene_name = self.current_scene_name.clone();
12684 self.ctx.current_line_no = self.current_line_no as i64;
12685 self.ctx.wait = runtime::wait::VmWait::default();
12686 self.halted = false;
12687 self.delayed_ret_form = None;
12688 self.restore_runtime_bindings_after_load();
12693 self.ctx.mark_runtime_load_completed();
12694 Ok(())
12695 }
12696
12697 fn restore_runtime_bindings_after_load(&mut self) {
12703 let form_ids: Vec<u32> = self.ctx.globals.stage_forms.keys().copied().collect();
12707 for form_id in form_ids {
12708 let Some(mut stage_form) = self.ctx.globals.stage_forms.remove(&form_id) else {
12709 continue;
12710 };
12711 crate::runtime::forms::stage::restore_stage_form_backends_after_load(
12712 &mut self.ctx,
12713 &mut stage_form,
12714 );
12715 self.ctx.globals.stage_forms.insert(form_id, stage_form);
12716 }
12717 }
12718
12719 fn drain_runtime_save_load_requests(&mut self) -> Result<()> {
12720 if self.ctx.take_pending_auto_savepoint() {
12724 self.build_local_save_snapshot();
12725 if let Some(snapshot) = self.ctx.local_save_snapshot.as_mut() {
12729 snapshot.save_msg = self.ctx.globals.syscom.current_save_message.clone();
12730 }
12731 }
12732 if let Some(req) = self.ctx.take_runtime_save_request() {
12733 self.perform_runtime_save_request(req)?;
12734 }
12735 if let Some(req) = self.ctx.take_runtime_load_request() {
12736 self.perform_runtime_load_request(req)?;
12737 }
12738 Ok(())
12739 }
12740
12741 fn exec_return(&mut self, args: Vec<Value>) -> Result<bool> {
12742 let Some(callee) = self.call_stack.pop() else {
12744 return Ok(false);
12745 };
12746
12747 let caller = match self.call_stack.last_mut() {
12749 Some(f) => f,
12750 None => {
12751 return Ok(false);
12753 }
12754 };
12755
12756 let (return_pc, ret_form) = callee
12763 .return_override
12764 .unwrap_or((caller.return_pc, caller.ret_form));
12765 if self.runtime_options.trace_call_return_pc {
12766 eprintln!(
12767 "[SG_CALL_PC] return depth={} pc=0x{:x} ret_form={} override={:?} args={:?}",
12768 self.call_stack.len() + 1,
12769 return_pc,
12770 ret_form,
12771 callee.return_override,
12772 args
12773 );
12774 }
12775 self.stream.set_prg_cntr(return_pc)?;
12776
12777 match ret_form {
12778 f if f == self.cfg.fm_int => {
12779 let v = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
12780 self.push_int(v);
12781 }
12782 f if f == self.cfg.fm_str => {
12783 let s = args
12784 .first()
12785 .and_then(|v| v.as_str().map(|s| s.to_string()))
12786 .unwrap_or_default();
12787 self.push_str(s);
12788 }
12789 _ => {
12790 }
12792 }
12793
12794 let frame_action_proc = callee.frame_action_proc;
12795 let excall_proc = callee.excall_proc;
12796 if excall_proc {
12797 self.mark_excall_script_proc_pop_requested();
12798 }
12799 self.recycle_call_frame(callee);
12800
12801 Ok(frame_action_proc)
12802 }
12803
12804 fn scene_base_call(&self) -> CallFrame {
12805 self.make_call_frame(self.cfg.fm_void, false, false, 0, None)
12806 }
12807
12808 fn load_scene_stream(
12809 &mut self,
12810 scene_name: &str,
12811 z_no: i32,
12812 ) -> Result<(SceneStream<'a>, usize)> {
12813 self.ensure_scene_pck_cache()?;
12814 let scene_no = self
12815 .scene_pck_cache
12816 .as_ref()
12817 .expect("scene pck cache initialized")
12818 .find_scene_no(scene_name)
12819 .ok_or_else(|| anyhow!("scene not found: {}", scene_name))?;
12820 let mut stream = self.cached_scene_stream(scene_no)?;
12821 sg_omv_trace!(
12822 self,
12823 "load_scene_stream resolved target={} scene_no={} z={} initial_pc=0x{:x} scn_len=0x{:x}",
12824 scene_name,
12825 scene_no,
12826 z_no,
12827 stream.get_prg_cntr(),
12828 stream.scn.len()
12829 );
12830 self.call_cmd_names = self
12831 .scene_pck_cache
12832 .as_ref()
12833 .expect("scene pck cache initialized")
12834 .inc_cmd_name_map
12835 .clone();
12836 self.user_cmd_names = stream.scn_cmd_name_map.clone();
12837 match stream.jump_to_z_label(z_no.max(0) as usize) {
12838 Ok(()) => {
12839 sg_omv_trace!(
12840 self,
12841 "load_scene_stream entered target={} scene_no={} z={} target_pc=0x{:x} user_cmd_cnt={} call_cmd_cnt={}",
12842 scene_name,
12843 scene_no,
12844 z_no,
12845 stream.get_prg_cntr(),
12846 stream.scn_cmd_name_map.len(),
12847 self.call_cmd_names.len()
12848 );
12849 }
12850 Err(e) => {
12851 sg_omv_trace!(
12852 self,
12853 "load_scene_stream failed target={} scene_no={} z={} error={}",
12854 scene_name,
12855 scene_no,
12856 z_no,
12857 e
12858 );
12859 return Err(e);
12860 }
12861 }
12862 Ok((stream, scene_no))
12863 }
12864
12865 fn jump_to_scene_name(&mut self, scene_name: &str, z_no: i32) -> Result<()> {
12866 sg_omv_trace!(self, "scene_jump target={} z={}", scene_name, z_no);
12867 let (stream, scene_no) = self.load_scene_stream(scene_name, z_no)?;
12868 self.stash_current_scene_user_props();
12869 self.stream = stream;
12870 self.current_scene_no = Some(scene_no);
12871 self.activate_scene_user_prop_scope(scene_no);
12872 self.current_scene_name = Some(scene_name.to_string());
12873 self.current_line_no = -1;
12874 self.ctx.current_scene_no = Some(scene_no as i64);
12875 self.ctx.current_scene_name = Some(scene_name.to_string());
12876 self.ctx.current_line_no = -1;
12877 sg_omv_trace!(
12878 self,
12879 "scene_jump_entered target={} scene_no={} z={} pc=0x{:x}",
12880 scene_name,
12881 scene_no,
12882 z_no,
12883 self.stream.get_prg_cntr()
12884 );
12885 Ok(())
12886 }
12887
12888 fn farcall_scene_name_ex(
12889 &mut self,
12890 scene_name: &str,
12891 z_no: i32,
12892 ret_form: i32,
12893 ex_call_proc: bool,
12894 scratch_source_args: &[Value],
12895 ) -> Result<()> {
12896 sg_omv_trace!(
12897 self,
12898 "scene_farcall target={} z={} ret_form={} ex_call_proc={} scratch_argc={}",
12899 scene_name,
12900 z_no,
12901 ret_form,
12902 ex_call_proc,
12903 scratch_source_args.len()
12904 );
12905 self.trace_cf_branch_farcall(
12906 self.stream.get_prg_cntr(),
12907 scene_name,
12908 z_no,
12909 ret_form,
12910 ex_call_proc,
12911 scratch_source_args,
12912 );
12913 if self.sg_debug_enabled()
12914 && ((scene_name == "sys20_adv00" && matches!(z_no, 10 | 13 | 17))
12915 || (scene_name == "sys20_adv01" && z_no == 0))
12916 {
12917 let args_dbg = scratch_source_args
12918 .iter()
12919 .map(|v| format!("{v:?}"))
12920 .collect::<Vec<_>>()
12921 .join(", ");
12922 self.sg_cgm_coord_trace_emit(format_args!(
12923 "farcall target={} z={} ret_form={} ex_call_proc={} argc={} args=[{}]",
12924 scene_name,
12925 z_no,
12926 ret_form,
12927 ex_call_proc,
12928 scratch_source_args.len(),
12929 args_dbg
12930 ));
12931 }
12932
12933 self.ensure_scene_pck_cache()?;
12934 let scene_no = self
12935 .scene_pck_cache
12936 .as_ref()
12937 .expect("scene pck cache initialized")
12938 .find_scene_no(scene_name)
12939 .ok_or_else(|| anyhow!("scene not found: {}", scene_name))?;
12940 let mut target_stream = self.cached_scene_stream(scene_no)?;
12941 target_stream.jump_to_z_label(z_no.max(0) as usize)?;
12942
12943 let return_pc = self.stream.get_prg_cntr();
12944 let depth = self.call_stack.len();
12945 let caller = self
12946 .call_stack
12947 .last_mut()
12948 .ok_or_else(|| anyhow!("call stack underflow entering FARCALL"))?;
12949 if self.runtime_options.trace_call_return_pc {
12950 eprintln!(
12951 "[SG_CALL_PC] cross-scene farcall set depth={} target_scene={} z={} return_pc=0x{:x} old=0x{:x}",
12952 depth, scene_no, z_no, return_pc, caller.return_pc
12953 );
12954 }
12955 caller.return_pc = return_pc;
12956 caller.return_scene_no = self.current_scene_no;
12957 caller.return_scene_name = self.current_scene_name.clone();
12958 caller.return_line_no = self.current_line_no;
12959 caller.ret_form = ret_form;
12960
12961 let target_user_cmd_names = target_stream.scn_cmd_name_map.clone();
12962 let target_call_cmd_names = self
12963 .scene_pck_cache
12964 .as_ref()
12965 .expect("scene pck cache initialized")
12966 .inc_cmd_name_map
12967 .clone();
12968 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
12969 let saved_user_cmd_names =
12970 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
12971 let saved_call_cmd_names =
12972 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
12973 let saved_current_scene_no = self.current_scene_no;
12974 let saved_current_scene_name = self.current_scene_name.clone();
12975 let saved_current_line_no = self.current_line_no;
12976
12977 self.enter_cross_scene_user_prop_scope(scene_no);
12978 self.current_scene_no = Some(scene_no);
12979 self.current_scene_name = Some(scene_name.to_string());
12980 self.current_line_no = -1;
12981 self.ctx.current_scene_no = Some(scene_no as i64);
12982 self.ctx.current_scene_name = Some(scene_name.to_string());
12983 self.ctx.current_line_no = -1;
12984
12985 let scratch_args = self.call_scratch_from_args(scratch_source_args);
12986 let mut call_frame = self.take_call_frame(
12987 self.cfg.fm_void,
12988 ex_call_proc,
12989 false,
12990 scratch_source_args.len(),
12991 Some(scratch_args),
12992 );
12993 call_frame.call_type = 2;
12994 self.call_stack.push(call_frame);
12995 self.scene_stack.push(SceneExecFrame {
12996 stream: saved_stream,
12997 user_cmd_names: saved_user_cmd_names,
12998 call_cmd_names: saved_call_cmd_names,
12999 current_scene_no: saved_current_scene_no,
13000 current_scene_name: saved_current_scene_name,
13001 current_line_no: saved_current_line_no,
13002 call_depth: self.call_stack.len(),
13003 });
13004
13005 sg_omv_trace!(
13006 self,
13007 "scene_farcall_entered target={} scene_no={} z={} pc=0x{:x} call_depth={} scene_stack={}",
13008 scene_name,
13009 scene_no,
13010 z_no,
13011 self.stream.get_prg_cntr(),
13012 self.call_stack.len(),
13013 self.scene_stack.len()
13014 );
13015 if ex_call_proc {
13016 self.mark_excall_script_proc_requested();
13017 }
13018 Ok(())
13019 }
13020
13021 #[inline(always)]
13022 fn at_cross_scene_return_boundary(&self) -> bool {
13023 self.scene_stack
13024 .last()
13025 .is_some_and(|saved| saved.call_depth == self.call_stack.len())
13026 }
13027
13028 fn return_from_scene(&mut self, args: Vec<Value>) -> Result<bool> {
13029 let Some(saved) = self.scene_stack.pop() else {
13030 return Ok(false);
13031 };
13032 if saved.call_depth != self.call_stack.len() {
13033 let expected = saved.call_depth;
13034 self.scene_stack.push(saved);
13035 bail!(
13036 "cross-scene RETURN at wrong call depth: current={} expected={}",
13037 self.call_stack.len(),
13038 expected
13039 );
13040 }
13041
13042 let callee = self
13043 .call_stack
13044 .pop()
13045 .ok_or_else(|| anyhow!("call stack underflow returning from scene"))?;
13046 let (return_pc, ret_form) = self
13047 .call_stack
13048 .last()
13049 .map(|caller| (caller.return_pc, caller.ret_form))
13050 .ok_or_else(|| anyhow!("caller frame missing returning from scene"))?;
13051
13052 sg_omv_trace!(
13053 self,
13054 "scene_return restore_scene={:?} restore_line={} ret_form={} args={:?}",
13055 saved.current_scene_name,
13056 saved.current_line_no,
13057 ret_form,
13058 args
13059 );
13060
13061 self.restore_cross_scene_user_prop_scope(saved.current_scene_no);
13064 self.stream = saved.stream;
13065 self.user_cmd_names = saved.user_cmd_names;
13066 self.call_cmd_names = saved.call_cmd_names;
13067 self.current_scene_no = saved.current_scene_no;
13068 self.current_scene_name = saved.current_scene_name;
13069 self.current_line_no = saved.current_line_no;
13070 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
13071 self.ctx.current_scene_name = self.current_scene_name.clone();
13072 self.ctx.current_line_no = self.current_line_no as i64;
13073 self.stream.set_prg_cntr(return_pc)?;
13074
13075 match ret_form {
13076 f if f == self.cfg.fm_int || f == self.cfg.fm_label => {
13077 let v = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
13078 self.push_int(v);
13079 }
13080 f if f == self.cfg.fm_str => {
13081 let s = args
13082 .first()
13083 .and_then(|v| v.as_str().map(|s| s.to_string()))
13084 .unwrap_or_default();
13085 self.push_str(s);
13086 }
13087 _ => {}
13088 }
13089
13090 let was_excall_proc = callee.excall_proc;
13091 let was_frame_action_proc = callee.frame_action_proc;
13092 self.recycle_call_frame(callee);
13093 if was_excall_proc {
13094 self.mark_excall_script_proc_pop_requested();
13095 }
13096 if self.cf_branch_trace_interesting_line() {
13097 self.sg_cf_branch_trace_emit(
13098 self.stream.get_prg_cntr(),
13099 format_args!("kind=RETURN_RESTORED ret_form={} args={:?}", ret_form, args),
13100 );
13101 }
13102 sg_omv_trace!(
13103 self,
13104 "scene_return_restored scene={:?} scene_no={:?} line={} pc=0x{:x} call_depth={} scene_stack={} frame_action={}",
13105 self.current_scene_name,
13106 self.current_scene_no,
13107 self.current_line_no,
13108 self.stream.get_prg_cntr(),
13109 self.call_stack.len(),
13110 self.scene_stack.len(),
13111 was_frame_action_proc
13112 );
13113 Ok(true)
13114 }
13115
13116 fn shrink_call_stack_to(&mut self, dst_cnt: usize) {
13127 let dst_cnt = dst_cnt.max(1).min(self.call_stack.len());
13128 while self.call_stack.len() > dst_cnt {
13129 if let Some(frame) = self.call_stack.pop() {
13130 self.recycle_call_frame(frame);
13131 }
13132 }
13133 while self
13134 .scene_stack
13135 .last()
13136 .is_some_and(|saved| saved.call_depth > dst_cnt)
13137 {
13138 self.scene_stack.pop();
13139 }
13140 }
13141
13142 fn exec_builtin_global_control(
13143 &mut self,
13144 form_id: i32,
13145 ret_form: i32,
13146 args: &[Value],
13147 ) -> Result<bool> {
13148 match form_id {
13149 constants::elm_value::GLOBAL_INIT_CALL_STACK => {
13150 self.shrink_call_stack_to(1);
13153 Ok(true)
13154 }
13155 constants::elm_value::GLOBAL_DEL_CALL_STACK => {
13156 let del_cnt = args.first().and_then(Value::as_i64).unwrap_or(0);
13159 if del_cnt > 0 {
13160 let del_cnt = usize::try_from(del_cnt).unwrap_or(usize::MAX);
13161 let dst_cnt = self.call_stack.len().saturating_sub(del_cnt).max(1);
13162 self.shrink_call_stack_to(dst_cnt);
13163 }
13164 Ok(true)
13165 }
13166 constants::elm_value::GLOBAL_SET_CALL_STACK_CNT => {
13167 let dst_cnt = args.first().and_then(Value::as_i64).unwrap_or(0);
13170 if dst_cnt >= 1
13171 && let Ok(dst_cnt) = usize::try_from(dst_cnt)
13172 && dst_cnt < self.call_stack.len()
13173 {
13174 self.shrink_call_stack_to(dst_cnt);
13175 }
13176 Ok(true)
13177 }
13178 constants::elm_value::GLOBAL_GET_CALL_STACK_CNT => {
13179 self.ctx
13183 .stack
13184 .push(Value::Int(self.call_stack.len() as i64));
13185 Ok(true)
13186 }
13187 constants::elm_value::GLOBAL_SAVEPOINT => {
13188 self.int_stack.push(1);
13194 self.save_point = Some(self.make_resume_point());
13195 self.build_local_save_snapshot();
13196 let _ = self.int_stack.pop();
13197 if ret_form != self.cfg.fm_void {
13198 self.ctx.stack.push(Value::Int(0));
13199 }
13200 Ok(true)
13201 }
13202 constants::elm_value::GLOBAL_CLEAR_SAVEPOINT => {
13203 self.save_point = None;
13204 self.ctx.local_save_snapshot = None;
13205 Ok(true)
13206 }
13207 constants::elm_value::GLOBAL_CHECK_SAVEPOINT => {
13208 let has = self
13209 .ctx
13210 .local_save_snapshot
13211 .as_ref()
13212 .map(|s| !s.local_stream.is_empty())
13213 .unwrap_or(false);
13214 self.ctx.stack.push(Value::Int(if has { 1 } else { 0 }));
13215 Ok(true)
13216 }
13217 constants::elm_value::GLOBAL_SELPOINT => {
13218 let point = self.make_resume_point();
13219 self.sel_point_stack.clear();
13220 self.sel_point_stack.push(point);
13221 Ok(true)
13222 }
13223 constants::elm_value::GLOBAL_CLEAR_SELPOINT => {
13224 self.sel_point_stack.clear();
13225 Ok(true)
13226 }
13227 constants::elm_value::GLOBAL_CHECK_SELPOINT => {
13228 self.ctx
13229 .stack
13230 .push(Value::Int(if self.has_sel_point() { 1 } else { 0 }));
13231 Ok(true)
13232 }
13233 constants::elm_value::GLOBAL_STACK_SELPOINT => {
13234 let point = self.make_resume_point();
13235 self.sel_point_stack.push(point);
13236 Ok(true)
13237 }
13238 constants::elm_value::GLOBAL_DROP_SELPOINT => {
13239 let _ = self.sel_point_stack.pop();
13240 Ok(true)
13241 }
13242 _ => Ok(false),
13243 }
13244 }
13245
13246 fn exec_mouse_get_pos(&mut self, elm: &[i32], form_id: i32, args: &[Value]) -> Result<bool> {
13247 if form_id != self.ctx.ids.form_global_mouse as i32
13248 && form_id != constants::global_form::MOUSE as i32
13249 && form_id != constants::fm::MOUSE
13250 {
13251 return Ok(false);
13252 }
13253 if elm.get(1).copied() != Some(self.ctx.ids.mouse_op_get_pos) {
13254 return Ok(false);
13255 }
13256
13257 let pos = [self.ctx.script_input.mouse_x, self.ctx.script_input.mouse_y];
13261 for (arg, value) in args.iter().zip(pos) {
13262 if let Value::Element(target) = arg.unwrap_named() {
13263 self.exec_assign(target.clone(), 1, Value::Int(i64::from(value)))?;
13264 }
13265 }
13266 Ok(true)
13267 }
13268
13269 fn exec_syscom_save_value_intlistref(
13270 &mut self,
13271 elm: &[i32],
13272 form_id: i32,
13273 ret_form: i32,
13274 args: &[Value],
13275 ) -> Result<bool> {
13276 use crate::runtime::forms::codes::{ELM_ARRAY, FM_SYSCOM, FORM_GLOBAL_SYSCOM, elm_value};
13277
13278 if form_id != FORM_GLOBAL_SYSCOM as i32 && form_id != FM_SYSCOM {
13279 return Ok(false);
13280 }
13281 let Some(op) = elm.get(1).copied() else {
13282 return Ok(false);
13283 };
13284 let (quick, write) = match op {
13285 elm_value::SYSCOM_GET_SAVE_VALUE => (false, false),
13286 elm_value::SYSCOM_GET_QUICK_SAVE_VALUE => (true, false),
13287 elm_value::SYSCOM_SET_SAVE_VALUE => (false, true),
13288 elm_value::SYSCOM_SET_QUICK_SAVE_VALUE => (true, true),
13289 _ => return Ok(false),
13290 };
13291
13292 if ret_form != self.cfg.fm_void {
13296 return Ok(false);
13297 }
13298 let raw_save_no = args.first().and_then(Value::as_i64).unwrap_or(-1);
13299 let Some(Value::Element(base_chain)) = args.get(1).map(Value::unwrap_named) else {
13300 return Ok(true);
13301 };
13302 if base_chain.is_empty() {
13303 return Ok(true);
13304 }
13305 let flag_index = args.get(2).and_then(Value::as_i64).unwrap_or(0);
13306 let flag_cnt_raw = args.get(3).and_then(Value::as_i64).unwrap_or(0);
13307 if flag_cnt_raw <= 0 {
13308 return Ok(true);
13309 }
13310 let flag_cnt = usize::try_from(flag_cnt_raw)
13311 .unwrap_or(usize::MAX)
13312 .min(crate::original_save::SAVE_FLAG_MAX_CNT);
13313
13314 if !write {
13315 let Some(values) = crate::runtime::forms::syscom::read_save_flag_values(
13316 &mut self.ctx,
13317 quick,
13318 raw_save_no,
13319 flag_cnt,
13320 ) else {
13321 return Ok(true);
13322 };
13323 for (i, value) in values.into_iter().enumerate() {
13324 let Some(index) = flag_index.checked_add(i as i64) else {
13325 break;
13326 };
13327 if index < 0 {
13328 continue;
13329 }
13330 let Ok(index) = i32::try_from(index) else {
13331 continue;
13332 };
13333 let mut target = base_chain.clone();
13334 target.push(ELM_ARRAY);
13335 target.push(index);
13336 self.exec_assign(target, 1, Value::Int(value))?;
13337 }
13338 } else {
13339 let mut values = Vec::with_capacity(flag_cnt);
13340 for i in 0..flag_cnt {
13341 let Some(index) = flag_index.checked_add(i as i64) else {
13342 break;
13343 };
13344 if index < 0 {
13345 values.push(0);
13346 continue;
13347 }
13348 let Ok(index) = i32::try_from(index) else {
13349 values.push(0);
13350 continue;
13351 };
13352 let mut source = base_chain.clone();
13353 source.push(ELM_ARRAY);
13354 source.push(index);
13355 self.exec_property(source)?;
13356 values.push(i64::from(self.pop_int()?));
13357 }
13358 let _ = crate::runtime::forms::syscom::write_save_flag_values(
13359 &mut self.ctx,
13360 quick,
13361 raw_save_no,
13362 &values,
13363 );
13364 }
13365
13366 self.ctx.stack.clear();
13368 Ok(true)
13369 }
13370
13371 fn exec_builtin_scene_form(
13372 &mut self,
13373 elm: &[i32],
13374 form_id: i32,
13375 al_id: i32,
13376 ret_form: i32,
13377 args: &[Value],
13378 ) -> Result<bool> {
13379 const FORM_GLOBAL_JUMP: i32 = crate::runtime::forms::codes::elm_value::GLOBAL_JUMP;
13380 const FORM_GLOBAL_FARCALL: i32 = crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL;
13381 const FORM_GLOBAL_SYSCOM: i32 = crate::runtime::forms::codes::FORM_GLOBAL_SYSCOM as i32;
13382 const FORM_SYSCOM: i32 = crate::runtime::forms::codes::FM_SYSCOM;
13383 const ELM_SYSCOM_CALL_EX: i32 = crate::runtime::forms::codes::elm_value::SYSCOM_CALL_EX;
13384 if (form_id == FORM_GLOBAL_SYSCOM || form_id == FORM_SYSCOM)
13385 && elm.get(1).copied() == Some(ELM_SYSCOM_CALL_EX)
13386 {
13387 self.sg_omv_trace_command(
13388 "builtin",
13389 elm,
13390 form_id,
13391 ELM_SYSCOM_CALL_EX,
13392 al_id,
13393 self.cfg.fm_void,
13394 args,
13395 );
13396 let scene_name = args.first().and_then(|v| v.as_str()).unwrap_or("");
13397 let z_no = if al_id == 1 {
13398 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
13399 } else {
13400 0
13401 };
13402 let scratch_args = if al_id == 1 && args.len() > 2 {
13403 &args[2..]
13404 } else {
13405 &[]
13406 };
13407 self.farcall_scene_name_ex(scene_name, z_no, self.cfg.fm_void, true, scratch_args)?;
13408 self.ctx.stack.clear();
13409 return Ok(true);
13410 }
13411 if form_id == FORM_GLOBAL_JUMP {
13412 self.sg_omv_trace_command("builtin", &[], form_id, form_id, al_id, ret_form, args);
13413 let scene_name = args.first().and_then(|v| v.as_str()).unwrap_or("");
13414 let z_no = if al_id >= 1 {
13415 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
13416 } else {
13417 0
13418 };
13419 if !scene_name.is_empty() {
13420 self.jump_to_scene_name(scene_name, z_no)?;
13421 }
13422 return Ok(true);
13423 }
13424 if form_id == FORM_GLOBAL_FARCALL {
13425 self.sg_omv_trace_command("builtin", &[], form_id, form_id, al_id, ret_form, args);
13426 let scene_name = args.first().and_then(|v| v.as_str()).unwrap_or("");
13427 let z_no = if al_id >= 1 {
13428 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
13429 } else {
13430 0
13431 };
13432 if !scene_name.is_empty() {
13433 self.farcall_scene_name_ex(
13434 scene_name,
13435 z_no,
13436 self.cfg.fm_int,
13437 false,
13438 if al_id >= 1 && args.len() > 2 {
13439 &args[2..]
13440 } else {
13441 &[]
13442 },
13443 )?;
13444 } else {
13445 self.push_default_for_ret(self.cfg.fm_int);
13446 }
13447 return Ok(true);
13448 }
13449 Ok(false)
13450 }
13451
13452 fn take_ctx_return(&mut self, ret_form: i32) -> Result<()> {
13453 if ret_form == self.cfg.fm_void {
13454 self.ctx.stack.clear();
13455 return Ok(());
13456 }
13457
13458 let v = self.ctx.pop();
13459 match ret_form {
13460 f if f == self.cfg.fm_int || f == self.cfg.fm_label => match v {
13461 Some(Value::Int(n)) => self.push_int(n as i32),
13462 Some(Value::NamedArg { value, .. }) => match *value {
13463 Value::Int(n) => self.push_int(n as i32),
13464 _ => bail!("non-int ctx return for form {}", ret_form),
13465 },
13466 Some(_) => bail!("non-int ctx return for form {}", ret_form),
13467 None => bail!(
13468 "missing ctx return int for form {}: scene={} scene_no={} line={} pc=0x{:x} vm_call={:?}",
13469 ret_form,
13470 self.current_scene_name.as_deref().unwrap_or("<none>"),
13471 self.current_scene_no
13472 .map(|v| v.to_string())
13473 .unwrap_or_else(|| "-".to_string()),
13474 self.current_line_no,
13475 self.stream.get_prg_cntr(),
13476 self.ctx.vm_call
13477 ),
13478 },
13479 f if f == self.cfg.fm_str => match v {
13480 Some(Value::Str(s)) => self.push_str(s),
13481 Some(Value::NamedArg { value, .. }) => match *value {
13482 Value::Str(s) => self.push_str(s),
13483 _ => bail!("non-str ctx return for form {}", ret_form),
13484 },
13485 Some(_) => bail!("non-str ctx return for form {}", ret_form),
13486 None => bail!("missing ctx return str for form {}", ret_form),
13487 },
13488 f if f == self.cfg.fm_list => match v {
13489 Some(Value::Element(elm)) => self.push_element(elm),
13490 Some(Value::NamedArg { value, .. }) => match *value {
13491 Value::Element(elm) => self.push_element(elm),
13492 _ => bail!("non-element ctx return for FM_LIST"),
13493 },
13494 Some(Value::List(_)) => {
13495 bail!("FM_LIST ctx return used raw Value::List; expected element reference")
13496 }
13497 Some(_) => bail!("non-element ctx return for FM_LIST"),
13498 None => bail!("missing ctx return element for FM_LIST"),
13499 },
13500 _ => match v {
13501 Some(Value::Element(elm)) => self.push_element(elm),
13502 Some(Value::NamedArg { value, .. }) => match *value {
13503 Value::Element(elm) => self.push_element(elm),
13504 _ => bail!("non-element ctx return for form {}", ret_form),
13505 },
13506 Some(_) => bail!("non-element ctx return for form {}", ret_form),
13507 None => bail!("missing ctx return element for form {}", ret_form),
13508 },
13509 }
13510 Ok(())
13511 }
13512
13513 fn push_default_for_ret(&mut self, ret_form: i32) {
13514 if ret_form == self.cfg.fm_int || ret_form == self.cfg.fm_label {
13515 self.push_int(0);
13516 } else if ret_form == self.cfg.fm_str {
13517 self.push_str(String::new());
13518 }
13519 }
13520
13521 fn update_compact_context_from_element(&mut self, elm: &[i32]) {
13522 let Some(raw_stage_form) = elm.first().copied() else {
13523 return;
13524 };
13525 if !crate::runtime::forms::stage::is_stage_form_id(&self.ctx, raw_stage_form) {
13526 return;
13527 }
13528 let stage_form =
13529 crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, raw_stage_form) as i32;
13530 let elm_array = if self.ctx.ids.elm_array != 0 {
13531 self.ctx.ids.elm_array
13532 } else {
13533 crate::runtime::forms::codes::ELM_ARRAY
13534 };
13535 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
13536 self.ctx.ids.stage_elm_object
13537 } else {
13538 crate::runtime::forms::codes::STAGE_ELM_OBJECT
13539 };
13540 let stage_mwnd = crate::runtime::forms::codes::STAGE_ELM_MWND;
13541 let stage_btnselitem = crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM;
13542
13543 fn is_array_token(token: i32, elm_array: i32) -> bool {
13544 token == elm_array || token == crate::runtime::forms::codes::ELM_ARRAY
13545 }
13546
13547 fn object_chain_tail_is_plain_object_ref(
13548 elm: &[i32],
13549 mut pos: usize,
13550 elm_array: i32,
13551 ) -> bool {
13552 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
13553 while pos + 2 < elm.len()
13554 && elm[pos] == object_child
13555 && is_array_token(elm[pos + 1], elm_array)
13556 {
13557 if elm[pos + 2] < 0 {
13558 return false;
13559 }
13560 pos += 3;
13561 }
13562 pos == elm.len()
13563 }
13564
13565 let resolved = if elm.len() >= 6
13566 && crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, elm[0]) as i32
13567 == stage_form
13568 && is_array_token(elm[1], elm_array)
13569 && elm[2] >= 0
13570 && elm[3] == stage_object
13571 && is_array_token(elm[4], elm_array)
13572 && elm[5] >= 0
13573 && object_chain_tail_is_plain_object_ref(elm, 6, elm_array)
13574 {
13575 Some((elm[2] as i64, elm[5] as usize))
13576 } else if elm.len() >= 9
13577 && crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, elm[0]) as i32
13578 == stage_form
13579 && is_array_token(elm[1], elm_array)
13580 && elm[2] >= 0
13581 && is_array_token(elm[4], elm_array)
13582 && elm[5] >= 0
13583 && ((elm[3] == stage_mwnd
13584 && matches!(
13585 elm[6],
13586 crate::runtime::forms::codes::elm_value::MWND_OBJECT
13587 | crate::runtime::forms::codes::elm_value::MWND_BUTTON
13588 | crate::runtime::forms::codes::elm_value::MWND_FACE
13589 ))
13590 || (elm[3] == stage_btnselitem
13591 && elm[6] == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT))
13592 && is_array_token(elm[7], elm_array)
13593 && elm[8] >= 0
13594 && object_chain_tail_is_plain_object_ref(elm, 9, elm_array)
13595 {
13596 Some((elm[2] as i64, elm[8] as usize))
13597 } else {
13598 None
13599 };
13600
13601 let Some((stage_idx, fallback_obj_idx)) = resolved else {
13602 return;
13603 };
13604 let runtime_slot = self.runtime_slot_from_object_chain(fallback_obj_idx, elm);
13605 let prev_chain = self.ctx.globals.current_object_chain.clone();
13606 let prev_stage_object = self.ctx.globals.current_stage_object;
13607 self.ctx.globals.current_object_chain = Some(elm.to_vec());
13608 self.ctx.globals.current_stage_object = Some((stage_idx, runtime_slot));
13609 if self.sg_mwnd_object_trace_enabled() && Self::sg_mwnd_chain_interesting(elm) {
13610 self.sg_mwnd_object_trace_emit(format_args!(
13611 "update_compact_context elm={:?} resolved_stage={} fallback_idx={} runtime_slot={} prev_chain={:?} prev_stage_object={:?}",
13612 elm,
13613 stage_idx,
13614 fallback_obj_idx,
13615 runtime_slot,
13616 prev_chain,
13617 prev_stage_object
13618 ));
13619 }
13620 }
13621
13622 fn update_compact_context_from_object_dispatch_chain(&mut self, elm: &[i32]) {
13623 let Some(raw_stage_form) = elm.first().copied() else {
13624 return;
13625 };
13626 if !crate::runtime::forms::stage::is_stage_form_id(&self.ctx, raw_stage_form) {
13627 return;
13628 }
13629 let stage_form =
13630 crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, raw_stage_form) as i32;
13631 let elm_array = if self.ctx.ids.elm_array != 0 {
13632 self.ctx.ids.elm_array
13633 } else {
13634 crate::runtime::forms::codes::ELM_ARRAY
13635 };
13636 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
13637 self.ctx.ids.stage_elm_object
13638 } else {
13639 crate::runtime::forms::codes::STAGE_ELM_OBJECT
13640 };
13641 let stage_mwnd = crate::runtime::forms::codes::STAGE_ELM_MWND;
13642 let stage_btnselitem = crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM;
13643 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
13644
13645 fn is_array_token(token: i32, elm_array: i32) -> bool {
13646 token == elm_array || token == crate::runtime::forms::codes::ELM_ARRAY
13647 }
13648
13649 let mut pos = if elm.len() >= 6
13650 && crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, elm[0]) as i32
13651 == stage_form
13652 && is_array_token(elm[1], elm_array)
13653 && elm[2] >= 0
13654 && elm[3] == stage_object
13655 && is_array_token(elm[4], elm_array)
13656 && elm[5] >= 0
13657 {
13658 6usize
13659 } else if elm.len() >= 9
13660 && crate::runtime::forms::stage::stage_storage_form_id(&self.ctx, elm[0]) as i32
13661 == stage_form
13662 && is_array_token(elm[1], elm_array)
13663 && elm[2] >= 0
13664 && is_array_token(elm[4], elm_array)
13665 && elm[5] >= 0
13666 && ((elm[3] == stage_mwnd
13667 && matches!(
13668 elm[6],
13669 crate::runtime::forms::codes::elm_value::MWND_OBJECT
13670 | crate::runtime::forms::codes::elm_value::MWND_BUTTON
13671 | crate::runtime::forms::codes::elm_value::MWND_FACE
13672 ))
13673 || (elm[3] == stage_btnselitem
13674 && elm[6] == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT))
13675 && is_array_token(elm[7], elm_array)
13676 && elm[8] >= 0
13677 {
13678 9usize
13679 } else {
13680 return;
13681 };
13682
13683 while pos + 2 < elm.len()
13684 && elm[pos] == object_child
13685 && is_array_token(elm[pos + 1], elm_array)
13686 && elm[pos + 2] >= 0
13687 {
13688 pos += 3;
13689 }
13690
13691 let object_ref = elm[..pos].to_vec();
13692 if self.sg_mwnd_object_trace_enabled() && Self::sg_mwnd_chain_interesting(elm) {
13693 self.sg_mwnd_object_trace_emit(format_args!(
13694 "update_context_from_dispatch elm={:?} object_ref={:?} pos={}",
13695 elm, object_ref, pos
13696 ));
13697 }
13698 self.update_compact_context_from_element(&object_ref);
13699 }
13700
13701 fn push_return_value_raw(&mut self, v: Value) {
13702 match v {
13703 Value::NamedArg { value, .. } => self.push_return_value_raw(*value),
13704 Value::Int(n) => self.push_int(n as i32),
13705 Value::Str(s) => self.push_str(s),
13706 Value::Element(elm) => {
13707 self.update_compact_context_from_element(&elm);
13708 self.push_element(elm);
13709 }
13710 Value::List(_) => {
13711 panic!("raw Value::List reached push_return_value_raw; expected runtime ref");
13712 }
13713 }
13714 }
13715
13716 fn exec_operate_1(&mut self, form_code: i32, opr: u8) -> Result<()> {
13721 if form_code != self.cfg.fm_int {
13722 self.trace_unknown_form(form_code, "exec_operate_1");
13723 self.push_int(0);
13724 return Ok(());
13725 }
13726
13727 let v = self.pop_int()?;
13728 let out = match opr {
13729 OP_PLUS => v,
13730 OP_MINUS => v.wrapping_neg(),
13731 OP_TILDE => !v,
13732 _ => v,
13733 };
13734 if self.cf_condition_trace_interesting_line() {
13735 self.sg_cf_condition_trace(
13736 self.stream.get_prg_cntr(),
13737 format!(
13738 "kind=OPERATE_1 op={} in={} out={}",
13739 Self::cf_condition_op_name(opr),
13740 v,
13741 out
13742 ),
13743 );
13744 }
13745 self.push_int(out);
13746 Ok(())
13747 }
13748
13749 fn exec_operate_2(&mut self, form_l: i32, form_r: i32, opr: u8) -> Result<()> {
13750 if form_l == self.cfg.fm_int && form_r == self.cfg.fm_int {
13752 let r = self.pop_int()?;
13753 let l = self.pop_int()?;
13754 let out = self.calc_int_int(l, r, opr);
13755 if self.cf_condition_trace_interesting_line() {
13756 self.sg_cf_condition_trace(
13757 self.stream.get_prg_cntr(),
13758 format!(
13759 "kind=OPERATE_2 op={} left={} right={} out={}",
13760 Self::cf_condition_op_name(opr),
13761 l,
13762 r,
13763 out
13764 ),
13765 );
13766 }
13767 self.push_int(out);
13768 return Ok(());
13769 }
13770
13771 if form_l == self.cfg.fm_str && form_r == self.cfg.fm_int {
13773 let r = self.pop_int()?;
13774 let l = self.pop_str()?;
13775 let out = self.calc_str_int(l, r, opr);
13776 self.push_str(out);
13777 return Ok(());
13778 }
13779
13780 if form_l == self.cfg.fm_str && form_r == self.cfg.fm_str {
13782 let r = self.pop_str()?;
13783 let l = self.pop_str()?;
13784 let out = self.calc_str_str(l, r, opr);
13785 match out {
13786 Value::Int(n) => self.push_int(n as i32),
13787 Value::Str(s) => self.push_str(s),
13788 _ => {
13789 self.push_int(0);
13790 }
13791 }
13792 return Ok(());
13793 }
13794
13795 self.trace_unknown_form(form_l, "exec_operate_2.left");
13797 self.trace_unknown_form(form_r, "exec_operate_2.right");
13798 self.push_int(0);
13799 Ok(())
13800 }
13801
13802 fn calc_int_int(&mut self, l: i32, r: i32, opr: u8) -> i32 {
13803 match opr {
13804 OP_PLUS => l.wrapping_add(r),
13805 OP_MINUS => l.wrapping_sub(r),
13806 OP_MULTIPLE => l.wrapping_mul(r),
13807 OP_DIVIDE => {
13808 if r == 0 {
13809 0
13810 } else {
13811 l.wrapping_div(r)
13812 }
13813 }
13814 OP_AMARI => {
13815 if r == 0 {
13816 0
13817 } else {
13818 l.wrapping_rem(r)
13819 }
13820 }
13821
13822 OP_EQUAL => (l == r) as i32,
13823 OP_NOT_EQUAL => (l != r) as i32,
13824 OP_GREATER => (l > r) as i32,
13825 OP_GREATER_EQUAL => (l >= r) as i32,
13826 OP_LESS => (l < r) as i32,
13827 OP_LESS_EQUAL => (l <= r) as i32,
13828
13829 OP_LOGICAL_OR => ((l != 0) || (r != 0)) as i32,
13830 OP_LOGICAL_AND => ((l != 0) && (r != 0)) as i32,
13831
13832 OP_OR => l | r,
13833 OP_AND => l & r,
13834 OP_HAT => l ^ r,
13835 OP_SL => l.wrapping_shl((r as u32) & 31),
13836 OP_SR => l.wrapping_shr((r as u32) & 31),
13837 OP_SR3 => ((l as u32).wrapping_shr((r as u32) & 31)) as i32,
13838
13839 _ => 0,
13840 }
13841 }
13842
13843 fn calc_str_int(&mut self, s: String, n: i32, opr: u8) -> String {
13844 match opr {
13845 OP_MULTIPLE => {
13846 if n <= 0 {
13847 return String::new();
13848 }
13849 let mut out = String::new();
13850 for _ in 0..(n as usize) {
13851 out.push_str(&s);
13852 }
13853 out
13854 }
13855 _ => s,
13856 }
13857 }
13858
13859 fn calc_str_str(&mut self, l: String, r: String, opr: u8) -> Value {
13860 match opr {
13861 OP_PLUS => Value::Str(format!("{l}{r}")),
13862 OP_EQUAL | OP_NOT_EQUAL | OP_GREATER | OP_GREATER_EQUAL | OP_LESS | OP_LESS_EQUAL => {
13863 let ll = l.to_lowercase();
13865 let rr = r.to_lowercase();
13866 let cmp = ll.cmp(&rr);
13867 let b = match opr {
13868 OP_EQUAL => cmp == std::cmp::Ordering::Equal,
13869 OP_NOT_EQUAL => cmp != std::cmp::Ordering::Equal,
13870 OP_GREATER => cmp == std::cmp::Ordering::Greater,
13871 OP_GREATER_EQUAL => cmp != std::cmp::Ordering::Less,
13872 OP_LESS => cmp == std::cmp::Ordering::Less,
13873 OP_LESS_EQUAL => cmp != std::cmp::Ordering::Greater,
13874 _ => false,
13875 };
13876 Value::Int(b as i64)
13877 }
13878 _ => Value::Int(0),
13879 }
13880 }
13881}
13882
13883#[cfg(test)]
13884mod user_command_resolution_tests {
13885 use super::resolve_named_user_command_number;
13886 use std::collections::HashMap;
13887
13888 #[test]
13889 fn include_command_shadows_same_named_scene_command() {
13890 let include_names = HashMap::from([(2, "Bg_Change".to_string())]);
13891 let local_names = HashMap::from([(7, "bg_change".to_string())]);
13892 assert_eq!(
13893 resolve_named_user_command_number(&include_names, &local_names, 5, "BG_CHANGE"),
13894 Some((2, true))
13895 );
13896 }
13897
13898 #[test]
13899 fn scene_command_number_is_offset_by_include_count() {
13900 let include_names = HashMap::new();
13901 let local_names = HashMap::from([(7, "local_proc".to_string())]);
13902 assert_eq!(
13903 resolve_named_user_command_number(&include_names, &local_names, 5, "LOCAL_PROC"),
13904 Some((12, false))
13905 );
13906 }
13907}
13908
13909#[cfg(test)]
13910mod global_call_stack_control_tests {
13911 use super::*;
13912 use crate::scene_stream::SceneStream;
13913 use std::path::PathBuf;
13914
13915 fn empty_scene_chunk() -> Vec<u8> {
13916 const HEADER_WORDS: usize = 33;
13917 const HEADER_SIZE: i32 = (HEADER_WORDS * 4) as i32;
13918 let mut words = [0i32; HEADER_WORDS];
13919 for idx in [
13920 0usize, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
13921 ] {
13922 words[idx] = HEADER_SIZE;
13923 }
13924 let mut out = Vec::with_capacity(HEADER_SIZE as usize);
13925 for word in words {
13926 out.extend_from_slice(&word.to_le_bytes());
13927 }
13928 out
13929 }
13930
13931 fn vm_with_depth(depth: usize) -> SceneVm<'static> {
13932 let chunk = Box::leak(empty_scene_chunk().into_boxed_slice());
13933 let stream = SceneStream::new(chunk).expect("empty scene stream");
13934 let ctx = CommandContext::new(PathBuf::from("."));
13935 let mut vm = SceneVm::new(stream, ctx);
13936 while vm.call_stack.len() < depth {
13937 let frame = vm.take_call_frame(vm.cfg.fm_void, false, false, 0, None);
13938 vm.call_stack.push(frame);
13939 }
13940 vm
13941 }
13942
13943 #[test]
13944 fn global_call_stack_controls_match_original_shrink_only_semantics() {
13945 let mut vm = vm_with_depth(4);
13946
13947 assert!(
13948 vm.exec_builtin_global_control(
13949 constants::elm_value::GLOBAL_GET_CALL_STACK_CNT,
13950 vm.cfg.fm_int,
13951 &[],
13952 )
13953 .unwrap()
13954 );
13955 assert_eq!(vm.ctx.stack.pop().and_then(|v| v.as_i64()), Some(4));
13956
13957 assert!(
13958 vm.exec_builtin_global_control(
13959 constants::elm_value::GLOBAL_DEL_CALL_STACK,
13960 vm.cfg.fm_void,
13961 &[Value::Int(2)],
13962 )
13963 .unwrap()
13964 );
13965 assert_eq!(vm.call_stack.len(), 2);
13966
13967 assert!(
13969 vm.exec_builtin_global_control(
13970 constants::elm_value::GLOBAL_SET_CALL_STACK_CNT,
13971 vm.cfg.fm_void,
13972 &[Value::Int(4)],
13973 )
13974 .unwrap()
13975 );
13976 assert_eq!(vm.call_stack.len(), 2);
13977
13978 assert!(
13979 vm.exec_builtin_global_control(
13980 constants::elm_value::GLOBAL_SET_CALL_STACK_CNT,
13981 vm.cfg.fm_void,
13982 &[Value::Int(1)],
13983 )
13984 .unwrap()
13985 );
13986 assert_eq!(vm.call_stack.len(), 1);
13987
13988 assert!(
13990 vm.exec_builtin_global_control(
13991 constants::elm_value::GLOBAL_DEL_CALL_STACK,
13992 vm.cfg.fm_void,
13993 &[Value::Int(99)],
13994 )
13995 .unwrap()
13996 );
13997 assert_eq!(vm.call_stack.len(), 1);
13998 assert!(
13999 vm.exec_builtin_global_control(
14000 constants::elm_value::GLOBAL_INIT_CALL_STACK,
14001 vm.cfg.fm_void,
14002 &[],
14003 )
14004 .unwrap()
14005 );
14006 assert_eq!(vm.call_stack.len(), 1);
14007 }
14008}
14009
14010#[cfg(test)]
14011mod call_property_reference_tests {
14012 use super::*;
14013 use crate::runtime::forms::codes::{ELM_ARRAY, ELM_GLOBAL_D, FM_INTLISTREF, FM_INTREF};
14014 use crate::scene_stream::SceneStream;
14015 use std::path::PathBuf;
14016
14017 fn empty_scene_chunk() -> Vec<u8> {
14018 const HEADER_WORDS: usize = 33;
14019 const HEADER_SIZE: i32 = (HEADER_WORDS * 4) as i32;
14020 let mut words = [0i32; HEADER_WORDS];
14021 for idx in [
14022 0usize, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
14023 ] {
14024 words[idx] = HEADER_SIZE;
14025 }
14026 let mut out = Vec::with_capacity(HEADER_SIZE as usize);
14027 for word in words {
14028 out.extend_from_slice(&word.to_le_bytes());
14029 }
14030 out
14031 }
14032
14033 #[test]
14034 fn marker_only_call_reference_does_not_create_an_indexless_array_chain() {
14035 let chunk = empty_scene_chunk();
14036 let stream = SceneStream::new(&chunk).expect("empty scene stream");
14037 let ctx = CommandContext::new(PathBuf::from("."));
14038 let mut vm = SceneVm::new(stream, ctx);
14039
14040 let target = vec![ELM_GLOBAL_D];
14041 let call_prop_id = 0;
14042 let call_prop_element =
14043 constants::elm::create(constants::elm::OWNER_CALL_PROP, 0, call_prop_id);
14044 vm.call_stack
14045 .last_mut()
14046 .expect("base call frame")
14047 .user_props
14048 .push(CallProp {
14049 scn_no: 0,
14050 prop_id: call_prop_id,
14051 form: FM_INTLISTREF,
14052 decl_size: 0,
14053 element: target.clone(),
14054 value: CallPropValue::Element(target.clone()),
14055 });
14056
14057 vm.exec_property(vec![call_prop_element, ELM_ARRAY])
14062 .expect("marker-only reference property step");
14063 assert_eq!(vm.pop_element().expect("referenced element"), target);
14064 }
14065
14066 #[test]
14067 fn indexed_call_reference_still_composes_the_array_index() {
14068 let chunk = empty_scene_chunk();
14069 let stream = SceneStream::new(&chunk).expect("empty scene stream");
14070 let ctx = CommandContext::new(PathBuf::from("."));
14071 let vm = SceneVm::new(stream, ctx);
14072
14073 let target = vec![ELM_GLOBAL_D];
14074 let prop = CallProp {
14075 scn_no: 0,
14076 prop_id: 0,
14077 form: FM_INTLISTREF,
14078 decl_size: 0,
14079 element: target.clone(),
14080 value: CallPropValue::Element(target),
14081 };
14082
14083 assert_eq!(vm.compose_call_prop_tail(&prop, &[ELM_ARRAY]), None);
14084 assert_eq!(
14085 vm.compose_call_prop_tail(&prop, &[ELM_ARRAY, 7]),
14086 Some(vec![ELM_GLOBAL_D, ELM_ARRAY, 7])
14087 );
14088 }
14089
14090 #[test]
14091 fn marker_only_call_reference_command_does_not_dispatch_indexless_intlist() {
14092 let chunk = empty_scene_chunk();
14093 let stream = SceneStream::new(&chunk).expect("empty scene stream");
14094 let ctx = CommandContext::new(PathBuf::from("."));
14095 let mut vm = SceneVm::new(stream, ctx);
14096
14097 let target = vec![ELM_GLOBAL_D];
14098 let call_prop_id = 0;
14099 vm.call_stack
14100 .last_mut()
14101 .expect("base call frame")
14102 .user_props
14103 .push(CallProp {
14104 scn_no: 0,
14105 prop_id: call_prop_id,
14106 form: FM_INTLISTREF,
14107 decl_size: 0,
14108 element: target.clone(),
14109 value: CallPropValue::Element(target.clone()),
14110 });
14111
14112 vm.exec_call_prop_command(0, 0, &[ELM_ARRAY], 0, vm.cfg.fm_void, &[])
14113 .expect("marker-only reference command step");
14114
14115 assert_eq!(vm.pop_element().expect("referenced element"), target);
14116 }
14117
14118 #[test]
14119 fn direct_call_reference_command_uses_reference_dispatch() {
14120 let chunk = empty_scene_chunk();
14121 let stream = SceneStream::new(&chunk).expect("empty scene stream");
14122 let ctx = CommandContext::new(PathBuf::from("."));
14123 let mut vm = SceneVm::new(stream, ctx);
14124
14125 let target = vec![ELM_GLOBAL_D];
14126 let call_prop_id = 0;
14127 let call_prop_element =
14128 constants::elm::create(constants::elm::OWNER_CALL_PROP, 0, call_prop_id);
14129 vm.call_stack
14130 .last_mut()
14131 .expect("base call frame")
14132 .user_props
14133 .push(CallProp {
14134 scn_no: 0,
14135 prop_id: call_prop_id,
14136 form: FM_INTLISTREF,
14137 decl_size: 0,
14138 element: target.clone(),
14139 value: CallPropValue::Element(target.clone()),
14140 });
14141
14142 vm.exec_command(
14143 vec![call_prop_element, ELM_ARRAY],
14144 0,
14145 vm.cfg.fm_void,
14146 &mut Vec::new(),
14147 )
14148 .expect("direct marker-only CALL_PROP command");
14149
14150 assert_eq!(vm.pop_element().expect("referenced element"), target);
14151 }
14152
14153 #[test]
14154 fn scalar_call_reference_is_resolved_in_two_property_steps() {
14155 let chunk = empty_scene_chunk();
14156 let stream = SceneStream::new(&chunk).expect("empty scene stream");
14157 let ctx = CommandContext::new(PathBuf::from("."));
14158 let mut vm = SceneVm::new(stream, ctx);
14159
14160 let user_prop_id = 7u16;
14161 let target = vec![constants::elm::create(
14162 constants::elm::OWNER_USER_PROP,
14163 0,
14164 user_prop_id as i32,
14165 )];
14166 let mut target_cell = UserPropCell::new(vm.cfg.fm_int, target.clone());
14167 target_cell.int_value = 42;
14168 vm.user_props.insert(user_prop_id, target_cell);
14169
14170 let call_prop_id = 0;
14171 let call_prop_element = vec![constants::elm::create(
14172 constants::elm::OWNER_CALL_PROP,
14173 0,
14174 call_prop_id,
14175 )];
14176 vm.call_stack
14177 .last_mut()
14178 .expect("base call frame")
14179 .user_props
14180 .push(CallProp {
14181 scn_no: 0,
14182 prop_id: call_prop_id,
14183 form: FM_INTREF,
14184 decl_size: 0,
14185 element: target.clone(),
14186 value: CallPropValue::Element(target.clone()),
14187 });
14188
14189 vm.exec_property(call_prop_element)
14193 .expect("first reference property step");
14194 let resolved_target = vm.pop_element().expect("referenced element");
14195 assert_eq!(resolved_target, target);
14196
14197 vm.exec_property(resolved_target)
14198 .expect("second scalar property step");
14199 assert_eq!(vm.pop_int().expect("referenced int value"), 42);
14200 }
14201}
14202
14203#[cfg(test)]
14204mod command_dispatch_tests {
14205 use super::*;
14206 use crate::runtime::forms::codes::{
14207 ELM_ARRAY, ELM_INTLIST_BIT16, ELM_INTLIST_GET_SIZE, ELM_INTLIST_RESIZE,
14208 ELM_STRLIST_GET_SIZE, ELM_STRLIST_RESIZE,
14209 };
14210 use crate::scene_stream::SceneStream;
14211 use std::path::PathBuf;
14212
14213 fn empty_scene_chunk() -> Vec<u8> {
14214 const HEADER_WORDS: usize = 33;
14215 const HEADER_SIZE: i32 = (HEADER_WORDS * 4) as i32;
14216 let mut words = [0i32; HEADER_WORDS];
14217 for idx in [
14218 0usize, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
14219 ] {
14220 words[idx] = HEADER_SIZE;
14221 }
14222 let mut out = Vec::with_capacity(HEADER_SIZE as usize);
14223 for word in words {
14224 out.extend_from_slice(&word.to_le_bytes());
14225 }
14226 out
14227 }
14228
14229 fn test_vm() -> SceneVm<'static> {
14230 let chunk = Box::leak(empty_scene_chunk().into_boxed_slice());
14231 let stream = SceneStream::new(chunk).expect("empty scene stream");
14232 SceneVm::new(stream, CommandContext::new(PathBuf::from(".")))
14233 }
14234
14235 #[test]
14236 fn legacy_global_world_slot_routes_camera_state_to_back_and_front() {
14237 let mut vm = test_vm();
14238 let mut args = vec![Value::Int(2304), Value::Int(80), Value::Int(3840)];
14239 let fm_void = vm.cfg.fm_void;
14240 vm.exec_command(
14241 vec![
14242 LEGACY_GLOBAL_WORLD_ELEMENT,
14243 ELM_ARRAY,
14244 0,
14245 crate::runtime::forms::codes::elm_value::WORLD_SET_CAMERA_EYE,
14246 ],
14247 0,
14248 fm_void,
14249 &mut args,
14250 )
14251 .expect("legacy GLOBAL.WORLD SET_CAMERA_EYE");
14252 vm.exec_assign(
14253 vec![
14254 LEGACY_GLOBAL_WORLD_ELEMENT,
14255 ELM_ARRAY,
14256 0,
14257 crate::runtime::forms::codes::elm_value::WORLD_CAMERA_VIEW_ANGLE,
14258 ],
14259 1,
14260 Value::Int(700),
14261 )
14262 .expect("legacy GLOBAL.WORLD CAMERA_VIEW_ANGLE");
14263
14264 let stage_form = if vm.ctx.ids.form_global_stage != 0 {
14265 vm.ctx.ids.form_global_stage
14266 } else {
14267 crate::runtime::forms::codes::FORM_GLOBAL_STAGE
14268 };
14269 let stage = &vm.ctx.globals.stage_forms[&stage_form];
14270 for stage_idx in [0i64, 1] {
14271 let world = &stage.world_lists[&stage_idx][0];
14272 assert_eq!(world.camera_eye_x.get_value(), 2304);
14273 assert_eq!(world.camera_eye_y.get_value(), 80);
14274 assert_eq!(world.camera_eye_z.get_value(), 3840);
14275 assert_eq!(world.camera_view_angle, 700);
14276 }
14277
14278 vm.exec_property(vec![
14279 LEGACY_GLOBAL_WORLD_ELEMENT,
14280 ELM_ARRAY,
14281 0,
14282 crate::runtime::forms::codes::elm_value::WORLD_CAMERA_VIEW_ANGLE,
14283 ])
14284 .expect("legacy GLOBAL.WORLD property read");
14285 assert_eq!(vm.pop_int().expect("camera view angle"), 700);
14286 }
14287
14288 #[test]
14289 fn current_global_msgbtn_is_not_reinterpreted_as_legacy_world() {
14290 let vm = test_vm();
14291 assert!(
14292 vm.legacy_global_world_stage_chain(&[LEGACY_GLOBAL_WORLD_ELEMENT], 1)
14293 .is_none()
14294 );
14295 }
14296
14297 #[test]
14298 fn save_summary_survives_deferred_message_savepoint_and_page_clear() {
14299 use crate::runtime::forms::{stage, syscom};
14300 let mut vm = test_vm();
14301 vm.ctx.globals.script.async_msg_mode = true;
14302 let project =
14303 std::env::temp_dir().join(format!("siglus-save-summary-{}", std::process::id()));
14304 vm.ctx.project_dir = project.clone();
14305 for (index, text) in ["最初のページ", "次のページ"].iter().enumerate() {
14306 assert!(stage::cd_text_current_mwnd(&mut vm.ctx, text, index as i64));
14307 vm.drain_runtime_save_load_requests().unwrap();
14308 assert_eq!(vm.ctx.local_save_snapshot.as_ref().unwrap().save_msg, *text);
14309 assert_eq!(
14310 vm.ctx.local_save_snapshot.as_ref().unwrap().save_full_msg,
14311 *text
14312 );
14313 syscom::append_current_save_message(&mut vm.ctx, " 続き");
14314 vm.perform_runtime_save_request(RuntimeSaveRequest {
14315 kind: RuntimeSaveKind::Normal,
14316 index,
14317 })
14318 .unwrap();
14319 let path = vm
14320 .runtime_save_file_path(RuntimeSaveKind::Normal, index)
14321 .unwrap();
14322 let header = crate::original_save::read_header_from_path(&path).unwrap();
14323 assert_eq!(header.message, format!("{text} 続き"));
14324 assert_eq!(header.full_message, header.message);
14325 vm.exec_command(
14327 vec![
14328 vm.ctx.ids.form_global_syscom as i32,
14329 crate::runtime::forms::codes::elm_value::SYSCOM_GET_SAVE_MESSAGE,
14330 ],
14331 0,
14332 vm.cfg.fm_str,
14333 &mut vec![Value::Int(index as i64)],
14334 )
14335 .unwrap();
14336 assert_eq!(vm.pop_str().unwrap(), header.message);
14337 for form in vm.ctx.globals.stage_forms.values_mut() {
14339 for list in form.mwnd_lists.values_mut() {
14340 for mwnd in list {
14341 mwnd.clear_ready = true;
14342 mwnd.msg_block_started = false;
14343 }
14344 }
14345 }
14346 }
14347 std::fs::remove_dir_all(project).unwrap();
14348 }
14349
14350 #[test]
14351 fn save_summary_keeps_text_appended_before_deferred_snapshot() {
14352 use crate::runtime::forms::syscom::append_current_save_message;
14353 let mut vm = test_vm();
14354 vm.ctx.globals.syscom.current_save_message = "previous block".into();
14355 vm.ctx.request_auto_savepoint();
14356 append_current_save_message(&mut vm.ctx, "new block");
14357 vm.drain_runtime_save_load_requests().unwrap();
14358 assert_eq!(
14359 vm.ctx.local_save_snapshot.as_ref().unwrap().save_msg,
14360 "new block"
14361 );
14362 vm.build_local_save_snapshot();
14364 assert!(
14365 vm.ctx
14366 .local_save_snapshot
14367 .as_ref()
14368 .unwrap()
14369 .save_msg
14370 .is_empty()
14371 );
14372 append_current_save_message(&mut vm.ctx, "after explicit savepoint");
14373 assert_eq!(
14374 vm.ctx.local_save_snapshot.as_ref().unwrap().save_msg,
14375 "after explicit savepoint"
14376 );
14377 }
14378
14379 #[test]
14380 fn mouse_get_pos_writes_script_variable_references() {
14381 let mut vm = test_vm();
14382 let x = vec![constants::elm::create(
14383 constants::elm::OWNER_USER_PROP,
14384 0,
14385 7,
14386 )];
14387 let y = vec![constants::elm::create(
14388 constants::elm::OWNER_USER_PROP,
14389 0,
14390 8,
14391 )];
14392 for (id, target) in [(7, &x), (8, &y)] {
14393 vm.user_props
14394 .insert(id, UserPropCell::new(vm.cfg.fm_int, target.clone()));
14395 }
14396 vm.ctx.script_input.on_mouse_move(100, 380);
14399 vm.ctx.input.on_mouse_move(900, 700);
14400 vm.exec_command(
14401 vec![
14402 vm.ctx.ids.form_global_mouse as i32,
14403 vm.ctx.ids.mouse_op_get_pos,
14404 ],
14405 0,
14406 vm.cfg.fm_void,
14407 &mut vec![Value::Element(x.clone()), Value::Element(y.clone())],
14408 )
14409 .unwrap();
14410 vm.exec_property(x).unwrap();
14411 assert_eq!(vm.pop_int().unwrap(), 100);
14412 vm.exec_property(y).unwrap();
14413 assert_eq!(vm.pop_int().unwrap(), 380);
14414 }
14415
14416 #[test]
14417 fn indexed_local_settings_keep_native_field_order_and_defaults() {
14418 let mut vm = test_vm();
14419 vm.ctx.tables.gameexe = Some(crate::formats::gameexe::GameexeConfig::from_text(
14420 "#WAKU.BTN.CNT=19",
14421 ));
14422 let mut bytes = vec![0; 342];
14425 for (offset, value) in [
14426 (0, 101i32),
14427 (4, 7),
14428 (8, 48),
14429 (12, 3),
14430 (20, 2),
14431 (56, 25),
14432 (60, 300),
14433 (64, 15),
14434 ] {
14435 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
14436 }
14437 for offset in [24, 45, 46, 55, 73, 330, 341] {
14438 bytes[offset] = 1;
14439 }
14440 vm.ctx.globals.script.font_name = "previous font".into();
14441 vm.ctx.globals.script.time_stop_flag = true;
14442 let mut rd = crate::original_save::OriginalStreamReader::new(&bytes);
14443 vm.read_indexed_local_settings(&mut rd).unwrap();
14444 assert!(rd.remaining().is_empty());
14445 let script = &vm.ctx.globals.script;
14446 assert_eq!((script.cur_koe_no, script.cur_chr_no), (101, 7));
14447 assert_eq!(
14448 (script.cur_read_flag_scn_no, script.cur_read_flag_flag_no),
14449 (48, 3)
14450 );
14451 assert_eq!(script.cursor_no, 2);
14452 assert_eq!(
14453 (
14454 script.auto_mode_moji_wait,
14455 script.auto_mode_min_wait,
14456 script.msg_speed
14457 ),
14458 (25, 300, 15)
14459 );
14460 assert!(
14461 script.auto_mode_flag && script.cursor_disp_off && script.wait_display_vsync_off_flag
14462 );
14463 assert!(!script.cursor_runtime_visible);
14464 assert!(script.key_disable.contains(&255));
14465 assert!(script.font_name.is_empty() && !script.time_stop_flag);
14466 assert_eq!(vm.ctx.globals.syscom.mwnd_btn_disable.get(&18), Some(&true));
14467 assert!(vm.ctx.globals.syscom.mwnd_btn_touch_disable);
14468 assert_eq!(vm.ctx.globals.syscom.replay_koe, Some((101, 7)));
14469 }
14470
14471 #[test]
14472 fn indexed_local_probe_checks_absolute_boundaries_and_rejects_newer_streams() {
14473 use crate::original_save::OriginalStreamWriter;
14474 let mut vm = test_vm();
14475 vm.ctx.tables.gameexe = Some(crate::formats::gameexe::GameexeConfig::from_text(
14476 "#WAKU.BTN.CNT=19",
14477 ));
14478 let mut w = OriginalStreamWriter::new();
14479 for value in [48, 60, 1885] {
14480 w.push_i32(value);
14481 }
14482 SceneVm::write_cpp_proc_record(&mut w, 1, &[], 0);
14483 w.push_i32(0);
14484 for _ in 0..3 {
14485 w.push_empty_element();
14486 }
14487 w.push_padding(16);
14488 w.push_str("native title");
14489 w.push_padding(322);
14490 for _ in 0..3 {
14491 w.push_i32(0);
14492 }
14493 w.push_padding(12 + 76);
14494 w.push_str("");
14495 w.push_padding(44 + 8);
14496 for _ in 0..7 {
14497 w.push_fixed_i32_list(&[1, 2, 3], 3);
14498 }
14499 let mut bytes = w.into_inner();
14500 assert!(vm.detect_indexed_local_stream(&bytes));
14501 bytes.pop();
14502 assert!(!vm.detect_indexed_local_stream(&bytes));
14503 for name in ["", "a", "init", "_01load", "long_scene_name"] {
14504 vm.current_scene_name = Some(name.into());
14505 assert!(
14506 !vm.detect_indexed_local_stream(&vm.build_original_local_stream()),
14507 "{name}"
14508 );
14509 }
14510 bytes[148..152].copy_from_slice(&i32::MAX.to_le_bytes());
14513 assert!(!vm.detect_indexed_local_stream(&bytes));
14514 }
14515
14516 #[test]
14517 fn short_native_records_preserve_boundaries_and_values() {
14518 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14519 let mut w = OriginalStreamWriter::new();
14520 for value in [7, 3, -1, 201] {
14521 w.push_i32(value);
14522 }
14523 w.push_raw(&[1, 0, 1, 0]);
14524 let mut object = vec![0u8; 1768];
14527 let put = |bytes: &mut [u8], offset, value: i32| {
14528 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
14529 };
14530 put(&mut object, 0, 1); put(&mut object, 12, 10);
14532 put(&mut object, 20, 300);
14533 put(&mut object, 176, 15); put(&mut object, 184, 201);
14535 put(&mut object, 188, 3); put(&mut object, 1124, 12); w.push_raw(&object);
14538 w.push_i32(12345);
14539 let bytes = w.into_inner();
14540 let mut rd = OriginalStreamReader::new(&bytes);
14541 rd.layout = NativeLocalLayout::ShortElements;
14542 let group = SceneVm::read_short_group(&mut rd).unwrap();
14543 assert_eq!(
14544 (group.order, group.layer, group.decided_button_no),
14545 (7, 3, 201)
14546 );
14547 assert!(group.started && group.cancel_flag);
14548 let object = SceneVm::read_short_object(&mut rd).unwrap();
14549 assert_eq!((object.rect_param.left, object.rect_param.right), (10, 300));
14550 assert_eq!((object.button.button_no, object.button.group_no), (201, 3));
14551 assert!(object.button.enabled);
14552 assert_eq!(object.runtime.prop_events.src_clip_left.value, 12);
14553 assert_eq!(rd.i32().unwrap(), 12345);
14554 assert!(rd.remaining().is_empty());
14555 }
14556
14557 #[test]
14558 fn short_native_nested_returns_keep_the_saved_result_types() {
14559 let mut vm = test_vm();
14560 let mut root = vm.scene_base_call();
14561 root.return_scene_name = Some("caller".into());
14562 root.return_line_no = 51;
14563 let mut gosub = vm.scene_base_call();
14564 gosub.call_type = 1;
14565 gosub.ret_form = vm.cfg.fm_int;
14566 gosub.return_scene_name = Some("caller".into());
14567 gosub.return_line_no = 237;
14568 let mut farcall = vm.scene_base_call();
14569 farcall.call_type = 2;
14570 farcall.ret_form = vm.cfg.fm_str;
14571 let mut frames = vec![root, gosub, farcall];
14572 vm.normalize_short_call_returns(&mut frames);
14573 assert_eq!(frames[0].return_line_no, 51);
14574 assert_eq!(frames[1].return_line_no, 237);
14575 vm.call_stack = frames;
14576 vm.scene_stack.push(SceneExecFrame {
14577 stream: vm.stream.clone(),
14578 user_cmd_names: vm.user_cmd_names.clone(),
14579 call_cmd_names: vm.call_cmd_names.clone(),
14580 current_scene_no: None,
14581 current_scene_name: Some("caller".into()),
14582 current_line_no: 237,
14583 call_depth: 3,
14584 });
14585 assert!(
14586 vm.return_from_scene(vec![Value::Str("result".into())])
14587 .unwrap()
14588 );
14589 assert_eq!(vm.pop_str().unwrap(), "result");
14590 vm.exec_return(Vec::new()).unwrap();
14591 assert_eq!(vm.pop_int().unwrap(), 0);
14593 assert_eq!(vm.call_stack.len(), 1);
14594 assert!(vm.int_stack.is_empty() && vm.str_stack.is_empty());
14595 }
14596
14597 #[test]
14598 fn short_native_backlog_has_one_speaker_name() {
14599 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14600 let mut w = OriginalStreamWriter::new();
14601 w.push_i32(1);
14602 w.push_bool(false);
14603 w.push_str("saved dialogue");
14604 w.push_str("speaker");
14605 w.push_i32(12);
14606 w.push_i32(34);
14607 w.push_extend_i32_list(&[123]);
14608 w.push_extend_i32_list(&[4]);
14609 w.push_i32(0);
14610 w.push_str("debug dialogue");
14611 w.push_i32(30);
14612 w.push_i32(3301);
14613 for value in [0, 0, 1] {
14614 w.push_i32(value);
14615 }
14616 w.push_bool(true);
14617 let bytes = w.into_inner();
14618 let mut rd = OriginalStreamReader::new(&bytes);
14619 rd.layout = NativeLocalLayout::ShortElements;
14620 let backlog = SceneVm::read_cpp_msg_back(&mut rd).unwrap();
14621 let entry = &backlog.history[0];
14622 assert_eq!(entry.msg_str, "saved dialogue");
14623 assert_eq!(entry.original_name, "speaker");
14624 assert_eq!(entry.disp_name, "speaker");
14625 assert_eq!((entry.pct_pos_x, entry.pct_pos_y), (12, 34));
14626 assert_eq!(entry.koe_no_list, vec![123]);
14627 assert_eq!(entry.debug_msg, "debug dialogue");
14628 assert_eq!((entry.scn_no, entry.line_no), (30, 3301));
14629 assert!(backlog.new_msg_flag);
14630 assert_eq!(backlog.history_insert_pos, 1);
14631 assert!(rd.remaining().is_empty());
14632 let mut truncated = OriginalStreamReader::new(&bytes[..bytes.len() - 1]);
14633 truncated.layout = NativeLocalLayout::ShortElements;
14634 assert!(SceneVm::read_cpp_msg_back(&mut truncated).is_err());
14635 }
14636
14637 #[test]
14638 fn short_native_probe_and_settings_have_their_own_layout() {
14639 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14640 let mut vm = test_vm();
14641 let buttons = vm.mwnd_waku_btn_count();
14642 let mut w = OriginalStreamWriter::new();
14643 for value in [0, 20, 30] {
14644 w.push_i32(value);
14645 }
14646 w.push_padding(82); w.push_i32(0); w.push_padding(3 * 64);
14649 let settings_offset = w.position();
14650 for value in [123, 4, 0, 77] {
14651 w.push_i32(value);
14652 }
14653 w.push_str("test title");
14654 w.push_padding(283 + buttons);
14655 let settings_end = w.position();
14656 w.push_padding(12 + 12 + 76); for _ in 0..6 {
14658 w.push_fixed_i32_list(&[1, 2], 2);
14659 }
14660 let mut bytes = w.into_inner();
14661 assert_eq!(
14662 vm.detect_indexed_local_layout(&bytes),
14663 Some(NativeLocalLayout::ShortElements)
14664 );
14665 let mut rd = OriginalStreamReader::new(&bytes);
14666 rd.layout = NativeLocalLayout::ShortElements;
14667 rd.skip(settings_offset).unwrap();
14668 vm.read_short_local_settings(&mut rd).unwrap();
14669 assert_eq!(rd.remaining().len(), bytes.len() - settings_end);
14670 assert_eq!(vm.ctx.globals.script.cur_koe_no, 123);
14671 assert_eq!(vm.ctx.globals.script.cur_chr_no, 4);
14672 assert_eq!(vm.ctx.globals.syscom.current_save_scene_title, "test title");
14673 assert_eq!(vm.ctx.globals.script.auto_mode_min_wait, -1);
14674 bytes.pop();
14675 assert_eq!(vm.detect_indexed_local_layout(&bytes), None);
14676 }
14677
14678 #[test]
14679 fn short_native_object_save_flags_follow_ini_order_and_ranges() {
14680 let mut vm = test_vm();
14681 vm.ctx.tables.gameexe = Some(siglus_assets::gameexe::GameexeConfig::from_text(
14682 "#OBJECT.000-255.SAVE = 1\n#OBJECT.250-255.SAVE = 0\n#OBJECT.252.SAVE = 1\n#OBJECT.252.USE = 0\n#OBJECT.254.SAVE = 1\n",
14683 ));
14684 assert!(vm.short_object_slot_saved(249));
14685 assert!(!vm.short_object_slot_saved(250));
14686 assert!(!vm.short_object_slot_saved(252));
14687 assert!(vm.short_object_slot_saved(254));
14688 assert!(!vm.short_object_slot_saved(255));
14689 }
14690
14691 #[test]
14692 fn indexed_local_groups_and_objects_keep_record_boundaries() {
14693 let mut bytes = vec![0; 152 + 2108];
14694 bytes[0..4].copy_from_slice(&7i32.to_le_bytes());
14695 bytes[20] = 1; bytes.extend_from_slice(&12345i32.to_le_bytes());
14697 let mut rd = crate::original_save::OriginalStreamReader::new(&bytes);
14698 rd.layout = NativeLocalLayout::Indexed;
14699 let group = SceneVm::read_cpp_group(&mut rd).unwrap();
14700 assert_eq!(group.order, 7);
14701 assert!(group.started);
14702 assert_eq!(rd.remaining().len(), 2108 + 4);
14703 let object = SceneVm::read_early_object(&mut rd).unwrap();
14704 assert!(!object.used);
14705 assert_eq!(rd.i32().unwrap(), 12345);
14706 assert!(rd.remaining().is_empty());
14707 }
14708
14709 #[test]
14710 fn create_emote_dispatches_with_position_overload_and_reinitializes_on_empty_file() {
14711 use crate::runtime::forms::codes;
14712 let mut vm = test_vm();
14713 let object = vec![
14714 codes::ELM_GLOBAL_FRONT,
14715 codes::ELM_STAGE_OBJECT,
14716 ELM_ARRAY,
14717 0,
14718 codes::ELM_OBJECT_CREATE_EMOTE,
14719 ];
14720 let mut args = vec![
14721 Value::Int(640),
14722 Value::Int(720),
14723 Value::Str("missing_emote_fixture".into()),
14724 Value::Int(1),
14725 Value::Int(320),
14726 Value::Int(80),
14727 Value::NamedArg {
14728 id: 0,
14729 value: Box::new(Value::Int(12)),
14730 },
14731 Value::NamedArg {
14732 id: 1,
14733 value: Box::new(Value::Int(-8)),
14734 },
14735 ];
14736 vm.exec_command(object.clone(), 2, vm.cfg.fm_void, &mut args)
14739 .unwrap();
14740 let stage = &vm.ctx.globals.stage_forms[&vm.ctx.ids.form_global_stage];
14741 let obj = &stage.object_lists[&1][0];
14742 assert_eq!(obj.object_type, 12);
14743 assert_eq!(obj.file_name.as_deref(), Some("missing_emote_fixture"));
14744 assert_eq!((obj.emote.width, obj.emote.height), (640, 720));
14745 assert_eq!((obj.emote.rep_x, obj.emote.rep_y), (12, -8));
14746 assert_eq!(obj.get_int_prop(&vm.ctx.ids, vm.ctx.ids.obj_disp), 1);
14747 assert_eq!(obj.get_int_prop(&vm.ctx.ids, vm.ctx.ids.obj_x), 320);
14748 assert_eq!(obj.get_int_prop(&vm.ctx.ids, vm.ctx.ids.obj_y), 80);
14749 assert!(vm.ctx.stack.is_empty() && vm.int_stack.is_empty());
14750
14751 vm.exec_command(
14752 object,
14753 0,
14754 vm.cfg.fm_void,
14755 &mut vec![Value::Int(640), Value::Int(720), Value::Str(String::new())],
14756 )
14757 .unwrap();
14758 let stage = &vm.ctx.globals.stage_forms[&vm.ctx.ids.form_global_stage];
14759 assert_eq!(stage.object_lists[&1][0].object_type, 0);
14760 }
14761
14762 #[test]
14763 fn current_local_save_layout_still_restores_font_and_stacks() {
14764 let mut source = test_vm();
14765 source.current_scene_name = Some("saved_scene".to_owned());
14766 source.current_line_no = 62;
14767 source.ctx.globals.script.font_name = "Test Font".to_owned();
14768 source.ctx.globals.script.font_bold = 1;
14769 source.ctx.globals.script.font_shadow = 2;
14770 source.int_stack = vec![-1, 123];
14771 source.str_stack = vec!["saved string".to_owned()];
14772 let bytes = source.build_original_local_stream();
14773 let mut restored = test_vm();
14774 let snapshot = restored.parse_original_local_stream(&bytes).unwrap();
14775 assert_eq!(snapshot.scene_name, "saved_scene");
14776 assert_eq!(snapshot.line_no, 62);
14777 assert_eq!(snapshot.int_stack, source.int_stack);
14778 assert_eq!(snapshot.str_stack, source.str_stack);
14779 assert_eq!(restored.ctx.globals.script.font_name, "Test Font");
14780 assert_eq!(restored.ctx.globals.script.font_bold, 1);
14781 assert_eq!(restored.ctx.globals.script.font_shadow, 2);
14782 }
14783
14784 #[test]
14785 fn early_object_record_preserves_animation_and_next_record_boundary() {
14786 use crate::original_save::OriginalStreamReader;
14787 let mut bytes = vec![0u8; 2115];
14790 for (offset, value) in [
14791 (0, 1i32),
14792 (364, 1),
14793 (372, 7), (412, 50),
14795 (416, 2),
14796 (420, -1), (432, 120),
14798 (472, 200),
14799 (476, 240), (480, 30),
14801 (484, 60),
14802 (492, 100),
14803 (496, 170),
14804 (500, 240),
14805 ] {
14806 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
14807 }
14808 bytes.extend_from_slice(&42i32.to_le_bytes());
14809 let mut rd = OriginalStreamReader::new(&bytes);
14810 rd.layout = NativeLocalLayout::Early;
14811 let obj = SceneVm::read_cpp_object(&mut rd).unwrap();
14812 assert_eq!(obj.object_type, 1);
14813 assert_eq!(obj.runtime.prop_events.patno.value, 7);
14814 assert_eq!((obj.base.order, obj.base.layer), (50, 2));
14815 assert!(!obj.button.enabled);
14816 assert_eq!(obj.runtime.prop_events.x.value, 120);
14817 assert_eq!(obj.runtime.prop_events.y.value, 240);
14818 assert_eq!(obj.runtime.prop_events.y.cur_value, 170);
14819 assert_eq!(
14820 (
14821 obj.runtime.prop_events.y.cur_time,
14822 obj.runtime.prop_events.y.end_time
14823 ),
14824 (30, 60)
14825 );
14826 assert!(obj.runtime.child_objects.is_empty());
14827 assert_eq!(rd.i32().unwrap(), 42);
14828 }
14829
14830 #[test]
14831 fn early_message_page_restores_packed_flags_and_glyphs_without_outline_fields() {
14832 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14833 let mut w = OriginalStreamWriter::new();
14834 for value in [
14835 24, 3, 20, 40, 0, 0, 34, -1, 16, 2, 1, 12, 2, 38, 0, 0, 0, 0, 0, 0,
14836 ] {
14837 w.push_i32(value);
14838 }
14839 for value in [3, 1, 16] {
14840 w.push_i32(value);
14841 } w.push_raw(&('「' as u16).to_le_bytes());
14843 w.push_i32(1);
14844 w.push_i32(0);
14845 w.push_bool(true); w.push_bool(false);
14847 w.push_i32(50);
14848 w.push_i32(60);
14849 w.push_bool(true);
14850 w.push_i32(1);
14851 w.push_i32(0);
14852 w.push_str("A");
14853 w.push_str("ruby");
14854 w.push_i32(1);
14855 for value in [0, 'A' as i32, 34, 2, 1, 20, 40] {
14856 w.push_i32(value);
14857 }
14858 w.push_bool(true);
14859 w.push_bool(false);
14860 w.push_i32(42);
14861 let bytes = w.into_inner();
14862 let mut rd = OriginalStreamReader::new(&bytes);
14863 rd.layout = NativeLocalLayout::Early;
14864 let mut m = runtime::globals::MwndState::default();
14865 let page = SceneVm::read_cpp_mwnd_message(&mut rd, &mut m).unwrap();
14866 assert_eq!(page.msg_text, "A");
14867 assert_eq!(page.cursor_pos, (20, 40));
14868 assert_eq!(page.indent_moji, Some('「'));
14869 assert!(page.cur_msg_type_decided && page.ruby_start_ready);
14870 assert!(!page.line_head);
14871 assert_eq!(page.ruby_start_pos, (50, 60));
14872 assert_eq!(page.glyphs[0].x, 20);
14873 assert_eq!(page.glyphs[0].y, 40);
14874 assert!(page.glyphs[0].appeared);
14875 assert!(!page.glyphs[0].fuchi);
14876 assert_eq!(page.msgbtn, None);
14877 assert_eq!(rd.i32().unwrap(), 42);
14878 }
14879
14880 #[test]
14881 fn legacy_local_pod_restores_flags_without_consuming_stack_data() {
14882 use crate::original_save::OriginalStreamReader;
14883 let mut bytes = vec![0u8; 344];
14884 for (offset, value) in [(0, 12i32), (4, 3), (52, 585), (56, -1)] {
14885 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
14886 }
14887 bytes[67] = 1; bytes[74] = 1; bytes[76 + 65] = 1; bytes[332] = 1; bytes[342] = 1; bytes.extend_from_slice(&42i32.to_le_bytes());
14893 let mut vm = test_vm();
14894 vm.ctx.globals.script.font_bold = 1;
14895 vm.ctx.globals.script.font_shadow = 2;
14896 let mut rd = OriginalStreamReader::new(&bytes);
14897 vm.read_cpp_local_data_pod(&mut rd, false).unwrap();
14898 let script = &vm.ctx.globals.script;
14899 assert_eq!((script.cur_koe_no, script.cur_chr_no), (12, 3));
14900 assert_eq!(script.msg_back_save_cntr, 585);
14901 assert_eq!((script.font_bold, script.font_shadow), (-1, -1));
14902 assert!(vm.ctx.globals.syscom.mwnd_btn_disable_all);
14903 assert!(script.cursor_disp_off && script.key_disable.contains(&65));
14904 assert!(script.quake_stop_flag && script.stage_time_stop_flag);
14905 assert_eq!(rd.i32().unwrap(), 42);
14906 }
14907
14908 #[test]
14909 fn legacy_object_gan_work_does_not_shift_the_next_object() {
14910 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14911 let vm = test_vm();
14912 let mut bytes = Vec::new();
14913 for name in ["first", "second"] {
14914 let mut obj = runtime::globals::ObjectState {
14915 file_name: Some(name.to_owned()),
14916 ..Default::default()
14917 };
14918
14919 let mut w = OriginalStreamWriter::new();
14920 vm.write_cpp_object(&mut w, &obj);
14921 let mut record = w.into_inner();
14922 let child_count = record.split_off(record.len() - 4);
14924 for value in [123i32, 2, 3] {
14925 record.extend_from_slice(&value.to_le_bytes());
14926 }
14927 record.extend_from_slice(&[1, 0, 1, 0, 1, 1, 0]);
14928 record.extend_from_slice(&child_count);
14929 bytes.extend(record);
14930 }
14931 let mut rd = OriginalStreamReader::new(&bytes);
14932 rd.layout = NativeLocalLayout::Legacy;
14933 for name in ["first", "second"] {
14934 let obj = SceneVm::read_cpp_object(&mut rd).unwrap();
14935 assert_eq!(obj.file_name.as_deref(), Some(name));
14936 assert!(obj.runtime.child_objects.is_empty());
14937 }
14938 assert!(rd.remaining().is_empty());
14939 }
14940
14941 #[test]
14942 fn legacy_backlog_entries_do_not_have_save_id_check_bytes() {
14943 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
14944 for early in [false, true] {
14945 let mut w = OriginalStreamWriter::new();
14946 w.push_i32(2);
14947 for message in ["first", "second"] {
14948 w.push_bool(false);
14949 w.push_str(message);
14950 w.push_str("name");
14951 w.push_str("display name");
14952 for value in [0, 0, 1, 123, 1, 2, 0] {
14953 w.push_i32(value);
14954 }
14955 w.push_str("");
14956 w.push_i32(36);
14957 w.push_i32(29);
14958 if !early {
14959 w.push_tid_zero();
14960 }
14961 }
14962 for value in [0, 1, 2] {
14963 w.push_i32(value);
14964 }
14965 if early {
14966 w.push_bool(true);
14967 } else {
14968 w.push_i32(1);
14969 }
14970 w.push_i32(42);
14971 let bytes = w.into_inner();
14972 let mut rd = OriginalStreamReader::new(&bytes);
14973 rd.layout = if early {
14974 NativeLocalLayout::Early
14975 } else {
14976 NativeLocalLayout::Legacy
14977 };
14978 let backlog = SceneVm::read_cpp_msg_back(&mut rd).unwrap();
14979 assert_eq!(backlog.history_cnt, 2);
14980 assert_eq!(backlog.history[0].msg_str, "first");
14981 assert_eq!(backlog.history[1].msg_str, "second");
14982 assert_eq!(backlog.history[1].koe_no_list, vec![123]);
14983 assert_eq!(backlog.history[1].chr_no_list, vec![2]);
14984 assert_eq!(backlog.history_insert_pos, 2);
14985 assert!(backlog.new_msg_flag);
14986 assert_eq!(rd.i32().unwrap(), 42);
14987 }
14988 }
14989
14990 #[test]
14991 fn get_line_no_returns_current_script_line_through_command_dispatch() {
14992 let mut chunk = empty_scene_chunk();
14993 let mut code = Vec::new();
14994 for line in [29i32, 83] {
14995 code.push(CD_NL);
14996 code.extend_from_slice(&line.to_le_bytes());
14997 }
14998 chunk[8..12].copy_from_slice(&(code.len() as i32).to_le_bytes());
14999 chunk.extend_from_slice(&code);
15000 let stream = SceneStream::new(Box::leak(chunk.into_boxed_slice())).unwrap();
15001 let mut vm = SceneVm::new(stream, CommandContext::new(PathBuf::from(".")));
15002
15003 for expected in [-1, 29, 83] {
15004 if expected >= 0 {
15005 assert!(vm.step_inner(false).unwrap());
15006 }
15007 vm.exec_command(
15008 vec![constants::elm_value::GLOBAL_GET_LINE_NO],
15009 0,
15010 vm.cfg.fm_int,
15011 &mut vec![],
15012 )
15013 .unwrap();
15014 assert_eq!(vm.pop_int().unwrap(), expected);
15015 assert!(vm.int_stack.is_empty());
15016 assert!(vm.ctx.stack.is_empty());
15017 }
15018 }
15019
15020 #[test]
15021 fn excall_indexed_stage_creates_menu_objects_and_preserves_properties() {
15022 use crate::runtime::forms::{codes, excall};
15023
15024 let mut vm = test_vm();
15025 vm.exec_command(
15026 vec![codes::ELM_GLOBAL_EXCALL, codes::ELM_EXCALL_ALLOC],
15027 0,
15028 vm.cfg.fm_void,
15029 &mut vec![],
15030 )
15031 .unwrap();
15032 let stage_form = excall::tick_targets(&vm.ctx).stage_form_id;
15033 for stage_idx in 0..3 {
15034 let mut object = vec![
15035 codes::ELM_GLOBAL_EXCALL,
15036 codes::ELM_EXCALL_STAGE,
15037 ELM_ARRAY,
15038 stage_idx,
15039 codes::ELM_STAGE_OBJECT,
15040 ELM_ARRAY,
15041 10,
15042 ];
15043 object.push(codes::ELM_OBJECT_CREATE_RECT);
15044 vm.exec_command(
15045 object.clone(),
15046 0,
15047 vm.cfg.fm_void,
15048 &mut [0, 0, 100, 80, 255, 255, 255, 255, 1]
15049 .into_iter()
15050 .map(Value::Int)
15051 .collect(),
15052 )
15053 .unwrap();
15054 assert!(
15055 vm.ctx.globals.stage_forms[&stage_form].object_lists[&(stage_idx as i64)][10].used
15056 );
15057
15058 *object.last_mut().unwrap() = codes::ELM_OBJECT_X;
15059 vm.exec_assign(object.clone(), 1, Value::Int(123 + stage_idx as i64))
15060 .unwrap();
15061 vm.exec_property(object).unwrap();
15062 assert_eq!(vm.pop_int().unwrap(), 123 + stage_idx);
15063 }
15064 assert!(
15065 !vm.ctx.render_list_with_effects().is_empty(),
15066 "menu objects must be drawable"
15067 );
15068 }
15069
15070 #[test]
15071 fn excall_indexed_stage_button_group_accepts_right_click_cancel() {
15072 use crate::runtime::forms::codes;
15073 use crate::runtime::input::VmMouseButton;
15074
15075 let mut vm = test_vm();
15076 vm.exec_command(
15077 vec![codes::ELM_GLOBAL_EXCALL, codes::ELM_EXCALL_ALLOC],
15078 0,
15079 vm.cfg.fm_void,
15080 &mut vec![],
15081 )
15082 .unwrap();
15083 vm.ctx.excall_state.ex_call_flag = true;
15084 let mut group = vec![
15085 codes::ELM_GLOBAL_EXCALL,
15086 codes::ELM_EXCALL_STAGE,
15087 ELM_ARRAY,
15088 1,
15089 codes::STAGE_ELM_OBJBTNGROUP,
15090 ELM_ARRAY,
15091 2,
15092 constants::GROUP_INIT,
15093 ];
15094 vm.exec_command(group.clone(), 0, vm.cfg.fm_void, &mut vec![])
15095 .unwrap();
15096 *group.last_mut().unwrap() = constants::GROUP_START_CANCEL;
15097 vm.exec_command(group.clone(), 0, vm.cfg.fm_void, &mut vec![])
15098 .unwrap();
15099 *group.last_mut().unwrap() = constants::GROUP_GET_DECIDED_NO;
15100 vm.exec_command(group.clone(), 0, vm.cfg.fm_int, &mut vec![])
15101 .unwrap();
15102 assert_eq!(vm.pop_int().unwrap(), -2);
15103 vm.ctx.on_mouse_down(VmMouseButton::Right);
15104 vm.ctx.on_mouse_up(VmMouseButton::Right);
15105 vm.exec_command(group, 0, vm.cfg.fm_int, &mut vec![])
15106 .unwrap();
15107 assert_eq!(vm.pop_int().unwrap(), -1);
15108 }
15109
15110 #[test]
15111 fn polling_menu_group_restart_preserves_held_child_button() {
15112 use crate::runtime::forms::{codes, excall};
15113 use crate::runtime::input::VmMouseButton;
15114
15115 for start_op in [constants::GROUP_START, constants::GROUP_START_CANCEL] {
15116 for release_inside in [true, false] {
15117 let mut vm = test_vm();
15118 vm.exec_command(
15119 vec![codes::ELM_GLOBAL_EXCALL, codes::ELM_EXCALL_ALLOC],
15120 0,
15121 vm.cfg.fm_void,
15122 &mut vec![],
15123 )
15124 .unwrap();
15125 vm.ctx.excall_state.ex_call_flag = true;
15126 let mut object = vec![
15127 codes::ELM_GLOBAL_EXCALL,
15128 codes::ELM_EXCALL_FRONT,
15129 codes::ELM_STAGE_OBJECT,
15130 ELM_ARRAY,
15131 62,
15132 ];
15133 for rect in [
15134 [0, 0, 500, 400, 255, 255, 255, 255, 1, 100, 100],
15135 [0, 0, 200, 80, 255, 255, 255, 255, 1, 20, 30],
15136 ] {
15137 let mut command = object.clone();
15138 command.push(codes::ELM_OBJECT_CREATE_RECT);
15139 vm.exec_command(
15140 command,
15141 2,
15142 vm.cfg.fm_void,
15143 &mut rect.into_iter().map(Value::Int).collect(),
15144 )
15145 .unwrap();
15146 if object.len() == 5 {
15147 object.extend([codes::ELM_OBJECT_CHILD, ELM_ARRAY, 1]);
15148 }
15149 }
15150 object.push(codes::ELM_OBJECT_SET_BUTTON);
15151 vm.exec_command(
15152 object,
15153 2,
15154 vm.cfg.fm_void,
15155 &mut [201, 0, 15, -1].into_iter().map(Value::Int).collect(),
15156 )
15157 .unwrap();
15158 let group = vec![
15159 codes::ELM_GLOBAL_EXCALL,
15160 codes::ELM_EXCALL_FRONT,
15161 codes::STAGE_ELM_OBJBTNGROUP,
15162 ELM_ARRAY,
15163 0,
15164 start_op,
15165 ];
15166 vm.exec_command(group.clone(), 0, vm.cfg.fm_void, &mut vec![])
15167 .unwrap();
15168 vm.ctx.on_mouse_move(150, 150);
15169 vm.ctx.on_mouse_down(VmMouseButton::Left);
15170 let form = excall::tick_targets(&vm.ctx).stage_form_id;
15171 assert_eq!(
15172 vm.ctx.globals.stage_forms[&form].group_lists[&1][0].pushed_button_no,
15173 201
15174 );
15175
15176 for _ in 0..3 {
15179 vm.exec_command(group.clone(), 0, vm.cfg.fm_void, &mut vec![])
15180 .unwrap();
15181 vm.ctx.input.next_frame();
15182 vm.ctx.tick_frame();
15183 let mut get_pushed = group.clone();
15184 *get_pushed.last_mut().unwrap() = constants::GROUP_GET_PUSHED_NO;
15185 vm.exec_command(get_pushed, 0, vm.cfg.fm_int, &mut vec![])
15186 .unwrap();
15187 assert_eq!(vm.pop_int().unwrap(), 201);
15188 }
15189 if !release_inside {
15190 vm.ctx.on_mouse_move(900, 600);
15191 }
15192 vm.ctx.on_mouse_up(VmMouseButton::Left);
15193 let state = &vm.ctx.globals.stage_forms[&form].group_lists[&1][0];
15194 assert_eq!(
15195 state.decided_button_no,
15196 if release_inside { 201 } else { -2 }
15197 );
15198 assert_eq!(state.pushed_button_no, -1);
15199 }
15200 }
15201 }
15202
15203 #[test]
15204 fn dialog_child_buttons_inherit_parent_layer_for_hover_and_click() {
15205 use crate::runtime::forms::{codes, excall};
15206 use crate::runtime::input::VmMouseButton;
15207
15208 for group_no in [-1, 8] {
15209 let mut vm = test_vm();
15210 vm.exec_command(
15211 vec![codes::ELM_GLOBAL_EXCALL, codes::ELM_EXCALL_ALLOC],
15212 0,
15213 vm.cfg.fm_void,
15214 &mut vec![],
15215 )
15216 .unwrap();
15217 vm.ctx.excall_state.ex_call_flag = true;
15218 let root = vec![
15219 codes::ELM_GLOBAL_EXCALL,
15220 codes::ELM_EXCALL_FRONT,
15221 codes::ELM_STAGE_OBJECT,
15222 ELM_ARRAY,
15223 69,
15224 ];
15225 let mut child = root.clone();
15226 child.extend([codes::ELM_OBJECT_CHILD, ELM_ARRAY, 1]);
15227 for (object, rect, button_no, layer) in [
15228 (
15229 &root,
15230 [0, 0, 400, 200, 255, 255, 255, 255, 1, 100, 200],
15231 111,
15232 300,
15233 ),
15234 (&child, [0, 0, 80, 40, 255, 255, 255, 255, 1, 20, 30], 1, 0),
15235 ] {
15236 let mut command = object.clone();
15237 command.push(codes::ELM_OBJECT_CREATE_RECT);
15238 vm.exec_command(
15239 command,
15240 2,
15241 vm.cfg.fm_void,
15242 &mut rect.into_iter().map(Value::Int).collect(),
15243 )
15244 .unwrap();
15245 let mut property = object.clone();
15246 property.push(codes::ELM_OBJECT_LAYER);
15247 vm.exec_assign(property, 1, Value::Int(layer)).unwrap();
15248 let mut command = object.clone();
15249 command.push(codes::ELM_OBJECT_SET_BUTTON);
15250 vm.exec_command(
15251 command,
15252 2,
15253 vm.cfg.fm_void,
15254 &mut [button_no, group_no, 1, -1]
15255 .into_iter()
15256 .map(Value::Int)
15257 .collect(),
15258 )
15259 .unwrap();
15260 }
15261 if group_no >= 0 {
15262 vm.exec_command(
15263 vec![
15264 codes::ELM_GLOBAL_EXCALL,
15265 codes::ELM_EXCALL_FRONT,
15266 codes::STAGE_ELM_OBJBTNGROUP,
15267 ELM_ARRAY,
15268 group_no as i32,
15269 constants::GROUP_START_CANCEL,
15270 ],
15271 0,
15272 vm.cfg.fm_void,
15273 &mut vec![],
15274 )
15275 .unwrap();
15276 }
15277 let form = excall::tick_targets(&vm.ctx).stage_form_id;
15278 vm.ctx.on_mouse_move(140, 245);
15279 let obj = &vm.ctx.globals.stage_forms[&form].object_lists[&1][69];
15280 assert!(
15281 obj.runtime.child_objects[1].button.hit,
15282 "child must win over the dialog background"
15283 );
15284 assert!(!obj.button.hit);
15285 vm.ctx.on_mouse_down(VmMouseButton::Left);
15286 assert!(
15287 vm.ctx.globals.stage_forms[&form].object_lists[&1][69]
15288 .runtime
15289 .child_objects[1]
15290 .button
15291 .pushed
15292 );
15293 vm.ctx.on_mouse_up(VmMouseButton::Left);
15294 if group_no >= 0 {
15295 assert_eq!(
15296 vm.ctx.globals.stage_forms[&form].group_lists[&1][group_no as usize]
15297 .decided_button_no,
15298 1
15299 );
15300 }
15301 }
15302 }
15303
15304 #[test]
15305 fn local_menu_flags_are_not_shadowed_by_object_children() {
15306 use crate::runtime::forms::codes;
15307 let mut vm = test_vm();
15308 let parent = vec![
15309 codes::ELM_GLOBAL_FRONT,
15310 codes::STAGE_ELM_OBJECT,
15311 ELM_ARRAY,
15312 95,
15313 ];
15314 let mut child = parent.clone();
15315 child.extend([
15316 codes::ELM_OBJECT_CHILD,
15317 ELM_ARRAY,
15318 codes::ELM_GLOBAL_B,
15319 codes::ELM_OBJECT_CREATE_RECT,
15320 ]);
15321 vm.exec_command(
15322 child,
15323 0,
15324 vm.cfg.fm_void,
15325 &mut vec![Value::Int(16), Value::Int(16), Value::Int(255)],
15326 )
15327 .unwrap();
15328 let flag = vec![codes::ELM_GLOBAL_B, ELM_ARRAY, 43];
15329 vm.ctx
15330 .globals
15331 .int_lists
15332 .entry(codes::ELM_GLOBAL_B as u32)
15333 .or_default()
15334 .resize(1000, 0);
15335 vm.ctx
15336 .globals
15337 .int_lists
15338 .get_mut(&(codes::ELM_GLOBAL_B as u32))
15339 .unwrap()[43] = -1;
15340
15341 for context in [
15344 None,
15345 Some(vec![
15346 codes::FORM_GLOBAL_STAGE as i32,
15347 ELM_ARRAY,
15348 1,
15349 codes::STAGE_ELM_OBJECT,
15350 ELM_ARRAY,
15351 95,
15352 ]),
15353 ] {
15354 vm.ctx.globals.current_object_chain = context;
15355 vm.ctx.globals.current_stage_object = Some((1, 95));
15356 vm.exec_property(flag.clone()).unwrap();
15357 assert_eq!(vm.pop_int().unwrap(), -1);
15358 vm.exec_assign(flag.clone(), 1, Value::Int(7)).unwrap();
15359 assert_eq!(
15360 vm.ctx.globals.int_lists[&(codes::ELM_GLOBAL_B as u32)][43],
15361 7
15362 );
15363 vm.exec_assign(flag.clone(), 1, Value::Int(-1)).unwrap();
15364 }
15365 }
15366
15367 #[test]
15368 fn op_seen_flag_reads_and_writes_the_persistent_global_list() {
15369 use crate::runtime::forms::codes::ELM_GLOBAL_G;
15370 let mut vm = test_vm();
15371 let elm = vec![ELM_GLOBAL_G, ELM_ARRAY, 153];
15372 vm.ctx
15373 .globals
15374 .int_lists
15375 .entry(ELM_GLOBAL_G as u32)
15376 .or_default()
15377 .resize(1000, 0);
15378 vm.ctx
15379 .globals
15380 .int_lists
15381 .get_mut(&(ELM_GLOBAL_G as u32))
15382 .unwrap()[153] = 1;
15383 vm.exec_property(elm.clone()).unwrap();
15384 assert_eq!(
15385 vm.pop_int().unwrap(),
15386 1,
15387 "read the OP flag restored from global.sav"
15388 );
15389 vm.exec_assign(elm.clone(), 1, Value::Int(2)).unwrap();
15390 assert_eq!(vm.ctx.globals.int_lists[&(ELM_GLOBAL_G as u32)][153], 2);
15391 vm.ctx.reset_for_scene_restart();
15392 vm.exec_property(elm).unwrap();
15393 assert_eq!(vm.pop_int().unwrap(), 2);
15394 }
15395
15396 #[test]
15397 fn wait_wipe_without_active_transition_returns_zero_each_time() {
15398 let mut vm = test_vm();
15399 for _ in 0..2 {
15400 vm.exec_command(
15401 vec![constants::elm_value::GLOBAL_WAIT_WIPE],
15402 0,
15403 vm.cfg.fm_int,
15404 &mut vec![],
15405 )
15406 .expect("WAIT_WIPE after the transition has finished");
15407 assert_eq!(vm.pop_int().unwrap(), 0);
15408 assert!(!vm.ctx.wait_poll());
15409 assert!(vm.ctx.stack.is_empty(), "return is delivered only once");
15410 }
15411 }
15412
15413 #[test]
15414 fn wait_wipe_returns_completion_result_but_inline_wipe_wait_is_void() {
15415 for (explicit_wait, key_skip) in
15416 [(true, false), (true, true), (false, false), (false, true)]
15417 {
15418 let mut vm = test_vm();
15419 vm.exec_command(
15420 vec![constants::elm_value::GLOBAL_WIPE],
15421 0,
15422 vm.cfg.fm_void,
15423 &mut vec![],
15424 )
15425 .unwrap();
15426 if explicit_wait {
15427 vm.exec_command(
15428 vec![constants::elm_value::GLOBAL_WAIT_WIPE],
15429 0,
15430 vm.cfg.fm_int,
15431 &mut vec![Value::NamedArg {
15432 id: 0,
15433 value: Box::new(Value::Int(1)),
15434 }],
15435 )
15436 .unwrap();
15437 } else {
15438 vm.ctx.wait.wait_wipe(true);
15439 }
15440 assert!(vm.ctx.wait_poll());
15441 if key_skip {
15442 assert!(vm.ctx.wait.notify_key(&mut vm.ctx.globals, &vm.ctx.ids));
15443 }
15444 vm.ctx.finish_wipe_runtime();
15445 assert!(!vm.ctx.wait_poll());
15446 if explicit_wait {
15447 assert_eq!(
15448 vm.ctx.pop().and_then(|v| v.as_i64()),
15449 Some(if key_skip { 1 } else { 0 })
15450 );
15451 }
15452 assert!(vm.ctx.stack.is_empty());
15453 assert!(!vm.ctx.wait_poll());
15454 assert!(vm.ctx.stack.is_empty());
15455 }
15456 }
15457
15458 #[test]
15459 fn wipe_without_arguments_starts_the_default_transition() {
15460 let mut vm = test_vm();
15461 vm.exec_command(
15462 vec![constants::elm_value::GLOBAL_WIPE],
15463 0,
15464 vm.cfg.fm_void,
15465 &mut vec![],
15466 )
15467 .unwrap();
15468 assert!(
15469 vm.ctx.globals.wipe.is_some(),
15470 "WIPE() is a real script command"
15471 );
15472 assert!(vm.ctx.wait.wipe);
15473 assert!(vm.is_blocked());
15474 }
15475
15476 #[test]
15477 fn global_title_commands_read_and_update_the_saved_scene_title() {
15478 let mut vm = test_vm();
15479 vm.exec_command(
15480 vec![constants::elm_value::GLOBAL_GET_TITLE],
15481 0,
15482 vm.cfg.fm_str,
15483 &mut vec![],
15484 )
15485 .unwrap();
15486 assert_eq!(vm.pop_str().unwrap(), "");
15487
15488 for title in ["第一章", "エピローグ", ""] {
15489 vm.exec_command(
15490 vec![constants::elm_value::GLOBAL_SET_TITLE],
15491 0,
15492 vm.cfg.fm_void,
15493 &mut vec![Value::Str(title.into())],
15494 )
15495 .unwrap();
15496 vm.exec_command(
15497 vec![constants::elm_value::GLOBAL_GET_TITLE],
15498 0,
15499 vm.cfg.fm_str,
15500 &mut vec![],
15501 )
15502 .unwrap();
15503 assert_eq!(vm.pop_str().unwrap(), title);
15504 assert!(vm.ctx.stack.is_empty());
15505 vm.build_local_save_snapshot();
15506 assert_eq!(
15507 vm.ctx
15508 .local_save_snapshot
15509 .as_ref()
15510 .unwrap()
15511 .save_scene_title,
15512 title
15513 );
15514 }
15515 }
15516
15517 #[test]
15518 fn global_get_scene_name_returns_active_scene_on_string_stack() {
15519 let mut vm = test_vm();
15520 for scene in ["_start", "menu", "frame_action_scene"] {
15521 vm.current_scene_name = Some(scene.into());
15522 vm.ctx.current_scene_name = Some(scene.into());
15523 vm.exec_command(
15524 vec![constants::elm_value::GLOBAL_GET_SCENE_NAME],
15525 0,
15526 vm.cfg.fm_str,
15527 &mut vec![],
15528 )
15529 .unwrap();
15530 assert_eq!(vm.pop_str().unwrap(), scene);
15531 assert!(vm.ctx.stack.is_empty());
15532 }
15533 }
15534
15535 #[test]
15536 fn global_owari_yields_to_host_for_exit_without_confirmation() {
15537 use crate::runtime::globals::SyscomPendingProcKind;
15538
15539 let mut vm = test_vm();
15540 vm.ctx.globals.syscom.menu_open = true;
15541 let generation = vm.ctx.proc_generation();
15542 vm.exec_command(
15543 vec![constants::elm_value::GLOBAL_OWARI],
15544 0,
15545 vm.cfg.fm_void,
15546 &mut vec![],
15547 )
15548 .expect("OWARI requests normal host shutdown");
15549 let pending = vm.ctx.globals.syscom.pending_proc.as_ref().unwrap();
15550 assert_eq!(pending.kind, SyscomPendingProcKind::EndGame);
15551 assert!(!pending.warning && !pending.se_play && !pending.fade_out);
15552 assert!(!vm.ctx.globals.syscom.menu_open);
15553 assert_ne!(vm.ctx.proc_generation(), generation);
15554 assert!(vm.ctx.stack.is_empty());
15555 }
15556
15557 #[test]
15558 fn global_returnmenu_matches_original_overload_control_flow() {
15559 use crate::runtime::globals::SyscomPendingProcKind;
15560
15561 let mut vm = test_vm();
15562 for (al_id, mut args, expected_target, expected_kind, expected_warning, expected_fade) in [
15563 (
15564 2,
15565 vec![Value::Str("menu".into()), Value::Int(7)],
15566 Some(("menu".into(), 7)),
15567 SyscomPendingProcKind::RestartScene,
15568 true,
15569 false,
15570 ),
15571 (
15572 1,
15573 vec![Value::Str("title".into())],
15574 Some(("title".into(), 0)),
15575 SyscomPendingProcKind::RestartScene,
15576 true,
15577 false,
15578 ),
15579 (
15580 0,
15581 vec![],
15582 None,
15583 SyscomPendingProcKind::ReturnToMenu,
15584 false,
15585 true,
15586 ),
15587 ] {
15588 let generation = vm.ctx.proc_generation();
15589 vm.exec_command(
15590 vec![constants::elm_value::GLOBAL_RETURNMENU],
15591 al_id,
15592 vm.cfg.fm_void,
15593 &mut args,
15594 )
15595 .unwrap();
15596 assert_ne!(vm.ctx.proc_generation(), generation);
15597 assert_eq!(vm.ctx.pending_scene_restart, expected_target);
15598 let pending = vm.ctx.globals.syscom.pending_proc.as_ref().unwrap();
15599 assert_eq!(pending.kind, expected_kind);
15600 assert_eq!(pending.warning, expected_warning);
15601 assert!(!pending.se_play);
15602 assert_eq!(pending.fade_out, expected_fade);
15603 assert!(!pending.leave_msgbk);
15604 assert!(vm.ctx.stack.is_empty());
15605 assert!(!vm.halted);
15606 }
15607 vm.ctx.pending_scene_restart = Some(("stale".into(), 7));
15608 vm.ctx.reset_for_scene_restart();
15609 assert!(vm.ctx.pending_scene_restart.is_none());
15610 assert!(vm.ctx.globals.syscom.pending_proc.is_none());
15611 }
15612
15613 #[test]
15614 fn user_string_commands_extract_map_date_and_time() {
15615 use crate::runtime::forms::codes::str_op;
15616
15617 let mut vm = test_vm();
15618 let prop_id = 119;
15619 vm.assign_user_prop(prop_id, None, Value::Str("0729a".into()));
15620 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
15621 for (start, len, expected) in [(0, 4, "0729"), (4, 1, "a")] {
15622 vm.exec_command(
15623 vec![head, str_op::MID],
15624 1,
15625 vm.cfg.fm_str,
15626 &mut vec![Value::Int(start), Value::Int(len)],
15627 )
15628 .expect("string MID command");
15629 assert_eq!(vm.pop_str().expect("substring"), expected);
15630 }
15631 vm.exec_command(vec![head, str_op::CNT], 0, vm.cfg.fm_int, &mut vec![])
15632 .expect("string CNT command");
15633 assert_eq!(vm.pop_int().expect("string length"), 5);
15634 assert_eq!(vm.user_props[&prop_id].str_value, "0729a");
15635 }
15636
15637 #[test]
15638 fn user_prop_strlist_resize_and_get_size_are_not_silently_ignored() {
15639 let mut vm = test_vm();
15640 let prop_id = 0u16;
15641 let mut cell = UserPropCell::new(
15642 vm.cfg.fm_strlist,
15643 vm.default_user_prop_element(prop_id, vm.cfg.fm_strlist),
15644 );
15645 cell.str_list.clear();
15646 vm.user_props.insert(prop_id, cell);
15647
15648 assert!(
15649 vm.exec_user_prop_list_command(
15650 prop_id,
15651 &[ELM_STRLIST_RESIZE],
15652 0,
15653 vm.cfg.fm_void,
15654 &[Value::Int(1)],
15655 )
15656 .expect("STRLIST.RESIZE")
15657 );
15658 assert_eq!(vm.user_props[&prop_id].str_list.len(), 1);
15659
15660 assert!(vm
15661 .exec_user_prop_list_command(
15662 prop_id,
15663 &[ELM_STRLIST_GET_SIZE],
15664 0,
15665 vm.cfg.fm_int,
15666 &[],
15667 )
15668 .expect("STRLIST.GET_SIZE"));
15669 assert_eq!(vm.pop_int().expect("STRLIST size"), 1);
15670 }
15671
15672 #[test]
15673 fn user_prop_intlist_resize_and_get_size_are_not_silently_ignored() {
15674 let mut vm = test_vm();
15675 let prop_id = 1u16;
15676 let mut cell = UserPropCell::new(
15677 vm.cfg.fm_intlist,
15678 vm.default_user_prop_element(prop_id, vm.cfg.fm_intlist),
15679 );
15680 cell.int_list.clear();
15681 vm.user_props.insert(prop_id, cell);
15682
15683 assert!(
15684 vm.exec_user_prop_list_command(
15685 prop_id,
15686 &[ELM_INTLIST_RESIZE],
15687 0,
15688 vm.cfg.fm_void,
15689 &[Value::Int(1)],
15690 )
15691 .expect("INTLIST.RESIZE")
15692 );
15693 assert_eq!(vm.user_props[&prop_id].int_list.len(), 1);
15694
15695 assert!(vm
15696 .exec_user_prop_list_command(
15697 prop_id,
15698 &[ELM_INTLIST_GET_SIZE],
15699 0,
15700 vm.cfg.fm_int,
15701 &[],
15702 )
15703 .expect("INTLIST.GET_SIZE"));
15704 assert_eq!(vm.pop_int().expect("INTLIST size"), 1);
15705 }
15706
15707 #[test]
15708 fn user_prop_packed_intlist_assignment_preserves_list_storage_and_element_stack() {
15709 let mut vm = test_vm();
15710 let prop_id = 98u16;
15711 let mut cell = UserPropCell::new(
15712 vm.cfg.fm_intlist,
15713 vm.default_user_prop_element(prop_id, vm.cfg.fm_intlist),
15714 );
15715 cell.int_list = vec![0; 200];
15716 vm.user_props.insert(prop_id, cell);
15717
15718 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
15719 let bit16 = vec![head, ELM_INTLIST_BIT16, ELM_ARRAY, 319];
15720
15721 vm.exec_assign(bit16.clone(), 1, Value::Int(0x5A3C))
15722 .expect("BIT16 assignment");
15723
15724 let cell = &vm.user_props[&prop_id];
15725 assert_eq!(cell.form, vm.cfg.fm_intlist);
15726 assert_eq!(cell.int_list.len(), 200);
15727 assert_eq!(
15728 SceneVm::get_user_int_list_value(&cell.int_list, 16, 319),
15729 Some(0x5A3C)
15730 );
15731
15732 vm.exec_property(bit16).expect("BIT16 property");
15733 assert_eq!(vm.pop_int().expect("BIT16 value"), 0x5A3C);
15734 assert!(vm.element_points.is_empty());
15735 assert!(vm.int_stack.is_empty());
15736 }
15737
15738 #[test]
15739 fn call_prop_packed_intlist_assignment_preserves_list_storage() {
15740 let vm = test_vm();
15741 let prop_id = 7;
15742 let element = vec![constants::elm::create(
15743 constants::elm::OWNER_CALL_PROP,
15744 0,
15745 prop_id,
15746 )];
15747 let mut prop = CallProp {
15748 scn_no: 0,
15749 prop_id,
15750 form: vm.cfg.fm_intlist,
15751 decl_size: 2,
15752 element,
15753 value: CallPropValue::IntList(vec![0; 2]),
15754 };
15755
15756 SceneVm::assign_call_prop_result(
15757 &mut prop,
15758 &[ELM_INTLIST_BIT16, ELM_ARRAY, 3],
15759 Value::Int(0x1234),
15760 )
15761 .expect("CALL_PROP BIT16 assignment");
15762
15763 assert_eq!(prop.form, vm.cfg.fm_intlist);
15764 let CallPropValue::IntList(values) = &prop.value else {
15765 panic!("CALL_PROP INTLIST storage changed form");
15766 };
15767 assert_eq!(values.len(), 2);
15768 assert_eq!(
15769 SceneVm::get_user_int_list_value(values, 16, 3),
15770 Some(0x1234)
15771 );
15772 }
15773
15774 #[test]
15775 fn negative_user_prop_list_index_does_not_replace_the_list_root() {
15776 let mut vm = test_vm();
15777 let prop_id = 0u16;
15778 let mut cell = UserPropCell::new(
15779 vm.cfg.fm_strlist,
15780 vm.default_user_prop_element(prop_id, vm.cfg.fm_strlist),
15781 );
15782 cell.str_list = vec!["keep".to_string()];
15783 vm.user_props.insert(prop_id, cell);
15784
15785 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
15786 vm.exec_assign(
15787 vec![head, ELM_ARRAY, -1],
15788 1,
15789 Value::Str("wrong".to_string()),
15790 )
15791 .expect("negative list assignment");
15792
15793 let cell = &vm.user_props[&prop_id];
15794 assert_eq!(cell.form, vm.cfg.fm_strlist);
15795 assert_eq!(cell.str_list, vec!["keep".to_string()]);
15796 }
15797}
15798#[cfg(test)]
15799mod call_frame_save_metadata_tests {
15800 use super::*;
15801 use crate::original_save::{OriginalStreamReader, OriginalStreamWriter};
15802 use crate::scene_stream::SceneStream;
15803 use std::path::PathBuf;
15804
15805 fn empty_scene_chunk() -> Vec<u8> {
15806 const HEADER_WORDS: usize = 33;
15807 const HEADER_SIZE: i32 = (HEADER_WORDS * 4) as i32;
15808 let mut words = [0i32; HEADER_WORDS];
15809 for idx in [
15810 0usize, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
15811 ] {
15812 words[idx] = HEADER_SIZE;
15813 }
15814 let mut out = Vec::with_capacity(HEADER_SIZE as usize);
15815 for word in words {
15816 out.extend_from_slice(&word.to_le_bytes());
15817 }
15818 out
15819 }
15820
15821 fn test_call_frame(
15822 return_pc: usize,
15823 ret_form: i32,
15824 return_override: Option<(usize, i32)>,
15825 ) -> CallFrame {
15826 CallFrame {
15827 call_type: 3,
15828 return_pc,
15829 return_scene_no: None,
15830 return_scene_name: None,
15831 return_line_no: -1,
15832 ret_form,
15833 return_override,
15834 excall_proc: false,
15835 frame_action_proc: false,
15836 arg_cnt: 0,
15837 delayed_ret_form: None,
15838 user_props: Vec::new(),
15839 int_args: Vec::new(),
15840 str_args: Vec::new(),
15841 }
15842 }
15843
15844 #[test]
15845 fn exec_return_prefers_per_callee_continuation_for_inline_calls() {
15846 let chunk = Box::leak(empty_scene_chunk().into_boxed_slice());
15847 let stream = SceneStream::new(chunk).expect("empty scene stream");
15848 let mut vm = SceneVm::new(stream, CommandContext::new(PathBuf::from(".")));
15849 vm.call_stack.clear();
15850 vm.call_stack.push(test_call_frame(8, vm.cfg.fm_void, None));
15851 vm.call_stack.push(test_call_frame(
15852 0,
15853 vm.cfg.fm_void,
15854 Some((12, vm.cfg.fm_int)),
15855 ));
15856
15857 assert!(
15858 !vm.exec_return(vec![Value::Int(77)])
15859 .expect("return from inline call")
15860 );
15861 assert_eq!(vm.stream.get_prg_cntr(), 12);
15862 assert_eq!(vm.pop_int().expect("inline return value"), 77);
15863 }
15864
15865 #[test]
15866 fn exec_return_falls_back_to_original_caller_metadata_for_saved_frames() {
15867 let chunk = Box::leak(empty_scene_chunk().into_boxed_slice());
15868 let stream = SceneStream::new(chunk).expect("empty scene stream");
15869 let mut vm = SceneVm::new(stream, CommandContext::new(PathBuf::from(".")));
15870 vm.call_stack.clear();
15871 vm.call_stack.push(test_call_frame(16, vm.cfg.fm_int, None));
15872 vm.call_stack.push(test_call_frame(0, vm.cfg.fm_void, None));
15873
15874 assert!(
15875 !vm.exec_return(vec![Value::Int(91)])
15876 .expect("return from saved-style call")
15877 );
15878 assert_eq!(vm.stream.get_prg_cntr(), 16);
15879 assert_eq!(vm.pop_int().expect("saved-style return value"), 91);
15880 }
15881
15882 #[test]
15883 fn call_frame_round_trip_keeps_caller_lexer_metadata() {
15884 let chunk = Box::leak(empty_scene_chunk().into_boxed_slice());
15885 let stream = SceneStream::new(chunk).expect("empty scene stream");
15886 let vm = SceneVm::new(stream, CommandContext::new(PathBuf::from(".")));
15887 let frame = CallFrame {
15888 call_type: 2,
15889 return_pc: 0x1234,
15890 return_scene_no: Some(7),
15891 return_scene_name: Some("caller_scene".to_string()),
15892 return_line_no: 2605,
15893 ret_form: vm.cfg.fm_void,
15894 return_override: None,
15895 excall_proc: false,
15896 frame_action_proc: false,
15897 arg_cnt: 0,
15898 delayed_ret_form: None,
15899 user_props: Vec::new(),
15900 int_args: Vec::new(),
15901 str_args: Vec::new(),
15902 };
15903 let mut writer = OriginalStreamWriter::new();
15904 vm.write_cpp_call_frame(&mut writer, &frame);
15905 let bytes = writer.into_inner();
15906 let mut reader = OriginalStreamReader::new(&bytes);
15907 let restored = vm.read_cpp_call_frame(&mut reader).expect("call frame");
15908 assert_eq!(restored.call_type, 2);
15909 assert_eq!(restored.return_pc, 0x1234);
15910 assert_eq!(restored.return_scene_name.as_deref(), Some("caller_scene"));
15911 assert_eq!(restored.return_line_no, 2605);
15912 }
15913}