ttg-batocera-storeputs our catalog on Batocera. It pulls games from the WarpEngine catalog on https://teletypegames.org onto the box, writes EmulationStation metadata and box art, and gives the store its own menu entry — Teletype Games — from which it can be re-run for new releases.
This repository is only the store definition: a config.json and two wrapper scripts. The logic is in the shared engine, warp-engine-batocera-store — this is the Teletype Games configuration of it.
For RetroArch instead of a Batocera box — desktop, Android, Steam Deck, handheld — use the TTG RetroArch Store.
list, sync, remove, config, with dry-run, per-title sync and overrides./userdata/roms/teletypegames/, not in the box's own systems, so nothing of yours can be touched by a sync or a prune. Favourites and play counts on our games survive a rewrite.gamelist.xml, so the games look scraped./api/download, but teletypegames.org deliberately leaves the store's warpstore/* user agent out of the download counts, so installs on a box do not show up in the statistics.Teletype Games ▸ "Update Teletype Games"
│
├─ GET /api/software the whole catalog
├─ keep platforms this box can run c64 → c64, plus4 → cplus4, tic80 → tic80
├─ pick the newest non-dev release that carries the right asset
├─ GET /api/download?path=<asset> .prg / .tic into the store's folder
├─ GET <software.imageUrl> box art
├─ write gamelist.xml title, desc, author, image
└─ write es_systems_ttg.cfg the menu entry
Repository: https://git.teletypegames.org/stores/ttg-batocera-store
Engine: warp-engine-batocera-store
RetroArch equivalent: TTG RetroArch Store
Batocera: https://batocera.org
Catalog side: WarpEngine (libs/ruby/warp_engine in the teletypegames repo)
Over SSH on the Batocera box:
curl -fsSL https://git.teletypegames.org/stores/ttg-batocera-store/raw/branch/master/install.sh | sh
Or from a checkout:
scp -r ttg-batocera-store root@batocera:/tmp/
ssh root@batocera /tmp/ttg-batocera-store/install.sh
The installer fetches the engine — pinned to the commits of warp-engine-batocera-store and warpstore this config was tested with, set at the top of install.sh (ENGINE_REF and WARPSTORE_REF override them) — puts it and this config in /userdata/system/batocera-store/ttg/, writes the ttg-store launcher, and runs the first sync — which downloads the games and writes the menu entry. Restart EmulationStation to see it:
batocera-es-swissknife --restart
Re-running the installer is the upgrade path: it refreshes the engine, the config (keeping the old one as config.json.bak) and the cached logo, and leaves state.json alone. Run from a checkout, it takes the logo from the checkout too.
Coming from a pre-4.0 install, that first sync also changes the layout: the games move out of c64/teletypegames/, tic80/teletypegames/ and ports/teletypegames/ into /userdata/roms/teletypegames/, and the old Ports ▸ Teletype Games Store entry goes away — the updater is inside the menu entry now. The move is a removal plus a fresh download rather than a file shuffle, which for today's catalog is minutes. Your gamelists are rewritten without our nodes, never deleted, and the gamelist.xml.ttg-backup copies are listed and left alone. -n sync shows all of it first; emulationstation.menu.mode: "merge" in config.json keeps the old layout.
On the device. Teletype Games ▸ "Update Teletype Games". It downloads anything new, then restarts EmulationStation. A script launched by ES gets no console, so the output goes to /userdata/system/batocera-store/ttg/store.log.
main carousel: … ▸ Teletype Games ▸ ┌ Commodore 64
├ Commodore Plus/4
├ TIC-80
├ Ports the native builds
└ Update Teletype Games the sync
The entry carries our own TTG logo, shipped in the store repository (logo.png, plus a vector logo.svg for themes that only look for an SVG) and installed by the engine into the logo folder of every theme on the box. A theme it cannot reach shows TELETYPE GAMES as text instead. Themes that came with the Batocera image live on the read-only system, so their copy is in the RAM overlay and would go after a reboot; a few lines the engine adds to /boot/postshare.sh put it back before EmulationStation starts, and the uninstall takes them out again.
Only the platforms that have something installed show up, and the games are launched exactly as the box would launch them: the engine copies the launch command and the emulator list off the box's own c64, cplus4, tic80 and ports systems, so whatever you configured for the C64 applies to our C64 games too. How that is put together is on the engine page.
Over SSH, through the launcher:
S=/userdata/system/batocera-store/ttg-store
$S list # compatible catalog entries; * installed, ^ update available
$S sync # download everything new, refresh gamelists and the menu
$S sync blessingofra # just one title (never prunes)
$S -n sync # dry run
$S remove c64-demo # uninstall (c64:c64-demo works too)
$S config # effective configuration, and where the menu entry lives
Global flags go before the subcommand: $S --roms-root /tmp/roms sync.
| Flag | Meaning |
|---|---|
--config <path> |
store config file (default /userdata/system/batocera-store/ttg/config.json) |
--base-url <url> |
override the catalog base URL |
--roms-root <path> |
override the ROMs root |
-n, --dry-run |
report actions, change nothing |
-v, --verbose |
verbose logging |
--version |
print version |
Two subcommands are for the machine, not the user: sync-from-es is what the menu entry calls, and apply-gamelists --wait-pid <pid> is the detached child it spawns.
/userdata/system/batocera-store/
├── ttg-store launcher
└── ttg/
├── store.py, warpstore.py the engine and its shared core
├── config.json the store that selected it
└── state.json, catalog.json, store.log
/userdata/system/configs/emulationstation/es_systems_ttg.cfg the menu entry
<every theme>/<its logo folder>/ttg.png, ttg.svg the entry's logo
/boot/postshare.sh a marked block that restores that logo at boot
/userdata/roms/teletypegames/ the store's own ROM folder
├── c64/
│ ├── blessingofra-2.2.0.prg, rabbit-1.0.0.prg, …
│ ├── images/blessingofra.png, …
│ └── gamelist.xml
├── cplus4/ …
├── tic80/ …
├── ports/
│ ├── rabbitroller.sh, images/rabbitroller.png
│ ├── .data/rabbitroller/ the unpacked program
│ └── gamelist.xml
└── store/ the menu entry itself
├── update.sh the sync, as the menu starts it
└── gamelist.xml
The box's own system folders are not touched at all: a prune cannot reach your ROMs because they are not in the same tree, and inside our folder only what state.json records is ever deleted — a ROM you drop in there yourself keeps its file and its gamelist entry.
/userdata/system/batocera-store/ttg/config.json, shipped by this repository:
{
"store": {
"id": "ttg",
"name": "Teletype Games",
"base_url": "https://teletypegames.org",
"api": {
"catalog": "/api/software",
"download": "/api/download"
}
},
"paths": {
"roms_root": "/userdata/roms",
"subfolder": "teletypegames"
},
"emulationstation": {
"menu": {
"mode": "system",
"updater": true,
"logo": [
"https://git.teletypegames.org/stores/ttg-batocera-store/raw/branch/master/logo.png",
"https://git.teletypegames.org/stores/ttg-batocera-store/raw/branch/master/logo.svg"
]
},
"folder_name": "Teletype Games",
"restart": true
},
"catalog": {
"statuses": ["released", "archived"],
"owner_id": null,
"only": [],
"exclude": []
},
"platforms": {
"c64": { "system": "c64", "kind": "cartridge", "ext": ".prg", "enabled": true },
"plus4": { "system": "cplus4", "kind": "cartridge", "ext": ".prg", "enabled": true },
"tic80": { "system": "tic80", "kind": "cartridge", "ext": ".tic", "enabled": true },
"ebitengine": { "system": "ports", "install": "port",
"kind": { "x86_64": "linux_x64", "aarch64": "linux_arm64" },
"ext": ".zip", "enabled": true },
"bevy": { "system": "ports", "install": "port",
"kind": { "x86_64": "linux_x64", "aarch64": "linux_arm64" },
"ext": ".zip", "enabled": true },
"godot": { "system": "ports", "install": "port",
"kind": { "x86_64": "linux_x64" },
"ext": ".zip", "enabled": true },
"love": { "system": "ports", "install": "port",
"kind": { "x86_64": "linux_x64" },
"ext": ".zip", "enabled": true }
},
"behavior": {
"prune": true,
"timeout": 30,
"insecure": false
}
}
The full key reference is on the engine page. Edits on the box survive until the next install, which keeps the old file as config.json.bak. Anything permanent belongs in the repository, whose CI (scripts/ci-check.sh) has the pinned engine load the config and fails on any key the engine does not know — a misspelt key would otherwise be silently ignored.
A platform is installable when its release asset is a file a system this box has can boot directly — "has" meaning declared in the box's own es_systems.cfg, which is an emulator that got built into this image:
| Catalog platform | Asset kind | Batocera system | Extension |
|---|---|---|---|
c64 |
cartridge |
c64 (VICE) |
.prg |
plus4 |
cartridge |
cplus4 (VICE xplus4) |
.prg |
tic80 |
cartridge |
tic80 |
.tic |
ebitengine, bevy, godot and love ship native Linux builds, and are installed the way Batocera's Ports system does it — a .sh launcher next to the unpacked program, under Teletype Games ▸ Ports, with the asset picked for the box's architecture (linux_x64 or linux_arm64; godot and love are x86_64-only). phaser ships only a web build, so it is not mapped. Most native titles are demo, which catalog.statuses leaves out; adding it is a config edit:
"catalog": { "statuses": ["released", "archived", "demo"] }
A platform is skipped when the box does not have its system; list reports skipped <name>: this box has no '<system>' system.
curl -fsSL https://git.teletypegames.org/stores/ttg-batocera-store/raw/branch/master/uninstall.sh | sh
It removes the games (including any unpacked port payload), our gamelists, the menu entry, the logos it put in your themes and its block in /boot/postshare.sh, the launcher and the store home, then restarts EmulationStation. DRY_RUN=1 shows what would go; KEEP_HOME=1 keeps state.json and the log for a reinstall.
Nothing of yours goes with it: everything the store installed is in one folder of its own, so an uninstall leaves the box exactly as it was. On a box that still uses the old layout, your gamelists are rewritten without our nodes rather than deleted, so play counts and favourites survive, and the gamelist.xml.ttg-backup copies are listed and left alone.
| Symptom | What to check |
|---|---|
| Nothing appears after an update | /userdata/system/batocera-store/ttg/store.log, then batocera-es-swissknife --restart |
skipped <name>: this box has no '<system>' system |
that emulator is not in this Batocera image |
| The Teletype Games entry is missing | the sync writes /userdata/system/configs/emulationstation/es_systems_ttg.cfg; check the log, then restart ES |
| TLS errors against a test instance | set behavior.insecure to true in config.json |
| A title should never be installed | add its catalog name to catalog.exclude in config.json |
| Gamelist looks wrong | ours is at /userdata/roms/teletypegames/<system>/gamelist.xml; a pre-4.0 original is at <system>/gamelist.xml.ttg-backup |
| The entry shows TELETYPE GAMES as text after a reboot | the ttg-store: theme logos block in /boot/postshare.sh; if it is missing, the log says why (/boot could not be remounted read-write) — re-run the installer |
skipped <name>: sold on the store's site |
a paid title; the store downloads anonymously, so buy it on the site |
| Games vanished after a sync | a missing ROMs mount aborts the sync by design; check /userdata/roms is mounted |