# Apollo PS4 SQLite Database Schema: Complete Guide to SAVES_DB_PATH Structure and Query Patterns

> Explore the Apollo PS4 SQLite database schema at SAVES_DB_PATH. Understand its structure and query patterns for managing game saves efficiently.

- Repository: [Damián Parrino/apollo-ps4](https://github.com/bucanero/apollo-ps4)
- Tags: api-reference
- Published: 2026-02-26

---

**The Apollo PS4 save manager stores all user-save metadata in a single SQLite database located at `/system_data/savedata/%08x/db/user/savedata.db`, utilizing a fixed 19-column schema and specific INSERT, UPDATE, and SELECT query patterns executed through memory-mapped VFS operations.**

Apollo PS4 is an open-source save game manager for jailbroken PlayStation 4 consoles that interacts directly with the system's `savedata.db` file. Understanding the **Apollo PS4 SQLite database schema** is critical for developers building save editing utilities or automation scripts. The database structure is defined by the PS4 operating system, but Apollo accesses it through well-defined patterns in [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c) and [`source/sqlite_db.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sqlite_db.c).

## SAVES_DB_PATH File Location and Access Pattern

The database resides at a path defined by the macro `SAVES_DB_PATH` in [`include/saves.h`](https://github.com/bucanero/apollo-ps4/blob/main/include/saves.h). The concrete path expands to:

```c
/system_data/savedata/%08x/db/user/savedata.db

```

Apollo substitutes the `%08x` placeholder with the current `apollo_config.user_id` to target the specific user slot. The application never creates this table; it only reads from and writes to the existing system database.

## Database Schema of the savedata Table

The `savedata` table contains a fixed set of columns that Apollo references throughout [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c). Based on the INSERT, UPDATE, and SELECT statements, the schema consists of the following fields:

| Column | Type | Description |
|--------|------|-------------|
| `title_id` | TEXT | Hexadecimal game identifier (e.g., `CUSA00001`) |
| `dir_name` | TEXT | Directory name containing the actual save files |
| `main_title` | TEXT | Primary display title shown in the UI |
| `sub_title` | TEXT | Secondary title or subtitle text |
| `detail` | TEXT | Free-form description field |
| `tmp_dir_name` | TEXT | Temporary directory used during mounting operations |
| `is_broken` | INTEGER | Corruption flag (0 or 1) |
| `user_param` | INTEGER | Custom parameter used by certain patches |
| `blocks` | INTEGER | Number of 4 KiB blocks allocated |
| `free_blocks` | INTEGER | Remaining free blocks within the save |
| `size_kib` | INTEGER | Total size in KiB (calculated as blocks × 4) |
| `mtime` | TEXT | ISO-8601 timestamp of last modification |
| `fake_broken` | INTEGER | Internal Apollo flag for fake-broken feature |
| `account_id` | INTEGER | Owner's PSN account identifier |
| `user_id` | INTEGER | Numeric user slot (1-16) |
| `faked_owner` | INTEGER | Flag indicating fake account ownership |
| `cloud_icon_url` | TEXT | Cloud icon URL (unused on PS4) |
| `cloud_revision` | INTEGER | Cloud revision number |
| `game_title_id` | TEXT | Original title ID from the game's SFO |

A **composite primary key** on `(title_id, dir_name)` is implied by the query patterns, as Apollo always addresses specific saves using both values in WHERE clauses.

## Typical Apollo Query Patterns

Apollo interacts with the database through three primary operation families, all utilizing `sqlite3_mprintf` for safe parameter binding and `sqlite3_exec` or prepared statements for execution.

### Inserting New Save Entries

When creating a new save in mount mode `CREATE2`, Apollo executes a full INSERT statement at `source/saves.c:120`:

```sql
INSERT INTO savedata(title_id, dir_name, main_title, sub_title, detail, tmp_dir_name, 
is_broken, user_param, blocks, free_blocks, size_kib, mtime, fake_broken, 
account_id, user_id, faked_owner, cloud_icon_url, cloud_revision, game_title_id) 
VALUES (%Q, %Q, '', '', '', '', 0, 0, %d, %d, %d, 
strftime('%%Y-%%m-%%dT%%H:%%M:%%S.00Z', CURRENT_TIMESTAMP), 0, %ld, %d, 0, '', 0, %Q);

```

This pattern populates all columns, calculating `size_kib` from the block count and generating an ISO-8601 timestamp.

### Updating Mutable Metadata

For user-initiated changes to titles or custom parameters, Apollo uses the UPDATE pattern found at `source/saves.c:162`:

```sql
UPDATE savedata SET (main_title, sub_title, detail, user_param) = (%Q, %Q, %Q, %ld) 
WHERE (title_id=%Q AND dir_name=%Q);

```

This targets the composite key to modify only the display metadata without affecting file allocation.

### Selecting Saves for the Browser UI

To populate the save list view, Apollo performs a minimal SELECT at `source/saves.c:1572`:

```sql
SELECT title_id, dir_name, main_title, blocks, account_id, sub_title 
FROM savedata

```

This retrieves only the fields necessary for the "Save Manager" list interface, optimizing memory usage during browsing.

### Retrieving Detailed Save Information

For the "Save Details" screen, Apollo queries specific fields with timestamp formatting at `source/saves.c:2324`:

```sql
SELECT sub_title, detail, free_blocks, size_kib, user_id, account_id, main_title, 
datetime(mtime) 
FROM savedata 
WHERE (title_id=%Q AND dir_name=%Q)

```

The `datetime(mtime)` function converts the stored ISO-8601 string to a human-readable format for display.

## Memory-Mapped Database Operations

All database interactions are abstracted through [`source/sqlite_db.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sqlite_db.c), which implements atomic read-modify-write cycles using the **memvfs** extension.

### Opening the Database

The `open_sqlite_db()` function loads the entire database file into memory:

```c
#include "sqlite_db.h"
#include "saves.h"

char dbpath[256];
snprintf(dbpath, sizeof(dbpath), SAVES_DB_PATH, apollo_config.user_id);

sqlite3 *db = open_sqlite_db(dbpath);
if (!db) {
    LOG("Failed to open save DB at %s", dbpath);
    return;
}

```

*Source reference:* `source/sqlite_db.c:17`

### Executing a Browse Query

Listing saves follows the pattern used in the UI implementation:

```c
sqlite3_stmt *stmt;
const char *sql = "SELECT title_id, dir_name, main_title, blocks, account_id, sub_title FROM savedata";

if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        const char *tid = (const char *)sqlite3_column_text(stmt, 0);
        const char *dir = (const char *)sqlite3_column_text(stmt, 1);
        int blocks = sqlite3_column_int(stmt, 3);
        // Populate UI list...
    }
    sqlite3_finalize(stmt);
}

```

### Updating Save Metadata

Modifying a specific save requires binding to the composite key:

```c
const char *new_sub = "Modified Subtitle";
char *sql = sqlite3_mprintf(
    "UPDATE savedata SET sub_title = %Q "
    "WHERE title_id = %Q AND dir_name = %Q",
    new_sub, title_id, dir_name
);

if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) {
    LOG("Update failed: %s", sqlite3_errmsg(db));
}
sqlite3_free(sql);

```

### Persisting Changes Atomically

After modifications, `save_sqlite_db()` writes the in-memory database back to disk:

```c
if (!save_sqlite_db(db, dbpath)) {
    LOG("Failed to write database changes");
}
sqlite3_close(db);

```

*Source reference:* `source/sqlite_db.c:52`

This pattern ensures atomic updates on the PS4's restrictive filesystem by dumping the complete modified database in a single operation.

## Summary

- **Apollo PS4 SQLite database schema** consists of a single `savedata` table with 19 columns including identifiers (`title_id`, `dir_name`), display metadata (`main_title`, `sub_title`), allocation metrics (`blocks`, `free_blocks`), and ownership fields (`account_id`, `user_id`).
- The **composite primary key** `(title_id, dir_name)` uniquely identifies each save entry across all query operations.
- **Query patterns** include full INSERT statements for new saves, targeted UPDATEs for metadata modification, and selective SELECTs for UI population versus detailed views.
- **Database access** is abstracted through `open_sqlite_db()` and `save_sqlite_db()` in [`source/sqlite_db.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sqlite_db.c), which utilize memory-mapped VFS for atomic file operations on the PS4 system partition.

## Frequently Asked Questions

### How does Apollo handle database corruption or locked files?

Apollo relies on the underlying PS4 operating system to maintain database integrity. The application opens the database in read-write mode through the memvfs extension, loading the entire file into memory before operations and atomically writing it back only upon successful completion of all queries. If the system reports corruption (indicated by the `is_broken` column flag), Apollo displays the save as corrupted in the UI but does not attempt automatic repair.

### Can I manually edit the savedata.db file while Apollo is running?

Manual editing is not recommended while Apollo is active. The application maintains an in-memory copy of the database through `open_sqlite_db()`, meaning any external modifications to the file on disk will be overwritten when Apollo calls `save_sqlite_db()` during the next save operation or application exit. To modify the database externally, ensure Apollo is completely terminated.

### What is the difference between title_id and game_title_id in the schema?

According to the source code in [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c), `title_id` represents the current identifier associated with the save mount point, while `game_title_id` stores the original value extracted from the game's PARAM.SFO file. In most cases these values are identical, but they may differ when using certain mounting features or when dealing with re-signed saves where the mount point differs from the original game identifier.

### Where does Apollo store the database path configuration?

The database path is defined as the macro `SAVES_DB_PATH` in [`include/saves.h`](https://github.com/bucanero/apollo-ps4/blob/main/include/saves.h) with the value `/system_data/savedata/%08x/db/user/savedata.db`. At runtime, Apollo formats this string with the current user ID from `apollo_config.user_id` to generate the absolute path for the specific PlayStation 4 user slot being managed.