{"id":"33b8adbe869eab63145026a9d206f832fd8160407b4a2810ee8cf4fdc2169957","pubkey":"66675158e6338fe89fda418e42a0bf2a7a2b132504dd347f015a18971b644430","created_at":1783164835,"kind":30817,"tags":[["d","playstation-1-memory-cards"],["title","PlayStation 1 Memory Cards"],["alt","Nostr Implementation Possibility: PlayStation 1 Memory Cards"],["k","38192","playstation 1 memory card block event"],["client","NostrHub"]],"content":"PlayStation 1 Memory Cards\n--------------------------\n\n`draft` `optional`\n\nThis NIP describes a method by which Sony PlayStation (PS1) memory cards can be\nstored, synchronized, versioned and shared over Nostr. A card is split into its\nnative 8 KB blocks, and each block is published as one addressable event, so a\ncard can be reconstructed, updated block-by-block, and rendered by clients\nwithout parsing the binary.\n\n## Motivation\n\nA PS1 memory card is a small, fixed-shape binary artifact: 128 KB, divided into\n16 blocks of 8192 bytes. Block 0 holds the header and directory; blocks 1–15\neach hold one save. Emulators read and write the card as a flat file.\n\nPublishing at block granularity means an in-game save touches only the events\nfor the blocks it occupies, events stay small, and updates are local. Using an\naddressable event per block gives free \"latest wins\" versioning: re-publishing a\nblock after a save overwrites the previous one, and consumers converge on the\nnewest state.\n\n## Block events\n\nA block is published as an _addressable_ event (as defined in\n[NIP-01](01.md)) of kind `38192`. A relay retains only the most recent event\nfor each `(kind, pubkey, d)` tuple, so a block is updated in place by\nre-publishing it.\n\nThe `content` field is exactly one block: **16384 lowercase hexadecimal\ncharacters** encoding the block's 8192 bytes, with no `0x` prefix, whitespace or\nseparators. It is the raw block image, byte-for-byte as it appears on the card\nat offset `block × 8192`.\n\n### Cards and the `d` tag\n\nEvery card has a **card id**: a stable, author-chosen identifier (e.g. `main`,\n`zelda-run`) scoped to the author's pubkey. A single pubkey MAY own several\ncards, distinguished by card id.\n\nThe card id is carried verbatim in the indexable `m` tag, and the `d` tag is the\ncard id joined to the block index by a hyphen:\n\n```\n[\"m\", \"<card-id>\"]\n[\"d\", \"<card-id>-<block>\"]\n```\n\n- `<card-id>` — the card id; the exact value of the `m` tag.\n- `<block>` — the block index, a decimal integer `0`–`15`.\n\nThe block index is the substring after the **last** `-`; the card id is\neverything before it. A card id therefore MUST match `^[^ ]+$` and MUST NOT end\nwith `-<digits>`. Block `0` is the card header/directory; blocks `1`–`15` hold\nsaves.\n\n### Tags\n\n| Tag        | Role                                                                                          | Required                        |\n| ---------- | --------------------------------------------------------------------------------------------- | ------------------------------- |\n| `d`        | Addressable identifier, `<card-id>-<block>`.                                                   | Yes                             |\n| `m`        | Card id, indexable so a whole card is fetched with `#m`.                                       | Yes                             |\n| `block`    | Block index, decimal `0`–`15`.                                                                 | Yes                             |\n| `x`        | SHA-256 (hex) of the block's 8192 raw bytes, for integrity.                                    | Recommended                     |\n| `state`    | Block allocation state: `header`, `first`, `middle`, `last` or `free`.                         | Recommended                     |\n| `alt`      | Human-readable summary of the block, per [NIP-31](31.md).                                      | Recommended                     |\n| `name`     | Human-friendly display name for the card.                                                      | No                              |\n| `title`    | Save title shown by the BIOS, decoded to UTF-8 (see below).                                    | No                              |\n| `filename` | Directory filename / product code of the save (e.g. `BASCUS-00001SOFTCARD`).                   | No                              |\n| `region`   | `America`, `Europe` or `Japan`.                                                                | No                              |\n| `icon`     | Base64-encoded rendering of the save's 16×16 icon, for previews.                               | No                              |\n\n`m` and `block` duplicate information present in `d`, but let relays and clients\nfilter and index without parsing the `d` string. Because relays index\nsingle-letter tags, `{\"kinds\":[38192],\"authors\":[\"<pubkey>\"],\"#m\":[\"<card-id>\"]}`\nreturns exactly one card.\n\n`state`, `title`, `filename` and `region` are all derivable from the block\nbytes; they are mirrored into tags so clients can list saves without decoding\nthe binary. Publishers SHOULD set them for blocks whose directory entry is known\n(typically by reading block 0). Consumers MUST treat the binary `content` as\nauthoritative and these tags as advisory.\n\n`title` is stored in the card's SC header in Shift-JIS. Publishers SHOULD decode\nit to UTF-8. A title composed entirely of full-width Latin characters (a common\nway games render English, e.g. `ＳＰＹＲＯ　ＴＨＥ　ＤＲＡＧＯＮ`) SHOULD be folded to\nhalf-width (`SPYRO THE DRAGON`); a title containing kana or kanji SHOULD be left\nas decoded.\n\n`region` MAY be derived from the second character of `filename` (`I`→Japan,\n`A`→America, `E`→Europe) or from the product-code prefix (`SLUS`/`SCUS`→America,\n`SLES`/`SCES`→Europe, `SLPS`/`SLPM`/`SCPS`→Japan).\n\n## Reconstructing a card\n\nTo rebuild the 131072-byte card image for a given author and card id:\n\n1. Fetch `{\"kinds\":[38192],\"authors\":[\"<pubkey>\"],\"#m\":[\"<card-id>\"]}`.\n2. Start from a 131072-byte buffer that is a freshly *formatted* card (valid\n   header and free directory), so that unpublished blocks read as empty rather\n   than as zeroes.\n3. For each event, take the block index from the `block` tag (or the tail of\n   `d`), decode the hex `content`, and copy the 8192 bytes to offset\n   `block × 8192`.\n4. If an `x` tag is present, verify the SHA-256 of the decoded bytes and reject\n   the block on mismatch.\n\nPublishers SHOULD publish block `0` (its directory is what makes the card valid)\nalong with every in-use block. Free blocks MAY be omitted.\n\nA save spanning several blocks marks its parts with `state` (`first`, then\n`middle`, then `last`); the block chain itself is recorded in the binary\ndirectory in block 0, which is authoritative.\n\n## Synchronization\n\nBecause block events are addressable, the current state of a block is simply its\nlatest event. A host that owns the card file can watch for external writes (an\nemulator saving a game) and re-publish each changed block, and a peer can\nsubscribe to the same filter and overlay incoming blocks onto its local card.\nConflicts resolve per block by `created_at` under the relay's replaceable-event\nrules — last writer wins — which suits a single player moving a card between\nmachines rather than simultaneous play.\n\n## Examples\n\nBlock 0 — the card header/directory:\n\n```jsonc\n{\n  \"kind\": 38192,\n  \"content\": \"4d43000000000000...<16384 hex chars total>...\",\n  \"tags\": [\n    [\"d\", \"main-0\"],\n    [\"m\", \"main\"],\n    [\"block\", \"0\"],\n    [\"state\", \"header\"],\n    [\"x\", \"9f2c...<sha256>\"],\n    [\"alt\", \"PS1 memory card 'main' — header/directory block\"]\n  ]\n}\n```\n\nBlock 1 — a single-block save:\n\n```jsonc\n{\n  \"kind\": 38192,\n  \"content\": \"5343110153...<16384 hex chars total>...\",\n  \"tags\": [\n    [\"d\", \"main-1\"],\n    [\"m\", \"main\"],\n    [\"block\", \"1\"],\n    [\"state\", \"first\"],\n    [\"title\", \"Software Defined Card\"],\n    [\"filename\", \"BASCUS-00001SOFTCARD\"],\n    [\"region\", \"America\"],\n    [\"x\", \"3ab8...<sha256>\"],\n    [\"alt\", \"PS1 save 'Software Defined Card' (BASCUS-00001SOFTCARD)\"]\n  ]\n}\n```\n\n## Security considerations\n\nSave data is arbitrary game state; publishing it to relays makes it public. This\nNIP does not define encryption — memory cards are treated as public content. A\npublisher wanting privacy SHOULD keep those blocks off public relays and use a\ngeneral-purpose mechanism such as a [NIP-59](59.md) gift wrap, rather than\nencrypting the block event itself.\n\nThe `x` tag lets consumers detect corruption or truncation of the hex payload\nbefore feeding bytes to an emulator.\n\nCards are scoped per pubkey; a card is only as trustworthy as the key that\nsigned its blocks. Consumers MUST NOT merge blocks from different authors into a\nsingle card.","sig":"b7fdded536f0672ec2428324679042ab0449e0426c172262efad22c39b0fa4dff8e2a2e1dff805cdfed553ce0c06c479dcfe4fd9f42d9c03956694045577541b"}