Files
ShadowMountPlus/src/main.c
T

705 lines
22 KiB
C

#include "sm_platform.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include "sm_runtime.h"
#include "sm_ampr_updater.h"
#include "sm_api_service.h"
#include "sm_types.h"
#include "sm_log.h"
#include "sm_shellcore_flags.h"
#include "sm_shellcore_hooks.h"
#include "sm_shellcore_service.h"
#include "sm_shell_icon.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_path_utils.h"
#include "sm_scan.h"
#include "sm_scanner.h"
#include "sm_time.h"
#include "sm_install.h"
#include "sm_appdb.h"
#include "sm_limits.h"
#include "sm_mdbg.h"
#include "sm_paths.h"
#ifndef SHADOWMOUNT_VERSION
#define SHADOWMOUNT_VERSION "unknown"
#endif
#ifndef SHADOWMOUNT_BUILD_TIME
#define SHADOWMOUNT_BUILD_TIME __DATE__ " " __TIME__
#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 STOP_FILE_POLL_INTERVAL_US 3000000ull
#define KINFO_PID_OFFSET 72
#define KINFO_TDNAME_OFFSET 447
//#define AUTHID_BASE 0x4801000000000013L
#define AUTHID_BASE 0x4800000000000006ull
static volatile sig_atomic_t g_stop_requested = 0;
static atomic_bool g_shutdown_on_going_stop_requested = false;
static atomic_bool g_runtime_sleep_mode_active = false;
static atomic_uint_fast64_t g_runtime_resume_grace_deadline_us = 0;
static _Atomic(uintptr_t) g_shutdown_stop_reason_bits = 0;
static atomic_uint_fast64_t g_next_stop_file_poll_us = 0;
static pthread_mutex_t g_runtime_mount_state_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct {
pthread_mutex_t reason_mutex;
char reason[128];
bool reset_attempts;
} immediate_scan_request_t;
static immediate_scan_request_t g_scan_now = {
.reason_mutex = PTHREAD_MUTEX_INITIALIZER,
.reason = {0},
};
extern unsigned char config_ini_example[];
extern unsigned int config_ini_example_len;
static void resolve_web_interface_address(const char *bind_address,
char *address,
size_t address_size) {
(void)strlcpy(address, bind_address, address_size);
if (strcmp(bind_address, "0.0.0.0") != 0)
return;
struct ifaddrs *ifaddr = NULL;
if (getifaddrs(&ifaddr) != 0)
return;
for (const struct ifaddrs *ifa = ifaddr; ifa != NULL;
ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET ||
strncmp(ifa->ifa_name, "lo", 2) == 0)
continue;
const struct sockaddr_in *in =
(const struct sockaddr_in *)ifa->ifa_addr;
if (inet_ntop(AF_INET, &in->sin_addr, address, address_size) != NULL &&
strncmp(address, "0.", 2) != 0)
break;
(void)strlcpy(address, bind_address, address_size);
}
freeifaddrs(ifaddr);
}
static void on_signal(int sig) {
int saved_errno = errno;
(void)sig;
g_stop_requested = 1;
sm_scanner_wake();
errno = saved_errno;
}
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);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
}
bool should_stop_requested(void) {
if (g_stop_requested ||
atomic_load_explicit(&g_shutdown_on_going_stop_requested,
memory_order_acquire))
return true;
uint64_t now_us = monotonic_time_us();
if (now_us != 0) {
uint64_t next_poll_us =
atomic_load_explicit(&g_next_stop_file_poll_us, memory_order_acquire);
if (next_poll_us != 0 && now_us < next_poll_us)
return false;
if (!atomic_compare_exchange_strong_explicit(
&g_next_stop_file_poll_us, &next_poll_us,
now_us + STOP_FILE_POLL_INTERVAL_US, memory_order_acq_rel,
memory_order_acquire))
return false;
}
if (remove(KILL_FILE) == 0) {
request_shutdown_stop("STOP file " 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";
static char g_shutdown_stop_reason[128];
bool already_requested =
atomic_exchange_explicit(&g_shutdown_on_going_stop_requested, true,
memory_order_acq_rel);
if (!already_requested) {
(void)strlcpy(g_shutdown_stop_reason, resolved_reason,
sizeof(g_shutdown_stop_reason));
atomic_store_explicit(&g_shutdown_stop_reason_bits,
(uintptr_t)g_shutdown_stop_reason,
memory_order_release);
log_debug("[SHUTDOWN] requested by %s", g_shutdown_stop_reason);
}
sm_scanner_wake();
wake_game_lifecycle_watcher();
}
bool runtime_sleep_mode_active(void) {
return atomic_load_explicit(&g_runtime_sleep_mode_active,
memory_order_acquire);
}
bool runtime_resume_grace_active(void) {
uint64_t deadline_us = atomic_load_explicit(
&g_runtime_resume_grace_deadline_us, memory_order_acquire);
uint64_t now_us = monotonic_time_us();
return deadline_us != 0 && now_us != 0 && now_us < deadline_us;
}
static void clear_scan_now_request(void) {
pthread_mutex_lock(&g_scan_now.reason_mutex);
g_scan_now.reason[0] = '\0';
g_scan_now.reset_attempts = false;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
}
bool request_runtime_sleep_mode(bool active, const char *reason) {
if (active) {
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, true,
memory_order_acq_rel);
if (previous)
return false;
clear_scan_now_request();
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, 0,
memory_order_release);
} else {
if (!runtime_sleep_mode_active())
return false;
uint64_t now_us = monotonic_time_us();
uint64_t deadline_us =
now_us != 0 ? now_us + RUNTIME_RESUME_GRACE_US : 0;
// Publish the deadline before allowing launch requests to observe that
// sleep mode ended.
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, deadline_us,
memory_order_release);
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active,
false, memory_order_acq_rel);
if (!previous)
return false;
}
const char *resolved_reason =
(reason && reason[0] != '\0') ? reason : "unknown sleep source";
log_debug("[SLEEP] %s by %s", active ? "entered" : "left",
resolved_reason);
sm_api_service_on_sleep_change(active);
sm_shellcore_service_on_sleep_change(active);
sm_ampr_updater_on_sleep_change(active);
sm_scanner_wake();
wake_game_lifecycle_watcher();
return true;
}
void runtime_mount_state_lock(void) {
pthread_mutex_lock(&g_runtime_mount_state_mutex);
}
void runtime_mount_state_unlock(void) {
pthread_mutex_unlock(&g_runtime_mount_state_mutex);
}
void request_scan_now(const char *reason) {
request_scan_now_with_options(reason, false);
}
void request_scan_now_with_options(const char *reason, bool reset_attempts) {
const char *resolved_reason =
(reason && reason[0] != '\0') ? reason : "unknown scan source";
if (runtime_sleep_mode_active())
return;
char log_reason[sizeof(g_scan_now.reason)];
bool should_log = false;
pthread_mutex_lock(&g_scan_now.reason_mutex);
if (g_scan_now.reason[0] == '\0') {
(void)strlcpy(g_scan_now.reason, resolved_reason, sizeof(g_scan_now.reason));
(void)strlcpy(log_reason, g_scan_now.reason, sizeof(log_reason));
should_log = true;
}
if (reset_attempts)
g_scan_now.reset_attempts = true;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
if (should_log)
log_debug("[SCAN] immediate scan requested by %s", log_reason);
sm_scanner_wake();
}
bool consume_scan_now_request(char *reason_out, size_t reason_out_size,
bool *reset_attempts_out) {
if (reason_out && reason_out_size > 0)
reason_out[0] = '\0';
if (reset_attempts_out)
*reset_attempts_out = false;
pthread_mutex_lock(&g_scan_now.reason_mutex);
if (g_scan_now.reason[0] == '\0') {
pthread_mutex_unlock(&g_scan_now.reason_mutex);
return false;
}
if (reason_out && reason_out_size > 0)
(void)strlcpy(reason_out, g_scan_now.reason, reason_out_size);
if (reset_attempts_out)
*reset_attempts_out = g_scan_now.reset_attempts;
g_scan_now.reason[0] = '\0';
g_scan_now.reset_attempts = false;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
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;
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) {
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 ((size_t)(end - ptr) >= sizeof(int)) {
int ki_structsize = 0;
memcpy(&ki_structsize, ptr, sizeof(ki_structsize));
if (ki_structsize <= KINFO_TDNAME_OFFSET ||
(size_t)ki_structsize > (size_t)(end - ptr)) {
found_pid = -1;
break;
}
pid_t ki_pid = 0;
memcpy(&ki_pid, &ptr[KINFO_PID_OFFSET], sizeof(ki_pid));
const char *ki_tdname = (const char *)&ptr[KINFO_TDNAME_OFFSET];
size_t tdname_size = (size_t)ki_structsize - KINFO_TDNAME_OFFSET;
ptr += ki_structsize;
if ((!exclude_self || ki_pid != mypid) &&
strnlen(ki_tdname, tdname_size) < tdname_size &&
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++) {
char scan_path[MAX_PATH];
if (!get_scan_path(i, 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 bool ensure_kstuff_noautomount_file(void) {
if (path_exists(KSTUFF_NOAUTOMOUNT_FILE))
return true;
int fd = open(KSTUFF_NOAUTOMOUNT_FILE, O_WRONLY | O_CREAT, 0666);
if (fd < 0) {
printf("[KSTUFF] Failed to create %s: %s\n", KSTUFF_NOAUTOMOUNT_FILE,
strerror(errno));
return false;
}
(void)close(fd);
printf("[KSTUFF] Created automount sentinel: %s\n",
KSTUFF_NOAUTOMOUNT_FILE);
return true;
}
static bool write_buffer_to_fd(int fd, const unsigned char *buf, size_t size) {
size_t offset = 0;
while (offset < size) {
ssize_t written = write(fd, buf + offset, size - offset);
if (written < 0) {
if (errno == EINTR)
continue;
return false;
}
if (written == 0) {
errno = EIO;
return false;
}
offset += (size_t)written;
}
return true;
}
static void ensure_runtime_config_file(void) {
int fd = open(CONFIG_FILE, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0) {
if (errno == EEXIST)
return;
printf("[CFG] Failed to create %s: %s\n", CONFIG_FILE, strerror(errno));
return;
}
size_t template_size = (size_t)config_ini_example_len;
int saved_errno = 0;
if (!write_buffer_to_fd(fd, config_ini_example, template_size))
saved_errno = errno;
if (close(fd) != 0 && saved_errno == 0)
saved_errno = errno;
if (saved_errno != 0) {
errno = saved_errno;
printf("[CFG] Failed to write %s: %s\n", CONFIG_FILE, strerror(errno));
(void)unlink(CONFIG_FILE);
return;
}
printf("[CFG] Created default config from template: %s\n", CONFIG_FILE);
}
static void cleanup_kstuff_noautomount_file(void) {
pid_t replacement_pid = find_pid_by_name(PAYLOAD_NAME, true);
if (replacement_pid > 0) {
log_debug("[KSTUFF] replacement instance pid=%ld; keeping %s",
(long)replacement_pid, KSTUFF_NOAUTOMOUNT_FILE);
return;
}
if (replacement_pid < 0) {
log_debug("[KSTUFF] process enumeration unavailable; keeping %s",
KSTUFF_NOAUTOMOUNT_FILE);
return;
}
if (unlink(KSTUFF_NOAUTOMOUNT_FILE) == 0) {
log_debug("[KSTUFF] removed shutdown sentinel: %s",
KSTUFF_NOAUTOMOUNT_FILE);
} else if (errno != ENOENT) {
log_debug("[KSTUFF] failed to remove %s: %s", 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;
pid_t existing_pid = 0;
sceUserServiceInitialize(0);
sceAppInstUtilInitialize();
kernel_set_ucred_authid(-1, AUTHID_BASE);
install_signal_handlers();
mkdir(LOG_DIR, 0777);
ensure_runtime_config_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 (!ensure_kstuff_noautomount_file()) {
sceUserServiceTerminate();
return 1;
}
if (existing_pid > 0) {
printf("[RESTART] Another instance is already running.\n");
if (!wait_for_existing_instance_exit(existing_pid)) {
printf("[KSTUFF] Keeping automount sentinel after failed handoff.\n");
sceUserServiceTerminate();
return 0;
}
restarted_previous_instance = true;
}
// Older builds removed the sentinel unconditionally during shutdown.
// Recreate it after handoff before starting any mount work.
if (!ensure_kstuff_noautomount_file()) {
sceUserServiceTerminate();
return 1;
}
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; steady-state scanner will stop if initialization cannot be completed");
char firmware_version[32];
get_firmware_version_string(firmware_version);
log_debug(
"ShadowMount+ v%s exFAT/UFS/PFS/LVD/MD. "
"FW: %s. "
"Build: %s. "
"Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor",
SHADOWMOUNT_VERSION, firmware_version, SHADOWMOUNT_BUILD_TIME);
if (restarted_previous_instance)
log_debug("[RESTART] Previous instance stopped, continuing startup");
load_runtime_config();
const runtime_config_t startup_cfg = runtime_config();
sm_notifications_init();
stop_conflicting_backpork();
sm_mdbg_init();
sm_kstuff_init();
if (!sm_shellcore_service_start())
log_debug(" [SHELLCORE] Unix socket service unavailable: %s",
strerror(errno));
else if (!sm_shellcore_hooks_start()) {
log_debug(" [SHELLCORE] lifecycle hooks unavailable; stock behavior kept");
notify_system_l10n(SM_L10N_SHELLCORE_HOOKS_FAILED);
}
if (startup_cfg.api_enabled)
sm_shell_icon_install_if_missing();
if (!refresh_game_lifecycle_watcher())
log_debug(" [GAME] lifecycle watcher unavailable");
// Publish the initial AppFocus only after its lifecycle/kstuff consumers.
if (!sm_shellcore_flags_start())
log_debug(" [SHELLFLAG] monitor unavailable");
if (!sm_ampr_updater_start())
log_debug(" [AMPR] update service unavailable: %s", strerror(errno));
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));
}
if (startup_cfg.api_enabled) {
char web_address[MAX_API_BIND_ADDRESS];
resolve_web_interface_address(startup_cfg.api_bind_address, web_address,
sizeof(web_address));
notify_system_l10n(SM_L10N_STARTUP_WEB, SHADOWMOUNT_VERSION,
web_address, startup_cfg.api_port);
} else {
notify_system_l10n(SM_L10N_STARTUP, SHADOWMOUNT_VERSION);
}
log_non_empty_scan_paths();
if (runtime_config().legacy_recursive_scan_forced) {
notify_system_info_l10n(SM_L10N_RECURSIVE_SCAN_DEPRECATED);
} else if (runtime_config().scan_depth > 1u) {
notify_system_info_l10n(SM_L10N_SCAN_DEPTH_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;
}
log_debug("[STARTUP] cleanup_staged_mount_links begin");
runtime_mount_state_lock();
cleanup_staged_mount_links();
log_debug("[STARTUP] cleanup_duplicate_title_mounts begin");
cleanup_duplicate_title_mounts();
runtime_mount_state_unlock();
if (!app_db_run_startup_maintenance())
log_debug(" [DB] startup snd0info maintenance unavailable");
log_debug("[STARTUP] scanner startup sync begin");
if (!sm_scanner_run_startup_sync()) {
log_debug("[STARTUP] scanner startup sync aborted");
goto shutdown;
}
log_debug("[STARTUP] scanner startup sync done");
if (!sm_api_service_start())
log_debug(" [API] HTTP/JSON service unavailable: %s", strerror(errno));
sm_scanner_run_loop();
shutdown:
sm_ampr_updater_stop();
sm_api_service_stop();
// Stop ShellCore producers before their lifecycle and mount-state owners.
sm_shellcore_flags_stop();
sm_shellcore_hooks_stop();
stop_game_lifecycle_watcher();
sm_shellcore_service_stop();
sm_scanner_shutdown();
sm_kstuff_shutdown();
sm_mdbg_shutdown();
bool title_mounts_released = shutdown_title_mounts();
bool image_mounts_released = false;
if (title_mounts_released) {
image_mounts_released = shutdown_image_mounts();
} else {
log_debug("[SHUTDOWN] image teardown skipped while title layers remain");
}
if (!image_mounts_released) {
log_debug("[SHUTDOWN] some image mounts or devices were not fully released");
}
if (title_mounts_released && image_mounts_released) {
cleanup_kstuff_noautomount_file();
} else {
log_debug("[KSTUFF] keeping automount disabled after incomplete teardown");
}
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");
}
sm_log_shutdown();
sceUserServiceTerminate();
return 0;
}