# Apollo PS4 param.sfo File Structure: How It Modifies User ID and Account ID Fields

> Discover how Apollo PS4 modifies User ID and Account ID in param.sfo files. Learn about the internal structure and direct memory manipulation techniques to alter these critical fields.

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

---

**Apollo PS4 parses the binary SFO container format to locate the `ACCOUNT_ID` and `PARAMS` keys, then overwrites the 8-byte Account ID and the 32-bit User ID embedded in the PARAMS structure using direct memory manipulation.**

The `param.sfo` file is a critical metadata container in every PS4 save game, storing the Account ID and User ID that tie save data to specific PlayStation Network profiles. Understanding the internal `param.sfo` file structure is essential for developers working with save game tools like Apollo PS4, which modifies these identifiers to enable save sharing across different accounts.

## Binary Layout of the param.sfo File Structure

The `param.sfo` file follows the generic PS4 SFO container format, identified by the magic value `0x46535000`. According to the Apollo PS4 source code in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c), the binary layout consists of a fixed header followed by three variable-length tables.

### SFO Header Format

The 20-byte header structure defines the offsets for the remaining tables:

| Offset | Size | Field |
|--------|------|-------|
| 0x00 | 4 bytes | **Magic** – `0x46535000` (`SFO_MAGIC`) |
| 0x04 | 4 bytes | **Version** – `0x0101` (`SFO_VERSION`) |
| 0x08 | 4 bytes | **Key-table offset** – Byte offset to the null-terminated key strings |
| 0x0C | 4 bytes | **Data-table offset** – Byte offset to the raw binary values |
| 0x10 | 4 bytes | **Number of entries** – Count of key/value pairs |

### Index Table and Entry Structure

Immediately following the header is the **index table**, containing one entry per key/value pair. Each entry is defined by the `sfo_index_table_t` structure in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c) (lines 17-23):

```c
typedef struct sfo_index_table_s {
    u16 key_offset;      // offset into the key table
    u16 param_format;    // data type (e.g. UTF-8, binary)
    u32 param_length;    // actual length of the value
    u32 param_max_length;// allocated size in the data table
    u32 data_offset;     // offset into the data table
} sfo_index_table_t;

```

Apollo uses this index to locate specific keys without scanning the entire file linearly.

## Key Data Structures: ACCOUNT_ID and PARAMS

Within the `param.sfo` data table, Apollo specifically targets two keys to modify ownership metadata: `ACCOUNT_ID` and `PARAMS`.

### ACCOUNT_ID Field

The `ACCOUNT_ID` key stores the 64-bit PlayStation Network identifier as raw binary data. According to [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c), the expected size is defined by `SFO_ACCOUNT_ID_SIZE` (8 bytes). The index table entry for this key points to an 8-byte region in the data table containing the little-endian account identifier.

### PARAMS Field and User ID Location

The `PARAMS` key contains a larger binary structure of type `sfo_param_params_t`, defined in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c) (lines 25-34):

```c
typedef struct sfo_param_params_s {
    u32 unk1;                     // usually zero
    u32 user_id;                  // <-- the User ID we want to change
    u8  unk2[32];                 // padding / unknown purpose
    u32 unk3;                     // usually zero
    char title_id_1[0x10];        // primary title ID
    char title_id_2[0x10];        // secondary title ID (mirrored)
    u32 unk4;                     // usually zero
    u8  chunk[0x3B0];             // additional save-metadata
} sfo_param_params_t;

```

The **User ID** is stored as a 32-bit unsigned integer at offset `0x04` within this structure (immediately following the `unk1` field).

## How Apollo Modifies User ID and Account ID Fields

Apollo implements targeted patching through two internal helper functions in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c), which are orchestrated by the public `patch_sfo` function.

### Patching the Account ID

The `sfo_patch_account` function (lines 63-71 in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c)) overwrites the 8-byte `ACCOUNT_ID` value:

```c
static void sfo_patch_account(sfo_context_t *inout, u64 account) {
    sfo_context_param_t *p;
    if (!account) return;
    p = sfo_context_get_param(inout, "ACCOUNT_ID");
    if (p != NULL && p->actual_length == SFO_ACCOUNT_ID_SIZE) {
        memcpy(p->value, &account, SFO_ACCOUNT_ID_SIZE);
    }
}

```

This function validates that the parameter exists and has the expected 8-byte length before using `memcpy` to replace the raw binary data.

### Patching the User ID

The `sfo_patch_user_id` function (lines 82-92 in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c)) updates the 32-bit field within the `PARAMS` structure:

```c
static void sfo_patch_user_id(sfo_context_t *inout, u32 userid) {
    sfo_context_param_t *p;
    if (userid == 0) return;
    p = sfo_context_get_param(inout, "PARAMS");
    if (p != NULL) {
        sfo_param_params_t *params = (sfo_param_params_t *)p->value;
        params->user_id = userid;
    }
}

```

This function casts the parameter's value buffer to the `sfo_param_params_t` structure type and directly assigns the new User ID to the `user_id` field.

### Orchestrating the Patch Process

Both helpers are called by `patch_sfo` (implemented in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c)), which provides the high-level interface used by the Apollo UI:

```c
int patch_sfo(const char *in_file_path, sfo_patch_t* patches) {
    sfo_context_t *sfo = sfo_alloc();
    if (sfo_read(sfo, in_file_path) < 0) { /* error handling */ }

    sfo_patch_titleid(sfo);               // copies title_id_1 → title_id_2
    sfo_patch_account(sfo, patches->account_id);
    sfo_patch_user_id(sfo, patches->user_id);
    // … optional PSID / directory patches …

    if (sfo_write(sfo, in_file_path) < 0) { /* error handling */ }
    sfo_free(sfo);
    return 0;
}

```

The `sfo_patch_t` structure (defined in [`include/sfo.h`](https://github.com/bucanero/apollo-ps4/blob/main/include/sfo.h) lines 22-28) carries the new identifiers:

```c
typedef struct {
    u32 flags;
    u32 user_id;      // <-- value passed to sfo_patch_user_id()
    u64 account_id;   // <-- value passed to sfo_patch_account()
    u8* psid;
    char* directory;
} sfo_patch_t;

```

When a user selects "Change Account ID" or "Change User ID" in the Apollo interface, the application populates this structure and invokes `patch_sfo` on the target `PARAM.SFO` file located in the save data directory.

## Summary

- **param.sfo Structure**: The file follows the PS4 SFO container format with a 20-byte header, an index table mapping keys to data offsets, and separate key and data tables. The `ACCOUNT_ID` key stores an 8-byte binary PSN identifier, while the `PARAMS` key contains a 0x3F8-byte structure holding the 32-bit User ID at offset 0x04.

- **Modification Method**: Apollo parses the SFO into an in-memory `sfo_context_t`, locates the target entries using `sfo_context_get_param()`, and overwrites the raw binary data. The `sfo_patch_account()` function uses `memcpy` to replace the 8-byte Account ID, while `sfo_patch_user_id()` casts the PARAMS buffer to `sfo_param_params_t*` and assigns the new User ID directly.

- **Implementation Path**: The high-level `patch_sfo()` function in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c) orchestrates the read-modify-write cycle, utilizing the `sfo_patch_t` structure defined in [`include/sfo.h`](https://github.com/bucanero/apollo-ps4/blob/main/include/sfo.h) to transport the new identifiers from the UI (implemented in [`source/saves.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/saves.c)) to the binary patching layer.

## Frequently Asked Questions

### How does Apollo PS4 locate the User ID and Account ID inside param.sfo without scanning the entire file?

Apollo leverages the SFO index table defined in the file header. After parsing the header in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c), it uses `sfo_context_get_param()` to perform a lookup by key name (e.g., "ACCOUNT_ID" or "PARAMS"). The index table entry provides the exact byte offset into the data table, allowing direct access without linear scanning.

### What data types are used for the Account ID and User ID in the PS4 param.sfo format?

The **Account ID** is stored as a **64-bit unsigned integer** (little-endian binary) under the `ACCOUNT_ID` key, occupying exactly 8 bytes (`SFO_ACCOUNT_ID_SIZE`). The **User ID** is a **32-bit unsigned integer** located at offset 0x04 within the `sfo_param_params_t` structure stored under the `PARAMS` key.

### Can Apollo modify other fields in param.sfo besides the User ID and Account ID?

Yes. The `patch_sfo()` function in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c) also calls `sfo_patch_titleid()`, which synchronizes the `title_id_2` field with `title_id_1` inside the PARAMS structure. Additionally, the `sfo_patch_t` structure supports patching the PSID and directory paths, though these require separate handling in the patching logic.

### Is it safe to manually edit param.sfo with a hex editor instead of using Apollo?

While possible, manual hex editing is risky because the SFO format requires consistent offsets between the index table, key table, and data table. Apollo ensures integrity by parsing the index table in [`source/sfo.c`](https://github.com/bucanero/apollo-ps4/blob/main/source/sfo.c) and using `sfo_write()` to recalculate offsets and maintain the correct binary layout. Direct hex editing without updating index entries can corrupt the file and make the save unreadable on PS4.