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
impl HookResolver
Sourcepub fn new(config: HookConfig, game_dir: PathBuf, temp_dir: PathBuf) -> Self
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).
Sourcepub fn load(
mods_dir: &Path,
game_dir: PathBuf,
temp_dir: PathBuf,
) -> (Self, Vec<(String, HookConfigError)>)
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.
Sourcepub fn load_default(
game_dir: PathBuf,
temp_dir: PathBuf,
) -> (Self, Vec<(String, HookConfigError)>)
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).
Sourcepub fn config(&self) -> &HookConfig
pub fn config(&self) -> &HookConfig
The merged config this resolver was built from.
Sourcepub fn game_dir(&self) -> &Path
pub fn game_dir(&self) -> &Path
The game root directory this resolver reconstructs original paths against.
Sourcepub fn resolve_path(&self, rel_key: &str) -> ResolvedAction
pub fn resolve_path(&self, rel_key: &str) -> ResolvedAction
Resolves a game-relative path for READ operations.
Flow:
- Walk the redirect/patch chain (
Self::resolve_chain) to find the terminal physical file and the full accumulated patch list. - 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.
- If any accumulated patch is a
- 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.
Sourcepub fn should_hide_original_entry(&self, rel_key: &str) -> bool
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.
Sourcepub fn resolve_write_target(&self, rel_key: &str) -> Option<PathBuf>
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.
Sourcepub fn has_write_redirect(&self, rel_key: &str) -> bool
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.
Sourcepub fn resolve_path_for_write(&self, rel_key: &str) -> ResolvedAction
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.
Sourcepub fn resolve_path_for_delete(&self, rel_key: &str) -> ResolvedAction
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_errormode blocks the delete (consistent with hard_error’s “hide missing files” read contract);graceful_fallbackmode 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 whosemodeactually 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”.
Sourcepub fn collect_mod_files_in_dir(&self, rel_dir: &str) -> Vec<String>
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).