From dd35af47b0d03df95ebadf5cecb413420018fcd6 Mon Sep 17 00:00:00 2001 From: VoidWhisper Date: Thu, 8 Jan 2026 23:36:18 +0000 Subject: [PATCH] Release v1.3: Optimized stability check, added USB root paths, and other minor fixes --- src/main.c | 206 +++++++++++++++++++++++++++++------------------------ 1 file changed, 113 insertions(+), 93 deletions(-) diff --git a/src/main.c b/src/main.c index 9ff4f90..318d78e 100644 --- a/src/main.c +++ b/src/main.c @@ -19,7 +19,6 @@ // --- Configuration --- #define SCAN_INTERVAL_US 3000000 -#define STABLE_WAIT_US 3000000 #define MAX_PENDING 512 #define MAX_PATH 1024 #define MAX_TITLE_ID 32 @@ -50,15 +49,23 @@ void log_debug(const char* fmt, ...); typedef struct notify_request { char unused[45]; char message[3075]; } notify_request_t; int sceKernelSendNotificationRequest(int, notify_request_t*, size_t, int); +// Scan Paths const char* SCAN_PATHS[] = { - "/data/homebrew", - "/mnt/usb0/homebrew", "/mnt/usb1/homebrew", "/mnt/usb2/homebrew", - "/mnt/usb3/homebrew", "/mnt/usb4/homebrew", "/mnt/usb5/homebrew", "/mnt/usb6/homebrew", - "/mnt/ext0/homebrew", "/mnt/ext1/homebrew", - "/data/etaHEN/games", - "/mnt/usb0/etaHEN/games", "/mnt/usb1/etaHEN/games", "/mnt/usb2/etaHEN/games", - "/mnt/usb3/etaHEN/games", "/mnt/usb4/etaHEN/games", "/mnt/usb5/etaHEN/games", "/mnt/usb6/etaHEN/games", - "/mnt/ext0/etaHEN/games", "/mnt/ext1/etaHEN/games", + // Internal + "/data/homebrew", "/data/etaHEN/games", + + // USB Subfolders + "/mnt/usb0/homebrew", "/mnt/usb1/homebrew", "/mnt/usb2/homebrew", "/mnt/usb3/homebrew", + "/mnt/usb4/homebrew", "/mnt/usb5/homebrew", "/mnt/usb6/homebrew", "/mnt/usb7/homebrew", + + "/mnt/usb0/etaHEN/games", "/mnt/usb1/etaHEN/games", "/mnt/usb2/etaHEN/games", "/mnt/usb3/etaHEN/games", + "/mnt/usb4/etaHEN/games", "/mnt/usb5/etaHEN/games", "/mnt/usb6/etaHEN/games", "/mnt/usb7/etaHEN/games", + + // USB Root Paths + "/mnt/usb0", "/mnt/usb1", "/mnt/usb2", "/mnt/usb3", + "/mnt/usb4", "/mnt/usb5", "/mnt/usb6", "/mnt/usb7", + "/mnt/ext0", "/mnt/ext1", + NULL }; @@ -70,12 +77,6 @@ struct GameCache { }; struct GameCache cache[MAX_PENDING]; -__attribute__((constructor)) static void sys_init(void) { - sceUserServiceInitialize(0); - kernel_set_ucred_authid(-1, 0x4801000000000013L); -} -__attribute__((destructor)) static void sys_fini(void) { sceUserServiceTerminate(); } - // --- LOGGING --- void log_to_file(const char* fmt, va_list args) { mkdir(LOG_DIR, 0777); @@ -90,7 +91,7 @@ void log_debug(const char* fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); printf("\n"); log_to_file(fmt, args); va_end(args); } -// --- STANDARD NOTIFICATION --- +// --- NOTIFICATIONS --- void notify_system(const char* fmt, ...) { notify_request_t req; memset(&req, 0, sizeof(req)); va_list args; va_start(args, fmt); vsnprintf(req.message, sizeof(req.message), fmt, args); va_end(args); @@ -98,14 +99,11 @@ void notify_system(const char* fmt, ...) { log_debug("NOTIFY: %s", req.message); } -// --- RICH TOAST IPC --- void trigger_rich_toast(const char* title_id, const char* game_name, const char* msg) { FILE* f = fopen(TOAST_FILE, "w"); if (f) { fprintf(f, "%s|%s|%s", title_id, game_name, msg); - fflush(f); - fclose(f); - log_debug("IPC: Sent toast request for %s", game_name); + fflush(f); fclose(f); } } @@ -113,40 +111,33 @@ void trigger_rich_toast(const char* title_id, const char* game_name, const char* bool is_installed(const char* title_id) { char path[MAX_PATH]; snprintf(path, sizeof(path), "/user/app/%s", title_id); struct stat st; return (stat(path, &st) == 0); } 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); } -// --- RECURSION SAFETY --- -off_t get_folder_size_recursive(const char* path, int depth) { - if (depth > 3) return 0; // Prevent stack overflow on massive games - off_t total = 0; DIR* d = opendir(path); if (!d) return 0; - struct dirent* entry; char full_path[MAX_PATH]; struct stat st; - while ((entry = readdir(d)) != NULL) { - if (entry->d_name[0] == '.') continue; - snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name); - if (stat(full_path, &st) == 0) { - if (S_ISDIR(st.st_mode)) total += get_folder_size_recursive(full_path, depth + 1); - else total += st.st_size; - } - } - closedir(d); return total; -} +// --- FAST STABILITY CHECK --- +bool wait_for_stability_fast(const char* path, const char* name) { + struct stat st; + time_t now = time(NULL); -bool wait_for_stability(const char* path, const char* name) { - log_debug(" [DEBUG] Checking stability for %s...", name); - off_t size1 = get_folder_size_recursive(path, 0); - if (size1 == 0) return false; - - int checks = 0; - while (true) { - sceKernelUsleep(STABLE_WAIT_US); - off_t size2 = get_folder_size_recursive(path, 0); - if (size1 == size2 && size1 > 0) { - log_debug(" [DEBUG] Stable."); - return true; + // 1. Check Root Folder Timestamp + if (stat(path, &st) != 0) return false; + double diff = difftime(now, st.st_mtime); + + // If modified > 10 seconds ago, it's stable. + if (diff > 10.0) { + // Double check sce_sys just to be sure + char sys_path[MAX_PATH]; + snprintf(sys_path, sizeof(sys_path), "%s/sce_sys", path); + if (stat(sys_path, &st) == 0) { + if (difftime(now, st.st_mtime) > 10.0) { + return true; + } + } else { + return true; // No sce_sys? Trust root. } - log_debug(" [WAIT] Copying %s... (%ld -> %ld)", name, size1, size2); - size1 = size2; - checks++; - if (checks > 100) return false; } + + // If we are here, files were modified recently. + log_debug(" [WAIT] %s modified %.0fs ago. Waiting...", name, diff); + sceKernelUsleep(2000000); // Wait 2s + return false; // Force re-scan next cycle } static int remount_system_ex(void) { @@ -232,7 +223,7 @@ bool get_game_info(const char* base_path, char* out_id, char* out_name) { return false; } -// --- COUNTING PHASE --- +// --- COUNTING --- int count_new_candidates() { int count = 0; for (int i = 0; SCAN_PATHS[i] != NULL; i++) { @@ -244,7 +235,8 @@ int count_new_candidates() { char title_id[MAX_TITLE_ID]; char title_name[MAX_TITLE_NAME]; if (!get_game_info(full_path, title_id, title_name)) continue; - + if (is_installed(title_id) && is_data_mounted(title_id)) continue; + bool already_seen = false; for(int k=0; kd_name[0] == '.') continue; char full_path[MAX_PATH]; snprintf(full_path, sizeof(full_path), "%s/%s", SCAN_PATHS[i], entry->d_name); @@ -338,52 +335,75 @@ void scan_all_paths() { } } else { continue; } - // Skip if already mounted - if (is_installed(title_id) && is_data_mounted(title_id)) { - log_debug(" [SKIP] %s (Ready)", title_name); + // 1. Skip if perfect + bool installed = is_installed(title_id); + if (installed && is_data_mounted(title_id)) { continue; } - notify_system("Found New Game: Installing %s...", title_name); - log_debug(" [ACTION] Installing: %s", title_name); - - if (!wait_for_stability(full_path, title_name)) continue; - mount_and_install(full_path, title_id, title_name); + // 2. Decide Action + bool is_remount = false; + if (installed) { + log_debug(" [ACTION] Remounting: %s", title_name); + // NOTIFICATION REMOVED FOR REMOUNT AS REQUESTED + is_remount = true; + } else { + log_debug(" [ACTION] Installing: %s", title_name); + notify_system("Installing: %s...", title_name); + + // FAST CHECK + if (!wait_for_stability_fast(full_path, title_name)) continue; + is_remount = false; + } + + mount_and_install(full_path, title_id, title_name, is_remount); } closedir(d); } } int main() { + // Initialize services + sceUserServiceInitialize(0); sceAppInstUtilInitialize(); - remove(LOCK_FILE); - remove(LOG_FILE); mkdir(LOG_DIR, 0777); - log_debug("SHADOWMOUNT v1.2 Beta (VOIDWHISPER EDITION) START"); - - notify_system("ShadowMount v1.2 Beta by VoidWhisper Loaded"); + kernel_set_ucred_authid(-1, 0x4801000000000013L); + remove(LOCK_FILE); + remove(LOG_FILE); + mkdir(LOG_DIR, 0777); + + log_debug("SHADOWMOUNT v1.3 START"); + + // --- STARTUP LOGIC --- int new_games = count_new_candidates(); - if (new_games > 0) { - notify_system("Found %d new games. Starting...", new_games); + + if (new_games == 0) { + // SCENARIO A: Nothing to do. + notify_system("ShadowMount v1.3: Library Ready.\n- VoidWhisper"); } else { - notify_system("No new dumps found. Monitoring..."); + // SCENARIO B: Work needed. + notify_system("ShadowMount v1.3: Found %d Games. Executing...", new_games); + + // Run the scan immediately to process them + scan_all_paths(); + + // Completion Message + notify_system("Library Synchronized. - VoidWhisper"); } + // --- DAEMON LOOP --- int lock = open(LOCK_FILE, O_CREAT | O_EXCL | O_RDWR, 0666); - if (lock < 0 && errno == EEXIST) { log_debug("FATAL: Daemon running."); return 0; } + if (lock < 0 && errno == EEXIST) { return 0; } - bool first_run = true; while (true) { if (access(KILL_FILE, F_OK) == 0) { remove(KILL_FILE); remove(LOCK_FILE); return 0; } - scan_all_paths(); - - if (first_run && new_games > 0) { - notify_system("All dumps configured. Enjoy! - VoidWhisper"); - first_run = false; - } - + // Sleep FIRST since we either just finished scan above, or library was ready. sceKernelUsleep(SCAN_INTERVAL_US); + + scan_all_paths(); } + + sceUserServiceTerminate(); return 0; } \ No newline at end of file