From 3190100de8dd5429cd28f9ce02903ecef8deefdd Mon Sep 17 00:00:00 2001 From: Roman Tarasov Date: Mon, 24 Aug 2026 22:06:20 +0300 Subject: [PATCH] Keep fakelib2 exclusive from global and emulator overlays --- README.md | 10 ++++----- config.ini.example | 5 +++-- src/sm_fakelib.c | 53 ++++++++++++++++++++++++++++++---------------- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2e539f4..caaca71 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,11 @@ Supported keys (all optional): - `legacy_mount_pfsc=1|0` (outer `.ffpfsc`; `1` selects the version 1.6 mount profile; default: `1`) - `nested_pfs_index_cache=1|0` (cache the containing PFS compressed-file index before attaching a nested image in legacy mode; default: `0`; optimized mode always requests it; the old `legacy_gddr5_cache` name remains accepted) - `backport_fakelib=1|0` (`1` mounts sandbox `fakelib` overlays for running games; default: `1`) -- `update_emulators=1|0` (`1` updates all emulators with matching files in a game's own fakelib; default: `1`) +- `update_emulators=1|0` (`1` updates all emulators with matching files in a game's own fakelib; `fakelib2` is excluded; default: `1`) - `emulators_path=` (folder containing emulator update files; default: `/data/shadowmount/emus`) - `auto_update_ampr=1|0` (check for a new `libSceAmpr.sprx` 30 seconds after startup and every four hours; default: `0`) - `ampr_update_url=` (AMPR emulator download URL; default: `https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx`) -- `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; default: `1`) +- `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; `fakelib2` remains exclusive; default: `1`) - `global_fakelib_path=` (global fakelib folder; default: `/data/shadowmount/fakelib`) - `global_fakelib_priority=game|global` (overlay priority when both global and game fakelib exist; default: `game`) - `global_fakelib_exclude=` (repeatable; disables the global fakelib overlay for matching titles) @@ -187,11 +187,11 @@ Backport overlay behavior: - The `backports` folder is ignored during normal game scanning. - A backport is applied automatically to the matching mounted game from any configured scan path. - If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used. -- ShadowMount+ checks the selected backport for `fakelib2` and then `fakelib`; if neither exists, it uses only `fakelib` from the original game source. The selected directory is mounted into the running game's sandbox `common/lib`. -- If `update_emulators=1`, matching files from `emulators_path` replace files in the selected game fakelib. The cache is refreshed when its sources change and expires after seven days without a game launch. +- ShadowMount+ checks the selected backport for `fakelib2` and then `fakelib`; if neither exists, it uses only `fakelib` from the original game source. The selected directory is mounted into the running game's sandbox `common/lib`. A backport `fakelib2` is always mounted directly and exclusively: neither emulator updates nor the global fakelib can replace or supplement it. +- If `update_emulators=1`, matching files from `emulators_path` replace files in the selected game fakelib, except when `fakelib2` is selected. The cache is refreshed when its sources change and expires after seven days without a game launch. - With `auto_update_ampr=1`, ShadowMount+ checks for AMPR updates 30 seconds after startup and every four hours. It downloads a missing or newer emulator and displays a notification after a successful update. - The backport notification adds `Emulators updated` when emulator files are updated for the launched game. -- If both global and per-game fakelib exist, they are combined in the game cache according to `global_fakelib_priority`. Without a per-game fakelib, the global folder is mounted directly. +- If both global and per-game fakelib exist, they are combined in the game cache according to `global_fakelib_priority`, unless the selected backport contains `fakelib2`. Without a per-game fakelib, the global folder is mounted directly. - Use repeatable `global_fakelib_exclude=` entries to skip the global fakelib for specific games without disabling per-game fakelib. - `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib and emulator updates. - For `backport_fakelib` to work correctly, the standalone `BackPork` payload must be disabled. Running both at the same time will conflict. diff --git a/config.ini.example b/config.ini.example index 741c8c0..c5d7a9b 100644 --- a/config.ini.example +++ b/config.ini.example @@ -135,7 +135,7 @@ # Updates for multiple emulators in the game fakelib: # 1/true/yes/on -> cache the game's fakelib before launch and replace matching -# cached files from emulators_path +# cached files from emulators_path; fakelib2 is never changed # 0/false/no/off -> disable emulator updates # Default: 1 # update_emulators=1 @@ -159,7 +159,8 @@ # ampr_update_url=https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx # Global fakelib overlay: -# 1/true/yes/on -> use global fakelib for every tracked game when present +# 1/true/yes/on -> use global fakelib for every tracked game when present; +# fakelib2 always remains exclusive when present # 0/false/no/off -> do not use the global fakelib folder # Default: 1 # global_fakelib=1 diff --git a/src/sm_fakelib.c b/src/sm_fakelib.c index 7b90993..270953f 100644 --- a/src/sm_fakelib.c +++ b/src/sm_fakelib.c @@ -67,6 +67,12 @@ typedef struct { char emulators_path[MAX_PATH]; } fakelib_cache_context_t; +typedef enum { + FAKELIB_SOURCE_NONE = 0, + FAKELIB_SOURCE_COMPOSABLE, + FAKELIB_SOURCE_FAKELIB2, +} fakelib_source_kind_t; + static fakelib_session_t g_fakelib_mount; static pthread_mutex_t g_fakelib_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t g_fakelib_cache_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -207,7 +213,7 @@ static bool resolve_sandbox_mount_path(const char *title_id, return found; } -static bool resolve_game_fakelib_source_for_path( +static fakelib_source_kind_t resolve_game_fakelib_source_for_path( const char *title_id, const char *game_path, char source_path[MAX_PATH]) { source_path[0] = '\0'; @@ -219,10 +225,13 @@ static bool resolve_game_fakelib_source_for_path( const struct { const char *root; const char *directory; + fakelib_source_kind_t kind; } candidates[] = { - {has_backport ? backport_path : NULL, "fakelib2"}, - {has_backport ? backport_path : NULL, "fakelib"}, - {has_game ? game_path : NULL, "fakelib"}, + {has_backport ? backport_path : NULL, "fakelib2", + FAKELIB_SOURCE_FAKELIB2}, + {has_backport ? backport_path : NULL, "fakelib", + FAKELIB_SOURCE_COMPOSABLE}, + {has_game ? game_path : NULL, "fakelib", FAKELIB_SOURCE_COMPOSABLE}, }; struct stat st; for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { @@ -232,16 +241,17 @@ static bool resolve_game_fakelib_source_for_path( candidates[i].root, candidates[i].directory); if (written > 0 && (size_t)written < MAX_PATH && stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) { - return true; + return candidates[i].kind; } } source_path[0] = '\0'; - return false; + return FAKELIB_SOURCE_NONE; } -static bool resolve_game_fakelib_source(const char *title_id, - char source_path[MAX_PATH]) { +static fakelib_source_kind_t +resolve_game_fakelib_source(const char *title_id, + char source_path[MAX_PATH]) { char game_path[MAX_PATH]; if (!read_mount_link(title_id, game_path, sizeof(game_path))) game_path[0] = '\0'; @@ -994,8 +1004,9 @@ static void prepare_title_cache(const char *title_id, const char *game_path) { return; char game_source_path[MAX_PATH]; - if (!resolve_game_fakelib_source_for_path(title_id, game_path, - game_source_path)) { + fakelib_source_kind_t source_kind = resolve_game_fakelib_source_for_path( + title_id, game_path, game_source_path); + if (source_kind != FAKELIB_SOURCE_COMPOSABLE) { remove_title_cache(title_id); return; } @@ -1151,12 +1162,16 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id, } } - char game_source_path[MAX_PATH]; - char global_source_path[MAX_PATH]; - char mount_path[MAX_PATH]; - bool has_game = resolve_game_fakelib_source(title_id, game_source_path); - bool has_global = - resolve_global_fakelib_source(title_id, global_source_path); + char game_source_path[MAX_PATH] = {0}; + char global_source_path[MAX_PATH] = {0}; + char mount_path[MAX_PATH] = {0}; + fakelib_source_kind_t source_kind = + resolve_game_fakelib_source(title_id, game_source_path); + bool has_game = source_kind != FAKELIB_SOURCE_NONE; + bool allows_composition = source_kind != FAKELIB_SOURCE_FAKELIB2; + bool has_global = allows_composition && + resolve_global_fakelib_source(title_id, + global_source_path); if (!has_global && !has_game) { pthread_mutex_unlock(&g_fakelib_mutex); return; @@ -1168,7 +1183,7 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id, size_t emulator_file_count = 0; bool cache_resolved = false; bool cache_includes_global = false; - if (has_game) { + if (source_kind == FAKELIB_SOURCE_COMPOSABLE) { char cache_path[MAX_PATH]; cache_resolved = resolve_cached_fakelib( title_id, game_source_path, cache_path, &emulator_file_count, @@ -1198,7 +1213,9 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id, sizeof(g_fakelib_mount.mount_path)); const char *source_path = has_game ? game_source_path : global_source_path; - const char *label = has_game ? "game" : "global"; + const char *label = !allows_composition + ? "fakelib2" + : (has_game ? "game" : "global"); if (!track_fakelib_overlay(title_id, source_path, mount_path, label)) { (void)cleanup_fakelib_mount(); pthread_mutex_unlock(&g_fakelib_mutex);