SDK (Software Dev. Kit)
Build and deploy community External Applications for compatible AtGames arcade devices using C++, SDL2, and Docker.
Overview
The AtGames External Application SDK lets community developers build games and utilities that run directly on compatible AtGames arcade devices.
External Applications are standard Linux executables compiled for the device's
aarch64 processor. The SDK handles cross-compilation inside Docker so you
never need to install a legacy Linux toolchain on your own machine.
The SDK ships two fully playable sample games : Brick Breaker and Skyfall. They demonstrate the complete integration from controls and audio to local leaderboards.
Latest features may require beta firmware
New External Applications features, such as writable USB storage support, may require enrollment in the beta program before they are available on standard firmware. These features will often arrive on Legends 4KP devices first, then roll out to other compatible devices over time.
Requirements
| Requirement | Notes |
|---|---|
| Docker | The entire build runs inside a container — required |
| C++ (SDL2) | Apps are written in C++ using SDL2 for graphics and input |
| A USB drive | Deployment means copying a folder to USB and plugging it in |
On Windows, use the included run.bat script or run .sh scripts via Git Bash
or WSL.
How It Works
The build system cross-compiles your C++ source code inside a Docker container
running Ubuntu 16.04. This ensures the output binary targets aarch64 Linux and
stays compatible with the firmware's glibc 2.26 userspace. The device might
reject binaries that depend on newer symbols.
When the build finishes, the ready-to-deploy files appear in dist/external/.
Copy the dist/ contents to the root of a USB drive and plug it in. The device
scans the USB automatically and shows your app under External Applications.
USB Layout
Each application lives in its own folder under external/ at the USB root:
external/
└── my-game/
├── my-game.elf
├── my-game.png
├── my-game.xml
└── res/
└── audio/| File | Description |
|---|---|
my-game.elf | Compiled binary |
my-game.png | Icon shown in the arcade menu |
my-game.xml | Metadata (name, developer, description…) |
res/audio/ | Optional audio assets |
The .elf, .png, and .xml files must all share the same base name and sit
directly in the app folder. The SDK build writes this layout automatically. You
only need to copy dist/ to the USB root.
App Metadata (XML)
The XML file provides the name and optional information shown by the firmware:
<root>
<name>My Game</name>
<developer>Studio Name</developer>
<category>Arcade</category>
<year>2026</year>
<copyright>Copyright 2026 Studio Name</copyright>
<description>A short description shown on the arcade display.</description>
</root>Only <name> is required. When at least one optional element is present, the
firmware shows an About button next to Launch with the full metadata.
Quick Start
Build the included Brick Breaker sample:
./run.sh brick-breaker-appOn Windows:
run.bat brick-breaker-appThe first run builds the Docker image automatically. Subsequent builds reuse it.
The output lands in dist/external/brick-breaker-app/. Copy dist/ to a USB
drive and the app appears on the device under External Applications.
Runtime Environment
The firmware passes these environment variables to every External Application at launch:
| Variable | Description |
|---|---|
ATGAMES_USERNAME | Account display name, or Legends ID as fallback. Empty for guests. |
ATGAMES_UNIQUE_ID | Stable per-device identifier derived from hardware. |
ATGAMES_DEVICE_TYPE_ID | Device model code, e.g. HA9920. |
ATGAMES_DEVICE_TYPE_NAME | Product family name, e.g. Legends Ultimate. |
ATGAMES_FIRMWARE_VERSION | Installed firmware version string. |
SDK Modules
Controls
sdk/controls/Controls.h provides a device-independent input interface.
Instead of handling raw joystick button numbers, your game receives high-level
logical events:
#include "Controls.h"
// Keep one Controls instance alive for the lifetime of your app.
AtGames::Controls controls;
// Initialize SDL controller support before opening the helper.
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
controls.open();
bool running = true;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
// SDL_QUIT still belongs to your app's normal lifetime handling.
if (event.type == SDL_QUIT) {
running = false;
}
// event() converts SDL input into one-shot AtGames control actions.
switch (controls.event(event)) {
case AtGames::ControlEvent::A:
case AtGames::ControlEvent::Start:
// Treat A and Start as confirm/start in this example.
startOrConfirm();
break;
case AtGames::ControlEvent::Back:
// Back is commonly used to close menus or leave the app.
closeMenuOrExit();
break;
default:
break;
}
}
// horizontal() is continuous, so read it every frame for held movement.
const float move = controls.horizontal(); // -1.0 left, 0.0 idle, 1.0 right
updatePlayer(move);
renderFrame();
}The usual integration flow is:
- Include
sdk/controls/Controls.h. - Initialize SDL with
SDL_INIT_GAMECONTROLLERandSDL_INIT_JOYSTICK. - Create one
AtGames::Controlsinstance and callcontrols.open()afterSDL_Init. - Pass each SDL event to
controls.event(event)for one-time actions such as confirm, start, back, or menu navigation. - Read
controls.horizontal()every frame for held left/right movement.
Available events include DpadUp, DpadDown, DpadLeft, DpadRight, A,
B, X, Y, Start, Back, Guide, Rewind, Rewind2, LeftShoulder,
RightShoulder, LeftTrigger, RightTrigger, and None.
The helper works with keyboard input for desktop development, SDL GameController devices on Legends hardware, and selected CE-mapped joystick fallbacks for navigation buttons.
Leaderboard
sdk/leaderboard/ provides local score storage saved directly to the USB drive,
with an optional full-screen SDL2 overlay. No server is required for local
scores.
// The ID must match the app folder name on the USB drive
AtGames::Leaderboard leaderboard("my-game");
// Submit a score at the end of a run
leaderboard.submitScore(score);
// Read the top 10 scores
auto top = leaderboard.topScores(10);Scores persist as JSON at external/my-game/data/leaderboard.json on the USB
drive. If the USB is not writable, scores stay in memory for the current session
and the game keeps running normally.
Optional overlay: Renders a full-screen leaderboard embedded directly in the binary:
AtGames::LeaderboardOverlay overlay(leaderboard);
overlay.open();
overlay.render(renderer, logicalWidth, logicalHeight);
overlay.close();Online Leaderboard Server (optional)
leaderboard-server/ is a Hono + Drizzle backend template for online
leaderboards, deployable to Cloudflare Workers with D1. Local leaderboards
work without it.
Compatibility Check
Every build automatically verifies the output binary for glibc symbol requirements. The maximum supported version is glibc 2.26 — binaries that depend on newer symbols will crash on the device.
You can also run the check manually:
sdk/check-compat.sh dist/external/my-game/my-game.elfSDK Structure
SDK/
├── run.sh / run.bat
├── apps/
│ ├── brick-breaker-app/
│ └── skyfall-app/
├── dist/external/
├── sdk/
│ ├── controls/
│ ├── leaderboard/
│ └── check-compat.sh
└── leaderboard-server/| Path | Description |
|---|---|
run.sh / run.bat | Entry point for all builds |
apps/brick-breaker-app/ | Sample: Brick Breaker (portrait SDL2) |
apps/skyfall-app/ | Sample: Skyfall (portrait SDL2) |
dist/external/ | Build output — copy to USB root |
sdk/controls/ | Shared arcade input helper |
sdk/leaderboard/ | Shared local leaderboard + overlay |
sdk/check-compat.sh | glibc symbol compatibility check |
leaderboard-server/ | Optional online leaderboard backend |