mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-21 22:22:28 -05:00
809 lines
24 KiB
C
809 lines
24 KiB
C
#include "sm_platform.h"
|
|
|
|
#include "sm_config_mount.h"
|
|
#include "sm_filesystem.h"
|
|
#include "sm_kstuff.h"
|
|
#include "sm_limits.h"
|
|
#include "sm_log.h"
|
|
#include "sm_path_utils.h"
|
|
#include "sm_time.h"
|
|
#include "sm_types.h"
|
|
|
|
#include <stdatomic.h>
|
|
|
|
#define KSTUFF_SYSENTVEC_TOGGLE_OFFSET 14
|
|
#define KSTUFF_SYSENTVEC_ENABLED 0xdeb7u
|
|
#define KSTUFF_SYSENTVEC_DISABLED 0xffffu
|
|
typedef struct {
|
|
bool active;
|
|
bool image_backed;
|
|
bool autopause_delay_valid;
|
|
bool pause_applied;
|
|
bool focus_override_active;
|
|
pid_t pid;
|
|
uint32_t app_id;
|
|
uint32_t autopause_delay_seconds;
|
|
uint64_t launch_time_us;
|
|
uint64_t pause_deadline_us;
|
|
char title_id[MAX_TITLE_ID];
|
|
} kstuff_game_entry_t;
|
|
|
|
typedef struct {
|
|
intptr_t sysentvec_ps5;
|
|
intptr_t sysentvec_ps4;
|
|
bool probe_available;
|
|
bool supported;
|
|
bool restore_on_empty;
|
|
bool current_app_focus_valid;
|
|
uint32_t current_app_focus_id;
|
|
uint64_t restore_retry_us;
|
|
kstuff_game_entry_t game;
|
|
} kstuff_state_t;
|
|
|
|
static kstuff_state_t g_kstuff;
|
|
static _Atomic uint32_t g_pending_app_focus_id;
|
|
static _Atomic bool g_pending_app_focus_valid;
|
|
static _Atomic bool g_pending_config_reload;
|
|
|
|
static bool refresh_kstuff_support_state(void);
|
|
static uint32_t get_pause_delay_seconds_for_title(const char *title_id,
|
|
bool *image_backed_out,
|
|
uint32_t *autopause_delay_out,
|
|
bool *autopause_delay_valid_out);
|
|
static void restore_kstuff_if_needed(const char *reason);
|
|
|
|
static char *trim_ascii_inplace(char *s) {
|
|
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
|
|
s++;
|
|
|
|
size_t len = strlen(s);
|
|
while (len > 0) {
|
|
char ch = s[len - 1u];
|
|
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
|
|
break;
|
|
s[len - 1u] = '\0';
|
|
len--;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static char *strip_inline_comment(char *s) {
|
|
char *comment = strpbrk(s, "#;");
|
|
if (comment)
|
|
*comment = '\0';
|
|
return trim_ascii_inplace(s);
|
|
}
|
|
|
|
static bool parse_pause_delay_seconds(const char *value,
|
|
uint32_t *delay_seconds_out) {
|
|
if (!value || !delay_seconds_out)
|
|
return false;
|
|
|
|
errno = 0;
|
|
char *end = NULL;
|
|
unsigned long parsed = strtoul(value, &end, 10);
|
|
if (errno != 0 || end == value || *end != '\0' ||
|
|
parsed > MAX_KSTUFF_PAUSE_DELAY_SECONDS) {
|
|
return false;
|
|
}
|
|
|
|
*delay_seconds_out = (uint32_t)parsed;
|
|
return true;
|
|
}
|
|
|
|
static bool get_autopause_delay_for_source(const char *source_path,
|
|
bool image_backed,
|
|
uint32_t *delay_seconds_out) {
|
|
if (!source_path || source_path[0] == '\0' || !delay_seconds_out)
|
|
return false;
|
|
|
|
char autopause_path[MAX_PATH];
|
|
int written = snprintf(autopause_path, sizeof(autopause_path), "%s/%s",
|
|
source_path, "autopause.txt");
|
|
if (written <= 0 || (size_t)written >= sizeof(autopause_path))
|
|
return false;
|
|
|
|
FILE *f = fopen(autopause_path, "r");
|
|
if (!f)
|
|
return false;
|
|
|
|
char line[128];
|
|
bool have_scalar = false;
|
|
bool have_direct = false;
|
|
bool have_image = false;
|
|
uint32_t scalar_delay = 0;
|
|
uint32_t direct_delay = 0;
|
|
uint32_t image_delay = 0;
|
|
|
|
while (fgets(line, sizeof(line), f)) {
|
|
char *value = strip_inline_comment(line);
|
|
if (value[0] == '\0')
|
|
continue;
|
|
|
|
char *eq = strchr(value, '=');
|
|
if (!eq) {
|
|
if (!have_scalar && parse_pause_delay_seconds(value, &scalar_delay))
|
|
have_scalar = true;
|
|
continue;
|
|
}
|
|
|
|
*eq = '\0';
|
|
char *key = trim_ascii_inplace(value);
|
|
char *arg = trim_ascii_inplace(eq + 1);
|
|
uint32_t parsed_delay = 0;
|
|
if (!parse_pause_delay_seconds(arg, &parsed_delay))
|
|
continue;
|
|
|
|
if (strcasecmp(key, "direct") == 0) {
|
|
direct_delay = parsed_delay;
|
|
have_direct = true;
|
|
} else if (strcasecmp(key, "image") == 0) {
|
|
image_delay = parsed_delay;
|
|
have_image = true;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (image_backed && have_image) {
|
|
*delay_seconds_out = image_delay;
|
|
return true;
|
|
}
|
|
if (!image_backed && have_direct) {
|
|
*delay_seconds_out = direct_delay;
|
|
return true;
|
|
}
|
|
if (have_scalar) {
|
|
uint64_t delay_seconds = scalar_delay;
|
|
if (image_backed)
|
|
delay_seconds *= 2u;
|
|
if (delay_seconds > MAX_KSTUFF_PAUSE_DELAY_SECONDS)
|
|
delay_seconds = MAX_KSTUFF_PAUSE_DELAY_SECONDS;
|
|
*delay_seconds_out = (uint32_t)delay_seconds;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool resolve_title_pause_source(const char *title_id,
|
|
char source_path[MAX_PATH],
|
|
bool *image_backed_out) {
|
|
source_path[0] = '\0';
|
|
*image_backed_out = false;
|
|
|
|
if (!title_id)
|
|
return false;
|
|
|
|
if (!read_mount_link(title_id, source_path, MAX_PATH))
|
|
return false;
|
|
|
|
*image_backed_out = is_under_image_mount_base(source_path);
|
|
return true;
|
|
}
|
|
|
|
static uint32_t get_configured_pause_delay_seconds(const char *title_id,
|
|
bool image_backed,
|
|
bool *override_applied_out) {
|
|
const runtime_config_t *cfg = runtime_config();
|
|
uint32_t delay_seconds =
|
|
image_backed ? cfg->kstuff_pause_delay_image_seconds
|
|
: cfg->kstuff_pause_delay_direct_seconds;
|
|
uint32_t override_delay_seconds = 0;
|
|
if (get_kstuff_pause_delay_override_for_title(title_id,
|
|
&override_delay_seconds)) {
|
|
*override_applied_out = true;
|
|
return override_delay_seconds;
|
|
}
|
|
|
|
*override_applied_out = false;
|
|
return delay_seconds;
|
|
}
|
|
|
|
static bool resolve_kstuff_sysentvec_addrs(intptr_t *ps5_out, intptr_t *ps4_out) {
|
|
if (!ps5_out || !ps4_out)
|
|
return false;
|
|
|
|
*ps5_out = 0;
|
|
*ps4_out = 0;
|
|
|
|
switch (kernel_get_fw_version() & 0xffff0000u) {
|
|
case 0x1000000:
|
|
case 0x1010000:
|
|
case 0x1020000:
|
|
case 0x1050000:
|
|
case 0x1100000:
|
|
case 0x1110000:
|
|
case 0x1120000:
|
|
case 0x1130000:
|
|
case 0x1140000:
|
|
case 0x2000000:
|
|
case 0x2200000:
|
|
case 0x2250000:
|
|
case 0x2260000:
|
|
case 0x2300000:
|
|
case 0x2500000:
|
|
case 0x2700000:
|
|
// Older jailbreak chains usually rely on Byepervisor instead of kstuff.
|
|
return false;
|
|
|
|
case 0x3000000:
|
|
case 0x3100000:
|
|
case 0x3200000:
|
|
case 0x3210000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xca0cd8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xca0e50;
|
|
return true;
|
|
|
|
case 0x4000000:
|
|
case 0x4020000:
|
|
case 0x4030000:
|
|
case 0x4500000:
|
|
case 0x4510000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xd11bb8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xd11d30;
|
|
return true;
|
|
|
|
case 0x5000000:
|
|
case 0x5020000:
|
|
case 0x5100000:
|
|
case 0x5500000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xe00be8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xe00d60;
|
|
return true;
|
|
|
|
case 0x6000000:
|
|
case 0x6020000:
|
|
case 0x6500000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xe210a8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xe21220;
|
|
return true;
|
|
|
|
case 0x7000000:
|
|
case 0x7010000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xe21ab8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xe21c30;
|
|
return true;
|
|
|
|
case 0x7200000:
|
|
case 0x7400000:
|
|
case 0x7600000:
|
|
case 0x7610000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xe21b78;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xe21cf0;
|
|
return true;
|
|
|
|
case 0x8000000:
|
|
case 0x8200000:
|
|
case 0x8400000:
|
|
case 0x8600000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xe21ca8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xe21e20;
|
|
return true;
|
|
|
|
case 0x9000000:
|
|
case 0x9050000:
|
|
case 0x9200000:
|
|
case 0x9400000:
|
|
case 0x9600000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xdba648;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xdba7c0;
|
|
return true;
|
|
|
|
case 0x10000000:
|
|
case 0x10010000:
|
|
case 0x10200000:
|
|
case 0x10400000:
|
|
case 0x10600000:
|
|
*ps5_out = KERNEL_ADDRESS_DATA_BASE + 0xdba6d8;
|
|
*ps4_out = KERNEL_ADDRESS_DATA_BASE + 0xdba850;
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool kstuff_sysentvec_is_enabled(intptr_t sysentvec_addr) {
|
|
return kernel_getshort(sysentvec_addr + KSTUFF_SYSENTVEC_TOGGLE_OFFSET) !=
|
|
KSTUFF_SYSENTVEC_DISABLED;
|
|
}
|
|
|
|
static uint16_t read_kstuff_sysentvec_toggle(intptr_t sysentvec_addr) {
|
|
return (uint16_t)kernel_getshort(sysentvec_addr +
|
|
KSTUFF_SYSENTVEC_TOGGLE_OFFSET);
|
|
}
|
|
|
|
static bool kstuff_sysentvec_toggle_is_known(uint16_t value) {
|
|
return value == KSTUFF_SYSENTVEC_ENABLED ||
|
|
value == KSTUFF_SYSENTVEC_DISABLED;
|
|
}
|
|
|
|
static void set_kstuff_sysentvec_enabled(intptr_t sysentvec_addr, bool enabled) {
|
|
(void)kernel_setshort(sysentvec_addr + KSTUFF_SYSENTVEC_TOGGLE_OFFSET,
|
|
enabled ? KSTUFF_SYSENTVEC_ENABLED
|
|
: KSTUFF_SYSENTVEC_DISABLED);
|
|
}
|
|
|
|
static bool read_kstuff_enabled_state(bool *ps5_out, bool *ps4_out) {
|
|
if (!refresh_kstuff_support_state())
|
|
return false;
|
|
|
|
bool ps5_enabled = kstuff_sysentvec_is_enabled(g_kstuff.sysentvec_ps5);
|
|
bool ps4_enabled = kstuff_sysentvec_is_enabled(g_kstuff.sysentvec_ps4);
|
|
if (ps5_out)
|
|
*ps5_out = ps5_enabled;
|
|
if (ps4_out)
|
|
*ps4_out = ps4_enabled;
|
|
return ps5_enabled && ps4_enabled;
|
|
}
|
|
|
|
static bool refresh_kstuff_support_state(void) {
|
|
if (!g_kstuff.probe_available) {
|
|
g_kstuff.supported = false;
|
|
return false;
|
|
}
|
|
|
|
uint16_t ps5_toggle = read_kstuff_sysentvec_toggle(g_kstuff.sysentvec_ps5);
|
|
uint16_t ps4_toggle = read_kstuff_sysentvec_toggle(g_kstuff.sysentvec_ps4);
|
|
g_kstuff.supported = kstuff_sysentvec_toggle_is_known(ps5_toggle) &&
|
|
kstuff_sysentvec_toggle_is_known(ps4_toggle);
|
|
return g_kstuff.supported;
|
|
}
|
|
|
|
static bool kstuff_state_matches(bool enabled, bool ps5_enabled,
|
|
bool ps4_enabled) {
|
|
return ps5_enabled == enabled && ps4_enabled == enabled;
|
|
}
|
|
|
|
static bool apply_kstuff_enabled_state(bool enabled, bool notify_user,
|
|
bool *fully_applied_out);
|
|
|
|
static void mark_restore_needed(void) {
|
|
g_kstuff.restore_on_empty = true;
|
|
g_kstuff.restore_retry_us = 0;
|
|
}
|
|
|
|
static bool tracked_game_requires_restore(void) {
|
|
return g_kstuff.restore_on_empty || g_kstuff.game.focus_override_active;
|
|
}
|
|
|
|
static void apply_kstuff_config_reload(void) {
|
|
if (!runtime_config()->kstuff_game_auto_toggle) {
|
|
sm_kstuff_game_shutdown();
|
|
return;
|
|
}
|
|
|
|
if (!g_kstuff.game.active)
|
|
return;
|
|
|
|
if (is_kstuff_pause_disabled_for_title(g_kstuff.game.title_id)) {
|
|
bool restore_needed = tracked_game_requires_restore();
|
|
log_debug(" [KSTUFF] cleared tracked game by config reload: %s",
|
|
g_kstuff.game.title_id);
|
|
memset(&g_kstuff.game, 0, sizeof(g_kstuff.game));
|
|
if (restore_needed) {
|
|
mark_restore_needed();
|
|
restore_kstuff_if_needed("config reload");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (g_kstuff.game.pause_applied)
|
|
return;
|
|
|
|
bool override_applied = false;
|
|
uint32_t delay_seconds =
|
|
get_configured_pause_delay_seconds(g_kstuff.game.title_id,
|
|
g_kstuff.game.image_backed,
|
|
&override_applied);
|
|
if (!override_applied && g_kstuff.game.autopause_delay_valid) {
|
|
delay_seconds = g_kstuff.game.autopause_delay_seconds;
|
|
}
|
|
g_kstuff.game.pause_deadline_us =
|
|
g_kstuff.game.launch_time_us == 0
|
|
? 0
|
|
: g_kstuff.game.launch_time_us + (uint64_t)delay_seconds * 1000000ull;
|
|
log_debug(" [KSTUFF] updated tracked game config: %s source=%s pause_delay=%us",
|
|
g_kstuff.game.title_id,
|
|
g_kstuff.game.image_backed ? "image" : "direct",
|
|
delay_seconds);
|
|
}
|
|
|
|
static uint32_t get_pause_delay_seconds_for_title(const char *title_id,
|
|
bool *image_backed_out,
|
|
uint32_t *autopause_delay_out,
|
|
bool *autopause_delay_valid_out) {
|
|
char source_path[MAX_PATH];
|
|
bool image_backed = false;
|
|
(void)resolve_title_pause_source(title_id, source_path, &image_backed);
|
|
|
|
*image_backed_out = image_backed;
|
|
*autopause_delay_out = 0;
|
|
*autopause_delay_valid_out = false;
|
|
|
|
bool override_applied = false;
|
|
uint32_t delay_seconds =
|
|
get_configured_pause_delay_seconds(title_id, image_backed,
|
|
&override_applied);
|
|
uint32_t autopause_delay_seconds = 0;
|
|
if (get_autopause_delay_for_source(source_path, image_backed,
|
|
&autopause_delay_seconds)) {
|
|
*autopause_delay_out = autopause_delay_seconds;
|
|
*autopause_delay_valid_out = true;
|
|
if (!override_applied)
|
|
return autopause_delay_seconds;
|
|
}
|
|
|
|
return delay_seconds;
|
|
}
|
|
|
|
static void restore_kstuff_if_needed(const char *reason) {
|
|
if (g_kstuff.game.active && g_kstuff.game.pause_applied)
|
|
return;
|
|
|
|
if (!g_kstuff.restore_on_empty)
|
|
return;
|
|
|
|
uint64_t now_us = monotonic_time_us();
|
|
if (now_us != 0 && g_kstuff.restore_retry_us != 0 &&
|
|
now_us < g_kstuff.restore_retry_us) {
|
|
return;
|
|
}
|
|
|
|
if (g_kstuff.game.active && !g_kstuff.game.pause_applied) {
|
|
if (g_kstuff.game.pause_deadline_us == 0 ||
|
|
(now_us != 0 && now_us >= g_kstuff.game.pause_deadline_us)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!sm_kstuff_is_supported()) {
|
|
g_kstuff.restore_retry_us =
|
|
now_us == 0 ? 1 : now_us + GAME_LIFECYCLE_POLL_INTERVAL_US;
|
|
return;
|
|
}
|
|
|
|
bool enabled = sm_kstuff_is_enabled();
|
|
if (!enabled)
|
|
enabled = sm_kstuff_set_enabled(true, true);
|
|
if (enabled) {
|
|
log_debug(" [KSTUFF] auto-resumed (%s)%s",
|
|
reason ? reason : "no reason",
|
|
g_kstuff.game.active ? ", pending delay for another tracked game"
|
|
: "");
|
|
} else {
|
|
log_debug(" [KSTUFF] failed to auto-resume (%s)",
|
|
reason ? reason : "no reason");
|
|
g_kstuff.restore_retry_us =
|
|
now_us == 0 ? 0 : now_us + KSTUFF_RESTORE_RETRY_US;
|
|
return;
|
|
}
|
|
|
|
g_kstuff.restore_on_empty = false;
|
|
g_kstuff.restore_retry_us = 0;
|
|
}
|
|
|
|
static void maybe_apply_kstuff_pause_for_slot(kstuff_game_entry_t *entry) {
|
|
if (!entry->active || entry->pause_applied)
|
|
return;
|
|
|
|
uint64_t now_us = monotonic_time_us();
|
|
if (now_us != 0 && now_us < entry->pause_deadline_us)
|
|
return;
|
|
|
|
if (g_kstuff.current_app_focus_valid && entry->app_id != 0 &&
|
|
g_kstuff.current_app_focus_id != entry->app_id) {
|
|
return;
|
|
}
|
|
|
|
if (!sm_kstuff_is_supported()) {
|
|
log_debug(" [KSTUFF] skipping auto-pause for %s pid=%ld: kstuff not "
|
|
"available",
|
|
entry->title_id, (long)entry->pid);
|
|
memset(entry, 0, sizeof(*entry));
|
|
return;
|
|
}
|
|
|
|
bool was_enabled = sm_kstuff_is_enabled();
|
|
bool fully_disabled = false;
|
|
(void)apply_kstuff_enabled_state(false, was_enabled, &fully_disabled);
|
|
if (!fully_disabled) {
|
|
log_debug(" [KSTUFF] failed to auto-pause for %s pid=%ld",
|
|
entry->title_id, (long)entry->pid);
|
|
return;
|
|
}
|
|
|
|
if (was_enabled) {
|
|
mark_restore_needed();
|
|
log_debug(" [KSTUFF] auto-paused for %s pid=%ld (%s launch)",
|
|
entry->title_id, (long)entry->pid,
|
|
entry->image_backed ? "image" : "direct");
|
|
} else {
|
|
log_debug(" [KSTUFF] pause deadline reached for %s pid=%ld but kstuff "
|
|
"was already disabled", entry->title_id, (long)entry->pid);
|
|
}
|
|
|
|
entry->pause_applied = true;
|
|
}
|
|
|
|
static void handle_kstuff_app_focus_change(uint32_t focused_app_id) {
|
|
g_kstuff.current_app_focus_valid = true;
|
|
g_kstuff.current_app_focus_id = focused_app_id;
|
|
|
|
if (!g_kstuff.game.active || g_kstuff.game.app_id == 0)
|
|
return;
|
|
|
|
if (focused_app_id != g_kstuff.game.app_id) {
|
|
if (!g_kstuff.game.pause_applied || g_kstuff.game.focus_override_active)
|
|
return;
|
|
|
|
bool enabled = sm_kstuff_is_enabled();
|
|
if (!enabled)
|
|
enabled = sm_kstuff_set_enabled(true, false);
|
|
if (!enabled) {
|
|
log_debug(" [KSTUFF] failed to enable on app focus leave "
|
|
"(tracked=0x%08X current=0x%08X)",
|
|
g_kstuff.game.app_id, focused_app_id);
|
|
return;
|
|
}
|
|
|
|
g_kstuff.game.focus_override_active = true;
|
|
log_debug(" [KSTUFF] temporarily enabled on app focus leave "
|
|
"(tracked=0x%08X current=0x%08X)",
|
|
g_kstuff.game.app_id, focused_app_id);
|
|
return;
|
|
}
|
|
|
|
if (!g_kstuff.game.focus_override_active)
|
|
return;
|
|
|
|
bool fully_disabled = false;
|
|
(void)apply_kstuff_enabled_state(false, false, &fully_disabled);
|
|
if (!fully_disabled) {
|
|
log_debug(" [KSTUFF] failed to restore disabled state on app focus return "
|
|
"(app_id=0x%08X)",
|
|
focused_app_id);
|
|
return;
|
|
}
|
|
|
|
g_kstuff.game.focus_override_active = false;
|
|
log_debug(" [KSTUFF] restored disabled state on app focus return "
|
|
"(app_id=0x%08X)",
|
|
focused_app_id);
|
|
}
|
|
|
|
uint64_t sm_kstuff_game_next_wake_us(uint64_t now_us) {
|
|
if (!sm_kstuff_game_feature_enabled())
|
|
return 0;
|
|
|
|
uint64_t next_wake_us = 0;
|
|
if (g_kstuff.game.active && !g_kstuff.game.pause_applied) {
|
|
if (g_kstuff.current_app_focus_valid && g_kstuff.game.app_id != 0 &&
|
|
g_kstuff.current_app_focus_id != g_kstuff.game.app_id) {
|
|
next_wake_us = 0;
|
|
} else if (g_kstuff.game.pause_deadline_us != 0) {
|
|
next_wake_us = g_kstuff.game.pause_deadline_us;
|
|
} else if (now_us == 0) {
|
|
next_wake_us = 1;
|
|
} else {
|
|
next_wake_us = now_us + GAME_LIFECYCLE_POLL_INTERVAL_US;
|
|
}
|
|
}
|
|
|
|
if (g_kstuff.restore_on_empty) {
|
|
uint64_t restore_wake_us = g_kstuff.restore_retry_us;
|
|
if (restore_wake_us == 0) {
|
|
if (g_kstuff.game.active && !g_kstuff.game.pause_applied) {
|
|
if (g_kstuff.game.pause_deadline_us != 0) {
|
|
restore_wake_us = g_kstuff.game.pause_deadline_us;
|
|
} else if (now_us == 0) {
|
|
restore_wake_us = 1;
|
|
} else {
|
|
restore_wake_us = now_us + GAME_LIFECYCLE_POLL_INTERVAL_US;
|
|
}
|
|
} else {
|
|
restore_wake_us = now_us == 0 ? 1 : now_us;
|
|
}
|
|
}
|
|
|
|
if (next_wake_us == 0 || restore_wake_us < next_wake_us)
|
|
next_wake_us = restore_wake_us;
|
|
}
|
|
|
|
return next_wake_us;
|
|
}
|
|
|
|
bool sm_kstuff_is_supported(void) {
|
|
return refresh_kstuff_support_state();
|
|
}
|
|
|
|
bool sm_kstuff_is_enabled(void) {
|
|
return read_kstuff_enabled_state(NULL, NULL);
|
|
}
|
|
|
|
static bool apply_kstuff_enabled_state(bool enabled, bool notify_user,
|
|
bool *fully_applied_out) {
|
|
bool ps5_enabled = false;
|
|
bool ps4_enabled = false;
|
|
bool is_enabled = read_kstuff_enabled_state(&ps5_enabled, &ps4_enabled);
|
|
if (!g_kstuff.supported)
|
|
return false;
|
|
bool fully_applied = kstuff_state_matches(enabled, ps5_enabled, ps4_enabled);
|
|
if (fully_applied) {
|
|
if (fully_applied_out)
|
|
*fully_applied_out = true;
|
|
return is_enabled;
|
|
}
|
|
|
|
if (ps5_enabled != enabled)
|
|
set_kstuff_sysentvec_enabled(g_kstuff.sysentvec_ps5, enabled);
|
|
if (ps4_enabled != enabled)
|
|
set_kstuff_sysentvec_enabled(g_kstuff.sysentvec_ps4, enabled);
|
|
|
|
is_enabled = read_kstuff_enabled_state(&ps5_enabled, &ps4_enabled);
|
|
fully_applied = kstuff_state_matches(enabled, ps5_enabled, ps4_enabled);
|
|
if (fully_applied_out)
|
|
*fully_applied_out = fully_applied;
|
|
|
|
if (fully_applied) {
|
|
log_debug(" [KSTUFF] %s both sysentvecs", enabled ? "enabled" : "disabled");
|
|
if (notify_user) {
|
|
notify_system_info(enabled ? "KStuff active again"
|
|
: "KStuff paused while the game is running");
|
|
}
|
|
} else {
|
|
log_debug(" [KSTUFF] %s request incomplete (ps5=%s ps4=%s)",
|
|
enabled ? "enable" : "disable", ps5_enabled ? "on" : "off",
|
|
ps4_enabled ? "on" : "off");
|
|
}
|
|
|
|
return is_enabled;
|
|
}
|
|
|
|
bool sm_kstuff_set_enabled(bool enabled, bool notify_user) {
|
|
return apply_kstuff_enabled_state(enabled, notify_user, NULL);
|
|
}
|
|
|
|
bool sm_kstuff_game_feature_enabled(void) {
|
|
return runtime_config()->kstuff_game_auto_toggle && g_kstuff.probe_available;
|
|
}
|
|
|
|
void sm_kstuff_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id,
|
|
uint64_t exec_time_us) {
|
|
if (!sm_kstuff_game_feature_enabled())
|
|
return;
|
|
|
|
if (g_kstuff.game.active) {
|
|
if (g_kstuff.game.pid == pid) {
|
|
log_debug(" [KSTUFF] already tracking pid=%ld for %s", (long)pid,
|
|
title_id);
|
|
return;
|
|
}
|
|
|
|
bool restore_needed = tracked_game_requires_restore();
|
|
log_debug(" [KSTUFF] handoff tracked game pid=%ld (%s) -> pid=%ld (%s)",
|
|
(long)g_kstuff.game.pid, g_kstuff.game.title_id, (long)pid,
|
|
title_id);
|
|
memset(&g_kstuff.game, 0, sizeof(g_kstuff.game));
|
|
if (restore_needed) {
|
|
mark_restore_needed();
|
|
restore_kstuff_if_needed("tracked game handoff");
|
|
}
|
|
}
|
|
|
|
if (is_kstuff_pause_disabled_for_title(title_id)) {
|
|
log_debug(" [KSTUFF] auto-pause disabled by config for %s pid=%ld "
|
|
"app_id=0x%08X", title_id, (long)pid, app_id);
|
|
return;
|
|
}
|
|
|
|
bool image_backed = false;
|
|
bool autopause_delay_valid = false;
|
|
uint32_t autopause_delay_seconds = 0;
|
|
uint32_t delay_seconds = get_pause_delay_seconds_for_title(
|
|
title_id, &image_backed, &autopause_delay_seconds,
|
|
&autopause_delay_valid);
|
|
uint64_t now_us = monotonic_time_us();
|
|
uint64_t base_time_us = exec_time_us != 0 ? exec_time_us : now_us;
|
|
|
|
g_kstuff.game.active = true;
|
|
g_kstuff.game.image_backed = image_backed;
|
|
g_kstuff.game.autopause_delay_valid = autopause_delay_valid;
|
|
g_kstuff.game.pause_applied = false;
|
|
g_kstuff.game.focus_override_active = false;
|
|
g_kstuff.game.pid = pid;
|
|
g_kstuff.game.app_id = app_id;
|
|
g_kstuff.game.autopause_delay_seconds = autopause_delay_seconds;
|
|
g_kstuff.game.launch_time_us = base_time_us;
|
|
g_kstuff.game.pause_deadline_us =
|
|
base_time_us == 0 ? 0 : base_time_us + (uint64_t)delay_seconds * 1000000ull;
|
|
(void)strlcpy(g_kstuff.game.title_id, title_id, sizeof(g_kstuff.game.title_id));
|
|
|
|
log_debug(" [KSTUFF] tracking game launch: %s pid=%ld app_id=0x%08X source=%s "
|
|
"pause_delay=%us", title_id, (long)pid, app_id,
|
|
image_backed ? "image" : "direct", delay_seconds);
|
|
}
|
|
|
|
void sm_kstuff_note_app_focus(uint32_t app_id) {
|
|
atomic_store(&g_pending_app_focus_id, app_id);
|
|
atomic_store(&g_pending_app_focus_valid, true);
|
|
}
|
|
|
|
void sm_kstuff_game_on_exit(pid_t pid) {
|
|
if (!g_kstuff.game.active || g_kstuff.game.pid != pid)
|
|
return;
|
|
|
|
bool restore_needed = tracked_game_requires_restore();
|
|
log_debug(" [KSTUFF] game stopped: %s pid=%ld",
|
|
g_kstuff.game.title_id, (long)pid);
|
|
memset(&g_kstuff.game, 0, sizeof(g_kstuff.game));
|
|
if (restore_needed) {
|
|
mark_restore_needed();
|
|
restore_kstuff_if_needed("tracked game exit");
|
|
}
|
|
}
|
|
|
|
void sm_kstuff_game_poll(void) {
|
|
if (atomic_exchange(&g_pending_config_reload, false))
|
|
apply_kstuff_config_reload();
|
|
|
|
if (!sm_kstuff_game_feature_enabled())
|
|
return;
|
|
|
|
if (atomic_exchange(&g_pending_app_focus_valid, false)) {
|
|
handle_kstuff_app_focus_change(atomic_load(&g_pending_app_focus_id));
|
|
}
|
|
|
|
restore_kstuff_if_needed("pending auto-resume");
|
|
maybe_apply_kstuff_pause_for_slot(&g_kstuff.game);
|
|
}
|
|
|
|
void sm_kstuff_game_shutdown(void) {
|
|
bool restore_needed = tracked_game_requires_restore();
|
|
memset(&g_kstuff.game, 0, sizeof(g_kstuff.game));
|
|
if (restore_needed) {
|
|
mark_restore_needed();
|
|
restore_kstuff_if_needed("watcher shutdown");
|
|
}
|
|
}
|
|
|
|
void sm_kstuff_on_config_reload(void) {
|
|
atomic_store(&g_pending_config_reload, true);
|
|
}
|
|
|
|
void sm_kstuff_init(void) {
|
|
memset(&g_kstuff, 0, sizeof(g_kstuff));
|
|
atomic_store(&g_pending_app_focus_id, 0);
|
|
atomic_store(&g_pending_app_focus_valid, false);
|
|
atomic_store(&g_pending_config_reload, false);
|
|
|
|
if (!resolve_kstuff_sysentvec_addrs(&g_kstuff.sysentvec_ps5,
|
|
&g_kstuff.sysentvec_ps4)) {
|
|
log_debug(" [KSTUFF] runtime control unavailable on firmware 0x%08X",
|
|
kernel_get_fw_version());
|
|
return;
|
|
}
|
|
g_kstuff.probe_available = true;
|
|
|
|
if (!refresh_kstuff_support_state()) {
|
|
log_debug(" [KSTUFF] runtime control probe ready; kstuff not present at "
|
|
"startup");
|
|
return;
|
|
}
|
|
|
|
log_debug(" [KSTUFF] runtime control ready (image_delay=%u "
|
|
"direct_delay=%u)",
|
|
runtime_config()->kstuff_pause_delay_image_seconds,
|
|
runtime_config()->kstuff_pause_delay_direct_seconds);
|
|
}
|
|
|
|
void sm_kstuff_shutdown(void) {
|
|
sm_kstuff_game_shutdown();
|
|
atomic_store(&g_pending_app_focus_id, 0);
|
|
atomic_store(&g_pending_app_focus_valid, false);
|
|
atomic_store(&g_pending_config_reload, false);
|
|
memset(&g_kstuff, 0, sizeof(g_kstuff));
|
|
}
|