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