mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-21 22:22:28 -05:00
Restore missing appmeta on remount and replace access checks with stat
This commit is contained in:
1 parent
5d0ca6d5d1
commit
47bded61da
12 files changed
+201
-60
No files matched your search
@@ -7,11 +7,13 @@
|
||||
|
||||
// Check whether a title is present in the installed app set.
|
||||
bool is_installed(const char *title_id);
|
||||
// Check whether /user/appmeta/<TITLE_ID>/param.json exists.
|
||||
bool has_appmeta_data(const char *title_id);
|
||||
// Check whether a title currently has mounted data.
|
||||
bool is_data_mounted(const char *title_id);
|
||||
// Read the mount.lnk source path for an installed title.
|
||||
// Read the mount.lnk source path for a title tracked under /user/app.
|
||||
bool read_mount_link(const char *title_id, char *out, size_t out_size);
|
||||
// Validate a /user/app/<TITLE_ID> entry and optionally build its path.
|
||||
// Validate a /user/app/<TITLE_ID> tracker entry and optionally build its path.
|
||||
bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
|
||||
size_t app_dir_size);
|
||||
// Recover staged mount links and warm image source mappings on startup.
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
// Return the last path component without modifying the input string.
|
||||
const char *get_filename_component(const char *path);
|
||||
// Return true when stat() can see the path.
|
||||
bool path_exists(const char *path);
|
||||
// Return true when a path lives under the image mount root.
|
||||
bool is_under_image_mount_base(const char *path);
|
||||
// Build "<scan_path>/backports" for a managed scan root.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define LOG_FILE_PREV "/data/shadowmount/debug.log.1"
|
||||
#define CONFIG_FILE "/data/shadowmount/config.ini"
|
||||
#define AUTOTUNE_FILE "/data/shadowmount/autotune.ini"
|
||||
#define APPMETA_BASE "/user/appmeta"
|
||||
#define APP_BASE "/user/app"
|
||||
#define KSTUFF_NOAUTOMOUNT_FILE "/data/.kstuff_noautomount"
|
||||
#define KILL_FILE "/data/shadowmount/STOP"
|
||||
#define TOAST_FILE "/data/shadowmount/notify.txt"
|
||||
|
||||
+5
-3
@@ -14,6 +14,7 @@
|
||||
#include "sm_mount_device.h"
|
||||
#include "sm_filesystem.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_scan.h"
|
||||
#include "sm_scanner.h"
|
||||
#include "sm_install.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#define RESTART_WAIT_MAX_US 60000000u
|
||||
#define KINFO_PID_OFFSET 72
|
||||
#define KINFO_TDNAME_OFFSET 447
|
||||
#define AUTHID_BASE 0x4801000000000013L
|
||||
|
||||
static volatile sig_atomic_t g_stop_requested = 0;
|
||||
static atomic_bool g_shutdown_on_going_stop_requested = false;
|
||||
@@ -68,7 +70,7 @@ void install_signal_handlers(void) {
|
||||
bool should_stop_requested(void) {
|
||||
if (g_stop_requested)
|
||||
return true;
|
||||
if (access(KILL_FILE, F_OK) == 0) {
|
||||
if (path_exists(KILL_FILE)) {
|
||||
remove(KILL_FILE);
|
||||
return true;
|
||||
}
|
||||
@@ -252,7 +254,7 @@ static void log_non_empty_scan_paths(void) {
|
||||
}
|
||||
|
||||
static void ensure_kstuff_noautomount_file(void) {
|
||||
if (access(KSTUFF_NOAUTOMOUNT_FILE, F_OK) == 0)
|
||||
if (path_exists(KSTUFF_NOAUTOMOUNT_FILE))
|
||||
return;
|
||||
|
||||
int fd = open(KSTUFF_NOAUTOMOUNT_FILE, O_RDONLY | O_CREAT, 0666);
|
||||
@@ -299,7 +301,7 @@ int main(void) {
|
||||
|
||||
sceUserServiceInitialize(0);
|
||||
sceAppInstUtilInitialize();
|
||||
kernel_set_ucred_authid(-1, 0x4801000000000013L);
|
||||
kernel_set_ucred_authid(-1, AUTHID_BASE);
|
||||
install_signal_handlers();
|
||||
|
||||
mkdir(LOG_DIR, 0777);
|
||||
|
||||
+23
-15
@@ -12,15 +12,23 @@
|
||||
// --- FILESYSTEM ---
|
||||
bool is_installed(const char *title_id) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/user/app/%s", title_id);
|
||||
snprintf(path, sizeof(path), "%s/%s", APP_BASE, title_id);
|
||||
struct stat st;
|
||||
return (stat(path, &st) == 0);
|
||||
}
|
||||
|
||||
bool has_appmeta_data(const char *title_id) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/%s/param.json", APPMETA_BASE, title_id);
|
||||
struct stat st;
|
||||
return (stat(path, &st) == 0 && S_ISREG(st.st_mode));
|
||||
}
|
||||
|
||||
bool is_data_mounted(const char *title_id) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/system_ex/app/%s/sce_sys/param.json",
|
||||
title_id);
|
||||
return (access(path, F_OK) == 0);
|
||||
return path_exists(path);
|
||||
}
|
||||
|
||||
static bool read_mount_link_file(const char *lnk_path, char *out,
|
||||
@@ -47,7 +55,7 @@ static bool read_mount_link_file(const char *lnk_path, char *out,
|
||||
|
||||
static void build_title_link_path(const char *title_id, const char *filename,
|
||||
char out[MAX_PATH]) {
|
||||
snprintf(out, MAX_PATH, "/user/app/%s/%s", title_id, filename);
|
||||
snprintf(out, MAX_PATH, "%s/%s/%s", APP_BASE, title_id, filename);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -71,10 +79,10 @@ typedef bool (*title_app_dir_iter_fn)(const char *title_id,
|
||||
static void for_each_title_app_dir(const char *open_error_context,
|
||||
bool stop_on_signal,
|
||||
title_app_dir_iter_fn fn, void *ctx) {
|
||||
DIR *d = opendir("/user/app");
|
||||
DIR *d = opendir(APP_BASE);
|
||||
if (!d) {
|
||||
if (errno != ENOENT)
|
||||
log_debug(" [LINK] open /user/app failed%s: %s",
|
||||
log_debug(" [LINK] open %s failed%s: %s", APP_BASE,
|
||||
open_error_context ? open_error_context : "", strerror(errno));
|
||||
return;
|
||||
}
|
||||
@@ -122,7 +130,7 @@ bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
|
||||
path_size = app_dir_size;
|
||||
}
|
||||
|
||||
int written = snprintf(path, path_size, "/user/app/%s", entry->d_name);
|
||||
int written = snprintf(path, path_size, "%s/%s", APP_BASE, entry->d_name);
|
||||
if (written < 0 || (size_t)written >= path_size) {
|
||||
if (app_dir && app_dir_size > 0)
|
||||
app_dir[0] = '\0';
|
||||
@@ -146,18 +154,18 @@ static bool source_path_needs_cleanup(const char *source_path,
|
||||
char image_source_path[MAX_PATH];
|
||||
bool has_image_source = resolve_image_source_from_mount_cache(
|
||||
source_path, image_source_path, sizeof(image_source_path));
|
||||
if (has_image_source && access(image_source_path, F_OK) != 0)
|
||||
if (has_image_source && !path_exists(image_source_path))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (access(source_path, F_OK) != 0)
|
||||
if (!path_exists(source_path))
|
||||
return true;
|
||||
if (path_matches_root_or_child(source_path, "/system_ex/app"))
|
||||
return true;
|
||||
|
||||
char eboot_path[MAX_PATH];
|
||||
snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin", source_path);
|
||||
if (access(eboot_path, F_OK) == 0)
|
||||
if (path_exists(eboot_path))
|
||||
return false;
|
||||
|
||||
if (!*tried_image_recovery && is_under_image_mount_base(source_path)) {
|
||||
@@ -165,7 +173,7 @@ static bool source_path_needs_cleanup(const char *source_path,
|
||||
*tried_image_recovery = true;
|
||||
}
|
||||
|
||||
return access(eboot_path, F_OK) != 0;
|
||||
return !path_exists(eboot_path);
|
||||
}
|
||||
|
||||
static bool resolve_mount_image_source_path(const title_link_paths_t *paths,
|
||||
@@ -759,7 +767,7 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
title_id, src_path, (unsigned)(src_st.st_mode & 077777));
|
||||
return false;
|
||||
}
|
||||
if (access(src_eboot, F_OK) != 0) {
|
||||
if (!path_exists(src_eboot)) {
|
||||
bool tried_image_recovery = false;
|
||||
if (source_path_needs_cleanup(src_path, &tried_image_recovery)) {
|
||||
log_debug(" [LINK] source eboot.bin missing for %s: %s", title_id,
|
||||
@@ -777,7 +785,7 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
if (state.mounted) {
|
||||
if (state.has_our_nullfs &&
|
||||
(state.top_is_our_nullfs || state.has_our_backport) &&
|
||||
access(dst_eboot, F_OK) == 0) {
|
||||
path_exists(dst_eboot)) {
|
||||
log_debug(" [LINK] mount stack already active: %s -> %s", src_path, dst);
|
||||
return true;
|
||||
}
|
||||
@@ -820,7 +828,7 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (access(dst_eboot, F_OK) != 0) {
|
||||
if (!path_exists(dst_eboot)) {
|
||||
log_debug(" [LINK] mounted nullfs but eboot.bin is missing at target: %s",
|
||||
dst_eboot);
|
||||
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) {
|
||||
@@ -880,11 +888,11 @@ static bool cleanup_mount_links_entry(const char *title_id,
|
||||
ctx->removed_source_root));
|
||||
if (!matches_removed_source)
|
||||
return true;
|
||||
should_remove = (has_image_source && access(image_source_path, F_OK) != 0) ||
|
||||
should_remove = (has_image_source && !path_exists(image_source_path)) ||
|
||||
source_path_needs_cleanup(source_path,
|
||||
&ctx->tried_image_recovery);
|
||||
} else {
|
||||
should_remove = (has_image_source && access(image_source_path, F_OK) != 0) ||
|
||||
should_remove = (has_image_source && !path_exists(image_source_path)) ||
|
||||
source_path_needs_cleanup(source_path,
|
||||
&ctx->tried_image_recovery);
|
||||
}
|
||||
|
||||
+2
-2
@@ -114,7 +114,7 @@ void prune_game_cache(void) {
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
if (!g_game_cache[k].valid)
|
||||
continue;
|
||||
if (access(g_game_cache[k].path, F_OK) == 0)
|
||||
if (path_exists(g_game_cache[k].path))
|
||||
continue;
|
||||
clear_game_cache_slot(k, "source removed");
|
||||
}
|
||||
@@ -135,7 +135,7 @@ void prune_game_cache_for_root(const char *root) {
|
||||
: g_game_cache[k].path;
|
||||
if (!path_matches_root_or_child(entry_root, root))
|
||||
continue;
|
||||
if (access(g_game_cache[k].path, F_OK) == 0)
|
||||
if (path_exists(g_game_cache[k].path))
|
||||
continue;
|
||||
clear_game_cache_slot(k, "source removed");
|
||||
}
|
||||
|
||||
+2
-2
@@ -858,7 +858,7 @@ void cleanup_stale_image_mounts(void) {
|
||||
if (!get_image_cache_entry(k, &cached_entry))
|
||||
continue;
|
||||
|
||||
if (access(cached_entry.path, F_OK) != 0) {
|
||||
if (!path_exists(cached_entry.path)) {
|
||||
log_debug(" [IMG][%s] Source removed, unmounting: %s",
|
||||
attach_backend_name(cached_entry.backend), cached_entry.path);
|
||||
if (unmount_image(cached_entry.path, cached_entry.unit_id,
|
||||
@@ -906,7 +906,7 @@ void cleanup_stale_image_mounts_for_root(const char *root) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (access(cached_entry.path, F_OK) != 0) {
|
||||
if (!path_exists(cached_entry.path)) {
|
||||
log_debug(" [IMG][%s] Source removed, unmounting: %s",
|
||||
attach_backend_name(cached_entry.backend), cached_entry.path);
|
||||
if (unmount_image(cached_entry.path, cached_entry.unit_id,
|
||||
|
||||
+121
-30
@@ -10,6 +10,7 @@
|
||||
#include "sm_appdb.h"
|
||||
#include "sm_title_state.h"
|
||||
#include "sm_image_cache.h"
|
||||
#include "sm_paths.h"
|
||||
|
||||
static bool write_link_file(const char *path, const char *value) {
|
||||
FILE *f = fopen(path, "w");
|
||||
@@ -34,17 +35,73 @@ static bool write_link_file(const char *path, const char *value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_appmeta_file(const char *name) {
|
||||
if (!name)
|
||||
return false;
|
||||
if (strcasecmp(name, "param.json") == 0 || strcasecmp(name, "param.sfo") == 0)
|
||||
return true;
|
||||
|
||||
const char *ext = strrchr(name, '.');
|
||||
if (!ext)
|
||||
return false;
|
||||
|
||||
return (strcasecmp(ext, ".png") == 0 || strcasecmp(ext, ".dds") == 0 ||
|
||||
strcasecmp(ext, ".at9") == 0);
|
||||
}
|
||||
|
||||
static bool copy_sce_sys_to_appmeta(const char *src_sce_sys,
|
||||
const char *user_appmeta_dir) {
|
||||
DIR *d = opendir(src_sce_sys);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
bool ok = true;
|
||||
struct dirent *e;
|
||||
char src_path[MAX_PATH];
|
||||
char dst_path[MAX_PATH];
|
||||
struct stat st;
|
||||
while ((e = readdir(d)) != NULL) {
|
||||
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
|
||||
continue;
|
||||
if (!is_appmeta_file(e->d_name))
|
||||
continue;
|
||||
|
||||
snprintf(src_path, sizeof(src_path), "%s/%s", src_sce_sys, e->d_name);
|
||||
if (stat(src_path, &st) != 0 || !S_ISREG(st.st_mode))
|
||||
continue;
|
||||
|
||||
snprintf(dst_path, sizeof(dst_path), "%s/%s", user_appmeta_dir, e->d_name);
|
||||
if (copy_file(src_path, dst_path) != 0) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (closedir(d) != 0)
|
||||
ok = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// --- Install/Remount Action ---
|
||||
static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
const char *title_name, bool is_remount,
|
||||
bool should_register) {
|
||||
char user_appmeta_dir[MAX_PATH];
|
||||
char user_app_dir[MAX_PATH];
|
||||
char user_sce_sys[MAX_PATH];
|
||||
char src_sce_sys[MAX_PATH];
|
||||
char src_snd0[MAX_PATH];
|
||||
char image_source_path[MAX_PATH];
|
||||
bool has_image_source = false;
|
||||
bool appmeta_missing = false;
|
||||
bool metadata_restaged = false;
|
||||
bool restage_staging = false;
|
||||
bool restage_appmeta = false;
|
||||
bool has_src_snd0 = false;
|
||||
|
||||
snprintf(user_app_dir, sizeof(user_app_dir), "/user/app/%s", title_id);
|
||||
snprintf(user_appmeta_dir, sizeof(user_appmeta_dir), "%s/%s", APPMETA_BASE,
|
||||
title_id);
|
||||
snprintf(user_app_dir, sizeof(user_app_dir), "%s/%s", APP_BASE, title_id);
|
||||
|
||||
if (is_under_image_mount_base(src_path)) {
|
||||
has_image_source = resolve_image_source_from_mount_cache(
|
||||
@@ -55,32 +112,66 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
}
|
||||
}
|
||||
|
||||
appmeta_missing = !has_appmeta_data(title_id);
|
||||
if (is_remount && appmeta_missing) {
|
||||
log_debug(" [REG] appmeta missing, restaging metadata only: %s", title_id);
|
||||
}
|
||||
|
||||
restage_staging = (!is_remount || should_register);
|
||||
restage_appmeta = (!is_remount || appmeta_missing);
|
||||
|
||||
// COPY FILES
|
||||
if (!is_remount) {
|
||||
snprintf(user_sce_sys, sizeof(user_sce_sys), "%s/sce_sys", user_app_dir);
|
||||
mkdir(user_app_dir, 0777);
|
||||
mkdir(user_sce_sys, 0777);
|
||||
|
||||
if (restage_staging || restage_appmeta) {
|
||||
snprintf(src_sce_sys, sizeof(src_sce_sys), "%s/sce_sys", src_path);
|
||||
if (copy_dir(src_sce_sys, user_sce_sys) != 0) {
|
||||
log_debug(" [COPY] Failed to copy sce_sys: %s -> %s", src_sce_sys,
|
||||
user_sce_sys);
|
||||
return false;
|
||||
}
|
||||
|
||||
char icon_src[MAX_PATH], icon_dst[MAX_PATH];
|
||||
snprintf(icon_src, sizeof(icon_src), "%s/sce_sys/icon0.png", src_path);
|
||||
snprintf(icon_dst, sizeof(icon_dst), "/user/app/%s/icon0.png", title_id);
|
||||
if (copy_file(icon_src, icon_dst) != 0) {
|
||||
log_debug(" [COPY] Failed to copy icon: %s -> %s", icon_src, icon_dst);
|
||||
return false;
|
||||
}
|
||||
snprintf(src_snd0, sizeof(src_snd0), "%s/snd0.at9", src_sce_sys);
|
||||
has_src_snd0 = path_exists(src_snd0);
|
||||
} else {
|
||||
log_debug(" [SPEED] Skipping file copy (Assets already exist)");
|
||||
}
|
||||
|
||||
if (restage_staging) {
|
||||
mkdir(APP_BASE, 0777);
|
||||
mkdir(user_app_dir, 0777);
|
||||
snprintf(user_sce_sys, sizeof(user_sce_sys), "%s/sce_sys", user_app_dir);
|
||||
mkdir(user_sce_sys, 0777);
|
||||
if (copy_dir(src_sce_sys, user_sce_sys) != 0) {
|
||||
log_debug(" [COPY] Failed to copy sce_sys staging: %s -> %s", src_sce_sys,
|
||||
user_sce_sys);
|
||||
return false;
|
||||
}
|
||||
|
||||
char icon_src[MAX_PATH];
|
||||
char icon_dst[MAX_PATH];
|
||||
snprintf(icon_src, sizeof(icon_src), "%s/icon0.png", src_sce_sys);
|
||||
snprintf(icon_dst, sizeof(icon_dst), "%s/icon0.png", user_app_dir);
|
||||
if (copy_file(icon_src, icon_dst) != 0) {
|
||||
log_debug(" [COPY] Failed to copy staged icon: %s -> %s", icon_src,
|
||||
icon_dst);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (restage_appmeta) {
|
||||
mkdir(APPMETA_BASE, 0777);
|
||||
mkdir(user_appmeta_dir, 0777);
|
||||
if (!copy_sce_sys_to_appmeta(src_sce_sys, user_appmeta_dir)) {
|
||||
log_debug(" [COPY] Failed to copy appmeta files: %s -> %s", src_sce_sys,
|
||||
user_appmeta_dir);
|
||||
return false;
|
||||
}
|
||||
metadata_restaged = true;
|
||||
}
|
||||
|
||||
if (!mount_title_nullfs(title_id, src_path)) {
|
||||
log_debug(" [LINK] nullfs mount failed: title=%s src=%s", title_id,
|
||||
src_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// WRITE TRACKER
|
||||
char lnk_path[MAX_PATH];
|
||||
mkdir(APP_BASE, 0777);
|
||||
mkdir(user_app_dir, 0777);
|
||||
snprintf(lnk_path, sizeof(lnk_path), "%s/mount.lnk", user_app_dir);
|
||||
if (!write_link_file(lnk_path, src_path))
|
||||
return false;
|
||||
@@ -104,25 +195,25 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (metadata_restaged && has_src_snd0) {
|
||||
int snd0_updates = update_snd0info(title_id);
|
||||
if (snd0_updates >= 0)
|
||||
log_debug(" [DB] snd0info force-updated after appmeta refresh rows=%d",
|
||||
snd0_updates);
|
||||
}
|
||||
log_debug(" [REG] Skip (already present in app.db)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// REGISTER
|
||||
char src_snd0[MAX_PATH];
|
||||
snprintf(src_snd0, sizeof(src_snd0), "%s/sce_sys/snd0.at9", src_path);
|
||||
bool has_src_snd0 = (access(src_snd0, F_OK) == 0);
|
||||
if (!metadata_restaged) {
|
||||
snprintf(src_snd0, sizeof(src_snd0), "%s/sce_sys/snd0.at9", src_path);
|
||||
has_src_snd0 = path_exists(src_snd0);
|
||||
}
|
||||
|
||||
mark_register_attempted(title_id);
|
||||
int res = sceAppInstUtilAppInstallTitleDir(title_id, "/user/app/", 0);
|
||||
int res = sceAppInstUtilAppInstallTitleDir(title_id, APP_BASE "/", 0);
|
||||
sceKernelUsleep(200000);
|
||||
|
||||
if (res == 0) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "sm_log.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_mount_defs.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_stability.h"
|
||||
|
||||
const char *attach_backend_name(attach_backend_t backend) {
|
||||
@@ -19,7 +20,7 @@ const char *attach_backend_name(attach_backend_t backend) {
|
||||
// --- Device Node Wait and Source Stability ---
|
||||
bool wait_for_dev_node_state(const char *devname, bool should_exist) {
|
||||
for (int i = 0; i < LVD_NODE_WAIT_RETRIES; i++) {
|
||||
if ((access(devname, F_OK) == 0) == should_exist)
|
||||
if (path_exists(devname) == should_exist)
|
||||
return true;
|
||||
sceKernelUsleep(LVD_NODE_WAIT_US);
|
||||
}
|
||||
|
||||
+3
-3
@@ -68,7 +68,7 @@ static struct PathStateEntry *create_path_state(const char *path) {
|
||||
for (int k = 0; k < PATH_STATE_CAPACITY; k++) {
|
||||
if (!g_path_state[k].valid)
|
||||
continue;
|
||||
if (access(g_path_state[k].path, F_OK) != 0) {
|
||||
if (!path_exists(g_path_state[k].path)) {
|
||||
evict_k = k;
|
||||
break;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ void prune_path_state(void) {
|
||||
for (int k = 0; k < PATH_STATE_CAPACITY; k++) {
|
||||
if (!g_path_state[k].valid || g_path_state[k].path[0] == '\0')
|
||||
continue;
|
||||
if (access(g_path_state[k].path, F_OK) == 0)
|
||||
if (path_exists(g_path_state[k].path))
|
||||
continue;
|
||||
memset(&g_path_state[k], 0, sizeof(g_path_state[k]));
|
||||
changed = true;
|
||||
@@ -177,7 +177,7 @@ void prune_path_state_for_root(const char *root) {
|
||||
continue;
|
||||
if (!path_matches_root_or_child(g_path_state[k].path, root))
|
||||
continue;
|
||||
if (access(g_path_state[k].path, F_OK) == 0)
|
||||
if (path_exists(g_path_state[k].path))
|
||||
continue;
|
||||
memset(&g_path_state[k], 0, sizeof(g_path_state[k]));
|
||||
changed = true;
|
||||
|
||||
@@ -9,6 +9,11 @@ const char *get_filename_component(const char *path) {
|
||||
return base ? base + 1 : path;
|
||||
}
|
||||
|
||||
bool path_exists(const char *path) {
|
||||
struct stat st;
|
||||
return (path && path[0] != '\0' && stat(path, &st) == 0);
|
||||
}
|
||||
|
||||
bool is_under_image_mount_base(const char *path) {
|
||||
size_t image_prefix_len = strlen(IMAGE_MOUNT_BASE);
|
||||
return (strncmp(path, IMAGE_MOUNT_BASE, image_prefix_len) == 0 &&
|
||||
|
||||
+30
-2
@@ -20,12 +20,35 @@
|
||||
|
||||
typedef struct {
|
||||
char discovered_param_roots[MAX_PENDING][MAX_PATH];
|
||||
char checked_appmeta_titles[MAX_PENDING][MAX_TITLE_ID];
|
||||
bool checked_appmeta_present[MAX_PENDING];
|
||||
int checked_appmeta_count;
|
||||
} scan_workspace_t;
|
||||
|
||||
// Reuse the largest transient scan buffer instead of placing ~512 KiB of path
|
||||
// state on the stack each cycle.
|
||||
static scan_workspace_t g_scan_workspace;
|
||||
|
||||
static void reset_scan_workspace(void) {
|
||||
g_scan_workspace.checked_appmeta_count = 0;
|
||||
}
|
||||
|
||||
static bool get_appmeta_present_for_scan_cycle(const char *title_id) {
|
||||
for (int i = 0; i < g_scan_workspace.checked_appmeta_count; i++) {
|
||||
if (strcmp(g_scan_workspace.checked_appmeta_titles[i], title_id) == 0)
|
||||
return g_scan_workspace.checked_appmeta_present[i];
|
||||
}
|
||||
|
||||
bool present = has_appmeta_data(title_id);
|
||||
if (g_scan_workspace.checked_appmeta_count < MAX_PENDING) {
|
||||
int slot = g_scan_workspace.checked_appmeta_count++;
|
||||
(void)strlcpy(g_scan_workspace.checked_appmeta_titles[slot], title_id,
|
||||
sizeof(g_scan_workspace.checked_appmeta_titles[slot]));
|
||||
g_scan_workspace.checked_appmeta_present[slot] = present;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
static bool is_under_discovered_param_root(
|
||||
const char *path, char discovered_param_roots[][MAX_PATH],
|
||||
int discovered_count) {
|
||||
@@ -147,6 +170,8 @@ static existing_directory_result_t handle_existing_directory_candidate(
|
||||
|
||||
bool in_app_db = app_db_title_list_contains(app_db_titles, info->title_id);
|
||||
bool installed = in_app_db && is_installed(info->title_id);
|
||||
bool appmeta_present =
|
||||
installed ? get_appmeta_present_for_scan_cycle(info->title_id) : false;
|
||||
link_matches_source = installed && link_matches_source;
|
||||
bool source_valid = false;
|
||||
if (link_matches_source) {
|
||||
@@ -155,12 +180,13 @@ static existing_directory_result_t handle_existing_directory_candidate(
|
||||
source_valid = is_data_mounted(info->title_id);
|
||||
}
|
||||
|
||||
if (source_valid) {
|
||||
if (source_valid && appmeta_present) {
|
||||
cache_game_entry(full_path, info->title_id, info->title_name);
|
||||
return EXISTING_DIRECTORY_PREFER_CURRENT;
|
||||
}
|
||||
|
||||
if (installed && has_tracked_path && strcmp(tracked_path, full_path) != 0 &&
|
||||
if (installed && appmeta_present && has_tracked_path &&
|
||||
strcmp(tracked_path, full_path) != 0 &&
|
||||
is_data_mounted(info->title_id)) {
|
||||
(void)strlcpy(preferred_existing_path_out, tracked_path, MAX_PATH);
|
||||
cache_game_entry(tracked_path, info->title_id, info->title_name);
|
||||
@@ -459,6 +485,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
|
||||
int max_candidates,
|
||||
int *total_found_out,
|
||||
bool *unstable_found_out) {
|
||||
reset_scan_workspace();
|
||||
int candidate_count = 0;
|
||||
const struct AppDbTitleList *app_db_titles = NULL;
|
||||
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
|
||||
@@ -482,6 +509,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
|
||||
int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates,
|
||||
int *total_found_out,
|
||||
bool *unstable_found_out) {
|
||||
reset_scan_workspace();
|
||||
int candidate_count = 0;
|
||||
const struct AppDbTitleList *app_db_titles = NULL;
|
||||
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
|
||||
|
||||
Reference in new issue
Block a user