{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://multiplayereverything.com/community-mod.schema.json",
  "title": "multiplayereverything.json",
  "description": "The manifest a mod's maker puts on their GitHub release, next to the mod's file. It tells the launcher how the mod installs (mod), or what the Flash game is (flash). The website checks it at approval and pins the release; the launcher checks it again before it installs anything. Any key not listed here is refused.",
  "type": "object",
  "additionalProperties": false,
  "required": ["format", "repo"],
  "oneOf": [{ "required": ["mod"] }, { "required": ["flash"] }],
  "properties": {
    "format": { "const": 1 },
    "repo": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9._-]{1,100}$", "description": "owner/name of the repository the release is in." },
    "flash": {
      "type": "object",
      "description": "A Flash game (launcher 1.0.43). The launcher makes its buttons (Play online, rooms up to maxPlayers, host and join by IP, the offline button) and starts Ruffle Together with the game file and its key map.",
      "additionalProperties": false,
      "required": ["game", "file", "keys", "maxPlayers"],
      "properties": {
        "game": { "type": "string", "minLength": 1, "maxLength": 80, "description": "The game's name: the card's title." },
        "file": { "type": "string", "pattern": "^[A-Za-z0-9._+-]{1,120}\\.[sS][wW][fF]$", "description": "The .swf file in your release, by name." },
        "keys": { "type": "string", "pattern": "^([a-z0-9][a-z0-9-]{0,47}|[A-Za-z0-9._+-]{1,120}\\.[kK][eE][yY][sS])$", "description": "A key map Ruffle Together has built in (gun-mayhem-2), or the .keys file in your release, by name." },
        "maxPlayers": { "type": "integer", "minimum": 2, "maximum": 6, "description": "How many play online." },
        "offline": {
          "type": "object",
          "additionalProperties": false,
          "description": "The offline button. {} is Play offline (one player); local: true is Play on one keyboard.",
          "properties": {
            "local": { "type": "boolean" },
            "keys": { "description": "Another built-in key map for it, or false for none.", "anyOf": [{ "const": false }, { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,47}$" }] },
            "label": { "type": "string", "maxLength": 40 },
            "description": { "type": "string", "maxLength": 120 }
          }
        },
        "ruffle": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)*$", "maxLength": 20, "description": "The oldest Ruffle Together the game runs in, such as 1.0.3.7." }
      }
    },
    "mod": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "game", "components", "launch"],
      "properties": {
        "id": { "type": "string", "pattern": "^[a-z0-9-]{1,48}$", "description": "Lowercase letters, digits and dashes. Must be your mod's id on the website." },
        "publisher": { "type": "string", "maxLength": 80 },
        "website": { "type": "string", "pattern": "^https://github\\.com/[^/]+/[^/]+$", "description": "https://github.com/owner/name, the same repository." },
        "game": {
          "type": "object",
          "additionalProperties": false,
          "required": ["name"],
          "properties": {
            "name": { "type": "string", "minLength": 1, "maxLength": 120 },
            "executable": { "type": "string", "pattern": "^[^/\\\\:$]*$", "description": "A file name, no folder." },
            "steamAppId": { "type": "string", "pattern": "^[0-9]*$" },
            "validationFiles": { "type": "array", "maxItems": 30, "items": { "type": "string", "maxLength": 300 } },
            "processNames": { "type": "array", "maxItems": 30, "items": { "type": "string", "maxLength": 300 } },
            "detection": {
              "type": "array",
              "maxItems": 10,
              "items": {
                "oneOf": [
                  { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "steam" }, "appId": { "type": "string" }, "installDir": { "type": "string" }, "subPath": { "type": "string", "minLength": 1, "pattern": "^[^$:/\\\\][^$:]*$", "description": "A folder inside the game's Steam folder, for example Atlas\\Binaries\\Win64. No .. and no drive." } } },
                  { "type": "object", "additionalProperties": false, "required": ["type"], "properties": { "type": { "const": "path" }, "path": { "type": "string" } } }
                ]
              }
            }
          }
        },
        "install": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "targetDir": { "type": "string", "pattern": "^\\$\\{GAME_DIR\\}", "description": "Must start with ${GAME_DIR} and never climb out of it." },
            "stateDir": { "type": "string", "pattern": "^\\$\\{(GAME_DIR|INSTALL_DIR)\\}", "description": "Where the launcher keeps its install record: inside ${GAME_DIR} or ${INSTALL_DIR}, never climbing out. The default is ${INSTALL_DIR}\\.installer." },
            "preserve": { "type": "array", "maxItems": 50, "items": { "type": "string", "maxLength": 300 } },
            "requires": {
              "type": "array",
              "maxItems": 20,
              "items": { "type": "object", "additionalProperties": false, "properties": { "path": { "type": "string", "pattern": "^[^$:]+$", "description": "A path inside the game's folder, without ${GAME_DIR}: BepInEx\\core, MelonLoader." }, "message": { "type": "string" } } }
            },
            "uninstallCleanup": { "type": "array", "maxItems": 50, "items": { "type": "string", "maxLength": 300 } },
            "uninstallCleanupWith": { "type": "string", "description": "The id of one of the components (the loader). uninstallCleanup is then removed only together with that component." }
          }
        },
        "components": {
          "type": "array",
          "minItems": 1,
          "maxItems": 30,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "source": { "type": "string", "minLength": 1, "description": "A path inside your release's file, or the file itself. No variables, no drive letters, no climbing out." },
              "target": { "type": "string", "pattern": "^\\$\\{(GAME_DIR|INSTALL_DIR)\\}", "description": "Must start with ${GAME_DIR} or ${INSTALL_DIR}." },
              "targetName": { "type": "string" },
              "required": { "type": "boolean" },
              "skipIfPresent": { "type": "array", "minItems": 1, "maxItems": 10, "items": { "type": "string", "pattern": "^[^$:/\\\\][^$:]*$", "description": "A path inside the game's folder, like BepInEx/core. The component is left out when the game already has it." } }
            }
          }
        },
        "launch": {
          "type": "object",
          "additionalProperties": false,
          "description": "Exactly one of uri and executable.",
          "oneOf": [{ "required": ["uri"] }, { "required": ["executable"] }],
          "properties": {
            "uri": { "type": "string", "pattern": "^steam://rungameid/([0-9]+|\\$\\{STEAM_APP_ID\\})$" },
            "executable": { "type": "string", "pattern": "^\\$\\{GAME_DIR\\}\\\\[^$:/]+\\.[eE][xX][eE]$", "description": "A program in the game's folder: ${GAME_DIR}\\Program.exe." },
            "hint": { "type": "string" }
          }
        },
        "package": {
          "type": "object",
          "additionalProperties": false,
          "required": ["extras"],
          "description": "A loader that comes from a second release of this same repository. The website pins its file and checksum at approval. Components read it as <dir>/<path>.",
          "properties": {
            "extras": {
              "type": "array",
              "minItems": 1,
              "maxItems": 3,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["dir", "tag", "asset"],
                "properties": {
                  "dir": { "type": "string", "pattern": "^[^$:/\\\\][^$:]*$", "description": "The folder the loader unpacks into inside the package, like loader." },
                  "tag": { "type": "string", "pattern": "^[A-Za-z0-9._+-]{1,120}$", "description": "The tag of the loader's release in your repository." },
                  "asset": { "type": "string", "pattern": "^[A-Za-z0-9._+-]{1,120}$", "description": "The file in that release." }
                }
              }
            }
          }
        }
      }
    }
  }
}
