Apollo-PS4 save_list_t Structure: How ReadList, ReadCodes, and UpdatePath Function Pointers Work
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 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:
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 ofsave_entry_tobjects representing individual save files or trophies.path– The base directory or URL that the list works against, which can be rewritten byUpdatePathbefore 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:
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, distinct implementations handle different backends:
ReadUserList– Queries the PS4'ssavedata.dbSQLite database on the HDD.ReadUsbList– Scans the directory structure of a mounted USB device usingopendirand parsesparam.sfofiles.ReadTrophyList– Iterates through trophy files in the user profile directory.ReadOnlineList– Performs an HTTP request toONLINE_URLand parses the JSON response.
The generic UI in 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.savepatchfiles and createscode_entry_tobjects 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 concatenatingUSB0_PATHwithPS4_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, 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 with their specific callback bindings:
/* 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 demonstrates the exact sequence in which the function pointers are exercised:
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_tstructure in Apollo-PS4 acts as a polymorphic container that abstracts HDD, USB, trophy, and online storage backends through function pointers. ReadListscans the storage container (SQLite database, directory, or HTTP endpoint) and returns a linked list ofsave_entry_tobjects.ReadCodesloads cheat codes, patches, or export commands for a selected entry, returning alist_t*ofcode_entry_tobjects.UpdatePathoptionally rewrites the base path before enumeration, handling dynamic mount points or user-specific directories.- The UI in
source/menu_main.ctreats 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 and source/menu_main.c. The ReadList implementations (ReadUserList, ReadUsbList, ReadTrophyList, ReadOnlineList) and ReadCodes implementations (ReadCodes, ReadTrophies, ReadOnlineSaves) reside in source/saves.c. The UpdatePath wrappers (update_hdd_path, update_usb_path, update_trophy_path) are defined in source/menu_main.c. These are bound to the global save_list_t instances in source/main.c.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →