> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/profile_authoring.md).

# Writing a Kouch emulator profile

A Kouch profile is a JSON document that tells Kouch **everything** it needs to run one emulator: where the program is (or where to download it), how to launch a game with it, how to pause/quit it, which keys do what, how players map onto its controller slots, and where its saves and config live. Anyone can write one and share it; the person importing it always sees a confirmation sheet first.

**How it travels.** Kouch has one file type, `.kod`. Usually it's a zip that can hold one profile, several, or a full backup (profiles plus their saves / states / screenshots / config) — but a hand-written profile is simply this JSON document saved with a `.kod` extension; Kouch tells the two apart by content. To produce a `.kod` from profiles you already have in Kouch, use Settings › Emulators › Export…. A `.kod` never contains a `system` folder: Kouch does not write one and refuses to read one.

The schema is `schemas/emulator-profile.v1.schema.json` (it covers every profile version) — point `"$schema"` at it and any JSON editor will validate as you type. Write `"version": 3` to use the sections marked *v2* and *v3* below; a `"version": 1` or `2` profile still loads unchanged. `profiles/example.generic.json` is a complete, neutral starting point.

## Ground rules for authors

1. **Never name, ship or point at ROMs, BIOS, firmware or keys.** A profile that references such files (in `args`, `env`, `config_edits`, anywhere) is refused at import. The *user* places firmware in the emulator's `system` folder themselves; your profile only declares that the folder exists.
2. **No console brand names** in ids, names or strings. Use the emulator's own name and neutral system ids (`"systems": ["my-system"]`).
3. **Sources are HTTPS only** and point at the emulator's *official* releases. A `direct_url` needs a `sha256`; a `github_release` gets its checksum from the release's own checksum file when there is one.
4. **You cannot turn on automatic installs for the user.** Kouch checks every emulator with a release source for new versions by itself (the user can switch that off for all of them), but installing them automatically is each user's own choice per emulator, off by default. `source.auto_update` is only a suggestion, shown as text in the import sheet.

## The file, section by section

```json
{
  "$schema": "https://…/schemas/emulator-profile.v1.schema.json",
  "version": 2,
  "id": "my-emulator",
  "name": "My Emulator",
```

`id` is a stable lowercase slug (folders on disk use it); `name` is what people see.

### Where the program is

Either the user already has it:

```json
  "launch": { "kind": "exe", "path": "C:/Emulators/MyEmulator/emu.exe" },
```

or Kouch installs it from a source (then `launch.path` is a placeholder Kouch overwrites after unpacking):

```json
  "launch": { "kind": "exe", "path": "emu.exe" },
  "source": {
    "kind": "github_release",
    "repo": "someone/my-emulator",
    "asset": "*-win64.zip",
    "install_subdir": null,
    "executable": "my-emulator-*/emu.exe",
    "auto_update": false
  },
```

`asset` is a glob over the release's file names. `executable` is the path of the program **inside the unpacked archive** (relative to `install_subdir` when set). Zip, 7z, tar.gz and tar are supported. An emulator in portable mode that keeps its settings file beside its program (and can’t be told to keep it elsewhere) lists it in `keep`, e.g. `"keep": ["settings.ini"]`: plain file names only. Each version installs into its own folder, so an update or a rollback copies those files from the version in use, and the user’s own settings follow. A project that also re-publishes a rolling release under a fixed tag (`latest`, `nightly`, `continuous`) sets `tag` to a glob for its versioned tags, e.g. `"tag": "v*"`, so Kouch sees each new version.

Where the emulator can come from (`source.kind`):

| `kind`           | Fields                                                                                                             | Versions and updates                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `github_release` | `repo` (`owner/repo`), `asset`                                                                                     | Kouch lists the releases; the user can install any of them or roll back                                                        |
| `gitlab_release` | `repo` (the project path, `group/project`; `project` works too), `asset`, optional `host` for a self-hosted GitLab | same as GitHub                                                                                                                 |
| `json_feed`      | `url` (the project's own release feed, HTTPS) and `feed` (field paths into it)                                     | the latest; every version if you add `feed.releases` (the path to its list of releases) and `feed.date`                        |
| `flatpak`        | `app_id`, optional `remote` (default `flathub`)                                                                    | Linux only, with a Flatpak launch; Kouch installs it for the user with `flatpak install --user` and lists the remote's history |
| `direct_url`     | `url` (HTTPS) and `sha256`                                                                                         | none: fine for a profile you keep yourself, but a shared `.kod` should use one of the others                                   |

**Linux downloads.** An AppImage asset is the program itself: Kouch saves it as `executable` when you give one (use a fixed name like `"MyEmulator.AppImage"`, so `launch.path` stays valid from one version to the next), else under the asset's own name, and marks it executable. Programs unpacked from a zip or 7z get the permissions they were packed with, and the `executable` (plus any AppImage next to it) is always marked executable; a tar keeps its own. On Linux, point an emulator's own config and data folders into Kouch's with `env`, for example `"XDG_CONFIG_HOME": "{config}/xdg-config"`, so it never writes into the user's home folder.

On Linux the emulator can also be a **Flatpak** (most emulators on a Steam Deck are):

```json
  "launch": { "kind": "flatpak", "app_id": "org.example.Emu", "extra_args": [] },
```

Kouch then runs `flatpak run <extra_args> <app_id> <args>`:

* **`args`** (below) go to the emulator, as for any program.
* **`extra_args`** are options for `flatpak run` itself, so they come before the app id. Use them when the emulator needs something its Flatpak doesn't allow by default: `--filesystem=<folder>` when the games or the user-data folders live outside what the Flatpak can see (placeholders work, e.g. `--filesystem={rom_dir}`), or `--env=NAME=value`. It can be left out.
* **Controller input over DSU** works as usual, because a Flatpak shares the host's network by default.
* **An AppImage** is not a Flatpak: use `"kind": "exe"` with the AppImage's path.

### One file for every OS (v3)

One profile can carry a section per OS, so the same `.kod` works on Windows and on Linux (a Steam Deck is Linux). Put what differs under `platforms`; everything else stays at the top:

```json
  "version": 3,
  "platforms": {
    "windows": {
      "launch": { "kind": "exe", "path": "Emu.exe" },
      "source": { "kind": "github_release", "repo": "someone/my-emulator", "asset": "*-windows-x64.zip", "executable": "Emu.exe" }
    },
    "linux": {
      "launch": { "kind": "flatpak", "app_id": "org.example.Emu" },
      "user_data": { "config_edits": [] }
    }
  },
```

* **Keys** are `windows`, `linux` and `macos`. Kouch for macOS is a future update: a `macos` section is checked and kept today, so profiles you write now already carry it.
* **A section** needs a `launch`. It may also have its own `source`, `args`, `env`, `working_dir` and `user_data` (config files usually live in different places per OS). Anything a section leaves out comes from the top level. A section's `user_data` replaces the top-level one as a whole.
* **With `platforms` there is no top-level `launch`**: each section has its own.
* **Flatpak is Linux only**: a `flatpak` launch or source in another section is refused.
* **A file with nothing for the user's OS:**
  * **In the import sheet,** Kouch offers what can still be done on this OS, before anything downloads: use an emulator already installed here (a copy or a Flatpak), use a build of the same project, or bring only the data.
  * **Imports that can't ask** (a bare profile, or a whole bundle at once) refuse it in plain words: "This emulator has no Linux version in this file. It has: Windows."
* **Kouch keeps every section** when it saves, backs up, sends over LAN or exports the profile, so a profile moved to another OS still works there.
* **Older profiles** (no `platforms`) keep working: one with an `.exe` launch (or a Windows-looking release asset) is a Windows profile, a Flatpak or AppImage one is a Linux profile, and anything else runs on any OS as before.

### Its online home (v3)

If you publish your `.kod` on GitHub or GitLab, say where, so Kouch can offer the user newer copies of the profile and show its history:

```json
  "origin": { "kind": "github", "repo": "someone/kouch-profiles", "path": "my-emulator.kod" },
  "revision": "1.4",
```

* **`kind`** is `github` or `gitlab`. For a self-hosted GitLab add `"host": "gitlab.example.org"`. `repo` is `owner/repo` on GitHub, the project path (`group/project`) on GitLab.
* **`path`** is the file's path in the repo. Kouch looks for it first among the repo's releases (a release asset with that name), else on `branch` (default: the repo's default branch), where its history is the commits touching it.
* **`revision`** is your own version of the profile file; bump it when you change the file.
* **Served from a website** (the kouch.dev emulator catalogue): `"origin": { "kind": "url", "url": "https://kouch.dev/emulators/<id>.kod" }`. An `index.json` beside the file lists each profile as `{ "<id>": { "version": 3, "revision": "…", "sha256": "…" } }`. Kouch reads that small index to see whether the `revision` changed, and downloads the `.kod` only when the user asks for the update. It must be https, it may only redirect within the same host, and its sha256 must match the index.
* **Profile updates are never applied by themselves.** The user checks, reads the history and applies; their own settings (toggles, option values, the installed emulator) stay.

### How a game is launched

```json
  "args": ["--fullscreen", "{rom}"],
  "working_dir": null,
  "env": {},
  "window_mode": "borderless",
```

Placeholders in `args`, `env` values and `working_dir`: `{rom}` (full path of the game file), `{rom_dir}`, `{rom_stem}` (file name without extension), `{profile_dir}`, and the user-data folders below: `{user_data}`, `{saves}`, `{states}`, `{screenshots}`, `{config}`, `{system}`, plus `{emulator_dir}` (folder of the program). The *Emulation* folder adds four more: `{roms}` (the main `roms` folder), `{bios}` (this emulator's firmware folder — the same folder as `{system}`), `{storage}` (large data that is not synced) and `{media}` (shared art). For controller input, `{dsu_host}`, `{dsu_port}` (players 1–4) and `{dsu_port_2}` (players 5–8) are where Kouch's DSU servers really listen — always use them instead of typing `127.0.0.1:26760`, because Kouch moves to free ports when another program holds the usual ones. A profile that still names `{dsu_port_3}` or `{dsu_port_4}` gets a warning: those servers are gone. An emulator that reads pads itself (through SDL, `native_gamepads: disable_in_emulator_config` so it sees only Steam’s virtual pads) binds each player with `{seat_pad_1}` … `{seat_pad_8}` in a config edit’s value, e.g. `"Pad2.Up": "SDL-{seat_pad_2}/DPadUp"`: at launch each becomes that seat’s Steam virtual-pad slot, so player N is always Kouch’s seat N, and a value naming an empty seat is written empty (unbound). A value that is only the placeholder, like `"Instance0.JoystickID": "{seat_pad_1}"`, is written as a number, for emulators that store a device index as one. The binding holds for the session; a reorder takes effect at the next launch, and Kouch shows that. Any folder placeholder can be written `{saves:/}` to get the path with `/` separators — use that in config files that treat `\` as an escape character (Qt-style INI files, for example), or the emulator reads a mangled path. `window_mode` is `borderless` (the default and recommended: Kouch takes the frame off the game's window and sizes it to its monitor, and the Quick Menu works over it), `windowed` or `fullscreen` (the emulator's own fullscreen; set its borderless-fullscreen option in `config_edits` when it has one, because exclusive fullscreen hides the Quick Menu on Windows). Kouch checks the game's window after launch: with `borderless` at 3 and 10 seconds, with `fullscreen` at 20 seconds. If it doesn't cover its monitor then, Kouch makes it borderless (or, for `fullscreen`, sends `hotkeys.fullscreen` when you set one). A window that already covers its monitor is never touched. On a Linux desktop, Kouch asks the window manager for fullscreen again instead; under the Deck's Game Mode every game is fullscreen anyway.

### Which systems and files

```json
  "systems": ["my-system"],
  "extensions": ["iso", "chd"],
```

`systems` are neutral ids you choose; they become the options in Settings › Paths and the detail sheet. `extensions` (no dots) let an "Any — detect by extension" folder route files to this emulator.

**Games that are folders.** Some systems keep a game as a folder of files rather than one file. An entry of the form `"<folder>/*.<ext>"`, e.g. `"code/*.rpx"`, means "a file with that extension inside a `code` folder": each match is one game, named after the folder above `code` (tags in `[...]`/`(...)` are dropped as usual), and `{rom}` is that file, so the emulator is started with it.

*v2:* an entry may be an object instead of a bare id:

```json
  "systems": [
    { "id": "my-system", "name": "My System", "folder": "mysystem", "art": { "thumbnails": "Index Folder Name" } }
  ],
  "data_folder": "myemu",
```

`folder` is the game folder's name in the user's *Emulation* folder (`Emulation\roms\<folder>`, on every disk they add); it defaults to the id — use the name other tools already use for that system so an existing folder is picked up as it is. `art.thumbnails` is the public thumbnail index's own folder name for the system, used for automatic art. `data_folder` names this emulator's `bios\`, `saves\` and `storage\` folders (default: `id`). Every folder name must be one plain name — no slashes, no `..`.

### Players

```json
  "max_players": 4,
  "players": [
    { "dsu": { "server": 0, "slot": 0 }, "vpad": null, "motion": true },
    { "dsu": { "server": 0, "slot": 1 }, "vpad": null, "motion": true },
    { "dsu": { "server": 0, "slot": 2 }, "vpad": null, "motion": false },
    { "dsu": { "server": 0, "slot": 3 }, "vpad": null, "motion": false }
  ],
  "dsu": null,
  "native_gamepads": "disable_in_emulator_config",
```

Kouch seats at most 8 players, and every one can have motion. It runs two DSU/Cemuhook servers (ports 26760 and 26761 by default); player *p* is server `p/4`, slot `p%4`. `max_players` is at most 8. List one entry per seat, and set `motion: true` on the seats that should carry gyro. `native_gamepads` says who reads the pads. `disable_in_emulator_config` (the default): the emulator gets its controllers from Kouch only, over DSU; bind only DSU devices in its config (your `config_edits`), so players aren't read twice. `leave_alone`: the emulator also reads pads itself (for example buttons through SDL when its DSU client carries only motion). Under Steam this matters: Steam gives everything it starts a list of pads SDL must ignore, which hides nearly every physical pad. With `leave_alone` Kouch removes that list from the emulator's environment so it can see them; with the default the list stays, and the emulator only sees Kouch's DSU seats.

#### Consoles with a pointer: aim by moving the controller

For a console whose controller points at the screen, players expect to aim by moving their pad, as with the original. Set it up the same way for every emulated controller, not only the first:

1. **Motion on the seat.** `motion: true` for each seat that points.
2. **Bind the DSU motion inputs.** In the emulator's controller config, bind its gyroscope and accelerometer inputs to the DSU device's gyro and accel axes (all six of each). Kouch sends gyro in degrees per second and acceleration in g, the DSU/Cemuhook units every DSU client expects.
3. **Turn on pointing from motion.** Most emulators have a separate "point with the gyro" group next to the stick pointer. Turn it on explicitly in `config_edits`, even when it's the emulator's default: a user's own config may have it off. Some emulators drop a setting from their file when it equals the default, so don't be surprised when the key is gone after a session.
4. **Give it a recenter button.** Gyro pointing drifts over a long session. Bind the group's recenter input to a button the game doesn't use (a stick click works well), and tell your players which one.
5. **Keep the stick as a fallback only if the two add up.** Some emulators add the stick pointer and the gyro pointer together, so a stick at rest adds nothing: keep both, and pads without a gyro still aim with the stick. If an emulator makes you choose one, choose motion for seats with `motion: true`.
6. **Offer a switch.** A `toggle` option in `options` (tab `controls`) that writes the group's enabled key lets a player turn pointing off per game.

```json
{
  "file": "{user_data}/config/Config/Controllers.ini",
  "format": "ini",
  "set": {
    "Pointer1.Gyroscope/Yaw Left": "`Gyro Yaw Left`",
    "Pointer1.Gyroscope/Yaw Right": "`Gyro Yaw Right`",
    "Pointer1.MotionPoint/Enabled": "True",
    "Pointer1.MotionPoint/Recenter": "`L3`"
  }
}
```

The section and key names above are placeholders: use your emulator's own. Check with a real pointer game before you publish: point the pad at the middle of the screen, press recenter, and the cursor should follow the pad to each edge.

#### Shaking

Steam reports a pad's acceleration only up to 2 g on each axis. A firm shake of the original controller reads 3–4 g, so a game that waits for a strong jolt may only react to some shakes. When the emulator can drive its own emulated shake from an expression, add one on top of the real motion:

* **Trigger it from the motion:** "on" while any accelerometer axis is near the 2 g cap (about 18 m/s² in emulators that count in m/s²), and held for a moment after the last spike (0.4–0.5 s), so a continuous shake stays continuous between swings.
* **Shake every part the player would hold.** If the emulated controller has an attachment with its own motion (a second hand-held piece), bind its shake too. A game that expects both pieces to move can stop and restart a shake when only one does.
* **Add a button.** A spare button that shakes while held, for players whose pad has no motion or who prefer it.
* **Check at rest.** Holding the pad still and pointing normally must never trigger it.

#### Rumble

A game's rumble can't come back to Kouch over DSU: the emulators' DSU clients have no rumble output. So the emulator rumbles a pad it can see, bound as an **output only**. Bind its motor and nothing else from that pad: buttons, sticks and motion stay on the DSU device, so nothing is read twice. Describe the pad and its motor with a `rumble` section, and Kouch writes every player's binding at each launch:

```json
"rumble": {
  "file": "{user_data}/config/Config/Controllers.ini",
  "format": "ini",
  "key": "Pad{player}.Rumble/Motor",
  "steam": "`XInput/{pad}/Gamepad:Motor L` | `XInput/{pad}/Gamepad:Motor R`"
}
```

* `file` and `format` follow the same rules as a config edit. `key` names one player's motor output, with `{player}` (1, 2, …). Kouch writes it for every player up to `max_players`.
* `steam` is the value when Steam's own virtual pads carry the rumble (the default on Windows). `{pad}` becomes the slot of Steam's virtual pad for the controller in that seat. Steam offers 4 such pads, so players 1–4 get rumble. The binding is fixed when the game starts: if players are reordered during a game, rumble stays with the old order until the game restarts, and Kouch offers **Restart game**.
* `vpad` (optional) is the value when Kouch's own virtual pads carry it: `{pad}` is that virtual pad's index. Rumble then goes back through Kouch and follows reorders at once. On Windows this needs the ViGEmBus driver, an **optional driver for a better experience on Windows**; Kouch never downloads it.
* A player with no pad (an empty seat, or a seat beyond Steam's 4) gets an empty value, which unbinds it: no rumble and no error, and nothing left over from an earlier session.
* Use your emulator's own names for the device and its motor outputs. Its log or its controller settings list the devices it sees.

### Pause, quit, hotkeys

```json
  "pause": { "method": "suspend" },
  "quit": { "method": "close", "force_kill_after_ms": 5000 },
  "hotkeys": {
    "pause": null,
    "save_state": { "keys": ["F1"] },
    "load_state": { "keys": ["F3"] },
    "reset": null,
    "screenshot": { "keys": ["F12"] },
    "quit": null
  },
  "on_disconnect": "hold_neutral",
```

`pause.method` is `suspend` (Kouch freezes the process while the Quick Menu is open) or `{ "method": "hotkey", "chord": { "keys": ["P"] } }` when the emulator has a real pause key — prefer the hotkey when one exists. `quit.method` is `close` (window close, then force-kill after the timeout), `hotkey` (needs `hotkeys.quit`) or `signal`. Hotkeys are what the Quick Menu's "Save state / Load state / Reset / Screenshot" actions send; leave one `null` and that action is hidden. `hotkeys.extra` adds more of the emulator's own hotkeys as Quick Menu buttons, so a player can change something mid-game without a restart. For example, a dual-screen emulator's swap and layout keys: `"extra": [{ "id": "swap_screens", "label": "Swap screens", "chord": { "keys": ["f9"] } }]`. Ids are lowercase letters, digits, `_` and `-`, unique, and not one of Kouch's own actions (`pause`, `save_state`, `load_state`, `reset`, `screenshot`, `quit`, `fullscreen`). A profile may declare at most 12. Key names are the emulator's own keyboard shortcuts (`"F1"`, `"Ctrl"` + `"S"` as `["Ctrl", "S"]`). Keep hotkeys off the keys Kouch's keyboard map uses (arrows, Enter, Space, Esc, Backspace, X, Y, M, Home, Q, End, Page Up/Down, `[`, `]`, `,`, `.`) unless they carry Ctrl or Alt: a keyboard player's press also reaches the emulator's window, so the hotkey would fire with the pad action. The validator warns about it. Clear the emulator's own defaults on those keys too, in its config (a pause menu on Escape, for example).

### Where the emulator keeps its data

```json
  "user_data": {
    "folders": {
      "saves": "saves",
      "states": "states",
      "screenshots": "screenshots",
      "config": "config",
      "system": "system"
    },
    "cloud": ["saves", "states", "screenshots", "config"],
    "config_edits": [
      {
        "file": "{emulator_dir}/settings.ini",
        "format": "ini",
        "set": {
          "Paths.SaveDir": "{saves}",
          "Paths.StateDir": "{states}",
          "Paths.SysDir": "{system}",
          "Input.Backend": "cemuhook",
          "Input.Native": "false"
        }
      }
    ]
  }
}
```

Every key in `folders` becomes a sub-folder of the emulator's saves folder. Once the user has set up their *Emulation* folder that is `Emulation\saves\<data_folder>\<folder>` — a link into their own Steam Cloud folder — except `system`, which is always `Emulation\bios\<data_folder>` and never inside the cloud folder. (Before setup it is `<Emulator data folder>/<id>/<folder>`.) `cloud` lists which of those the user *may* sync to Steam Cloud — `system` is never allowed there, and Kouch never reads it. `config_edits` are applied before every launch so the emulator uses those folders and the DSU input: `file` is the emulator's own config (templated path, must be under `{emulator_dir}`, `{user_data}`, `{profile_dir}` or `{storage}`); `format` is `ini` (`"Section.key"`; a section whose name has a dot goes in brackets, `"[DisplayLayout.Landscape].DisplayStretch"`), `json`/`toml` (dotted path), `text` (`key=value` lines) or `xml` (a `/` path from the root element: `"content/mlc_path"`; `"mappings/entry[3]/button"` picks the 3rd repeated element, creating missing ones; a final `@name` sets an attribute). The original file is backed up as `<file>.kouch-backup` before the first edit and "Restore emulator config" in Settings puts it back.

**Folders an emulator can't move (`links`).** When an emulator reads something from a fixed place inside its own folder (a keys folder, say) and its settings can't point it elsewhere, add a link: `"links": [{ "link": "{config}/keys", "target": "{system}" }]`. Before each launch Kouch makes `link` a directory link to `target`, so the user's files stay in Kouch's folder. `link` must be under `{emulator_dir}`, `{user_data}`, `{config}`, `{storage}` or `{saves}`; `target` under a user-data placeholder. Kouch never reads through a link, and everything under a link inside the user-data folder is treated as private (below). A real folder with files where a link belongs is left alone, with a warning. Links are also the dependable way to place an emulator's storage: some emulators ignore a custom storage path that doesn't exist yet, and a link's target is always created first.

**Emulators that need the user's own files (`system_required`).** Set `"system_required": true` in `user_data` when the emulator can't start a game without the user's own keys or firmware in `system`. While that folder is empty, Kouch refuses the launch and tells the user which folder to copy their files into, instead of opening the emulator on an error dialog. Kouch only ever checks whether the folder is empty.

**System files kept among saves (`private`).** Some emulators keep an emulated system storage inside their user data, holding installed system software, tickets and key files next to game saves. List those paths in `private` and Kouch treats them exactly like `system`: never listed, read, synced to Steam Cloud, sent over LAN, packed into a `.kod` or moved. Everything else in the folder still syncs.

```json
    "private": ["saves/nand/sys/**", "saves/nand/ticket/**", "saves/nand/title/00000001/**"]
```

Paths are relative to the emulator's user-data folder and use `/`. `*` and `?` match within one name, `**` matches any number of folders, and a folder name covers everything inside it. A path that starts with `/`, a drive letter or contains `..` is refused. Kouch's own rules always apply on top: `system` is never touched whether or not you list it.

**The user's own system files (`system_files`).** List the files the emulator needs from the user (keys, a BIOS, firmware) so that when the user drops them onto Kouch, or picks them in Settings, Kouch copies each one into this emulator's `system` folder (after one confirm; Settings can turn this off). Kouch never downloads, syncs, sends or packs these files, and never says where to get them.

```json
    "system_files": [
      { "names": ["keys.txt"], "what": "Keys", "required": true },
      { "patterns": ["*.bin"], "sizes": [2097152], "what": "BIOS", "sub_path_choices": [
          { "label": "USA", "sub_path": "BIOS/USA" }, { "label": "Europe", "sub_path": "BIOS/EUR" } ] },
      { "names": ["boot.bin"], "sub_path": "Boot/USA", "read_at": "saves" }
    ]
```

`names` match ignoring case and are written in your spelling; a `pattern` only ever matches together with one of the `sizes`. `kind` is `file` (the default), `zip` (kept as a zip), `folder` (copied whole) or `installer_package` (the emulator installs it: `install` is its command with `{file}`, or `{"dialog": "<where its menu is>"}`). `sub_path` is where inside `system` it goes; `sub_path_choices` lets the user pick one (a region). `what`, `required`, `features` and `regions` are shown in the sheet.

**System files read from another folder (`read_at`).** Some emulators read a system file from a folder that also holds data Kouch syncs or exports: keys beside the settings, a boot ROM beside the memory cards. Don't put those in `config` or `saves` yourself. Set `read_at` to the folder key (optionally `"<key>:<path>"`): the file stays in `system`, and for each game session Kouch copies it to `<folder>/<path>/<sub_path>/<name>`, then removes the copy after the emulator quits (after a crash, at the next start, before anything else). A copy the emulator changed is left in place. That location is always private, so the file is never exported, synced or sent from there, even if the user put it there by hand. `read_at` works for single files (`file` or `zip`), never for `system` itself.

**One game's files (`game_files`).** For a game to be exported on its own (the game menu's *Export this game*), Kouch has to tell that game's saves, states and settings apart from every other game's. Each rule names one of the `folders` (`part`, never `system`) and a glob inside it that uses the game's keys: `{title_id}` (read by `game_id`), `{rom_stem}` (the game file's name without extension), `{game_hash}` (its SHA-1) or `{game_version}`.

```json
    "game_files": [
      { "part": "saves", "glob": "**/{title_id}/**" },
      { "part": "states", "glob": "{rom_stem}*" },
      { "part": "config", "glob": "game/{title_id}.ini" }
    ]
```

Globs follow the `private` rules above (and private paths are never exported). A key can be reshaped with transforms, applied left to right: `|lower`, `|upper`, `|slice:<from>:<to>` (characters, `|slice:<from>` for the rest) and `|hex` (each character as two lowercase hex digits). So a 16-digit id kept as two 8-digit folders is `{title_id|slice:0:8|lower}/{title_id|slice:8:16|lower}/**`, and a folder named after the hex of a 4-character code is `{title_id|slice:0:4|hex}`. A game whose key isn't known, or is too short for a slice, has no files; it never matches another game's. Without `game_files`, the game menu offers the whole emulator's export instead. On import, a game's files go into the other system's copy of the game (found by title id, then hash, then file name), renamed for its file name where `{rom_stem}` was used; when that game isn't in the library yet, they wait for a scan that finds it.

### Game ids and names (`game_id`)

`game_id` tells Kouch how to read a game's id, its version and its own title, for mods kept per title id, one game's exports and clean names. It's one reader, or a list tried in order: the id comes from the first reader that finds one, the name from the first that finds a name.

```json
  "game_id": [
    { "from": "header", "offset": 0, "len": 6, "name": { "offset": 32, "len": 64 }, "extensions": ["iso"] },
    { "from": "meta_file", "path": "meta/meta.xml", "field": "title_id", "name_field": "longname_en" },
    { "from": "filename", "pattern": "\\[(?P<id>[0-9A-F]{16})\\]" },
    { "from": "smdh", "extensions": ["cci", "cxi"] }
  ]
```

* `header`: `len` bytes at `offset` in the game file (or `file` inside a folder game), read with `encoding` (`ascii`, `hex`, `hex_le`, `be_uint`, `le_uint`). `version` and `name` are more fields of the same kind; a name's trailing NULs and spaces are trimmed.
* `meta_file`: a field of a metadata file inside a folder game (XML element, JSON dotted path, or INI key). `version_field` and `name_field` likewise; line breaks in a name become spaces.
* `filename`: a regular expression over the file name with a named group `id`, and optionally `version` and `name`.
* `smdh`: the English title from a handheld image's icon data, read only when the image isn't encrypted; an encrypted one gives no name.

Only what the game file itself carries is read, never anything encrypted, and never a `system`, `bios` or key file.

**Clean names.** Kouch shows each game under a clean name and never renames the file (the file name stays in the game's details). The name comes from, best first: the user's own `gamelist.xml`; the system's list, by the file's checksum; the title the game carries (`name` above); the system's list, by the longest list title the file name's words start with; and last the file name itself, with separators turned into spaces, tag groups, versions, region and language codes and checksum-like tokens dropped, in Title Case.

### Launch options (v2)

Options are the settings people change per emulator or per game from the game-style options menu (tabs Graphics, Controls, Audio, Multiplayer, Advanced). Each one is applied at launch as extra arguments or as a config edit:

```json
  "options": [
    { "id": "scale", "label": "Resolution", "tab": "graphics", "type": "choice",
      "values": [{ "value": 1, "label": "Native" }, { "value": 2, "label": "2×" }],
      "default": 1,
      "apply": { "kind": "arg", "args": ["--scale={value}"] } },
    { "id": "fullscreen", "label": "Fullscreen", "tab": "graphics", "type": "toggle", "default": true,
      "apply": { "kind": "arg", "args": ["--fullscreen"], "args_off": ["--windowed"] } },
    { "id": "volume", "label": "Volume", "tab": "audio", "type": "slider", "min": 0, "max": 100, "step": 5, "default": 80,
      "apply": { "kind": "config_edit", "file": "{emulator_dir}/settings.ini", "format": "ini", "key": "Audio.Volume" } },
    { "id": "vsync", "label": "VSync", "tab": "graphics", "type": "toggle", "default": false,
      "apply": { "kind": "config_edit", "file": "{emulator_dir}/settings.ini", "format": "ini", "key": "Video.VSync", "on": 1, "off": 0 } }
  ],
```

`type` is `toggle`, `choice` (needs `values`), `slider` (needs `min` < `max`) or `text`; `default` must be a valid value. `{value}` in `args` is replaced by the chosen value; a toggle's `args` are used when it is on and `args_off` when it is off. For `config_edit`, a toggle writes `on`/`off` if given (otherwise `true`/`false`). A game's own setting wins over the emulator-wide one, which wins over `default`. A profile with no options shows an Advanced tab listing its raw `args`.

**Graphics settings, the easy way (v3).** Put every graphics setting the emulator has on the `graphics` tab, so people can change them in Kouch's Graphics sheet without opening the emulator. Three optional fields keep them consistent between emulators:

* **`role`** says what the setting does: `renderer`, `internal_resolution`, `output_filter`, `aspect`, `anti_aliasing`, `anisotropic`, `vsync`, `frame_limit`, `post_processing`, `hdr`, `docked_mode`, `screen_layout`, `stereo_3d`, `enhancement` or `other`. Kouch groups by it, and Automatic graphics (below) picks settings by it.
* **`section`** is a group label inside the tab, e.g. `"Enhancements"` or `"Post-processing"`, matching the emulator's own screens.
* **`restart: true`** when a change only takes effect after the game restarts; the Quick Menu then offers "Restart game to apply".

Leave V-Sync off by default. **Presets** set several options with one choice; each option stays adjustable afterwards:

```json
  "presets": [
    { "id": "performance", "label": "Performance", "values": { "scale": 1, "vsync": false } },
    { "id": "quality", "label": "Quality", "values": { "scale": 2 } }
  ],
```

A preset may only name options the profile has, with values they accept.

**Automatic graphics.** A new emulator starts on Automatic. At every launch, Kouch picks its graphics settings for the device as it is right then, so a handheld docked to a TV gets TV settings, and back in the hand it gets handheld ones. It picks by `role`:

* **`internal_resolution`:** the highest value whose output height fits the display. On a handheld in the hand, it's capped at 800; otherwise the GPU caps it too: 1080 for a low tier (an integrated GPU, or a handheld on a TV), 1440 for a mid tier, the display for a high tier. Kouch reads each value's height from its label, e.g. `2× (1280×1056)`, `1920x1080`, `1080p` or `4K`; a display the label says it is for wins over the frame size, so `4× (2560×2112) for 1440p` counts as 1440. For a label that only gives a scale, like `2×` or `1× (native)`, it uses `native_height`. Values with no height, such as "Auto" or "The game's own", are never picked.
* **`docked_mode`:** docked, except on a handheld in the hand. A toggle is `true` for docked. A choice goes by labels containing "Docked" and "Handheld", or by the hint below.
* **`anti_aliasing` and `anisotropic`:** by how strong the GPU is, from labels like `4× MSAA` or `16×`. A handheld or an integrated GPU gets anti-aliasing off and 2× filtering; a mid card gets 2× and 8×; a card with 8 GB or more gets 4× and 16×.
* **`renderer`:** the recommended one (`default` unless the hint names another).

Everything else keeps its `default`, including output and texture filters, which are a matter of taste rather than of the device. A game's own settings still win. If the user changes a graphics setting by hand, that emulator switches to Manual, and Automatic stays one choice away. The optional `auto` block steers it:

```json
  "auto": {
    "internal_resolution": { "native_height": 720, "native_height_docked": 1080, "handheld_max_height": 800,
                             "max_height": 2160, "heights": { "3": 720 } },
    "docked_mode": { "docked": 1, "handheld": 0 },
    "renderer": 1,
    "presets": { "handheld": "performance", "docked": "balanced", "laptop": "balanced", "desktop": "quality" }
  },
```

* `heights` gives a value's output height directly (keyed by the value as text) and beats its label.
* `native_height_docked` is used when docked mode is picked.
* `presets` names a preset per device; its values win over the rules above. `docked` falls back to `handheld`.

Every name must exist: the preset ids, the device keys, and values that the `docked_mode` and `renderer` options accept.

#### Controller types

For a console with more than one way to hold its controller, declare the choices as a `controller` option. Kouch shows it on the game page above Play (icon and name), on the Options tab, and in the Quick Menu's Controls page (a change there needs the game restarted). Each value is one controller type:

```json
{ "id": "controller", "label": "Controller", "tab": "controls", "type": "controller", "default": "attachment",
  "values": [
    { "value": "attachment", "label": "With attachment", "icon": "kouch:remote-attachment",
      "needs": ["motion", "pointer", "second_piece"],
      "edits": [{ "file": "{user_data}/config/Controllers.ini", "format": "ini",
                  "set": { "Pad1.Extension": "Attachment" } }] },
    { "value": "sideways", "label": "Sideways", "icon": "kouch:remote-sideways", "needs": ["motion"],
      "edits": [{ "file": "{user_data}/config/Controllers.ini", "format": "ini",
                  "set": { "Pad1.Extension": "None", "Pad1.Options/Sideways": "True" } }] }
  ] }
```

* `icon` is one of Kouch's own neutral icons, `kouch:<name>` with one of `remote-upright`, `remote-sideways`, `remote-attachment`, `classic-pad`, `pro-pad`, `split-pair`, `split-half`, `standard-pad`, `touchpad-pad`, `trackpad-pad`, `handheld`, `keyboard`, `mouse`, `cube-pad`, `tablet-pad`, or your own image inside the `.kod`, as `icons/<file>.png` or `.webp`. Every controller type needs one. Your own images go in the `.kod` zip under `icons/`. Each must be a real PNG or WebP whose contents match its name, at most 256 KB, and a `.kod` carries at most 64. Kouch copies the ones your options name when the file is imported, shows the neutral icon for any that are missing or refused, and packs them again on export.
* `edits` are the emulator's own bindings for that type, with the same rules as `user_data.config_edits`. Only the chosen type's edits are written, at launch. `apply` isn't needed for a `controller` option; add one only if the emulator also wants the choice somewhere else.
* A `choice` option's values may carry `edits` too, for a setting that changes several keys at once (a multitap that also moves which pad section each player uses, for example). `icon` and `needs` stay for `controller` options.
* `needs` says what the player's pad must have: `motion`, `pointer` (pointing by motion) or `second_piece` (something held in the other hand).
* `default` is the console's usual controller.

**Per-game defaults.** Some games support only some controller types. `game_defaults`, keyed by the game's title id (see `game_id`), sets the profile's own value for a game:

```json
"game_defaults": { "TITLE01": { "controller": "sideways" } }
```

The value that wins, highest first: the user's choice for that game, then `game_defaults` for its title id, then the user's choice for the emulator, then the option's `default`. A value the option doesn't accept is skipped. `game_defaults` works for every option type, not only controllers. Kouch's own repository never ships per-game rules; they come from `.kod` files you share and from each user's settings.

### Mods (v2)

```json
  "mods": { "dir": "{storage}/mods/{game_hash}", "method": "link", "order_file": null }
```

`dir` is where the emulator looks for a game's mods — it may use `{storage}`, `{user_data}`, `{emulator_dir}`, `{rom_stem}`, `{game_hash}` (the game file's SHA-1), `{title_id}` and `{game_version}`, with the same transforms as `game_files` (`{title_id|lower}`). Kouch puts each enabled mod in its own folder there before launch (`link` makes a folder link, `copy` copies the files) and removes them after the game exits. `enable` (a `file`/`format`/`key`/`value` config edit) switches the emulator's mod loading on for that session; `order_file` is a file Kouch writes with one enabled mod folder per line, in load order. Mods are data only — Kouch refuses programs and scripts inside one.

**Emulators that load only the mods listed in their settings (`list_entry`).** Some emulators keep a list of enabled packs in their own XML settings and ignore a pack folder that isn't listed. `list_entry` adds one entry per enabled mod for the session:

```json
  "mods": {
    "dir": "{config}/graphicPacks", "method": "link",
    "list_entry": { "file": "{config}/settings.xml", "list": "content/GraphicPack",
                    "element": "Entry", "key": "filename", "value": "graphicPacks/{mod_id}/rules.txt" }
  }
```

Before launch, Kouch adds `<Entry filename="graphicPacks/<mod id>/rules.txt"/>` under `<content><GraphicPack>` for every enabled mod that isn't listed yet, recording each first. After the game (or at the next start after a crash) it removes only those entries, from the file as the emulator left it, so the emulator's other changes stay. An entry that was already there, the user's own, even one marked as switched off, is never touched. Keys compare with `\\` and `/` alike and ignoring case. `value` may use only `{mod_id}`; `file` follows the `enable.file` rules, and a settings file that doesn't exist yet is left alone.

A new entry can carry elements of its own, such as the pack's chosen preset, filled from the game's options:

```json
    "children": [{ "element": "Preset", "attrs": { "category": "Resolution", "preset": "{option:resolution}" } }]
```

Attribute values may use `{mod_id}` and `{option:<id>}`, the game's value for one of the profile's `options` (its own setting, else the emulator's, else the option's default). A child whose option has no value is left out, and so is an attribute that comes out empty. The children go with their entry after the session; an entry that was already there keeps its own.

An emulator that reads those values as elements rather than attributes gets `text` instead of `attrs` (or both): `{ "element": "Preset", "text": { "category": "Resolution", "preset": "{option:resolution}" } }` becomes `<Preset><category>Resolution</category><preset>1920x1080</preset></Preset>`. Check which one the emulator reads; one that reads elements silently ignores attributes.

**Packs the emulator downloads itself (`title_packs`).** Some emulators download community packs for every game into one folder, each with a rules file that lists the title ids it's for. `title_packs` lets an option (a resolution, say) take effect through the game's own pack without the user adding it as a mod:

```json
    "title_packs": { "dir": "{config}/graphicPacks/downloadedGraphicPacks", "entry_prefix": "graphicPacks/downloadedGraphicPacks",
                     "rules": "rules.txt", "option": "resolution", "categories": ["Resolution", "TV Resolution"] }
```

At launch, when the game's title id is known and `option` has a value, Kouch reads the rules files under `dir` (up to four folders deep, nothing else there) and picks the one whose `[Definition] titleIds` include the game and that has a `[Preset]` in one of `categories` (compared whole, ignoring case, so a second screen's "Gamepad Resolution" never counts) whose name starts with the value (`1920x1080` matches `1920x1080 (Full HD)`), else one named after the height (`1080p`). A pack with no categories counts when its name or path contains one of them. It's listed for the session under `list_entry`'s list, as `<entry_prefix>/<path to its rules file>`, with a `Preset` of the preset's own `category` and its `preset` name, and removed afterwards like a mod's entry. With no such pack the option has no effect for that game, and the launch log says so. `title_packs` needs `list_entry`.

**One mod at a time (`layout`).** By default (`"layout": "per_mod"`) each enabled mod gets its own folder, `<dir>/<mod id>`. Some emulators load a game's mod straight from one folder (its `romfs/`, `exefs/` and so on). For those, `"layout": "single"` places the one enabled mod *as* `dir`. With more than one enabled, or when `dir` already exists, none is applied.

**Packaging a mod.** A mod is a folder named after its id in the user's `Emulation\mods` folder (or under `mods/` inside a `.kod`):

```
mods/<id>/kouch-mod.json   { "id": "<id>", "name": "HD textures", "version": "1.0", "description": "...",
                             "targets": { "profile": "my-emulator", "title_ids": ["<title id>"], "game_hashes": ["<sha1>"],
                                          "titles": ["<file name without extension>"], "game_version": "1.0.2" } }
mods/<id>/files/...        exactly what goes into <dir>/<id>/
```

`targets` decides which games offer the mod.

* **Leave it out** to offer the mod for every game of an emulator with a `mods` section.
* **`profile`** must match when it's given.
* **Matching a game:** the mod is offered when any of the lists matches, case-insensitively: a title id in `title_ids` (the portable key; see "Game ids and names"), the game file's SHA-1 in `game_hashes`, or its file name without extension in `titles`.
* **`game_version`** records the version the mod was made for. On a mismatch Kouch says "Made for version 1.0.2, you have 1.1.0" and still applies the mod. A mod containing a program or script (`.exe`, `.dll`, `.bat`, `.ps1`, `.sh`, …), a link, or anything under a `system`/`bios` folder is refused as a whole. Kouch never overwrites a folder that is already there, and everything it placed is removed after the game — even after a crash, at the next start.

### Netplay (v2)

```json
  "netplay": { "protocol": "udp", "port": 7000, "host_args": ["--host", "{port}"], "join_args": ["--connect", "{peer_addr}:{port}"] }
```

How to start the emulator's own netplay as host or guest. Kouch carries the traffic over Steam, so `{peer_addr}` is always a local address Kouch provides.

### What the emulator can't do (v3)

```json
  "unsupported": {
    "rumble": "The emulator rumbles only the device its buttons come from, and DSU has no rumble.",
    "netplay": "Online play can only be started from the emulator's own menus."
  }
```

When a feature Kouch could set up isn't possible with this emulator, say so here with the reason, so users and reviewers know it was checked rather than forgotten. The keys are `rumble`, `netplay`, `motion`, `save_states`, `pause`, `mods`, `fullscreen`, `screenshots` and `seat_order` (the emulator can't be told which seat's pad a player uses), and each needs a reason.

## Testing your profile

1. Import it: save the JSON as `my-emulator.kod`, drop it on the Kouch window (or Settings › Emulators › Import profile). The sheet shows what it declares; fix any listed problems.
2. Add a game folder for your system in Settings › Paths, rescan, launch a game.
3. Open the Quick Menu (hold Select + Start): players should be listed, the game paused, and Save/Load/Screenshot should work if you set hotkeys.
4. Quit from the Quick Menu; check that the save landed in the `saves` folder.

Share the file wherever you like — a `kouch://import-profile?url=https://…/my-emulator.kod` link opens the confirmation sheet directly in Kouch.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/profile_authoring.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
