From cbd0fce4f92e991f4c8f0fa6742c84834b2c27fd Mon Sep 17 00:00:00 2001 From: Roman Tarasov Date: Sun, 10 May 2026 18:24:29 +0300 Subject: [PATCH] Fix snd0Info paths --- include/sm_appdb.h | 4 + src/main.c | 2 + src/sm_appdb.c | 191 ++++++++++++++++++++++++++++++++++++---- src/sm_game_lifecycle.c | 45 +++++++++- 4 files changed, 219 insertions(+), 23 deletions(-) diff --git a/include/sm_appdb.h b/include/sm_appdb.h index bc73425..aaf80ad 100644 --- a/include/sm_appdb.h +++ b/include/sm_appdb.h @@ -7,8 +7,12 @@ struct AppDbTitleList; // Close the cached app.db handle and release related resources. void shutdown_app_db(void); +// Normalize existing snd0info rows during process startup. +bool app_db_run_startup_maintenance(void); // Update snd0 metadata for a registered title. int update_snd0info(const char *title_id); +// Normalize an existing snd0Info path for one title. +int normalize_snd0info_for_title(const char *title_id); // Check whether a title ID exists in a cached app.db title list. bool app_db_title_list_contains(const struct AppDbTitleList *list, const char *title_id); diff --git a/src/main.c b/src/main.c index 122d37e..248eab3 100644 --- a/src/main.c +++ b/src/main.c @@ -500,6 +500,8 @@ int main(void) { cleanup_staged_mount_links(); log_debug("[STARTUP] cleanup_duplicate_title_mounts begin"); cleanup_duplicate_title_mounts(); + if (!app_db_run_startup_maintenance()) + log_debug(" [DB] startup snd0info maintenance unavailable"); log_debug("[STARTUP] scanner startup sync begin"); if (!sm_scanner_run_startup_sync()) { log_debug("[STARTUP] scanner startup sync aborted"); diff --git a/src/sm_appdb.c b/src/sm_appdb.c index 0066866..06966cd 100644 --- a/src/sm_appdb.c +++ b/src/sm_appdb.c @@ -11,15 +11,23 @@ static sqlite3 *g_app_db; static sqlite3_stmt *g_app_db_stmt_update_snd0; +static sqlite3_stmt *g_app_db_stmt_normalize_snd0; static struct AppDbTitleList g_app_db_title_cache; static bool g_app_db_title_cache_ready = false; static time_t g_app_db_title_cache_mtime = 0; +#define APP_DB_STARTUP_MAINTENANCE_RETRIES 3 +#define APP_DB_STARTUP_MAINTENANCE_BUSY_TIMEOUT_MS 250 + static void close_app_db(void) { if (g_app_db_stmt_update_snd0) { sqlite3_finalize(g_app_db_stmt_update_snd0); g_app_db_stmt_update_snd0 = NULL; } + if (g_app_db_stmt_normalize_snd0) { + sqlite3_finalize(g_app_db_stmt_normalize_snd0); + g_app_db_stmt_normalize_snd0 = NULL; + } if (g_app_db) { sqlite3_close(g_app_db); g_app_db = NULL; @@ -89,6 +97,111 @@ static int app_db_prepare_with_retry(const char *sql, sqlite3_stmt **stmt_out, return -1; } +static bool app_db_exec_with_retry(const char *sql, int max_attempts, + const char *label, int *changes_out) { + if (changes_out) + *changes_out = 0; + + for (int attempt = 0; attempt < max_attempts; attempt++) { + if (!ensure_app_db_open()) + return false; + + char *err_msg = NULL; + int rc = sqlite3_exec(g_app_db, sql, NULL, NULL, &err_msg); + if (rc == SQLITE_OK) { + if (changes_out) + *changes_out = sqlite3_changes(g_app_db); + sqlite3_free(err_msg); + return true; + } + + if (app_db_wait_retry(rc, attempt, max_attempts, false)) { + sqlite3_free(err_msg); + continue; + } + + if (err_msg) { + log_debug(" [DB] exec failed for %s: rc=%d err=%s", label, rc, err_msg); + } else { + log_debug(" [DB] exec failed for %s: rc=%d err=%s", label, rc, + sqlite3_errmsg(g_app_db)); + } + sqlite3_free(err_msg); + + close_app_db(); + return false; + } + + close_app_db(); + return false; +} + +static int app_db_query_int(const char *sql, const char *label) { + sqlite3_stmt *stmt = NULL; + int prep_rc = app_db_prepare_with_retry(sql, &stmt, + APP_DB_STARTUP_MAINTENANCE_RETRIES, + label); + if (prep_rc != SQLITE_OK) + return -1; + + int result = -1; + for (int attempt = 0; attempt < APP_DB_QUERY_BUSY_RETRIES; attempt++) { + int rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + result = sqlite3_column_int(stmt, 0); + break; + } + if (app_db_wait_retry(rc, attempt, APP_DB_QUERY_BUSY_RETRIES, false)) + continue; + + log_debug(" [DB] query failed for %s: rc=%d err=%s", label, rc, + sqlite3_errmsg(g_app_db)); + break; + } + + sqlite3_finalize(stmt); + return result; +} + +bool app_db_run_startup_maintenance(void) { + if (!ensure_app_db_open()) + return false; + (void)sqlite3_busy_timeout(g_app_db, + APP_DB_STARTUP_MAINTENANCE_BUSY_TIMEOUT_MS); + + const char *backfill_count_sql = + "SELECT COUNT(*) FROM tbl_contentinfo " + "WHERE instr(snd0Info, '/user/app/') > 0 " + "OR instr(snd0Info, '/sce_sys') > 0;"; + int pending_backfill = + app_db_query_int(backfill_count_sql, "snd0info normalize backfill check"); + if (pending_backfill < 0) { + close_app_db(); + return false; + } + + int changes = 0; + if (pending_backfill > 0) { + const char *backfill_sql = + "UPDATE tbl_contentinfo " + "SET snd0Info = replace(replace(snd0Info, '/user/app/', " + "'/user/appmeta/'), '/sce_sys', '') " + "WHERE instr(snd0Info, '/user/app/') > 0 " + "OR instr(snd0Info, '/sce_sys') > 0;"; + + if (!app_db_exec_with_retry(backfill_sql, + APP_DB_STARTUP_MAINTENANCE_RETRIES, + "snd0info startup normalize", &changes)) + return false; + } + + log_debug(" [DB] snd0info startup maintenance done rows=%d pending=%d", + changes, pending_backfill); + + close_app_db(); + return true; +} + int update_snd0info(const char *title_id) { if (!g_app_db_stmt_update_snd0) { const char *sql = @@ -107,10 +220,8 @@ int update_snd0info(const char *title_id) { sqlite3_clear_bindings(g_app_db_stmt_update_snd0); if (sqlite3_bind_text(g_app_db_stmt_update_snd0, 1, title_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { - if (g_app_db) { - log_debug(" [DB] bind failed for snd0info update: %s", - sqlite3_errmsg(g_app_db)); - } + log_debug(" [DB] bind failed for snd0info update: %s", + sqlite3_errmsg(g_app_db)); close_app_db(); return -1; } @@ -125,10 +236,55 @@ int update_snd0info(const char *title_id) { if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false)) continue; - if (g_app_db) { - log_debug(" [DB] step failed for snd0info update: rc=%d err=%s", rc, + log_debug(" [DB] step failed for snd0info update: rc=%d err=%s", rc, + sqlite3_errmsg(g_app_db)); + close_app_db(); + return -1; + } + + close_app_db(); + return -1; +} + +int normalize_snd0info_for_title(const char *title_id) { + if (!g_app_db_stmt_normalize_snd0) { + const char *sql = + "UPDATE tbl_contentinfo " + "SET snd0Info = replace(replace(snd0Info, '/user/app/', " + "'/user/appmeta/'), '/sce_sys', '') " + "WHERE titleId = ?1 " + "AND (instr(snd0Info, '/user/app/') > 0 " + "OR instr(snd0Info, '/sce_sys') > 0);"; + int prep_rc = app_db_prepare_with_retry(sql, &g_app_db_stmt_normalize_snd0, + APP_DB_PREPARE_BUSY_RETRIES, + "snd0info normalize"); + if (prep_rc != SQLITE_OK) + return -1; + } + + for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; attempt++) { + sqlite3_reset(g_app_db_stmt_normalize_snd0); + sqlite3_clear_bindings(g_app_db_stmt_normalize_snd0); + if (sqlite3_bind_text(g_app_db_stmt_normalize_snd0, 1, title_id, -1, + SQLITE_TRANSIENT) != SQLITE_OK) { + log_debug(" [DB] bind failed for snd0info normalize: %s", sqlite3_errmsg(g_app_db)); + close_app_db(); + return -1; } + + int rc = sqlite3_step(g_app_db_stmt_normalize_snd0); + if (rc == SQLITE_DONE) { + int changes = sqlite3_changes(g_app_db); + close_app_db(); + return changes; + } + + if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false)) + continue; + + log_debug(" [DB] step failed for snd0info normalize: rc=%d err=%s", rc, + sqlite3_errmsg(g_app_db)); close_app_db(); return -1; } @@ -176,26 +332,23 @@ static bool load_app_db_title_list(struct AppDbTitleList *list) { "WHERE titleId != '' " "ORDER BY titleId;"; sqlite3_stmt *stmt = NULL; - int prep_rc = app_db_prepare_with_retry(sql, &stmt, APP_DB_PREPARE_BUSY_RETRIES, + int prep_rc = app_db_prepare_with_retry(sql, &stmt, + APP_DB_PREPARE_BUSY_RETRIES, "title list query"); - if (prep_rc != SQLITE_OK) { - close_app_db(); + if (prep_rc != SQLITE_OK) return false; - } int busy_attempts = 0; while (!should_stop_requested()) { int rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { const char *title_id = (const char *)sqlite3_column_text(stmt, 0); - if (title_id && title_id[0] != '\0') { - if (!append_app_db_title(list, title_id)) { - log_debug(" [DB] title list allocation failed"); - sqlite3_finalize(stmt); - close_app_db(); - free_app_db_title_list(list); - return false; - } + if (!append_app_db_title(list, title_id)) { + log_debug(" [DB] title list allocation failed"); + sqlite3_finalize(stmt); + close_app_db(); + free_app_db_title_list(list); + return false; } continue; } @@ -213,7 +366,7 @@ static bool load_app_db_title_list(struct AppDbTitleList *list) { } log_debug(" [DB] title list query failed: rc=%d err=%s", rc, - (g_app_db ? sqlite3_errmsg(g_app_db) : "unknown")); + sqlite3_errmsg(g_app_db)); sqlite3_finalize(stmt); close_app_db(); free_app_db_title_list(list); diff --git a/src/sm_game_lifecycle.c b/src/sm_game_lifecycle.c index 437c159..b75c409 100644 --- a/src/sm_game_lifecycle.c +++ b/src/sm_game_lifecycle.c @@ -6,6 +6,7 @@ #include #include +#include "sm_appdb.h" #include "sm_fakelib.h" #include "sm_game_lifecycle.h" #include "sm_kstuff.h" @@ -38,6 +39,8 @@ 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; +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); @@ -48,6 +51,32 @@ static void publish_active_game_pid(pid_t pid) { sm_scanner_wake(); } +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]) { + 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; + } + 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) { return strncmp(title_id, "PPSA", 4) == 0 || strncmp(title_id, "CUSA", 4) == 0; } @@ -145,7 +174,7 @@ static bool dispatch_game_launch(int kq, pid_t pid, uint64_t exec_time_us, log_debug(" [GAME] started: %s pid=%ld app_id=0x%08X", title_id, (long)pid, app_id); - publish_active_game_pid(pid); + publish_active_game(pid, title_id); sm_kstuff_game_on_exec(pid, title_id, app_id, exec_time_us); sm_fakelib_game_on_exec(pid, title_id); return true; @@ -392,6 +421,8 @@ static void handle_game_exec(int kq, pid_t pid) { } static void handle_game_exit(pid_t pid) { + char title_id[MAX_TITLE_ID] = {0}; + bool had_active_title = consume_active_game_title(pid, title_id); pending_game_launch_t *entry = find_pending_game_launch(pid); if (entry) clear_pending_game_launch(entry); @@ -399,6 +430,12 @@ static void handle_game_exit(pid_t pid) { publish_active_game_pid(0); sm_fakelib_game_on_exit(pid); sm_kstuff_game_on_exit(pid); + if (had_active_title) { + 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", + snd0_updates, title_id); + } } static void restore_suspended_game_if_alive(int kq, pid_t pid) { @@ -467,7 +504,7 @@ static void *game_lifecycle_watcher_main(void *arg) { clear_all_pending_game_launches(); sm_fakelib_game_shutdown(); sm_kstuff_game_shutdown(); - publish_active_game_pid(0); + publish_active_game(0, NULL); sleep_cleanup_done = true; } @@ -527,7 +564,7 @@ static void *game_lifecycle_watcher_main(void *arg) { clear_all_pending_game_launches(); sm_fakelib_game_shutdown(); sm_kstuff_game_shutdown(); - publish_active_game_pid(0); + publish_active_game(0, NULL); close(kq); log_debug(" [GAME] lifecycle watcher stopped"); return NULL; @@ -600,7 +637,7 @@ void stop_game_lifecycle_watcher(void) { (void)pthread_join(g_game_lifecycle_thread, NULL); g_game_lifecycle_thread_started = false; g_game_lifecycle_stop_requested = 0; - publish_active_game_pid(0); + publish_active_game(0, NULL); close_game_lifecycle_wake_pipe(); }