30817:playstation-1-memory-cards
PlayStation 1 Memory Cards
- signed by
- npub1ven4z...sxddfh
- published
- 2026-07-04
- event
- 33b8adbe86...9957
PlayStation 1 Memory Cards
draft optional
This NIP describes a method by which Sony PlayStation (PS1) memory cards can be stored, synchronized, versioned and shared over Nostr. A card is split into its native 8 KB blocks, and each block is published as one addressable event, so a card can be reconstructed, updated block-by-block, and rendered by clients without parsing the binary.
Motivation
A PS1 memory card is a small, fixed-shape binary artifact: 128 KB, divided into 16 blocks of 8192 bytes. Block 0 holds the header and directory; blocks 1–15 each hold one save. Emulators read and write the card as a flat file.
Publishing at block granularity means an in-game save touches only the events for the blocks it occupies, events stay small, and updates are local. Using an addressable event per block gives free "latest wins" versioning: re-publishing a block after a save overwrites the previous one, and consumers converge on the newest state.
Block events
A block is published as an addressable event (as defined in
NIP-01) of kind 38192. A relay retains only the most recent event
for each (kind, pubkey, d) tuple, so a block is updated in place by
re-publishing it.
The content field is exactly one block: 16384 lowercase hexadecimal
characters encoding the block's 8192 bytes, with no 0x prefix, whitespace or
separators. It is the raw block image, byte-for-byte as it appears on the card
at offset block × 8192.
Cards and the d tag
Every card has a card id: a stable, author-chosen identifier (e.g. main,
zelda-run) scoped to the author's pubkey. A single pubkey MAY own several
cards, distinguished by card id.
The card id is carried verbatim in the indexable m tag, and the d tag is the
card id joined to the block index by a hyphen:
["m", "<card-id>"]
["d", "<card-id>-<block>"]
<card-id>— the card id; the exact value of themtag.<block>— the block index, a decimal integer0–15.
The block index is the substring after the last -; the card id is
everything before it. A card id therefore MUST match ^[^ ]+$ and MUST NOT end
with -<digits>. Block 0 is the card header/directory; blocks 1–15 hold
saves.
Tags
| Tag | Role | Required |
|---|---|---|
d |
Addressable identifier, <card-id>-<block>. |
Yes |
m |
Card id, indexable so a whole card is fetched with #m. |
Yes |
block |
Block index, decimal 0–15. |
Yes |
x |
SHA-256 (hex) of the block's 8192 raw bytes, for integrity. | Recommended |
state |
Block allocation state: header, first, middle, last or free. |
Recommended |
alt |
Human-readable summary of the block, per NIP-31. | Recommended |
name |
Human-friendly display name for the card. | No |
title |
Save title shown by the BIOS, decoded to UTF-8 (see below). | No |
filename |
Directory filename / product code of the save (e.g. BASCUS-00001SOFTCARD). |
No |
region |
America, Europe or Japan. |
No |
icon |
Base64-encoded rendering of the save's 16×16 icon, for previews. | No |
m and block duplicate information present in d, but let relays and clients
filter and index without parsing the d string. Because relays index
single-letter tags, {"kinds":[38192],"authors":["<pubkey>"],"#m":["<card-id>"]}
returns exactly one card.
state, title, filename and region are all derivable from the block
bytes; they are mirrored into tags so clients can list saves without decoding
the binary. Publishers SHOULD set them for blocks whose directory entry is known
(typically by reading block 0). Consumers MUST treat the binary content as
authoritative and these tags as advisory.
title is stored in the card's SC header in Shift-JIS. Publishers SHOULD decode
it to UTF-8. A title composed entirely of full-width Latin characters (a common
way games render English, e.g. SPYRO THE DRAGON) SHOULD be folded to
half-width (SPYRO THE DRAGON); a title containing kana or kanji SHOULD be left
as decoded.
region MAY be derived from the second character of filename (I→Japan,
A→America, E→Europe) or from the product-code prefix (SLUS/SCUS→America,
SLES/SCES→Europe, SLPS/SLPM/SCPS→Japan).
Reconstructing a card
To rebuild the 131072-byte card image for a given author and card id:
- Fetch
{"kinds":[38192],"authors":["<pubkey>"],"#m":["<card-id>"]}. - Start from a 131072-byte buffer that is a freshly formatted card (valid header and free directory), so that unpublished blocks read as empty rather than as zeroes.
- For each event, take the block index from the
blocktag (or the tail ofd), decode the hexcontent, and copy the 8192 bytes to offsetblock × 8192. - If an
xtag is present, verify the SHA-256 of the decoded bytes and reject the block on mismatch.
Publishers SHOULD publish block 0 (its directory is what makes the card valid)
along with every in-use block. Free blocks MAY be omitted.
A save spanning several blocks marks its parts with state (first, then
middle, then last); the block chain itself is recorded in the binary
directory in block 0, which is authoritative.
Synchronization
Because block events are addressable, the current state of a block is simply its
latest event. A host that owns the card file can watch for external writes (an
emulator saving a game) and re-publish each changed block, and a peer can
subscribe to the same filter and overlay incoming blocks onto its local card.
Conflicts resolve per block by created_at under the relay's replaceable-event
rules — last writer wins — which suits a single player moving a card between
machines rather than simultaneous play.
Examples
Block 0 — the card header/directory:
{
"kind": 38192,
"content": "4d43000000000000...<16384 hex chars total>...",
"tags": [
["d", "main-0"],
["m", "main"],
["block", "0"],
["state", "header"],
["x", "9f2c...<sha256>"],
["alt", "PS1 memory card 'main' — header/directory block"]
]
}
Block 1 — a single-block save:
{
"kind": 38192,
"content": "5343110153...<16384 hex chars total>...",
"tags": [
["d", "main-1"],
["m", "main"],
["block", "1"],
["state", "first"],
["title", "Software Defined Card"],
["filename", "BASCUS-00001SOFTCARD"],
["region", "America"],
["x", "3ab8...<sha256>"],
["alt", "PS1 save 'Software Defined Card' (BASCUS-00001SOFTCARD)"]
]
}
Security considerations
Save data is arbitrary game state; publishing it to relays makes it public. This NIP does not define encryption — memory cards are treated as public content. A publisher wanting privacy SHOULD keep those blocks off public relays and use a general-purpose mechanism such as a NIP-59 gift wrap, rather than encrypting the block event itself.
The x tag lets consumers detect corruption or truncation of the hex payload
before feeding bytes to an emulator.
Cards are scoped per pubkey; a card is only as trustworthy as the key that signed its blocks. Consumers MUST NOT merge blocks from different authors into a single card.
Discussion
Connect a key to comment.