Implement save file parser for journey

This commit is contained in:
Patrick Auernig 2023-07-30 22:54:43 +02:00
commit ff20a33304
11 changed files with 975 additions and 0 deletions

390
Cargo.lock generated Normal file
View File

@ -0,0 +1,390 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "binrw"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab81d22cbd2d745852348b2138f3db2103afa8ce043117a374581926a523e267"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b019a3efebe7f453612083202887b6f1ace59e20d010672e336eea4ed5be97"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bumpalo"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "jrny-save"
version = "0.1.0"
dependencies = [
"binrw",
"chrono",
"urlencoding",
]
[[package]]
name = "js-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "num-traits"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.27",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[workspace]
resolver = "2"
members = [
"crates/save",
]
[workspace.package]
version = "0.1.0"
edition = "2021"

10
crates/save/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "jrny-save"
version.workspace = true
edition.workspace = true
[dependencies]
binrw = "0.11"
chrono = "0.4"
urlencoding = "2.1"

View File

@ -0,0 +1,130 @@
use std::io::SeekFrom;
use binrw::{BinRead, NullString};
#[derive(Debug)]
pub struct Companions(Vec<CompanionWithId>);
impl Companions {
pub fn iter(&self) -> std::slice::Iter<CompanionWithId> {
self.0.iter()
}
pub fn count(&self) -> usize {
self.0.len()
}
}
impl BinRead for Companions {
type Args<'a> = ();
fn read_options<R>(
reader: &mut R,
endian: binrw::Endian,
_args: Self::Args<'_>,
) -> binrw::BinResult<Self>
where
R: std::io::Read + std::io::Seek,
{
let mut companions = Vec::new();
loop {
let mut marker = [0u8; 4];
reader.seek(SeekFrom::Current(28))?;
reader.read_exact(&mut marker)?;
reader.seek(SeekFrom::Current(-32))?;
if marker != [0x01, 0x00, 0x10, 0x01] {
break;
}
let companion: CompanionWithId = <_>::read_options(reader, endian, ())?;
reader.seek(SeekFrom::Current(4))?;
companions.push(companion);
}
Ok(Self(companions))
}
}
#[derive(Debug)]
pub struct CompanionSymbols(Vec<CompanionWithSymbol>);
impl CompanionSymbols {
pub fn iter(&self) -> std::slice::Iter<CompanionWithSymbol> {
self.0.iter()
}
pub fn count(&self) -> usize {
self.0.len()
}
}
impl BinRead for CompanionSymbols {
type Args<'a> = ();
fn read_options<R>(
reader: &mut R,
endian: binrw::Endian,
_args: Self::Args<'_>,
) -> binrw::BinResult<Self>
where
R: std::io::Read + std::io::Seek,
{
let mut companions = Vec::new();
loop {
let companion: CompanionWithSymbol = <_>::read_options(reader, endian, ())?;
if companion.name.is_empty() {
reader.seek(SeekFrom::Current(-60))?;
break;
}
companions.push(companion);
}
let padding = 960 - (companions.len() * 60);
reader.seek(SeekFrom::Current(padding as i64))?;
Ok(Self(companions))
}
}
#[derive(Debug, BinRead)]
pub struct CompanionWithId {
#[brw(pad_size_to = 24, map = |raw: NullString| raw.to_string())]
pub name: String,
#[brw(assert(steam_id != 0))]
pub steam_id: u32,
}
impl CompanionWithId {
pub fn steam_id_v3(&self) -> String {
format!("[U:1:{}]", self.steam_id)
}
pub fn steam_url(&self) -> String {
let steam_id = self.steam_id_v3();
let encoded_steam_id = urlencoding::encode(&steam_id);
format!("https://steamcommunity.com/profiles/{}", encoded_steam_id)
}
}
#[derive(Debug, BinRead)]
pub struct CompanionWithSymbol {
#[brw(pad_size_to = 52, map = |raw: NullString| raw.to_string())]
pub name: String,
#[brw(count = 4)]
_unknown1: Vec<u8>,
#[brw(assert((0..=21).contains(&symbol)))]
pub symbol: u32,
}

51
crates/save/src/glyphs.rs Normal file
View File

@ -0,0 +1,51 @@
use binrw::BinRead;
#[derive(Debug, BinRead)]
pub struct Glyphs(#[brw(count = 6)] Vec<LevelGlyphs>);
impl Glyphs {
const COUNT: [usize; 6] = [3, 3, 4, 3, 4, 4];
pub fn all<'a>(&'a self) -> impl Iterator<Item = (usize, Vec<bool>)> + 'a {
self.0.iter().enumerate().map(|(level, glyphs)| {
let glyphs = (0..Self::COUNT[level])
.into_iter()
.map(|glyph_idx| glyphs.has_collected(glyph_idx).unwrap())
.collect();
(level, glyphs)
})
}
pub fn count(&self) -> usize {
self.0.len()
}
pub fn has_collected(&self, level: usize, index: usize) -> Option<bool> {
if level >= self.0.len() {
return None;
}
self.0[level].has_collected(index)
}
}
#[derive(Debug, BinRead)]
pub struct LevelGlyphs {
status_flags: u8,
#[brw(count = 343)]
_unused: Vec<u8>,
}
impl LevelGlyphs {
pub fn has_collected(&self, index: usize) -> Option<bool> {
if index > 8 {
return None;
}
Some(((self.status_flags >> index) & 0x01) == 0x01)
}
}

168
crates/save/src/lib.rs Normal file
View File

@ -0,0 +1,168 @@
mod companion;
mod glyphs;
mod murals;
mod test;
use core::fmt;
use std::io::Read;
use binrw::{BinRead, BinReaderExt};
use chrono::{DateTime, NaiveDateTime, Utc};
use crate::companion::{CompanionSymbols, CompanionWithId, Companions};
use crate::glyphs::Glyphs;
use crate::murals::Murals;
pub const LEVEL_NAMES: [&str; 12] = [
"Chapter Select",
"Broken Bridge",
"Pink Desert",
"Sunken City",
"Underground",
"Tower",
"Snow",
"Paradise",
"Credits",
"Level Bryan",
"Level Matt",
"Level Chris",
];
#[derive(Debug, PartialEq, Eq)]
pub enum RobeColor {
Red,
White,
}
impl fmt::Display for RobeColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Red => write!(f, "Red"),
Self::White => write!(f, "White"),
}
}
}
#[binrw::binread]
#[derive(Debug)]
#[brw(little, magic = b"\x16\x00\x00\x80\x00\x00\x00\x00")]
pub struct Savefile {
pub robe: u32,
pub symbol: u32,
pub scarf_length: u32,
#[brw(count = 4)]
_unknown1: Vec<u8>,
#[brw(assert(current_level <= 12))]
pub current_level: u64,
pub total_collected_symbols: u32,
#[brw(assert(collected_symbols <= 21))]
pub collected_symbols: u32,
pub murals: Murals,
#[brw(count = 22)]
_unknown2: Vec<u8>,
#[brw(parse_with = parse_last_played)]
pub last_played: DateTime<Utc>,
#[brw(count = 4)]
_unknown3: Vec<u8>,
pub journey_count: u64,
pub glyphs: Glyphs,
#[brw(count = 2404)]
_unknown4: Vec<u8>,
pub companion_symbols: CompanionSymbols,
pub companions_met: u32,
#[brw(count = 1024)]
_unknown6: Vec<u8>,
pub total_companions_met: u32,
#[brw(count = 24)]
_unknown7: Vec<u8>,
pub companions: Companions,
}
impl Savefile {
pub fn from_reader<R>(mut reader: R) -> std::io::Result<Self>
where
R: Read + BinReaderExt,
{
// TODO: implement error type
Ok(reader.read_le().unwrap())
}
pub fn current_companions<'a>(&'a self) -> impl Iterator<Item = &'a CompanionWithId> {
self.companions
.iter()
.enumerate()
.filter_map(|(idx, item)| {
if idx < self.companions_met as usize {
Some(item)
} else {
None
}
})
}
pub fn past_companions<'a>(&'a self) -> impl Iterator<Item = &'a CompanionWithId> {
self.companions
.iter()
.enumerate()
.filter_map(|(idx, item)| {
if idx >= self.companions_met as usize {
Some(item)
} else {
None
}
})
}
pub fn current_level_name(&self) -> &'static str {
LEVEL_NAMES[self.current_level as usize]
}
pub fn robe_color(&self) -> RobeColor {
if self.robe > 3 {
RobeColor::White
} else {
RobeColor::Red
}
}
pub fn robe_tier(&self) -> u32 {
match self.robe_color() {
RobeColor::Red => self.robe + 1,
RobeColor::White => self.robe - 2,
}
}
}
#[binrw::parser(reader, endian)]
fn parse_last_played() -> binrw::BinResult<DateTime<Utc>> {
let timestamp: i64 = <_>::read_options(reader, endian, ())?;
let timestamp = (timestamp / 10_000) - 11_644_473_600_000;
let datetime = NaiveDateTime::from_timestamp_millis(timestamp).unwrap();
let datetime = DateTime::from_utc(datetime, Utc);
Ok(datetime)
}

36
crates/save/src/murals.rs Normal file
View File

@ -0,0 +1,36 @@
use binrw::BinRead;
#[derive(Debug, BinRead)]
pub struct Murals {
status_flags: u16,
}
impl Murals {
const COUNT: [usize; 7] = [1, 1, 2, 2, 1, 1, 2];
pub fn has_found(&self, level_index: usize, index: usize) -> Option<bool> {
if level_index > 6 {
return None;
}
if index >= Self::COUNT[level_index] {
return None;
}
let pos = Self::COUNT[0..level_index].iter().sum::<usize>();
let mask = 0x01 << (pos + index);
Some((self.status_flags & mask) == mask)
}
pub fn all<'a>(&'a self) -> impl Iterator<Item = (usize, Vec<bool>)> + 'a {
Self::COUNT.iter().enumerate().map(|(level, murals)| {
let murals = (0..*murals)
.into_iter()
.map(|mural| self.has_found(level, mural).unwrap())
.collect::<Vec<_>>();
(level, murals)
})
}
}

156
crates/save/src/test.rs Normal file
View File

@ -0,0 +1,156 @@
#![cfg(test)]
use std::io::Cursor;
use binrw::BinReaderExt;
use chrono::NaiveDate;
use crate::*;
#[test]
fn general_info() {
let savefile = savefile();
assert_eq!(savefile.robe, 3);
assert_eq!(savefile.robe_color(), RobeColor::Red);
assert_eq!(savefile.robe_tier(), 4);
assert_eq!(savefile.symbol, 7);
assert_eq!(savefile.scarf_length, 27);
assert_eq!(savefile.current_level, 1);
assert_eq!(savefile.total_collected_symbols, 107);
assert_eq!(savefile.collected_symbols, 21);
assert_eq!(savefile.journey_count, 21);
assert_eq!(savefile.companions_met, 6);
assert_eq!(savefile.total_companions_met, 21);
}
#[test]
fn last_played() {
let savefile = savefile();
let expected = NaiveDate::from_ymd_opt(2023, 07, 28).unwrap();
let expected = expected.and_hms_milli_opt(14, 17, 45, 893).unwrap();
assert_eq!(savefile.last_played.naive_utc(), expected);
}
#[test]
fn companion_info() {
let savefile = savefile();
assert_eq!(savefile.companions.count(), 8);
assert_eq!(savefile.companion_symbols.count(), 8);
for (a, b) in savefile
.companions
.iter()
.zip(savefile.companion_symbols.iter())
{
assert_eq!(a.name, b.name);
}
let companion = savefile
.companion_symbols
.iter()
.find(|x| x.name == "Wanderer")
.unwrap();
assert_eq!(companion.symbol, 6);
let companion = savefile
.companion_symbols
.iter()
.find(|x| x.name == "Rythulian")
.unwrap();
assert_eq!(companion.symbol, 19);
let companion = savefile
.companion_symbols
.iter()
.find(|x| x.name == "Machine")
.unwrap();
assert_eq!(companion.symbol, 20);
}
#[test]
fn companion_order() {
let savefile = savefile();
let current = savefile.current_companions().collect::<Vec<_>>();
let past = savefile.past_companions().collect::<Vec<_>>();
assert_eq!(current.len(), 6);
assert_eq!(past.len(), 2);
assert_eq!(current[0].name, "Wanderer".to_string());
assert_eq!(current[1].name, "Rythulian".to_string());
assert_eq!(past[0].name, "Machine".to_string());
}
#[test]
fn glyph_status() {
let savefile = savefile();
const FOUND: [&[bool]; 6] = [
&[true, false, true],
&[true, false, false],
&[true, false, false, true],
&[true, true, true],
&[true, false, true, true],
&[false, true, false, true],
];
for (level_idx, level_found) in FOUND.into_iter().enumerate() {
for (glyph_idx, has_found) in level_found.into_iter().enumerate() {
assert_eq!(
savefile.glyphs.has_collected(level_idx, glyph_idx),
Some(*has_found),
"level {}, glyph {} is incorrect",
level_idx,
glyph_idx
);
}
}
assert_eq!(savefile.glyphs.has_collected(0, 69), None);
}
#[test]
fn murals() {
let savefile = savefile();
const FOUND: [&[bool]; 7] = [
&[false],
&[false],
&[true, true],
&[false, true],
&[false],
&[true],
&[true, false],
];
for (level_idx, level_found) in FOUND.into_iter().enumerate() {
for (mural_idx, has_found) in level_found.into_iter().enumerate() {
assert_eq!(
savefile.murals.has_found(level_idx, mural_idx),
Some(*has_found),
"level {}, mural {} is incorrect",
level_idx,
mural_idx
)
}
}
assert_eq!(savefile.murals.has_found(69, 420), None);
}
fn savefile() -> Savefile {
const TEST_FILE: &[u8] = include_bytes!("../test.bin");
let mut savefile = Cursor::new(TEST_FILE);
savefile.read_le().expect("parsing failed")
}

BIN
crates/save/test.bin Normal file

Binary file not shown.

5
rust-toolchain.toml Normal file
View File

@ -0,0 +1,5 @@
[toolchain]
channel = "1.71.0"
components = ["rustfmt", "clippy"]
targets = ["x86_64-unknown-linux-gnu"]
profile = "minimal"

18
rustfmt.toml Normal file
View File

@ -0,0 +1,18 @@
edition = "2021"
# Just enforce it here as well instead of relying on editorconfig alone
hard_tabs = false
tab_spaces = 4
newline_style = "Unix"
imports_granularity = "Module"
group_imports = "StdExternalCrate"
force_multiline_blocks = false
fn_single_line = false
comment_width = 100
wrap_comments = true
hex_literal_case = "Upper"
blank_lines_upper_bound = 2
overflow_delimited_expr = true
reorder_impl_items = true