mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-06 14:00:36 +02:00
148 lines
5.2 KiB
C
148 lines
5.2 KiB
C
/* appdb.c - display names from /system_data/priv/mms/app.db (read-only).
|
|
* Method follows the proven on-console pattern (SonicLoader et al.): SQLite
|
|
* query, never raw byte-scanning (an earlier revision byte-scanned app.db
|
|
* and returned wrong names: the file reads fine, the parse was garbage).
|
|
* Tiers, first hit wins:
|
|
* 1. tbl_appbrowse.titleName (when the firmware carries it)
|
|
* 2. tbl_appinfo val for key TITLE / TITLE_xx (localized display titles)
|
|
* 3. /user/appmeta/<id>/param.json title fields (ShadowMountPlus pattern)
|
|
* Every tier is schema-guarded and fail-soft: missing tables, missing keys,
|
|
* unreadable files, and locked DBs all fall through to the next source.
|
|
* Host-testable via appdb_title_from() with a synthetic DB path. */
|
|
#include "appdb.h"
|
|
#include "jsonlite.h"
|
|
#include "log.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include "sqlite3.h"
|
|
|
|
#define APPDB_PATH "/system_data/priv/mms/app.db"
|
|
#define APPMETA_BASE "/user/appmeta"
|
|
|
|
static int valid_tid(const char *t){
|
|
if(!t) return 0;
|
|
size_t n = strlen(t);
|
|
if(n != 9) return 0;
|
|
for(size_t i = 0; i < n; i++){
|
|
char c = t[i];
|
|
if(!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int table_exists(sqlite3 *db, const char *name){
|
|
sqlite3_stmt *st = NULL;
|
|
int found = 0;
|
|
if(sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?1 LIMIT 1;",
|
|
-1, &st, NULL) != SQLITE_OK)
|
|
return 0;
|
|
sqlite3_bind_text(st, 1, name, -1, SQLITE_TRANSIENT);
|
|
if(sqlite3_step(st) == SQLITE_ROW) found = 1;
|
|
sqlite3_finalize(st);
|
|
return found;
|
|
}
|
|
|
|
/* Copy first non-empty TEXT result of a one-row query. 0 on hit. */
|
|
static int query_name(sqlite3 *db, const char *sql, const char *titleId,
|
|
char *out, size_t cap){
|
|
sqlite3_stmt *st = NULL;
|
|
int rc = -1;
|
|
if(sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_text(st, 1, titleId, -1, SQLITE_TRANSIENT);
|
|
if(sqlite3_step(st) == SQLITE_ROW){
|
|
const unsigned char *v = sqlite3_column_text(st, 0);
|
|
if(v && v[0]){
|
|
strncpy(out, (const char *)v, cap - 1);
|
|
out[cap - 1] = 0;
|
|
if(out[0] && strlen(out) > 1) rc = 0;
|
|
}
|
|
}
|
|
sqlite3_finalize(st);
|
|
return rc;
|
|
}
|
|
|
|
/* param.json title fields: small single read, jsonlite parse. Best-effort. */
|
|
static int paramjson_title(const char *appmeta_base, const char *titleId,
|
|
char *out, size_t cap){
|
|
char path[256];
|
|
int n = snprintf(path, sizeof path, "%s/%s/param.json",
|
|
appmeta_base ? appmeta_base : APPMETA_BASE, titleId);
|
|
if(n <= 0 || (size_t)n >= sizeof path) return -1;
|
|
int fd = open(path, O_RDONLY);
|
|
if(fd < 0) return -1;
|
|
char buf[4096];
|
|
ssize_t r = read(fd, buf, sizeof buf - 1);
|
|
close(fd);
|
|
if(r <= 0) return -1;
|
|
buf[r] = 0;
|
|
jl_val_t *root = jl_parse(buf, (size_t)r);
|
|
if(!root) return -1;
|
|
static const char *keys[] = { "titleName", "title", "name", NULL };
|
|
int rc = -1;
|
|
for(int i = 0; keys[i]; i++){
|
|
const jl_val_t *v = jl_obj_get(root, keys[i]);
|
|
if(v && v->type == JL_STRING && v->str && strlen(v->str) > 1){
|
|
strncpy(out, v->str, cap - 1);
|
|
out[cap - 1] = 0;
|
|
rc = 0;
|
|
break;
|
|
}
|
|
}
|
|
jl_free(root);
|
|
return rc;
|
|
}
|
|
|
|
int appdb_title_from(const char *dbpath, const char *appmeta_base,
|
|
const char *titleId, char *out, size_t cap){
|
|
if(!out || cap == 0) return -1;
|
|
out[0] = 0;
|
|
if(!valid_tid(titleId)) return -1;
|
|
if(!dbpath) dbpath = APPDB_PATH;
|
|
|
|
sqlite3 *db = NULL;
|
|
int orc = sqlite3_open_v2(dbpath, &db,
|
|
SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, NULL);
|
|
if(orc != SQLITE_OK){
|
|
if(db) sqlite3_close(db);
|
|
return -1;
|
|
}
|
|
sqlite3_busy_timeout(db, 2000);
|
|
|
|
/* Tier 1: friendly browse name. */
|
|
if(table_exists(db, "tbl_appbrowse")){
|
|
if(query_name(db, "SELECT titleName FROM tbl_appbrowse WHERE titleId=?1 "
|
|
"AND titleName IS NOT NULL AND titleName<>'' LIMIT 1;",
|
|
titleId, out, cap) == 0){
|
|
log_msg("name: %s via appdb-browse", out);
|
|
sqlite3_close(db);
|
|
return 0;
|
|
}
|
|
}
|
|
/* Tier 2: localized TITLE keys; ORDER BY key prefers bare TITLE
|
|
* (system language) over TITLE_00..NN variants. */
|
|
if(table_exists(db, "tbl_appinfo")){
|
|
if(query_name(db, "SELECT val FROM tbl_appinfo WHERE titleId=?1 "
|
|
"AND (key='TITLE' OR key LIKE 'TITLE\\_%' ESCAPE '\\') "
|
|
"AND val IS NOT NULL AND val<>'' ORDER BY key LIMIT 1;",
|
|
titleId, out, cap) == 0){
|
|
log_msg("name: %s via appdb-info", out);
|
|
sqlite3_close(db);
|
|
return 0;
|
|
}
|
|
}
|
|
sqlite3_close(db);
|
|
|
|
/* Tier 3: staged appmeta descriptor (no DB needed). */
|
|
if(paramjson_title(appmeta_base, titleId, out, cap) == 0){
|
|
log_msg("name: %s via paramjson", out);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int appdb_title(const char *titleId, char *out, size_t cap){
|
|
return appdb_title_from(NULL, NULL, titleId, out, cap);
|
|
}
|