Skip to main content

GameStorage

Struct GameStorage 

Source
pub struct GameStorage { /* private fields */ }
Expand description

Provides a stateful API for game file management.

Implementations§

Source§

impl GameStorage

Source

pub fn discover_relative( &self, rel_path: &Path, mode: DiscoverMode, ) -> Result<Vec<PathBuf>>

Discover paths in a specific subdirectory relative to the root.

§Errors

Returns an error if the directory cannot be read.

Source

pub fn discover(&self, mode: DiscoverMode) -> Result<Vec<PathBuf>>

Discover all paths (files and directories) in the game directory not starting with ..

§Errors

Returns an error if the game directory cannot be read.

Source§

impl GameStorage

Source

pub fn read_bytes(&self, path: &Path) -> Result<Vec<u8>>

Read a game-relative file’s raw bytes, hook-aware.

§Errors

Returns an error if the resolved file cannot be read, or (in hook-aware mode) if a matched rule blocks the read, see GameStorage::resolve_read_path.

Source

pub fn write_bytes(&self, path: &Path, contents: &[u8]) -> Result<()>

Write a game-relative file’s raw bytes, hook-aware: transparently forwarded to wherever the active redirection rules say this path should be written, same as read_bytes reads it from.

§Errors

Returns an error if the resolved location cannot be written, or (in hook-aware mode) if a matched rule blocks the write, see GameStorage::resolve_write_path.

Source

pub fn read_text(&self, path: &Path) -> Result<String>

Read text file with Windows-1252 encoding (game-specific), hook-aware.

§Errors

Returns an error if the resolved file cannot be read, or (in hook-aware mode) if a matched rule blocks the read.

Source

pub fn write_text(&self, path: &Path, content: &str) -> Result<()>

Write text file with Windows-1252 encoding (game-specific), hook-aware: transparently forwarded per the active redirection rules, same as GameStorage::write_bytes.

§Errors

Returns an error if the resolved location cannot be written, or (in hook-aware mode) if a matched rule blocks the write.

Source

pub fn exists(&self, path: &Path) -> bool

True if path (relative to the game root) resolves to a file that actually exists, the mod’s replacement if one is active and present, the original otherwise. Never errors: a blocked (hard_error) resolution is reported as “does not exist”, matching what the game process would experience trying to open it.

Source

pub fn remove_file(&self, path: &Path) -> Result<()>

Removes a game-relative file, hook-aware: if a mod redirect (static replacement, or a file previously written into a mod’s target) is active for this path, that file is removed instead of the original.

§Errors

Returns an error if the resolved file cannot be removed, or (in hook-aware mode) if a matched rule is in hard_error mode with no hooked file present, the delete is blocked, same as the game process would see.

Source

pub fn list_files(&self, rel_dir: &Path) -> Result<Vec<String>>

Lists the file names directly under rel_dir (relative to the game root, non-recursive) as the game process would actually see them, hook-aware: real on-disk files are kept unless a matched mod rule hides/replaces them (per [supreme_hook_config::HookResolver::should_hide_original_entry]), and any file a mod injects into rel_dir is added (per [supreme_hook_config::HookResolver::collect_mod_files_in_dir]), the same two decisions the game process’s own directory-listing hook makes. With no resolver attached, this is just the plain names of the files physically present in rel_dir.

A missing rel_dir is not an error: it’s treated the same as an empty one, since a mod can still inject files into a directory that doesn’t exist on disk at all.

Result is sorted and deduplicated, so callers get a stable order regardless of the OS’s own directory-listing order or of a name appearing both physically and (redundantly) in a mod’s injection list.

§Errors

Returns an error if rel_dir exists but cannot be read.

Source§

impl GameStorage

Source

pub fn calculate_hash(&self, path: &Path) -> Result<String>

Calculate SHA256 hash of a file, for content-addressed storage.

§Errors

Returns an error if the file cannot be opened or read.

Source

pub fn list_snapshots_profiles(&self) -> Result<Vec<String>>

List all available snapshot profiles: finds and filters .json files.

§Errors

Returns an error if the profiles directory cannot be read.

Source

pub fn clean_up_empty_dirs(&self, start_dir: &Path, limit_dir: &Path)

Clean up empty directories recursively.

Source

pub fn write_report<T: Serialize>(&self, name: &str, value: &T) -> Result<()>

Write a JSON report to .supreme/{name}.json

§Errors

Returns an error if the report directory cannot be created or the file written.

Source

pub fn get_report_path(&self, name: &str) -> PathBuf

Get the path to a report file.

Source§

impl GameStorage

Source

pub fn auth_info(&self) -> Result<AuthInfo, HybridStorageError>

Ask the server where its auth page lives (auth_info). The page URL is the server’s own (like the results URL), so the launcher never has to guess how the backend is exposed to browsers.

§Errors

See GameStorage::get_auth_action; a backend predating the auth API answers a version-mismatch signature.

Source

pub fn auth_poll(&self, token: &str) -> Result<AuthPoll, HybridStorageError>

Poll the one-time link token (auth_poll). The token is 64 hex chars (the launcher generated it), safe in a URL unencoded.

§Errors

See GameStorage::get_auth_action.

Source

pub fn auth_account( &self, user_id: i64, auth_key: &str, ) -> Result<AccountDto, HybridStorageError>

Re-read the linked account through its device key (auth_account, GET with user_id + auth_key - the same pair the room pushes carry, so the pair is URL-safe by construction here too: a decimal id and a 64-hex key). The launcher caches the account record from link time while the nickname and the avatar are mutable; this is the refresh that follows a rename (and an avatar change) without a re-link - the server links uploads by the key’s identity, the pushed name is display only.

§Errors

See GameStorage::get_auth_action; a stale key answers the same refusal the room pushes give, verbatim.

Source

pub fn fetch_auth_avatar( &self, user_id: i64, version_sha: &str, ) -> Result<Vec<u8>, HybridStorageError>

Fetch one account’s avatar bytes (auth_avatar). version_sha is the account’s current avatar_sha256 (the v= param, an immutable-cache key: the same sha is always the same bytes); the server answers raw image bytes, never JSON.

§Errors

Transport failures and non-2xx responses are errors; a backend predating the action answers a version-mismatch signature.

Source

pub fn revoke_auth_key(&self, auth_key: &str) -> Result<(), HybridStorageError>

Revoke this device’s auth key (auth_revoke). Called on logout; the key dies server-side, a stolen copy of mods.json stops working. Fire-and-forget by design: a failed revoke (server gone, network down) never blocks the local logout, the key still expires server-side per the backend’s own policy.

§Errors

See GameStorage::post_room_action.

Source§

impl GameStorage

Source

pub fn fetch_contents_batch( &self, hashes: &[&str], ) -> Result<Vec<(String, Vec<u8>)>, HybridStorageError>

Source

pub fn get_content(&self, hash: &str) -> Result<Vec<u8>, HybridStorageError>

Fetch content by hash: the launcher-side local cache (.supreme/contents/<hash>[.xz]) first, then up to three remote attempts (with inter-attempt sleeps) that are saved into the cache, xz-decoded and hash-verified.

§Errors

Transport/decoding errors surface after the retries; offline storage without the hash cached returns HybridStorageError::NotFound.

Source

pub fn get_content_once( &self, hash: &str, ) -> Result<Vec<u8>, HybridStorageError>

GameStorage::get_content with a single remote attempt.

Identical semantics (local .xz/raw cache first, remote fetch saved into the cache, xz decoded, hash verified) minus the retry loop and its inter-attempt sleeps. For an embedded consumer whose fetches run on a latency-sensitive path (the hook DLL’s managed files do their cold-miss fetch on a game thread: bounded timeouts, degrade to vanilla on failure), the retry loop’s worst case (3 × global timeout + 1.5 s of sleeps) is not an acceptable stall, so it gets a one-shot variant and applies its own retry policy (or none) instead.

§Errors

Same error set as GameStorage::get_content, reached in one attempt instead of up to three.

Source

pub fn upload_content( &self, hash: &str, data: &[u8], ) -> Result<(), HybridStorageError>

Source§

impl GameStorage

Source

pub fn list_online_profiles( &self, ) -> Result<Vec<OnlineProfileInfo>, HybridStorageError>

List available profiles from the online server.

Source

pub fn sync_online_profiles( &self, ) -> Result<HashSet<String>, HybridStorageError>

Sync online profiles: downloads missing manifests and returns a set of filenames available online.

Source

pub fn get_profile( &self, profile_name: &str, ) -> Result<Vec<u8>, HybridStorageError>

Source§

impl GameStorage

Source

pub fn create_room( &self, env: &str, difficulty: &str, host: &str, name: &str, starts_at: i64, ends_at: i64, identity: Option<RoomIdentity<'_>>, ) -> Result<RoomDto, HybridStorageError>

Create a tournament room (room_create). The server generates the room id (r_ + 12 hex chars) and created_at.

the room has a planning window, starts_at (epoch seconds; may reach 13 h into the past, 7 days into the future) and ends_at (after starts_at, at most 7 days out; a host may not have two rooms sharing a UTC calendar day), and an optional display name (empty = the server picks a snowboarding word pair; a supplied name must pass the server’s blocklist).

identity attaches the logged-in account when present (the room becomes account-owned and verified); None creates as a guest.

§Errors

See GameStorage::post_room_action.

Source

pub fn list_rooms(&self) -> Result<Vec<RoomSummaryDto>, HybridStorageError>

List open rooms (room_list; the server only returns rooms whose ends_at is in the future).

§Errors

See GameStorage::post_room_action.

Source

pub fn get_room(&self, id: &str) -> Result<RoomDto, HybridStorageError>

Fetch full room state (room_get).

§Errors

See GameStorage::post_room_action.

Source

pub fn join_room( &self, id: &str, player: &str, identity: Option<RoomIdentity<'_>>, ) -> Result<RoomDto, HybridStorageError>

Join a room as player (room_join), with the logged-in account’s identity attached when present (a guest join stays None; the server refuses a guest whose name matches a registered nickname, that refusal arrives verbatim).

§Errors

See GameStorage::post_room_action.

Source

pub fn remove_room( &self, id: &str, player: &str, identity: Option<RoomIdentity<'_>>, ) -> Result<(), HybridStorageError>

Remove a room you created (room_remove). The server only accepts the request from the room’s creator, only while no other player has joined, and only BEFORE the room’s start time; the player identifies as the creator by player name (guest rooms) and/or the account identity when logged in. The room’s files are deleted server-side; the content-addressed blobs are shared and stay.

§Errors

See GameStorage::post_room_action; the server’s refusal messages (not the creator, players have joined, room already started, unknown room) arrive verbatim. A backend predating the action answers a version-mismatch signature.

Source

pub fn fetch_room_records( &self, room_id: &str, ) -> Result<Vec<RoomRecordDto>, HybridStorageError>

Fetch the best record per player (room_records, the DLL’s polling endpoint).

§Errors

See GameStorage::post_room_action.

Source

pub fn fetch_room_high_times( &self, room_id: &str, player: &str, ) -> Result<Option<(String, i64)>, HybridStorageError>

Fetch the server-rendered high-times file for the selected track’s room and player (room_high_times): the exact text the game’s Saved_Data/<env>_<diff>_high_times.txt should contain, the FULL room leaderboard (every record the server has, ascending, plus dummy padding only when fewer than five), so the game’s high-score screen shows everyone. Entry rank N pairs with Replay_N.dat for the game’s five replay slots.

Returns Ok(Some((text, entries))) on success (entries = the rendered entry count, diagnostics), and Ok(None) when the server predates the action (version mismatch signature), the caller falls back to local generation (high_times_entries_from_records).

§Errors

See GameStorage::post_room_action; all non-version-mismatch errors propagate.

Source

pub fn fetch_room_high_times_version( &self, room_id: &str, player: &str, ) -> Result<Option<String>, HybridStorageError>

Ask the server for the CONTENT HASH of the current high-times file for the selected track’s room and player (room_high_times_version): the sha-256 hex digest of the exact text GameStorage::fetch_room_high_times would return right now. The cheap half of the CAS copy rule, hash the local file, and only download the body when the digests differ.

Returns Ok(Some(version)) on success and Ok(None) when the server predates the action (version-mismatch signature), the caller then degrades to fetching the body and byte-comparing it against the local file, which avoids spurious writes on old servers too.

§Errors

See GameStorage::post_room_action; all non-version-mismatch errors propagate.

Source

pub fn upload_room_record( &self, room_id: &str, player: &str, time_ticks: u32, dat_path: &Path, splits_ms: Option<[i64; 2]>, ) -> Result<RoomUploadResult, HybridStorageError>

Upload a tournament record for player (room_upload_record).

time_ticks is the race time in the game’s 10 ms replay ticks. The .dat at dat_path is first pushed into the server CAS through the existing unified chunked upload, then claimed with room_upload_record {id, player, time_ticks, hash, splits_ms?}. splits_ms (whole-millisecond [checkpoint1_ms, checkpoint2_ms]) lets the server render the room’s high-times entries with real split times; None uploads the record without them (the server then falls back to finish-only splits).

accepted: false (worse or equal than the player’s existing personal best) is a successful RoomUploadResult, not an error.

§Errors

See GameStorage::post_room_action; also fails when time_ticks is 0 (the server would reject it and the blob upload would be wasted) or when reading dat_path fails.

Source§

impl GameStorage

Source

pub fn upload_profile( &self, profile_name: &str, data: &[u8], tx: Option<Sender<SnapshotEvent>>, ) -> Result<(), HybridStorageError>

Source

pub fn upload_replay( &self, snapshot_name: &str, data: &[u8], tx: Option<Sender<SnapshotEvent>>, ) -> Result<(), HybridStorageError>

Upload a replay snapshot (json file) to the backend.

Source

pub fn commit_snapshot( &self, snapshot_name: &str, ) -> Result<(), HybridStorageError>

Commit a staged snapshot.

Source

pub fn commit_snapshot_type( &self, subdirectory: &str, snapshot_filename_with_ext: &str, ) -> Result<(), HybridStorageError>

Source

pub fn fetch_replay( &self, snapshot_name: &str, ) -> Result<Vec<u8>, HybridStorageError>

Fetch a replay snapshot JSON by name. Tries local .supreme/replays first, then online at /replays/.

Source§

impl GameStorage

Source

pub fn get_max_upload_size(&self) -> Result<usize, HybridStorageError>

Get maximum upload size from server

Source

pub fn upload_file_unified( &self, subdirectory: &str, data: &[u8], file_type: &str, filename: Option<&str>, tx: Option<Sender<SnapshotEvent>>, ) -> Result<(), HybridStorageError>

Unified chunked upload for all files (single chunk for small files, multiple for large)

Source§

impl GameStorage

Source

pub fn new(game_path: &Path) -> Self

Create a new GameStorage instance for local-only storage, with no hook-awareness: read_text/write_text/etc. always operate on the literal path under game_path, exactly as before mod redirection existed. Use GameStorage::with_hooks for pages that need to see the game the way a mod-redirected game process would.

Source

pub fn with_hooks( game_path: &Path, mods_dir: &Path, temp_dir: PathBuf, ) -> (Self, Vec<(String, HookConfigError)>)

Create a GameStorage instance whose read_*/write_*/exists/ remove_file/list_files calls are routed through the same redirection rules the game process would apply at runtime, per mods.json and each enabled mod’s mod.json under mods_dir.

temp_dir is only used for staging patched replacement files (see [HookResolver::resolve_path]), most redirection rules never touch it. Writes always land on the original file or a mod’s own target, never in temp_dir.

Mods listed in mods.json whose mod.json couldn’t be read or parsed are skipped rather than failing the whole call; they’re returned alongside the instance so the caller can surface them (e.g. in the mod manager page) rather than have them silently dropped.

Source

pub fn is_hook_aware(&self) -> bool

True if this instance routes reads/writes through mod redirection rules (i.e. was built with GameStorage::with_hooks or GameStorage::with_hook_resolver).

Source

pub fn hook_config(&self) -> Option<&HookConfig>

The loaded hook resolver’s rule config, when hook-aware (see GameStorage::with_hook_resolver).

The launcher uses this to resolve a game-relative path to its redirect target with the exact same rule machinery the DLL applies at runtime (e.g. the tournament instance high_times file the selected track’s Saved_Data/…_high_times.txt rule points at), the launcher and the hook can then never disagree about where a redirected file lives.

Source

pub fn with_hook_resolver( self, mods_dir: &Path, temp_dir: PathBuf, ) -> (Self, Vec<(String, HookConfigError)>)

Attaches mod-redirection hook-awareness to an already-built GameStorage, e.g. one built with GameStorage::with_online, which (unlike GameStorage::new) has no hook-aware counterpart constructor of its own. Consumes and returns self so it composes with any other constructor rather than needing a dedicated combined one for every combination.

Same loading contract as GameStorage::with_hooks: mods listed in mods.json whose mod.json couldn’t be read or parsed are skipped rather than failing the whole call, and returned alongside the instance.

Source

pub fn with_online(game_path: &Path, api_url: &str) -> Self

Create a new GameStorage instance with online fallback support at api_url.

Source

pub fn with_online_timeouts( game_path: &Path, api_url: &str, timeout_connect: Duration, timeout_global: Duration, ) -> Self

GameStorage::with_online with explicit HTTP timeouts.

The launcher-facing [with_online] defaults (10 s connect / 300 s global) are right for interactive pages, but an embedded consumer, e.g. the tournament provider inside the hook DLL, whose cold-miss fetch runs on a game thread that must never stall for minutes, needs tighter bounds (bounded timeouts, blocking HTTP only on the cold path). Platform-neutral: only the ureq agent config differs.

Source

pub fn is_online(&self) -> bool

Check if online features are configured.

Source

pub fn clear_token(&mut self)

Clear the online token for this instance. Useful for testing unauthorized access.

Source

pub fn game_path(&self) -> &PathBuf

Get the root game path

Source

pub fn profiles_path(&self) -> PathBuf

Get the profiles directory path

Source

pub fn contents_path(&self) -> PathBuf

Get the contents directory path

Trait Implementations§

Source§

impl Clone for GameStorage

Source§

fn clone(&self) -> GameStorage

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,