45 kilobytes hiding in plain sight

5 min read
apple-iicopy-protectionretrocomputingapple-panic-series6502

After the custom RWTS loads tracks 3–35, execution passes to a decompressor. The total compressed payload is 45 KB. The uncompressed game data is larger than the Apple II’s accessible address space. Something had to give.

The decompressor is hand-rolled 6502 — about 120 lines of assembly. Spent a day tracing it in AppleWin before the pattern became clear: it’s a literal/run-length scheme. A flag byte signals whether the next token is a literal run (copy N bytes as-is) or a compressed run (repeat one byte N times). No Huffman, no LZ, no dictionary. Extremely simple, effective enough.

The decompressed game data lands across multiple memory regions. Some of it goes into the language card (the 16 KB bank-switched RAM above $D000). Some of it goes into the zero-page area the decompressor frees up once it’s done. The layout is tight — there are maybe 200 bytes of slack in the final configuration.

Once the decompressor finishes, it jumps to the game entry point. At that moment Apple Panic looks exactly like any other Apple II program sitting in memory. There’s no remaining evidence on the stack, in the registers, or in memory that any of this happened.

Wrote a Python script to decompress the payload offline. The output matches what AppleWin shows after the game loads. We now have a complete picture of the disk from VTOC to game entry.

Decompressor size: ~120 instructions. Scheme: literal/run-length. Compressed size: 45,312 bytes. Uncompressed: fills available RAM with ~200 bytes free.