Skip to content

Emulator atari800

Retro Jack edited this page Jun 9, 2026 · 8 revisions

atari800 — Atari 800XL

The Atari 800XL bundle is the only one in GenX-DOS where we ended up compiling the emulator from source, because nothing else fit.

Where we started — and got blocked

The shortlist was three projects. All three fell out, in order:

  1. jsA8E (AnimaInCorpore/A8E) — looked promising; a real WASM build of A8E, working in a browser. No LICENSE file in the repo, though. Under default copyright we can't legally redistribute, so it was out.
  2. EmulatorJS — covers a lot of ground but the upstream supported-systems list does not include Atari 800; the closest core is a5200. No good.
  3. libretro-atari800 in the official RetroArch emscripten nightly — plausible because the same nightly ships ~90 other cores. We downloaded the full 760 MB RetroArch.7z from buildbot.libretro.com/nightly/emscripten/RetroArch.7z to verify. atari800 is not one of the cores in the web build. (Useful side-effect: that audit confirmed VICE, gearcoleco, and o2em are in there, which seeded the C64 / Coleco / Odyssey² bundles we did later.)

So we built atari800 from source.

The toolchain

emscripten via emsdk, fetched into /tmp because the build needs to be reproducible and that's the path the recipe encodes:

cd /tmp && git clone --depth 1 https://github.com/emscripten-core/emsdk.git
cd /tmp/emsdk && ./emsdk install latest && ./emsdk activate latest
source /tmp/emsdk/emsdk_env.sh

emsdk lives at ~/emsdk/ on this machine for persistence between sessions.

The fake sdl2-config problem

atari800's autoconf uses AM_PATH_SDL2, which shells out to sdl2-config. emscripten ships SDL2 as a port (it's pulled in automatically by -sUSE_SDL=2) but doesn't ship the sdl2-config binary. The build dies on detection before it even gets to the C files.

The fix was a six-line shim that pretends to be sdl2-config and tells autoconf whatever it wants to hear:

cat > /tmp/fake-sdl2-config <<'EOF'
#!/bin/sh
case "$1" in
  --cflags|--cflags-only-I|--libs|--libs-only-l|--static-libs|--prefix) echo "" ;;
  --version) echo "2.30.0" ;;
esac
EOF
chmod +x /tmp/fake-sdl2-config

Then SDL2_CONFIG=/tmp/fake-sdl2-config emconfigure ./configure … runs cleanly.

Configure flags, each one earned by a real failure

SDL2_CONFIG=/tmp/fake-sdl2-config emconfigure ./configure \
  --target=default \
  --with-video=sdl2 \
  --with-sound=sdl2 \
  --without-opengl \
  --disable-netsio \
  --disable-riodevice --disable-rserial --disable-rnetwork \
  --disable-audiorecording --disable-videorecording \
  --enable-altirra_bios \
  CFLAGS="-O2 -sUSE_SDL=2" LIBS="-sUSE_SDL=2"

Why each flag is there:

  • --without-opengl — atari800's SDL+GL path calls eglCreateContext, which fails in emscripten and leaves the canvas black. The software SDL renderer works fine.
  • --disable-netsio / --disable-riodevice / --disable-rserial / --disable-rnetwork — these all need clock_gettime or a real host TTY/serial port, both of which trip emcc's strict implicit-declaration check. Nothing about them works in a browser anyway.
  • --disable-audiorecording / --disable-videorecording — strips RLE/ZMBV/WAV/ADPCM/μ-law codecs we don't need.
  • --enable-altirra_bios — this is the big one. It bakes Avery Lee's AltirraOS-XL 3.41 directly into the WASM. No copyrighted Atari OS ROM needs to ship alongside the bundle; the emulator falls back to AltirraOS automatically when no real ROM is found. This is also why emulators/atari800/ has no roms/ directory full of .bin files — the OS lives inside atari800.wasm.
  • -sUSE_SDL=2 — pulls emscripten's SDL2 port for canvas, keyboard, and WebAudio.

The relink step

The plain emmake make builds a Node-style src/atari800 script, not a browser-loadable bundle. To get the browser artefacts:

cd src
OBJS=$(ls atari800-*.o sdl/atari800-*.o roms/atari800-*.o codecs/atari800-*.o atari_ntsc/atari800-*.o 2>/dev/null | grep -v video_gl)
emcc -O2 -sUSE_SDL=2 -sASYNCIFY -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=64MB \
  -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS -sEXIT_RUNTIME=0 \
  -o atari800.html $OBJS -lm

-sASYNCIFY is non-optional. atari800's for(;;) main loop in sdl/main.c would hang the browser without it. ASYNCIFY suspends and resumes at SDL_Delay so the page can paint between frames.

Output: atari800.js (186 KB) + atari800.wasm (1.7 MB). The default emcc shell HTML gets thrown away — play.html replaces it.

The play.html contract

play.html sets up window.Module before injecting atari800.js:

  • canvas — the <canvas> element
  • arguments['/game.atr'] for disks, ['-cart', '/game.rom'] for cartridges, optionally ['-cart-type', N, '-cart', '/game.rom'] for headerless cart dumps (Star Raiders needs cartType:1 = Standard 8KB)
  • preRun — calls Module.FS.createPreloadedFile('/', vfsName, romUrl, true, false) to fetch the ROM into the WASM virtual FS before main() runs

One game-source gotcha

The Star Raiders .atr shipped as a8b_Star_Raiders_1979_Atari_US_k_file is a cart-binary-in-disk-wrapper — it boots straight into a 6502 CIM at $A184. The right source is the 8 KB .rom from a8b_cart_Star_Raiders_1979_Atari_US, launched with -cart-type 1.

A note on headless verification

Headless chromium screenshots of SDL2/WASM canvases look black even when frames are being drawn — SwiftShader/compositor timing makes them unreliable. Verify in a real browser, or read pixels via getImageData from a probe page.

Bundle layout

emulators/atari800/
├── play.html
├── atari800.js
├── atari800.wasm        ← AltirraOS baked in here
├── games.json
├── controls.html
├── build-wasm.sh        ← reproducible build recipe
└── roms/                ← cart / disk / cassette images

The reproducible build script lives in the Retro-Jack/atari800 fork — upstream source untouched, just build-wasm.sh + BUILDING-WASM.md on top.

Related

Clone this wiki locally