1use anyhow::Result;
2
3use crate::runtime::globals::{
4 SaveSlotState, SyscomPendingProc, SyscomPendingProcKind, ToggleFeatureState, ValueFeatureState,
5};
6use crate::runtime::{CommandContext, RuntimeSaveKind, Value};
7use std::fs;
8use std::path::{Path, PathBuf};
9
10use crate::assets::RgbaImage;
11use crate::original_save::{self, SaveKind};
12
13use super::prop_access;
14
15use super::codes::syscom_op::*;
16
17struct Call<'a> {
18 op: i32,
19 params: &'a [Value],
20}
21
22fn parse_call<'a>(ctx: &CommandContext, form_id: u32, args: &'a [Value]) -> Option<Call<'a>> {
23 let (chain_pos, chain) = prop_access::parse_element_chain_ctx(ctx, form_id, args)?;
24 if chain.len() < 2 {
25 return None;
26 }
27 let params = prop_access::script_args(args, chain_pos);
28 Some(Call {
29 op: chain[1],
30 params,
31 })
32}
33
34fn p_i64(params: &[Value], idx: usize) -> i64 {
35 params.get(idx).and_then(|v| v.as_i64()).unwrap_or(0)
36}
37fn p_bool(params: &[Value], idx: usize) -> bool {
38 p_i64(params, idx) != 0
39}
40
41fn sg_debug_enabled_local() -> bool {
42 std::env::var_os("SG_DEBUG").is_some()
43}
44
45fn set_syscom_pending_proc(ctx: &mut CommandContext, kind: SyscomPendingProcKind) {
46 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
47 kind,
48 warning: false,
49 se_play: false,
50 fade_out: false,
51 leave_msgbk: false,
52 save_id: 0,
53 });
54 ctx.globals.syscom.menu_open = false;
55 ctx.globals.syscom.menu_kind = None;
56 ctx.globals.syscom.menu_result = None;
57}
58
59fn gameexe_unquoted_owned(ctx: &CommandContext, key: &str) -> String {
60 ctx.tables
61 .gameexe
62 .as_ref()
63 .and_then(|cfg| cfg.get_unquoted(key))
64 .unwrap_or("")
65 .to_string()
66}
67
68
69fn gameexe_value_owned(ctx: &CommandContext, key: &str) -> String {
70 ctx.tables
71 .gameexe
72 .as_ref()
73 .and_then(|cfg| cfg.get_value(key))
74 .unwrap_or("")
75 .to_string()
76}
77
78fn parse_i64_list_local(raw: &str) -> Vec<i64> {
79 raw.split(|c: char| c == ',' || c.is_whitespace())
80 .filter_map(|part| {
81 let t = part.trim();
82 if t.is_empty() {
83 None
84 } else {
85 t.parse::<i64>().ok()
86 }
87 })
88 .collect()
89}
90
91fn parse_first_i64_local(raw: &str) -> Option<i64> {
92 raw.split(|c: char| c == ',' || c.is_whitespace())
93 .find_map(|part| {
94 let t = part.trim();
95 if t.is_empty() { None } else { t.parse::<i64>().ok() }
96 })
97}
98
99fn config_mouse_cursor_hide_onoff_default(ctx: &CommandContext) -> i64 {
100 parse_first_i64_local(&gameexe_value_owned(ctx, "CONFIG.MOUSE_CURSOR_HIDE_ONOFF"))
101 .unwrap_or(0)
102 .clamp(0, 1)
103}
104
105fn config_mouse_cursor_hide_time_default(ctx: &CommandContext) -> i64 {
106 parse_first_i64_local(&gameexe_value_owned(ctx, "CONFIG.MOUSE_CURSOR_HIDE_TIME"))
107 .unwrap_or(5000)
108 .max(0)
109}
110
111fn config_filter_color_default(ctx: &CommandContext) -> (i64, i64, i64, i64) {
112 let raw = gameexe_value_owned(ctx, "CONFIG.FILTER_COLOR");
113 let vals = parse_i64_list_local(&raw);
114 if vals.len() >= 4 {
115 (
116 vals[0].clamp(0, 255),
117 vals[1].clamp(0, 255),
118 vals[2].clamp(0, 255),
119 vals[3].clamp(0, 255),
120 )
121 } else {
122 (0, 0, 0, 128)
123 }
124}
125
126
127fn local_extra_index(params: &[Value]) -> usize {
128 p_i64(params, 0).clamp(0, 3) as usize
129}
130
131fn local_extra_value_param(params: &[Value]) -> bool {
132 let value_idx = if params.len() >= 2 { 1 } else { 0 };
133 p_bool(params, value_idx)
134}
135
136fn local_extra_i64_param(params: &[Value]) -> i64 {
137 let value_idx = if params.len() >= 2 { 1 } else { 0 };
138 p_i64(params, value_idx)
139}
140
141fn get_local_extra(op: i32, params: &[Value], st: &crate::runtime::globals::SyscomRuntimeState) -> Option<i64> {
142 let idx = local_extra_index(params);
143 let sw = st.local_extra_switches.get(idx).copied().unwrap_or(st.local_extra_switch);
144 let mode = st.local_extra_modes.get(idx).copied().unwrap_or(st.local_extra_mode);
145 Some(match op {
146 GET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG => if sw.onoff { 1 } else { 0 },
147 GET_LOCAL_EXTRA_SWITCH_ENABLE_FLAG => if sw.enable { 1 } else { 0 },
148 GET_LOCAL_EXTRA_SWITCH_EXIST_FLAG => if sw.exist { 1 } else { 0 },
149 CHECK_LOCAL_EXTRA_SWITCH_ENABLE => sw.check_enabled(),
150 GET_LOCAL_EXTRA_MODE_VALUE => mode.value,
151 GET_LOCAL_EXTRA_MODE_ENABLE_FLAG => if mode.enable { 1 } else { 0 },
152 GET_LOCAL_EXTRA_MODE_EXIST_FLAG => if mode.exist { 1 } else { 0 },
153 CHECK_LOCAL_EXTRA_MODE_ENABLE => mode.check_enabled(),
154 _ => return None,
155 })
156}
157
158fn set_local_extra(op: i32, params: &[Value], st: &mut crate::runtime::globals::SyscomRuntimeState) -> bool {
159 let idx = local_extra_index(params);
160 let value = local_extra_value_param(params);
161 match op {
162 SET_LOCAL_EXTRA_SWITCH_ONOFF_FLAG => st.local_extra_switches[idx].onoff = value,
163 SET_LOCAL_EXTRA_SWITCH_ENABLE_FLAG => st.local_extra_switches[idx].enable = value,
164 SET_LOCAL_EXTRA_SWITCH_EXIST_FLAG => st.local_extra_switches[idx].exist = value,
165 SET_LOCAL_EXTRA_MODE_ENABLE_FLAG => st.local_extra_modes[idx].enable = value,
166 SET_LOCAL_EXTRA_MODE_EXIST_FLAG => st.local_extra_modes[idx].exist = value,
167 SET_LOCAL_EXTRA_MODE_VALUE => st.local_extra_modes[idx].value = local_extra_i64_param(params),
168 _ => return false,
169 }
170 st.local_extra_switch = st.local_extra_switches[0];
171 st.local_extra_mode = st.local_extra_modes[0];
172 true
173}
174
175fn get_toggle_get(op: i32, st: &crate::runtime::globals::SyscomRuntimeState) -> Option<i64> {
176 Some(match op {
177 GET_READ_SKIP_ONOFF_FLAG => {
178 if st.read_skip.onoff {
179 1
180 } else {
181 0
182 }
183 }
184 GET_READ_SKIP_ENABLE_FLAG => {
185 if st.read_skip.enable {
186 1
187 } else {
188 0
189 }
190 }
191 GET_READ_SKIP_EXIST_FLAG => {
192 if st.read_skip.exist {
193 1
194 } else {
195 0
196 }
197 }
198 CHECK_READ_SKIP_ENABLE => st.read_skip.check_enabled(),
199 GET_AUTO_SKIP_ONOFF_FLAG => {
200 if st.auto_skip.onoff {
201 1
202 } else {
203 0
204 }
205 }
206 GET_AUTO_SKIP_ENABLE_FLAG => {
207 if st.auto_skip.enable {
208 1
209 } else {
210 0
211 }
212 }
213 GET_AUTO_SKIP_EXIST_FLAG => {
214 if st.auto_skip.exist {
215 1
216 } else {
217 0
218 }
219 }
220 CHECK_AUTO_SKIP_ENABLE => st.auto_skip.check_enabled(),
221 GET_AUTO_MODE_ONOFF_FLAG => {
222 if st.auto_mode.onoff {
223 1
224 } else {
225 0
226 }
227 }
228 GET_AUTO_MODE_ENABLE_FLAG => {
229 if st.auto_mode.enable {
230 1
231 } else {
232 0
233 }
234 }
235 GET_AUTO_MODE_EXIST_FLAG => {
236 if st.auto_mode.exist {
237 1
238 } else {
239 0
240 }
241 }
242 CHECK_AUTO_MODE_ENABLE => st.auto_mode.check_enabled(),
243 GET_HIDE_MWND_ONOFF_FLAG => {
244 if st.hide_mwnd.onoff {
245 1
246 } else {
247 0
248 }
249 }
250 GET_HIDE_MWND_ENABLE_FLAG => {
251 if st.hide_mwnd.enable {
252 1
253 } else {
254 0
255 }
256 }
257 GET_HIDE_MWND_EXIST_FLAG => {
258 if st.hide_mwnd.exist {
259 1
260 } else {
261 0
262 }
263 }
264 CHECK_HIDE_MWND_ENABLE => st.hide_mwnd.check_enabled(),
265 GET_MSG_BACK_ENABLE_FLAG => {
266 if st.msg_back.enable {
267 1
268 } else {
269 0
270 }
271 }
272 GET_MSG_BACK_EXIST_FLAG => {
273 if st.msg_back.exist {
274 1
275 } else {
276 0
277 }
278 }
279 CHECK_MSG_BACK_ENABLE => st.msg_back.check_enabled(),
280 CHECK_MSG_BACK_OPEN => {
281 if st.msg_back_open {
282 1
283 } else {
284 0
285 }
286 }
287 GET_RETURN_TO_SEL_ENABLE_FLAG => {
288 if st.return_to_sel.enable {
289 1
290 } else {
291 0
292 }
293 }
294 GET_RETURN_TO_SEL_EXIST_FLAG => {
295 if st.return_to_sel.exist {
296 1
297 } else {
298 0
299 }
300 }
301 CHECK_RETURN_TO_SEL_ENABLE => st.return_to_sel.check_enabled(),
302 GET_RETURN_TO_MENU_ENABLE_FLAG => {
303 if st.return_to_menu.enable {
304 1
305 } else {
306 0
307 }
308 }
309 GET_RETURN_TO_MENU_EXIST_FLAG => {
310 if st.return_to_menu.exist {
311 1
312 } else {
313 0
314 }
315 }
316 CHECK_RETURN_TO_MENU_ENABLE => st.return_to_menu.check_enabled(),
317 GET_END_GAME_ENABLE_FLAG => {
318 if st.end_game.enable {
319 1
320 } else {
321 0
322 }
323 }
324 GET_END_GAME_EXIST_FLAG => {
325 if st.end_game.exist {
326 1
327 } else {
328 0
329 }
330 }
331 CHECK_END_GAME_ENABLE => st.end_game.check_enabled(),
332 GET_SAVE_ENABLE_FLAG => {
333 if st.save_feature.enable {
334 1
335 } else {
336 0
337 }
338 }
339 GET_SAVE_EXIST_FLAG => {
340 if st.save_feature.exist {
341 1
342 } else {
343 0
344 }
345 }
346 CHECK_SAVE_ENABLE => st.save_feature.check_enabled(),
347 GET_LOAD_ENABLE_FLAG => {
348 if st.load_feature.enable {
349 1
350 } else {
351 0
352 }
353 }
354 GET_LOAD_EXIST_FLAG => {
355 if st.load_feature.exist {
356 1
357 } else {
358 0
359 }
360 }
361 CHECK_LOAD_ENABLE => st.load_feature.check_enabled(),
362 _ => return None,
363 })
364}
365
366fn apply_toggle_set(
367 op: i32,
368 v: bool,
369 st: &mut crate::runtime::globals::SyscomRuntimeState,
370) -> bool {
371 match op {
372 SET_READ_SKIP_ONOFF_FLAG => st.read_skip.onoff = v,
373 SET_READ_SKIP_ENABLE_FLAG => st.read_skip.enable = v,
374 SET_READ_SKIP_EXIST_FLAG => st.read_skip.exist = v,
375 SET_AUTO_SKIP_ONOFF_FLAG => st.auto_skip.onoff = v,
376 SET_AUTO_SKIP_ENABLE_FLAG => st.auto_skip.enable = v,
377 SET_AUTO_SKIP_EXIST_FLAG => st.auto_skip.exist = v,
378 SET_AUTO_MODE_ONOFF_FLAG => st.auto_mode.onoff = v,
379 SET_AUTO_MODE_ENABLE_FLAG => st.auto_mode.enable = v,
380 SET_AUTO_MODE_EXIST_FLAG => st.auto_mode.exist = v,
381 SET_HIDE_MWND_ONOFF_FLAG => st.hide_mwnd.onoff = v,
382 SET_HIDE_MWND_ENABLE_FLAG => st.hide_mwnd.enable = v,
383 SET_HIDE_MWND_EXIST_FLAG => st.hide_mwnd.exist = v,
384 SET_MSG_BACK_ENABLE_FLAG => st.msg_back.enable = v,
385 SET_MSG_BACK_EXIST_FLAG => st.msg_back.exist = v,
386 SET_RETURN_TO_SEL_ENABLE_FLAG => st.return_to_sel.enable = v,
387 SET_RETURN_TO_SEL_EXIST_FLAG => st.return_to_sel.exist = v,
388 SET_RETURN_TO_MENU_ENABLE_FLAG => st.return_to_menu.enable = v,
389 SET_RETURN_TO_MENU_EXIST_FLAG => st.return_to_menu.exist = v,
390 SET_END_GAME_ENABLE_FLAG => st.end_game.enable = v,
391 SET_END_GAME_EXIST_FLAG => st.end_game.exist = v,
392 SET_SAVE_ENABLE_FLAG => st.save_feature.enable = v,
393 SET_SAVE_EXIST_FLAG => st.save_feature.exist = v,
394 SET_LOAD_ENABLE_FLAG => st.load_feature.enable = v,
395 SET_LOAD_EXIST_FLAG => st.load_feature.exist = v,
396 _ => return false,
397 }
398 true
399}
400
401
402fn truncate_to_chars(s: &mut String, max_chars: usize) {
403 if s.chars().count() > max_chars {
404 *s = s.chars().take(max_chars).collect();
405 }
406}
407
408pub(crate) fn append_current_save_message(ctx: &mut CommandContext, msg: &str) {
414 if msg.is_empty() {
415 return;
416 }
417 ctx.globals.syscom.current_save_message.push_str(msg);
418 truncate_to_chars(&mut ctx.globals.syscom.current_save_message, 256);
419 ctx.globals.syscom.current_save_full_message.push_str(msg);
420 truncate_to_chars(&mut ctx.globals.syscom.current_save_full_message, 256);
421
422 if let Some(snapshot) = ctx.local_save_snapshot.as_mut() {
423 snapshot.save_msg.push_str(msg);
424 truncate_to_chars(&mut snapshot.save_msg, 256);
425 snapshot.save_full_msg.push_str(msg);
426 truncate_to_chars(&mut snapshot.save_full_msg, 256);
427 }
428}
429
430fn save_load_trace_enabled() -> bool {
431 std::env::var_os("SG_SAVELOAD_TRACE").is_some()
432}
433
434fn trace_save_load_event(ctx: &CommandContext, label: &str, quick: bool, idx: usize, path: Option<&Path>) {
435 if !save_load_trace_enabled() {
436 return;
437 }
438 let kind = if quick { "quick" } else { "normal" };
439 if let Some(path) = path {
440 eprintln!(
441 "[SG_SAVELOAD_TRACE][SYSCOM] {label} kind={kind} idx={idx} path={} exists={}",
442 path.display(),
443 path.exists()
444 );
445 } else {
446 eprintln!("[SG_SAVELOAD_TRACE][SYSCOM] {label} kind={kind} idx={idx}");
447 }
448}
449
450fn ensure_slot(slots: &mut Vec<SaveSlotState>, idx: usize) -> &mut SaveSlotState {
451 if slots.len() <= idx {
452 slots.resize_with(idx + 1, SaveSlotState::default);
453 }
454 &mut slots[idx]
455}
456
457pub fn menu_save_slot(ctx: &mut CommandContext, quick: bool, idx: usize) {
458 let path = slot_path_with_counts(
459 &ctx.project_dir,
460 quick,
461 idx,
462 configured_save_count(ctx, false),
463 configured_save_count(ctx, true),
464 );
465 trace_save_load_event(ctx, "menu_save_slot", quick, idx, Some(&path));
466 let kind = if quick { RuntimeSaveKind::Quick } else { RuntimeSaveKind::Normal };
472 ctx.request_runtime_save(kind, idx);
473}
474
475pub fn menu_load_slot(ctx: &mut CommandContext, quick: bool, idx: usize) {
476 let path = slot_path_with_counts(
477 &ctx.project_dir,
478 quick,
479 idx,
480 configured_save_count(ctx, false),
481 configured_save_count(ctx, true),
482 );
483 trace_save_load_event(ctx, "menu_load_slot", quick, idx, Some(&path));
484 let save_cnt = configured_save_count(ctx, false);
485 let quick_cnt = configured_save_count(ctx, true);
486 if quick {
487 ensure_slot_loaded_with_counts(
488 &ctx.project_dir,
489 true,
490 save_cnt,
491 quick_cnt,
492 &mut ctx.globals.syscom.quick_save_slots,
493 idx,
494 );
495 ctx.request_runtime_load(RuntimeSaveKind::Quick, idx);
496 } else {
497 ensure_slot_loaded_with_counts(
498 &ctx.project_dir,
499 false,
500 save_cnt,
501 quick_cnt,
502 &mut ctx.globals.syscom.save_slots,
503 idx,
504 );
505 ctx.request_runtime_load(RuntimeSaveKind::Normal, idx);
506 }
507}
508
509fn saveload_alert_on(ctx: &CommandContext) -> bool {
510 cfg_get_int(&ctx.globals.syscom, GET_SAVELOAD_ALERT_ONOFF, 1) != 0
511}
512
513fn slot_exists_for_menu_action(ctx: &mut CommandContext, quick: bool, idx: usize) -> bool {
514 let save_cnt = configured_save_count(ctx, false);
515 let quick_cnt = configured_save_count(ctx, true);
516 if quick {
517 ensure_slot_loaded_with_counts(
518 &ctx.project_dir,
519 true,
520 save_cnt,
521 quick_cnt,
522 &mut ctx.globals.syscom.quick_save_slots,
523 idx,
524 );
525 ctx.globals
526 .syscom
527 .quick_save_slots
528 .get(idx)
529 .map(|slot| slot.exist)
530 .unwrap_or(false)
531 } else {
532 ensure_slot_loaded_with_counts(
533 &ctx.project_dir,
534 false,
535 save_cnt,
536 quick_cnt,
537 &mut ctx.globals.syscom.save_slots,
538 idx,
539 );
540 ctx.globals
541 .syscom
542 .save_slots
543 .get(idx)
544 .map(|slot| slot.exist)
545 .unwrap_or(false)
546 }
547}
548
549fn request_confirmed_save_or_load(
550 ctx: &mut CommandContext,
551 kind: SyscomPendingProcKind,
552 idx: usize,
553 warning: bool,
554 needs_existing_slot: bool,
555) -> bool {
556 if warning && saveload_alert_on(ctx) && (!needs_existing_slot || slot_exists_for_menu_action(ctx, matches!(kind, SyscomPendingProcKind::QuickSave | SyscomPendingProcKind::QuickLoad), idx)) {
557 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
558 kind,
559 warning: true,
560 se_play: false,
561 fade_out: false,
562 leave_msgbk: false,
563 save_id: idx as i64,
564 });
565 ctx.globals.syscom.menu_open = false;
566 ctx.globals.syscom.menu_kind = None;
567 ctx.globals.syscom.menu_result = None;
568 true
569 } else {
570 false
571 }
572}
573
574fn local_save_available(ctx: &CommandContext) -> bool {
575 ctx.local_save_snapshot
576 .as_ref()
577 .map(|s| !s.local_stream.is_empty())
578 .unwrap_or(false)
579 || ctx.pending_auto_savepoint
580}
581
582fn local_save_file_exists(ctx: &CommandContext, kind: SaveKind, idx: usize) -> bool {
583 let path = original_save::save_file_path_with_counts(
584 &ctx.project_dir,
585 configured_save_count(ctx, false),
586 configured_save_count(ctx, true),
587 kind,
588 idx,
589 );
590 original_save::read_slot_from_path(&path)
591 .map(|slot| slot.exist)
592 .unwrap_or(false)
593}
594
595pub(crate) fn save_dir(project_dir: &Path) -> PathBuf {
596 original_save::save_dir(project_dir)
597}
598
599fn slot_path_with_counts(project_dir: &Path, quick: bool, idx: usize, save_cnt: usize, quick_cnt: usize) -> PathBuf {
600 let kind = if quick { SaveKind::Quick } else { SaveKind::Normal };
601 original_save::save_file_path_with_counts(project_dir, save_cnt, quick_cnt, kind, idx)
602}
603
604
605#[derive(Clone, Copy, Debug, PartialEq, Eq)]
606enum SaveThumbType {
607 Bmp,
608 Png,
609}
610
611#[derive(Clone, Copy, Debug)]
612struct SaveThumbConfig {
613 enabled: bool,
614 thumb_type: SaveThumbType,
615 width: u32,
616 height: u32,
617}
618
619fn save_thumb_config(ctx: &CommandContext) -> SaveThumbConfig {
620 let mut out = SaveThumbConfig {
621 enabled: false,
622 thumb_type: SaveThumbType::Bmp,
623 width: 200,
624 height: 150,
625 };
626
627 let Some(cfg) = ctx.tables.gameexe.as_ref() else {
628 return out;
629 };
630
631 out.enabled = cfg
632 .get_usize("#SAVE_THUMB.USE")
633 .or_else(|| cfg.get_usize("SAVE_THUMB.USE"))
634 .unwrap_or(0)
635 != 0;
636 out.thumb_type = match cfg
637 .get_usize("#SAVE_THUMB.TYPE")
638 .or_else(|| cfg.get_usize("SAVE_THUMB.TYPE"))
639 .unwrap_or(0)
640 {
641 1 => SaveThumbType::Png,
642 _ => SaveThumbType::Bmp,
643 };
644
645 if let Some(entry) = cfg.get_entry("#SAVE_THUMB.SIZE").or_else(|| cfg.get_entry("SAVE_THUMB.SIZE")) {
646 let w = entry.item_unquoted(0).and_then(|v| v.trim().parse::<u32>().ok()).unwrap_or(out.width);
647 let h = entry.item_unquoted(1).and_then(|v| v.trim().parse::<u32>().ok()).unwrap_or(out.height);
648 if w != 0 && h != 0 {
649 out.width = w;
650 out.height = h;
651 }
652 }
653
654 out
655}
656
657pub(crate) fn thumb_candidate_paths(dir: &Path, idx: i64) -> [PathBuf; 2] {
658 let project_dir = dir.parent().unwrap_or(dir);
659 original_save::thumb_candidate_paths_for_no(project_dir, idx.max(0) as usize)
660}
661
662fn thumb_path_for_no_with_config(project_dir: &Path, config: SaveThumbConfig, save_no: usize) -> PathBuf {
663 let stem = format!("{save_no:04}");
664 let ext = match config.thumb_type {
665 SaveThumbType::Bmp => "bmp",
666 SaveThumbType::Png => "png",
667 };
668 save_dir(project_dir).join(format!("{stem}.{ext}"))
669}
670
671fn pick_thumb_source_name(ctx: &CommandContext) -> Option<String> {
672 let table = ctx.tables.thumb_table.as_ref()?;
673 let mut form_ids: Vec<u32> = ctx.globals.stage_forms.keys().copied().collect();
674 form_ids.sort_unstable();
675 for form_id in form_ids {
676 let Some(stage) = ctx.globals.stage_forms.get(&form_id) else {
677 continue;
678 };
679 let mut stage_ids: Vec<i64> = stage.object_lists.keys().copied().collect();
680 stage_ids.sort_unstable();
681 for stage_idx in stage_ids {
682 let Some(objs) = stage.object_lists.get(&stage_idx) else {
683 continue;
684 };
685 for obj in objs.iter().rev() {
686 if let Some(file) = obj.file_name.as_deref() {
687 if let Some(mapped) = table.get_by_file_stem(file) {
688 return Some(mapped.clone());
689 }
690 }
691 }
692 }
693 }
694 None
695}
696
697fn capture_slot_thumb(ctx: &mut CommandContext, config: SaveThumbConfig) -> RgbaImage {
698 if let Some(name) = pick_thumb_source_name(ctx) {
699 if let Ok(img_id) = ctx.images.load_g00(&name, 0) {
700 if let Some(img) = ctx.images.get(img_id) {
701 return resize_rgba(img.as_ref(), config.width, config.height);
702 }
703 }
704 }
705
706 let img = ctx.capture_frame_rgba();
707 resize_rgba(&img, config.width, config.height)
708}
709
710pub(crate) fn prepare_runtime_save_thumb_capture(ctx: &mut CommandContext) {
711 let config = save_thumb_config(ctx);
712 if !config.enabled {
713 ctx.globals.save_thumb_capture_image = None;
714 return;
715 }
716
717 ctx.globals.save_thumb_capture_image = Some(capture_slot_thumb(ctx, config));
718}
719
720pub(crate) fn prepare_runtime_save_thumb_capture_from_image(
721 ctx: &mut CommandContext,
722 img: &RgbaImage,
723) {
724 let config = save_thumb_config(ctx);
725 if !config.enabled {
726 ctx.globals.save_thumb_capture_image = None;
727 return;
728 }
729
730 ctx.globals.save_thumb_capture_image = Some(resize_rgba(img, config.width, config.height));
731}
732
733fn write_slot_thumb_for_save_no(ctx: &mut CommandContext, save_no: usize) {
734 let config = save_thumb_config(ctx);
735 if !config.enabled {
736 return;
737 }
738 let Some(img) = ctx.globals.save_thumb_capture_image.clone() else {
739 ctx.unknown
740 .record_note(&format!("save_thumb.capture.missing:{save_no}"));
741 return;
742 };
743 let path = thumb_path_for_no_with_config(&ctx.project_dir, config, save_no);
744 if save_load_trace_enabled() {
745 eprintln!(
746 "[SG_SAVELOAD_TRACE][SYSCOM] write_slot_thumb save_no={} path={} size={}x{} type={:?}",
747 save_no,
748 path.display(),
749 img.width,
750 img.height,
751 config.thumb_type
752 );
753 }
754 let _ = fs::remove_file(&path);
755 let result = match config.thumb_type {
756 SaveThumbType::Bmp => write_rgba_bmp_top_down(&path, &img),
757 SaveThumbType::Png => write_rgba_png_opaque(&path, &img),
758 };
759 if let Err(err) = result {
760 eprintln!("[SG_SAVE] failed to write save thumb {}: {err:#}", path.display());
761 }
762}
763
764pub(crate) fn write_runtime_slot_thumb(ctx: &mut CommandContext, save_no: usize) {
765 write_slot_thumb_for_save_no(ctx, save_no);
766}
767
768fn escape_str(s: &str) -> String {
769 let mut out = String::with_capacity(s.len());
770 for ch in s.chars() {
771 match ch {
772 '\\' => out.push_str("\\\\"),
773 '\n' => out.push_str("\\n"),
774 '\r' => out.push_str("\\r"),
775 '\t' => out.push_str("\\t"),
776 _ => out.push(ch),
777 }
778 }
779 out
780}
781
782fn unescape_str(s: &str) -> String {
783 let mut out = String::with_capacity(s.len());
784 let mut it = s.chars();
785 while let Some(ch) = it.next() {
786 if ch == '\\' {
787 match it.next() {
788 Some('n') => out.push('\n'),
789 Some('r') => out.push('\r'),
790 Some('t') => out.push('\t'),
791 Some('\\') => out.push('\\'),
792 Some(other) => out.push(other),
793 None => break,
794 }
795 } else {
796 out.push(ch);
797 }
798 }
799 out
800}
801
802fn write_slot(path: &Path, slot: &SaveSlotState) {
803 if let Err(err) = original_save::write_slot_file(path, slot) {
804 eprintln!("[SG_SAVE] failed to write original save file {}: {err:#}", path.display());
805 }
806}
807
808fn read_slot(path: &Path) -> Option<SaveSlotState> {
809 original_save::read_slot_from_path(path)
810}
811
812pub fn write_global_save(ctx: &CommandContext) {
813 let mut stream = original_save::OriginalStreamWriter::new();
814 stream.push_i64(ctx.globals.syscom.total_play_time);
815
816 let fixed_flag_cnt = ctx
817 .tables
818 .gameexe
819 .as_ref()
820 .and_then(|cfg| cfg.get_usize("#GLOBAL_FLAG.CNT").or_else(|| cfg.get_usize("GLOBAL_FLAG.CNT")))
821 .unwrap_or(1000)
822 .min(10000);
823 let cg_flag_cnt = ctx
824 .tables
825 .cgtable_flag_cnt
826 .or_else(|| {
827 ctx.tables
828 .gameexe
829 .as_ref()
830 .and_then(|cfg| cfg.get_usize("#CGTABLE_FLAG_CNT").or_else(|| cfg.get_usize("CGTABLE_FLAG_CNT")))
831 })
832 .unwrap_or(ctx.tables.cg_flags.len())
833 .max(ctx.tables.cg_flags.len());
834 let bgm_cnt = ctx
835 .tables
836 .gameexe
837 .as_ref()
838 .and_then(|cfg| cfg.get_usize("#BGM.CNT").or_else(|| cfg.get_usize("BGM.CNT")))
839 .unwrap_or(32)
840 .max(ctx.globals.bgm_table_flags.len());
841 let g = ctx
842 .globals
843 .int_lists
844 .get(&(crate::runtime::forms::codes::ELM_GLOBAL_G as u32))
845 .map(Vec::as_slice)
846 .unwrap_or(&[]);
847 let z = ctx
848 .globals
849 .int_lists
850 .get(&(crate::runtime::forms::codes::ELM_GLOBAL_Z as u32))
851 .map(Vec::as_slice)
852 .unwrap_or(&[]);
853 let m = ctx
854 .globals
855 .str_lists
856 .get(&(crate::runtime::forms::codes::ELM_GLOBAL_M as u32))
857 .map(Vec::as_slice)
858 .unwrap_or(&[]);
859 let namae_global = ctx
860 .globals
861 .str_lists
862 .get(&(crate::runtime::forms::codes::ELM_GLOBAL_NAMAE_GLOBAL as u32))
863 .map(Vec::as_slice)
864 .unwrap_or(&[]);
865
866 stream.push_fixed_i32_list(g, fixed_flag_cnt);
867 stream.push_fixed_i32_list(z, fixed_flag_cnt);
868 stream.push_fixed_str_list(m, fixed_flag_cnt);
869 stream.push_fixed_str_list(namae_global, 26 + 26 * 26);
870 stream.push_i32(0);
871
872 let cg_flags: Vec<i64> = ctx.tables.cg_flags.iter().map(|v| *v as i64).collect();
873 stream.push_fixed_i32_list(&cg_flags, cg_flag_cnt);
874
875 let bgm_flags: Vec<i64> = ctx
876 .globals
877 .bgm_table_flags
878 .iter()
879 .map(|v| if *v { 1 } else { 0 })
880 .collect();
881 stream.push_fixed_i32_list(&bgm_flags, bgm_cnt);
882
883 stream.push_i32(0);
886
887 let payload = stream.into_inner();
888 if let Err(err) = original_save::write_global_save_file(&ctx.project_dir, &payload) {
889 eprintln!("[SG_SAVE] failed to write global.sav: {err:#}");
890 }
891}
892
893fn load_global_save(ctx: &mut CommandContext) {
894 let Ok(payload) = original_save::read_global_save_file(&ctx.project_dir) else {
895 return;
896 };
897 let mut rd = original_save::OriginalStreamReader::new(&payload);
898 let Ok(total_play_time) = rd.i64() else {
899 return;
900 };
901 ctx.globals.syscom.total_play_time = total_play_time;
902 if let Ok(g) = rd.fixed_i32_list() {
903 ctx.globals
904 .int_lists
905 .insert(crate::runtime::forms::codes::ELM_GLOBAL_G as u32, g);
906 } else {
907 return;
908 }
909 if let Ok(z) = rd.fixed_i32_list() {
910 ctx.globals
911 .int_lists
912 .insert(crate::runtime::forms::codes::ELM_GLOBAL_Z as u32, z);
913 } else {
914 return;
915 }
916 if let Ok(m) = rd.fixed_str_list() {
917 ctx.globals
918 .str_lists
919 .insert(crate::runtime::forms::codes::ELM_GLOBAL_M as u32, m);
920 } else {
921 return;
922 }
923 if let Ok(namae_global) = rd.fixed_str_list() {
924 ctx.globals.str_lists.insert(
925 crate::runtime::forms::codes::ELM_GLOBAL_NAMAE_GLOBAL as u32,
926 namae_global,
927 );
928 }
929 let _ = rd.i32();
930 if let Ok(cg) = rd.fixed_i32_list() {
931 ctx.tables.cg_flags = cg.into_iter().map(|v| if v != 0 { 1 } else { 0 }).collect();
932 }
933 if let Ok(bgm) = rd.fixed_i32_list() {
934 ctx.globals.bgm_table_flags = bgm.into_iter().map(|v| v != 0).collect();
935 }
936 if let Ok(chrkoe_cnt) = rd.i32() {
937 for _ in 0..chrkoe_cnt.max(0) {
938 let _ = rd.string();
939 let _ = rd.i32();
940 }
941 }
942}
943
944
945fn ensure_slot_loaded_with_counts(
946 project_dir: &Path,
947 quick: bool,
948 save_cnt: usize,
949 quick_cnt: usize,
950 slots: &mut Vec<SaveSlotState>,
951 idx: usize,
952) {
953 let path = slot_path_with_counts(project_dir, quick, idx, save_cnt, quick_cnt);
954 if save_load_trace_enabled() {
955 let before_exist = slots.get(idx).map(|s| s.exist).unwrap_or(false);
956 eprintln!(
957 "[SG_SAVELOAD_TRACE][SYSCOM] ensure_slot_loaded kind={} idx={} path={} file_exists={} cached_exist={}",
958 if quick { "quick" } else { "normal" },
959 idx,
960 path.display(),
961 path.exists(),
962 before_exist
963 );
964 }
965 if let Some(slot) = read_slot(&path) {
966 let s = ensure_slot(slots, idx);
967 *s = slot;
968 return;
969 }
970 if !slots.get(idx).map(|s| s.exist).unwrap_or(false) {
971 let s = ensure_slot(slots, idx);
972 *s = SaveSlotState::default();
973 }
974}
975
976
977fn reload_slot_from_disk_with_counts(
978 project_dir: &Path,
979 quick: bool,
980 save_cnt: usize,
981 quick_cnt: usize,
982 slots: &mut Vec<SaveSlotState>,
983 idx: usize,
984) {
985 let path = slot_path_with_counts(project_dir, quick, idx, save_cnt, quick_cnt);
986 let next = read_slot(&path).unwrap_or_default();
987 let s = ensure_slot(slots, idx);
988 *s = next;
989}
990
991fn sync_slots_from_disk_with_counts(
992 project_dir: &Path,
993 quick: bool,
994 save_cnt: usize,
995 quick_cnt: usize,
996 slots: &mut Vec<SaveSlotState>,
997 count: usize,
998) {
999 if slots.len() < count {
1000 slots.resize_with(count, SaveSlotState::default);
1001 }
1002 for idx in 0..count {
1003 reload_slot_from_disk_with_counts(project_dir, quick, save_cnt, quick_cnt, slots, idx);
1004 }
1005}
1006
1007pub(crate) fn sync_save_slots_from_disk(ctx: &mut CommandContext, quick: bool) {
1008 if save_load_trace_enabled() {
1009 eprintln!(
1010 "[SG_SAVELOAD_TRACE][SYSCOM] sync_save_slots_from_disk kind={}",
1011 if quick { "quick" } else { "normal" }
1012 );
1013 }
1014 let project_dir = ctx.project_dir.clone();
1015 let save_cnt = configured_save_count(ctx, false);
1016 let quick_cnt = configured_save_count(ctx, true);
1017 if quick {
1018 sync_slots_from_disk_with_counts(
1019 &project_dir,
1020 true,
1021 save_cnt,
1022 quick_cnt,
1023 &mut ctx.globals.syscom.quick_save_slots,
1024 quick_cnt,
1025 );
1026 } else {
1027 sync_slots_from_disk_with_counts(
1028 &project_dir,
1029 false,
1030 save_cnt,
1031 quick_cnt,
1032 &mut ctx.globals.syscom.save_slots,
1033 save_cnt,
1034 );
1035 }
1036}
1037
1038fn persist_slot_with_counts(
1039 project_dir: &Path,
1040 quick: bool,
1041 save_cnt: usize,
1042 quick_cnt: usize,
1043 slots: &[SaveSlotState],
1044 idx: usize,
1045) {
1046 if let Some(slot) = slots.get(idx) {
1047 let path = slot_path_with_counts(project_dir, quick, idx, save_cnt, quick_cnt);
1048 if path.exists() {
1049 match original_save::read_header_from_path(&path) {
1050 Ok(old_header) => {
1051 let header = original_save::OriginalSaveHeader::from_slot(
1052 slot,
1053 old_header.data_size.max(0) as usize,
1054 );
1055 if let Err(err) = original_save::write_header_in_place(&path, &header) {
1056 eprintln!(
1057 "[SG_SAVE] failed to update original save header {}: {err:#}",
1058 path.display()
1059 );
1060 }
1061 return;
1062 }
1063 Err(err) => {
1064 eprintln!(
1065 "[SG_SAVE] failed to read original save header {}: {err:#}",
1066 path.display()
1067 );
1068 }
1069 }
1070 }
1071 write_slot(&path, slot);
1072 }
1073}
1074
1075
1076fn slot_thumb_save_no(save_cnt: usize, quick_cnt: usize, quick: bool, idx: usize) -> usize {
1077 let kind = if quick { SaveKind::Quick } else { SaveKind::Normal };
1078 original_save::original_save_no(save_cnt, quick_cnt, kind, idx)
1079}
1080
1081fn remove_thumb_file(project_dir: &Path, save_cnt: usize, quick_cnt: usize, quick: bool, config: SaveThumbConfig, idx: usize) {
1082 if !config.enabled {
1083 return;
1084 }
1085 let save_no = slot_thumb_save_no(save_cnt, quick_cnt, quick, idx);
1086 let _ = fs::remove_file(thumb_path_for_no_with_config(project_dir, config, save_no));
1087}
1088
1089fn copy_thumb_file(project_dir: &Path, save_cnt: usize, quick_cnt: usize, quick: bool, config: SaveThumbConfig, src: usize, dst: usize) {
1090 if !config.enabled {
1091 return;
1092 }
1093 let src_no = slot_thumb_save_no(save_cnt, quick_cnt, quick, src);
1094 let dst_no = slot_thumb_save_no(save_cnt, quick_cnt, quick, dst);
1095 let src_path = thumb_path_for_no_with_config(project_dir, config, src_no);
1096 let dst_path = thumb_path_for_no_with_config(project_dir, config, dst_no);
1097 if src_path.exists() {
1098 if let Some(parent) = dst_path.parent() {
1099 let _ = fs::create_dir_all(parent);
1100 }
1101 let _ = fs::copy(src_path, dst_path);
1102 } else {
1103 let _ = fs::remove_file(dst_path);
1104 }
1105}
1106
1107fn swap_thumb_file(project_dir: &Path, save_cnt: usize, quick_cnt: usize, quick: bool, config: SaveThumbConfig, a: usize, b: usize) {
1108 if !config.enabled || a == b {
1109 return;
1110 }
1111 let a_no = slot_thumb_save_no(save_cnt, quick_cnt, quick, a);
1112 let b_no = slot_thumb_save_no(save_cnt, quick_cnt, quick, b);
1113 let pa = thumb_path_for_no_with_config(project_dir, config, a_no);
1114 let pb = thumb_path_for_no_with_config(project_dir, config, b_no);
1115 let tmp = pa.with_extension(format!(
1116 "{}.swap",
1117 pa.extension().and_then(|v| v.to_str()).unwrap_or("tmp")
1118 ));
1119 let a_exists = pa.exists();
1120 let b_exists = pb.exists();
1121 if a_exists {
1122 let _ = fs::rename(&pa, &tmp);
1123 }
1124 if b_exists {
1125 if let Some(parent) = pa.parent() {
1126 let _ = fs::create_dir_all(parent);
1127 }
1128 let _ = fs::rename(&pb, &pa);
1129 } else {
1130 let _ = fs::remove_file(&pa);
1131 }
1132 if a_exists {
1133 if let Some(parent) = pb.parent() {
1134 let _ = fs::create_dir_all(parent);
1135 }
1136 let _ = fs::rename(&tmp, &pb);
1137 } else {
1138 let _ = fs::remove_file(&pb);
1139 }
1140 let _ = fs::remove_file(&tmp);
1141}
1142
1143fn copy_save_file(project_dir: &Path, quick: bool, save_cnt: usize, quick_cnt: usize, src: usize, dst: usize) {
1144 let src_path = slot_path_with_counts(project_dir, quick, src, save_cnt, quick_cnt);
1145 let dst_path = slot_path_with_counts(project_dir, quick, dst, save_cnt, quick_cnt);
1146 if src_path.exists() {
1147 if let Some(parent) = dst_path.parent() {
1148 let _ = fs::create_dir_all(parent);
1149 }
1150 let _ = fs::copy(src_path, dst_path);
1151 } else {
1152 let _ = fs::remove_file(dst_path);
1153 }
1154}
1155
1156fn swap_save_file(project_dir: &Path, quick: bool, save_cnt: usize, quick_cnt: usize, a: usize, b: usize) {
1157 if a == b {
1158 return;
1159 }
1160 let pa = slot_path_with_counts(project_dir, quick, a, save_cnt, quick_cnt);
1161 let pb = slot_path_with_counts(project_dir, quick, b, save_cnt, quick_cnt);
1162 let tmp = pa.with_extension("sav.swap");
1163 let a_exists = pa.exists();
1164 let b_exists = pb.exists();
1165 if a_exists {
1166 let _ = fs::rename(&pa, &tmp);
1167 }
1168 if b_exists {
1169 if let Some(parent) = pa.parent() {
1170 let _ = fs::create_dir_all(parent);
1171 }
1172 let _ = fs::rename(&pb, &pa);
1173 } else {
1174 let _ = fs::remove_file(&pa);
1175 }
1176 if a_exists {
1177 if let Some(parent) = pb.parent() {
1178 let _ = fs::create_dir_all(parent);
1179 }
1180 let _ = fs::rename(&tmp, &pb);
1181 } else {
1182 let _ = fs::remove_file(&pb);
1183 }
1184 let _ = fs::remove_file(&tmp);
1185}
1186
1187fn copy_slot(
1188 project_dir: &Path,
1189 quick: bool,
1190 save_cnt: usize,
1191 quick_cnt: usize,
1192 thumb_config: SaveThumbConfig,
1193 slots: &mut Vec<SaveSlotState>,
1194 src: usize,
1195 dst: usize,
1196) -> bool {
1197 ensure_slot_loaded_with_counts(project_dir, quick, save_cnt, quick_cnt, slots, src);
1198 let Some(src_slot) = slots.get(src).cloned() else {
1199 return false;
1200 };
1201 if !src_slot.exist {
1202 return false;
1203 }
1204 *ensure_slot(slots, dst) = src_slot;
1205 copy_save_file(project_dir, quick, save_cnt, quick_cnt, src, dst);
1206 copy_thumb_file(project_dir, save_cnt, quick_cnt, quick, thumb_config, src, dst);
1207 true
1208}
1209
1210fn change_slot(
1211 project_dir: &Path,
1212 quick: bool,
1213 save_cnt: usize,
1214 quick_cnt: usize,
1215 thumb_config: SaveThumbConfig,
1216 slots: &mut Vec<SaveSlotState>,
1217 a: usize,
1218 b: usize,
1219) -> bool {
1220 ensure_slot_loaded_with_counts(project_dir, quick, save_cnt, quick_cnt, slots, a);
1221 ensure_slot_loaded_with_counts(project_dir, quick, save_cnt, quick_cnt, slots, b);
1222 let max_idx = a.max(b);
1223 if slots.len() <= max_idx {
1224 slots.resize_with(max_idx + 1, SaveSlotState::default);
1225 }
1226 slots.swap(a, b);
1227 swap_save_file(project_dir, quick, save_cnt, quick_cnt, a, b);
1228 swap_thumb_file(project_dir, save_cnt, quick_cnt, quick, thumb_config, a, b);
1229 true
1230}
1231
1232fn delete_slot(
1233 project_dir: &Path,
1234 quick: bool,
1235 save_cnt: usize,
1236 quick_cnt: usize,
1237 thumb_config: SaveThumbConfig,
1238 slots: &mut Vec<SaveSlotState>,
1239 idx: usize,
1240) -> bool {
1241 ensure_slot_loaded_with_counts(project_dir, quick, save_cnt, quick_cnt, slots, idx);
1242 let existed = slots.get(idx).map(|s| s.exist).unwrap_or(false);
1243 *ensure_slot(slots, idx) = SaveSlotState::default();
1244 let path = slot_path_with_counts(project_dir, quick, idx, save_cnt, quick_cnt);
1245 let _ = fs::remove_file(path);
1246 remove_thumb_file(project_dir, save_cnt, quick_cnt, quick, thumb_config, idx);
1247 existed
1248}
1249
1250fn capture_flags_path(image_path: &Path) -> PathBuf {
1251 let mut p = image_path.to_path_buf();
1252 let ext = p
1253 .extension()
1254 .and_then(|v| v.to_str())
1255 .map(|v| format!("{v}.siglus_flags"))
1256 .unwrap_or_else(|| "siglus_flags".to_string());
1257 p.set_extension(ext);
1258 p
1259}
1260
1261fn named_i64(params: &[Value], id: i32, default: i64) -> i64 {
1262 params
1263 .iter()
1264 .find_map(|v| match v {
1265 Value::NamedArg { id: nid, value } if *nid == id => value.as_i64(),
1266 _ => None,
1267 })
1268 .unwrap_or(default)
1269}
1270
1271fn named_element(params: &[Value], id: i32) -> Option<Vec<i32>> {
1272 params.iter().find_map(|v| match v {
1273 Value::NamedArg { id: nid, value } if *nid == id => match value.as_ref() {
1274 Value::Element(chain) => Some(chain.clone()),
1275 _ => None,
1276 },
1277 _ => None,
1278 })
1279}
1280
1281fn save_capture_flags_sidecar(ctx: &CommandContext, image_path: &Path, params: &[Value]) {
1282 let Some(flag_chain) = named_element(params, 2) else {
1283 return;
1284 };
1285 let Some(flag_form) = flag_chain.first().copied() else {
1286 return;
1287 };
1288 let flag_index = named_i64(params, 3, 0).max(0) as usize;
1289 let flag_cnt = named_i64(params, 4, 0).max(0) as usize;
1290 let str_chain = named_element(params, 5);
1291 let str_index = named_i64(params, 6, 0).max(0) as usize;
1292 let str_cnt = named_i64(params, 7, 0).max(0) as usize;
1293
1294 let mut out = String::new();
1295 out.push_str("version=1\n");
1296 out.push_str(&format!("flag_cnt={flag_cnt}\n"));
1297 if let Some(list) = ctx.globals.int_lists.get(&(flag_form as u32)) {
1298 for i in 0..flag_cnt {
1299 let v = list.get(flag_index + i).copied().unwrap_or(0);
1300 out.push_str(&format!("flag.{i}={v}\n"));
1301 }
1302 }
1303 if let Some(str_form) = str_chain.and_then(|v| v.first().copied()) {
1304 out.push_str(&format!("str_cnt={str_cnt}\n"));
1305 if let Some(list) = ctx.globals.str_lists.get(&(str_form as u32)) {
1306 for i in 0..str_cnt {
1307 let v = list.get(str_index + i).cloned().unwrap_or_default();
1308 out.push_str(&format!("str.{i}={}\n", escape_str(&v)));
1309 }
1310 }
1311 }
1312 if let Some(parent) = image_path.parent() {
1313 let _ = fs::create_dir_all(parent);
1314 }
1315 let _ = fs::write(capture_flags_path(image_path), out);
1316}
1317
1318fn load_capture_flags_sidecar(
1319 ctx: &mut CommandContext,
1320 image_path: &Path,
1321 params: &[Value],
1322) -> bool {
1323 let path = capture_flags_path(image_path);
1324 let Ok(data) = fs::read_to_string(path) else {
1325 return image_path.exists();
1326 };
1327 if let Some(flag_chain) = named_element(params, 2) {
1328 if let Some(flag_form) = flag_chain.first().copied() {
1329 let flag_index = named_i64(params, 3, 0).max(0) as usize;
1330 let flag_cnt = named_i64(params, 4, 0).max(0) as usize;
1331 let mut values = vec![0_i64; flag_cnt];
1332 for line in data.lines() {
1333 if let Some((k, v)) = line.split_once('=') {
1334 if let Some(i) = k
1335 .strip_prefix("flag.")
1336 .and_then(|x| x.parse::<usize>().ok())
1337 {
1338 if i < values.len() {
1339 values[i] = v.trim().parse::<i64>().unwrap_or(0);
1340 }
1341 }
1342 }
1343 }
1344 let list = ctx.globals.int_lists.entry(flag_form as u32).or_default();
1345 if list.len() < flag_index + flag_cnt {
1346 list.resize(flag_index + flag_cnt, 0);
1347 }
1348 for (i, v) in values.into_iter().enumerate() {
1349 list[flag_index + i] = v;
1350 }
1351 }
1352 }
1353 if let Some(str_chain) = named_element(params, 5) {
1354 if let Some(str_form) = str_chain.first().copied() {
1355 let str_index = named_i64(params, 6, 0).max(0) as usize;
1356 let str_cnt = named_i64(params, 7, 0).max(0) as usize;
1357 let mut values = vec![String::new(); str_cnt];
1358 for line in data.lines() {
1359 if let Some((k, v)) = line.split_once('=') {
1360 if let Some(i) = k.strip_prefix("str.").and_then(|x| x.parse::<usize>().ok()) {
1361 if i < values.len() {
1362 values[i] = unescape_str(v);
1363 }
1364 }
1365 }
1366 }
1367 let list = ctx.globals.str_lists.entry(str_form as u32).or_default();
1368 if list.len() < str_index + str_cnt {
1369 list.resize(str_index + str_cnt, String::new());
1370 }
1371 for (i, v) in values.into_iter().enumerate() {
1372 list[str_index + i] = v;
1373 }
1374 }
1375 }
1376 true
1377}
1378
1379fn write_msg_back(ctx: &CommandContext) {
1380 let form_id = ctx.ids.form_global_msgbk;
1381 if form_id == 0 {
1382 return;
1383 }
1384 let Some(st) = ctx.globals.msgbk_forms.get(&form_id) else {
1385 return;
1386 };
1387 let dir = save_dir(&ctx.project_dir);
1388 let path = dir.join("msg_back.txt");
1389
1390 let mut out = String::new();
1391 for idx in st.ordered_history_indices() {
1392 let Some(entry) = st.history.get(idx) else { continue; };
1393 out.push_str(&format!("-- entry {} --\n", idx));
1394 if !entry.original_name.is_empty() || !entry.disp_name.is_empty() {
1395 out.push_str("NAME: ");
1396 out.push_str(&entry.disp_name);
1397 out.push('\n');
1398 }
1399 if !entry.msg_str.is_empty() {
1400 if entry.pct_flag {
1401 out.push_str(&format!(
1402 "IMG: {} {} {}\n",
1403 entry.msg_str, entry.pct_pos_x, entry.pct_pos_y
1404 ));
1405 } else {
1406 out.push_str("TEXT: ");
1407 out.push_str(&entry.msg_str);
1408 out.push('\n');
1409 }
1410 }
1411 for (koe_no, chara_no) in entry.koe_no_list.iter().zip(entry.chr_no_list.iter()) {
1412 out.push_str(&format!("KOE: {} {}\n", koe_no, chara_no));
1413 }
1414 if entry.scn_no >= 0 || entry.line_no >= 0 {
1415 out.push_str(&format!("SCENE_LINE: {} {}\n", entry.scn_no, entry.line_no));
1416 }
1417 out.push('\n');
1418 }
1419 let _ = std::fs::write(path, out);
1420}
1421
1422fn open_msg_back_proc(ctx: &mut CommandContext) -> bool {
1423 if ctx.globals.script.msg_back_disable || ctx.globals.syscom.msg_back.check_enabled() == 0 {
1424 return false;
1425 }
1426 ctx.globals.syscom.read_skip.onoff = false;
1427 ctx.globals.syscom.msg_back_open = true;
1428 ctx.globals.syscom.msg_back_proc_initialized = false;
1429 let form_id = ctx.ids.form_global_msgbk;
1430 let (count, target) = ctx
1431 .globals
1432 .msgbk_forms
1433 .get(&form_id)
1434 .map(|st| {
1435 let indices = st.ordered_history_indices();
1436 let target = if indices.contains(&st.history_last_pos) {
1437 st.history_last_pos as isize
1438 } else {
1439 indices.last().copied().map(|idx| idx as isize).unwrap_or(-1)
1440 };
1441 (indices.len(), target)
1442 })
1443 .unwrap_or((0, -1));
1444 ctx.globals.syscom.msg_back_view_pos = count.saturating_sub(1);
1445 ctx.globals.syscom.msg_back_target_no = target;
1446 true
1447}
1448
1449fn configured_save_count(ctx: &CommandContext, quick: bool) -> usize {
1450 let keys: [&str; 2] = if quick {
1451 ["#QUICK_SAVE.CNT", "QUICK_SAVE.CNT"]
1452 } else {
1453 ["#SAVE.CNT", "SAVE.CNT"]
1454 };
1455 let default_count = if quick { 3 } else { 10 };
1456 ctx.tables
1457 .gameexe
1458 .as_ref()
1459 .and_then(|cfg| keys.iter().find_map(|key| cfg.get_usize(*key)))
1460 .unwrap_or(default_count)
1461 .min(10000)
1462}
1463
1464fn first_free_slot(slots: &[SaveSlotState]) -> i64 {
1465 for (i, s) in slots.iter().enumerate() {
1466 if !s.exist {
1467 return i as i64;
1468 }
1469 }
1470 slots.len() as i64
1471}
1472
1473fn slot_i64(slot: &SaveSlotState, op: i32) -> i64 {
1474 match op {
1475 GET_SAVE_EXIST | GET_QUICK_SAVE_EXIST => {
1476 if slot.exist {
1477 1
1478 } else {
1479 0
1480 }
1481 }
1482 GET_SAVE_YEAR | GET_QUICK_SAVE_YEAR => slot.year,
1483 GET_SAVE_MONTH | GET_QUICK_SAVE_MONTH => slot.month,
1484 GET_SAVE_DAY | GET_QUICK_SAVE_DAY => slot.day,
1485 GET_SAVE_WEEKDAY | GET_QUICK_SAVE_WEEKDAY => slot.weekday,
1486 GET_SAVE_HOUR | GET_QUICK_SAVE_HOUR => slot.hour,
1487 GET_SAVE_MINUTE | GET_QUICK_SAVE_MINUTE => slot.minute,
1488 GET_SAVE_SECOND | GET_QUICK_SAVE_SECOND => slot.second,
1489 GET_SAVE_MILLISECOND | GET_QUICK_SAVE_MILLISECOND => slot.millisecond,
1490 _ => 0,
1491 }
1492}
1493
1494fn slot_str(slot: &SaveSlotState, op: i32) -> String {
1495 match op {
1496 GET_SAVE_TITLE | GET_QUICK_SAVE_TITLE => slot.title.clone(),
1497 GET_SAVE_MESSAGE | GET_QUICK_SAVE_MESSAGE => slot.message.clone(),
1498 GET_SAVE_FULL_MESSAGE | GET_QUICK_SAVE_FULL_MESSAGE => slot.full_message.clone(),
1499 GET_SAVE_COMMENT | GET_QUICK_SAVE_COMMENT => slot.comment.clone(),
1500 GET_SAVE_APPEND_DIR | GET_QUICK_SAVE_APPEND_DIR => slot.append_dir.clone(),
1501 GET_SAVE_APPEND_NAME | GET_QUICK_SAVE_APPEND_NAME => slot.append_name.clone(),
1502 _ => String::new(),
1503 }
1504}
1505
1506fn cfg_get_int(st: &crate::runtime::globals::SyscomRuntimeState, key: i32, default: i64) -> i64 {
1507 st.config_int.get(&key).copied().unwrap_or(default)
1508}
1509
1510fn cfg_set_int(st: &mut crate::runtime::globals::SyscomRuntimeState, key: i32, value: i64) {
1511 st.config_int.insert(key, value);
1512}
1513
1514fn volume_to_raw(v: i64) -> u8 {
1515 let v = v.clamp(0, 100);
1516 ((v * 255) / 100) as u8
1517}
1518
1519pub(crate) fn apply_audio_config(ctx: &mut CommandContext) {
1520 use crate::audio::TrackKind;
1521 let all_vol = cfg_get_int(&ctx.globals.syscom, GET_ALL_VOLUME, 100);
1522 let all_on = cfg_get_int(&ctx.globals.syscom, GET_ALL_ONOFF, 1) != 0;
1523 let all_raw = if all_on { volume_to_raw(all_vol) } else { 0 };
1524
1525 let bgm_vol = cfg_get_int(&ctx.globals.syscom, GET_BGM_VOLUME, 100);
1526 let bgm_on = cfg_get_int(&ctx.globals.syscom, GET_BGM_ONOFF, 1) != 0;
1527 let bgm_raw = if bgm_on { volume_to_raw(bgm_vol) } else { 0 };
1528
1529 let se_vol = cfg_get_int(&ctx.globals.syscom, GET_SE_VOLUME, 100);
1530 let se_on = cfg_get_int(&ctx.globals.syscom, GET_SE_ONOFF, 1) != 0;
1531 let se_raw = if se_on { volume_to_raw(se_vol) } else { 0 };
1532
1533 let pcm_vol = cfg_get_int(&ctx.globals.syscom, GET_PCM_VOLUME, 100);
1534 let pcm_on = cfg_get_int(&ctx.globals.syscom, GET_PCM_ONOFF, 1) != 0;
1535 let pcm_raw = if pcm_on { volume_to_raw(pcm_vol) } else { 0 };
1536
1537 let koe_vol = cfg_get_int(&ctx.globals.syscom, GET_KOE_VOLUME, 100);
1538 let koe_on = cfg_get_int(&ctx.globals.syscom, GET_KOE_ONOFF, 1) != 0;
1539 let koe_raw = if koe_on { volume_to_raw(koe_vol) } else { 0 };
1540
1541 let mov_vol = cfg_get_int(&ctx.globals.syscom, GET_MOV_VOLUME, 100);
1542 let mov_on = cfg_get_int(&ctx.globals.syscom, GET_MOV_ONOFF, 1) != 0;
1543 let mov_raw = if mov_on { volume_to_raw(mov_vol) } else { 0 };
1544
1545 let eff_bgm = (all_raw as u16 * bgm_raw as u16 / 255) as u8;
1546 let eff_se = (all_raw as u16 * se_raw as u16 / 255) as u8;
1547 let eff_pcm = (all_raw as u16 * pcm_raw as u16 / 255) as u8;
1548 let eff_koe = (all_raw as u16 * koe_raw as u16 / 255) as u8;
1549 let eff_mov = (all_raw as u16 * mov_raw as u16 / 255) as u8;
1550
1551 ctx.audio
1552 .set_track_master_volume_raw(TrackKind::Bgm, eff_bgm);
1553 ctx.audio.set_track_master_volume_raw(TrackKind::Se, eff_se);
1554 ctx.audio
1555 .set_track_master_volume_raw(TrackKind::Pcm, eff_pcm);
1556 ctx.audio
1557 .set_track_master_volume_raw(TrackKind::Koe, eff_koe);
1558 ctx.audio
1559 .set_track_master_volume_raw(TrackKind::Mov, eff_mov);
1560}
1561
1562fn cfg_get_str(st: &crate::runtime::globals::SyscomRuntimeState, key: i32) -> String {
1563 st.config_str.get(&key).cloned().unwrap_or_default()
1564}
1565
1566fn cfg_set_str(st: &mut crate::runtime::globals::SyscomRuntimeState, key: i32, value: String) {
1567 st.config_str.insert(key, value);
1568}
1569
1570fn join_game_path(base: &Path, raw: &str) -> PathBuf {
1571 if raw.is_empty() {
1572 return base.to_path_buf();
1573 }
1574 let norm = raw.replace('\\', "/");
1575 let p = Path::new(&norm);
1576 if p.is_absolute() {
1577 p.to_path_buf()
1578 } else {
1579 base.join(p)
1580 }
1581}
1582
1583fn opaque_rgba(img: &RgbaImage) -> RgbaImage {
1584 let mut rgba = img.rgba.clone();
1585 for px in rgba.chunks_exact_mut(4) {
1586 px[3] = 255;
1587 }
1588 RgbaImage {
1589 width: img.width,
1590 height: img.height,
1591 center_x: 0,
1592 center_y: 0,
1593 rgba,
1594 }
1595}
1596
1597fn write_rgba_png(path: &Path, img: &RgbaImage) -> Result<()> {
1598 if let Some(parent) = path.parent() {
1599 fs::create_dir_all(parent)?;
1600 }
1601 let Some(buf) = image::RgbaImage::from_raw(img.width, img.height, img.rgba.clone()) else {
1602 anyhow::bail!("invalid rgba buffer for {}x{} image", img.width, img.height);
1603 };
1604 buf.save(path)?;
1605 Ok(())
1606}
1607
1608fn write_rgba_png_opaque(path: &Path, img: &RgbaImage) -> Result<()> {
1609 let opaque = opaque_rgba(img);
1610 write_rgba_png(path, &opaque)
1611}
1612
1613fn push_u16_le(out: &mut Vec<u8>, value: u16) {
1614 out.extend_from_slice(&value.to_le_bytes());
1615}
1616
1617fn push_u32_le(out: &mut Vec<u8>, value: u32) {
1618 out.extend_from_slice(&value.to_le_bytes());
1619}
1620
1621fn push_i32_le(out: &mut Vec<u8>, value: i32) {
1622 out.extend_from_slice(&value.to_le_bytes());
1623}
1624
1625fn write_rgba_bmp_top_down(path: &Path, img: &RgbaImage) -> Result<()> {
1626 if let Some(parent) = path.parent() {
1627 fs::create_dir_all(parent)?;
1628 }
1629 let width = img.width;
1630 let height = img.height;
1631 if width == 0 || height == 0 {
1632 anyhow::bail!("invalid zero-sized bmp image {}x{}", width, height);
1633 }
1634 let pixel_size = width.saturating_mul(height).saturating_mul(4);
1635 let file_size = 14u32.saturating_add(40).saturating_add(pixel_size);
1636 let mut out = Vec::with_capacity(file_size as usize);
1637
1638 out.extend_from_slice(b"BM");
1639 push_u32_le(&mut out, file_size);
1640 push_u16_le(&mut out, 0);
1641 push_u16_le(&mut out, 0);
1642 push_u32_le(&mut out, 14 + 40);
1643
1644 push_u32_le(&mut out, 40);
1645 push_i32_le(&mut out, width as i32);
1646 push_i32_le(&mut out, -(height as i32));
1647 push_u16_le(&mut out, 1);
1648 push_u16_le(&mut out, 32);
1649 push_u32_le(&mut out, 0);
1650 push_u32_le(&mut out, 0);
1651 push_i32_le(&mut out, 0);
1652 push_i32_le(&mut out, 0);
1653 push_u32_le(&mut out, 0);
1654 push_u32_le(&mut out, 0);
1655
1656 for px in img.rgba.chunks_exact(4) {
1657 out.push(px[2]);
1658 out.push(px[1]);
1659 out.push(px[0]);
1660 out.push(px[3]);
1661 }
1662 fs::write(path, out)?;
1663 Ok(())
1664}
1665
1666fn resize_rgba(img: &RgbaImage, w: u32, h: u32) -> RgbaImage {
1667 if img.width == 0 || img.height == 0 || w == 0 || h == 0 {
1668 return img.clone();
1669 }
1670 if img.width == w && img.height == h {
1671 return img.clone();
1672 }
1673 let mut out = vec![0u8; (w * h * 4) as usize];
1674 for y in 0..h {
1675 let src_y = (y as u64 * img.height as u64 / h as u64) as u32;
1676 for x in 0..w {
1677 let src_x = (x as u64 * img.width as u64 / w as u64) as u32;
1678 let si = ((src_y * img.width + src_x) * 4) as usize;
1679 let di = ((y * w + x) * 4) as usize;
1680 out[di..di + 4].copy_from_slice(&img.rgba[si..si + 4]);
1681 }
1682 }
1683 RgbaImage {
1684 width: w,
1685 height: h,
1686 center_x: 0,
1687 center_y: 0,
1688 rgba: out,
1689 }
1690}
1691
1692pub(crate) fn resize_capture_rgba_nearest(img: &RgbaImage, w: u32, h: u32) -> RgbaImage {
1693 resize_rgba(img, w, h)
1694}
1695
1696
1697fn font_exists(project_dir: &Path, name: &str) -> bool {
1698 if name.is_empty() {
1699 return false;
1700 }
1701 if crate::text_render::font_name_matches_embedded_default(name) {
1702 return true;
1703 }
1704
1705 let name_lower = name.to_ascii_lowercase();
1706 for font_dir in [project_dir.join("font"), project_dir.join("fonts")] {
1707 let Ok(entries) = fs::read_dir(font_dir) else {
1708 continue;
1709 };
1710 for entry in entries.flatten() {
1711 let path = entry.path();
1712 if !path.is_file() {
1713 continue;
1714 }
1715 let ext = path
1716 .extension()
1717 .and_then(|s| s.to_str())
1718 .unwrap_or("")
1719 .to_ascii_lowercase();
1720 if ext != "ttf" && ext != "otf" && ext != "ttc" {
1721 continue;
1722 }
1723 let stem = path
1724 .file_stem()
1725 .and_then(|s| s.to_str())
1726 .unwrap_or("")
1727 .to_ascii_lowercase();
1728 if stem == name_lower {
1729 return true;
1730 }
1731 }
1732 }
1733 false
1734}
1735
1736pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
1737 let Some(call) = parse_call(ctx, form_id, args) else {
1738 return Ok(false);
1739 };
1740 let op = call.op;
1741 let params = call.params;
1742
1743 {
1744 let st = &ctx.globals.syscom;
1745 if let Some(v) = get_local_extra(op, params, st) {
1746 ctx.push(Value::Int(v));
1747 return Ok(true);
1748 }
1749 if let Some(v) = get_toggle_get(op, st) {
1750 ctx.push(Value::Int(v));
1751 return Ok(true);
1752 }
1753 }
1754 {
1755 let st = &mut ctx.globals.syscom;
1756 if set_local_extra(op, params, st) {
1757 ctx.push(Value::Int(0));
1758 return Ok(true);
1759 }
1760 if apply_toggle_set(op, p_bool(params, 0), st) {
1761 ctx.push(Value::Int(0));
1762 return Ok(true);
1763 }
1764 }
1765
1766 match op {
1767 CALL_EX => {
1768 return Ok(false);
1773 }
1774 CALL_SYSCOM_MENU => {
1775 ctx.globals.syscom.menu_open = false;
1776 ctx.globals.syscom.menu_kind = None;
1777 ctx.globals.syscom.menu_result = None;
1778 ctx.globals.syscom.read_skip.onoff = false;
1779 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
1780 kind: SyscomPendingProcKind::OpenSyscomMenu,
1781 warning: false,
1782 se_play: false,
1783 fade_out: false,
1784 leave_msgbk: false,
1785 save_id: 0,
1786 });
1787 ctx.globals.syscom.last_menu_call = CALL_SYSCOM_MENU;
1788 return Ok(true);
1789 }
1790 SET_SYSCOM_MENU_ENABLE => ctx.globals.syscom.syscom_menu_disable = false,
1791 SET_SYSCOM_MENU_DISABLE => ctx.globals.syscom.syscom_menu_disable = true,
1792 SET_MWND_BTN_ENABLE => {
1793 if params.is_empty() {
1794 ctx.globals.syscom.mwnd_btn_disable_all = false;
1795 if sg_debug_enabled_local() {
1796 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_ENABLE all disable_all=false");
1797 }
1798 } else {
1799 let idx = p_i64(params, 0);
1800 ctx.globals.syscom.mwnd_btn_disable.insert(idx, false);
1801 if sg_debug_enabled_local() {
1802 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_ENABLE idx={} disabled=false", idx);
1803 }
1804 }
1805 }
1806 SET_MWND_BTN_DISABLE => {
1807 if params.is_empty() {
1808 ctx.globals.syscom.mwnd_btn_disable_all = true;
1809 if sg_debug_enabled_local() {
1810 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_DISABLE all disable_all=true");
1811 }
1812 } else {
1813 let idx = p_i64(params, 0);
1814 ctx.globals.syscom.mwnd_btn_disable.insert(idx, true);
1815 if sg_debug_enabled_local() {
1816 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_DISABLE idx={} disabled=true", idx);
1817 }
1818 }
1819 }
1820 SET_MWND_BTN_TOUCH_ENABLE => {
1821 ctx.globals.syscom.mwnd_btn_touch_disable = false;
1822 if sg_debug_enabled_local() {
1823 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_TOUCH_ENABLE touch_disable=false");
1824 }
1825 }
1826 SET_MWND_BTN_TOUCH_DISABLE => {
1827 ctx.globals.syscom.mwnd_btn_touch_disable = true;
1828 if sg_debug_enabled_local() {
1829 eprintln!("[SG_DEBUG][BUTTON_TRACE][SYSCOM] SET_MWND_BTN_TOUCH_DISABLE touch_disable=true");
1830 }
1831 }
1832 INIT_SYSCOM_FLAG => {
1833 let enabled = ToggleFeatureState { onoff: false, enable: true, exist: true };
1834 ctx.globals.syscom.read_skip = enabled;
1835 ctx.globals.syscom.auto_skip = enabled;
1836 ctx.globals.syscom.auto_mode = enabled;
1837 ctx.globals.syscom.hide_mwnd = enabled;
1838 ctx.globals.syscom.local_extra_switch = enabled;
1839 ctx.globals.syscom.local_extra_mode = ValueFeatureState { value: 0, enable: true, exist: true };
1840 ctx.globals.syscom.local_extra_switches = [enabled; 4];
1841 ctx.globals.syscom.local_extra_modes = [ValueFeatureState { value: 0, enable: true, exist: true }; 4];
1842 ctx.globals.syscom.msg_back = enabled;
1843 ctx.globals.syscom.return_to_sel = enabled;
1844 ctx.globals.syscom.return_to_menu = enabled;
1845 ctx.globals.syscom.end_game = enabled;
1846 ctx.globals.syscom.save_feature = enabled;
1847 ctx.globals.syscom.load_feature = enabled;
1848 ctx.globals.syscom.msg_back_open = false;
1849 load_global_save(ctx);
1850 }
1851 OPEN_MSG_BACK => {
1852 if open_msg_back_proc(ctx) {
1853 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
1854 kind: SyscomPendingProcKind::MsgBack,
1855 warning: false,
1856 se_play: false,
1857 fade_out: false,
1858 leave_msgbk: false,
1859 save_id: 0,
1860 });
1861 }
1862 ctx.globals.syscom.last_menu_call = OPEN_MSG_BACK;
1863 }
1864 CLOSE_MSG_BACK => {
1865 ctx.globals.syscom.msg_back_open = false;
1866 ctx.globals.syscom.msg_back_proc_initialized = false;
1867 ctx.globals.syscom.last_menu_call = CLOSE_MSG_BACK;
1868 }
1869 RETURN_TO_SEL => {
1870 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
1871 kind: SyscomPendingProcKind::ReturnToSel,
1872 warning: p_bool(params, 0),
1873 se_play: p_bool(params, 1),
1874 fade_out: p_bool(params, 2),
1875 leave_msgbk: false,
1876 save_id: 0,
1877 });
1878 ctx.globals.syscom.last_menu_call = RETURN_TO_SEL;
1879 ctx.globals.syscom.menu_open = false;
1880 }
1881 RETURN_TO_MENU => {
1882 let leave_msgbk = params
1883 .iter()
1884 .find(|v| v.named_id() == Some(0))
1885 .and_then(Value::as_i64)
1886 .unwrap_or(0)
1887 != 0;
1888 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
1889 kind: SyscomPendingProcKind::ReturnToMenu,
1890 warning: p_bool(params, 0),
1891 se_play: p_bool(params, 1),
1892 fade_out: p_bool(params, 2),
1893 leave_msgbk,
1894 save_id: 0,
1895 });
1896 ctx.globals.syscom.last_menu_call = RETURN_TO_MENU;
1897 ctx.globals.syscom.menu_open = false;
1898 }
1899 END_GAME => {
1900 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
1901 kind: SyscomPendingProcKind::EndGame,
1902 warning: p_bool(params, 0),
1903 se_play: p_bool(params, 1),
1904 fade_out: p_bool(params, 2),
1905 leave_msgbk: false,
1906 save_id: 0,
1907 });
1908 ctx.globals.syscom.last_menu_call = END_GAME;
1909 ctx.globals.syscom.menu_open = false;
1910 }
1911 REPLAY_KOE => ctx.globals.syscom.replay_koe = Some((p_i64(params, 0), p_i64(params, 1))),
1912 CHECK_REPLAY_KOE => {
1913 let v = if ctx.globals.syscom.replay_koe.is_some() {
1914 1
1915 } else {
1916 0
1917 };
1918 ctx.push(Value::Int(v));
1919 return Ok(true);
1920 }
1921 GET_REPLAY_KOE_KOE_NO => {
1922 let v = ctx.globals.syscom.replay_koe.map(|v| v.0).unwrap_or(-1);
1923 ctx.push(Value::Int(v));
1924 return Ok(true);
1925 }
1926 GET_REPLAY_KOE_CHARA_NO => {
1927 let v = ctx.globals.syscom.replay_koe.map(|v| v.1).unwrap_or(-1);
1928 ctx.push(Value::Int(v));
1929 return Ok(true);
1930 }
1931 CLEAR_REPLAY_KOE => ctx.globals.syscom.replay_koe = None,
1932 GET_CURRENT_SAVE_SCENE_TITLE => {
1933 let v = ctx.globals.syscom.current_save_scene_title.clone();
1934 ctx.push(Value::Str(v));
1935 return Ok(true);
1936 }
1937 GET_CURRENT_SAVE_MESSAGE => {
1938 let v = ctx.globals.syscom.current_save_message.clone();
1939 ctx.push(Value::Str(v));
1940 return Ok(true);
1941 }
1942 GET_TOTAL_PLAY_TIME => {
1943 let v = ctx.globals.syscom.total_play_time;
1944 ctx.push(Value::Int(v));
1945 return Ok(true);
1946 }
1947 SET_TOTAL_PLAY_TIME => {
1948 ctx.globals.syscom.total_play_time = p_i64(params, 0);
1949 write_global_save(ctx);
1950 },
1951 CALL_SAVE_MENU => {
1952 sync_save_slots_from_disk(ctx, false);
1953 set_syscom_pending_proc(ctx, SyscomPendingProcKind::OpenSave);
1954 ctx.globals.syscom.last_menu_call = CALL_SAVE_MENU;
1955 return Ok(true);
1956 }
1957 CALL_LOAD_MENU => {
1958 sync_save_slots_from_disk(ctx, false);
1959 set_syscom_pending_proc(ctx, SyscomPendingProcKind::OpenLoad);
1960 ctx.globals.syscom.last_menu_call = CALL_LOAD_MENU;
1961 return Ok(true);
1962 }
1963 SAVE => {
1964 let idx = p_i64(params, 0).max(0) as usize;
1965 let warning = p_bool(params, 1);
1966 let ok = idx < configured_save_count(ctx, false) && local_save_available(ctx);
1967 if ok && !request_confirmed_save_or_load(ctx, SyscomPendingProcKind::Save, idx, warning, true) {
1968 if ok {
1969 menu_save_slot(ctx, false, idx);
1970 }
1971 }
1972 ctx.globals.syscom.last_menu_call = SAVE;
1973 ctx.push(Value::Int(if ok { 1 } else { 0 }));
1974 return Ok(true);
1975 }
1976 LOAD => {
1977 let idx = p_i64(params, 0).max(0) as usize;
1978 let warning = p_bool(params, 1);
1979 let ok = idx < configured_save_count(ctx, false)
1980 && local_save_file_exists(ctx, SaveKind::Normal, idx);
1981 if ok && !request_confirmed_save_or_load(ctx, SyscomPendingProcKind::Load, idx, warning, false) {
1982 menu_load_slot(ctx, false, idx);
1983 }
1984 ctx.globals.syscom.last_menu_call = LOAD;
1985 }
1986 QUICK_SAVE => {
1987 let idx = p_i64(params, 0).max(0) as usize;
1988 let warning = p_bool(params, 1);
1989 let ok = idx < configured_save_count(ctx, true) && local_save_available(ctx);
1990 if ok && !request_confirmed_save_or_load(ctx, SyscomPendingProcKind::QuickSave, idx, warning, true) {
1991 if ok {
1992 menu_save_slot(ctx, true, idx);
1993 }
1994 }
1995 ctx.globals.syscom.last_menu_call = QUICK_SAVE;
1996 ctx.push(Value::Int(if ok { 1 } else { 0 }));
1997 return Ok(true);
1998 }
1999 QUICK_LOAD => {
2000 let idx = p_i64(params, 0).max(0) as usize;
2001 let warning = p_bool(params, 1);
2002 let ok = idx < configured_save_count(ctx, true)
2003 && local_save_file_exists(ctx, SaveKind::Quick, idx);
2004 if ok && !request_confirmed_save_or_load(ctx, SyscomPendingProcKind::QuickLoad, idx, warning, false) {
2005 menu_load_slot(ctx, true, idx);
2006 }
2007 ctx.globals.syscom.last_menu_call = QUICK_LOAD;
2008 }
2009 END_SAVE => {
2010 let _warning = p_bool(params, 0);
2013 let ok = local_save_available(ctx);
2014 if ok {
2015 ctx.request_runtime_save(RuntimeSaveKind::End, 0);
2016 }
2017 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2018 return Ok(true);
2019 }
2020 END_LOAD => {
2021 if local_save_file_exists(ctx, SaveKind::End, 0) {
2024 ctx.request_runtime_load(RuntimeSaveKind::End, 0);
2025 }
2026 ctx.globals.syscom.last_menu_call = END_LOAD;
2027 },
2028 INNER_SAVE => {
2029 let idx = p_i64(params, 0).max(0) as usize;
2030 let ok = local_save_available(ctx);
2031 if ok {
2032 ctx.request_runtime_save(RuntimeSaveKind::Inner, idx);
2033 }
2034 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2035 return Ok(true);
2036 }
2037 INNER_LOAD => {
2038 let idx = p_i64(params, 0).max(0) as usize;
2039 ctx.globals.syscom.last_menu_call = INNER_LOAD;
2040 let exists = ctx
2041 .globals
2042 .syscom
2043 .inner_save_streams
2044 .get(idx)
2045 .map(|s| !s.is_empty())
2046 .unwrap_or(false);
2047 if exists {
2048 ctx.request_runtime_load(RuntimeSaveKind::Inner, idx);
2049 }
2050 ctx.push(Value::Int(if exists { 1 } else { 0 }));
2051 return Ok(true);
2052 }
2053 CLEAR_INNER_SAVE => {
2054 let idx = p_i64(params, 0).max(0) as usize;
2055 let existed = ctx
2056 .globals
2057 .syscom
2058 .inner_save_streams
2059 .get(idx)
2060 .map(|s| !s.is_empty())
2061 .unwrap_or(false);
2062 if let Some(slot) = ctx.globals.syscom.inner_save_streams.get_mut(idx) {
2063 slot.clear();
2064 }
2065 ctx.globals.syscom.inner_save_exists = ctx.globals.syscom.inner_save_streams.iter().any(|s| !s.is_empty());
2066 ctx.push(Value::Int(if existed { 1 } else { 0 }));
2067 return Ok(true);
2068 }
2069 COPY_INNER_SAVE => {
2070 let from = p_i64(params, 0).max(0) as usize;
2071 let to = p_i64(params, 1).max(0) as usize;
2072 let Some(stream) = ctx.globals.syscom.inner_save_streams.get(from).cloned().filter(|s| !s.is_empty()) else {
2073 ctx.push(Value::Int(0));
2074 return Ok(true);
2075 };
2076 if ctx.globals.syscom.inner_save_streams.len() <= to {
2077 ctx.globals.syscom.inner_save_streams.resize_with(to + 1, Vec::new);
2078 }
2079 ctx.globals.syscom.inner_save_streams[to] = stream;
2080 ctx.globals.syscom.inner_save_exists = true;
2081 ctx.push(Value::Int(1));
2082 return Ok(true);
2083 }
2084 CHECK_INNER_SAVE => {
2085 let idx = p_i64(params, 0).max(0) as usize;
2086 let v = if ctx
2087 .globals
2088 .syscom
2089 .inner_save_streams
2090 .get(idx)
2091 .map(|s| !s.is_empty())
2092 .unwrap_or(false)
2093 {
2094 1
2095 } else {
2096 0
2097 };
2098 ctx.push(Value::Int(v));
2099 return Ok(true);
2100 }
2101 MSG_BACK_LOAD => {
2102 ctx.globals.syscom.pending_proc = Some(SyscomPendingProc {
2103 kind: SyscomPendingProcKind::BacklogLoad,
2104 warning: p_bool(params, 0),
2105 se_play: p_bool(params, 1),
2106 fade_out: p_bool(params, 2),
2107 leave_msgbk: false,
2108 save_id: ctx.globals.syscom.msg_back_load_tid,
2109 });
2110 ctx.globals.syscom.last_menu_call = MSG_BACK_LOAD;
2111 ctx.globals.syscom.msg_back_open = false;
2112 write_msg_back(ctx);
2113 }
2114 GET_SAVE_CNT => {
2115 let v = configured_save_count(ctx, false) as i64;
2116 ctx.push(Value::Int(v));
2117 return Ok(true);
2118 }
2119 GET_QUICK_SAVE_CNT => {
2120 let v = configured_save_count(ctx, true) as i64;
2121 ctx.push(Value::Int(v));
2122 return Ok(true);
2123 }
2124 GET_SAVE_NEW_NO => {
2125 sync_save_slots_from_disk(ctx, false);
2126 let v = first_free_slot(&ctx.globals.syscom.save_slots);
2127 ctx.push(Value::Int(v));
2128 return Ok(true);
2129 }
2130 GET_QUICK_SAVE_NEW_NO => {
2131 sync_save_slots_from_disk(ctx, true);
2132 let v = first_free_slot(&ctx.globals.syscom.quick_save_slots);
2133 ctx.push(Value::Int(v));
2134 return Ok(true);
2135 }
2136 GET_SAVE_EXIST | GET_SAVE_YEAR | GET_SAVE_MONTH | GET_SAVE_DAY | GET_SAVE_WEEKDAY
2137 | GET_SAVE_HOUR | GET_SAVE_MINUTE | GET_SAVE_SECOND | GET_SAVE_MILLISECOND => {
2138 let idx = p_i64(params, 0).max(0) as usize;
2139 {
2140 let save_cnt = configured_save_count(ctx, false);
2141 let quick_cnt = configured_save_count(ctx, true);
2142 ensure_slot_loaded_with_counts(
2143 &ctx.project_dir,
2144 false,
2145 save_cnt,
2146 quick_cnt,
2147 &mut ctx.globals.syscom.save_slots,
2148 idx,
2149 );
2150 }
2151 let v = ctx
2152 .globals
2153 .syscom
2154 .save_slots
2155 .get(idx)
2156 .map(|s| slot_i64(s, op))
2157 .unwrap_or(0);
2158 ctx.push(Value::Int(v));
2159 return Ok(true);
2160 }
2161 GET_SAVE_TITLE
2162 | GET_SAVE_MESSAGE
2163 | GET_SAVE_FULL_MESSAGE
2164 | GET_SAVE_COMMENT
2165 | GET_SAVE_APPEND_DIR
2166 | GET_SAVE_APPEND_NAME => {
2167 let idx = p_i64(params, 0).max(0) as usize;
2168 {
2169 let save_cnt = configured_save_count(ctx, false);
2170 let quick_cnt = configured_save_count(ctx, true);
2171 ensure_slot_loaded_with_counts(
2172 &ctx.project_dir,
2173 false,
2174 save_cnt,
2175 quick_cnt,
2176 &mut ctx.globals.syscom.save_slots,
2177 idx,
2178 );
2179 }
2180 let v = ctx
2181 .globals
2182 .syscom
2183 .save_slots
2184 .get(idx)
2185 .map(|s| slot_str(s, op))
2186 .unwrap_or_default();
2187 ctx.push(Value::Str(v));
2188 return Ok(true);
2189 }
2190 SET_SAVE_COMMENT => {
2191 let idx = p_i64(params, 0).max(0) as usize;
2192 let comment = params
2193 .get(1)
2194 .and_then(|v| v.as_str())
2195 .unwrap_or("")
2196 .to_string();
2197 let slot = ensure_slot(&mut ctx.globals.syscom.save_slots, idx);
2198 slot.exist = true;
2199 slot.comment = comment;
2200 {
2201 let save_cnt = configured_save_count(ctx, false);
2202 let quick_cnt = configured_save_count(ctx, true);
2203 persist_slot_with_counts(
2204 &ctx.project_dir,
2205 false,
2206 save_cnt,
2207 quick_cnt,
2208 &ctx.globals.syscom.save_slots,
2209 idx,
2210 );
2211 }
2212 }
2213 GET_SAVE_VALUE => {
2214 let idx = p_i64(params, 0).max(0) as usize;
2215 {
2216 let save_cnt = configured_save_count(ctx, false);
2217 let quick_cnt = configured_save_count(ctx, true);
2218 ensure_slot_loaded_with_counts(
2219 &ctx.project_dir,
2220 false,
2221 save_cnt,
2222 quick_cnt,
2223 &mut ctx.globals.syscom.save_slots,
2224 idx,
2225 );
2226 }
2227 if let Some(Value::Element(chain)) = params.get(1).map(|v| v.unwrap_named()) {
2228 let Some(form_id) = chain.first().copied() else {
2229 ctx.push(Value::Int(0));
2230 return Ok(true);
2231 };
2232 let flag_index = p_i64(params, 2).max(0) as usize;
2233 let flag_cnt = p_i64(params, 3).max(0) as usize;
2234 let values: Vec<i64> = (0..flag_cnt)
2235 .map(|i| {
2236 ctx.globals
2237 .syscom
2238 .save_slots
2239 .get(idx)
2240 .and_then(|s| s.values.get(&(i as i32)).copied())
2241 .unwrap_or(0)
2242 })
2243 .collect();
2244 let list = ctx.globals.int_lists.entry(form_id as u32).or_default();
2245 if list.len() < flag_index + flag_cnt {
2246 list.resize(flag_index + flag_cnt, 0);
2247 }
2248 for (i, v) in values.into_iter().enumerate() {
2249 list[flag_index + i] = v;
2250 }
2251 ctx.push(Value::Int(0));
2252 return Ok(true);
2253 }
2254 let key = p_i64(params, 1) as i32;
2255 let v = ctx
2256 .globals
2257 .syscom
2258 .save_slots
2259 .get(idx)
2260 .and_then(|s| s.values.get(&key).copied())
2261 .unwrap_or(0);
2262 ctx.push(Value::Int(v));
2263 return Ok(true);
2264 }
2265 SET_SAVE_VALUE => {
2266 let idx = p_i64(params, 0).max(0) as usize;
2267 if let Some(Value::Element(chain)) = params.get(1).map(|v| v.unwrap_named()) {
2268 let Some(form_id) = chain.first().copied() else {
2269 return Ok(true);
2270 };
2271 let flag_index = p_i64(params, 2).max(0) as usize;
2272 let flag_cnt = p_i64(params, 3).max(0) as usize;
2273 let values: Vec<i64> = (0..flag_cnt)
2274 .map(|i| {
2275 ctx.globals
2276 .int_lists
2277 .get(&(form_id as u32))
2278 .and_then(|list| list.get(flag_index + i).copied())
2279 .unwrap_or(0)
2280 })
2281 .collect();
2282 let slot = ensure_slot(&mut ctx.globals.syscom.save_slots, idx);
2283 slot.exist = true;
2284 for (i, v) in values.into_iter().enumerate() {
2285 slot.values.insert(i as i32, v);
2286 }
2287 {
2288 let save_cnt = configured_save_count(ctx, false);
2289 let quick_cnt = configured_save_count(ctx, true);
2290 persist_slot_with_counts(
2291 &ctx.project_dir,
2292 false,
2293 save_cnt,
2294 quick_cnt,
2295 &ctx.globals.syscom.save_slots,
2296 idx,
2297 );
2298 }
2299 return Ok(true);
2300 }
2301 let key = p_i64(params, 1) as i32;
2302 let val = p_i64(params, 2);
2303 let slot = ensure_slot(&mut ctx.globals.syscom.save_slots, idx);
2304 slot.exist = true;
2305 slot.values.insert(key, val);
2306 {
2307 let save_cnt = configured_save_count(ctx, false);
2308 let quick_cnt = configured_save_count(ctx, true);
2309 persist_slot_with_counts(
2310 &ctx.project_dir,
2311 false,
2312 save_cnt,
2313 quick_cnt,
2314 &ctx.globals.syscom.save_slots,
2315 idx,
2316 );
2317 }
2318 }
2319 GET_QUICK_SAVE_EXIST
2320 | GET_QUICK_SAVE_YEAR
2321 | GET_QUICK_SAVE_MONTH
2322 | GET_QUICK_SAVE_DAY
2323 | GET_QUICK_SAVE_WEEKDAY
2324 | GET_QUICK_SAVE_HOUR
2325 | GET_QUICK_SAVE_MINUTE
2326 | GET_QUICK_SAVE_SECOND
2327 | GET_QUICK_SAVE_MILLISECOND => {
2328 let idx = p_i64(params, 0).max(0) as usize;
2329 {
2330 let save_cnt = configured_save_count(ctx, false);
2331 let quick_cnt = configured_save_count(ctx, true);
2332 ensure_slot_loaded_with_counts(
2333 &ctx.project_dir,
2334 true,
2335 save_cnt,
2336 quick_cnt,
2337 &mut ctx.globals.syscom.quick_save_slots,
2338 idx,
2339 );
2340 }
2341 let v = ctx
2342 .globals
2343 .syscom
2344 .quick_save_slots
2345 .get(idx)
2346 .map(|s| slot_i64(s, op))
2347 .unwrap_or(0);
2348 ctx.push(Value::Int(v));
2349 return Ok(true);
2350 }
2351 GET_QUICK_SAVE_TITLE
2352 | GET_QUICK_SAVE_MESSAGE
2353 | GET_QUICK_SAVE_FULL_MESSAGE
2354 | GET_QUICK_SAVE_COMMENT
2355 | GET_QUICK_SAVE_APPEND_DIR
2356 | GET_QUICK_SAVE_APPEND_NAME => {
2357 let idx = p_i64(params, 0).max(0) as usize;
2358 {
2359 let save_cnt = configured_save_count(ctx, false);
2360 let quick_cnt = configured_save_count(ctx, true);
2361 ensure_slot_loaded_with_counts(
2362 &ctx.project_dir,
2363 true,
2364 save_cnt,
2365 quick_cnt,
2366 &mut ctx.globals.syscom.quick_save_slots,
2367 idx,
2368 );
2369 }
2370 let v = ctx
2371 .globals
2372 .syscom
2373 .quick_save_slots
2374 .get(idx)
2375 .map(|s| slot_str(s, op))
2376 .unwrap_or_default();
2377 ctx.push(Value::Str(v));
2378 return Ok(true);
2379 }
2380 SET_QUICK_SAVE_COMMENT => {
2381 let idx = p_i64(params, 0).max(0) as usize;
2382 let comment = params
2383 .get(1)
2384 .and_then(|v| v.as_str())
2385 .unwrap_or("")
2386 .to_string();
2387 let slot = ensure_slot(&mut ctx.globals.syscom.quick_save_slots, idx);
2388 slot.exist = true;
2389 slot.comment = comment;
2390 {
2391 let save_cnt = configured_save_count(ctx, false);
2392 let quick_cnt = configured_save_count(ctx, true);
2393 persist_slot_with_counts(
2394 &ctx.project_dir,
2395 true,
2396 save_cnt,
2397 quick_cnt,
2398 &ctx.globals.syscom.quick_save_slots,
2399 idx,
2400 );
2401 }
2402 }
2403 GET_QUICK_SAVE_VALUE => {
2404 let idx = p_i64(params, 0).max(0) as usize;
2405 {
2406 let save_cnt = configured_save_count(ctx, false);
2407 let quick_cnt = configured_save_count(ctx, true);
2408 ensure_slot_loaded_with_counts(
2409 &ctx.project_dir,
2410 true,
2411 save_cnt,
2412 quick_cnt,
2413 &mut ctx.globals.syscom.quick_save_slots,
2414 idx,
2415 );
2416 }
2417 if let Some(Value::Element(chain)) = params.get(1).map(|v| v.unwrap_named()) {
2418 let Some(form_id) = chain.first().copied() else {
2419 ctx.push(Value::Int(0));
2420 return Ok(true);
2421 };
2422 let flag_index = p_i64(params, 2).max(0) as usize;
2423 let flag_cnt = p_i64(params, 3).max(0) as usize;
2424 let values: Vec<i64> = (0..flag_cnt)
2425 .map(|i| {
2426 ctx.globals
2427 .syscom
2428 .quick_save_slots
2429 .get(idx)
2430 .and_then(|s| s.values.get(&(i as i32)).copied())
2431 .unwrap_or(0)
2432 })
2433 .collect();
2434 let list = ctx.globals.int_lists.entry(form_id as u32).or_default();
2435 if list.len() < flag_index + flag_cnt {
2436 list.resize(flag_index + flag_cnt, 0);
2437 }
2438 for (i, v) in values.into_iter().enumerate() {
2439 list[flag_index + i] = v;
2440 }
2441 ctx.push(Value::Int(0));
2442 return Ok(true);
2443 }
2444 let key = p_i64(params, 1) as i32;
2445 let v = ctx
2446 .globals
2447 .syscom
2448 .quick_save_slots
2449 .get(idx)
2450 .and_then(|s| s.values.get(&key).copied())
2451 .unwrap_or(0);
2452 ctx.push(Value::Int(v));
2453 return Ok(true);
2454 }
2455 SET_QUICK_SAVE_VALUE => {
2456 let idx = p_i64(params, 0).max(0) as usize;
2457 if let Some(Value::Element(chain)) = params.get(1).map(|v| v.unwrap_named()) {
2458 let Some(form_id) = chain.first().copied() else {
2459 return Ok(true);
2460 };
2461 let flag_index = p_i64(params, 2).max(0) as usize;
2462 let flag_cnt = p_i64(params, 3).max(0) as usize;
2463 let values: Vec<i64> = (0..flag_cnt)
2464 .map(|i| {
2465 ctx.globals
2466 .int_lists
2467 .get(&(form_id as u32))
2468 .and_then(|list| list.get(flag_index + i).copied())
2469 .unwrap_or(0)
2470 })
2471 .collect();
2472 let slot = ensure_slot(&mut ctx.globals.syscom.quick_save_slots, idx);
2473 slot.exist = true;
2474 for (i, v) in values.into_iter().enumerate() {
2475 slot.values.insert(i as i32, v);
2476 }
2477 {
2478 let save_cnt = configured_save_count(ctx, false);
2479 let quick_cnt = configured_save_count(ctx, true);
2480 persist_slot_with_counts(
2481 &ctx.project_dir,
2482 true,
2483 save_cnt,
2484 quick_cnt,
2485 &ctx.globals.syscom.quick_save_slots,
2486 idx,
2487 );
2488 }
2489 return Ok(true);
2490 }
2491 let key = p_i64(params, 1) as i32;
2492 let val = p_i64(params, 2);
2493 let slot = ensure_slot(&mut ctx.globals.syscom.quick_save_slots, idx);
2494 slot.exist = true;
2495 slot.values.insert(key, val);
2496 {
2497 let save_cnt = configured_save_count(ctx, false);
2498 let quick_cnt = configured_save_count(ctx, true);
2499 persist_slot_with_counts(
2500 &ctx.project_dir,
2501 true,
2502 save_cnt,
2503 quick_cnt,
2504 &ctx.globals.syscom.quick_save_slots,
2505 idx,
2506 );
2507 }
2508 }
2509 GET_END_SAVE_EXIST => {
2510 let save_cnt = configured_save_count(ctx, false);
2511 let quick_cnt = configured_save_count(ctx, true);
2512 let end_path = original_save::save_file_path_with_counts(
2513 &ctx.project_dir,
2514 save_cnt,
2515 quick_cnt,
2516 SaveKind::End,
2517 0,
2518 );
2519 let v = if ctx.globals.syscom.end_save_exists || end_path.exists() { 1 } else { 0 };
2520 ctx.push(Value::Int(v));
2521 return Ok(true);
2522 }
2523 COPY_SAVE => {
2524 let src = p_i64(params, 0).max(0) as usize;
2525 let dst = p_i64(params, 1).max(0) as usize;
2526 let save_cnt = configured_save_count(ctx, false);
2527 let quick_cnt = configured_save_count(ctx, true);
2528 let thumb_config = save_thumb_config(ctx);
2529 let ok = copy_slot(
2530 &ctx.project_dir,
2531 false,
2532 save_cnt,
2533 quick_cnt,
2534 thumb_config,
2535 &mut ctx.globals.syscom.save_slots,
2536 src,
2537 dst,
2538 );
2539 ctx.globals.syscom.last_menu_call = op;
2540 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2541 return Ok(true);
2542 }
2543 COPY_QUICK_SAVE => {
2544 let src = p_i64(params, 0).max(0) as usize;
2545 let dst = p_i64(params, 1).max(0) as usize;
2546 let save_cnt = configured_save_count(ctx, false);
2547 let quick_cnt = configured_save_count(ctx, true);
2548 let thumb_config = save_thumb_config(ctx);
2549 let ok = copy_slot(
2550 &ctx.project_dir,
2551 true,
2552 save_cnt,
2553 quick_cnt,
2554 thumb_config,
2555 &mut ctx.globals.syscom.quick_save_slots,
2556 src,
2557 dst,
2558 );
2559 ctx.globals.syscom.last_menu_call = op;
2560 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2561 return Ok(true);
2562 }
2563 CHANGE_SAVE => {
2564 let a = p_i64(params, 0).max(0) as usize;
2565 let b = p_i64(params, 1).max(0) as usize;
2566 let save_cnt = configured_save_count(ctx, false);
2567 let quick_cnt = configured_save_count(ctx, true);
2568 let thumb_config = save_thumb_config(ctx);
2569 let ok = change_slot(
2570 &ctx.project_dir,
2571 false,
2572 save_cnt,
2573 quick_cnt,
2574 thumb_config,
2575 &mut ctx.globals.syscom.save_slots,
2576 a,
2577 b,
2578 );
2579 ctx.globals.syscom.last_menu_call = op;
2580 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2581 return Ok(true);
2582 }
2583 CHANGE_QUICK_SAVE => {
2584 let a = p_i64(params, 0).max(0) as usize;
2585 let b = p_i64(params, 1).max(0) as usize;
2586 let save_cnt = configured_save_count(ctx, false);
2587 let quick_cnt = configured_save_count(ctx, true);
2588 let thumb_config = save_thumb_config(ctx);
2589 let ok = change_slot(
2590 &ctx.project_dir,
2591 true,
2592 save_cnt,
2593 quick_cnt,
2594 thumb_config,
2595 &mut ctx.globals.syscom.quick_save_slots,
2596 a,
2597 b,
2598 );
2599 ctx.globals.syscom.last_menu_call = op;
2600 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2601 return Ok(true);
2602 }
2603 DELETE_SAVE => {
2604 let idx = p_i64(params, 0).max(0) as usize;
2605 let save_cnt = configured_save_count(ctx, false);
2606 let quick_cnt = configured_save_count(ctx, true);
2607 let thumb_config = save_thumb_config(ctx);
2608 let ok = delete_slot(
2609 &ctx.project_dir,
2610 false,
2611 save_cnt,
2612 quick_cnt,
2613 thumb_config,
2614 &mut ctx.globals.syscom.save_slots,
2615 idx,
2616 );
2617 ctx.globals.syscom.last_menu_call = op;
2618 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2619 return Ok(true);
2620 }
2621 DELETE_QUICK_SAVE => {
2622 let idx = p_i64(params, 0).max(0) as usize;
2623 let save_cnt = configured_save_count(ctx, false);
2624 let quick_cnt = configured_save_count(ctx, true);
2625 let thumb_config = save_thumb_config(ctx);
2626 let ok = delete_slot(
2627 &ctx.project_dir,
2628 true,
2629 save_cnt,
2630 quick_cnt,
2631 thumb_config,
2632 &mut ctx.globals.syscom.quick_save_slots,
2633 idx,
2634 );
2635 ctx.globals.syscom.last_menu_call = op;
2636 ctx.push(Value::Int(if ok { 1 } else { 0 }));
2637 return Ok(true);
2638 }
2639 CALL_CONFIG_MENU
2640 | CALL_CONFIG_WINDOW_MODE_MENU
2641 | CALL_CONFIG_VOLUME_MENU
2642 | CALL_CONFIG_BGMFADE_MENU
2643 | CALL_CONFIG_KOEMODE_MENU
2644 | CALL_CONFIG_CHARAKOE_MENU
2645 | CALL_CONFIG_JITAN_MENU
2646 | CALL_CONFIG_MESSAGE_SPEED_MENU
2647 | CALL_CONFIG_FILTER_COLOR_MENU
2648 | CALL_CONFIG_AUTO_MODE_MENU
2649 | CALL_CONFIG_FONT_MENU
2650 | CALL_CONFIG_SYSTEM_MENU
2651 | CALL_CONFIG_MOVIE_MENU => {
2652 set_syscom_pending_proc(ctx, SyscomPendingProcKind::OpenConfig);
2653 ctx.globals.syscom.last_menu_call = op;
2654 }
2655 SET_WINDOW_MODE => cfg_set_int(&mut ctx.globals.syscom, GET_WINDOW_MODE, p_i64(params, 0)),
2656 SET_WINDOW_MODE_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_WINDOW_MODE, 0),
2657 GET_WINDOW_MODE => {
2658 let v = cfg_get_int(&ctx.globals.syscom, GET_WINDOW_MODE, 0);
2659 ctx.push(Value::Int(v));
2660 return Ok(true);
2661 }
2662 SET_WINDOW_MODE_SIZE => cfg_set_int(
2663 &mut ctx.globals.syscom,
2664 GET_WINDOW_MODE_SIZE,
2665 p_i64(params, 0),
2666 ),
2667 SET_WINDOW_MODE_SIZE_DEFAULT => {
2668 cfg_set_int(&mut ctx.globals.syscom, GET_WINDOW_MODE_SIZE, 0)
2669 }
2670 GET_WINDOW_MODE_SIZE => {
2671 let v = cfg_get_int(&ctx.globals.syscom, GET_WINDOW_MODE_SIZE, 0);
2672 ctx.push(Value::Int(v));
2673 return Ok(true);
2674 }
2675 CHECK_WINDOW_MODE_SIZE_ENABLE => {
2676 ctx.push(Value::Int(1));
2677 return Ok(true);
2678 }
2679 SET_ALL_VOLUME => {
2680 cfg_set_int(&mut ctx.globals.syscom, GET_ALL_VOLUME, p_i64(params, 0));
2681 apply_audio_config(ctx);
2682 }
2683 SET_BGM_VOLUME => {
2684 cfg_set_int(&mut ctx.globals.syscom, GET_BGM_VOLUME, p_i64(params, 0));
2685 apply_audio_config(ctx);
2686 }
2687 SET_KOE_VOLUME => {
2688 cfg_set_int(&mut ctx.globals.syscom, GET_KOE_VOLUME, p_i64(params, 0));
2689 apply_audio_config(ctx);
2690 }
2691 SET_PCM_VOLUME => {
2692 cfg_set_int(&mut ctx.globals.syscom, GET_PCM_VOLUME, p_i64(params, 0));
2693 apply_audio_config(ctx);
2694 }
2695 SET_SE_VOLUME => {
2696 cfg_set_int(&mut ctx.globals.syscom, GET_SE_VOLUME, p_i64(params, 0));
2697 apply_audio_config(ctx);
2698 }
2699 SET_MOV_VOLUME => {
2700 cfg_set_int(&mut ctx.globals.syscom, GET_MOV_VOLUME, p_i64(params, 0));
2701 apply_audio_config(ctx);
2702 }
2703 SET_SOUND_VOLUME => {
2704 cfg_set_int(&mut ctx.globals.syscom, GET_SOUND_VOLUME, p_i64(params, 0));
2705 }
2706 SET_ALL_VOLUME_DEFAULT => {
2707 cfg_set_int(&mut ctx.globals.syscom, GET_ALL_VOLUME, 100);
2708 apply_audio_config(ctx);
2709 }
2710 SET_BGM_VOLUME_DEFAULT => {
2711 cfg_set_int(&mut ctx.globals.syscom, GET_BGM_VOLUME, 100);
2712 apply_audio_config(ctx);
2713 }
2714 SET_KOE_VOLUME_DEFAULT => {
2715 cfg_set_int(&mut ctx.globals.syscom, GET_KOE_VOLUME, 100);
2716 apply_audio_config(ctx);
2717 }
2718 SET_PCM_VOLUME_DEFAULT => {
2719 cfg_set_int(&mut ctx.globals.syscom, GET_PCM_VOLUME, 100);
2720 apply_audio_config(ctx);
2721 }
2722 SET_SE_VOLUME_DEFAULT => {
2723 cfg_set_int(&mut ctx.globals.syscom, GET_SE_VOLUME, 100);
2724 apply_audio_config(ctx);
2725 }
2726 SET_MOV_VOLUME_DEFAULT => {
2727 cfg_set_int(&mut ctx.globals.syscom, GET_MOV_VOLUME, 100);
2728 apply_audio_config(ctx);
2729 }
2730 SET_SOUND_VOLUME_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_SOUND_VOLUME, 100),
2731 GET_ALL_VOLUME | GET_BGM_VOLUME | GET_KOE_VOLUME | GET_PCM_VOLUME | GET_SE_VOLUME
2732 | GET_MOV_VOLUME | GET_SOUND_VOLUME => {
2733 let v = cfg_get_int(&ctx.globals.syscom, op, 100);
2734 ctx.push(Value::Int(v));
2735 return Ok(true);
2736 }
2737 SET_ALL_ONOFF => {
2738 cfg_set_int(
2739 &mut ctx.globals.syscom,
2740 GET_ALL_ONOFF,
2741 if p_bool(params, 0) { 1 } else { 0 },
2742 );
2743 apply_audio_config(ctx);
2744 }
2745 SET_BGM_ONOFF => {
2746 cfg_set_int(
2747 &mut ctx.globals.syscom,
2748 GET_BGM_ONOFF,
2749 if p_bool(params, 0) { 1 } else { 0 },
2750 );
2751 apply_audio_config(ctx);
2752 }
2753 SET_KOE_ONOFF => {
2754 cfg_set_int(
2755 &mut ctx.globals.syscom,
2756 GET_KOE_ONOFF,
2757 if p_bool(params, 0) { 1 } else { 0 },
2758 );
2759 apply_audio_config(ctx);
2760 }
2761 SET_PCM_ONOFF => {
2762 cfg_set_int(
2763 &mut ctx.globals.syscom,
2764 GET_PCM_ONOFF,
2765 if p_bool(params, 0) { 1 } else { 0 },
2766 );
2767 apply_audio_config(ctx);
2768 }
2769 SET_SE_ONOFF => {
2770 cfg_set_int(
2771 &mut ctx.globals.syscom,
2772 GET_SE_ONOFF,
2773 if p_bool(params, 0) { 1 } else { 0 },
2774 );
2775 apply_audio_config(ctx);
2776 }
2777 SET_MOV_ONOFF => {
2778 cfg_set_int(
2779 &mut ctx.globals.syscom,
2780 GET_MOV_ONOFF,
2781 if p_bool(params, 0) { 1 } else { 0 },
2782 );
2783 apply_audio_config(ctx);
2784 }
2785 SET_SOUND_ONOFF => {
2786 cfg_set_int(
2787 &mut ctx.globals.syscom,
2788 GET_SOUND_ONOFF,
2789 if p_bool(params, 0) { 1 } else { 0 },
2790 );
2791 }
2792 SET_ALL_ONOFF_DEFAULT => {
2793 cfg_set_int(&mut ctx.globals.syscom, GET_ALL_ONOFF, 1);
2794 apply_audio_config(ctx);
2795 }
2796 SET_BGM_ONOFF_DEFAULT => {
2797 cfg_set_int(&mut ctx.globals.syscom, GET_BGM_ONOFF, 1);
2798 apply_audio_config(ctx);
2799 }
2800 SET_KOE_ONOFF_DEFAULT => {
2801 cfg_set_int(&mut ctx.globals.syscom, GET_KOE_ONOFF, 1);
2802 apply_audio_config(ctx);
2803 }
2804 SET_PCM_ONOFF_DEFAULT => {
2805 cfg_set_int(&mut ctx.globals.syscom, GET_PCM_ONOFF, 1);
2806 apply_audio_config(ctx);
2807 }
2808 SET_SE_ONOFF_DEFAULT => {
2809 cfg_set_int(&mut ctx.globals.syscom, GET_SE_ONOFF, 1);
2810 apply_audio_config(ctx);
2811 }
2812 SET_MOV_ONOFF_DEFAULT => {
2813 cfg_set_int(&mut ctx.globals.syscom, GET_MOV_ONOFF, 1);
2814 apply_audio_config(ctx);
2815 }
2816 SET_SOUND_ONOFF_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_SOUND_ONOFF, 1),
2817 GET_ALL_ONOFF | GET_BGM_ONOFF | GET_KOE_ONOFF | GET_PCM_ONOFF | GET_SE_ONOFF
2818 | GET_MOV_ONOFF | GET_SOUND_ONOFF => {
2819 let v = cfg_get_int(&ctx.globals.syscom, op, 1);
2820 ctx.push(Value::Int(v));
2821 return Ok(true);
2822 }
2823 SET_BGMFADE_VOLUME => cfg_set_int(
2824 &mut ctx.globals.syscom,
2825 GET_BGMFADE_VOLUME,
2826 p_i64(params, 0),
2827 ),
2828 SET_BGMFADE_ONOFF => cfg_set_int(
2829 &mut ctx.globals.syscom,
2830 GET_BGMFADE_ONOFF,
2831 if p_bool(params, 0) { 1 } else { 0 },
2832 ),
2833 SET_BGMFADE_VOLUME_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_BGMFADE_VOLUME, 100),
2834 SET_BGMFADE_ONOFF_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_BGMFADE_ONOFF, 1),
2835 GET_BGMFADE_VOLUME | GET_BGMFADE_ONOFF => {
2836 let default = if op == GET_BGMFADE_ONOFF { 1 } else { 100 };
2837 let v = cfg_get_int(&ctx.globals.syscom, op, default);
2838 ctx.push(Value::Int(v));
2839 return Ok(true);
2840 }
2841 SET_KOEMODE => cfg_set_int(&mut ctx.globals.syscom, GET_KOEMODE, p_i64(params, 0)),
2842 SET_KOEMODE_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_KOEMODE, 0),
2843 GET_KOEMODE => {
2844 let v = cfg_get_int(&ctx.globals.syscom, GET_KOEMODE, 0);
2845 ctx.push(Value::Int(v));
2846 return Ok(true);
2847 }
2848 SET_CHARAKOE_ONOFF => cfg_set_int(
2849 &mut ctx.globals.syscom,
2850 GET_CHARAKOE_ONOFF,
2851 if p_bool(params, 0) { 1 } else { 0 },
2852 ),
2853 SET_CHARAKOE_ONOFF_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_CHARAKOE_ONOFF, 1),
2854 GET_CHARAKOE_ONOFF => {
2855 let v = cfg_get_int(&ctx.globals.syscom, GET_CHARAKOE_ONOFF, 1);
2856 ctx.push(Value::Int(v));
2857 return Ok(true);
2858 }
2859 SET_CHARAKOE_VOLUME => cfg_set_int(
2860 &mut ctx.globals.syscom,
2861 GET_CHARAKOE_VOLUME,
2862 p_i64(params, 0),
2863 ),
2864 SET_CHARAKOE_VOLUME_DEFAULT => {
2865 cfg_set_int(&mut ctx.globals.syscom, GET_CHARAKOE_VOLUME, 100)
2866 }
2867 GET_CHARAKOE_VOLUME => {
2868 let v = cfg_get_int(&ctx.globals.syscom, GET_CHARAKOE_VOLUME, 100);
2869 ctx.push(Value::Int(v));
2870 return Ok(true);
2871 }
2872 SET_JITAN_NORMAL_ONOFF => cfg_set_int(
2873 &mut ctx.globals.syscom,
2874 GET_JITAN_NORMAL_ONOFF,
2875 if p_bool(params, 0) { 1 } else { 0 },
2876 ),
2877 SET_JITAN_NORMAL_ONOFF_DEFAULT => {
2878 cfg_set_int(&mut ctx.globals.syscom, GET_JITAN_NORMAL_ONOFF, 0)
2879 }
2880 GET_JITAN_NORMAL_ONOFF => {
2881 let v = cfg_get_int(&ctx.globals.syscom, GET_JITAN_NORMAL_ONOFF, 0);
2882 ctx.push(Value::Int(v));
2883 return Ok(true);
2884 }
2885 SET_JITAN_AUTO_MODE_ONOFF => cfg_set_int(
2886 &mut ctx.globals.syscom,
2887 GET_JITAN_AUTO_MODE_ONOFF,
2888 if p_bool(params, 0) { 1 } else { 0 },
2889 ),
2890 SET_JITAN_AUTO_MODE_ONOFF_DEFAULT => {
2891 cfg_set_int(&mut ctx.globals.syscom, GET_JITAN_AUTO_MODE_ONOFF, 0)
2892 }
2893 GET_JITAN_AUTO_MODE_ONOFF => {
2894 let v = cfg_get_int(&ctx.globals.syscom, GET_JITAN_AUTO_MODE_ONOFF, 0);
2895 ctx.push(Value::Int(v));
2896 return Ok(true);
2897 }
2898 SET_JITAN_KOE_REPLAY_ONOFF => cfg_set_int(
2899 &mut ctx.globals.syscom,
2900 GET_JITAN_KOE_REPLAY_ONOFF,
2901 if p_bool(params, 0) { 1 } else { 0 },
2902 ),
2903 SET_JITAN_KOE_REPLAY_ONOFF_DEFAULT => {
2904 cfg_set_int(&mut ctx.globals.syscom, GET_JITAN_KOE_REPLAY_ONOFF, 0)
2905 }
2906 GET_JITAN_KOE_REPLAY_ONOFF => {
2907 let v = cfg_get_int(&ctx.globals.syscom, GET_JITAN_KOE_REPLAY_ONOFF, 0);
2908 ctx.push(Value::Int(v));
2909 return Ok(true);
2910 }
2911 SET_JITAN_SPEED => cfg_set_int(&mut ctx.globals.syscom, GET_JITAN_SPEED, p_i64(params, 0)),
2912 SET_JITAN_SPEED_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_JITAN_SPEED, 0),
2913 GET_JITAN_SPEED => {
2914 let v = cfg_get_int(&ctx.globals.syscom, GET_JITAN_SPEED, 0);
2915 ctx.push(Value::Int(v));
2916 return Ok(true);
2917 }
2918 SET_MESSAGE_SPEED => {
2919 cfg_set_int(&mut ctx.globals.syscom, GET_MESSAGE_SPEED, p_i64(params, 0))
2920 }
2921 SET_MESSAGE_SPEED_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_MESSAGE_SPEED, 20),
2922 GET_MESSAGE_SPEED => {
2923 let v = cfg_get_int(&ctx.globals.syscom, GET_MESSAGE_SPEED, 20);
2924 ctx.push(Value::Int(v));
2925 return Ok(true);
2926 }
2927 SET_MESSAGE_NOWAIT => {
2928 let v = p_bool(params, 0);
2929 ctx.globals.script.msg_nowait = v;
2930 cfg_set_int(
2931 &mut ctx.globals.syscom,
2932 GET_MESSAGE_NOWAIT,
2933 if v { 1 } else { 0 },
2934 );
2935 }
2936 SET_MESSAGE_NOWAIT_DEFAULT => {
2937 ctx.globals.script.msg_nowait = false;
2938 cfg_set_int(&mut ctx.globals.syscom, GET_MESSAGE_NOWAIT, 0);
2939 }
2940 GET_MESSAGE_NOWAIT => {
2941 let v = if ctx.globals.script.msg_nowait {
2942 1
2943 } else {
2944 cfg_get_int(&ctx.globals.syscom, GET_MESSAGE_NOWAIT, 0)
2945 };
2946 ctx.push(Value::Int(v));
2947 return Ok(true);
2948 }
2949 SET_AUTO_MODE_MOJI_WAIT => {
2950 let v = p_i64(params, 0);
2951 ctx.globals.script.auto_mode_moji_wait = v;
2952 cfg_set_int(&mut ctx.globals.syscom, GET_AUTO_MODE_MOJI_WAIT, v);
2953 }
2954 SET_AUTO_MODE_MOJI_WAIT_DEFAULT => {
2955 ctx.globals.script.auto_mode_moji_wait = -1;
2956 cfg_set_int(&mut ctx.globals.syscom, GET_AUTO_MODE_MOJI_WAIT, -1);
2957 }
2958 GET_AUTO_MODE_MOJI_WAIT => {
2959 let v = ctx.globals.script.auto_mode_moji_wait;
2960 ctx.push(Value::Int(v));
2961 return Ok(true);
2962 }
2963 SET_AUTO_MODE_MIN_WAIT => {
2964 let v = p_i64(params, 0);
2965 ctx.globals.script.auto_mode_min_wait = v;
2966 cfg_set_int(&mut ctx.globals.syscom, GET_AUTO_MODE_MIN_WAIT, v);
2967 }
2968 SET_AUTO_MODE_MIN_WAIT_DEFAULT => {
2969 ctx.globals.script.auto_mode_min_wait = -1;
2970 cfg_set_int(&mut ctx.globals.syscom, GET_AUTO_MODE_MIN_WAIT, -1);
2971 }
2972 GET_AUTO_MODE_MIN_WAIT => {
2973 let v = ctx.globals.script.auto_mode_min_wait;
2974 ctx.push(Value::Int(v));
2975 return Ok(true);
2976 }
2977 SET_MOUSE_CURSOR_HIDE_ONOFF => cfg_set_int(
2978 &mut ctx.globals.syscom,
2979 GET_MOUSE_CURSOR_HIDE_ONOFF,
2980 if p_bool(params, 0) { 1 } else { 0 },
2981 ),
2982 SET_MOUSE_CURSOR_HIDE_ONOFF_DEFAULT => {
2983 let v = config_mouse_cursor_hide_onoff_default(ctx);
2984 cfg_set_int(&mut ctx.globals.syscom, GET_MOUSE_CURSOR_HIDE_ONOFF, v)
2985 }
2986 GET_MOUSE_CURSOR_HIDE_ONOFF => {
2987 let v = cfg_get_int(
2988 &ctx.globals.syscom,
2989 GET_MOUSE_CURSOR_HIDE_ONOFF,
2990 config_mouse_cursor_hide_onoff_default(ctx),
2991 );
2992 ctx.push(Value::Int(v));
2993 return Ok(true);
2994 }
2995 SET_MOUSE_CURSOR_HIDE_TIME => cfg_set_int(
2996 &mut ctx.globals.syscom,
2997 GET_MOUSE_CURSOR_HIDE_TIME,
2998 p_i64(params, 0),
2999 ),
3000 SET_MOUSE_CURSOR_HIDE_TIME_DEFAULT => {
3001 let v = config_mouse_cursor_hide_time_default(ctx);
3002 cfg_set_int(&mut ctx.globals.syscom, GET_MOUSE_CURSOR_HIDE_TIME, v)
3003 }
3004 GET_MOUSE_CURSOR_HIDE_TIME => {
3005 let v = cfg_get_int(
3006 &ctx.globals.syscom,
3007 GET_MOUSE_CURSOR_HIDE_TIME,
3008 config_mouse_cursor_hide_time_default(ctx),
3009 );
3010 ctx.push(Value::Int(v));
3011 return Ok(true);
3012 }
3013 SET_FILTER_COLOR_R => cfg_set_int(
3014 &mut ctx.globals.syscom,
3015 GET_FILTER_COLOR_R,
3016 p_i64(params, 0),
3017 ),
3018 SET_FILTER_COLOR_G => cfg_set_int(
3019 &mut ctx.globals.syscom,
3020 GET_FILTER_COLOR_G,
3021 p_i64(params, 0),
3022 ),
3023 SET_FILTER_COLOR_B => cfg_set_int(
3024 &mut ctx.globals.syscom,
3025 GET_FILTER_COLOR_B,
3026 p_i64(params, 0),
3027 ),
3028 SET_FILTER_COLOR_A => cfg_set_int(
3029 &mut ctx.globals.syscom,
3030 GET_FILTER_COLOR_A,
3031 p_i64(params, 0),
3032 ),
3033 SET_FILTER_COLOR_R_DEFAULT => {
3034 let (r, _, _, _) = config_filter_color_default(ctx);
3035 cfg_set_int(&mut ctx.globals.syscom, GET_FILTER_COLOR_R, r)
3036 }
3037 SET_FILTER_COLOR_G_DEFAULT => {
3038 let (_, g, _, _) = config_filter_color_default(ctx);
3039 cfg_set_int(&mut ctx.globals.syscom, GET_FILTER_COLOR_G, g)
3040 }
3041 SET_FILTER_COLOR_B_DEFAULT => {
3042 let (_, _, b, _) = config_filter_color_default(ctx);
3043 cfg_set_int(&mut ctx.globals.syscom, GET_FILTER_COLOR_B, b)
3044 }
3045 SET_FILTER_COLOR_A_DEFAULT => {
3046 let (_, _, _, a) = config_filter_color_default(ctx);
3047 cfg_set_int(&mut ctx.globals.syscom, GET_FILTER_COLOR_A, a)
3048 }
3049 GET_FILTER_COLOR_R | GET_FILTER_COLOR_G | GET_FILTER_COLOR_B | GET_FILTER_COLOR_A => {
3050 let (r, g, b, a) = config_filter_color_default(ctx);
3051 let default = match op {
3052 GET_FILTER_COLOR_R => r,
3053 GET_FILTER_COLOR_G => g,
3054 GET_FILTER_COLOR_B => b,
3055 GET_FILTER_COLOR_A => a,
3056 _ => 0,
3057 };
3058 let v = cfg_get_int(&ctx.globals.syscom, op, default);
3059 ctx.push(Value::Int(v));
3060 return Ok(true);
3061 }
3062 SET_OBJECT_DISP_ONOFF => cfg_set_int(
3063 &mut ctx.globals.syscom,
3064 GET_OBJECT_DISP_ONOFF,
3065 if p_bool(params, 0) { 1 } else { 0 },
3066 ),
3067 SET_OBJECT_DISP_ONOFF_DEFAULT => {
3068 cfg_set_int(&mut ctx.globals.syscom, GET_OBJECT_DISP_ONOFF, 1)
3069 }
3070 GET_OBJECT_DISP_ONOFF => {
3071 let v = cfg_get_int(&ctx.globals.syscom, GET_OBJECT_DISP_ONOFF, 1);
3072 ctx.push(Value::Int(v));
3073 return Ok(true);
3074 }
3075 SET_GLOBAL_EXTRA_SWITCH_ONOFF => cfg_set_int(
3076 &mut ctx.globals.syscom,
3077 GET_GLOBAL_EXTRA_SWITCH_ONOFF,
3078 if p_bool(params, 0) { 1 } else { 0 },
3079 ),
3080 SET_GLOBAL_EXTRA_SWITCH_ONOFF_DEFAULT => {
3081 cfg_set_int(&mut ctx.globals.syscom, GET_GLOBAL_EXTRA_SWITCH_ONOFF, 0)
3082 }
3083 GET_GLOBAL_EXTRA_SWITCH_ONOFF => {
3084 let v = cfg_get_int(&ctx.globals.syscom, GET_GLOBAL_EXTRA_SWITCH_ONOFF, 0);
3085 ctx.push(Value::Int(v));
3086 return Ok(true);
3087 }
3088 SET_GLOBAL_EXTRA_MODE_VALUE => cfg_set_int(
3089 &mut ctx.globals.syscom,
3090 GET_GLOBAL_EXTRA_MODE_VALUE,
3091 p_i64(params, 0),
3092 ),
3093 SET_GLOBAL_EXTRA_MODE_VALUE_DEFAULT => {
3094 cfg_set_int(&mut ctx.globals.syscom, GET_GLOBAL_EXTRA_MODE_VALUE, 0)
3095 }
3096 GET_GLOBAL_EXTRA_MODE_VALUE => {
3097 let v = cfg_get_int(&ctx.globals.syscom, GET_GLOBAL_EXTRA_MODE_VALUE, 0);
3098 ctx.push(Value::Int(v));
3099 return Ok(true);
3100 }
3101 SET_SAVELOAD_ALERT_ONOFF => cfg_set_int(
3102 &mut ctx.globals.syscom,
3103 GET_SAVELOAD_ALERT_ONOFF,
3104 if p_bool(params, 0) { 1 } else { 0 },
3105 ),
3106 SET_SAVELOAD_ALERT_ONOFF_DEFAULT => {
3107 cfg_set_int(&mut ctx.globals.syscom, GET_SAVELOAD_ALERT_ONOFF, 1)
3108 }
3109 GET_SAVELOAD_ALERT_ONOFF => {
3110 let v = cfg_get_int(&ctx.globals.syscom, GET_SAVELOAD_ALERT_ONOFF, 1);
3111 ctx.push(Value::Int(v));
3112 return Ok(true);
3113 }
3114 SET_SLEEP_ONOFF => cfg_set_int(
3115 &mut ctx.globals.syscom,
3116 GET_SLEEP_ONOFF,
3117 if p_bool(params, 0) { 1 } else { 0 },
3118 ),
3119 SET_SLEEP_ONOFF_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_SLEEP_ONOFF, 1),
3120 GET_SLEEP_ONOFF => {
3121 let v = cfg_get_int(&ctx.globals.syscom, GET_SLEEP_ONOFF, 1);
3122 ctx.push(Value::Int(v));
3123 return Ok(true);
3124 }
3125 SET_NO_WIPE_ANIME_ONOFF => cfg_set_int(
3126 &mut ctx.globals.syscom,
3127 GET_NO_WIPE_ANIME_ONOFF,
3128 if p_bool(params, 0) { 1 } else { 0 },
3129 ),
3130 SET_NO_WIPE_ANIME_ONOFF_DEFAULT => {
3131 cfg_set_int(&mut ctx.globals.syscom, GET_NO_WIPE_ANIME_ONOFF, 0)
3132 }
3133 GET_NO_WIPE_ANIME_ONOFF => {
3134 let v = cfg_get_int(&ctx.globals.syscom, GET_NO_WIPE_ANIME_ONOFF, 0);
3135 ctx.push(Value::Int(v));
3136 return Ok(true);
3137 }
3138 SET_SKIP_WIPE_ANIME_ONOFF => cfg_set_int(
3139 &mut ctx.globals.syscom,
3140 GET_SKIP_WIPE_ANIME_ONOFF,
3141 if p_bool(params, 0) { 1 } else { 0 },
3142 ),
3143 SET_SKIP_WIPE_ANIME_ONOFF_DEFAULT => {
3144 cfg_set_int(&mut ctx.globals.syscom, GET_SKIP_WIPE_ANIME_ONOFF, 0)
3145 }
3146 GET_SKIP_WIPE_ANIME_ONOFF => {
3147 let v = cfg_get_int(&ctx.globals.syscom, GET_SKIP_WIPE_ANIME_ONOFF, 0);
3148 ctx.push(Value::Int(v));
3149 return Ok(true);
3150 }
3151 SET_NO_MWND_ANIME_ONOFF => cfg_set_int(
3152 &mut ctx.globals.syscom,
3153 GET_NO_MWND_ANIME_ONOFF,
3154 if p_bool(params, 0) { 1 } else { 0 },
3155 ),
3156 SET_NO_MWND_ANIME_ONOFF_DEFAULT => {
3157 cfg_set_int(&mut ctx.globals.syscom, GET_NO_MWND_ANIME_ONOFF, 0)
3158 }
3159 GET_NO_MWND_ANIME_ONOFF => {
3160 let v = cfg_get_int(&ctx.globals.syscom, GET_NO_MWND_ANIME_ONOFF, 0);
3161 ctx.push(Value::Int(v));
3162 return Ok(true);
3163 }
3164 SET_WHEEL_NEXT_MESSAGE_ONOFF => cfg_set_int(
3165 &mut ctx.globals.syscom,
3166 GET_WHEEL_NEXT_MESSAGE_ONOFF,
3167 if p_bool(params, 0) { 1 } else { 0 },
3168 ),
3169 SET_WHEEL_NEXT_MESSAGE_ONOFF_DEFAULT => {
3170 cfg_set_int(&mut ctx.globals.syscom, GET_WHEEL_NEXT_MESSAGE_ONOFF, 1)
3171 }
3172 GET_WHEEL_NEXT_MESSAGE_ONOFF => {
3173 let v = cfg_get_int(&ctx.globals.syscom, GET_WHEEL_NEXT_MESSAGE_ONOFF, 1);
3174 ctx.push(Value::Int(v));
3175 return Ok(true);
3176 }
3177 SET_KOE_DONT_STOP_ONOFF => cfg_set_int(
3178 &mut ctx.globals.syscom,
3179 GET_KOE_DONT_STOP_ONOFF,
3180 if p_bool(params, 0) { 1 } else { 0 },
3181 ),
3182 SET_KOE_DONT_STOP_ONOFF_DEFAULT => {
3183 cfg_set_int(&mut ctx.globals.syscom, GET_KOE_DONT_STOP_ONOFF, 0)
3184 }
3185 GET_KOE_DONT_STOP_ONOFF => {
3186 let v = cfg_get_int(&ctx.globals.syscom, GET_KOE_DONT_STOP_ONOFF, 0);
3187 ctx.push(Value::Int(v));
3188 return Ok(true);
3189 }
3190 SET_SKIP_UNREAD_MESSAGE_ONOFF => cfg_set_int(
3191 &mut ctx.globals.syscom,
3192 GET_SKIP_UNREAD_MESSAGE_ONOFF,
3193 if p_bool(params, 0) { 1 } else { 0 },
3194 ),
3195 SET_SKIP_UNREAD_MESSAGE_ONOFF_DEFAULT => {
3196 cfg_set_int(&mut ctx.globals.syscom, GET_SKIP_UNREAD_MESSAGE_ONOFF, 0)
3197 }
3198 GET_SKIP_UNREAD_MESSAGE_ONOFF => {
3199 let v = cfg_get_int(&ctx.globals.syscom, GET_SKIP_UNREAD_MESSAGE_ONOFF, 0);
3200 ctx.push(Value::Int(v));
3201 return Ok(true);
3202 }
3203 SET_PLAY_SILENT_SOUND_ONOFF => cfg_set_int(
3204 &mut ctx.globals.syscom,
3205 GET_PLAY_SILENT_SOUND_ONOFF,
3206 if p_bool(params, 0) { 1 } else { 0 },
3207 ),
3208 SET_PLAY_SILENT_SOUND_ONOFF_DEFAULT => {
3209 cfg_set_int(&mut ctx.globals.syscom, GET_PLAY_SILENT_SOUND_ONOFF, 0)
3210 }
3211 GET_PLAY_SILENT_SOUND_ONOFF => {
3212 let v = cfg_get_int(&ctx.globals.syscom, GET_PLAY_SILENT_SOUND_ONOFF, 0);
3213 ctx.push(Value::Int(v));
3214 return Ok(true);
3215 }
3216 SET_FONT_NAME => {
3217 let v = params
3218 .get(0)
3219 .and_then(|v| v.as_str())
3220 .unwrap_or("")
3221 .to_string();
3222 cfg_set_str(&mut ctx.globals.syscom, GET_FONT_NAME, v);
3223 }
3224 SET_FONT_NAME_DEFAULT => cfg_set_str(&mut ctx.globals.syscom, GET_FONT_NAME, String::new()),
3225 GET_FONT_NAME => {
3226 let v = cfg_get_str(&ctx.globals.syscom, GET_FONT_NAME);
3227 ctx.push(Value::Str(v));
3228 return Ok(true);
3229 }
3230 IS_FONT_EXIST => {
3231 let name = params.get(0).and_then(|v| v.as_str()).unwrap_or("");
3232 let exists = font_exists(&ctx.project_dir, name);
3233 ctx.push(Value::Int(if exists { 1 } else { 0 }));
3234 return Ok(true);
3235 }
3236 SET_FONT_BOLD => cfg_set_int(
3237 &mut ctx.globals.syscom,
3238 GET_FONT_BOLD,
3239 if p_bool(params, 0) { 1 } else { 0 },
3240 ),
3241 SET_FONT_BOLD_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_FONT_BOLD, 0),
3242 GET_FONT_BOLD => {
3243 let v = cfg_get_int(&ctx.globals.syscom, GET_FONT_BOLD, 0);
3244 ctx.push(Value::Int(v));
3245 return Ok(true);
3246 }
3247 SET_FONT_DECORATION => cfg_set_int(
3248 &mut ctx.globals.syscom,
3249 GET_FONT_DECORATION,
3250 p_i64(params, 0),
3251 ),
3252 SET_FONT_DECORATION_DEFAULT => cfg_set_int(&mut ctx.globals.syscom, GET_FONT_DECORATION, 0),
3253 GET_FONT_DECORATION => {
3254 let v = cfg_get_int(&ctx.globals.syscom, GET_FONT_DECORATION, 0);
3255 ctx.push(Value::Int(v));
3256 return Ok(true);
3257 }
3258 CREATE_CAPTURE_BUFFER => {
3259 let w = p_i64(params, 0).max(1) as u32;
3260 let h = p_i64(params, 1).max(1) as u32;
3261 ctx.globals.syscom.capture_size = Some((w, h));
3262 ctx.globals.syscom.capture_buffer = None;
3263 }
3264 DESTROY_CAPTURE_BUFFER => {
3265 ctx.globals.syscom.capture_buffer = None;
3266 ctx.globals.syscom.capture_size = None;
3267 }
3268 CAPTURE_TO_CAPTURE_BUFFER => {
3269 let mut img = ctx.capture_frame_rgba();
3270 if let Some((w, h)) = ctx.globals.syscom.capture_size {
3271 img = resize_rgba(&img, w, h);
3272 }
3273 ctx.globals.syscom.capture_buffer = Some(img);
3274 }
3275 SAVE_CAPTURE_BUFFER_TO_FILE => {
3276 let file_name = params.get(0).and_then(|v| v.as_str()).unwrap_or("");
3277 let extension = params.get(1).and_then(|v| v.as_str()).unwrap_or("");
3278 let mut name = file_name.to_string();
3279 if !extension.is_empty()
3280 && !name
3281 .to_ascii_lowercase()
3282 .ends_with(&format!(".{}", extension.to_ascii_lowercase()))
3283 {
3284 name.push('.');
3285 name.push_str(extension);
3286 }
3287 let path = join_game_path(&ctx.project_dir, &name);
3288 if ctx.globals.syscom.capture_buffer.is_none() {
3289 let mut img = ctx.capture_frame_rgba();
3290 if let Some((w, h)) = ctx.globals.syscom.capture_size {
3291 img = resize_rgba(&img, w, h);
3292 }
3293 ctx.globals.syscom.capture_buffer = Some(img);
3294 }
3295 if let Some(img) = ctx.globals.syscom.capture_buffer.as_ref() {
3296 write_rgba_png(&path, img);
3297 save_capture_flags_sidecar(ctx, &path, params);
3298 ctx.push(Value::Int(1));
3299 } else {
3300 ctx.push(Value::Int(0));
3301 }
3302 return Ok(true);
3303 }
3304 LOAD_FLAG_FROM_CAPTURE_FILE => {
3305 let file_name = params.get(0).and_then(|v| v.as_str()).unwrap_or("");
3306 let extension = params.get(1).and_then(|v| v.as_str()).unwrap_or("");
3307 let mut name = file_name.to_string();
3308 if !extension.is_empty()
3309 && !name
3310 .to_ascii_lowercase()
3311 .ends_with(&format!(".{}", extension.to_ascii_lowercase()))
3312 {
3313 name.push('.');
3314 name.push_str(extension);
3315 }
3316 let path = join_game_path(&ctx.project_dir, &name);
3317 let ok = load_capture_flags_sidecar(ctx, &path, params);
3318 ctx.push(Value::Int(if ok { 1 } else { 0 }));
3319 return Ok(true);
3320 }
3321 CAPTURE_AND_SAVE_BUFFER_TO_PNG => {
3322 let file_name = params.get(2).and_then(|v| v.as_str()).unwrap_or("");
3323 let path = join_game_path(&ctx.project_dir, file_name);
3324 let mut img = ctx.capture_frame_rgba();
3325 if let Some((w, h)) = ctx.globals.syscom.capture_size {
3326 img = resize_rgba(&img, w, h);
3327 }
3328 write_rgba_png(&path, &img);
3329 }
3330 OPEN_TWEET_DIALOG => {
3331 log::error!("SYSCOM.OPEN_TWEET_DIALOG is not implemented in this port");
3332 }
3333 SET_RETURN_SCENE_ONCE => {
3334 let name = params
3335 .get(0)
3336 .and_then(|v| v.as_str())
3337 .unwrap_or("")
3338 .to_string();
3339 let z_no = p_i64(params, 1);
3340 ctx.globals.syscom.return_scene_once = Some((name, z_no));
3341 }
3342 GET_SYSTEM_EXTRA_INT_VALUE => {
3343 let v = ctx.globals.syscom.system_extra_int_value;
3344 ctx.push(Value::Int(v));
3345 return Ok(true);
3346 }
3347 GET_SYSTEM_EXTRA_STR_VALUE => {
3348 let v = ctx.globals.syscom.system_extra_str_value.clone();
3349 ctx.push(Value::Str(v));
3350 return Ok(true);
3351 }
3352 _ => {
3353 return Ok(false);
3354 }
3355 }
3356
3357 ctx.push(Value::Int(0));
3358 Ok(true)
3359}