JAPS1ME/docs/FORMATS.md

121 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/`.