modemwars/README.md
2026-08-23 02:09:40 -05:00

92 lines
6 KiB
Markdown

# Modem Wars (Commodore 64, 1988) - annotated disassembly
A complete, reassemblable, commented disassembly of Dan Bunten's *Modem Wars* (Ozark Softscape /
Electronic Arts, 1988) produced from the disk image `Modem_Wars_1988_Electronic_Arts_b4.d64`.
* `docs/overview.md` - how the disk is organised, how the game boots, the fast-loader protocol, the
sector cipher, the run-time memory map and the overlay system. Start here.
* `docs/knowledgeBase.md` - every routine and data block with its purpose (generated from the
survey pass of the annotation work).
* `docs/titleScreen.png`, `docs/screenshots/` - the loader's title picture decoded straight from the
disk bytes, and screenshots taken by driving the game in VICE
(`extracted/vice/playSession.py` boots it on a private X display and feeds it real key events).
* `docs/track1_DanBuntenLetter.txt` - the letter to pirates that is hidden in plain text on track 1.
* `disassembly/` - the sources:
* `boot/` - the two directory files (`ea`, `load`), the C128 boot sector and its CBM80 stub, and
the title-picture data;
* `drive/` - the code that runs inside the 1541 (bootstrap sector and fast loader / cipher);
* `game/` - the game itself: main program, every overlay variant, data blocks;
* `c64.inc`, `kernal.inc`, `drive1541.inc`, `zeropage.inc`, `drive_zp.inc` - symbol files;
* `XREF.txt` - cross reference (callers, readers, writers) for every label and zero-page location;
* `INDEX.txt` - one line per source file with its address range;
* `verify.sh` - reassembles every file with `ca65`/`ld65` and compares it byte for byte with the
bytes extracted from the disk.
* `extracted/` - raw and decrypted sectors, the directory files, the reconstructed memory images and
the VICE automation used to validate them.
* `annotations/` - the comment/label database (JSON) that `tools/build.py` merges into the sources.
* `tools/` - the disassembler pipeline:
* `build.py` - rebuilds the whole of `disassembly/` from the decrypted sectors plus `annotations/`;
* `m6502.py` / `discover.py` - instruction decoder, recursive-descent tracer, and the discovery
pass that follows interrupt vectors and self-modified jump tables into code the tracer alone
cannot reach;
* `render.py` - turns a traced memory image into annotated ca65 source (bit art for graphics,
grids for tables, per-file table of contents);
* `findStrings.py` - finds the bit-7-terminated message strings still sitting in data areas;
* `chunks.py`, `mergeSurvey.py`, `extractAssets.py`, `hwSymbols.py` - supporting tools;
* `phase2_annotate.js`, `phase3_finish.js` - the multi-agent passes that produced the comments.
The listing is never edited by hand. Everything a human contributed lives in `annotations/*.json`
(labels, comments, data types, zero-page names) and `tools/build.py` regenerates `disassembly/` from
the decrypted sectors plus those annotations, so the sources can always be reproduced from the disk.
## What is in the disassembly
24 source files, 56,000 lines, covering 74,741 bytes: the C128 boot sector and its C64 cartridge
stub, both directory files, the code that runs inside the 1541 drive, the main program, every
overlay variant, and all of the data. About 65% of those bytes are code and the rest data.
* **Every one of the 21,627 instructions carries a comment** explaining what it is for, not what the
mnemonic does.
* Every routine has a header giving its purpose, its inputs, its outputs and its callers, and every
source file opens with a table of contents.
* All 164 zero-page locations the game uses are named and described, as are the work-RAM variables,
the unit record arrays and the hardware registers.
* Data is rendered as what it is: character and sprite graphics as bit art, tables as rows of the
right width, strings as text, jump tables as address lists.
* `verify.sh` reassembles all 24 files with `ca65`/`ld65` and compares each against the bytes taken
off the disk. All 24 match exactly, so nothing in the listing is guesswork about the bytes.
## Rebuilding and verifying
```
python3 tools/build.py # regenerate disassembly/ from extracted/ + annotations/
./disassembly/verify.sh # reassemble everything and compare with the original bytes
```
Requires Python 3 and the cc65 tool chain (`ca65`, `ld65`).
## Conventions used in the listings
* Every instruction line ends with `; XXXX` - its address - followed by the comment.
* Labels are camelCase; `sub_XXXX` / `L_XXXX` / `D_XXXX` are auto-generated names for code and data
that has not (yet) been given a meaningful name.
* Labels are scoped to a source file. The `$6F00-$87FF` and `$E000-$EFFF` regions exist in two
variants each; when code in one file calls into such a region the name of the variant that really
contains code at that address is used, and the other variant's name is mentioned in the comment.
* Strings end with a character whose bit 7 is set and are written as `.byte "TEXT",'T'|$80`.
* Hardware registers use the names from `c64.inc` (`VIC_*`, `SID_*`, `CIA1_*`, `CIA2_*`).
## SwiftLink driver
`swiftlink/` holds an alternate opponent module that drives a **SwiftLink cartridge** (a 6551 ACIA on
the expansion port) instead of the shipped module's bit-banged user-port UART, so the game can play
over a modem or a null-modem cable at up to 38400 baud instead of 300/1200. The shipped module is
layered and only its bottom layer is hardware specific, so the packet layer, the ARQ frame layer, the
ring buffers and the link state machine are left byte-for-byte alone: 434 of the module's 4096 bytes
changed, every routine kept its address, and a SwiftLink player and a stock-driver player can still
play each other at a shared rate.
`./swiftlink/build.sh` assembles the module and writes `Modem_Wars_SwiftLink.d64`, a copy of the game
disk that differs from the original in four sectors. See `swiftlink/README.md` for the hardware it
expects, the baud and hot-key tables, the direct-connect mode, the address-by-address map of what
changed, and an honest account of what the VICE testing did and did not prove.