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];