mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-20 05:40:07 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
851c269647 | ||
|
|
c9b3415048 |
No files matched your search
@@ -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
@@ -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, ¶m_st) &&
|
||||
get_game_info(mount_point, ¶m_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();
|
||||
|
||||
Reference in new issue
Block a user