8.4 KiB
PS2 Save and Memory-Card Formats
This guide describes the formats JAPS2ME currently reads or writes. Some fields are reserved, undocumented, or vary between producers; those are called out rather than assigned meanings. The MAX Drive and memory-card notes were informed by the mymcplusplus project, which is also credited in the main README.
CodeBreaker .cbs
A CodeBreaker file has a fixed header followed by an encrypted, compressed record body.
Integers are little-endian. JAPS2ME identifies the format by its four-byte CFU\0
signature.
Header
| Offset | Size | Meaning |
|---|---|---|
0x00 |
4 | Magic: CFU\0 |
0x04 |
4 | Reserved/unknown |
0x08 |
4 | Header length in bytes; at least 124 |
0x0C |
4 | Decompressed body length |
0x10 |
4 | Stored length; JAPS2ME writes the complete file length |
0x14 |
32 | Save-directory name, NUL-terminated or zero-padded |
0x34 |
8 | Directory creation timestamp, stored as PS2 time bytes |
0x3C |
8 | Directory modification timestamp |
0x44 |
8 | Reserved/unknown |
0x4C |
4 | Directory mode |
0x50 |
12 | Reserved/unknown |
0x5C |
Variable | Display title, extending to the end of the header |
The title begins at byte 92. The smallest header is 124 bytes, leaving at least 32 bytes for a title. Longer titles require a longer header. In the current encoder the title is written as UTF-8 and the header is extended when needed. The parser accepts the stored-length field as either the complete file length or the body length, for compatibility with existing files.
Body
The body is zlib-compressed and then RC4-encrypted with the fixed CodeBreaker state used by the format. Reading reverses that order: RC4-decrypt, then zlib-decompress. The decompressed stream is a sequence of file records, with no padding between them:
| Record offset | Size | Meaning |
|---|---|---|
0x00 |
8 | File creation timestamp |
0x08 |
8 | File modification timestamp |
0x10 |
4 | File data length in bytes |
0x14 |
2 | PS2 directory-entry mode |
0x16 |
2 | Reserved |
0x18 |
8 | Reserved/unknown fields |
0x20 |
32 | File name, NUL-terminated or zero-padded |
0x40 |
Variable | File data, exactly the length specified above |
The next record begins immediately after the file data. The body ends after the final record. JAPS2ME bounds-checks record headers, names, and data lengths and caps the decompressed body at 64 MiB.
Datel MAX Drive .max
MAX Drive files have a 92-byte header followed by an LZARI-compressed payload. The
header begins with the 12-byte Ps2PowerSave signature. Integers are little-endian.
| Offset | Size | Meaning |
|---|---|---|
0x00 |
12 | Magic: Ps2PowerSave |
0x0C |
4 | CRC-32 |
0x10 |
32 | Save-directory name |
0x30 |
32 | Display title |
0x50 |
4 | Compressed-length field; producer conventions vary |
0x54 |
4 | File count |
0x58 |
4 | Decompressed body length |
0x5C |
— | Compressed payload begins |
The CRC is calculated over the full 92-byte header with bytes 0x0C..0x10 zeroed,
followed by the compressed payload. It is checked before decompression. The length
field commonly includes four bytes beyond the compressed payload; some files instead
store the uncompressed length there, in which case JAPS2ME treats the payload as
extending to end-of-file.
After LZARI decompression, each file record begins with a 36-byte header:
| Record offset | Size | Meaning |
|---|---|---|
0x00 |
4 | File data length in bytes |
0x04 |
32 | File name |
0x24 |
Variable | File data, followed by format-specific alignment padding |
After a record's data, the next record offset is
round_up(file_end + 8, 16) - 8, where file_end is the offset immediately after
the file data. This is the format's 16-byte alignment rule with an 8-byte offset
adjustment.
The decompressed body must contain exactly the declared number of records and no
unexpected trailing bytes. MAX files do not carry the per-file timestamps and modes
found in a CodeBreaker file; JAPS2ME assigns supported default values when importing.
The decompressed-size limit is 64 MiB and the file-count limit is 4096.
Sony PS2 memory-card filesystem
A PS2 memory card is not a standard FAT filesystem. It has Sony's own superblock, a two-level indirect FAT, fixed-size directory entries, and optional ECC spare bytes attached to each page. “FAT-like” describes the cluster-chain allocation approach, not compatibility with FAT12/16/32 tools.
Image geometry and superblock
A standard data page is 512 bytes, and a standard cluster is two pages (1024 bytes). An erase block is normally 16 pages. ECC-bearing images interleave spare data after each page; for a 512-byte page this implementation expects 16 spare bytes, making a raw page 528 bytes. The spare area is outside the filesystem's file payloads.
The first 512 data bytes contain the superblock. Important fields include:
| Offset | Meaning |
|---|---|
0x00 |
Magic: Sony PS2 Memory Card Format (28 bytes) |
0x1C |
Format version string (12 bytes) |
0x28 |
Page size (u16) |
0x2A |
Pages per cluster (u16) |
0x2C |
Pages per erase block (u16) |
0x30 |
Total clusters on card (u32) |
0x34 |
First allocatable cluster (u32) |
0x38 |
End of allocatable range, exclusive (u32) |
0x3C |
Root-directory FAT cluster (u32; normally zero) |
0x40, 0x44 |
Good-block values used for card geometry/ECC |
0x50 |
Indirect-FAT cluster list (up to 32 u32 entries) |
0xD0 |
Bad erase-block list (up to 32 u32 entries) |
The superblock and FAT metadata are addressed as raw card clusters. Directory and file cluster numbers are logical numbers relative to the first allocatable cluster; the image offset is found by adding the superblock's allocatable-cluster offset.
FAT and allocation chains
The superblock points to indirect FAT clusters. Each indirect FAT entry points to a
FAT cluster, and each FAT entry describes one allocatable cluster. A chain link sets
the high bit (0x80000000) and stores the next logical cluster number in the lower
bits. The all-ones value 0xFFFFFFFF marks the end of an allocated chain; an
unallocated entry commonly has the high bit clear, such as 0x7FFFFFFF.
Files and directories can span multiple clusters. Readers follow links until the end-of-chain marker, detect loops and invalid links, and use the directory entry's length to distinguish payload bytes from cluster padding. Free space is the count of unallocated FAT clusters multiplied by the cluster size; it is not the sum of unused bytes in allocated clusters.
Directory entries
Each directory entry occupies 512 bytes. Relevant fields are:
| Offset | Size | Meaning |
|---|---|---|
0x00 |
2 | Mode and entry-type flags |
0x04 |
4 | Length: entry count for a directory, bytes for a regular file |
0x08 |
8 | Creation timestamp |
0x10 |
4 | First cluster |
0x14 |
4 | Parent-directory entry index |
0x18 |
8 | Modification timestamp |
0x20 |
4 | Attribute field |
0x40 |
448 | Name, normally NUL-terminated |
Common mode bits are read (0x0001), write (0x0002), execute (0x0004), file
(0x0010), directory (0x0020), and exists (0x8000).
Directories begin with . and ... Those entries have length zero. A save's entry
in its parent records the save directory's entry count, including . and ..; the
root has no parent, so its own . entry carries the root entry count. The root
directory itself may occupy a multi-cluster FAT chain.
ECC and JAPS2ME behavior
JAPS2ME reads plain images and images with ECC spare areas. When it writes data pages to an ECC-bearing image, it regenerates the affected page's spare-area ECC. It does not currently offer an ECC-ignore option or validate the existing spare-area ECC on reads.
Save imports are staged on a temporary image, re-opened, and checked before replacing the original card. Deletion currently writes directly to its target, so use a backup and keep PCSX2 closed before destructive operations. JAPS2ME's save model is flat: export and deletion reject nested directories rather than silently treating them as ordinary files.
References
- mymcplusplus, a technical reference for PS2 memory-card filesystem behavior and save formats.
- The corresponding JAPS2ME implementations:
src/codebreaker/format.rs,src/max_drive.rs, andsrc/memory_card/.