100 lines
2.7 KiB
Rust
100 lines
2.7 KiB
Rust
// Copyright (C) 2020 "Maxime “pep” Buquet <pep@bouah.net>"
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU Affero General Public License as published by the
|
|
// Free Software Foundation, either version 3 of the License, or (at your
|
|
// option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
|
// for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#![feature(debug_non_exhaustive)]
|
|
|
|
/// Nintendo Pokemon gen1 save state handling, based on
|
|
/// https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_in_Generation_I
|
|
|
|
mod error;
|
|
mod party;
|
|
mod species;
|
|
|
|
use std::fmt;
|
|
use std::ops::Range;
|
|
use std::slice::Iter;
|
|
use std::convert::TryFrom;
|
|
|
|
use crate::error::Error;
|
|
use crate::party::PokemonList;
|
|
|
|
/// Compute checksum of a specific region.
|
|
pub fn make_checksum(iter: Iter<u8>) -> u8 {
|
|
iter.fold(255, |acc, x| acc.wrapping_sub(*x))
|
|
}
|
|
|
|
pub fn verify_checksum(iter: Iter<u8>, checksum: u8) -> bool {
|
|
make_checksum(iter) == checksum
|
|
}
|
|
|
|
/// Party Data
|
|
const PARTY_DATA_RANGE: Range<usize> = 0x2F2C..0x30bf;
|
|
|
|
/// Main Data Checksum
|
|
const MAIN_DATA_CHECKSUM: usize = 0x3523;
|
|
|
|
/// Area validated by the Main Data Checksum
|
|
const MAIN_DATA_CHECKSUM_RANGE: Range<usize> = 0x2598..0x3523;
|
|
|
|
/// A Save State
|
|
#[derive(PartialEq)]
|
|
pub struct SaveState {
|
|
_data: Vec<u8>,
|
|
|
|
/// Player name. offset: 0x2598, size: 0xB
|
|
pub player_name: String,
|
|
|
|
/// Party Data. offset: 0x2F2C, size: 0x194
|
|
pub party: PokemonList,
|
|
}
|
|
|
|
impl fmt::Debug for SaveState {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("SaveState")
|
|
.field("party", &self.party)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Vec<u8>> for SaveState {
|
|
type Error = Error;
|
|
|
|
fn try_from(data: Vec<u8>) -> Result<SaveState, Error> {
|
|
// Length of a savestate is 32k
|
|
if data.len() != 32768 {
|
|
return Err(Error::InvalidSaveLength)
|
|
}
|
|
|
|
if ! verify_checksum(data[MAIN_DATA_CHECKSUM_RANGE].iter(), data[MAIN_DATA_CHECKSUM]) {
|
|
return Err(Error::InvalidChecksum)
|
|
}
|
|
|
|
Ok(SaveState {
|
|
_data: data.clone(),
|
|
player_name: String::from("Foo"),
|
|
party: PokemonList::try_from(&data[PARTY_DATA_RANGE])?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SaveState {
|
|
pub fn new(data: Vec<u8>) -> Result<SaveState, Error> {
|
|
SaveState::try_from(data)
|
|
}
|
|
|
|
pub fn into_vec(self) -> Vec<u8> {
|
|
self._data
|
|
}
|
|
}
|