#pragma once #include #include #include #include #include #include namespace sppm { constexpr std::uint16_t kWidth = 640; constexpr std::uint16_t kHeight = 360; constexpr std::size_t kPixelsPerLayer = static_cast(kWidth) * kHeight; constexpr std::size_t kLayerCount = 4; constexpr std::size_t kPaletteSize = 256; constexpr std::size_t kMaxFrames = 9999; constexpr std::size_t kMaxAudioTracks = 4; struct Color { std::uint8_t r = 0; std::uint8_t g = 0; std::uint8_t b = 0; std::uint8_t a = 255; }; using Palette = std::array; // Palette index 0 is transparent. Other entries can be changed by the editor. Palette defaultPalette(); struct Frame { // A, B, C, D from front to back; one palette index per 640x360 pixel. std::array, kLayerCount> layers; Frame(); }; struct AudioTrack { std::string name; // e.g. BGM, SE1, SE2, SE3 std::uint32_t sampleRate = 0; // 8000..48000 Hz std::uint8_t channels = 1; // 1 or 2 std::uint8_t referenceFps = 0; // BGM's native animation speed; 0 means use the note's speed std::uint8_t referenceSpeed = 0; // SPPM v3 BGM speed index (1..8); 0 means legacy fps std::vector frameTriggers; // SE cues; BGM always starts at frame 0 std::vector pcm; // interleaved signed 16-bit samples }; // A public identity is safe to put in an .sppm file. It contains an Ed25519 // verification key and a one-way, app-specific device tag; neither field // exposes a raw console identifier. struct PublicIdentity { std::array key{}; std::array deviceTag{}; }; struct SigningIdentity { PublicIdentity publicIdentity; std::array secretKey{}; // Present only to keep validating pre-v3 console-HMAC files after migration. std::array legacyHmacKey{}; }; struct Metadata { std::string title; std::string description; std::string authorName; std::string originalAuthorName; std::string lastEditorName; // Creator identities, not file IDs. Exactly 16 uppercase hexadecimal chars. std::string authorFsid; std::string originalAuthorFsid; std::string lastEditorFsid; // Present for v3 public-key-signed files. Zero values indicate a legacy // author record which cannot be retrospectively cryptographically bound. PublicIdentity authorIdentity; PublicIdentity originalAuthorIdentity; PublicIdentity lastEditorIdentity; // File lineage IDs. parentId is empty only for an original Switchnote. std::string currentId; std::string rootId; std::string parentId; std::uint64_t createdUnixSeconds = 0; std::uint64_t modifiedUnixSeconds = 0; std::uint32_t coverFrame = 0; std::uint8_t fps = 12; std::uint8_t speed = 0; // SPPM v3 DSi speed index (1..8); 0 means legacy fps bool locked = false; bool allowSpinoffs = true; bool bgmFollowsSpeed = true; bool loopPlayback = true; }; struct Document { Metadata metadata; Palette palette = defaultPalette(); std::vector frames; std::vector audioTracks; }; bool isValidFsid(std::string_view fsid); bool hasPublicIdentity(const PublicIdentity& identity); std::string deriveFsid(const PublicIdentity& identity); std::string identityFingerprint(const PublicIdentity& identity); std::array deriveDeviceTag(const std::vector& deviceMaterial); std::array deriveDeviceWrapKey(const std::vector& deviceMaterial, const std::array& salt); bool createSigningIdentity(const std::array& deviceTag, SigningIdentity& identity, std::string& error); bool sealSigningSecret(const SigningIdentity& identity, const std::array& wrapKey, const std::array& nonce, std::array& sealed, std::string& error); bool unsealSigningSecret(const std::array& sealed, const std::array& wrapKey, const std::array& nonce, SigningIdentity& identity, std::string& error); // Domain-separated 64-bit ID helper used for non-identity note and lineage IDs. // Creator FSIDs are instead derived from PublicIdentity. std::string deriveFsid(const std::array& secret, const std::array& nonce, std::string_view domain); bool validate(const Document& document, std::string& error); double frameRate(const Metadata& metadata); bool serialize(const Document& document, std::vector& bytes, std::string& error); bool deserialize(const std::vector& bytes, Document& document, std::string& error); bool loadFile(const std::string& path, Document& document, std::string& error); bool saveFile(const std::string& path, const Document& document, std::string& error); // Console-local authenticated .sppm envelopes. Legacy v1 notes remain readable; // new saves include a 256-bit HMAC-SHA-256 footer verified with the local key. bool deserializeSigned(const std::vector& bytes, Document& document, const std::array& signingKey, std::string& error); bool loadSignedFile(const std::string& path, Document& document, const std::array& signingKey, std::string& error); bool saveSignedFile(const std::string& path, const Document& document, const std::array& signingKey, std::string& error); // v3 is publicly verifiable: no console-local secret is needed to validate a // received file. It signs a domain-separated SHA-256 digest with Ed25519. bool savePublicSignedFile(const std::string& path, const Document& document, const SigningIdentity& identity, std::string& error); bool signMessage(const SigningIdentity& identity, const std::uint8_t* message, std::size_t messageSize, std::array& signature, std::string& error); // Preserves the original author and root ID, while giving the fork a new // current ID, parent ID, author, creation time and editable state. bool createSpinoff(const Document& parent, std::string_view newAuthorName, std::string_view newAuthorFsid, std::string_view newCurrentId, std::uint64_t nowUnixSeconds, Document& child, std::string& error); } // namespace sppm