# Apollo-PS4 save_list_t Structure: How ReadList, ReadCodes, and UpdatePath Function Pointers Work

> Explore the Apollo-PS4 save_list_t structure and its ReadList, ReadCodes, and UpdatePath function pointers. Understand how Apollo abstracts storage and unifies save sources for seamless UI integration.

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

---

**The `save_list_t` structure in Apollo-PS4 uses the function pointers `ReadList`, `ReadCodes`, and `UpdatePath` to abstract storage operations across HDD, USB, trophies, and online databases, allowing the UI to treat all save sources uniformly.**

The `save_list_t` structure is the central abstraction in the Apollo-PS4 save manager that decouples the user interface from specific storage backends. Defined in the bucanero/apollo-ps4 repository, this structure enables the application to handle PS4 saves, USB exports, trophy files, and online patches through a single, generic interface. Understanding how its function pointers operate is essential for developers extending the tool or debugging save-loading issues.

## Understanding the save_list_t Structure Definition

The complete definition resides in [`include/saves.h`](https://github.com/bucanero/apollo-ps4/blob/main/include/saves.h) and serves as a façade that couples a base path with three pluggable behaviors.

### Core Fields and Data Members

The structure begins with data members that maintain the list state:

```c
typedef struct
{
    list_t *list;                 // Loaded list of save_entry_t objects
    char    path[128];            // Base path (USB mount, HDD folder, URL)
    const char *title;            // UI title (filled at runtime)
    uint8_t id;                   // Menu identifier (e.g., MENU_HDD_SAVES)
    // ... function pointers follow
} save_list_t;

```

* **`list`** – Holds the actual array of `save_entry_t` objects representing individual save files or trophies.
* **`path`** – The base directory or URL that the list works against, which can be rewritten by `UpdatePath` before enumeration.
* **`id`** – Identifier used by the menu system to differentiate the various lists (HDD, USB, trophies, etc.).

### Function Pointer Signatures

The final members are the three function pointers that provide polymorphic behavior:

```c
    void   (*UpdatePath)(char *);                 // Optional path-adjuster
    int    (*ReadList)(const char *);             // Returns list_t* of entries
    list_t*(*ReadCodes)(const char *);            // Returns list_t* of cheat/patch codes

```

These pointers allow each concrete list to define its own logic for path resolution, container enumeration, and code loading without modifying the generic UI code.

## How Function Pointers Drive Storage Abstraction

The three callbacks create a uniform contract that the menu system invokes regardless of whether the underlying storage is a local SQLite database, a USB mass storage device, or a remote HTTP endpoint.

### ReadList: Enumerating Save Entries

The `ReadList` function pointer is responsible for scanning the container specified by `path` and returning a `list_t*` populated with `save_entry_t` objects. In [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c), distinct implementations handle different backends:

* **`ReadUserList`** – Queries the PS4's `savedata.db` SQLite database on the HDD.
* **`ReadUsbList`** – Scans the directory structure of a mounted USB device using `opendir` and parses `param.sfo` files.
* **`ReadTrophyList`** – Iterates through trophy files in the user profile directory.
* **`ReadOnlineList`** – Performs an HTTP request to `ONLINE_URL` and parses the JSON response.

The generic UI in [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c) simply calls `save_list->ReadList(save_list->path)` without knowledge of these implementation details.

### ReadCodes: Loading Cheat and Patch Commands

Once a user selects a specific save entry, the `ReadCodes` function pointer loads the available cheat codes, patches, or export commands for that entry. This pointer typically points to:

* **`ReadCodes`** (generic) – Loads `.savepatch` files and creates `code_entry_t` objects with `_createCmdCode`.
* **`ReadTrophies`** – Handles trophy-specific operations like unlocking or syncing.
* **`ReadOnlineSaves`** – Parses online database entries for download options.
* **`ReadVmc1Codes`** / **`ReadVmc2Codes`** – Manages Virtual Memory Card import/export commands.

The UI invokes this as `save_list->ReadCodes(selected_entry)`, which returns a `list_t*` of commands that populate the secondary menu.

### UpdatePath: Dynamic Path Resolution

The `UpdatePath` function pointer provides an optional hook to rewrite the base `path` before enumeration occurs. This is critical for storage backends where the mount point or user context changes dynamically:

* **`update_hdd_path`** – Adjusts the path when the active user ID changes, pointing to `/user/home/<user_id>/savedata/`.
* **`update_usb_path`** – Constructs the full USB path by concatenating `USB0_PATH` with `PS4_SAVES_PATH_USB`.
* **`update_trophy_path`** – Sets the trophy directory based on the current user profile.
* **`update_db_path`** – Handles online database URL construction (though often the path is static).

In [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c), the `ReloadUserSaves` function checks `if (save_list->UpdatePath)` before calling it, allowing lists that don't need dynamic path adjustment (like fixed online URLs) to set this pointer to `NULL`.

## Concrete Implementations in Apollo-PS4

The global list objects are instantiated in [`source/main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/main.c) with their specific callback bindings:

```c
/* HDD save list */
save_list_t hdd_saves = {
    .id         = MENU_HDD_SAVES,
    .title      = NULL,
    .list       = NULL,
    .path       = "",
    .ReadList   = &ReadUserList,
    .ReadCodes  = &ReadCodes,
    .UpdatePath = &update_hdd_path,
};

/* USB save list */
save_list_t usb_saves = {
    .id         = MENU_USB_SAVES,
    .title      = NULL,
    .list       = NULL,
    .path       = "",
    .ReadList   = &ReadUsbList,
    .ReadCodes  = &ReadCodes,
    .UpdatePath = &update_usb_path,
};

/* Trophy list */
save_list_t trophies = {
    .id         = MENU_TROPHIES,
    .title      = NULL,
    .list       = NULL,
    .path       = "",
    .ReadList   = &ReadTrophyList,
    .ReadCodes  = &ReadTrophies,
    .UpdatePath = &update_trophy_path,
};

/* Online DB list */
save_list_t online_saves = {
    .id         = MENU_ONLINE_DB,
    .title      = NULL,
    .list       = NULL,
    .path       = ONLINE_URL,
    .ReadList   = &ReadOnlineList,
    .ReadCodes  = &ReadOnlineSaves,
    .UpdatePath = &update_db_path,
};

```

This pattern repeats for VMC (Virtual Memory Card) lists, demonstrating how the `save_list_t` structure scales across different storage backends without requiring UI code changes.

## The UI Consumption Flow

The generic reload routine in [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c) demonstrates the exact sequence in which the function pointers are exercised:

```c
static int ReloadUserSaves(save_list_t *save_list)
{
    init_loading_screen(_("Loading save games..."));

    /* Free previous entries */
    if (save_list->list) {
        UnloadGameList(save_list->list);
        save_list->list = NULL;
    }

    /* Adjust path if the list requires dynamic resolution */
    if (save_list->UpdatePath)
        save_list->UpdatePath(save_list->path);

    /* Enumerate entries using the list-specific implementation */
    save_list->list = save_list->ReadList(save_list->path);

    /* Optional sorting based on user configuration */
    if (apollo_config.doSort == SORT_BY_NAME)
        list_bubbleSort(save_list->list, &sortSaveList_Compare);
    else if (apollo_config.doSort == SORT_BY_TITLE_ID)
        list_bubbleSort(save_list->list, &sortSaveList_Compare_TitleID);

    stop_loading_screen();
    return save_list->list ? list_count(save_list->list) : 0;
}

```

When a user selects a specific entry, the UI later calls `save_list->ReadCodes(selected_entry)` to populate the command menu, completing the three-phase lifecycle: **path resolution**, **entry enumeration**, and **code loading**.

## Summary

- The **`save_list_t`** structure in Apollo-PS4 acts as a polymorphic container that abstracts HDD, USB, trophy, and online storage backends through function pointers.
- **`ReadList`** scans the storage container (SQLite database, directory, or HTTP endpoint) and returns a linked list of `save_entry_t` objects.
- **`ReadCodes`** loads cheat codes, patches, or export commands for a selected entry, returning a `list_t*` of `code_entry_t` objects.
- **`UpdatePath`** optionally rewrites the base path before enumeration, handling dynamic mount points or user-specific directories.
- The UI in [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c) treats all lists uniformly by calling these callbacks, enabling new storage backends to be added without modifying the menu logic.

## Frequently Asked Questions

### What is the purpose of the save_list_t structure in Apollo-PS4?

The `save_list_t` structure serves as a generic abstraction layer that allows the Apollo-PS4 UI to handle diverse storage backends—such as HDD saves, USB devices, trophy files, and online databases—through a uniform interface. By encapsulating the list data along with function pointers for path management, enumeration, and code loading, the structure eliminates the need for storage-specific logic in the menu system.

### How does the ReadList function pointer differ from ReadCodes?

The `ReadList` function pointer is responsible for **enumerating containers**—it scans a directory, database, or URL and returns a `list_t*` of `save_entry_t` objects representing individual saves or trophies. In contrast, `ReadCodes` operates on a **single selected entry**, loading the cheat codes, patches, or export commands applicable to that specific file and returning a `list_t*` of `code_entry_t` objects. The first runs during menu initialization; the second runs when a user selects a specific save.

### Why is UpdatePath optional in the save_list_t structure?

The `UpdatePath` function pointer is optional (can be `NULL`) because not all storage backends require dynamic path resolution. HDD, USB, and trophy lists use `UpdatePath` to adjust for changing user IDs or mount points (e.g., `/mnt/usb0/` vs `/mnt/usb1/`), whereas the online database list uses a static URL defined at compile time and sets `UpdatePath` to `NULL`. The UI checks `if (save_list->UpdatePath)` before invoking it, ensuring safe operation for lists with fixed paths.

### Where are the concrete implementations of these function pointers defined?

The concrete implementations are defined in [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c) and [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c). The `ReadList` implementations (`ReadUserList`, `ReadUsbList`, `ReadTrophyList`, `ReadOnlineList`) and `ReadCodes` implementations (`ReadCodes`, `ReadTrophies`, `ReadOnlineSaves`) reside in [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c). The `UpdatePath` wrappers (`update_hdd_path`, `update_usb_path`, `update_trophy_path`) are defined in [`source/menu_main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c). These are bound to the global `save_list_t` instances in [`source/main.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/main.c).