commit ff20a333046c3dbc6c1a47ae72f29660c36d8caa Author: Patrick Auernig Date: Sun Jul 30 22:54:43 2023 +0200 Implement save file parser for journey diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d2e397c --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..75e6eb0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ + "crates/save", +] + + +[workspace.package] +version = "0.1.0" +edition = "2021" + diff --git a/crates/save/Cargo.toml b/crates/save/Cargo.toml new file mode 100644 index 0000000..b0c9945 --- /dev/null +++ b/crates/save/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jrny-save" +version.workspace = true +edition.workspace = true + + +[dependencies] +binrw = "0.11" +chrono = "0.4" +urlencoding = "2.1" diff --git a/crates/save/src/companion.rs b/crates/save/src/companion.rs new file mode 100644 index 0000000..87d3c7b --- /dev/null +++ b/crates/save/src/companion.rs @@ -0,0 +1,130 @@ +use std::io::SeekFrom; + +use binrw::{BinRead, NullString}; + + +#[derive(Debug)] +pub struct Companions(Vec); + +impl Companions { + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + + pub fn count(&self) -> usize { + self.0.len() + } +} + +impl BinRead for Companions { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: binrw::Endian, + _args: Self::Args<'_>, + ) -> binrw::BinResult + 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); + +impl CompanionSymbols { + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + + pub fn count(&self) -> usize { + self.0.len() + } +} + +impl BinRead for CompanionSymbols { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: binrw::Endian, + _args: Self::Args<'_>, + ) -> binrw::BinResult + 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, + + #[brw(assert((0..=21).contains(&symbol)))] + pub symbol: u32, +} diff --git a/crates/save/src/glyphs.rs b/crates/save/src/glyphs.rs new file mode 100644 index 0000000..8e3e7fa --- /dev/null +++ b/crates/save/src/glyphs.rs @@ -0,0 +1,51 @@ +use binrw::BinRead; + + +#[derive(Debug, BinRead)] +pub struct Glyphs(#[brw(count = 6)] Vec); + +impl Glyphs { + const COUNT: [usize; 6] = [3, 3, 4, 3, 4, 4]; + + pub fn all<'a>(&'a self) -> impl Iterator)> + '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 { + 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, +} + +impl LevelGlyphs { + pub fn has_collected(&self, index: usize) -> Option { + if index > 8 { + return None; + } + + Some(((self.status_flags >> index) & 0x01) == 0x01) + } +} diff --git a/crates/save/src/lib.rs b/crates/save/src/lib.rs new file mode 100644 index 0000000..ce0fed3 --- /dev/null +++ b/crates/save/src/lib.rs @@ -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, + + #[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, + + #[brw(parse_with = parse_last_played)] + pub last_played: DateTime, + + #[brw(count = 4)] + _unknown3: Vec, + + pub journey_count: u64, + + pub glyphs: Glyphs, + + #[brw(count = 2404)] + _unknown4: Vec, + + pub companion_symbols: CompanionSymbols, + + pub companions_met: u32, + + #[brw(count = 1024)] + _unknown6: Vec, + + pub total_companions_met: u32, + + #[brw(count = 24)] + _unknown7: Vec, + + pub companions: Companions, +} + +impl Savefile { + pub fn from_reader(mut reader: R) -> std::io::Result + where + R: Read + BinReaderExt, + { + // TODO: implement error type + Ok(reader.read_le().unwrap()) + } + + pub fn current_companions<'a>(&'a self) -> impl Iterator { + 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 { + 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> { + 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) +} diff --git a/crates/save/src/murals.rs b/crates/save/src/murals.rs new file mode 100644 index 0000000..a41cc8b --- /dev/null +++ b/crates/save/src/murals.rs @@ -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 { + if level_index > 6 { + return None; + } + + if index >= Self::COUNT[level_index] { + return None; + } + + let pos = Self::COUNT[0..level_index].iter().sum::(); + let mask = 0x01 << (pos + index); + Some((self.status_flags & mask) == mask) + } + + pub fn all<'a>(&'a self) -> impl Iterator)> + 'a { + Self::COUNT.iter().enumerate().map(|(level, murals)| { + let murals = (0..*murals) + .into_iter() + .map(|mural| self.has_found(level, mural).unwrap()) + .collect::>(); + + (level, murals) + }) + } +} diff --git a/crates/save/src/test.rs b/crates/save/src/test.rs new file mode 100644 index 0000000..f0a066e --- /dev/null +++ b/crates/save/src/test.rs @@ -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::>(); + let past = savefile.past_companions().collect::>(); + + 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") +} diff --git a/crates/save/test.bin b/crates/save/test.bin new file mode 100644 index 0000000..17579f6 Binary files /dev/null and b/crates/save/test.bin differ diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8825301 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.71.0" +components = ["rustfmt", "clippy"] +targets = ["x86_64-unknown-linux-gnu"] +profile = "minimal" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..b99c2a4 --- /dev/null +++ b/rustfmt.toml @@ -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