{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://recomplauncher.com/schema/recomplauncher.json",
  "title": "recomplauncher.json",
  "description": "How RecompLauncher sets up, starts and configures a recomp. See docs/DEVELOPERS.md.",
  "type": "object",
  "required": [
    "format"
  ],
  "properties": {
    "inputs": {
      "type": "array",
      "maxItems": 32,
      "description": "Named game/disc/DLC inputs, explicit shared-key requirements and NVIDIA DLSS (a file the launcher downloads from NVIDIA). See SETUP-INPUTS.md. No multidisc hashing, BIOS, firmware, font or system-library support.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,64}$"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "role": {
            "enum": [
              "game",
              "disc",
              "dlc",
              "update",
              "key",
              "runtime"
            ],
            "default": "game",
            "description": "runtime: a file the launcher fetches itself (only with shared nvidia.dlss)."
          },
          "optional": {
            "type": "boolean",
            "default": false
          },
          "shared": {
            "enum": [
              "switch.prod-keys",
              "switch.title-keys",
              "wiiu.common-key",
              "nvidia.dlss"
            ],
            "description": "Shared across games, kept once by the launcher. nvidia.dlss is downloaded from NVIDIA on Windows and Linux PCs with an NVIDIA RTX card (first in RecompLauncher 0.90 Beta 37); launchers from then on skip optional inputs with a shared type or role they don't know."
          },
          "destination": {
            "type": "string",
            "pattern": "^(?!/)(?!.*(?:\\\\|:|[{}]))(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//).+[^/]$",
            "description": "Optional installation-relative file path. No folders, absolute paths, parent components or placeholders."
          },
          "formats": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "enum": [
                    "file",
                    "folder"
                  ]
                },
                "extensions": {
                  "type": "array",
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9]+$"
                  }
                },
                "requires": {
                  "type": "array",
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{1,64}$"
                  },
                  "description": "IDs of required shared-key inputs in this manifest, activated only for this selected format."
                }
              },
              "additionalProperties": false
            }
          }
        },
        "allOf": [
          {
            "if": {
              "required": [
                "shared"
              ],
              "properties": {
                "shared": {
                  "const": "nvidia.dlss"
                }
              }
            },
            "then": {
              "required": [
                "role"
              ],
              "properties": {
                "role": {
                  "const": "runtime"
                }
              },
              "not": {
                "required": [
                  "formats"
                ]
              }
            }
          },
          {
            "if": {
              "required": [
                "shared"
              ],
              "properties": {
                "shared": {
                  "enum": [
                    "switch.prod-keys",
                    "switch.title-keys",
                    "wiiu.common-key"
                  ]
                }
              }
            },
            "then": {
              "required": [
                "role"
              ],
              "properties": {
                "role": {
                  "const": "key"
                },
                "optional": {
                  "const": false
                }
              },
              "not": {
                "required": [
                  "formats"
                ]
              }
            }
          },
          {
            "if": {
              "not": {
                "required": [
                  "shared"
                ]
              }
            },
            "then": {
              "required": [
                "formats"
              ],
              "properties": {
                "formats": {
                  "minItems": 1
                },
                "role": {
                  "enum": [
                    "game",
                    "disc",
                    "dlc",
                    "update"
                  ]
                }
              }
            }
          }
        ],
        "additionalProperties": false
      }
    },
    "$schema": {
      "type": "string"
    },
    "format": {
      "const": 1,
      "description": "Always 1."
    },
    "name": {
      "type": "string",
      "description": "Your recomp's name, as players see it. The launcher shows it for a build added from a folder (Developer tools); a listed recomp uses its name on recomplauncher.com."
    },
    "repo": {
      "type": "string",
      "description": "Your GitHub repo, owner/name. Only needed for profiles built into the launcher."
    },
    "game": {
      "type": "object",
      "description": "The player's own copy of the game.",
      "properties": {
        "kind": {
          "enum": [
            "iso",
            "folder",
            "iso_or_folder"
          ],
          "default": "iso"
        },
        "label": {
          "type": "string",
          "description": "What to ask for. Name the region or serial if only one works."
        },
        "check": {
          "type": "object",
          "description": "How to tell it's the right game.",
          "properties": {
            "file": {
              "type": "string",
              "description": "A file in the game's folder, like default.xex."
            },
            "xdvdfs": {
              "type": "string",
              "description": "A file on an Xbox 360 disc image."
            },
            "iso9660": {
              "type": "string",
              "description": "A file in the top folder of a PS2 (or any ISO 9660) disc image."
            },
            "nup_build_id": {
              "type": "string",
              "pattern": "^[0-9A-Fa-f]{8,64}$",
              "description": "The build ID of a Nintendo Switch or Switch 2 .nup package (hex, 8 to 64 digits; a prefix such as the first 8 is fine), read from the exefs/main of its newest title. A folder with exefs/main is checked the same way. Judged by the file's content, never its name. Needs no keys."
            },
            "ids": {
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^[A-Za-z0-9._-]{3,16}$"
              },
              "description": "The disc or cartridge IDs it reads, as on the copy: a GameCube or Wii disc's (GZ2E01, or GZ2 for every region), an N64 cartridge's game code, a PlayStation disc's serial. Another game's copy is refused, and the player's copies are recognised by it."
            }
          },
          "additionalProperties": false
        },
        "dumps": {
          "type": "array",
          "description": "Known good dumps (from Redump), for the verified badge.",
          "items": {
            "type": "object",
            "required": [
              "name",
              "crc32"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "Shown as 'Matches the verified <name> disc'."
              },
              "size": {
                "type": "integer",
                "minimum": 0
              },
              "crc32": {
                "type": "string",
                "pattern": "^[0-9a-fA-F]{8}$"
              }
            },
            "additionalProperties": false
          }
        },
        "update": {
          "type": "object",
          "description": "A second thing the game needs beside the player's copy, picked on its own (a title update, or a PS4 update folder kept apart from the game). The launcher puts it in the game's folder and keeps it across updates.",
          "required": [
            "into",
            "label"
          ],
          "properties": {
            "into": {
              "type": "string",
              "description": "The folder inside your release it goes in, like game/Content or update."
            },
            "label": {
              "type": "string",
              "description": "What to ask for, like 'Your title update (TU 1.03)'."
            },
            "accepts": {
              "enum": [
                "file",
                "folder",
                "file or folder"
              ],
              "default": "file",
              "description": "Whether the player picks a file, a folder, or either."
            },
            "optional": {
              "type": "boolean",
              "default": false,
              "description": "The game plays without it; the player can skip it."
            },
            "name": {
              "type": "string",
              "description": "The name it must have there (it's put in under this name)."
            },
            "sha256": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{64}$",
              "description": "The one known file: checked when it's picked."
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "start": {
      "type": "object",
      "required": [
        "program"
      ],
      "properties": {
        "program": {
          "$ref": "#/definitions/programs"
        },
        "args": {
          "$ref": "#/definitions/args"
        },
        "env": {
          "type": "object",
          "description": "Environment variables for your game, like SDL hints. Paths such as {install} fill in. RECOMPLAUNCHER_ names and system ones like PATH are left out.",
          "propertyNames": {
            "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
          },
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false
    },
    "prepare": {
      "type": "object",
      "description": "Your own setup step, run without a window after the copy is checked.",
      "required": [
        "program"
      ],
      "properties": {
        "program": {
          "$ref": "#/definitions/programs"
        },
        "args": {
          "$ref": "#/definitions/args"
        },
        "label": {
          "type": "string",
          "description": "Shown under 'Getting the extras'."
        },
        "timeout": {
          "type": "integer",
          "minimum": 1,
          "default": 300,
          "description": "Seconds."
        }
      },
      "additionalProperties": false
    },
    "config": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "format",
          "file"
        ],
        "properties": {
          "id": {
            "type": "string",
            "default": "main"
          },
          "format": {
            "enum": [
              "toml",
              "ini",
              "json"
            ]
          },
          "file": {
            "$ref": "#/definitions/places"
          },
          "game_path": {
            "type": "string",
            "description": "The key that holds the path to the player's copy."
          },
          "ensure": {
            "$ref": "#/definitions/values",
            "description": "Set when missing or empty."
          },
          "always": {
            "$ref": "#/definitions/values",
            "description": "Set on every start."
          },
          "new": {
            "$ref": "#/definitions/values",
            "description": "Set when the file is created."
          }
        },
        "additionalProperties": false
      }
    },
    "folders": {
      "type": "object",
      "description": "Named places. 'saves' and 'logs' are used by the launcher itself.",
      "additionalProperties": {
        "$ref": "#/definitions/places"
      }
    },
    "exit_codes": {
      "type": "object",
      "description": "Exit code (as a string) to the message shown to the player. \"ok\" instead of a message: the game closes with that code when it's quit normally, so it isn't shown as a crash.",
      "additionalProperties": {
        "type": "string"
      }
    },
    "settings": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "help": {
            "type": "string"
          },
          "in": {
            "type": "string",
            "default": "main",
            "description": "A config id, or 'args' for the command line."
          },
          "choices": {
            "type": "array",
            "minItems": 2,
            "items": {
              "type": "object",
              "required": [
                "label"
              ],
              "properties": {
                "label": {
                  "type": "string"
                },
                "set": {
                  "$ref": "#/definitions/values"
                },
                "args": {
                  "$ref": "#/definitions/args"
                }
              },
              "additionalProperties": false
            }
          },
          "defaults": {
            "$ref": "#/definitions/values",
            "description": "The game's own values for keys the file doesn't have yet."
          },
          "when": {
            "$ref": "#/definitions/values",
            "description": "Greyed out unless these keys have these values."
          },
          "group": {
            "type": "string",
            "description": "A heading it's shown under, like \"Controls\". Settings keep their order; a heading shows where the group changes."
          },
          "advanced": {
            "type": "boolean",
            "default": false,
            "description": "Hidden until the player presses Show Advanced Options. Its value stays, and it's still written."
          },
          "slider": {
            "type": "object",
            "description": "A number on a slider instead of choices. The value is read from \"key\" like \"set\" keys (Section/Key), and written with \"format\". A value off the step (set in the game's own menu) is shown as it is and left alone until the player moves the slider.",
            "required": [
              "min",
              "max"
            ],
            "properties": {
              "key": {
                "type": "string",
                "description": "The key it sets in the settings file (Section/Key for INI sections and TOML tables). Not needed with \"in\": \"args\"."
              },
              "min": {
                "type": "number"
              },
              "max": {
                "type": "number"
              },
              "step": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "How far one move goes. A hundredth of the range when it isn't said."
              },
              "format": {
                "type": "string",
                "default": "%g",
                "description": "How the number is written, printf style: \"%.2f\", \"%d\", \"%g\"."
              },
              "args": {
                "$ref": "#/definitions/args",
                "description": "With \"in\": \"args\": the arguments, with {value} for the number. The same as the setting's own \"args\"."
              }
            },
            "additionalProperties": false
          },
          "args": {
            "$ref": "#/definitions/args",
            "description": "For a slider with \"in\": \"args\": its arguments, with {value} for the number, like [\"--sens\", \"{value}\"]. Passed only once the player has moved it."
          },
          "bindings": {
            "type": "object",
            "description": "Key bindings instead of choices: the game's actions, each with the keys it's on in the settings file. The game page shows each action with its keys as chips; the player presses a chip and then the new key, mouse button or wheel. Only actions the player changes are written.",
            "required": [
              "actions"
            ],
            "properties": {
              "separator": {
                "type": "string",
                "description": "Joins several keys on one action, in the order the player set them. Default \"|\"."
              },
              "max": {
                "type": "integer",
                "minimum": 1,
                "maximum": 8,
                "description": "Keys one action can have. Default 2."
              },
              "actions": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "object",
                  "required": [
                    "label",
                    "key"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "description": "Where it's written, with the same rules as \"set\": Section/Key for INI or TOML, nested/keys for JSON."
                    },
                    "pad": {
                      "type": "boolean",
                      "description": "It takes a controller button too."
                    }
                  }
                }
              },
              "names": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "The launcher's name for a key or button, to the name your file uses for it, like {\"Return\": \"Enter\"}. Without one, a name the file already uses for that key is kept."
              }
            }
          }
        },
        "additionalProperties": false,
        "anyOf": [
          {
            "required": [
              "choices"
            ]
          },
          {
            "required": [
              "slider"
            ]
          },
          {
            "required": [
              "bindings"
            ]
          }
        ]
      }
    },
    "groups": {
      "type": "array",
      "description": "Groups used in settings' \"group\". A group marked advanced is hidden as a whole until the player shows advanced options.",
      "items": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The name the settings use in \"group\"."
          },
          "advanced": {
            "type": "boolean",
            "default": false
          }
        },
        "additionalProperties": false
      }
    },
    "saves": {
      "type": "array",
      "description": "Where the game keeps its saves: folders, files, or files matching a * pattern. Backups and cloud saves copy these.",
      "items": {
        "type": "string"
      },
      "examples": [
        [
          "{local}/My Game/saves",
          "{install}/settings.toml"
        ]
      ]
    },
    "games": {
      "type": "array",
      "minItems": 2,
      "maxItems": 32,
      "description": "Several games in one release (a whole series). Each gets its own tile, play time, achievements and shortcuts; the install, updates and removal stay one. One game or none: the recomp shows as one game. A list in the catalog entry is used when the release has none; this one wins.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9]{1,16}$",
            "description": "Unique within the recomp. The game's full id is <recomp id>--<id>."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Shown on its tile."
          },
          "args": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Added after start.args when this game is played. None: Play opens your own menu, and the tile says to pick the game there."
          },
          "title_id": {
            "type": "string",
            "pattern": "^[0-9A-Fa-f]{8}$",
            "description": "Xbox 360: this game's title id. Its unlock file is <title_id>.toml."
          },
          "inputs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The setup inputs this game needs. Inputs no game lists are shared by all."
          },
          "saves": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Where this game keeps its own saves, like the top-level saves. None: the recomp's saves are shared."
          }
        }
      },
      "examples": [
        [
          {
            "id": "gow2",
            "name": "Gears of War 2",
            "args": [
              "--game=gow2"
            ],
            "title_id": "4D53082D",
            "inputs": [
              "disc2"
            ]
          },
          {
            "id": "gow3",
            "name": "Gears of War 3",
            "args": [
              "--game=gow3"
            ],
            "title_id": "4D530906",
            "inputs": [
              "disc3"
            ]
          }
        ]
      ]
    },
    "looks": {
      "type": "object",
      "description": "Looks (ReShade filters) for this game. Leave it out and they work with what the launcher works out itself.",
      "properties": {
        "allowed": {
          "type": "boolean",
          "default": true,
          "description": "false if your recomp has its own filters, or ReShade doesn't work with it."
        },
        "reason": {
          "type": "string",
          "description": "Shown to the player when looks are off."
        },
        "graphics": {
          "enum": [
            "d3d12",
            "d3d11",
            "d3d9",
            "opengl",
            "vulkan"
          ],
          "description": "The graphics API your recomp draws with."
        },
        "graphics_key": {
          "type": "string",
          "description": "A settings key that holds the graphics API the player picked, in the config named by 'in'."
        },
        "in": {
          "type": "string",
          "default": "main"
        },
        "lines": {
          "type": "integer",
          "minimum": 0,
          "description": "Lines for the CRT looks, when the console's usual number is wrong for your game."
        }
      },
      "additionalProperties": false
    },
    "mods": {
      "type": "object",
      "description": "Where players get mods for your game. Leave it out if the launcher already knows (Zelda 64: Recompiled, Banjo: Recompiled, Unleashed Recompiled, Marathon Recompiled and the Ship of Harkinian family).",
      "properties": {
        "thunderstore": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._-]+$",
          "description": "Your Thunderstore community, as in thunderstore.io/c/<community>. Needs 'data'."
        },
        "data": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._ -]+$",
          "description": "Your data folder's name (in AppData\\Local on Windows, ~/.config on Linux). Mods go in its mods folder, or in the game's own mods folder when portable.txt is there."
        },
        "gamebanana": {
          "type": "integer",
          "minimum": 1,
          "description": "Your game's id on GameBanana, as in gamebanana.com/games/<id>. Needs 'loader'."
        },
        "loader": {
          "enum": [
            "nrm",
            "otr",
            "hmm"
          ],
          "description": "How your game reads mods: nrm (.nrm files in the data folder's mods folder, needs 'data'), otr (.otr and .o2r files in a mods folder next to the executable) or hmm (Hedge Mod Manager mods with a mod.ini, needs 'user')."
        },
        "user": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._ -]+$",
          "description": "For hmm: your folder in AppData\\Roaming that holds cpkredir.ini."
        }
      },
      "additionalProperties": false
    },
    "actions": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "help": {
            "type": "string"
          },
          "open": {
            "type": "string",
            "description": "A folder name."
          },
          "clear": {
            "type": "string",
            "description": "A folder name and subfolder, like saves/cache."
          }
        },
        "additionalProperties": false
      }
    },
    "requires": {
      "description": "The launcher features this profile uses. Parts needing a feature the player's launcher doesn't have are left out (no Easy Setup, no settings tab for it); the game still installs and plays.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/launcherFeature"
      },
      "uniqueItems": true
    },
    "min": {
      "description": "The oldest launcher version this profile's Easy Setup, settings and installer parts work with, like \"0.90.0-beta.27\". Older launchers leave those parts out.",
      "type": "string",
      "pattern": "^v?\\d+\\.\\d+\\.\\d+(-[A-Za-z]+\\.\\d+)?$"
    }
  },
  "additionalProperties": false,
  "definitions": {
    "programs": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "string"
      },
      "description": "Paths inside your release, tried in order."
    },
    "args": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "values": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "description": "Key to value, written exactly as given (TOML strings keep their quotes)."
    },
    "places": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "path"
        ],
        "properties": {
          "path": {
            "type": "string"
          },
          "if": {
            "type": "string",
            "description": "Used only when this path exists."
          }
        },
        "additionalProperties": false
      }
    },
    "launcherFeature": {
      "description": "A launcher feature a profile can say it uses (\"requires\"). A launcher that doesn't have one leaves out the parts that need it, and the game installs and plays the normal way. RecompLauncher 0.90 Beta 27 supports these.",
      "enum": [
        "start",
        "start.linux",
        "start.macos",
        "setup",
        "console",
        "clone",
        "makes",
        "player_note",
        "installer",
        "needs",
        "exit_codes",
        "files.folder",
        "files.flag",
        "files.env",
        "files.setup",
        "files.accepts",
        "files.contains",
        "files.sha256",
        "files.update",
        "settings.toml",
        "settings.ini",
        "settings.json",
        "settings.args",
        "settings.choices",
        "settings.slider",
        "settings.bindings",
        "settings.groups"
      ]
    }
  }
}
