mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-21 14:12:29 -05:00
1.5beta6 Refactor image mount flow, add per-image RO/RW overrides, and improve mount error reporting
- Rename mount base from /data/ufsmnt to /data/imgmnt - Add per-image mount mode overrides via config: image_ro=<filename>, image_rw=<filename> - Improve mount failure notifications. - Optimize stale-link recovery path.
This commit is contained in:
1 parent
32ae0c9c50
commit
3f81ae589f
3 files changed
+280
-117
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# ShadowMountPlus (PS5)
|
||||
|
||||
**Version:** `1.5beta5`
|
||||
**Version:** `1.5beta6`
|
||||
|
||||
**Repository:** https://github.com/drakmor/shadowMountPlus
|
||||
|
||||
@@ -40,6 +40,8 @@ This file is optional. If it does not exist, built-in defaults are used.
|
||||
Supported keys (all optional):
|
||||
- `debug=1|0` (`1` enables `log_debug` output to console + `/data/shadowmount/debug.log`; default is `1`)
|
||||
- `mount_read_only=1|0` (default: `1`)
|
||||
- `image_ro=<image_filename>` (repeatable; force read-only mode for this image filename)
|
||||
- `image_rw=<image_filename>` (repeatable; force read-write mode for this image filename)
|
||||
- `recursive_scan=1|0` (`0` = scan only first-level subfolders, `1` = recursive scan without depth limit; default: `0`)
|
||||
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `10`)
|
||||
- `stability_wait_seconds=<0..3600>` (minimum source age before processing; default: `10`)
|
||||
@@ -52,9 +54,22 @@ Supported keys (all optional):
|
||||
- `md_exfat_sector_size=<value>` (default: `512`)
|
||||
- `md_ufs_sector_size=<value>` (default: `512`)
|
||||
|
||||
Per-image mode override behavior:
|
||||
- Match is done by image file name (without path).
|
||||
- File names with spaces are supported.
|
||||
- If multiple rules target the same file name, the last one in config wins.
|
||||
- If no rule matches, global `mount_read_only` is used.
|
||||
- Example:
|
||||
```ini
|
||||
mount_read_only=1
|
||||
image_rw=PPSA08766-Cocoon-01.004.000.ffpfs
|
||||
image_rw=DIRT 5.exfat
|
||||
image_ro=legacy_dump.ffpkg
|
||||
```
|
||||
|
||||
Scan path behavior:
|
||||
- If at least one `scanpath=...` is present, only those custom paths are used.
|
||||
- `/data/ufsmnt` is always added automatically, even with custom paths.
|
||||
- `/data/imgmnt` is always added automatically, even with custom paths.
|
||||
- With `recursive_scan=0` (default), only first-level subfolders are checked.
|
||||
- With `recursive_scan=1`, subfolders are scanned recursively.
|
||||
- Full scan loop runs every `scan_interval_seconds` (default: `10`).
|
||||
@@ -67,7 +82,7 @@ Validation:
|
||||
|
||||
Image mountpoints are created under:
|
||||
|
||||
`/data/ufsmnt/<image_name>-<fs_suffix>`
|
||||
`/data/imgmnt/<image_name>-<fs_suffix>`
|
||||
|
||||
Image layout requirement (`.ffpkg`, `.exfat`, `.ffpfs`):
|
||||
- Game files must be placed at the image root.
|
||||
@@ -89,7 +104,7 @@ Default scan locations:
|
||||
- `/mnt/usb0` .. `/mnt/usb7`
|
||||
- `/mnt/ext0`
|
||||
- `/mnt/ext1`
|
||||
- `/data/ufsmnt` (mounted image content scan)
|
||||
- `/data/imgmnt` (mounted image content scan)
|
||||
|
||||
You can override scan roots with `scanpath=...` entries in `/data/shadowmount/config.ini`.
|
||||
|
||||
@@ -190,7 +205,7 @@ If a game is not mounted:
|
||||
- Check `/data/shadowmount/debug.log` and system notifications from ShadowMount+.
|
||||
- Verify scan roots:
|
||||
- if `scanpath=...` is set, only these paths are scanned;
|
||||
- `/data/ufsmnt` is always scanned.
|
||||
- `/data/imgmnt` is always scanned.
|
||||
- Verify scan depth:
|
||||
- `recursive_scan=0` scans only first-level subfolders;
|
||||
- `recursive_scan=1` scans recursively.
|
||||
@@ -198,7 +213,7 @@ If a game is not mounted:
|
||||
- Verify game structure:
|
||||
- folder game: `<GAME_DIR>/sce_sys/param.json`;
|
||||
- image game (`.ffpkg` / `.exfat` / `.ffpfs`): `sce_sys/param.json` must be at image root (no extra top-level folder).
|
||||
- If you see `missing/invalid param.json` for an image, check via FTP that `/data/ufsmnt/<TITLE_ID>/` contains full game files and `sce_sys/param.json`.
|
||||
- If you see `missing/invalid param.json` for an image, check via FTP that `/data/imgmnt/<TITLE_ID>/` contains full game files and `sce_sys/param.json`.
|
||||
- If you see image mount failure, check image integrity and filesystem type (`.ffpkg`=UFS, `.exfat`=exFAT, `.ffpfs`=PFS).
|
||||
- If you see duplicate titleId notification, keep only one source per `<TITLE_ID>`.
|
||||
|
||||
|
||||
+7
-1
@@ -14,6 +14,12 @@ debug=1
|
||||
# 0/false/no/off/rw -> read-write mounts
|
||||
mount_read_only=1
|
||||
|
||||
# Per-image mount mode override by file name (can be repeated many times):
|
||||
# image_ro=<image_filename>
|
||||
# image_rw=<image_filename>
|
||||
# image_ro=PPSA17221.ffpfs
|
||||
# image_rw=My Game Dump.exfat
|
||||
|
||||
# Recursive scan mode:
|
||||
# 1/true/yes/on -> scan subdirectories recursively (no depth limit)
|
||||
# 0/false/no/off -> scan only first-level subdirectories (default)
|
||||
@@ -41,6 +47,6 @@ ufs_backend=lvd
|
||||
|
||||
# Optional custom scan roots (can be repeated):
|
||||
# If at least one scanpath=... is present, only these paths are used.
|
||||
# "/data/ufsmnt" is always added automatically.
|
||||
# "/data/imgmnt" is always added automatically.
|
||||
# scanpath=/data/homebrew
|
||||
# scanpath=/data/etaHEN/games
|
||||
+252
-110
@@ -30,7 +30,8 @@
|
||||
#define DEFAULT_SCAN_INTERVAL_US 10000000u
|
||||
#define DEFAULT_STABILITY_WAIT_SECONDS 10u
|
||||
#define MAX_PENDING 512
|
||||
#define MAX_UFS_MOUNTS 64
|
||||
#define MAX_IMAGE_MOUNTS 64
|
||||
#define MAX_IMAGE_MODE_RULES 128
|
||||
#define PATH_STATE_CAPACITY MAX_PENDING
|
||||
#define TITLE_STATE_CAPACITY MAX_PENDING
|
||||
#define STATE_HASH_SIZE 1024u
|
||||
@@ -50,8 +51,8 @@
|
||||
#define MAX_PATH 1024
|
||||
#define MAX_TITLE_ID 32
|
||||
#define MAX_TITLE_NAME 256
|
||||
#define SHADOWMOUNT_VERSION "1.5beta5"
|
||||
#define UFS_MOUNT_BASE "/data/ufsmnt"
|
||||
#define SHADOWMOUNT_VERSION "1.5beta6"
|
||||
#define IMAGE_MOUNT_BASE "/data/imgmnt"
|
||||
#define LOG_DIR "/data/shadowmount"
|
||||
#define LOG_FILE "/data/shadowmount/debug.log"
|
||||
#define LOG_FILE_PREV "/data/shadowmount/debug.log.1"
|
||||
@@ -227,8 +228,9 @@ bool is_data_mounted(const char *title_id);
|
||||
void notify_system(const char *fmt, ...);
|
||||
void log_debug(const char *fmt, ...);
|
||||
static void close_app_db(void);
|
||||
static void shutdown_ufs_mounts(void);
|
||||
static void shutdown_image_mounts(void);
|
||||
static void ensure_runtime_config_ready(void);
|
||||
static void cleanup_stale_image_mounts(void);
|
||||
|
||||
// Standard Notification
|
||||
typedef struct notify_request {
|
||||
@@ -357,10 +359,21 @@ static runtime_config_t g_runtime_cfg;
|
||||
static bool g_runtime_cfg_ready = false;
|
||||
static sqlite3 *g_app_db = NULL;
|
||||
static sqlite3_stmt *g_app_db_stmt_update_snd0 = NULL;
|
||||
static char g_last_image_mount_errmsg[256];
|
||||
static struct AppDbTitleList g_app_db_title_cache = {0};
|
||||
static bool g_app_db_title_cache_ready = false;
|
||||
static time_t g_app_db_title_cache_mtime = 0;
|
||||
static scan_candidate_t g_scan_candidates[MAX_PENDING];
|
||||
static char g_scan_discovered_param_roots[MAX_PENDING][MAX_PATH];
|
||||
|
||||
struct UfsCache {
|
||||
typedef struct {
|
||||
char filename[MAX_PATH];
|
||||
bool mount_read_only;
|
||||
bool valid;
|
||||
} image_mode_rule_t;
|
||||
static image_mode_rule_t g_image_mode_rules[MAX_IMAGE_MODE_RULES];
|
||||
|
||||
struct ImageCache {
|
||||
// Absolute source image path.
|
||||
char path[MAX_PATH];
|
||||
// Attached unit id (lvdN/mdN), -1 when unknown.
|
||||
@@ -370,7 +383,7 @@ struct UfsCache {
|
||||
// Slot occupancy flag.
|
||||
bool valid;
|
||||
};
|
||||
struct UfsCache ufs_cache[MAX_UFS_MOUNTS];
|
||||
struct ImageCache image_cache[MAX_IMAGE_MOUNTS];
|
||||
|
||||
static volatile sig_atomic_t g_stop_requested = 0;
|
||||
|
||||
@@ -432,7 +445,16 @@ static void init_runtime_scan_paths_defaults(void) {
|
||||
for (int i = 0; DEFAULT_SCAN_PATHS[i] != NULL; i++)
|
||||
(void)add_runtime_scan_path(DEFAULT_SCAN_PATHS[i]);
|
||||
// UFS mount root must always be present.
|
||||
(void)add_runtime_scan_path(UFS_MOUNT_BASE);
|
||||
(void)add_runtime_scan_path(IMAGE_MOUNT_BASE);
|
||||
}
|
||||
|
||||
static const char *get_filename_component(const char *path) {
|
||||
if (!path)
|
||||
return "";
|
||||
const char *base = strrchr(path, '/');
|
||||
if (!base)
|
||||
base = strrchr(path, '\\');
|
||||
return base ? base + 1 : path;
|
||||
}
|
||||
|
||||
static void cache_game_entry(const char *path, const char *title_id,
|
||||
@@ -448,12 +470,12 @@ static void cache_game_entry(const char *path, const char *title_id,
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_under_ufs_mount_base(const char *path) {
|
||||
static bool is_under_image_mount_base(const char *path) {
|
||||
if (!path)
|
||||
return false;
|
||||
size_t ufs_prefix_len = strlen(UFS_MOUNT_BASE);
|
||||
return (strncmp(path, UFS_MOUNT_BASE, ufs_prefix_len) == 0 &&
|
||||
path[ufs_prefix_len] == '/');
|
||||
size_t image_prefix_len = strlen(IMAGE_MOUNT_BASE);
|
||||
return (strncmp(path, IMAGE_MOUNT_BASE, image_prefix_len) == 0 &&
|
||||
path[image_prefix_len] == '/');
|
||||
}
|
||||
|
||||
// --- Fast Path/Title State Cache (hash indexed) ---
|
||||
@@ -696,7 +718,7 @@ static void notify_duplicate_title_once(const char *title_id, const char *path_a
|
||||
}
|
||||
|
||||
static bool is_missing_param_scan_limited(const char *path) {
|
||||
if (!is_under_ufs_mount_base(path))
|
||||
if (!is_under_image_mount_base(path))
|
||||
return false;
|
||||
struct PathStateEntry *entry = find_path_state(path);
|
||||
if (!entry)
|
||||
@@ -705,7 +727,7 @@ static bool is_missing_param_scan_limited(const char *path) {
|
||||
}
|
||||
|
||||
static void record_missing_param_failure(const char *path) {
|
||||
if (!is_under_ufs_mount_base(path))
|
||||
if (!is_under_image_mount_base(path))
|
||||
return;
|
||||
|
||||
struct PathStateEntry *entry = get_or_create_path_state(path);
|
||||
@@ -787,22 +809,22 @@ static void clear_image_mount_attempts(const char *path) {
|
||||
}
|
||||
|
||||
// --- Active Mount Session Caches ---
|
||||
static void cache_ufs_mount(const char *path, int unit_id,
|
||||
static void cache_image_mount(const char *path, int unit_id,
|
||||
attach_backend_t backend) {
|
||||
for (int k = 0; k < MAX_UFS_MOUNTS; k++) {
|
||||
if (ufs_cache[k].valid && strcmp(ufs_cache[k].path, path) == 0) {
|
||||
ufs_cache[k].unit_id = unit_id;
|
||||
ufs_cache[k].backend = backend;
|
||||
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
|
||||
if (image_cache[k].valid && strcmp(image_cache[k].path, path) == 0) {
|
||||
image_cache[k].unit_id = unit_id;
|
||||
image_cache[k].backend = backend;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < MAX_UFS_MOUNTS; k++) {
|
||||
if (!ufs_cache[k].valid) {
|
||||
(void)strlcpy(ufs_cache[k].path, path, sizeof(ufs_cache[k].path));
|
||||
ufs_cache[k].unit_id = unit_id;
|
||||
ufs_cache[k].backend = backend;
|
||||
ufs_cache[k].valid = true;
|
||||
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
|
||||
if (!image_cache[k].valid) {
|
||||
(void)strlcpy(image_cache[k].path, path, sizeof(image_cache[k].path));
|
||||
image_cache[k].unit_id = unit_id;
|
||||
image_cache[k].backend = backend;
|
||||
image_cache[k].valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -897,6 +919,17 @@ void notify_system(const char *fmt, ...) {
|
||||
log_debug("NOTIFY: %s", req.message);
|
||||
}
|
||||
|
||||
static void notify_image_mount_failed(const char *path, int mount_err) {
|
||||
if (g_last_image_mount_errmsg[0] != '\0') {
|
||||
notify_system("Image mount failed: 0x%08X (%s)\n%s\n%s",
|
||||
(uint32_t)mount_err, strerror(mount_err),
|
||||
g_last_image_mount_errmsg, path);
|
||||
return;
|
||||
}
|
||||
notify_system("Image mount failed: 0x%08X (%s)\n%s", (uint32_t)mount_err,
|
||||
strerror(mount_err), path);
|
||||
}
|
||||
|
||||
void trigger_rich_toast(const char *title_id, const char *game_name,
|
||||
const char *msg) {
|
||||
FILE *f = fopen(TOAST_FILE, "w");
|
||||
@@ -921,13 +954,12 @@ bool is_data_mounted(const char *title_id) {
|
||||
return (access(path, F_OK) == 0);
|
||||
}
|
||||
|
||||
static bool read_mount_link(const char *title_id, char *out, size_t out_size) {
|
||||
if (out_size == 0)
|
||||
static bool read_mount_link_file(const char *lnk_path, char *out,
|
||||
size_t out_size) {
|
||||
if (!lnk_path || out_size == 0)
|
||||
return false;
|
||||
out[0] = '\0';
|
||||
|
||||
char lnk_path[MAX_PATH];
|
||||
snprintf(lnk_path, sizeof(lnk_path), "/user/app/%s/mount.lnk", title_id);
|
||||
FILE *f = fopen(lnk_path, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
@@ -944,6 +976,16 @@ static bool read_mount_link(const char *title_id, char *out, size_t out_size) {
|
||||
return out[0] != '\0';
|
||||
}
|
||||
|
||||
static bool read_mount_link(const char *title_id, char *out, size_t out_size) {
|
||||
if (out_size == 0)
|
||||
return false;
|
||||
out[0] = '\0';
|
||||
|
||||
char lnk_path[MAX_PATH];
|
||||
snprintf(lnk_path, sizeof(lnk_path), "/user/app/%s/mount.lnk", title_id);
|
||||
return read_mount_link_file(lnk_path, out, out_size);
|
||||
}
|
||||
|
||||
static bool path_matches_root_or_child(const char *path, const char *root) {
|
||||
if (!path || !root || root[0] == '\0')
|
||||
return false;
|
||||
@@ -962,6 +1004,7 @@ static void cleanup_mount_links(const char *removed_source_root,
|
||||
return;
|
||||
}
|
||||
|
||||
bool tried_image_recovery = false;
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(d)) != NULL) {
|
||||
if (should_stop_requested())
|
||||
@@ -992,22 +1035,33 @@ static void cleanup_mount_links(const char *removed_source_root,
|
||||
char source_path[MAX_PATH];
|
||||
bool should_remove = false;
|
||||
bool matches_removed_source = false;
|
||||
if (!read_mount_link(entry->d_name, source_path, sizeof(source_path))) {
|
||||
if (!read_mount_link_file(lnk_path, source_path, sizeof(source_path))) {
|
||||
should_remove = true;
|
||||
} else if (removed_source_root && removed_source_root[0] != '\0') {
|
||||
matches_removed_source =
|
||||
path_matches_root_or_child(source_path, removed_source_root);
|
||||
should_remove = matches_removed_source;
|
||||
} else {
|
||||
if (access(source_path, F_OK) != 0) {
|
||||
should_remove = true;
|
||||
} else if (path_matches_root_or_child(source_path, "/system_ex/app")) {
|
||||
should_remove = true;
|
||||
}
|
||||
|
||||
if (!should_remove) {
|
||||
if (removed_source_root && removed_source_root[0] != '\0') {
|
||||
matches_removed_source =
|
||||
path_matches_root_or_child(source_path, removed_source_root);
|
||||
should_remove = matches_removed_source;
|
||||
} else {
|
||||
char eboot_path[MAX_PATH];
|
||||
snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin", source_path);
|
||||
if (access(eboot_path, F_OK) != 0)
|
||||
if (access(source_path, F_OK) != 0) {
|
||||
should_remove = true;
|
||||
} else if (path_matches_root_or_child(source_path, "/system_ex/app")) {
|
||||
should_remove = true;
|
||||
} else {
|
||||
char eboot_path[MAX_PATH];
|
||||
snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin", source_path);
|
||||
if (access(eboot_path, F_OK) != 0) {
|
||||
if (!tried_image_recovery &&
|
||||
path_matches_root_or_child(source_path, IMAGE_MOUNT_BASE)) {
|
||||
cleanup_stale_image_mounts();
|
||||
tried_image_recovery = true;
|
||||
}
|
||||
if (access(eboot_path, F_OK) != 0)
|
||||
should_remove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1257,6 +1311,39 @@ static bool load_app_db_title_list(struct AppDbTitleList *list) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void invalidate_app_db_title_cache(void) {
|
||||
g_app_db_title_cache_ready = false;
|
||||
g_app_db_title_cache_mtime = 0;
|
||||
}
|
||||
|
||||
static bool get_app_db_title_list_cached(
|
||||
const struct AppDbTitleList **list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
*list_out = NULL;
|
||||
|
||||
struct stat st;
|
||||
int app_db_stat_rc = stat(APP_DB_PATH, &st);
|
||||
|
||||
if (!g_app_db_title_cache_ready ||
|
||||
(app_db_stat_rc == 0 && g_app_db_title_cache_mtime != st.st_mtime)) {
|
||||
struct AppDbTitleList fresh = {0};
|
||||
if (app_db_stat_rc == 0 && load_app_db_title_list(&fresh)) {
|
||||
free_app_db_title_list(&g_app_db_title_cache);
|
||||
g_app_db_title_cache = fresh;
|
||||
g_app_db_title_cache_mtime = st.st_mtime;
|
||||
g_app_db_title_cache_ready = true;
|
||||
} else {
|
||||
free_app_db_title_list(&fresh);
|
||||
if (!g_app_db_title_cache_ready)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*list_out = &g_app_db_title_cache;
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- FAST STABILITY CHECK ---
|
||||
static bool is_path_stable_now(const char *path, double *root_diff_out,
|
||||
int *stat_errno_out) {
|
||||
@@ -1292,8 +1379,7 @@ bool wait_for_stability_fast(const char *path, const char *name) {
|
||||
} else {
|
||||
log_debug(" [WAIT] %s modified %.0fs ago. Waiting...", name, diff);
|
||||
}
|
||||
sceKernelUsleep(2000000); // Wait 2s
|
||||
return false; // Force re-scan next cycle
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Mount/Copy Helpers for Install Action ---
|
||||
@@ -1456,19 +1542,14 @@ static bool wait_for_dev_node_state(const char *devname, bool should_exist) {
|
||||
|
||||
static bool is_source_stable_for_mount(const char *path, const char *name,
|
||||
const char *tag) {
|
||||
struct stat st;
|
||||
ensure_runtime_config_ready();
|
||||
if (stat(path, &st) != 0)
|
||||
return false;
|
||||
|
||||
double age = difftime(time(NULL), st.st_mtime);
|
||||
if (age < 0.0)
|
||||
double age = 0.0;
|
||||
int st_err = 0;
|
||||
if (is_path_stable_now(path, &age, &st_err))
|
||||
return true;
|
||||
if (age < (double)g_runtime_cfg.stability_wait_seconds) {
|
||||
log_debug(" [%s] %s modified %.0fs ago, waiting...", tag, name, age);
|
||||
if (st_err != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
log_debug(" [%s] %s modified %.0fs ago, waiting...", tag, name, age);
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Runtime Config Parsing ---
|
||||
@@ -1485,6 +1566,7 @@ static void init_runtime_config_defaults(void) {
|
||||
g_runtime_cfg.lvd_sector_pfs = LVD_SECTOR_SIZE_PFS;
|
||||
g_runtime_cfg.md_sector_exfat = MD_SECTOR_SIZE_EXFAT;
|
||||
g_runtime_cfg.md_sector_ufs = MD_SECTOR_SIZE_UFS;
|
||||
memset(g_image_mode_rules, 0, sizeof(g_image_mode_rules));
|
||||
init_runtime_scan_paths_defaults();
|
||||
g_runtime_cfg_ready = true;
|
||||
}
|
||||
@@ -1627,6 +1709,41 @@ static bool load_runtime_config(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "image_ro") == 0 ||
|
||||
strcasecmp(key, "image_rw") == 0) {
|
||||
bool rule_read_only = (strcasecmp(key, "image_ro") == 0);
|
||||
bool applied = false;
|
||||
const char *filename = get_filename_component(value);
|
||||
if (filename[0] != '\0') {
|
||||
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
|
||||
if (!g_image_mode_rules[k].valid)
|
||||
continue;
|
||||
if (strcasecmp(g_image_mode_rules[k].filename, filename) != 0)
|
||||
continue;
|
||||
g_image_mode_rules[k].mount_read_only = rule_read_only;
|
||||
applied = true;
|
||||
break;
|
||||
}
|
||||
if (!applied) {
|
||||
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
|
||||
if (g_image_mode_rules[k].valid)
|
||||
continue;
|
||||
(void)strlcpy(g_image_mode_rules[k].filename, filename,
|
||||
sizeof(g_image_mode_rules[k].filename));
|
||||
g_image_mode_rules[k].mount_read_only = rule_read_only;
|
||||
g_image_mode_rules[k].valid = true;
|
||||
applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!applied) {
|
||||
log_debug(" [CFG] invalid image mode rule at line %d: %s=%s", line_no,
|
||||
key, value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "recursive_scan") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
@@ -1729,13 +1846,19 @@ static bool load_runtime_config(void) {
|
||||
init_runtime_scan_paths_defaults();
|
||||
} else {
|
||||
// UFS mount root is always required for remount scanning.
|
||||
(void)add_runtime_scan_path(UFS_MOUNT_BASE);
|
||||
(void)add_runtime_scan_path(IMAGE_MOUNT_BASE);
|
||||
}
|
||||
|
||||
int image_rule_count = 0;
|
||||
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
|
||||
if (g_image_mode_rules[k].valid)
|
||||
image_rule_count++;
|
||||
}
|
||||
|
||||
log_debug(" [CFG] loaded: debug=%d ro=%d recursive_scan=%d "
|
||||
"exfat_backend=%s ufs_backend=%s "
|
||||
"lvd_sec(exfat=%u ufs=%u pfs=%u) md_sec(exfat=%u ufs=%u) "
|
||||
"scan_interval_s=%u stability_wait_s=%u scan_paths=%d",
|
||||
"scan_interval_s=%u stability_wait_s=%u scan_paths=%d image_rules=%d",
|
||||
g_runtime_cfg.debug_enabled ? 1 : 0,
|
||||
g_runtime_cfg.mount_read_only ? 1 : 0,
|
||||
g_runtime_cfg.recursive_scan ? 1 : 0,
|
||||
@@ -1744,7 +1867,8 @@ static bool load_runtime_config(void) {
|
||||
g_runtime_cfg.lvd_sector_exfat, g_runtime_cfg.lvd_sector_ufs,
|
||||
g_runtime_cfg.lvd_sector_pfs, g_runtime_cfg.md_sector_exfat,
|
||||
g_runtime_cfg.md_sector_ufs, g_runtime_cfg.scan_interval_us / 1000000u,
|
||||
g_runtime_cfg.stability_wait_seconds, g_scan_path_count);
|
||||
g_runtime_cfg.stability_wait_seconds, g_scan_path_count,
|
||||
image_rule_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2032,10 +2156,9 @@ static void strip_extension(const char *filename, char *out, size_t out_size) {
|
||||
out[len] = '\0';
|
||||
}
|
||||
|
||||
static void build_ufs_mount_point(const char *file_path, image_fs_type_t fs_type,
|
||||
static void build_image_mount_point(const char *file_path, image_fs_type_t fs_type,
|
||||
char *mount_point, size_t mount_point_size) {
|
||||
const char *filename = strrchr(file_path, '/');
|
||||
filename = filename ? filename + 1 : file_path;
|
||||
const char *filename = get_filename_component(file_path);
|
||||
char mount_name[MAX_PATH];
|
||||
strip_extension(filename, mount_name, sizeof(mount_name));
|
||||
const char *suffix = "unknown";
|
||||
@@ -2045,15 +2168,15 @@ static void build_ufs_mount_point(const char *file_path, image_fs_type_t fs_type
|
||||
suffix = "exfat";
|
||||
else if (fs_type == IMAGE_FS_PFS)
|
||||
suffix = "pfs";
|
||||
snprintf(mount_point, mount_point_size, "%s/%s-%s", UFS_MOUNT_BASE,
|
||||
snprintf(mount_point, mount_point_size, "%s/%s-%s", IMAGE_MOUNT_BASE,
|
||||
mount_name, suffix);
|
||||
}
|
||||
|
||||
static bool unmount_ufs_image(const char *file_path, int unit_id,
|
||||
static bool unmount_image(const char *file_path, int unit_id,
|
||||
attach_backend_t backend) {
|
||||
char mount_point[MAX_PATH];
|
||||
image_fs_type_t fs_type = detect_image_fs_type(file_path);
|
||||
build_ufs_mount_point(file_path, fs_type, mount_point, sizeof(mount_point));
|
||||
build_image_mount_point(file_path, fs_type, mount_point, sizeof(mount_point));
|
||||
int resolved_unit = unit_id;
|
||||
attach_backend_t resolved_backend = backend;
|
||||
|
||||
@@ -2103,19 +2226,33 @@ static bool unmount_ufs_image(const char *file_path, int unit_id,
|
||||
}
|
||||
|
||||
// --- Image Attach + nmount Pipeline ---
|
||||
static bool mount_ufs_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
static bool mount_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
ensure_runtime_config_ready();
|
||||
const bool mount_read_only = g_runtime_cfg.mount_read_only;
|
||||
g_last_image_mount_errmsg[0] = '\0';
|
||||
bool mount_mode_overridden = false;
|
||||
bool mount_read_only = g_runtime_cfg.mount_read_only;
|
||||
const char *filename = get_filename_component(file_path);
|
||||
if (filename[0] != '\0') {
|
||||
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
|
||||
if (!g_image_mode_rules[k].valid)
|
||||
continue;
|
||||
if (strcasecmp(g_image_mode_rules[k].filename, filename) != 0)
|
||||
continue;
|
||||
mount_read_only = g_image_mode_rules[k].mount_read_only;
|
||||
mount_mode_overridden = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if already in UFS cache
|
||||
for (int k = 0; k < MAX_UFS_MOUNTS; k++) {
|
||||
if (ufs_cache[k].valid && strcmp(ufs_cache[k].path, file_path) == 0)
|
||||
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
|
||||
if (image_cache[k].valid && strcmp(image_cache[k].path, file_path) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build mount point from image path.
|
||||
char mount_point[MAX_PATH];
|
||||
build_ufs_mount_point(file_path, fs_type, mount_point, sizeof(mount_point));
|
||||
build_image_mount_point(file_path, fs_type, mount_point, sizeof(mount_point));
|
||||
|
||||
// Check if directory exists and is populated
|
||||
struct stat mst;
|
||||
@@ -2138,7 +2275,7 @@ static bool mount_ufs_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
&existing_unit)) {
|
||||
log_debug(" [IMG][%s] Already mounted: %s",
|
||||
backend_name(existing_backend), mount_point);
|
||||
cache_ufs_mount(file_path, existing_unit, existing_backend);
|
||||
cache_image_mount(file_path, existing_unit, existing_backend);
|
||||
return true;
|
||||
}
|
||||
log_debug(" [IMG] Mount point exists and is non-empty but is not an "
|
||||
@@ -2162,9 +2299,13 @@ static bool mount_ufs_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
|
||||
log_debug(" [IMG] Mounting image (%s): %s -> %s", image_fs_name(fs_type),
|
||||
file_path, mount_point);
|
||||
if (mount_mode_overridden) {
|
||||
log_debug(" [CFG] Image mode override: %s -> %s", file_path,
|
||||
mount_read_only ? "ro" : "rw");
|
||||
}
|
||||
|
||||
// Create mount point
|
||||
mkdir(UFS_MOUNT_BASE, 0777);
|
||||
mkdir(IMAGE_MOUNT_BASE, 0777);
|
||||
mkdir(mount_point, 0777);
|
||||
|
||||
attach_backend_t attach_backend = ATTACH_BACKEND_LVD;
|
||||
@@ -2385,6 +2526,8 @@ static bool mount_ufs_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
if (ret != 0) {
|
||||
int mount_errno = errno;
|
||||
if (mount_errmsg[0] != '\0') {
|
||||
(void)strlcpy(g_last_image_mount_errmsg, mount_errmsg,
|
||||
sizeof(g_last_image_mount_errmsg));
|
||||
log_debug(" [IMG][%s] nmount %s errmsg: %s",
|
||||
backend_name(attach_backend), mount_mode, mount_errmsg);
|
||||
}
|
||||
@@ -2400,7 +2543,7 @@ static bool mount_ufs_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
log_fs_stats("IMG", mount_point, image_fs_name(fs_type));
|
||||
|
||||
// Cache it
|
||||
cache_ufs_mount(file_path, unit_id, attach_backend);
|
||||
cache_image_mount(file_path, unit_id, attach_backend);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2410,34 +2553,34 @@ static void cleanup_stale_image_mounts(void) {
|
||||
if (should_stop_requested())
|
||||
return;
|
||||
|
||||
for (int k = 0; k < MAX_UFS_MOUNTS; k++) {
|
||||
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
|
||||
if (should_stop_requested())
|
||||
return;
|
||||
if (ufs_cache[k].valid && access(ufs_cache[k].path, F_OK) != 0) {
|
||||
if (image_cache[k].valid && access(image_cache[k].path, F_OK) != 0) {
|
||||
log_debug(" [IMG][%s] Source removed, unmounting: %s",
|
||||
backend_name(ufs_cache[k].backend), ufs_cache[k].path);
|
||||
if (unmount_ufs_image(ufs_cache[k].path, ufs_cache[k].unit_id,
|
||||
ufs_cache[k].backend)) {
|
||||
ufs_cache[k].valid = false;
|
||||
backend_name(image_cache[k].backend), image_cache[k].path);
|
||||
if (unmount_image(image_cache[k].path, image_cache[k].unit_id,
|
||||
image_cache[k].backend)) {
|
||||
image_cache[k].valid = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ufs_cache[k].valid)
|
||||
if (!image_cache[k].valid)
|
||||
continue;
|
||||
|
||||
image_fs_type_t fs_type = detect_image_fs_type(ufs_cache[k].path);
|
||||
image_fs_type_t fs_type = detect_image_fs_type(image_cache[k].path);
|
||||
|
||||
char mount_point[MAX_PATH];
|
||||
build_ufs_mount_point(ufs_cache[k].path, fs_type, mount_point,
|
||||
build_image_mount_point(image_cache[k].path, fs_type, mount_point,
|
||||
sizeof(mount_point));
|
||||
if (is_active_image_mount_point(mount_point))
|
||||
continue;
|
||||
|
||||
char source_path[MAX_PATH];
|
||||
(void)strlcpy(source_path, ufs_cache[k].path, sizeof(source_path));
|
||||
(void)strlcpy(source_path, image_cache[k].path, sizeof(source_path));
|
||||
log_debug(" [IMG][%s] mount lost, retrying: %s -> %s",
|
||||
backend_name(ufs_cache[k].backend), source_path, mount_point);
|
||||
backend_name(image_cache[k].backend), source_path, mount_point);
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
if (!cache[i].valid || strcmp(cache[i].path, mount_point) != 0)
|
||||
@@ -2449,25 +2592,24 @@ static void cleanup_stale_image_mounts(void) {
|
||||
}
|
||||
clear_missing_param_entry(mount_point);
|
||||
|
||||
ufs_cache[k].valid = false;
|
||||
if (mount_ufs_image(source_path, fs_type)) {
|
||||
image_cache[k].valid = false;
|
||||
if (mount_image(source_path, fs_type)) {
|
||||
clear_image_mount_attempts(source_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
int mount_err = errno;
|
||||
if (bump_image_mount_attempts(source_path) == 1) {
|
||||
notify_system("Image mount failed: 0x%08X\n%s", (uint32_t)mount_err,
|
||||
source_path);
|
||||
notify_image_mount_failed(source_path, mount_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup_mount_dirs(void) {
|
||||
DIR *d = opendir(UFS_MOUNT_BASE);
|
||||
DIR *d = opendir(IMAGE_MOUNT_BASE);
|
||||
if (!d) {
|
||||
if (errno != ENOENT)
|
||||
log_debug(" [IMG] open %s failed: %s", UFS_MOUNT_BASE, strerror(errno));
|
||||
log_debug(" [IMG] open %s failed: %s", IMAGE_MOUNT_BASE, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2479,7 +2621,7 @@ static void cleanup_mount_dirs(void) {
|
||||
continue;
|
||||
|
||||
char full_path[MAX_PATH];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", UFS_MOUNT_BASE, entry->d_name);
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", IMAGE_MOUNT_BASE, entry->d_name);
|
||||
|
||||
bool is_dir = false;
|
||||
if (entry->d_type == DT_DIR) {
|
||||
@@ -2515,24 +2657,23 @@ static void maybe_mount_image_file(const char *full_path,
|
||||
return;
|
||||
if (is_image_mount_limited(full_path))
|
||||
return;
|
||||
if (mount_ufs_image(full_path, fs_type))
|
||||
if (mount_image(full_path, fs_type))
|
||||
clear_image_mount_attempts(full_path);
|
||||
else {
|
||||
int mount_err = errno;
|
||||
if (bump_image_mount_attempts(full_path) == 1) {
|
||||
notify_system("Image mount failed: 0x%08X\n%s",
|
||||
(uint32_t)mount_err, full_path);
|
||||
notify_image_mount_failed(full_path, mount_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void shutdown_ufs_mounts(void) {
|
||||
for (int k = 0; k < MAX_UFS_MOUNTS; k++) {
|
||||
if (!ufs_cache[k].valid)
|
||||
static void shutdown_image_mounts(void) {
|
||||
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
|
||||
if (!image_cache[k].valid)
|
||||
continue;
|
||||
(void)unmount_ufs_image(ufs_cache[k].path, ufs_cache[k].unit_id,
|
||||
ufs_cache[k].backend);
|
||||
ufs_cache[k].valid = false;
|
||||
(void)unmount_image(image_cache[k].path, image_cache[k].unit_id,
|
||||
image_cache[k].backend);
|
||||
image_cache[k].valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2745,7 +2886,7 @@ static bool try_collect_candidate_for_directory(
|
||||
|
||||
has_param_json = directory_has_param_json(full_path, ¶m_st);
|
||||
|
||||
if (is_under_ufs_mount_base(full_path) && !is_active_image_mount_point(full_path)) {
|
||||
if (is_under_image_mount_base(full_path) && !is_active_image_mount_point(full_path)) {
|
||||
log_debug(" [SKIP] inactive mount path: %s", full_path);
|
||||
return has_param_json;
|
||||
}
|
||||
@@ -2926,8 +3067,8 @@ static int collect_scan_candidates(scan_candidate_t *candidates,
|
||||
int max_candidates,
|
||||
int *total_found_out) {
|
||||
int candidate_count = 0;
|
||||
struct AppDbTitleList app_db_titles = {0};
|
||||
bool app_db_titles_ready = load_app_db_title_list(&app_db_titles);
|
||||
const struct AppDbTitleList *app_db_titles = NULL;
|
||||
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
|
||||
char (*discovered_param_roots)[MAX_PATH] = g_scan_discovered_param_roots;
|
||||
int discovered_param_root_count = 0;
|
||||
memset(discovered_param_roots, 0, sizeof(g_scan_discovered_param_roots));
|
||||
@@ -2940,7 +3081,6 @@ static int collect_scan_candidates(scan_candidate_t *candidates,
|
||||
if (should_stop_requested())
|
||||
goto done;
|
||||
|
||||
bool scan_images_in_root = (strcmp(SCAN_PATHS[i], UFS_MOUNT_BASE) != 0);
|
||||
DIR *d = opendir(SCAN_PATHS[i]);
|
||||
if (!d)
|
||||
continue;
|
||||
@@ -2972,7 +3112,7 @@ static int collect_scan_candidates(scan_candidate_t *candidates,
|
||||
}
|
||||
}
|
||||
|
||||
if (scan_images_in_root && is_regular)
|
||||
if (strcmp(SCAN_PATHS[i], IMAGE_MOUNT_BASE) != 0 && is_regular)
|
||||
maybe_mount_image_file(full_path, entry->d_name);
|
||||
if (!is_dir)
|
||||
continue;
|
||||
@@ -2980,12 +3120,12 @@ static int collect_scan_candidates(scan_candidate_t *candidates,
|
||||
if (g_runtime_cfg.recursive_scan) {
|
||||
collect_candidates_recursively(
|
||||
full_path, candidates, max_candidates, &candidate_count,
|
||||
&app_db_titles, app_db_titles_ready, discovered_param_roots,
|
||||
app_db_titles, app_db_titles_ready, discovered_param_roots,
|
||||
&discovered_param_root_count);
|
||||
} else {
|
||||
(void)try_collect_candidate_for_directory(
|
||||
full_path, candidates, max_candidates, &candidate_count,
|
||||
&app_db_titles, app_db_titles_ready, discovered_param_roots,
|
||||
app_db_titles, app_db_titles_ready, discovered_param_roots,
|
||||
&discovered_param_root_count);
|
||||
}
|
||||
}
|
||||
@@ -2994,8 +3134,6 @@ static int collect_scan_candidates(scan_candidate_t *candidates,
|
||||
done:
|
||||
if (total_found_out)
|
||||
*total_found_out = discovered_param_root_count;
|
||||
free_app_db_title_list(&app_db_titles);
|
||||
close_app_db();
|
||||
return candidate_count;
|
||||
}
|
||||
|
||||
@@ -3068,6 +3206,7 @@ bool mount_and_install(const char *src_path, const char *title_id,
|
||||
sceKernelUsleep(200000);
|
||||
|
||||
if (res == 0) {
|
||||
invalidate_app_db_title_cache();
|
||||
log_debug(" [REG] Installed NEW!");
|
||||
trigger_rich_toast(title_id, title_name, "Installed");
|
||||
if (has_src_snd0) {
|
||||
@@ -3076,6 +3215,7 @@ bool mount_and_install(const char *src_path, const char *title_id,
|
||||
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
|
||||
}
|
||||
} else if ((uint32_t)res == 0x80990002u) {
|
||||
invalidate_app_db_title_cache();
|
||||
log_debug(" [REG] Restored.");
|
||||
if (has_src_snd0) {
|
||||
int snd0_updates = update_snd0info(title_id);
|
||||
@@ -3170,10 +3310,10 @@ int main(void) {
|
||||
(void)unlink(LOG_FILE_PREV);
|
||||
(void)rename(LOG_FILE, LOG_FILE_PREV);
|
||||
|
||||
log_debug("SHADOWMOUNT+ v%s exFAT/UFS/LVD/MD. Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor", SHADOWMOUNT_VERSION);
|
||||
log_debug("ShadowMount+ v%s exFAT/UFS/PFS/LVD/MD. Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor", SHADOWMOUNT_VERSION);
|
||||
load_runtime_config();
|
||||
|
||||
notify_system("ShadowMount+ v%s exFAT/UFS",
|
||||
notify_system("ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
SHADOWMOUNT_VERSION);
|
||||
|
||||
|
||||
@@ -3242,7 +3382,9 @@ int main(void) {
|
||||
scan_all_paths();
|
||||
}
|
||||
|
||||
shutdown_ufs_mounts();
|
||||
shutdown_image_mounts();
|
||||
free_app_db_title_list(&g_app_db_title_cache);
|
||||
invalidate_app_db_title_cache();
|
||||
close_app_db();
|
||||
if (lock >= 0) {
|
||||
close(lock);
|
||||
|
||||
Reference in new issue
Block a user