1use anyhow::{Context, Result, bail};
4use encoding_rs::SHIFT_JIS;
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::sync::{Arc, Mutex, OnceLock, Weak};
8
9#[derive(Debug, Clone, Default)]
10pub struct GanPat {
11 pub pat_no: i32,
12 pub x: i32,
13 pub y: i32,
14 pub z: i32,
15 pub tr: u8,
16 pub wait: i32,
17 pub keika_time: i32,
18}
19
20#[derive(Debug, Clone, Default)]
21pub struct GanSet {
22 pub pat_list: Vec<GanPat>,
23 pub total_time: i32,
24}
25
26#[derive(Debug, Clone, Default)]
27pub struct GanData {
28 pub g00_file_name: String,
29 pub set_list: Vec<GanSet>,
30}
31
32impl GanData {
33 pub fn load(path: &Path) -> Result<Self> {
34 let buf = crate::resource::read_file_bytes(path)
35 .with_context(|| format!("read gan: {:?}", path))?;
36 if buf.len() < 8 {
37 bail!("gan too short: {:?}", path);
38 }
39 let mut data = GanData::default();
40 data.analize(&buf)?;
41 Ok(data)
42 }
43
44 fn analize(&mut self, buf: &[u8]) -> Result<()> {
45 let mut sp = 0usize;
46 let code = read_i32(buf, &mut sp)?;
47 if code != 10000 {
48 bail!("gan bad version code: {code}");
49 }
50 let version = read_i32(buf, &mut sp)?;
51 if version != 10000 {
52 bail!("gan unsupported version: {version}");
53 }
54
55 loop {
56 let code = read_i32(buf, &mut sp)?;
57 if code == 10100 {
58 let len = read_i32(buf, &mut sp)? as usize;
59 let s = read_bytes(buf, &mut sp, len)?;
60 self.g00_file_name = decode_sjis(s);
61 continue;
62 }
63 if code == 20000 {
64 let set_cnt = read_i32(buf, &mut sp)?;
65 if set_cnt > 0 {
66 for _ in 0..set_cnt {
67 let mut set = GanSet::default();
68 let mut keika_time = 0i32;
69 analize_set(buf, &mut sp, &mut set, &mut keika_time)?;
70 self.set_list.push(set);
71 }
72 }
73 return Ok(());
74 }
75
76 bail!("gan unexpected code: {code}");
77 }
78 }
79}
80
81fn analize_set(buf: &[u8], sp: &mut usize, set: &mut GanSet, keika_time: &mut i32) -> Result<()> {
82 let code = read_i32(buf, sp)?;
83 if code != 30000 {
84 bail!("gan set missing PAT_COUNT: {code}");
85 }
86 let pat_cnt = read_i32(buf, sp)?;
87 for _ in 0..pat_cnt {
88 let pat = analize_pat(buf, sp, keika_time)?;
89 set.pat_list.push(pat);
90 }
91 set.total_time = *keika_time;
92 Ok(())
93}
94
95fn analize_pat(buf: &[u8], sp: &mut usize, keika_time: &mut i32) -> Result<GanPat> {
96 let mut pat = GanPat {
97 tr: 255,
98 ..Default::default()
99 };
100 loop {
101 let code = read_i32(buf, sp)?;
102 if code == 999999 {
103 break;
104 }
105 match code {
106 30100 => pat.pat_no = read_i32(buf, sp)?,
107 30101 => pat.x = read_i32(buf, sp)?,
108 30102 => pat.y = read_i32(buf, sp)?,
109 30103 => pat.wait = read_i32(buf, sp)?,
110 30104 => {
111 let v = read_i32(buf, sp)?;
112 pat.tr = v.clamp(0, 255) as u8;
113 }
114 30105 => pat.z = read_i32(buf, sp)?,
115 _ => bail!("gan unexpected pat code: {code}"),
116 }
117 }
118 *keika_time += pat.wait;
119 pat.keika_time = *keika_time;
120 Ok(pat)
121}
122
123fn decode_sjis(bytes: &[u8]) -> String {
124 let (cow, _, had_err) = SHIFT_JIS.decode(bytes);
125 if had_err {
126 String::from_utf8_lossy(bytes).into_owned()
127 } else {
128 cow.into_owned()
129 }
130}
131
132fn read_i32(buf: &[u8], sp: &mut usize) -> Result<i32> {
133 let bytes = read_bytes(buf, sp, 4)?;
134 Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
135}
136
137fn read_bytes<'a>(buf: &'a [u8], sp: &mut usize, len: usize) -> Result<&'a [u8]> {
138 if *sp + len > buf.len() {
139 bail!("gan out of range");
140 }
141 let out = &buf[*sp..*sp + len];
142 *sp += len;
143 Ok(out)
144}
145
146#[derive(Debug, Default, Clone)]
147pub struct GanState {
148 data: Option<Arc<GanData>>,
149 current_pat: Option<GanPat>,
150
151 gan_name: String,
152 now_time: i32,
153 anm_set_no: i32,
154 next_anm_set_no: i32,
155
156 anm_start: bool,
157 anm_pause: bool,
158 anm_loop_flag: bool,
159 anm_real_time_flag: bool,
160 next_anm_flag: bool,
161 next_anm_loop_flag: bool,
162 next_anm_real_time_flag: bool,
163}
164
165impl GanState {
166 pub(crate) fn read_short_work(
167 &mut self,
168 reader: &mut crate::original_save::OriginalStreamReader<'_>,
169 ) -> Result<()> {
170 self.now_time = reader.i32()?;
171 self.anm_start = reader.bool()?;
172 self.anm_pause = reader.bool()?;
173 self.anm_set_no = reader.i32()?;
174 self.anm_loop_flag = reader.bool()?;
175 self.anm_real_time_flag = reader.bool()?;
176 Ok(())
177 }
178
179 pub(crate) fn read_original_work(
180 &mut self,
181 reader: &mut crate::original_save::OriginalStreamReader<'_>,
182 ) -> Result<()> {
183 self.now_time = reader.i32()?;
184 self.anm_set_no = reader.i32()?;
185 self.next_anm_set_no = reader.i32()?;
186 self.anm_start = reader.bool()?;
187 self.anm_pause = reader.bool()?;
188 self.anm_loop_flag = reader.bool()?;
189 self.anm_real_time_flag = reader.bool()?;
190 self.next_anm_flag = reader.bool()?;
191 self.next_anm_loop_flag = reader.bool()?;
192 self.next_anm_real_time_flag = reader.bool()?;
193 Ok(())
194 }
195
196 pub fn reset(&mut self) {
197 *self = GanState::default();
198 }
199
200 pub fn current_pat(&self) -> Option<&GanPat> {
201 self.current_pat.as_ref()
202 }
203
204 pub fn is_active(&self) -> bool {
205 self.data.is_some() && self.anm_start && !self.anm_pause
206 }
207
208 pub fn load_gan(&mut self, project_dir: &Path, append_dir: &str, name: &str) -> Result<()> {
209 self.reset();
210 self.load_gan_only(project_dir, append_dir, name)
211 }
212
213 pub fn load_gan_only(
214 &mut self,
215 project_dir: &Path,
216 append_dir: &str,
217 name: &str,
218 ) -> Result<()> {
219 if name.trim().is_empty() {
220 return Ok(());
221 }
222 self.gan_name = name.to_string();
223 let path = resolve_gan_path(project_dir, append_dir, name)
224 .with_context(|| format!("resolve gan path: {name}"))?;
225 self.data = Some(load_cached_gan_data(&path)?);
226 Ok(())
227 }
228
229 pub fn start_anm(&mut self, set_no: i32, loop_flag: bool, real_time_flag: bool) {
230 self.now_time = 0;
231 self.anm_start = true;
232 self.anm_pause = false;
233 self.anm_set_no = set_no;
234 self.anm_loop_flag = loop_flag;
235 self.anm_real_time_flag = real_time_flag;
236 self.next_anm_flag = false;
237 }
238
239 pub fn next_anm(&mut self, set_no: i32, loop_flag: bool, real_time_flag: bool) {
240 if self.anm_start {
241 if self.next_anm_flag {
242 self.start_anm(self.next_anm_set_no, false, self.next_anm_real_time_flag);
243 } else {
244 self.anm_loop_flag = false;
245 }
246 self.next_anm_flag = true;
247 self.next_anm_set_no = set_no;
248 self.next_anm_loop_flag = loop_flag;
249 self.next_anm_real_time_flag = real_time_flag;
250 } else {
251 self.start_anm(set_no, loop_flag, real_time_flag);
252 }
253 }
254
255 pub fn pause_anm(&mut self) {
256 self.anm_pause = true;
257 }
258
259 pub fn resume_anm(&mut self) {
260 self.anm_pause = false;
261 }
262
263 pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
264 let mut game = past_game_time.max(0);
265 let mut real = past_real_time.max(0);
266
267 let Some(data) = self.data.as_ref() else {
268 self.current_pat = None;
269 return;
270 };
271 if self.anm_set_no < 0 || self.anm_set_no as usize >= data.set_list.len() {
272 self.current_pat = None;
273 return;
274 }
275 let set = &data.set_list[self.anm_set_no as usize];
276 if set.pat_list.is_empty() {
277 self.current_pat = None;
278 return;
279 }
280 let total_time = set.total_time;
281 let first_pat = set.pat_list[0].clone();
282 let last_pat = set.pat_list[set.pat_list.len() - 1].clone();
283 if !self.anm_start || total_time <= 0 {
284 self.current_pat = Some(first_pat);
285 return;
286 }
287
288 if !self.anm_pause {
289 if self.anm_real_time_flag {
290 self.now_time += real;
291 } else {
292 self.now_time += game;
293 }
294 }
295
296 if !self.anm_loop_flag && self.now_time >= total_time {
297 if self.next_anm_flag {
298 self.start_anm(
299 self.next_anm_set_no,
300 self.next_anm_loop_flag,
301 self.next_anm_real_time_flag,
302 );
303 let overshoot = self.now_time - total_time;
304 game -= overshoot;
305 real -= overshoot;
306 self.update_time(game, real);
307 } else {
308 self.now_time = total_time;
309 self.current_pat = Some(last_pat);
310 }
311 return;
312 }
313
314 if set.total_time > 0 {
315 self.now_time %= set.total_time;
316 }
317
318 for pat in &set.pat_list {
319 if pat.keika_time >= self.now_time {
320 self.current_pat = Some(pat.clone());
321 break;
322 }
323 }
324 }
325}
326
327fn load_cached_gan_data(path: &Path) -> Result<Arc<GanData>> {
328 static CACHE: OnceLock<Mutex<HashMap<PathBuf, Weak<GanData>>>> = OnceLock::new();
338 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
339
340 if let Some(data) = cache
341 .lock()
342 .unwrap_or_else(|poisoned| poisoned.into_inner())
343 .get(path)
344 .and_then(Weak::upgrade)
345 {
346 return Ok(data);
347 }
348
349 let data = Arc::new(GanData::load(path)?);
350 cache
351 .lock()
352 .unwrap_or_else(|poisoned| poisoned.into_inner())
353 .insert(path.to_path_buf(), Arc::downgrade(&data));
354 Ok(data)
355}
356
357fn resolve_gan_path(project_dir: &Path, append_dir: &str, name: &str) -> Result<PathBuf> {
358 let mut candidates: Vec<PathBuf> = Vec::new();
359 let mut norm = name.replace('\\', "/");
360 if !norm.ends_with(".gan") && !norm.contains('.') {
361 norm.push_str(".gan");
362 }
363
364 let path = PathBuf::from(&norm);
365 if path.is_absolute()
366 && let Some(path) = crate::resource::resolve_game_file(&path)?
367 {
368 return Ok(path);
369 }
370
371 if !append_dir.is_empty() {
372 candidates.push(project_dir.join(append_dir).join("gan").join(&norm));
373 candidates.push(project_dir.join(append_dir).join(&norm));
374 }
375 candidates.push(project_dir.join("gan").join(&norm));
376 candidates.push(project_dir.join(&norm));
377 candidates.push(project_dir.join("dat").join(&norm));
378
379 for cand in candidates {
380 if let Some(path) = crate::resource::resolve_game_file(&cand)? {
381 return Ok(path);
382 }
383 }
384
385 bail!("gan file not found: {name}")
386}