Compare commits

..
6 Commits
Author SHA1 Message Date
Drakmor 3530d8d88e Merge pull request #107 from blackbearreloaded/main
Add support to auto load and register FAKE title IDs
2026-08-28 21:37:40 +03:00
BlackBearReloaded 4dafdde002 lifecycle: support FAKE title IDs 2026-08-27 22:35:00 -04:00
Roman Tarasov 8566c0294c Revert "Revert "Attempting to restore occupied mount points for nullfs""
This reverts commit 16873e01f2.
2026-06-28 16:53:42 +03:00
Roman Tarasov 56dd338abd Prefer fakelib2 over fakelib for game overlays
Check app0/fakelib2 before resolving the legacy per-game fakelib
source. Fall back to app0/fakelib when fakelib2 is missing or is
not a directory.
2026-06-28 16:49:30 +03:00
Drakmor 090723879c Update README.md 2026-06-07 02:43:46 +03:00
Roman Tarasov 16873e01f2 Revert "Attempting to restore occupied mount points for nullfs"
This reverts commit c41c3eeba8.
2026-06-03 21:30:59 +03:00
7 changed files with 34 additions and 88 deletions

No files matched your search

+2 -4
View File
@@ -61,8 +61,6 @@ Supported keys (all optional):
- `image_rw=<image_filename>` (repeatable; force read-write mode for this image filename)
- `image_sector=<image_filename>:<sector_size>` (repeatable; force sector size for this image filename)
- `scan_depth=<1..2>` (`1` = scan only first-level subfolders, `2` = also scan one additional nested level; default: `1`)
- `watch_subdirs=1|0` (`1` keeps vnode watchers on scan root subdirectories; default: `0`)
- `watch_image_files=1|0` (`1` keeps vnode watchers on discovered image files; default: `1`)
- `recursive_scan=1|0` (deprecated compatibility key; `1` forces `scan_depth=2`)
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `15`)
- `stability_wait_seconds=<0..3600>` (minimum source age before processing; default: `10`)
@@ -126,7 +124,7 @@ 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.
- If `/mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`.
- If `/mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib2` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`; otherwise it falls back to `app0/fakelib`.
- If `global_fakelib=1` and `global_fakelib_path` exists as a directory, ShadowMount+ also mounts that folder into the same sandbox `common/lib`.
- When both global and per-game fakelib exist, the default gives the game's own `fakelib` priority by mounting `/data/shadowmount/fakelib` first and then the game-specific fakelib.
- `global_fakelib_priority=global` reverses that priority.
@@ -209,7 +207,7 @@ mkpfs pack folder --verify --no-compress --no-adjust-output-file-extension --ver
'./pfs_image.dat'
```
Then pack the nested image into a compressed PFS container:
Then pack the nested image (**pfs_image.dat**) into a compressed PFS container:
```bash
mkpfs pack file --verify --version PS5 --inode-bits 32 \
+1 -13
View File
@@ -45,18 +45,6 @@
# Default: 1
# scan_depth=1
# Directory watcher granularity:
# 1 -> keep vnode watchers on scan root subdirectories
# 0 -> watch only scan roots/parents and rely on targeted/full scans for children
# Default: 0
# watch_subdirs=0
# Image file watcher granularity:
# 1 -> keep vnode watchers on discovered image files
# 0 -> detect image file changes through targeted/full scans
# Default: 1
# watch_image_files=1
# Legacy compatibility:
# recursive_scan=1 forces scan_depth=2
# Default: disabled
@@ -79,7 +67,7 @@
# ufs_backend=lvd
# Sandbox fakelib backport watcher:
# 1/true/yes/on -> mount /mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib into common/lib
# 1/true/yes/on -> mount app0/fakelib2 when present, otherwise app0/fakelib, into common/lib
# 0/false/no/off -> disable fakelib watcher
# Default: 1
# backport_fakelib=1
-2
View File
@@ -116,8 +116,6 @@ typedef struct runtime_config {
bool kstuff_game_auto_toggle;
bool kstuff_crash_detection_enabled;
bool legacy_recursive_scan_forced;
bool watch_subdirs;
bool watch_image_files;
char global_fakelib_path[MAX_PATH];
uint32_t global_fakelib_exclude_title_count;
char global_fakelib_exclude_title_ids[MAX_FAKELIB_EXCLUDE_RULES][MAX_TITLE_ID];
-23
View File
@@ -257,8 +257,6 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
state->cfg.kstuff_game_auto_toggle = true;
state->cfg.kstuff_crash_detection_enabled = true;
state->cfg.legacy_recursive_scan_forced = false;
state->cfg.watch_subdirs = false;
state->cfg.watch_image_files = true;
(void)strlcpy(state->cfg.global_fakelib_path, DEFAULT_GLOBAL_FAKELIB_PATH,
sizeof(state->cfg.global_fakelib_path));
state->cfg.scan_depth = DEFAULT_SCAN_DEPTH;
@@ -1243,24 +1241,6 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
continue;
}
if (strcasecmp(key, "watch_subdirs") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
continue;
}
state->cfg.watch_subdirs = bval;
continue;
}
if (strcasecmp(key, "watch_image_files") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
continue;
}
state->cfg.watch_image_files = bval;
continue;
}
if (strcasecmp(key, "backport_fakelib") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
@@ -1508,7 +1488,6 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
"legacy_recursive_scan_forced=%d backport_fakelib=%d "
"global_fakelib=%d global_fakelib_priority=%s "
"global_fakelib_path=%s global_fakelib_exclude=%u "
"watch_subdirs=%d watch_image_files=%d "
"kstuff_game_auto_toggle=%d kstuff_crash_detection=%d "
"kstuff_pause_delay_image_s=%u kstuff_pause_delay_direct_s=%u "
"exfat_backend=%s ufs_backend=%s "
@@ -1526,8 +1505,6 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
state->cfg.global_fakelib_mount_first ? "game" : "global",
state->cfg.global_fakelib_path,
state->cfg.global_fakelib_exclude_title_count,
state->cfg.watch_subdirs ? 1 : 0,
state->cfg.watch_image_files ? 1 : 0,
state->cfg.kstuff_game_auto_toggle ? 1 : 0,
state->cfg.kstuff_crash_detection_enabled ? 1 : 0,
state->cfg.kstuff_pause_delay_image_seconds,
+8 -2
View File
@@ -125,11 +125,17 @@ static bool resolve_sandbox_context(const char *title_id,
return false;
char source_path[MAX_PATH];
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib",
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib2",
sandbox_id);
struct stat st;
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode))
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) {
(void)strlcpy(game_source_path, source_path, MAX_PATH);
} else {
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib",
sandbox_id);
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode))
(void)strlcpy(game_source_path, source_path, MAX_PATH);
}
char sandbox_root[MAX_PATH];
snprintf(sandbox_root, sizeof(sandbox_root), "/mnt/sandbox/%s", sandbox_id);
+3 -1
View File
@@ -78,7 +78,9 @@ static bool consume_active_game_title(pid_t pid,
}
static bool is_supported_title_id(const char *title_id) {
return strncmp(title_id, "PPSA", 4) == 0 || strncmp(title_id, "CUSA", 4) == 0;
return strncmp(title_id, "PPSA", 4) == 0 ||
strncmp(title_id, "CUSA", 4) == 0 ||
strncmp(title_id, "FAKE", 4) == 0;
}
static uint64_t min_nonzero_u64(uint64_t a, uint64_t b) {
+20 -43
View File
@@ -240,12 +240,7 @@ static bool fakelib_runtime_config_changed(const runtime_config_t *old_cfg,
static uint32_t scanner_config_topology_hash(void) {
uint32_t hash = 2166136261u;
int scan_path_count = get_scan_path_count();
uint32_t values[] = {
runtime_config()->scan_depth,
runtime_config()->watch_subdirs ? 1u : 0u,
runtime_config()->watch_image_files ? 1u : 0u,
(uint32_t)scan_path_count,
};
uint32_t values[] = {runtime_config()->scan_depth, (uint32_t)scan_path_count};
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
for (unsigned shift = 0; shift < 32u; shift += 8u) {
@@ -589,7 +584,6 @@ static bool resolve_watch_tree_rebuild_target(const scanner_watch_entry_t *entry
typedef struct {
int kq;
int scan_root_index;
bool watch_subdirs;
} register_watch_tree_ctx_t;
static sm_scan_tree_dir_visit_t register_watch_directory_visit(
@@ -598,11 +592,9 @@ static sm_scan_tree_dir_visit_t register_watch_directory_visit(
scanner_watch_kind_t kind =
(depth_from_root == 0u) ? SCANNER_WATCH_SCAN_ROOT
: SCANNER_WATCH_SCAN_SUBDIR;
if (depth_from_root == 0u || ctx->watch_subdirs) {
if (!register_scanner_watch_entry(ctx->kq, ctx->scan_root_index, dir_path,
kind, (uint8_t)depth_from_root)) {
return SM_SCAN_TREE_DIR_ABORT;
}
if (!register_scanner_watch_entry(ctx->kq, ctx->scan_root_index, dir_path, kind,
(uint8_t)depth_from_root)) {
return SM_SCAN_TREE_DIR_ABORT;
}
return SM_SCAN_TREE_DIR_DESCEND;
@@ -645,28 +637,19 @@ static bool rebuild_scan_root_watch_tree(int kq, int scan_root_index) {
return true;
}
const runtime_config_t *cfg = runtime_config();
if (!cfg->watch_subdirs && !cfg->watch_image_files) {
if (!register_scanner_watch_entry(kq, scan_root_index, scan_root,
SCANNER_WATCH_SCAN_ROOT, 0u)) {
return false;
}
} else {
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.watch_subdirs = cfg->watch_subdirs,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file =
cfg->watch_image_files ? register_watch_image_visit : NULL,
};
if (!sm_scan_tree_walk(scan_root, scan_root, 0u, scan_depth, &callbacks,
&walk_ctx)) {
return false;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = register_watch_image_visit,
};
if (!sm_scan_tree_walk(scan_root, scan_root, 0u, scan_depth, &callbacks,
&walk_ctx)) {
return false;
}
char backport_root[MAX_PATH];
@@ -707,15 +690,10 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (!remove_scan_root_watch_entries_for_path(scan_root_index, rebuild_path))
return false;
const runtime_config_t *cfg = runtime_config();
if (!cfg->watch_subdirs && !cfg->watch_image_files) {
clear_scan_root_watch_tree_state(scan_root_index);
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (rebuild_depth > scan_depth) {
clear_scan_root_watch_tree_state(scan_root_index);
return true;
@@ -724,11 +702,10 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.watch_subdirs = cfg->watch_subdirs,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = cfg->watch_image_files ? register_watch_image_visit : NULL,
.on_image_file = register_watch_image_visit,
};
if (!sm_scan_tree_walk(scan_root, rebuild_path, rebuild_depth,
scan_depth - rebuild_depth, &callbacks, &walk_ctx)) {