mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-22 06:32:25 -05:00
Add optional AppInstallAll mode with FW 12+ override
This commit is contained in:
1 parent
4e9afa7885
commit
6df8a472e3
10 files changed
+597
-26
No files matched your search
@@ -43,6 +43,7 @@ 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 `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)
|
||||
|
||||
@@ -24,6 +24,12 @@ mount_read_only=1
|
||||
# 0/false/no/off -> default mount options (default)
|
||||
force_mount=0
|
||||
|
||||
# Use batch registration for newly staged titles
|
||||
# 1/true/yes/on -> submit queued installs through sceAppInstUtilAppInstallAll
|
||||
# 0/false/no/off -> register each title directly (default on FW below 12.00)
|
||||
# Forced on automatically on firmware 12.00 and newer
|
||||
app_install_all=0
|
||||
|
||||
# Per-image mount mode override by file name (can be repeated many times):
|
||||
# image_ro=<image_filename>
|
||||
# image_rw=<image_filename>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef SM_INSTALL_QUEUE_H
|
||||
#define SM_INSTALL_QUEUE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct scan_candidate scan_candidate_t;
|
||||
|
||||
// Return whether a title is already waiting for async batch installation.
|
||||
bool is_title_install_pending(const char *title_id);
|
||||
// Return the next pending-install wake deadline in monotonic microseconds.
|
||||
uint64_t sm_install_next_wake_us(uint64_t now_us);
|
||||
// Poll async batch installs for success/timeout completion only.
|
||||
void sm_install_poll_pending(void);
|
||||
// Poll async batch installs for success/timeout completion and auto-submit
|
||||
// queued installs when possible.
|
||||
void sm_install_service_pending(void);
|
||||
// Queue a staged title for the next batch install request.
|
||||
bool sm_install_queue_candidate(const scan_candidate_t *candidate,
|
||||
bool has_src_snd0);
|
||||
// Submit all queued titles when no batch install is currently pending.
|
||||
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);
|
||||
|
||||
#endif
|
||||
@@ -37,6 +37,7 @@ typedef struct {
|
||||
|
||||
// --- SDK Imports ---
|
||||
int sceAppInstUtilInitialize(void);
|
||||
int sceAppInstUtilAppInstallAll(void);
|
||||
int sceAppInstUtilAppInstallTitleDir(const char *title_id,
|
||||
const char *install_path, void *reserved);
|
||||
int sceKernelGetAppInfo(pid_t pid, app_info_t *info);
|
||||
|
||||
@@ -106,6 +106,8 @@ typedef struct runtime_config {
|
||||
bool quiet_mode;
|
||||
bool mount_read_only;
|
||||
bool force_mount;
|
||||
bool app_install_all_enabled;
|
||||
bool app_install_all_forced;
|
||||
bool backport_fakelib_enabled;
|
||||
bool kstuff_game_auto_toggle;
|
||||
bool kstuff_crash_detection_enabled;
|
||||
|
||||
+43
-2
@@ -96,6 +96,8 @@ 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 uint32_t runtime_firmware_major_version(void);
|
||||
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')
|
||||
@@ -149,6 +151,25 @@ static bool parse_ini_line(char *line, char **key_out, char **value_out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t runtime_firmware_major_version(void) {
|
||||
uint32_t fw = kernel_get_fw_version();
|
||||
uint32_t major_bcd = (fw >> 24) & 0xFFu;
|
||||
return ((major_bcd >> 4) & 0xFu) * 10u + (major_bcd & 0xFu);
|
||||
}
|
||||
|
||||
static void apply_firmware_runtime_overrides(runtime_config_state_t *state) {
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
if (runtime_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)];
|
||||
@@ -228,6 +249,8 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
|
||||
state->cfg.quiet_mode = false;
|
||||
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.kstuff_game_auto_toggle = true;
|
||||
state->cfg.kstuff_crash_detection_enabled = false;
|
||||
@@ -249,6 +272,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) {
|
||||
@@ -283,6 +307,8 @@ static void apply_reloadable_runtime_fields(runtime_config_state_t *dst,
|
||||
dst->cfg.quiet_mode = src->cfg.quiet_mode;
|
||||
dst->cfg.mount_read_only = src->cfg.mount_read_only;
|
||||
dst->cfg.force_mount = src->cfg.force_mount;
|
||||
dst->cfg.app_install_all_enabled = src->cfg.app_install_all_enabled;
|
||||
dst->cfg.app_install_all_forced = src->cfg.app_install_all_forced;
|
||||
dst->cfg.backport_fakelib_enabled = src->cfg.backport_fakelib_enabled;
|
||||
dst->cfg.kstuff_game_auto_toggle = src->cfg.kstuff_game_auto_toggle;
|
||||
dst->cfg.kstuff_crash_detection_enabled =
|
||||
@@ -1119,6 +1145,16 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "app_install_all") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.app_install_all_enabled = bval;
|
||||
state->cfg.app_install_all_forced = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "image_ro") == 0 ||
|
||||
strcasecmp(key, "image_rw") == 0) {
|
||||
bool rule_read_only = (strcasecmp(key, "image_ro") == 0);
|
||||
@@ -1331,6 +1367,8 @@ 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++) {
|
||||
if (state->image_mode_rules[k].valid)
|
||||
@@ -1343,7 +1381,8 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
kstuff_delay_rule_count++;
|
||||
}
|
||||
|
||||
log_debug(" [CFG] loaded: debug=%d quiet=%d ro=%d force=%d scan_depth=%u "
|
||||
log_debug(" [CFG] loaded: debug=%d quiet=%d ro=%d force=%d "
|
||||
"app_install_all=%d app_install_all_forced=%d scan_depth=%u "
|
||||
"legacy_recursive_scan_forced=%d backport_fakelib=%d "
|
||||
"kstuff_game_auto_toggle=%d kstuff_crash_detection=%d "
|
||||
"kstuff_pause_delay_image_s=%u kstuff_pause_delay_direct_s=%u "
|
||||
@@ -1353,7 +1392,9 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
"kstuff_no_pause=%d kstuff_delay_rules=%d",
|
||||
state->cfg.debug_enabled ? 1 : 0, state->cfg.quiet_mode ? 1 : 0,
|
||||
state->cfg.mount_read_only ? 1 : 0,
|
||||
state->cfg.force_mount ? 1 : 0, state->cfg.scan_depth,
|
||||
state->cfg.force_mount ? 1 : 0,
|
||||
state->cfg.app_install_all_enabled ? 1 : 0,
|
||||
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.kstuff_game_auto_toggle ? 1 : 0,
|
||||
|
||||
+49
-7
@@ -1,6 +1,8 @@
|
||||
#include "sm_platform.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_runtime.h"
|
||||
#include "sm_install.h"
|
||||
#include "sm_install_queue.h"
|
||||
#include "sm_types.h"
|
||||
#include "sm_game_cache.h"
|
||||
#include "sm_log.h"
|
||||
@@ -85,7 +87,9 @@ static bool copy_sce_sys_to_appmeta(const char *src_sce_sys,
|
||||
// --- Install/Remount Action ---
|
||||
static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
const char *title_name, bool is_remount,
|
||||
bool should_register) {
|
||||
bool should_register,
|
||||
bool use_app_install_all,
|
||||
bool *has_src_snd0_out) {
|
||||
char user_appmeta_dir[MAX_PATH];
|
||||
char user_app_dir[MAX_PATH];
|
||||
char user_sce_sys[MAX_PATH];
|
||||
@@ -212,12 +216,20 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
return true;
|
||||
}
|
||||
|
||||
// REGISTER
|
||||
if (!metadata_restaged) {
|
||||
snprintf(src_snd0, sizeof(src_snd0), "%s/sce_sys/snd0.at9", src_path);
|
||||
has_src_snd0 = path_exists(src_snd0);
|
||||
}
|
||||
|
||||
if (use_app_install_all) {
|
||||
if (has_src_snd0_out)
|
||||
*has_src_snd0_out = has_src_snd0;
|
||||
log_debug(" [REG] Prepared for batch install: %s (%s)", title_name,
|
||||
title_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// REGISTER
|
||||
mark_register_attempted(title_id);
|
||||
int res = sceAppInstUtilAppInstallTitleDir(title_id, APP_BASE "/", 0);
|
||||
sceKernelUsleep(200000);
|
||||
@@ -253,23 +265,50 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
// --- Execution (per discovered candidate) ---
|
||||
void process_scan_candidates(const scan_candidate_t *candidates,
|
||||
int candidate_count) {
|
||||
sm_install_poll_pending();
|
||||
|
||||
for (int i = 0; i < candidate_count; i++) {
|
||||
if (should_stop_requested())
|
||||
return;
|
||||
|
||||
const scan_candidate_t *c = &candidates[i];
|
||||
bool has_src_snd0 = false;
|
||||
bool use_app_install_all =
|
||||
runtime_config()->app_install_all_enabled && !c->in_app_db;
|
||||
if (c->installed) {
|
||||
log_debug(" [ACTION] Remounting: %s", c->title_name);
|
||||
} else {
|
||||
log_debug(" [ACTION] Installing: %s (%s)", c->title_name, c->title_id);
|
||||
notify_system_info("Installing: %s (%s)...", c->title_name,
|
||||
c->title_id);
|
||||
if (!use_app_install_all) {
|
||||
notify_system_info("Installing: %s (%s)...", c->title_name,
|
||||
c->title_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (mount_and_install(c->path, c->title_id, c->title_name, c->installed,
|
||||
!c->in_app_db)) {
|
||||
clear_failed_mount_attempts(c->title_id);
|
||||
cache_game_entry(c->path, c->title_id, c->title_name);
|
||||
!c->in_app_db, use_app_install_all,
|
||||
&has_src_snd0)) {
|
||||
if (use_app_install_all) {
|
||||
if (!sm_install_queue_candidate(c, has_src_snd0)) {
|
||||
log_debug(" [REG] Failed to queue staged install: %s (%s)",
|
||||
c->title_name, c->title_id);
|
||||
if (should_stop_requested())
|
||||
return;
|
||||
|
||||
uint8_t failed_attempts = bump_failed_mount_attempts(c->title_id);
|
||||
if (failed_attempts == MAX_FAILED_MOUNT_ATTEMPTS) {
|
||||
log_debug(" [RETRY] limit reached (%u/%u): %s (%s)",
|
||||
(unsigned)failed_attempts,
|
||||
(unsigned)MAX_FAILED_MOUNT_ATTEMPTS, c->title_name,
|
||||
c->title_id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
clear_failed_mount_attempts(c->title_id);
|
||||
} else {
|
||||
clear_failed_mount_attempts(c->title_id);
|
||||
cache_game_entry(c->path, c->title_id, c->title_name);
|
||||
}
|
||||
} else {
|
||||
if (should_stop_requested())
|
||||
return;
|
||||
@@ -283,4 +322,7 @@ void process_scan_candidates(const scan_candidate_t *candidates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sm_install_submit_queued())
|
||||
sm_install_note_submit_failure();
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include "sm_install_queue.h"
|
||||
#include "sm_types.h"
|
||||
#include "sm_appdb.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_game_cache.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_time.h"
|
||||
#include "sm_title_state.h"
|
||||
|
||||
#define INSTALL_WAIT_TIMEOUT_US (300ull * 1000000ull)
|
||||
#define INSTALL_POLL_INTERVAL_US (2ull * 1000000ull)
|
||||
#define INSTALL_SUBMIT_RETRY_US (2ull * 1000000ull)
|
||||
|
||||
typedef enum {
|
||||
INSTALL_TRACK_NONE = 0,
|
||||
INSTALL_TRACK_QUEUED,
|
||||
INSTALL_TRACK_SUBMITTED,
|
||||
} install_track_state_t;
|
||||
|
||||
typedef struct {
|
||||
char title_id[MAX_TITLE_ID];
|
||||
char title_name[MAX_TITLE_NAME];
|
||||
char source_path[MAX_PATH];
|
||||
uint64_t requested_at_us;
|
||||
bool has_src_snd0;
|
||||
install_track_state_t state;
|
||||
} pending_install_entry_t;
|
||||
|
||||
static pending_install_entry_t g_pending_installs[MAX_PENDING];
|
||||
static uint64_t g_pending_install_poll_due_us = 0;
|
||||
static uint64_t g_queued_install_submit_due_us = 0;
|
||||
static int g_tracked_install_count = 0;
|
||||
static int g_submitted_install_count = 0;
|
||||
static bool g_queued_install_batch_announced = false;
|
||||
static bool g_queued_install_submit_failure_notified = false;
|
||||
|
||||
static int count_queued_installs(void);
|
||||
static uint8_t note_pending_install_failure(const pending_install_entry_t *entry);
|
||||
static void drop_queued_install_entry(pending_install_entry_t *entry);
|
||||
|
||||
static bool install_queue_active(void) {
|
||||
return runtime_config()->app_install_all_enabled || 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')
|
||||
return NULL;
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
if (g_pending_installs[i].state == INSTALL_TRACK_NONE)
|
||||
continue;
|
||||
if (strcmp(g_pending_installs[i].title_id, title_id) == 0)
|
||||
return &g_pending_installs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pending_install_entry_t *reserve_pending_install_entry(
|
||||
const char *title_id) {
|
||||
pending_install_entry_t *entry = find_pending_install_entry(title_id);
|
||||
if (entry)
|
||||
return entry;
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
if (g_pending_installs[i].state == INSTALL_TRACK_NONE)
|
||||
return &g_pending_installs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uint64_t next_pending_install_timeout_us(void) {
|
||||
if (g_submitted_install_count <= 0)
|
||||
return 0;
|
||||
|
||||
uint64_t next_deadline = 0;
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
const pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_SUBMITTED || entry->requested_at_us == 0)
|
||||
continue;
|
||||
|
||||
uint64_t deadline_us = entry->requested_at_us + INSTALL_WAIT_TIMEOUT_US;
|
||||
if (next_deadline == 0 || deadline_us < next_deadline)
|
||||
next_deadline = deadline_us;
|
||||
}
|
||||
|
||||
return next_deadline;
|
||||
}
|
||||
|
||||
static void schedule_pending_install_poll(uint64_t now_us) {
|
||||
if (g_submitted_install_count <= 0) {
|
||||
g_pending_install_poll_due_us = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
g_pending_install_poll_due_us =
|
||||
now_us == 0 ? 1 : now_us + INSTALL_POLL_INTERVAL_US;
|
||||
}
|
||||
|
||||
static void schedule_queued_install_submit(uint64_t now_us) {
|
||||
if (count_queued_installs() <= 0 || g_submitted_install_count > 0) {
|
||||
g_queued_install_submit_due_us = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
g_queued_install_submit_due_us = now_us == 0 ? 1 : now_us;
|
||||
}
|
||||
|
||||
static void schedule_queued_install_submit_retry(uint64_t now_us) {
|
||||
if (count_queued_installs() <= 0 || g_submitted_install_count > 0) {
|
||||
g_queued_install_submit_due_us = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
g_queued_install_submit_due_us =
|
||||
now_us == 0 ? 1 : now_us + INSTALL_SUBMIT_RETRY_US;
|
||||
}
|
||||
|
||||
static void clear_pending_install_entry(pending_install_entry_t *entry) {
|
||||
if (!entry)
|
||||
return;
|
||||
if (entry->state == INSTALL_TRACK_SUBMITTED && g_submitted_install_count > 0)
|
||||
g_submitted_install_count--;
|
||||
if (entry->state != INSTALL_TRACK_NONE && g_tracked_install_count > 0)
|
||||
g_tracked_install_count--;
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
if (g_submitted_install_count <= 0)
|
||||
g_pending_install_poll_due_us = 0;
|
||||
if (g_tracked_install_count <= 0) {
|
||||
g_queued_install_submit_due_us = 0;
|
||||
g_queued_install_batch_announced = false;
|
||||
g_queued_install_submit_failure_notified = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_title_install_pending(const char *title_id) {
|
||||
return find_pending_install_entry(title_id) != NULL;
|
||||
}
|
||||
|
||||
static void append_batch_install_line(char *message, size_t message_size,
|
||||
const char *title_name,
|
||||
const char *title_id, int *shown_count) {
|
||||
if (!message || message_size == 0 || !title_name || !title_id || !shown_count)
|
||||
return;
|
||||
|
||||
size_t used = strlen(message);
|
||||
if (used >= message_size)
|
||||
return;
|
||||
|
||||
int written = snprintf(message + used, message_size - used, "\n%s (%s)",
|
||||
title_name, title_id);
|
||||
if (written < 0 || (size_t)written >= message_size - used)
|
||||
return;
|
||||
|
||||
(*shown_count)++;
|
||||
}
|
||||
|
||||
bool sm_install_queue_candidate(const scan_candidate_t *candidate,
|
||||
bool has_src_snd0) {
|
||||
if (!candidate || candidate->title_id[0] == '\0' ||
|
||||
candidate->title_name[0] == '\0' || candidate->path[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!runtime_config()->app_install_all_enabled)
|
||||
return false;
|
||||
|
||||
bool was_queue_empty = (count_queued_installs() <= 0);
|
||||
pending_install_entry_t *entry =
|
||||
reserve_pending_install_entry(candidate->title_id);
|
||||
if (!entry) {
|
||||
log_debug(" [REG] install queue full, cannot queue %s (%s)",
|
||||
candidate->title_name, candidate->title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
install_track_state_t previous_state = entry->state;
|
||||
if (previous_state == INSTALL_TRACK_SUBMITTED)
|
||||
return true;
|
||||
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
(void)strlcpy(entry->title_id, candidate->title_id, sizeof(entry->title_id));
|
||||
(void)strlcpy(entry->title_name, candidate->title_name,
|
||||
sizeof(entry->title_name));
|
||||
(void)strlcpy(entry->source_path, candidate->path, sizeof(entry->source_path));
|
||||
entry->has_src_snd0 = has_src_snd0;
|
||||
entry->state = INSTALL_TRACK_QUEUED;
|
||||
if (previous_state == INSTALL_TRACK_NONE)
|
||||
g_tracked_install_count++;
|
||||
if (was_queue_empty) {
|
||||
g_queued_install_batch_announced = false;
|
||||
g_queued_install_submit_failure_notified = false;
|
||||
}
|
||||
schedule_queued_install_submit(monotonic_time_us());
|
||||
return true;
|
||||
}
|
||||
|
||||
static void finalize_pending_install_success(pending_install_entry_t *entry) {
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
log_debug(" [REG] Installed: %s (%s)", entry->title_name, entry->title_id);
|
||||
notify_game_installed_rich(entry->title_id);
|
||||
if (entry->has_src_snd0) {
|
||||
int snd0_updates = update_snd0info(entry->title_id);
|
||||
if (snd0_updates >= 0)
|
||||
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
|
||||
}
|
||||
cache_game_entry(entry->source_path, entry->title_id, entry->title_name);
|
||||
clear_failed_mount_attempts(entry->title_id);
|
||||
clear_pending_install_entry(entry);
|
||||
}
|
||||
|
||||
static void finalize_pending_install_timeout(pending_install_entry_t *entry) {
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
log_debug(" [REG] Install timeout after 5 minutes: %s (%s)",
|
||||
entry->title_name, entry->title_id);
|
||||
notify_system("Install failed: %s (%s)\nTimed out after 5 minutes",
|
||||
entry->title_name, entry->title_id);
|
||||
(void)note_pending_install_failure(entry);
|
||||
clear_pending_install_entry(entry);
|
||||
}
|
||||
|
||||
static void poll_pending_installs(void) {
|
||||
if (g_submitted_install_count <= 0)
|
||||
return;
|
||||
|
||||
invalidate_app_db_title_cache();
|
||||
|
||||
const struct AppDbTitleList *app_db_titles = NULL;
|
||||
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
|
||||
uint64_t now_us = monotonic_time_us();
|
||||
|
||||
if (!app_db_titles_ready)
|
||||
log_debug(" [DB] app.db title list unavailable while polling installs");
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_SUBMITTED)
|
||||
continue;
|
||||
|
||||
if (app_db_titles_ready &&
|
||||
app_db_title_list_contains(app_db_titles, entry->title_id)) {
|
||||
finalize_pending_install_success(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now_us != 0 && entry->requested_at_us != 0 &&
|
||||
now_us >= entry->requested_at_us &&
|
||||
now_us - entry->requested_at_us >= INSTALL_WAIT_TIMEOUT_US) {
|
||||
finalize_pending_install_timeout(entry);
|
||||
}
|
||||
}
|
||||
|
||||
schedule_pending_install_poll(now_us);
|
||||
if (g_submitted_install_count == 0)
|
||||
schedule_queued_install_submit(now_us);
|
||||
}
|
||||
|
||||
uint64_t sm_install_next_wake_us(uint64_t now_us) {
|
||||
if (!install_queue_active())
|
||||
return 0;
|
||||
|
||||
if (g_submitted_install_count <= 0)
|
||||
return g_queued_install_submit_due_us;
|
||||
|
||||
uint64_t next_wake_us = next_pending_install_timeout_us();
|
||||
|
||||
if (g_pending_install_poll_due_us != 0 &&
|
||||
(next_wake_us == 0 || g_pending_install_poll_due_us < next_wake_us)) {
|
||||
next_wake_us = g_pending_install_poll_due_us;
|
||||
}
|
||||
|
||||
if (g_queued_install_submit_due_us != 0 &&
|
||||
(next_wake_us == 0 || g_queued_install_submit_due_us < next_wake_us)) {
|
||||
next_wake_us = g_queued_install_submit_due_us;
|
||||
}
|
||||
|
||||
if (next_wake_us == 0)
|
||||
next_wake_us = now_us == 0 ? 1 : now_us + INSTALL_POLL_INTERVAL_US;
|
||||
|
||||
return next_wake_us;
|
||||
}
|
||||
|
||||
void sm_install_poll_pending(void) {
|
||||
poll_pending_installs();
|
||||
}
|
||||
|
||||
void sm_install_service_pending(void) {
|
||||
if (!install_queue_active())
|
||||
return;
|
||||
|
||||
sm_install_poll_pending();
|
||||
if (count_queued_installs() > 0 && !sm_install_submit_queued())
|
||||
sm_install_note_submit_failure();
|
||||
}
|
||||
|
||||
static void log_batch_submit_retry_limit(const pending_install_entry_t *entry) {
|
||||
uint8_t failed_attempts = get_failed_mount_attempts(entry->title_id);
|
||||
if (failed_attempts == MAX_FAILED_MOUNT_ATTEMPTS) {
|
||||
log_debug(" [RETRY] limit reached (%u/%u): %s (%s)",
|
||||
(unsigned)failed_attempts,
|
||||
(unsigned)MAX_FAILED_MOUNT_ATTEMPTS, entry->title_name,
|
||||
entry->title_id);
|
||||
}
|
||||
}
|
||||
|
||||
static int count_queued_installs(void) {
|
||||
int queued_count = g_tracked_install_count - g_submitted_install_count;
|
||||
return queued_count > 0 ? queued_count : 0;
|
||||
}
|
||||
|
||||
static void log_queued_install_batch(void) {
|
||||
int queued_count = count_queued_installs();
|
||||
if (queued_count <= 0)
|
||||
return;
|
||||
|
||||
log_debug(" [REG] Batch install request contains %d title(s):", queued_count);
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
const pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_QUEUED)
|
||||
continue;
|
||||
log_debug(" - %s (%s)", entry->title_name, entry->title_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void notify_queued_install_batch(void) {
|
||||
int queued_count = count_queued_installs();
|
||||
if (queued_count <= 0 || g_queued_install_batch_announced)
|
||||
return;
|
||||
|
||||
char message[3075];
|
||||
int shown_count = 0;
|
||||
snprintf(message, sizeof(message), "Batch install queued (%d):", queued_count);
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
const pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_QUEUED)
|
||||
continue;
|
||||
append_batch_install_line(message, sizeof(message), entry->title_name,
|
||||
entry->title_id, &shown_count);
|
||||
}
|
||||
|
||||
if (shown_count < queued_count) {
|
||||
size_t used = strlen(message);
|
||||
if (used < sizeof(message)) {
|
||||
(void)snprintf(message + used, sizeof(message) - used, "\n... and %d more",
|
||||
queued_count - shown_count);
|
||||
}
|
||||
}
|
||||
|
||||
notify_system_info("%s", message);
|
||||
g_queued_install_batch_announced = true;
|
||||
}
|
||||
|
||||
static uint8_t note_pending_install_failure(const pending_install_entry_t *entry) {
|
||||
if (!entry)
|
||||
return 0;
|
||||
|
||||
uint8_t failed_attempts = bump_failed_mount_attempts(entry->title_id);
|
||||
log_batch_submit_retry_limit(entry);
|
||||
return failed_attempts;
|
||||
}
|
||||
|
||||
static void drop_queued_install_entry(pending_install_entry_t *entry) {
|
||||
if (!entry || entry->state != INSTALL_TRACK_QUEUED)
|
||||
return;
|
||||
|
||||
log_debug(" [REG] Dropping queued install after retry limit: %s (%s)",
|
||||
entry->title_name, entry->title_id);
|
||||
clear_pending_install_entry(entry);
|
||||
}
|
||||
|
||||
bool sm_install_submit_queued(void) {
|
||||
int queued_count = count_queued_installs();
|
||||
if (queued_count <= 0)
|
||||
return true;
|
||||
|
||||
if (g_submitted_install_count > 0)
|
||||
return true;
|
||||
|
||||
uint64_t now_us = monotonic_time_us();
|
||||
if (g_queued_install_submit_due_us != 0 && now_us != 0 &&
|
||||
now_us < g_queued_install_submit_due_us) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log_queued_install_batch();
|
||||
notify_queued_install_batch();
|
||||
|
||||
int res = sceAppInstUtilAppInstallAll();
|
||||
if (res != 0) {
|
||||
log_debug(" [REG] Batch install request failed: 0x%x", res);
|
||||
if (!g_queued_install_submit_failure_notified) {
|
||||
notify_system("Batch install failed for %d app(s).\ncode=0x%08X",
|
||||
queued_count, (uint32_t)res);
|
||||
g_queued_install_submit_failure_notified = true;
|
||||
}
|
||||
schedule_queued_install_submit_retry(now_us);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_queued_install_submit_due_us = 0;
|
||||
g_queued_install_submit_failure_notified = false;
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_QUEUED)
|
||||
continue;
|
||||
entry->state = INSTALL_TRACK_SUBMITTED;
|
||||
entry->requested_at_us = now_us;
|
||||
g_submitted_install_count++;
|
||||
clear_failed_mount_attempts(entry->title_id);
|
||||
}
|
||||
|
||||
invalidate_app_db_title_cache();
|
||||
schedule_pending_install_poll(now_us);
|
||||
return true;
|
||||
}
|
||||
|
||||
void sm_install_note_submit_failure(void) {
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
if (entry->state != INSTALL_TRACK_QUEUED)
|
||||
continue;
|
||||
|
||||
if (note_pending_install_failure(entry) >= MAX_FAILED_MOUNT_ATTEMPTS)
|
||||
drop_queued_install_entry(entry);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "sm_title_state.h"
|
||||
#include "sm_image_cache.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_install_queue.h"
|
||||
|
||||
typedef struct {
|
||||
char discovered_param_roots[MAX_PENDING][MAX_PATH];
|
||||
@@ -193,6 +194,9 @@ static existing_directory_result_t handle_existing_directory_candidate(
|
||||
return EXISTING_DIRECTORY_PREFER_CACHED;
|
||||
}
|
||||
|
||||
if (!in_app_db && is_title_install_pending(info->title_id))
|
||||
return EXISTING_DIRECTORY_HANDLED;
|
||||
|
||||
if (!in_app_db && was_register_attempted(info->title_id)) {
|
||||
return EXISTING_DIRECTORY_HANDLED;
|
||||
}
|
||||
|
||||
+29
-17
@@ -10,6 +10,7 @@
|
||||
#include "sm_game_lifecycle.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_install.h"
|
||||
#include "sm_install_queue.h"
|
||||
#include "sm_kstuff.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
@@ -807,22 +808,20 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason,
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
|
||||
if (candidate_count > 0) {
|
||||
if (startup_sync) {
|
||||
int new_games = 0;
|
||||
for (int i = 0; i < candidate_count; i++) {
|
||||
if (!candidates[i].installed)
|
||||
new_games++;
|
||||
}
|
||||
if (new_games > 0)
|
||||
notify_system_info("Found %d new games. Executing...", new_games);
|
||||
if (candidate_count > 0 && startup_sync) {
|
||||
int new_games = 0;
|
||||
for (int i = 0; i < candidate_count; i++) {
|
||||
if (!candidates[i].installed)
|
||||
new_games++;
|
||||
}
|
||||
|
||||
process_scan_candidates(candidates, candidate_count);
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
if (new_games > 0)
|
||||
notify_system_info("Found %d new games. Executing...", new_games);
|
||||
}
|
||||
|
||||
process_scan_candidates(candidates, candidate_count);
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
|
||||
mount_backport_overlays(&unstable_found);
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
@@ -858,8 +857,7 @@ static bool run_targeted_scan_cycle(int scan_root_index,
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
|
||||
if (candidate_count > 0)
|
||||
process_scan_candidates(candidates, candidate_count);
|
||||
process_scan_candidates(candidates, candidate_count);
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
|
||||
@@ -911,8 +909,15 @@ static int find_due_dirty_scan_root(uint64_t now_us) {
|
||||
return selected_root;
|
||||
}
|
||||
|
||||
static uint64_t compute_next_scan_deadline_us(uint64_t full_resync_due_us) {
|
||||
static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
|
||||
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);
|
||||
|
||||
if (install_wake_us != 0 &&
|
||||
(next_deadline == 0 || install_wake_us < next_deadline)) {
|
||||
next_deadline = install_wake_us;
|
||||
}
|
||||
|
||||
if (g_scanner_config_reload_pending &&
|
||||
(next_deadline == 0 ||
|
||||
@@ -1192,6 +1197,12 @@ void sm_scanner_run_loop(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t install_wake_us = sm_install_next_wake_us(now_us);
|
||||
if (install_wake_us != 0 && now_us >= install_wake_us) {
|
||||
sm_install_service_pending();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now_us >= next_full_resync_us) {
|
||||
bool unstable_found = false;
|
||||
if (!run_full_scan_cycle(false, NULL, &unstable_found))
|
||||
@@ -1266,7 +1277,8 @@ void sm_scanner_run_loop(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t deadline_us = compute_next_scan_deadline_us(next_full_resync_us);
|
||||
uint64_t deadline_us =
|
||||
compute_next_scan_deadline_us(now_us, next_full_resync_us);
|
||||
struct timespec timeout;
|
||||
build_wait_timeout(&timeout, now_us, deadline_us);
|
||||
|
||||
|
||||
Reference in new issue
Block a user