Compare commits

..
2 Commits
Author SHA1 Message Date
Roman Tarasov 851c269647 v 1.6beta2 Stabilization of registration
Wait for /dev/lvd1 to be freed to avoid remounting after closing y2bj
The number of game registration attempts has been increased to 2x
The backport is mounted only after successful registration to ensure its successful completion. If you delete a game with a backport, re-registration will only occur after a reboot.
2026-02-27 01:07:49 +03:00
Roman Tarasov c9b3415048 Fixed instant game mounting 2026-02-25 04:05:03 +03:00
2 changed files with 218 additions and 48 deletions

No files matched your search

+5 -3
View File
@@ -1,6 +1,6 @@
# ShadowMountPlus (PS5)
**Version:** `1.6test2`
**Version:** `1.6beta1`
**Repository:** https://github.com/drakmor/shadowMountPlus
@@ -197,11 +197,13 @@ Add ShadowMountPlus to your `autoload.txt` for **plk-autoloader** to ensure it s
**Sample Configuration:**
```ini
!1000
kstuff.elf
!1000
notify.elf ; Optional - Remove this line if you do not want Rich Toasts
!1000
shadowmountplus.elf
!3000
ps5-backpork.elf
!1000
kstuff.elf
```
---
+213 -45
View File
@@ -36,7 +36,8 @@
#define TITLE_STATE_CAPACITY MAX_PENDING
#define STATE_HASH_SIZE 1024u
#define MAX_SCAN_PATHS 128
#define MAX_FAILED_MOUNT_ATTEMPTS 1
#define MAX_FAILED_MOUNT_ATTEMPTS 2
#define MAX_REGISTER_ATTEMPTS 2
#define MAX_MISSING_PARAM_SCAN_ATTEMPTS 3
#define MAX_IMAGE_MOUNT_ATTEMPTS 3
#define MAX_LAYERED_UNMOUNT_ATTEMPTS 4
@@ -52,7 +53,7 @@
#define MAX_PATH 1024
#define MAX_TITLE_ID 32
#define MAX_TITLE_NAME 256
#define SHADOWMOUNT_VERSION "1.6test2"
#define SHADOWMOUNT_VERSION "1.6beta1"
#define PAYLOAD_NAME "shadowmountplus.elf"
#define IMAGE_MOUNT_BASE "/data/imgmnt"
#define IMAGE_MOUNT_SUBDIR_UFS "ufsmnt"
@@ -237,6 +238,8 @@ static void close_app_db(void);
static void shutdown_image_mounts(void);
static void ensure_runtime_config_ready(void);
static void cleanup_stale_image_mounts(void);
static void log_fs_stats(const char *tag, const char *path,
const char *type_hint);
static bool directory_has_param_json(const char *dir_path,
struct stat *param_st_out);
@@ -307,7 +310,7 @@ static uint16_t g_path_state_hash[STATE_HASH_SIZE];
struct TitleStateEntry {
char title_id[MAX_TITLE_ID];
uint8_t mount_reg_attempts;
bool register_attempted_once;
uint8_t register_attempts;
bool duplicate_notified_once;
bool valid;
};
@@ -642,7 +645,7 @@ static struct TitleStateEntry *create_title_state(const char *title_id) {
if (!g_title_state[k].valid)
continue;
if (g_title_state[k].mount_reg_attempts == 0 &&
!g_title_state[k].register_attempted_once) {
g_title_state[k].register_attempts == 0) {
evict_k = k;
break;
}
@@ -710,13 +713,13 @@ static void prune_path_state(void) {
static bool was_register_attempted(const char *title_id) {
struct TitleStateEntry *entry = find_title_state(title_id);
return entry ? entry->register_attempted_once : false;
return entry ? (entry->register_attempts >= MAX_REGISTER_ATTEMPTS) : false;
}
static void mark_register_attempted(const char *title_id) {
struct TitleStateEntry *entry = get_or_create_title_state(title_id);
if (entry)
entry->register_attempted_once = true;
if (entry && entry->register_attempts < UINT8_MAX)
entry->register_attempts++;
}
static void notify_duplicate_title_once(const char *title_id, const char *path_a,
@@ -1023,39 +1026,127 @@ static bool read_mount_link(const char *title_id, char *out, size_t out_size) {
return read_mount_link_file(lnk_path, out, out_size);
}
static int remount_system_ex(void) {
struct iovec iov[] = {
IOVEC_ENTRY("from"), IOVEC_ENTRY("/dev/ssd0.system_ex"),
IOVEC_ENTRY("fspath"), IOVEC_ENTRY("/system_ex"),
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("exfatfs"),
IOVEC_ENTRY("large"), IOVEC_ENTRY("yes"),
IOVEC_ENTRY("timezone"), IOVEC_ENTRY("static"),
IOVEC_ENTRY("async"), IOVEC_ENTRY(NULL),
IOVEC_ENTRY("ignoreacl"), IOVEC_ENTRY(NULL)};
return nmount(iov, IOVEC_SIZE(iov), MNT_UPDATE);
}
static bool mount_title_nullfs(const char *title_id, const char *src_path) {
char dst[MAX_PATH];
char src_eboot[MAX_PATH];
char dst_eboot[MAX_PATH];
snprintf(dst, sizeof(dst), "/system_ex/app/%s", title_id);
snprintf(src_eboot, sizeof(src_eboot), "%s/eboot.bin", src_path);
snprintf(dst_eboot, sizeof(dst_eboot), "%s/eboot.bin", dst);
struct statfs mntst;
if (statfs(dst, &mntst) == 0 && strcmp(mntst.f_mntonname, dst) == 0) {
if (strcmp(mntst.f_fstypename, "nullfs") == 0 &&
strcmp(mntst.f_mntfromname, src_path) == 0)
return true;
log_debug(" [LINK] Mount point busy: %s (%s from %s)", dst,
mntst.f_fstypename, mntst.f_mntfromname);
struct stat src_st;
if (stat(src_path, &src_st) != 0) {
log_debug(" [LINK] source path check failed for %s -> %s: %s", title_id,
src_path, strerror(errno));
return false;
}
if (mkdir(dst, 0755) != 0 && errno != EEXIST) {
log_debug(" [LINK] Failed to create mount directory for title %s: %s",
title_id, strerror(errno));
if (!S_ISDIR(src_st.st_mode)) {
log_debug(" [LINK] source path is not a directory for %s: %s mode=0%o",
title_id, src_path, (unsigned)(src_st.st_mode & 077777));
return false;
}
if (access(src_eboot, F_OK) != 0) {
size_t base_len = sizeof(IMAGE_MOUNT_BASE) - 1u;
bool under_image_root =
(strncmp(src_path, IMAGE_MOUNT_BASE, base_len) == 0) &&
(src_path[base_len] == '\0' || src_path[base_len] == '/');
if (under_image_root)
cleanup_stale_image_mounts();
if (access(src_eboot, F_OK) != 0) {
log_debug(" [LINK] source eboot.bin missing for %s: %s", title_id,
src_eboot);
return false;
}
}
struct statfs mntst;
if (statfs(dst, &mntst) == 0) {
if (strcmp(mntst.f_mntonname, dst) == 0) {
if (strcmp(mntst.f_fstypename, "nullfs") == 0 &&
strcmp(mntst.f_mntfromname, src_path) == 0) {
if (access(dst_eboot, F_OK) == 0) {
log_debug(" [LINK] nullfs already mounted: %s -> %s", src_path, dst);
return true;
}
}
if (strcmp(mntst.f_fstypename, "nullfs") == 0) {
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) {
if (unmount(dst, MNT_FORCE) != 0 && errno != ENOENT &&
errno != EINVAL) {
log_debug(" [LINK] failed to unmount stale nullfs for %s: %s", dst,
strerror(errno));
return false;
}
}
} else {
log_debug(" [LINK] mount point busy for %s: dst=%s type=%s from=%s "
"flags=0x%lX",
title_id, dst, mntst.f_fstypename, mntst.f_mntfromname,
(unsigned long)mntst.f_flags);
return false;
}
}
} else if (errno != ENOENT) {
log_debug(" [LINK] statfs failed for %s (%s): %s", title_id, dst,
strerror(errno));
}
int mkdir_res = mkdir(dst, 0755);
if (mkdir_res != 0) {
if (errno != EEXIST) {
log_debug(" [LINK] Failed to create mount directory for title %s: %s",
title_id, strerror(errno));
return false;
}
struct stat dst_st;
if (stat(dst, &dst_st) != 0) {
log_debug(" [LINK] mount directory exists but stat failed for %s: %s",
dst, strerror(errno));
return false;
}
if (!S_ISDIR(dst_st.st_mode)) {
log_debug(" [LINK] mount target is not a directory: %s mode=0%o", dst,
(unsigned)(dst_st.st_mode & 077777));
return false;
}
}
struct iovec iov[] = {
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("nullfs"),
IOVEC_ENTRY("from"), IOVEC_ENTRY(src_path),
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("nullfs"),
IOVEC_ENTRY("from"), IOVEC_ENTRY(src_path),
IOVEC_ENTRY("fspath"), IOVEC_ENTRY(dst),
};
if (nmount(iov, IOVEC_SIZE(iov), 0) != 0) {
log_debug(" [LINK] Failed to auto-mount title %s -> %s: %s", title_id,
src_path, strerror(errno));
log_debug(" [LINK] Failed to auto-mount nullfs title=%s src=%s dst=%s: %s",
title_id, src_path, dst, strerror(errno));
return false;
}
log_debug(" [LINK] Auto-mounted title %s -> %s", title_id, src_path);
if (access(dst_eboot, F_OK) != 0) {
log_debug(" [LINK] mounted nullfs but eboot.bin is missing at target: %s",
dst_eboot);
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) {
if (unmount(dst, MNT_FORCE) != 0 && errno != ENOENT && errno != EINVAL) {
log_debug(" [LINK] failed to rollback empty nullfs mount %s: %s", dst,
strerror(errno));
}
}
return false;
}
log_debug(" [LINK] nullfs mounted: %s -> %s", src_path, dst);
return true;
}
@@ -2131,6 +2222,34 @@ static bool is_active_image_mount_point(const char *path) {
return is_path_mountpoint(path);
}
static bool wait_for_lvd1_release(void) {
bool was_waiting = false;
while (true) {
struct statfs *mntbuf = NULL;
int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT);
bool mounted = false;
for (int i = 0; i < mntcount && mntbuf; i++) {
if (strcmp(mntbuf[i].f_mntfromname, "/dev/lvd1") == 0) {
mounted = true;
break;
}
}
if (!mounted)
break;
if (!was_waiting) {
log_debug(" [IMG][LVD] waiting for /dev/lvd1 to be released...");
was_waiting = true;
}
if (should_stop_requested())
return false;
sceKernelUsleep(500000);
}
if (was_waiting)
log_debug(" [IMG][LVD] /dev/lvd1 released");
return true;
}
// --- Device Detach Helpers ---
static bool detach_lvd_unit(int unit_id) {
if (unit_id < 0)
@@ -2317,6 +2436,34 @@ static void build_image_mount_point(const char *file_path, image_fs_type_t fs_ty
image_fs_subdir(fs_type), mount_name);
}
static void mount_backport_overlay_if_present(const char *mount_point,
const char *title_id,
bool mount_read_only) {
char backport_path[MAX_PATH];
struct stat backport_st;
snprintf(backport_path, sizeof(backport_path), "%s/%s",
g_runtime_cfg.backports_path, title_id);
if (stat(backport_path, &backport_st) != 0 || !S_ISDIR(backport_st.st_mode))
return;
struct iovec overlay_iov[] = {
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("unionfs"),
IOVEC_ENTRY("from"), IOVEC_ENTRY(backport_path),
IOVEC_ENTRY("fspath"), IOVEC_ENTRY(mount_point)};
int overlay_flags = mount_read_only ? MNT_RDONLY : 0;
if (nmount(overlay_iov, IOVEC_SIZE(overlay_iov), overlay_flags) == 0) {
log_debug(" [IMG] backport overlay mounted (%s): %s -> %s",
mount_read_only ? "ro" : "rw", backport_path, mount_point);
return;
}
int overlay_err = errno;
log_debug(" [IMG] backport overlay failed: %s -> %s (%s)", backport_path,
mount_point, strerror(overlay_err));
notify_system("Backport overlay failed: %s\n%s\n0x%08X", title_id,
backport_path, (uint32_t)overlay_err);
}
static bool unmount_image(const char *file_path, int unit_id,
attach_backend_t backend) {
char mount_point[MAX_PATH];
@@ -2714,26 +2861,14 @@ static bool mount_image(const char *file_path, image_fs_type_t fs_type) {
if (directory_has_param_json(mount_point, &param_st) &&
get_game_info(mount_point, &param_st, title_id, title_name) &&
title_id[0] != '\0') {
char backport_path[MAX_PATH];
struct stat backport_st;
snprintf(backport_path, sizeof(backport_path), "%s/%s",
g_runtime_cfg.backports_path, title_id);
if (stat(backport_path, &backport_st) == 0 && S_ISDIR(backport_st.st_mode)) {
struct iovec overlay_iov[] = {
IOVEC_ENTRY("fstype"), IOVEC_ENTRY("unionfs"),
IOVEC_ENTRY("from"), IOVEC_ENTRY(backport_path),
IOVEC_ENTRY("fspath"), IOVEC_ENTRY(mount_point)};
int overlay_flags = mount_read_only ? MNT_RDONLY : 0;
if (nmount(overlay_iov, IOVEC_SIZE(overlay_iov), overlay_flags) == 0) {
log_debug(" [IMG] backport overlay mounted (%s): %s -> %s",
mount_read_only ? "ro" : "rw", backport_path, mount_point);
} else {
int overlay_err = errno;
log_debug(" [IMG] backport overlay failed: %s -> %s (%s)", backport_path,
mount_point, strerror(overlay_err));
notify_system("Backport overlay failed: %s\n%s\n0x%08X", title_id,
backport_path, (uint32_t)overlay_err);
}
const struct AppDbTitleList *app_db_titles = NULL;
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
if (!app_db_titles_ready ||
app_db_title_list_contains(app_db_titles, title_id)) {
mount_backport_overlay_if_present(mount_point, title_id, mount_read_only);
} else {
log_debug(" [IMG] backport overlay deferred until registration: %s",
title_id);
}
}
@@ -3198,7 +3333,12 @@ static bool try_collect_candidate_for_directory(
}
if (!in_app_db && was_register_attempted(title_id)) {
log_debug(" [SKIP] register/install already attempted once: %s (%s)",
uint8_t register_attempts = 0;
struct TitleStateEntry *entry = find_title_state(title_id);
if (entry)
register_attempts = entry->register_attempts;
log_debug(" [SKIP] register/install retry limit reached (%u/%u): %s (%s)",
(unsigned)register_attempts, (unsigned)MAX_REGISTER_ATTEMPTS,
title_name, title_id);
return true;
}
@@ -3418,6 +3558,7 @@ bool mount_and_install(const char *src_path, const char *title_id,
// WRITE TRACKER
char lnk_path[MAX_PATH];
snprintf(user_app_dir, sizeof(user_app_dir), "/user/app/%s", title_id);
snprintf(lnk_path, sizeof(lnk_path), "/user/app/%s/mount.lnk", title_id);
bool link_ok = false;
FILE *flnk = fopen(lnk_path, "w");
@@ -3436,8 +3577,14 @@ bool mount_and_install(const char *src_path, const char *title_id,
if (!link_ok) {
return false;
}
log_debug(" [LINK] mount.lnk created: %s -> %s", lnk_path, src_path);
(void)mount_title_nullfs(title_id, src_path);
if (!mount_title_nullfs(title_id, src_path)) {
log_debug(" [LINK] mount.lnk created but nullfs mount failed: title=%s "
"src=%s",
title_id, src_path);
return false;
}
if (!should_register) {
log_debug(" [REG] Skip (already present in app.db)");
@@ -3477,6 +3624,15 @@ bool mount_and_install(const char *src_path, const char *title_id,
(uint32_t)res);
return false;
}
bool src_mount_read_only = g_runtime_cfg.mount_read_only;
struct statfs src_sfs;
if (statfs(src_path, &src_sfs) == 0 &&
strcmp(src_sfs.f_mntonname, src_path) == 0) {
src_mount_read_only = ((src_sfs.f_flags & MNT_RDONLY) != 0);
}
mount_backport_overlay_if_present(src_path, title_id, src_mount_read_only);
return true;
}
@@ -3563,6 +3719,13 @@ int main(void) {
log_debug("ShadowMount+ v%s exFAT/UFS/PFS/LVD/MD. Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor", SHADOWMOUNT_VERSION);
load_runtime_config();
if (mkdir("/system_ex/app", 0777) != 0 && errno != EEXIST) {
log_debug(" [MOUNT] failed to create /system_ex/app: %s", strerror(errno));
}
if (remount_system_ex() != 0) {
log_debug(" [MOUNT] remount_system_ex failed: %s", strerror(errno));
}
notify_system("ShadowMount+ v%s exFAT/UFS/PFS",
SHADOWMOUNT_VERSION);
@@ -3593,6 +3756,10 @@ int main(void) {
notify_system("ShadowMount+: Recursive scan enabled.");
cleanup_mount_dirs();
if (!wait_for_lvd1_release()) {
log_debug("[SHUTDOWN] stop requested while waiting /dev/lvd1 release");
goto shutdown;
}
// --- STARTUP LOGIC ---
cleanup_lost_sources_before_scan();
@@ -3632,6 +3799,7 @@ int main(void) {
scan_all_paths();
}
shutdown:
shutdown_image_mounts();
free_app_db_title_list(&g_app_db_title_cache);
invalidate_app_db_title_cache();