How Apollo Save Tool Mounts and Manages trophy.img Files for PS4 Trophy Support

Apollo Save Tool mounts trophy.img files by treating trophy data as encrypted save-data blocks, decrypting the sealed key with the Orbis file-system API, and exposing the mounted image under /data/apollo/mount/ for read/write access while synchronizing progress to a SQLite database.

The Apollo Save Tool for PlayStation 4 provides comprehensive trophy editing capabilities by allowing users to mount and manage trophy.img files directly. This open-source utility, available in the bucanero/apollo-ps4 repository, treats trophy data as a specialized save-data block that requires specific decryption and mounting procedures to access the underlying SQLite database and icon files.

Understanding Trophy Data as Save Data Blocks

Apollo Save Tool treats PlayStation 4 trophy data as a save-data block with special handling. When a save entry is marked with SAVE_FLAG_TROPHY (value 128), the tool initiates a specialized mount process that differs from standard save data mounting. This approach allows the tool to leverage the same encryption and file-system abstractions used for regular saves while providing access to the trophy-specific SQLite database and icon resources stored within the trophy.img container.

Step-by-Step Mount Process

The mounting sequence involves path construction, image creation, sealed key decryption, and Orbis API integration. Each step is orchestrated across three core source files: source/saves.c, source/sd.c, and source/sqlite_db.c.

Detecting Trophy Saves via SAVE_FLAG_TROPHY

When the UI loads a save entry, it checks the flags field for the SAVE_FLAG_TROPHY bit. If detected, orbis_SaveMount() routes execution to the trophy-specific mounting logic:

if (mount_mode & SAVE_FLAG_TROPHY) { 
    // Trophy-specific mount logic
}

Source: orbis_SaveMount() lines 87-92 in source/saves.cGitHub link

Building HDD Trophy Paths

The tool constructs the absolute paths to the trophy.img and sealedkey files using the user's PlayStation Network ID (apollo_config.user_id) and the game's title ID:

snprintf(keyPath, sizeof(keyPath), TROPHY_PATH_HDD "%s/sealedkey",
         apollo_config.user_id, save->title_id);
snprintf(volumePath, sizeof(volumePath), TROPHY_PATH_HDD "%s/trophy.img",
         apollo_config.user_id, save->title_id);

The TROPHY_PATH_HDD macro resolves to /user/home/%08x/trophy/data/ (defined in include/saves.h line 36), resulting in paths like /user/home/12345678/trophy/data/ABCD1234/trophy.img.

Source: orbis_SaveMount() in source/saves.cGitHub link

Creating the Trophy Image

If the trophy.img file does not exist, orbis_SaveMount() executes the creation branch (lines 103-115). It opens the SQLite database for the save, inserts a placeholder row, then invokes createSave() to generate a clean PFS image and corresponding sealed key:

// Creation logic for missing trophy.img
createSave(volumePath, keyPath, save);

Source: orbis_SaveMount() lines 103-115 in source/saves.cGitHub link

Decrypting the Sealed Key

The mountSave() function in source/sd.c receives the volumePath and keyPath, then decrypts the sealed key using the PlayStation 4's cryptographic services:

ret = decryptSealedKeyAtPath(volumeKeyPath, decryptedSealedKey);
if (ret != 0) {
    LOG("Failed to decrypt sealed key");
    return 0;
}

This step is critical because the trophy.img is encrypted with a key that is itself encrypted by the console's unique key derivation hardware.

Source: mountSave() in source/sd.cGitHub link

Mounting with Orbis File System API

After decryption, the tool initializes the mount options and invokes the Orbis kernel to mount the image:

sceFsInitMountSaveDataOpt(&opt);
opt.budgetid = "system";
ret = sceFsMountSaveData(&opt, volumePath, mountPath, decryptedSealedKey);

The sceFsMountSaveData function mounts the PFS-formatted trophy.img to a temporary sandbox directory, making the trophy files accessible through standard file I/O operations.

Source: mountSave() lines 89-105 in source/sd.cGitHub link

Exposing the Mounted Directory

After successful mounting, orbis_SaveMount() logs the mount point and copies the directory name back to the caller:

LOG("'%s/%s' mountPath (%s)", save->title_id, save->dir_name, mountDir);
strlcpy(mount_path, save->dir_name, ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE);

The UI can now access files under /data/apollo/mount/<dir_name>/sce_sys/, which contains the trophy_local.db SQLite database and icon resources. This layout mirrors the structure used for standard save data, ensuring consistency across the codebase.

Source: orbis_SaveMount() lines 44-48 in source/saves.cGitHub link

Managing Trophy Progress with SQLite

While the mounted trophy.img provides raw file access, the visible progress displayed in the UI originates from a separate SQLite database located at TROPHY_DB_PATH (/user/home/%08x/trophy/db/trophy_local.db). The tool provides three core functions for manipulating this data:

Function Purpose Location
trophy_unlock() Marks a trophy as unlocked, updates flag tables, increments counters, and recomputes group/title progress source/sqlite_db.c lines 390-428
trophy_lock() Re-locks a previously unlocked trophy, clears timestamps, and decrements progress counters source/sqlite_db.c lines 430-468
trophySet_delete() Removes an entire trophy set from the DB and deletes the physical trophy.img and sealedkey files source/sqlite_db.c lines 470-508

The UI populates the Trophies menu by executing a SELECT query against tbl_trophy_flag (see saves.c lines 691-704), then decorates each entry with the appropriate icon (bronze, silver, gold, or platinum) based on the unlocked column value.

Practical Implementation Example

Below is a minimal code snippet demonstrating how a developer could implement a command to unlock all trophies for a selected game using the Apollo Save Tool's internal APIs:

/* Assume `save_entry_t *game` has been obtained from the UI */
int unlock_all_trophies(const save_entry_t *game) {
    char query[256];
    sqlite3 *db;
    char dbpath[256];

    /* Open the trophy DB for the current user */
    snprintf(dbpath, sizeof(dbpath), TROPHY_DB_PATH, apollo_config.user_id);
    db = open_sqlite_db(dbpath);
    if (!db) return 0;

    /* Simple loop over every trophy id for the game */
    for (int id = 0; id < game->blocks; ++id) {
        /* Unlock each trophy – type is stored in the DB, pass 0 to let the helper resolve it */
        trophy_unlock(game, id, /*group_id*/0, /*type*/0);
    }
    sqlite3_close(db);
    return 1;
}

Key implementation details:

  • Uses the TROPHY_DB_PATH macro defined in include/saves.h to construct the correct database path.
  • Leverages the existing trophy_unlock() helper which handles counter increments and progress recomputation automatically.
  • Assumes the trophy image is already mounted via orbis_SaveMount() before database operations occur.

Summary

Apollo Save Tool mounts and manages trophy.img files through a sophisticated multi-layered approach that bridges PlayStation 4 system APIs with user-friendly file access:

  • Detection: The tool identifies trophy entries via the SAVE_FLAG_TROPHY bit flag in source/saves.c.
  • Path Construction: HDD paths are built using TROPHY_PATH_HDD macros incorporating the user's PSN ID and title ID.
  • Image Creation: Missing trophy images are generated via createSave() with proper PFS formatting and sealed keys.
  • Cryptographic Mounting: mountSave() in source/sd.c decrypts sealed keys and invokes sceFsMountSaveData to mount the encrypted image.
  • Database Management: Trophy progress is synchronized to trophy_local.db via functions like trophy_unlock() and trophy_lock() in source/sqlite_db.c.

This architecture allows seamless read/write access to trophy data while maintaining compatibility with the PlayStation 4's security model and file system expectations.

Frequently Asked Questions

How does Apollo Save Tool identify which games have trophy support?

Apollo Save Tool checks the flags field of each save entry for the SAVE_FLAG_TROPHY bit (value 128) when loading the save list in source/saves.c. If this bit is set, the tool routes the mount operation through the trophy-specific logic in orbis_SaveMount() rather than standard save mounting, ensuring the correct paths and decryption keys are used for trophy data.

What happens if the trophy.img file doesn't exist on the HDD?

If orbis_SaveMount() detects a missing trophy.img during the mount process, it executes the creation branch (lines 103-115 in source/saves.c). This process opens the SQLite database, inserts a placeholder row, and calls createSave() to generate a new PFS-formatted image along with its corresponding sealed key file, effectively initializing a fresh trophy container for the game.

Where are trophy files mounted for editing?

After successful decryption and mounting via sceFsMountSaveData in source/sd.c, the trophy image is exposed under /data/apollo/mount/<dir_name>/ where <dir_name> represents the save directory name. This sandboxed path contains the sce_sys/ subdirectory with trophy_local.db and icon resources, allowing the UI to perform standard file I/O operations as if accessing normal save data.

How does the tool keep trophy unlock states synchronized?

Trophy progress is maintained in the trophy_local.db SQLite database located at /user/home/%08x/trophy/db/trophy_local.db. The tool uses functions like trophy_unlock() and trophy_lock() in source/sqlite_db.c to update the tbl_trophy_flag table, increment or decrement counters, and recompute group progress, ensuring the database state remains consistent with the mounted trophy image contents.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →