mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-22 06:32:25 -05:00
467 lines
14 KiB
C
467 lines
14 KiB
C
#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_manual.h"
|
|
#include "sm_runtime.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];
|
|
char manual_source_path[MAX_PATH];
|
|
uint64_t requested_at_us;
|
|
bool has_src_snd0;
|
|
bool manual;
|
|
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;
|
|
}
|
|
|
|
void sm_install_forget_pending_title(const char *title_id) {
|
|
pending_install_entry_t *entry = find_pending_install_entry(title_id);
|
|
if (entry)
|
|
clear_pending_install_entry(entry);
|
|
}
|
|
|
|
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));
|
|
(void)strlcpy(entry->manual_source_path, candidate->manual_source_path,
|
|
sizeof(entry->manual_source_path));
|
|
entry->manual = candidate->manual;
|
|
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);
|
|
if (entry->manual)
|
|
sm_manual_note_installed(entry->manual_source_path, entry->title_id,
|
|
entry->title_name);
|
|
clear_register_attempts(entry->title_id);
|
|
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();
|
|
|
|
struct AppDbTitleList app_db_titles = {0};
|
|
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);
|
|
free_app_db_title_list(&app_db_titles);
|
|
}
|
|
|
|
uint64_t sm_install_next_wake_us(uint64_t now_us) {
|
|
if (runtime_sleep_mode_active())
|
|
return 0;
|
|
|
|
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) {
|
|
if (runtime_sleep_mode_active())
|
|
return;
|
|
|
|
poll_pending_installs();
|
|
}
|
|
|
|
void sm_install_service_pending(void) {
|
|
if (runtime_sleep_mode_active())
|
|
return;
|
|
|
|
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) {
|
|
if (runtime_sleep_mode_active())
|
|
return true;
|
|
|
|
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);
|
|
}
|
|
}
|