Skip to content

Repository files navigation

lameboy

A Game Boy / Game Boy Color emulator packaged as a BBS door: callers connect over telnet/SSH and play retro games in the BBS, with CP437 block/ASCII rendering, ANSI-music or streamed-PCM sound, per-user saves, network link play, and a ROM browser built for slow, remote terminals. It runs on any BBS that launches a door over stdio or hands it a connection via a DOOR32.SYS dropfile — Synchronet, EleBBS, Mystic, and friends, on Linux, Windows, macOS, or FreeBSD.

The Game Boy emulation itself is the Apache-2.0 gameboy_core crate, vendored and patched to expose its APU for the ANSI-music engine (see NOTICE and vendor/gameboy_core/PATCH-NOTES.md).

What makes it a door

What it takes to make an emulator behave as a multi-user door over a remote link. Full detail (with file/symbol pointers) is in PATCH-NOTES.md.

  • Sound without a sound card — a door has no local audio device, so the Game Boy's audio reaches the caller two ways, selectable in the menu: ANSI (SyncTERM ANSI-music/MML — universal but monophonic) and APC (real PCM streamed as SyncTERM audio APCs — full Game Boy audio, on a terminal that supports them). Local sound-card playback (rodio) is an optional localaudio build feature, off by default, so the distributed binary is pure-Rust with no native audio dependency.
  • Cross-BBS connection layer — the door talks to the caller over whatever the BBS provides: an inherited socket named in a DOOR32.SYS dropfile (the Windows-BBS norm, where the handle is a Winsock SOCKET, not an fd), or stdio. Its own input decoder and raw output replace a console-only terminal library, so it works the same on a Unix pty, a Windows BBS socket, or a redirected pipe.
  • CP437 output layer — the menu/UI Unicode glyphs are emitted as CP437 bytes (e.g. the 0xDF half-block), which is what BBS terminals expect from a door; raw UTF-8 would garble on both CP437 and UTF-8 clients.
  • Door-friendly launch — flags work without a positional ROM (the door is launched with no ROM and shows its browser), it reads the caller's identity from the dropfile, and drops back to the menu on quit instead of exiting.
  • ROM browser rebuilt for large libraries — type-ahead jump, a Tab-cycled All / Game Boy Color / Game Boy filter, and names with a GB/GBC type tag. The responsive Game Boy-style menu uses 80x24 as its minimum and expands the game list and square preview viewport on larger terminals. The ROM directory is a sysop setting (--roms / config), not a caller-facing browser — no letting callers wander the server's filesystem.
  • Offline game-art previews — a bundled sysop tool fetches only the Libretro artwork matching installed ROMs into a local, gitignored cache. SIXEL is used only when CTerm/Primary DA advertises it; every other terminal gets an internal ANSI half-block rendering. Caller sessions never make web requests.
  • Per-user state — render mode, sound, and filter preferences persist per BBS user, and battery saves are isolated under .saves/<user>/.
  • Link backpressure pacing — the transmit frame rate is capped (default 20 fps, --fps N) and frames are skipped when a slow/congested link can't keep up, so the door degrades gracefully instead of flooding the connection.
  • Live resize — a door connection delivers no SIGWINCH/resize events and a door pty's size is frozen at launch, so terminal size is tracked by probing with a cursor-position report.

Install (sysops)

Prebuilt, dependency-free binaries are attached to each release — Linux (x86_64 / arm64 / armv7 / i686, static), Windows (x86_64 / i686), macOS (arm64 / x86_64), and FreeBSD (x86_64). No libasound2 or other runtime libs required.

  1. Unpack the archive for your platform into a directory under your BBS's external programs (e.g. xtrn/gb/), so you have …/gb/lameboy.
  2. Drop your own legally-obtained .gb / .gbc ROMs into a roms/ folder beside the binary.
  3. Optional: populate local menu artwork for those ROMs: python3 tools/fetch_game_art.py. Existing images are skipped, and artwork is not included in or required by the door distribution.
  4. Add the door in your BBS's door/external-program config (SCFG on Synchronet, the door manager on EleBBS/Mystic/…) with the matching command line below, then recycle/restart the BBS.

Verify a download against SHA256SUMS.txt from the release.

Running as a door

The only argument that matters is how the caller is connected; everything else has sane defaults (and a lameboy.ini, below). The two setups below cover most BBSes — the flag reference is further down.

Synchronet

Add it in SCFG → External ProgramsOnline Programs. The executable here is the released lameboy.exe (rename it to anything you like). The simplest setup uses the Standard I/O method (stdio) and --user %4 — no drop file:

[gameboy]
 1: Name ........................ Game Boy
 2: Internal Code ............... GAMEBOY
 3: Start-up Directory .......... c:\sbbs\xtrn\gb
 4: Command Line ................ lameboy.exe --user %4
 5: Clean-up Command Line .......
 6: Execution Cost .............. None
 7: Access Requirements .........
 8: Execution Requirements ......
 9: Multiple Concurrent Users ... Yes
10: Native Executable ........... Yes
11: I/O Method .................. Standard
12: Use Shell or New Context .... No
13: Modify User Data ............ No
14: Execute on Event ............ No
15: Pause After Execution ....... No
16: Disable Local Display ....... No
17: BBS Drop File Type .......... DOOR32.SYS
18: Place Drop File In .......... Node Directory
19: Time Options...

%4 is the zero-padded user number; it keys per-user saves and preferences.

Prefer a socket? Set line 11 I/O Method to Socket and line 4 to lameboy.exe --dropfile %f. Leave the drop file in the Node Directory (line 18); Synchronet expands %f to its full path, so the door finds it regardless of the working directory. --user is then optional — the identity is read from the drop file.

EleBBS / Mystic / other DOOR32.SYS BBSes

Configure a native door using the socket I/O method and a DOOR32.SYS drop file, then point --dropfile at it:

  Door type / executable ..... Native
  I/O method ................. Socket
  Drop file .................. DOOR32.SYS
  Command line ............... lameboy --dropfile DOOR32.SYS
                              (or the full path to the node's DOOR32.SYS)

--dropfile must resolve to the DOOR32.SYS file — the door reads the inherited socket handle from it. You may pass the file itself or the directory that contains it (e.g. --dropfile .\; the bare-directory form needs v0.4.1+). If the drop file can't be read, the door falls back to stdio — which a socket-mode door isn't connected to, so its output never reaches the caller (the tell-tale symptom: raw escape codes pile up on the server console while the user sees nothing).

The sound mode (Off / ANSI / APC) is chosen by the caller in the menu and persists per user (APC streams full PCM to terminals that support SyncTERM audio APCs). The released build never opens a local sound device, so --mute is unnecessary (it's still accepted).

lameboy.ini (sysop defaults)

Optional file beside the binary, so you don't repeat settings on the command line. Copy lameboy.ini.example to lameboy.ini and edit. Command-line flags override it:

roms = roms        ; ROM directory (default: roms/ beside the binary)
fps  = 20          ; transmit frame-rate cap, 5-60
ansi_music = true  ; offer ANSI-music sound (false = silent/APC only)
link_server = futureland.today:7676 ; public link-play relay

Command-line flags

All optional; each overrides lameboy.ini. Only --dropfile/--user (the per-call connection) are normally passed by the BBS.

Flag Description
--dropfile <path> DOOR32.SYS dropfile: use its inherited socket + user identity
--user <id> Per-user key for saves + preferences (e.g. Synchronet %4)
--roms <path> ROM directory (default: roms/ beside the binary)
--fps <n> Transmit frame-rate cap, 5–60 (default 20)
--ansi-music Force ANSI-music sound on (default on; disable via the ini)
--mute Never open a local sound device (localaudio builds only)
--link <host:port> Link-cable relay for network multiplayer
--block / --ascii Force a render mode (otherwise the caller's saved choice)

Network Link Play

lameboy.ini.example ships with link_server = futureland.today:7676 so callers from different BBSes can meet in the same multiplayer lobby. Leave that enabled unless you specifically want a private relay; a larger shared relay means more players available for link games like Pokemon trades/battles and Tetris.

To run your own relay instead, build and start the bundled link server, then point link_server or --link at it:

cd link-server
cargo build --release
target/release/gb-link-server 7676

The relay listens on 0.0.0.0, so the relay host must allow inbound TCP on that port. For a public relay, also forward/open that port on the BBS firewall or router. Door instances only need outbound access to the configured host:port.

Building

cargo build --release                       # door build: pure-Rust, no audio deps
cp target/release/lameboy ./lameboy   # deploy next to roms/

cargo build --release --features localaudio # optional: real local sound-card audio

The default build omits rodio, so the binary is pure-Rust and links no native audio library — that's what lets release CI cross-compile it for every target. --features localaudio adds rodio for real playback when running locally (needs ALSA headers on Linux: libasound2-dev). The vendored, patched gameboy_core is built from vendor/ (see its PATCH-NOTES.md for the APU change that powers ANSI music). target/, roms/, and the built binary are gitignored.

Releases are built by CI on a v* tag.

Game artwork

Artwork is optional and strictly local at runtime. From the door directory:

python3 tools/fetch_game_art.py                 # box art for every ROM in roms/
python3 tools/fetch_game_art.py --kind snap     # gameplay screenshots instead
python3 tools/fetch_game_art.py --match '*Kirby*' --force

The tool maps .gb and .gbc files to the corresponding Libretro thumbnail repositories, applies RetroArch's filename substitutions, downloads only exact matches, skips cached files, and writes misses to art/missing.txt. Run python3 tools/fetch_game_art.py --help for alternate ROM/cache directories, parallelism, dry-run, and filtering options. Downloaded images under art/ are gitignored; the repository ships only the fetcher and cache documentation.

The menu probes CTerm and xterm-family graphics capabilities. It renders a square, bandwidth-capped SIXEL image when explicitly supported and otherwise uses its built-in ANSI half-block renderer at the selected color depth. No external gif2ans, img2sixel, ImageMagick, or runtime network access is needed.

Controls

Menu

Key Action
↑ / ↓ Move within the active game list or menu
← / → / Tab Switch between the Game and Menu panels
Space Cycle the game filter (All → GBC → GB) in the game list
Type letters Jump to a game (type-ahead) in the list
← / → / Space Change a value inside an options page
Z / Enter Play the selected game / confirm
Esc Back from options; otherwise quit
Q Quit the door

In-game

Key Action
Arrow keys D-Pad
Z A button
X B button
Enter Start
Space Select
Esc / Q Return to the menu

Save files

Battery saves are written automatically on exit and loaded on launch. With a --user key they are isolated under .saves/<user>/; without one they fall back to a shared .saves/ next to the ROM.

License

lameboy is MIT — see LICENSE. It bundles third-party components under their own licenses, listed in NOTICE (notably the Apache-2.0 gameboy_core emulator).

Acknowledgments

  • gameboy_core — Game Boy emulation core (Apache-2.0)
  • crossterm — terminal handling
  • rodio — optional local sound-card playback (localaudio feature)

About

Door game: Gameboy color emulator with sound for BBS

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages