siglus_scene_vm/runtime/forms/
system.rs1use anyhow::Result;
2use std::fs::{self, OpenOptions};
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6use crate::runtime::{CommandContext, Value};
7
8use super::prop_access;
9use super::syscom;
10
11const CHECK_ACTIVE: i32 = 0;
12const SHELL_OPEN_FILE: i32 = 1;
13const CHECK_DUMMY_FILE_ONCE: i32 = 2;
14const OPEN_DIALOG_FOR_CHIHAYA_BENCH: i32 = 3;
15const GET_SPEC_INFO_FOR_CHIHAYA_BENCH: i32 = 4;
16const SHELL_OPEN_WEB: i32 = 5;
17const CHECK_FILE_EXIST: i32 = 6;
18const DEBUG_MESSAGEBOX_OK: i32 = 7;
19const DEBUG_MESSAGEBOX_OKCANCEL: i32 = 8;
20const DEBUG_MESSAGEBOX_YESNO: i32 = 9;
21const DEBUG_MESSAGEBOX_YESNOCANCEL: i32 = 10;
22const DEBUG_WRITE_LOG: i32 = 11;
23const CHECK_FILE_EXIST_SAVE_DIR: i32 = 12;
24const CHECK_DEBUG_FLAG: i32 = 13;
25const GET_CALENDAR: i32 = 14;
26const GET_UNIX_TIME: i32 = 15;
27const GET_LANGUAGE: i32 = 16;
28const MESSAGEBOX_OK: i32 = 17;
29const MESSAGEBOX_OKCANCEL: i32 = 18;
30const MESSAGEBOX_YESNO: i32 = 19;
31const MESSAGEBOX_YESNOCANCEL: i32 = 20;
32const CLEAR_DUMMY_FILE: i32 = 21;
33
34struct Call<'a> {
35 op: i32,
36 params: &'a [Value],
37}
38
39fn parse_call<'a>(ctx: &CommandContext, form_id: u32, args: &'a [Value]) -> Option<Call<'a>> {
40 let (chain_pos, chain) = prop_access::parse_element_chain_ctx(ctx, form_id, args)?;
41 if chain.len() < 2 {
42 return None;
43 }
44 let params = prop_access::script_args(args, chain_pos);
45 Some(Call {
46 op: chain[1],
47 params,
48 })
49}
50
51fn p_str(params: &[Value], idx: usize) -> &str {
52 params.get(idx).and_then(|v| v.as_str()).unwrap_or("")
53}
54
55fn join_game_path(base: &Path, raw: &str) -> PathBuf {
56 if raw.is_empty() {
57 return base.to_path_buf();
58 }
59 let norm = raw.replace('\\', "/");
60 let p = Path::new(&norm);
61 if p.is_absolute() {
62 p.to_path_buf()
63 } else {
64 base.join(p)
65 }
66}
67
68pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
69 let Some(call) = parse_call(ctx, form_id, args) else {
70 return Ok(false);
71 };
72
73 match call.op {
74 GET_CALENDAR => {
75 let tm = local_time_fields();
76 let vals = [tm.0, tm.1, tm.2, tm.3, tm.4, tm.5, tm.6, tm.7];
77 for (idx, value) in vals.iter().enumerate() {
78 if let Some(Value::Element(chain)) = call.params.get(idx) {
79 prop_access::assign_to_chain(ctx, chain, Value::Int(*value));
80 }
81 }
82 return Ok(true);
83 }
84 GET_UNIX_TIME => {
85 let t = crate::platform_time::unix_time_secs() as i64;
86 ctx.push(Value::Int(t));
87 return Ok(true);
88 }
89 CHECK_ACTIVE => {
90 ctx.push(Value::Int(if ctx.globals.system.active_flag {
91 1
92 } else {
93 0
94 }));
95 return Ok(true);
96 }
97 CHECK_DEBUG_FLAG => {
98 ctx.push(Value::Int(if ctx.globals.system.debug_flag {
99 1
100 } else {
101 0
102 }));
103 return Ok(true);
104 }
105 SHELL_OPEN_FILE => {
106 let path = join_game_path(&ctx.project_dir, p_str(call.params, 0));
107 if path.exists() {
108 let _ = ctx.net.open_file(&path);
109 }
110 ctx.globals
111 .system
112 .debug_logs
113 .push(format!("shell_open_file:{}", path.display()));
114 return Ok(true);
115 }
116 SHELL_OPEN_WEB => {
117 let url = p_str(call.params, 0);
118 let _ = ctx.net.open_url(url);
119 ctx.globals
120 .system
121 .debug_logs
122 .push(format!("shell_open_web:{url}"));
123 return Ok(true);
124 }
125 CHECK_FILE_EXIST => {
126 let path = join_game_path(&ctx.project_dir, p_str(call.params, 0));
127 ctx.push(Value::Int(if path.exists() { 1 } else { 0 }));
128 return Ok(true);
129 }
130 CHECK_FILE_EXIST_SAVE_DIR => {
131 let save_dir = syscom::save_dir(&ctx.project_dir);
132 let path = join_game_path(&save_dir, p_str(call.params, 0));
133 ctx.push(Value::Int(if path.exists() { 1 } else { 0 }));
134 return Ok(true);
135 }
136 CHECK_DUMMY_FILE_ONCE => {
137 let name = p_str(call.params, 0);
138 let key = call.params.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
139 let code = p_str(call.params, 2);
140 let sig = format!("{name}:{key}:{code}");
141 ctx.globals.system.dummy_checks.insert(sig);
142 return Ok(true);
143 }
144 CLEAR_DUMMY_FILE => {
145 ctx.globals.system.dummy_checks.clear();
146 return Ok(true);
147 }
148 MESSAGEBOX_OK | MESSAGEBOX_OKCANCEL | MESSAGEBOX_YESNO | MESSAGEBOX_YESNOCANCEL => {
149 let text = messagebox_text(ctx, call.params);
150 if let Some(ret) = handle_messagebox(ctx, call.op, false, text) {
151 ctx.push(Value::Int(ret));
152 }
153 return Ok(true);
154 }
155 DEBUG_MESSAGEBOX_OK
156 | DEBUG_MESSAGEBOX_OKCANCEL
157 | DEBUG_MESSAGEBOX_YESNO
158 | DEBUG_MESSAGEBOX_YESNOCANCEL => {
159 let text = messagebox_text(ctx, call.params);
160 if ctx.globals.system.debug_flag {
161 if let Some(ret) = handle_messagebox(ctx, call.op, true, text) {
162 ctx.push(Value::Int(ret));
163 }
164 } else {
165 ctx.push(Value::Int(0));
166 }
167 return Ok(true);
168 }
169 DEBUG_WRITE_LOG => {
170 if ctx.globals.system.debug_flag {
171 let s = match call.params.get(0) {
172 Some(Value::Int(v)) => v.to_string(),
173 Some(Value::Str(s)) => s.clone(),
174 _ => String::new(),
175 };
176 write_debug_log(
177 &ctx.project_dir,
178 &s,
179 ctx.current_scene_name.as_deref(),
180 ctx.current_line_no,
181 );
182 ctx.globals.system.debug_logs.push(s);
183 }
184 return Ok(true);
185 }
186 GET_SPEC_INFO_FOR_CHIHAYA_BENCH => {
187 ctx.push(Value::Str(ctx.globals.system.spec_info.clone()));
188 return Ok(true);
189 }
190 OPEN_DIALOG_FOR_CHIHAYA_BENCH => {
191 ctx.globals
192 .system
193 .bench_dialogs
194 .push(p_str(call.params, 0).to_string());
195 return Ok(true);
196 }
197 GET_LANGUAGE => {
198 ctx.push(Value::Str(ctx.globals.system.language_code.clone()));
199 return Ok(true);
200 }
201 _ => {}
202 }
203
204 Ok(false)
205}
206
207fn messagebox_text(ctx: &CommandContext, params: &[Value]) -> String {
208 match params.first() {
209 Some(Value::Int(v)) => v.to_string(),
210 Some(Value::Str(s)) => s.clone(),
211 Some(v) => v.as_str().unwrap_or("").to_string(),
212 None => {
213 if let Some(name) = ctx.current_scene_name.as_deref() {
214 format!("{name}:{}", ctx.current_line_no)
215 } else {
216 String::new()
217 }
218 }
219 }
220}
221
222fn handle_messagebox(
223 ctx: &mut CommandContext,
224 kind: i32,
225 debug_only: bool,
226 text: String,
227) -> Option<i64> {
228 ctx.globals
229 .system
230 .messagebox_history
231 .push(crate::runtime::globals::SystemMessageBoxRecord {
232 kind,
233 text: text.clone(),
234 debug_only,
235 });
236
237 let buttons = messagebox_buttons(kind);
238 let max_value = buttons.iter().map(|b| b.value).max().unwrap_or(0);
239 if !ctx.globals.system.messagebox_response_queue.is_empty() {
240 let v = ctx.globals.system.messagebox_response_queue.remove(0);
241 return Some(v.clamp(0, max_value));
242 }
243
244 ctx.request_system_messagebox(kind, debug_only, text, buttons);
245 None
246}
247
248fn messagebox_buttons(kind: i32) -> Vec<crate::runtime::globals::SystemMessageBoxButton> {
249 let raw: &[(&str, i64)] = match kind {
250 MESSAGEBOX_OK | DEBUG_MESSAGEBOX_OK => &[("OK", 0)],
251 MESSAGEBOX_OKCANCEL | DEBUG_MESSAGEBOX_OKCANCEL => &[("OK", 0), ("CANCEL", 1)],
252 MESSAGEBOX_YESNO | DEBUG_MESSAGEBOX_YESNO => &[("YES", 0), ("NO", 1)],
253 MESSAGEBOX_YESNOCANCEL | DEBUG_MESSAGEBOX_YESNOCANCEL => {
254 &[("YES", 0), ("NO", 1), ("CANCEL", 2)]
255 }
256 _ => &[("OK", 0)],
257 };
258 raw.iter()
259 .map(
260 |(label, value)| crate::runtime::globals::SystemMessageBoxButton {
261 label: (*label).to_string(),
262 value: *value,
263 },
264 )
265 .collect()
266}
267
268fn local_time_fields() -> (i64, i64, i64, i64, i64, i64, i64, i64) {
269 let now = crate::platform_time::local_time_fields();
270 (
271 now.year as i64,
272 now.month as i64,
273 now.day as i64,
274 now.weekday_sunday0 as i64,
275 now.hour as i64,
276 now.minute as i64,
277 now.second as i64,
278 now.millisecond as i64,
279 )
280}
281
282fn write_debug_log(project_dir: &Path, msg: &str, scene_name: Option<&str>, line_no: i64) {
283 if msg.is_empty() {
284 return;
285 }
286 let dir = project_dir.join("__DEBUG_LOG");
287 let _ = fs::create_dir_all(&dir);
288 let path = dir.join("debug_log.txt");
289 let stamp = crate::platform_time::local_log_timestamp();
290 if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(&path) {
291 if let Some(scene) = scene_name {
292 let _ = writeln!(f, "{}\t({}.ss line={})\t{}", stamp, scene, line_no, msg);
293 } else {
294 let _ = writeln!(f, "{}\t{}", stamp, msg);
295 }
296 }
297}