Skip to main content

siglus_scene_vm/
original_save.rs

1use anyhow::{anyhow, bail, Context, Result};
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use crate::runtime::globals::SaveSlotState;
6
7pub const SAVE_APPEND_DIR_MAX_LEN: usize = 256;
8pub const SAVE_APPEND_NAME_MAX_LEN: usize = 256;
9pub const SAVE_TITLE_MAX_LEN: usize = 256;
10pub const SAVE_MESSAGE_MAX_LEN: usize = 256;
11pub const SAVE_FULL_MESSAGE_MAX_LEN: usize = 256;
12pub const SAVE_COMMENT_MAX_LEN: usize = 256;
13pub const SAVE_COMMENT2_MAX_LEN: usize = 256;
14pub const SAVE_FLAG_MAX_CNT: usize = 256;
15
16const SAVE_FIXED_STRING_CNT: usize = 7;
17pub const SAVE_HEADER_SIZE: usize = 10 * 4 + SAVE_FIXED_STRING_CNT * 256 * 2 + SAVE_FLAG_MAX_CNT * 4 + 4;
18pub const GLOBAL_SAVE_HEADER_SIZE: usize = 12;
19pub const CONFIG_SAVE_HEADER_SIZE: usize = 12;
20pub const READ_SAVE_HEADER_SIZE: usize = 16;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum SaveKind {
24    Normal,
25    Quick,
26    End,
27}
28
29#[derive(Debug, Clone)]
30pub struct OriginalSaveHeader {
31    pub major_version: i32,
32    pub minor_version: i32,
33    pub year: i32,
34    pub month: i32,
35    pub day: i32,
36    pub weekday: i32,
37    pub hour: i32,
38    pub minute: i32,
39    pub second: i32,
40    pub millisecond: i32,
41    pub append_dir: String,
42    pub append_name: String,
43    pub title: String,
44    pub message: String,
45    pub full_message: String,
46    pub comment: String,
47    pub comment2: String,
48    pub flag: [i32; SAVE_FLAG_MAX_CNT],
49    pub data_size: i32,
50}
51
52impl Default for OriginalSaveHeader {
53    fn default() -> Self {
54        Self {
55            major_version: 1,
56            minor_version: 0,
57            year: 0,
58            month: 0,
59            day: 0,
60            weekday: 0,
61            hour: 0,
62            minute: 0,
63            second: 0,
64            millisecond: 0,
65            append_dir: String::new(),
66            append_name: String::new(),
67            title: String::new(),
68            message: String::new(),
69            full_message: String::new(),
70            comment: String::new(),
71            comment2: String::new(),
72            flag: [0; SAVE_FLAG_MAX_CNT],
73            data_size: 0,
74        }
75    }
76}
77
78impl OriginalSaveHeader {
79    pub fn from_slot(slot: &SaveSlotState, packed_size: usize) -> Self {
80        let mut flag = [0i32; SAVE_FLAG_MAX_CNT];
81        for (idx, dst) in flag.iter_mut().enumerate() {
82            *dst = slot.values.get(&(idx as i32)).copied().unwrap_or(0) as i32;
83        }
84        Self {
85            major_version: 1,
86            minor_version: 0,
87            year: slot.year as i32,
88            month: slot.month as i32,
89            day: slot.day as i32,
90            weekday: slot.weekday as i32,
91            hour: slot.hour as i32,
92            minute: slot.minute as i32,
93            second: slot.second as i32,
94            millisecond: slot.millisecond as i32,
95            append_dir: slot.append_dir.clone(),
96            append_name: slot.append_name.clone(),
97            title: slot.title.clone(),
98            message: slot.message.clone(),
99            full_message: slot.full_message.clone(),
100            comment: slot.comment.clone(),
101            comment2: String::new(),
102            flag,
103            data_size: packed_size as i32,
104        }
105    }
106
107    pub fn to_slot(&self) -> SaveSlotState {
108        let mut slot = SaveSlotState::default();
109        slot.exist = self.major_version == 1 && self.minor_version == 0;
110        slot.year = self.year as i64;
111        slot.month = self.month as i64;
112        slot.day = self.day as i64;
113        slot.weekday = self.weekday as i64;
114        slot.hour = self.hour as i64;
115        slot.minute = self.minute as i64;
116        slot.second = self.second as i64;
117        slot.millisecond = self.millisecond as i64;
118        slot.append_dir = self.append_dir.clone();
119        slot.append_name = self.append_name.clone();
120        slot.title = self.title.clone();
121        slot.message = self.message.clone();
122        slot.full_message = self.full_message.clone();
123        slot.comment = self.comment.clone();
124        for (idx, value) in self.flag.iter().enumerate() {
125            if *value != 0 {
126                slot.values.insert(idx as i32, *value as i64);
127            }
128        }
129        slot
130    }
131
132    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
133        if bytes.len() < SAVE_HEADER_SIZE {
134            bail!("save header too short: {} < {}", bytes.len(), SAVE_HEADER_SIZE);
135        }
136        let mut rd = Reader::new(bytes);
137        let major_version = rd.i32()?;
138        let minor_version = rd.i32()?;
139        let year = rd.i32()?;
140        let month = rd.i32()?;
141        let day = rd.i32()?;
142        let weekday = rd.i32()?;
143        let hour = rd.i32()?;
144        let minute = rd.i32()?;
145        let second = rd.i32()?;
146        let millisecond = rd.i32()?;
147        let append_dir = rd.utf16_fixed(SAVE_APPEND_DIR_MAX_LEN)?;
148        let append_name = rd.utf16_fixed(SAVE_APPEND_NAME_MAX_LEN)?;
149        let title = rd.utf16_fixed(SAVE_TITLE_MAX_LEN)?;
150        let message = rd.utf16_fixed(SAVE_MESSAGE_MAX_LEN)?;
151        let full_message = rd.utf16_fixed(SAVE_FULL_MESSAGE_MAX_LEN)?;
152        let comment = rd.utf16_fixed(SAVE_COMMENT_MAX_LEN)?;
153        let comment2 = rd.utf16_fixed(SAVE_COMMENT2_MAX_LEN)?;
154        let mut flag = [0i32; SAVE_FLAG_MAX_CNT];
155        for dst in &mut flag {
156            *dst = rd.i32()?;
157        }
158        let data_size = rd.i32()?;
159        Ok(Self {
160            major_version,
161            minor_version,
162            year,
163            month,
164            day,
165            weekday,
166            hour,
167            minute,
168            second,
169            millisecond,
170            append_dir,
171            append_name,
172            title,
173            message,
174            full_message,
175            comment,
176            comment2,
177            flag,
178            data_size,
179        })
180    }
181
182    pub fn to_bytes(&self) -> Vec<u8> {
183        let mut out = Vec::with_capacity(SAVE_HEADER_SIZE);
184        push_i32(&mut out, self.major_version);
185        push_i32(&mut out, self.minor_version);
186        push_i32(&mut out, self.year);
187        push_i32(&mut out, self.month);
188        push_i32(&mut out, self.day);
189        push_i32(&mut out, self.weekday);
190        push_i32(&mut out, self.hour);
191        push_i32(&mut out, self.minute);
192        push_i32(&mut out, self.second);
193        push_i32(&mut out, self.millisecond);
194        push_utf16_fixed(&mut out, &self.append_dir, SAVE_APPEND_DIR_MAX_LEN);
195        push_utf16_fixed(&mut out, &self.append_name, SAVE_APPEND_NAME_MAX_LEN);
196        push_utf16_fixed(&mut out, &self.title, SAVE_TITLE_MAX_LEN);
197        push_utf16_fixed(&mut out, &self.message, SAVE_MESSAGE_MAX_LEN);
198        push_utf16_fixed(&mut out, &self.full_message, SAVE_FULL_MESSAGE_MAX_LEN);
199        push_utf16_fixed(&mut out, &self.comment, SAVE_COMMENT_MAX_LEN);
200        push_utf16_fixed(&mut out, &self.comment2, SAVE_COMMENT2_MAX_LEN);
201        for v in &self.flag {
202            push_i32(&mut out, *v);
203        }
204        push_i32(&mut out, self.data_size);
205        debug_assert_eq!(out.len(), SAVE_HEADER_SIZE);
206        out
207    }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct OriginalGlobalSaveHeader {
212    pub major_version: i32,
213    pub minor_version: i32,
214    pub global_data_size: i32,
215}
216
217impl OriginalGlobalSaveHeader {
218    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
219        if bytes.len() < GLOBAL_SAVE_HEADER_SIZE {
220            bail!("global save header too short: {} < {}", bytes.len(), GLOBAL_SAVE_HEADER_SIZE);
221        }
222        let mut rd = Reader::new(bytes);
223        Ok(Self {
224            major_version: rd.i32()?,
225            minor_version: rd.i32()?,
226            global_data_size: rd.i32()?,
227        })
228    }
229
230    pub fn to_bytes(&self) -> Vec<u8> {
231        let mut out = Vec::with_capacity(GLOBAL_SAVE_HEADER_SIZE);
232        push_i32(&mut out, self.major_version);
233        push_i32(&mut out, self.minor_version);
234        push_i32(&mut out, self.global_data_size);
235        out
236    }
237}
238
239#[derive(Debug, Clone)]
240pub struct OriginalLocalSaveEnvelope {
241    pub save_id: [u16; 7],
242    pub append_dir: String,
243    pub append_name: String,
244    pub title: String,
245    pub message: String,
246    pub full_message: String,
247    pub local_stream: Vec<u8>,
248    pub local_ex_stream: Vec<u8>,
249    pub sel_saves: Vec<OriginalLocalSaveEnvelope>,
250}
251
252impl OriginalLocalSaveEnvelope {
253    pub fn from_slot_with_streams(slot: &SaveSlotState, local_stream: Vec<u8>, local_ex_stream: Vec<u8>) -> Self {
254        Self {
255            save_id: save_id_from_slot(slot),
256            append_dir: slot.append_dir.clone(),
257            append_name: slot.append_name.clone(),
258            title: slot.title.clone(),
259            message: slot.message.clone(),
260            full_message: slot.full_message.clone(),
261            local_stream,
262            local_ex_stream,
263            sel_saves: Vec::new(),
264        }
265    }
266
267    pub fn empty_from_slot(slot: &SaveSlotState) -> Self {
268        Self::from_slot_with_streams(slot, Vec::new(), Vec::new())
269    }
270
271    pub fn to_bytes(&self) -> Vec<u8> {
272        let mut out = Vec::new();
273        write_envelope(&mut out, self, true);
274        out
275    }
276
277    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
278        let mut rd = Reader::new(bytes);
279        read_envelope(&mut rd, true)
280    }
281}
282
283
284fn save_id_from_slot(slot: &SaveSlotState) -> [u16; 7] {
285    fn w(v: i64) -> u16 {
286        v.clamp(0, u16::MAX as i64) as u16
287    }
288    [
289        w(slot.year),
290        w(slot.month),
291        w(slot.day),
292        w(slot.hour),
293        w(slot.minute),
294        w(slot.second),
295        w(slot.millisecond),
296    ]
297}
298
299fn write_envelope(out: &mut Vec<u8>, env: &OriginalLocalSaveEnvelope, include_sel_saves: bool) {
300    for v in env.save_id {
301        push_u16(out, v);
302    }
303    push_str_len(out, &env.append_dir);
304    push_str_len(out, &env.append_name);
305    push_str_len(out, &env.title);
306    push_str_len(out, &env.message);
307    push_str_len(out, &env.full_message);
308    push_i32(out, env.local_stream.len() as i32);
309    out.extend_from_slice(&env.local_stream);
310    push_i32(out, env.local_ex_stream.len() as i32);
311    out.extend_from_slice(&env.local_ex_stream);
312    if include_sel_saves {
313        push_i32(out, env.sel_saves.len() as i32);
314        for child in &env.sel_saves {
315            write_envelope(out, child, false);
316        }
317    }
318}
319
320fn read_envelope(rd: &mut Reader<'_>, include_sel_saves: bool) -> Result<OriginalLocalSaveEnvelope> {
321    let mut save_id = [0u16; 7];
322    for dst in &mut save_id {
323        *dst = rd.u16()?;
324    }
325    let append_dir = rd.str_len()?;
326    let append_name = rd.str_len()?;
327    let title = rd.str_len()?;
328    let message = rd.str_len()?;
329    let full_message = rd.str_len()?;
330    let local_size = rd.i32()?.max(0) as usize;
331    let local_stream = rd.take(local_size)?.to_vec();
332    let local_ex_size = rd.i32()?.max(0) as usize;
333    let local_ex_stream = rd.take(local_ex_size)?.to_vec();
334    let mut sel_saves = Vec::new();
335    if include_sel_saves {
336        let sel_save_cnt = rd.i32()?.max(0) as usize;
337        for _ in 0..sel_save_cnt {
338            sel_saves.push(read_envelope(rd, false)?);
339        }
340    }
341    Ok(OriginalLocalSaveEnvelope {
342        save_id,
343        append_dir,
344        append_name,
345        title,
346        message,
347        full_message,
348        local_stream,
349        local_ex_stream,
350        sel_saves,
351    })
352}
353
354pub struct OriginalStreamWriter {
355    data: Vec<u8>,
356}
357
358impl OriginalStreamWriter {
359    pub fn new() -> Self {
360        Self { data: Vec::new() }
361    }
362
363    pub fn into_inner(self) -> Vec<u8> {
364        self.data
365    }
366
367    pub fn push_i32(&mut self, v: i32) {
368        push_i32(&mut self.data, v);
369    }
370
371    pub fn push_i64(&mut self, v: i64) {
372        push_i64(&mut self.data, v);
373    }
374
375    pub fn push_u32(&mut self, v: u32) {
376        push_u32(&mut self.data, v);
377    }
378
379    pub fn push_raw(&mut self, bytes: &[u8]) {
380        self.data.extend_from_slice(bytes);
381    }
382
383    pub fn position(&self) -> usize {
384        self.data.len()
385    }
386
387    pub fn push_bool(&mut self, v: bool) {
388        self.data.push(if v { 1 } else { 0 });
389    }
390
391    pub fn push_padding(&mut self, n: usize) {
392        self.data.resize(self.data.len() + n, 0);
393    }
394
395    pub fn push_element(&mut self, codes: &[i32]) {
396        for idx in 0..31 {
397            push_i32(&mut self.data, codes.get(idx).copied().unwrap_or(0));
398        }
399        push_i32(&mut self.data, codes.len().min(31) as i32);
400    }
401
402    pub fn push_empty_element(&mut self) {
403        self.push_element(&[]);
404    }
405
406    pub fn push_empty_proc(&mut self) {
407        self.push_i32(0);
408        self.push_empty_element();
409        self.push_i32(0);
410        self.push_i32(0);
411        self.push_bool(false);
412        self.push_bool(false);
413        self.push_bool(false);
414        self.push_i32(0);
415    }
416
417    pub fn push_len_bytes(&mut self, bytes: &[u8]) {
418        push_i32(&mut self.data, bytes.len() as i32);
419        self.data.extend_from_slice(bytes);
420    }
421
422    pub fn push_str(&mut self, s: &str) {
423        push_str_len(&mut self.data, s);
424    }
425
426    pub fn push_fixed_i32_list(&mut self, values: &[i64], fixed_len: usize) {
427        let jump_pos = self.data.len();
428        push_i32(&mut self.data, 0);
429        push_i32(&mut self.data, fixed_len as i32);
430        for idx in 0..fixed_len {
431            push_i32(&mut self.data, values.get(idx).copied().unwrap_or(0) as i32);
432        }
433        let end = self.data.len() as i32;
434        patch_i32(&mut self.data, jump_pos, end);
435    }
436
437    pub fn push_fixed_str_list(&mut self, values: &[String], fixed_len: usize) {
438        let jump_pos = self.data.len();
439        push_i32(&mut self.data, 0);
440        push_i32(&mut self.data, fixed_len as i32);
441        for idx in 0..fixed_len {
442            push_str_len(&mut self.data, values.get(idx).map(String::as_str).unwrap_or(""));
443        }
444        let end = self.data.len() as i32;
445        patch_i32(&mut self.data, jump_pos, end);
446    }
447
448    pub fn push_extend_i32_list(&mut self, values: &[i64]) {
449        push_i32(&mut self.data, values.len() as i32);
450        for v in values {
451            push_i32(&mut self.data, *v as i32);
452        }
453    }
454
455    pub fn push_extend_str_list(&mut self, values: &[String]) {
456        push_i32(&mut self.data, values.len() as i32);
457        for v in values {
458            push_str_len(&mut self.data, v);
459        }
460    }
461
462    pub fn push_tid(&mut self, tid: &[u16; 7]) {
463        for v in tid {
464            push_u16(&mut self.data, *v);
465        }
466    }
467
468    pub fn push_empty_fixed_array(&mut self) {
469        let jump_pos = self.data.len();
470        push_i32(&mut self.data, 0);
471        push_i32(&mut self.data, 0);
472        let end = self.data.len() as i32;
473        patch_i32(&mut self.data, jump_pos, end);
474    }
475
476    pub fn push_fixed_items<T, F>(&mut self, values: &[T], mut write_one: F)
477    where
478        F: FnMut(&mut OriginalStreamWriter, &T),
479    {
480        let jump_pos = self.data.len();
481        push_i32(&mut self.data, 0);
482        push_i32(&mut self.data, values.len() as i32);
483        for value in values {
484            write_one(self, value);
485        }
486        let end = self.data.len() as i32;
487        patch_i32(&mut self.data, jump_pos, end);
488    }
489
490    pub fn push_extend_items<T, F>(&mut self, values: &[T], mut write_one: F)
491    where
492        F: FnMut(&mut OriginalStreamWriter, &T),
493    {
494        push_i32(&mut self.data, values.len() as i32);
495        for value in values {
496            write_one(self, value);
497        }
498    }
499
500    pub fn push_tid_zero(&mut self) {
501        self.push_padding(14);
502    }
503}
504
505pub struct OriginalStreamReader<'a> {
506    rd: Reader<'a>,
507}
508
509impl<'a> OriginalStreamReader<'a> {
510    pub fn new(data: &'a [u8]) -> Self {
511        Self { rd: Reader::new(data) }
512    }
513
514    pub fn i32(&mut self) -> Result<i32> {
515        self.rd.i32()
516    }
517
518    pub fn i64(&mut self) -> Result<i64> {
519        self.rd.i64()
520    }
521
522    pub fn bool(&mut self) -> Result<bool> {
523        Ok(self.rd.u8()? != 0)
524    }
525
526    pub fn skip(&mut self, n: usize) -> Result<()> {
527        self.rd.take(n).map(|_| ())
528    }
529
530    pub fn element(&mut self) -> Result<Vec<i32>> {
531        let mut codes = [0i32; 31];
532        for dst in &mut codes {
533            *dst = self.rd.i32()?;
534        }
535        let cnt = self.rd.i32()?.clamp(0, 31) as usize;
536        Ok(codes[..cnt].to_vec())
537    }
538
539    pub fn skip_element(&mut self) -> Result<()> {
540        self.skip(31 * 4 + 4)
541    }
542
543    pub fn skip_empty_proc(&mut self) -> Result<()> {
544        let _ = self.i32()?;
545        self.skip_element()?;
546        let _ = self.i32()?;
547        let _ = self.i32()?;
548        let _ = self.bool()?;
549        let _ = self.bool()?;
550        let _ = self.bool()?;
551        let _ = self.i32()?;
552        Ok(())
553    }
554
555    pub fn tid(&mut self) -> Result<[u16; 7]> {
556        let mut out = [0u16; 7];
557        for v in &mut out {
558            *v = self.rd.u16()?;
559        }
560        Ok(out)
561    }
562
563    pub fn take_raw(&mut self, n: usize) -> Result<&'a [u8]> {
564        self.rd.take(n)
565    }
566
567    pub fn len_bytes(&mut self) -> Result<Vec<u8>> {
568        let n = self.rd.i32()?;
569        if n < 0 {
570            bail!("negative byte length {}", n);
571        }
572        Ok(self.rd.take(n as usize)?.to_vec())
573    }
574
575    pub fn remaining(&self) -> &'a [u8] {
576        &self.rd.data[self.rd.pos..]
577    }
578
579    pub fn string(&mut self) -> Result<String> {
580        self.rd.str_len()
581    }
582
583    fn finish_fixed_array(&mut self, jump: i32) -> Result<()> {
584        if jump < 0 {
585            bail!("negative fixed-array jump {}", jump);
586        }
587        let jump = jump as usize;
588        if jump > self.rd.data.len() {
589            bail!("fixed-array jump out of bounds: jump {}, stream {}", jump, self.rd.data.len());
590        }
591        if self.rd.pos > jump {
592            bail!("fixed-array reader overran jump: pos {}, jump {}", self.rd.pos, jump);
593        }
594        self.rd.pos = jump;
595        Ok(())
596    }
597
598    pub fn fixed_i32_list(&mut self) -> Result<Vec<i64>> {
599        let jump = self.rd.i32()?;
600        let cnt = self.rd.i32()?.max(0) as usize;
601        let mut out = Vec::with_capacity(cnt);
602        for _ in 0..cnt {
603            out.push(self.rd.i32()? as i64);
604        }
605        self.finish_fixed_array(jump)?;
606        Ok(out)
607    }
608
609    pub fn fixed_str_list(&mut self) -> Result<Vec<String>> {
610        let jump = self.rd.i32()?;
611        let cnt = self.rd.i32()?.max(0) as usize;
612        let mut out = Vec::with_capacity(cnt);
613        for _ in 0..cnt {
614            out.push(self.rd.str_len()?);
615        }
616        self.finish_fixed_array(jump)?;
617        Ok(out)
618    }
619
620    pub fn extend_i32_list(&mut self) -> Result<Vec<i64>> {
621        let cnt = self.rd.i32()?.max(0) as usize;
622        let mut out = Vec::with_capacity(cnt);
623        for _ in 0..cnt {
624            out.push(self.rd.i32()? as i64);
625        }
626        Ok(out)
627    }
628
629    pub fn fixed_items<T, F>(&mut self, mut read_one: F) -> Result<Vec<T>>
630    where
631        F: FnMut(&mut OriginalStreamReader<'a>) -> Result<T>,
632    {
633        let jump = self.rd.i32()?;
634        let cnt = self.rd.i32()?.max(0) as usize;
635        let mut out = Vec::with_capacity(cnt);
636        for _ in 0..cnt {
637            out.push(read_one(self)?);
638        }
639        self.finish_fixed_array(jump)?;
640        Ok(out)
641    }
642
643    pub fn extend_items<T, F>(&mut self, mut read_one: F) -> Result<Vec<T>>
644    where
645        F: FnMut(&mut OriginalStreamReader<'a>) -> Result<T>,
646    {
647        let cnt = self.rd.i32()?.max(0) as usize;
648        let mut out = Vec::with_capacity(cnt);
649        for _ in 0..cnt {
650            out.push(read_one(self)?);
651        }
652        Ok(out)
653    }
654
655    pub fn skip_fixed_items<F>(&mut self, mut skip_one: F) -> Result<()>
656    where
657        F: FnMut(&mut OriginalStreamReader<'a>) -> Result<()>,
658    {
659        let jump = self.rd.i32()?;
660        let cnt = self.rd.i32()?.max(0) as usize;
661        for _ in 0..cnt {
662            skip_one(self)?;
663        }
664        self.finish_fixed_array(jump)
665    }
666}
667
668pub fn save_dir(project_dir: &Path) -> PathBuf {
669    project_dir.join("savedata")
670}
671
672pub fn original_save_no(save_cnt: usize, quick_save_cnt: usize, kind: SaveKind, idx: usize) -> usize {
673    match kind {
674        SaveKind::Normal => idx,
675        SaveKind::Quick => save_cnt + idx,
676        SaveKind::End => save_cnt + quick_save_cnt + idx,
677    }
678}
679
680pub fn save_file_path_for_no(project_dir: &Path, save_no: usize) -> PathBuf {
681    save_dir(project_dir).join(format!("{save_no:04}.sav"))
682}
683
684pub fn save_file_path_with_counts(project_dir: &Path, save_cnt: usize, quick_save_cnt: usize, kind: SaveKind, idx: usize) -> PathBuf {
685    save_file_path_for_no(project_dir, original_save_no(save_cnt, quick_save_cnt, kind, idx))
686}
687
688pub fn slot_save_no(project_dir: &Path, kind: SaveKind, idx: usize) -> usize {
689    let save_cnt = configured_count(project_dir, false);
690    let quick_save_cnt = configured_count(project_dir, true);
691    original_save_no(save_cnt, quick_save_cnt, kind, idx)
692}
693
694pub fn save_file_path(project_dir: &Path, kind: SaveKind, idx: usize) -> PathBuf {
695    save_file_path_for_no(project_dir, slot_save_no(project_dir, kind, idx))
696}
697
698pub fn thumb_candidate_paths_for_no(project_dir: &Path, save_no: usize) -> [PathBuf; 2] {
699    let stem = format!("{save_no:04}");
700    let dir = save_dir(project_dir);
701    [dir.join(format!("{stem}.png")), dir.join(format!("{stem}.bmp"))]
702}
703
704pub fn thumb_candidate_paths_with_counts(project_dir: &Path, save_cnt: usize, quick_save_cnt: usize, kind: SaveKind, idx: usize) -> [PathBuf; 2] {
705    thumb_candidate_paths_for_no(project_dir, original_save_no(save_cnt, quick_save_cnt, kind, idx))
706}
707
708pub fn read_header_from_path(path: &Path) -> Result<OriginalSaveHeader> {
709    let data = fs::read(path).with_context(|| format!("read save header {}", path.display()))?;
710    OriginalSaveHeader::from_bytes(&data[..data.len().min(SAVE_HEADER_SIZE)])
711}
712
713pub fn read_header(project_dir: &Path, kind: SaveKind, idx: usize) -> Option<OriginalSaveHeader> {
714    let path = save_file_path(project_dir, kind, idx);
715    read_header_from_path(&path).ok()
716}
717
718pub fn read_slot(project_dir: &Path, kind: SaveKind, idx: usize) -> Option<SaveSlotState> {
719    read_header(project_dir, kind, idx).map(|h| h.to_slot())
720}
721
722pub fn read_slot_from_path(path: &Path) -> Option<SaveSlotState> {
723    read_header_from_path(path).ok().map(|h| h.to_slot())
724}
725
726pub fn write_header_in_place(path: &Path, header: &OriginalSaveHeader) -> Result<()> {
727    let mut data = fs::read(path).with_context(|| format!("read save file {}", path.display()))?;
728    if data.len() < SAVE_HEADER_SIZE {
729        bail!("save file too short for header update: {}", path.display());
730    }
731    data[..SAVE_HEADER_SIZE].copy_from_slice(&header.to_bytes());
732    fs::write(path, data).with_context(|| format!("write save file {}", path.display()))
733}
734
735pub fn write_local_save_file(path: &Path, slot: &SaveSlotState, env: &OriginalLocalSaveEnvelope) -> Result<()> {
736    let payload = env.to_bytes();
737    let packed = pack_buffer(&payload);
738    let header = OriginalSaveHeader::from_slot(slot, packed.len());
739    let mut out = header.to_bytes();
740    out.extend_from_slice(&packed);
741    if let Some(parent) = path.parent() {
742        fs::create_dir_all(parent).with_context(|| format!("create save dir {}", parent.display()))?;
743    }
744    fs::write(path, out).with_context(|| format!("write save file {}", path.display()))
745}
746
747pub fn write_slot_file(path: &Path, slot: &SaveSlotState) -> Result<()> {
748    let env = OriginalLocalSaveEnvelope::empty_from_slot(slot);
749    write_local_save_file(path, slot, &env)
750}
751
752pub fn read_local_save_file(path: &Path) -> Result<(OriginalSaveHeader, OriginalLocalSaveEnvelope)> {
753    let data = fs::read(path).with_context(|| format!("read save file {}", path.display()))?;
754    if data.len() < SAVE_HEADER_SIZE {
755        bail!("save file too short: {}", path.display());
756    }
757    let header = OriginalSaveHeader::from_bytes(&data[..SAVE_HEADER_SIZE])?;
758    let data_size = header.data_size.max(0) as usize;
759    let end = SAVE_HEADER_SIZE
760        .checked_add(data_size)
761        .ok_or_else(|| anyhow!("save data size overflow"))?;
762    if end > data.len() {
763        bail!("save payload truncated: need {}, have {}", end, data.len());
764    }
765    let payload = unpack_buffer(&data[SAVE_HEADER_SIZE..end])?;
766    let env = OriginalLocalSaveEnvelope::from_bytes(&payload)?;
767    Ok((header, env))
768}
769
770pub fn write_global_save_file(project_dir: &Path, global_stream: &[u8]) -> Result<()> {
771    let packed = pack_buffer(global_stream);
772    let header = OriginalGlobalSaveHeader {
773        major_version: 2,
774        minor_version: 0,
775        global_data_size: packed.len() as i32,
776    };
777    let path = save_dir(project_dir).join("global.sav");
778    if let Some(parent) = path.parent() {
779        fs::create_dir_all(parent).with_context(|| format!("create save dir {}", parent.display()))?;
780    }
781    let mut out = header.to_bytes();
782    out.extend_from_slice(&packed);
783    fs::write(&path, out).with_context(|| format!("write global save file {}", path.display()))
784}
785
786pub fn read_global_save_file(project_dir: &Path) -> Result<Vec<u8>> {
787    let path = save_dir(project_dir).join("global.sav");
788    let data = fs::read(&path).with_context(|| format!("read global save file {}", path.display()))?;
789    if data.len() < GLOBAL_SAVE_HEADER_SIZE {
790        bail!("global save file too short: {}", path.display());
791    }
792    let header = OriginalGlobalSaveHeader::from_bytes(&data[..GLOBAL_SAVE_HEADER_SIZE])?;
793    if header.major_version != 2 || header.minor_version != 0 {
794        bail!("unsupported global save version {}.{}", header.major_version, header.minor_version);
795    }
796    let size = header.global_data_size.max(0) as usize;
797    let end = GLOBAL_SAVE_HEADER_SIZE
798        .checked_add(size)
799        .ok_or_else(|| anyhow!("global save data size overflow"))?;
800    if end > data.len() {
801        bail!("global save payload truncated: need {}, have {}", end, data.len());
802    }
803    unpack_buffer(&data[GLOBAL_SAVE_HEADER_SIZE..end])
804}
805
806pub fn pack_buffer(src: &[u8]) -> Vec<u8> {
807    let mut out = Vec::with_capacity(8 + src.len() + (src.len() + 7) / 8);
808    push_u32(&mut out, 0);
809    push_u32(&mut out, src.len() as u32);
810    for chunk in src.chunks(8) {
811        let mut flag = 0u8;
812        for i in 0..chunk.len() {
813            flag |= 1u8 << i;
814        }
815        out.push(flag);
816        out.extend_from_slice(chunk);
817    }
818    let arc_size = out.len() as u32;
819    out[0..4].copy_from_slice(&arc_size.to_le_bytes());
820    tpc_xor(&mut out);
821    out
822}
823
824pub fn unpack_buffer(src: &[u8]) -> Result<Vec<u8>> {
825    let mut data = src.to_vec();
826    tpc_xor(&mut data);
827    if data.len() < 8 {
828        bail!("lzss payload too short");
829    }
830    let arc_size = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
831    let org_size = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
832    if arc_size > data.len() {
833        bail!("lzss arc_size out of bounds: {} > {}", arc_size, data.len());
834    }
835    let mut out = Vec::with_capacity(org_size);
836    let mut p = 8usize;
837    while out.len() < org_size {
838        if p >= arc_size {
839            bail!("lzss payload ended before output was complete");
840        }
841        let mut flags = data[p];
842        p += 1;
843        for _ in 0..8 {
844            if out.len() >= org_size {
845                break;
846            }
847            if flags & 1 != 0 {
848                if p >= arc_size {
849                    bail!("lzss literal out of bounds");
850                }
851                out.push(data[p]);
852                p += 1;
853            } else {
854                if p + 2 > arc_size {
855                    bail!("lzss backref out of bounds");
856                }
857                let token = u16::from_le_bytes([data[p], data[p + 1]]);
858                p += 2;
859                let offset = (token >> 4) as usize;
860                let len = ((token & 0x0f) as usize) + 2;
861                if offset == 0 || offset > out.len() {
862                    bail!("lzss invalid backref offset {} at out {}", offset, out.len());
863                }
864                let base = out.len() - offset;
865                for i in 0..len {
866                    let b = out[base + i];
867                    out.push(b);
868                    if out.len() >= org_size {
869                        break;
870                    }
871                }
872            }
873            flags >>= 1;
874        }
875    }
876    Ok(out)
877}
878
879pub fn configured_count(project_dir: &Path, quick: bool) -> usize {
880    let keys: [&str; 2] = if quick {
881        ["#QUICK_SAVE.CNT", "QUICK_SAVE.CNT"]
882    } else {
883        ["#SAVE.CNT", "SAVE.CNT"]
884    };
885    configured_usize_any(project_dir, &keys, if quick { 3 } else { 10 }).min(10000)
886}
887
888pub fn configured_flag_count(project_dir: &Path) -> usize {
889    configured_usize_any(project_dir, &["#FLAG.CNT", "FLAG.CNT"], 1000).min(10000)
890}
891
892pub fn configured_mwnd_waku_btn_count(project_dir: &Path) -> usize {
893    configured_usize_any(project_dir, &["#WAKU.BTN.CNT", "WAKU.BTN.CNT"], 8).min(256)
894}
895
896fn configured_usize_any(_project_dir: &Path, _keys: &[&str], default_value: usize) -> usize {
897    default_value
898}
899
900fn patch_i32(out: &mut [u8], off: usize, v: i32) {
901    out[off..off + 4].copy_from_slice(&v.to_le_bytes());
902}
903
904fn push_i32(out: &mut Vec<u8>, v: i32) {
905    out.extend_from_slice(&v.to_le_bytes());
906}
907
908fn push_i64(out: &mut Vec<u8>, v: i64) {
909    out.extend_from_slice(&v.to_le_bytes());
910}
911
912fn push_u16(out: &mut Vec<u8>, v: u16) {
913    out.extend_from_slice(&v.to_le_bytes());
914}
915
916fn push_u32(out: &mut Vec<u8>, v: u32) {
917    out.extend_from_slice(&v.to_le_bytes());
918}
919
920fn push_str_len(out: &mut Vec<u8>, s: &str) {
921    let utf16: Vec<u16> = s.encode_utf16().collect();
922    push_i32(out, utf16.len().min(i32::MAX as usize) as i32);
923    for ch in utf16 {
924        out.extend_from_slice(&ch.to_le_bytes());
925    }
926}
927
928fn push_utf16_fixed(out: &mut Vec<u8>, s: &str, units: usize) {
929    let mut written = 0usize;
930    for ch in s.encode_utf16().take(units.saturating_sub(1)) {
931        out.extend_from_slice(&ch.to_le_bytes());
932        written += 1;
933    }
934    while written < units {
935        out.extend_from_slice(&0u16.to_le_bytes());
936        written += 1;
937    }
938}
939
940struct Reader<'a> {
941    data: &'a [u8],
942    pos: usize,
943}
944
945impl<'a> Reader<'a> {
946    fn new(data: &'a [u8]) -> Self {
947        Self { data, pos: 0 }
948    }
949
950    fn take(&mut self, n: usize) -> Result<&'a [u8]> {
951        let end = self.pos.checked_add(n).ok_or_else(|| anyhow!("reader overflow"))?;
952        if end > self.data.len() {
953            bail!("reader out of bounds: need {}, have {}", end, self.data.len());
954        }
955        let out = &self.data[self.pos..end];
956        self.pos = end;
957        Ok(out)
958    }
959
960    fn i32(&mut self) -> Result<i32> {
961        let b = self.take(4)?;
962        Ok(i32::from_le_bytes(b.try_into().unwrap()))
963    }
964
965    fn u8(&mut self) -> Result<u8> {
966        let b = self.take(1)?;
967        Ok(b[0])
968    }
969
970    fn u16(&mut self) -> Result<u16> {
971        let b = self.take(2)?;
972        Ok(u16::from_le_bytes(b.try_into().unwrap()))
973    }
974
975    fn i64(&mut self) -> Result<i64> {
976        let b = self.take(8)?;
977        Ok(i64::from_le_bytes(b.try_into().unwrap()))
978    }
979
980    fn utf16_fixed(&mut self, units: usize) -> Result<String> {
981        let bytes = self.take(units * 2)?;
982        let mut u16s = Vec::new();
983        for i in 0..units {
984            let p = i * 2;
985            let w = u16::from_le_bytes([bytes[p], bytes[p + 1]]);
986            if w == 0 {
987                break;
988            }
989            u16s.push(w);
990        }
991        Ok(String::from_utf16_lossy(&u16s))
992    }
993
994    fn str_len(&mut self) -> Result<String> {
995        let len = self.i32()?;
996        if len < 0 {
997            bail!("negative string length {}", len);
998        }
999        let len = len as usize;
1000        let bytes = self.take(len * 2)?;
1001        let mut u16s = Vec::with_capacity(len);
1002        for i in 0..len {
1003            let p = i * 2;
1004            u16s.push(u16::from_le_bytes([bytes[p], bytes[p + 1]]));
1005        }
1006        Ok(String::from_utf16_lossy(&u16s))
1007    }
1008}
1009
1010fn tpc_xor(data: &mut [u8]) {
1011    for (i, b) in data.iter_mut().enumerate() {
1012        *b ^= TPC_ANGOU_TABLE[i & 0xff];
1013    }
1014}
1015
1016const TPC_ANGOU_TABLE: [u8; 256] = [
1017    0x8b,0xe5,0x5d,0xc3,0xa1,0xe0,0x30,0x44,0x00,0x85,0xc0,0x74,0x09,0x5f,0x5e,0x33,
1018    0xc0,0x5b,0x8b,0xe5,0x5d,0xc3,0x8b,0x45,0x0c,0x85,0xc0,0x75,0x14,0x8b,0x55,0xec,
1019    0x83,0xc2,0x20,0x52,0x6a,0x00,0xe8,0xf5,0x28,0x01,0x00,0x83,0xc4,0x08,0x89,0x45,
1020    0x0c,0x8b,0x45,0xe4,0x6a,0x00,0x6a,0x00,0x50,0x53,0xff,0x15,0x34,0xb1,0x43,0x00,
1021    0x8b,0x45,0x10,0x85,0xc0,0x74,0x05,0x8b,0x4d,0xec,0x89,0x08,0x8a,0x45,0xf0,0x84,
1022    0xc0,0x75,0x78,0xa1,0xe0,0x30,0x44,0x00,0x8b,0x7d,0xe8,0x8b,0x75,0x0c,0x85,0xc0,
1023    0x75,0x44,0x8b,0x1d,0xd0,0xb0,0x43,0x00,0x85,0xff,0x76,0x37,0x81,0xff,0x00,0x00,
1024    0x04,0x00,0x6a,0x00,0x76,0x43,0x8b,0x45,0xf8,0x8d,0x55,0xfc,0x52,0x68,0x00,0x00,
1025    0x04,0x00,0x56,0x50,0xff,0x15,0x2c,0xb1,0x43,0x00,0x6a,0x05,0xff,0xd3,0xa1,0xe0,
1026    0x30,0x44,0x00,0x81,0xef,0x00,0x00,0x04,0x00,0x81,0xc6,0x00,0x00,0x04,0x00,0x85,
1027    0xc0,0x74,0xc5,0x8b,0x5d,0xf8,0x53,0xe8,0xf4,0xfb,0xff,0xff,0x8b,0x45,0x0c,0x83,
1028    0xc4,0x04,0x5f,0x5e,0x5b,0x8b,0xe5,0x5d,0xc3,0x8b,0x55,0xf8,0x8d,0x4d,0xfc,0x51,
1029    0x57,0x56,0x52,0xff,0x15,0x2c,0xb1,0x43,0x00,0xeb,0xd8,0x8b,0x45,0xe8,0x83,0xc0,
1030    0x20,0x50,0x6a,0x00,0xe8,0x47,0x28,0x01,0x00,0x8b,0x7d,0xe8,0x89,0x45,0xf4,0x8b,
1031    0xf0,0xa1,0xe0,0x30,0x44,0x00,0x83,0xc4,0x08,0x85,0xc0,0x75,0x56,0x8b,0x1d,0xd0,
1032    0xb0,0x43,0x00,0x85,0xff,0x76,0x49,0x81,0xff,0x00,0x00,0x04,0x00,0x6a,0x00,0x76,
1033];