Add files via upload
This commit is contained in:
35 files changed
+14482
No files matched your search
@@ -0,0 +1,188 @@
|
||||
# Switchnote `.sppm` format
|
||||
|
||||
`.sppm` is Switchnote Studio's own offline animation format. It is not a Nintendo `.ppm`
|
||||
file. All integers are unsigned little-endian unless stated otherwise. Readers
|
||||
must check every length before allocating or advancing. `include/sppm.hpp` and
|
||||
`source/sppm.cpp` are the reference implementation.
|
||||
|
||||
The Fakenote Studio repository supplies 1280×720 UI mockups and 640×360 sample
|
||||
artwork, with editor buttons for layers A through D. It does **not** specify a
|
||||
binary format or an exact ink palette. The payload uses a 640×360
|
||||
canvas, four indexed layers, and 256 editable RGBA palette entries. Entry 0 is
|
||||
transparent. The four layers composite D, C, B, A from back to front; pattern
|
||||
brushes write ordinary palette indices in their chosen pattern.
|
||||
|
||||
## Header
|
||||
|
||||
| Offset | Size | Value |
|
||||
| ---: | ---: | --- |
|
||||
| 0 | 4 | ASCII `SPPM` |
|
||||
| 4 | 2 | Payload version `2` (`1` remains readable) |
|
||||
| 6 | 2 | Header size `36` |
|
||||
| 8 | 8 | Total file byte length |
|
||||
| 16 | 4 | CRC-32/IEEE of bytes from offset 36 to end |
|
||||
| 20 | 2 | Canvas width `640` |
|
||||
| 22 | 2 | Canvas height `360` |
|
||||
| 24 | 1 | Layer count `4` |
|
||||
| 25 | 2 | Palette count `256` |
|
||||
| 27 | 1 | Playback FPS, 1–30 |
|
||||
| 28 | 1 | Flags: bit 0 locked, bit 1 allows spin-offs, bit 2 BGM follows animation speed, bit 3 stops after the final frame (clear = loop) |
|
||||
| 29 | 4 | Frame count, 1–9999 |
|
||||
| 33 | 1 | Audio track count, 0–4 |
|
||||
| 34 | 2 | Reserved zero |
|
||||
|
||||
Maximum file size is 512 MiB. The CRC protects against accidental damage;
|
||||
it is not an authentication tag.
|
||||
|
||||
## Legacy signed v2 envelope
|
||||
|
||||
Legacy files may use the binary/RLE payload with the header version set to `2`,
|
||||
followed by the four-byte ASCII marker `SPPS` and a 32-byte HMAC-SHA-256 tag.
|
||||
The tag covers the complete v2 payload, including its header, metadata, frames
|
||||
and audio. Readers verify the tag in constant time before decoding the payload.
|
||||
The HMAC key is retained in an encrypted, console-bound `identity-v2-*.bin`
|
||||
record. This is a legacy local-file integrity scheme; current app saves use the
|
||||
publicly verifiable Ed25519-signed v3 envelope below. Neither scheme is
|
||||
Nintendo's signature system or proof of a real-world creator identity.
|
||||
Readers accept both old v1 and current v2 payloads inside an authenticated
|
||||
envelope.
|
||||
`.sppm` is a compact binary encoding with RLE layers, not plaintext and not an
|
||||
encrypted format.
|
||||
|
||||
## Public v3 signatures
|
||||
|
||||
Gallery files use the same SPPM payload with its marker set to version `3`,
|
||||
followed by `SPP3`, public author/original-author/editor identities, and a
|
||||
64-byte Ed25519 signature. The signed digest covers the complete payload and
|
||||
all three identities. The server validates the signature and checks each
|
||||
available FSID against its included public identity. This is self-certifying:
|
||||
it proves that the included editor key signed those bytes, not that the key
|
||||
belongs to a Nintendo account or a verified human.
|
||||
|
||||
The Ed25519 secret is encrypted at rest with XSalsa20-Poly1305 and a wrapping
|
||||
key derived from system-specific console material. On CFW, this material is
|
||||
available to homebrew and is not a hardware-backed, non-exportable secret; a
|
||||
compromised console can still expose its signing key. The protection is
|
||||
intended to make a copied SD identity file unusable on an ordinary different
|
||||
console, not to resist a compromised device.
|
||||
|
||||
## Palette and metadata
|
||||
|
||||
Immediately after the header are 256 RGBA entries, each four bytes in R, G,
|
||||
B, A order. The next five fields are UTF-8 strings with `u16 byte length` then
|
||||
that many bytes: title (at most 256 bytes), description (4096), author name
|
||||
(128), original author name (128), and last editor name (128). NUL bytes are
|
||||
forbidden. The reader rejects malformed UTF-8 and preserves valid bytes;
|
||||
the frontend is responsible for display and editing.
|
||||
|
||||
The default drawing palette uses pink at index 6 and purple at index 10.
|
||||
When reading older SPPMs, the exact opaque legacy colors `(92,224,36)` and
|
||||
`(26,40,72)` are remapped to `(239,68,133)` and `(132,66,183)`. Palette indices
|
||||
are unchanged; saving writes the migrated colors back into the SPPM.
|
||||
|
||||
Six IDs follow, each exactly 16 uppercase hexadecimal ASCII characters:
|
||||
|
||||
1. Author FSID, the current Switchnote creator.
|
||||
2. Original author FSID, retained through all spin-offs.
|
||||
3. Last editor FSID.
|
||||
4. Current note ID, newly allocated for a spin-off.
|
||||
5. Root note ID, retained through all spin-offs.
|
||||
6. Parent note ID, the immediate source Switchnote. Sixteen ASCII zeroes encode
|
||||
an absent parent on an original note. Zeroes are never a valid allocated ID.
|
||||
|
||||
The metadata ends with creation time (`u64` Unix seconds UTC), modification
|
||||
time (`u64` Unix seconds UTC), and cover frame index (`u32`, zero-based).
|
||||
Original notes have `currentId == rootId`, no parent, and author FSID equal to
|
||||
original author FSID. A spin-off must have a new current ID different from its
|
||||
parent and root IDs. Editing changes the last editor and modification time;
|
||||
the original author and root ID do not change.
|
||||
|
||||
## Frames
|
||||
|
||||
Frames appear sequentially. Each frame contains four layer records in A–D
|
||||
order. A layer record has a codec byte (`1` for RLE), a `u32` compressed byte
|
||||
length, a `u32` CRC-32/IEEE of the compressed bytes, then the compressed data.
|
||||
Decoded data is exactly 230,400 row-major `u8` palette indices. The decoder
|
||||
rejects output beyond that size, truncated packets, unknown codecs, and excess
|
||||
data. A compressed layer may contain at most 460,800 bytes.
|
||||
|
||||
RLE packets have a one-byte command. Bit 7 set means repeat the following
|
||||
single palette index `(command & 0x7F) + 1` times. Bit 7 clear means copy that
|
||||
many literal palette indices from the following bytes. Both packet types allow
|
||||
lengths 1–128. The writer uses repeat packets for runs of at least three.
|
||||
|
||||
## Audio
|
||||
|
||||
Audio track 0 is background music, starting at frame 0. Tracks 1–3 are sound
|
||||
effects with frame cues. Tracks are stored in order and each record contains:
|
||||
|
||||
| Field | Encoding |
|
||||
| --- | --- |
|
||||
| Track index | `u8`, zero-based |
|
||||
| Channels | `u8`, mono `1` or stereo `2` |
|
||||
| BGM reference FPS | `u8`, 1–30 for BGM (or 0 to use note speed); zero for SE. Added in payload v2. |
|
||||
| Sample rate | `u32`, 8000–48000 Hz |
|
||||
| Name | `u16` byte length + up to 64 UTF-8 bytes |
|
||||
| Trigger count | `u16` |
|
||||
| Trigger frames | That many `u16` zero-based frame indices, strictly increasing |
|
||||
| PCM sample count | `u32`, counts interleaved samples, not stereo pairs |
|
||||
| PCM CRC | `u32` CRC-32/IEEE of sample bytes |
|
||||
| PCM | Signed 16-bit little-endian interleaved samples |
|
||||
|
||||
BGM has no triggers. A sound effect may be cued multiple times. Every cue must
|
||||
refer to an existing frame. Total decoded PCM is limited to 32 Mi samples.
|
||||
To store SE without music, make track 0 an empty BGM entry with a valid rate
|
||||
and channel count.
|
||||
|
||||
With the BGM speed flag set, playback rate is `current FPS / reference FPS`:
|
||||
changing animation speed changes music tempo and pitch together. With the flag
|
||||
clear, BGM keeps its imported timing. SE tracks always play at their recorded
|
||||
speed. The v1 payload defaults to speed-linked BGM.
|
||||
The playback-loop flag defaults to looping for older files. When set, playback
|
||||
stops all Switchnote tracks at the end of the final frame; when clear, frame zero
|
||||
and its BGM/SE cues start together on each loop.
|
||||
|
||||
## IDs and spin-offs
|
||||
|
||||
Lineage IDs are **64-bit pseudonymous identifiers**, generated as the first
|
||||
eight HMAC-SHA-256 digest bytes rendered in uppercase hex. The HMAC key is a
|
||||
32-byte per-install secret generated with the platform cryptographic random
|
||||
generator and kept in the encrypted console-local identity record. The
|
||||
message is:
|
||||
|
||||
```
|
||||
ASCII("FlipnoteNX-FSID-v1") || 00 || ASCII(domain) || 00 || 16-byte nonce
|
||||
```
|
||||
|
||||
This historical domain string is retained to keep existing Switchnote identities, signatures, and saved files compatible.
|
||||
|
||||
The portable core exposes `deriveFsid(secret, nonce, domain)` so the Switch
|
||||
frontend can provide randomness through libnx. It uses a fresh random nonce
|
||||
for each new note ID. Creator FSIDs are the first eight SHA-256 bytes of a
|
||||
domain-separated Ed25519 public key and a 16-byte one-way device tag. These
|
||||
values are still 16 hexadecimal characters (64 bits): an FSID is a public
|
||||
identifier, not a password or proof of authorship.
|
||||
|
||||
Gallery coin and comment writes require a random, single-use, 30-second server
|
||||
challenge signed with the console's Ed25519 key. The server verifies the
|
||||
signature, FSID/key binding, request method/path/body, and requesting IP. Coin
|
||||
limits and poster ownership use the full 256-bit signing-key fingerprint,
|
||||
independent of the shortened FSID and public device tag. Gallery HTTP is LAN-only and does not encrypt public data in
|
||||
transit; do not expose the development service to the public internet.
|
||||
|
||||
Creating a spin-off is an explicit operation. It requires the source to allow
|
||||
spin-offs and not be locked. It copies frames, palette, and audio; keeps the
|
||||
root note ID and original author credit; sets parent ID to the source current
|
||||
ID; allocates a new current ID; sets the new author and last editor; resets
|
||||
creation/modification time; and makes the child editable. The child may be
|
||||
saved independently without modifying the parent.
|
||||
|
||||
## Safe writing
|
||||
|
||||
Signed saves serialize and validate the complete document, write a sibling
|
||||
temporary file, then replace the target. If the SD filesystem refuses to
|
||||
replace an existing file by rename, the old file is moved to a unique sibling
|
||||
backup, the new file is installed, and the old file is restored if installation
|
||||
fails. Signed loads authenticate the envelope before replacing the caller's
|
||||
document; legacy loads still validate all bounds, IDs, frame data, CRCs, and
|
||||
metadata.
|
||||
Reference in new issue
Block a user
