121 lines
5.6 KiB
Markdown
121 lines
5.6 KiB
Markdown
# PS1 Save and Memory-Card Formats
|
||
|
||
This guide describes the formats JAPS1ME currently reads or writes. The notes
|
||
were informed by the [MemcardRex project](https://github.com/ShendoXT/memcardrex),
|
||
which is also credited in the main README. Corresponding JAPS1ME
|
||
implementations: `src/gme.rs` and `src/ps1/`.
|
||
|
||
## DexDrive `.gme`
|
||
|
||
A `.gme` file is a 3904-byte header followed by a 131072-byte raw card image
|
||
(134976 bytes total). JAPS1ME identifies the format by length plus the `MC`
|
||
signature at offset 3904.
|
||
|
||
### Header
|
||
|
||
| Offset | Size | Meaning |
|
||
| --- | ---: | --- |
|
||
| `0x00` | 11 | Magic: `123-456-STD` (canonical files) |
|
||
| `0x12`–`0x15` | — | Version/status bytes written by MemcardRex (`0x01`, `0x01`/`M`) |
|
||
| `0x16`–`0x25` | 15 | Per-slot type bytes (copies of each directory frame's type) |
|
||
| `0x26`–`0x35` | 15 | Per-slot next-pointer bytes |
|
||
| `0x40`–`0xF3F` | 15 × 256 | Per-slot comments (system-codepage text, NUL-terminated) |
|
||
|
||
Some files in the wild have a zeroed or otherwise non-canonical header. Like
|
||
MemcardRex, JAPS1ME accepts those when the size is 134976 and bytes at 3904
|
||
are `MC`, and reports a `missing DexDrive header magic` warning. Comments are
|
||
decoded as Windows-1252 and are informational only; `convert` never modifies
|
||
them.
|
||
|
||
## Raw card image (`.srm`/`.mcd`/`.mcr`)
|
||
|
||
The raw image is 131072 bytes (1024 frames of 128 bytes). RetroArch `.srm`,
|
||
DuckStation `.mcd`, ePSXe `.mcr`, and similar formats share this layout; only
|
||
the file extension differs. JAPS1ME reads all of these as the same raw card
|
||
image. `convert` writes exactly the `.gme` payload bytes `3904..` with no
|
||
modification, so directory checksums and save data are preserved verbatim.
|
||
|
||
### Per-game filenames
|
||
|
||
New-card install accepts a source named `GAMENAME (REGIONCODE)` with an
|
||
optional trailing DuckStation slot suffix. It writes `GAMENAME (REGIONCODE).srm`
|
||
for RetroArch and `GAMENAME (REGIONCODE)_SLOT.mcd` for DuckStation (default
|
||
slot `1`). For example, `Crash Bandicoot (USA).srm` becomes
|
||
`Crash Bandicoot (USA)_1.mcd` in a DuckStation directory. Multi-disc title
|
||
labels are retained; RetroArch users must manually copy and rename a card for
|
||
the next disc, while DuckStation uses the same per-game card across discs.
|
||
|
||
### Geometry
|
||
|
||
| Raw offset | Size | Meaning |
|
||
| --- | ---: | --- |
|
||
| `0x00` | 128 | Card header frame (`MC` magic) |
|
||
| `0x80`–`0x7FF` | 15 × 128 | Directory frames (slots 1–15) |
|
||
| `0x2000`– | 15 × 8192 | Save data blocks |
|
||
|
||
### Directory frames
|
||
|
||
Each 128-byte frame describes one slot. Relevant fields:
|
||
|
||
| Offset | Size | Meaning |
|
||
| --- | ---: | --- |
|
||
| `0x00` | 1 | Slot type: `0xA0` formatted, `0x51` initial, `0x52`/`0x53` middle/end link, `0xA1`–`0xA3` deleted variants |
|
||
| `0x04` | 3 | Payload size, little-endian |
|
||
| `0x08` | 1 | Next-slot pointer (`0xFF` = end of chain) |
|
||
| `0x0A` | 2 | Region (`BA` America, `BE` Europe, `BI` Japan) |
|
||
| `0x0C` | 10 | Product code |
|
||
| `0x16` | 8 | Save identifier |
|
||
| `0x7F` | 1 | XOR checksum over bytes `0x00..0x7F` |
|
||
|
||
A save is an initial slot plus its link chain (up to 15 slots). Links not
|
||
reachable from any initial slot are orphaned; `list` reports them and counts
|
||
them as free, matching MemcardRex's broken-link handling.
|
||
|
||
### Save titles
|
||
|
||
Each data block carries a 64-byte Shift-JIS title at block offset 4,
|
||
NUL-terminated on an even byte boundary. JAPS1ME decodes it with a
|
||
malformed-sequence replacement and falls back to the save name
|
||
(`region + product code + identifier`) when no usable title exists.
|
||
|
||
### Save icons
|
||
|
||
Each initial save block carries a 16x16, 4bpp icon (the grid shown on the PS1
|
||
BIOS memory-card screen). JAPS1ME reads it for `export-icon` only and never
|
||
writes it back.
|
||
|
||
| Block offset | Size | Meaning |
|
||
| --- | ---: | --- |
|
||
| `0x60` | 32 | Palette: 16 x 16-bit entries (`R` bits 0–4, `G` bits 5–9, `B` bits 10–14, bit 15 flag) |
|
||
| `0x80` | 128 | Primary icon bitmap: 16 rows x 8 bytes, low nibble = left pixel, high nibble = right pixel |
|
||
|
||
Two further frames follow at `0x100`/`0x180` for animated icons; JAPS1ME
|
||
exports only the primary frame. Any palette entry whose red, green, blue, and
|
||
bit-15 flag are all zero is treated as transparent, matching MemcardRex's
|
||
`loadPalette`/`loadIcons`. Channel values are expanded to 8-bit by shifting the
|
||
5-bit components left by 3. `export-icon --palette NAME` can replace the HSL hue
|
||
of colored palette entries while preserving saturation and lightness; `NAME` is a
|
||
preset (`RED`, `GREEN`, `BLUE`, `YELLOW`, `PURPLE`, `ORANGE`) or a saturated
|
||
`#RRGGBB` hex color, and transparent entries, exact black/white, and achromatic
|
||
grays remain unchanged. Its optional `--frame circle` scales the icon down so the
|
||
whole square fits inside the canvas's inscribed circle, centers it on transparent
|
||
padding, and then applies a binary circular alpha mask, so no icon pixel is
|
||
cropped and no border is drawn.
|
||
|
||
### Imports
|
||
|
||
`import` copies a save's directory frames and data blocks from a source card
|
||
into the target's first free slots in slot order. Link bytes are relinked
|
||
(`0x51` initial, `0x52` middle, `0x53` end, `0xFF`-terminated), the initial
|
||
frame's size field is recomputed as `blocks * 8192`, and each touched frame's
|
||
XOR checksum is regenerated. Imports from a deleted source slot are normalized
|
||
to active link types. The updated card is re-parsed before it replaces the
|
||
target.
|
||
|
||
## References
|
||
|
||
- [MemcardRex](https://github.com/ShendoXT/memcardrex): `MemcardRex.Core/ps1card.cs`
|
||
(`getGmeHeader`, `OpenMemoryCard`, `calculateXOR`, `loadStringData`,
|
||
`FindSaveLinks`, `loadPalette`, `loadIcons`) and `MemcardRex.Core/Hardware/DexDrive.cs` (128-byte frame
|
||
protocol and XOR framing on the wire).
|
||
- The corresponding JAPS1ME implementations: `src/gme.rs`, `src/ps1/`.
|