JAPS2ME/docs/FORMATS.md

188 lines
8.4 KiB
Markdown

# 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](https://github.com/Adubbz/mymcplusplus), 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](https://github.com/Adubbz/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`, and `src/memory_card/`.