Files
ps5upload/payload/include/content_db.h
T
phantomptrandClaude Opus 5.5 9a620c6026 Hidden home-screen icons stay hidden after their app starts (#450)
The system sets an app's icon visible again whenever the app starts, from
ps5upload, the home screen or a forwarder (measured on a FW 13.60 Phat: visible
again within 5 s of launch). The helper now remembers the titles hidden through
ps5upload (/data/ps5upload/hidden_icons.txt, seeded once from what app.db has
hidden) and a thread hides them again when app.db changes, writing only rows
that came back. On the Phat a relaunched hidden game was hidden again 2 s
after the system showed it, with no repeat writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
2026-10-14 20:06:55 -07:00

162 lines
6.7 KiB
C

#ifndef PS5UPLOAD_CONTENT_DB_H
#define PS5UPLOAD_CONTENT_DB_H
#include <stddef.h>
/*
* The console's content databases, read (and repaired) through a real
* SQLite that the payload links statically.
*
* Background: no PS5 firmware ships libSceSqlite.sprx under any library
* path, so every dlsym(RTLD_DEFAULT, "sqlite3_open_v2") in this codebase
* resolved to NULL on every console. Two handlers carried a full SQL
* implementation behind that probe and neither had ever executed; the
* hand-rolled b-tree walker in appdb_scan.h was doing all the work.
*
* That was the right fix for reading title ids, and it stays as the
* fallback here. What it cannot do is (a) reassemble a row whose columns
* are serialized blobs — which is why play-time queries were declared
* impossible — or (b) write. Repairing a console that lists a title it
* refuses to delete means writing.
*
* "Sony does not ship SQLite" is a fact about the platform, not about
* what we can link. See payload/Makefile for the build flags.
*
* Everything here is bounded and emits JSON into a caller-owned buffer.
*/
/* The two databases. app.db drives the home-screen tiles; appinfo.db
* drives Settings -> Storage. They can disagree with each other and with
* what is actually on disk, which is the usual shape of a broken title. */
/* Overridable so the host selftest can point them at fixtures; the
* payload always builds with the real console paths. */
#ifndef CONTENT_DB_APP
#define CONTENT_DB_APP "/system_data/priv/mms/app.db"
#endif
#ifndef CONTENT_DB_APPINFO
#define CONTENT_DB_APPINFO "/system_data/priv/mms/appinfo.db"
#endif
/* A copy of app.db taken before the first home-screen icon change (#407),
* kept so a hide can always be undone by hand. Only the first is kept. */
#ifndef CONTENT_DB_APP_BACKUP
#define CONTENT_DB_APP_BACKUP "/data/ps5upload/app.db.before-hide"
#endif
/* One row of app.db's title table. */
typedef struct {
char title_id[16];
char name[256];
int app_id;
} content_db_app_t;
/*
* Installed titles as rows, so each caller can shape its own response
* (the app list and the "recently played" list want different envelopes
* and different filters).
*
* `source` (optional) receives a static string naming what answered:
* "sqlite", "sqlite-immutable" or "scan".
*
* Returns the row count (>= 0), or -1 if nothing could be read at all.
*/
int content_db_apps(content_db_app_t *out, int max, const char **source);
/*
* Run a read-only SELECT and hand each row to `row_cb` as an array of
* NUL-terminated column strings (NULL for SQL NULL). Return non-zero from
* the callback to stop early.
*
* This exists for queries whose rows need bespoke parsing rather than a
* generic JSON projection -- play time, for instance, lives as a JSON
* document inside a single column and has to be picked apart per row.
*
* Returns the number of rows delivered, or -1 if the query could not run.
*/
int content_db_select_text(const char *db_path, const char *sql, int max_rows,
void *ctx,
int (*row_cb)(void *ctx, int ncol,
const char *const *vals));
/*
* Installed titles from app.db: title id, app id and display name.
*
* Emits {"apps":[{"title_id":"..","app_id":N,"name":".."}, ...],
* "source":"sqlite"|"scan"}
*
* `source` is reported rather than hidden: a caller that sees "scan"
* knows the SQL open failed and the row set came from the byte-level
* reader, which recovers ids reliably but names only sometimes.
*
* Never fails outright — falls back to appdb_scan.h. Returns 0 on
* success, non-zero only if `buf` is too small to hold even an error.
*/
int content_db_apps_json(char *buf, size_t cap, size_t *written);
/*
* Key/value rows from appinfo.db for one title.
*
* appinfo.db stores per-title metadata as (titleId, key, val) triples --
* CONTENT_VERSION, VERSION_FILE_URI and friends. Emits
* {"ok":true,"title_id":"..","rows":[{"key":"..","val":".."}, ...]}
*
* Pass a NULL or empty `keys` for every key, or a comma-separated list
* to filter. There is no b-tree fallback: a key/value projection cannot
* be recovered from a byte scan, so an open failure is reported as one.
*/
int content_db_appinfo_json(const char *title_id, const char *keys,
char *buf, size_t cap, size_t *written);
/*
* Set one appinfo.db value. This writes to a live system database.
*
* Guarded, because getting it wrong bricks the title's Settings entry:
* - the title must not be running (a running app holds the row);
* - the row must already exist -- this updates, it never inserts, so a
* typo in `key` changes nothing instead of adding a key the shell
* will never read;
* - the write runs inside BEGIN IMMEDIATE and is rolled back unless
* exactly one row changed.
*
* Callers are expected to have snapshotted both databases first
* (fs_ops::backup_content_databases on the engine side).
*
* Returns 0 on success. On failure returns non-zero and writes a short
* human-readable reason into `err`.
*/
int content_db_appinfo_set(const char *title_id, const char *key,
const char *val, char *err, size_t errcap);
/*
* Hide or show a title's home-screen icon (#407): sets `visible` in every
* user's icon table in app.db (tables found by shape: a titleId and a
* visible column; tbl_iconinfo_<userid> on the console). Nothing is
* deleted; the title stays installed and runs as before.
*
* Guarded like content_db_appinfo_set: the id must be a title id, system
* apps (NPXS...) are refused, app.db is backed up once before the first
* change (CONTENT_DB_APP_BACKUP), the write runs inside BEGIN IMMEDIATE,
* and it is rolled back unless at least one row and at most one row per
* table changed.
*
* Returns 0 on success, else non-zero with a short reason in `err`.
*/
int content_db_icon_set_visible(const char *title_id, int visible, char *err,
size_t errcap);
/*
* The titles whose icon is hidden in any user's icon table, leaving out the
* system's own apps (NPXS...), which the system hides the same way:
* {"ok":true,"hidden":["ITEM00001",...]} (sorted, no duplicates), or
* {"ok":false,"error":".."}. Read-only.
*/
int content_db_hidden_json(char *buf, size_t cap, size_t *written);
/*
* Hide again whichever of `ids` (`n` title ids) the system made visible: one transaction, and
* only rows with visible != 0 are written, so a pass with nothing to undo leaves app.db as it
* was. Returns how many icons it hid again, or -1 with a short reason in `err` (#450).
*/
int content_db_icons_rehide(const char *const *ids, int n, char *err, size_t errcap);
#endif /* PS5UPLOAD_CONTENT_DB_H */