Files
ps5upload/payload/include/content_db.h
T
phantomptrandClaude Opus 5.5 ece147d720 Games: hide a title's icon from the PS5 home screen, and show it again (#407)
For shortcuts and loaders that, once set up, only clutter the home
screen. ⋯ → "Hide from home screen" on a Games card; a "Hidden" badge
marks it, "N hidden from the home screen" at the top filters to them,
and ⋯ → "Show on home screen" puts it back.

How: every user's icon table in app.db (tbl_iconinfo_<user>) has a
`visible` flag; the system hides some of its own apps with it. The
helper (app.icon_set, app.hidden_list) flips only that flag: nothing is
deleted, the title stays installed and playable. Guards: title ids only,
system apps (NPXS) refused, tables found by shape (titleId + visible),
app.db backed up once to /data/ps5upload/app.db.before-hide before the
first change, BEGIN IMMEDIATE (fails before any change if the system
holds the lock), and rolled back unless at least one and at most one
row per table changed. The hidden list is read live from app.db, so it
cannot drift from the console, and leaves out the system's own hidden
apps. The home screen redraws when an app is next opened and closed;
the console shows a notification saying so.

Verified on the Phat (FW 13.60): WebKit Autoloader hidden, listed, shown
again; a system app and an unknown id refused; the backup written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
2026-10-11 00:36:44 -07:00

155 lines
6.3 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);
#endif /* PS5UPLOAD_CONTENT_DB_H */