Modem Wars for the C64 disassembled, commented, and updated with SwiftLink support.
Find a file
2026-08-23 18:47:11 -05:00
annotations Initial commit. 2026-08-23 02:09:40 -05:00
disassembly Initial commit. 2026-08-23 02:09:40 -05:00
docs Added box art. 2026-08-23 18:47:11 -05:00
extracted Initial commit. 2026-08-23 02:09:40 -05:00
survey Initial commit. 2026-08-23 02:09:40 -05:00
swiftlink Initial commit. 2026-08-23 02:09:40 -05:00
tools Initial commit. 2026-08-23 02:09:40 -05:00
.gitattributes Initial commit. 2026-08-23 02:09:40 -05:00
.gitignore Initial commit. 2026-08-23 02:09:40 -05:00
Modem_Wars.pdf Initial commit. 2026-08-23 02:09:40 -05:00
Modem_Wars_1988_Electronic_Arts_b4.d64 Initial commit. 2026-08-23 02:09:40 -05:00
Modem_Wars_SwiftLink.d64 Initial commit. 2026-08-23 02:09:40 -05:00
README.md Initial commit. 2026-08-23 02:09:40 -05:00

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/ 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.