sky_save/
save.rs

1//! Handles loading and storing the save data.
2
3use crate::error::SaveError;
4use crate::offsets::{active, general, save, stored};
5use crate::{ActivePokemon, PmdString, StoredPokemon};
6use arrayvec::ArrayVec;
7use bitvec::bitarr;
8use bitvec::field::BitField;
9use bitvec::order::Lsb0;
10use bitvec::slice::BitSlice;
11use bitvec::view::BitView;
12use std::fs;
13use std::ops::Range;
14use std::path::Path;
15
16/// File size must be at least 128Kib.
17const MIN_SAVE_LEN: usize = 0x20000;
18
19fn checksum(data: &[u8], data_range: Range<usize>) -> [u8; 4] {
20    (data[data_range]
21        .chunks(4)
22        .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) // Safe, four bytes.O
23        .fold(0u64, |acc, u| acc + u as u64) as u32)
24        .to_le_bytes()
25}
26
27fn load_save_slice(data: &[u8], active_save_block: ActiveSaveBlock, range: Range<usize>) -> &[u8] {
28    &data[range.start + active_save_block as usize..range.end + active_save_block as usize]
29}
30
31fn store_save_slice(
32    data: &mut [u8],
33    active_save_block: ActiveSaveBlock,
34    range: Range<usize>,
35    value: &[u8],
36) {
37    data[range.start + active_save_block as usize..range.end + active_save_block as usize]
38        .copy_from_slice(value);
39}
40
41fn load_save_bits(
42    data: &BitSlice<u8, Lsb0>,
43    active_save_block: ActiveSaveBlock,
44    range: Range<usize>,
45) -> &BitSlice<u8, Lsb0> {
46    &data[range.start + active_save_block as usize * 8..range.end + active_save_block as usize * 8]
47}
48
49fn store_save_bits(
50    data: &mut BitSlice<u8, Lsb0>,
51    active_save_block: ActiveSaveBlock,
52    range: Range<usize>,
53    value: &BitSlice<u8, Lsb0>,
54) {
55    data[range.start + active_save_block as usize * 8..range.end + active_save_block as usize * 8]
56        .copy_from_bitslice(value);
57}
58
59/// The current active save block.
60/// Holds it's start offset.
61#[derive(Debug, Copy, Clone, Eq, PartialEq)]
62#[repr(usize)]
63pub enum ActiveSaveBlock {
64    Primary = save::PRIMARY_SAVE.start,
65    Backup = save::BACKUP_SAVE.start,
66}
67
68/// Holds general information about the saved game.
69#[derive(Debug)]
70pub struct General {
71    pub team_name: PmdString,
72    pub held_money: u32,
73    pub sp_episode_held_money: u32,
74    pub stored_money: u32,
75    pub number_of_adventures: i32,
76    pub explorer_rank: u32,
77}
78
79impl General {
80    fn load(data: &[u8], active_save_block: ActiveSaveBlock) -> Self {
81        let team_name = load_save_slice(data, active_save_block, general::TEAM_NAME);
82        let held_money = load_save_bits(
83            data.view_bits(),
84            active_save_block,
85            general::HELD_MONEY_BITS,
86        );
87        let sp_episode_held_money = load_save_bits(
88            data.view_bits(),
89            active_save_block,
90            general::SP_EPISODE_HELD_MONEY_BITS,
91        );
92        let stored_money = load_save_bits(
93            data.view_bits(),
94            active_save_block,
95            general::STORED_MONEY_BITS,
96        );
97        let number_of_adventures =
98            load_save_slice(data, active_save_block, general::NUMBER_OF_ADVENTURERS)
99                .try_into()
100                .unwrap();
101        let explorer_rank = load_save_slice(data, active_save_block, general::EXPLORER_RANK)
102            .try_into()
103            .unwrap();
104
105        Self {
106            team_name: PmdString::from(team_name),
107            held_money: held_money.load_le(),
108            sp_episode_held_money: sp_episode_held_money.load_le(),
109            stored_money: stored_money.load_le(),
110            number_of_adventures: i32::from_le_bytes(number_of_adventures),
111            explorer_rank: u32::from_le_bytes(explorer_rank),
112        }
113    }
114
115    fn save(&self, data: &mut [u8], active_save_block: ActiveSaveBlock) {
116        store_save_slice(
117            data,
118            active_save_block,
119            general::TEAM_NAME,
120            self.team_name.to_save_bytes().as_slice(),
121        );
122
123        store_save_bits(
124            data.view_bits_mut(),
125            active_save_block,
126            general::HELD_MONEY_BITS,
127            &self.held_money.to_le_bytes().view_bits::<Lsb0>()[0..24],
128        );
129        store_save_bits(
130            data.view_bits_mut(),
131            active_save_block,
132            general::SP_EPISODE_HELD_MONEY_BITS,
133            &self.sp_episode_held_money.to_le_bytes().view_bits::<Lsb0>()[0..24],
134        );
135        store_save_bits(
136            data.view_bits_mut(),
137            active_save_block,
138            general::STORED_MONEY_BITS,
139            &self.stored_money.to_le_bytes().view_bits::<Lsb0>()[0..24],
140        );
141        store_save_slice(
142            data,
143            active_save_block,
144            general::NUMBER_OF_ADVENTURERS,
145            &self.number_of_adventures.to_le_bytes(),
146        );
147        store_save_slice(
148            data,
149            active_save_block,
150            general::EXPLORER_RANK,
151            &self.explorer_rank.to_le_bytes(),
152        );
153    }
154}
155
156/// The main structure of `sky-save`.
157/// Contains the save data bytes and every structure the library parses.
158/// Selectively loads data from the `active_save_block`.
159#[derive(Debug)]
160pub struct SkySave {
161    pub data: Vec<u8>,
162    pub active_save_block: ActiveSaveBlock,
163    pub quicksave_valid: bool,
164
165    pub general: General,
166    pub stored_pokemon: ArrayVec<StoredPokemon, 720>,
167    pub active_pokemon: ArrayVec<ActivePokemon, 4>,
168}
169
170impl SkySave {
171    /// Load and validates the save data from a slice of bytes.
172    /// The save data is validated by checking its length and calculating the checksums.
173    /// The save file is divided into three blocks: primary, backup, and quicksave.
174    /// For each block, the first four bytes are the checksum, and it is calculated as follows:
175    /// - Convert every four bytes, from start to end, to unsigned 32-bit integers. And then sum them together.
176    /// - Truncate the result to a 32-bit integer.
177    /// - Convert the result to little-endian bytes.
178    /// - Compare with bytes 0 to 3 to check for validity.
179    ///
180    /// After validation, every structure is parsed from the save data.
181    pub fn from_slice<S: AsRef<[u8]>>(data: S) -> Result<Self, SaveError> {
182        let data = data.as_ref();
183
184        if data.len() < MIN_SAVE_LEN {
185            return Err(SaveError::InvalidSize);
186        }
187
188        let pri_read: [u8; 4] = data[save::PRIMARY_READ_CHECKSUM].try_into().unwrap(); // Safe, four bytes.
189        let backup_read: [u8; 4] = data[save::BACKUP_READ_CHECKSUM].try_into().unwrap(); // Safe, four bytes.
190        let quick_read: [u8; 4] = data[save::QUICKSAVE_READ_CHECKSUM].try_into().unwrap(); // Safe, four bytes.
191
192        let pri_sum = checksum(data, save::PRIMARY_CHECKSUM);
193        let backup_sum = checksum(data, save::BACKUP_CHECKSUM);
194        let quick_sum = checksum(data, save::QUICKSAVE_CHECKSUM);
195
196        let pri_matches = pri_sum == pri_read;
197        let backup_matches = backup_sum == backup_read;
198        let quick_matches = quick_sum == quick_read;
199
200        if !pri_matches && !backup_matches {
201            return Err(SaveError::InvalidChecksum {
202                pri_expected: pri_read,
203                pri_found: pri_sum,
204                bak_expected: backup_read,
205                bak_found: backup_sum,
206            });
207        }
208
209        let active_save_block = if pri_matches {
210            ActiveSaveBlock::Primary
211        } else {
212            ActiveSaveBlock::Backup
213        };
214
215        let general = General::load(data, active_save_block);
216        let bits = load_save_bits(data.view_bits(), active_save_block, stored::STORED_PKM_BITS);
217
218        let stored_pokemon: ArrayVec<StoredPokemon, 720> = bits
219            .chunks(stored::STORED_PKM_BIT_LEN)
220            .map(StoredPokemon::from_bitslice)
221            .collect();
222
223        let bits = load_save_bits(data.view_bits(), active_save_block, active::ACTIVE_PKM_BITS);
224        let active_pokemon: ArrayVec<ActivePokemon, 4> = bits
225            .chunks(active::ACTIVE_PKM_BIT_LEN)
226            .map(ActivePokemon::from_bitslice)
227            .collect();
228
229        Ok(SkySave {
230            data: data.to_vec(),
231            active_save_block,
232            quicksave_valid: quick_matches,
233            general,
234            stored_pokemon,
235            active_pokemon,
236        })
237    }
238
239    /// Loads save data from a file.
240    pub fn open<P: AsRef<Path>>(filename: P) -> Result<Self, SaveError> {
241        let data = fs::read(filename).map_err(SaveError::Io)?;
242        Self::from_slice(&data)
243    }
244
245    /// Recalculates the checksums for each save block.
246    /// Writes the checksums to the save data.
247    pub fn fix_checksums(&mut self) {
248        let pri_sum = checksum(&self.data, save::PRIMARY_CHECKSUM);
249        let backup_sum = checksum(&self.data, save::BACKUP_CHECKSUM);
250        let quick_sum = checksum(&self.data, save::QUICKSAVE_CHECKSUM);
251
252        self.data[save::PRIMARY_READ_CHECKSUM].copy_from_slice(&pri_sum);
253        self.data[save::BACKUP_READ_CHECKSUM].copy_from_slice(&backup_sum);
254        self.data[save::QUICKSAVE_READ_CHECKSUM].copy_from_slice(&quick_sum);
255    }
256
257    /// Saves all changes to `data`. Recalculates the checksums and writes to a file.
258    pub fn save<P: AsRef<Path>>(&mut self, filename: P) -> Result<(), SaveError> {
259        let active_range = match self.active_save_block {
260            ActiveSaveBlock::Primary => save::PRIMARY_SAVE,
261            ActiveSaveBlock::Backup => save::BACKUP_SAVE,
262        };
263
264        let backup = match self.active_save_block {
265            ActiveSaveBlock::Primary => save::BACKUP_SAVE.start,
266            ActiveSaveBlock::Backup => save::PRIMARY_SAVE.start,
267        };
268
269        self.general.save(&mut self.data, self.active_save_block);
270
271        // Saving does not allocate on the heap.
272        let stored = self
273            .stored_pokemon
274            .iter()
275            .map(StoredPokemon::to_bits)
276            .enumerate()
277            .fold(
278                bitarr![u8, Lsb0; 0; stored::STORED_PKM_BIT_LEN * stored::STORED_PKM_COUNT],
279                |mut acc, (idx, a)| {
280                    acc[idx * stored::STORED_PKM_BIT_LEN..(idx + 1) * stored::STORED_PKM_BIT_LEN]
281                        .copy_from_bitslice(&a[0..stored::STORED_PKM_BIT_LEN]);
282                    acc
283                },
284            );
285        store_save_bits(
286            self.data.view_bits_mut(),
287            self.active_save_block,
288            stored::STORED_PKM_BITS,
289            stored.as_bitslice(),
290        );
291
292        let active = self
293            .active_pokemon
294            .iter()
295            .map(ActivePokemon::to_bits)
296            .enumerate()
297            .fold(
298                bitarr![u8, Lsb0; 0; active::ACTIVE_PKM_BIT_LEN * active::ACTIVE_PKM_COUNT],
299                |mut acc, (idx, a)| {
300                    acc[idx * active::ACTIVE_PKM_BIT_LEN..(idx + 1) * active::ACTIVE_PKM_BIT_LEN]
301                        .copy_from_bitslice(&a[0..active::ACTIVE_PKM_BIT_LEN]);
302                    acc
303                },
304            );
305        store_save_bits(
306            self.data.view_bits_mut(),
307            self.active_save_block,
308            active::ACTIVE_PKM_BITS,
309            active.as_bitslice(),
310        );
311
312        self.data.copy_within(active_range, backup);
313        self.fix_checksums();
314
315        fs::write(filename, &self.data).map_err(SaveError::Io)
316    }
317}