1use anyhow::{anyhow, bail, Result};
4use std::collections::BTreeMap;
5use std::fmt::Write as _;
6
7use crate::elm_code;
8use crate::runtime::globals::{
9 ObjectFrameActionState, PendingButtonAction, PendingButtonActionKind, PendingFrameActionFinish,
10};
11use crate::runtime::{self, constants, CommandContext, RuntimeLoadRequest, RuntimeSaveKind, RuntimeSaveRequest, Value};
12use crate::scene_stream::SceneStream;
13use siglus_assets::scene_pck::{find_scene_pck_in_project, ScenePck, ScenePckDecodeOptions};
14
15const CD_NONE: u8 = constants::cd::NONE;
16const CD_NL: u8 = constants::cd::NL;
17const CD_PUSH: u8 = constants::cd::PUSH;
18const CD_POP: u8 = constants::cd::POP;
19const CD_COPY: u8 = constants::cd::COPY;
20const CD_PROPERTY: u8 = constants::cd::PROPERTY;
21const CD_COPY_ELM: u8 = constants::cd::COPY_ELM;
22const CD_DEC_PROP: u8 = constants::cd::DEC_PROP;
23const CD_ELM_POINT: u8 = constants::cd::ELM_POINT;
24const CD_ARG: u8 = constants::cd::ARG;
25
26const CD_GOTO: u8 = constants::cd::GOTO;
27const CD_GOTO_TRUE: u8 = constants::cd::GOTO_TRUE;
28const CD_GOTO_FALSE: u8 = constants::cd::GOTO_FALSE;
29const CD_GOSUB: u8 = constants::cd::GOSUB;
30const CD_GOSUBSTR: u8 = constants::cd::GOSUBSTR;
31const CD_RETURN: u8 = constants::cd::RETURN;
32const CD_EOF: u8 = constants::cd::EOF;
33
34const CD_ASSIGN: u8 = constants::cd::ASSIGN;
35const CD_OPERATE_1: u8 = constants::cd::OPERATE_1;
36const CD_OPERATE_2: u8 = constants::cd::OPERATE_2;
37
38const CD_COMMAND: u8 = constants::cd::COMMAND;
39const CD_TEXT: u8 = constants::cd::TEXT;
40const CD_NAME: u8 = constants::cd::NAME;
41const CD_SEL_BLOCK_START: u8 = constants::cd::SEL_BLOCK_START;
42const CD_SEL_BLOCK_END: u8 = constants::cd::SEL_BLOCK_END;
43
44const OP_PLUS: u8 = constants::op::PLUS;
45const OP_MINUS: u8 = constants::op::MINUS;
46const OP_MULTIPLE: u8 = constants::op::MULTIPLE;
47const OP_DIVIDE: u8 = constants::op::DIVIDE;
48const OP_AMARI: u8 = constants::op::AMARI;
49
50const OP_EQUAL: u8 = constants::op::EQUAL;
51const OP_NOT_EQUAL: u8 = constants::op::NOT_EQUAL;
52const OP_GREATER: u8 = constants::op::GREATER;
53const OP_GREATER_EQUAL: u8 = constants::op::GREATER_EQUAL;
54const OP_LESS: u8 = constants::op::LESS;
55const OP_LESS_EQUAL: u8 = constants::op::LESS_EQUAL;
56
57const OP_LOGICAL_AND: u8 = constants::op::LOGICAL_AND;
58const OP_LOGICAL_OR: u8 = constants::op::LOGICAL_OR;
59
60const OP_TILDE: u8 = constants::op::TILDE;
61const OP_AND: u8 = constants::op::AND;
62const OP_OR: u8 = constants::op::OR;
63const OP_HAT: u8 = constants::op::HAT;
64const OP_SL: u8 = constants::op::SL;
65const OP_SR: u8 = constants::op::SR;
66const OP_SR3: u8 = constants::op::SR3;
67
68const CALL_SCRATCH_SIZE: usize = 32;
70
71#[derive(Debug, Clone, Copy)]
76pub struct VmConfig {
77 pub fm_void: i32,
78 pub fm_int: i32,
79 pub fm_str: i32,
80 pub fm_label: i32,
81 pub fm_list: i32,
82 pub fm_intlist: i32,
83 pub fm_strlist: i32,
84 pub max_steps: u64,
85}
86
87impl VmConfig {
88 pub fn from_env() -> Self {
89 fn env_u64(key: &str, default: u64) -> u64 {
90 std::env::var(key)
91 .ok()
92 .and_then(|v| v.parse::<u64>().ok())
93 .unwrap_or(default)
94 }
95
96 Self {
97 fm_void: constants::fm::VOID,
98 fm_int: constants::fm::INT,
99 fm_str: constants::fm::STR,
100 fm_label: constants::fm::LABEL,
101 fm_list: constants::fm::LIST,
102 fm_intlist: constants::fm::INTLIST,
103 fm_strlist: constants::fm::STRLIST,
104 max_steps: env_u64("SIGLUS_VM_MAX_STEPS", 0),
105 }
106 }
107}
108
109#[derive(Debug, Clone)]
110struct CallProp {
111 prop_id: i32,
112 form: i32,
113 decl_size: usize,
114 element: Vec<i32>,
115 value: CallPropValue,
116}
117
118#[derive(Debug, Clone)]
119enum CallPropValue {
120 Int(i32),
121 Str(String),
122 Element(Vec<i32>),
123 IntList(Vec<i32>),
124 StrList(Vec<String>),
125}
126
127#[derive(Debug, Clone)]
128struct CallFrame {
129 return_pc: usize,
130 ret_form: i32,
131 return_override: Option<(usize, i32)>,
132 excall_proc: bool,
133 frame_action_proc: bool,
134 arg_cnt: usize,
135 delayed_ret_form: Option<i32>,
136 user_props: Vec<CallProp>,
137 int_args: Vec<i32>,
138 str_args: Vec<String>,
139}
140
141#[derive(Debug, Clone)]
142struct UserPropCell {
143 form: i32,
144 int_value: i32,
145 str_value: String,
146 element: Vec<i32>,
147 int_list: Vec<i32>,
148 str_list: Vec<String>,
149 list_items: Vec<UserPropCell>,
150}
151
152impl UserPropCell {
153 fn new(form: i32, element: Vec<i32>) -> Self {
154 Self {
155 form,
156 int_value: 0,
157 str_value: String::new(),
158 element,
159 int_list: Vec::new(),
160 str_list: Vec::new(),
161 list_items: Vec::new(),
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
167struct SceneExecFrame<'a> {
168 stream: SceneStream<'a>,
169 user_cmd_names: std::collections::HashMap<u32, String>,
170 call_cmd_names: std::collections::HashMap<u32, String>,
171 int_stack: Vec<i32>,
172 str_stack: Vec<String>,
173 element_points: Vec<usize>,
174 call_stack: Vec<CallFrame>,
175 gosub_return_stack: Vec<(usize, i32)>,
176 user_props: BTreeMap<u16, UserPropCell>,
177 current_scene_no: Option<usize>,
178 current_scene_name: Option<String>,
179 current_line_no: i32,
180 ret_form: i32,
181 excall_proc: bool,
182}
183
184
185#[derive(Debug, Clone)]
186struct InterpreterExecState<'a> {
187 stream: SceneStream<'a>,
188 user_cmd_names: std::collections::HashMap<u32, String>,
189 call_cmd_names: std::collections::HashMap<u32, String>,
190 int_stack: Vec<i32>,
191 str_stack: Vec<String>,
192 element_points: Vec<usize>,
193 call_stack: Vec<CallFrame>,
194 gosub_return_stack: Vec<(usize, i32)>,
195 user_props: BTreeMap<u16, UserPropCell>,
196 scene_stack: Vec<SceneExecFrame<'a>>,
197 current_scene_no: Option<usize>,
198 current_scene_name: Option<String>,
199 current_line_no: i32,
200}
201
202#[derive(Debug, Clone)]
203struct RuntimeDiskSnapshot {
204 scene_name: String,
205 scene_no: i32,
206 line_no: i32,
207 pc: i32,
208 int_stack: Vec<i32>,
209 str_stack: Vec<String>,
210 element_points: Vec<usize>,
211 call_stack: Vec<CallFrame>,
212}
213
214fn resize_i64_vec(mut v: Vec<i64>, n: usize) -> Vec<i64> {
215 v.resize(n, 0);
216 v
217}
218
219fn resize_string_vec(mut v: Vec<String>, n: usize) -> Vec<String> {
220 v.resize_with(n, String::new);
221 v
222}
223
224#[derive(Clone)]
225struct VmResumePoint<'a> {
226 stream: SceneStream<'a>,
227 user_cmd_names: std::collections::HashMap<u32, String>,
228 call_cmd_names: std::collections::HashMap<u32, String>,
229 int_stack: Vec<i32>,
230 str_stack: Vec<String>,
231 element_points: Vec<usize>,
232 call_stack: Vec<CallFrame>,
233 gosub_return_stack: Vec<(usize, i32)>,
234 user_props: BTreeMap<u16, UserPropCell>,
235 current_scene_no: Option<usize>,
236 current_scene_name: Option<String>,
237 current_line_no: i32,
238 globals: runtime::globals::GlobalState,
239}
240
241pub struct SceneVm<'a> {
242 pub cfg: VmConfig,
243 stream: SceneStream<'a>,
244
245 pub ctx: CommandContext,
246
247 int_stack: Vec<i32>,
249 str_stack: Vec<String>,
250 element_points: Vec<usize>,
251
252 call_stack: Vec<CallFrame>,
253 gosub_return_stack: Vec<(usize, i32)>,
254 user_props: BTreeMap<u16, UserPropCell>,
255 scene_stack: Vec<SceneExecFrame<'a>>,
256 save_point: Option<VmResumePoint<'a>>,
257 sel_point_stack: Vec<VmResumePoint<'a>>,
258 current_scene_no: Option<usize>,
259 current_scene_name: Option<String>,
260 current_line_no: i32,
261
262 pub unknown_opcodes: BTreeMap<u8, u64>,
263 pub unknown_forms: BTreeMap<i32, u64>,
264
265 steps: u64,
266 halted: bool,
267
268 delayed_ret_form: Option<i32>,
270 script_input_synced_this_frame: bool,
271 yield_safe_after_step: bool,
272
273 user_cmd_names: std::collections::HashMap<u32, String>,
274 call_cmd_names: std::collections::HashMap<u32, String>,
275
276 scene_pck_cache: Option<ScenePck>,
279 scene_stream_cache: BTreeMap<usize, SceneStream<'a>>,
280}
281
282#[derive(Debug, Clone)]
283struct FrameActionWork {
284 stage_idx: i64,
285 obj_idx: usize,
286 ch_idx: Option<usize>,
287 global_form_id: Option<u32>,
288 object_chain: Option<Vec<i32>>,
289 frame_action_chain: Option<Vec<i32>>,
290 scn_name: String,
291 cmd_name: String,
292 args: Vec<Value>,
293 count: i64,
294 end_time: i64,
295}
296
297impl<'a> SceneVm<'a> {
298 fn trace_unknown_form(&mut self, form_code: i32, site: &str) {
299 *self.unknown_forms.entry(form_code).or_insert(0) += 1;
300 if std::env::var_os("SIGLUS_TRACE_UNKNOWN_FORMS").is_some() {
301 eprintln!(
302 "[vm unknown form] site={} form={} pc=0x{:x}",
303 site,
304 form_code,
305 self.stream.get_prg_cntr()
306 );
307 }
308 }
309
310 fn blank_call_int_args() -> Vec<i32> {
311 vec![0; CALL_SCRATCH_SIZE]
312 }
313
314 fn blank_call_str_args() -> Vec<String> {
315 vec![String::new(); CALL_SCRATCH_SIZE]
316 }
317
318 fn make_call_frame(
319 &self,
320 ret_form: i32,
321 excall_proc: bool,
322 frame_action_proc: bool,
323 arg_cnt: usize,
324 scratch_args: Option<(Vec<i32>, Vec<String>)>,
325 ) -> CallFrame {
326 let (int_args, str_args) = scratch_args
327 .unwrap_or_else(|| (Self::blank_call_int_args(), Self::blank_call_str_args()));
328 CallFrame {
329 return_pc: 0,
330 ret_form,
331 return_override: None,
332 excall_proc,
333 frame_action_proc,
334 arg_cnt,
335 delayed_ret_form: None,
336 user_props: Vec::new(),
337 int_args,
338 str_args,
339 }
340 }
341
342 fn shared_user_prop_count(&self) -> usize {
343 self.scene_pck_cache
344 .as_ref()
345 .map(|pck| pck.inc_props.len())
346 .unwrap_or_else(|| self.stream.header.scn_prop_cnt.max(0) as usize)
347 }
348
349 fn enter_cross_scene_user_prop_scope(&mut self) -> BTreeMap<u16, UserPropCell> {
350 let saved_user_props = std::mem::take(&mut self.user_props);
351 let shared_count = self.shared_user_prop_count();
352 self.user_props = saved_user_props
353 .iter()
354 .filter_map(|(&prop_id, cell)| {
355 if (prop_id as usize) < shared_count {
356 Some((prop_id, cell.clone()))
357 } else {
358 None
359 }
360 })
361 .collect();
362 saved_user_props
363 }
364
365 fn restore_cross_scene_user_prop_scope(
366 &mut self,
367 mut saved_user_props: BTreeMap<u16, UserPropCell>,
368 ) {
369 let shared_count = self.shared_user_prop_count();
370 for prop_id in 0..shared_count {
371 saved_user_props.remove(&(prop_id as u16));
372 }
373 for (&prop_id, cell) in self.user_props.iter() {
374 if (prop_id as usize) < shared_count {
375 saved_user_props.insert(prop_id, cell.clone());
376 }
377 }
378 self.user_props = saved_user_props;
379 }
380
381 fn capture_interpreter_exec_state(&self) -> InterpreterExecState<'a> {
382 InterpreterExecState {
383 stream: self.stream.clone(),
384 user_cmd_names: self.user_cmd_names.clone(),
385 call_cmd_names: self.call_cmd_names.clone(),
386 int_stack: self.int_stack.clone(),
387 str_stack: self.str_stack.clone(),
388 element_points: self.element_points.clone(),
389 call_stack: self.call_stack.clone(),
390 gosub_return_stack: self.gosub_return_stack.clone(),
391 user_props: self.user_props.clone(),
392 scene_stack: self.scene_stack.clone(),
393 current_scene_no: self.current_scene_no,
394 current_scene_name: self.current_scene_name.clone(),
395 current_line_no: self.current_line_no,
396 }
397 }
398
399 fn restore_interpreter_exec_state(&mut self, saved: InterpreterExecState<'a>) {
400 self.stream = saved.stream;
401 self.user_cmd_names = saved.user_cmd_names;
402 self.call_cmd_names = saved.call_cmd_names;
403 self.int_stack = saved.int_stack;
404 self.str_stack = saved.str_stack;
405 self.element_points = saved.element_points;
406 self.call_stack = saved.call_stack;
407 self.gosub_return_stack = saved.gosub_return_stack;
408 self.user_props = saved.user_props;
409 self.scene_stack = saved.scene_stack;
410 self.current_scene_no = saved.current_scene_no;
411 self.current_scene_name = saved.current_scene_name;
412 self.current_line_no = saved.current_line_no;
413 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
414 self.ctx.current_scene_name = self.current_scene_name.clone();
415 self.ctx.current_line_no = self.current_line_no as i64;
416 }
417
418 pub fn new(stream: SceneStream<'a>, ctx: CommandContext) -> Self {
419 let cfg = VmConfig::from_env();
420 let user_cmd_names = stream.scn_cmd_name_map.clone();
421 let base_call = CallFrame {
422 return_pc: 0,
423 ret_form: cfg.fm_void,
424 return_override: None,
425 excall_proc: false,
426 frame_action_proc: false,
427 arg_cnt: 0,
428 delayed_ret_form: None,
429 user_props: Vec::new(),
430 int_args: Self::blank_call_int_args(),
431 str_args: Self::blank_call_str_args(),
432 };
433 Self {
434 cfg,
435 stream,
436 ctx,
437 int_stack: Vec::new(),
438 str_stack: Vec::new(),
439 element_points: Vec::new(),
440 call_stack: vec![base_call],
441 gosub_return_stack: Vec::new(),
442 user_props: BTreeMap::new(),
443 scene_stack: Vec::new(),
444 save_point: None,
445 sel_point_stack: Vec::new(),
446 current_scene_no: None,
447 current_scene_name: None,
448 current_line_no: -1,
449 unknown_opcodes: BTreeMap::new(),
450 unknown_forms: BTreeMap::new(),
451
452 steps: 0,
453 halted: false,
454 delayed_ret_form: None,
455 script_input_synced_this_frame: false,
456 yield_safe_after_step: false,
457 user_cmd_names,
458 call_cmd_names: std::collections::HashMap::new(),
459 scene_pck_cache: None,
460 scene_stream_cache: BTreeMap::new(),
461 }
462 }
463
464 pub fn with_config(cfg: VmConfig, stream: SceneStream<'a>, ctx: CommandContext) -> Self {
465 let user_cmd_names = stream.scn_cmd_name_map.clone();
466 let base_call = CallFrame {
467 return_pc: 0,
468 ret_form: cfg.fm_void,
469 return_override: None,
470 excall_proc: false,
471 frame_action_proc: false,
472 arg_cnt: 0,
473 delayed_ret_form: None,
474 user_props: Vec::new(),
475 int_args: Self::blank_call_int_args(),
476 str_args: Self::blank_call_str_args(),
477 };
478 Self {
479 cfg,
480 stream,
481 ctx,
482 int_stack: Vec::new(),
483 str_stack: Vec::new(),
484 element_points: Vec::new(),
485 call_stack: vec![base_call],
486 gosub_return_stack: Vec::new(),
487 user_props: BTreeMap::new(),
488 scene_stack: Vec::new(),
489 save_point: None,
490 sel_point_stack: Vec::new(),
491 current_scene_no: None,
492 current_scene_name: None,
493 current_line_no: -1,
494 unknown_opcodes: BTreeMap::new(),
495 unknown_forms: BTreeMap::new(),
496
497 steps: 0,
498 halted: false,
499 delayed_ret_form: None,
500 script_input_synced_this_frame: false,
501 yield_safe_after_step: false,
502 user_cmd_names,
503 call_cmd_names: std::collections::HashMap::new(),
504 scene_pck_cache: None,
505 scene_stream_cache: BTreeMap::new(),
506 }
507 }
508
509 pub fn is_blocked(&mut self) -> bool {
510 self.ctx.wait_poll()
511 }
512
513 pub fn is_halted(&self) -> bool {
514 self.halted
515 }
516
517 pub fn proc_generation(&self) -> u64 {
518 self.ctx.proc_generation()
519 }
520
521 pub fn last_proc_kind(&self) -> runtime::ProcKind {
522 self.ctx.last_proc_kind()
523 }
524
525 pub fn current_scene_name(&self) -> Option<&str> {
526 self.current_scene_name.as_deref()
527 }
528
529 pub fn current_line_no(&self) -> i32 {
530 self.current_line_no
531 }
532
533 pub fn current_scene_no(&self) -> Option<usize> {
534 self.current_scene_no
535 }
536
537 pub fn take_runtime_load_completed(&mut self) -> bool {
538 self.ctx.take_runtime_load_completed()
539 }
540
541 pub fn call_syscom_configured_scene(&mut self, key: &str) -> Result<bool> {
542 let entry = self
551 .ctx
552 .tables
553 .gameexe
554 .as_ref()
555 .and_then(|cfg| cfg.get_entry(key).or_else(|| cfg.get_entry(&format!("#{key}"))));
556 let Some(entry) = entry else {
557 if std::env::var_os("SG_PROC_FLOW_TRACE").is_some() {
558 eprintln!(
559 "[SG_PROC_FLOW] syscom_config_scene key={} raw=<missing> scene={:?} line={} pending_proc={:?}",
560 key,
561 self.current_scene_name.as_deref(),
562 self.current_line_no,
563 self.ctx.globals.syscom.pending_proc
564 );
565 }
566 return Ok(false);
567 };
568
569 let scene_name = entry
570 .item_unquoted(0)
571 .map(|s| s.trim().trim_matches('\"').trim().to_string())
572 .unwrap_or_default();
573 let z_no = entry
574 .item_unquoted(1)
575 .and_then(|s| s.trim().parse::<i32>().ok())
576 .unwrap_or(0);
577 let raw = format!("{scene_name},{z_no}");
578
579 if scene_name.is_empty() {
580 if std::env::var_os("SG_PROC_FLOW_TRACE").is_some() {
581 eprintln!(
582 "[SG_PROC_FLOW] syscom_config_scene key={} raw={:?} target=<empty> scene={:?} line={}",
583 key,
584 raw,
585 self.current_scene_name.as_deref(),
586 self.current_line_no
587 );
588 }
589 return Ok(false);
590 }
591
592 if std::env::var_os("SG_PROC_FLOW_TRACE").is_some() {
593 eprintln!(
594 "[SG_PROC_FLOW] syscom_config_scene key={} raw={:?} target={} z={} before_scene={:?} line={} scene_stack={} call_depth={}",
595 key,
596 raw,
597 scene_name,
598 z_no,
599 self.current_scene_name.as_deref(),
600 self.current_line_no,
601 self.scene_stack.len(),
602 self.call_stack.len()
603 );
604 }
605 self.farcall_scene_name_ex(&scene_name, z_no, self.cfg.fm_void, true, &[])?;
606 if std::env::var_os("SG_PROC_FLOW_TRACE").is_some() {
607 eprintln!(
608 "[SG_PROC_FLOW] syscom_config_scene entered key={} now_scene={:?} line={} scene_stack={} call_depth={}",
609 key,
610 self.current_scene_name.as_deref(),
611 self.current_line_no,
612 self.scene_stack.len(),
613 self.call_stack.len()
614 );
615 }
616 Ok(true)
617 }
618
619 fn vm_trace_matches(&self) -> bool {
620 if std::env::var_os("SIGLUS_TRACE_VM").is_none() {
621 return false;
622 }
623 if let Ok(filter) = std::env::var("SIGLUS_TRACE_VM_SCENE") {
624 if !filter.is_empty() && self.current_scene_name.as_deref() != Some(filter.as_str()) {
625 return false;
626 }
627 }
628 if let Ok(range) = std::env::var("SIGLUS_TRACE_VM_PC") {
629 if let Some((start, end)) = range.split_once("..") {
630 let parse = |s: &str| {
631 usize::from_str_radix(s.trim_start_matches("0x"), 16)
632 .or_else(|_| s.parse::<usize>())
633 };
634 if let (Ok(start), Ok(end)) = (parse(start), parse(end)) {
635 let pc = self.stream.get_prg_cntr();
636 if pc < start || pc > end {
637 return false;
638 }
639 }
640 }
641 }
642 true
643 }
644
645 fn vm_trace_stack_summary(&self) -> String {
646 let mut out = String::new();
647 let int_tail_start = self.int_stack.len().saturating_sub(8);
648 let int_tail = &self.int_stack[int_tail_start..];
649 let _ = write!(
650 &mut out,
651 "call_depth={} int_len={} str_len={} elm_points={:?} int_tail={:?}",
652 self.call_stack.len(),
653 self.int_stack.len(),
654 self.str_stack.len(),
655 self.element_points,
656 int_tail
657 );
658 if let Some(last) = self.str_stack.last() {
659 let preview = if last.chars().count() > 48 {
660 let mut tmp = last.chars().take(48).collect::<String>();
661 tmp.push('…');
662 tmp
663 } else {
664 last.clone()
665 };
666 let _ = write!(&mut out, " str_top={:?}", preview);
667 }
668 out
669 }
670
671 fn vm_trace(&self, pc: Option<usize>, msg: impl AsRef<str>) {
672 if !self.vm_trace_matches() {
673 return;
674 }
675 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
676 let scene_no = self
677 .current_scene_no
678 .map(|v| v.to_string())
679 .unwrap_or_else(|| "-".to_string());
680 let pc_text = pc
681 .map(|v| format!("0x{v:x}"))
682 .unwrap_or_else(|| "-".to_string());
683 eprintln!(
684 "[SG_VM_TRACE] scene={} scene_no={} line={} pc={} {} | {}",
685 scene,
686 scene_no,
687 self.current_line_no,
688 pc_text,
689 msg.as_ref(),
690 self.vm_trace_stack_summary()
691 );
692 }
693
694 fn vm_opcode_name(opcode: u8) -> &'static str {
695 match opcode {
696 CD_NONE => "NONE",
697 CD_NL => "NL",
698 CD_PUSH => "PUSH",
699 CD_POP => "POP",
700 CD_COPY => "COPY",
701 CD_PROPERTY => "PROPERTY",
702 CD_COPY_ELM => "COPY_ELM",
703 CD_DEC_PROP => "DEC_PROP",
704 CD_ELM_POINT => "ELM_POINT",
705 CD_ARG => "ARG",
706 CD_GOTO => "GOTO",
707 CD_GOTO_TRUE => "GOTO_TRUE",
708 CD_GOTO_FALSE => "GOTO_FALSE",
709 CD_GOSUB => "GOSUB",
710 CD_GOSUBSTR => "GOSUBSTR",
711 CD_RETURN => "RETURN",
712 CD_EOF => "EOF",
713 CD_ASSIGN => "ASSIGN",
714 CD_OPERATE_1 => "OPERATE_1",
715 CD_OPERATE_2 => "OPERATE_2",
716 CD_COMMAND => "COMMAND",
717 CD_TEXT => "TEXT",
718 CD_NAME => "NAME",
719 CD_SEL_BLOCK_START => "SEL_BLOCK_START",
720 CD_SEL_BLOCK_END => "SEL_BLOCK_END",
721 _ => "UNKNOWN",
722 }
723 }
724
725 fn vm_trace_opcode(&self, pc: usize, opcode: u8, phase: &str) {
726 if !self.vm_trace_matches() {
727 return;
728 }
729 self.vm_trace(
730 Some(pc),
731 format!(
732 "{} opcode={}({:#04x})",
733 phase,
734 Self::vm_opcode_name(opcode),
735 opcode
736 ),
737 );
738 }
739 fn sg_debug_enabled() -> bool {
740 std::env::var_os("SG_DEBUG").is_some()
741 }
742
743 fn sg_cgm_coord_trace(&self, msg: impl AsRef<str>) {
744 if !Self::sg_debug_enabled() {
745 return;
746 }
747 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
748 let scene_no = self
749 .current_scene_no
750 .map(|v| v.to_string())
751 .unwrap_or_else(|| "-".to_string());
752 eprintln!(
753 "[SG_DEBUG][CGM_COORD_TRACE][VM] scene={} scene_no={} line={} pc=0x{:x} {}",
754 scene,
755 scene_no,
756 self.current_line_no,
757 self.stream.get_prg_cntr(),
758 msg.as_ref()
759 );
760 }
761
762 fn trace_cgm_coord_assign(&self, elm: &[i32], rhs: &Value) {
763 if !Self::sg_debug_enabled() || elm.len() < 3 {
764 return;
765 }
766 let array_op = if self.ctx.ids.elm_array != 0 {
767 self.ctx.ids.elm_array
768 } else {
769 crate::runtime::forms::codes::ELM_ARRAY
770 };
771 if elm[1] != array_op {
772 return;
773 }
774 let head = elm[0] as u32;
775 let idx = elm[2];
776 if head == crate::runtime::forms::codes::elm_value::GLOBAL_B as u32 {
777 let interesting = (100..=129).contains(&idx)
778 || (140..=169).contains(&idx)
779 || (180..=209).contains(&idx);
780 if interesting {
781 self.sg_cgm_coord_trace(format!("global B[{}] <- {:?}", idx, rhs));
782 }
783 } else if head == crate::runtime::forms::codes::elm_value::GLOBAL_S as u32
784 && (1120..=1139).contains(&idx)
785 {
786 self.sg_cgm_coord_trace(format!("global S[{}] <- {:?}", idx, rhs));
787 }
788 }
789
790
791 fn cf_branch_trace_interesting_line(&self) -> bool {
792 if self.current_scene_name.as_deref() != Some("sys10_cf01") {
793 return false;
794 }
795 matches!(self.current_line_no, 700..=730 | 870..=895)
796 }
797
798 fn cf_condition_trace_interesting_line(&self) -> bool {
799 if !Self::sg_debug_enabled() {
800 return false;
801 }
802 matches!(
803 self.current_scene_name.as_deref(),
804 Some("sys10_cf01")
805 ) && matches!(self.current_line_no, 700..=730 | 870..=895)
806 }
807
808 fn cf_condition_trace_prop_name(prop_id: u16) -> Option<&'static str> {
809 match prop_id {
810 14 => Some("ip_mx"),
811 15 => Some("ip_my"),
812 16 => Some("ip_wheel"),
813 18 => Some("ip_bl_is"),
814 19 => Some("ip_br_is"),
815 20 => Some("ip_bl_on"),
816 21 => Some("ip_br_on"),
817 22 => Some("ip_key_enable_enter"),
818 23 => Some("ip_key_enable_esc"),
819 24 => Some("ip_key_is_enter"),
820 25 => Some("ip_key_is_esc"),
821 26 => Some("ip_key_on_enter"),
822 27 => Some("ip_key_on_esc"),
823 39 => Some("cntr_now"),
824 40 => Some("cntr_exit"),
825 41 => Some("skip_flag"),
826 _ => None,
827 }
828 }
829
830 fn cf_condition_trace_value_summary(&self, cell: &UserPropCell, array_idx: Option<usize>) -> String {
831 if let Some(idx) = array_idx {
832 if cell.form == self.cfg.fm_intlist {
833 return format!("intlist[{}]={}", idx, cell.int_list.get(idx).copied().unwrap_or(0));
834 }
835 if cell.form == self.cfg.fm_strlist {
836 return format!("strlist[{}]={:?}", idx, cell.str_list.get(idx).cloned().unwrap_or_default());
837 }
838 if let Some(slot) = cell.list_items.get(idx) {
839 return format!("list[{}] form={} int={} str={:?} int_list_len={} str_list_len={} items={}",
840 idx,
841 slot.form,
842 slot.int_value,
843 slot.str_value,
844 slot.int_list.len(),
845 slot.str_list.len(),
846 slot.list_items.len()
847 );
848 }
849 return format!("array[{}] <missing> form={} int_list_len={} str_list_len={} items={}",
850 idx, cell.form, cell.int_list.len(), cell.str_list.len(), cell.list_items.len());
851 }
852 if cell.form == self.cfg.fm_int {
853 return format!("int={}", cell.int_value);
854 }
855 if cell.form == self.cfg.fm_str {
856 return format!("str={:?}", cell.str_value);
857 }
858 if cell.form == self.cfg.fm_intlist {
859 let preview = cell.int_list.iter().take(20).copied().collect::<Vec<_>>();
860 return format!("intlist len={} head={:?}", cell.int_list.len(), preview);
861 }
862 if cell.form == self.cfg.fm_strlist {
863 let preview = cell.str_list.iter().take(6).cloned().collect::<Vec<_>>();
864 return format!("strlist len={} head={:?}", cell.str_list.len(), preview);
865 }
866 format!("form={} int={} str={:?} int_list_len={} str_list_len={} items={}",
867 cell.form, cell.int_value, cell.str_value, cell.int_list.len(), cell.str_list.len(), cell.list_items.len())
868 }
869
870 fn sg_cf_condition_trace(&self, pc: usize, msg: impl AsRef<str>) {
871 if !Self::sg_debug_enabled() {
872 return;
873 }
874 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
875 let scene_no = self
876 .current_scene_no
877 .map(|v| v.to_string())
878 .unwrap_or_else(|| "-".to_string());
879 let int_tail_start = self.int_stack.len().saturating_sub(12);
880 let int_tail = &self.int_stack[int_tail_start..];
881 eprintln!(
882 "[SG_DEBUG][CF_CONDITION_TRACE] scene={} scene_no={} line={} pc=0x{:x} {} | int_tail={:?}",
883 scene,
884 scene_no,
885 self.current_line_no,
886 pc,
887 msg.as_ref(),
888 int_tail
889 );
890 }
891
892 fn trace_cf_condition_user_prop_read(&self, pc: usize, prop_id: u16, array_idx: Option<usize>, cell: &UserPropCell, elm: &[i32]) {
893 if !self.cf_condition_trace_interesting_line() {
894 return;
895 }
896 let Some(name) = Self::cf_condition_trace_prop_name(prop_id) else {
897 return;
898 };
899 self.sg_cf_condition_trace(
900 pc,
901 format!(
902 "kind=USER_PROP_READ prop={}({}) array={:?} value={} elm={:?}",
903 prop_id,
904 name,
905 array_idx,
906 self.cf_condition_trace_value_summary(cell, array_idx),
907 elm
908 ),
909 );
910 }
911
912 fn trace_cf_condition_user_prop_assign(&self, pc: usize, prop_id: u16, array_idx: Option<usize>, old: Option<&UserPropCell>, new: Option<&UserPropCell>, rhs: &Value, elm: &[i32]) {
913 if !self.cf_condition_trace_interesting_line() {
914 return;
915 }
916 let Some(name) = Self::cf_condition_trace_prop_name(prop_id) else {
917 return;
918 };
919 let old_summary = old
920 .map(|cell| self.cf_condition_trace_value_summary(cell, array_idx))
921 .unwrap_or_else(|| "<default/missing>".to_string());
922 let new_summary = new
923 .map(|cell| self.cf_condition_trace_value_summary(cell, array_idx))
924 .unwrap_or_else(|| "<missing>".to_string());
925 self.sg_cf_condition_trace(
926 pc,
927 format!(
928 "kind=USER_PROP_ASSIGN prop={}({}) array={:?} old={} new={} rhs={:?} elm={:?}",
929 prop_id,
930 name,
931 array_idx,
932 old_summary,
933 new_summary,
934 rhs,
935 elm
936 ),
937 );
938 }
939
940 fn cf_condition_op_name(opr: u8) -> &'static str {
941 match opr {
942 OP_PLUS => "+",
943 OP_MINUS => "-",
944 OP_MULTIPLE => "*",
945 OP_DIVIDE => "/",
946 OP_AMARI => "%",
947 OP_EQUAL => "==",
948 OP_NOT_EQUAL => "!=",
949 OP_GREATER => ">",
950 OP_GREATER_EQUAL => ">=",
951 OP_LESS => "<",
952 OP_LESS_EQUAL => "<=",
953 OP_LOGICAL_AND => "&&",
954 OP_LOGICAL_OR => "||",
955 OP_TILDE => "~",
956 OP_AND => "&",
957 OP_OR => "|",
958 OP_HAT => "^",
959 OP_SL => "<<",
960 OP_SR => ">>",
961 OP_SR3 => ">>>",
962 _ => "?",
963 }
964 }
965
966 fn cf_branch_trace_stack_snapshot(&self) -> String {
967 let int_tail_start = self.int_stack.len().saturating_sub(16);
968 let int_tail = &self.int_stack[int_tail_start..];
969 let str_tail_start = self.str_stack.len().saturating_sub(4);
970 let str_tail = &self.str_stack[str_tail_start..];
971 let (cur_l, cur_s, arg_cnt) = if let Some(frame) = self.call_stack.last() {
972 let l_take = frame.int_args.len().min(16);
973 let s_take = frame.str_args.len().min(6);
974 (
975 format!("{:?}", &frame.int_args[..l_take]),
976 format!("{:?}", &frame.str_args[..s_take]),
977 frame.arg_cnt,
978 )
979 } else {
980 ("[]".to_string(), "[]".to_string(), 0)
981 };
982 format!(
983 "int_len={} int_tail={:?} str_len={} str_tail={:?} elm_points={:?} call_depth={} arg_cnt={} cur_call_l0_15={} cur_call_s0_5={}",
984 self.int_stack.len(),
985 int_tail,
986 self.str_stack.len(),
987 str_tail,
988 self.element_points,
989 self.call_stack.len(),
990 arg_cnt,
991 cur_l,
992 cur_s,
993 )
994 }
995
996 fn sg_cf_branch_trace(&self, pc: usize, msg: impl AsRef<str>) {
997 if !Self::sg_debug_enabled() {
998 return;
999 }
1000 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1001 let scene_no = self
1002 .current_scene_no
1003 .map(|v| v.to_string())
1004 .unwrap_or_else(|| "-".to_string());
1005 eprintln!(
1006 "[SG_DEBUG][CF_BRANCH_TRACE] scene={} scene_no={} line={} pc=0x{:x} {} | {}",
1007 scene,
1008 scene_no,
1009 self.current_line_no,
1010 pc,
1011 msg.as_ref(),
1012 self.cf_branch_trace_stack_snapshot(),
1013 );
1014 }
1015
1016 fn trace_cf_branch_goto(
1017 &self,
1018 pc: usize,
1019 opcode_name: &str,
1020 label_no: i32,
1021 cond: i32,
1022 taken: bool,
1023 before_tail: &[i32],
1024 ) {
1025 if self.cf_branch_trace_interesting_line() {
1026 self.sg_cf_branch_trace(
1027 pc,
1028 format!(
1029 "kind=GOTO opcode={} label={} cond={} taken={} before_int_tail={:?}",
1030 opcode_name, label_no, cond, taken, before_tail
1031 ),
1032 );
1033 }
1034 }
1035
1036 fn trace_cf_branch_farcall(
1037 &self,
1038 pc: usize,
1039 scene_name: &str,
1040 z_no: i32,
1041 ret_form: i32,
1042 ex_call_proc: bool,
1043 scratch_source_args: &[Value],
1044 ) {
1045 if !(self.current_scene_name.as_deref() == Some("sys10_cf01")
1046 && matches!(self.current_line_no, 700..=730 | 870..=895)
1047 && matches!(scene_name, "sys10_sm00" | "sys10_cf00")
1048 && matches!(z_no, 14 | 15))
1049 {
1050 return;
1051 }
1052 let args_dbg = scratch_source_args
1053 .iter()
1054 .map(|v| format!("{v:?}"))
1055 .collect::<Vec<_>>()
1056 .join(", ");
1057 self.sg_cf_branch_trace(
1058 pc,
1059 format!(
1060 "kind=FARCALL target={} z={} ret_form={} ex_call_proc={} argc={} args=[{}]",
1061 scene_name,
1062 z_no,
1063 ret_form,
1064 ex_call_proc,
1065 scratch_source_args.len(),
1066 args_dbg
1067 ),
1068 );
1069 }
1070
1071 fn sg_omv_trace(&self, msg: impl AsRef<str>) {
1072 if !Self::sg_debug_enabled() {
1073 return;
1074 }
1075 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
1076 let scene_no = self
1077 .current_scene_no
1078 .map(|v| v.to_string())
1079 .unwrap_or_else(|| "-".to_string());
1080 eprintln!(
1081 "[SG_DEBUG][OMV_TRACE] scene={} scene_no={} line={} pc=0x{:x} {}",
1082 scene,
1083 scene_no,
1084 self.current_line_no,
1085 self.stream.get_prg_cntr(),
1086 msg.as_ref()
1087 );
1088 }
1089
1090 fn sg_omv_trace_command(
1091 &self,
1092 phase: &str,
1093 elm: &[i32],
1094 form_id: i32,
1095 op_id: i32,
1096 al_id: i32,
1097 ret_form: i32,
1098 args: &[Value],
1099 ) {
1100 if !Self::sg_debug_enabled() {
1101 return;
1102 }
1103
1104 let label = if form_id == crate::runtime::forms::codes::elm_value::GLOBAL_JUMP
1105 || (form_id == crate::runtime::forms::codes::FM_GLOBAL
1106 && op_id == crate::runtime::forms::codes::elm_value::GLOBAL_JUMP)
1107 {
1108 Some("GLOBAL.JUMP")
1109 } else if form_id == crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL
1110 || (form_id == crate::runtime::forms::codes::FM_GLOBAL
1111 && op_id == crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL)
1112 {
1113 Some("GLOBAL.FARCALL")
1114 } else if (form_id as u32 == constants::global_form::SYSCOM || form_id == constants::fm::SYSCOM)
1115 && op_id == crate::runtime::forms::codes::elm_value::SYSCOM_CALL_EX
1116 {
1117 Some("SYSCOM.CALL_EX")
1118 } else if form_id as u32 == constants::global_form::MOV || form_id == constants::fm::MOV {
1119 Some("MOV")
1120 } else if form_id == constants::fm::OBJECT
1121 && matches!(
1122 op_id,
1123 crate::runtime::forms::codes::object_op::CREATE_MOVIE
1124 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_LOOP
1125 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_WAIT
1126 | crate::runtime::forms::codes::object_op::CREATE_MOVIE_WAIT_KEY
1127 )
1128 {
1129 Some("OBJECT.CREATE_MOVIE")
1130 } else {
1131 None
1132 };
1133 let Some(label) = label else {
1134 return;
1135 };
1136
1137 let args_dbg = args
1138 .iter()
1139 .take(8)
1140 .map(|v| format!("{v:?}"))
1141 .collect::<Vec<_>>()
1142 .join(", ");
1143 self.sg_omv_trace(format!(
1144 "{} {} form={} op={} al_id={} ret_form={} elm={:?} argc={} args=[{}]",
1145 phase,
1146 label,
1147 form_id,
1148 op_id,
1149 al_id,
1150 ret_form,
1151 elm,
1152 args.len(),
1153 args_dbg
1154 ));
1155 }
1156
1157
1158 fn vm_scn_cmd_context(&self, pc: usize) -> String {
1159 let cnt = self.stream.header.scn_cmd_cnt.max(0) as usize;
1160 let mut prev: Option<(usize, usize)> = None;
1161 let mut next: Option<(usize, usize)> = None;
1162 for cmd_no in 0..cnt {
1163 let Ok(off) = self.stream.scn_cmd_offset(cmd_no) else {
1164 continue;
1165 };
1166 if off <= pc {
1167 prev = Some(match prev {
1168 Some(cur) if cur.1 > off => cur,
1169 _ => (cmd_no, off),
1170 });
1171 }
1172 if off > pc {
1173 next = Some(match next {
1174 Some(cur) if cur.1 < off => cur,
1175 _ => (cmd_no, off),
1176 });
1177 }
1178 }
1179
1180 let mut out = String::new();
1181 if let Some((cmd_no, off)) = prev {
1182 let name = self.stream.scn_cmd_name_map.get(&(cmd_no as u32)).map(String::as_str).unwrap_or("<unnamed>");
1183 let _ = write!(&mut out, "prev_scn_cmd=#{}:{}@0x{:x} delta={} ", cmd_no, name, off, pc.saturating_sub(off));
1184 } else {
1185 let _ = write!(&mut out, "prev_scn_cmd=<none> " );
1186 }
1187 if let Some((cmd_no, off)) = next {
1188 let name = self.stream.scn_cmd_name_map.get(&(cmd_no as u32)).map(String::as_str).unwrap_or("<unnamed>");
1189 let _ = write!(&mut out, "next_scn_cmd=#{}:{}@0x{:x} distance={}", cmd_no, name, off, off.saturating_sub(pc));
1190 } else {
1191 let _ = write!(&mut out, "next_scn_cmd=<none>" );
1192 }
1193 out
1194 }
1195
1196 pub fn take_script_proc_request(&mut self) -> bool {
1197 let requested = self.ctx.excall_state.script_proc_requested;
1198 self.ctx.excall_state.script_proc_requested = false;
1199 requested
1200 }
1201
1202 pub fn take_script_proc_pop_request(&mut self) -> bool {
1203 let requested = self.ctx.excall_state.script_proc_pop_requested;
1204 self.ctx.excall_state.script_proc_pop_requested = false;
1205 requested
1206 }
1207
1208 fn mark_excall_script_proc_requested(&mut self) {
1209 self.halted = false;
1210 self.ctx.excall_state.ex_call_flag = true;
1211 self.ctx.excall_state.script_proc_requested = true;
1212 }
1213
1214 fn mark_excall_script_proc_pop_requested(&mut self) {
1215 self.ctx.excall_state.ex_call_flag = false;
1216 self.ctx.excall_state.script_proc_pop_requested = true;
1217 self.ctx.input.clear_all();
1218 }
1219
1220 fn push_call_arg_value(&mut self, arg: &Value) {
1221 match arg {
1222 Value::NamedArg { value, .. } => self.push_call_arg_value(value),
1223 Value::Int(n) => self.push_int(*n as i32),
1224 Value::Str(s) => self.push_str(s.clone()),
1225 Value::Element(elm) => self.push_element(elm.clone()),
1226 Value::List(items) => {
1227 for item in items {
1228 self.push_call_arg_value(item);
1229 }
1230 }
1231 }
1232 }
1233
1234 fn run_user_cmd_inline_at_offset(
1235 &mut self,
1236 cmd_name: &str,
1237 offset: usize,
1238 return_pc: usize,
1239 end_offset: Option<usize>,
1240 _expected_return_pc: Option<usize>,
1241 ret_form: i32,
1242 call_args: &[Value],
1243 frame_action_proc: bool,
1244 ) -> Result<bool> {
1245 let base_depth = self.call_stack.len();
1246 let saved_halted = self.halted;
1247 let saved_scene_no = self.current_scene_no;
1248 let saved_pc = self.stream.get_prg_cntr();
1249 let saved_call_stack = self.call_stack.clone();
1250 let saved_caller_return = self
1251 .call_stack
1252 .last()
1253 .map(|caller| (caller.return_pc, caller.ret_form));
1254 let saved_int_stack = self.int_stack.clone();
1255 let saved_str_stack = self.str_stack.clone();
1256 let saved_element_points = self.element_points.clone();
1257 let saved_gosub_return_stack = self.gosub_return_stack.clone();
1258
1259 if let Some(caller) = self.call_stack.last_mut() {
1260 if std::env::var_os("SIGLUS_TRACE_CALL_RETURN_PC").is_some() {
1261 eprintln!(
1262 "[SG_CALL_PC] inline set cmd={} depth={} saved_pc=0x{:x} return_pc=0x{:x} old=0x{:x}",
1263 cmd_name,
1264 base_depth,
1265 saved_pc,
1266 return_pc,
1267 caller.return_pc
1268 );
1269 }
1270 caller.return_pc = return_pc;
1271 caller.ret_form = ret_form;
1272 }
1273 for arg in call_args {
1274 self.push_call_arg_value(arg);
1275 }
1276 self.call_stack.push(self.make_call_frame(
1277 self.cfg.fm_void,
1278 false,
1279 frame_action_proc,
1280 call_args.len(),
1281 None,
1282 ));
1283 self.stream.set_prg_cntr(offset)?;
1284
1285 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1286 eprintln!(
1287 "[SG_FRAME_ACTION_CALL] run cmd={} scene={:?} offset=0x{:x} return_pc=0x{:x} args={:?}",
1288 cmd_name,
1289 self.current_scene_no,
1290 offset,
1291 return_pc,
1292 call_args
1293 );
1294 }
1295
1296 let max_steps = std::env::var("SIGLUS_INLINE_USER_CMD_MAX_STEPS")
1297 .ok()
1298 .and_then(|s| s.parse::<u64>().ok())
1299 .unwrap_or(0);
1300 let mut steps: u64 = 0;
1301 let mut run_error = None;
1302 loop {
1303 if let Some(end) = end_offset {
1304 if self.stream.get_prg_cntr() >= end {
1305 break;
1306 }
1307 }
1308 let wait_generation_before_step = self.ctx.wait.block_generation();
1309 let proc_generation_before_step = self.ctx.proc_generation();
1310 let running = match self.step_inner(false) {
1311 Ok(v) => v,
1312 Err(e) => {
1313 run_error = Some(e);
1314 break;
1315 }
1316 };
1317 if self.halted || !running {
1318 break;
1319 }
1320 if self.ctx.proc_generation() != proc_generation_before_step {
1321 break;
1322 }
1323 if self.ctx.wait.block_generation() != wait_generation_before_step && self.ctx.wait_poll() {
1324 break;
1325 }
1326 if self.call_stack.len() == base_depth {
1327 break;
1333 }
1334 steps = steps.saturating_add(1);
1335 if max_steps > 0 && steps >= max_steps {
1336 run_error = Some(anyhow!(
1337 "inline user command exceeded SIGLUS_INLINE_USER_CMD_MAX_STEPS: cmd={}",
1338 cmd_name
1339 ));
1340 break;
1341 }
1342 }
1343
1344 let captured_inline_return = if ret_form == self.cfg.fm_int || ret_form == self.cfg.fm_label {
1345 if self.int_stack.len() > saved_int_stack.len() {
1346 self.int_stack.last().copied().map(|v| Value::Int(v as i64))
1347 } else {
1348 None
1349 }
1350 } else if ret_form == self.cfg.fm_str {
1351 if self.str_stack.len() > saved_str_stack.len() {
1352 self.str_stack.last().cloned().map(Value::Str)
1353 } else {
1354 None
1355 }
1356 } else {
1357 None
1358 };
1359
1360 if self.current_scene_no == saved_scene_no {
1361 self.int_stack = saved_int_stack;
1362 self.str_stack = saved_str_stack;
1363 self.element_points = saved_element_points;
1364 self.gosub_return_stack = saved_gosub_return_stack;
1365 self.call_stack = saved_call_stack;
1366 self.halted = saved_halted;
1367 self.stream.set_prg_cntr(saved_pc)?;
1368 }
1369 if let (Some((return_pc, ret_form)), Some(caller)) =
1370 (saved_caller_return, self.call_stack.get_mut(base_depth.saturating_sub(1)))
1371 {
1372 if std::env::var_os("SIGLUS_TRACE_CALL_RETURN_PC").is_some() {
1373 eprintln!(
1374 "[SG_CALL_PC] inline restore cmd={} depth={} return_pc=0x{:x} old=0x{:x}",
1375 cmd_name,
1376 base_depth,
1377 return_pc,
1378 caller.return_pc
1379 );
1380 }
1381 caller.return_pc = return_pc;
1382 caller.ret_form = ret_form;
1383 }
1384 if let Some(v) = captured_inline_return {
1385 self.ctx.stack.push(v);
1386 }
1387
1388 if let Some(e) = run_error {
1389 return Err(e);
1390 }
1391
1392 Ok(true)
1393 }
1394
1395 fn ensure_scene_pck_cache(&mut self) -> Result<()> {
1396 if self.scene_pck_cache.is_none() {
1397 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1398 {
1399 let scene_pck_path = self.ctx.project_dir.join("Scene.pck");
1400 let bytes = crate::resource::read_file_bytes(&scene_pck_path)?;
1401 let exe = ["key.toml", "Key.toml"]
1402 .iter()
1403 .find_map(|name| {
1404 let p = self.ctx.project_dir.join(name);
1405 if !crate::resource::wasm_path_is_file(&p) {
1406 return None;
1407 }
1408 let text = crate::resource::read_file_to_string(&p).ok()?;
1409 siglus_assets::key_toml::parse_key_toml(&text)
1410 .ok()
1411 .and_then(|cfg| cfg.exe_key16)
1412 .map(|v| v.to_vec())
1413 });
1414 let opt = ScenePckDecodeOptions {
1415 exe_angou_element: exe,
1416 easy_angou_code: Some(siglus_assets::keys::SCENE_KEY.to_vec()),
1417 };
1418 self.scene_pck_cache = Some(ScenePck::load_and_rebuild_from_bytes(bytes, &opt)?);
1419 }
1420
1421 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1422 {
1423 let scene_pck_path = find_scene_pck_in_project(&self.ctx.project_dir)?;
1424 let opt = ScenePckDecodeOptions::from_project_dir(&self.ctx.project_dir)?;
1425 self.scene_pck_cache = Some(ScenePck::load_and_rebuild(&scene_pck_path, &opt)?);
1426 }
1427 }
1428 Ok(())
1429 }
1430
1431 fn cached_scene_stream(&mut self, scene_no: usize) -> Result<SceneStream<'a>> {
1432 self.ensure_scene_pck_cache()?;
1433 if !self.scene_stream_cache.contains_key(&scene_no) {
1434 let chunk = {
1435 let pck = self
1436 .scene_pck_cache
1437 .as_ref()
1438 .expect("scene pck cache initialized");
1439 pck.scn_data_slice(scene_no)?.to_vec()
1440 };
1441 let chunk_leaked: &'static [u8] = Box::leak(chunk.into_boxed_slice());
1442 let stream = SceneStream::new(chunk_leaked)?;
1443 self.scene_stream_cache.insert(scene_no, stream);
1444 }
1445 Ok(self
1446 .scene_stream_cache
1447 .get(&scene_no)
1448 .expect("scene stream cached")
1449 .clone())
1450 }
1451
1452 fn run_scene_user_cmd_inline_at_cached_scene_offset(
1453 &mut self,
1454 target_scene_no: usize,
1455 cmd_name: &str,
1456 target_offset: usize,
1457 call_args: &[Value],
1458 ret_form: i32,
1459 preserve_return_pc: bool,
1460 frame_action_proc: bool,
1461 ) -> Result<bool> {
1462 let target_stream = self.cached_scene_stream(target_scene_no)?;
1463 if target_offset > target_stream.scn.len() {
1464 bail!(
1465 "scene_pck: user command offset out of bounds: cmd={} scn_no={} offset=0x{:x} scn_len=0x{:x}",
1466 cmd_name,
1467 target_scene_no,
1468 target_offset,
1469 target_stream.scn.len()
1470 );
1471 }
1472 let (target_call_cmd_names, target_scene_name) = {
1473 let pck = self
1474 .scene_pck_cache
1475 .as_ref()
1476 .expect("scene pck cache initialized");
1477 (
1478 pck.inc_cmd_name_map.clone(),
1479 pck.find_scene_name(target_scene_no).map(ToOwned::to_owned),
1480 )
1481 };
1482
1483 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
1484 let target_user_cmd_names = self.stream.scn_cmd_name_map.clone();
1485 let saved_user_cmd_names =
1486 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
1487 let saved_call_cmd_names =
1488 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
1489 let saved_current_scene_no = self.current_scene_no;
1490 let saved_current_scene_name = self.current_scene_name.clone();
1491 let saved_current_line_no = self.current_line_no;
1492 let saved_ctx_scene_no = self.ctx.current_scene_no;
1493 let saved_ctx_scene_name = self.ctx.current_scene_name.clone();
1494 let saved_ctx_line_no = self.ctx.current_line_no;
1495 let saved_halted = self.halted;
1496 let saved_user_props = self.enter_cross_scene_user_prop_scope();
1497
1498 self.current_scene_no = Some(target_scene_no);
1499 self.current_scene_name = target_scene_name;
1500 self.current_line_no = -1;
1501 self.ctx.current_scene_no = Some(target_scene_no as i64);
1502 self.ctx.current_scene_name = self.current_scene_name.clone();
1503 self.ctx.current_line_no = -1;
1504
1505 let target_return_pc = if preserve_return_pc {
1506 saved_stream.get_prg_cntr()
1507 } else {
1508 self.stream.scn.len()
1509 };
1510 let result = self.run_user_cmd_inline_at_offset(
1511 cmd_name,
1512 target_offset,
1513 target_return_pc,
1514 None,
1515 None,
1516 ret_form,
1517 call_args,
1518 frame_action_proc,
1519 );
1520
1521 self.stream = saved_stream;
1522 self.user_cmd_names = saved_user_cmd_names;
1523 self.call_cmd_names = saved_call_cmd_names;
1524 self.current_scene_no = saved_current_scene_no;
1525 self.current_scene_name = saved_current_scene_name;
1526 self.current_line_no = saved_current_line_no;
1527 self.ctx.current_scene_no = saved_ctx_scene_no;
1528 self.ctx.current_scene_name = saved_ctx_scene_name;
1529 self.ctx.current_line_no = saved_ctx_line_no;
1530 self.halted = saved_halted;
1531 self.restore_cross_scene_user_prop_scope(saved_user_props);
1532 result
1533 }
1534
1535 fn run_scene_user_cmd_inline(
1536 &mut self,
1537 scn_name: Option<&str>,
1538 cmd_name: &str,
1539 call_args: &[Value],
1540 ret_form: i32,
1541 frame_action_proc: bool,
1542 ) -> Result<bool> {
1543 if frame_action_proc {
1544 return self.run_scene_user_cmd_frame_action_proc(scn_name, cmd_name, call_args);
1545 }
1546
1547 let current_scene_no = self.current_scene_no;
1548 let is_current_scene = match scn_name {
1549 None => true,
1550 Some(name) if name.is_empty() => true,
1551 Some(name) => self
1552 .current_scene_name
1553 .as_deref()
1554 .map(|cur| cur.eq_ignore_ascii_case(name))
1555 .unwrap_or(false),
1556 };
1557
1558 if is_current_scene {
1562 let Some(_target_scene_no) = current_scene_no else {
1563 return Ok(false);
1564 };
1565 let cmd_no = match self.user_cmd_names.iter().find_map(|(no, name)| {
1566 if name.eq_ignore_ascii_case(cmd_name) {
1567 Some(*no as usize)
1568 } else {
1569 None
1570 }
1571 }) {
1572 Some(v) => v,
1573 None => {
1574 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1575 eprintln!(
1576 "[SG_FRAME_ACTION_CALL] current-scene user command not found: cmd={} scene={:?} scn_name={:?}",
1577 cmd_name,
1578 self.current_scene_no,
1579 scn_name
1580 );
1581 }
1582 return Ok(false);
1583 }
1584 };
1585 let offset = self.stream.scn_cmd_offset(cmd_no)?;
1586 let return_pc = self.stream.get_prg_cntr();
1587 return self.run_user_cmd_inline_at_offset(
1588 cmd_name,
1589 offset,
1590 return_pc,
1591 None,
1592 Some(return_pc),
1593 ret_form,
1594 call_args,
1595 frame_action_proc,
1596 );
1597 }
1598
1599 let Some(name) = scn_name.filter(|name| !name.is_empty()) else {
1600 return Ok(false);
1601 };
1602 self.ensure_scene_pck_cache()?;
1603 let Some(target_scene_no) = self
1604 .scene_pck_cache
1605 .as_ref()
1606 .expect("scene pck cache initialized")
1607 .find_scene_no(name)
1608 else {
1609 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1610 eprintln!(
1611 "[SG_FRAME_ACTION_CALL] target scene not found: scn_name={} cmd={}",
1612 name, cmd_name
1613 );
1614 }
1615 return Ok(false);
1616 };
1617
1618 let target_stream = self.cached_scene_stream(target_scene_no)?;
1619 let cmd_no = match target_stream
1620 .scn_cmd_name_map
1621 .iter()
1622 .find_map(|(no, name)| {
1623 if name.eq_ignore_ascii_case(cmd_name) {
1624 Some(*no as usize)
1625 } else {
1626 None
1627 }
1628 }) {
1629 Some(v) => v,
1630 None => {
1631 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1632 eprintln!(
1633 "[SG_FRAME_ACTION_CALL] user command not found: cmd={} target_scene={} scn_name={:?}",
1634 cmd_name,
1635 target_scene_no,
1636 scn_name
1637 );
1638 }
1639 return Ok(false);
1640 }
1641 };
1642 let offset = target_stream.scn_cmd_offset(cmd_no)?;
1643 self.run_scene_user_cmd_inline_at_cached_scene_offset(
1644 target_scene_no,
1645 cmd_name,
1646 offset,
1647 call_args,
1648 ret_form,
1649 false,
1650 frame_action_proc,
1651 )
1652 }
1653
1654 fn run_scene_user_cmd_frame_action_proc(
1655 &mut self,
1656 scn_name: Option<&str>,
1657 cmd_name: &str,
1658 call_args: &[Value],
1659 ) -> Result<bool> {
1660 let saved_exec = self.capture_interpreter_exec_state();
1661 let saved_scene_no = self.current_scene_no;
1662 let saved_scene_stack_len = self.scene_stack.len();
1663 let saved_call_depth = self.call_stack.len();
1664
1665 let current_scene_no = self.current_scene_no;
1666 let is_current_scene = match scn_name {
1667 None => true,
1668 Some(name) if name.is_empty() => true,
1669 Some(name) => self
1670 .current_scene_name
1671 .as_deref()
1672 .map(|cur| cur.eq_ignore_ascii_case(name))
1673 .unwrap_or(false),
1674 };
1675
1676 if is_current_scene {
1677 let Some(_) = current_scene_no else {
1678 return Ok(false);
1679 };
1680 let Some(cmd_no) = self.user_cmd_names.iter().find_map(|(no, name)| {
1681 if name.eq_ignore_ascii_case(cmd_name) {
1682 Some(*no as usize)
1683 } else {
1684 None
1685 }
1686 }) else {
1687 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1688 eprintln!(
1689 "[SG_FRAME_ACTION_CALL] current-scene user command not found: cmd={} scene={:?} scn_name={:?}",
1690 cmd_name,
1691 self.current_scene_no,
1692 scn_name
1693 );
1694 }
1695 return Ok(false);
1696 };
1697 let offset = self.stream.scn_cmd_offset(cmd_no)?;
1698 self.enter_current_scene_user_cmd_proc_at_offset(
1699 offset,
1700 self.cfg.fm_void,
1701 call_args,
1702 false,
1703 true,
1704 )?;
1705 } else {
1706 let Some(name) = scn_name.filter(|name| !name.is_empty()) else {
1707 return Ok(false);
1708 };
1709 self.ensure_scene_pck_cache()?;
1710 let Some(target_scene_no) = self
1711 .scene_pck_cache
1712 .as_ref()
1713 .expect("scene pck cache initialized")
1714 .find_scene_no(name)
1715 else {
1716 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1717 eprintln!(
1718 "[SG_FRAME_ACTION_CALL] target scene not found: scn_name={} cmd={}",
1719 name, cmd_name
1720 );
1721 }
1722 return Ok(false);
1723 };
1724
1725 let target_stream = self.cached_scene_stream(target_scene_no)?;
1726 let Some(cmd_no) = target_stream.scn_cmd_name_map.iter().find_map(|(no, name)| {
1727 if name.eq_ignore_ascii_case(cmd_name) {
1728 Some(*no as usize)
1729 } else {
1730 None
1731 }
1732 }) else {
1733 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1734 eprintln!(
1735 "[SG_FRAME_ACTION_CALL] user command not found: cmd={} target_scene={} scn_name={:?}",
1736 cmd_name,
1737 target_scene_no,
1738 scn_name
1739 );
1740 }
1741 return Ok(false);
1742 };
1743 let offset = target_stream.scn_cmd_offset(cmd_no)?;
1744 self.enter_scene_user_cmd_at_scene_offset_ex(
1745 target_scene_no,
1746 offset,
1747 call_args,
1748 self.cfg.fm_void,
1749 false,
1750 true,
1751 )?;
1752 }
1753
1754 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1755 eprintln!(
1756 "[SG_FRAME_ACTION_CALL] proc enter cmd={} scene={:?} depth={} args={:?}",
1757 cmd_name,
1758 self.current_scene_no,
1759 self.call_stack.len(),
1760 call_args
1761 );
1762 }
1763
1764 let mut completed_by_return = false;
1765 let mut stopped_at_proc_boundary = false;
1766 let mut stopped_at_wait_boundary = false;
1767 let mut run_error = None;
1768 let max_steps = std::env::var("SIGLUS_FRAME_ACTION_MAX_STEPS")
1769 .ok()
1770 .and_then(|s| s.parse::<u64>().ok())
1771 .unwrap_or(0);
1772 let mut steps: u64 = 0;
1773 loop {
1774 let wait_generation_before_step = self.ctx.wait.block_generation();
1775 let proc_generation_before_step = self.ctx.proc_generation();
1776 let running = match self.step_inner(false) {
1777 Ok(v) => v,
1778 Err(e) => {
1779 run_error = Some(e);
1780 break;
1781 }
1782 };
1783 if self.current_scene_no == saved_scene_no
1784 && self.scene_stack.len() == saved_scene_stack_len
1785 && self.call_stack.len() == saved_call_depth
1786 {
1787 completed_by_return = true;
1788 break;
1789 }
1790 if self.halted || !running {
1791 break;
1792 }
1793 if self.ctx.proc_generation() != proc_generation_before_step {
1794 stopped_at_proc_boundary = true;
1795 break;
1796 }
1797 if self.ctx.wait.block_generation() != wait_generation_before_step && self.ctx.wait_poll() {
1798 stopped_at_wait_boundary = true;
1799 break;
1800 }
1801 steps = steps.saturating_add(1);
1802 if max_steps > 0 && steps >= max_steps {
1803 run_error = Some(anyhow!(
1804 "frame_action user command exceeded SIGLUS_FRAME_ACTION_MAX_STEPS: cmd={} scene={:?}",
1805 cmd_name,
1806 scn_name
1807 ));
1808 break;
1809 }
1810 }
1811
1812 let restore_callback_lexer = self.current_scene_no == saved_scene_no;
1820 if restore_callback_lexer {
1821 if std::env::var_os("SIGLUS_TRACE_FRAME_ACTION_CALL").is_some() {
1822 eprintln!(
1823 "[SG_FRAME_ACTION_CALL] proc exit cmd={} scene={:?} completed={} proc_boundary={} wait_boundary={} error={} restoring caller lexer state",
1824 cmd_name,
1825 scn_name,
1826 completed_by_return,
1827 stopped_at_proc_boundary,
1828 stopped_at_wait_boundary,
1829 run_error.is_some()
1830 );
1831 }
1832 self.restore_interpreter_exec_state(saved_exec);
1833 }
1834
1835 if let Some(e) = run_error {
1836 return Err(e);
1837 }
1838
1839 Ok(true)
1840 }
1841
1842 fn enter_current_scene_user_cmd_proc_at_offset(
1843 &mut self,
1844 offset: usize,
1845 ret_form: i32,
1846 call_args: &[Value],
1847 excall_proc: bool,
1848 frame_action_proc: bool,
1849 ) -> Result<bool> {
1850 let return_pc = self.stream.get_prg_cntr();
1851 let depth = self.call_stack.len();
1852 let Some(caller) = self.call_stack.last_mut() else {
1853 return Ok(false);
1854 };
1855 if std::env::var_os("SIGLUS_TRACE_CALL_RETURN_PC").is_some() {
1856 eprintln!(
1857 "[SG_CALL_PC] proc-call set depth={} offset=0x{:x} return_pc=0x{:x} old=0x{:x} frame_action={}",
1858 depth,
1859 offset,
1860 return_pc,
1861 caller.return_pc,
1862 frame_action_proc
1863 );
1864 }
1865 caller.return_pc = return_pc;
1866 caller.ret_form = ret_form;
1867 for arg in call_args {
1868 self.push_call_arg_value(arg);
1869 }
1870 self.call_stack.push(self.make_call_frame(
1871 self.cfg.fm_void,
1872 excall_proc,
1873 frame_action_proc,
1874 call_args.len(),
1875 None,
1876 ));
1877 self.stream.set_prg_cntr(offset)?;
1878 if excall_proc {
1879 self.mark_excall_script_proc_requested();
1880 }
1881 Ok(true)
1882 }
1883
1884 fn enter_scene_user_cmd_at_scene_offset_ex(
1885 &mut self,
1886 target_scene_no: usize,
1887 target_offset: usize,
1888 call_args: &[Value],
1889 ret_form: i32,
1890 ex_call_proc: bool,
1891 frame_action_proc: bool,
1892 ) -> Result<bool> {
1893 let target_stream = self.cached_scene_stream(target_scene_no)?;
1894 if target_offset > target_stream.scn.len() {
1895 bail!(
1896 "scene_pck: user command offset out of bounds: scn_no={} offset=0x{:x} scn_len=0x{:x}",
1897 target_scene_no,
1898 target_offset,
1899 target_stream.scn.len()
1900 );
1901 }
1902
1903 let saved = SceneExecFrame {
1904 stream: self.stream.clone(),
1905 user_cmd_names: self.user_cmd_names.clone(),
1906 call_cmd_names: self.call_cmd_names.clone(),
1907 int_stack: std::mem::take(&mut self.int_stack),
1908 str_stack: std::mem::take(&mut self.str_stack),
1909 element_points: std::mem::take(&mut self.element_points),
1910 call_stack: std::mem::take(&mut self.call_stack),
1911 gosub_return_stack: std::mem::take(&mut self.gosub_return_stack),
1912 user_props: self.enter_cross_scene_user_prop_scope(),
1913 current_scene_no: self.current_scene_no,
1914 current_scene_name: self.current_scene_name.clone(),
1915 current_line_no: self.current_line_no,
1916 ret_form,
1917 excall_proc: ex_call_proc,
1918 };
1919 self.scene_stack.push(saved);
1920
1921 self.stream = target_stream;
1922 self.user_cmd_names = self.stream.scn_cmd_name_map.clone();
1923 self.call_cmd_names = self
1924 .scene_pck_cache
1925 .as_ref()
1926 .expect("scene pck cache initialized")
1927 .inc_cmd_name_map
1928 .clone();
1929 self.current_scene_no = Some(target_scene_no);
1930 self.current_scene_name = self
1931 .scene_pck_cache
1932 .as_ref()
1933 .expect("scene pck cache initialized")
1934 .find_scene_name(target_scene_no)
1935 .map(ToOwned::to_owned);
1936 self.current_line_no = -1;
1937 self.ctx.current_scene_no = Some(target_scene_no as i64);
1938 self.ctx.current_scene_name = self.current_scene_name.clone();
1939 self.ctx.current_line_no = -1;
1940
1941 for arg in call_args {
1942 self.push_call_arg_value(arg);
1943 }
1944 self.call_stack.push(self.make_call_frame(
1945 self.cfg.fm_void,
1946 ex_call_proc,
1947 frame_action_proc,
1948 call_args.len(),
1949 None,
1950 ));
1951 self.stream.set_prg_cntr(target_offset)?;
1952 if ex_call_proc {
1953 self.mark_excall_script_proc_requested();
1954 }
1955 Ok(true)
1956 }
1957
1958 fn enter_scene_user_cmd_call(
1959 &mut self,
1960 scn_name: Option<&str>,
1961 cmd_name: &str,
1962 call_args: &[Value],
1963 ) -> Result<bool> {
1964 let current_scene_no = self.current_scene_no;
1965 self.ensure_scene_pck_cache()?;
1966
1967 let target_scene_no = match scn_name {
1968 Some(name) if !name.is_empty() => self
1969 .scene_pck_cache
1970 .as_ref()
1971 .expect("scene pck cache initialized")
1972 .find_scene_no(name)
1973 .or(current_scene_no),
1974 _ => current_scene_no,
1975 };
1976 let Some(target_scene_no) = target_scene_no else {
1977 return Ok(false);
1978 };
1979
1980 if Some(target_scene_no) == self.current_scene_no {
1985 let Some(cmd_no) = self.user_cmd_names.iter().find_map(|(no, name)| {
1986 if name.eq_ignore_ascii_case(cmd_name) {
1987 Some(*no as usize)
1988 } else {
1989 None
1990 }
1991 }) else {
1992 if std::env::var_os("SG_DEBUG").is_some() {
1993 eprintln!(
1994 "[SG_DEBUG][BUTTON] user command not found for ex-call: scene={:?} cmd={}",
1995 scn_name, cmd_name
1996 );
1997 }
1998 return Ok(false);
1999 };
2000 let offset = self.stream.scn_cmd_offset(cmd_no)?;
2001 if std::env::var_os("SG_DEBUG").is_some() {
2002 eprintln!(
2003 "[SG_DEBUG][BUTTON] enter local user command scene={:?} cmd={} cmd_no={} offset=0x{:x}",
2004 scn_name,
2005 cmd_name,
2006 cmd_no,
2007 offset
2008 );
2009 }
2010 return self.enter_current_scene_user_cmd_at_offset(offset, call_args);
2011 }
2012
2013 let target_stream = self.cached_scene_stream(target_scene_no)?;
2014 let Some(cmd_no) = target_stream
2015 .scn_cmd_name_map
2016 .iter()
2017 .find_map(|(no, name)| {
2018 if name.eq_ignore_ascii_case(cmd_name) {
2019 Some(*no as usize)
2020 } else {
2021 None
2022 }
2023 })
2024 else {
2025 if std::env::var_os("SG_DEBUG").is_some() {
2026 eprintln!(
2027 "[SG_DEBUG][BUTTON] target user command not found for ex-call: target_scene={} scn_name={:?} cmd={}",
2028 target_scene_no,
2029 scn_name,
2030 cmd_name
2031 );
2032 }
2033 return Ok(false);
2034 };
2035 let offset = target_stream.scn_cmd_offset(cmd_no)?;
2036 if std::env::var_os("SG_DEBUG").is_some() {
2037 eprintln!(
2038 "[SG_DEBUG][BUTTON] enter target user command target_scene={} scn_name={:?} cmd={} cmd_no={} offset=0x{:x}",
2039 target_scene_no,
2040 scn_name,
2041 cmd_name,
2042 cmd_no,
2043 offset
2044 );
2045 }
2046 self.enter_scene_user_cmd_at_scene_offset(target_scene_no, offset, call_args)
2047 }
2048
2049 fn enter_current_scene_user_cmd_at_offset(
2050 &mut self,
2051 offset: usize,
2052 call_args: &[Value],
2053 ) -> Result<bool> {
2054 self.enter_current_scene_user_cmd_proc_at_offset(
2055 offset,
2056 self.cfg.fm_void,
2057 call_args,
2058 true,
2059 false,
2060 )
2061 }
2062
2063 fn enter_scene_user_cmd_at_scene_offset(
2064 &mut self,
2065 target_scene_no: usize,
2066 target_offset: usize,
2067 call_args: &[Value],
2068 ) -> Result<bool> {
2069 self.enter_scene_user_cmd_at_scene_offset_ex(
2070 target_scene_no,
2071 target_offset,
2072 call_args,
2073 self.cfg.fm_void,
2074 true,
2075 false,
2076 )
2077 }
2078
2079 fn run_current_scene_user_cmd_inline(
2080 &mut self,
2081 cmd_name: &str,
2082 call_args: &[Value],
2083 ) -> Result<bool> {
2084 self.run_scene_user_cmd_inline(None, cmd_name, call_args, self.cfg.fm_void, false)
2085 }
2086
2087 fn run_scene_user_cmd_inline_at_scene_offset(
2088 &mut self,
2089 pck: &ScenePck,
2090 target_scene_no: usize,
2091 cmd_name: &str,
2092 target_offset: usize,
2093 call_args: &[Value],
2094 preserve_return_pc: bool,
2095 frame_action_proc: bool,
2096 ) -> Result<bool> {
2097 let chunk = pck.scn_data_slice(target_scene_no)?;
2098 let chunk_leaked: &'static [u8] = Box::leak(chunk.to_vec().into_boxed_slice());
2099 let target_stream: SceneStream<'a> = SceneStream::new(chunk_leaked)?;
2100 if target_offset > target_stream.scn.len() {
2101 bail!(
2102 "scene_pck: user command offset out of bounds: cmd={} scn_no={} offset=0x{:x} scn_len=0x{:x}",
2103 cmd_name,
2104 target_scene_no,
2105 target_offset,
2106 target_stream.scn.len()
2107 );
2108 }
2109
2110 let saved_stream = std::mem::replace(&mut self.stream, target_stream);
2111 let target_user_cmd_names = self.stream.scn_cmd_name_map.clone();
2112 let target_call_cmd_names = pck.inc_cmd_name_map.clone();
2113 let saved_user_cmd_names =
2114 std::mem::replace(&mut self.user_cmd_names, target_user_cmd_names);
2115 let saved_call_cmd_names =
2116 std::mem::replace(&mut self.call_cmd_names, target_call_cmd_names);
2117 let saved_current_scene_no = self.current_scene_no;
2118 let saved_current_scene_name = self.current_scene_name.clone();
2119 let saved_current_line_no = self.current_line_no;
2120 let saved_ctx_scene_no = self.ctx.current_scene_no;
2121 let saved_ctx_scene_name = self.ctx.current_scene_name.clone();
2122 let saved_ctx_line_no = self.ctx.current_line_no;
2123 let saved_halted = self.halted;
2124 let saved_user_props = self.enter_cross_scene_user_prop_scope();
2125
2126 self.current_scene_no = Some(target_scene_no);
2127 self.current_scene_name = pck.find_scene_name(target_scene_no).map(ToOwned::to_owned);
2128 self.current_line_no = -1;
2129 self.ctx.current_scene_no = Some(target_scene_no as i64);
2130 self.ctx.current_scene_name = self.current_scene_name.clone();
2131 self.ctx.current_line_no = -1;
2132
2133 let target_return_pc = if preserve_return_pc {
2134 saved_stream.get_prg_cntr()
2135 } else {
2136 self.stream.scn.len()
2137 };
2138 let result = self.run_user_cmd_inline_at_offset(
2139 cmd_name,
2140 target_offset,
2141 target_return_pc,
2142 None,
2143 None,
2144 self.cfg.fm_void,
2145 call_args,
2146 frame_action_proc,
2147 );
2148
2149 self.stream = saved_stream;
2150 self.user_cmd_names = saved_user_cmd_names;
2151 self.call_cmd_names = saved_call_cmd_names;
2152 self.current_scene_no = saved_current_scene_no;
2153 self.current_scene_name = saved_current_scene_name;
2154 self.current_line_no = saved_current_line_no;
2155 self.ctx.current_scene_no = saved_ctx_scene_no;
2156 self.ctx.current_scene_name = saved_ctx_scene_name;
2157 self.ctx.current_line_no = saved_ctx_line_no;
2158 self.halted = saved_halted;
2159 self.restore_cross_scene_user_prop_scope(saved_user_props);
2160 result
2161 }
2162
2163 fn collect_object_frame_action_work_recursive(
2164 obj: &crate::runtime::globals::ObjectState,
2165 stage_idx: i64,
2166 obj_idx: usize,
2167 object_chain: Vec<i32>,
2168 out: &mut Vec<FrameActionWork>,
2169 ) {
2170 let fa = &obj.frame_action;
2171 if !fa.cmd_name.is_empty() {
2172 let mut frame_action_chain = object_chain.clone();
2173 frame_action_chain.push(crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION);
2174 out.push(FrameActionWork {
2175 stage_idx,
2176 obj_idx,
2177 ch_idx: None,
2178 global_form_id: None,
2179 object_chain: Some(object_chain.clone()),
2180 frame_action_chain: Some(frame_action_chain),
2181 scn_name: fa.scn_name.clone(),
2182 cmd_name: fa.cmd_name.clone(),
2183 args: fa.args.clone(),
2184 count: fa.counter.get_count(),
2185 end_time: fa.end_time,
2186 });
2187 }
2188 for (ch_idx, ch) in obj.frame_action_ch.iter().enumerate() {
2189 if !ch.cmd_name.is_empty() {
2190 let mut frame_action_chain = object_chain.clone();
2191 frame_action_chain
2192 .push(crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH);
2193 frame_action_chain.push(crate::runtime::forms::codes::ELM_ARRAY);
2194 frame_action_chain.push(ch_idx as i32);
2195 out.push(FrameActionWork {
2196 stage_idx,
2197 obj_idx,
2198 ch_idx: Some(ch_idx),
2199 global_form_id: None,
2200 object_chain: Some(object_chain.clone()),
2201 frame_action_chain: Some(frame_action_chain),
2202 scn_name: ch.scn_name.clone(),
2203 cmd_name: ch.cmd_name.clone(),
2204 args: ch.args.clone(),
2205 count: ch.counter.get_count(),
2206 end_time: ch.end_time,
2207 });
2208 }
2209 }
2210 for (child_idx, child) in obj.runtime.child_objects.iter().enumerate() {
2211 let child_has_frame_action = !child.frame_action.cmd_name.is_empty()
2212 || child.frame_action_ch.iter().any(|ch| !ch.cmd_name.is_empty());
2213 let child_has_nested_work = !child.runtime.child_objects.is_empty();
2214 if child.used || child_has_frame_action || child_has_nested_work {
2215 let mut child_chain = object_chain.clone();
2216 child_chain.push(crate::runtime::forms::codes::elm_value::OBJECT_CHILD);
2217 child_chain.push(crate::runtime::forms::codes::ELM_ARRAY);
2218 child_chain.push(child_idx as i32);
2219 Self::collect_object_frame_action_work_recursive(
2220 child,
2221 stage_idx,
2222 child_idx,
2223 child_chain,
2224 out,
2225 );
2226 }
2227 }
2228 }
2229
2230 fn object_child_from_chain_mut<'b>(
2231 mut obj: &'b mut crate::runtime::globals::ObjectState,
2232 object_chain: &[i32],
2233 mut pos: usize,
2234 elm_array: i32,
2235 ) -> Option<&'b mut crate::runtime::globals::ObjectState> {
2236 while pos + 2 < object_chain.len() {
2237 let op = object_chain[pos];
2238 if op != crate::runtime::forms::codes::elm_value::OBJECT_CHILD {
2239 break;
2240 }
2241 if object_chain[pos + 1] != elm_array
2242 && object_chain[pos + 1] != crate::runtime::forms::codes::ELM_ARRAY
2243 {
2244 return None;
2245 }
2246 let child_idx = object_chain[pos + 2].max(0) as usize;
2247 obj = obj.runtime.child_objects.get_mut(child_idx)?;
2248 pos += 3;
2249 }
2250 Some(obj)
2251 }
2252
2253 fn object_from_frame_action_chain_mut<'b>(
2254 objects: &'b mut [crate::runtime::globals::ObjectState],
2255 object_chain: &[i32],
2256 elm_array: i32,
2257 ) -> Option<&'b mut crate::runtime::globals::ObjectState> {
2258 if object_chain.len() < 6 || object_chain[1] != elm_array || object_chain[4] != elm_array {
2259 return None;
2260 }
2261 let obj = objects.get_mut(object_chain[5].max(0) as usize)?;
2262 Self::object_child_from_chain_mut(obj, object_chain, 6, elm_array)
2263 }
2264
2265 fn object_from_mwnd_frame_action_chain_mut<'b>(
2266 mwnds: &'b mut [crate::runtime::globals::MwndState],
2267 object_chain: &[i32],
2268 elm_array: i32,
2269 ) -> Option<&'b mut crate::runtime::globals::ObjectState> {
2270 if object_chain.len() < 9
2271 || object_chain[1] != elm_array
2272 || object_chain[4] != elm_array
2273 || object_chain[7] != elm_array
2274 {
2275 return None;
2276 }
2277 if object_chain[3] != crate::runtime::forms::codes::elm_value::STAGE_MWND {
2278 return None;
2279 }
2280 let mwnd_idx = object_chain[5].max(0) as usize;
2281 let selector = object_chain[6];
2282 let obj_idx = object_chain[8].max(0) as usize;
2283 let mwnd = mwnds.get_mut(mwnd_idx)?;
2284 if selector == crate::runtime::forms::codes::elm_value::MWND_BUTTON {
2285 let obj = mwnd.button_list.get_mut(obj_idx)?;
2286 return Self::object_child_from_chain_mut(obj, object_chain, 9, elm_array);
2287 }
2288 if selector == crate::runtime::forms::codes::elm_value::MWND_FACE {
2289 let obj = mwnd.face_list.get_mut(obj_idx)?;
2290 return Self::object_child_from_chain_mut(obj, object_chain, 9, elm_array);
2291 }
2292 if selector == crate::runtime::forms::codes::elm_value::MWND_OBJECT {
2293 let obj = mwnd.object_list.get_mut(obj_idx)?;
2294 return Self::object_child_from_chain_mut(obj, object_chain, 9, elm_array);
2295 }
2296 None
2297 }
2298
2299 fn with_frame_action_mut<R>(
2300 &mut self,
2301 item: &FrameActionWork,
2302 f: impl FnOnce(&mut crate::runtime::globals::ObjectFrameActionState) -> R,
2303 ) -> Option<R> {
2304 if item.stage_idx < 0 {
2305 let form_id = item.global_form_id?;
2306 if let Some(idx) = item.ch_idx {
2307 let list = self.ctx.globals.frame_action_lists.get_mut(&form_id)?;
2308 return list.get_mut(idx).map(f);
2309 }
2310 return self.ctx.globals.frame_actions.get_mut(&form_id).map(f);
2311 }
2312
2313 let chain = item.object_chain.as_ref()?;
2314 if chain.len() < 6 {
2315 return None;
2316 }
2317 let stage_idx = chain[2] as i64;
2318 let elm_array = self.ctx.ids.elm_array;
2319 let mut form_ids: Vec<u32> = self.ctx.globals.stage_forms.keys().copied().collect();
2320 form_ids.sort_unstable();
2321 for form_id in form_ids {
2322 let Some(st) = self.ctx.globals.stage_forms.get_mut(&form_id) else {
2323 continue;
2324 };
2325 let obj = if chain.get(3).copied()
2326 == Some(crate::runtime::forms::codes::elm_value::STAGE_MWND)
2327 {
2328 let Some(mwnds) = st.mwnd_lists.get_mut(&stage_idx) else {
2329 continue;
2330 };
2331 let Some(obj) = Self::object_from_mwnd_frame_action_chain_mut(mwnds, chain, elm_array)
2332 else {
2333 continue;
2334 };
2335 obj
2336 } else {
2337 let Some(objects) = st.object_lists.get_mut(&stage_idx) else {
2338 continue;
2339 };
2340 let Some(obj) = Self::object_from_frame_action_chain_mut(objects, chain, elm_array)
2341 else {
2342 continue;
2343 };
2344 obj
2345 };
2346 if let Some(idx) = item.ch_idx {
2347 return obj.frame_action_ch.get_mut(idx).map(f);
2348 }
2349 return Some(f(&mut obj.frame_action));
2350 }
2351 None
2352 }
2353
2354 fn begin_frame_action_finish(
2355 &mut self,
2356 item: &FrameActionWork,
2357 ) -> Option<(String, Vec<Value>)> {
2358 self.with_frame_action_mut(item, |fa| {
2359 if fa.cmd_name.is_empty() || fa.end_time < 0 {
2360 return None;
2361 }
2362 if fa.counter.get_count() < fa.end_time {
2363 return None;
2364 }
2365
2366 let cmd_name = fa.cmd_name.clone();
2367 let args = fa.args.clone();
2368 let final_count = if fa.end_time == -1 { 0 } else { fa.end_time };
2369 fa.counter.set_count(final_count);
2370 fa.scn_name.clear();
2371 fa.cmd_name.clear();
2372 fa.end_flag = true;
2373 Some((cmd_name, args))
2374 })?
2375 }
2376
2377 fn end_frame_action_finish(&mut self, item: &FrameActionWork) {
2378 let _ = self.with_frame_action_mut(item, |fa| {
2379 fa.end_flag = false;
2383 });
2384 }
2385
2386 fn make_frame_action_call_args(
2387 frame_action_chain: Option<&Vec<i32>>,
2388 object_chain: Option<&Vec<i32>>,
2389 args: &[Value],
2390 ) -> Vec<Value> {
2391 let mut call_args = Vec::with_capacity(args.len() + 2);
2392 if let Some(frame_action_chain) = frame_action_chain {
2393 call_args.push(Value::Element(frame_action_chain.clone()));
2394 }
2395 if let Some(object_chain) = object_chain {
2396 call_args.push(Value::Element(object_chain.clone()));
2397 }
2398 call_args.extend(args.iter().cloned());
2399 call_args
2400 }
2401
2402 fn runtime_slot_from_object_children(
2403 mut obj: &mut crate::runtime::globals::ObjectState,
2404 fallback_slot: usize,
2405 chain: &[i32],
2406 mut pos: usize,
2407 elm_array: i32,
2408 next_slot: &mut usize,
2409 ) -> usize {
2410 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
2411 let mut slot = obj.runtime_slot_or(fallback_slot);
2412 while pos + 2 < chain.len() {
2413 if chain[pos] == object_child
2414 && (chain[pos + 1] == elm_array
2415 || chain[pos + 1] == crate::runtime::forms::codes::ELM_ARRAY)
2416 {
2417 let child_idx = chain[pos + 2].max(0) as usize;
2418 if obj.runtime.child_objects.len() <= child_idx {
2419 obj.runtime
2420 .child_objects
2421 .resize_with(child_idx + 1, crate::runtime::globals::ObjectState::default);
2422 }
2423 let child = &mut obj.runtime.child_objects[child_idx];
2424 slot = child.ensure_runtime_slot(next_slot);
2425 obj = child;
2426 pos += 3;
2427 } else {
2428 pos += 1;
2429 }
2430 }
2431 slot
2432 }
2433
2434 fn runtime_slot_from_object_chain(
2435 &mut self,
2436 stage_idx: i64,
2437 fallback_obj_idx: usize,
2438 chain: &[i32],
2439 ) -> usize {
2440 let stage_form = self.ctx.ids.form_global_stage;
2441 let elm_array = if self.ctx.ids.elm_array != 0 {
2442 self.ctx.ids.elm_array
2443 } else {
2444 crate::runtime::forms::codes::ELM_ARRAY
2445 };
2446
2447 let Some(st) = self.ctx.globals.stage_forms.get_mut(&stage_form) else {
2448 return fallback_obj_idx;
2449 };
2450 let next_slot = st
2451 .next_nested_object_slot
2452 .entry(stage_idx)
2453 .or_insert(100000);
2454
2455 if chain.get(3).copied() == Some(crate::runtime::forms::codes::elm_value::STAGE_MWND)
2456 && chain.len() >= 9
2457 {
2458 let mwnd_idx = chain.get(5).copied().unwrap_or(0).max(0) as usize;
2459 let selector = chain.get(6).copied().unwrap_or(0);
2460 let obj_idx = chain
2461 .get(8)
2462 .copied()
2463 .unwrap_or(fallback_obj_idx as i32)
2464 .max(0) as usize;
2465 let Some(mwnds) = st.mwnd_lists.get_mut(&stage_idx) else {
2466 return obj_idx;
2467 };
2468 let Some(mwnd) = mwnds.get_mut(mwnd_idx) else {
2469 return obj_idx;
2470 };
2471 if selector == crate::runtime::forms::codes::elm_value::MWND_BUTTON {
2472 let Some(obj) = mwnd.button_list.get_mut(obj_idx) else {
2473 return obj_idx;
2474 };
2475 return Self::runtime_slot_from_object_children(
2476 obj, obj_idx, chain, 9, elm_array, next_slot,
2477 );
2478 }
2479 if selector == crate::runtime::forms::codes::elm_value::MWND_FACE {
2480 let Some(obj) = mwnd.face_list.get_mut(obj_idx) else {
2481 return obj_idx;
2482 };
2483 return Self::runtime_slot_from_object_children(
2484 obj, obj_idx, chain, 9, elm_array, next_slot,
2485 );
2486 }
2487 if selector == crate::runtime::forms::codes::elm_value::MWND_OBJECT {
2488 let Some(obj) = mwnd.object_list.get_mut(obj_idx) else {
2489 return obj_idx;
2490 };
2491 return Self::runtime_slot_from_object_children(
2492 obj, obj_idx, chain, 9, elm_array, next_slot,
2493 );
2494 }
2495 return obj_idx;
2496 }
2497
2498 if chain.get(3).copied() == Some(crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM)
2499 && chain.len() >= 9
2500 && chain.get(6).copied() == Some(crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT)
2501 {
2502 let item_idx = chain.get(5).copied().unwrap_or(0).max(0) as usize;
2503 let obj_idx = chain
2504 .get(8)
2505 .copied()
2506 .unwrap_or(fallback_obj_idx as i32)
2507 .max(0) as usize;
2508 let Some(items) = st.btnselitem_lists.get_mut(&stage_idx) else {
2509 return obj_idx;
2510 };
2511 let Some(item) = items.get_mut(item_idx) else {
2512 return obj_idx;
2513 };
2514 let Some(obj) = item.object_list.get_mut(obj_idx) else {
2515 return obj_idx;
2516 };
2517 return Self::runtime_slot_from_object_children(
2518 obj, obj_idx, chain, 9, elm_array, next_slot,
2519 );
2520 }
2521
2522 let top_idx = chain
2523 .get(5)
2524 .copied()
2525 .unwrap_or(fallback_obj_idx as i32)
2526 .max(0) as usize;
2527 let Some(list) = st.object_lists.get_mut(&stage_idx) else {
2528 return top_idx;
2529 };
2530 if top_idx >= list.len() {
2531 return top_idx;
2532 }
2533 let obj = &mut list[top_idx];
2534 Self::runtime_slot_from_object_children(obj, top_idx, chain, 6, elm_array, next_slot)
2535 }
2536
2537 fn set_frame_action_current_object(
2538 &mut self,
2539 item: &FrameActionWork,
2540 ) -> (Option<(i64, usize)>, Option<Vec<i32>>) {
2541 let prev_target = self.ctx.globals.current_stage_object;
2542 let prev_chain = self.ctx.globals.current_object_chain.clone();
2543 if let Some(chain) = item.object_chain.clone() {
2544 let top_idx = chain.get(5).copied().unwrap_or(item.obj_idx as i32).max(0) as usize;
2545 let runtime_slot = self.runtime_slot_from_object_chain(item.stage_idx, top_idx, &chain);
2546 self.ctx.globals.current_stage_object = Some((item.stage_idx, runtime_slot));
2547 self.ctx.globals.current_object_chain = Some(chain);
2548 } else {
2549 self.ctx.globals.current_stage_object = None;
2550 self.ctx.globals.current_object_chain = None;
2551 }
2552 (prev_target, prev_chain)
2553 }
2554
2555 fn restore_frame_action_current_object(
2556 &mut self,
2557 prev_target: Option<(i64, usize)>,
2558 prev_chain: Option<Vec<i32>>,
2559 ) {
2560 self.ctx.globals.current_stage_object = prev_target;
2561 self.ctx.globals.current_object_chain = prev_chain;
2562 }
2563
2564 fn frame_action_work_from_pending_finish(
2565 &self,
2566 pending: &PendingFrameActionFinish,
2567 ) -> FrameActionWork {
2568 let object_chain = pending.object_chain.clone();
2569 let (stage_idx, obj_idx) = object_chain
2570 .as_ref()
2571 .and_then(|chain| {
2572 if chain.len() >= 6 {
2573 Some((chain[2] as i64, chain[5].max(0) as usize))
2574 } else {
2575 None
2576 }
2577 })
2578 .unwrap_or((-1, usize::MAX));
2579
2580 let ch_idx = if let Some(chain) = object_chain.as_ref() {
2581 let base = chain.len();
2582 if pending.frame_action_chain.len() >= base + 3
2583 && pending.frame_action_chain[base]
2584 == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH
2585 && pending.frame_action_chain[base + 1] == crate::runtime::forms::codes::ELM_ARRAY
2586 {
2587 Some(pending.frame_action_chain[base + 2].max(0) as usize)
2588 } else {
2589 None
2590 }
2591 } else if pending.frame_action_chain.len() >= 3
2592 && pending.frame_action_chain[1] == crate::runtime::forms::codes::ELM_ARRAY
2593 {
2594 Some(pending.frame_action_chain[2].max(0) as usize)
2595 } else {
2596 None
2597 };
2598
2599 let global_form_id = if object_chain.is_none() {
2600 pending
2601 .frame_action_chain
2602 .first()
2603 .copied()
2604 .map(|v| v as u32)
2605 } else {
2606 None
2607 };
2608
2609 FrameActionWork {
2610 stage_idx,
2611 obj_idx,
2612 ch_idx,
2613 global_form_id,
2614 object_chain,
2615 frame_action_chain: Some(pending.frame_action_chain.clone()),
2616 scn_name: pending.scn_name.clone(),
2617 cmd_name: pending.cmd_name.clone(),
2618 args: pending.args.clone(),
2619 count: pending.end_time,
2620 end_time: pending.end_time,
2621 }
2622 }
2623
2624 fn run_pending_frame_action_finish(&mut self, pending: PendingFrameActionFinish) -> Result<()> {
2625 if pending.cmd_name.is_empty() {
2626 return Ok(());
2627 }
2628 let item = self.frame_action_work_from_pending_finish(&pending);
2629 let final_count = if pending.end_time == -1 {
2630 0
2631 } else {
2632 pending.end_time
2633 };
2634 let _ = self.with_frame_action_mut(&item, |fa| {
2635 fa.scn_name.clear();
2639 fa.cmd_name.clear();
2640 fa.args = pending.args.clone();
2641 fa.end_time = pending.end_time;
2642 fa.counter.set_count(final_count);
2643 fa.end_flag = true;
2644 });
2645
2646 let call_args = Self::make_frame_action_call_args(
2647 item.frame_action_chain.as_ref(),
2648 item.object_chain.as_ref(),
2649 &pending.args,
2650 );
2651 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
2652 let result = self.run_scene_user_cmd_inline(
2653 Some(&pending.scn_name),
2654 &pending.cmd_name,
2655 &call_args,
2656 self.cfg.fm_void,
2657 true,
2658 );
2659 self.restore_frame_action_current_object(prev_target, prev_chain);
2660
2661 let _ = self.with_frame_action_mut(&item, |fa| {
2662 fa.end_flag = false;
2663 });
2664 if let Err(e) = result {
2665 self.ctx.unknown.record_note(&format!(
2666 "frame_action.finish.failed:{}:{}:{e}",
2667 pending.scn_name, pending.cmd_name
2668 ));
2669 }
2670 Ok(())
2671 }
2672
2673 fn run_pending_button_action(&mut self, action: PendingButtonAction) -> Result<()> {
2674 self.ctx.input.use_current();
2681 self.script_input_synced_this_frame = false;
2682
2683 match action.kind {
2684 PendingButtonActionKind::UserCall {
2685 scn_name,
2686 cmd_name,
2687 z_no,
2688 } => {
2689 if scn_name.is_empty() {
2690 return Ok(());
2691 }
2692 if std::env::var_os("SG_DEBUG").is_some() {
2693 eprintln!(
2694 "[SG_DEBUG][BUTTON] run action scene={} cmd={} z_no={}",
2695 scn_name, cmd_name, z_no
2696 );
2697 }
2698 if !cmd_name.is_empty() {
2699 let _ = self.enter_scene_user_cmd_call(Some(&scn_name), &cmd_name, &[])?;
2700 } else if z_no >= 0 {
2701 self.farcall_scene_name_ex(
2702 &scn_name,
2703 z_no as i32,
2704 self.cfg.fm_void,
2705 true,
2706 &[],
2707 )?;
2708 }
2709 }
2710 PendingButtonActionKind::Syscom {
2711 sys_type,
2712 sys_type_opt,
2713 mode,
2714 } => {
2715 self.run_pending_button_syscom_action(sys_type, sys_type_opt, mode)?;
2716 }
2717 }
2718 Ok(())
2719 }
2720
2721 fn syscom_proc_trace_enabled() -> bool {
2722 std::env::var_os("SG_SYSCOM_PROC_TRACE").is_some()
2723 || std::env::var_os("SG_DEBUG").is_some()
2724 }
2725
2726 fn syscom_trace_state(&self) -> String {
2727 let st = &self.ctx.globals.syscom;
2728 let msgbk_form = self.ctx.ids.form_global_msgbk;
2729 let msgbk_count = self
2730 .ctx
2731 .globals
2732 .msgbk_forms
2733 .get(&msgbk_form)
2734 .map(|m| m.history.len())
2735 .unwrap_or(0);
2736 let msgbk_visible_count = self
2737 .ctx
2738 .globals
2739 .msgbk_forms
2740 .get(&msgbk_form)
2741 .map(|m| {
2742 m.history
2743 .iter()
2744 .filter(|entry| {
2745 entry.pct_flag
2746 || !entry.msg_str.is_empty()
2747 || !entry.disp_name.is_empty()
2748 || !entry.original_name.is_empty()
2749 || !entry.koe_no_list.is_empty()
2750 })
2751 .count()
2752 })
2753 .unwrap_or(0);
2754 format!(
2755 "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={}",
2756 st.read_skip.onoff,
2757 st.auto_mode.onoff,
2758 st.hide_mwnd.onoff,
2759 st.msg_back_open,
2760 st.msg_back.check_enabled(),
2761 st.pending_proc,
2762 msgbk_form,
2763 msgbk_count,
2764 msgbk_visible_count,
2765 self.ctx.ui.message_waiting(),
2766 self.ctx.ui.message_visible_chars(),
2767 self.ctx.ui.message_wait_message_len(),
2768 self.ctx.ui.message_text().unwrap_or("").chars().count()
2769 )
2770 }
2771
2772 fn syscom_button_op(sys_type: i64) -> Option<(i32, &'static str)> {
2773 use crate::runtime::forms::codes::syscom_op;
2774 Some(match sys_type {
2775 1 => (syscom_op::CALL_SAVE_MENU, "CALL_SAVE_MENU"),
2776 2 => (syscom_op::CALL_LOAD_MENU, "CALL_LOAD_MENU"),
2777 3 => (syscom_op::SET_READ_SKIP_ONOFF_FLAG, "SET_READ_SKIP_ONOFF_FLAG"),
2778 4 => (syscom_op::SET_AUTO_MODE_ONOFF_FLAG, "SET_AUTO_MODE_ONOFF_FLAG"),
2779 5 => (syscom_op::RETURN_TO_SEL, "RETURN_TO_SEL"),
2780 6 => (syscom_op::SET_HIDE_MWND_ONOFF_FLAG, "SET_HIDE_MWND_ONOFF_FLAG"),
2781 7 => (syscom_op::OPEN_MSG_BACK, "OPEN_MSG_BACK"),
2782 8 => (syscom_op::REPLAY_KOE, "REPLAY_KOE"),
2783 9 => (syscom_op::QUICK_SAVE, "QUICK_SAVE"),
2784 10 => (syscom_op::QUICK_LOAD, "QUICK_LOAD"),
2785 11 => (syscom_op::CALL_CONFIG_MENU, "CALL_CONFIG_MENU"),
2786 12 => (syscom_op::SET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG, "SET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG"),
2787 13 => (syscom_op::SET_LOCAL_EXTRA_MODE_VALUE, "SET_LOCAL_EXTRA_MODE_VALUE"),
2788 14 => (syscom_op::SET_GLOBAL_EXTRA_SWITCH_ONOFF, "SET_GLOBAL_EXTRA_SWITCH_ONOFF"),
2789 15 => (syscom_op::SET_GLOBAL_EXTRA_MODE_VALUE, "SET_GLOBAL_EXTRA_MODE_VALUE"),
2790 _ => return None,
2791 })
2792 }
2793
2794 fn dispatch_syscom_button_op(&mut self, op: i32, params: &[Value]) -> Result<bool> {
2795 let form_id = if self.ctx.ids.form_global_syscom != 0 {
2801 self.ctx.ids.form_global_syscom as i32
2802 } else {
2803 constants::global_form::SYSCOM as i32
2804 };
2805
2806 let saved_call = self.ctx.vm_call.take();
2807 let saved_stack_len = self.ctx.stack.len();
2808 self.ctx.vm_call = Some(runtime::VmCallMeta {
2809 element: vec![form_id, op],
2810 al_id: 0,
2811 ret_form: self.cfg.fm_void as i64,
2812 });
2813
2814 let result = runtime::dispatch_form_code(&mut self.ctx, form_id as u32, params);
2815
2816 self.ctx.vm_call = saved_call;
2817 self.ctx.stack.truncate(saved_stack_len);
2818 result
2819 }
2820
2821 fn run_pending_button_syscom_action(
2822 &mut self,
2823 sys_type: i64,
2824 sys_type_opt: i64,
2825 mode: i64,
2826 ) -> Result<()> {
2827 let trace = Self::syscom_proc_trace_enabled();
2828 let Some((op, op_name)) = Self::syscom_button_op(sys_type) else {
2829 if trace {
2830 eprintln!(
2831 "[SYSCOM_PROC_TRACE] button sys_type={} sys_opt={} mode={} resolved=UNKNOWN before {}",
2832 sys_type,
2833 sys_type_opt,
2834 mode,
2835 self.syscom_trace_state()
2836 );
2837 }
2838 return Ok(());
2839 };
2840
2841 if matches!(sys_type, 1 | 9) {
2842 crate::runtime::forms::syscom::prepare_runtime_save_thumb_capture(&mut self.ctx);
2843 }
2844
2845 let params: Vec<Value> = match sys_type {
2852 1 | 2 => Vec::new(),
2853 3 | 4 => vec![Value::Int(if mode == 0 { 1 } else { 0 })],
2854 6 => vec![Value::Int(1)],
2855 5 => vec![Value::Int(1), Value::Int(1), Value::Int(1)],
2856 7 | 8 | 11 => Vec::new(),
2857 9 => vec![Value::Int(sys_type_opt), Value::Int(1), Value::Int(1)],
2858 10 => vec![
2859 Value::Int(sys_type_opt),
2860 Value::Int(1),
2861 Value::Int(1),
2862 Value::Int(1),
2863 ],
2864 12 | 14 => vec![
2865 Value::Int(sys_type_opt),
2866 Value::Int(if mode == 0 { 1 } else { 0 }),
2867 ],
2868 13 | 15 => vec![Value::Int(sys_type_opt), Value::Int(mode + 1)],
2869 _ => Vec::new(),
2870 };
2871 if trace {
2872 eprintln!(
2873 "[SYSCOM_PROC_TRACE] button sys_type={} sys_opt={} mode={} resolved={}({}) params={:?} before {}",
2874 sys_type,
2875 sys_type_opt,
2876 mode,
2877 op_name,
2878 op,
2879 params,
2880 self.syscom_trace_state()
2881 );
2882 }
2883 let dispatch_result = self.dispatch_syscom_button_op(op, ¶ms);
2884 if trace {
2885 let status = match dispatch_result.as_ref() {
2886 Ok(handled) => format!("ok handled={}", handled),
2887 Err(err) => format!("err={}", err),
2888 };
2889 eprintln!(
2890 "[SYSCOM_PROC_TRACE] after resolved={}({}) status={} {}",
2891 op_name,
2892 op,
2893 status,
2894 self.syscom_trace_state()
2895 );
2896 }
2897 dispatch_result?;
2898 Ok(())
2899 }
2900
2901 fn drain_pending_button_actions(&mut self) -> Result<()> {
2902 let mut budget = 64usize;
2903 while !self.ctx.globals.pending_button_actions.is_empty() {
2904 if budget == 0 {
2905 bail!("button action queue did not drain");
2906 }
2907 budget -= 1;
2908 let pending = std::mem::take(&mut self.ctx.globals.pending_button_actions);
2909 for action in pending {
2910 self.run_pending_button_action(action)?;
2911 }
2912 }
2913 Ok(())
2914 }
2915
2916 pub fn process_pending_button_actions(&mut self) -> Result<()> {
2917 self.drain_pending_button_actions()
2918 }
2919
2920 fn drain_pending_frame_action_finishes(&mut self) -> Result<()> {
2921 let mut budget = 64usize;
2922 while !self.ctx.globals.pending_frame_action_finishes.is_empty() {
2923 if budget == 0 {
2924 bail!("frame action finish queue did not drain");
2925 }
2926 budget -= 1;
2927 let pending = std::mem::take(&mut self.ctx.globals.pending_frame_action_finishes);
2928 for item in pending {
2929 self.run_pending_frame_action_finish(item)?;
2930 }
2931 }
2932 Ok(())
2933 }
2934
2935 pub fn tick_frame(&mut self) -> Result<()> {
2936 let trace = std::env::var_os("SG_TICK_TRACE").is_some()
2937 || std::env::var_os("SG_FRAME_ACTION_TRACE").is_some();
2938 if trace {
2939 eprintln!(
2940 "[SG_TICK_TRACE] tick_frame start blocked={} halted={} scene={:?}",
2941 self.is_blocked(),
2942 self.halted,
2943 self.current_scene_name()
2944 );
2945 }
2946 self.drain_pending_button_actions()?;
2947 if self.ctx.globals.syscom.pending_proc.is_some() {
2948 if trace || std::env::var_os("SG_DEBUG").is_some() {
2949 eprintln!(
2950 "[SG_DEBUG][SYSCOM_PROC] stop frame tick before frame actions pending_proc={:?}",
2951 self.ctx.globals.syscom.pending_proc
2952 );
2953 }
2954 return Ok(());
2955 }
2956 if self.ctx.globals.script.frame_action_time_stop_flag && trace {
2957 eprintln!(
2958 "[SG_TICK_TRACE] frame_action_time_stop_flag set; executing callbacks with frozen frame-action time"
2959 );
2960 }
2961
2962 self.ctx.tick_frame();
2968
2969 let mut work: Vec<FrameActionWork> = Vec::new();
2970 let mut global_form_ids: Vec<u32> =
2971 self.ctx.globals.frame_actions.keys().copied().collect();
2972 global_form_ids.sort_unstable();
2973 for form_id in global_form_ids {
2974 let Some(fa) = self.ctx.globals.frame_actions.get(&form_id) else {
2975 continue;
2976 };
2977 if !fa.cmd_name.is_empty() {
2978 work.push(FrameActionWork {
2979 stage_idx: -1,
2980 obj_idx: usize::MAX,
2981 ch_idx: None,
2982 global_form_id: Some(form_id),
2983 object_chain: None,
2984 frame_action_chain: Some(vec![form_id as i32]),
2985 scn_name: fa.scn_name.clone(),
2986 cmd_name: fa.cmd_name.clone(),
2987 args: fa.args.clone(),
2988 count: fa.counter.get_count(),
2989 end_time: fa.end_time,
2990 });
2991 }
2992 }
2993 let mut global_list_form_ids: Vec<u32> = self
2994 .ctx
2995 .globals
2996 .frame_action_lists
2997 .keys()
2998 .copied()
2999 .collect();
3000 global_list_form_ids.sort_unstable();
3001 for form_id in global_list_form_ids {
3002 let Some(list) = self.ctx.globals.frame_action_lists.get(&form_id) else {
3003 continue;
3004 };
3005 for (idx, fa) in list.iter().enumerate() {
3006 if !fa.cmd_name.is_empty() {
3007 work.push(FrameActionWork {
3008 stage_idx: -1,
3009 obj_idx: usize::MAX,
3010 ch_idx: Some(idx),
3011 global_form_id: Some(form_id),
3012 object_chain: None,
3013 frame_action_chain: Some(vec![
3014 form_id as i32,
3015 crate::runtime::forms::codes::ELM_ARRAY,
3016 idx as i32,
3017 ]),
3018 scn_name: fa.scn_name.clone(),
3019 cmd_name: fa.cmd_name.clone(),
3020 args: fa.args.clone(),
3021 count: fa.counter.get_count(),
3022 end_time: fa.end_time,
3023 });
3024 }
3025 }
3026 }
3027 let mut stage_form_ids: Vec<u32> = self.ctx.globals.stage_forms.keys().copied().collect();
3028 stage_form_ids.sort_unstable();
3029 for form_id in stage_form_ids {
3030 let Some(st) = self.ctx.globals.stage_forms.get(&form_id) else {
3031 continue;
3032 };
3033 let mut stage_ids: Vec<i64> = st.object_lists.keys().copied().collect();
3034 stage_ids.sort_unstable();
3035 for stage_idx in stage_ids {
3036 let Some(objs) = st.object_lists.get(&stage_idx) else {
3037 continue;
3038 };
3039 for (obj_idx, obj) in objs.iter().enumerate() {
3040 if st.is_embedded_object_slot(stage_idx, obj_idx) {
3041 continue;
3042 }
3043 let object_chain = vec![
3044 self.ctx.ids.form_global_stage as i32,
3045 self.ctx.ids.elm_array,
3046 stage_idx as i32,
3047 self.ctx.ids.stage_elm_object,
3048 self.ctx.ids.elm_array,
3049 obj_idx as i32,
3050 ];
3051 Self::collect_object_frame_action_work_recursive(
3052 obj,
3053 stage_idx,
3054 obj_idx,
3055 object_chain,
3056 &mut work,
3057 );
3058 }
3059 }
3060
3061 let mut mwnd_stage_ids: Vec<i64> = st.mwnd_lists.keys().copied().collect();
3062 mwnd_stage_ids.sort_unstable();
3063 for stage_idx in mwnd_stage_ids {
3064 let Some(mwnds) = st.mwnd_lists.get(&stage_idx) else {
3065 continue;
3066 };
3067 for (mwnd_idx, mwnd) in mwnds.iter().enumerate() {
3068 for (obj_idx, obj) in mwnd.button_list.iter().enumerate() {
3069 let object_chain = vec![
3070 self.ctx.ids.form_global_stage as i32,
3071 self.ctx.ids.elm_array,
3072 stage_idx as i32,
3073 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3074 self.ctx.ids.elm_array,
3075 mwnd_idx as i32,
3076 crate::runtime::forms::codes::elm_value::MWND_BUTTON,
3077 self.ctx.ids.elm_array,
3078 obj_idx as i32,
3079 ];
3080 Self::collect_object_frame_action_work_recursive(
3081 obj,
3082 stage_idx,
3083 obj_idx,
3084 object_chain,
3085 &mut work,
3086 );
3087 }
3088 for (obj_idx, obj) in mwnd.face_list.iter().enumerate() {
3089 let object_chain = vec![
3090 self.ctx.ids.form_global_stage as i32,
3091 self.ctx.ids.elm_array,
3092 stage_idx as i32,
3093 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3094 self.ctx.ids.elm_array,
3095 mwnd_idx as i32,
3096 crate::runtime::forms::codes::elm_value::MWND_FACE,
3097 self.ctx.ids.elm_array,
3098 obj_idx as i32,
3099 ];
3100 Self::collect_object_frame_action_work_recursive(
3101 obj,
3102 stage_idx,
3103 obj_idx,
3104 object_chain,
3105 &mut work,
3106 );
3107 }
3108 for (obj_idx, obj) in mwnd.object_list.iter().enumerate() {
3109 let object_chain = vec![
3110 self.ctx.ids.form_global_stage as i32,
3111 self.ctx.ids.elm_array,
3112 stage_idx as i32,
3113 crate::runtime::forms::codes::elm_value::STAGE_MWND,
3114 self.ctx.ids.elm_array,
3115 mwnd_idx as i32,
3116 crate::runtime::forms::codes::elm_value::MWND_OBJECT,
3117 self.ctx.ids.elm_array,
3118 obj_idx as i32,
3119 ];
3120 Self::collect_object_frame_action_work_recursive(
3121 obj,
3122 stage_idx,
3123 obj_idx,
3124 object_chain,
3125 &mut work,
3126 );
3127 }
3128 }
3129 }
3130 }
3131 if trace {
3132 eprintln!("[SG_TICK_TRACE] frame_action work items={}", work.len());
3133 }
3134 for item in work {
3138 if trace {
3139 eprintln!(
3140 "[SG_TICK_TRACE] invoke stage={} obj={} ch={:?} global={:?} cmd={} count={} end_time={} args={:?}",
3141 item.stage_idx,
3142 item.obj_idx,
3143 item.ch_idx,
3144 item.global_form_id,
3145 item.cmd_name,
3146 item.count,
3147 item.end_time,
3148 item.args
3149 );
3150 }
3151 let call_args = Self::make_frame_action_call_args(
3156 item.frame_action_chain.as_ref(),
3157 item.object_chain.as_ref(),
3158 &item.args,
3159 );
3160 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
3161 if let Err(e) = self.run_scene_user_cmd_inline(
3162 Some(&item.scn_name),
3163 &item.cmd_name,
3164 &call_args,
3165 self.cfg.fm_void,
3166 true,
3167 ) {
3168 self.ctx.unknown.record_note(&format!(
3169 "frame_action.call.failed:{}:{}:{e}",
3170 item.scn_name, item.cmd_name
3171 ));
3172 }
3173 self.restore_frame_action_current_object(prev_target, prev_chain);
3174
3175 if let Some((finish_cmd_name, finish_args)) = self.begin_frame_action_finish(&item) {
3176 if trace {
3177 eprintln!(
3178 "[SG_TICK_TRACE] finish stage={} obj={} ch={:?} global={:?} cmd={} args={:?}",
3179 item.stage_idx,
3180 item.obj_idx,
3181 item.ch_idx,
3182 item.global_form_id,
3183 finish_cmd_name,
3184 finish_args
3185 );
3186 }
3187 let finish_call_args = Self::make_frame_action_call_args(
3188 item.frame_action_chain.as_ref(),
3189 item.object_chain.as_ref(),
3190 &finish_args,
3191 );
3192 let (prev_target, prev_chain) = self.set_frame_action_current_object(&item);
3193 if let Err(e) = self.run_scene_user_cmd_inline(
3194 Some(&item.scn_name),
3195 &finish_cmd_name,
3196 &finish_call_args,
3197 self.cfg.fm_void,
3198 true,
3199 ) {
3200 self.ctx.unknown.record_note(&format!(
3201 "frame_action.finish_call.failed:{}:{}:{e}",
3202 item.scn_name, finish_cmd_name
3203 ));
3204 }
3205 self.restore_frame_action_current_object(prev_target, prev_chain);
3206 self.end_frame_action_finish(&item);
3207 }
3208 }
3209 if trace {
3210 eprintln!(
3211 "[SG_TICK_TRACE] after frame-action callbacks blocked={} halted={}",
3212 self.is_blocked(),
3213 self.halted
3214 );
3215 }
3216 self.script_input_synced_this_frame = false;
3217 Ok(())
3218 }
3219
3220 pub fn restart_scene_name(&mut self, scene_name: &str, z_no: i32) -> Result<()> {
3221 let (stream, scene_no) = self.load_scene_stream(scene_name, z_no)?;
3222 self.ctx.reset_for_scene_restart();
3223 self.stream = stream;
3224 self.int_stack.clear();
3225 self.str_stack.clear();
3226 self.element_points.clear();
3227 self.call_stack.clear();
3228 self.call_stack.push(self.scene_base_call());
3229 self.gosub_return_stack.clear();
3230 self.user_props.clear();
3231 self.scene_stack.clear();
3232 self.save_point = None;
3233 self.ctx.local_save_snapshot = None;
3234 self.sel_point_stack.clear();
3235 self.current_scene_no = Some(scene_no);
3236 self.current_scene_name = Some(scene_name.to_string());
3237 self.current_line_no = -1;
3238 self.ctx.current_scene_no = Some(scene_no as i64);
3239 self.ctx.current_scene_name = Some(scene_name.to_string());
3240 self.ctx.current_line_no = -1;
3241 self.halted = false;
3242 self.delayed_ret_form = None;
3243 Ok(())
3244 }
3245
3246 fn make_resume_point(&self) -> VmResumePoint<'a> {
3247 VmResumePoint {
3248 stream: self.stream.clone(),
3249 user_cmd_names: self.user_cmd_names.clone(),
3250 call_cmd_names: self.call_cmd_names.clone(),
3251 int_stack: self.int_stack.clone(),
3252 str_stack: self.str_stack.clone(),
3253 element_points: self.element_points.clone(),
3254 call_stack: self.call_stack.clone(),
3255 gosub_return_stack: self.gosub_return_stack.clone(),
3256 user_props: self.user_props.clone(),
3257 current_scene_no: self.current_scene_no,
3258 current_scene_name: self.current_scene_name.clone(),
3259 current_line_no: self.current_line_no,
3260 globals: self.ctx.globals.clone(),
3261 }
3262 }
3263
3264 fn restore_resume_point(&mut self, point: VmResumePoint<'a>) {
3265 self.stream = point.stream;
3266 self.user_cmd_names = point.user_cmd_names;
3267 self.call_cmd_names = point.call_cmd_names;
3268 self.int_stack = point.int_stack;
3269 self.str_stack = point.str_stack;
3270 self.element_points = point.element_points;
3271 self.call_stack = point.call_stack;
3272 self.gosub_return_stack = point.gosub_return_stack;
3273 self.user_props = point.user_props;
3274 self.current_scene_no = point.current_scene_no;
3275 self.current_scene_name = point.current_scene_name;
3276 self.current_line_no = point.current_line_no;
3277 let mut restored_globals = point.globals;
3278 restored_globals.syscom.pending_proc = None;
3279 restored_globals.syscom.menu_open = false;
3280 restored_globals.syscom.menu_kind = None;
3281 restored_globals.syscom.msg_back_open = false;
3282 self.ctx.globals = restored_globals;
3283 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
3284 self.ctx.current_scene_name = self.current_scene_name.clone();
3285 self.ctx.current_line_no = self.current_line_no as i64;
3286 self.ctx.wait = runtime::wait::VmWait::default();
3287 self.ctx.stack.clear();
3288 self.halted = false;
3289 self.delayed_ret_form = None;
3290 }
3291
3292 pub fn has_sel_point(&self) -> bool {
3293 !self.sel_point_stack.is_empty()
3294 }
3295
3296 pub fn restore_last_sel_point(&mut self) -> bool {
3297 let Some(point) = self.sel_point_stack.last().cloned() else {
3298 return false;
3299 };
3300 self.restore_resume_point(point);
3301 true
3302 }
3303
3304 pub fn step(&mut self) -> Result<bool> {
3305 self.step_inner(true)
3306 }
3307
3308 pub fn begin_script_proc_pump(&mut self) {
3312 self.steps = 0;
3313 }
3314
3315 pub fn run_script_proc(&mut self) -> Result<bool> {
3319 self.begin_script_proc_pump();
3320 self.run_script_proc_continue()
3321 }
3322
3323 pub fn run_script_proc_continue(&mut self) -> Result<bool> {
3328 if self.halted {
3329 return Ok(false);
3330 }
3331 if self.is_blocked() {
3332 return Ok(true);
3333 }
3334
3335 if !self.script_input_synced_this_frame {
3336 self.ctx.sync_script_input_from_runtime();
3337 self.ctx.input.next_frame();
3338 self.script_input_synced_this_frame = true;
3339 }
3340
3341 loop {
3342 let proc_generation_before = self.ctx.proc_generation();
3343 let running = self.step_inner(true)?;
3344 if !running || self.halted {
3345 return Ok(running);
3346 }
3347
3348 if self.is_blocked() {
3349 return Ok(true);
3350 }
3351 if self.ctx.proc_generation() != proc_generation_before {
3352 return Ok(true);
3353 }
3354 }
3355 }
3356
3357 #[allow(dead_code)]
3358 fn step_inner(&mut self, respect_wait: bool) -> Result<bool> {
3359 self.yield_safe_after_step = false;
3360 if self.halted {
3361 return Ok(false);
3362 }
3363
3364 if respect_wait {
3368 let blocked = self.ctx.wait_poll();
3369 if blocked {
3370 return Ok(true);
3371 }
3372 }
3373
3374 if respect_wait {
3380 let delayed = self
3381 .call_stack
3382 .last_mut()
3383 .and_then(|frame| frame.delayed_ret_form.take());
3384 if let Some(rf) = delayed {
3385 if self.ctx.stack.is_empty() {
3386 self.push_default_for_ret(rf);
3387 } else {
3388 self.take_ctx_return(rf)?;
3389 }
3390 }
3391 }
3392
3393 if self.cfg.max_steps > 0 && self.steps >= self.cfg.max_steps {
3394 let scene = self.current_scene_name.as_deref().unwrap_or("<none>");
3395 let scene_no = self
3396 .current_scene_no
3397 .map(|v| v.to_string())
3398 .unwrap_or_else(|| "-".to_string());
3399 bail!(
3400 "VM reached SIGLUS_VM_MAX_STEPS={} (possible infinite loop) scene={} scene_no={} line={} pc=0x{:x}",
3401 self.cfg.max_steps,
3402 scene,
3403 scene_no,
3404 self.current_line_no,
3405 self.stream.get_prg_cntr()
3406 );
3407 }
3408 self.steps += 1;
3409
3410 let pc_before = self.stream.get_prg_cntr();
3411 let opcode = match self.stream.pop_u8() {
3412 Ok(v) => v,
3413 Err(_) => {
3414 if self.return_from_scene(Vec::new())? {
3415 return Ok(true);
3416 }
3417 self.halted = true;
3418 return Ok(false);
3419 }
3420 };
3421
3422 self.vm_trace_opcode(pc_before, opcode, "before");
3423
3424 match opcode {
3425 CD_NL => {
3426 let line_no = self.stream.pop_i32()?;
3427 self.current_line_no = line_no;
3428 self.ctx.current_line_no = line_no as i64;
3429 self.yield_safe_after_step = true;
3430 if self.element_points.is_empty() {
3435 self.ctx.globals.current_object_chain = None;
3436 self.ctx.globals.current_stage_object = None;
3437 }
3438 }
3439
3440 CD_PUSH => {
3441 let form_code = self.stream.pop_i32()?;
3442 self.exec_push(form_code)?;
3443 }
3444 CD_POP => {
3445 let form_code = self.stream.pop_i32()?;
3446 self.exec_pop(form_code)?;
3447 }
3448 CD_COPY => {
3449 let form_code = self.stream.pop_i32()?;
3450 self.exec_copy(form_code)?;
3451 }
3452
3453 CD_ELM_POINT => {
3454 self.element_points.push(self.int_stack.len());
3455 self.vm_trace(
3456 None,
3457 format!("ELM_POINT push start={} ", self.int_stack.len()),
3458 );
3459 }
3460 CD_COPY_ELM => {
3461 self.exec_copy_element()?;
3462 }
3463
3464 CD_PROPERTY => {
3465 let elm = self.pop_element()?;
3466 self.vm_trace(None, format!("CD_PROPERTY elm={:?}", elm));
3467 self.exec_property(elm)?;
3468 }
3469 CD_DEC_PROP => {
3470 let form_code = self.stream.pop_i32()?;
3471 let prop_id = self.stream.pop_i32()?;
3472
3473 let size = if form_code == self.cfg.fm_intlist || form_code == self.cfg.fm_strlist {
3474 self.pop_int()?.max(0) as usize
3475 } else {
3476 0usize
3477 };
3478 let prop_element = vec![constants::elm::create(
3479 constants::elm::OWNER_CALL_PROP,
3480 0,
3481 prop_id,
3482 )];
3483 let value = if form_code == self.cfg.fm_int {
3484 CallPropValue::Int(0)
3485 } else if form_code == self.cfg.fm_str {
3486 CallPropValue::Str(String::new())
3487 } else if form_code == self.cfg.fm_intlist {
3488 CallPropValue::IntList(vec![0; size])
3489 } else if form_code == self.cfg.fm_strlist {
3490 CallPropValue::StrList(vec![String::new(); size])
3491 } else {
3492 CallPropValue::Element(prop_element.clone())
3493 };
3494
3495 let frame = self
3496 .call_stack
3497 .last_mut()
3498 .ok_or_else(|| anyhow!("call stack underflow"))?;
3499 frame.user_props.push(CallProp {
3500 prop_id,
3501 form: form_code,
3502 decl_size: size,
3503 element: prop_element,
3504 value,
3505 });
3506 }
3507 CD_ARG => {
3508 let (frame_action_proc, actual_arg_cnt, forms): (bool, usize, Vec<i32>) = {
3511 let frame = self
3512 .call_stack
3513 .last()
3514 .ok_or_else(|| anyhow!("call stack underflow"))?;
3515 (
3516 frame.frame_action_proc,
3517 frame.arg_cnt,
3518 frame.user_props.iter().map(|p| p.form).collect(),
3519 )
3520 };
3521
3522 if frame_action_proc {
3523 if forms.first().copied() != Some(crate::runtime::forms::codes::FM_FRAMEACTION)
3524 {
3525 bail!("frame_action CD_ARG requires first argument to be FM_FRAMEACTION");
3526 }
3527 if actual_arg_cnt != forms.len() {
3528 bail!(
3529 "frame_action CD_ARG argument count mismatch: declared={} actual={}",
3530 forms.len(),
3531 actual_arg_cnt
3532 );
3533 }
3534 }
3535
3536 let mut values: Vec<CallPropValue> = Vec::with_capacity(forms.len());
3538 for &form in forms.iter().rev() {
3539 let v = if form == self.cfg.fm_int {
3540 CallPropValue::Int(self.pop_int()?)
3541 } else if form == self.cfg.fm_str {
3542 CallPropValue::Str(self.pop_str()?)
3543 } else {
3544 CallPropValue::Element(self.pop_element()?)
3545 };
3546 values.push(v);
3547 }
3548 values.reverse();
3549
3550 let frame = self
3551 .call_stack
3552 .last_mut()
3553 .ok_or_else(|| anyhow!("call stack underflow"))?;
3554 for (prop, v) in frame.user_props.iter_mut().zip(values.into_iter()) {
3555 match (&v, prop.form) {
3556 (CallPropValue::Int(_), f) if f == self.cfg.fm_int => {
3557 prop.value = v;
3558 }
3559 (CallPropValue::Str(_), f) if f == self.cfg.fm_str => {
3560 prop.value = v;
3561 }
3562 (CallPropValue::Element(e), _) => {
3563 prop.element = e.clone();
3566 if matches!(
3567 prop.form,
3568 crate::runtime::forms::codes::FM_INTREF
3569 | crate::runtime::forms::codes::FM_STRREF
3570 | crate::runtime::forms::codes::FM_INTLISTREF
3571 | crate::runtime::forms::codes::FM_STRLISTREF
3572 | crate::runtime::forms::codes::FM_LIST
3573 ) {
3574 prop.value = CallPropValue::Element(e.clone());
3575 }
3576 }
3577 _ => {
3578 prop.value = v;
3579 }
3580 }
3581 }
3582 }
3583
3584 CD_GOTO => {
3585 let label_no = self.stream.pop_i32()?;
3586 self.sg_omv_trace(format!("GOTO label={} taken=true", label_no));
3587 self.stream.jump_to_label(label_no.max(0) as usize)?;
3588 }
3589 CD_GOTO_TRUE => {
3590 let label_no = self.stream.pop_i32()?;
3591 let before_tail_start = self.int_stack.len().saturating_sub(16);
3592 let before_tail = self.int_stack[before_tail_start..].to_vec();
3593 let cond = self.pop_int()?;
3594 let taken = cond != 0;
3595 self.sg_omv_trace(format!("GOTO_TRUE label={} cond={} taken={}", label_no, cond, taken));
3596 self.trace_cf_branch_goto(pc_before, "GOTO_TRUE", label_no, cond, taken, &before_tail);
3597 if taken {
3598 self.stream.jump_to_label(label_no.max(0) as usize)?;
3599 }
3600 }
3601 CD_GOTO_FALSE => {
3602 let label_no = self.stream.pop_i32()?;
3603 let before_tail_start = self.int_stack.len().saturating_sub(16);
3604 let before_tail = self.int_stack[before_tail_start..].to_vec();
3605 let cond = self.pop_int()?;
3606 let taken = cond == 0;
3607 self.sg_omv_trace(format!("GOTO_FALSE label={} cond={} taken={}", label_no, cond, taken));
3608 self.trace_cf_branch_goto(pc_before, "GOTO_FALSE", label_no, cond, taken, &before_tail);
3609 if taken {
3610 self.stream.jump_to_label(label_no.max(0) as usize)?;
3611 }
3612 }
3613 CD_GOSUB => {
3614 let label_no = self.stream.pop_i32()?;
3615 let _args = self.pop_arg_list()?;
3616 let return_pc = self.stream.get_prg_cntr();
3617 self.vm_trace(
3618 Some(pc_before),
3619 format!("GOSUB label={} return_pc=0x{return_pc:x}", label_no),
3620 );
3621
3622 let caller = self
3624 .call_stack
3625 .last_mut()
3626 .ok_or_else(|| anyhow!("call stack underflow"))?;
3627 caller.return_pc = return_pc;
3628 caller.ret_form = self.cfg.fm_int;
3629 self.gosub_return_stack.push((return_pc, self.cfg.fm_int));
3630
3631 let scratch_args = self.call_scratch_from_args(&_args);
3633 let mut callee = self.make_call_frame(
3634 self.cfg.fm_void,
3635 false,
3636 false,
3637 _args.len(),
3638 Some(scratch_args),
3639 );
3640 callee.return_override = Some((return_pc, self.cfg.fm_int));
3641 self.call_stack.push(callee);
3642
3643 self.stream.jump_to_label(label_no.max(0) as usize)?;
3644 }
3645 CD_GOSUBSTR => {
3646 let label_no = self.stream.pop_i32()?;
3647 let _args = self.pop_arg_list()?;
3648 let return_pc = self.stream.get_prg_cntr();
3649 self.vm_trace(
3650 Some(pc_before),
3651 format!("GOSUBSTR label={} return_pc=0x{return_pc:x}", label_no),
3652 );
3653
3654 let caller = self
3655 .call_stack
3656 .last_mut()
3657 .ok_or_else(|| anyhow!("call stack underflow"))?;
3658 caller.return_pc = return_pc;
3659 caller.ret_form = self.cfg.fm_str;
3660 self.gosub_return_stack.push((return_pc, self.cfg.fm_str));
3661
3662 let scratch_args = self.call_scratch_from_args(&_args);
3663 let mut callee = self.make_call_frame(
3664 self.cfg.fm_void,
3665 false,
3666 false,
3667 _args.len(),
3668 Some(scratch_args),
3669 );
3670 callee.return_override = Some((return_pc, self.cfg.fm_str));
3671 self.call_stack.push(callee);
3672
3673 self.stream.jump_to_label(label_no.max(0) as usize)?;
3674 }
3675 CD_RETURN => {
3676 let args = self.pop_arg_list()?;
3677 self.sg_omv_trace(format!("RETURN argc={} args={:?} call_depth={} scene_stack={}", args.len(), args, self.call_stack.len(), self.scene_stack.len()));
3678 if self.call_stack.len() == 1 {
3679 if self.return_from_scene(args.clone())? {
3680 return Ok(true);
3681 }
3682 self.halted = true;
3683 return Ok(false);
3684 }
3685 if self.exec_return(args)? {
3686 return Ok(false);
3687 }
3688 }
3689
3690 CD_ASSIGN => {
3691 let _left_form = self.stream.pop_i32()?;
3692 let right_form = self.stream.pop_i32()?;
3693 let al_id = self.stream.pop_i32()?;
3694 let rhs = self.pop_value_for_form(right_form)?;
3695 let elm = self.pop_element()?;
3696 self.exec_assign(elm, al_id, rhs)?;
3697 }
3698
3699 CD_OPERATE_1 => {
3700 let form_code = self.stream.pop_i32()?;
3701 let opr = self.stream.pop_u8()?;
3702 self.exec_operate_1(form_code, opr)?;
3703 }
3704 CD_OPERATE_2 => {
3705 let form_l = self.stream.pop_i32()?;
3706 let form_r = self.stream.pop_i32()?;
3707 let opr = self.stream.pop_u8()?;
3708 self.exec_operate_2(form_l, form_r, opr)?;
3709 }
3710 CD_COMMAND => {
3711 let arg_list_id = self.stream.pop_i32()?;
3713 let mut args = self.pop_arg_list()?;
3714 let elm = self.pop_element()?;
3715
3716 let named_arg_cnt = self.stream.pop_i32()?;
3717 if named_arg_cnt < 0 {
3718 bail!("negative named_arg_cnt={named_arg_cnt}");
3719 }
3720
3721 let mut named_ids: Vec<i32> = Vec::with_capacity(named_arg_cnt as usize);
3722 for _ in 0..(named_arg_cnt as usize) {
3723 named_ids.push(self.stream.pop_i32()?);
3724 }
3725
3726 if !named_ids.is_empty() {
3727 let n = named_ids.len().min(args.len());
3728 for a in 0..n {
3729 let idx = args.len() - 1 - a;
3730 let id = named_ids[a];
3731 let v = std::mem::replace(&mut args[idx], crate::runtime::Value::Int(0));
3732 args[idx] = crate::runtime::Value::NamedArg {
3733 id,
3734 value: Box::new(v),
3735 };
3736 }
3737 }
3738
3739 let ret_form = self.stream.pop_i32()?;
3740 if let Some(raw_head) = elm.first().copied() {
3741 let form_id = self.canonical_runtime_form_id(raw_head as u32) as i32;
3742 let op_id = if elm.len() >= 2 { elm[1] } else { arg_list_id };
3743 self.sg_omv_trace_command("CD_COMMAND", &elm, form_id, op_id, arg_list_id, ret_form, &args);
3744 }
3745 let _ = self.ctx.take_read_flag_no_request();
3746 let block_generation = self.ctx.wait.block_generation();
3747 let proc_generation = self.ctx.proc_generation();
3748 self.exec_command(elm, arg_list_id, ret_form, &mut args)?;
3749 self.drain_runtime_save_load_requests()?;
3750 if self.ctx.take_read_flag_no_request() {
3751 let read_flag_no = self.stream.pop_i32()?;
3752 self.ctx.submit_read_flag_no(read_flag_no);
3753 }
3754 if self.ctx.proc_generation() != proc_generation {
3755 return Ok(true);
3756 }
3757 if respect_wait
3758 && self.ctx.wait.block_generation() != block_generation
3759 && self.ctx.wait_poll()
3760 {
3761 return Ok(true);
3762 }
3763 }
3764 CD_TEXT => {
3765 let rf_flag_no = self.stream.pop_i32()?;
3766 let text = self.pop_str()?;
3767 if !crate::runtime::forms::stage::cd_text_current_mwnd(
3768 &mut self.ctx,
3769 &text,
3770 rf_flag_no as i64,
3771 ) {
3772 self.ctx.ui.set_message(text);
3773 }
3774 }
3775 CD_NAME => {
3776 let name = self.pop_str()?;
3777 if !crate::runtime::forms::stage::cd_name_current_mwnd(&mut self.ctx, &name) {
3778 self.ctx.ui.set_name(name);
3779 }
3780 }
3781 CD_SEL_BLOCK_START => {
3782 }
3785 CD_SEL_BLOCK_END => {
3786 self.push_int(0);
3789 }
3790
3791 CD_EOF => {
3792 if self.return_from_scene(Vec::new())? {
3793 return Ok(true);
3794 }
3795 self.halted = true;
3796 return Ok(false);
3797 }
3798
3799 CD_NONE => {
3800 *self.unknown_opcodes.entry(opcode).or_insert(0) += 1;
3803 let scn_cmd_context = self.vm_scn_cmd_context(pc_before);
3804 eprintln!(
3805 "VM hit CD_NONE scene={} line={} pc=0x{:x} {} bytes={:02x?}; stopping",
3806 self.current_scene_name.as_deref().unwrap_or("<none>"),
3807 self.current_line_no,
3808 pc_before,
3809 scn_cmd_context,
3810 &self.stream.scn[pc_before.saturating_sub(8)..self.stream.scn.len().min(pc_before + 16)]
3811 );
3812 self.halted = true;
3813 return Ok(false);
3814 }
3815
3816 other => {
3817 *self.unknown_opcodes.entry(other).or_insert(0) += 1;
3818 println!(
3819 "VM unknown opcode=0x{other:02x} at pc=0x{:x}; stopping",
3820 pc_before
3821 );
3822 self.halted = true;
3823 return Ok(false);
3824 }
3825 }
3826
3827 Ok(true)
3828 }
3829
3830 pub fn run(&mut self) -> Result<()> {
3831 while self.step()? {}
3832 Ok(())
3833 }
3834
3835 fn push_int(&mut self, v: i32) {
3840 self.int_stack.push(v);
3841 self.vm_trace(None, format!("push_int {}", v));
3842 }
3843
3844 fn pop_int(&mut self) -> Result<i32> {
3845 match self.int_stack.pop() {
3846 Some(v) => {
3847 self.vm_trace(None, format!("pop_int -> {}", v));
3848 Ok(v)
3849 }
3850 None => {
3851 self.vm_trace(None, "pop_int underflow");
3852 Err(anyhow!(
3853 "int stack underflow: scene={} scene_no={} line={} pc=0x{:x}",
3854 self.current_scene_name.as_deref().unwrap_or("<none>"),
3855 self.current_scene_no
3856 .map(|v| v.to_string())
3857 .unwrap_or_else(|| "-".to_string()),
3858 self.current_line_no,
3859 self.stream.get_prg_cntr()
3860 ))
3861 }
3862 }
3863 }
3864
3865 fn peek_int(&self) -> Result<i32> {
3866 self.int_stack
3867 .last()
3868 .copied()
3869 .ok_or_else(|| anyhow!("int stack underflow"))
3870 }
3871
3872 fn push_str(&mut self, s: String) {
3873 let preview = if s.chars().count() > 48 {
3874 let mut tmp = s.chars().take(48).collect::<String>();
3875 tmp.push('…');
3876 tmp
3877 } else {
3878 s.clone()
3879 };
3880 self.str_stack.push(s);
3881 self.vm_trace(None, format!("push_str {:?}", preview));
3882 }
3883
3884 fn pop_str(&mut self) -> Result<String> {
3885 match self.str_stack.pop() {
3886 Some(v) => {
3887 let preview = if v.chars().count() > 48 {
3888 let mut tmp = v.chars().take(48).collect::<String>();
3889 tmp.push('…');
3890 tmp
3891 } else {
3892 v.clone()
3893 };
3894 self.vm_trace(None, format!("pop_str -> {:?}", preview));
3895 Ok(v)
3896 }
3897 None => {
3898 self.vm_trace(None, "pop_str underflow");
3899 Err(anyhow!("str stack underflow"))
3900 }
3901 }
3902 }
3903
3904 fn peek_str(&self) -> Result<String> {
3905 self.str_stack
3906 .last()
3907 .cloned()
3908 .ok_or_else(|| anyhow!("str stack underflow"))
3909 }
3910
3911 fn push_element(&mut self, elm: Vec<i32>) {
3912 self.element_points.push(self.int_stack.len());
3913 self.int_stack.extend_from_slice(&elm);
3914 self.vm_trace(None, format!("push_element {:?}", elm));
3915 }
3916
3917 fn pop_element(&mut self) -> Result<Vec<i32>> {
3918 let start = match self.element_points.pop() {
3919 Some(v) => v,
3920 None => {
3921 self.vm_trace(None, "pop_element underflow (missing ELM_POINT)");
3922 return Err(anyhow!("element stack underflow (missing ELM_POINT)"));
3923 }
3924 };
3925 if start > self.int_stack.len() {
3926 self.vm_trace(
3927 None,
3928 format!(
3929 "pop_element invalid start={} len={}",
3930 start,
3931 self.int_stack.len()
3932 ),
3933 );
3934 bail!(
3935 "invalid element point start={start} len={}",
3936 self.int_stack.len()
3937 );
3938 }
3939 let elm = self.int_stack[start..].to_vec();
3940 self.int_stack.truncate(start);
3941 self.vm_trace(None, format!("pop_element -> {:?}", elm));
3942 Ok(elm)
3943 }
3944
3945 fn extract_array_index(&self, elm: &[i32]) -> Option<usize> {
3946 if elm.len() >= 3 && elm[1] == self.ctx.ids.elm_array {
3947 let idx = elm[2];
3948 if idx >= 0 {
3949 return Some(idx as usize);
3950 }
3951 }
3952 None
3953 }
3954
3955 fn user_prop_decl(&self, prop_id: u16) -> Option<(i32, usize)> {
3956 let prop_idx = prop_id as usize;
3957 if let Some(pck) = self.scene_pck_cache.as_ref() {
3958 if prop_idx < pck.inc_props.len() {
3959 let decl = &pck.inc_props[prop_idx];
3960 return Some((decl.form, decl.size.max(0) as usize));
3961 }
3962 } else if prop_idx < self.stream.header.scn_prop_cnt.max(0) as usize {
3963 let off = (self.stream.header.scn_prop_list_ofs.max(0) as usize)
3971 .checked_add(prop_idx * 8)?;
3972 if off + 8 <= self.stream.chunk.len() {
3973 let form = i32::from_le_bytes(self.stream.chunk[off..off + 4].try_into().unwrap());
3974 let size =
3975 i32::from_le_bytes(self.stream.chunk[off + 4..off + 8].try_into().unwrap());
3976 return Some((form, size.max(0) as usize));
3977 }
3978 }
3979
3980 let local_idx = prop_idx.saturating_sub(
3981 self.scene_pck_cache
3982 .as_ref()
3983 .map(|pck| pck.inc_props.len())
3984 .unwrap_or(0),
3985 );
3986 let list_ofs = self.stream.header.scn_prop_list_ofs.max(0) as usize;
3987 let cnt = self.stream.header.scn_prop_cnt.max(0) as usize;
3988 if local_idx < cnt {
3989 let off = list_ofs.checked_add(local_idx * 8)?;
3990 if off + 8 <= self.stream.chunk.len() {
3991 let form = i32::from_le_bytes(self.stream.chunk[off..off + 4].try_into().unwrap());
3992 let size =
3993 i32::from_le_bytes(self.stream.chunk[off + 4..off + 8].try_into().unwrap());
3994 return Some((form, size.max(0) as usize));
3995 }
3996 }
3997 None
3998 }
3999
4000 fn default_user_prop_element(&self, prop_id: u16, _form: i32) -> Vec<i32> {
4001 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
4002 vec![head]
4003 }
4004
4005 fn default_user_prop_slot_element(&self, prop_id: u16, idx: usize) -> Vec<i32> {
4006 vec![
4007 constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32),
4008 self.ctx.ids.elm_array,
4009 idx as i32,
4010 ]
4011 }
4012
4013 fn default_user_prop_cell(&self, prop_id: u16) -> UserPropCell {
4014 let (form, size) = self
4015 .user_prop_decl(prop_id)
4016 .unwrap_or((self.cfg.fm_list, 0));
4017 let mut cell = UserPropCell::new(form, self.default_user_prop_element(prop_id, form));
4018 if form == self.cfg.fm_intlist {
4019 cell.int_list = vec![0; size];
4020 } else if form == self.cfg.fm_strlist {
4021 cell.str_list = vec![String::new(); size];
4022 } else if form == self.cfg.fm_list && size > 0 {
4023 let mut items = Vec::with_capacity(size);
4024 for i in 0..size {
4025 let mut slot = UserPropCell::new(
4026 self.cfg.fm_list,
4027 self.default_user_prop_slot_element(prop_id, i),
4028 );
4029 slot.form = self.cfg.fm_list;
4030 items.push(slot);
4031 }
4032 cell.list_items = items;
4033 }
4034 cell
4035 }
4036
4037 fn user_prop_cell_from_value(
4038 &self,
4039 rhs: Value,
4040 declared_form: i32,
4041 element: Vec<i32>,
4042 prop_id: Option<u16>,
4043 ) -> UserPropCell {
4044 let mut cell = UserPropCell::new(declared_form, element.clone());
4045 match rhs {
4046 Value::NamedArg { value, .. } => {
4047 return self.user_prop_cell_from_value(*value, declared_form, element, prop_id);
4048 }
4049 Value::Int(n) => {
4050 cell.form = self.cfg.fm_int;
4051 cell.int_value = n as i32;
4052 }
4053 Value::Str(s) => {
4054 cell.form = self.cfg.fm_str;
4055 cell.str_value = s;
4056 }
4057 Value::Element(e) => {
4058 cell.form = declared_form;
4059 cell.element = e;
4060 }
4061 Value::List(items) => {
4062 if declared_form == self.cfg.fm_intlist {
4063 cell.form = self.cfg.fm_intlist;
4064 cell.int_list = items
4065 .into_iter()
4066 .map(|item| item.as_i64().unwrap_or(0) as i32)
4067 .collect();
4068 } else if declared_form == self.cfg.fm_strlist {
4069 cell.form = self.cfg.fm_strlist;
4070 cell.str_list = items
4071 .into_iter()
4072 .map(|item| item.as_str().unwrap_or("").to_string())
4073 .collect();
4074 } else {
4075 cell.form = self.cfg.fm_list;
4076 let mut out = Vec::with_capacity(items.len());
4077 for (idx, item) in items.into_iter().enumerate() {
4078 let slot_element = if let Some(pid) = prop_id {
4079 self.default_user_prop_slot_element(pid, idx)
4080 } else {
4081 vec![]
4082 };
4083 out.push(self.user_prop_cell_from_value(
4084 item,
4085 self.cfg.fm_list,
4086 slot_element,
4087 prop_id,
4088 ));
4089 }
4090 cell.list_items = out;
4091 }
4092 }
4093 }
4094 cell
4095 }
4096
4097 fn consume_array_sub<'b>(&self, sub: &'b [i32]) -> Option<(usize, &'b [i32])> {
4098 if sub.len() >= 2 && self.call_array_marker(sub[0]) && sub[1] >= 0 {
4099 Some((sub[1] as usize, &sub[2..]))
4100 } else {
4101 None
4102 }
4103 }
4104
4105 fn intlist_bit_get(values: &[i32], bit: i32, index: usize) -> i32 {
4106 let word = values
4107 .get(index / (32 / bit as usize))
4108 .copied()
4109 .unwrap_or(0) as u32;
4110 let shift = (index % (32 / bit as usize)) * bit as usize;
4111 let mask = ((1u32 << bit) - 1) << shift;
4112 ((word & mask) >> shift) as i32
4113 }
4114
4115 fn intlist_dispatch_read(
4116 &mut self,
4117 values: &[i32],
4118 sub: &[i32],
4119 fallback_element: &[i32],
4120 ) -> Result<()> {
4121 use crate::runtime::forms::codes::{
4122 ELM_ARRAY, ELM_INTLIST_BIT, ELM_INTLIST_BIT16, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4,
4123 ELM_INTLIST_BIT8, ELM_INTLIST_GET_SIZE,
4124 };
4125 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
4126 &[][..]
4127 } else {
4128 sub
4129 };
4130 if sub.is_empty() {
4131 self.push_element(fallback_element.to_vec());
4132 return Ok(());
4133 }
4134 if let Some((idx, rest)) = self.consume_array_sub(sub) {
4135 if !rest.is_empty() {
4136 bail!("unsupported chained intlist array access {:?}", sub);
4137 }
4138 self.push_int(values.get(idx).copied().unwrap_or(0));
4139 return Ok(());
4140 }
4141 match sub[0] {
4142 ELM_INTLIST_GET_SIZE => {
4143 self.push_int(values.len() as i32);
4144 }
4145 ELM_INTLIST_BIT | ELM_INTLIST_BIT2 | ELM_INTLIST_BIT4 | ELM_INTLIST_BIT8
4146 | ELM_INTLIST_BIT16 => {
4147 let bit = match sub[0] {
4148 ELM_INTLIST_BIT => 1,
4149 ELM_INTLIST_BIT2 => 2,
4150 ELM_INTLIST_BIT4 => 4,
4151 ELM_INTLIST_BIT8 => 8,
4152 _ => 16,
4153 };
4154 if let Some((idx, rest)) = self.consume_array_sub(&sub[1..]) {
4155 if !rest.is_empty() {
4156 bail!("unsupported chained intlist bit access {:?}", sub);
4157 }
4158 self.push_int(Self::intlist_bit_get(values, bit, idx));
4159 } else {
4160 let mut chained = fallback_element.to_vec();
4161 chained.extend_from_slice(sub);
4162 self.push_element(chained);
4163 }
4164 }
4165 _ => self.push_element(fallback_element.to_vec()),
4166 }
4167 Ok(())
4168 }
4169
4170 fn push_user_prop_cell_result(
4171 &mut self,
4172 cell: &UserPropCell,
4173 sub: &[i32],
4174 full_elm: &[i32],
4175 ) -> Result<()> {
4176 use crate::runtime::forms::codes::{
4177 ELM_STRLIST_GET_SIZE, FM_INTLISTREF, FM_INTREF, FM_STRLISTREF, FM_STRREF,
4178 };
4179
4180 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
4181 &[][..]
4182 } else {
4183 sub
4184 };
4185 if cell.form == self.cfg.fm_int && sub.is_empty() {
4186 self.push_int(cell.int_value);
4187 return Ok(());
4188 }
4189 if cell.form == self.cfg.fm_str {
4190 if sub.is_empty() {
4191 self.push_str(cell.str_value.clone());
4192 } else {
4193 self.call_prop_eval_str_op(&cell.str_value, sub[0], &[], 0)?;
4194 }
4195 return Ok(());
4196 }
4197 if cell.form == self.cfg.fm_intlist {
4198 return self.intlist_dispatch_read(&cell.int_list, sub, &cell.element);
4199 }
4200 if cell.form == self.cfg.fm_strlist {
4201 if sub.is_empty() {
4202 self.push_element(cell.element.clone());
4203 } else if let Some((idx, rest)) = self.consume_array_sub(sub) {
4204 let cur = cell.str_list.get(idx).cloned().unwrap_or_default();
4205 if rest.is_empty() {
4206 self.push_str(cur);
4207 } else {
4208 self.call_prop_eval_str_op(&cur, rest[0], &[], 0)?;
4209 }
4210 } else if sub[0] == ELM_STRLIST_GET_SIZE {
4211 self.push_int(cell.str_list.len() as i32);
4212 } else {
4213 self.push_element(cell.element.clone());
4214 }
4215 return Ok(());
4216 }
4217 if matches!(
4218 cell.form,
4219 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF
4220 ) {
4221 self.push_element(cell.element.clone());
4222 return Ok(());
4223 }
4224 if let Some((idx, rest)) = self.consume_array_sub(sub) {
4225 let slot = if let Some(slot) = cell.list_items.get(idx) {
4226 slot.clone()
4227 } else {
4228 let mut tmp = UserPropCell::new(
4229 self.cfg.fm_list,
4230 self.default_user_prop_slot_element(elm_code::code(full_elm[0]), idx),
4231 );
4232 tmp.form = self.cfg.fm_list;
4233 tmp
4234 };
4235 return self.push_user_prop_cell_result(&slot, rest, full_elm);
4236 }
4237 self.push_element(cell.element.clone());
4238 Ok(())
4239 }
4240
4241 fn default_value_like(&self, v: &Value) -> Value {
4242 match v {
4243 Value::NamedArg { value, .. } => self.default_value_like(value),
4244 Value::Int(_) => Value::Int(0),
4245 Value::Str(_) => Value::Str(String::new()),
4246 Value::Element(_) => Value::Element(Vec::new()),
4247 Value::List(_) => Value::List(Vec::new()),
4248 }
4249 }
4250 fn call_scratch_from_args(&self, args: &[Value]) -> (Vec<i32>, Vec<String>) {
4251 let mut int_args = Self::blank_call_int_args();
4252 let mut str_args = Self::blank_call_str_args();
4253 let mut int_pos = 0usize;
4254 let mut str_pos = 0usize;
4255 for v in args {
4256 match v {
4257 Value::NamedArg { value, .. } => match value.as_ref() {
4258 Value::Int(n) => {
4259 if int_pos < int_args.len() {
4260 int_args[int_pos] = *n as i32;
4261 int_pos += 1;
4262 }
4263 }
4264 Value::Str(s) => {
4265 if str_pos < str_args.len() {
4266 str_args[str_pos] = s.clone();
4267 str_pos += 1;
4268 }
4269 }
4270 _ => {}
4271 },
4272 Value::Int(n) => {
4273 if int_pos < int_args.len() {
4274 int_args[int_pos] = *n as i32;
4275 int_pos += 1;
4276 }
4277 }
4278 Value::Str(s) => {
4279 if str_pos < str_args.len() {
4280 str_args[str_pos] = s.clone();
4281 str_pos += 1;
4282 }
4283 }
4284 _ => {}
4285 }
4286 }
4287 (int_args, str_args)
4288 }
4289
4290 fn call_array_marker(&self, code: i32) -> bool {
4291 let mapped = self.ctx.ids.elm_array;
4292 code == crate::runtime::forms::codes::ELM_ARRAY || (mapped >= 0 && code == mapped)
4293 }
4294
4295 fn resolve_call_frame_index(&self, idx: i32) -> Option<usize> {
4296 if idx < 0 {
4297 return None;
4298 }
4299 let depth = self.call_stack.len();
4300 let rev = idx as usize;
4301 if rev >= depth {
4302 return None;
4303 }
4304 Some(depth - 1 - rev)
4305 }
4306
4307 fn current_call_frame_index(&self) -> Option<usize> {
4308 if self.call_stack.is_empty() {
4309 None
4310 } else {
4311 Some(self.call_stack.len() - 1)
4312 }
4313 }
4314
4315 fn find_call_prop_index_in_frame(&self, frame_idx: usize, call_prop_id: i32) -> Option<usize> {
4316 if call_prop_id < 0 {
4322 return None;
4323 }
4324 let frame = self.call_stack.get(frame_idx)?;
4325 let idx = call_prop_id as usize;
4326 if idx < frame.user_props.len() {
4327 Some(idx)
4328 } else {
4329 None
4330 }
4331 }
4332
4333 fn call_prop_element(prop_id: i32) -> Vec<i32> {
4334 vec![constants::elm::create(constants::elm::OWNER_CALL_PROP, 0, prop_id)]
4335 }
4336
4337 fn call_prop_value_from_rhs(&self, rhs: &Value) -> (i32, CallPropValue) {
4338 match rhs {
4339 Value::NamedArg { value, .. } => self.call_prop_value_from_rhs(value),
4340 Value::Int(n) => (self.cfg.fm_int, CallPropValue::Int(*n as i32)),
4341 Value::Str(s) => (self.cfg.fm_str, CallPropValue::Str(s.clone())),
4342 Value::Element(e) => (self.cfg.fm_list, CallPropValue::Element(e.clone())),
4343 Value::List(_) => (self.cfg.fm_list, CallPropValue::Element(Vec::new())),
4344 }
4345 }
4346
4347 fn ensure_call_prop_index_for_assign(
4348 &mut self,
4349 frame_idx: usize,
4350 call_prop_id: i32,
4351 rhs: &Value,
4352 ) -> Result<usize> {
4353 if let Some(idx) = self.find_call_prop_index_in_frame(frame_idx, call_prop_id) {
4354 return Ok(idx);
4355 }
4356 if call_prop_id < 0 {
4357 bail!("negative CALL_PROP id {}", call_prop_id);
4358 }
4359
4360 let (form, value) = self.call_prop_value_from_rhs(rhs);
4365 let target_idx = call_prop_id as usize;
4366 let frame = self
4367 .call_stack
4368 .get_mut(frame_idx)
4369 .ok_or_else(|| anyhow!("call stack frame out of range"))?;
4370 while frame.user_props.len() <= target_idx {
4371 let idx = frame.user_props.len() as i32;
4372 frame.user_props.push(CallProp {
4373 prop_id: idx,
4374 form: self.cfg.fm_list,
4375 decl_size: 0,
4376 element: Self::call_prop_element(idx),
4377 value: CallPropValue::Element(Self::call_prop_element(idx)),
4378 });
4379 }
4380 let prop = frame
4381 .user_props
4382 .get_mut(target_idx)
4383 .ok_or_else(|| anyhow!("CALL_PROP slot allocation failed"))?;
4384 prop.form = form;
4385 prop.element = Self::call_prop_element(call_prop_id);
4386 prop.value = value;
4387 Ok(target_idx)
4388 }
4389
4390 fn is_direct_value_form(&self, form: i32) -> bool {
4391 form == self.cfg.fm_int
4392 || form == self.cfg.fm_str
4393 || form == self.cfg.fm_intlist
4394 || form == self.cfg.fm_strlist
4395 }
4396
4397 fn call_prop_effective_element(&self, prop: &CallProp) -> Vec<i32> {
4398 match &prop.value {
4399 CallPropValue::Element(e) if !e.is_empty() => e.clone(),
4400 _ => prop.element.clone(),
4401 }
4402 }
4403
4404 fn compose_call_prop_tail(&self, prop: &CallProp, sub: &[i32]) -> Option<Vec<i32>> {
4405 if sub.is_empty() || self.is_direct_value_form(prop.form) {
4406 return None;
4407 }
4408 let mut element = self.call_prop_effective_element(prop);
4409 if element.is_empty() {
4410 return None;
4411 }
4412 element.extend_from_slice(sub);
4413 Some(element)
4414 }
4415
4416 fn compose_user_prop_tail(
4417 &self,
4418 prop_id: u16,
4419 cell: &UserPropCell,
4420 sub: &[i32],
4421 ) -> Option<Vec<i32>> {
4422 if sub.is_empty() || self.is_direct_value_form(cell.form) {
4423 return None;
4424 }
4425 if let Some((idx, rest)) = self.consume_array_sub(sub) {
4426 let slot = cell.list_items.get(idx)?;
4427 let default_slot = self.default_user_prop_slot_element(prop_id, idx);
4428 if slot.element.is_empty() || slot.element == default_slot {
4429 return None;
4430 }
4431 if rest.is_empty() || self.is_direct_value_form(slot.form) {
4432 return Some(slot.element.clone());
4433 }
4434 let mut element = slot.element.clone();
4435 element.extend_from_slice(rest);
4436 return Some(element);
4437 }
4438
4439 let default_root = self.default_user_prop_element(prop_id, cell.form);
4440 if cell.element.is_empty() || cell.element == default_root {
4441 return None;
4442 }
4443 let mut element = cell.element.clone();
4444 element.extend_from_slice(sub);
4445 Some(element)
4446 }
4447
4448 fn push_call_prop_result(
4449 &mut self,
4450 prop: &CallProp,
4451 sub: &[i32],
4452 full_elm: &[i32],
4453 ) -> Result<()> {
4454 use crate::runtime::forms::codes::{
4455 ELM_ARRAY, ELM_STRLIST_GET_SIZE, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR,
4456 FM_STRLIST, FM_STRLISTREF, FM_STRREF,
4457 };
4458
4459 let sub = if sub.len() == 1 && self.call_array_marker(sub[0]) {
4460 &[][..]
4461 } else {
4462 sub
4463 };
4464 match prop.form {
4465 FM_INT if sub.is_empty() => {
4466 if let CallPropValue::Int(n) = &prop.value {
4467 self.push_int(*n);
4468 } else {
4469 bail!("CALL_PROP int storage mismatch for {:?}", full_elm);
4470 }
4471 }
4472 FM_STR if sub.is_empty() => {
4473 if let CallPropValue::Str(s) = &prop.value {
4474 self.push_str(s.clone());
4475 } else {
4476 bail!("CALL_PROP str storage mismatch for {:?}", full_elm);
4477 }
4478 }
4479 FM_STR => {
4480 if let CallPropValue::Str(s) = &prop.value {
4481 self.call_prop_eval_str_op(s, sub[0], &[], 0)?;
4482 } else {
4483 bail!("CALL_PROP str storage mismatch for {:?}", full_elm);
4484 }
4485 }
4486 FM_INTLIST => {
4487 if let CallPropValue::IntList(v) = &prop.value {
4488 self.intlist_dispatch_read(v, sub, &prop.element)?;
4489 } else {
4490 bail!("CALL_PROP intlist storage mismatch for {:?}", full_elm);
4491 }
4492 }
4493 FM_STRLIST => {
4494 if let CallPropValue::StrList(v) = &prop.value {
4495 if sub.is_empty() {
4496 self.push_element(prop.element.clone());
4497 } else if let Some((idx, rest)) = self.consume_array_sub(sub) {
4498 let current = v.get(idx).cloned().unwrap_or_default();
4499 if rest.is_empty() {
4500 self.push_str(current);
4501 } else {
4502 self.call_prop_eval_str_op(¤t, rest[0], &[], 0)?;
4503 }
4504 } else if sub[0] == ELM_STRLIST_GET_SIZE {
4505 self.push_int(v.len() as i32);
4506 } else {
4507 self.push_element(prop.element.clone());
4508 }
4509 } else {
4510 bail!("CALL_PROP strlist storage mismatch for {:?}", full_elm);
4511 }
4512 }
4513 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF => {
4514 self.push_element(prop.element.clone());
4515 }
4516 _ if !sub.is_empty() => {
4517 self.push_element(prop.element.clone());
4518 }
4519 _ => {
4520 self.push_element(prop.element.clone());
4521 }
4522 }
4523 Ok(())
4524 }
4525
4526 fn str_display_width_char(ch: char) -> usize {
4527 if ch.is_ascii() {
4528 1
4529 } else {
4530 2
4531 }
4532 }
4533
4534 fn str_display_width(s: &str) -> usize {
4535 s.chars().map(Self::str_display_width_char).sum()
4536 }
4537
4538 fn str_left_len(s: &str, limit: usize) -> String {
4539 let mut width = 0usize;
4540 let mut out = String::new();
4541 for ch in s.chars() {
4542 let w = Self::str_display_width_char(ch);
4543 if width + w > limit {
4544 break;
4545 }
4546 width += w;
4547 out.push(ch);
4548 }
4549 out
4550 }
4551
4552 fn str_right_len(s: &str, limit: usize) -> String {
4553 let mut width = 0usize;
4554 let mut out: Vec<char> = Vec::new();
4555 for ch in s.chars().rev() {
4556 let w = Self::str_display_width_char(ch);
4557 if width + w > limit {
4558 break;
4559 }
4560 width += w;
4561 out.push(ch);
4562 }
4563 out.into_iter().rev().collect()
4564 }
4565
4566 fn str_mid_len(s: &str, start_width: usize, len_width: Option<usize>) -> String {
4567 let mut width = 0usize;
4568 let mut out = String::new();
4569 for ch in s.chars() {
4570 let ch_width = Self::str_display_width_char(ch);
4571 if width >= start_width {
4572 if let Some(limit) = len_width {
4573 if Self::str_display_width(&out) + ch_width > limit {
4574 break;
4575 }
4576 }
4577 out.push(ch);
4578 }
4579 width += ch_width;
4580 }
4581 out
4582 }
4583
4584 fn call_prop_eval_str_op(
4585 &mut self,
4586 current: &str,
4587 op: i32,
4588 params: &[Value],
4589 al_id: i32,
4590 ) -> Result<()> {
4591 use crate::runtime::forms::codes::str_op;
4592 match op {
4593 str_op::UPPER => {
4594 self.push_str(current.chars().map(|c| c.to_ascii_uppercase()).collect())
4595 }
4596 str_op::LOWER => {
4597 self.push_str(current.chars().map(|c| c.to_ascii_lowercase()).collect())
4598 }
4599 str_op::CNT => self.push_int(current.chars().count() as i32),
4600 str_op::LEN => self.push_int(Self::str_display_width(current) as i32),
4601 str_op::LEFT => {
4602 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4603 self.push_str(current.chars().take(len).collect());
4604 }
4605 str_op::LEFT_LEN => {
4606 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4607 self.push_str(Self::str_left_len(current, len));
4608 }
4609 str_op::RIGHT => {
4610 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4611 let total = current.chars().count();
4612 let start = total.saturating_sub(len);
4613 self.push_str(current.chars().skip(start).collect());
4614 }
4615 str_op::RIGHT_LEN => {
4616 let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4617 self.push_str(Self::str_right_len(current, len));
4618 }
4619 str_op::MID => {
4620 let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4621 if al_id == 0 || params.len() <= 1 {
4622 self.push_str(current.chars().skip(start).collect());
4623 } else {
4624 let len = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4625 self.push_str(current.chars().skip(start).take(len).collect());
4626 }
4627 }
4628 str_op::MID_LEN => {
4629 let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4630 let len = if al_id == 0 || params.len() <= 1 {
4631 None
4632 } else {
4633 Some(params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize)
4634 };
4635 self.push_str(Self::str_mid_len(current, start, len));
4636 }
4637 str_op::SEARCH => {
4638 let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
4639 let hay = current.to_ascii_lowercase();
4640 let needle = needle.to_ascii_lowercase();
4641 self.push_int(hay.find(&needle).map(|v| v as i32).unwrap_or(-1));
4642 }
4643 str_op::SEARCH_LAST => {
4644 let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
4645 let hay = current.to_ascii_lowercase();
4646 let needle = needle.to_ascii_lowercase();
4647 self.push_int(hay.rfind(&needle).map(|v| v as i32).unwrap_or(-1));
4648 }
4649 str_op::GET_CODE => {
4650 let pos = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4651 let code = current.chars().nth(pos).map(|c| c as i32).unwrap_or(-1);
4652 self.push_int(code);
4653 }
4654 str_op::TONUM => self.push_int(current.parse::<i32>().unwrap_or(0)),
4655 _ => bail!("unsupported CALL_PROP string op {}", op),
4656 }
4657 Ok(())
4658 }
4659
4660 fn assign_call_prop_result(prop: &mut CallProp, sub: &[i32], rhs: Value) -> Result<()> {
4661 use crate::runtime::forms::codes::{
4662 ELM_ARRAY, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST,
4663 FM_STRLISTREF, FM_STRREF,
4664 };
4665
4666 match prop.form {
4667 FM_INT if sub.is_empty() => match rhs {
4668 Value::Int(n) => {
4669 prop.value = CallPropValue::Int(n as i32);
4670 }
4671 _ => bail!("unsupported CALL_PROP int assign sub={:?}", sub),
4672 },
4673 FM_STR if sub.is_empty() => match rhs {
4674 Value::Str(s) => {
4675 prop.value = CallPropValue::Str(s);
4676 }
4677 _ => bail!("unsupported CALL_PROP str assign sub={:?}", sub),
4678 },
4679 FM_INTLIST if sub.len() >= 2 && sub[0] == ELM_ARRAY => match rhs {
4680 Value::Int(n) => {
4681 let idx = sub[1].max(0) as usize;
4682 let mut dst = match std::mem::replace(
4683 &mut prop.value,
4684 CallPropValue::IntList(Vec::new()),
4685 ) {
4686 CallPropValue::IntList(v) => v,
4687 other => {
4688 prop.value = other;
4689 bail!("CALL_PROP intlist storage mismatch");
4690 }
4691 };
4692 if dst.len() <= idx {
4693 dst.resize(idx + 1, 0);
4694 }
4695 dst[idx] = n as i32;
4696 prop.value = CallPropValue::IntList(dst);
4697 }
4698 _ => bail!("unsupported CALL_PROP intlist assign sub={:?}", sub),
4699 },
4700 FM_STRLIST if sub.len() >= 2 && sub[0] == ELM_ARRAY => match rhs {
4701 Value::Str(s) => {
4702 let idx = sub[1].max(0) as usize;
4703 let mut dst = match std::mem::replace(
4704 &mut prop.value,
4705 CallPropValue::StrList(Vec::new()),
4706 ) {
4707 CallPropValue::StrList(v) => v,
4708 other => {
4709 prop.value = other;
4710 bail!("CALL_PROP strlist storage mismatch");
4711 }
4712 };
4713 if dst.len() <= idx {
4714 dst.resize_with(idx + 1, String::new);
4715 }
4716 dst[idx] = s;
4717 prop.value = CallPropValue::StrList(dst);
4718 }
4719 _ => bail!("unsupported CALL_PROP strlist assign sub={:?}", sub),
4720 },
4721 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF => match rhs {
4722 Value::Element(e) => {
4723 prop.element = e.clone();
4724 prop.value = CallPropValue::Element(e);
4725 }
4726 _ => bail!("unsupported CALL_PROP ref assign sub={:?}", sub),
4727 },
4728 _ => bail!(
4729 "unsupported call prop assign form={} sub={:?}",
4730 prop.form,
4731 sub
4732 ),
4733 }
4734 Ok(())
4735 }
4736
4737 fn exec_user_prop_list_init_command(
4738 &mut self,
4739 prop_id: u16,
4740 sub: &[i32],
4741 ret_form: i32,
4742 ) -> Result<bool> {
4743 use crate::runtime::forms::codes::{ELM_INTLIST_INIT, ELM_STRLIST_INIT};
4744
4745 if sub.len() != 1 {
4746 return Ok(false);
4747 }
4748 let (form, size) = self
4749 .user_prop_decl(prop_id)
4750 .unwrap_or((self.cfg.fm_list, 0));
4751 if form == self.cfg.fm_intlist && sub[0] == ELM_INTLIST_INIT {
4752 let mut cell = self
4753 .user_props
4754 .remove(&prop_id)
4755 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
4756 cell.form = form;
4757 cell.element = self.default_user_prop_element(prop_id, form);
4758 cell.int_list.clear();
4759 cell.int_list.resize(size, 0);
4760 self.user_props.insert(prop_id, cell);
4761 self.push_default_for_ret(ret_form);
4762 return Ok(true);
4763 }
4764 if form == self.cfg.fm_strlist && sub[0] == ELM_STRLIST_INIT {
4765 let mut cell = self
4766 .user_props
4767 .remove(&prop_id)
4768 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
4769 cell.form = form;
4770 cell.element = self.default_user_prop_element(prop_id, form);
4771 cell.str_list.clear();
4772 cell.str_list.resize_with(size, String::new);
4773 self.user_props.insert(prop_id, cell);
4774 self.push_default_for_ret(ret_form);
4775 return Ok(true);
4776 }
4777 Ok(false)
4778 }
4779
4780 fn exec_call_prop_command(
4781 &mut self,
4782 frame_idx: usize,
4783 prop_idx: usize,
4784 sub: &[i32],
4785 al_id: i32,
4786 ret_form: i32,
4787 args: &[Value],
4788 ) -> Result<()> {
4789 use crate::runtime::forms::codes::{
4790 ELM_ARRAY, ELM_INTLIST_BIT, ELM_INTLIST_BIT16, ELM_INTLIST_BIT2, ELM_INTLIST_BIT4,
4791 ELM_INTLIST_BIT8, ELM_INTLIST_CLEAR, ELM_INTLIST_GET_SIZE, ELM_INTLIST_INIT,
4792 ELM_INTLIST_RESIZE, ELM_INTLIST_SETS, ELM_STRLIST_GET_SIZE, ELM_STRLIST_INIT,
4793 ELM_STRLIST_RESIZE, FM_INT, FM_INTLIST, FM_INTLISTREF, FM_INTREF, FM_STR, FM_STRLIST,
4794 FM_STRLISTREF, FM_STRREF,
4795 };
4796
4797 let (form, decl_size, mut value, mut element) = {
4798 let prop = self
4799 .call_stack
4800 .get(frame_idx)
4801 .and_then(|f| f.user_props.get(prop_idx))
4802 .ok_or_else(|| anyhow!("call prop frame/index out of range"))?;
4803 (prop.form, prop.decl_size, prop.value.clone(), prop.element.clone())
4804 };
4805
4806 let mut write_back = false;
4807
4808 if !sub.is_empty() && !self.is_direct_value_form(form) {
4809 let mut composed = match &value {
4810 CallPropValue::Element(e) if !e.is_empty() => e.clone(),
4811 _ => element.clone(),
4812 };
4813 if !composed.is_empty() {
4814 composed.extend_from_slice(sub);
4815 let mut owned_args = args.to_vec();
4816 self.exec_command(composed, al_id, ret_form, &mut owned_args)?;
4817 return Ok(());
4818 }
4819 }
4820
4821 match form {
4822 FM_INT => {
4823 if sub.is_empty() {
4824 if al_id == 0 {
4825 match &value {
4826 CallPropValue::Int(n) => self.push_int(*n),
4827 _ => bail!("CALL_PROP int storage mismatch"),
4828 }
4829 } else {
4830 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
4831 value = CallPropValue::Int(rhs);
4832 write_back = true;
4833 self.push_default_for_ret(ret_form);
4834 }
4835 } else {
4836 self.push_element(element.clone());
4837 }
4838 }
4839 FM_STR => {
4840 let current = match &value {
4841 CallPropValue::Str(s) => s.clone(),
4842 _ => bail!("CALL_PROP str storage mismatch"),
4843 };
4844 if sub.is_empty() {
4845 if al_id == 0 {
4846 self.push_str(current);
4847 } else {
4848 let rhs = args
4849 .first()
4850 .and_then(|v| v.as_str())
4851 .unwrap_or("")
4852 .to_string();
4853 value = CallPropValue::Str(rhs);
4854 write_back = true;
4855 self.push_default_for_ret(ret_form);
4856 }
4857 } else {
4858 self.call_prop_eval_str_op(¤t, sub[0], args, al_id)?;
4859 }
4860 }
4861 FM_INTLIST => {
4862 let mut list = match value {
4863 CallPropValue::IntList(v) => v,
4864 _ => bail!("CALL_PROP intlist storage mismatch"),
4865 };
4866 if sub.is_empty() {
4867 self.push_element(element.clone());
4868 } else if sub.len() >= 2 && sub[0] == ELM_ARRAY {
4869 let idx = sub[1].max(0) as usize;
4870 if al_id == 0 {
4871 self.push_int(list.get(idx).copied().unwrap_or(0));
4872 } else {
4873 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
4874 if list.len() <= idx {
4875 list.resize(idx + 1, 0);
4876 }
4877 list[idx] = rhs;
4878 write_back = true;
4879 self.push_default_for_ret(ret_form);
4880 }
4881 } else {
4882 match sub[0] {
4883 ELM_INTLIST_BIT | ELM_INTLIST_BIT2 | ELM_INTLIST_BIT4
4884 | ELM_INTLIST_BIT8 | ELM_INTLIST_BIT16 => {
4885 self.push_element(element.clone());
4886 }
4887 ELM_INTLIST_INIT => {
4888 list.clear();
4889 list.resize(decl_size, 0);
4890 write_back = true;
4891 self.push_default_for_ret(ret_form);
4892 }
4893 ELM_INTLIST_RESIZE => {
4894 let new_len =
4895 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4896 list.resize(new_len, 0);
4897 write_back = true;
4898 self.push_default_for_ret(ret_form);
4899 }
4900 ELM_INTLIST_GET_SIZE => self.push_int(list.len() as i32),
4901 ELM_INTLIST_CLEAR => {
4902 let start =
4903 args.get(0).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4904 let end =
4905 args.get(1).and_then(|v| v.as_i64()).unwrap_or(-1).max(-1) as isize;
4906 let clear_value = if al_id == 0 {
4907 0
4908 } else {
4909 args.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32
4910 };
4911 if !list.is_empty() && end >= 0 {
4912 let end = usize::min(end as usize, list.len().saturating_sub(1));
4913 for i in start..=end {
4914 if i < list.len() {
4915 list[i] = clear_value;
4916 }
4917 }
4918 }
4919 write_back = true;
4920 self.push_default_for_ret(ret_form);
4921 }
4922 ELM_INTLIST_SETS => {
4923 let start =
4924 args.get(0).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4925 for (off, v) in args.iter().skip(1).enumerate() {
4926 let idx = start + off;
4927 if list.len() <= idx {
4928 list.resize(idx + 1, 0);
4929 }
4930 list[idx] = v.as_i64().unwrap_or(0) as i32;
4931 }
4932 write_back = true;
4933 self.push_default_for_ret(ret_form);
4934 }
4935 _ => bail!("unsupported CALL_PROP intlist op {:?}", sub),
4936 }
4937 }
4938 value = CallPropValue::IntList(list);
4939 }
4940 FM_STRLIST => {
4941 let mut list = match value {
4942 CallPropValue::StrList(v) => v,
4943 _ => bail!("CALL_PROP strlist storage mismatch"),
4944 };
4945 if sub.is_empty() {
4946 self.push_element(element.clone());
4947 } else if sub.len() >= 2 && sub[0] == ELM_ARRAY {
4948 let idx = sub[1].max(0) as usize;
4949 if list.len() <= idx {
4950 list.resize_with(idx + 1, String::new);
4951 }
4952 if sub.len() == 2 {
4953 if al_id == 0 {
4954 self.push_str(list[idx].clone());
4955 } else {
4956 let rhs = args
4957 .first()
4958 .and_then(|v| v.as_str())
4959 .unwrap_or("")
4960 .to_string();
4961 list[idx] = rhs;
4962 write_back = true;
4963 self.push_default_for_ret(ret_form);
4964 }
4965 } else {
4966 let current = list[idx].clone();
4967 self.call_prop_eval_str_op(¤t, sub[2], args, al_id)?;
4968 }
4969 } else {
4970 match sub[0] {
4971 ELM_STRLIST_INIT => {
4972 list.clear();
4973 list.resize_with(decl_size, String::new);
4974 write_back = true;
4975 self.push_default_for_ret(ret_form);
4976 }
4977 ELM_STRLIST_RESIZE => {
4978 let new_len =
4979 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
4980 list.resize_with(new_len, String::new);
4981 write_back = true;
4982 self.push_default_for_ret(ret_form);
4983 }
4984 ELM_STRLIST_GET_SIZE => self.push_int(list.len() as i32),
4985 _ => bail!("unsupported CALL_PROP strlist op {:?}", sub),
4986 }
4987 }
4988 value = CallPropValue::StrList(list);
4989 }
4990 FM_INTREF | FM_STRREF | FM_INTLISTREF | FM_STRLISTREF => {
4991 if sub.is_empty() {
4992 if al_id == 0 {
4993 if let CallPropValue::Element(e) = &value {
4994 if e.is_empty() {
4995 self.push_element(element.clone());
4996 } else {
4997 self.push_element(e.clone());
4998 }
4999 } else {
5000 self.push_element(element.clone());
5001 }
5002 } else {
5003 let rhs = args.first().cloned().unwrap_or(Value::Element(Vec::new()));
5004 match rhs {
5005 Value::Element(e) => {
5006 element = e.clone();
5007 value = CallPropValue::Element(e);
5008 write_back = true;
5009 }
5010 _ => bail!("CALL_PROP ref assign requires element"),
5011 }
5012 self.push_default_for_ret(ret_form);
5013 }
5014 } else if let CallPropValue::Element(e) = &value {
5015 if e.is_empty() {
5016 self.push_element(element.clone());
5017 } else {
5018 self.push_element(e.clone());
5019 }
5020 } else {
5021 self.push_element(element.clone());
5022 }
5023 }
5024 _ => {
5025 if !sub.is_empty() || al_id == 0 {
5026 self.push_element(element.clone());
5027 } else {
5028 bail!("unsupported CALL_PROP form {}", form);
5029 }
5030 }
5031 }
5032
5033 if write_back {
5034 let prop = self
5035 .call_stack
5036 .get_mut(frame_idx)
5037 .and_then(|f| f.user_props.get_mut(prop_idx))
5038 .ok_or_else(|| anyhow!("call prop frame/index out of range"))?;
5039 prop.value = value;
5040 prop.element = element;
5041 }
5042 Ok(())
5043 }
5044
5045 fn exec_call_property(&mut self, elm: &[i32]) -> Result<bool> {
5046 self.vm_trace(None, format!("exec_call_property elm={:?}", elm));
5047 use crate::runtime::forms::codes::{
5048 ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, ELM_INTLIST_GET_SIZE,
5049 ELM_STRLIST_GET_SIZE, FM_CALL, FM_CALLLIST,
5050 };
5051
5052 if elm.is_empty() {
5053 return Ok(false);
5054 }
5055 let head = elm[0];
5056 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
5057 return Ok(false);
5058 }
5059
5060 let current_idx = self
5061 .current_call_frame_index()
5062 .ok_or_else(|| anyhow!("call stack underflow"))?;
5063
5064 let tail: &[i32] = if head == FM_CALLLIST {
5065 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
5066 bail!("malformed CALLLIST access: {:?}", elm);
5067 }
5068 self.resolve_call_frame_index(elm[2])
5069 .ok_or_else(|| anyhow!("CALLLIST index out of range: {}", elm[2]))?;
5070 &elm[3..]
5071 } else {
5072 &elm[1..]
5073 };
5074
5075 if tail.is_empty() {
5076 self.push_element(elm.to_vec());
5077 return Ok(true);
5078 }
5079
5080 match tail[0] {
5081 ELM_CALL_L => {
5082 let sub = &tail[1..];
5083 if sub.is_empty() {
5084 self.push_element(elm.to_vec());
5085 } else if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5086 let idx = sub[1].max(0) as usize;
5087 let v = self.call_stack[current_idx]
5088 .int_args
5089 .get(idx)
5090 .copied()
5091 .unwrap_or(0);
5092 self.push_int(v);
5093 } else if sub[0] == ELM_INTLIST_GET_SIZE {
5094 self.push_int(self.call_stack[current_idx].int_args.len() as i32);
5095 } else {
5096 self.push_element(elm[..elm.len() - sub.len()].to_vec());
5097 }
5098 return Ok(true);
5099 }
5100 ELM_CALL_K => {
5101 let sub = &tail[1..];
5102 if sub.is_empty() {
5103 self.push_element(elm.to_vec());
5104 } else if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5105 let idx = sub[1].max(0) as usize;
5106 let v = self.call_stack[current_idx]
5107 .str_args
5108 .get(idx)
5109 .cloned()
5110 .unwrap_or_default();
5111 if sub.len() == 2 {
5112 self.push_str(v);
5113 } else {
5114 self.call_prop_eval_str_op(&v, sub[2], &[], 0)?;
5115 }
5116 } else if sub[0] == ELM_STRLIST_GET_SIZE {
5117 self.push_int(self.call_stack[current_idx].str_args.len() as i32);
5118 } else {
5119 self.push_element(elm[..elm.len() - sub.len()].to_vec());
5120 }
5121 return Ok(true);
5122 }
5123 _ => {}
5124 }
5125
5126 if elm_code::owner(tail[0]) != elm_code::ELM_OWNER_CALL_PROP {
5127 bail!("invalid CALL property owner for {:?}", elm);
5128 }
5129
5130 let call_prop_id = elm_code::code(tail[0]) as i32;
5131 let prop_idx = self
5132 .find_call_prop_index_in_frame(current_idx, call_prop_id)
5133 .ok_or_else(|| anyhow!("missing CALL_PROP id={} for {:?}", call_prop_id, elm))?;
5134 let prop = self.call_stack[current_idx].user_props[prop_idx].clone();
5135 let sub = &tail[1..];
5136 if let Some(composed) = self.compose_call_prop_tail(&prop, sub) {
5137 self.exec_property(composed)?;
5138 return Ok(true);
5139 }
5140 self.push_call_prop_result(&prop, sub, elm)?;
5141 Ok(true)
5142 }
5143
5144 fn exec_call_assign(&mut self, elm: &[i32], al_id: i32, rhs: Value) -> Result<bool> {
5145 use crate::runtime::forms::codes::{
5146 ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, FM_CALL, FM_CALLLIST,
5147 };
5148
5149 if elm.is_empty() {
5150 return Ok(false);
5151 }
5152 let head = elm[0];
5153 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
5154 return Ok(false);
5155 }
5156
5157 let current_idx = match self.current_call_frame_index() {
5158 Some(v) => v,
5159 None => return Ok(true),
5160 };
5161
5162 let tail: &[i32] = if head == FM_CALLLIST {
5163 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
5164 bail!("malformed CALLLIST assign: {:?}", elm);
5165 }
5166 self.resolve_call_frame_index(elm[2])
5167 .ok_or_else(|| anyhow!("CALLLIST index out of range: {}", elm[2]))?;
5168 &elm[3..]
5169 } else {
5170 &elm[1..]
5171 };
5172
5173 if tail.is_empty() {
5174 return Ok(true);
5175 }
5176
5177 match tail[0] {
5178 ELM_CALL_L => {
5179 let sub = &tail[1..];
5180 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5181 let idx = sub[1].max(0) as usize;
5182 if let Value::Int(n) = rhs {
5183 let frame = &mut self.call_stack[current_idx];
5184 if frame.int_args.len() <= idx {
5185 frame.int_args.resize(idx + 1, 0);
5186 }
5187 frame.int_args[idx] = n as i32;
5188 }
5189 }
5190 return Ok(true);
5191 }
5192 ELM_CALL_K => {
5193 let sub = &tail[1..];
5194 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5195 let idx = sub[1].max(0) as usize;
5196 if let Value::Str(s) = rhs {
5197 let frame = &mut self.call_stack[current_idx];
5198 if frame.str_args.len() <= idx {
5199 frame.str_args.resize_with(idx + 1, String::new);
5200 }
5201 frame.str_args[idx] = s;
5202 }
5203 }
5204 return Ok(true);
5205 }
5206 _ => {}
5207 }
5208
5209 if elm_code::owner(tail[0]) != elm_code::ELM_OWNER_CALL_PROP {
5210 bail!("invalid CALL assign owner for {:?}", elm);
5211 }
5212 let call_prop_id = elm_code::code(tail[0]) as i32;
5213 let sub = &tail[1..];
5214 let prop_idx = self.ensure_call_prop_index_for_assign(current_idx, call_prop_id, &rhs)?;
5215 let prop_for_compose = self.call_stack[current_idx].user_props[prop_idx].clone();
5216 if let Some(composed) = self.compose_call_prop_tail(&prop_for_compose, sub) {
5217 self.exec_assign(composed, al_id, rhs)?;
5218 return Ok(true);
5219 }
5220 let frame = &mut self.call_stack[current_idx];
5221 let prop = frame
5222 .user_props
5223 .get_mut(prop_idx)
5224 .ok_or_else(|| anyhow!("missing CALL_PROP slot assign id={}", call_prop_id))?;
5225 Self::assign_call_prop_result(prop, sub, rhs)?;
5226 Ok(true)
5227 }
5228
5229 fn exec_call_command(
5230 &mut self,
5231 elm: &[i32],
5232 al_id: i32,
5233 ret_form: i32,
5234 args: &[Value],
5235 ) -> Result<bool> {
5236 use crate::runtime::forms::codes::{
5237 ELM_ARRAY, ELM_CALL_K, ELM_CALL_L, ELM_GLOBAL_CUR_CALL, ELM_INTLIST_CLEAR, ELM_INTLIST_GET_SIZE,
5238 ELM_INTLIST_INIT, ELM_INTLIST_RESIZE, ELM_INTLIST_SETS, ELM_STRLIST_GET_SIZE,
5239 ELM_STRLIST_INIT, ELM_STRLIST_RESIZE, FM_CALL, FM_CALLLIST,
5240 };
5241
5242 if elm.is_empty() {
5243 return Ok(false);
5244 }
5245 let head = elm[0];
5246 if head != FM_CALL && head != FM_CALLLIST && head != ELM_GLOBAL_CUR_CALL {
5247 return Ok(false);
5248 }
5249
5250 let current_idx = match self.current_call_frame_index() {
5251 Some(v) => v,
5252 None => {
5253 self.push_default_for_ret(ret_form);
5254 return Ok(true);
5255 }
5256 };
5257
5258 let tail: &[i32] = if head == FM_CALLLIST {
5259 if elm.len() < 3 || !self.call_array_marker(elm[1]) {
5260 self.push_default_for_ret(ret_form);
5261 return Ok(true);
5262 }
5263 let Some(_selected_idx) = self.resolve_call_frame_index(elm[2]) else {
5264 self.push_default_for_ret(ret_form);
5265 return Ok(true);
5266 };
5267 &elm[3..]
5268 } else {
5269 &elm[1..]
5270 };
5271
5272 if tail.is_empty() {
5273 self.push_default_for_ret(ret_form);
5274 return Ok(true);
5275 }
5276
5277 match tail[0] {
5278 ELM_CALL_L => {
5279 let sub = &tail[1..];
5280 if sub.is_empty() {
5281 self.push_default_for_ret(ret_form);
5282 return Ok(true);
5283 }
5284 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5285 let idx = sub[1].max(0) as usize;
5286 if al_id == 1 {
5287 let rhs = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
5288 let frame = &mut self.call_stack[current_idx];
5289 if frame.int_args.len() <= idx {
5290 frame.int_args.resize(idx + 1, 0);
5291 }
5292 frame.int_args[idx] = rhs;
5293 self.push_default_for_ret(ret_form);
5294 } else {
5295 let v = self.call_stack[current_idx]
5296 .int_args
5297 .get(idx)
5298 .copied()
5299 .unwrap_or(0);
5300 self.push_int(v);
5301 }
5302 return Ok(true);
5303 }
5304 match sub[0] {
5305 ELM_INTLIST_INIT => {
5306 self.call_stack[current_idx].int_args = Self::blank_call_int_args();
5307 }
5308 ELM_INTLIST_RESIZE => {
5309 let new_len =
5310 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5311 self.call_stack[current_idx].int_args.resize(new_len, 0);
5312 }
5313 ELM_INTLIST_GET_SIZE => {
5314 self.push_int(self.call_stack[current_idx].int_args.len() as i32);
5315 }
5316 ELM_INTLIST_CLEAR => {
5317 let start =
5318 args.get(0).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5319 let end =
5320 args.get(1).and_then(|v| v.as_i64()).unwrap_or(-1).max(-1) as isize;
5321 let value = if al_id == 0 {
5322 0
5323 } else {
5324 args.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32
5325 };
5326 let frame = &mut self.call_stack[current_idx];
5327 if !frame.int_args.is_empty() && end >= 0 {
5328 let end =
5329 usize::min(end as usize, frame.int_args.len().saturating_sub(1));
5330 for i in start..=end {
5331 if i < frame.int_args.len() {
5332 frame.int_args[i] = value;
5333 }
5334 }
5335 }
5336 }
5337 ELM_INTLIST_SETS => {
5338 let start =
5339 args.get(0).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5340 let frame = &mut self.call_stack[current_idx];
5341 for (off, v) in args.iter().skip(1).enumerate() {
5342 let idx = start + off;
5343 if frame.int_args.len() <= idx {
5344 frame.int_args.resize(idx + 1, 0);
5345 }
5346 frame.int_args[idx] = v.as_i64().unwrap_or(0) as i32;
5347 }
5348 }
5349 _ => self.push_default_for_ret(ret_form),
5350 }
5351 return Ok(true);
5352 }
5353 ELM_CALL_K => {
5354 let sub = &tail[1..];
5355 if sub.is_empty() {
5356 self.push_default_for_ret(ret_form);
5357 return Ok(true);
5358 }
5359 if sub.len() >= 2 && self.call_array_marker(sub[0]) {
5360 let idx = sub[1].max(0) as usize;
5361 if sub.len() == 2 {
5362 if al_id == 1 {
5363 let rhs = args
5364 .first()
5365 .and_then(|v| v.as_str())
5366 .unwrap_or("")
5367 .to_string();
5368 let frame = &mut self.call_stack[current_idx];
5369 if frame.str_args.len() <= idx {
5370 frame.str_args.resize_with(idx + 1, String::new);
5371 }
5372 frame.str_args[idx] = rhs;
5373 self.push_default_for_ret(ret_form);
5374 } else {
5375 let v = self.call_stack[current_idx]
5376 .str_args
5377 .get(idx)
5378 .cloned()
5379 .unwrap_or_default();
5380 self.push_str(v);
5381 }
5382 } else {
5383 let v = self.call_stack[current_idx]
5384 .str_args
5385 .get(idx)
5386 .cloned()
5387 .unwrap_or_default();
5388 self.call_prop_eval_str_op(&v, sub[2], args, al_id)?;
5389 }
5390 return Ok(true);
5391 }
5392 match sub[0] {
5393 ELM_STRLIST_INIT => {
5394 self.call_stack[current_idx].str_args = Self::blank_call_str_args();
5395 }
5396 ELM_STRLIST_RESIZE => {
5397 let new_len =
5398 args.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
5399 self.call_stack[current_idx]
5400 .str_args
5401 .resize_with(new_len, String::new);
5402 }
5403 ELM_STRLIST_GET_SIZE => {
5404 self.push_int(self.call_stack[current_idx].str_args.len() as i32);
5405 }
5406 _ => self.push_default_for_ret(ret_form),
5407 }
5408 return Ok(true);
5409 }
5410 _ => {
5411 if elm_code::owner(tail[0]) == elm_code::ELM_OWNER_CALL_PROP {
5412 let call_prop_id = elm_code::code(tail[0]) as i32;
5413 let prop_idx = self
5414 .find_call_prop_index_in_frame(current_idx, call_prop_id)
5415 .ok_or_else(|| {
5416 anyhow!(
5417 "missing CALL_PROP command id={} for {:?}",
5418 call_prop_id,
5419 elm
5420 )
5421 })?;
5422 self.exec_call_prop_command(
5423 current_idx,
5424 prop_idx,
5425 &tail[1..],
5426 al_id,
5427 ret_form,
5428 args,
5429 )?;
5430 return Ok(true);
5431 }
5432 bail!("unsupported CALL command chain {:?}", elm);
5433 }
5434 }
5435 }
5436
5437 fn push_property_value(&mut self, v: Value, array_idx: Option<usize>) {
5438 match v {
5439 Value::NamedArg { value, .. } => self.push_property_value(*value, array_idx),
5440 Value::Int(n) => self.push_int(n as i32),
5441 Value::Str(s) => self.push_str(s),
5442 Value::Element(elm) => self.push_element(elm),
5443 Value::List(items) => {
5444 if let Some(i) = array_idx {
5445 if let Some(item) = items.get(i).cloned() {
5446 self.push_property_value(item, None);
5447 } else {
5448 self.push_int(0);
5449 }
5450 } else {
5451 panic!("raw Value::List used as property result; expected runtime ref");
5452 }
5453 }
5454 }
5455 }
5456
5457 fn assign_user_prop(&mut self, prop_id: u16, array_idx: Option<usize>, rhs: Value) {
5458 let decl = self
5459 .user_prop_decl(prop_id)
5460 .unwrap_or((self.cfg.fm_list, 0));
5461 if let Some(i) = array_idx {
5462 let slot_element = self.default_user_prop_slot_element(prop_id, i);
5463 let default_entry = self.default_user_prop_cell(prop_id);
5464 let existing_form = self
5465 .user_props
5466 .get(&prop_id)
5467 .map(|e| e.form)
5468 .unwrap_or(default_entry.form);
5469 if existing_form == self.cfg.fm_intlist {
5470 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
5471 if entry.int_list.len() <= i {
5472 entry.int_list.resize(i + 1, 0);
5473 }
5474 entry.int_list[i] = rhs.as_i64().unwrap_or(0) as i32;
5475 return;
5476 }
5477 if existing_form == self.cfg.fm_strlist {
5478 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
5479 if entry.str_list.len() <= i {
5480 entry.str_list.resize_with(i + 1, String::new);
5481 }
5482 entry.str_list[i] = rhs.as_str().unwrap_or("").to_string();
5483 return;
5484 }
5485 let list_form = self.cfg.fm_list;
5486 let new_slot =
5487 self.user_prop_cell_from_value(rhs, list_form, slot_element, Some(prop_id));
5488 let head = constants::elm::create(constants::elm::OWNER_USER_PROP, 0, prop_id as i32);
5489 let elm_array = self.ctx.ids.elm_array;
5490 let entry = self.user_props.entry(prop_id).or_insert(default_entry);
5491 if entry.list_items.len() <= i {
5492 let cur = entry.list_items.len();
5493 entry
5494 .list_items
5495 .resize_with(i + 1, || UserPropCell::new(list_form, Vec::new()));
5496 for idx in cur..entry.list_items.len() {
5497 entry.list_items[idx].form = list_form;
5498 entry.list_items[idx].element = vec![head, elm_array, idx as i32];
5499 }
5500 }
5501 entry.list_items[i] = new_slot;
5502 } else {
5503 let element = self.default_user_prop_element(prop_id, decl.0);
5504 let cell = self.user_prop_cell_from_value(rhs, decl.0, element, Some(prop_id));
5505 self.user_props.insert(prop_id, cell);
5506 }
5507 }
5508
5509 fn exec_copy_element(&mut self) -> Result<()> {
5510 let start = match self.element_points.last().copied() {
5511 Some(v) => v,
5512 None => {
5513 self.vm_trace(None, "COPY_ELM missing prior ELM_POINT");
5514 return Err(anyhow!("COPY_ELM without a prior ELM_POINT"));
5515 }
5516 };
5517 if start > self.int_stack.len() {
5518 self.vm_trace(
5519 None,
5520 format!(
5521 "COPY_ELM invalid start={} len={}",
5522 start,
5523 self.int_stack.len()
5524 ),
5525 );
5526 bail!(
5527 "invalid element point start={start} len={}",
5528 self.int_stack.len()
5529 );
5530 }
5531 let slice = self.int_stack[start..].to_vec();
5532 if Self::sg_mwnd_object_trace_enabled() && Self::sg_mwnd_chain_interesting(&slice) {
5533 self.sg_mwnd_object_trace(format!(
5534 "COPY_ELM slice={:?} before_current_chain={:?} before_current_stage_object={:?}",
5535 slice,
5536 self.ctx.globals.current_object_chain,
5537 self.ctx.globals.current_stage_object
5538 ));
5539 }
5540 self.element_points.push(self.int_stack.len());
5541 self.int_stack.extend_from_slice(&slice);
5542 self.vm_trace(None, format!("COPY_ELM copied {:?}", slice));
5543 Ok(())
5544 }
5545
5546 fn pop_value_for_form(&mut self, form_code: i32) -> Result<Value> {
5547 if form_code == self.cfg.fm_void {
5548 return Ok(Value::Int(0));
5549 }
5550 if form_code == self.cfg.fm_int {
5551 return Ok(Value::Int(self.pop_int()? as i64));
5552 }
5553 if form_code == self.cfg.fm_str {
5554 return Ok(Value::Str(self.pop_str()?));
5555 }
5556 if form_code == self.cfg.fm_label {
5557 return Ok(Value::Int(self.pop_int()? as i64));
5558 }
5559 if form_code == self.cfg.fm_list {
5560 let nested = self.pop_arg_list()?;
5561 return Ok(Value::List(nested));
5562 }
5563
5564 self.trace_unknown_form(form_code, "pop_value_for_form");
5566 Ok(Value::Element(self.pop_element()?))
5567 }
5568
5569 fn pop_arg_list(&mut self) -> Result<Vec<Value>> {
5570 let arg_cnt_i32 = self.stream.pop_i32()?;
5571 if arg_cnt_i32 < 0 {
5572 bail!("negative arg_cnt={arg_cnt_i32}");
5573 }
5574 let arg_cnt = arg_cnt_i32 as usize;
5575 let mut out: Vec<Value> = vec![Value::Int(0); arg_cnt];
5576
5577 for i in (0..arg_cnt).rev() {
5579 let form_code = self.stream.pop_i32()?;
5580 let v = self.pop_value_for_form(form_code)?;
5581 out[i] = v;
5582 }
5583 Ok(out)
5584 }
5585
5586 fn exec_push(&mut self, form_code: i32) -> Result<()> {
5587 if form_code == self.cfg.fm_void {
5588 return Ok(());
5589 }
5590 if form_code == self.cfg.fm_int {
5591 let v = self.stream.pop_i32()?;
5592 self.push_int(v);
5593 return Ok(());
5594 }
5595 if form_code == self.cfg.fm_str {
5596 let s = self.stream.pop_str()?;
5597 self.push_str(s);
5598 return Ok(());
5599 }
5600
5601 self.trace_unknown_form(form_code, "exec_push");
5603 Ok(())
5604 }
5605
5606 fn exec_pop(&mut self, form_code: i32) -> Result<()> {
5607 if form_code == self.cfg.fm_void {
5608 return Ok(());
5609 }
5610 if form_code == self.cfg.fm_int {
5611 let _ = self.pop_int()?;
5612 return Ok(());
5613 }
5614 if form_code == self.cfg.fm_str {
5615 let _ = self.pop_str()?;
5616 return Ok(());
5617 }
5618
5619 self.trace_unknown_form(form_code, "exec_pop");
5620 Ok(())
5621 }
5622
5623 fn exec_copy(&mut self, form_code: i32) -> Result<()> {
5624 if form_code == self.cfg.fm_void {
5625 return Ok(());
5626 }
5627 if form_code == self.cfg.fm_int {
5628 let v = self.peek_int()?;
5629 self.push_int(v);
5630 return Ok(());
5631 }
5632 if form_code == self.cfg.fm_str {
5633 let s = self.peek_str()?;
5634 self.push_str(s);
5635 return Ok(());
5636 }
5637
5638 self.trace_unknown_form(form_code, "exec_copy");
5640 Ok(())
5641 }
5642
5643 fn canonical_runtime_form_id(&self, form_id: u32) -> u32 {
5648 let ids = &self.ctx.ids;
5649
5650 if constants::is_stage_global_form(form_id, ids.form_global_stage) {
5651 return constants::global_form::STAGE_ALT;
5652 }
5653 if constants::matches_form_id(form_id, ids.form_global_mov, constants::global_form::MOV) {
5654 return constants::global_form::MOV;
5655 }
5656 if constants::matches_form_id(form_id, ids.form_global_bgm, constants::global_form::BGM) {
5657 return constants::global_form::BGM;
5658 }
5659 if constants::matches_form_id(
5660 form_id,
5661 ids.form_global_bgm_table,
5662 constants::global_form::BGMTABLE,
5663 ) {
5664 return constants::global_form::BGMTABLE;
5665 }
5666 if constants::matches_form_id(form_id, ids.form_global_math, constants::global_form::MATH) {
5667 return constants::global_form::MATH;
5668 }
5669 if constants::matches_form_id(form_id, ids.form_global_pcm, constants::global_form::PCM) {
5670 return constants::global_form::PCM;
5671 }
5672 if constants::matches_form_id(
5673 form_id,
5674 ids.form_global_pcmch,
5675 constants::global_form::PCMCH,
5676 ) {
5677 return constants::global_form::PCMCH;
5678 }
5679 if constants::matches_form_id(form_id, ids.form_global_se, constants::global_form::SE) {
5680 return constants::global_form::SE;
5681 }
5682 if constants::matches_form_id(
5683 form_id,
5684 ids.form_global_pcm_event,
5685 constants::global_form::PCMEVENT,
5686 ) {
5687 return constants::global_form::PCMEVENT;
5688 }
5689 if constants::matches_form_id(
5690 form_id,
5691 ids.form_global_excall,
5692 constants::global_form::EXCALL,
5693 ) {
5694 return constants::global_form::EXCALL;
5695 }
5696 if constants::matches_form_id(
5697 form_id,
5698 ids.form_global_screen,
5699 constants::global_form::SCREEN,
5700 ) {
5701 return constants::global_form::SCREEN;
5702 }
5703 if constants::matches_form_id(
5704 form_id,
5705 ids.form_global_msgbk,
5706 constants::global_form::MSGBK,
5707 ) {
5708 return constants::global_form::MSGBK;
5709 }
5710 if constants::matches_form_id(
5711 form_id,
5712 ids.form_global_koe_st,
5713 constants::global_form::KOE_ST,
5714 ) {
5715 return constants::global_form::KOE_ST;
5716 }
5717 if constants::matches_form_id(form_id, ids.form_global_key, constants::global_form::KEY) {
5718 return constants::global_form::KEY;
5719 }
5720 if form_id == constants::global_form::COUNTER {
5721 return constants::global_form::COUNTER;
5722 }
5723 if constants::matches_form_id(
5724 form_id,
5725 ids.form_global_frame_action,
5726 constants::global_form::FRAME_ACTION,
5727 ) {
5728 return constants::global_form::FRAME_ACTION;
5729 }
5730 if form_id == constants::global_form::TIMEWAIT {
5731 return constants::global_form::TIMEWAIT;
5732 }
5733 if form_id == constants::global_form::TIMEWAIT_KEY {
5734 return constants::global_form::TIMEWAIT_KEY;
5735 }
5736
5737 form_id
5738 }
5739
5740
5741 fn sg_mwnd_object_trace_enabled() -> bool {
5742 std::env::var_os("SG_DEBUG").is_some()
5743 }
5744
5745 fn sg_mwnd_object_trace(&self, msg: impl AsRef<str>) {
5746 if Self::sg_mwnd_object_trace_enabled() {
5747 eprintln!("[SG_DEBUG][MWND_OBJECT_TRACE][VM] {}", msg.as_ref());
5748 }
5749 }
5750
5751 fn sg_mwnd_chain_interesting(elm: &[i32]) -> bool {
5752 elm.iter().any(|v| {
5753 *v == crate::runtime::forms::codes::STAGE_ELM_MWND
5754 || *v == crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM
5755 || *v == crate::runtime::forms::codes::elm_value::MWND_OBJECT
5756 || *v == crate::runtime::forms::codes::elm_value::MWND_BUTTON
5757 || *v == crate::runtime::forms::codes::elm_value::MWND_FACE
5758 || *v == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT
5759 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CHILD
5760 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE
5761 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE_RECT
5762 || *v == crate::runtime::forms::codes::elm_value::OBJECT_CREATE_STRING
5763 || *v == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION
5764 || *v == crate::runtime::forms::codes::elm_value::OBJECT_FRAME_ACTION_CH
5765 })
5766 }
5767
5768 fn is_global_indexed_list_head(&self, head: i32) -> bool {
5769 if head < 0 {
5770 return false;
5771 }
5772 let head = head as u32;
5773 crate::runtime::constants::global_form::INT_LIST_FORMS.contains(&head)
5774 || crate::runtime::constants::global_form::STR_LIST_FORMS.contains(&head)
5775 }
5776
5777 fn is_global_indexed_list_chain(&self, elm: &[i32]) -> bool {
5778 if elm.len() < 3 || !self.is_global_indexed_list_head(elm[0]) {
5779 return false;
5780 }
5781 elm[1] == self.ctx.ids.elm_array || elm[1] == crate::runtime::forms::codes::ELM_ARRAY
5782 }
5783
5784 fn current_object_chain_has_child_index(&self, child_idx: i32) -> bool {
5785 if child_idx < 0 {
5786 return false;
5787 }
5788 let Some(chain) = self.ctx.globals.current_object_chain.as_ref() else {
5789 return false;
5790 };
5791 let stage_form = if self.ctx.ids.form_global_stage != 0 {
5792 self.ctx.ids.form_global_stage as i32
5793 } else {
5794 crate::runtime::forms::codes::FORM_GLOBAL_STAGE as i32
5795 };
5796 let elm_array = if self.ctx.ids.elm_array != 0 {
5797 self.ctx.ids.elm_array
5798 } else {
5799 crate::runtime::forms::codes::ELM_ARRAY
5800 };
5801 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
5802 self.ctx.ids.stage_elm_object
5803 } else {
5804 crate::runtime::forms::codes::STAGE_ELM_OBJECT
5805 };
5806 if chain.len() < 6
5807 || chain[0] != stage_form
5808 || chain[1] != elm_array
5809 || chain[2] < 0
5810 {
5811 return false;
5812 }
5813
5814 let stage_idx = chain[2] as i64;
5815 let Some(stage_state) = self.ctx.globals.stage_forms.get(&(stage_form as u32)) else {
5816 return false;
5817 };
5818
5819 fn descend_child_chain<'a>(
5820 mut obj: &'a crate::runtime::globals::ObjectState,
5821 chain: &[i32],
5822 mut pos: usize,
5823 elm_array: i32,
5824 ) -> Option<&'a crate::runtime::globals::ObjectState> {
5825 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
5826 while pos + 2 < chain.len() {
5827 if chain[pos] == object_child && chain[pos + 1] == elm_array && chain[pos + 2] >= 0 {
5828 let idx = chain[pos + 2] as usize;
5829 obj = obj.runtime.child_objects.get(idx)?;
5830 pos += 3;
5831 } else {
5832 break;
5833 }
5834 }
5835 Some(obj)
5836 }
5837
5838 let current_obj = (|| -> Option<&crate::runtime::globals::ObjectState> {
5839 if chain[3] == stage_object {
5840 if chain[4] != elm_array || chain[5] < 0 {
5841 return None;
5842 }
5843 let top_idx = chain[5] as usize;
5844 let list = stage_state.object_lists.get(&stage_idx)?;
5845 let obj = list.get(top_idx)?;
5846 descend_child_chain(obj, chain, 6, elm_array)
5847 } else if chain[3] == crate::runtime::forms::codes::STAGE_ELM_MWND {
5848 if chain.len() < 9
5849 || chain[4] != elm_array
5850 || chain[5] < 0
5851 || chain[7] != elm_array
5852 || chain[8] < 0
5853 {
5854 return None;
5855 }
5856 let mwnd_idx = chain[5] as usize;
5857 let selector = chain[6];
5858 let obj_idx = chain[8] as usize;
5859 let mwnds = stage_state.mwnd_lists.get(&stage_idx)?;
5860 let mwnd = mwnds.get(mwnd_idx)?;
5861 let list = if selector == constants::MWND_BUTTON {
5862 &mwnd.button_list
5863 } else if selector == constants::MWND_FACE {
5864 &mwnd.face_list
5865 } else if selector == constants::MWND_OBJECT {
5866 &mwnd.object_list
5867 } else {
5868 return None;
5869 };
5870 let obj = list.get(obj_idx)?;
5871 descend_child_chain(obj, chain, 9, elm_array)
5872 } else if chain[3] == crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM {
5873 if chain.len() < 9
5874 || chain[4] != elm_array
5875 || chain[5] < 0
5876 || chain[7] != elm_array
5877 || chain[8] < 0
5878 {
5879 return None;
5880 }
5881 if chain[6] != crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT {
5882 return None;
5883 }
5884 let item_idx = chain[5] as usize;
5885 let obj_idx = chain[8] as usize;
5886 let items = stage_state.btnselitem_lists.get(&stage_idx)?;
5887 let item = items.get(item_idx)?;
5888 let obj = item.object_list.get(obj_idx)?;
5889 descend_child_chain(obj, chain, 9, elm_array)
5890 } else {
5891 None
5892 }
5893 })();
5894
5895 let Some(current_obj) = current_obj else {
5896 return false;
5897 };
5898 (child_idx as usize) < current_obj.runtime.child_objects.len()
5899 }
5900
5901 fn object_array_property_op(&self, op: i32) -> bool {
5902 let ids = &self.ctx.ids;
5903 op == crate::runtime::forms::codes::elm_value::OBJECT_CHILD
5904 || (ids.obj_x_rep != 0 && op == ids.obj_x_rep)
5905 || (ids.obj_y_rep != 0 && op == ids.obj_y_rep)
5906 || (ids.obj_z_rep != 0 && op == ids.obj_z_rep)
5907 || (ids.obj_tr_rep != 0 && op == ids.obj_tr_rep)
5908 || (ids.obj_f != 0 && op == ids.obj_f)
5909 || (ids.obj_frame_action_ch != 0 && op == ids.obj_frame_action_ch)
5910 }
5911
5912 fn is_current_object_child_tail(&self, elm: &[i32]) -> bool {
5913 if elm.len() < 2 {
5914 return false;
5915 }
5916 if elm[0] < 0 {
5917 return false;
5918 }
5919 if elm[1] != self.ctx.ids.elm_array && elm[1] != crate::runtime::forms::codes::ELM_ARRAY {
5920 return false;
5921 }
5922 if self.object_array_property_op(elm[0]) {
5923 return false;
5924 }
5925 if elm.len() == 2 {
5926 return true;
5927 }
5928 if self.object_array_property_op(elm[2]) {
5929 return elm[2] == crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
5930 }
5931 elm[2] == self.ctx.ids.elm_array
5932 || elm[2] == crate::runtime::forms::codes::ELM_ARRAY
5933 || elm[2] == crate::runtime::forms::codes::ELM_UP
5934 || self.compact_object_op_allowed(elm[2])
5935 }
5936
5937 fn global_indexed_list_must_dispatch_direct(&self, elm: &[i32]) -> bool {
5938 self.is_global_indexed_list_chain(elm) && !self.is_current_object_child_tail(elm)
5939 }
5940
5941 fn dispatch_global_indexed_list_property_direct(&mut self, elm: &[i32]) -> Result<bool> {
5942 if !self.global_indexed_list_must_dispatch_direct(elm) {
5943 return Ok(false);
5944 }
5945 let form_id = elm[0] as u32;
5946 self.ctx.vm_call = Some(runtime::VmCallMeta {
5947 element: elm.to_vec(),
5948 al_id: 0,
5949 ret_form: self.cfg.fm_int as i64,
5950 });
5951 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &[])? {
5952 self.ctx.vm_call = None;
5953 bail!("unhandled global indexed-list property chain {:?}", elm);
5954 }
5955 self.ctx.vm_call = None;
5956 if let Some(v) = self.ctx.pop() {
5957 self.push_return_value_raw(v);
5958 } else {
5959 bail!("global indexed-list property returned no value: {:?}", elm);
5960 }
5961 Ok(true)
5962 }
5963
5964 fn dispatch_global_indexed_list_assign_direct(&mut self, elm: &[i32], al_id: i32, rhs: Value) -> Result<bool> {
5965 if !self.global_indexed_list_must_dispatch_direct(elm) {
5966 return Ok(false);
5967 }
5968 let form_id = elm[0] as u32;
5969 let args: Vec<Value> = vec![rhs];
5970 self.ctx.vm_call = Some(runtime::VmCallMeta {
5971 element: elm.to_vec(),
5972 al_id: al_id as i64,
5973 ret_form: 0,
5974 });
5975 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
5976 self.ctx.vm_call = None;
5977 bail!("unhandled global indexed-list assignment chain {:?}", elm);
5978 }
5979 self.ctx.vm_call = None;
5980 self.ctx.stack.clear();
5981 self.drain_pending_frame_action_finishes()?;
5982 Ok(true)
5983 }
5984
5985 fn dispatch_global_indexed_list_command_direct(
5986 &mut self,
5987 elm: &[i32],
5988 al_id: i32,
5989 ret_form: i32,
5990 args: &mut Vec<Value>,
5991 ) -> Result<bool> {
5992 if !self.global_indexed_list_must_dispatch_direct(elm) {
5993 return Ok(false);
5994 }
5995 let form_id = elm[0] as u32;
5996 self.ctx.vm_call = Some(runtime::VmCallMeta {
5997 element: elm.to_vec(),
5998 al_id: al_id as i64,
5999 ret_form: ret_form as i64,
6000 });
6001 if !runtime::dispatch_form_code(&mut self.ctx, form_id, args)? {
6002 self.ctx.vm_call = None;
6003 bail!("unhandled global indexed-list command chain {:?}", elm);
6004 }
6005 self.ctx.vm_call = None;
6006 self.drain_pending_frame_action_finishes()?;
6007 if ret_form != self.cfg.fm_void {
6008 self.take_ctx_return(ret_form)?;
6009 } else {
6010 self.ctx.stack.clear();
6011 }
6012 Ok(true)
6013 }
6014
6015
6016 fn try_parent_slot_property(&mut self, elm: &[i32]) -> bool {
6017 if elm.len() != 3 || elm[1] != self.ctx.ids.elm_array || elm[2] <= 0 {
6018 return false;
6019 }
6020 if self.ctx.globals.current_object_chain.is_some() && self.compact_object_op_allowed(elm[0])
6026 {
6027 return false;
6028 }
6029 let parent_form = elm[2] as u32;
6030 let slot = elm[0];
6031 let ret_form = self
6032 .ctx
6033 .vm_call
6034 .as_ref()
6035 .map(|m| m.ret_form)
6036 .unwrap_or(self.cfg.fm_int as i64);
6037 if ret_form == self.cfg.fm_str as i64 {
6038 let value = self
6039 .ctx
6040 .globals
6041 .str_props
6042 .get(&parent_form)
6043 .and_then(|m| m.get(&slot))
6044 .cloned()
6045 .unwrap_or_default();
6046 self.push_str(value);
6047 } else if let Some(value) = self
6048 .ctx
6049 .globals
6050 .str_props
6051 .get(&parent_form)
6052 .and_then(|m| m.get(&slot))
6053 .cloned()
6054 {
6055 self.push_str(value);
6056 } else {
6057 let value = self
6058 .ctx
6059 .globals
6060 .int_props
6061 .get(&parent_form)
6062 .and_then(|m| m.get(&slot).copied())
6063 .unwrap_or(0);
6064 self.push_int(value as i32);
6065 }
6066 true
6067 }
6068
6069 fn compact_object_op_allowed(&self, op: i32) -> bool {
6070 op >= 0 && op <= 187
6071 }
6072
6073 fn compact_object_op_allowed_for_element(
6074 &self,
6075 elm: &[i32],
6076 allow_ambiguous_single_token_object_op: bool,
6077 ) -> bool {
6078 let Some(op) = elm.first().copied() else {
6079 return false;
6080 };
6081 if elm.len() == 1 && !allow_ambiguous_single_token_object_op {
6082 return false;
6083 }
6084 self.compact_object_op_allowed(op)
6085 }
6086
6087 fn current_object_has_child_index(&self, child_idx: i32) -> bool {
6088 if self.current_object_chain_has_child_index(child_idx) {
6089 return true;
6090 }
6091 if child_idx < 0 {
6092 return false;
6093 }
6094 let Some((stage_idx, obj_idx)) = self.ctx.globals.current_stage_object else {
6095 return false;
6096 };
6097 let stage_form = self.ctx.ids.form_global_stage;
6098 let Some(stage_state) = self.ctx.globals.stage_forms.get(&stage_form) else {
6099 return false;
6100 };
6101 let Some(list) = stage_state.object_lists.get(&stage_idx) else {
6102 return false;
6103 };
6104 let Some(obj) = list.get(obj_idx) else {
6105 return false;
6106 };
6107 (child_idx as usize) < obj.runtime.child_objects.len()
6108 }
6109
6110 fn try_compact_object_chain(
6111 &self,
6112 elm: &[i32],
6113 allow_ambiguous_single_token_object_op: bool,
6114 ) -> Option<Vec<i32>> {
6115 if elm.is_empty() {
6116 return None;
6117 }
6118
6119 let op = elm[0];
6120 if !self.compact_object_op_allowed_for_element(
6121 elm,
6122 allow_ambiguous_single_token_object_op,
6123 ) {
6124 return None;
6125 }
6126
6127 let elm_array = if self.ctx.ids.elm_array != 0 {
6128 self.ctx.ids.elm_array
6129 } else {
6130 crate::runtime::forms::codes::ELM_ARRAY
6131 };
6132 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
6133 self.ctx.ids.stage_elm_object
6134 } else {
6135 crate::runtime::forms::codes::STAGE_ELM_OBJECT
6136 };
6137
6138 let looks_like_absolute_stage_alias_object = elm.len() >= 4
6139 && constants::is_stage_global_form(elm[0] as u32, self.ctx.ids.form_global_stage)
6140 && elm[1] == stage_object
6141 && (elm[2] == elm_array || elm[2] == crate::runtime::forms::codes::ELM_ARRAY);
6142
6143 if looks_like_absolute_stage_alias_object {
6144 return None;
6145 }
6146
6147 if let Some(prefix) = &self.ctx.globals.current_object_chain {
6153 if self.is_current_object_child_tail(elm) && self.current_object_has_child_index(elm[0]) {
6154 let mut synthetic = prefix.clone();
6155 synthetic.push(crate::runtime::forms::codes::elm_value::OBJECT_CHILD);
6156 synthetic.push(elm_array);
6157 synthetic.push(elm[0]);
6158 if elm.len() > 2 {
6159 if elm[2] == crate::runtime::forms::codes::elm_value::OBJECT_CHILD {
6160 synthetic.extend_from_slice(&elm[3..]);
6161 } else {
6162 synthetic.extend_from_slice(&elm[2..]);
6163 }
6164 }
6165 if Self::sg_mwnd_object_trace_enabled()
6166 && (Self::sg_mwnd_chain_interesting(elm)
6167 || Self::sg_mwnd_chain_interesting(&synthetic))
6168 {
6169 eprintln!(
6170 "[SG_DEBUG][MWND_OBJECT_TRACE][VM] try_compact child-shorthand elm={:?} prefix={:?} synthetic={:?}",
6171 elm,
6172 prefix,
6173 synthetic
6174 );
6175 }
6176 return Some(synthetic);
6177 }
6178 }
6179
6180 if elm.len() >= 4
6184 && elm[1] >= 0
6185 && (elm[2] == elm_array || elm[2] == crate::runtime::forms::codes::ELM_ARRAY)
6186 && elm[3] >= 0
6187 {
6188 let stage_idx = elm[1];
6189 if !(0..3).contains(&stage_idx) {
6190 return None;
6191 }
6192 let stage_form = if self.ctx.ids.form_global_stage != 0 {
6193 self.ctx.ids.form_global_stage as i32
6194 } else {
6195 crate::runtime::forms::codes::FORM_GLOBAL_STAGE as i32
6196 };
6197 let mut synthetic = vec![
6198 stage_form,
6199 elm_array,
6200 stage_idx,
6201 stage_object,
6202 elm_array,
6203 elm[3],
6204 op,
6205 ];
6206 if elm.len() > 4 {
6207 synthetic.extend_from_slice(&elm[4..]);
6208 }
6209 if Self::sg_mwnd_object_trace_enabled()
6210 && (Self::sg_mwnd_chain_interesting(elm)
6211 || Self::sg_mwnd_chain_interesting(&synthetic))
6212 {
6213 eprintln!(
6214 "[SG_DEBUG][MWND_OBJECT_TRACE][VM] try_compact absolute elm={:?} synthetic={:?}",
6215 elm,
6216 synthetic
6217 );
6218 }
6219 return Some(synthetic);
6220 }
6221
6222 None
6223 }
6224
6225 fn try_parent_slot_assign(&mut self, elm: &[i32], rhs: &Value) -> bool {
6226 if elm.len() != 3 || elm[1] != self.ctx.ids.elm_array || elm[2] <= 0 {
6227 return false;
6228 }
6229 if self.ctx.globals.current_object_chain.is_some() && self.compact_object_op_allowed(elm[0])
6232 {
6233 return false;
6234 }
6235 let parent_form = elm[2] as u32;
6236 let slot = elm[0];
6237 match rhs {
6238 Value::Str(s) => {
6239 self.ctx
6240 .globals
6241 .str_props
6242 .entry(parent_form)
6243 .or_default()
6244 .insert(slot, s.clone());
6245 }
6246 Value::Int(n) => {
6247 self.ctx
6248 .globals
6249 .int_props
6250 .entry(parent_form)
6251 .or_default()
6252 .insert(slot, *n);
6253 }
6254 Value::NamedArg { value, .. } => return self.try_parent_slot_assign(elm, value),
6255 _ => return false,
6256 }
6257 true
6258 }
6259
6260 fn exec_property(&mut self, mut elm: Vec<i32>) -> Result<()> {
6261 if std::env::var_os("SG_TITLE_CHAIN_TRACE").is_some()
6262 && self.current_scene_name.as_deref() == Some("sys10_tt01")
6263 && matches!(elm.first().copied(), Some(83 | 84 | 24 | 25))
6264 {
6265 eprintln!(
6266 "[SG_TITLE_CHAIN_TRACE] line={} elm={:?} current_object_chain={:?} current_stage_object={:?}",
6267 self.current_line_no,
6268 elm,
6269 self.ctx.globals.current_object_chain,
6270 self.ctx.globals.current_stage_object
6271 );
6272 }
6273 self.vm_trace(None, format!("exec_property enter elm={:?}", elm));
6274 if elm.is_empty() {
6275 self.push_int(0);
6276 return Ok(());
6277 }
6278 if self.exec_call_property(&elm)? {
6280 self.vm_trace(
6281 None,
6282 format!("exec_property handled by call-property elm={:?}", elm),
6283 );
6284 return Ok(());
6285 }
6286
6287 let head = elm[0];
6288 let head_owner = elm_code::owner(head);
6289 if head_owner == elm_code::ELM_OWNER_CALL_PROP {
6290 let current_idx = self
6291 .current_call_frame_index()
6292 .ok_or_else(|| anyhow!("call stack underflow"))?;
6293 let call_prop_id = elm_code::code(head) as i32;
6294 let prop_idx = self
6295 .find_call_prop_index_in_frame(current_idx, call_prop_id)
6296 .ok_or_else(|| {
6297 anyhow!("missing direct CALL_PROP id={} for {:?}", call_prop_id, elm)
6298 })?;
6299 let prop = self.call_stack[current_idx].user_props[prop_idx].clone();
6300 if let Some(composed) = self.compose_call_prop_tail(&prop, &elm[1..]) {
6301 self.exec_property(composed)?;
6302 self.vm_trace(
6303 None,
6304 format!("exec_property direct CALL_PROP composed elm={:?}", elm),
6305 );
6306 return Ok(());
6307 }
6308 self.push_call_prop_result(&prop, &elm[1..], &elm)?;
6309 self.vm_trace(
6310 None,
6311 format!("exec_property direct CALL_PROP elm={:?}", elm),
6312 );
6313 return Ok(());
6314 }
6315
6316 if head_owner == elm_code::ELM_OWNER_USER_PROP {
6317 let prop_id = elm_code::code(head);
6318 let cell = self
6319 .user_props
6320 .get(&prop_id)
6321 .cloned()
6322 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
6323 let array_idx = self.extract_array_index(&elm);
6324 self.trace_cf_condition_user_prop_read(
6325 self.stream.get_prg_cntr(),
6326 prop_id,
6327 array_idx,
6328 &cell,
6329 &elm,
6330 );
6331 self.push_user_prop_cell_result(&cell, &elm[1..], &elm)?;
6332 self.vm_trace(
6333 None,
6334 format!("exec_property direct USER_PROP elm={:?}", elm),
6335 );
6336 return Ok(());
6337 }
6338
6339 if head_owner != elm_code::ELM_OWNER_FORM {
6340 bail!(
6341 "unsupported property owner {} for element {:?}",
6342 head_owner,
6343 elm
6344 );
6345 }
6346
6347 if self.dispatch_global_indexed_list_property_direct(&elm)? {
6348 self.vm_trace(None, format!("exec_property handled by global indexed-list elm={:?}", elm));
6349 return Ok(());
6350 }
6351
6352 if self.try_parent_slot_property(&elm) {
6353 self.vm_trace(
6354 None,
6355 format!("exec_property handled by parent-slot elm={:?}", elm),
6356 );
6357 return Ok(());
6358 }
6359 if let Some(synthetic) = self.try_compact_object_chain(&elm, false) {
6360 self.vm_trace(
6361 None,
6362 format!(
6363 "exec_property compact-object elm={:?} synthetic={:?}",
6364 elm, synthetic
6365 ),
6366 );
6367 self.ctx.vm_call = Some(runtime::VmCallMeta {
6368 element: synthetic.clone(),
6369 al_id: 0,
6370 ret_form: self.cfg.fm_int as i64,
6371 });
6372 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32);
6373 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &[])? {
6374 self.ctx.vm_call = None;
6375 bail!(
6376 "unhandled compact object property chain {:?} -> {:?}",
6377 elm,
6378 synthetic
6379 );
6380 }
6381 self.ctx.vm_call = None;
6382 self.update_compact_context_from_object_dispatch_chain(&synthetic);
6383 if let Some(v) = self.ctx.pop() {
6384 self.push_return_value_raw(v);
6385 } else {
6386 bail!("compact object property chain returned no value: {:?}", elm);
6387 }
6388 return Ok(());
6389 }
6390
6391 let form_id = self.canonical_runtime_form_id(head as u32);
6392 let args: Vec<Value> = Vec::new();
6393 self.ctx.vm_call = Some(runtime::VmCallMeta {
6394 element: elm.clone(),
6395 al_id: 0,
6396 ret_form: self.cfg.fm_int as i64,
6397 });
6398
6399 self.vm_trace(
6400 None,
6401 format!("exec_property dispatch form_id={} elm={:?}", form_id, elm),
6402 );
6403 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
6404 self.ctx.vm_call = None;
6405 bail!("unhandled form property chain {:?}", elm);
6406 }
6407
6408 self.ctx.vm_call = None;
6409 if let Some(v) = self.ctx.pop() {
6410 self.push_return_value_raw(v);
6411 } else {
6412 bail!("property chain returned no value: {:?}", elm);
6413 }
6414
6415 Ok(())
6416 }
6417
6418 fn exec_assign(&mut self, elm: Vec<i32>, al_id: i32, rhs: Value) -> Result<()> {
6419 if elm.is_empty() {
6420 return Ok(());
6421 }
6422
6423 self.trace_cgm_coord_assign(&elm, &rhs);
6424
6425 if self.exec_call_assign(&elm, al_id, rhs.clone())? {
6427 return Ok(());
6428 }
6429
6430 let head = elm[0];
6431 let head_owner = elm_code::owner(head);
6432 if head_owner == elm_code::ELM_OWNER_CALL_PROP {
6433 let current_idx = self
6434 .current_call_frame_index()
6435 .ok_or_else(|| anyhow!("call stack underflow"))?;
6436 let call_prop_id = elm_code::code(head) as i32;
6437 let prop_idx = self
6438 .find_call_prop_index_in_frame(current_idx, call_prop_id)
6439 .ok_or_else(|| {
6440 anyhow!(
6441 "missing direct CALL_PROP assign id={} for {:?}",
6442 call_prop_id,
6443 elm
6444 )
6445 })?;
6446 let prop_for_compose = self.call_stack[current_idx].user_props[prop_idx].clone();
6447 if let Some(composed) = self.compose_call_prop_tail(&prop_for_compose, &elm[1..]) {
6448 self.exec_assign(composed, al_id, rhs)?;
6449 return Ok(());
6450 }
6451 let frame = self
6452 .call_stack
6453 .get_mut(current_idx)
6454 .ok_or_else(|| anyhow!("call stack underflow"))?;
6455 let prop = frame.user_props.get_mut(prop_idx).ok_or_else(|| {
6456 anyhow!("missing direct CALL_PROP slot assign id={}", call_prop_id)
6457 })?;
6458 Self::assign_call_prop_result(prop, &elm[1..], rhs)?;
6459 return Ok(());
6460 }
6461
6462 if head_owner == elm_code::ELM_OWNER_USER_PROP {
6463 let prop_id = elm_code::code(head);
6464 let array_idx = self.extract_array_index(&elm);
6465 let old_cell = self.user_props.get(&prop_id).cloned();
6466 self.assign_user_prop(prop_id, array_idx, rhs.clone());
6467 let new_cell = self.user_props.get(&prop_id);
6468 self.trace_cf_condition_user_prop_assign(
6469 self.stream.get_prg_cntr(),
6470 prop_id,
6471 array_idx,
6472 old_cell.as_ref(),
6473 new_cell,
6474 &rhs,
6475 &elm,
6476 );
6477 return Ok(());
6478 }
6479
6480 if head_owner != elm_code::ELM_OWNER_FORM {
6481 bail!(
6482 "unsupported assignment owner {} for element {:?}",
6483 head_owner,
6484 elm
6485 );
6486 }
6487
6488 if self.dispatch_global_indexed_list_assign_direct(&elm, al_id, rhs.clone())? {
6489 return Ok(());
6490 }
6491
6492 if self.try_parent_slot_assign(&elm, &rhs) {
6493 return Ok(());
6494 }
6495 if let Some(synthetic) = self.try_compact_object_chain(&elm, true) {
6496 self.vm_trace(
6497 None,
6498 format!(
6499 "exec_assign compact-object elm={:?} synthetic={:?} al_id={} rhs={:?}",
6500 elm, synthetic, al_id, rhs
6501 ),
6502 );
6503 let args: Vec<Value> = vec![rhs];
6504 self.ctx.vm_call = Some(runtime::VmCallMeta {
6505 element: synthetic.clone(),
6506 al_id: al_id as i64,
6507 ret_form: 0,
6508 });
6509 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32);
6510 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
6511 self.ctx.vm_call = None;
6512 bail!(
6513 "unhandled compact object assignment chain {:?} -> {:?}",
6514 elm,
6515 synthetic
6516 );
6517 }
6518 self.ctx.vm_call = None;
6519 self.update_compact_context_from_object_dispatch_chain(&synthetic);
6520 self.ctx.stack.clear();
6521 self.drain_pending_frame_action_finishes()?;
6522 return Ok(());
6523 }
6524
6525 let form_id = self.canonical_runtime_form_id(head as u32);
6526 self.vm_trace(
6527 None,
6528 format!(
6529 "exec_assign dispatch form_id={} elm={:?} al_id={} rhs={:?}",
6530 form_id, elm, al_id, rhs
6531 ),
6532 );
6533 let args: Vec<Value> = vec![rhs];
6534 if (std::env::var_os("SIGLUS_TRACE_VM_COMMANDS").is_some()) {
6535 eprintln!(
6536 "[vm form assign] form={} al_id={} elm={:?} rhs={:?}",
6537 form_id,
6538 al_id,
6539 elm,
6540 args.first()
6541 );
6542 }
6543 self.ctx.vm_call = Some(runtime::VmCallMeta {
6544 element: elm.clone(),
6545 al_id: al_id as i64,
6546 ret_form: 0,
6547 });
6548
6549 if !runtime::dispatch_form_code(&mut self.ctx, form_id, &args)? {
6550 self.ctx.vm_call = None;
6551 bail!("unhandled form assignment chain {:?}", elm);
6552 }
6553 self.ctx.vm_call = None;
6554 self.ctx.stack.clear();
6555 self.drain_pending_frame_action_finishes()?;
6556 Ok(())
6557 }
6558
6559 fn dispatch_owner_named_command(
6560 &mut self,
6561 owner: u8,
6562 raw_head: i32,
6563 ret_form: i32,
6564 args: &[Value],
6565 ) -> Result<bool> {
6566 let cmd_no = elm_code::code(raw_head) as u32;
6567 if owner == elm_code::ELM_OWNER_USER_CMD {
6568 let inc_cmd_cnt = self.call_cmd_names.len() as u32;
6580 let local_cmd_no = if cmd_no < inc_cmd_cnt {
6581 let Some(name) = self.call_cmd_names.get(&cmd_no) else {
6582 return Ok(false);
6583 };
6584 match self.user_cmd_names.iter().find_map(|(no, local_name)| {
6585 if local_name.eq_ignore_ascii_case(name) {
6586 Some(*no)
6587 } else {
6588 None
6589 }
6590 }) {
6591 Some(no) => no,
6592 None => return Ok(false),
6593 }
6594 } else {
6595 cmd_no - inc_cmd_cnt
6596 };
6597
6598 let Some(name) = self.user_cmd_names.get(&local_cmd_no).cloned() else {
6599 self.sg_omv_trace(format!(
6600 "USER_CMD unresolved raw_head={} cmd_no={} local_cmd_no={} inc_cmd_cnt={} ret_form={} argc={}",
6601 raw_head,
6602 cmd_no,
6603 local_cmd_no,
6604 inc_cmd_cnt,
6605 ret_form,
6606 args.len()
6607 ));
6608 return Ok(false);
6609 };
6610 let offset = self.stream.scn_cmd_offset(local_cmd_no as usize)?;
6611 self.sg_omv_trace(format!(
6612 "USER_CMD enter name={} raw_head={} cmd_no={} local_cmd_no={} offset=0x{:x} ret_form={} argc={} current_pc=0x{:x}",
6613 name,
6614 raw_head,
6615 cmd_no,
6616 local_cmd_no,
6617 offset,
6618 ret_form,
6619 args.len(),
6620 self.stream.get_prg_cntr()
6621 ));
6622 return self.enter_current_scene_user_cmd_proc_at_offset(
6623 offset,
6624 ret_form,
6625 args,
6626 false,
6627 false,
6628 );
6629 }
6630
6631 let name = match owner {
6632 o if o == elm_code::ELM_OWNER_CALL_CMD => self.call_cmd_names.get(&cmd_no).cloned(),
6633 _ => None,
6634 };
6635 let Some(name) = name else {
6636 return Ok(false);
6637 };
6638 runtime::dispatch_named_command(&mut self.ctx, &name, args)
6639 }
6640
6641 fn command_consumes_read_flag_no(&self, elm: &[i32]) -> bool {
6642 fn global_consumes(op: i32) -> bool {
6643 matches!(
6644 op,
6645 crate::runtime::forms::codes::elm_value::GLOBAL_PRINT
6646 | crate::runtime::forms::codes::elm_value::GLOBAL_SEL
6647 | crate::runtime::forms::codes::elm_value::GLOBAL_SEL_CANCEL
6648 | crate::runtime::forms::codes::elm_value::GLOBAL_SELMSG
6649 | crate::runtime::forms::codes::elm_value::GLOBAL_SELMSG_CANCEL
6650 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN
6651 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN_CANCEL
6652 | crate::runtime::forms::codes::elm_value::GLOBAL_SELBTN_START
6653 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE
6654 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE_PLAY_WAIT
6655 | crate::runtime::forms::codes::elm_value::GLOBAL_KOE_PLAY_WAIT_KEY
6656 )
6657 }
6658
6659 fn mwnd_consumes(op: i32) -> bool {
6660 matches!(
6661 op,
6662 crate::runtime::forms::codes::elm_value::MWND_PRINT
6663 | crate::runtime::forms::codes::elm_value::MWND_SEL
6664 | crate::runtime::forms::codes::elm_value::MWND_SEL_CANCEL
6665 | crate::runtime::forms::codes::elm_value::MWND_SELMSG
6666 | crate::runtime::forms::codes::elm_value::MWND_SELMSG_CANCEL
6667 | crate::runtime::forms::codes::elm_value::MWND_KOE
6668 | crate::runtime::forms::codes::elm_value::MWND_KOE_PLAY_WAIT
6669 | crate::runtime::forms::codes::elm_value::MWND_KOE_PLAY_WAIT_KEY
6670 )
6671 }
6672
6673 for pair in elm.windows(2) {
6679 let form_id = self.canonical_runtime_form_id(pair[0] as u32) as i32;
6680 let op = pair[1];
6681 if form_id == crate::runtime::forms::codes::FM_GLOBAL && global_consumes(op) {
6682 return true;
6683 }
6684 if form_id == crate::runtime::forms::codes::FM_MWND && mwnd_consumes(op) {
6685 return true;
6686 }
6687 }
6688
6689 false
6690 }
6691
6692 fn exec_command(
6693 &mut self,
6694 elm: Vec<i32>,
6695 al_id: i32,
6696 ret_form: i32,
6697 args: &mut Vec<Value>,
6698 ) -> Result<()> {
6699 if elm.is_empty() {
6700 self.push_default_for_ret(ret_form);
6701 return Ok(());
6702 }
6703
6704 if self.exec_call_command(&elm, al_id, ret_form, args)? {
6705 return Ok(());
6706 }
6707
6708 let raw_head = elm[0];
6709 let owner = elm_code::owner(raw_head);
6710
6711 if owner == elm_code::ELM_OWNER_CALL_PROP {
6712 let current_idx = self
6713 .current_call_frame_index()
6714 .ok_or_else(|| anyhow!("call stack underflow"))?;
6715 let call_prop_id = elm_code::code(raw_head) as i32;
6716 let prop_idx = self
6717 .find_call_prop_index_in_frame(current_idx, call_prop_id)
6718 .ok_or_else(|| {
6719 anyhow!("missing direct CALL_PROP command id={} for {:?}", call_prop_id, elm)
6720 })?;
6721 let prop = self.call_stack[current_idx].user_props[prop_idx].clone();
6722 if let Some(composed) = self.compose_call_prop_tail(&prop, &elm[1..]) {
6723 self.exec_command(composed, al_id, ret_form, args)?;
6724 return Ok(());
6725 }
6726 self.push_default_for_ret(ret_form);
6727 return Ok(());
6728 }
6729
6730 if owner == elm_code::ELM_OWNER_USER_PROP {
6731 let prop_id = elm_code::code(raw_head);
6732 if self.exec_user_prop_list_init_command(prop_id, &elm[1..], ret_form)? {
6733 return Ok(());
6734 }
6735 let cell = self
6736 .user_props
6737 .get(&prop_id)
6738 .cloned()
6739 .unwrap_or_else(|| self.default_user_prop_cell(prop_id));
6740 if let Some(composed) = self.compose_user_prop_tail(prop_id, &cell, &elm[1..]) {
6741 self.exec_command(composed, al_id, ret_form, args)?;
6742 return Ok(());
6743 }
6744 self.push_default_for_ret(ret_form);
6745 return Ok(());
6746 }
6747
6748 match owner {
6749 o if o == elm_code::ELM_OWNER_FORM => {
6750 if elm.len() == 1
6754 && elm[0] == crate::runtime::forms::codes::elm_value::GLOBAL_WIPE
6755 && args.is_empty()
6756 && ret_form == self.cfg.fm_void
6757 {
6758 self.vm_trace(None, "suppress bare residual GLOBAL.WIPE command".to_string());
6759 return Ok(());
6760 }
6761
6762 if self.dispatch_global_indexed_list_command_direct(&elm, al_id, ret_form, args)? {
6763 return Ok(());
6764 }
6765 if let Some(synthetic) = self.try_compact_object_chain(&elm, true) {
6766 self.vm_trace(
6767 None,
6768 format!(
6769 "exec_command compact-object elm={:?} synthetic={:?} al_id={} ret_form={} args={:?}",
6770 elm, synthetic, al_id, ret_form, args
6771 ),
6772 );
6773 if Self::sg_mwnd_object_trace_enabled()
6774 && (Self::sg_mwnd_chain_interesting(&elm) || Self::sg_mwnd_chain_interesting(&synthetic))
6775 {
6776 self.sg_mwnd_object_trace(format!(
6777 "exec_command compact elm={:?} synthetic={:?} al_id={} ret_form={} args={:?} current_chain={:?} current_stage_object={:?}",
6778 elm,
6779 synthetic,
6780 al_id,
6781 ret_form,
6782 args,
6783 self.ctx.globals.current_object_chain,
6784 self.ctx.globals.current_stage_object
6785 ));
6786 }
6787 self.ctx.vm_call = Some(runtime::VmCallMeta {
6788 element: synthetic.clone(),
6789 al_id: al_id as i64,
6790 ret_form: ret_form as i64,
6791 });
6792 let form_id = self.canonical_runtime_form_id(synthetic[0] as u32) as i32;
6793 let op_id = if synthetic.len() >= 2 { synthetic[1] } else { al_id };
6794 self.sg_omv_trace_command(
6795 "compact",
6796 &synthetic,
6797 form_id,
6798 op_id,
6799 al_id,
6800 ret_form,
6801 args,
6802 );
6803 if !runtime::dispatch_form_code(&mut self.ctx, form_id as u32, args)? {
6804 self.ctx.vm_call = None;
6805 bail!(
6806 "unhandled compact object command chain {:?} -> {:?}",
6807 elm,
6808 synthetic
6809 );
6810 }
6811 self.ctx.vm_call = None;
6812 self.update_compact_context_from_object_dispatch_chain(&synthetic);
6813 self.drain_pending_frame_action_finishes()?;
6814 if ret_form != self.cfg.fm_void {
6815 if !self.ctx.stack.is_empty() {
6816 self.take_ctx_return(ret_form)?;
6817 return Ok(());
6818 }
6819 if self.ctx.wait_poll() {
6820 if let Some(frame) = self.call_stack.last_mut() {
6821 frame.delayed_ret_form = Some(ret_form);
6822 } else {
6823 self.delayed_ret_form = Some(ret_form);
6824 }
6825 return Ok(());
6826 }
6827 }
6828 self.take_ctx_return(ret_form)?;
6829 return Ok(());
6830 }
6831
6832 let form_id = self.canonical_runtime_form_id(raw_head as u32) as i32;
6833 if self.exec_builtin_global_control(form_id, ret_form)? {
6834 if ret_form != self.cfg.fm_void {
6835 self.take_ctx_return(ret_form)?;
6836 } else {
6837 self.ctx.stack.clear();
6838 }
6839 return Ok(());
6840 }
6841 if self.exec_builtin_scene_form(&elm, form_id, al_id, ret_form, args)? {
6842 return Ok(());
6843 }
6844
6845 let op_id = if elm.len() >= 2 { elm[1] } else { al_id };
6846 self.ctx.vm_call = Some(runtime::VmCallMeta {
6847 element: elm.clone(),
6848 al_id: al_id as i64,
6849 ret_form: ret_form as i64,
6850 });
6851
6852 if (std::env::var_os("SIGLUS_TRACE_VM_COMMANDS").is_some()) {
6853 let elm_tail = elm
6854 .iter()
6855 .map(|v| v.to_string())
6856 .collect::<Vec<_>>()
6857 .join(",");
6858 let args_dbg = args
6859 .iter()
6860 .map(|v| format!("{v:?}"))
6861 .collect::<Vec<_>>()
6862 .join(", ");
6863 eprintln!(
6864 "[vm form cmd] form={} op={} argc={} ret_form={} al_id={} elm=[{}] args=[{}]",
6865 form_id,
6866 op_id,
6867 args.len(),
6868 ret_form,
6869 al_id,
6870 elm_tail,
6871 args_dbg
6872 );
6873 }
6874
6875 self.sg_omv_trace_command(
6876 "dispatch",
6877 &elm,
6878 form_id,
6879 op_id,
6880 al_id,
6881 ret_form,
6882 args,
6883 );
6884
6885 if !runtime::dispatch_form_code(&mut self.ctx, form_id as u32, args)? {
6886 self.ctx.vm_call = None;
6887 bail!("unhandled form command chain {:?}", elm);
6888 }
6889 self.ctx.vm_call = None;
6890 self.drain_pending_frame_action_finishes()?;
6891 }
6892 o if o == elm_code::ELM_OWNER_USER_CMD || o == elm_code::ELM_OWNER_CALL_CMD => {
6893 if (std::env::var_os("SIGLUS_TRACE_VM_COMMANDS").is_some()) {
6894 let cmd_no = elm_code::code(raw_head);
6895 let elm_tail = elm
6896 .iter()
6897 .map(|v| v.to_string())
6898 .collect::<Vec<_>>()
6899 .join(",");
6900 let args_dbg = args
6901 .iter()
6902 .map(|v| format!("{v:?}"))
6903 .collect::<Vec<_>>()
6904 .join(", ");
6905 eprintln!(
6906 "[vm owner cmd] owner={} cmd_no={} argc={} ret_form={} al_id={} elm=[{}] args=[{}]",
6907 owner,
6908 cmd_no,
6909 args.len(),
6910 ret_form,
6911 al_id,
6912 elm_tail,
6913 args_dbg
6914 );
6915 }
6916
6917 if !self.dispatch_owner_named_command(owner, raw_head, ret_form, args)? {
6918 bail!("unhandled owner command chain {:?}", elm);
6919 }
6920 if owner == elm_code::ELM_OWNER_USER_CMD {
6921 return Ok(());
6925 }
6926 }
6927 _ => {
6928 bail!("unsupported command owner {} for element {:?}", owner, elm);
6929 }
6930 }
6931
6932 if ret_form != self.cfg.fm_void {
6933 if !self.ctx.stack.is_empty() {
6934 self.take_ctx_return(ret_form)?;
6935 return Ok(());
6936 }
6937 if self.ctx.wait_poll() {
6938 if let Some(frame) = self.call_stack.last_mut() {
6939 frame.delayed_ret_form = Some(ret_form);
6940 } else {
6941 self.delayed_ret_form = Some(ret_form);
6942 }
6943 return Ok(());
6944 }
6945 }
6946
6947 self.take_ctx_return(ret_form)?;
6948 Ok(())
6949 }
6950
6951
6952 fn save_kind_to_original(kind: RuntimeSaveKind) -> Option<crate::original_save::SaveKind> {
6953 match kind {
6954 RuntimeSaveKind::Normal => Some(crate::original_save::SaveKind::Normal),
6955 RuntimeSaveKind::Quick => Some(crate::original_save::SaveKind::Quick),
6956 RuntimeSaveKind::End => Some(crate::original_save::SaveKind::End),
6957 RuntimeSaveKind::Inner => None,
6958 }
6959 }
6960
6961 fn configured_runtime_save_count(&self, quick: bool) -> usize {
6962 let keys: [&str; 2] = if quick {
6963 ["#QUICK_SAVE.CNT", "QUICK_SAVE.CNT"]
6964 } else {
6965 ["#SAVE.CNT", "SAVE.CNT"]
6966 };
6967 let default_count = if quick { 3 } else { 10 };
6968 self.ctx
6969 .tables
6970 .gameexe
6971 .as_ref()
6972 .and_then(|cfg| keys.iter().find_map(|key| cfg.get_usize(*key)))
6973 .unwrap_or(default_count)
6974 .min(10000)
6975 }
6976
6977 fn runtime_save_file_path(&self, kind: RuntimeSaveKind, index: usize) -> Option<std::path::PathBuf> {
6978 let save_kind = Self::save_kind_to_original(kind)?;
6979 let save_cnt = self.configured_runtime_save_count(false);
6980 let quick_cnt = self.configured_runtime_save_count(true);
6981 Some(crate::original_save::save_file_path_with_counts(
6982 &self.ctx.project_dir,
6983 save_cnt,
6984 quick_cnt,
6985 save_kind,
6986 index,
6987 ))
6988 }
6989
6990 fn stamp_slot_with_local_time(slot: &mut crate::runtime::globals::SaveSlotState) {
6991 let now = crate::platform_time::local_time_fields();
6992 slot.exist = true;
6993 slot.year = now.year as i64;
6994 slot.month = now.month as i64;
6995 slot.day = now.day as i64;
6996 slot.weekday = now.weekday_sunday0 as i64;
6998 slot.hour = now.hour as i64;
6999 slot.minute = now.minute as i64;
7000 slot.second = now.second as i64;
7001 slot.millisecond = now.millisecond as i64;
7002 }
7003
7004 fn ensure_runtime_slot_for_save(&mut self, req: RuntimeSaveRequest) -> crate::runtime::globals::SaveSlotState {
7013 let mut slot = crate::runtime::globals::SaveSlotState::default();
7014 Self::stamp_slot_with_local_time(&mut slot);
7015 if let Some(snapshot) = self.ctx.local_save_snapshot.as_ref() {
7016 slot.title = snapshot.save_scene_title.clone();
7017 slot.message = snapshot.save_msg.clone();
7018 slot.full_message = if snapshot.save_full_msg.is_empty() {
7019 snapshot.save_msg.clone()
7020 } else {
7021 snapshot.save_full_msg.clone()
7022 };
7023 slot.append_dir = snapshot.append_dir.clone();
7024 slot.append_name = snapshot.append_name.clone();
7025 } else {
7026 slot.title = self.ctx.globals.syscom.current_save_scene_title.clone();
7029 slot.message = self.ctx.globals.syscom.current_save_message.clone();
7030 slot.full_message = if self.ctx.globals.syscom.current_save_full_message.is_empty() {
7031 self.ctx.globals.syscom.current_save_message.clone()
7032 } else {
7033 self.ctx.globals.syscom.current_save_full_message.clone()
7034 };
7035 slot.append_dir = self.ctx.globals.append_dir.clone();
7036 slot.append_name = self.ctx.globals.append_name.clone();
7037 }
7038
7039 match req.kind {
7040 RuntimeSaveKind::Normal => {
7041 if self.ctx.globals.syscom.save_slots.len() <= req.index {
7042 self.ctx.globals.syscom.save_slots.resize_with(req.index + 1, Default::default);
7043 }
7044 self.ctx.globals.syscom.save_slots[req.index] = slot.clone();
7045 }
7046 RuntimeSaveKind::Quick => {
7047 if self.ctx.globals.syscom.quick_save_slots.len() <= req.index {
7048 self.ctx.globals.syscom.quick_save_slots.resize_with(req.index + 1, Default::default);
7049 }
7050 self.ctx.globals.syscom.quick_save_slots[req.index] = slot.clone();
7051 }
7052 RuntimeSaveKind::End | RuntimeSaveKind::Inner => {}
7053 }
7054 slot
7055 }
7056
7057 fn local_flag_count(&self) -> usize {
7058 self.ctx
7059 .tables
7060 .gameexe
7061 .as_ref()
7062 .and_then(|cfg| cfg.get_usize("#FLAG.CNT").or_else(|| cfg.get_usize("FLAG.CNT")))
7063 .unwrap_or(1000)
7064 .min(10000)
7065 }
7066
7067 fn mwnd_waku_btn_count(&self) -> usize {
7068 self.ctx
7069 .tables
7070 .gameexe
7071 .as_ref()
7072 .and_then(|cfg| cfg.get_usize("#WAKU.BTN.CNT").or_else(|| cfg.get_usize("WAKU.BTN.CNT")))
7073 .unwrap_or(8)
7074 .min(256)
7075 }
7076
7077 fn int_list_by_element(&self, elm: i32) -> &[i64] {
7078 self.ctx
7079 .globals
7080 .int_lists
7081 .get(&(elm as u32))
7082 .map(Vec::as_slice)
7083 .unwrap_or(&[])
7084 }
7085
7086 fn str_list_by_element(&self, elm: i32) -> &[String] {
7087 self.ctx
7088 .globals
7089 .str_lists
7090 .get(&(elm as u32))
7091 .map(Vec::as_slice)
7092 .unwrap_or(&[])
7093 }
7094
7095 fn build_cpp_local_data_pod(&self) -> Vec<u8> {
7096 let mut out = Vec::with_capacity(356);
7097 let script = &self.ctx.globals.script;
7098 let syscom = &self.ctx.globals.syscom;
7099 let push_i32 = |out: &mut Vec<u8>, v: i64| out.extend_from_slice(&(v as i32).to_le_bytes());
7100 let push_bool = |out: &mut Vec<u8>, v: bool| out.push(if v { 1 } else { 0 });
7101
7102 push_i32(&mut out, 0);
7103 push_i32(&mut out, 0);
7104 push_i32(&mut out, 0);
7105 push_i32(&mut out, 0);
7106 push_i32(&mut out, script.cursor_no);
7107
7108 push_bool(&mut out, syscom.syscom_menu_disable);
7109 push_bool(&mut out, script.hide_mwnd_disable);
7110 push_bool(&mut out, script.msg_back_disable);
7111 push_bool(&mut out, script.shortcut_disable);
7112
7113 push_bool(&mut out, script.skip_disable);
7114 push_bool(&mut out, script.ctrl_disable);
7115 push_bool(&mut out, script.not_stop_skip_by_click);
7116 push_bool(&mut out, script.not_skip_msg_by_click);
7117 push_bool(&mut out, script.skip_unread_message);
7118 push_bool(&mut out, script.auto_mode_flag);
7119 while out.len() % 4 != 0 { out.push(0); }
7120 push_i32(&mut out, script.auto_mode_moji_wait);
7121 push_i32(&mut out, script.auto_mode_min_wait);
7122 push_i32(&mut out, script.auto_mode_moji_cnt);
7123 push_i32(&mut out, script.mouse_cursor_hide_onoff);
7124 push_i32(&mut out, script.mouse_cursor_hide_time);
7125 push_i32(&mut out, 0);
7126
7127 push_i32(&mut out, script.msg_speed);
7128 push_bool(&mut out, script.msg_nowait);
7129 push_bool(&mut out, script.async_msg_mode);
7130 push_bool(&mut out, script.async_msg_mode_once);
7131 push_bool(&mut out, false);
7132 push_bool(&mut out, script.skip_trigger);
7133 push_bool(&mut out, script.koe_dont_stop_on_flag);
7134 push_bool(&mut out, script.koe_dont_stop_off_flag);
7135
7136 push_bool(&mut out, syscom.mwnd_btn_disable_all);
7137 push_bool(&mut out, syscom.mwnd_btn_touch_disable);
7138 push_bool(&mut out, script.mwnd_anime_on_flag);
7139 push_bool(&mut out, script.mwnd_anime_off_flag);
7140 push_bool(&mut out, script.mwnd_disp_off_flag);
7141
7142 push_bool(&mut out, script.msg_back_off);
7143 push_bool(&mut out, script.msg_back_disp_off);
7144 while out.len() % 4 != 0 { out.push(0); }
7145 push_i32(&mut out, script.font_bold);
7146 push_i32(&mut out, script.font_shadow);
7147
7148 push_bool(&mut out, script.cursor_disp_off);
7149 push_bool(&mut out, script.cursor_move_by_key_disable);
7150 for key in 0u16..=255u16 {
7151 push_bool(&mut out, script.key_disable.contains(&(key as u8)));
7152 }
7153
7154 push_bool(&mut out, script.quake_stop_flag);
7155 push_bool(&mut out, script.emote_mouth_stop_flag);
7156 push_bool(&mut out, self.ctx.globals.cg_table_off);
7157 push_bool(&mut out, script.bgmfade_flag);
7158 push_bool(&mut out, script.dont_set_save_point);
7159 push_bool(&mut out, script.ignore_r_flag);
7160 push_bool(&mut out, script.wait_display_vsync_off_flag);
7161
7162 push_bool(&mut out, script.time_stop_flag);
7163 push_bool(&mut out, script.counter_time_stop_flag);
7164 push_bool(&mut out, script.frame_action_time_stop_flag);
7165 push_bool(&mut out, script.stage_time_stop_flag);
7166 while out.len() % 4 != 0 { out.push(0); }
7167 debug_assert_eq!(out.len(), 356);
7168 out
7169 }
7170
7171 fn write_cpp_syscom_menu(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7172 let base = w.position();
7173 let s = &self.ctx.globals.syscom;
7174 let push_ex = |w: &mut crate::original_save::OriginalStreamWriter, exist: bool, enable: bool| {
7175 w.push_bool(exist);
7176 w.push_bool(enable);
7177 };
7178 push_ex(w, s.read_skip.exist, s.read_skip.enable);
7179 push_ex(w, false, false);
7180 push_ex(w, s.auto_skip.exist, s.auto_skip.enable);
7181 push_ex(w, s.auto_mode.exist, s.auto_mode.enable);
7182 push_ex(w, s.hide_mwnd.exist, s.hide_mwnd.enable);
7183 push_ex(w, s.msg_back.exist, s.msg_back.enable);
7184 push_ex(w, s.save_feature.exist, s.save_feature.enable);
7185 push_ex(w, s.load_feature.exist, s.load_feature.enable);
7186 push_ex(w, s.return_to_sel.exist, s.return_to_sel.enable);
7187 push_ex(w, true, true);
7188 push_ex(w, false, false);
7189 push_ex(w, false, false);
7190 push_ex(w, s.return_to_menu.exist, s.return_to_menu.enable);
7191 push_ex(w, s.end_game.exist, s.end_game.enable);
7192 push_ex(w, true, true);
7193 for i in 0..4 {
7194 let sw = s.local_extra_switches.get(i).copied().unwrap_or(if i == 0 { s.local_extra_switch } else { runtime::globals::ToggleFeatureState::default() });
7195 w.push_bool(sw.exist);
7196 w.push_bool(sw.enable);
7197 w.push_bool(sw.onoff);
7198 }
7199 while (w.position() - base) % 4 != 0 { w.push_bool(false); }
7200 for i in 0..4 {
7201 let mode = s.local_extra_modes.get(i).copied().unwrap_or(if i == 0 { s.local_extra_mode } else { runtime::globals::ValueFeatureState::default() });
7202 w.push_bool(mode.exist);
7203 w.push_bool(mode.enable);
7204 w.push_padding(2);
7205 w.push_i32(mode.value as i32);
7206 }
7207 }
7208
7209 fn write_empty_counter_param(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7210 w.push_bool(false);
7211 w.push_bool(false);
7212 w.push_bool(false);
7213 w.push_bool(false);
7214 w.push_i32(0);
7215 w.push_i32(0);
7216 w.push_i32(0);
7217 w.push_i32(0);
7218 }
7219
7220 fn write_empty_frame_action(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7221 w.push_i32(0);
7222 w.push_str("");
7223 w.push_str("");
7224 w.push_i32(0);
7225 self.write_empty_counter_param(w);
7226 }
7227
7228 fn write_empty_btn_select(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7229 w.push_i32(0);
7230 w.push_padding(112);
7231 w.push_bool(false);
7232 w.push_bool(false);
7233 w.push_bool(false);
7234 w.push_bool(false);
7235 w.push_bool(false);
7236 w.push_bool(false);
7237 w.push_str("");
7238 w.push_i32(0);
7239 w.push_i32(0);
7240 }
7241
7242 fn write_empty_stage(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7243 w.push_empty_fixed_array();
7244 w.push_empty_fixed_array();
7245 w.push_empty_fixed_array();
7246 self.write_empty_btn_select(w);
7247 w.push_empty_fixed_array();
7248 w.push_empty_fixed_array();
7249 w.push_empty_fixed_array();
7250 }
7251
7252 fn write_empty_screen(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7253 w.push_empty_fixed_array();
7254 w.push_padding(16);
7255 w.push_empty_fixed_array();
7256 }
7257
7258 fn write_empty_sound(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7259 w.push_str("");
7260 w.push_i32(0);
7261 w.push_i32(0);
7262 w.push_bool(false);
7263 w.push_bool(false);
7264 w.push_i32(0);
7265 w.push_i32(0);
7266 w.push_empty_fixed_array();
7267 w.push_i32(0);
7268 w.push_str("");
7269 }
7270
7271 fn write_empty_msg_back(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7272 w.push_i32(0);
7273 w.push_i32(0);
7274 w.push_i32(0);
7275 w.push_i32(0);
7276 w.push_bool(false);
7277 }
7278
7279 fn write_cpp_prop(&self, w: &mut crate::original_save::OriginalStreamWriter, prop_id: i32, cell: &UserPropCell) {
7280 w.push_i32(prop_id);
7281 w.push_i32(cell.form);
7282 w.push_i32(cell.int_value);
7283 w.push_str(&cell.str_value);
7284 w.push_element(&cell.element);
7285 w.push_extend_items(&cell.list_items, |w, item| self.write_cpp_prop(w, 0, item));
7286 w.push_i32(cell.list_items.len() as i32);
7287 if cell.form == self.cfg.fm_intlist {
7288 let vals: Vec<i64> = cell.int_list.iter().map(|v| *v as i64).collect();
7289 w.push_extend_i32_list(&vals);
7290 } else if cell.form == self.cfg.fm_strlist {
7291 w.push_extend_str_list(&cell.str_list);
7292 }
7293 }
7294
7295 fn read_cpp_prop(&self, rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<(i32, UserPropCell)> {
7296 let prop_id = rd.i32()?;
7297 let form = rd.i32()?;
7298 let int_value = rd.i32()?;
7299 let str_value = rd.string()?;
7300 let element = rd.element()?;
7301 let list_items = rd.extend_items(|rd| {
7302 let (_id, cell) = self.read_cpp_prop(rd)?;
7303 Ok(cell)
7304 })?;
7305 let _exp_cnt = rd.i32()?;
7306 let mut cell = UserPropCell::new(form, element);
7307 cell.int_value = int_value;
7308 cell.str_value = str_value;
7309 cell.list_items = list_items;
7310 if form == self.cfg.fm_intlist {
7311 cell.int_list = rd.extend_i32_list()?.into_iter().map(|v| v as i32).collect();
7312 } else if form == self.cfg.fm_strlist {
7313 cell.str_list = rd.extend_items(|rd| rd.string())?;
7314 }
7315 Ok((prop_id, cell))
7316 }
7317
7318 fn write_cpp_inc_prop_list(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7319 let shared = self.shared_user_prop_count();
7320 let props: Vec<(i32, UserPropCell)> = (0..shared)
7321 .map(|idx| {
7322 let prop_id = idx as u16;
7323 let cell = self.user_props.get(&prop_id).cloned().unwrap_or_else(|| self.default_user_prop_cell(prop_id));
7324 (idx as i32, cell)
7325 })
7326 .collect();
7327 w.push_fixed_items(&props, |w, (id, cell)| self.write_cpp_prop(w, *id, cell));
7328 }
7329
7330 fn read_cpp_inc_prop_list(&mut self, rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<()> {
7331 let props = rd.fixed_items(|rd| self.read_cpp_prop(rd))?;
7332 for (idx, (_stored_id, cell)) in props.into_iter().enumerate() {
7333 self.user_props.insert(idx as u16, cell);
7334 }
7335 Ok(())
7336 }
7337
7338 fn write_cpp_current_scene_prop_lists(&self, w: &mut crate::original_save::OriginalStreamWriter) {
7339 let shared = self.shared_user_prop_count();
7340 let mut props: Vec<(i32, UserPropCell)> = Vec::new();
7341 let scene_prop_cnt = self.stream.header.scn_prop_cnt.max(0) as usize;
7342 for idx in 0..scene_prop_cnt {
7343 let prop_id = (shared + idx) as u16;
7344 if let Some(cell) = self.user_props.get(&prop_id).cloned() {
7345 props.push((idx as i32, cell));
7346 } else if let Some((_, _)) = self.user_prop_decl(prop_id) {
7347 props.push((idx as i32, self.default_user_prop_cell(prop_id)));
7348 }
7349 }
7350 if props.is_empty() {
7351 w.push_i32(0);
7352 return;
7353 }
7354 w.push_i32(1);
7355 w.push_str(self.current_scene_name.as_deref().unwrap_or(""));
7356 w.push_fixed_items(&props, |w, (id, cell)| self.write_cpp_prop(w, *id, cell));
7357 }
7358
7359 fn read_cpp_scene_prop_lists(&mut self, rd: &mut crate::original_save::OriginalStreamReader<'_>, current_scene_name: &str) -> Result<()> {
7360 let shared = self.shared_user_prop_count();
7361 let scene_prop_cnt = rd.i32()?.max(0) as usize;
7362 for _ in 0..scene_prop_cnt {
7363 let scene_name = rd.string()?;
7364 let props = rd.fixed_items(|rd| self.read_cpp_prop(rd))?;
7365 if scene_name == current_scene_name {
7366 for (idx, (_stored_id, cell)) in props.into_iter().enumerate() {
7367 self.user_props.insert((shared + idx) as u16, cell);
7368 }
7369 }
7370 }
7371 Ok(())
7372 }
7373
7374 fn write_cpp_call_prop(&self, w: &mut crate::original_save::OriginalStreamWriter, prop: &CallProp) {
7375 w.push_i32(self.current_scene_no.unwrap_or(0) as i32);
7376 w.push_i32(prop.prop_id);
7377 let mut cell = UserPropCell::new(prop.form, prop.element.clone());
7378 match &prop.value {
7379 CallPropValue::Int(v) => cell.int_value = *v,
7380 CallPropValue::Str(v) => cell.str_value = v.clone(),
7381 CallPropValue::Element(v) => cell.element = v.clone(),
7382 CallPropValue::IntList(v) => cell.int_list = v.clone(),
7383 CallPropValue::StrList(v) => cell.str_list = v.clone(),
7384 }
7385 self.write_cpp_prop(w, prop.prop_id, &cell);
7386 }
7387
7388 fn read_cpp_call_prop(&self, rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<CallProp> {
7389 let _scn_no = rd.i32()?;
7390 let declared_prop_id = rd.i32()?;
7391 let (_stored_id, cell) = self.read_cpp_prop(rd)?;
7392 let value = if cell.form == self.cfg.fm_int {
7393 CallPropValue::Int(cell.int_value)
7394 } else if cell.form == self.cfg.fm_str {
7395 CallPropValue::Str(cell.str_value.clone())
7396 } else if cell.form == self.cfg.fm_intlist {
7397 CallPropValue::IntList(cell.int_list.clone())
7398 } else if cell.form == self.cfg.fm_strlist {
7399 CallPropValue::StrList(cell.str_list.clone())
7400 } else {
7401 CallPropValue::Element(cell.element.clone())
7402 };
7403 Ok(CallProp {
7404 prop_id: declared_prop_id,
7405 form: cell.form,
7406 decl_size: cell.int_list.len().max(cell.str_list.len()).max(cell.list_items.len()),
7407 element: cell.element,
7408 value,
7409 })
7410 }
7411
7412 fn write_cpp_call_frame(&self, w: &mut crate::original_save::OriginalStreamWriter, frame: &CallFrame) {
7413 let l: Vec<i64> = frame.int_args.iter().map(|v| *v as i64).collect();
7414 w.push_extend_i32_list(&l);
7415 w.push_extend_str_list(&frame.str_args);
7416 w.push_extend_items(&frame.user_props, |w, prop| self.write_cpp_call_prop(w, prop));
7417 let call_type = if frame.frame_action_proc { 3 } else if frame.return_pc != 0 { 1 } else { 0 };
7418 w.push_i32(call_type);
7419 w.push_i32(frame.ret_form);
7420 w.push_str(self.current_scene_name.as_deref().unwrap_or(""));
7421 w.push_i32(self.current_line_no);
7422 w.push_i32(frame.return_pc as i32);
7423 }
7424
7425 fn read_cpp_call_frame(&self, rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<CallFrame> {
7426 let int_args: Vec<i32> = rd.extend_i32_list()?.into_iter().map(|v| v as i32).collect();
7427 let str_args: Vec<String> = rd.extend_items(|rd| rd.string())?;
7428 let user_props = rd.extend_items(|rd| self.read_cpp_call_prop(rd))?;
7429 let call_type = rd.i32()?;
7430 let ret_form = rd.i32()?;
7431 let _scn_name = rd.string()?;
7432 let _line_no = rd.i32()?;
7433 let return_pc = rd.i32()?.max(0) as usize;
7434 Ok(CallFrame {
7435 return_pc,
7436 ret_form,
7437 return_override: None,
7438 excall_proc: false,
7439 frame_action_proc: call_type == 3,
7440 arg_cnt: 0,
7441 delayed_ret_form: None,
7442 user_props,
7443 int_args,
7444 str_args,
7445 })
7446 }
7447
7448
7449 fn save_i32(v: i64) -> i32 {
7450 v.clamp(i32::MIN as i64, i32::MAX as i64) as i32
7451 }
7452
7453 fn write_cpp_counter_param(&self, w: &mut crate::original_save::OriginalStreamWriter, c: &runtime::globals::Counter) {
7454 let (is_running, real_flag, frame_mode, frame_loop_flag, frame_start_value, frame_end_value, frame_time, cur_time) = c.save_parts();
7455 w.push_bool(is_running);
7456 w.push_bool(real_flag);
7457 w.push_bool(frame_mode);
7458 w.push_bool(frame_loop_flag);
7459 w.push_i32(Self::save_i32(frame_start_value));
7460 w.push_i32(Self::save_i32(frame_end_value));
7461 w.push_i32(Self::save_i32(frame_time));
7462 w.push_i32(Self::save_i32(cur_time));
7463 }
7464
7465 fn read_cpp_counter_param(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::Counter> {
7466 let is_running = rd.bool()?;
7467 let real_flag = rd.bool()?;
7468 let frame_mode = rd.bool()?;
7469 let frame_loop_flag = rd.bool()?;
7470 let frame_start_value = rd.i32()? as i64;
7471 let frame_end_value = rd.i32()? as i64;
7472 let frame_time = rd.i32()? as i64;
7473 let cur_time = rd.i32()? as i64;
7474 Ok(runtime::globals::Counter::from_save_parts(
7475 is_running,
7476 real_flag,
7477 frame_mode,
7478 frame_loop_flag,
7479 frame_start_value,
7480 frame_end_value,
7481 frame_time,
7482 cur_time,
7483 ))
7484 }
7485
7486 fn write_cpp_value_prop(w: &mut crate::original_save::OriginalStreamWriter, value: &Value) {
7487 use crate::runtime::forms::codes;
7488 w.push_i32(0);
7489 match value {
7490 Value::Str(s) => {
7491 w.push_i32(codes::FM_STR);
7492 w.push_i32(0);
7493 w.push_str(s);
7494 }
7495 Value::Int(v) => {
7496 w.push_i32(codes::FM_INT);
7497 w.push_i32(Self::save_i32(*v));
7498 w.push_str("");
7499 }
7500 _ => {
7501 w.push_i32(codes::FM_INT);
7502 w.push_i32(0);
7503 w.push_str("");
7504 }
7505 }
7506 w.push_empty_element();
7507 w.push_i32(0);
7508 w.push_i32(0);
7509 }
7510
7511 fn read_cpp_value_prop(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<Value> {
7512 use crate::runtime::forms::codes;
7513 let _id = rd.i32()?;
7514 let form = rd.i32()?;
7515 let int_value = rd.i32()?;
7516 let str_value = rd.string()?;
7517 let _element = rd.element()?;
7518 let _exp_list: Vec<Value> = rd.extend_items(|rd| Self::read_cpp_value_prop(rd))?;
7519 let _exp_cnt = rd.i32()?;
7520 if form == codes::FM_INTLIST {
7521 let _ = rd.extend_i32_list()?;
7522 } else if form == codes::FM_STRLIST {
7523 let _ = rd.extend_items(|rd| rd.string())?;
7524 }
7525 if form == codes::FM_STR {
7526 Ok(Value::Str(str_value))
7527 } else {
7528 Ok(Value::Int(int_value as i64))
7529 }
7530 }
7531
7532 fn write_cpp_frame_action(&self, w: &mut crate::original_save::OriginalStreamWriter, fa: &runtime::globals::ObjectFrameActionState) {
7533 w.push_i32(Self::save_i32(fa.end_time));
7534 w.push_str(&fa.scn_name);
7535 w.push_str(&fa.cmd_name);
7536 w.push_extend_items(&fa.args, |w, arg| Self::write_cpp_value_prop(w, arg));
7537 self.write_cpp_counter_param(w, &fa.counter);
7538 }
7539
7540 fn read_cpp_frame_action(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::ObjectFrameActionState> {
7541 let end_time = rd.i32()? as i64;
7542 let scn_name = rd.string()?;
7543 let cmd_name = rd.string()?;
7544 let args = rd.extend_items(|rd| Self::read_cpp_value_prop(rd))?;
7545 let counter = Self::read_cpp_counter_param(rd)?;
7546 Ok(runtime::globals::ObjectFrameActionState {
7547 scn_name,
7548 cmd_name,
7549 counter,
7550 end_time,
7551 real_time_flag: false,
7552 end_flag: false,
7553 args,
7554 })
7555 }
7556
7557 fn write_cpp_int_event_raw(w: &mut crate::original_save::OriginalStreamWriter, e: &runtime::int_event::IntEvent) {
7558 w.push_i32(e.def_value);
7559 w.push_i32(e.value);
7560 w.push_i32(e.cur_time);
7561 w.push_i32(e.end_time);
7562 w.push_i32(e.delay_time);
7563 w.push_i32(e.start_value);
7564 w.push_i32(e.cur_value);
7565 w.push_i32(e.end_value);
7566 w.push_i32(e.loop_type);
7567 w.push_i32(e.speed_type);
7568 w.push_i32(e.real_flag);
7569 }
7570
7571 fn read_cpp_int_event_raw(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::int_event::IntEvent> {
7572 let def_value = rd.i32()?;
7573 Ok(runtime::int_event::IntEvent {
7574 def_value,
7575 value: rd.i32()?,
7576 cur_time: rd.i32()?,
7577 end_time: rd.i32()?,
7578 delay_time: rd.i32()?,
7579 start_value: rd.i32()?,
7580 cur_value: rd.i32()?,
7581 end_value: rd.i32()?,
7582 loop_type: rd.i32()?,
7583 speed_type: rd.i32()?,
7584 real_flag: rd.i32()?,
7585 })
7586 }
7587
7588 fn write_cpp_save_event(w: &mut crate::original_save::OriginalStreamWriter, e: &runtime::int_event::IntEvent) {
7589 w.push_i32(e.loop_type);
7590 if e.loop_type != -1 {
7591 Self::write_cpp_int_event_raw(w, e);
7592 } else {
7593 w.push_i32(e.value);
7594 }
7595 }
7596
7597 fn read_cpp_save_event(rd: &mut crate::original_save::OriginalStreamReader<'_>, def_value: i32) -> Result<runtime::int_event::IntEvent> {
7598 let loop_type = rd.i32()?;
7599 if loop_type != -1 {
7600 let mut e = Self::read_cpp_int_event_raw(rd)?;
7601 e.loop_type = loop_type;
7602 Ok(e)
7603 } else {
7604 let mut e = runtime::int_event::IntEvent::new(def_value);
7605 e.loop_type = -1;
7606 e.value = rd.i32()?;
7607 e.cur_value = e.value;
7608 Ok(e)
7609 }
7610 }
7611
7612 fn write_cpp_int_event_extend_list(&self, w: &mut crate::original_save::OriginalStreamWriter, values: &[runtime::int_event::IntEvent]) {
7613 w.push_extend_items(values, |w, e| Self::write_cpp_int_event_raw(w, e));
7614 }
7615
7616 fn read_cpp_int_event_extend_list(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<Vec<runtime::int_event::IntEvent>> {
7617 rd.extend_items(|rd| Self::read_cpp_int_event_raw(rd))
7618 }
7619
7620 fn write_cpp_group(&self, w: &mut crate::original_save::OriginalStreamWriter, g: &runtime::globals::GroupState) {
7621 w.push_i32(Self::save_i32(g.order));
7622 w.push_i32(Self::save_i32(g.layer));
7623 w.push_i32(Self::save_i32(g.cancel_priority));
7624 w.push_i32(Self::save_i32(g.cancel_se_no));
7625 w.push_i32(Self::save_i32(g.decided_button_no));
7626 w.push_i32(Self::save_i32(g.result));
7627 w.push_i32(Self::save_i32(g.result_button_no));
7628 w.push_bool(g.started);
7629 w.push_bool(false);
7630 w.push_bool(g.wait_flag);
7631 w.push_bool(g.cancel_flag);
7632 w.push_empty_element();
7633 }
7634
7635 fn read_cpp_group(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::GroupState> {
7636 let mut g = runtime::globals::GroupState::default();
7637 g.order = rd.i32()? as i64;
7638 g.layer = rd.i32()? as i64;
7639 g.cancel_priority = rd.i32()? as i64;
7640 g.cancel_se_no = rd.i32()? as i64;
7641 g.decided_button_no = rd.i32()? as i64;
7642 g.result = rd.i32()? as i64;
7643 g.result_button_no = rd.i32()? as i64;
7644 g.started = rd.bool()?;
7645 let _pause_flag = rd.bool()?;
7646 g.wait_flag = rd.bool()?;
7647 g.cancel_flag = rd.bool()?;
7648 let _target_object = rd.element()?;
7649 Ok(g)
7650 }
7651
7652 fn write_cpp_object(&self, w: &mut crate::original_save::OriginalStreamWriter, obj: &runtime::globals::ObjectState) {
7653 let b = &obj.base;
7654 let ev = &obj.runtime.prop_events;
7655 w.push_i32(Self::save_i32(obj.object_type));
7656 w.push_i32(Self::save_i32(b.wipe_copy));
7657 w.push_i32(Self::save_i32(b.wipe_erase));
7658 w.push_i32(Self::save_i32(b.click_disable));
7659 w.push_i32(0); w.push_i32(0); w.push_i32(0); w.push_i32(0); w.push_i32(0);
7661 w.push_i32(Self::save_i32(obj.string_param.moji_size));
7663 w.push_i32(Self::save_i32(obj.string_param.moji_space_x));
7664 w.push_i32(Self::save_i32(obj.string_param.moji_space_y));
7665 w.push_i32(Self::save_i32(obj.string_param.moji_cnt));
7666 w.push_i32(Self::save_i32(obj.string_param.moji_color));
7667 w.push_i32(Self::save_i32(obj.string_param.shadow_color));
7668 w.push_i32(Self::save_i32(obj.string_param.fuchi_color));
7669 w.push_i32(Self::save_i32(obj.string_param.shadow_mode));
7670 w.push_i32(Self::save_i32(obj.number_value));
7672 w.push_i32(Self::save_i32(obj.number_param.keta_max));
7673 w.push_i32(Self::save_i32(obj.number_param.disp_zero));
7674 w.push_i32(Self::save_i32(obj.number_param.disp_sign));
7675 w.push_i32(Self::save_i32(obj.number_param.tumeru_sign));
7676 w.push_i32(Self::save_i32(obj.number_param.space_mod));
7677 w.push_i32(Self::save_i32(obj.number_param.space));
7678 if obj.object_type == 4 {
7679 let wp = &obj.weather_param;
7680 for v in [wp.weather_type, wp.cnt, wp.pat_mode, wp.pat_no_00, wp.pat_no_01, wp.pat_time, wp.move_time_x, wp.move_time_y, wp.sin_time_x, wp.sin_time_y, wp.sin_power_x, wp.sin_power_y, wp.center_x, wp.center_y, wp.center_rotate, wp.appear_range, wp.zoom_min, wp.zoom_max] {
7681 w.push_i32(Self::save_i32(v));
7682 }
7683 Self::write_cpp_int_event_raw(w, &ev.color_add_r);
7684 Self::write_cpp_int_event_raw(w, &ev.color_add_g);
7685 Self::write_cpp_int_event_raw(w, &ev.color_add_b);
7686 for v in [b.mask_no, b.tonecurve_no, b.light_no, b.fog_use, b.culling, b.alpha_test, b.alpha_blend, b.blend, 0] {
7687 w.push_i32(Self::save_i32(v));
7688 }
7689 }
7690 w.push_i32(Self::save_i32(obj.thumb_save_no));
7691 w.push_bool(obj.movie.loop_flag);
7692 w.push_bool(obj.movie.auto_free_flag);
7693 w.push_bool(obj.movie.real_time_flag);
7694 w.push_bool(obj.movie.pause_flag);
7695 if obj.object_type == 10 {
7696 w.push_i32(Self::save_i32(obj.emote.width));
7697 w.push_i32(Self::save_i32(obj.emote.height));
7698 for _ in 0..8 { w.push_i32(0); }
7699 w.push_i32(0);
7700 w.push_i32(0);
7701 w.push_i32(Self::save_i32(obj.emote.rep_x));
7702 w.push_i32(Self::save_i32(obj.emote.rep_y));
7703 }
7704 if obj.button.enabled {
7705 w.push_i32(1);
7706 w.push_i32(Self::save_i32(obj.button.sys_type));
7707 w.push_i32(Self::save_i32(obj.button.sys_type_opt));
7708 w.push_i32(Self::save_i32(obj.button.action_no));
7709 w.push_i32(Self::save_i32(obj.button.se_no));
7710 w.push_i32(Self::save_i32(obj.button.button_no));
7711 w.push_empty_element();
7712 w.push_i32(if obj.button.push_keep { 1 } else { 0 });
7713 w.push_i32(Self::save_i32(obj.button.state));
7714 w.push_i32(Self::save_i32(obj.button.mode));
7715 w.push_i32(Self::save_i32(obj.button.cut_no));
7716 w.push_i32(-1);
7717 w.push_i32(-1);
7718 w.push_i32(Self::save_i32(obj.button.decided_action_z_no));
7719 w.push_i32(0);
7720 w.push_i32(if obj.button.alpha_test { 1 } else { 0 });
7721 } else {
7722 w.push_i32(0);
7723 }
7724 w.push_i32(Self::save_i32(b.disp));
7725 w.push_i32(Self::save_i32(b.patno));
7726 w.push_i32(Self::save_i32(b.order));
7727 w.push_i32(Self::save_i32(b.layer));
7728 w.push_i32(Self::save_i32(b.world));
7729 w.push_i32(Self::save_i32(b.child_sort_type));
7730 for e in [&ev.x, &ev.y, &ev.z, &ev.center_x, &ev.center_y, &ev.center_z, &ev.center_rep_x, &ev.center_rep_y, &ev.center_rep_z, &ev.scale_x, &ev.scale_y, &ev.scale_z, &ev.rotate_x, &ev.rotate_y, &ev.rotate_z] {
7731 Self::write_cpp_save_event(w, e);
7732 }
7733 w.push_i32(Self::save_i32(b.clip_use));
7734 for e in [&ev.clip_left, &ev.clip_top, &ev.clip_right, &ev.clip_bottom] { Self::write_cpp_save_event(w, e); }
7735 w.push_i32(Self::save_i32(b.src_clip_use));
7736 for e in [&ev.src_clip_left, &ev.src_clip_top, &ev.src_clip_right, &ev.src_clip_bottom, &ev.tr, &ev.mono, &ev.reverse, &ev.bright, &ev.dark, &ev.color_r, &ev.color_g, &ev.color_b, &ev.color_rate, &ev.color_add_r, &ev.color_add_g, &ev.color_add_b] {
7737 Self::write_cpp_save_event(w, e);
7738 }
7739 for v in [b.mask_no, b.tonecurve_no, b.light_no, b.fog_use, b.culling, b.alpha_test, b.alpha_blend, b.blend, 0] {
7740 w.push_i32(Self::save_i32(v));
7741 }
7742 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.x_rep);
7743 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.y_rep);
7744 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.z_rep);
7745 self.write_cpp_int_event_extend_list(w, &obj.runtime.prop_event_lists.tr_rep);
7746 w.push_extend_i32_list(&obj.runtime.prop_lists.f);
7747 w.push_str(obj.file_name.as_deref().unwrap_or(""));
7748 w.push_str(obj.string_value.as_deref().unwrap_or(""));
7749 w.push_str(&obj.button.decided_action_scn_name);
7750 w.push_str(&obj.button.decided_action_cmd_name);
7751 if obj.object_type == 10 { for _ in 0..8 { w.push_str(""); } }
7752 self.write_cpp_frame_action(w, &obj.frame_action);
7753 w.push_extend_items(&obj.frame_action_ch, |w, fa| self.write_cpp_frame_action(w, fa));
7754 w.push_str(obj.gan_file.as_deref().unwrap_or(""));
7755 w.push_extend_items(&obj.runtime.child_objects, |w, child| self.write_cpp_object(w, child));
7756 }
7757
7758 fn read_cpp_object(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::ObjectState> {
7759 let mut obj = runtime::globals::ObjectState::default();
7760 obj.object_type = rd.i32()? as i64;
7761 obj.base.wipe_copy = rd.i32()? as i64;
7762 obj.base.wipe_erase = rd.i32()? as i64;
7763 obj.base.click_disable = rd.i32()? as i64;
7764 rd.skip(20)?;
7765 obj.string_param.moji_size = rd.i32()? as i64;
7766 obj.string_param.moji_space_x = rd.i32()? as i64;
7767 obj.string_param.moji_space_y = rd.i32()? as i64;
7768 obj.string_param.moji_cnt = rd.i32()? as i64;
7769 obj.string_param.moji_color = rd.i32()? as i64;
7770 obj.string_param.shadow_color = rd.i32()? as i64;
7771 obj.string_param.fuchi_color = rd.i32()? as i64;
7772 obj.string_param.shadow_mode = rd.i32()? as i64;
7773 obj.number_value = rd.i32()? as i64;
7774 obj.number_param.keta_max = rd.i32()? as i64;
7775 obj.number_param.disp_zero = rd.i32()? as i64;
7776 obj.number_param.disp_sign = rd.i32()? as i64;
7777 obj.number_param.tumeru_sign = rd.i32()? as i64;
7778 obj.number_param.space_mod = rd.i32()? as i64;
7779 obj.number_param.space = rd.i32()? as i64;
7780 if obj.object_type == 4 {
7781 obj.weather_param.weather_type = rd.i32()? as i64;
7782 obj.weather_param.cnt = rd.i32()? as i64;
7783 obj.weather_param.pat_mode = rd.i32()? as i64;
7784 obj.weather_param.pat_no_00 = rd.i32()? as i64;
7785 obj.weather_param.pat_no_01 = rd.i32()? as i64;
7786 obj.weather_param.pat_time = rd.i32()? as i64;
7787 obj.weather_param.move_time_x = rd.i32()? as i64;
7788 obj.weather_param.move_time_y = rd.i32()? as i64;
7789 obj.weather_param.sin_time_x = rd.i32()? as i64;
7790 obj.weather_param.sin_time_y = rd.i32()? as i64;
7791 obj.weather_param.sin_power_x = rd.i32()? as i64;
7792 obj.weather_param.sin_power_y = rd.i32()? as i64;
7793 obj.weather_param.center_x = rd.i32()? as i64;
7794 obj.weather_param.center_y = rd.i32()? as i64;
7795 obj.weather_param.center_rotate = rd.i32()? as i64;
7796 obj.weather_param.appear_range = rd.i32()? as i64;
7797 obj.weather_param.zoom_min = rd.i32()? as i64;
7798 obj.weather_param.zoom_max = rd.i32()? as i64;
7799 obj.runtime.prop_events.color_add_r = Self::read_cpp_int_event_raw(rd)?;
7800 obj.runtime.prop_events.color_add_g = Self::read_cpp_int_event_raw(rd)?;
7801 obj.runtime.prop_events.color_add_b = Self::read_cpp_int_event_raw(rd)?;
7802 obj.base.mask_no = rd.i32()? as i64;
7803 obj.base.tonecurve_no = rd.i32()? as i64;
7804 obj.base.light_no = rd.i32()? as i64;
7805 obj.base.fog_use = rd.i32()? as i64;
7806 obj.base.culling = rd.i32()? as i64;
7807 obj.base.alpha_test = rd.i32()? as i64;
7808 obj.base.alpha_blend = rd.i32()? as i64;
7809 obj.base.blend = rd.i32()? as i64;
7810 let _ = rd.i32()?;
7811 }
7812 obj.thumb_save_no = rd.i32()? as i64;
7813 obj.movie.loop_flag = rd.bool()?;
7814 obj.movie.auto_free_flag = rd.bool()?;
7815 obj.movie.real_time_flag = rd.bool()?;
7816 obj.movie.pause_flag = rd.bool()?;
7817 if obj.object_type == 10 {
7818 obj.emote.width = rd.i32()? as i64;
7819 obj.emote.height = rd.i32()? as i64;
7820 rd.skip(8 * 4)?;
7821 let _ = rd.i32()?;
7822 let _ = rd.i32()?;
7823 obj.emote.rep_x = rd.i32()? as i64;
7824 obj.emote.rep_y = rd.i32()? as i64;
7825 }
7826 let button_exist = rd.i32()? != 0;
7827 if button_exist {
7828 obj.button.enabled = true;
7829 obj.button.sys_type = rd.i32()? as i64;
7830 obj.button.sys_type_opt = rd.i32()? as i64;
7831 obj.button.action_no = rd.i32()? as i64;
7832 obj.button.se_no = rd.i32()? as i64;
7833 obj.button.button_no = rd.i32()? as i64;
7834 rd.skip_element()?;
7835 obj.button.push_keep = rd.i32()? != 0;
7836 obj.button.state = rd.i32()? as i64;
7837 obj.button.mode = rd.i32()? as i64;
7838 obj.button.cut_no = rd.i32()? as i64;
7839 let _ = rd.i32()?;
7840 let _ = rd.i32()?;
7841 obj.button.decided_action_z_no = rd.i32()? as i64;
7842 let _ = rd.i32()?;
7843 obj.button.alpha_test = rd.i32()? != 0;
7844 }
7845 obj.base.disp = rd.i32()? as i64;
7846 obj.base.patno = rd.i32()? as i64;
7847 obj.base.order = rd.i32()? as i64;
7848 obj.base.layer = rd.i32()? as i64;
7849 obj.base.world = rd.i32()? as i64;
7850 obj.base.child_sort_type = rd.i32()? as i64;
7851 obj.runtime.prop_events.x = Self::read_cpp_save_event(rd, 0)?;
7852 obj.runtime.prop_events.y = Self::read_cpp_save_event(rd, 0)?;
7853 obj.runtime.prop_events.z = Self::read_cpp_save_event(rd, 0)?;
7854 obj.runtime.prop_events.center_x = Self::read_cpp_save_event(rd, 0)?;
7855 obj.runtime.prop_events.center_y = Self::read_cpp_save_event(rd, 0)?;
7856 obj.runtime.prop_events.center_z = Self::read_cpp_save_event(rd, 0)?;
7857 obj.runtime.prop_events.center_rep_x = Self::read_cpp_save_event(rd, 0)?;
7858 obj.runtime.prop_events.center_rep_y = Self::read_cpp_save_event(rd, 0)?;
7859 obj.runtime.prop_events.center_rep_z = Self::read_cpp_save_event(rd, 0)?;
7860 obj.runtime.prop_events.scale_x = Self::read_cpp_save_event(rd, 1000)?;
7861 obj.runtime.prop_events.scale_y = Self::read_cpp_save_event(rd, 1000)?;
7862 obj.runtime.prop_events.scale_z = Self::read_cpp_save_event(rd, 1000)?;
7863 obj.runtime.prop_events.rotate_x = Self::read_cpp_save_event(rd, 0)?;
7864 obj.runtime.prop_events.rotate_y = Self::read_cpp_save_event(rd, 0)?;
7865 obj.runtime.prop_events.rotate_z = Self::read_cpp_save_event(rd, 0)?;
7866 obj.base.clip_use = rd.i32()? as i64;
7867 obj.runtime.prop_events.clip_left = Self::read_cpp_save_event(rd, 0)?;
7868 obj.runtime.prop_events.clip_top = Self::read_cpp_save_event(rd, 0)?;
7869 obj.runtime.prop_events.clip_right = Self::read_cpp_save_event(rd, 0)?;
7870 obj.runtime.prop_events.clip_bottom = Self::read_cpp_save_event(rd, 0)?;
7871 obj.base.src_clip_use = rd.i32()? as i64;
7872 obj.runtime.prop_events.src_clip_left = Self::read_cpp_save_event(rd, 0)?;
7873 obj.runtime.prop_events.src_clip_top = Self::read_cpp_save_event(rd, 0)?;
7874 obj.runtime.prop_events.src_clip_right = Self::read_cpp_save_event(rd, 0)?;
7875 obj.runtime.prop_events.src_clip_bottom = Self::read_cpp_save_event(rd, 0)?;
7876 obj.runtime.prop_events.tr = Self::read_cpp_save_event(rd, 255)?;
7877 obj.runtime.prop_events.mono = Self::read_cpp_save_event(rd, 0)?;
7878 obj.runtime.prop_events.reverse = Self::read_cpp_save_event(rd, 0)?;
7879 obj.runtime.prop_events.bright = Self::read_cpp_save_event(rd, 0)?;
7880 obj.runtime.prop_events.dark = Self::read_cpp_save_event(rd, 0)?;
7881 obj.runtime.prop_events.color_r = Self::read_cpp_save_event(rd, 0)?;
7882 obj.runtime.prop_events.color_g = Self::read_cpp_save_event(rd, 0)?;
7883 obj.runtime.prop_events.color_b = Self::read_cpp_save_event(rd, 0)?;
7884 obj.runtime.prop_events.color_rate = Self::read_cpp_save_event(rd, 0)?;
7885 obj.runtime.prop_events.color_add_r = Self::read_cpp_save_event(rd, 0)?;
7886 obj.runtime.prop_events.color_add_g = Self::read_cpp_save_event(rd, 0)?;
7887 obj.runtime.prop_events.color_add_b = Self::read_cpp_save_event(rd, 0)?;
7888 obj.base.mask_no = rd.i32()? as i64;
7889 obj.base.tonecurve_no = rd.i32()? as i64;
7890 obj.base.light_no = rd.i32()? as i64;
7891 obj.base.fog_use = rd.i32()? as i64;
7892 obj.base.culling = rd.i32()? as i64;
7893 obj.base.alpha_test = rd.i32()? as i64;
7894 obj.base.alpha_blend = rd.i32()? as i64;
7895 obj.base.blend = rd.i32()? as i64;
7896 let _flags = rd.i32()?;
7897 obj.runtime.prop_event_lists.x_rep = Self::read_cpp_int_event_extend_list(rd)?;
7898 obj.runtime.prop_event_lists.y_rep = Self::read_cpp_int_event_extend_list(rd)?;
7899 obj.runtime.prop_event_lists.z_rep = Self::read_cpp_int_event_extend_list(rd)?;
7900 obj.runtime.prop_event_lists.tr_rep = Self::read_cpp_int_event_extend_list(rd)?;
7901 obj.runtime.prop_lists.f = rd.extend_i32_list()?;
7902 let file_name = rd.string()?;
7903 obj.file_name = if file_name.is_empty() { None } else { Some(file_name) };
7904 let string_value = rd.string()?;
7905 obj.string_value = if string_value.is_empty() { None } else { Some(string_value) };
7906 obj.button.decided_action_scn_name = rd.string()?;
7907 obj.button.decided_action_cmd_name = rd.string()?;
7908 if obj.object_type == 10 { for _ in 0..8 { let _ = rd.string()?; } }
7909 obj.frame_action = Self::read_cpp_frame_action(rd)?;
7910 obj.frame_action_ch = rd.extend_items(|rd| Self::read_cpp_frame_action(rd))?;
7911 let gan_file = rd.string()?;
7912 obj.gan_file = if gan_file.is_empty() { None } else { Some(gan_file) };
7913 obj.runtime.child_objects = rd.extend_items(|rd| Self::read_cpp_object(rd))?;
7914 obj.used = obj.object_type != 0 || obj.file_name.is_some() || obj.string_value.is_some();
7915 Ok(obj)
7916 }
7917
7918 fn write_cpp_mwnd(&self, w: &mut crate::original_save::OriginalStreamWriter, m: &runtime::globals::MwndState) {
7919 w.push_i32(Self::save_i32(m.world));
7920 w.push_i32(Self::save_i32(m.layer));
7921 w.push_i32(if m.open { 1 } else { 0 });
7922 w.push_i32(Self::save_i32(m.window_pos.map(|p| p.0).unwrap_or(0)));
7923 w.push_i32(Self::save_i32(m.window_pos.map(|p| p.1).unwrap_or(0)));
7924 w.push_i32(Self::save_i32(m.window_size.map(|p| p.0).unwrap_or(0)));
7925 w.push_i32(Self::save_i32(m.window_size.map(|p| p.1).unwrap_or(0)));
7926 w.push_i32(Self::save_i32(m.open_anime_type));
7927 w.push_i32(Self::save_i32(m.open_anime_time));
7928 w.push_i32(Self::save_i32(m.close_anime_type));
7929 w.push_i32(Self::save_i32(m.close_anime_time));
7930 w.push_i64(0);
7931 w.push_bool(m.msg_block_started);
7932 w.push_bool(false);
7933 w.push_bool(m.open);
7934 w.push_bool(!m.name_text.is_empty());
7935 w.push_bool(m.clear_ready);
7936 w.push_padding(3);
7937 w.push_i32(0); w.push_i32(0); w.push_i32(0);
7938 w.push_bool(m.slide_msg);
7939 w.push_padding(3);
7940 w.push_i32(Self::save_i32(m.slide_time));
7941 w.push_i32(m.koe.map(|p| Self::save_i32(p.0)).unwrap_or(0));
7942 w.push_bool(m.koe.is_some());
7943 w.push_padding(3);
7944 w.push_i32(Self::save_i32(m.open_anime_type));
7945 w.push_i32(Self::save_i32(m.open_anime_time));
7946 w.push_i32(0);
7947 w.push_i32(Self::save_i32(m.close_anime_type));
7948 w.push_i32(Self::save_i32(m.close_anime_time));
7949 w.push_i32(0);
7950 w.push_i32(1);
7951 w.push_bool(false);
7952 w.push_padding(3);
7953 w.push_str(&m.msg_text);
7954 w.push_str(&m.waku_file);
7955 w.push_str(&m.name_text);
7956 w.push_i32(0);
7957 w.push_i32(0);
7958 w.push_i32(0);
7959 w.push_extend_items(&m.object_list, |w, obj| self.write_cpp_object(w, obj));
7960 w.push_extend_items(&m.button_list, |w, obj| self.write_cpp_object(w, obj));
7961 w.push_extend_items(&m.face_list, |w, obj| self.write_cpp_object(w, obj));
7962 }
7963
7964 fn read_cpp_mwnd(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::MwndState> {
7965 let mut m = runtime::globals::MwndState::default();
7966 m.world = rd.i32()? as i64;
7967 m.layer = rd.i32()? as i64;
7968 m.open = rd.i32()? != 0;
7969 let wx = rd.i32()? as i64;
7970 let wy = rd.i32()? as i64;
7971 m.window_pos = Some((wx, wy));
7972 let ww = rd.i32()? as i64;
7973 let wh = rd.i32()? as i64;
7974 m.window_size = Some((ww, wh));
7975 m.open_anime_type = rd.i32()? as i64;
7976 m.open_anime_time = rd.i32()? as i64;
7977 m.close_anime_type = rd.i32()? as i64;
7978 m.close_anime_time = rd.i32()? as i64;
7979 let _ = rd.i64()?;
7980 m.msg_block_started = rd.bool()?;
7981 let _ = rd.bool()?;
7982 m.open = rd.bool()?;
7983 let _ = rd.bool()?;
7984 m.clear_ready = rd.bool()?;
7985 rd.skip(3)?;
7986 let _ = rd.i32()?; let _ = rd.i32()?; let _ = rd.i32()?;
7987 m.slide_msg = rd.bool()?;
7988 rd.skip(3)?;
7989 m.slide_time = rd.i32()? as i64;
7990 let koe_no = rd.i32()? as i64;
7991 let koe_play = rd.bool()?;
7992 rd.skip(3)?;
7993 if koe_play { m.koe = Some((koe_no, 0)); }
7994 m.open_anime_type = rd.i32()? as i64;
7995 m.open_anime_time = rd.i32()? as i64;
7996 let _ = rd.i32()?;
7997 m.close_anime_type = rd.i32()? as i64;
7998 m.close_anime_time = rd.i32()? as i64;
7999 let _ = rd.i32()?;
8000 let _ = rd.i32()?;
8001 let _ = rd.bool()?;
8002 rd.skip(3)?;
8003 m.msg_text = rd.string()?;
8004 m.waku_file = rd.string()?;
8005 m.name_text = rd.string()?;
8006 let _ = rd.i32()?; let _ = rd.i32()?; let _ = rd.i32()?;
8007 m.object_list = rd.extend_items(|rd| Self::read_cpp_object(rd))?;
8008 m.button_list = rd.extend_items(|rd| Self::read_cpp_object(rd))?;
8009 m.face_list = rd.extend_items(|rd| Self::read_cpp_object(rd))?;
8010 Ok(m)
8011 }
8012
8013 fn write_cpp_world(&self, w: &mut crate::original_save::OriginalStreamWriter, world: &runtime::globals::WorldState) {
8014 w.push_i32(world.mode);
8015 for e in [&world.camera_eye_x, &world.camera_eye_y, &world.camera_eye_z, &world.camera_pint_x, &world.camera_pint_y, &world.camera_pint_z, &world.camera_up_x, &world.camera_up_y, &world.camera_up_z] {
8016 Self::write_cpp_int_event_raw(w, e);
8017 }
8018 for v in [world.camera_view_angle, world.mono, world.order, world.layer, world.wipe_copy, world.wipe_erase] { w.push_i32(v); }
8019 }
8020
8021 fn read_cpp_world(rd: &mut crate::original_save::OriginalStreamReader<'_>, world_no: i32) -> Result<runtime::globals::WorldState> {
8022 let mut world = runtime::globals::WorldState::new(world_no);
8023 world.mode = rd.i32()?;
8024 world.camera_eye_x = Self::read_cpp_int_event_raw(rd)?;
8025 world.camera_eye_y = Self::read_cpp_int_event_raw(rd)?;
8026 world.camera_eye_z = Self::read_cpp_int_event_raw(rd)?;
8027 world.camera_pint_x = Self::read_cpp_int_event_raw(rd)?;
8028 world.camera_pint_y = Self::read_cpp_int_event_raw(rd)?;
8029 world.camera_pint_z = Self::read_cpp_int_event_raw(rd)?;
8030 world.camera_up_x = Self::read_cpp_int_event_raw(rd)?;
8031 world.camera_up_y = Self::read_cpp_int_event_raw(rd)?;
8032 world.camera_up_z = Self::read_cpp_int_event_raw(rd)?;
8033 world.camera_view_angle = rd.i32()?;
8034 world.mono = rd.i32()?;
8035 world.order = rd.i32()?;
8036 world.layer = rd.i32()?;
8037 world.wipe_copy = rd.i32()?;
8038 world.wipe_erase = rd.i32()?;
8039 Ok(world)
8040 }
8041
8042 fn write_cpp_effect(&self, w: &mut crate::original_save::OriginalStreamWriter, e: &runtime::globals::ScreenEffectState) {
8043 for ev in [&e.x, &e.y, &e.z, &e.mono, &e.reverse, &e.bright, &e.dark, &e.color_r, &e.color_g, &e.color_b, &e.color_rate, &e.color_add_r, &e.color_add_g, &e.color_add_b] {
8044 Self::write_cpp_int_event_raw(w, ev);
8045 }
8046 for v in [e.begin_order, e.end_order, e.begin_layer, e.end_layer, e.wipe_copy, e.wipe_erase] { w.push_i32(v); }
8047 }
8048
8049 fn read_cpp_effect(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::ScreenEffectState> {
8050 let mut e = runtime::globals::ScreenEffectState::default();
8051 e.x = Self::read_cpp_int_event_raw(rd)?;
8052 e.y = Self::read_cpp_int_event_raw(rd)?;
8053 e.z = Self::read_cpp_int_event_raw(rd)?;
8054 e.mono = Self::read_cpp_int_event_raw(rd)?;
8055 e.reverse = Self::read_cpp_int_event_raw(rd)?;
8056 e.bright = Self::read_cpp_int_event_raw(rd)?;
8057 e.dark = Self::read_cpp_int_event_raw(rd)?;
8058 e.color_r = Self::read_cpp_int_event_raw(rd)?;
8059 e.color_g = Self::read_cpp_int_event_raw(rd)?;
8060 e.color_b = Self::read_cpp_int_event_raw(rd)?;
8061 e.color_rate = Self::read_cpp_int_event_raw(rd)?;
8062 e.color_add_r = Self::read_cpp_int_event_raw(rd)?;
8063 e.color_add_g = Self::read_cpp_int_event_raw(rd)?;
8064 e.color_add_b = Self::read_cpp_int_event_raw(rd)?;
8065 e.begin_order = rd.i32()?;
8066 e.end_order = rd.i32()?;
8067 e.begin_layer = rd.i32()?;
8068 e.end_layer = rd.i32()?;
8069 e.wipe_copy = rd.i32()?;
8070 e.wipe_erase = rd.i32()?;
8071 Ok(e)
8072 }
8073
8074 fn write_cpp_quake(&self, w: &mut crate::original_save::OriginalStreamWriter, q: &runtime::globals::ScreenQuakeState) {
8075 w.push_i32(q.quake_type);
8076 w.push_i32(q.vec);
8077 w.push_i32(q.power);
8078 w.push_i32(0);
8079 w.push_i32(0);
8080 w.push_i32(if q.ending { 1 } else { 0 });
8081 w.push_i32(0);
8082 w.push_i32(0);
8083 w.push_i32(0);
8084 w.push_i32(0);
8085 w.push_i32(q.center_x);
8086 w.push_i32(q.center_y);
8087 w.push_i32(q.begin_order);
8088 w.push_i32(q.end_order);
8089 }
8090
8091 fn read_cpp_quake(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::ScreenQuakeState> {
8092 let mut q = runtime::globals::ScreenQuakeState::default();
8093 q.quake_type = rd.i32()?;
8094 q.vec = rd.i32()?;
8095 q.power = rd.i32()?;
8096 let _cur_time = rd.i32()?;
8097 let _total_time = rd.i32()?;
8098 q.ending = rd.i32()? != 0;
8099 let _end_cur_time = rd.i32()?;
8100 let _end_total_time = rd.i32()?;
8101 let _cnt = rd.i32()?;
8102 let _end_cnt = rd.i32()?;
8103 q.center_x = rd.i32()?;
8104 q.center_y = rd.i32()?;
8105 q.begin_order = rd.i32()?;
8106 q.end_order = rd.i32()?;
8107 Ok(q)
8108 }
8109
8110 fn write_cpp_btn_select(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8111 w.push_i32(0);
8112 w.push_padding(112);
8113 w.push_bool(false);
8114 w.push_bool(false);
8115 w.push_bool(false);
8116 w.push_bool(false);
8117 w.push_bool(false);
8118 w.push_bool(false);
8119 w.push_str("");
8120 w.push_i32(0);
8121 w.push_i32(0);
8122 }
8123
8124 fn read_cpp_btn_select(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<()> {
8125 let _ = rd.i32()?;
8126 rd.skip(112)?;
8127 for _ in 0..6 { let _ = rd.bool()?; }
8128 let _ = rd.string()?;
8129 let _ = rd.i32()?;
8130 let _ = rd.i32()?;
8131 Ok(())
8132 }
8133
8134 fn write_cpp_stage(&self, w: &mut crate::original_save::OriginalStreamWriter, stage_idx: i64) {
8135 let form_id = self.ctx.ids.form_global_stage;
8136 let st = self.ctx.globals.stage_forms.get(&form_id);
8137 let empty_groups: Vec<runtime::globals::GroupState> = Vec::new();
8138 let empty_objects: Vec<runtime::globals::ObjectState> = Vec::new();
8139 let empty_mwnds: Vec<runtime::globals::MwndState> = Vec::new();
8140 let empty_worlds: Vec<runtime::globals::WorldState> = Vec::new();
8141 let empty_effects: Vec<runtime::globals::ScreenEffectState> = Vec::new();
8142 let empty_quakes: Vec<runtime::globals::ScreenQuakeState> = Vec::new();
8143 let groups = st.and_then(|s| s.group_lists.get(&stage_idx)).unwrap_or(&empty_groups);
8144 let objects = st.and_then(|s| s.object_lists.get(&stage_idx)).unwrap_or(&empty_objects);
8145 let mwnds = st.and_then(|s| s.mwnd_lists.get(&stage_idx)).unwrap_or(&empty_mwnds);
8146 let worlds = st.and_then(|s| s.world_lists.get(&stage_idx)).unwrap_or(&empty_worlds);
8147 let effects = st.and_then(|s| s.effect_lists.get(&stage_idx)).unwrap_or(&empty_effects);
8148 let quakes = st.and_then(|s| s.quake_lists.get(&stage_idx)).unwrap_or(&empty_quakes);
8149 w.push_fixed_items(groups, |w, g| self.write_cpp_group(w, g));
8150 w.push_fixed_items(objects, |w, obj| self.write_cpp_object(w, obj));
8151 w.push_fixed_items(mwnds, |w, m| self.write_cpp_mwnd(w, m));
8152 self.write_cpp_btn_select(w);
8153 w.push_fixed_items(worlds, |w, world| self.write_cpp_world(w, world));
8154 w.push_fixed_items(effects, |w, e| self.write_cpp_effect(w, e));
8155 w.push_fixed_items(quakes, |w, q| self.write_cpp_quake(w, q));
8156 }
8157
8158 fn read_cpp_stage(rd: &mut crate::original_save::OriginalStreamReader<'_>, stage_idx: i64) -> Result<runtime::globals::StageFormState> {
8159 let mut st = runtime::globals::StageFormState::default();
8160 st.initialized_from_gameexe = true;
8161 st.group_lists.insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_group(rd))?);
8162 st.object_lists.insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_object(rd))?);
8163 st.mwnd_lists.insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_mwnd(rd))?);
8164 Self::read_cpp_btn_select(rd)?;
8165 let mut world_no = 0i32;
8166 let worlds = rd.fixed_items(|rd| { let w = Self::read_cpp_world(rd, world_no); world_no += 1; w })?;
8167 st.world_lists.insert(stage_idx, worlds);
8168 st.effect_lists.insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_effect(rd))?);
8169 st.quake_lists.insert(stage_idx, rd.fixed_items(|rd| Self::read_cpp_quake(rd))?);
8170 Ok(st)
8171 }
8172
8173 fn write_cpp_screen(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8174 let form_id = self.ctx.ids.form_global_screen;
8175 let screen = self.ctx.globals.screen_forms.get(&form_id).cloned().unwrap_or_default();
8176 w.push_fixed_items(&screen.effect_list, |w, e| self.write_cpp_effect(w, e));
8177 w.push_i32(Self::save_i32(screen.shake.last_value));
8178 w.push_i64(0);
8179 w.push_i32(0);
8180 w.push_fixed_items(&screen.quake_list, |w, q| self.write_cpp_quake(w, q));
8181 }
8182
8183 fn read_cpp_screen(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::ScreenFormState> {
8184 let effect_list = rd.fixed_items(|rd| Self::read_cpp_effect(rd))?;
8185 let mut shake = runtime::globals::ScreenShakeState::default();
8186 shake.last_value = rd.i32()? as i64;
8187 let _ = rd.i64()?;
8188 let _ = rd.i32()?;
8189 let quake_list = rd.fixed_items(|rd| Self::read_cpp_quake(rd))?;
8190 Ok(runtime::globals::ScreenFormState { effect_list, quake_list, shake })
8191 }
8192
8193 const ORIGINAL_PCMCH_DEFAULT_CNT: usize = 16;
8194 const ORIGINAL_PCMCH_MAX_CNT: usize = 256;
8195
8196 fn original_pcmch_count(&self) -> usize {
8197 self.ctx
8198 .tables
8199 .gameexe
8200 .as_ref()
8201 .and_then(|cfg| cfg.get_usize("#PCMCH.CNT").or_else(|| cfg.get_usize("PCMCH.CNT")))
8202 .unwrap_or(Self::ORIGINAL_PCMCH_DEFAULT_CNT)
8203 .min(Self::ORIGINAL_PCMCH_MAX_CNT)
8204 }
8205
8206 fn write_cpp_pcmch_default(w: &mut crate::original_save::OriginalStreamWriter) {
8207 w.push_str("");
8208 w.push_str("");
8209 w.push_i32(-1);
8210 w.push_i32(-1);
8211 w.push_i32(0);
8212 w.push_i32(-1);
8213 w.push_i32(255);
8214 w.push_i32(0);
8215 w.push_bool(false);
8216 w.push_bool(false);
8217 w.push_bool(false);
8218 w.push_bool(false);
8219 w.push_bool(false);
8220 }
8221
8222 fn read_cpp_pcmch(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<()> {
8223 let _pcm_name = rd.string()?;
8224 let _bgm_name = rd.string()?;
8225 let _koe_no = rd.i32()?;
8226 let _se_no = rd.i32()?;
8227 let _volume_type = rd.i32()?;
8228 let _chara_no = rd.i32()?;
8229 let _volume = rd.i32()?;
8230 let _delay_time = rd.i32()?;
8231 let _loop_flag = rd.bool()?;
8232 let _bgm_fade_target_flag = rd.bool()?;
8233 let _bgm_fade2_target_flag = rd.bool()?;
8234 let _bgm_fade_source_flag = rd.bool()?;
8235 let _ready_flag = rd.bool()?;
8236 Ok(())
8237 }
8238
8239 fn write_cpp_sound(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8240 w.push_str("");
8244 w.push_i32(255);
8245 w.push_i32(0);
8246 w.push_bool(false);
8247 w.push_bool(false);
8248 w.push_i32(255);
8249 w.push_i32(255);
8250 let pcmch_defaults = vec![(); self.original_pcmch_count()];
8251 w.push_fixed_items(&pcmch_defaults, |w, _| Self::write_cpp_pcmch_default(w));
8252 w.push_i32(255);
8253 w.push_str("");
8254 }
8255
8256 fn read_cpp_sound(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<()> {
8257 let _bgm_regist_name = rd.string()?;
8258 let _bgm_volume = rd.i32()?;
8259 let _bgm_delay_time = rd.i32()?;
8260 let _bgm_loop_flag = rd.bool()?;
8261 let _bgm_pause_flag = rd.bool()?;
8262 let _koe_volume = rd.i32()?;
8263 let _pcm_volume = rd.i32()?;
8264 rd.fixed_items(|rd| Self::read_cpp_pcmch(rd))?;
8265 let _se_volume = rd.i32()?;
8266 let _mov_file_name = rd.string()?;
8267 Ok(())
8268 }
8269
8270 fn write_cpp_pcm_event(&self, w: &mut crate::original_save::OriginalStreamWriter, ev: &runtime::globals::PcmEventState) {
8271 let ty = if ev.random { 2 } else if ev.looped { 1 } else if ev.active { 0 } else { -1 };
8272 w.push_i32(ty);
8273 if ty == 1 || ty == 2 {
8274 w.push_i32(0);
8275 w.push_i32(ev.volume_type);
8276 w.push_i32(ev.chara_no);
8277 w.push_bool(ev.bgm_fade_target_flag);
8278 w.push_bool(ev.bgm_fade2_target_flag);
8279 w.push_bool(ev.bgm_fade2_source_flag);
8280 w.push_bool(ev.real_flag);
8281 w.push_bool(ev.time_type);
8282 w.push_extend_items(&ev.lines, |w, line| {
8283 w.push_str(&line.file_name);
8284 w.push_i32(line.min_time);
8285 w.push_i32(line.max_time);
8286 w.push_i32(line.probability);
8287 });
8288 }
8289 }
8290
8291 fn read_cpp_pcm_event(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::PcmEventState> {
8292 let ty = rd.i32()?;
8293 let mut ev = runtime::globals::PcmEventState::default();
8294 ev.active = ty >= 0;
8295 ev.looped = ty == 1;
8296 ev.random = ty == 2;
8297 if ty == 1 || ty == 2 {
8298 let _ = rd.i32()?;
8299 ev.volume_type = rd.i32()?;
8300 ev.chara_no = rd.i32()?;
8301 ev.bgm_fade_target_flag = rd.bool()?;
8302 ev.bgm_fade2_target_flag = rd.bool()?;
8303 ev.bgm_fade2_source_flag = rd.bool()?;
8304 ev.real_flag = rd.bool()?;
8305 ev.time_type = rd.bool()?;
8306 ev.lines = rd.extend_items(|rd| Ok(runtime::globals::PcmEventLine { file_name: rd.string()?, min_time: rd.i32()?, max_time: rd.i32()?, probability: rd.i32()? }))?;
8307 }
8308 Ok(ev)
8309 }
8310
8311 fn write_cpp_editbox(&self, w: &mut crate::original_save::OriginalStreamWriter, e: &runtime::globals::EditBoxState) {
8312 w.push_bool(e.created);
8313 w.push_i32(e.rect_x);
8314 w.push_i32(e.rect_y);
8315 w.push_i32(e.rect_w);
8316 w.push_i32(e.rect_h);
8317 w.push_i32(e.moji_size);
8318 }
8319
8320 fn read_cpp_editbox(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::EditBoxState> {
8321 let mut e = runtime::globals::EditBoxState::default();
8322 e.created = rd.bool()?;
8323 e.rect_x = rd.i32()?;
8324 e.rect_y = rd.i32()?;
8325 e.rect_w = rd.i32()?;
8326 e.rect_h = rd.i32()?;
8327 e.moji_size = rd.i32()?;
8328 e.text.clear();
8329 Ok(e)
8330 }
8331
8332 fn write_cpp_msg_back(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8333 let msgbk = self.ctx.globals.msgbk_forms.values().next().cloned().unwrap_or_default();
8334 w.push_i32(msgbk.history_cnt as i32);
8335 let count = msgbk.history_cnt.min(msgbk.history.len());
8336 for entry in msgbk.history.iter().take(count) {
8337 w.push_bool(entry.pct_flag);
8338 w.push_str(&entry.msg_str);
8339 w.push_str(&entry.original_name);
8340 w.push_str(&entry.disp_name);
8341 w.push_i32(entry.pct_pos_x);
8342 w.push_i32(entry.pct_pos_y);
8343 w.push_extend_i32_list(&entry.koe_no_list);
8344 w.push_extend_i32_list(&entry.chr_no_list);
8345 w.push_i32(Self::save_i32(entry.koe_play_no));
8346 w.push_str(&entry.debug_msg);
8347 w.push_i32(Self::save_i32(entry.scn_no));
8348 w.push_i32(Self::save_i32(entry.line_no));
8349 w.push_tid_zero();
8350 w.push_bool(entry.save_id_check_flag);
8351 }
8352 w.push_i32(msgbk.history_start_pos as i32);
8353 w.push_i32(msgbk.history_last_pos as i32);
8354 w.push_i32(msgbk.history_insert_pos as i32);
8355 w.push_i32(if msgbk.new_msg_flag { 1 } else { 0 });
8356 }
8357
8358 fn read_cpp_msg_back(rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<runtime::globals::MsgBackState> {
8359 let cnt = rd.i32()?.max(0) as usize;
8360 let mut st = runtime::globals::MsgBackState::default();
8361 st.history.clear();
8362 for _ in 0..cnt {
8363 let mut entry = runtime::globals::MsgBackEntry::default();
8364 entry.pct_flag = rd.bool()?;
8365 entry.msg_str = rd.string()?;
8366 entry.original_name = rd.string()?;
8367 entry.disp_name = rd.string()?;
8368 entry.pct_pos_x = rd.i32()?;
8369 entry.pct_pos_y = rd.i32()?;
8370 entry.koe_no_list = rd.extend_i32_list()?;
8371 entry.chr_no_list = rd.extend_i32_list()?;
8372 entry.koe_play_no = rd.i32()? as i64;
8373 entry.debug_msg = rd.string()?;
8374 entry.scn_no = rd.i32()? as i64;
8375 entry.line_no = rd.i32()? as i64;
8376 rd.skip(14)?;
8377 entry.save_id_check_flag = rd.bool()?;
8378 st.history.push(entry);
8379 }
8380 st.history_cnt = cnt;
8381 st.history_cnt_max = cnt.max(256);
8382 st.history_start_pos = rd.i32()?.max(0) as usize;
8383 st.history_last_pos = rd.i32()?.max(0) as usize;
8384 st.history_insert_pos = rd.i32()?.max(0) as usize;
8385 st.new_msg_flag = rd.i32()? != 0;
8386 if st.history.len() < st.history_cnt_max { st.history.resize_with(st.history_cnt_max, runtime::globals::MsgBackEntry::default); }
8387 Ok(st)
8388 }
8389
8390
8391 fn parse_cpp_tail_state(&mut self, rd: &mut crate::original_save::OriginalStreamReader<'_>, current_scene_name: &str) -> Result<Vec<CallFrame>> {
8392 self.read_cpp_inc_prop_list(rd)?;
8393 self.read_cpp_scene_prop_lists(rd, current_scene_name)?;
8394
8395 let counter_list = rd.fixed_items(|rd| Self::read_cpp_counter_param(rd))?;
8396 if !counter_list.is_empty() {
8397 self.ctx.globals.counter_lists.insert(crate::runtime::forms::codes::FORM_GLOBAL_COUNTER, counter_list);
8398 }
8399
8400 let frame_action = Self::read_cpp_frame_action(rd)?;
8401 self.ctx.globals.frame_actions.insert(self.ctx.ids.form_global_frame_action, frame_action);
8402
8403 let frame_action_ch = rd.fixed_items(|rd| Self::read_cpp_frame_action(rd))?;
8404 if !frame_action_ch.is_empty() {
8405 self.ctx.globals.frame_action_lists.insert(self.ctx.ids.form_global_frame_action_ch, frame_action_ch);
8406 }
8407
8408 let g00buf_files = rd.fixed_items(|rd| rd.string())?;
8409 self.ctx.globals.g00buf.clear();
8410 self.ctx.globals.g00buf_names.clear();
8411 self.ctx.globals.g00buf.resize(g00buf_files.len(), None);
8412 self.ctx.globals.g00buf_names.resize(g00buf_files.len(), None);
8413 for (idx, name) in g00buf_files.into_iter().enumerate() {
8414 if !name.is_empty() {
8415 self.ctx.globals.g00buf_names[idx] = Some(name.clone());
8416 if let Ok(img_id) = self.ctx.images.load_g00(&name, 0) {
8417 self.ctx.globals.g00buf[idx] = Some(img_id);
8418 }
8419 }
8420 }
8421
8422 let masks = rd.fixed_items(|rd| {
8423 let x_event = Self::read_cpp_int_event_raw(rd)?;
8424 let y_event = Self::read_cpp_int_event_raw(rd)?;
8425 let name = rd.string()?;
8426 Ok(runtime::globals::MaskState {
8427 name: if name.is_empty() { None } else { Some(name) },
8428 x_event,
8429 y_event,
8430 extra_int: std::collections::HashMap::new(),
8431 script_events: std::collections::HashMap::new(),
8432 })
8433 })?;
8434 if !masks.is_empty() {
8435 self.ctx.globals.mask_lists.insert(self.ctx.ids.form_global_mask, runtime::globals::MaskListState { masks });
8436 }
8437
8438 let mut st = runtime::globals::StageFormState::default();
8439 let back = Self::read_cpp_stage(rd, 0)?;
8440 let front = Self::read_cpp_stage(rd, 1)?;
8441 st.initialized_from_gameexe = true;
8442 st.group_lists.extend(back.group_lists);
8443 st.object_lists.extend(back.object_lists);
8444 st.mwnd_lists.extend(back.mwnd_lists);
8445 st.world_lists.extend(back.world_lists);
8446 st.effect_lists.extend(back.effect_lists);
8447 st.quake_lists.extend(back.quake_lists);
8448 st.group_lists.extend(front.group_lists);
8449 st.object_lists.extend(front.object_lists);
8450 st.mwnd_lists.extend(front.mwnd_lists);
8451 st.world_lists.extend(front.world_lists);
8452 st.effect_lists.extend(front.effect_lists);
8453 st.quake_lists.extend(front.quake_lists);
8454 self.ctx.globals.stage_forms.insert(self.ctx.ids.form_global_stage, st);
8455
8456 let screen = Self::read_cpp_screen(rd)?;
8457 self.ctx.globals.screen_forms.insert(self.ctx.ids.form_global_screen, screen);
8458
8459 Self::read_cpp_sound(rd)?;
8460
8461 let pcm_events = rd.fixed_items(|rd| Self::read_cpp_pcm_event(rd))?;
8462 if !pcm_events.is_empty() {
8463 self.ctx.globals.pcm_event_lists.insert(self.ctx.ids.form_global_pcm_event, pcm_events);
8464 }
8465
8466 let editboxes = rd.fixed_items(|rd| Self::read_cpp_editbox(rd))?;
8467 if !editboxes.is_empty() {
8468 self.ctx.globals.editbox_lists.insert(self.ctx.ids.form_global_editbox, runtime::globals::EditBoxListState { boxes: editboxes });
8469 }
8470
8471 let call_cnt = rd.i32()?.max(0) as usize;
8472 let mut call_stack = Vec::with_capacity(call_cnt.max(1));
8473 for _ in 0..call_cnt {
8474 call_stack.push(self.read_cpp_call_frame(rd)?);
8475 }
8476 if call_stack.is_empty() {
8477 call_stack.push(self.scene_base_call());
8478 }
8479
8480 let msg_back = Self::read_cpp_msg_back(rd)?;
8481 self.ctx.globals.msgbk_forms.insert(self.ctx.ids.form_global_msgbk, msg_back);
8482
8483 self.ctx.globals.syscom.sel_save_stock_stream = rd.len_bytes()?;
8484 let inner_cnt = rd.i32()?.max(0) as usize;
8485 self.ctx.globals.syscom.inner_save_streams.clear();
8486 for _ in 0..inner_cnt {
8487 self.ctx.globals.syscom.inner_save_streams.push(rd.len_bytes()?);
8488 }
8489 self.ctx.globals.syscom.inner_save_exists = self.ctx.globals.syscom.inner_save_streams.iter().any(|s| !s.is_empty());
8490 let sel_save_cnt = rd.i32()?.max(0) as usize;
8491 self.ctx.globals.syscom.sel_save_ids.clear();
8492 for _ in 0..sel_save_cnt {
8493 self.ctx.globals.syscom.sel_save_ids.push(rd.tid()?);
8494 }
8495 Ok(call_stack)
8496 }
8497
8498
8499
8500 fn write_cpp_proc_record(
8501 w: &mut crate::original_save::OriginalStreamWriter,
8502 proc_type: i32,
8503 element: &[i32],
8504 option: i32,
8505 ) {
8506 w.push_i32(proc_type);
8507 w.push_element(element);
8508 w.push_i32(0);
8509 w.push_i32(0);
8510 w.push_bool(false);
8511 w.push_bool(false);
8512 w.push_bool(false);
8513 w.push_i32(option);
8514 }
8515
8516 fn write_cpp_runtime_proc_stack(&self, w: &mut crate::original_save::OriginalStreamWriter) {
8517 let proc_type = if matches!(self.ctx.last_proc_kind(), runtime::ProcKind::Script) { 1 } else { 0 };
8522 Self::write_cpp_proc_record(w, proc_type, &[], 0);
8523 w.push_i32(0);
8524 }
8525
8526 fn read_cpp_proc_record(&self, rd: &mut crate::original_save::OriginalStreamReader<'_>) -> Result<()> {
8527 let _proc_type = rd.i32()?;
8528 let _element = rd.element()?;
8529 let _arg_list_id = rd.i32()?;
8530 let _arg_list: Vec<()> = rd.extend_items(|rd| {
8531 let _ = self.read_cpp_prop(rd)?;
8532 Ok(())
8533 })?;
8534 let _key_skip_enable_flag = rd.bool()?;
8535 let _skip_disable_flag = rd.bool()?;
8536 let _return_value_flag = rd.bool()?;
8537 let _option = rd.i32()?;
8538 Ok(())
8539 }
8540
8541 fn cpp_mwnd_element(stage_idx: i64, mwnd_no: Option<usize>) -> Vec<i32> {
8542 let Some(no) = mwnd_no else {
8543 return Vec::new();
8544 };
8545 let stage_head = match stage_idx {
8546 0 => crate::runtime::forms::codes::ELM_GLOBAL_BACK,
8547 2 => crate::runtime::forms::codes::ELM_GLOBAL_NEXT,
8548 _ => crate::runtime::forms::codes::ELM_GLOBAL_FRONT,
8549 };
8550 vec![
8551 stage_head,
8552 crate::runtime::forms::codes::ELM_STAGE_MWND,
8553 crate::runtime::forms::codes::ELM_ARRAY,
8554 no as i32,
8555 ]
8556 }
8557
8558 fn decode_cpp_mwnd_element(elm: &[i32]) -> Option<(i64, usize)> {
8559 if elm.len() < 4 {
8560 return None;
8561 }
8562 let stage_idx = if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_BACK {
8563 0
8564 } else if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_FRONT {
8565 1
8566 } else if elm[0] == crate::runtime::forms::codes::ELM_GLOBAL_NEXT {
8567 2
8568 } else {
8569 return None;
8570 };
8571 if elm[1] != crate::runtime::forms::codes::ELM_STAGE_MWND {
8572 return None;
8573 }
8574 if elm[2] != crate::runtime::forms::codes::ELM_ARRAY {
8575 return None;
8576 }
8577 if elm[3] < 0 {
8578 return None;
8579 }
8580 Some((stage_idx, elm[3] as usize))
8581 }
8582
8583 fn apply_saved_current_mwnd_elements(
8584 &mut self,
8585 cur_mwnd: &[i32],
8586 cur_sel_mwnd: &[i32],
8587 last_mwnd: &[i32],
8588 ) {
8589 self.ctx.globals.current_mwnd_no = None;
8590 self.ctx.globals.current_sel_mwnd_no = None;
8591 self.ctx.globals.last_mwnd_no = None;
8592
8593 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(cur_mwnd) {
8594 self.ctx.globals.current_mwnd_stage_idx = stage;
8595 self.ctx.globals.current_mwnd_no = Some(no);
8596 }
8597 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(cur_sel_mwnd) {
8598 self.ctx.globals.current_sel_mwnd_stage_idx = stage;
8599 self.ctx.globals.current_sel_mwnd_no = Some(no);
8600 }
8601 if let Some((stage, no)) = Self::decode_cpp_mwnd_element(last_mwnd) {
8602 self.ctx.globals.last_mwnd_stage_idx = stage;
8603 self.ctx.globals.last_mwnd_no = Some(no);
8604 }
8605 }
8606
8607 fn current_local_save_id(&self) -> [u16; 7] {
8608 let now = crate::platform_time::local_time_fields();
8609 [
8610 now.year.clamp(0, u16::MAX as i32) as u16,
8611 now.month as u16,
8612 now.day as u16,
8613 now.hour as u16,
8614 now.minute as u16,
8615 now.second as u16,
8616 now.millisecond as u16,
8617 ]
8618 }
8619
8620 fn build_local_save_snapshot(&mut self) {
8625 let local_stream = self.build_original_local_stream();
8626 let local_ex_stream = self.build_original_local_ex_stream();
8627 let snapshot = crate::runtime::LocalSaveSnapshot {
8628 save_id: self.current_local_save_id(),
8629 append_dir: self.ctx.globals.append_dir.clone(),
8630 append_name: self.ctx.globals.append_name.clone(),
8631 save_scene_title: self.ctx.globals.syscom.current_save_scene_title.clone(),
8632 save_msg: String::new(),
8633 save_full_msg: self.ctx.globals.syscom.current_save_full_message.clone(),
8634 local_stream,
8635 local_ex_stream,
8636 sel_saves: self
8637 .ctx
8638 .local_save_snapshot
8639 .as_ref()
8640 .map(|s| s.sel_saves.clone())
8641 .unwrap_or_default(),
8642 };
8643 self.ctx.local_save_snapshot = Some(snapshot);
8644 }
8645
8646 fn build_original_local_stream(&self) -> Vec<u8> {
8647 let mut w = crate::original_save::OriginalStreamWriter::new();
8648 let scene_name = self.current_scene_name.as_deref().unwrap_or("");
8649 let flag_cnt = self.local_flag_count();
8650 use crate::runtime::forms::codes;
8651
8652 w.push_str(scene_name);
8653 w.push_i32(self.current_line_no);
8654 w.push_i32(self.stream.get_prg_cntr() as i32);
8655
8656 self.write_cpp_runtime_proc_stack(&mut w);
8657 let cur_mwnd = Self::cpp_mwnd_element(
8658 self.ctx.globals.current_mwnd_stage_idx,
8659 self.ctx.globals.current_mwnd_no,
8660 );
8661 let cur_sel_mwnd = Self::cpp_mwnd_element(
8662 self.ctx.globals.current_sel_mwnd_stage_idx,
8663 self.ctx.globals.current_sel_mwnd_no,
8664 );
8665 let last_mwnd = Self::cpp_mwnd_element(
8666 self.ctx.globals.last_mwnd_stage_idx,
8667 self.ctx.globals.last_mwnd_no,
8668 );
8669 w.push_element(&cur_mwnd);
8670 w.push_element(&cur_sel_mwnd);
8671 w.push_element(&last_mwnd);
8672 w.push_str(&self.ctx.globals.syscom.current_save_scene_title);
8673 let current_full_message = if self.ctx.globals.syscom.current_save_full_message.is_empty() {
8674 self.ctx.globals.syscom.current_save_message.as_str()
8675 } else {
8676 self.ctx.globals.syscom.current_save_full_message.as_str()
8677 };
8678 w.push_str(current_full_message);
8679
8680 let btn_cnt = self.mwnd_waku_btn_count();
8681 for idx in 0..btn_cnt {
8682 w.push_bool(self.ctx.globals.syscom.mwnd_btn_disable.get(&(idx as i64)).copied().unwrap_or(false));
8683 }
8684 w.push_str(&self.ctx.globals.script.font_name);
8685 w.push_raw(&self.build_cpp_local_data_pod());
8686
8687 w.push_i32(self.int_stack.len() as i32);
8688 for v in &self.int_stack { w.push_i32(*v); }
8689 w.push_i32(self.str_stack.len() as i32);
8690 for s in &self.str_stack { w.push_str(s); }
8691 w.push_i32(self.element_points.len() as i32);
8692 for p in &self.element_points { w.push_i32(*p as i32); }
8693
8694 w.push_i32(self.ctx.globals.local_real_time.clamp(i32::MIN as i64, i32::MAX as i64) as i32);
8695 w.push_i32(self.ctx.globals.local_game_time.clamp(i32::MIN as i64, i32::MAX as i64) as i32);
8696 w.push_i32(self.ctx.globals.local_wipe_time.clamp(i32::MIN as i64, i32::MAX as i64) as i32);
8697 self.write_cpp_syscom_menu(&mut w);
8698
8699 let fog = &self.ctx.globals.fog_global;
8700 w.push_str(&fog.name);
8701 Self::write_cpp_int_event_raw(&mut w, &fog.x_event);
8702 w.push_i32(fog.near as i32);
8703 w.push_i32(fog.far as i32);
8704
8705 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_A), flag_cnt);
8706 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_B), flag_cnt);
8707 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_C), flag_cnt);
8708 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_D), flag_cnt);
8709 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_E), flag_cnt);
8710 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_F), flag_cnt);
8711 w.push_fixed_i32_list(self.int_list_by_element(codes::ELM_GLOBAL_X), flag_cnt);
8712 w.push_fixed_str_list(self.str_list_by_element(codes::ELM_GLOBAL_S), flag_cnt);
8713 w.push_extend_i32_list(&self.ctx.globals.local_flag_h);
8714 w.push_extend_i32_list(&self.ctx.globals.local_flag_i);
8715 w.push_extend_i32_list(&self.ctx.globals.local_flag_j);
8716 w.push_fixed_str_list(self.str_list_by_element(codes::ELM_GLOBAL_NAMAE_LOCAL), 26 + 26 * 26);
8717
8718 self.write_cpp_inc_prop_list(&mut w);
8719 self.write_cpp_current_scene_prop_lists(&mut w);
8720
8721 let counter_list = self.ctx.globals.counter_lists.values().next().cloned().unwrap_or_default();
8722 w.push_fixed_items(&counter_list, |w, c| self.write_cpp_counter_param(w, c));
8723
8724 let frame_action = self.ctx.globals.frame_actions.values().next().cloned().unwrap_or_default();
8725 self.write_cpp_frame_action(&mut w, &frame_action);
8726
8727 let frame_action_ch = self.ctx.globals.frame_action_lists.values().next().cloned().unwrap_or_default();
8728 w.push_fixed_items(&frame_action_ch, |w, fa| self.write_cpp_frame_action(w, fa));
8729
8730 w.push_fixed_items(&self.ctx.globals.g00buf_names, |w, name| w.push_str(name.as_deref().unwrap_or("")));
8732
8733 let mask_list = self.ctx.globals.mask_lists.values().next().map(|m| m.masks.clone()).unwrap_or_default();
8734 w.push_fixed_items(&mask_list, |w, m| {
8735 Self::write_cpp_int_event_raw(w, &m.x_event);
8736 Self::write_cpp_int_event_raw(w, &m.y_event);
8737 w.push_str(m.name.as_deref().unwrap_or(""));
8738 });
8739
8740 self.write_cpp_stage(&mut w, 0);
8741 self.write_cpp_stage(&mut w, 1);
8742 self.write_cpp_screen(&mut w);
8743 self.write_cpp_sound(&mut w);
8744
8745 let pcm_events = self.ctx.globals.pcm_event_lists.values().next().cloned().unwrap_or_default();
8746 w.push_fixed_items(&pcm_events, |w, ev| self.write_cpp_pcm_event(w, ev));
8747
8748 let editboxes = self.ctx.globals.editbox_lists.values().next().map(|e| e.boxes.clone()).unwrap_or_default();
8749 w.push_fixed_items(&editboxes, |w, e| self.write_cpp_editbox(w, e));
8750
8751 w.push_i32(self.call_stack.len() as i32);
8752 for frame in &self.call_stack {
8753 self.write_cpp_call_frame(&mut w, frame);
8754 }
8755 self.write_cpp_msg_back(&mut w);
8756
8757 w.push_len_bytes(&self.ctx.globals.syscom.sel_save_stock_stream);
8758 w.push_i32(self.ctx.globals.syscom.inner_save_streams.len() as i32);
8759 for stream in &self.ctx.globals.syscom.inner_save_streams {
8760 w.push_len_bytes(stream);
8761 }
8762 w.push_i32(self.ctx.globals.syscom.sel_save_ids.len() as i32);
8763 for tid in &self.ctx.globals.syscom.sel_save_ids {
8764 w.push_tid(tid);
8765 }
8766 w.into_inner()
8767 }
8768
8769 fn build_original_local_ex_stream(&self) -> Vec<u8> {
8770 let mut w = crate::original_save::OriginalStreamWriter::new();
8771 let s = &self.ctx.globals.syscom;
8772 for i in 0..4 {
8773 let sw = s.local_extra_switches.get(i).copied().unwrap_or(if i == 0 { s.local_extra_switch } else { runtime::globals::ToggleFeatureState::default() });
8774 w.push_bool(sw.exist);
8775 w.push_bool(sw.enable);
8776 w.push_bool(sw.onoff);
8777 }
8778 for i in 0..4 {
8779 let mode = s.local_extra_modes.get(i).copied().unwrap_or(if i == 0 { s.local_extra_mode } else { runtime::globals::ValueFeatureState::default() });
8780 w.push_bool(mode.exist);
8781 w.push_bool(mode.enable);
8782 w.push_padding(2);
8783 w.push_i32(mode.value as i32);
8784 }
8785 let out = w.into_inner();
8786 debug_assert_eq!(out.len(), 44);
8787 out
8788 }
8789
8790 fn parse_original_local_ex_stream(&mut self, local_ex_stream: &[u8]) -> Result<()> {
8791 if local_ex_stream.len() < 44 { return Ok(()); }
8792 let mut rd = crate::original_save::OriginalStreamReader::new(local_ex_stream);
8793 for i in 0..4 {
8794 self.ctx.globals.syscom.local_extra_switches[i].exist = rd.bool()?;
8795 self.ctx.globals.syscom.local_extra_switches[i].enable = rd.bool()?;
8796 self.ctx.globals.syscom.local_extra_switches[i].onoff = rd.bool()?;
8797 }
8798 for i in 0..4 {
8799 self.ctx.globals.syscom.local_extra_modes[i].exist = rd.bool()?;
8800 self.ctx.globals.syscom.local_extra_modes[i].enable = rd.bool()?;
8801 rd.skip(2)?;
8802 self.ctx.globals.syscom.local_extra_modes[i].value = rd.i32()? as i64;
8803 }
8804 self.ctx.globals.syscom.local_extra_switch = self.ctx.globals.syscom.local_extra_switches[0];
8805 self.ctx.globals.syscom.local_extra_mode = self.ctx.globals.syscom.local_extra_modes[0];
8806 Ok(())
8807 }
8808
8809 fn parse_original_local_stream(&mut self, local_stream: &[u8]) -> Result<RuntimeDiskSnapshot> {
8810 let mut rd = crate::original_save::OriginalStreamReader::new(local_stream);
8811 let flag_cnt = self.local_flag_count();
8812 use crate::runtime::forms::codes;
8813
8814 let scene_name = rd.string()?;
8815 let line_no = rd.i32()?;
8816 let pc = rd.i32()?;
8817
8818 self.read_cpp_proc_record(&mut rd)?;
8819 let proc_stack_cnt = rd.i32()?.max(0) as usize;
8820 for _ in 0..proc_stack_cnt { self.read_cpp_proc_record(&mut rd)?; }
8821 let cur_mwnd = rd.element()?;
8822 let cur_sel_mwnd = rd.element()?;
8823 let last_mwnd = rd.element()?;
8824 self.apply_saved_current_mwnd_elements(&cur_mwnd, &cur_sel_mwnd, &last_mwnd);
8825 self.ctx.globals.syscom.current_save_scene_title = rd.string()?;
8826 self.ctx.globals.syscom.current_save_full_message = rd.string()?;
8827 self.ctx.globals.syscom.current_save_message.clear();
8828
8829 let btn_cnt = self.mwnd_waku_btn_count();
8830 self.ctx.globals.syscom.mwnd_btn_disable.clear();
8831 for idx in 0..btn_cnt {
8832 if rd.bool()? {
8833 self.ctx.globals.syscom.mwnd_btn_disable.insert(idx as i64, true);
8834 }
8835 }
8836 self.ctx.globals.script.font_name = rd.string()?;
8837 rd.skip(356)?;
8838
8839 let int_cnt = rd.i32()?.max(0) as usize;
8840 let mut int_stack = Vec::with_capacity(int_cnt);
8841 for _ in 0..int_cnt { int_stack.push(rd.i32()?); }
8842 let str_cnt = rd.i32()?.max(0) as usize;
8843 let mut str_stack = Vec::with_capacity(str_cnt);
8844 for _ in 0..str_cnt { str_stack.push(rd.string()?); }
8845 let ep_cnt = rd.i32()?.max(0) as usize;
8846 let mut element_points = Vec::with_capacity(ep_cnt);
8847 for _ in 0..ep_cnt { element_points.push(rd.i32()?.max(0) as usize); }
8848
8849 self.ctx.globals.local_real_time = rd.i32()? as i64;
8850 self.ctx.globals.local_game_time = rd.i32()? as i64;
8851 self.ctx.globals.local_wipe_time = rd.i32()? as i64;
8852 rd.skip(76)?;
8853
8854 let fog_name = rd.string()?;
8855 let fog_x = Self::read_cpp_int_event_raw(&mut rd)?;
8856 let fog_near = rd.i32()?;
8857 let fog_far = rd.i32()?;
8858 self.ctx.globals.fog_global.name = fog_name;
8859 self.ctx.globals.fog_global.enabled = !self.ctx.globals.fog_global.name.is_empty();
8860 self.ctx.globals.fog_global.texture_image_id = None;
8861 if self.ctx.globals.fog_global.enabled {
8862 match self.ctx.images.load_g00(&self.ctx.globals.fog_global.name, 0) {
8863 Ok(id) => self.ctx.globals.fog_global.texture_image_id = Some(id),
8864 Err(e) => log::error!(
8865 "load_local fog texture '{}' failed: {e}",
8866 self.ctx.globals.fog_global.name
8867 ),
8868 }
8869 }
8870 self.ctx.globals.fog_global.x_event = fog_x;
8871 self.ctx.globals.fog_global.scroll_x = self.ctx.globals.fog_global.x_event.get_total_value() as f32;
8872 self.ctx.globals.fog_global.near = fog_near as f32;
8873 self.ctx.globals.fog_global.far = fog_far as f32;
8874
8875 let a = rd.fixed_i32_list()?;
8876 let b = rd.fixed_i32_list()?;
8877 let c = rd.fixed_i32_list()?;
8878 let d = rd.fixed_i32_list()?;
8879 let e = rd.fixed_i32_list()?;
8880 let f = rd.fixed_i32_list()?;
8881 let x = rd.fixed_i32_list()?;
8882 let s = rd.fixed_str_list()?;
8883 let h = rd.extend_i32_list()?;
8884 let i = rd.extend_i32_list()?;
8885 let j = rd.extend_i32_list()?;
8886 let namae_local = rd.fixed_str_list()?;
8887 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_A as u32, resize_i64_vec(a, flag_cnt));
8888 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_B as u32, resize_i64_vec(b, flag_cnt));
8889 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_C as u32, resize_i64_vec(c, flag_cnt));
8890 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_D as u32, resize_i64_vec(d, flag_cnt));
8891 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_E as u32, resize_i64_vec(e, flag_cnt));
8892 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_F as u32, resize_i64_vec(f, flag_cnt));
8893 self.ctx.globals.int_lists.insert(codes::ELM_GLOBAL_X as u32, resize_i64_vec(x, flag_cnt));
8894 self.ctx.globals.str_lists.insert(codes::ELM_GLOBAL_S as u32, resize_string_vec(s, flag_cnt));
8895 self.ctx.globals.local_flag_h = h;
8896 self.ctx.globals.local_flag_i = i;
8897 self.ctx.globals.local_flag_j = j;
8898 self.ctx.globals.str_lists.insert(codes::ELM_GLOBAL_NAMAE_LOCAL as u32, resize_string_vec(namae_local, 26 + 26 * 26));
8899
8900 let call_stack = self.parse_cpp_tail_state(&mut rd, &scene_name)?;
8901
8902 Ok(RuntimeDiskSnapshot {
8903 scene_name,
8904 scene_no: -1,
8905 line_no,
8906 pc,
8907 int_stack,
8908 str_stack,
8909 element_points,
8910 call_stack,
8911 })
8912 }
8913
8914 fn save_load_trace_enabled() -> bool {
8915 std::env::var_os("SG_SAVELOAD_TRACE").is_some()
8916 }
8917
8918 fn perform_runtime_save_request(&mut self, req: RuntimeSaveRequest) -> Result<()> {
8919 if req.kind == RuntimeSaveKind::Inner {
8920 let Some(snapshot) = self.ctx.local_save_snapshot.as_ref() else {
8924 log::error!(
8925 "[SG_SAVELOAD] inner save dropped idx={}: no local_save snapshot",
8926 req.index
8927 );
8928 return Ok(());
8929 };
8930 if Self::save_load_trace_enabled() {
8931 eprintln!("[SG_SAVELOAD_TRACE][VM] save inner idx={}", req.index);
8932 }
8933 if self.ctx.globals.syscom.inner_save_streams.len() <= req.index {
8934 self.ctx.globals.syscom.inner_save_streams.resize_with(req.index + 1, Vec::new);
8935 }
8936 self.ctx.globals.syscom.inner_save_streams[req.index] = snapshot.local_stream.clone();
8937 self.ctx.globals.syscom.inner_save_exists = true;
8938 return Ok(());
8939 }
8940
8941 if self.ctx.local_save_snapshot.is_none() {
8946 log::error!(
8947 "[SG_SAVELOAD] save dropped (kind={:?} idx={}): no local_save snapshot. \
8948 SAVEPOINT has not fired in the current message block - either the script \
8949 set dont_set_save_point or auto-SAVEPOINT wasn't reached yet. No file written.",
8950 req.kind, req.index
8951 );
8952 return Ok(());
8953 }
8954
8955 let refreshed_ex = self.build_original_local_ex_stream();
8958 if let Some(snapshot) = self.ctx.local_save_snapshot.as_mut() {
8959 snapshot.local_ex_stream = refreshed_ex;
8960 }
8961
8962 let slot = self.ensure_runtime_slot_for_save(req);
8963 let Some(path) = self.runtime_save_file_path(req.kind, req.index) else { return Ok(()); };
8964 if Self::save_load_trace_enabled() {
8965 eprintln!(
8966 "[SG_SAVELOAD_TRACE][VM] save begin kind={:?} idx={} path={} file_exists_before={}",
8967 req.kind,
8968 req.index,
8969 path.display(),
8970 path.exists()
8971 );
8972 }
8973 let snapshot = self
8974 .ctx
8975 .local_save_snapshot
8976 .as_ref()
8977 .expect("snapshot presence checked above");
8978 let env = crate::original_save::OriginalLocalSaveEnvelope {
8979 save_id: snapshot.save_id,
8980 append_dir: snapshot.append_dir.clone(),
8981 append_name: snapshot.append_name.clone(),
8982 title: snapshot.save_scene_title.clone(),
8983 message: snapshot.save_msg.clone(),
8984 full_message: snapshot.save_full_msg.clone(),
8985 local_stream: snapshot.local_stream.clone(),
8986 local_ex_stream: snapshot.local_ex_stream.clone(),
8987 sel_saves: snapshot.sel_saves.clone(),
8988 };
8989 crate::original_save::write_local_save_file(&path, &slot, &env)?;
8990 crate::runtime::forms::syscom::write_global_save(&self.ctx);
8991 if Self::save_load_trace_enabled() {
8992 eprintln!(
8993 "[SG_SAVELOAD_TRACE][VM] save written kind={:?} idx={} path={} bytes={}",
8994 req.kind,
8995 req.index,
8996 path.display(),
8997 std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0)
8998 );
8999 }
9000 if let Some(saved_slot) = crate::original_save::read_slot_from_path(&path) {
9001 match req.kind {
9002 RuntimeSaveKind::Normal => {
9003 if self.ctx.globals.syscom.save_slots.len() <= req.index {
9004 self.ctx.globals.syscom.save_slots.resize_with(req.index + 1, Default::default);
9005 }
9006 self.ctx.globals.syscom.save_slots[req.index] = saved_slot;
9007 }
9008 RuntimeSaveKind::Quick => {
9009 if self.ctx.globals.syscom.quick_save_slots.len() <= req.index {
9010 self.ctx.globals.syscom.quick_save_slots.resize_with(req.index + 1, Default::default);
9011 }
9012 self.ctx.globals.syscom.quick_save_slots[req.index] = saved_slot;
9013 }
9014 RuntimeSaveKind::End => {
9015 self.ctx.globals.syscom.end_save_exists = true;
9016 }
9017 RuntimeSaveKind::Inner => {}
9018 }
9019 }
9020 if let Some(save_kind) = Self::save_kind_to_original(req.kind) {
9021 let save_no = crate::original_save::original_save_no(
9022 self.configured_runtime_save_count(false),
9023 self.configured_runtime_save_count(true),
9024 save_kind,
9025 req.index,
9026 );
9027 if Self::save_load_trace_enabled() {
9028 eprintln!(
9029 "[SG_SAVELOAD_TRACE][VM] save thumb write kind={:?} idx={} original_save_no={}",
9030 req.kind,
9031 req.index,
9032 save_no
9033 );
9034 }
9035 crate::runtime::forms::syscom::write_runtime_slot_thumb(&mut self.ctx, save_no);
9036 }
9037 Ok(())
9038 }
9039
9040 fn perform_runtime_load_request(&mut self, req: RuntimeLoadRequest) -> Result<()> {
9041 if Self::save_load_trace_enabled() {
9042 eprintln!("[SG_SAVELOAD_TRACE][VM] load begin kind={:?} idx={}", req.kind, req.index);
9043 }
9044 struct LoadedEnvelopeMeta {
9045 save_id: [u16; 7],
9046 append_dir: String,
9047 append_name: String,
9048 title: String,
9049 message: String,
9050 full_message: String,
9051 sel_saves: Vec<crate::original_save::OriginalLocalSaveEnvelope>,
9052 }
9053 let (local_stream, local_ex_stream, loaded_meta) = if req.kind == RuntimeSaveKind::Inner {
9054 let Some(stream) = self.ctx.globals.syscom.inner_save_streams.get(req.index).cloned() else { return Ok(()); };
9055 (stream, Vec::new(), None)
9056 } else {
9057 let Some(path) = self.runtime_save_file_path(req.kind, req.index) else { return Ok(()); };
9058 if Self::save_load_trace_enabled() {
9059 eprintln!(
9060 "[SG_SAVELOAD_TRACE][VM] load read kind={:?} idx={} path={} file_exists={}",
9061 req.kind,
9062 req.index,
9063 path.display(),
9064 path.exists()
9065 );
9066 }
9067 let (_header, env) = crate::original_save::read_local_save_file(&path)?;
9068 let meta = LoadedEnvelopeMeta {
9069 save_id: env.save_id,
9070 append_dir: env.append_dir.clone(),
9071 append_name: env.append_name.clone(),
9072 title: env.title.clone(),
9073 message: env.message.clone(),
9074 full_message: env.full_message.clone(),
9075 sel_saves: env.sel_saves.clone(),
9076 };
9077 (env.local_stream, env.local_ex_stream, Some(meta))
9078 };
9079 if let Some(meta) = loaded_meta.as_ref() {
9080 let append_dir = meta.append_dir.clone();
9081 let append_name = meta.append_name.clone();
9082 self.ctx.globals.append_dir = append_dir.clone();
9083 self.ctx.globals.append_name = append_name;
9084 self.ctx.images.set_current_append_dir(append_dir.clone());
9085 self.ctx.movie.set_current_append_dir(append_dir.clone());
9086 self.ctx.bgm.set_current_append_dir(append_dir);
9087 }
9088 self.scene_stack.clear();
9093 self.sel_point_stack.clear();
9094 self.save_point = None;
9095 self.ctx.local_save_snapshot = None;
9096 self.ctx.begin_runtime_load_apply();
9097 let snapshot = self.parse_original_local_stream(&local_stream)?;
9098 self.parse_original_local_ex_stream(&local_ex_stream)?;
9099 if let Some(meta) = loaded_meta {
9104 self.ctx.local_save_snapshot = Some(crate::runtime::LocalSaveSnapshot {
9105 save_id: meta.save_id,
9106 append_dir: meta.append_dir,
9107 append_name: meta.append_name,
9108 save_scene_title: meta.title,
9109 save_msg: String::new(),
9110 save_full_msg: self.ctx.globals.syscom.current_save_full_message.clone(),
9111 local_stream: local_stream.clone(),
9112 local_ex_stream: local_ex_stream.clone(),
9113 sel_saves: meta.sel_saves,
9114 });
9115 let snap = self.ctx.local_save_snapshot.as_ref().unwrap();
9120 self.ctx.globals.syscom.current_save_scene_title = snap.save_scene_title.clone();
9121 }
9122 if snapshot.scene_name.is_empty() {
9123 log::error!(
9124 "[SG_SAVELOAD] aborting load (kind={:?} idx={}): saved snapshot has empty scene_name. \
9125 This save file is unusable; please delete it.",
9126 req.kind, req.index
9127 );
9128 return Ok(());
9129 }
9130 let (mut stream, scene_no) = self.load_scene_stream(&snapshot.scene_name, 0)?;
9131 stream.set_prg_cntr(snapshot.pc.max(0) as usize)?;
9132 self.stream = stream;
9133 self.int_stack = snapshot.int_stack;
9134 self.str_stack = snapshot.str_stack;
9135 self.element_points = snapshot.element_points;
9136 self.call_stack = snapshot.call_stack;
9137 if self.call_stack.is_empty() {
9138 self.call_stack.push(self.scene_base_call());
9139 }
9140 self.gosub_return_stack.clear();
9141 self.current_scene_no = if snapshot.scene_no >= 0 { Some(snapshot.scene_no as usize) } else { Some(scene_no) };
9142 self.current_scene_name = Some(snapshot.scene_name);
9143 self.current_line_no = snapshot.line_no;
9144 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
9145 self.ctx.current_scene_name = self.current_scene_name.clone();
9146 self.ctx.current_line_no = self.current_line_no as i64;
9147 self.ctx.wait = runtime::wait::VmWait::default();
9148 self.halted = false;
9149 self.delayed_ret_form = None;
9150 self.restore_runtime_bindings_after_load();
9158 self.ctx.mark_runtime_load_completed();
9159 Ok(())
9160 }
9161
9162 fn restore_runtime_bindings_after_load(&mut self) {
9177 struct RebuildTask {
9178 stage_idx: i64,
9179 path: String,
9180 runtime_slot: usize,
9181 obj_snapshot: runtime::globals::ObjectState,
9182 }
9183
9184 fn needs_gfx_restore(obj: &runtime::globals::ObjectState) -> bool {
9189 if !obj.used {
9190 return false;
9191 }
9192 let has_file = obj.file_name.as_deref().map(|s| !s.is_empty()).unwrap_or(false);
9193 has_file && matches!(obj.object_type, 2 | 8 | 10 | 11)
9194 }
9195
9196 fn assign_child_slots_and_backend(
9197 obj: &mut runtime::globals::ObjectState,
9198 next_nested: &mut usize,
9199 ) {
9200 if needs_gfx_restore(obj) {
9201 obj.backend = runtime::globals::ObjectBackend::Gfx;
9202 }
9203 for child in &mut obj.runtime.child_objects {
9204 if child.nested_runtime_slot.is_none() {
9205 child.nested_runtime_slot = Some(*next_nested);
9206 *next_nested += 1;
9207 }
9208 assign_child_slots_and_backend(child, next_nested);
9209 }
9210 }
9211
9212 fn collect_rebuild_tasks(
9213 out: &mut Vec<RebuildTask>,
9214 stage_idx: i64,
9215 path: String,
9216 slot_hint: usize,
9217 obj: &runtime::globals::ObjectState,
9218 ) {
9219 let runtime_slot = obj.runtime_slot_or(slot_hint);
9220 if needs_gfx_restore(obj) {
9221 out.push(RebuildTask {
9222 stage_idx,
9223 path: path.clone(),
9224 runtime_slot,
9225 obj_snapshot: obj.clone(),
9226 });
9227 }
9228 for (child_idx, child) in obj.runtime.child_objects.iter().enumerate() {
9229 collect_rebuild_tasks(
9230 out,
9231 stage_idx,
9232 format!("{path}.child[{child_idx}]"),
9233 child_idx,
9234 child,
9235 );
9236 }
9237 }
9238
9239 let stage_form_ids: Vec<u32> = self.ctx.globals.stage_forms.keys().copied().collect();
9245 for form_id in &stage_form_ids {
9246 let Some(stage_form) = self.ctx.globals.stage_forms.get_mut(form_id) else {
9247 continue;
9248 };
9249 let mut stage_ids: Vec<i64> = stage_form
9250 .object_lists
9251 .keys()
9252 .chain(stage_form.mwnd_lists.keys())
9253 .copied()
9254 .collect();
9255 stage_ids.sort_unstable();
9256 stage_ids.dedup();
9257
9258 for stage_idx in stage_ids {
9259 let mut next_nested = stage_form
9260 .next_nested_object_slot
9261 .get(&stage_idx)
9262 .copied()
9263 .unwrap_or(100000)
9264 .max(100000);
9265 let mut next_embedded = stage_form
9266 .next_embedded_object_slot
9267 .get(&stage_idx)
9268 .copied()
9269 .unwrap_or(200000)
9270 .max(200000);
9271 let existing_embedded = stage_form.embedded_object_slots.clone();
9272 let mut embedded_assignments: Vec<(String, usize)> = Vec::new();
9273 let mut alloc_embedded = |key: String| -> usize {
9274 let full = format!("{stage_idx}:{key}");
9275 if let Some(slot) = existing_embedded.get(&full).copied() {
9276 return slot;
9277 }
9278 let slot = next_embedded;
9279 next_embedded += 1;
9280 embedded_assignments.push((full, slot));
9281 slot
9282 };
9283
9284 if let Some(objs) = stage_form.object_lists.get_mut(&stage_idx) {
9285 for obj in objs.iter_mut() {
9286 assign_child_slots_and_backend(obj, &mut next_nested);
9287 }
9288 }
9289 if let Some(mwnds) = stage_form.mwnd_lists.get_mut(&stage_idx) {
9290 for (mwnd_idx, m) in mwnds.iter_mut().enumerate() {
9291 for (i, obj) in m.button_list.iter_mut().enumerate() {
9292 if obj.nested_runtime_slot.is_none() {
9293 obj.nested_runtime_slot = Some(alloc_embedded(format!(
9294 "mwnd_button_{stage_idx}_{mwnd_idx}_{i}"
9295 )));
9296 }
9297 assign_child_slots_and_backend(obj, &mut next_nested);
9298 }
9299 for (i, obj) in m.face_list.iter_mut().enumerate() {
9300 if obj.nested_runtime_slot.is_none() {
9301 obj.nested_runtime_slot = Some(alloc_embedded(format!(
9302 "mwnd_face_{stage_idx}_{mwnd_idx}_{i}"
9303 )));
9304 }
9305 assign_child_slots_and_backend(obj, &mut next_nested);
9306 }
9307 for (i, obj) in m.object_list.iter_mut().enumerate() {
9308 if obj.nested_runtime_slot.is_none() {
9309 obj.nested_runtime_slot = Some(alloc_embedded(format!(
9310 "mwnd_object_{stage_idx}_{mwnd_idx}_{i}"
9311 )));
9312 }
9313 assign_child_slots_and_backend(obj, &mut next_nested);
9314 }
9315 }
9316 }
9317
9318 for (key, slot) in embedded_assignments {
9319 stage_form.embedded_object_slots.entry(key).or_insert(slot);
9320 }
9321 stage_form
9322 .next_nested_object_slot
9323 .insert(stage_idx, next_nested);
9324 stage_form
9325 .next_embedded_object_slot
9326 .insert(stage_idx, next_embedded);
9327 }
9328 }
9329
9330 let mut tasks: Vec<RebuildTask> = Vec::new();
9331 for form_id in &stage_form_ids {
9332 let Some(stage_form) = self.ctx.globals.stage_forms.get(form_id) else {
9333 continue;
9334 };
9335 let mut stage_ids: Vec<i64> = stage_form
9336 .object_lists
9337 .keys()
9338 .chain(stage_form.mwnd_lists.keys())
9339 .copied()
9340 .collect();
9341 stage_ids.sort_unstable();
9342 stage_ids.dedup();
9343 for stage_idx in stage_ids {
9344 if let Some(objs) = stage_form.object_lists.get(&stage_idx) {
9345 for (obj_idx, obj) in objs.iter().enumerate() {
9346 collect_rebuild_tasks(
9347 &mut tasks,
9348 stage_idx,
9349 format!("stage[{stage_idx}].object[{obj_idx}]"),
9350 obj_idx,
9351 obj,
9352 );
9353 }
9354 }
9355 if let Some(mwnds) = stage_form.mwnd_lists.get(&stage_idx) {
9356 for (mwnd_idx, m) in mwnds.iter().enumerate() {
9357 for (i, obj) in m.button_list.iter().enumerate() {
9358 collect_rebuild_tasks(
9359 &mut tasks,
9360 stage_idx,
9361 format!("stage[{stage_idx}].mwnd[{mwnd_idx}].button[{i}]"),
9362 i,
9363 obj,
9364 );
9365 }
9366 for (i, obj) in m.face_list.iter().enumerate() {
9367 collect_rebuild_tasks(
9368 &mut tasks,
9369 stage_idx,
9370 format!("stage[{stage_idx}].mwnd[{mwnd_idx}].face[{i}]"),
9371 i,
9372 obj,
9373 );
9374 }
9375 for (i, obj) in m.object_list.iter().enumerate() {
9376 collect_rebuild_tasks(
9377 &mut tasks,
9378 stage_idx,
9379 format!("stage[{stage_idx}].mwnd[{mwnd_idx}].object[{i}]"),
9380 i,
9381 obj,
9382 );
9383 }
9384 }
9385 }
9386 }
9387 }
9388
9389 let mut unsupported_count = 0usize;
9390 for form_id in &stage_form_ids {
9391 let Some(stage_form) = self.ctx.globals.stage_forms.get(form_id) else {
9392 continue;
9393 };
9394 for (_stage_idx, objs) in &stage_form.object_lists {
9395 for obj in objs {
9396 if obj.used && obj.object_type != 0 && !needs_gfx_restore(obj) {
9397 unsupported_count += 1;
9398 }
9399 }
9400 }
9401 }
9402 if unsupported_count > 0 {
9403 log::warn!(
9404 "[SG_SAVELOAD] {unsupported_count} loaded top-level object(s) have type-specific backends whose runtime sprite IDs are not reconstructed by the Gfx restore path"
9405 );
9406 }
9407
9408 for task in tasks {
9409 if let Err(err) = self.ctx.gfx.restore_gfx_object_from_globals(
9410 &mut self.ctx.images,
9411 &mut self.ctx.layers,
9412 task.stage_idx,
9413 task.runtime_slot as i64,
9414 &task.obj_snapshot,
9415 ) {
9416 log::warn!(
9417 "[SG_SAVELOAD] restore_gfx_object_from_globals path={} slot={} file={:?} failed: {err:#}",
9418 task.path,
9419 task.runtime_slot,
9420 task.obj_snapshot.file_name
9421 );
9422 }
9423 }
9424 }
9425
9426 fn drain_runtime_save_load_requests(&mut self) -> Result<()> {
9427 if self.ctx.take_pending_auto_savepoint() {
9431 self.build_local_save_snapshot();
9432 }
9433 if let Some(req) = self.ctx.take_runtime_save_request() {
9434 self.perform_runtime_save_request(req)?;
9435 }
9436 if let Some(req) = self.ctx.take_runtime_load_request() {
9437 self.perform_runtime_load_request(req)?;
9438 }
9439 Ok(())
9440 }
9441
9442 fn exec_return(&mut self, args: Vec<Value>) -> Result<bool> {
9443 let Some(callee) = self.call_stack.pop() else {
9445 return Ok(false);
9446 };
9447
9448 let caller = match self.call_stack.last_mut() {
9450 Some(f) => f,
9451 None => {
9452 return Ok(false);
9454 }
9455 };
9456
9457 let return_pc = caller.return_pc;
9463 let ret_form = caller.ret_form;
9464 if std::env::var_os("SIGLUS_TRACE_CALL_RETURN_PC").is_some() {
9465 eprintln!(
9466 "[SG_CALL_PC] return depth={} pc=0x{:x} ret_form={} override={:?} args={:?}",
9467 self.call_stack.len() + 1,
9468 return_pc,
9469 ret_form,
9470 callee.return_override,
9471 args
9472 );
9473 }
9474 self.stream.set_prg_cntr(return_pc)?;
9475
9476 match ret_form {
9477 f if f == self.cfg.fm_int => {
9478 let v = args.get(0).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
9479 self.push_int(v);
9480 }
9481 f if f == self.cfg.fm_str => {
9482 let s = args
9483 .get(0)
9484 .and_then(|v| v.as_str().map(|s| s.to_string()))
9485 .unwrap_or_default();
9486 self.push_str(s);
9487 }
9488 _ => {
9489 }
9491 }
9492
9493 if callee.excall_proc {
9494 self.mark_excall_script_proc_pop_requested();
9495 }
9496
9497 Ok(callee.frame_action_proc)
9498 }
9499
9500 fn scene_base_call(&self) -> CallFrame {
9501 self.make_call_frame(self.cfg.fm_void, false, false, 0, None)
9502 }
9503
9504 fn load_scene_stream(
9505 &mut self,
9506 scene_name: &str,
9507 z_no: i32,
9508 ) -> Result<(SceneStream<'a>, usize)> {
9509 self.ensure_scene_pck_cache()?;
9510 let scene_no = self
9511 .scene_pck_cache
9512 .as_ref()
9513 .expect("scene pck cache initialized")
9514 .find_scene_no(scene_name)
9515 .ok_or_else(|| anyhow!("scene not found: {}", scene_name))?;
9516 let mut stream = self.cached_scene_stream(scene_no)?;
9517 self.sg_omv_trace(format!(
9518 "load_scene_stream resolved target={} scene_no={} z={} initial_pc=0x{:x} scn_len=0x{:x}",
9519 scene_name,
9520 scene_no,
9521 z_no,
9522 stream.get_prg_cntr(),
9523 stream.scn.len()
9524 ));
9525 self.call_cmd_names = self
9526 .scene_pck_cache
9527 .as_ref()
9528 .expect("scene pck cache initialized")
9529 .inc_cmd_name_map
9530 .clone();
9531 self.user_cmd_names = stream.scn_cmd_name_map.clone();
9532 match stream.jump_to_z_label(z_no.max(0) as usize) {
9533 Ok(()) => {
9534 self.sg_omv_trace(format!(
9535 "load_scene_stream entered target={} scene_no={} z={} target_pc=0x{:x} user_cmd_cnt={} call_cmd_cnt={}",
9536 scene_name,
9537 scene_no,
9538 z_no,
9539 stream.get_prg_cntr(),
9540 stream.scn_cmd_name_map.len(),
9541 self.call_cmd_names.len()
9542 ));
9543 }
9544 Err(e) => {
9545 self.sg_omv_trace(format!(
9546 "load_scene_stream failed target={} scene_no={} z={} error={}",
9547 scene_name,
9548 scene_no,
9549 z_no,
9550 e
9551 ));
9552 return Err(e);
9553 }
9554 }
9555 Ok((stream, scene_no))
9556 }
9557
9558 fn jump_to_scene_name(&mut self, scene_name: &str, z_no: i32) -> Result<()> {
9559 self.sg_omv_trace(format!("scene_jump target={} z={}", scene_name, z_no));
9560 let (stream, scene_no) = self.load_scene_stream(scene_name, z_no)?;
9561 self.stream = stream;
9562 self.current_scene_no = Some(scene_no);
9563 self.current_scene_name = Some(scene_name.to_string());
9564 self.current_line_no = -1;
9565 self.ctx.current_scene_no = Some(scene_no as i64);
9566 self.ctx.current_scene_name = Some(scene_name.to_string());
9567 self.ctx.current_line_no = -1;
9568 self.sg_omv_trace(format!(
9569 "scene_jump_entered target={} scene_no={} z={} pc=0x{:x}",
9570 scene_name,
9571 scene_no,
9572 z_no,
9573 self.stream.get_prg_cntr()
9574 ));
9575 Ok(())
9576 }
9577
9578 fn farcall_scene_name_ex(
9579 &mut self,
9580 scene_name: &str,
9581 z_no: i32,
9582 ret_form: i32,
9583 ex_call_proc: bool,
9584 scratch_source_args: &[Value],
9585 ) -> Result<()> {
9586 self.sg_omv_trace(format!(
9587 "scene_farcall target={} z={} ret_form={} ex_call_proc={} scratch_argc={}",
9588 scene_name,
9589 z_no,
9590 ret_form,
9591 ex_call_proc,
9592 scratch_source_args.len()
9593 ));
9594 self.trace_cf_branch_farcall(
9595 self.stream.get_prg_cntr(),
9596 scene_name,
9597 z_no,
9598 ret_form,
9599 ex_call_proc,
9600 scratch_source_args,
9601 );
9602 if (scene_name == "sys20_adv00" && matches!(z_no, 10 | 13 | 17))
9603 || (scene_name == "sys20_adv01" && z_no == 0)
9604 {
9605 let args_dbg = scratch_source_args
9606 .iter()
9607 .map(|v| format!("{v:?}"))
9608 .collect::<Vec<_>>()
9609 .join(", ");
9610 self.sg_cgm_coord_trace(format!(
9611 "farcall target={} z={} ret_form={} ex_call_proc={} argc={} args=[{}]",
9612 scene_name,
9613 z_no,
9614 ret_form,
9615 ex_call_proc,
9616 scratch_source_args.len(),
9617 args_dbg
9618 ));
9619 }
9620 let saved = SceneExecFrame {
9621 stream: self.stream.clone(),
9622 user_cmd_names: self.user_cmd_names.clone(),
9623 call_cmd_names: self.call_cmd_names.clone(),
9624 int_stack: std::mem::take(&mut self.int_stack),
9625 str_stack: std::mem::take(&mut self.str_stack),
9626 element_points: std::mem::take(&mut self.element_points),
9627 call_stack: std::mem::take(&mut self.call_stack),
9628 gosub_return_stack: std::mem::take(&mut self.gosub_return_stack),
9629 user_props: self.enter_cross_scene_user_prop_scope(),
9630 current_scene_no: self.current_scene_no,
9631 current_scene_name: self.current_scene_name.clone(),
9632 current_line_no: self.current_line_no,
9633 ret_form,
9634 excall_proc: ex_call_proc,
9635 };
9636 self.scene_stack.push(saved);
9637 let (stream, scene_no) = self.load_scene_stream(scene_name, z_no)?;
9638 self.stream = stream;
9639 let scratch_args = self.call_scratch_from_args(scratch_source_args);
9640 self.call_stack.push(self.make_call_frame(
9641 self.cfg.fm_void,
9642 false,
9643 false,
9644 scratch_source_args.len(),
9645 Some(scratch_args),
9646 ));
9647 self.current_scene_no = Some(scene_no);
9648 self.current_scene_name = Some(scene_name.to_string());
9649 self.current_line_no = -1;
9650 self.ctx.current_scene_no = Some(scene_no as i64);
9651 self.ctx.current_scene_name = Some(scene_name.to_string());
9652 self.ctx.current_line_no = -1;
9653 self.sg_omv_trace(format!(
9654 "scene_farcall_entered target={} scene_no={} z={} pc=0x{:x} call_depth={} scene_stack={}",
9655 scene_name,
9656 scene_no,
9657 z_no,
9658 self.stream.get_prg_cntr(),
9659 self.call_stack.len(),
9660 self.scene_stack.len()
9661 ));
9662 if ex_call_proc {
9663 self.mark_excall_script_proc_requested();
9664 }
9665 Ok(())
9666 }
9667
9668 fn return_from_scene(&mut self, args: Vec<Value>) -> Result<bool> {
9669 let Some(saved) = self.scene_stack.pop() else {
9670 return Ok(false);
9671 };
9672 self.sg_omv_trace(format!(
9673 "scene_return restore_scene={:?} restore_line={} ret_form={} args={:?}",
9674 saved.current_scene_name,
9675 saved.current_line_no,
9676 saved.ret_form,
9677 args
9678 ));
9679 self.stream = saved.stream;
9680 self.int_stack = saved.int_stack;
9681 self.str_stack = saved.str_stack;
9682 self.element_points = saved.element_points;
9683 self.call_stack = saved.call_stack;
9684 self.gosub_return_stack = saved.gosub_return_stack;
9685 self.restore_cross_scene_user_prop_scope(saved.user_props);
9686 self.current_scene_no = saved.current_scene_no;
9687 self.current_scene_name = saved.current_scene_name;
9688 self.current_line_no = saved.current_line_no;
9689 self.ctx.current_scene_no = self.current_scene_no.map(|v| v as i64);
9690 self.ctx.current_scene_name = self.current_scene_name.clone();
9691 self.ctx.current_line_no = self.current_line_no as i64;
9692 self.user_cmd_names = saved.user_cmd_names;
9693 self.call_cmd_names = saved.call_cmd_names;
9694 let was_excall_proc = saved.excall_proc;
9695
9696 match saved.ret_form {
9697 f if f == self.cfg.fm_int || f == self.cfg.fm_label => {
9698 let v = args.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
9699 self.push_int(v);
9700 }
9701 f if f == self.cfg.fm_str => {
9702 let s = args
9703 .first()
9704 .and_then(|v| v.as_str().map(|s| s.to_string()))
9705 .unwrap_or_default();
9706 self.push_str(s);
9707 }
9708 _ => {}
9709 }
9710 if was_excall_proc {
9711 self.mark_excall_script_proc_pop_requested();
9712 }
9713 if self.cf_branch_trace_interesting_line() {
9714 self.sg_cf_branch_trace(
9715 self.stream.get_prg_cntr(),
9716 format!("kind=RETURN_RESTORED ret_form={} args={:?}", saved.ret_form, args),
9717 );
9718 }
9719 self.sg_omv_trace(format!(
9720 "scene_return_restored scene={:?} scene_no={:?} line={} pc=0x{:x} call_depth={} scene_stack={}",
9721 self.current_scene_name,
9722 self.current_scene_no,
9723 self.current_line_no,
9724 self.stream.get_prg_cntr(),
9725 self.call_stack.len(),
9726 self.scene_stack.len()
9727 ));
9728 Ok(true)
9729 }
9730
9731 fn exec_builtin_global_control(&mut self, form_id: i32, ret_form: i32) -> Result<bool> {
9732 match form_id {
9733 constants::elm_value::GLOBAL_SAVEPOINT => {
9734 self.int_stack.push(1);
9740 self.save_point = Some(self.make_resume_point());
9741 self.build_local_save_snapshot();
9742 let _ = self.int_stack.pop();
9743 if ret_form != self.cfg.fm_void {
9744 self.ctx.stack.push(Value::Int(0));
9745 }
9746 Ok(true)
9747 }
9748 constants::elm_value::GLOBAL_CLEAR_SAVEPOINT => {
9749 self.save_point = None;
9750 self.ctx.local_save_snapshot = None;
9751 Ok(true)
9752 }
9753 constants::elm_value::GLOBAL_CHECK_SAVEPOINT => {
9754 let has = self
9755 .ctx
9756 .local_save_snapshot
9757 .as_ref()
9758 .map(|s| !s.local_stream.is_empty())
9759 .unwrap_or(false);
9760 self.ctx.stack.push(Value::Int(if has { 1 } else { 0 }));
9761 Ok(true)
9762 }
9763 constants::elm_value::GLOBAL_SELPOINT => {
9764 let point = self.make_resume_point();
9765 self.sel_point_stack.clear();
9766 self.sel_point_stack.push(point);
9767 Ok(true)
9768 }
9769 constants::elm_value::GLOBAL_CLEAR_SELPOINT => {
9770 self.sel_point_stack.clear();
9771 Ok(true)
9772 }
9773 constants::elm_value::GLOBAL_CHECK_SELPOINT => {
9774 self.ctx
9775 .stack
9776 .push(Value::Int(if self.has_sel_point() { 1 } else { 0 }));
9777 Ok(true)
9778 }
9779 constants::elm_value::GLOBAL_STACK_SELPOINT => {
9780 let point = self.make_resume_point();
9781 self.sel_point_stack.push(point);
9782 Ok(true)
9783 }
9784 constants::elm_value::GLOBAL_DROP_SELPOINT => {
9785 let _ = self.sel_point_stack.pop();
9786 Ok(true)
9787 }
9788 _ => Ok(false),
9789 }
9790 }
9791
9792 fn exec_builtin_scene_form(
9793 &mut self,
9794 elm: &[i32],
9795 form_id: i32,
9796 al_id: i32,
9797 ret_form: i32,
9798 args: &[Value],
9799 ) -> Result<bool> {
9800 const FORM_GLOBAL_JUMP: i32 = crate::runtime::forms::codes::elm_value::GLOBAL_JUMP;
9801 const FORM_GLOBAL_FARCALL: i32 = crate::runtime::forms::codes::elm_value::GLOBAL_FARCALL;
9802 const FORM_GLOBAL_SYSCOM: i32 = crate::runtime::forms::codes::FORM_GLOBAL_SYSCOM as i32;
9803 const FORM_SYSCOM: i32 = crate::runtime::forms::codes::FM_SYSCOM;
9804 const ELM_SYSCOM_CALL_EX: i32 = crate::runtime::forms::codes::elm_value::SYSCOM_CALL_EX;
9805 if (form_id == FORM_GLOBAL_SYSCOM || form_id == FORM_SYSCOM)
9806 && elm.get(1).copied() == Some(ELM_SYSCOM_CALL_EX)
9807 {
9808 self.sg_omv_trace_command("builtin", elm, form_id, ELM_SYSCOM_CALL_EX, al_id, self.cfg.fm_void, args);
9809 let scene_name = args.get(0).and_then(|v| v.as_str()).unwrap_or("");
9810 let z_no = if al_id == 1 {
9811 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
9812 } else {
9813 0
9814 };
9815 let scratch_args = if al_id == 1 && args.len() > 2 {
9816 &args[2..]
9817 } else {
9818 &[]
9819 };
9820 self.farcall_scene_name_ex(scene_name, z_no, self.cfg.fm_void, true, scratch_args)?;
9821 self.ctx.request_proc_boundary(runtime::ProcKind::Script);
9822 self.ctx.stack.clear();
9823 return Ok(true);
9824 }
9825 if form_id == FORM_GLOBAL_JUMP {
9826 self.sg_omv_trace_command("builtin", &[], form_id, form_id, al_id, ret_form, args);
9827 let scene_name = args.get(0).and_then(|v| v.as_str()).unwrap_or("");
9828 let z_no = if al_id >= 1 {
9829 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
9830 } else {
9831 0
9832 };
9833 if !scene_name.is_empty() {
9834 self.jump_to_scene_name(scene_name, z_no)?;
9835 }
9836 return Ok(true);
9837 }
9838 if form_id == FORM_GLOBAL_FARCALL {
9839 self.sg_omv_trace_command("builtin", &[], form_id, form_id, al_id, ret_form, args);
9840 let scene_name = args.get(0).and_then(|v| v.as_str()).unwrap_or("");
9841 let z_no = if al_id >= 1 {
9842 args.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32
9843 } else {
9844 0
9845 };
9846 if !scene_name.is_empty() {
9847 self.farcall_scene_name_ex(
9848 scene_name,
9849 z_no,
9850 self.cfg.fm_int,
9851 false,
9852 if al_id >= 1 && args.len() > 2 {
9853 &args[2..]
9854 } else {
9855 &[]
9856 },
9857 )?;
9858 } else {
9859 self.push_default_for_ret(self.cfg.fm_int);
9860 }
9861 return Ok(true);
9862 }
9863 Ok(false)
9864 }
9865
9866 fn take_ctx_return(&mut self, ret_form: i32) -> Result<()> {
9867 if ret_form == self.cfg.fm_void {
9868 self.ctx.stack.clear();
9869 return Ok(());
9870 }
9871
9872 let v = self.ctx.pop();
9873 match ret_form {
9874 f if f == self.cfg.fm_int || f == self.cfg.fm_label => match v {
9875 Some(Value::Int(n)) => self.push_int(n as i32),
9876 Some(Value::NamedArg { value, .. }) => match *value {
9877 Value::Int(n) => self.push_int(n as i32),
9878 _ => bail!("non-int ctx return for form {}", ret_form),
9879 },
9880 Some(_) => bail!("non-int ctx return for form {}", ret_form),
9881 None => bail!(
9882 "missing ctx return int for form {}: scene={} scene_no={} line={} pc=0x{:x} vm_call={:?}",
9883 ret_form,
9884 self.current_scene_name.as_deref().unwrap_or("<none>"),
9885 self.current_scene_no
9886 .map(|v| v.to_string())
9887 .unwrap_or_else(|| "-".to_string()),
9888 self.current_line_no,
9889 self.stream.get_prg_cntr(),
9890 self.ctx.vm_call
9891 ),
9892 },
9893 f if f == self.cfg.fm_str => match v {
9894 Some(Value::Str(s)) => self.push_str(s),
9895 Some(Value::NamedArg { value, .. }) => match *value {
9896 Value::Str(s) => self.push_str(s),
9897 _ => bail!("non-str ctx return for form {}", ret_form),
9898 },
9899 Some(_) => bail!("non-str ctx return for form {}", ret_form),
9900 None => bail!("missing ctx return str for form {}", ret_form),
9901 },
9902 f if f == self.cfg.fm_list => match v {
9903 Some(Value::Element(elm)) => self.push_element(elm),
9904 Some(Value::NamedArg { value, .. }) => match *value {
9905 Value::Element(elm) => self.push_element(elm),
9906 _ => bail!("non-element ctx return for FM_LIST"),
9907 },
9908 Some(Value::List(_)) => {
9909 bail!("FM_LIST ctx return used raw Value::List; expected element reference")
9910 }
9911 Some(_) => bail!("non-element ctx return for FM_LIST"),
9912 None => bail!("missing ctx return element for FM_LIST"),
9913 },
9914 _ => match v {
9915 Some(Value::Element(elm)) => self.push_element(elm),
9916 Some(Value::NamedArg { value, .. }) => match *value {
9917 Value::Element(elm) => self.push_element(elm),
9918 _ => bail!("non-element ctx return for form {}", ret_form),
9919 },
9920 Some(_) => bail!("non-element ctx return for form {}", ret_form),
9921 None => bail!("missing ctx return element for form {}", ret_form),
9922 },
9923 }
9924 Ok(())
9925 }
9926
9927 fn push_default_for_ret(&mut self, ret_form: i32) {
9928 if ret_form == self.cfg.fm_int || ret_form == self.cfg.fm_label {
9929 self.push_int(0);
9930 } else if ret_form == self.cfg.fm_str {
9931 self.push_str(String::new());
9932 }
9933 }
9934
9935 fn update_compact_context_from_element(&mut self, elm: &[i32]) {
9936 let stage_form = if self.ctx.ids.form_global_stage != 0 {
9937 self.ctx.ids.form_global_stage as i32
9938 } else {
9939 crate::runtime::forms::codes::FORM_GLOBAL_STAGE as i32
9940 };
9941 let elm_array = if self.ctx.ids.elm_array != 0 {
9942 self.ctx.ids.elm_array
9943 } else {
9944 crate::runtime::forms::codes::ELM_ARRAY
9945 };
9946 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
9947 self.ctx.ids.stage_elm_object
9948 } else {
9949 crate::runtime::forms::codes::STAGE_ELM_OBJECT
9950 };
9951 let stage_mwnd = crate::runtime::forms::codes::STAGE_ELM_MWND;
9952 let stage_btnselitem = crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM;
9953
9954 fn is_array_token(token: i32, elm_array: i32) -> bool {
9955 token == elm_array || token == crate::runtime::forms::codes::ELM_ARRAY
9956 }
9957
9958 fn object_chain_tail_is_plain_object_ref(
9959 elm: &[i32],
9960 mut pos: usize,
9961 elm_array: i32,
9962 ) -> bool {
9963 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
9964 while pos + 2 < elm.len()
9965 && elm[pos] == object_child
9966 && is_array_token(elm[pos + 1], elm_array)
9967 {
9968 if elm[pos + 2] < 0 {
9969 return false;
9970 }
9971 pos += 3;
9972 }
9973 pos == elm.len()
9974 }
9975
9976 let resolved = if elm.len() >= 6
9977 && elm[0] == stage_form
9978 && is_array_token(elm[1], elm_array)
9979 && elm[2] >= 0
9980 && elm[3] == stage_object
9981 && is_array_token(elm[4], elm_array)
9982 && elm[5] >= 0
9983 && object_chain_tail_is_plain_object_ref(elm, 6, elm_array)
9984 {
9985 Some((elm[2] as i64, elm[5] as usize))
9986 } else if elm.len() >= 9
9987 && elm[0] == stage_form
9988 && is_array_token(elm[1], elm_array)
9989 && elm[2] >= 0
9990 && elm[3] == stage_mwnd
9991 && is_array_token(elm[4], elm_array)
9992 && elm[5] >= 0
9993 && matches!(
9994 elm[6],
9995 crate::runtime::forms::codes::elm_value::MWND_OBJECT
9996 | crate::runtime::forms::codes::elm_value::MWND_BUTTON
9997 | crate::runtime::forms::codes::elm_value::MWND_FACE
9998 )
9999 && is_array_token(elm[7], elm_array)
10000 && elm[8] >= 0
10001 && object_chain_tail_is_plain_object_ref(elm, 9, elm_array)
10002 {
10003 Some((elm[2] as i64, elm[8] as usize))
10004 } else if elm.len() >= 9
10005 && elm[0] == stage_form
10006 && is_array_token(elm[1], elm_array)
10007 && elm[2] >= 0
10008 && elm[3] == stage_btnselitem
10009 && is_array_token(elm[4], elm_array)
10010 && elm[5] >= 0
10011 && elm[6] == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT
10012 && is_array_token(elm[7], elm_array)
10013 && elm[8] >= 0
10014 && object_chain_tail_is_plain_object_ref(elm, 9, elm_array)
10015 {
10016 Some((elm[2] as i64, elm[8] as usize))
10017 } else {
10018 None
10019 };
10020
10021 let Some((stage_idx, fallback_obj_idx)) = resolved else {
10022 return;
10023 };
10024 let runtime_slot = self.runtime_slot_from_object_chain(stage_idx, fallback_obj_idx, elm);
10025 let prev_chain = self.ctx.globals.current_object_chain.clone();
10026 let prev_stage_object = self.ctx.globals.current_stage_object;
10027 self.ctx.globals.current_object_chain = Some(elm.to_vec());
10028 self.ctx.globals.current_stage_object = Some((stage_idx, runtime_slot));
10029 if Self::sg_mwnd_object_trace_enabled() && Self::sg_mwnd_chain_interesting(elm) {
10030 self.sg_mwnd_object_trace(format!(
10031 "update_compact_context elm={:?} resolved_stage={} fallback_idx={} runtime_slot={} prev_chain={:?} prev_stage_object={:?}",
10032 elm,
10033 stage_idx,
10034 fallback_obj_idx,
10035 runtime_slot,
10036 prev_chain,
10037 prev_stage_object
10038 ));
10039 }
10040 }
10041
10042 fn update_compact_context_from_object_dispatch_chain(&mut self, elm: &[i32]) {
10043 if elm.is_empty() {
10044 return;
10045 }
10046 let stage_form = if self.ctx.ids.form_global_stage != 0 {
10047 self.ctx.ids.form_global_stage as i32
10048 } else {
10049 crate::runtime::forms::codes::FORM_GLOBAL_STAGE as i32
10050 };
10051 let elm_array = if self.ctx.ids.elm_array != 0 {
10052 self.ctx.ids.elm_array
10053 } else {
10054 crate::runtime::forms::codes::ELM_ARRAY
10055 };
10056 let stage_object = if self.ctx.ids.stage_elm_object != 0 {
10057 self.ctx.ids.stage_elm_object
10058 } else {
10059 crate::runtime::forms::codes::STAGE_ELM_OBJECT
10060 };
10061 let stage_mwnd = crate::runtime::forms::codes::STAGE_ELM_MWND;
10062 let stage_btnselitem = crate::runtime::forms::codes::STAGE_ELM_BTNSELITEM;
10063 let object_child = crate::runtime::forms::codes::elm_value::OBJECT_CHILD;
10064
10065 fn is_array_token(token: i32, elm_array: i32) -> bool {
10066 token == elm_array || token == crate::runtime::forms::codes::ELM_ARRAY
10067 }
10068
10069 let mut pos = if elm.len() >= 6
10070 && elm[0] == stage_form
10071 && is_array_token(elm[1], elm_array)
10072 && elm[2] >= 0
10073 && elm[3] == stage_object
10074 && is_array_token(elm[4], elm_array)
10075 && elm[5] >= 0
10076 {
10077 6usize
10078 } else if elm.len() >= 9
10079 && elm[0] == stage_form
10080 && is_array_token(elm[1], elm_array)
10081 && elm[2] >= 0
10082 && elm[3] == stage_mwnd
10083 && is_array_token(elm[4], elm_array)
10084 && elm[5] >= 0
10085 && matches!(
10086 elm[6],
10087 crate::runtime::forms::codes::elm_value::MWND_OBJECT
10088 | crate::runtime::forms::codes::elm_value::MWND_BUTTON
10089 | crate::runtime::forms::codes::elm_value::MWND_FACE
10090 )
10091 && is_array_token(elm[7], elm_array)
10092 && elm[8] >= 0
10093 {
10094 9usize
10095 } else if elm.len() >= 9
10096 && elm[0] == stage_form
10097 && is_array_token(elm[1], elm_array)
10098 && elm[2] >= 0
10099 && elm[3] == stage_btnselitem
10100 && is_array_token(elm[4], elm_array)
10101 && elm[5] >= 0
10102 && elm[6] == crate::runtime::forms::codes::ELM_BTNSELITEM_OBJECT
10103 && is_array_token(elm[7], elm_array)
10104 && elm[8] >= 0
10105 {
10106 9usize
10107 } else {
10108 return;
10109 };
10110
10111 while pos + 2 < elm.len()
10112 && elm[pos] == object_child
10113 && is_array_token(elm[pos + 1], elm_array)
10114 && elm[pos + 2] >= 0
10115 {
10116 pos += 3;
10117 }
10118
10119 let object_ref = elm[..pos].to_vec();
10120 if Self::sg_mwnd_object_trace_enabled() && Self::sg_mwnd_chain_interesting(elm) {
10121 self.sg_mwnd_object_trace(format!(
10122 "update_context_from_dispatch elm={:?} object_ref={:?} pos={}",
10123 elm,
10124 object_ref,
10125 pos
10126 ));
10127 }
10128 self.update_compact_context_from_element(&object_ref);
10129 }
10130
10131 fn push_return_value_raw(&mut self, v: Value) {
10132 match v {
10133 Value::NamedArg { value, .. } => self.push_return_value_raw(*value),
10134 Value::Int(n) => self.push_int(n as i32),
10135 Value::Str(s) => self.push_str(s),
10136 Value::Element(elm) => {
10137 self.update_compact_context_from_element(&elm);
10138 self.push_element(elm);
10139 }
10140 Value::List(_) => {
10141 panic!("raw Value::List reached push_return_value_raw; expected runtime ref");
10142 }
10143 }
10144 }
10145
10146 fn exec_operate_1(&mut self, form_code: i32, opr: u8) -> Result<()> {
10151 if form_code != self.cfg.fm_int {
10152 self.trace_unknown_form(form_code, "exec_operate_1");
10153 self.push_int(0);
10154 return Ok(());
10155 }
10156
10157 let v = self.pop_int()?;
10158 let out = match opr {
10159 OP_PLUS => v,
10160 OP_MINUS => v.wrapping_neg(),
10161 OP_TILDE => !v,
10162 _ => v,
10163 };
10164 if self.cf_condition_trace_interesting_line() {
10165 self.sg_cf_condition_trace(
10166 self.stream.get_prg_cntr(),
10167 format!(
10168 "kind=OPERATE_1 op={} in={} out={}",
10169 Self::cf_condition_op_name(opr),
10170 v,
10171 out
10172 ),
10173 );
10174 }
10175 self.push_int(out);
10176 Ok(())
10177 }
10178
10179 fn exec_operate_2(&mut self, form_l: i32, form_r: i32, opr: u8) -> Result<()> {
10180 if form_l == self.cfg.fm_int && form_r == self.cfg.fm_int {
10182 let r = self.pop_int()?;
10183 let l = self.pop_int()?;
10184 let out = self.calc_int_int(l, r, opr);
10185 if self.cf_condition_trace_interesting_line() {
10186 self.sg_cf_condition_trace(
10187 self.stream.get_prg_cntr(),
10188 format!(
10189 "kind=OPERATE_2 op={} left={} right={} out={}",
10190 Self::cf_condition_op_name(opr),
10191 l,
10192 r,
10193 out
10194 ),
10195 );
10196 }
10197 self.push_int(out);
10198 return Ok(());
10199 }
10200
10201 if form_l == self.cfg.fm_str && form_r == self.cfg.fm_int {
10203 let r = self.pop_int()?;
10204 let l = self.pop_str()?;
10205 let out = self.calc_str_int(l, r, opr);
10206 self.push_str(out);
10207 return Ok(());
10208 }
10209
10210 if form_l == self.cfg.fm_str && form_r == self.cfg.fm_str {
10212 let r = self.pop_str()?;
10213 let l = self.pop_str()?;
10214 let out = self.calc_str_str(l, r, opr);
10215 match out {
10216 Value::Int(n) => self.push_int(n as i32),
10217 Value::Str(s) => self.push_str(s),
10218 _ => {
10219 self.push_int(0);
10220 }
10221 }
10222 return Ok(());
10223 }
10224
10225 self.trace_unknown_form(form_l, "exec_operate_2.left");
10227 self.trace_unknown_form(form_r, "exec_operate_2.right");
10228 self.push_int(0);
10229 Ok(())
10230 }
10231
10232 fn calc_int_int(&mut self, l: i32, r: i32, opr: u8) -> i32 {
10233 match opr {
10234 OP_PLUS => l.wrapping_add(r),
10235 OP_MINUS => l.wrapping_sub(r),
10236 OP_MULTIPLE => l.wrapping_mul(r),
10237 OP_DIVIDE => {
10238 if r == 0 {
10239 0
10240 } else {
10241 l.wrapping_div(r)
10242 }
10243 }
10244 OP_AMARI => {
10245 if r == 0 {
10246 0
10247 } else {
10248 l.wrapping_rem(r)
10249 }
10250 }
10251
10252 OP_EQUAL => (l == r) as i32,
10253 OP_NOT_EQUAL => (l != r) as i32,
10254 OP_GREATER => (l > r) as i32,
10255 OP_GREATER_EQUAL => (l >= r) as i32,
10256 OP_LESS => (l < r) as i32,
10257 OP_LESS_EQUAL => (l <= r) as i32,
10258
10259 OP_LOGICAL_OR => ((l != 0) || (r != 0)) as i32,
10260 OP_LOGICAL_AND => ((l != 0) && (r != 0)) as i32,
10261
10262 OP_OR => l | r,
10263 OP_AND => l & r,
10264 OP_HAT => l ^ r,
10265 OP_SL => l.wrapping_shl((r as u32) & 31),
10266 OP_SR => l.wrapping_shr((r as u32) & 31),
10267 OP_SR3 => ((l as u32).wrapping_shr((r as u32) & 31)) as i32,
10268
10269 _ => 0,
10270 }
10271 }
10272
10273 fn calc_str_int(&mut self, s: String, n: i32, opr: u8) -> String {
10274 match opr {
10275 OP_MULTIPLE => {
10276 if n <= 0 {
10277 return String::new();
10278 }
10279 let mut out = String::new();
10280 for _ in 0..(n as usize) {
10281 out.push_str(&s);
10282 }
10283 out
10284 }
10285 _ => s,
10286 }
10287 }
10288
10289 fn calc_str_str(&mut self, l: String, r: String, opr: u8) -> Value {
10290 match opr {
10291 OP_PLUS => Value::Str(format!("{l}{r}")),
10292 OP_EQUAL | OP_NOT_EQUAL | OP_GREATER | OP_GREATER_EQUAL | OP_LESS | OP_LESS_EQUAL => {
10293 let ll = l.to_lowercase();
10295 let rr = r.to_lowercase();
10296 let cmp = ll.cmp(&rr);
10297 let b = match opr {
10298 OP_EQUAL => cmp == std::cmp::Ordering::Equal,
10299 OP_NOT_EQUAL => cmp != std::cmp::Ordering::Equal,
10300 OP_GREATER => cmp == std::cmp::Ordering::Greater,
10301 OP_GREATER_EQUAL => cmp != std::cmp::Ordering::Less,
10302 OP_LESS => cmp == std::cmp::Ordering::Less,
10303 OP_LESS_EQUAL => cmp != std::cmp::Ordering::Greater,
10304 _ => false,
10305 };
10306 Value::Int(b as i64)
10307 }
10308 _ => Value::Int(0),
10309 }
10310 }
10311}