mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-22 06:32:25 -05:00
396 lines
12 KiB
C
396 lines
12 KiB
C
#include "sm_platform.h"
|
|
|
|
#include <stdatomic.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include "sm_runtime.h"
|
|
#include "sm_types.h"
|
|
#include "sm_log.h"
|
|
#include "sm_shellcore_flags.h"
|
|
#include "sm_config_mount.h"
|
|
#include "sm_game_lifecycle.h"
|
|
#include "sm_kstuff.h"
|
|
#include "sm_mount_device.h"
|
|
#include "sm_filesystem.h"
|
|
#include "sm_image.h"
|
|
#include "sm_scan.h"
|
|
#include "sm_scanner.h"
|
|
#include "sm_install.h"
|
|
#include "sm_appdb.h"
|
|
#include "sm_limits.h"
|
|
#include "sm_paths.h"
|
|
|
|
#ifndef SHADOWMOUNT_VERSION
|
|
#define SHADOWMOUNT_VERSION "unknown"
|
|
#endif
|
|
|
|
#define PAYLOAD_NAME "shadowmountplus.elf"
|
|
#define BACKPORK_PROCESS_NAME "backpork.elf"
|
|
#define BACKPORK_PROCESS_NAME_ALT "ps5-backpork.elf"
|
|
#define RESTART_WAIT_POLL_US 200000u
|
|
#define RESTART_WAIT_MAX_US 60000000u
|
|
#define KINFO_PID_OFFSET 72
|
|
#define KINFO_TDNAME_OFFSET 447
|
|
|
|
static volatile sig_atomic_t g_stop_requested = 0;
|
|
static atomic_bool g_shutdown_on_going_stop_requested = false;
|
|
static atomic_bool g_scan_now_requested = false;
|
|
static _Atomic(uintptr_t) g_shutdown_stop_reason_bits = 0;
|
|
static _Atomic(uintptr_t) g_scan_now_reason_bits = 0;
|
|
|
|
static void on_signal(int sig) {
|
|
(void)sig;
|
|
g_stop_requested = 1;
|
|
sm_scanner_wake();
|
|
}
|
|
|
|
void install_signal_handlers(void) {
|
|
struct sigaction sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = on_signal;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
sigaction(SIGQUIT, &sa, NULL);
|
|
sigaction(SIGABRT, &sa, NULL);
|
|
}
|
|
|
|
bool should_stop_requested(void) {
|
|
if (g_stop_requested)
|
|
return true;
|
|
if (access(KILL_FILE, F_OK) == 0) {
|
|
remove(KILL_FILE);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void request_shutdown_stop(const char *reason) {
|
|
const char *resolved_reason =
|
|
(reason && reason[0] != '\0') ? reason : "unknown shutdown source";
|
|
bool already_requested =
|
|
atomic_exchange_explicit(&g_shutdown_on_going_stop_requested, true,
|
|
memory_order_acq_rel);
|
|
if (!already_requested) {
|
|
atomic_store_explicit(&g_shutdown_stop_reason_bits,
|
|
(uintptr_t)resolved_reason, memory_order_release);
|
|
log_debug("[SHUTDOWN] requested by %s", resolved_reason);
|
|
}
|
|
g_stop_requested = 1;
|
|
sm_scanner_wake();
|
|
}
|
|
|
|
void request_scan_now(const char *reason) {
|
|
const char *resolved_reason =
|
|
(reason && reason[0] != '\0') ? reason : "unknown scan source";
|
|
bool already_requested =
|
|
atomic_exchange_explicit(&g_scan_now_requested, true, memory_order_acq_rel);
|
|
if (!already_requested) {
|
|
atomic_store_explicit(&g_scan_now_reason_bits, (uintptr_t)resolved_reason,
|
|
memory_order_release);
|
|
log_debug("[SCAN] immediate scan requested by %s", resolved_reason);
|
|
}
|
|
sm_scanner_wake();
|
|
}
|
|
|
|
bool consume_scan_now_request(const char **reason_out) {
|
|
if (!atomic_load_explicit(&g_scan_now_requested, memory_order_acquire))
|
|
return false;
|
|
|
|
if (reason_out)
|
|
*reason_out = (const char *)atomic_load_explicit(&g_scan_now_reason_bits,
|
|
memory_order_acquire);
|
|
atomic_store_explicit(&g_scan_now_reason_bits, (uintptr_t)0,
|
|
memory_order_release);
|
|
atomic_store_explicit(&g_scan_now_requested, false, memory_order_release);
|
|
return true;
|
|
}
|
|
|
|
bool sleep_with_stop_check(unsigned int total_us) {
|
|
const unsigned int chunk_us = 200000;
|
|
unsigned int slept = 0;
|
|
while (slept < total_us) {
|
|
if (should_stop_requested())
|
|
return true;
|
|
if (atomic_load_explicit(&g_scan_now_requested, memory_order_acquire))
|
|
return false;
|
|
unsigned int remain = total_us - slept;
|
|
unsigned int step = remain < chunk_us ? remain : chunk_us;
|
|
sceKernelUsleep(step);
|
|
slept += step;
|
|
}
|
|
return should_stop_requested();
|
|
}
|
|
|
|
static void get_firmware_version_string(char out[32]) {
|
|
uint32_t fw = kernel_get_fw_version();
|
|
uint32_t major_bcd = (fw >> 24) & 0xFFu;
|
|
uint32_t minor_bcd = (fw >> 16) & 0xFFu;
|
|
uint32_t major =
|
|
((major_bcd >> 4) & 0xFu) * 10u + (major_bcd & 0xFu);
|
|
uint32_t minor =
|
|
((minor_bcd >> 4) & 0xFu) * 10u + (minor_bcd & 0xFu);
|
|
|
|
if (major == 0 && minor == 0) {
|
|
(void)strlcpy(out, "unknown", 32);
|
|
return;
|
|
}
|
|
|
|
snprintf(out, 32, "%u.%02u", major, minor);
|
|
}
|
|
|
|
pid_t find_pid_by_name(const char *name, bool exclude_self) {
|
|
if (!name || name[0] == '\0')
|
|
return -1;
|
|
|
|
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0};
|
|
size_t buf_size = 0;
|
|
if (sysctl(mib, 4, NULL, &buf_size, NULL, 0) != 0)
|
|
return -1;
|
|
if (buf_size == 0)
|
|
return 0;
|
|
|
|
uint8_t *buf = malloc(buf_size);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
if (sysctl(mib, 4, buf, &buf_size, NULL, 0) != 0) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
pid_t mypid = exclude_self ? getpid() : -1;
|
|
pid_t found_pid = 0;
|
|
uint8_t *ptr = buf;
|
|
uint8_t *end = buf + buf_size;
|
|
while (ptr < end) {
|
|
int ki_structsize = *(int *)ptr;
|
|
pid_t ki_pid = *(pid_t *)&ptr[KINFO_PID_OFFSET];
|
|
const char *ki_tdname = (const char *)&ptr[KINFO_TDNAME_OFFSET];
|
|
ptr += ki_structsize;
|
|
if ((!exclude_self || ki_pid != mypid) && strcmp(ki_tdname, name) == 0) {
|
|
found_pid = ki_pid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
return found_pid;
|
|
}
|
|
|
|
static bool wait_for_existing_instance_exit(pid_t target_pid) {
|
|
pid_t last_signaled_pid = 0;
|
|
for (unsigned int waited_us = 0; waited_us <= RESTART_WAIT_MAX_US;
|
|
waited_us += RESTART_WAIT_POLL_US) {
|
|
if (target_pid != last_signaled_pid) {
|
|
if (kill(target_pid, SIGTERM) == 0) {
|
|
printf("[RESTART] Requested shutdown of running instance pid=%ld.\n",
|
|
(long)target_pid);
|
|
last_signaled_pid = target_pid;
|
|
} else if (errno != ESRCH) {
|
|
printf("[RESTART] Failed to signal pid=%ld: %s\n", (long)target_pid,
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
target_pid = find_pid_by_name(PAYLOAD_NAME, true);
|
|
if (target_pid == 0)
|
|
return true;
|
|
if (target_pid < 0) {
|
|
printf("[RESTART] Failed to enumerate running processes.\n");
|
|
return false;
|
|
}
|
|
if (sleep_with_stop_check(RESTART_WAIT_POLL_US))
|
|
return false;
|
|
}
|
|
|
|
printf("[RESTART] Timed out waiting for previous instance to exit.\n");
|
|
return false;
|
|
}
|
|
|
|
static void log_non_empty_scan_paths(void) {
|
|
for (int i = 0; i < get_scan_path_count(); i++) {
|
|
const char *scan_path = get_scan_path(i);
|
|
if (!scan_path)
|
|
continue;
|
|
|
|
DIR *d = opendir(scan_path);
|
|
if (!d)
|
|
continue;
|
|
|
|
bool non_empty = false;
|
|
struct dirent *entry;
|
|
while ((entry = readdir(d)) != NULL) {
|
|
if ((entry->d_name[0] == '.' && entry->d_name[1] == '\0') ||
|
|
(entry->d_name[0] == '.' && entry->d_name[1] == '.' &&
|
|
entry->d_name[2] == '\0')) {
|
|
continue;
|
|
}
|
|
non_empty = true;
|
|
break;
|
|
}
|
|
closedir(d);
|
|
|
|
if (non_empty)
|
|
log_fs_stats("SCAN", scan_path, NULL);
|
|
}
|
|
}
|
|
|
|
static void ensure_kstuff_noautomount_file(void) {
|
|
if (access(KSTUFF_NOAUTOMOUNT_FILE, F_OK) == 0)
|
|
return;
|
|
|
|
int fd = open(KSTUFF_NOAUTOMOUNT_FILE, O_RDONLY | O_CREAT, 0666);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
printf("[KSTUFF] Created startup sentinel: %s\n",
|
|
KSTUFF_NOAUTOMOUNT_FILE);
|
|
return;
|
|
}
|
|
|
|
printf("[KSTUFF] Failed to create %s: %s\n", KSTUFF_NOAUTOMOUNT_FILE,
|
|
strerror(errno));
|
|
}
|
|
|
|
static void stop_conflicting_backpork(void) {
|
|
if (!runtime_config()->backport_fakelib_enabled)
|
|
return;
|
|
|
|
const char *names[] = {BACKPORK_PROCESS_NAME, BACKPORK_PROCESS_NAME_ALT};
|
|
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
|
|
while (true) {
|
|
pid_t pid = find_pid_by_name(names[i], false);
|
|
if (pid <= 0)
|
|
break;
|
|
|
|
if (kill(pid, SIGKILL) != 0) {
|
|
if (errno != ESRCH) {
|
|
log_debug(" [FAKELIB] failed to stop %s pid=%ld: %s", names[i],
|
|
(long)pid, strerror(errno));
|
|
}
|
|
break;
|
|
}
|
|
|
|
log_debug(" [FAKELIB] stopped conflicting %s pid=%ld", names[i],
|
|
(long)pid);
|
|
sceKernelUsleep(100000);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
bool restarted_previous_instance = false;
|
|
bool kstuff_runtime_enabled = false;
|
|
pid_t existing_pid = 0;
|
|
|
|
sceUserServiceInitialize(0);
|
|
sceAppInstUtilInitialize();
|
|
kernel_set_ucred_authid(-1, 0x4801000000000013L);
|
|
install_signal_handlers();
|
|
|
|
mkdir(LOG_DIR, 0777);
|
|
ensure_kstuff_noautomount_file();
|
|
existing_pid = find_pid_by_name(PAYLOAD_NAME, true);
|
|
if (existing_pid < 0) {
|
|
printf("[RESTART] Failed to enumerate running processes.\n");
|
|
sceUserServiceTerminate();
|
|
return 1;
|
|
}
|
|
if (existing_pid > 0) {
|
|
printf("[RESTART] Another instance is already running.\n");
|
|
if (!wait_for_existing_instance_exit(existing_pid)) {
|
|
sceUserServiceTerminate();
|
|
return 0;
|
|
}
|
|
restarted_previous_instance = true;
|
|
}
|
|
syscall(SYS_thr_set_name, -1, PAYLOAD_NAME);
|
|
|
|
if (remove(KILL_FILE) == 0) {
|
|
printf("[STOP] Cleared stale stop flag at startup: %s\n", KILL_FILE);
|
|
} else if (errno != ENOENT) {
|
|
printf("[STOP] Could not clear %s: %s\n", KILL_FILE, strerror(errno));
|
|
}
|
|
|
|
(void)unlink(LOG_FILE_PREV);
|
|
(void)rename(LOG_FILE, LOG_FILE_PREV);
|
|
if (!sm_scanner_init())
|
|
log_debug(" [SCAN] scanner service init incomplete; fallback path remains available");
|
|
|
|
char firmware_version[32];
|
|
get_firmware_version_string(firmware_version);
|
|
log_debug(
|
|
"ShadowMount+ v%s exFAT/UFS/PFS/LVD/MD. "
|
|
"FW: %s. "
|
|
"Build: %s %s. "
|
|
"Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor",
|
|
SHADOWMOUNT_VERSION, firmware_version, __DATE__, __TIME__);
|
|
if (restarted_previous_instance)
|
|
log_debug("[RESTART] Previous instance stopped, continuing startup");
|
|
load_runtime_config();
|
|
sm_notifications_init();
|
|
stop_conflicting_backpork();
|
|
if (!sm_shellcore_flags_start())
|
|
log_debug(" [SHELLFLAG] monitor unavailable");
|
|
kstuff_runtime_enabled = runtime_config()->kstuff_game_auto_toggle;
|
|
if (kstuff_runtime_enabled)
|
|
sm_kstuff_init();
|
|
if (!start_game_lifecycle_watcher())
|
|
log_debug(" [GAME] lifecycle watcher unavailable");
|
|
|
|
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);
|
|
log_non_empty_scan_paths();
|
|
|
|
if (runtime_config()->legacy_recursive_scan_forced) {
|
|
notify_system_info("ShadowMount+: recursive_scan=1 deprecated, using scan_depth=2.");
|
|
} else if (runtime_config()->scan_depth > 1u) {
|
|
notify_system_info("ShadowMount+: scan depth %u enabled.",
|
|
runtime_config()->scan_depth);
|
|
}
|
|
|
|
cleanup_mount_dirs();
|
|
if (!wait_for_lvd_release()) {
|
|
log_debug("[SHUTDOWN] stop requested while waiting /dev/lvd2 release");
|
|
goto shutdown;
|
|
}
|
|
|
|
cleanup_staged_mount_links();
|
|
cleanup_duplicate_title_mounts();
|
|
if (!sm_scanner_run_startup_sync())
|
|
goto shutdown;
|
|
sm_scanner_run_loop();
|
|
|
|
shutdown:
|
|
sm_shellcore_flags_stop();
|
|
stop_game_lifecycle_watcher();
|
|
sm_scanner_shutdown();
|
|
if (kstuff_runtime_enabled)
|
|
sm_kstuff_shutdown();
|
|
shutdown_title_mounts();
|
|
if (!shutdown_image_mounts()) {
|
|
log_debug("[SHUTDOWN] some image mounts or devices were not fully released");
|
|
}
|
|
shutdown_app_db();
|
|
|
|
if (atomic_load_explicit(&g_shutdown_on_going_stop_requested,
|
|
memory_order_acquire)) {
|
|
const char *shutdown_reason =
|
|
(const char *)atomic_load_explicit(&g_shutdown_stop_reason_bits,
|
|
memory_order_acquire);
|
|
log_debug("[SHUTDOWN] cleanup complete for %s",
|
|
shutdown_reason ? shutdown_reason : "unknown shutdown source");
|
|
}
|
|
|
|
sceUserServiceTerminate();
|
|
return 0;
|
|
}
|