Implement save file parser for journey
This commit is contained in:
commit
ff20a33304
390
Cargo.lock
generated
Normal file
390
Cargo.lock
generated
Normal 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
11
Cargo.toml
Normal 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
10
crates/save/Cargo.toml
Normal 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"
|
130
crates/save/src/companion.rs
Normal file
130
crates/save/src/companion.rs
Normal 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
51
crates/save/src/glyphs.rs
Normal 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
168
crates/save/src/lib.rs
Normal 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
36
crates/save/src/murals.rs
Normal 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
156
crates/save/src/test.rs
Normal 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
BIN
crates/save/test.bin
Normal file
Binary file not shown.
5
rust-toolchain.toml
Normal file
5
rust-toolchain.toml
Normal 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
18
rustfmt.toml
Normal 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
|
Loading…
Reference in New Issue
Block a user