Accounts + cloud saves: the front door for the web engine

- mrpgi-vault (standalone crate): tiny signup/login/world-save service.
  Argon2 password hashes, random session cookies, JSON-file storage,
  naive per-IP rate limit, 4MB body cap. tiny_http, no framework, no db.
  Runs as a container on the dealgod network; nginx proxies
  /engine/api/ to it.
- core: WorldBundle — a whole world (manifest + every room) as one JSON
  document, with World::to_bundle/apply_bundle.
- web bridge: mrpgi_request_save / mrpgi_alloc / mrpgi_world_in exports
  + an mrpgi_world_saved JS plugin import; the page's SAVE button pulls
  the live world out of the running engine, LOAD swaps it back in.
  SAVE_REQUESTED is an AtomicBool on purpose — wasm's thread-less
  Mutex<bool> is a plain static to LLVM and release builds const-folded
  the read (the only writer is JS-visible, not IR-visible).
- web UI: SIGN IN tab → create account (own username + password) /
  sign in / Save world / Load world / sign out. Session restores on
  revisit.

Verified live end-to-end through Cloudflare: signup → save → reload →
anonymous rejected; throwaway account removed after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-05 11:38:27 +10:00
parent 584424c939
commit cd7f658444
262 changed files with 1370 additions and 12 deletions

View File

@ -1,4 +1,11 @@
# Web builds: miniquad's JS imports (console_log, init_webgl, ...) are
# resolved at runtime by web/mq_js_bundle.js — tell the linker to allow them.
# The mrpgi_* exports are the page's save/load bridge into the engine.
[target.wasm32-unknown-unknown]
rustflags = ["-C", "link-arg=--allow-undefined"]
rustflags = [
"-C", "link-arg=--allow-undefined",
"-C", "link-arg=--export=mrpgi_alloc",
"-C", "link-arg=--export=mrpgi_world_in",
"-C", "link-arg=--export=mrpgi_request_save",
"-C", "link-arg=--export=mrpgi_save_pending",
]

View File

@ -231,6 +231,16 @@ pub fn find_spawn(fb: &FrameBuffer, prefer: (i32, i32)) -> (i32, i32) {
prefer
}
/// A whole world in one JSON document: the manifest + every room. What cloud
/// saves store and what `to_bundle`/`apply_bundle` round-trip.
#[derive(Clone, Serialize, Deserialize)]
pub struct WorldBundle {
#[serde(default)]
pub manifest: GameManifest,
#[serde(default)]
pub rooms: HashMap<u32, RoomDoc>,
}
/// The loaded game: manifest + sprites + the current room baked into a
/// framebuffer, plus an in-memory overlay of rooms that differ from disk
/// (unsaved editor work, or lore upserted live over the bus).
@ -304,6 +314,40 @@ impl World {
self.base.join(format!("rooms/room{}.json", n))
}
/// Snapshot the whole world as one JSON document — manifest + every room
/// (disk, overlay, and the live current room). The single-file project
/// format used by cloud saves and shareable world files.
pub fn to_bundle(&self) -> WorldBundle {
let mut rooms: HashMap<u32, RoomDoc> = HashMap::new();
// rooms on disk
if let Ok(rd) = std::fs::read_dir(self.base.join("rooms")) {
for e in rd.flatten() {
let name = e.file_name().to_string_lossy().into_owned();
if let Some(n) = name.strip_prefix("room").and_then(|s| s.strip_suffix(".json")).and_then(|s| s.parse().ok()) {
if let Some(doc) = self.peek_room(n) {
rooms.insert(n, doc);
}
}
}
}
// overlay wins over disk; the live doc wins over everything
for (n, doc) in &self.overlay {
rooms.insert(*n, doc.clone());
}
rooms.insert(self.current, self.doc.clone());
WorldBundle { manifest: self.manifest.clone(), rooms }
}
/// Replace the loaded world with a bundle (sprites are kept — bundles
/// carry world data, not art). The caller decides what to do next
/// (usually `Command::NewGame`).
pub fn apply_bundle(&mut self, b: WorldBundle) {
self.manifest = b.manifest;
self.overlay = b.rooms.into_iter().collect();
let start = self.manifest.start_room;
self.goto_room(start);
}
/// Read a room doc without switching to it: overlay first, then disk.
pub fn peek_room(&self, n: u32) -> Option<RoomDoc> {
if let Some(doc) = self.overlay.get(&n) {

284
mrpgi-vault/Cargo.lock generated Normal file
View File

@ -0,0 +1,284 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
]
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chunked_transfer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "mrpgi-vault"
version = "0.1.0"
dependencies = [
"argon2",
"getrandom",
"serde_json",
"tiny_http",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tiny_http"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
dependencies = [
"ascii",
"chunked_transfer",
"httpdate",
"log",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

18
mrpgi-vault/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "mrpgi-vault"
version = "0.1.0"
edition = "2021"
description = "The front door for monsterrobot.games/engine — tiny account + world-save service. Argon2 hashes, session cookies, JSON files on disk. No framework, no database."
# Standalone on purpose (not a member of the parent workspace): it ships to
# the VPS as a folder and builds there in a bare rust container.
[workspace]
[dependencies]
tiny_http = "0.12"
serde_json = "1"
argon2 = "0.5"
getrandom = "0.2"
[profile.release]
strip = true

12
mrpgi-vault/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM rust:1-alpine AS build
RUN apk add --no-cache musl-dev
WORKDIR /src
COPY Cargo.toml ./
COPY src ./src
RUN cargo build --release
FROM alpine:3
COPY --from=build /src/target/release/mrpgi-vault /usr/local/bin/mrpgi-vault
ENV VAULT_DATA=/data VAULT_ADDR=0.0.0.0:8200
VOLUME /data
CMD ["mrpgi-vault"]

224
mrpgi-vault/src/main.rs Normal file
View File

@ -0,0 +1,224 @@
//! mrpgi-vault — the "front door" for the web engine: signup/login and one
//! saved world per user.
//!
//! POST /api/signup {"user","pass"} create account, start a session
//! POST /api/login {"user","pass"} start a session
//! POST /api/logout end the session
//! GET /api/me {"user":"matt"} or 401
//! GET /api/world the user's saved world JSON (404 if none)
//! PUT /api/world save (body = WorldBundle JSON, ≤ 4 MB)
//! GET /api/ping health check
//!
//! Storage is the filesystem: VAULT_DATA/users.json + VAULT_DATA/worlds/<user>.json.
//! Sessions are in-memory (a restart signs everyone out — they log back in).
//! Single-threaded on purpose: requests are tiny, ordering is trivial, and
//! there is nothing to lock.
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::Argon2;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::io::Read;
use std::path::PathBuf;
use tiny_http::{Header, Method, Response, Server};
const MAX_BODY: u64 = 4 * 1024 * 1024;
const SESSION_COOKIE: &str = "mrpgi_session";
fn main() {
let data: PathBuf = std::env::var("VAULT_DATA").unwrap_or_else(|_| "vault-data".into()).into();
std::fs::create_dir_all(data.join("worlds")).expect("can't create data dir");
let addr = std::env::var("VAULT_ADDR").unwrap_or_else(|_| "0.0.0.0:8200".into());
let server = Server::http(&addr).unwrap_or_else(|e| panic!("can't bind {}: {}", addr, e));
eprintln!("mrpgi-vault listening on {} (data: {})", addr, data.display());
let users_path = data.join("users.json");
let mut users: HashMap<String, String> = std::fs::read_to_string(&users_path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
let mut sessions: HashMap<String, String> = HashMap::new();
// ponytail: naive per-minute rate limit; a real limiter if this ever gets popular
let mut rate: HashMap<String, (u64, u32)> = HashMap::new();
for mut req in server.incoming_requests() {
let method = req.method().clone();
let path = req.url().split('?').next().unwrap_or("").to_string();
let ip = req.remote_addr().map(|a| a.ip().to_string()).unwrap_or_default();
let session_user = cookie_token(&req).and_then(|t| sessions.get(&t).cloned());
match (method, path.as_str()) {
(Method::Get, "/api/ping") => respond(req, 200, json!({"ok": true}), None),
(Method::Post, "/api/signup") | (Method::Post, "/api/login") => {
if !allow(&mut rate, &ip) {
respond(req, 429, json!({"error": "slow down a little"}), None);
continue;
}
let body = read_body(&mut req);
let v: Value = serde_json::from_str(&body).unwrap_or(Value::Null);
let user = v["user"].as_str().unwrap_or("").trim().to_lowercase();
let pass = v["pass"].as_str().unwrap_or("");
if !valid_username(&user) {
respond(req, 400, json!({"error": "username: 2-20 chars, a-z 0-9 _ -"}), None);
continue;
}
if pass.len() < 4 || pass.len() > 100 {
respond(req, 400, json!({"error": "password: at least 4 characters"}), None);
continue;
}
let signup = path == "/api/signup";
if signup {
if users.contains_key(&user) {
respond(req, 409, json!({"error": "that name is taken"}), None);
continue;
}
let salt = SaltString::encode_b64(&random_bytes()).expect("salt");
let hash = Argon2::default().hash_password(pass.as_bytes(), &salt).expect("hash").to_string();
users.insert(user.clone(), hash);
let j = serde_json::to_string_pretty(&users).expect("users json");
if let Err(e) = std::fs::write(&users_path, j) {
users.remove(&user);
eprintln!("users.json write failed: {}", e);
respond(req, 500, json!({"error": "storage hiccup, try again"}), None);
continue;
}
} else {
let ok = users
.get(&user)
.and_then(|h| PasswordHash::new(h).ok())
.map(|h| Argon2::default().verify_password(pass.as_bytes(), &h).is_ok())
.unwrap_or(false);
if !ok {
respond(req, 401, json!({"error": "wrong name or password"}), None);
continue;
}
}
let token = hex(&random_bytes());
sessions.insert(token.clone(), user.clone());
respond(req, 200, json!({"user": user}), Some(set_cookie(&token, false)));
}
(Method::Post, "/api/logout") => {
if let Some(t) = cookie_token(&req) {
sessions.remove(&t);
}
respond(req, 200, json!({"ok": true}), Some(set_cookie("", true)));
}
(Method::Get, "/api/me") => match session_user {
Some(u) => respond(req, 200, json!({"user": u}), None),
None => respond(req, 401, json!({"error": "not signed in"}), None),
},
(Method::Get, "/api/world") => match session_user {
Some(u) => match std::fs::read_to_string(data.join("worlds").join(format!("{}.json", u))) {
Ok(w) => respond_raw(req, 200, w.into_bytes()),
Err(_) => respond(req, 404, json!({"error": "no saved world yet"}), None),
},
None => respond(req, 401, json!({"error": "not signed in"}), None),
},
(Method::Put, "/api/world") => match session_user {
Some(u) => {
let body = read_body(&mut req);
// must at least parse as JSON — the vault stores worlds, not mystery bytes
if serde_json::from_str::<Value>(&body).is_err() {
respond(req, 400, json!({"error": "not valid JSON"}), None);
continue;
}
match std::fs::write(data.join("worlds").join(format!("{}.json", u)), &body) {
Ok(()) => respond(req, 200, json!({"ok": true, "bytes": body.len()}), None),
Err(e) => {
eprintln!("world write failed for {}: {}", u, e);
respond(req, 500, json!({"error": "storage hiccup, try again"}), None)
}
}
}
None => respond(req, 401, json!({"error": "not signed in"}), None),
},
_ => respond(req, 404, json!({"error": "no such route"}), None),
}
}
}
/// ponytail: 20 auth attempts per IP per minute, forgotten on restart.
fn allow(rate: &mut HashMap<String, (u64, u32)>, ip: &str) -> bool {
let min = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() / 60)
.unwrap_or(0);
let count = {
let e = rate.entry(ip.to_string()).or_insert((min, 0));
if e.0 != min {
*e = (min, 0);
}
e.1 += 1;
e.1
};
if rate.len() > 10_000 {
rate.retain(|_, v| v.0 == min);
}
count <= 20
}
fn valid_username(u: &str) -> bool {
(2..=20).contains(&u.len()) && u.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')
}
fn random_bytes() -> [u8; 32] {
let mut b = [0u8; 32];
getrandom::getrandom(&mut b).expect("os rng");
b
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{:02x}", x)).collect()
}
fn read_body(req: &mut tiny_http::Request) -> String {
let mut body = String::new();
let _ = req.as_reader().take(MAX_BODY).read_to_string(&mut body);
body
}
fn cookie_token(req: &tiny_http::Request) -> Option<String> {
req.headers()
.iter()
.find(|h| h.field.equiv("Cookie"))
.and_then(|h| {
h.value
.as_str()
.split(';')
.map(|c| c.trim())
.find_map(|c| c.strip_prefix(&format!("{}=", SESSION_COOKIE)))
.map(|t| t.to_string())
})
}
fn set_cookie(token: &str, clear: bool) -> Header {
let v = if clear {
format!("{}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax", SESSION_COOKIE)
} else {
// Secure is fine even though the origin hop is http: the browser only
// ever sees this through Cloudflare's https.
format!("{}={}; Path=/; Max-Age=2592000; HttpOnly; Secure; SameSite=Lax", SESSION_COOKIE, token)
};
Header::from_bytes("Set-Cookie", v).expect("cookie header")
}
fn respond(req: tiny_http::Request, code: u16, v: Value, cookie: Option<Header>) {
let mut resp = Response::from_data(v.to_string().into_bytes()).with_status_code(code);
resp.add_header(Header::from_bytes("Content-Type", "application/json").expect("header"));
if let Some(c) = cookie {
resp.add_header(c);
}
let _ = req.respond(resp);
}
fn respond_raw(req: tiny_http::Request, code: u16, bytes: Vec<u8>) {
let mut resp = Response::from_data(bytes).with_status_code(code);
resp.add_header(Header::from_bytes("Content-Type", "application/json").expect("header"));
let _ = req.respond(resp);
}

View File

@ -0,0 +1 @@
{"rustc_fingerprint":17712373269760344233,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/johnking/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"8434194601806579904":{"success":true,"status":"","code":0,"stdout":"rustc 1.96.1 (31fca3adb 2026-06-26)\nbinary: rustc\ncommit-hash: 31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd\ncommit-date: 2026-06-26\nhost: aarch64-apple-darwin\nrelease: 1.96.1\nLLVM version: 22.1.2\n","stderr":""}},"successes":{}}

View File

@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View File

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
86ae46afb58ab1d7

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"alloc\", \"default\", \"password-hash\", \"rand\"]","declared_features":"[\"alloc\", \"default\", \"password-hash\", \"rand\", \"simple\", \"std\", \"zeroize\"]","target":5931530492013982456,"profile":14448301111190550315,"path":9372947182154681435,"deps":[[5799347126265914943,"base64ct",false,5216475541427589564],[6742268975477224606,"password_hash",false,6756513097304912490],[8700459469608572718,"blake2",false,8612054694365025106]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/argon2-3f5ee4fb8340e8aa/dep-lib-argon2","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
906f0093f39162dd

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"serde\", \"serde_test\", \"std\"]","target":12466981117961934896,"profile":14448301111190550315,"path":10439826151162417335,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/ascii-1104f37ddeab9395/dep-lib-ascii","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
bc114eeadda46448

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"alloc\"]","declared_features":"[\"alloc\", \"std\"]","target":15548948006327107948,"profile":14448301111190550315,"path":1949270628458667453,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/base64ct-a4d0ed5025aec93d/dep-lib-base64ct","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
52d3dcab1d2a8477

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"default\", \"reset\", \"simd\", \"simd_asm\", \"simd_opt\", \"size_opt\", \"std\"]","target":8092008059563395214,"profile":14448301111190550315,"path":16306611394366467273,"deps":[[17475753849556516473,"digest",false,2421399842352504203]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/blake2-8f6adc3c8b0d0129/dep-lib-blake2","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[]","target":4098124618827574291,"profile":14448301111190550315,"path":12088544747430999442,"deps":[[10520923840501062997,"generic_array",false,7334621468793735140]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/block-buffer-fdbb50ec95b79a63/dep-lib-block_buffer","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
2033dc63f205a331

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"core\", \"rustc-dep-of-std\"]","target":13840298032947503755,"profile":14448301111190550315,"path":5128381429035242896,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/cfg-if-f0701d1aa87624ef/dep-lib-cfg_if","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[]","target":1411173362047441990,"profile":14448301111190550315,"path":2988204354487611051,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/chunked_transfer-cc0677e7e57f295d/dep-lib-chunked_transfer","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"getrandom\", \"rand_core\", \"std\"]","target":12082577455412410174,"profile":14448301111190550315,"path":6287400986718769698,"deps":[[6918147871599447195,"typenum",false,2305459257349576253],[10520923840501062997,"generic_array",false,7334621468793735140]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crypto-common-58fba869b7470246/dep-lib-crypto_common","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
8b9d9bd6538a9a21

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"block-buffer\", \"core-api\", \"default\", \"mac\", \"subtle\"]","declared_features":"[\"alloc\", \"blobby\", \"block-buffer\", \"const-oid\", \"core-api\", \"default\", \"dev\", \"mac\", \"oid\", \"rand_core\", \"std\", \"subtle\"]","target":7510122432137863311,"profile":14448301111190550315,"path":2626043635653780130,"deps":[[6039282458970808711,"crypto_common",false,15040832999292844657],[10626340395483396037,"block_buffer",false,5192442065127828434],[17003143334332120809,"subtle",false,7647771212883598475]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/digest-62a103f77ec59270/dep-lib-digest","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[10520923840501062997,"build_script_build",false,16926981868460223644]],"local":[{"Precalculated":"0.14.7"}],"rustflags":[],"config":0,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"more_lengths\"]","declared_features":"[\"more_lengths\", \"serde\", \"zeroize\"]","target":13084005262763373425,"profile":14448301111190550315,"path":14122682200335157817,"deps":[[6918147871599447195,"typenum",false,2305459257349576253],[10520923840501062997,"build_script_build",false,11477909908590141390]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/generic-array-b21ba31fb35fc7f8/dep-lib-generic_array","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"more_lengths\"]","declared_features":"[\"more_lengths\", \"serde\", \"zeroize\"]","target":12318548087768197662,"profile":8695675999177943201,"path":8157989698062146706,"deps":[[5398981501050481332,"version_check",false,2763491749387108647]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/generic-array-b81b004cdc7fb183/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
18b510818526a576

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"compiler_builtins\", \"core\", \"custom\", \"js\", \"js-sys\", \"linux_disable_fallback\", \"rdrand\", \"rustc-dep-of-std\", \"std\", \"test-in-browser\", \"wasm-bindgen\"]","target":16244099637825074703,"profile":14448301111190550315,"path":2015537051588455227,"deps":[[7098700569944897890,"libc",false,544013598059495998],[7667230146095136825,"cfg_if",false,3576709067677905696]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/getrandom-5269d62445c0d70b/dep-lib-getrandom","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
134a4823ab8bf488

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[]","target":12509520342503990962,"profile":14448301111190550315,"path":12079649545973907445,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/httpdate-4869782b1ce86385/dep-lib-httpdate","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
3d7dcc5fa37ff0c0

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"no-panic\"]","target":18426369533666673425,"profile":14448301111190550315,"path":5837542558989549293,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/itoa-cfc90c86c6c08fc4/dep-lib-itoa","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":5408242616063297496,"profile":4476325643821061672,"path":15245630090102365948,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/libc-005a82011928bd6b/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[7098700569944897890,"build_script_build",false,10975649544351500720]],"local":[{"RerunIfChanged":{"output":"release/build/libc-1ff43fcd12c0d7dd/output","paths":["build.rs"]}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_FREEBSD_VERSION","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_MUSL_V1_2_3","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_TIME_BITS","val":null}}],"rustflags":[],"config":0,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
3e8251317cb98c07

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":17682796336736096309,"profile":14820467123912276182,"path":12081287477241155684,"deps":[[7098700569944897890,"build_script_build",false,15848156144284133075]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/libc-b9b325d6e9b99c42/dep-lib-libc","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
2469b7b3cb4ebed2

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"kv\", \"kv_serde\", \"kv_std\", \"kv_sval\", \"kv_unstable\", \"kv_unstable_serde\", \"kv_unstable_std\", \"kv_unstable_sval\", \"max_level_debug\", \"max_level_error\", \"max_level_info\", \"max_level_off\", \"max_level_trace\", \"max_level_warn\", \"release_max_level_debug\", \"release_max_level_error\", \"release_max_level_info\", \"release_max_level_off\", \"release_max_level_trace\", \"release_max_level_warn\", \"serde\", \"serde_core\", \"std\", \"sval\", \"sval_ref\", \"value-bag\"]","target":6550155848337067049,"profile":14448301111190550315,"path":485682493016981769,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/log-4787a7b041ff5c86/dep-lib-log","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
51ea67cd3681053a

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"core\", \"default\", \"libc\", \"logging\", \"rustc-dep-of-std\", \"std\", \"use_std\"]","target":11745930252914242013,"profile":14448301111190550315,"path":13810881138279972546,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/memchr-c52d0ece40bd82c0/dep-lib-memchr","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
1eb2bdac55a78cc9

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[]","target":2092925202467021945,"profile":14448301111190550315,"path":4942398508502643691,"deps":[[8578586876803397814,"serde_json",false,5435485944888288525],[11023519408959114924,"getrandom",false,8549281822470616344],[17370996505528185903,"tiny_http",false,15952634872308680628],[18112009879309521262,"argon2",false,15542356301966651014]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/mrpgi-vault-67046901d5e81c19/dep-bin-mrpgi-vault","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[\"alloc\", \"default\", \"rand_core\"]","declared_features":"[\"alloc\", \"default\", \"getrandom\", \"rand_core\", \"std\"]","target":5392353821897108982,"profile":14448301111190550315,"path":1904482029795294681,"deps":[[5799347126265914943,"base64ct",false,5216475541427589564],[17003143334332120809,"subtle",false,7647771212883598475],[18130209639506977569,"rand_core",false,8529331815588514722]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/password-hash-cd42295ce7d7f61a/dep-lib-password_hash","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

View File

@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@ -0,0 +1 @@
a24b978f18465e76

View File

@ -0,0 +1 @@
{"rustc":11744402056482654987,"features":"[]","declared_features":"[\"alloc\", \"getrandom\", \"serde\", \"serde1\", \"std\"]","target":13770603672348587087,"profile":14448301111190550315,"path":11280912365008844905,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/rand_core-578e775640ec7aa3/dep-lib-rand_core","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}

Some files were not shown because too many files have changed in this diff Show More