Skip to main content

HookResolver

Struct HookResolver 

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

Owns a merged HookConfig plus the two directories every resolution needs: the game’s root directory (to reconstruct original paths) and a scratch directory (to stage patched replacement files).

Implementations§

Source§

impl HookResolver

Source

pub fn new(config: HookConfig, game_dir: PathBuf, temp_dir: PathBuf) -> Self

Builds a resolver directly from an already-loaded HookConfig. Prefer HookResolver::load unless you’ve already got a HookConfig from somewhere else (e.g. a launcher page previewing a config that hasn’t been written to mods_dir yet).

Source

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

Loads and merges every enabled mod’s rules from mods_dir (see HookConfig::load_from_mods_dir), rewriting every rule’s relative target to be absolute against game_dir, and wraps the result in a resolver ready to use.

Skipped mods (present in the active preset’s enabled list but whose mod.json couldn’t be read/parsed) are returned alongside the resolver rather than logged here, this crate takes no logging dependency beyond the log facade, so it’s the caller’s call how (or whether) to surface them.

A missing mods.json is treated as “no mods enabled” rather than a fatal error, the resolver falls back to an empty rule set and every resolution becomes a no-op PassThrough.

Source

pub fn load_default( game_dir: PathBuf, temp_dir: PathBuf, ) -> (Self, Vec<(String, HookConfigError)>)

Convenience wrapper around HookResolver::load using the conventional <game_dir>/.supreme/mods mods directory (DEFAULT_MODS_DIR_NAME).

Source

pub fn config(&self) -> &HookConfig

The merged config this resolver was built from.

Source

pub fn game_dir(&self) -> &Path

The game root directory this resolver reconstructs original paths against.

Source

pub fn resolve_path(&self, rel_key: &str) -> ResolvedAction

Resolves a game-relative path for READ operations.

Flow:

  1. Walk the redirect/patch chain (Self::resolve_chain) to find the terminal physical file and the full accumulated patch list.
  2. If patches exist, stage to a structured, persistent temp path.
    • If any accumulated patch is a MemoryPatch::Script, always re-read the base file and re-run every patch as script can derive its output from the current time variable, so it must run fresh on every read rather than being served from a stale cached stage.
    • Otherwise, if the temp file already exists and is newer than the base file, reuse it; otherwise read base + apply patches + write to the structured temp path.
  3. If no patches, return the resolved file directly.

Not cached beyond the temp-file reuse above: external writers may change the underlying files at any time, so every call re-evaluates filesystem state.

Source

pub fn should_hide_original_entry(&self, rel_key: &str) -> bool

Whether a directory listing should hide the original game file for rel_key, so that listings agree with what a direct open of the same rel_key would do (resolve_path): a hard_error rule hides the original unconditionally, the moment it matches, exactly as it would on a read, it never falls back to showing stale original content just because nothing’s been staged in its place yet. A graceful_fallback rule only hides the original once a real replacement exists on disk to show instead; until then the original stays visible, same as a read would fall back to serving it.

Deliberately uses only the pure decision layer (Self::resolve_chain, existence checks only, no I/O writes) rather than the full resolve_path, a directory listing walks every entry in the directory, and resolve_path stages patched files to disk on demand, which would mean every entry in a patched directory gets eagerly (re-)staged just to decide whether to show it. Patch staging stays lazy, deferred to the first time a file is actually opened.

Source

pub fn resolve_write_target(&self, rel_key: &str) -> Option<PathBuf>

Resolves the physical target path for a rel_key destined for a write-type operation, ensuring its parent directory exists. Returns None if no rule matches (at any stage of the chain), the directory couldn’t be created, or the target is the original file. Call Self::has_write_redirect to distinguish an unredirected path from a configured target that could not be prepared.

Source

pub fn has_write_redirect(&self, rel_key: &str) -> bool

Whether a write rule has a target, even when its directory cannot be prepared. A rename must fail instead of falling through to the unmodified game path in that case.

Source

pub fn resolve_path_for_write(&self, rel_key: &str) -> ResolvedAction

Resolves a game-relative path for WRITE/CREATE/OVERWRITE operations.

Never opens the original game file in write mode: if the target doesn’t exist yet, it’s seeded by copying the original first (see seed_target_file). Patches are not applied for writes, they’re a read-time transform.

Source

pub fn resolve_path_for_delete(&self, rel_key: &str) -> ResolvedAction

Resolves a game-relative path for DELETE operations.

  • If a hooked file exists at the end of the redirect chain (static config replacement, or a file previously written/created into a target), the delete is redirected there.
  • If no hooked file exists: the last rule reached while walking the chain decides the fallback, hard_error mode blocks the delete (consistent with hard_error’s “hide missing files” read contract); graceful_fallback mode falls back to the original. This is deliberately the last rule reached, not the first one matched, a delete on a path that was redirected one or more times follows the chain to its end, same as a read or a write would, and it’s the rule governing that terminal stage whose mode actually applies.

Always re-checks the filesystem fresh, same as resolve_path. Patches are irrelevant here.

Deliberately does not consult HookConfig::resolve the way resolve_path does: a delete must land on exactly the file the create wrote, so both share one suffix implementation (compute_target_path, via [crate::matched_suffix]) as the single source of truth for “where does this rel_key’s file live”.

Source

pub fn collect_mod_files_in_dir(&self, rel_dir: &str) -> Vec<String>

Collects mod filenames directly under a given relative directory, by scanning each matching wildcard rule’s target directory on disk, plus injecting the single file named by any matching literal-glob rule (whose target is a file, not a directory to scan, see RedirectionRule::is_literal_glob).

Trait Implementations§

Source§

impl Debug for HookResolver

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. 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> 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, 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,