Compare commits

..
1 Commits
Author SHA1 Message Date
Roman Tarasov 24fba2be21 Reduce scanner directory watchers by default 2026-06-03 21:13:07 +03:00
42 changed files with 326 additions and 3230 deletions

No files matched your search

-3
View File
@@ -1,8 +1,5 @@
*.o
*.elf
*.so
*.pyc
__pycache__/
daemon.lock
debug.log
notify.txt
+1 -5
View File
@@ -18,8 +18,7 @@ KERNEL_SYS_STUB_SRCS := $(PS5_SCE_STUBS_DIR)/libkernel_sys.c src/libkernel_sys_e
ASSET_SRCS := src/notify_icon_asset.c src/config_ini_example_asset.c
SRCS := src/main.c $(wildcard src/sm_*.c) $(ASSET_SRCS)
ASM_SRCS := src/sm_shellcore_bridge.S
OBJS := $(SRCS:.c=.o) $(ASM_SRCS:.S=.o)
OBJS := $(SRCS:.c=.o)
HEADERS := $(wildcard include/*.h)
# Targets
@@ -44,8 +43,5 @@ src/config_ini_example_asset.c: config.ini.example
src/%.o: src/%.c $(HEADERS)
$(CC) $(CFLAGS) -c -o $@ $<
src/%.o: src/%.S
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f shadowmountplus.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) src/notify_icon_asset.c src/config_ini_example_asset.c
+5 -3
View File
@@ -56,11 +56,13 @@ Supported keys (all optional):
- `quiet_mode=1|0` (`1` suppresses plain informational popups but keeps rich toasts; default is `0`)
- `mount_read_only=1|0` (default: `1`)
- `force_mount=1|0` (mounting even damaged file systems; default: `0`)
- `app_install_all=1|0` (`1` stages new titles and submits them through the stock batch `sceAppInstUtilAppInstallAll`; default: `0` on every firmware. On FW `12.00+`, the default per-title path is provided by the SceShellCore TitleDir bridge.)
- `app_install_all=1|0` (`1` stages new titles and submits them through `sceAppInstUtilAppInstallAll`; default: `0` on FW below `12.00`, forced `1` on FW `12.00+`)
- `image_ro=<image_filename>` (repeatable; force read-only mode for this image filename)
- `image_rw=<image_filename>` (repeatable; force read-write mode for this image filename)
- `image_sector=<image_filename>:<sector_size>` (repeatable; force sector size for this image filename)
- `scan_depth=<1..2>` (`1` = scan only first-level subfolders, `2` = also scan one additional nested level; default: `1`)
- `watch_subdirs=1|0` (`1` keeps vnode watchers on scan root subdirectories; default: `0`)
- `watch_image_files=1|0` (`1` keeps vnode watchers on discovered image files; default: `1`)
- `recursive_scan=1|0` (deprecated compatibility key; `1` forces `scan_depth=2`)
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `15`)
- `stability_wait_seconds=<0..3600>` (minimum source age before processing; default: `10`)
@@ -124,7 +126,7 @@ Backport overlay behavior:
- The `backports` folder is ignored during normal game scanning.
- A backport is applied automatically to the matching mounted game from any configured scan path.
- If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used.
- If `/mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib2` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`; otherwise it falls back to `app0/fakelib`.
- If `/mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`.
- If `global_fakelib=1` and `global_fakelib_path` exists as a directory, ShadowMount+ also mounts that folder into the same sandbox `common/lib`.
- When both global and per-game fakelib exist, the default gives the game's own `fakelib` priority by mounting `/data/shadowmount/fakelib` first and then the game-specific fakelib.
- `global_fakelib_priority=global` reverses that priority.
@@ -207,7 +209,7 @@ mkpfs pack folder --verify --no-compress --no-adjust-output-file-extension --ver
'./pfs_image.dat'
```
Then pack the nested image (**pfs_image.dat**) into a compressed PFS container:
Then pack the nested image into a compressed PFS container:
```bash
mkpfs pack file --verify --version PS5 --inode-bits 32 \
+13 -1
View File
@@ -45,6 +45,18 @@
# Default: 1
# scan_depth=1
# Directory watcher granularity:
# 1 -> keep vnode watchers on scan root subdirectories
# 0 -> watch only scan roots/parents and rely on targeted/full scans for children
# Default: 0
# watch_subdirs=0
# Image file watcher granularity:
# 1 -> keep vnode watchers on discovered image files
# 0 -> detect image file changes through targeted/full scans
# Default: 1
# watch_image_files=1
# Legacy compatibility:
# recursive_scan=1 forces scan_depth=2
# Default: disabled
@@ -67,7 +79,7 @@
# ufs_backend=lvd
# Sandbox fakelib backport watcher:
# 1/true/yes/on -> mount app0/fakelib2 when present, otherwise app0/fakelib, into common/lib
# 1/true/yes/on -> mount /mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib into common/lib
# 0/false/no/off -> disable fakelib watcher
# Default: 1
# backport_fakelib=1
-18
View File
@@ -5,8 +5,6 @@
#include <stdbool.h>
#include <stddef.h>
#include "sm_limits.h"
// 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.
@@ -15,18 +13,6 @@ bool has_appmeta_data(const char *title_id);
bool is_data_mounted(const char *title_id);
// 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);
// Atomically persist the mount.lnk source path for a title.
bool write_mount_link(const char *title_id, const char *source_path);
// Read the optional backing image path retained for an image-backed title.
bool read_mount_image_link(const char *title_id, char *out, size_t out_size);
// Persist the outer backing-image path for an already staged title.
bool write_mount_image_link(const char *title_id, const char *image_path);
bool read_mount_image_chain(
const char *title_id,
char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *count_out);
bool write_mount_image_chain(
const char *title_id,
const char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t count);
// 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);
@@ -40,10 +26,6 @@ int remount_system_ex(void);
bool mount_title_nullfs(const char *title_id, const char *src_path);
// Roll back a just-mounted title nullfs layer when publication is aborted.
bool rollback_title_nullfs_mount(const char *title_id, const char *src_path);
// Drop runtime layers for a title while preserving its metadata/link files.
bool unmount_title_runtime_layers(const char *title_id);
// Release every managed title layer while preserving link metadata.
void unmount_all_title_runtime_layers(void);
// Reconcile the title mount stack against the expected source/backport state.
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
const char *expected_backport_path,
-7
View File
@@ -11,13 +11,6 @@ void log_fs_stats(const char *tag, const char *path, const char *type_hint);
bool mount_image(const char *file_path, image_fs_type_t fs_type);
// Unmount an image mount point and detach its backing device.
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend);
// Unmount a runtime image without deleting persistent title metadata links.
bool unmount_runtime_image(const char *file_path, int unit_id,
attach_backend_t backend);
// Release one cached runtime image mount. An absent live mount is harmless.
bool release_runtime_image_mount(const char *file_path);
// Release all discovery/runtime image mounts without deleting title metadata.
bool release_runtime_image_mounts(void);
// Reconcile cached image mounts with current sources and remount if needed.
void cleanup_stale_image_mounts(void);
// Reconcile cached image mounts that belong to a specific scan root.
-14
View File
@@ -20,9 +20,6 @@ bool cache_image_mount(const char *path, const char *mount_point,
bool cache_image_source_mapping(const char *path, const char *mount_point);
// Return a cached image mount entry by index.
bool get_image_cache_entry(int index, image_cache_entry_t *entry_out);
// Find a cached source path with one cache lock acquisition.
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
int *index_out);
// Mark a cached image mount entry as invalid.
void invalidate_image_cache_entry(int index);
// Resolve a device mapping from the in-memory mount cache.
@@ -33,16 +30,5 @@ bool resolve_device_from_mount_cache(const char *mount_point,
bool resolve_image_source_from_mount_cache(const char *mount_point,
char *path_out,
size_t path_out_size);
// Resolve the image whose mount point owns a mounted child path.
bool resolve_owning_image_source_from_mount_cache(const char *path,
char *path_out,
size_t path_out_size);
// Resolve every owning image from the outermost source to the innermost one.
size_t resolve_image_source_chain_from_mount_cache(
const char *path,
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]);
// Resolve only the outermost owning image source.
bool resolve_outermost_image_source_from_mount_cache(
const char *path, char *path_out, size_t path_out_size);
#endif
-24
View File
@@ -1,24 +0,0 @@
#ifndef SM_IMAGE_INDEX_H
#define SM_IMAGE_INDEX_H
#include <stdbool.h>
#include <sys/stat.h>
struct AppDbTitleList;
// Return true when an image must be opened to discover/install its contents.
bool sm_image_index_needs_scan(const char *path, const struct stat *st,
const struct AppDbTitleList *app_db_titles,
bool app_db_titles_ready);
// Start a new fingerprint generation before mounting an image.
void sm_image_index_begin_scan(const char *path, const struct stat *st);
// Associate a discovered mounted game with its outer backing image.
void sm_image_index_record_game(const char *game_path, const char *title_id);
// Commit one fingerprint after its mounted content was scanned.
void sm_image_index_complete_scan(const char *path);
// Atomically persist accumulated index changes at the scan-cycle boundary.
void sm_image_index_flush(void);
// Drop persistent entries whose backing image disappeared.
void sm_image_index_prune(void);
#endif
-2
View File
@@ -24,7 +24,5 @@ bool sm_install_queue_candidate(const scan_candidate_t *candidate,
bool sm_install_submit_queued(void);
// Record retry state for every queued title after a batch submit failure.
void sm_install_note_submit_failure(void);
// Return true while an install still needs its staged mounts.
bool sm_install_has_pending_work(void);
#endif
-2
View File
@@ -22,13 +22,11 @@
#define MAX_MISSING_PARAM_SCAN_ATTEMPTS 3
#define MAX_IMAGE_MOUNT_ATTEMPTS 3
#define MAX_LAYERED_UNMOUNT_ATTEMPTS 4
#define MAX_IMAGE_CHAIN_DEPTH 8
#define LVD_RELEASE_WAIT_MAX_US 15000000u
#define LVD_RELEASE_WAIT_POLL_US 500000u
#define GAME_LIFECYCLE_POLL_INTERVAL_US 250000u
#define GAME_APPINFO_LOOKUP_TIMEOUT_US 1000000u
#define GAME_SLEEP_EXIT_TIMEOUT_US 5000000u
#define KSTUFF_RESTORE_RETRY_US 1000000u
#define SHELLCORE_FLAG_POLL_INTERVAL_US 100000u
#define MIN_SCAN_DEPTH 1u
-1
View File
@@ -13,7 +13,6 @@
#define AUTOTUNE_FILE "/data/shadowmount/autotune.ini"
#define MANUAL_LIST_FILE "/data/shadowmount/manual.lst"
#define MANUAL_STATUS_FILE "/data/shadowmount/manual.status"
#define IMAGE_INDEX_FILE_PATH "/data/shadowmount/image_index.bin"
#define APPMETA_BASE "/user/appmeta"
#define APP_BASE "/user/app"
#define KSTUFF_NOAUTOMOUNT_FILE "/data/.kstuff_noautomount"
+1 -8
View File
@@ -41,16 +41,9 @@ static inline uint32_t sm_firmware_major_version(void) {
return ((major_bcd >> 4) & 0xFu) * 10u + (major_bcd & 0xFu);
}
static inline bool sm_set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
// --- SDK Imports ---
int sceAppInstUtilInitialize(void);
int sceAppInstUtilAppInstallAll(void *reserved);
int sceAppInstUtilAppInstallAll(void);
int sceAppInstUtilAppUnInstall(const char *title_id);
int sceKernelGetAppInfo(pid_t pid, app_info_t *info);
int sceKernelUsleep(unsigned int microseconds);
+2 -7
View File
@@ -21,12 +21,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
int max_candidates,
int *total_found_out,
bool *unstable_found_out);
// Mount the stable backport overlay, when present, for one runtime title.
bool mount_backport_overlay_for_title(const char *source_path,
const char *title_id,
const char *owning_scan_root,
bool *unstable_found_out);
// Remove discovery title/image mounts while preserving persistent metadata.
bool release_scan_runtime_mounts(void);
// Mount stable backport overlays for already mounted titles.
void mount_backport_overlays(bool *unstable_found_out);
#endif
-19
View File
@@ -1,19 +0,0 @@
#ifndef SM_SHELLCORE_HOOKS_H
#define SM_SHELLCORE_HOOKS_H
#include <stdbool.h>
// Install/remove the fail-open SceShellCore lifecycle bridge.
bool sm_shellcore_hooks_start(void);
void sm_shellcore_hooks_stop(void);
// Return whether this firmware uses the internal TitleDir bridge.
bool sm_shellcore_install_bridge_enabled(void);
// Invoke the internal TitleDir path through one armed AppInstallAll dispatch.
// Returns false when the bridge is unavailable or was not consumed.
bool sm_shellcore_install_title_dir(const char *title_id,
const char *install_dir,
int *result_out);
#endif
-29
View File
@@ -1,29 +0,0 @@
#ifndef SM_SHELLCORE_OFFSETS_H
#define SM_SHELLCORE_OFFSETS_H
#include <stdint.h>
typedef enum {
SM_SHELLCORE_TARGET_LAUNCH_APP = 0,
SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE,
SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR,
SM_SHELLCORE_TARGET_INSTALL_ALL,
SM_SHELLCORE_TARGET_COUNT
} sm_shellcore_target_t;
typedef struct {
uintptr_t offset;
uint8_t patch_size;
} sm_shellcore_target_offset_t;
typedef struct {
uint16_t firmware;
const char *name;
sm_shellcore_target_offset_t targets[SM_SHELLCORE_TARGET_COUNT];
} sm_shellcore_firmware_offsets_t;
const sm_shellcore_firmware_offsets_t *
sm_shellcore_offsets_for_firmware(uint32_t raw_firmware);
const char *sm_shellcore_target_name(sm_shellcore_target_t target);
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef SM_SHELLCORE_PROTOCOL_H
#define SM_SHELLCORE_PROTOCOL_H
#include <stdint.h>
#include "sm_limits.h"
#define SM_SHELLCORE_SOCKET_PATH "/system_tmp/shadowmount.sock"
#define SM_SHELLCORE_PROTOCOL_MAGIC 0x534d5343u
#define SM_SHELLCORE_PROTOCOL_VERSION 1u
typedef enum {
SM_SHELLCORE_REQUEST_LAUNCH = 1,
SM_SHELLCORE_REQUEST_LAUNCH_FAILED = 2
} sm_shellcore_request_op_t;
typedef struct {
uint32_t magic;
uint16_t version;
uint16_t operation;
char title_id[MAX_TITLE_ID];
} sm_shellcore_request_t;
typedef struct {
int32_t status;
} sm_shellcore_response_t;
#endif
-26
View File
@@ -1,26 +0,0 @@
#ifndef SM_SHELLCORE_REMOTE_H
#define SM_SHELLCORE_REMOTE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include "sm_shellcore_offsets.h"
typedef struct {
pid_t pid;
uintptr_t image_base;
uintptr_t targets[SM_SHELLCORE_TARGET_COUNT];
const sm_shellcore_firmware_offsets_t *offsets;
} sm_shellcore_remote_t;
// Resolve and validate every lifecycle target in a running SceShellCore.
bool sm_shellcore_remote_resolve(pid_t pid, sm_shellcore_remote_t *remote_out);
// Read or write another process while handling post-8.20 write restrictions.
bool sm_remote_process_read(pid_t pid, uintptr_t address, void *buffer,
size_t size);
bool sm_remote_process_write(pid_t pid, uintptr_t address, const void *buffer,
size_t size);
#endif
-19
View File
@@ -1,19 +0,0 @@
#ifndef SM_SHELLCORE_SERVICE_H
#define SM_SHELLCORE_SERVICE_H
#include <stdbool.h>
// Start/stop the local Unix-socket owner used by the SceShellCore bridge.
bool sm_shellcore_service_start(void);
void sm_shellcore_service_stop(void);
// Release transient title/image mounts after the lifecycle watcher confirms
// that the game process exited.
bool sm_shellcore_release_title_runtime(const char *title_id);
// Ensure that a managed title has its image/nullfs/backport runtime stack.
// Unmanaged stock titles are treated as already ready.
bool sm_shellcore_ensure_title_runtime(const char *title_id);
// Return true while ShellCore owns a title mount prepared for launch or exit.
bool sm_shellcore_service_has_prepared_mount(void);
#endif
+3
View File
@@ -109,12 +109,15 @@ typedef struct runtime_config {
bool mount_read_only;
bool force_mount;
bool app_install_all_enabled;
bool app_install_all_forced;
bool backport_fakelib_enabled;
bool global_fakelib_enabled;
bool global_fakelib_mount_first;
bool kstuff_game_auto_toggle;
bool kstuff_crash_detection_enabled;
bool legacy_recursive_scan_forced;
bool watch_subdirs;
bool watch_image_files;
char global_fakelib_path[MAX_PATH];
uint32_t global_fakelib_exclude_title_count;
char global_fakelib_exclude_title_ids[MAX_FAKELIB_EXCLUDE_RULES][MAX_TITLE_ID];
-11
View File
@@ -8,8 +8,6 @@
#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_config_mount.h"
#include "sm_game_lifecycle.h"
#include "sm_kstuff.h"
@@ -475,13 +473,6 @@ int main(void) {
log_debug(" [SHELLFLAG] monitor unavailable");
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");
}
if (!refresh_game_lifecycle_watcher())
log_debug(" [GAME] lifecycle watcher unavailable");
@@ -523,8 +514,6 @@ int main(void) {
sm_scanner_run_loop();
shutdown:
sm_shellcore_hooks_stop();
sm_shellcore_service_stop();
sm_shellcore_flags_stop();
stop_game_lifecycle_watcher();
sm_scanner_shutdown();
+43 -2
View File
@@ -98,6 +98,7 @@ static bool lookup_kstuff_delay_override_in_file(const char *path,
static bool upsert_kstuff_delay_override_in_file(const char *path,
const char *title_id,
uint32_t delay_seconds);
static void apply_firmware_runtime_overrides(runtime_config_state_t *state);
static char *trim_ascii(char *s) {
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
@@ -151,6 +152,19 @@ static bool parse_ini_line(char *line, char **key_out, char **value_out) {
return true;
}
static void apply_firmware_runtime_overrides(runtime_config_state_t *state) {
if (!state)
return;
if (sm_firmware_major_version() >= 12u) {
state->cfg.app_install_all_enabled = true;
state->cfg.app_install_all_forced = true;
return;
}
state->cfg.app_install_all_forced = false;
}
static const runtime_config_state_t *active_runtime_state(void) {
return &g_runtime_state_slots[atomic_load_explicit(&g_runtime_state_active_index,
memory_order_acquire)];
@@ -236,12 +250,15 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
state->cfg.mount_read_only = (IMAGE_MOUNT_READ_ONLY != 0);
state->cfg.force_mount = false;
state->cfg.app_install_all_enabled = false;
state->cfg.app_install_all_forced = false;
state->cfg.backport_fakelib_enabled = true;
state->cfg.global_fakelib_enabled = true;
state->cfg.global_fakelib_mount_first = true;
state->cfg.kstuff_game_auto_toggle = true;
state->cfg.kstuff_crash_detection_enabled = true;
state->cfg.legacy_recursive_scan_forced = false;
state->cfg.watch_subdirs = false;
state->cfg.watch_image_files = true;
(void)strlcpy(state->cfg.global_fakelib_path, DEFAULT_GLOBAL_FAKELIB_PATH,
sizeof(state->cfg.global_fakelib_path));
state->cfg.scan_depth = DEFAULT_SCAN_DEPTH;
@@ -261,6 +278,7 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
memset(state->image_mode_rules, 0, sizeof(state->image_mode_rules));
clear_kstuff_title_rules(state);
init_runtime_scan_paths_defaults(state);
apply_firmware_runtime_overrides(state);
}
static config_file_stamp_t read_config_file_stamp(void) {
@@ -1180,6 +1198,7 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
continue;
}
state->cfg.app_install_all_enabled = bval;
state->cfg.app_install_all_forced = false;
continue;
}
@@ -1224,6 +1243,24 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
continue;
}
if (strcasecmp(key, "watch_subdirs") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
continue;
}
state->cfg.watch_subdirs = bval;
continue;
}
if (strcasecmp(key, "watch_image_files") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
continue;
}
state->cfg.watch_image_files = bval;
continue;
}
if (strcasecmp(key, "backport_fakelib") == 0) {
if (!parse_bool_ini(value, &bval)) {
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
@@ -1452,6 +1489,7 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
log_debug(" [CFG] recursive_scan=1 is deprecated; forcing scan_depth=2");
}
apply_firmware_runtime_overrides(state);
int image_rule_count = 0;
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
@@ -1466,10 +1504,11 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
}
log_debug(" [CFG] loaded: debug=%d quiet=%d ro=%d force=%d "
"app_install_all=%d scan_depth=%u "
"app_install_all=%d app_install_all_forced=%d scan_depth=%u "
"legacy_recursive_scan_forced=%d backport_fakelib=%d "
"global_fakelib=%d global_fakelib_priority=%s "
"global_fakelib_path=%s global_fakelib_exclude=%u "
"watch_subdirs=%d watch_image_files=%d "
"kstuff_game_auto_toggle=%d kstuff_crash_detection=%d "
"kstuff_pause_delay_image_s=%u kstuff_pause_delay_direct_s=%u "
"exfat_backend=%s ufs_backend=%s "
@@ -1480,13 +1519,15 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
state->cfg.mount_read_only ? 1 : 0,
state->cfg.force_mount ? 1 : 0,
state->cfg.app_install_all_enabled ? 1 : 0,
state->cfg.scan_depth,
state->cfg.app_install_all_forced ? 1 : 0, state->cfg.scan_depth,
state->cfg.legacy_recursive_scan_forced ? 1 : 0,
state->cfg.backport_fakelib_enabled ? 1 : 0,
state->cfg.global_fakelib_enabled ? 1 : 0,
state->cfg.global_fakelib_mount_first ? "game" : "global",
state->cfg.global_fakelib_path,
state->cfg.global_fakelib_exclude_title_count,
state->cfg.watch_subdirs ? 1 : 0,
state->cfg.watch_image_files ? 1 : 0,
state->cfg.kstuff_game_auto_toggle ? 1 : 0,
state->cfg.kstuff_crash_detection_enabled ? 1 : 0,
state->cfg.kstuff_pause_delay_image_seconds,
+2 -8
View File
@@ -125,17 +125,11 @@ static bool resolve_sandbox_context(const char *title_id,
return false;
char source_path[MAX_PATH];
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib2",
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib",
sandbox_id);
struct stat st;
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode)) {
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode))
(void)strlcpy(game_source_path, source_path, MAX_PATH);
} else {
snprintf(source_path, sizeof(source_path), "/mnt/sandbox/%s/app0/fakelib",
sandbox_id);
if (stat(source_path, &st) == 0 && S_ISDIR(st.st_mode))
(void)strlcpy(game_source_path, source_path, MAX_PATH);
}
char sandbox_root[MAX_PATH];
snprintf(sandbox_root, sizeof(sandbox_root), "/mnt/sandbox/%s", sandbox_id);
+11 -188
View File
@@ -9,8 +9,6 @@
#include "sm_path_utils.h"
#include "sm_paths.h"
static bool unmount_controlled_mount_stack(const char *path);
// --- FILESYSTEM ---
bool is_installed(const char *title_id) {
char path[MAX_PATH];
@@ -126,131 +124,6 @@ bool read_mount_link(const char *title_id, char *out, size_t out_size) {
return read_mount_link_file(paths.mount_link, out, out_size);
}
static bool write_link_lines_atomic(const char *path,
const char *const *lines,
size_t count) {
char temp_path[MAX_PATH];
int written = snprintf(temp_path, sizeof(temp_path), "%s.tmp", path);
if (written < 0 || (size_t)written >= sizeof(temp_path))
return false;
FILE *file = fopen(temp_path, "w");
if (!file)
return false;
bool ok = true;
for (size_t i = 0; i < count; ++i) {
if (!lines[i] || lines[i][0] == '\0' ||
fprintf(file, "%s%s", lines[i], i + 1u < count ? "\n" : "") < 0) {
ok = false;
break;
}
}
if (ok && fflush(file) != 0)
ok = false;
if (ok && fsync(fileno(file)) != 0)
ok = false;
if (fclose(file) != 0)
ok = false;
if (ok && rename(temp_path, path) == 0)
return true;
int saved_errno = errno != 0 ? errno : EIO;
(void)unlink(temp_path);
errno = saved_errno;
return false;
}
bool write_mount_link(const char *title_id, const char *source_path) {
if (!title_id || title_id[0] == '\0' || !source_path ||
source_path[0] == '\0') {
return false;
}
title_link_paths_t paths;
build_title_link_paths(title_id, &paths);
const char *lines[] = {source_path};
return write_link_lines_atomic(paths.mount_link, lines, 1);
}
bool read_mount_image_link(const char *title_id, char *out, size_t out_size) {
if (!title_id || !out || out_size == 0)
return false;
out[0] = '\0';
title_link_paths_t paths;
build_title_link_paths(title_id, &paths);
return read_mount_link_file(paths.mount_image_link, out, out_size);
}
bool write_mount_image_link(const char *title_id, const char *image_path) {
if (!title_id || title_id[0] == '\0' || !image_path ||
image_path[0] == '\0') {
return false;
}
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH] = {{0}};
(void)strlcpy(chain[0], image_path, sizeof(chain[0]));
return write_mount_image_chain(title_id, chain, 1);
}
bool read_mount_image_chain(
const char *title_id,
char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *count_out) {
if (!title_id || !paths || !count_out)
return false;
*count_out = 0;
title_link_paths_t link_paths;
build_title_link_paths(title_id, &link_paths);
FILE *file = fopen(link_paths.mount_image_link, "r");
if (!file)
return false;
bool ok = true;
while (*count_out < MAX_IMAGE_CHAIN_DEPTH &&
fgets(paths[*count_out], MAX_PATH, file)) {
size_t len = strcspn(paths[*count_out], "\r\n");
if (paths[*count_out][len] == '\0' && len + 1u >= MAX_PATH) {
int next = fgetc(file);
if (next != EOF && next != '\n' && next != '\r') {
ok = false;
break;
}
}
paths[*count_out][len] = '\0';
if (len == 0) {
ok = false;
break;
}
(*count_out)++;
}
if (ok && *count_out == MAX_IMAGE_CHAIN_DEPTH && fgetc(file) != EOF)
ok = false;
if (ferror(file))
ok = false;
if (fclose(file) != 0)
ok = false;
if (!ok)
*count_out = 0;
return ok && *count_out > 0;
}
bool write_mount_image_chain(
const char *title_id,
const char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t count) {
if (!title_id || title_id[0] == '\0' || !paths || count == 0 ||
count > MAX_IMAGE_CHAIN_DEPTH) {
return false;
}
title_link_paths_t link_paths;
build_title_link_paths(title_id, &link_paths);
const char *lines[MAX_IMAGE_CHAIN_DEPTH];
for (size_t i = 0; i < count; ++i)
lines[i] = paths[i];
return write_link_lines_atomic(link_paths.mount_image_link, lines, count);
}
bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
size_t app_dir_size) {
if (!entry)
@@ -292,13 +165,8 @@ 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) {
if (!path_exists(image_source_path))
return true;
// The image mount point is intentionally absent outside install/launch.
if (!path_exists(source_path))
return false;
}
if (has_image_source && !path_exists(image_source_path))
return true;
}
if (!path_exists(source_path))
@@ -633,43 +501,6 @@ bool rollback_title_nullfs_mount(const char *title_id, const char *src_path) {
return true;
}
bool unmount_title_runtime_layers(const char *title_id) {
if (!title_id || title_id[0] == '\0')
return false;
char path[MAX_PATH];
int written = snprintf(path, sizeof(path), "/system_ex/app/%s", title_id);
if (written < 0 || (size_t)written >= sizeof(path))
return false;
if (!unmount_controlled_mount_stack(path))
return false;
log_debug(" [LINK] runtime layers released: %s", title_id);
return true;
}
static bool unmount_managed_title_entry(
const char *title_id, const title_link_paths_t *paths, void *ctx) {
(void)ctx;
char source_path[MAX_PATH];
if (!read_mount_link_file(paths->mount_link, source_path,
sizeof(source_path))) {
return true;
}
title_mount_state_t state;
if (!inspect_title_stack(title_id, source_path, NULL, &state) ||
!state.has_our_nullfs) {
return true;
}
(void)unmount_title_runtime_layers(title_id);
return true;
}
void unmount_all_title_runtime_layers(void) {
for_each_title_app_dir(" during runtime mount cleanup", false,
unmount_managed_title_entry, NULL);
}
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
const char *expected_backport_path,
bool *overlay_active_out) {
@@ -1140,7 +971,6 @@ typedef struct {
bool tried_image_recovery;
bool force_remove_matching_source;
bool match_usb_sources;
bool preserve_mount_links;
} cleanup_mount_links_ctx_t;
static bool cleanup_mount_links_entry(const char *title_id,
@@ -1212,19 +1042,17 @@ static bool cleanup_mount_links_entry(const char *title_id,
stack_matches_link = state.has_nullfs_from_root;
}
if (stack_matches_link && !top_is_ours) {
if (inspected && stack_matches_link && !top_is_ours) {
keep_mount_link = true;
log_debug(" [LINK] keeping mount link for %s: stack still active but "
"top layer is not ours", state.system_ex_path);
} else if (top_is_ours && stack_matches_link) {
if (!ctx->preserve_mount_links) {
if (rename(paths->mount_link, paths->staged_mount_link) != 0) {
log_debug(" [LINK] stage failed for %s: %s", paths->mount_link,
strerror(errno));
return true;
}
mount_link_staged = true;
} else if (inspected && top_is_ours && stack_matches_link) {
if (rename(paths->mount_link, paths->staged_mount_link) != 0) {
log_debug(" [LINK] stage failed for %s: %s", paths->mount_link,
strerror(errno));
return true;
}
mount_link_staged = true;
if (!unmount_controlled_mount_stack(state.system_ex_path)) {
keep_mount_link = true;
log_debug(" [LINK] failed to unmount mount stack for %s",
@@ -1238,9 +1066,6 @@ static bool cleanup_mount_links_entry(const char *title_id,
}
}
if (ctx->preserve_mount_links)
keep_mount_link = true;
if (!keep_mount_link) {
int unlink_res = mount_link_staged ? unlink(paths->staged_mount_link)
: unlink(paths->mount_link);
@@ -1259,7 +1084,8 @@ static bool cleanup_mount_links_entry(const char *title_id,
}
if (mount_link_staged && rename(paths->staged_mount_link, paths->mount_link) == 0) {
log_debug(" [LINK] restored staged mount link: %s", paths->mount_link);
log_debug(" [LINK] restored mount link after failed cleanup: %s",
paths->mount_link);
} else if (mount_link_staged) {
log_debug(" [LINK] restore failed for %s: %s", paths->mount_link,
strerror(errno));
@@ -1275,7 +1101,6 @@ void cleanup_mount_links(const char *removed_source_root,
.tried_image_recovery = false,
.force_remove_matching_source = false,
.match_usb_sources = false,
.preserve_mount_links = false,
};
for_each_title_app_dir("", !unmount_system_ex_bind, cleanup_mount_links_entry,
&ctx);
@@ -1291,7 +1116,6 @@ void cleanup_mount_links_for_source_unmount(const char *source_root) {
.tried_image_recovery = false,
.force_remove_matching_source = true,
.match_usb_sources = false,
.preserve_mount_links = false,
};
for_each_title_app_dir(" during source unmount cleanup", false,
cleanup_mount_links_entry, &ctx);
@@ -1304,7 +1128,6 @@ void cleanup_usb_mount_links_for_suspend(void) {
.tried_image_recovery = false,
.force_remove_matching_source = false,
.match_usb_sources = true,
.preserve_mount_links = true,
};
for_each_title_app_dir(" during USB suspend cleanup", false,
cleanup_mount_links_entry, &ctx);
+2 -14
View File
@@ -18,18 +18,6 @@ struct GameCache {
static struct GameCache g_game_cache[MAX_PENDING];
static bool game_cache_source_exists(const struct GameCache *entry) {
if (path_exists(entry->path))
return true;
if (!is_under_image_mount_base(entry->path))
return false;
char image_path[MAX_PATH];
return read_mount_image_link(entry->title_id, image_path,
sizeof(image_path)) &&
path_exists(image_path);
}
static bool resolve_game_cache_owning_scan_root(const char *path,
char owning_scan_root[MAX_PATH]) {
char resolved_source_path[MAX_PATH];
@@ -126,7 +114,7 @@ void prune_game_cache(void) {
for (int k = 0; k < MAX_PENDING; k++) {
if (!g_game_cache[k].valid)
continue;
if (game_cache_source_exists(&g_game_cache[k]))
if (path_exists(g_game_cache[k].path))
continue;
clear_game_cache_slot(k, "source removed");
}
@@ -147,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 (game_cache_source_exists(&g_game_cache[k]))
if (path_exists(g_game_cache[k].path))
continue;
clear_game_cache_slot(k, "source removed");
}
+42 -128
View File
@@ -1,5 +1,6 @@
#include "sm_platform.h"
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/event.h>
@@ -7,17 +8,13 @@
#include "sm_appdb.h"
#include "sm_fakelib.h"
#include "sm_filesystem.h"
#include "sm_game_lifecycle.h"
#include "sm_kstuff.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_mdbg.h"
#include "sm_path_utils.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_scanner.h"
#include "sm_shellcore_service.h"
#include "sm_time.h"
#define MAX_PENDING_GAME_EXEC_CANDIDATES 32
@@ -42,12 +39,10 @@ static pthread_cond_t g_game_lifecycle_start_cond = PTHREAD_COND_INITIALIZER;
static bool g_game_lifecycle_start_ready = false;
static bool g_game_lifecycle_start_success = false;
static _Atomic pid_t g_active_game_pid = 0;
// Title storage is owned exclusively by the lifecycle thread. Other threads
// consume only the atomically published pid through the public state helper.
static pthread_mutex_t g_active_game_mutex = PTHREAD_MUTEX_INITIALIZER;
static char g_active_game_title_id[MAX_TITLE_ID];
static bool register_game_exit_watch(int kq, pid_t pid);
static void handle_game_exit(pid_t pid);
static void publish_active_game_pid(pid_t pid) {
pid_t previous_pid = atomic_exchange(&g_active_game_pid, pid);
@@ -57,40 +52,29 @@ static void publish_active_game_pid(pid_t pid) {
}
static void publish_active_game(pid_t pid, const char *title_id) {
pthread_mutex_lock(&g_active_game_mutex);
if (pid > 0 && title_id && title_id[0] != '\0')
(void)strlcpy(g_active_game_title_id, title_id,
sizeof(g_active_game_title_id));
else
g_active_game_title_id[0] = '\0';
pthread_mutex_unlock(&g_active_game_mutex);
publish_active_game_pid(pid);
}
static bool consume_active_game_title(pid_t pid,
char title_id_out[MAX_TITLE_ID]) {
if (pid <= 0 || atomic_load(&g_active_game_pid) != pid ||
g_active_game_title_id[0] == '\0')
return false;
(void)strlcpy(title_id_out, g_active_game_title_id, MAX_TITLE_ID);
g_active_game_title_id[0] = '\0';
return true;
}
static bool snapshot_active_game(pid_t pid, char title_id[MAX_TITLE_ID]) {
title_id[0] = '\0';
if (pid <= 0 || g_active_game_title_id[0] == '\0')
return false;
(void)strlcpy(title_id, g_active_game_title_id, MAX_TITLE_ID);
return true;
}
static bool title_is_usb_backed(const char *title_id, const char *source_path,
bool has_mount_link) {
char image_path[MAX_PATH];
if (read_mount_image_link(title_id, image_path, sizeof(image_path)) &&
is_usb_storage_path(image_path)) {
return true;
bool matched = false;
pthread_mutex_lock(&g_active_game_mutex);
if (pid > 0 && atomic_load(&g_active_game_pid) == pid &&
g_active_game_title_id[0] != '\0') {
(void)strlcpy(title_id_out, g_active_game_title_id, MAX_TITLE_ID);
matched = true;
}
return has_mount_link && is_usb_storage_path(source_path);
if (matched)
g_active_game_title_id[0] = '\0';
pthread_mutex_unlock(&g_active_game_mutex);
return matched;
}
static bool is_supported_title_id(const char *title_id) {
@@ -182,11 +166,6 @@ static bool is_process_alive(pid_t pid) {
static bool dispatch_game_launch(int kq, pid_t pid, uint64_t exec_time_us,
const char *title_id, uint32_t app_id) {
if (!is_data_mounted(title_id) &&
!sm_shellcore_ensure_title_runtime(title_id)) {
return false;
}
if (!register_game_exit_watch(kq, pid)) {
log_debug(" [GAME] skipping launch tracking for %s pid=%ld without exit watch",
title_id, (long)pid);
@@ -226,7 +205,7 @@ static void drain_game_lifecycle_wake_pipe(void) {
}
}
static bool drain_game_lifecycle_events_nowait(int kq) {
static bool discard_game_lifecycle_events_nowait(int kq) {
struct kevent events[16];
struct timespec timeout;
memset(&timeout, 0, sizeof(timeout));
@@ -242,15 +221,17 @@ static bool drain_game_lifecycle_events_nowait(int kq) {
}
if (nev == 0)
return true;
for (int i = 0; i < nev; ++i) {
if (events[i].filter == EVFILT_PROC &&
(events[i].fflags & NOTE_EXIT) != 0) {
handle_game_exit((pid_t)events[i].ident);
}
}
}
}
static bool set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
static void set_game_lifecycle_start_result(bool success) {
pthread_mutex_lock(&g_game_lifecycle_start_mutex);
g_game_lifecycle_start_success = success;
@@ -445,13 +426,11 @@ static void handle_game_exit(pid_t pid) {
pending_game_launch_t *entry = find_pending_game_launch(pid);
if (entry)
clear_pending_game_launch(entry);
if (had_active_title)
if (atomic_load(&g_active_game_pid) == pid)
publish_active_game_pid(0);
sm_fakelib_game_on_exit(pid);
sm_kstuff_game_on_exit(pid);
if (had_active_title) {
if (!sm_shellcore_release_title_runtime(title_id))
log_debug(" [SHELLCORE] runtime release deferred/failed: %s", title_id);
int snd0_updates = normalize_snd0info_for_title(title_id);
if (snd0_updates >= 0)
log_debug(" [DB] snd0info normalized after game exit rows=%d title=%s",
@@ -459,57 +438,21 @@ static void handle_game_exit(pid_t pid) {
}
}
static bool terminate_usb_game_for_sleep(int kq, pid_t pid,
const char *title_id) {
if (kill(pid, SIGKILL) != 0 && errno != ESRCH) {
log_debug(" [SLEEP] failed to kill USB game: %s pid=%ld: %s", title_id,
(long)pid, strerror(errno));
return false;
}
log_debug(" [SLEEP] killing USB game before suspend: %s pid=%ld", title_id,
(long)pid);
static void restore_suspended_game_if_alive(int kq, pid_t pid) {
if (pid <= 0 || !is_process_alive(pid))
return;
char title_id[MAX_TITLE_ID];
uint32_t app_id = 0;
if (!resolve_game_title_id(pid, title_id, &app_id))
return;
if (!is_supported_title_id(title_id))
return;
uint64_t now_us = monotonic_time_us();
uint64_t deadline_us =
now_us == 0 ? 0 : now_us + GAME_SLEEP_EXIT_TIMEOUT_US;
while (!g_game_lifecycle_stop_requested && !should_stop_requested()) {
struct kevent event;
struct timespec timeout = {
.tv_sec = 0,
.tv_nsec = (long)GAME_LIFECYCLE_POLL_INTERVAL_US * 1000L,
};
int nev = kevent(kq, NULL, 0, &event, 1, &timeout);
if (nev < 0) {
if (errno == EINTR)
continue;
log_debug(" [SLEEP] NOTE_EXIT wait failed for %s pid=%ld: %s",
title_id, (long)pid, strerror(errno));
return false;
}
if (nev > 0) {
if (event.filter == EVFILT_READ &&
event.ident == (uintptr_t)g_game_lifecycle_wake_pipe[0]) {
drain_game_lifecycle_wake_pipe();
} else if (event.filter == EVFILT_PROC &&
(event.fflags & NOTE_EXIT) != 0) {
pid_t exited_pid = (pid_t)event.ident;
handle_game_exit(exited_pid);
if (exited_pid == pid) {
log_debug(" [SLEEP] USB game stopped: %s pid=%ld", title_id,
(long)pid);
return true;
}
}
}
now_us = monotonic_time_us();
if (deadline_us != 0 && now_us != 0 && now_us >= deadline_us) {
log_debug(" [SLEEP] timed out waiting NOTE_EXIT: %s pid=%ld", title_id,
(long)pid);
return false;
}
}
return false;
log_debug(" [GAME] resumed: %s pid=%ld app_id=0x%08X", title_id,
(long)pid, app_id);
(void)dispatch_game_launch(kq, pid, now_us, title_id, app_id);
}
static void *game_lifecycle_watcher_main(void *arg) {
@@ -558,36 +501,10 @@ static void *game_lifecycle_watcher_main(void *arg) {
if (runtime_sleep_mode_active()) {
if (!sleep_cleanup_done) {
suspended_game_pid = atomic_load(&g_active_game_pid);
char usb_game_title_id[MAX_TITLE_ID];
bool active_game = snapshot_active_game(suspended_game_pid,
usb_game_title_id);
char source_path[MAX_PATH];
bool has_mount_link =
active_game && read_mount_link(usb_game_title_id, source_path,
sizeof(source_path));
bool usb_game =
active_game && title_is_usb_backed(
usb_game_title_id, source_path, has_mount_link);
clear_all_pending_game_launches();
sm_fakelib_game_shutdown();
sm_kstuff_sleep_enter();
bool usb_cleanup_allowed = true;
if (usb_game) {
usb_cleanup_allowed = terminate_usb_game_for_sleep(
kq, suspended_game_pid, usb_game_title_id);
if (usb_cleanup_allowed) {
suspended_game_pid = 0;
}
}
if (usb_cleanup_allowed) {
runtime_mount_state_lock();
unmount_usb_sources_for_suspend();
runtime_mount_state_unlock();
} else {
log_debug("[SLEEP] USB cleanup skipped while game process remains");
}
// Keep a live non-USB game published while it is suspended so scanner
// work cannot race its unchanged runtime mount.
publish_active_game(0, NULL);
sleep_cleanup_done = true;
}
@@ -606,13 +523,10 @@ static void *game_lifecycle_watcher_main(void *arg) {
continue;
}
if (sleep_cleanup_done) {
if (!drain_game_lifecycle_events_nowait(kq))
if (!discard_game_lifecycle_events_nowait(kq))
break;
sleep_cleanup_done = false;
if (runtime_sleep_mode_active())
continue;
if (suspended_game_pid > 0 && is_process_alive(suspended_game_pid))
handle_game_exec(kq, suspended_game_pid);
restore_suspended_game_if_alive(kq, suspended_game_pid);
sm_kstuff_sleep_leave();
suspended_game_pid = 0;
}
@@ -664,8 +578,8 @@ bool start_game_lifecycle_watcher(void) {
log_debug(" [GAME] wake pipe creation failed: %s", strerror(errno));
return false;
}
if (!sm_set_fd_nonblocking(g_game_lifecycle_wake_pipe[0]) ||
!sm_set_fd_nonblocking(g_game_lifecycle_wake_pipe[1])) {
if (!set_fd_nonblocking(g_game_lifecycle_wake_pipe[0]) ||
!set_fd_nonblocking(g_game_lifecycle_wake_pipe[1])) {
log_debug(" [GAME] wake pipe nonblocking setup failed: %s",
strerror(errno));
close_game_lifecycle_wake_pipe();
+31 -147
View File
@@ -515,20 +515,15 @@ static const image_backend_ops_t *get_image_backend_ops(attach_backend_t backend
return NULL;
}
static bool get_cached_image_mount(const char *file_path,
image_cache_entry_t *entry_out,
int *index_out) {
image_cache_entry_t entry;
int index = -1;
if (!find_image_cache_entry(file_path, &entry, &index) ||
entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0) {
return false;
static bool is_cached_image_path(const char *file_path) {
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
if (get_image_cache_entry(k, &cached_entry) &&
strcmp(cached_entry.path, file_path) == 0) {
return true;
}
}
if (entry_out)
*entry_out = entry;
if (index_out)
*index_out = index;
return true;
return false;
}
static bool directory_has_visible_entries(const char *path) {
@@ -572,7 +567,7 @@ static bool reject_mounted_image_io(const char *file_path,
log_debug(" [IMG][%s] unreadable or damaged mount (%s -> %s, %s): %s",
attach_backend_name(attach_backend), devname, mount_point, stage,
strerror(io_err));
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = io_err;
return false;
}
@@ -591,8 +586,7 @@ static bool prepare_image_mount_retry(const image_cache_entry_t *cached_entry,
log_debug(" [IMG][%s] mount unreadable, retrying: %s -> %s: %s",
attach_backend_name(cached_entry->backend), source_path,
mount_point, strerror(root_err));
if (!unmount_runtime_image(source_path, cached_entry->unit_id,
cached_entry->backend))
if (!unmount_image(source_path, cached_entry->unit_id, cached_entry->backend))
return false;
} else {
log_debug(" [IMG][%s] mount lost, retrying: %s -> %s",
@@ -623,8 +617,7 @@ static bool reuse_existing_image_mount(const char *file_path,
log_debug(" [IMG][%s] Existing mount unreadable, reattaching: %s -> %s: %s",
attach_backend_name(existing_backend), file_path, mount_point,
strerror(root_err));
if (!unmount_runtime_image(file_path, existing_unit,
existing_backend)) {
if (!unmount_image(file_path, existing_unit, existing_backend)) {
if (cache_failed_out)
*cache_failed_out = true;
return false;
@@ -664,26 +657,10 @@ static bool handle_cached_or_existing_image_mount_locked(
if (runtime_sleep_mode_active())
return true;
image_cache_entry_t cached_entry;
int cached_index = -1;
if (get_cached_image_mount(file_path, &cached_entry, &cached_index)) {
int root_err = 0;
if (is_active_image_mount_point(mount_point) &&
is_image_mount_root_accessible(mount_point, &root_err)) {
if (success_out)
*success_out = true;
return true;
}
log_debug(" [IMG][%s] cached mount is not active, reattaching: %s -> "
"%s",
attach_backend_name(cached_entry.backend), file_path,
mount_point);
if (!unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
return true;
}
invalidate_image_cache_entry(cached_index);
if (is_cached_image_path(file_path)) {
if (success_out)
*success_out = true;
return true;
}
bool cache_failed = false;
@@ -937,7 +914,7 @@ static bool validate_mounted_image(const char *file_path, image_fs_type_t fs_typ
min_device_sector);
}
sm_error_mark_notified();
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = EINVAL;
return false;
}
@@ -1031,7 +1008,7 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
return false;
}
if (runtime_sleep_mode_active()) {
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
runtime_mount_state_unlock();
return false;
}
@@ -1053,7 +1030,7 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
(unsigned)MAX_IMAGE_MOUNTS);
log_debug(" [IMG] image cache full, rolling back mount: %s -> %s",
file_path, mount_point);
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = ENOSPC;
runtime_mount_state_unlock();
return false;
@@ -1062,31 +1039,20 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
return true;
}
static bool unmount_child_image_mounts_for_container(
const char *mount_point, bool cleanup_source_state) {
static bool unmount_child_image_mounts_for_container(const char *mount_point) {
bool all_unmounted = true;
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!path_matches_root_or_child(cached_entry.path, mount_point))
continue;
log_debug(" [IMG] container child unmount: %s source=%s",
mount_point, cached_entry.path);
bool unmounted = cleanup_source_state
? unmount_image(cached_entry.path,
cached_entry.unit_id,
cached_entry.backend)
: unmount_runtime_image(cached_entry.path,
cached_entry.unit_id,
cached_entry.backend);
if (unmounted) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
invalidate_image_cache_entry(k);
continue;
}
@@ -1099,9 +1065,7 @@ static bool unmount_child_image_mounts_for_container(
return all_unmounted;
}
static bool unmount_image_impl(const char *file_path, int unit_id,
attach_backend_t backend,
bool cleanup_source_state) {
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend) {
char mount_point[MAX_PATH];
image_fs_type_t fs_type = detect_image_fs_type_for_path(file_path, NULL);
build_image_mount_point_for_fs(file_path, fs_type, mount_point);
@@ -1121,16 +1085,14 @@ static bool unmount_image_impl(const char *file_path, int unit_id,
resolved_unit);
if (fs_type == IMAGE_FS_PFSC_CONTAINER &&
!unmount_child_image_mounts_for_container(mount_point,
cleanup_source_state)) {
!unmount_child_image_mounts_for_container(mount_point)) {
return false;
}
if (cleanup_source_state) {
// Permanent source cleanup removes title links before releasing the image.
cleanup_mount_links_for_source_unmount(mount_point);
clear_cached_game(mount_point);
}
// Remove mount.lnk and unmount /system_ex/app/<titleid> that point to this
// source before unmounting the virtual disk itself.
cleanup_mount_links_for_source_unmount(mount_point);
clear_cached_game(mount_point);
// Unmount stacked layers (unionfs over image fs).
for (int i = 0; i < MAX_LAYERED_UNMOUNT_ATTEMPTS; i++) {
@@ -1190,72 +1152,10 @@ static bool unmount_image_impl(const char *file_path, int unit_id,
return detach_ok;
}
bool unmount_image(const char *file_path, int unit_id,
attach_backend_t backend) {
return unmount_image_impl(file_path, unit_id, backend, true);
}
bool unmount_runtime_image(const char *file_path, int unit_id,
attach_backend_t backend) {
return unmount_image_impl(file_path, unit_id, backend, false);
}
bool release_runtime_image_mount(const char *file_path) {
image_cache_entry_t entry;
int index = -1;
if (!find_image_cache_entry(file_path, &entry, &index) ||
entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0) {
return true;
}
if (!unmount_runtime_image(entry.path, entry.unit_id, entry.backend))
return false;
invalidate_image_cache_entry(index);
return true;
}
bool release_runtime_image_mounts(void) {
bool all_released = true;
for (int pass = 0; pass < MAX_LAYERED_UNMOUNT_ATTEMPTS; ++pass) {
bool any_remaining = false;
bool progress = false;
for (int k = 0; k < MAX_IMAGE_MOUNTS; ++k) {
image_cache_entry_t entry;
if (!get_image_cache_entry(k, &entry))
continue;
if (entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0)
continue;
if (unmount_runtime_image(entry.path, entry.unit_id, entry.backend)) {
invalidate_image_cache_entry(k);
progress = true;
} else {
any_remaining = true;
all_released = false;
}
}
if (!any_remaining)
return true;
if (!progress)
break;
}
return all_released;
}
void cleanup_stale_image_mounts(void) {
if (should_stop_requested())
return;
bool has_cached_mount = false;
for (int k = 0; k < MAX_IMAGE_MOUNTS; ++k) {
image_cache_entry_t entry;
if (get_image_cache_entry(k, &entry) &&
entry.backend != ATTACH_BACKEND_NONE && entry.unit_id >= 0) {
has_cached_mount = true;
break;
}
}
if (!has_cached_mount)
return;
log_debug(" [IMG] stale image cleanup begin");
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
@@ -1263,10 +1163,6 @@ void cleanup_stale_image_mounts(void) {
return;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
log_debug(" [IMG][%s] stale cleanup check: slot=%d source=%s mount=%s",
attach_backend_name(cached_entry.backend), k, cached_entry.path,
@@ -1317,10 +1213,6 @@ void cleanup_stale_image_mounts_for_root(const char *root) {
return;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!path_matches_root_or_child(cached_entry.path, root) &&
!path_matches_root_or_child(cached_entry.mount_point, root)) {
continue;
@@ -1362,17 +1254,13 @@ bool unmount_usb_image_mounts_for_suspend(void) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!is_usb_storage_path(cached_entry.path))
continue;
log_debug(" [IMG][%s] USB suspend unmount: %s",
attach_backend_name(cached_entry.backend), cached_entry.path);
if (unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
log_debug(" [IMG][%s] USB suspend unmounted: %s",
attach_backend_name(cached_entry.backend), cached_entry.path);
invalidate_image_cache_entry(k);
@@ -1471,13 +1359,9 @@ bool shutdown_image_mounts(void) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
invalidate_image_cache_entry(k);
progress = true;
continue;
+7 -121
View File
@@ -15,16 +15,6 @@ struct ImageCache {
static struct ImageCache g_image_cache[MAX_IMAGE_MOUNTS];
static pthread_mutex_t g_image_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static void copy_cache_entry(int index, image_cache_entry_t *entry_out) {
memset(entry_out, 0, sizeof(*entry_out));
(void)strlcpy(entry_out->path, g_image_cache[index].path,
sizeof(entry_out->path));
(void)strlcpy(entry_out->mount_point, g_image_cache[index].mount_point,
sizeof(entry_out->mount_point));
entry_out->unit_id = g_image_cache[index].unit_id;
entry_out->backend = g_image_cache[index].backend;
}
static int find_cache_index(const char *path, const char *mount_point) {
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
if (!g_image_cache[k].valid)
@@ -65,18 +55,6 @@ static int upsert_image_source_mapping(const char *path, const char *mount_point
bool cache_image_source_mapping(const char *path, const char *mount_point) {
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = find_cache_index(path, mount_point);
if (entry_index >= 0) {
const struct ImageCache *entry = &g_image_cache[entry_index];
// A metadata-only path mapping must never retarget a live mount entry.
// The live entry owns its actual filesystem mount point until release.
if (entry->backend != ATTACH_BACKEND_NONE && entry->unit_id >= 0 &&
(strcmp(entry->path, path) != 0 ||
strcmp(entry->mount_point, mount_point) != 0)) {
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
}
bool ok = upsert_image_source_mapping(path, mount_point) >= 0;
pthread_mutex_unlock(&g_image_cache_mutex);
return ok;
@@ -105,25 +83,13 @@ bool get_image_cache_entry(int index, image_cache_entry_t *entry_out) {
return false;
}
copy_cache_entry(index, entry_out);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
int *index_out) {
if (!path || !entry_out)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_cache_index(path, NULL);
if (index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
copy_cache_entry(index, entry_out);
if (index_out)
*index_out = index;
memset(entry_out, 0, sizeof(*entry_out));
(void)strlcpy(entry_out->path, g_image_cache[index].path,
sizeof(entry_out->path));
(void)strlcpy(entry_out->mount_point, g_image_cache[index].mount_point,
sizeof(entry_out->mount_point));
entry_out->unit_id = g_image_cache[index].unit_id;
entry_out->backend = g_image_cache[index].backend;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
@@ -174,83 +140,3 @@ bool resolve_image_source_from_mount_cache(const char *mount_point,
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
static int find_owning_cache_index(const char *path) {
int best_index = -1;
size_t best_length = 0;
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (!g_image_cache[i].valid)
continue;
size_t length = strlen(g_image_cache[i].mount_point);
if (length <= best_length || strncmp(path, g_image_cache[i].mount_point,
length) != 0) {
continue;
}
if (path[length] != '\0' && path[length] != '/')
continue;
best_index = i;
best_length = length;
}
return best_index;
}
static size_t find_owning_chain_indices(const char *path,
int indices[MAX_IMAGE_CHAIN_DEPTH]) {
char current[MAX_PATH];
(void)strlcpy(current, path, sizeof(current));
size_t count = 0;
while (count < MAX_IMAGE_CHAIN_DEPTH) {
int index = find_owning_cache_index(current);
if (index < 0)
break;
indices[count++] = index;
(void)strlcpy(current, g_image_cache[index].path, sizeof(current));
}
return count;
}
bool resolve_owning_image_source_from_mount_cache(const char *path,
char *path_out,
size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_owning_cache_index(path);
if (index >= 0)
(void)strlcpy(path_out, g_image_cache[index].path, path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return index >= 0;
}
size_t resolve_image_source_chain_from_mount_cache(
const char *path,
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]) {
if (!path || !chain)
return 0;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
for (size_t i = 0; i < count; ++i)
(void)strlcpy(chain[i], g_image_cache[indices[count - i - 1u]].path,
MAX_PATH);
pthread_mutex_unlock(&g_image_cache_mutex);
return count;
}
bool resolve_outermost_image_source_from_mount_cache(
const char *path, char *path_out, size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
if (count > 0)
(void)strlcpy(path_out, g_image_cache[indices[count - 1u]].path,
path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return count > 0;
}
-309
View File
@@ -1,309 +0,0 @@
#include "sm_platform.h"
#include "sm_appdb.h"
#include "sm_filesystem.h"
#include "sm_image_cache.h"
#include "sm_image_index.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_path_utils.h"
#include "sm_paths.h"
#define IMAGE_INDEX_FILE IMAGE_INDEX_FILE_PATH
#define IMAGE_INDEX_TEMP_FILE LOG_DIR "/image_index.bin.tmp"
#define IMAGE_INDEX_MAGIC 0x58444953u
#define IMAGE_INDEX_VERSION 3u
typedef struct {
char path[MAX_PATH];
int64_t size;
int64_t mtime_sec;
int32_t mtime_nsec;
uint8_t complete;
uint8_t valid;
uint8_t reserved[2];
} image_index_entry_t;
typedef struct {
uint16_t image_index;
uint8_t valid;
uint8_t reserved;
char title_id[MAX_TITLE_ID];
} image_index_title_t;
typedef struct {
uint32_t magic;
uint32_t version;
uint32_t entry_capacity;
uint32_t title_capacity;
} image_index_header_t;
static image_index_entry_t g_image_index[MAX_IMAGE_MOUNTS];
static image_index_title_t g_image_titles[MAX_PENDING];
static bool g_image_index_loaded;
static bool g_image_index_dirty;
static bool stamp_matches(const image_index_entry_t *entry,
const struct stat *st) {
return entry->size == (int64_t)st->st_size &&
entry->mtime_sec == (int64_t)st->st_mtim.tv_sec &&
entry->mtime_nsec == (int32_t)st->st_mtim.tv_nsec;
}
static int find_entry(const char *path) {
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (g_image_index[i].valid && strcmp(g_image_index[i].path, path) == 0)
return i;
}
return -1;
}
static void clear_entry_titles(int entry_index) {
for (int i = 0; i < MAX_PENDING; ++i) {
if (g_image_titles[i].valid &&
g_image_titles[i].image_index == (uint16_t)entry_index) {
memset(&g_image_titles[i], 0, sizeof(g_image_titles[i]));
}
}
}
static void clear_entry(int entry_index) {
clear_entry_titles(entry_index);
memset(&g_image_index[entry_index], 0, sizeof(g_image_index[entry_index]));
g_image_index_dirty = true;
}
static bool save_index(void) {
if (!g_image_index_dirty)
return true;
FILE *file = fopen(IMAGE_INDEX_TEMP_FILE, "wb");
if (!file)
return false;
image_index_header_t header = {
.magic = IMAGE_INDEX_MAGIC,
.version = IMAGE_INDEX_VERSION,
.entry_capacity = MAX_IMAGE_MOUNTS,
.title_capacity = MAX_PENDING,
};
bool ok = fwrite(&header, sizeof(header), 1, file) == 1 &&
fwrite(g_image_index, sizeof(g_image_index), 1, file) == 1 &&
fwrite(g_image_titles, sizeof(g_image_titles), 1, file) == 1 &&
fflush(file) == 0;
if (ok && fsync(fileno(file)) != 0)
ok = false;
if (fclose(file) != 0)
ok = false;
if (!ok || rename(IMAGE_INDEX_TEMP_FILE, IMAGE_INDEX_FILE) != 0) {
int saved_errno = errno;
if (saved_errno == 0)
saved_errno = EIO;
(void)unlink(IMAGE_INDEX_TEMP_FILE);
errno = saved_errno;
return false;
}
g_image_index_dirty = false;
return true;
}
static bool loaded_index_is_valid(void) {
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
const image_index_entry_t *entry = &g_image_index[i];
if (!entry->valid)
continue;
if (entry->complete > 1 || entry->path[0] == '\0' ||
memchr(entry->path, '\0', sizeof(entry->path)) == NULL) {
return false;
}
}
for (int i = 0; i < MAX_PENDING; ++i) {
const image_index_title_t *title = &g_image_titles[i];
if (!title->valid)
continue;
if (title->image_index >= MAX_IMAGE_MOUNTS ||
!g_image_index[title->image_index].valid ||
title->title_id[0] == '\0' ||
memchr(title->title_id, '\0', sizeof(title->title_id)) == NULL) {
return false;
}
}
return true;
}
static void load_index(void) {
if (g_image_index_loaded)
return;
g_image_index_loaded = true;
FILE *file = fopen(IMAGE_INDEX_FILE, "rb");
if (!file)
return;
image_index_header_t header;
bool ok = fread(&header, sizeof(header), 1, file) == 1 &&
header.magic == IMAGE_INDEX_MAGIC &&
header.version == IMAGE_INDEX_VERSION &&
header.entry_capacity == MAX_IMAGE_MOUNTS &&
header.title_capacity == MAX_PENDING &&
fread(g_image_index, sizeof(g_image_index), 1, file) == 1 &&
fread(g_image_titles, sizeof(g_image_titles), 1, file) == 1;
(void)fclose(file);
if (ok)
ok = loaded_index_is_valid();
if (!ok) {
memset(g_image_index, 0, sizeof(g_image_index));
memset(g_image_titles, 0, sizeof(g_image_titles));
(void)unlink(IMAGE_INDEX_FILE);
log_debug(" [IMGIDX] invalid cache discarded: %s", IMAGE_INDEX_FILE);
}
}
static int reserve_entry(const char *path) {
int entry_index = find_entry(path);
if (entry_index >= 0)
return entry_index;
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (!g_image_index[i].valid) {
g_image_index[i].valid = 1;
(void)strlcpy(g_image_index[i].path, path, sizeof(g_image_index[i].path));
return i;
}
}
return -1;
}
static bool cached_titles_ready(int entry_index,
const struct AppDbTitleList *app_db_titles,
bool app_db_titles_ready) {
const char *image_path = g_image_index[entry_index].path;
for (int i = 0; i < MAX_PENDING; ++i) {
if (!g_image_titles[i].valid ||
g_image_titles[i].image_index != (uint16_t)entry_index) {
continue;
}
const char *title_id = g_image_titles[i].title_id;
if (!is_installed(title_id) || !has_appmeta_data(title_id))
return false;
char runtime_path[MAX_PATH];
char linked_image[MAX_PATH];
if (!read_mount_link(title_id, runtime_path, sizeof(runtime_path)) ||
!is_under_image_mount_base(runtime_path) ||
!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) {
return false;
}
if (app_db_titles_ready &&
!app_db_title_list_contains(app_db_titles, title_id)) {
return false;
}
}
return true;
}
bool sm_image_index_needs_scan(const char *path, const struct stat *st,
const struct AppDbTitleList *app_db_titles,
bool app_db_titles_ready) {
load_index();
int entry_index = find_entry(path);
if (entry_index < 0 || !g_image_index[entry_index].complete ||
!stamp_matches(&g_image_index[entry_index], st)) {
return true;
}
return !cached_titles_ready(entry_index, app_db_titles, app_db_titles_ready);
}
void sm_image_index_begin_scan(const char *path, const struct stat *st) {
load_index();
int entry_index = reserve_entry(path);
if (entry_index < 0) {
log_debug(" [IMGIDX] cache full, scanning without fingerprint: %s", path);
return;
}
clear_entry_titles(entry_index);
image_index_entry_t *entry = &g_image_index[entry_index];
entry->size = (int64_t)st->st_size;
entry->mtime_sec = (int64_t)st->st_mtim.tv_sec;
entry->mtime_nsec = (int32_t)st->st_mtim.tv_nsec;
entry->complete = 0;
entry->valid = 1;
g_image_index_dirty = true;
}
void sm_image_index_record_game(const char *game_path, const char *title_id) {
load_index();
char image_path[MAX_PATH];
if (!resolve_outermost_image_source_from_mount_cache(
game_path, image_path, sizeof(image_path))) {
return;
}
int entry_index = find_entry(image_path);
if (entry_index < 0) {
struct stat st;
if (stat(image_path, &st) != 0)
return;
sm_image_index_begin_scan(image_path, &st);
entry_index = find_entry(image_path);
if (entry_index < 0)
return;
}
char tracked_path[MAX_PATH];
char linked_image[MAX_PATH];
if (is_installed(title_id) &&
read_mount_link(title_id, tracked_path, sizeof(tracked_path)) &&
strcmp(tracked_path, game_path) == 0 &&
(!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) &&
!write_mount_image_link(title_id, image_path)) {
log_debug(" [IMGIDX] failed to repair image link: %s -> %s", title_id,
image_path);
}
for (int i = 0; i < MAX_PENDING; ++i) {
if (g_image_titles[i].valid &&
g_image_titles[i].image_index == (uint16_t)entry_index &&
strcmp(g_image_titles[i].title_id, title_id) == 0) {
return;
}
}
for (int i = 0; i < MAX_PENDING; ++i) {
if (g_image_titles[i].valid)
continue;
g_image_titles[i].valid = 1;
g_image_titles[i].image_index = (uint16_t)entry_index;
(void)strlcpy(g_image_titles[i].title_id, title_id,
sizeof(g_image_titles[i].title_id));
g_image_index_dirty = true;
return;
}
log_debug(" [IMGIDX] title cache full: %s", title_id);
}
void sm_image_index_complete_scan(const char *path) {
load_index();
int entry_index = find_entry(path);
if (entry_index < 0)
return;
image_index_entry_t *entry = &g_image_index[entry_index];
if (!entry->complete) {
entry->complete = 1;
g_image_index_dirty = true;
}
}
void sm_image_index_flush(void) {
load_index();
if (!save_index())
log_debug(" [IMGIDX] failed to persist scan index: %s", strerror(errno));
}
void sm_image_index_prune(void) {
struct stat st;
load_index();
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (g_image_index[i].valid && stat(g_image_index[i].path, &st) != 0 &&
errno == ENOENT) {
clear_entry(i);
}
}
if (!save_index())
log_debug(" [IMGIDX] failed to persist prune: %s", strerror(errno));
}
+44 -36
View File
@@ -14,7 +14,6 @@
#include "sm_image_cache.h"
#include "sm_paths.h"
#include "sm_manual.h"
#include "sm_shellcore_hooks.h"
#include <dlfcn.h>
@@ -62,6 +61,29 @@ static app_install_title_dir_fn_t resolve_app_install_title_dir(void) {
return NULL;
}
static bool write_link_file(const char *path, const char *value) {
FILE *f = fopen(path, "w");
if (!f) {
log_debug(" [LINK] open failed for %s: %s", path, strerror(errno));
return false;
}
int saved_errno = 0;
if (fprintf(f, "%s", value) < 0)
saved_errno = errno;
if (fflush(f) != 0 && saved_errno == 0)
saved_errno = errno;
if (fclose(f) != 0 && saved_errno == 0)
saved_errno = errno;
if (saved_errno != 0) {
errno = saved_errno;
log_debug(" [LINK] write failed for %s: %s", path, strerror(errno));
return false;
}
return true;
}
static bool is_appmeta_file(const char *name) {
if (!name)
return false;
@@ -117,11 +139,11 @@ static bool copy_optional_trophy_metadata_file(const char *src_sce_sys,
char dst_path[MAX_PATH];
snprintf(src_path, sizeof(src_path), "%s/%s", src_sce_sys, relative_path);
if (!path_exists(src_path))
if (access(src_path, F_OK) != 0)
return true;
snprintf(dst_path, sizeof(dst_path), "%s/%s", dst_base, relative_path);
if (path_exists(dst_path))
if (access(dst_path, F_OK) == 0)
return true;
if (copy_file(src_path, dst_path) == 0)
@@ -171,8 +193,7 @@ static bool mount_and_install(const char *src_path, const char *title_id,
char user_sce_sys[MAX_PATH];
char src_sce_sys[MAX_PATH];
char src_snd0[MAX_PATH];
char image_source_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
size_t image_source_count = 0;
char image_source_path[MAX_PATH];
bool has_image_source = false;
bool appmeta_missing = false;
bool metadata_restaged = false;
@@ -185,9 +206,8 @@ static bool mount_and_install(const char *src_path, const char *title_id,
snprintf(user_app_dir, sizeof(user_app_dir), "%s/%s", APP_BASE, title_id);
if (is_under_image_mount_base(src_path)) {
image_source_count = resolve_image_source_chain_from_mount_cache(
src_path, image_source_chain);
has_image_source = image_source_count > 0;
has_image_source = resolve_image_source_from_mount_cache(
src_path, image_source_path, sizeof(image_source_path));
if (!has_image_source) {
log_debug(" [LINK] image source lookup failed for %s: %s", title_id,
src_path);
@@ -269,8 +289,7 @@ static bool mount_and_install(const char *src_path, const char *title_id,
mkdir(APP_BASE, 0777);
mkdir(user_app_dir, 0777);
snprintf(lnk_path, sizeof(lnk_path), "%s/mount.lnk", user_app_dir);
if (!write_mount_link(title_id, src_path)) {
log_debug(" [LINK] write failed for %s: %s", lnk_path, strerror(errno));
if (!write_link_file(lnk_path, src_path)) {
(void)rollback_title_nullfs_mount(title_id, src_path);
runtime_mount_state_unlock();
return false;
@@ -282,18 +301,17 @@ static bool mount_and_install(const char *src_path, const char *title_id,
snprintf(img_lnk_path, sizeof(img_lnk_path), "%s/mount_img.lnk",
user_app_dir);
if (has_image_source) {
if (!write_mount_image_chain(title_id, image_source_chain,
image_source_count)) {
if (!write_link_file(img_lnk_path, image_source_path)) {
(void)unlink(lnk_path);
(void)rollback_title_nullfs_mount(title_id, src_path);
runtime_mount_state_unlock();
return false;
}
log_debug(" [LINK] mount_img.lnk created: %s -> %s layers=%zu",
img_lnk_path, image_source_chain[0], image_source_count);
if (!cache_image_source_mapping(image_source_chain[0], src_path)) {
log_debug(" [LINK] mount_img.lnk created: %s -> %s", img_lnk_path,
image_source_path);
if (!cache_image_source_mapping(image_source_path, src_path)) {
log_debug(" [LINK] image source cache update failed: %s -> %s",
image_source_chain[0], src_path);
src_path, image_source_path);
}
} else if (unlink(img_lnk_path) != 0 && errno != ENOENT) {
log_debug(" [LINK] remove failed for %s: %s", img_lnk_path,
@@ -324,27 +342,17 @@ static bool mount_and_install(const char *src_path, const char *title_id,
}
// REGISTER
int res = 0;
if (sm_shellcore_install_bridge_enabled()) {
if (!sm_shellcore_install_title_dir(title_id, APP_BASE "/", &res)) {
log_debug(" [REG] internal AppInstallTitleDir bridge unavailable");
notify_system("Register failed: %s (%s)\nTitleDir bridge unavailable",
title_name, title_id);
return false;
}
mark_register_attempted(title_id);
} else {
app_install_title_dir_fn_t app_install_title_dir_fn =
resolve_app_install_title_dir();
if (!app_install_title_dir_fn) {
log_debug(" [REG] sceAppInstUtilAppInstallTitleDir unavailable");
notify_system("Register failed: %s (%s)\nAppInstallTitleDir unavailable",
title_name, title_id);
return false;
}
mark_register_attempted(title_id);
res = app_install_title_dir_fn(title_id, APP_BASE "/", NULL);
app_install_title_dir_fn_t app_install_title_dir_fn =
resolve_app_install_title_dir();
if (!app_install_title_dir_fn) {
log_debug(" [REG] sceAppInstUtilAppInstallTitleDir unavailable");
notify_system("Register failed: %s (%s)\nAppInstallTitleDir unavailable",
title_name, title_id);
return false;
}
mark_register_attempted(title_id);
int res = app_install_title_dir_fn(title_id, APP_BASE "/", 0);
sceKernelUsleep(200000);
if (res == 0) {
+1 -8
View File
@@ -9,7 +9,6 @@
#include "sm_log.h"
#include "sm_manual.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_time.h"
#include "sm_title_state.h"
@@ -50,10 +49,6 @@ static bool install_queue_active(void) {
return runtime_config()->app_install_all_enabled || g_tracked_install_count > 0;
}
bool sm_install_has_pending_work(void) {
return g_tracked_install_count > 0;
}
static pending_install_entry_t *find_pending_install_entry(
const char *title_id) {
if (!title_id || title_id[0] == '\0')
@@ -285,8 +280,6 @@ static void poll_pending_installs(void) {
if (g_submitted_install_count == 0)
schedule_queued_install_submit(now_us);
free_app_db_title_list(&app_db_titles);
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
log_debug(" [IMG] deferred release after install completion");
}
uint64_t sm_install_next_wake_us(uint64_t now_us) {
@@ -432,7 +425,7 @@ bool sm_install_submit_queued(void) {
log_queued_install_batch();
notify_queued_install_batch();
int res = sceAppInstUtilAppInstallAll(NULL);
int res = sceAppInstUtilAppInstallAll();
if (res != 0) {
log_debug(" [REG] Batch install request failed: 0x%x", res);
if (!g_queued_install_submit_failure_notified) {
+34 -82
View File
@@ -4,7 +4,6 @@
#include "sm_scan_tree.h"
#include "sm_types.h"
#include "sm_game_cache.h"
#include "sm_game_lifecycle.h"
#include "sm_gameinfo.h"
#include "sm_log.h"
#include "sm_config_mount.h"
@@ -17,11 +16,9 @@
#include "sm_stability.h"
#include "sm_title_state.h"
#include "sm_image_cache.h"
#include "sm_image_index.h"
#include "sm_image.h"
#include "sm_install_queue.h"
#include "sm_manual.h"
#include "sm_shellcore_service.h"
typedef struct {
char discovered_param_roots[MAX_PENDING][MAX_PATH];
@@ -270,11 +267,9 @@ static existing_directory_result_t handle_existing_directory_candidate(
link_matches_source = installed && link_matches_source;
bool source_valid = false;
if (link_matches_source) {
char eboot_path[MAX_PATH];
int written = snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin",
full_path);
source_valid = written > 0 && (size_t)written < sizeof(eboot_path) &&
path_exists(eboot_path);
source_valid = is_data_mounted(info->title_id);
if (!source_valid && mount_title_nullfs(info->title_id, full_path))
source_valid = is_data_mounted(info->title_id);
}
if (source_valid && appmeta_present) {
@@ -370,8 +365,6 @@ static bool try_collect_candidate_for_directory(
if (probe_result == DIRECTORY_CANDIDATE_DESCEND)
return false;
sm_image_index_record_game(full_path, info.title_id);
int duplicate_candidate_index = find_scan_candidate_index_by_title_id(
candidates, *candidate_count, info.title_id);
const char *duplicate_candidate_path =
@@ -474,36 +467,17 @@ static bool collect_candidate_image_visit(const char *image_path,
(void)depth_from_root;
collect_candidates_walk_ctx_t *ctx = (collect_candidates_walk_ctx_t *)ctx_ptr;
struct stat image_st;
if (stat(image_path, &image_st) != 0)
if (!maybe_mount_image_file(image_path, image_name, ctx->unstable_found_out))
return true;
bool persistent_image = !is_under_image_mount_base(image_path);
if (persistent_image &&
!sm_image_index_needs_scan(image_path, &image_st, ctx->app_db->titles,
ctx->app_db->titles_ready)) {
return true;
}
bool image_unstable = false;
if (!maybe_mount_image_file(image_path, image_name, &image_unstable)) {
if (image_unstable && ctx->unstable_found_out)
*ctx->unstable_found_out = true;
return true;
}
if (persistent_image)
sm_image_index_begin_scan(image_path, &image_st);
char mount_point[MAX_PATH];
get_image_mount_point_for_source(image_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, ctx->manual_source_path, ctx->candidates,
ctx->max_candidates, ctx->candidate_count, ctx->app_db,
ctx->discovered_param_roots, ctx->discovered_param_root_count,
&image_unstable);
if (image_unstable && ctx->unstable_found_out)
*ctx->unstable_found_out = true;
if (persistent_image && !image_unstable && !should_stop_requested() &&
!runtime_sleep_mode_active()) {
sm_image_index_complete_scan(image_path);
if (ctx->manual_source_path && is_pfsc_image_mount_base_or_child(image_path)) {
char mount_point[MAX_PATH];
get_image_mount_point_for_source(image_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, ctx->manual_source_path, ctx->candidates,
ctx->max_candidates, ctx->candidate_count, ctx->app_db,
ctx->discovered_param_roots, ctx->discovered_param_root_count,
ctx->unstable_found_out);
}
return true;
}
@@ -572,29 +546,15 @@ static void collect_scan_candidates_from_manual_path(
const char *name = get_filename_component(manual_path);
if (S_ISREG(st.st_mode) &&
is_supported_image_file_path(manual_path, name)) {
if (!sm_image_index_needs_scan(manual_path, &st, app_db->titles,
app_db->titles_ready))
if (!maybe_mount_image_file(manual_path, name, unstable_found_out))
return;
bool image_unstable = false;
if (!maybe_mount_image_file(manual_path, name, &image_unstable)) {
if (image_unstable && unstable_found_out)
*unstable_found_out = true;
return;
}
sm_image_index_begin_scan(manual_path, &st);
char mount_point[MAX_PATH];
get_image_mount_point_for_source(manual_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, manual_path, candidates, max_candidates, candidate_count,
app_db, discovered_param_roots, discovered_param_root_count,
&image_unstable);
if (image_unstable && unstable_found_out)
*unstable_found_out = true;
if (!image_unstable && !should_stop_requested() &&
!runtime_sleep_mode_active()) {
sm_image_index_complete_scan(manual_path);
}
unstable_found_out);
return;
}
@@ -693,13 +653,21 @@ static bool resolve_backport_path(const char *title_id,
return false;
}
bool mount_backport_overlay_for_title(const char *source_path,
const char *title_id,
const char *owning_scan_root,
bool *unstable_found_out) {
typedef struct {
bool *unstable_found_out;
} backport_overlay_ctx_t;
static bool mount_backport_overlay_for_cached_game(const char *source_path,
const char *title_id,
const char *title_name,
const char *owning_scan_root,
void *ctx_ptr) {
(void)title_name;
if (should_stop_requested() || runtime_sleep_mode_active())
return false;
backport_overlay_ctx_t *ctx = (backport_overlay_ctx_t *)ctx_ptr;
char backport_path[MAX_PATH];
if (!resolve_backport_path(title_id, owning_scan_root, backport_path)) {
return true;
@@ -714,9 +682,9 @@ bool mount_backport_overlay_for_title(const char *source_path,
return true;
}
if (!wait_for_stability_fast(backport_path, "BKP")) {
if (unstable_found_out)
*unstable_found_out = true;
return false;
if (ctx->unstable_found_out)
*ctx->unstable_found_out = true;
return true;
}
overlay_active = false;
if (!reconcile_title_backport_mount(title_id, source_path, backport_path,
@@ -736,23 +704,11 @@ bool mount_backport_overlay_for_title(const char *source_path,
return true;
}
bool release_scan_runtime_mounts(void) {
if (runtime_sleep_mode_active())
return false;
if (sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount()) {
return true;
}
runtime_mount_state_lock();
if (sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount()) {
runtime_mount_state_unlock();
return true;
}
unmount_all_title_runtime_layers();
bool released = release_runtime_image_mounts();
runtime_mount_state_unlock();
return released;
void mount_backport_overlays(bool *unstable_found_out) {
backport_overlay_ctx_t ctx = {
.unstable_found_out = unstable_found_out,
};
for_each_cached_game_entry(NULL, mount_backport_overlay_for_cached_game, &ctx);
}
// --- Unified Scan Pass (images + game candidates) ---
@@ -763,7 +719,6 @@ void cleanup_lost_sources_before_scan(void) {
cleanup_mount_links(NULL, true);
// 3) Unmount stale image mounts for deleted image files.
cleanup_stale_image_mounts();
sm_image_index_prune();
// 4) Drop stale path-state entries.
prune_path_state();
}
@@ -772,7 +727,6 @@ void cleanup_lost_sources_for_scan_root(const char *scan_root) {
prune_game_cache_for_root(scan_root);
cleanup_mount_links(scan_root, true);
cleanup_stale_image_mounts_for_root(scan_root);
sm_image_index_prune();
prune_path_state_for_root(scan_root);
}
@@ -894,7 +848,6 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
if (total_found_out)
*total_found_out = discovered_param_root_count;
sm_image_index_flush();
free_app_db_title_list(&blocked_ppsa_titles);
free_app_db_title_list(&app_db_titles);
return candidate_count;
@@ -944,7 +897,6 @@ int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates,
if (total_found_out)
*total_found_out = discovered_param_root_count;
sm_image_index_flush();
free_app_db_title_list(&blocked_ppsa_titles);
free_app_db_title_list(&app_db_titles);
return candidate_count;
+78 -59
View File
@@ -1,5 +1,6 @@
#include "sm_platform.h"
#include <fcntl.h>
#include <stdlib.h>
#include <sys/event.h>
#include <sys/select.h>
@@ -22,7 +23,6 @@
#include "sm_scan.h"
#include "sm_scan_tree.h"
#include "sm_scanner.h"
#include "sm_shellcore_service.h"
#include "sm_time.h"
#include "sm_types.h"
@@ -109,6 +109,14 @@ static void schedule_manual_probe(uint64_t now_us) {
g_scanner_manual_probe_due_us = now_us + SCANNER_MANUAL_PROBE_INTERVAL_US;
}
static bool set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
static void reset_scanner_root_states(void) {
memset(g_scanner_root_states, 0, sizeof(g_scanner_root_states));
}
@@ -232,7 +240,12 @@ static bool fakelib_runtime_config_changed(const runtime_config_t *old_cfg,
static uint32_t scanner_config_topology_hash(void) {
uint32_t hash = 2166136261u;
int scan_path_count = get_scan_path_count();
uint32_t values[] = {runtime_config()->scan_depth, (uint32_t)scan_path_count};
uint32_t values[] = {
runtime_config()->scan_depth,
runtime_config()->watch_subdirs ? 1u : 0u,
runtime_config()->watch_image_files ? 1u : 0u,
(uint32_t)scan_path_count,
};
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
for (unsigned shift = 0; shift < 32u; shift += 8u) {
@@ -576,6 +589,7 @@ static bool resolve_watch_tree_rebuild_target(const scanner_watch_entry_t *entry
typedef struct {
int kq;
int scan_root_index;
bool watch_subdirs;
} register_watch_tree_ctx_t;
static sm_scan_tree_dir_visit_t register_watch_directory_visit(
@@ -584,9 +598,11 @@ static sm_scan_tree_dir_visit_t register_watch_directory_visit(
scanner_watch_kind_t kind =
(depth_from_root == 0u) ? SCANNER_WATCH_SCAN_ROOT
: SCANNER_WATCH_SCAN_SUBDIR;
if (!register_scanner_watch_entry(ctx->kq, ctx->scan_root_index, dir_path, kind,
(uint8_t)depth_from_root)) {
return SM_SCAN_TREE_DIR_ABORT;
if (depth_from_root == 0u || ctx->watch_subdirs) {
if (!register_scanner_watch_entry(ctx->kq, ctx->scan_root_index, dir_path,
kind, (uint8_t)depth_from_root)) {
return SM_SCAN_TREE_DIR_ABORT;
}
}
return SM_SCAN_TREE_DIR_DESCEND;
@@ -629,19 +645,28 @@ static bool rebuild_scan_root_watch_tree(int kq, int scan_root_index) {
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = register_watch_image_visit,
};
if (!sm_scan_tree_walk(scan_root, scan_root, 0u, scan_depth, &callbacks,
&walk_ctx)) {
return false;
const runtime_config_t *cfg = runtime_config();
if (!cfg->watch_subdirs && !cfg->watch_image_files) {
if (!register_scanner_watch_entry(kq, scan_root_index, scan_root,
SCANNER_WATCH_SCAN_ROOT, 0u)) {
return false;
}
} else {
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.watch_subdirs = cfg->watch_subdirs,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file =
cfg->watch_image_files ? register_watch_image_visit : NULL,
};
if (!sm_scan_tree_walk(scan_root, scan_root, 0u, scan_depth, &callbacks,
&walk_ctx)) {
return false;
}
}
char backport_root[MAX_PATH];
@@ -682,10 +707,15 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (!remove_scan_root_watch_entries_for_path(scan_root_index, rebuild_path))
return false;
const runtime_config_t *cfg = runtime_config();
if (!cfg->watch_subdirs && !cfg->watch_image_files) {
clear_scan_root_watch_tree_state(scan_root_index);
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (rebuild_depth > scan_depth) {
clear_scan_root_watch_tree_state(scan_root_index);
return true;
@@ -694,10 +724,11 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.watch_subdirs = cfg->watch_subdirs,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = register_watch_image_visit,
.on_image_file = cfg->watch_image_files ? register_watch_image_visit : NULL,
};
if (!sm_scan_tree_walk(scan_root, rebuild_path, rebuild_depth,
scan_depth - rebuild_depth, &callbacks, &walk_ctx)) {
@@ -977,8 +1008,9 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason,
if (should_abort_scan_cycle())
return false;
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
log_debug(" [IMG] some discovery mounts remain busy");
mount_backport_overlays(&unstable_found);
if (should_abort_scan_cycle())
return false;
if (unstable_found_out)
*unstable_found_out = unstable_found;
@@ -1015,8 +1047,9 @@ static bool run_targeted_scan_cycle(int scan_root_index,
if (should_abort_scan_cycle())
return false;
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
log_debug(" [IMG] some discovery mounts remain busy");
mount_backport_overlays(&unstable_found);
if (should_abort_scan_cycle())
return false;
if (unstable_found_out)
*unstable_found_out = unstable_found;
@@ -1063,8 +1096,7 @@ static int find_due_dirty_scan_root(uint64_t now_us) {
}
static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
uint64_t full_resync_due_us,
bool include_scan_work) {
uint64_t full_resync_due_us) {
uint64_t next_deadline = full_resync_due_us;
uint64_t install_wake_us = sm_install_next_wake_us(now_us);
@@ -1084,7 +1116,7 @@ static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
next_deadline = g_scanner_config_probe_due_us;
}
if (include_scan_work && g_scanner_manual_scan_due_us != 0 &&
if (g_scanner_manual_scan_due_us != 0 &&
(next_deadline == 0 || g_scanner_manual_scan_due_us < next_deadline)) {
next_deadline = g_scanner_manual_scan_due_us;
}
@@ -1094,14 +1126,12 @@ static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
next_deadline = g_scanner_manual_probe_due_us;
}
if (include_scan_work) {
for (int i = 0; i < get_scan_path_count(); i++) {
const scanner_root_state_t *state = &g_scanner_root_states[i];
if (!state->dirty)
continue;
if (next_deadline == 0 || state->ready_after_us < next_deadline)
next_deadline = state->ready_after_us;
}
for (int i = 0; i < get_scan_path_count(); i++) {
const scanner_root_state_t *state = &g_scanner_root_states[i];
if (!state->dirty)
continue;
if (next_deadline == 0 || state->ready_after_us < next_deadline)
next_deadline = state->ready_after_us;
}
return next_deadline;
@@ -1284,8 +1314,8 @@ bool sm_scanner_init(void) {
close_scanner_wake_pipe();
return false;
}
if (!sm_set_fd_nonblocking(g_scanner_wake_pipe[0]) ||
!sm_set_fd_nonblocking(g_scanner_wake_pipe[1])) {
if (!set_fd_nonblocking(g_scanner_wake_pipe[0]) ||
!set_fd_nonblocking(g_scanner_wake_pipe[1])) {
log_debug(" [SCAN] wake pipe nonblocking setup failed: %s",
strerror(errno));
close_scanner_wake_pipe();
@@ -1374,7 +1404,6 @@ void sm_scanner_run_loop(void) {
uint64_t next_full_resync_us =
monotonic_time_us() + scanner_full_resync_interval_us();
bool was_sleeping = false;
bool scan_work_blocked = false;
while (true) {
if (should_stop_requested()) {
@@ -1410,11 +1439,8 @@ void sm_scanner_run_loop(void) {
}
}
bool game_mount_busy = sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount();
char scan_reason[128];
if (!game_mount_busy &&
consume_scan_now_request(scan_reason, sizeof(scan_reason))) {
if (consume_scan_now_request(scan_reason, sizeof(scan_reason))) {
bool unstable_found = false;
if (!run_full_scan_cycle(false, scan_reason, &unstable_found)) {
if (runtime_sleep_mode_active())
@@ -1441,14 +1467,10 @@ void sm_scanner_run_loop(void) {
}
uint64_t now_us = monotonic_time_us();
game_mount_busy = sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount();
if (game_mount_busy) {
if (sm_game_lifecycle_has_active_game()) {
next_full_resync_us = 0;
scan_work_blocked = true;
} else if (scan_work_blocked) {
scan_work_blocked = false;
next_full_resync_us = now_us + scanner_full_resync_interval_us();
} else if (next_full_resync_us == 0) {
next_full_resync_us = now_us;
}
if (config_probe_due(now_us)) {
@@ -1504,7 +1526,7 @@ void sm_scanner_run_loop(void) {
continue;
}
if (!game_mount_busy && g_scanner_manual_scan_due_us != 0 &&
if (g_scanner_manual_scan_due_us != 0 &&
now_us >= g_scanner_manual_scan_due_us) {
g_scanner_manual_scan_due_us = 0;
invalidate_app_db_title_cache();
@@ -1540,8 +1562,7 @@ void sm_scanner_run_loop(void) {
continue;
}
if (!game_mount_busy && next_full_resync_us != 0 &&
now_us >= next_full_resync_us) {
if (next_full_resync_us != 0 && now_us >= next_full_resync_us) {
bool unstable_found = false;
if (!run_full_scan_cycle(false, NULL, &unstable_found)) {
if (runtime_sleep_mode_active())
@@ -1567,16 +1588,14 @@ void sm_scanner_run_loop(void) {
continue;
}
int cleanup_root_index =
game_mount_busy ? -1 : find_pending_cleanup_scan_root();
int cleanup_root_index = find_pending_cleanup_scan_root();
if (cleanup_root_index >= 0) {
g_scanner_root_states[cleanup_root_index].cleanup_pending = false;
cleanup_lost_sources_for_scan_root(get_scan_path(cleanup_root_index));
continue;
}
int dirty_root_index =
game_mount_busy ? -1 : find_due_dirty_scan_root(now_us);
int dirty_root_index = find_due_dirty_scan_root(now_us);
if (dirty_root_index >= 0) {
bool cleanup_pending =
g_scanner_root_states[dirty_root_index].cleanup_pending;
@@ -1639,8 +1658,8 @@ void sm_scanner_run_loop(void) {
continue;
}
uint64_t deadline_us = compute_next_scan_deadline_us(
now_us, next_full_resync_us, !game_mount_busy);
uint64_t deadline_us =
compute_next_scan_deadline_us(now_us, next_full_resync_us);
struct timespec timeout;
const struct timespec *timeout_ptr =
build_wait_timeout(&timeout, now_us, deadline_us);
-198
View File
@@ -1,198 +0,0 @@
/* Position-independent bridge copied into SceShellCore executable memory. */
.text
.p2align 4
.global sm_shellcore_bridge_blob_start
.global sm_shellcore_bridge_blob_end
.global sm_shellcore_bridge_launch_hook
.global sm_shellcore_bridge_install_all_hook
.global sm_shellcore_bridge_launch_trampoline
.global sm_shellcore_bridge_install_all_trampoline
.global sm_shellcore_bridge_install_title_dir
.global sm_shellcore_bridge_install_armed
.global sm_shellcore_bridge_install_title_id
.global sm_shellcore_bridge_install_dir
.global sm_shellcore_bridge_socket
.global sm_shellcore_bridge_connect
.global sm_shellcore_bridge_read
.global sm_shellcore_bridge_write
.global sm_shellcore_bridge_close
sm_shellcore_bridge_blob_start:
sm_shellcore_bridge_launch_hook:
pushq %rbx
pushq %r12
movq %rsi, %r12
pushfq
pushq %rax
pushq %rdi
pushq %rsi
pushq %rdx
pushq %rcx
pushq %r8
pushq %r9
pushq %r10
pushq %r11
subq $8, %rsp
movl $1, %edi
/* RSI still contains title_id. */
call sm_shellcore_bridge_exchange
addq $8, %rsp
popq %r11
popq %r10
popq %r9
popq %r8
popq %rcx
popq %rdx
popq %rsi
popq %rdi
popq %rax
popfq
subq $8, %rsp
call *sm_shellcore_bridge_launch_trampoline(%rip)
addq $8, %rsp
movq %rax, %rbx
testl %eax, %eax
jns 1f
movl $2, %edi
movq %r12, %rsi
subq $8, %rsp
call sm_shellcore_bridge_exchange
addq $8, %rsp
1:
movq %rbx, %rax
popq %r12
popq %rbx
ret
.p2align 4
/*
* FW 12+ removed the public AppInstallTitleDir RPC. AppInstallAll still
* reaches this internal dispatcher, so one explicitly armed request is
* redirected to the internal TitleDir implementation. Every ordinary
* AppInstallAll call follows its original trampoline unchanged.
*/
.p2align 4
sm_shellcore_bridge_install_all_hook:
cmpb $0, sm_shellcore_bridge_install_armed(%rip)
je 1f
movb $0, sm_shellcore_bridge_install_armed(%rip)
leaq sm_shellcore_bridge_install_title_id(%rip), %rdi
leaq sm_shellcore_bridge_install_dir(%rip), %rsi
movl $1, %edx
xorl %ecx, %ecx
jmp *sm_shellcore_bridge_install_title_dir(%rip)
1:
jmp *sm_shellcore_bridge_install_all_trampoline(%rip)
/* int exchange(uint16_t operation, const char *title_id) */
.p2align 4
sm_shellcore_bridge_exchange:
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %r12
subq $160, %rsp
/* Build the fixed 40-byte request. */
movq $0, -64(%rbp)
movq $0, -56(%rbp)
movq $0, -48(%rbp)
movq $0, -40(%rbp)
movq $0, -32(%rbp)
movl $0x534d5343, -64(%rbp)
movw $1, -60(%rbp)
movw %di, -58(%rbp)
testq %rsi, %rsi
je 2f
leaq -56(%rbp), %rdi
movl $31, %ecx
1:
movb (%rsi), %al
testb %al, %al
je 2f
movb %al, (%rdi)
incq %rsi
incq %rdi
decl %ecx
jne 1b
2:
/* Use libkernel_sys wrappers. Direct syscalls from SceShellCore are fatal. */
movl $1, %edi
movl $1, %esi
xorl %edx, %edx
call *sm_shellcore_bridge_socket(%rip)
testl %eax, %eax
js 8f
movl %eax, %ebx
/* connect(fd, &sockaddr, sockaddr.sun_len) */
movl %ebx, %edi
leaq sm_shellcore_bridge_sockaddr(%rip), %rsi
movl $31, %edx
call *sm_shellcore_bridge_connect(%rip)
testl %eax, %eax
jne 7f
/* write(fd, &request, sizeof(request)) */
movl %ebx, %edi
leaq -64(%rbp), %rsi
movl $40, %edx
call *sm_shellcore_bridge_write(%rip)
cmpq $40, %rax
jne 7f
/* Only status is needed by launch and launch-failure requests. */
movl %ebx, %edi
leaq -68(%rbp), %rsi
movl $4, %edx
call *sm_shellcore_bridge_read(%rip)
cmpq $4, %rax
jne 7f
movl -68(%rbp), %r12d
jmp 5f
7:
movl $5, %r12d
5:
movl %ebx, %edi
call *sm_shellcore_bridge_close(%rip)
movl %r12d, %eax
jmp 9f
8:
movl $5, %eax
9:
addq $160, %rsp
popq %r12
popq %rbx
popq %rbp
ret
.p2align 3
sm_shellcore_bridge_launch_trampoline:
.quad 0
sm_shellcore_bridge_install_all_trampoline:
.quad 0
sm_shellcore_bridge_install_title_dir:
.quad 0
sm_shellcore_bridge_socket:
.quad 0
sm_shellcore_bridge_connect:
.quad 0
sm_shellcore_bridge_read:
.quad 0
sm_shellcore_bridge_write:
.quad 0
sm_shellcore_bridge_close:
.quad 0
sm_shellcore_bridge_sockaddr:
.byte 31, 1
.asciz "/system_tmp/shadowmount.sock"
.zero 75
.p2align 3
sm_shellcore_bridge_install_armed:
.byte 0
sm_shellcore_bridge_install_title_id:
.zero 32
sm_shellcore_bridge_install_dir:
.zero 1024
sm_shellcore_bridge_blob_end:
.section .note.GNU-stack,"",@progbits
+6 -1
View File
@@ -7,6 +7,7 @@
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_shellcore_flags.h"
#define SHELLCORE_FLAG_WAITMODE_OR 2u
@@ -508,7 +509,11 @@ static void set_shellcore_flag_start_result(bool success) {
}
static void enter_sleep_mode_and_cleanup(const char *reason) {
(void)request_runtime_sleep_mode(true, reason);
if (request_runtime_sleep_mode(true, reason)) {
runtime_mount_state_lock();
unmount_usb_sources_for_suspend();
runtime_mount_state_unlock();
}
}
static void reset_shellcore_flag_state(shellcore_flag_monitor_t *flag) {
-471
View File
@@ -1,471 +0,0 @@
#include "sm_platform.h"
#include <signal.h>
#include <sys/mman.h>
#include <pthread.h>
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_runtime.h"
#include "sm_shellcore_hooks.h"
#include "sm_shellcore_remote.h"
#define SHELLCORE_BASE_HOOK_COUNT 1u
#define SHELLCORE_MAX_HOOK_COUNT 2u
#define MAX_HOOK_PROLOGUE_SIZE 32u
#define ABSOLUTE_JUMP_SIZE 12u
/*
* Private payload-SDK ABI from src/crt/kernel.h. The public
* <ps5/kernel.h> intentionally omits dynlib_obj_t/kernel_dynlib_obj(), but
* kernel_dynlib_obj() copies the complete 0x180-byte object. Keep the local
* definition layout-checked because ShellCore bridge placement needs the
* module bounds and its executable eh_frame area.
*/
typedef struct {
uintptr_t next;
uintptr_t path;
uintptr_t unknown0[2];
uint32_t refcount;
uintptr_t handle;
uintptr_t mapbase;
uintptr_t mapsize;
uintptr_t textsize;
uintptr_t database;
uintptr_t datasize;
uintptr_t unknown1;
uintptr_t unknown1size;
uintptr_t entry;
uintptr_t unknown2;
uintptr_t vaddrbase;
uint32_t tlsindex;
uintptr_t tlsinit;
uintptr_t tlsinitsize;
uintptr_t tlssize;
uintptr_t tlsoffset;
uintptr_t tlsalign;
uintptr_t pltgot;
uintptr_t unknown3[6];
uintptr_t init;
uintptr_t fini;
uintptr_t eh_frame_hdr;
uintptr_t eh_frame_hdr_size;
uintptr_t eh_frame;
uintptr_t eh_frame_size;
int32_t status;
int32_t flags;
uintptr_t unknown4[5];
uintptr_t dynsec;
uintptr_t unknown5[6];
} shellcore_dynlib_obj_t;
_Static_assert(offsetof(shellcore_dynlib_obj_t, mapbase) == 0x30,
"unexpected dynlib_obj mapbase offset");
_Static_assert(offsetof(shellcore_dynlib_obj_t, eh_frame) == 0x108,
"unexpected dynlib_obj eh_frame offset");
_Static_assert(sizeof(shellcore_dynlib_obj_t) == 0x180,
"unexpected dynlib_obj size");
int kernel_dynlib_obj(pid_t pid, uint32_t handle, shellcore_dynlib_obj_t *obj);
extern const uint8_t sm_shellcore_bridge_blob_start[];
extern const uint8_t sm_shellcore_bridge_blob_end[];
extern const uint8_t sm_shellcore_bridge_launch_hook[];
extern const uint8_t sm_shellcore_bridge_install_all_hook[];
extern const uint8_t sm_shellcore_bridge_launch_trampoline[];
extern const uint8_t sm_shellcore_bridge_install_all_trampoline[];
extern const uint8_t sm_shellcore_bridge_install_title_dir[];
extern const uint8_t sm_shellcore_bridge_install_armed[];
extern const uint8_t sm_shellcore_bridge_install_title_id[];
extern const uint8_t sm_shellcore_bridge_install_dir[];
extern const uint8_t sm_shellcore_bridge_socket[];
extern const uint8_t sm_shellcore_bridge_connect[];
extern const uint8_t sm_shellcore_bridge_read[];
extern const uint8_t sm_shellcore_bridge_write[];
extern const uint8_t sm_shellcore_bridge_close[];
typedef struct {
const uint8_t *slot;
const char *name;
} shellcore_import_t;
static const shellcore_import_t k_bridge_imports[] = {
{sm_shellcore_bridge_socket, "socket"},
{sm_shellcore_bridge_connect, "connect"},
{sm_shellcore_bridge_read, "read"},
{sm_shellcore_bridge_write, "write"},
{sm_shellcore_bridge_close, "close"},
};
typedef struct {
bool installed;
sm_shellcore_remote_t remote;
uintptr_t bridge_address;
size_t hook_count;
sm_shellcore_target_t target_ids[SHELLCORE_MAX_HOOK_COUNT];
uint8_t original[SHELLCORE_MAX_HOOK_COUNT][MAX_HOOK_PROLOGUE_SIZE];
uint8_t original_size[SHELLCORE_MAX_HOOK_COUNT];
} shellcore_hooks_state_t;
static shellcore_hooks_state_t g_hooks;
static pthread_mutex_t g_install_mutex = PTHREAD_MUTEX_INITIALIZER;
static uintptr_t remote_bridge_symbol(const uint8_t *symbol) {
return g_hooks.bridge_address +
(uintptr_t)(symbol - sm_shellcore_bridge_blob_start);
}
static void build_absolute_jump(uint8_t jump[ABSOLUTE_JUMP_SIZE],
uintptr_t destination) {
jump[0] = 0x48;
jump[1] = 0xb8;
memcpy(jump + 2, &destination, sizeof(destination));
jump[10] = 0xff;
jump[11] = 0xe0;
}
static bool patch_remote_jump(pid_t pid, uintptr_t source,
uintptr_t destination, size_t patch_size) {
if (patch_size < ABSOLUTE_JUMP_SIZE || patch_size > MAX_HOOK_PROLOGUE_SIZE) {
return false;
}
uint8_t patch[MAX_HOOK_PROLOGUE_SIZE];
memset(patch, 0x90, patch_size);
build_absolute_jump(patch, destination);
if (!sm_remote_process_write(pid, source, patch, patch_size))
return false;
uint8_t verify[MAX_HOOK_PROLOGUE_SIZE];
return sm_remote_process_read(pid, source, verify, patch_size) &&
memcmp(verify, patch, patch_size) == 0;
}
static bool verify_remote_bytes(pid_t pid, uintptr_t address,
const void *expected, size_t size) {
uint8_t buffer[256];
const uint8_t *bytes = (const uint8_t *)expected;
while (size != 0) {
size_t chunk = size < sizeof(buffer) ? size : sizeof(buffer);
if (!sm_remote_process_read(pid, address, buffer, chunk) ||
memcmp(buffer, bytes, chunk) != 0) {
return false;
}
address += chunk;
bytes += chunk;
size -= chunk;
}
return true;
}
static bool append_trampoline(uint8_t *bridge, size_t bridge_capacity,
size_t *cursor, const uint8_t *original,
size_t original_size, uintptr_t return_address,
uintptr_t remote_base,
uintptr_t *remote_trampoline_out) {
size_t aligned = (*cursor + 15u) & ~(size_t)15u;
size_t required = aligned + original_size + ABSOLUTE_JUMP_SIZE;
if (required > bridge_capacity)
return false;
memcpy(bridge + aligned, original, original_size);
build_absolute_jump(bridge + aligned + original_size, return_address);
*remote_trampoline_out = remote_base + aligned;
*cursor = required;
return true;
}
static void set_bridge_pointer(uint8_t *bridge, const uint8_t *local_symbol,
uintptr_t value) {
size_t offset = (size_t)(local_symbol - sm_shellcore_bridge_blob_start);
memcpy(bridge + offset, &value, sizeof(value));
}
static bool resolve_bridge_imports(pid_t pid, uint8_t *bridge) {
uint32_t handle = UINT32_MAX;
if (kernel_dynlib_handle(pid, "libkernel_sys.sprx", &handle) != 0)
return false;
for (size_t i = 0; i < sizeof(k_bridge_imports) / sizeof(k_bridge_imports[0]);
++i) {
uintptr_t address =
(uintptr_t)kernel_dynlib_dlsym(pid, handle, k_bridge_imports[i].name);
if (address == 0) {
log_debug(" [SHELLCORE] failed to resolve libkernel %s",
k_bridge_imports[i].name);
return false;
}
set_bridge_pointer(bridge, k_bridge_imports[i].slot, address);
}
return true;
}
static pid_t find_shellcore_pid(void) {
pid_t pid = find_pid_by_name("SceShellCore", true);
if (pid <= 0)
pid = find_pid_by_name("SceShellCore.elf", true);
return pid;
}
bool sm_shellcore_install_bridge_enabled(void) {
uint16_t firmware =
(uint16_t)((kernel_get_fw_version() >> 16) & 0xffffu);
return firmware >= 0x1200u;
}
static bool install_hooks_for_pid(pid_t pid) {
const size_t blob_size =
(size_t)(sm_shellcore_bridge_blob_end - sm_shellcore_bridge_blob_start);
const size_t bridge_capacity =
blob_size + SHELLCORE_MAX_HOOK_COUNT *
(MAX_HOOK_PROLOGUE_SIZE + ABSOLUTE_JUMP_SIZE + 16u);
uint8_t *bridge = calloc(1, bridge_capacity);
if (!bridge)
return false;
memcpy(bridge, sm_shellcore_bridge_blob_start, blob_size);
if (kill(pid, SIGSTOP) != 0) {
free(bridge);
return false;
}
bool ok = false;
sm_shellcore_remote_t remote;
shellcore_dynlib_obj_t object;
if (!sm_shellcore_remote_resolve(pid, &remote) ||
kernel_dynlib_obj(pid, 0, &object) != 0) {
log_debug(" [SHELLCORE] stopped-process layout resolution failed");
goto done;
}
if (object.mapbase != remote.image_base ||
object.eh_frame < object.mapbase ||
object.eh_frame_size < bridge_capacity ||
object.eh_frame > UINTPTR_MAX - bridge_capacity ||
object.eh_frame + bridge_capacity > object.mapbase + object.mapsize) {
log_debug(" [SHELLCORE] invalid layout: base=0x%lx map=0x%lx+0x%lx "
"eh=0x%lx+0x%lx bridge=0x%zx",
(unsigned long)remote.image_base, (unsigned long)object.mapbase,
(unsigned long)object.mapsize, (unsigned long)object.eh_frame,
(unsigned long)object.eh_frame_size, bridge_capacity);
goto done;
}
log_debug(" [SHELLCORE] layout validated: map=0x%lx+0x%lx eh=0x%lx+0x%lx",
(unsigned long)object.mapbase, (unsigned long)object.mapsize,
(unsigned long)object.eh_frame,
(unsigned long)object.eh_frame_size);
const bool install_hook_enabled = sm_shellcore_install_bridge_enabled();
const size_t hook_count = install_hook_enabled
? SHELLCORE_MAX_HOOK_COUNT
: SHELLCORE_BASE_HOOK_COUNT;
uintptr_t targets[SHELLCORE_MAX_HOOK_COUNT] = {
remote.targets[SM_SHELLCORE_TARGET_LAUNCH_APP],
remote.targets[SM_SHELLCORE_TARGET_INSTALL_ALL],
};
sm_shellcore_target_t target_ids[SHELLCORE_MAX_HOOK_COUNT] = {
SM_SHELLCORE_TARGET_LAUNCH_APP,
SM_SHELLCORE_TARGET_INSTALL_ALL,
};
for (size_t i = 0; i < hook_count; ++i) {
uint8_t patch_size = remote.offsets->targets[target_ids[i]].patch_size;
if (patch_size < ABSOLUTE_JUMP_SIZE ||
patch_size > MAX_HOOK_PROLOGUE_SIZE ||
!sm_remote_process_read(pid, targets[i], g_hooks.original[i],
patch_size)) {
goto done;
}
static const uint8_t function_prologue[] = {0x55, 0x48, 0x89, 0xe5};
if (memcmp(g_hooks.original[i], function_prologue,
sizeof(function_prologue)) != 0) {
log_debug(" [SHELLCORE] unexpected prologue: %s at 0x%lx",
sm_shellcore_target_name(target_ids[i]),
(unsigned long)targets[i]);
goto done;
}
g_hooks.original_size[i] = patch_size;
}
size_t cursor = blob_size;
uintptr_t launch_trampoline = 0;
uintptr_t install_all_trampoline = 0;
if (!append_trampoline(bridge, bridge_capacity, &cursor, g_hooks.original[0],
g_hooks.original_size[0],
targets[0] + g_hooks.original_size[0], object.eh_frame,
&launch_trampoline)) {
goto done;
}
set_bridge_pointer(bridge, sm_shellcore_bridge_launch_trampoline,
launch_trampoline);
if (install_hook_enabled) {
if (!append_trampoline(bridge, bridge_capacity, &cursor,
g_hooks.original[1], g_hooks.original_size[1],
targets[1] + g_hooks.original_size[1],
object.eh_frame, &install_all_trampoline)) {
goto done;
}
set_bridge_pointer(bridge, sm_shellcore_bridge_install_all_trampoline,
install_all_trampoline);
set_bridge_pointer(
bridge, sm_shellcore_bridge_install_title_dir,
remote.targets[SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR]);
}
if (!resolve_bridge_imports(pid, bridge))
goto done;
if (kernel_mprotect(pid, (intptr_t)object.mapbase, object.mapsize,
PROT_READ | PROT_WRITE | PROT_EXEC) != 0 ||
!sm_remote_process_write(pid, object.eh_frame, bridge, cursor) ||
!verify_remote_bytes(pid, object.eh_frame, bridge, cursor)) {
log_debug(" [SHELLCORE] bridge write verification failed");
goto done;
}
uintptr_t launch_hook =
object.eh_frame + (uintptr_t)(sm_shellcore_bridge_launch_hook -
sm_shellcore_bridge_blob_start);
uintptr_t install_all_hook =
object.eh_frame +
(uintptr_t)(sm_shellcore_bridge_install_all_hook -
sm_shellcore_bridge_blob_start);
if (!patch_remote_jump(pid, targets[0], launch_hook,
g_hooks.original_size[0]) ||
(install_hook_enabled &&
!patch_remote_jump(pid, targets[1], install_all_hook,
g_hooks.original_size[1]))) {
goto rollback;
}
g_hooks.remote = remote;
g_hooks.bridge_address = object.eh_frame;
g_hooks.hook_count = hook_count;
memcpy(g_hooks.target_ids, target_ids,
hook_count * sizeof(g_hooks.target_ids[0]));
g_hooks.installed = true;
ok = true;
log_debug(" [SHELLCORE] hooks installed: fw=%s pid=%ld lifecycle=1 "
"install=%d",
remote.offsets->name, (long)pid, install_hook_enabled ? 1 : 0);
goto done;
rollback:
for (size_t i = 0; i < hook_count; ++i) {
(void)sm_remote_process_write(pid, targets[i], g_hooks.original[i],
g_hooks.original_size[i]);
}
done:
free(bridge);
(void)kill(pid, SIGCONT);
return ok;
}
bool sm_shellcore_hooks_start(void) {
pthread_mutex_lock(&g_install_mutex);
if (g_hooks.installed)
goto installed;
memset(&g_hooks, 0, sizeof(g_hooks));
pid_t pid = find_shellcore_pid();
if (pid <= 0) {
log_debug(" [SHELLCORE] process not found");
pthread_mutex_unlock(&g_install_mutex);
return false;
}
bool ok = install_hooks_for_pid(pid);
pthread_mutex_unlock(&g_install_mutex);
return ok;
installed:
pthread_mutex_unlock(&g_install_mutex);
return true;
}
void sm_shellcore_hooks_stop(void) {
pthread_mutex_lock(&g_install_mutex);
if (!g_hooks.installed) {
pthread_mutex_unlock(&g_install_mutex);
return;
}
pid_t pid = g_hooks.remote.pid;
if (kill(pid, SIGSTOP) == 0) {
for (size_t i = 0; i < g_hooks.hook_count; ++i) {
sm_shellcore_target_t target = g_hooks.target_ids[i];
(void)sm_remote_process_write(pid, g_hooks.remote.targets[target],
g_hooks.original[i],
g_hooks.original_size[i]);
}
(void)kill(pid, SIGCONT);
}
memset(&g_hooks, 0, sizeof(g_hooks));
pthread_mutex_unlock(&g_install_mutex);
}
bool sm_shellcore_install_title_dir(const char *title_id,
const char *install_dir,
int *result_out) {
if (!title_id || !install_dir || !result_out)
return false;
size_t title_len = strnlen(title_id, MAX_TITLE_ID);
size_t dir_len = strnlen(install_dir, MAX_PATH);
if (title_len == 0 || title_len >= MAX_TITLE_ID || dir_len == 0 ||
dir_len >= MAX_PATH) {
return false;
}
pthread_mutex_lock(&g_install_mutex);
if (!g_hooks.installed ||
g_hooks.hook_count < SHELLCORE_MAX_HOOK_COUNT) {
pthread_mutex_unlock(&g_install_mutex);
return false;
}
char remote_title[MAX_TITLE_ID] = {0};
char remote_dir[MAX_PATH] = {0};
memcpy(remote_title, title_id, title_len);
memcpy(remote_dir, install_dir, dir_len);
pid_t pid = g_hooks.remote.pid;
uint8_t armed = 1;
bool written =
sm_remote_process_write(pid,
remote_bridge_symbol(
sm_shellcore_bridge_install_title_id),
remote_title, sizeof(remote_title)) &&
sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_dir),
remote_dir, sizeof(remote_dir)) &&
sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed),
&armed, sizeof(armed));
if (!written) {
armed = 0;
(void)sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &armed,
sizeof(armed));
pthread_mutex_unlock(&g_install_mutex);
return false;
}
int result = sceAppInstUtilAppInstallAll(NULL);
uint8_t remaining = 1;
bool read_back = sm_remote_process_read(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &remaining,
sizeof(remaining));
bool consumed = read_back && remaining == 0;
if (!consumed) {
armed = 0;
(void)sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &armed,
sizeof(armed));
}
pthread_mutex_unlock(&g_install_mutex);
if (!consumed) {
log_debug(" [SHELLCORE] AppInstallAll trigger was not consumed: %s",
title_id);
return false;
}
log_debug(" [SHELLCORE] AppInstallAll bridge consumed: %s result=0x%08x",
title_id, (uint32_t)result);
*result_out = result;
return true;
}
-33
View File
@@ -1,33 +0,0 @@
#include "sm_shellcore_offsets.h"
#include <stddef.h>
static const sm_shellcore_firmware_offsets_t g_shellcore_offsets[] = {
#include "sm_shellcore_offsets.inc"
};
const sm_shellcore_firmware_offsets_t *
sm_shellcore_offsets_for_firmware(uint32_t raw_firmware) {
uint16_t firmware = (uint16_t)((raw_firmware >> 16) & 0xffffu);
for (size_t i = 0;
i < sizeof(g_shellcore_offsets) / sizeof(g_shellcore_offsets[0]); ++i) {
if (g_shellcore_offsets[i].firmware == firmware)
return &g_shellcore_offsets[i];
}
return NULL;
}
const char *sm_shellcore_target_name(sm_shellcore_target_t target) {
switch (target) {
case SM_SHELLCORE_TARGET_LAUNCH_APP:
return "launchApp";
case SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE:
return "unmountWorkspace";
case SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR:
return "AppInstallTitleDirMain";
case SM_SHELLCORE_TARGET_INSTALL_ALL:
return "AppInstallAll";
default:
return "unknown";
}
}
-285
View File
@@ -1,285 +0,0 @@
{
.firmware = 0x0100u,
.name = "1.00",
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
},
{
.firmware = 0x1000u,
.name = "10.00",
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
},
{
.firmware = 0x1001u,
.name = "10.01",
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
},
{
.firmware = 0x0101u,
.name = "1.01",
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
},
{
.firmware = 0x0102u,
.name = "1.02",
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
},
{
.firmware = 0x1020u,
.name = "10.20",
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d61c0u, .patch_size = 16u}},
},
{
.firmware = 0x1040u,
.name = "10.40",
.targets = {{.offset = 0x5ca0f0u, .patch_size = 12u}, {.offset = 0x605090u, .patch_size = 13u}, {.offset = 0x2b6760u, .patch_size = 12u}, {.offset = 0x17d61e0u, .patch_size = 16u}},
},
{
.firmware = 0x1060u,
.name = "10.60",
.targets = {{.offset = 0x5cb980u, .patch_size = 12u}, {.offset = 0x606920u, .patch_size = 13u}, {.offset = 0x2b80c0u, .patch_size = 12u}, {.offset = 0x17d7a60u, .patch_size = 16u}},
},
{
.firmware = 0x1100u,
.name = "11.00",
.targets = {{.offset = 0x62a940u, .patch_size = 12u}, {.offset = 0x6668d0u, .patch_size = 15u}, {.offset = 0x2bd880u, .patch_size = 12u}, {.offset = 0x2c4e60u, .patch_size = 15u}},
},
{
.firmware = 0x0112u,
.name = "1.12",
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x347dc0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa063a0u, .patch_size = 14u}},
},
{
.firmware = 0x1120u,
.name = "11.20",
.targets = {{.offset = 0x62aab0u, .patch_size = 12u}, {.offset = 0x666a40u, .patch_size = 15u}, {.offset = 0x2bd930u, .patch_size = 12u}, {.offset = 0x2c4f10u, .patch_size = 15u}},
},
{
.firmware = 0x0114u,
.name = "1.14",
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x347dc0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa06660u, .patch_size = 14u}},
},
{
.firmware = 0x1140u,
.name = "11.40",
.targets = {{.offset = 0x62ba90u, .patch_size = 12u}, {.offset = 0x667a20u, .patch_size = 15u}, {.offset = 0x2be910u, .patch_size = 12u}, {.offset = 0x2c5ef0u, .patch_size = 15u}},
},
{
.firmware = 0x1160u,
.name = "11.60",
.targets = {{.offset = 0x633500u, .patch_size = 12u}, {.offset = 0x66f490u, .patch_size = 15u}, {.offset = 0x2c2c10u, .patch_size = 12u}, {.offset = 0x2ca1f0u, .patch_size = 15u}},
},
{
.firmware = 0x1200u,
.name = "12.00",
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
},
{
.firmware = 0x1202u,
.name = "12.02",
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
},
{
.firmware = 0x1220u,
.name = "12.20",
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
},
{
.firmware = 0x1240u,
.name = "12.40",
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
},
{
.firmware = 0x1260u,
.name = "12.60",
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x684970u, .patch_size = 15u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
},
{
.firmware = 0x1270u,
.name = "12.70",
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x684970u, .patch_size = 15u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
},
{
.firmware = 0x0200u,
.name = "2.00",
.targets = {{.offset = 0x3a4660u, .patch_size = 12u}, {.offset = 0x3a5aa0u, .patch_size = 12u}, {.offset = 0x1df8f0u, .patch_size = 12u}, {.offset = 0xaf3740u, .patch_size = 14u}},
},
{
.firmware = 0x0220u,
.name = "2.20",
.targets = {{.offset = 0x3a4930u, .patch_size = 12u}, {.offset = 0x3a5d70u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3aa0u, .patch_size = 14u}},
},
{
.firmware = 0x0225u,
.name = "2.25",
.targets = {{.offset = 0x3a4e80u, .patch_size = 12u}, {.offset = 0x3a62c0u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3fe0u, .patch_size = 14u}},
},
{
.firmware = 0x0226u,
.name = "2.26",
.targets = {{.offset = 0x3a65f0u, .patch_size = 12u}, {.offset = 0x3a7a30u, .patch_size = 12u}, {.offset = 0x1e0d50u, .patch_size = 12u}, {.offset = 0xaf57a0u, .patch_size = 14u}},
},
{
.firmware = 0x0230u,
.name = "2.30",
.targets = {{.offset = 0x3a6d90u, .patch_size = 12u}, {.offset = 0x3a81d0u, .patch_size = 12u}, {.offset = 0x1e0f00u, .patch_size = 12u}, {.offset = 0xaf6360u, .patch_size = 14u}},
},
{
.firmware = 0x0250u,
.name = "2.50",
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3a9580u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
},
{
.firmware = 0x0270u,
.name = "2.70",
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3a9580u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
},
{
.firmware = 0x0300u,
.name = "3.00",
.targets = {{.offset = 0x3f1960u, .patch_size = 12u}, {.offset = 0x3f2ef0u, .patch_size = 12u}, {.offset = 0x2138e0u, .patch_size = 12u}, {.offset = 0x212ab0u, .patch_size = 12u}},
},
{
.firmware = 0x0310u,
.name = "3.10",
.targets = {{.offset = 0x3f19a0u, .patch_size = 12u}, {.offset = 0x3f2f30u, .patch_size = 12u}, {.offset = 0x213920u, .patch_size = 12u}, {.offset = 0x212af0u, .patch_size = 12u}},
},
{
.firmware = 0x0320u,
.name = "3.20",
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f2fe0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
},
{
.firmware = 0x0321u,
.name = "3.21",
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f2fe0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
},
{
.firmware = 0x0400u,
.name = "4.00",
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x433d90u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
},
{
.firmware = 0x0403u,
.name = "4.03",
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x433d90u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
},
{
.firmware = 0x0450u,
.name = "4.50",
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x4344e0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
},
{
.firmware = 0x0451u,
.name = "4.51",
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x4344e0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
},
{
.firmware = 0x0500u,
.name = "5.00",
.targets = {{.offset = 0x498950u, .patch_size = 12u}, {.offset = 0x49a0a0u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
},
{
.firmware = 0x0502u,
.name = "5.02",
.targets = {{.offset = 0x498940u, .patch_size = 12u}, {.offset = 0x49a090u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
},
{
.firmware = 0x0510u,
.name = "5.10",
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49b650u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
},
{
.firmware = 0x0550u,
.name = "5.50",
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49b650u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
},
{
.firmware = 0x0600u,
.name = "6.00",
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4fdf70u, .patch_size = 15u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
},
{
.firmware = 0x0602u,
.name = "6.02",
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4fdf70u, .patch_size = 15u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
},
{
.firmware = 0x0650u,
.name = "6.50",
.targets = {{.offset = 0x4c73f0u, .patch_size = 12u}, {.offset = 0x4fdfe0u, .patch_size = 15u}, {.offset = 0x26ba20u, .patch_size = 12u}, {.offset = 0x271c20u, .patch_size = 12u}},
},
{
.firmware = 0x0700u,
.name = "7.00",
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x5a85d0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
},
{
.firmware = 0x0701u,
.name = "7.01",
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x5a85d0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
},
{
.firmware = 0x0720u,
.name = "7.20",
.targets = {{.offset = 0x568940u, .patch_size = 12u}, {.offset = 0x5a85f0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
},
{
.firmware = 0x0740u,
.name = "7.40",
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
},
{
.firmware = 0x0760u,
.name = "7.60",
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
},
{
.firmware = 0x0761u,
.name = "7.61",
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
},
{
.firmware = 0x0800u,
.name = "8.00",
.targets = {{.offset = 0x597eb0u, .patch_size = 12u}, {.offset = 0x5d3060u, .patch_size = 13u}, {.offset = 0x29a730u, .patch_size = 12u}, {.offset = 0x2a12f0u, .patch_size = 12u}},
},
{
.firmware = 0x0820u,
.name = "8.20",
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x5d4130u, .patch_size = 13u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
},
{
.firmware = 0x0840u,
.name = "8.40",
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x5d4130u, .patch_size = 13u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
},
{
.firmware = 0x0860u,
.name = "8.60",
.targets = {{.offset = 0x598d60u, .patch_size = 12u}, {.offset = 0x5d3f10u, .patch_size = 13u}, {.offset = 0x29a750u, .patch_size = 12u}, {.offset = 0x2a1310u, .patch_size = 12u}},
},
{
.firmware = 0x0900u,
.name = "9.00",
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x6088e0u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
},
{
.firmware = 0x0905u,
.name = "9.05",
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x6088e0u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
},
{
.firmware = 0x0920u,
.name = "9.20",
.targets = {{.offset = 0x5cbdc0u, .patch_size = 12u}, {.offset = 0x608600u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
},
{
.firmware = 0x0940u,
.name = "9.40",
.targets = {{.offset = 0x5cc490u, .patch_size = 12u}, {.offset = 0x608cd0u, .patch_size = 13u}, {.offset = 0x2b7ba0u, .patch_size = 12u}, {.offset = 0x2be7b0u, .patch_size = 12u}},
},
{
.firmware = 0x0960u,
.name = "9.60",
.targets = {{.offset = 0x5cc500u, .patch_size = 12u}, {.offset = 0x608d40u, .patch_size = 13u}, {.offset = 0x2b7c10u, .patch_size = 12u}, {.offset = 0x2be820u, .patch_size = 12u}},
}
-144
View File
@@ -1,144 +0,0 @@
#include "sm_platform.h"
#include <ps5/mdbg.h>
#include "sm_log.h"
#include "sm_shellcore_remote.h"
#define X86_PAGE_FRAME 0x000ffffffffff000ull
#define X86_PAGE_VALID 0x001ull
#define X86_PAGE_LARGE 0x080ull
static uintptr_t resolve_vmspace_pmap(uintptr_t vmspace) {
uint32_t version = kernel_get_fw_version() >> 16;
if (version >= 0x0100u && version <= 0x0102u)
return vmspace + 0x2c0u;
if (version >= 0x0105u && version <= 0x0550u)
return vmspace + 0x2e0u;
if (version >= 0x0600u && version <= 0x1270u)
return vmspace + 0x2e8u;
return 0;
}
static bool resolve_process_paging(pid_t pid, uint64_t *cr3_out,
uint64_t *dmap_out) {
uintptr_t process = (uintptr_t)kernel_get_proc(pid);
if (!process)
return false;
uintptr_t vmspace = (uintptr_t)kernel_getlong(
process + (uintptr_t)KERNEL_OFFSET_PROC_P_VMSPACE);
if (!vmspace)
return false;
uintptr_t pmap = resolve_vmspace_pmap(vmspace);
if (!pmap)
return false;
uint64_t paging[2] = {0};
if (kernel_copyout((intptr_t)(pmap + 32u), paging, sizeof(paging)) != 0)
return false;
if (!paging[0] || !paging[1])
return false;
*cr3_out = paging[1];
*dmap_out = paging[0] - paging[1];
return true;
}
static uint64_t virtual_to_physical(uint64_t address, uint64_t dmap,
uint64_t page_map,
uint64_t *physical_limit_out) {
page_map &= X86_PAGE_FRAME;
for (int shift = 39; shift >= 12; shift -= 9) {
uint64_t index = (address >> shift) & 0x1ffu;
page_map = kernel_getlong((intptr_t)(dmap + page_map + index * 8u));
if ((page_map & X86_PAGE_VALID) == 0)
return UINT64_MAX;
if ((page_map & X86_PAGE_LARGE) != 0 || shift == 12) {
page_map &= (1ull << 52) - (1ull << shift);
page_map |= address & ((1ull << shift) - 1ull);
if (physical_limit_out)
*physical_limit_out = (page_map | ((1ull << shift) - 1ull)) + 1ull;
return page_map;
}
page_map &= X86_PAGE_FRAME;
}
return UINT64_MAX;
}
bool sm_remote_process_read(pid_t pid, uintptr_t address, void *buffer,
size_t size) {
if (!buffer || size == 0)
return false;
return mdbg_copyout(pid, (intptr_t)address, buffer, size) == 0;
}
bool sm_remote_process_write(pid_t pid, uintptr_t address, const void *buffer,
size_t size) {
if (!buffer || size == 0)
return false;
if ((kernel_get_fw_version() >> 16) <= 0x0820u)
return mdbg_copyin(pid, buffer, (intptr_t)address, size) == 0;
void *probe = malloc(size);
if (!probe)
return false;
bool faulted = sm_remote_process_read(pid, address, probe, size);
free(probe);
if (!faulted)
return false;
uint64_t cr3 = 0;
uint64_t dmap = 0;
if (!resolve_process_paging(pid, &cr3, &dmap))
return false;
const uint8_t *source = (const uint8_t *)buffer;
while (size != 0) {
uint64_t physical_limit = 0;
uint64_t physical =
virtual_to_physical(address, dmap, cr3, &physical_limit);
if (physical == UINT64_MAX || physical_limit <= physical)
return false;
size_t chunk = (size_t)(physical_limit - physical);
if (chunk > size)
chunk = size;
if (kernel_copyin(source, (intptr_t)(dmap + physical), chunk) != 0)
return false;
address += chunk;
source += chunk;
size -= chunk;
}
return true;
}
bool sm_shellcore_remote_resolve(pid_t pid, sm_shellcore_remote_t *remote_out) {
if (pid <= 0 || !remote_out)
return false;
memset(remote_out, 0, sizeof(*remote_out));
const sm_shellcore_firmware_offsets_t *firmware =
sm_shellcore_offsets_for_firmware(kernel_get_fw_version());
if (!firmware) {
log_debug(" [SHELLCORE] unsupported firmware: 0x%08x",
kernel_get_fw_version());
return false;
}
uintptr_t image_base = (uintptr_t)kernel_dynlib_mapbase_addr(pid, 0);
if (!image_base) {
log_debug(" [SHELLCORE] failed to resolve image base for pid=%ld",
(long)pid);
return false;
}
for (int target = 0; target < SM_SHELLCORE_TARGET_COUNT; ++target) {
remote_out->targets[target] = image_base + firmware->targets[target].offset;
}
remote_out->pid = pid;
remote_out->image_base = image_base;
remote_out->offsets = firmware;
log_debug(" [SHELLCORE] resolved fw=%s pid=%ld base=0x%lx", firmware->name,
(long)pid, (unsigned long)image_base);
return true;
}
-395
View File
@@ -1,395 +0,0 @@
#include "sm_platform.h"
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "sm_filesystem.h"
#include "sm_image.h"
#include "sm_log.h"
#include "sm_path_utils.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_scanner.h"
#include "sm_shellcore_protocol.h"
#include "sm_shellcore_service.h"
_Static_assert(sizeof(sm_shellcore_request_t) == 40,
"unexpected ShellCore request size");
_Static_assert(sizeof(sm_shellcore_response_t) == 4,
"unexpected ShellCore response size");
typedef struct {
pthread_t thread;
pthread_mutex_t mutex;
bool started;
bool stop_requested;
int listen_fd;
// Owner of the prepared runtime mount, not the lifecycle active-game state.
char prepared_title_id[MAX_TITLE_ID];
} shellcore_service_state_t;
static shellcore_service_state_t g_service = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.listen_fd = -1,
};
static void publish_prepared_title(const char *title_id) {
bool changed = false;
pthread_mutex_lock(&g_service.mutex);
if (strcmp(g_service.prepared_title_id, title_id) != 0) {
(void)strlcpy(g_service.prepared_title_id, title_id,
sizeof(g_service.prepared_title_id));
changed = true;
}
pthread_mutex_unlock(&g_service.mutex);
if (changed)
sm_scanner_wake();
}
static void clear_prepared_title(const char *title_id) {
bool changed = false;
pthread_mutex_lock(&g_service.mutex);
if (strcmp(g_service.prepared_title_id, title_id) == 0) {
g_service.prepared_title_id[0] = '\0';
changed = true;
}
pthread_mutex_unlock(&g_service.mutex);
if (changed)
sm_scanner_wake();
}
static bool read_full(int fd, void *buffer, size_t size) {
uint8_t *bytes = (uint8_t *)buffer;
while (size != 0) {
ssize_t count = recv(fd, bytes, size, 0);
if (count == 0)
return false;
if (count < 0) {
if (errno == EINTR)
continue;
return false;
}
bytes += (size_t)count;
size -= (size_t)count;
}
return true;
}
static bool write_full(int fd, const void *buffer, size_t size) {
const uint8_t *bytes = (const uint8_t *)buffer;
while (size != 0) {
ssize_t count = send(fd, bytes, size, 0);
if (count == 0)
return false;
if (count < 0) {
if (errno == EINTR)
continue;
return false;
}
bytes += (size_t)count;
size -= (size_t)count;
}
return true;
}
static bool find_required_image_layer(const char *root,
const char *runtime_source,
unsigned int depth,
char image_path[MAX_PATH]) {
DIR *dir = opendir(root);
if (!dir)
return false;
bool found = false;
struct dirent *entry;
while (!found && (entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.')
continue;
char path[MAX_PATH];
int written = snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
if (written < 0 || (size_t)written >= sizeof(path))
continue;
struct stat st;
if (stat(path, &st) != 0)
continue;
if (S_ISREG(st.st_mode) &&
is_supported_image_file_path(path, entry->d_name)) {
char mount_point[MAX_PATH];
get_image_mount_point_for_source(path, mount_point);
if (path_matches_root_or_child(runtime_source, mount_point)) {
(void)strlcpy(image_path, path, MAX_PATH);
found = true;
}
continue;
}
if (depth > 0 && S_ISDIR(st.st_mode))
found = find_required_image_layer(path, runtime_source, depth - 1u,
image_path);
}
closedir(dir);
return found;
}
static bool repair_image_chain_for_runtime_source(
const char *title_id, const char *runtime_source, const char *eboot_path,
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *image_count) {
while (*image_count < MAX_IMAGE_CHAIN_DEPTH) {
char mounted_root[MAX_PATH];
get_image_mount_point_for_source(image_chain[*image_count - 1u],
mounted_root);
char image_path[MAX_PATH];
if (!find_required_image_layer(mounted_root, runtime_source,
MAX_SCAN_DEPTH, image_path)) {
return false;
}
const char *name = strrchr(image_path, '/');
name = name ? name + 1 : image_path;
bool unstable = false;
if (!maybe_mount_image_file(image_path, name, &unstable))
return false;
(void)strlcpy(image_chain[*image_count], image_path, MAX_PATH);
(*image_count)++;
if (!write_mount_image_chain(title_id, image_chain, *image_count)) {
log_debug(" [SHELLCORE] failed to persist repaired image chain: %s",
title_id);
} else {
log_debug(" [SHELLCORE] image chain repaired: title=%s layers=%zu",
title_id, *image_count);
}
if (path_exists(eboot_path)) {
return true;
}
}
return false;
}
static bool prepare_image_source(const char *title_id,
const char *runtime_source) {
char eboot_path[MAX_PATH];
int written = snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin",
runtime_source);
if (written < 0 || (size_t)written >= sizeof(eboot_path))
return false;
if (path_exists(eboot_path))
return true;
if (!is_under_image_mount_base(runtime_source))
return false;
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
size_t image_count = 0;
if (!read_mount_image_chain(title_id, image_chain, &image_count))
return false;
// The USB filesystem can become available shortly after WORKING. Avoid
// repeatedly entering the image attach path until its outer layer exists.
if (!path_exists(image_chain[0]))
return false;
for (size_t i = 0; i < image_count; ++i) {
const char *name = strrchr(image_chain[i], '/');
name = name ? name + 1 : image_chain[i];
bool unstable = false;
if (!maybe_mount_image_file(image_chain[i], name, &unstable)) {
log_debug(" [SHELLCORE] image chain mount failed: title=%s layer=%zu "
"path=%s",
title_id, i, image_chain[i]);
return false;
}
}
if (path_exists(eboot_path))
return true;
if (repair_image_chain_for_runtime_source(title_id, runtime_source,
eboot_path, image_chain,
&image_count))
return true;
log_debug(" [SHELLCORE] image chain ready but eboot missing: %s",
eboot_path);
return false;
}
static bool prepare_title_runtime(const char *title_id,
const char *source_path) {
if (!prepare_image_source(title_id, source_path))
return false;
runtime_mount_state_lock();
bool ready = !runtime_sleep_mode_active() &&
mount_title_nullfs(title_id, source_path);
if (ready)
ready = mount_backport_overlay_for_title(source_path, title_id, NULL, NULL);
runtime_mount_state_unlock();
return ready;
}
bool sm_shellcore_ensure_title_runtime(const char *title_id) {
if (!title_id || title_id[0] == '\0' || runtime_sleep_mode_active())
return false;
char source_path[MAX_PATH];
if (!read_mount_link(title_id, source_path, sizeof(source_path)))
return true;
return prepare_title_runtime(title_id, source_path);
}
static int handle_launch_request(const char *title_id) {
if (!title_id || title_id[0] == '\0' || runtime_sleep_mode_active())
return EINVAL;
char source_path[MAX_PATH];
if (!read_mount_link(title_id, source_path, sizeof(source_path)))
return ENOENT;
publish_prepared_title(title_id);
bool ready = prepare_title_runtime(title_id, source_path);
if (!ready) {
clear_prepared_title(title_id);
return EIO;
}
log_debug(" [SHELLCORE] launch mount ready: %s", title_id);
return 0;
}
static void release_backing_image(const char *title_id) {
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
size_t image_count = 0;
if (!read_mount_image_chain(title_id, image_chain, &image_count))
return;
for (size_t layer = image_count; layer > 0; --layer) {
(void)release_runtime_image_mount(image_chain[layer - 1u]);
}
}
bool sm_shellcore_release_title_runtime(const char *title_id) {
if (!title_id || title_id[0] == '\0')
return false;
runtime_mount_state_lock();
bool released = unmount_title_runtime_layers(title_id);
if (released)
release_backing_image(title_id);
runtime_mount_state_unlock();
if (released)
clear_prepared_title(title_id);
return released;
}
bool sm_shellcore_service_has_prepared_mount(void) {
pthread_mutex_lock(&g_service.mutex);
bool prepared = g_service.prepared_title_id[0] != '\0';
pthread_mutex_unlock(&g_service.mutex);
return prepared;
}
static int handle_launch_failed_request(const char *title_id) {
if (!title_id || title_id[0] == '\0')
return 0;
pthread_mutex_lock(&g_service.mutex);
bool prepared = strcmp(g_service.prepared_title_id, title_id) == 0;
pthread_mutex_unlock(&g_service.mutex);
if (!prepared)
return 0;
return sm_shellcore_release_title_runtime(title_id) ? 0 : EBUSY;
}
static void handle_client(int fd) {
sm_shellcore_request_t request;
sm_shellcore_response_t response;
if (!read_full(fd, &request, sizeof(request)))
return;
if (request.magic != SM_SHELLCORE_PROTOCOL_MAGIC ||
request.version != SM_SHELLCORE_PROTOCOL_VERSION ||
strnlen(request.title_id, sizeof(request.title_id)) ==
sizeof(request.title_id)) {
response.status = EPROTO;
} else if (request.operation == SM_SHELLCORE_REQUEST_LAUNCH) {
response.status = handle_launch_request(request.title_id);
} else if (request.operation == SM_SHELLCORE_REQUEST_LAUNCH_FAILED) {
response.status = handle_launch_failed_request(request.title_id);
} else {
response.status = ENOTSUP;
}
(void)write_full(fd, &response, sizeof(response));
}
static void *service_thread_main(void *arg) {
(void)arg;
while (true) {
int client = accept(g_service.listen_fd, NULL, NULL);
if (client < 0) {
if (errno == EINTR)
continue;
pthread_mutex_lock(&g_service.mutex);
bool stopping = g_service.stop_requested;
pthread_mutex_unlock(&g_service.mutex);
if (!stopping)
log_debug(" [SHELLCORE] accept failed: %s", strerror(errno));
break;
}
handle_client(client);
close(client);
}
return NULL;
}
bool sm_shellcore_service_start(void) {
if (g_service.started)
return true;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return false;
struct sockaddr_un address;
memset(&address, 0, sizeof(address));
address.sun_family = AF_UNIX;
(void)strlcpy(address.sun_path, SM_SHELLCORE_SOCKET_PATH,
sizeof(address.sun_path));
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
if (bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0 ||
listen(fd, 4) != 0) {
int saved_errno = errno;
close(fd);
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
errno = saved_errno;
return false;
}
g_service.listen_fd = fd;
g_service.stop_requested = false;
g_service.prepared_title_id[0] = '\0';
int rc = pthread_create(&g_service.thread, NULL, service_thread_main, NULL);
if (rc != 0) {
close(fd);
g_service.listen_fd = -1;
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
errno = rc;
return false;
}
g_service.started = true;
return true;
}
void sm_shellcore_service_stop(void) {
if (!g_service.started)
return;
pthread_mutex_lock(&g_service.mutex);
g_service.stop_requested = true;
int fd = g_service.listen_fd;
g_service.listen_fd = -1;
pthread_mutex_unlock(&g_service.mutex);
if (fd >= 0)
(void)shutdown(fd, SHUT_RDWR);
if (fd >= 0)
close(fd);
pthread_join(g_service.thread, NULL);
g_service.started = false;
g_service.stop_requested = false;
g_service.prepared_title_id[0] = '\0';
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
}
-229
View File
@@ -1,229 +0,0 @@
#!/usr/bin/env python3
"""Generate SceShellCore lifecycle offsets from unpacked firmware files.
The generator identifies functions through semantic log strings, finds the
containing function prologue and emits a compact firmware offset table. It is
intentionally independent from IDA databases so the checked-in table can be
reproduced from unpacked firmware files.
"""
from __future__ import annotations
import argparse
import struct
from dataclasses import dataclass
from pathlib import Path
from capstone import CS_ARCH_X86, CS_MODE_64, Cs
PROLOGUE = b"\x55\x48\x89\xe5"
TARGET_NAMES = (
"launch_app",
"unmount_workspace",
"install_title_dir",
"install_all",
)
@dataclass
class LoadSegment:
file_offset: int
virtual_address: int
file_size: int
flags: int
class ShellCore:
def __init__(self, path: Path):
self.path = path
self.data = path.read_bytes()
header = struct.unpack_from("<16sHHIQQQIHHHHHH", self.data, 0)
phoff, phentsize, phnum = header[5], header[9], header[10]
self.loads: list[LoadSegment] = []
self.executable: LoadSegment | None = None
for index in range(phnum):
values = struct.unpack_from(
"<IIQQQQQQ", self.data, phoff + index * phentsize
)
p_type, flags, offset, address, _, file_size, _, _ = values
if p_type != 1:
continue
segment = LoadSegment(offset, address, file_size, flags)
self.loads.append(segment)
if flags & 1:
self.executable = segment
if self.executable is None:
raise ValueError(f"{path}: executable PT_LOAD not found")
def file_to_virtual(self, offset: int) -> int:
for segment in self.loads:
relative = offset - segment.file_offset
if 0 <= relative < segment.file_size:
return segment.virtual_address + relative
raise ValueError(f"file offset 0x{offset:x} is not mapped")
def virtual_to_file(self, address: int) -> int:
for segment in self.loads:
relative = address - segment.virtual_address
if 0 <= relative < segment.file_size:
return segment.file_offset + relative
raise ValueError(f"virtual address 0x{address:x} is not mapped")
def string_addresses(self, needle: bytes, use_string_start: bool) -> list[int]:
result: list[int] = []
offset = 0
while True:
offset = self.data.find(needle, offset)
if offset < 0:
return result
string_offset = offset
if use_string_start:
string_offset = self.data.rfind(
b"\0", max(0, offset - 256), offset
) + 1
result.append(self.file_to_virtual(string_offset))
offset += 1
def rip_xrefs(self, targets: list[int], slop: int = 0) -> list[int]:
segment = self.executable
assert segment is not None
code = self.data[
segment.file_offset : segment.file_offset + segment.file_size
]
result: list[int] = []
for index in range(len(code) - 7):
if code[index] not in (0x48, 0x4C):
continue
if code[index + 1] not in (0x8D, 0x8B):
continue
if code[index + 2] & 0xC7 != 0x05:
continue
displacement = struct.unpack_from("<i", code, index + 3)[0]
instruction_address = segment.virtual_address + index
destination = instruction_address + 7 + displacement
if any(target - slop <= destination <= target + slop for target in targets):
result.append(instruction_address)
return result
def preceding_prologue(self, address: int, window: int = 0x40000) -> int:
end = self.virtual_to_file(address)
start = max(self.executable.file_offset, end - window) # type: ignore[union-attr]
found = self.data.rfind(PROLOGUE, start, end)
if found < 0:
raise ValueError(f"no prologue before 0x{address:x}")
return self.file_to_virtual(found)
def locate_targets(self) -> dict[str, int]:
launch_strings = self.string_addresses(b"launchApp(%s)", True)
launch_xrefs = self.rip_xrefs(launch_strings)
if len(launch_xrefs) != 1:
raise ValueError(f"launchApp xrefs: {launch_xrefs!r}")
launch = self.preceding_prologue(launch_xrefs[0])
unmount_strings = self.string_addresses(
b"m_workspace.unmountWorkspace()", False
)
unmount_groups = {
self.preceding_prologue(xref)
for xref in self.rip_xrefs(unmount_strings, slop=8)
}
unmount_groups.discard(launch)
if len(unmount_groups) != 1:
raise ValueError(f"unmountWorkspace candidates: {unmount_groups!r}")
unmount = next(iter(unmount_groups))
install_strings = self.string_addresses(b"AppInstallTitleDirMain", False)
install_groups: dict[int, int] = {}
for xref in self.rip_xrefs(install_strings, slop=8):
prologue = self.preceding_prologue(xref)
install_groups[prologue] = install_groups.get(prologue, 0) + 1
if not install_groups:
raise ValueError("AppInstallTitleDirMain xrefs not found")
best_count = max(install_groups.values())
best = [address for address, count in install_groups.items() if count == best_count]
if len(best) != 1:
raise ValueError(f"ambiguous AppInstallTitleDirMain: {install_groups!r}")
app_install_all_strings = self.string_addresses(b"AppInstallAll()", True)
app_install_all_xrefs = self.rip_xrefs(app_install_all_strings)
call_targets: dict[int, int] = {}
for xref in app_install_all_xrefs:
xref_file = self.virtual_to_file(xref)
for call_file in range(xref_file - 32, xref_file):
if self.data[call_file] != 0xE8:
continue
call_address = self.file_to_virtual(call_file)
displacement = struct.unpack_from("<i", self.data, call_file + 1)[0]
target = call_address + 5 + displacement
call_targets[target] = call_targets.get(target, 0) + 1
if not call_targets:
raise ValueError("AppInstallAll call target not found")
app_install_all = max(call_targets, key=call_targets.get)
return {
"launch_app": launch,
"unmount_workspace": unmount,
"install_title_dir": best[0],
"install_all": app_install_all,
}
def patch_size(self, address: int, minimum: int = 12) -> int:
offset = self.virtual_to_file(address)
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
size = 0
for instruction in decoder.disasm(self.data[offset : offset + 64], address):
size += instruction.size
if size >= minimum:
return size
raise ValueError(f"cannot determine patch size at 0x{address:x}")
def firmware_key(name: str) -> int:
major, minor = (int(part) for part in name.split(".", 1))
if major > 99 or minor > 99:
raise ValueError(f"firmware version is not BCD-compatible: {name}")
major_bcd = ((major // 10) << 4) | (major % 10)
minor_bcd = ((minor // 10) << 4) | (minor % 10)
return (major_bcd << 8) | minor_bcd
def generate(root: Path) -> str:
firmware_dirs = sorted(
(path for path in root.iterdir() if (path / "system/vsh/SceShellCore.elf").is_file()),
key=lambda path: tuple(int(part) for part in path.name.split(".")),
)
records: list[str] = []
for firmware_dir in firmware_dirs:
shellcore = ShellCore(firmware_dir / "system/vsh/SceShellCore.elf")
targets = shellcore.locate_targets()
records.append(
" {\n"
f" .firmware = 0x{firmware_key(firmware_dir.name):04x}u,\n"
f" .name = \"{firmware_dir.name}\",\n"
" .targets = {"
+ ", ".join(
"{.offset = "
f"0x{targets[name]:x}u, .patch_size = "
f"{shellcore.patch_size(targets[name])}u}}"
for name in TARGET_NAMES
)
+ "},\n }"
)
return "/* Generated by tools/generate_shellcore_offsets.py. */\n" + ",\n".join(records) + "\n"
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("root", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
output = generate(args.root)
if args.output:
with args.output.open("w", encoding="utf-8", newline="\n") as file:
file.write(output)
else:
print(output, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,77 +0,0 @@
#!/usr/bin/env python3
"""Generate SceShellCore lifecycle offsets from a flat ELF directory.
Input files must be named ``SceShellCoreXXX.elf`` or ``SceShellCoreXXXX.elf``.
The last two digits are the minor firmware version; the preceding digits are
the major version. For example, ``SceShellCore1140.elf`` is firmware 11.40.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
from generate_shellcore_offsets import ShellCore, TARGET_NAMES, firmware_key
SHELLCORE_NAME = re.compile(r"^SceShellCore(\d{3,4})\.elf$")
def firmware_files(root: Path) -> list[tuple[int, int, Path]]:
result: list[tuple[int, int, Path]] = []
for path in root.iterdir():
if not path.is_file():
continue
match = SHELLCORE_NAME.fullmatch(path.name)
if match is None:
continue
version = match.group(1)
result.append((int(version[:-2]), int(version[-2:]), path))
return sorted(result, key=lambda item: (item[0], item[1]))
def generate(root: Path) -> str:
records: list[str] = []
for major, minor, path in firmware_files(root):
firmware_name = f"{major}.{minor:02d}"
shellcore = ShellCore(path)
targets = shellcore.locate_targets()
records.append(
" {\n"
f" .firmware = 0x{firmware_key(firmware_name):04x}u,\n"
f" .name = \"{firmware_name}\",\n"
" .targets = {"
+ ", ".join(
"{.offset = "
f"0x{targets[name]:x}u, .patch_size = "
f"{shellcore.patch_size(targets[name])}u}}"
for name in TARGET_NAMES
)
+ "},\n }"
)
return (
"/* Generated by tools/generate_shellcore_offsets_from_files.py. */\n"
+ ",\n".join(records)
+ "\n"
)
def main() -> int:
parser = argparse.ArgumentParser(
description="Generate offsets from SceShellCoreXXXX.elf files"
)
parser.add_argument("root", type=Path, help="directory containing the ELF files")
parser.add_argument("--output", type=Path)
args = parser.parse_args()
output = generate(args.root)
if args.output:
with args.output.open("w", encoding="utf-8", newline="\n") as file:
file.write(output)
else:
print(output, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/env python3
"""Verify that generated ShellCore hook prologues are safe to copy."""
from __future__ import annotations
import argparse
from pathlib import Path
from capstone import CS_ARCH_X86, CS_GRP_CALL, CS_GRP_JUMP, CS_MODE_64, Cs
from capstone.x86 import X86_OP_MEM, X86_REG_RIP
from generate_shellcore_offsets import ShellCore
def verify_firmware(path: Path) -> None:
shellcore = ShellCore(path)
targets = shellcore.locate_targets()
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
decoder.detail = True
names = ["launch_app"]
# AppInstallAll is hooked after the public TitleDir RPC disappeared.
try:
firmware = int(path.parents[2].name.replace(".", ""), 16)
except ValueError:
firmware = 0
if firmware >= 0x1200:
names.append("install_all")
for name in names:
address = targets[name]
patch_size = shellcore.patch_size(address)
offset = shellcore.virtual_to_file(address)
code = shellcore.data[offset : offset + patch_size]
instructions = list(decoder.disasm(code, address))
if sum(instruction.size for instruction in instructions) != patch_size:
raise ValueError(f"{path}: {name}: split instruction")
for instruction in instructions:
if instruction.group(CS_GRP_CALL) or instruction.group(CS_GRP_JUMP):
raise ValueError(
f"{path}: {name}: control flow in copied prologue at "
f"0x{instruction.address:x}"
)
for operand in instruction.operands:
if operand.type == X86_OP_MEM and operand.mem.base == X86_REG_RIP:
raise ValueError(
f"{path}: {name}: RIP-relative operand at "
f"0x{instruction.address:x}"
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("root", type=Path)
args = parser.parse_args()
firmware_dirs = sorted(
path
for path in args.root.iterdir()
if (path / "system/vsh/SceShellCore.elf").is_file()
)
for firmware_dir in firmware_dirs:
verify_firmware(firmware_dir / "system/vsh/SceShellCore.elf")
print(f"verified {len(firmware_dirs)} firmware trampoline layouts")
return 0
if __name__ == "__main__":
raise SystemExit(main())