JAPS1ME/docs/FORMATS.md

121 lines
5.6 KiB
Markdown
Raw Permalink Normal View History

2026-10-12 17:00:28 -04:00
# 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`)
2026-10-12 17:00:28 -04:00
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
2026-10-12 17:00:28 -04:00
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.
2026-10-12 17:00:28 -04:00
### 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.
2026-10-12 17:00:28 -04:00
## 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
2026-10-12 17:00:28 -04:00
protocol and XOR framing on the wire).
- The corresponding JAPS1ME implementations: `src/gme.rs`, `src/ps1/`.