Reverse-engineering notes for Battle Bugs, the tactical insect-warfare game published by Sierra On-Line in 1994 and developed by Epyx — the last game Epyx shipped before the studio wound down.
This repository documents the on-disk formats: the resource container the whole
game is built out of, the 16-colour planar image codec, the text tables, the
level layout, the sprite organisation, and the audio middleware. The compressed
image encoding was taken from the decompressor in BUGS.EXE, and
docs/11 records the routine addresses so the claims can
be checked against the code rather than believed.
No game data is included here. The tools read a copy of the game you already
own. Every structural claim in docs/ is re-checkable in one command:
python tools/bb_verify.py --dir /path/to/Bugs
which prints 59 PASS/FAIL lines against the real files.
Published as is. These are reverse-engineering notes, not a specification: they are revisited and corrected over time, but no finding here is guaranteed to be complete or correct, whether or not a given section says so. What is marked open in docs/10 is what is known to be unresolved, not the limit of what might be wrong. Prefer the verifier over the prose.
The demo build (README.TXT is titled BATTLE BUGS DEMO; BUGS.EXE is
dated 1994-07-14, 206,531 bytes). The retail build ships three additional
resource files that the demo omits but the executable still names — see
docs/09-executable.md.
| Executable | Real-mode 16-bit MZ, no DOS extender, 1647 relocations |
| Extra memory | EMS (probes EMMXXXX0, INT 67h) |
| Video | 640×480 or 800×600, 16 colours, planar (VGA mode 12h / VESA 102h) |
| Music | XMIDI (FORM…XDIR) through Miles Design AIL drivers |
| Digital audio | DIGPAK (The Audio Solution), 8-bit unsigned PCM |
| Configurator | Separate BBCONFIG.EXE, built with Borland C++ |
16 colours at 800×600, in 1994. The obvious way to read "800×600" on a 1994 box is "expensive". The actual engineering is the opposite: by staying at 4 bits per pixel and planar, a full screen is 240 KB of video memory the CPU never has to touch as a linear framebuffer, and the mode is plain VESA 102h with a 640×480 mode 12h fallback that needs no VESA driver at all. The art budget, not the resolution, is what 16 colours buys back.
One asset set, two resolutions. A full-screen background is stored as 40 tiles of 128×60 — exactly a 5×8 grid covering 640×480. Screens that also support 800×600 ship a second resource holding four bands: 800×60 top, 80×480 left, 80×480 right, 800×60 bottom. 640+80+80 = 800, 480+60+60 = 600. The higher mode is the same picture with a frame drawn around it, not a different picture.
A codec built around vertical runs and duplicate bitplanes. The image compression is per-bitplane, and within a plane it runs down 8-pixel columns rather than along scanlines. On top of that, any bitplane identical to an earlier one is stored as a single byte naming its twin — which is common in 16-colour art and is where most of the compression actually comes from. Both choices are invisible from the outside and both defeat conventional RLE fitting; docs/10 keeps a note on exactly how.
The radio. Battle Bugs delivers its manual, its unit stats and most of its
jokes through an in-game radio with numbered channels. Those channels are 40
216×144 images in CARDS — unit stat cards, terrain cards for the food you fight
over (a Plop Tart™ has HEIGHT: 2, CLIMBABLE: YES), the command reference, and
parody TV listings for station KNSX (The Lice Is Right, Dung Ball of
Fortune, Movie: Nesting with the Enemy).
A demo with the retail skeleton left in. CARDS has 106 slots and fills 40;
BUGS has 44 sprite slots — 22 unit types × 2 sides — and fills 14. The empty
slots are two-byte stubs, so the demo is the retail layout with most of the
payload removed rather than a separately authored build.
| Document | Contents |
|---|---|
| 01 — File inventory | Every shipped file, size, role, and which are missing from the demo |
| 02 — Container format | The single offset-table idiom the whole game uses, and its nesting |
| 03 — Image format | The 9-byte header, the planar strip layout, and the compression |
| 04 — Video modes and palettes | Mode selection, the 7 palettes, the dual-resolution screen scheme |
| 05 — Text | PERMSTR / TEMPSTR / level-name tables and their three idioms |
| 06 — Levels | Six-entry battle groups, terrain grids, object arrays |
| 07 — Units and sprites | The 22×2 slot map and the eight-facing mirrored animation layout |
| 08 — Audio | XMIDI, the AIL/DIGPAK driver bundle, the sample banks |
| 09 — Executable and memory | MZ layout, EMS use, the retail files the demo drops, BBCONFIG.DAT |
| 10 — Open questions | Tile metadata, level records, and a note on how the codec resisted analysis |
| 11 — Disassembly notes | Routine addresses in BUGS.EXE: decompressor, blitter, hit test, mode set |
Python 3, Pillow only for rendering.
python tools/bb_extract.py list # container census
python tools/bb_extract.py strings # PERMSTR + TEMPSTR
python tools/bb_extract.py levels # battle names + terrain grids
python tools/bb_extract.py render CARDS out/ # the radio channel cards
python tools/bb_extract.py --palette 4 screens out/ # 640x480 backgrounds
python tools/bb_extract.py --palette 1 tiles out/ # 396 terrain tiles
python tools/bb_extract.py --palette 1 sprites out/ # unit sprites, all facings
python tools/bb_extract.py raw SONGS out/ # 38 playable .xmi files
python tools/bb_verify.py # re-check the documentation
Point everything at your install with --dir or BB_DIR. Nothing in an image
record names the palette it expects, so --palette is yours to pick: 1 for the
battlefield and its units, 2 for the interface, 4 for the title screen.
Solved: container format, text tables, palettes, the image codec in both its raw and compressed forms, the dual-resolution screen scheme, level structure, sprite organisation, audio containers, executable characteristics.
Every image in the game decodes — 40 cards, 11 reassembled 640×480 screens, 396 terrain tiles, 1034 unit sprite frames.
Partly solved: the per-tile metadata trailers in TILES.BIN. The grammar is
settled — three items per tile, each either a six-entry block or a
back-reference — and 347 of 396 tiles parse; what the entries mean, and the
block-length rule that would resolve the rest, are not.
Still open: the coordinate encoding in the level object records, the sprite
parameter tables, and the RECORDS.BIN obfuscation. See
docs/10.