Commodore 64 · 1982 · Brøderbund Software

About this page

Footprint

Every byte of the C64's 64 KB, one pixel each, 512 to a row. Lit pixels are what this game is: its code, graphics, level data, sound, text, tables and variables, exactly where they sit in memory. Dim areas are screen and working memory; dark is unused.

WhatBytesOf 64 KB
Program23,66136.1 %
Code12,30618.8 %
Graphics4,6207.0 %
Text1660.3 %
Tables5,7538.8 %
Variables8161.2 %
Screen, bitmap, colour, stack, I/O22,40034.2 %
ROM the game runs under8,19212.5 %
Unused11,28317.2 %

Status

Tiersilver
Coverage100 % of the bytes the game uses have a description
Build analysedcartridge image, CBM80 header at $8004, cold start $9593
Copyagent-draft
Toolsemulator: VICE x64sc 3.10 with vice-mcp, disassembler: regenerator2000 0.9.20
Modelclaude-opus-5 (Claude Code), with nine subagents on the same model
Kit version0.0.1

Contributors

Other places to love this game

What the game does, and where we found it

Read this before annotating code. What the game is documented to do, with verification status against the binary. Statuses: open (documented, not found yet), traced (in the code, could not be exercised; say what was tried), confirmed (in the code, consistent with the emulator), live (observed directly), differs (the code does something else). "Absent" is not a status.

Sources: the C64 manual scanned at https://archive.org/details/Choplifter_1982_Broderbund (text only; no images were taken from it), the C64 entry on c64-wiki (https://www.c64-wiki.com/wiki/Choplifter), the game's own title screen, and play in the emulator. Every screenshot in reference/ was taken from the emulator during this run.

Features

FeatureStatusWhere
Helicopter flown with a joystick in control port 1liveread at $DC01 from $976A, $97AD, $A71A, $A741
A short press of the fire button shootslive$9774 compares the hold counter $96 with 15; under 15 it calls $97D2
A longer press turns the helicopterlive15 or more calls $97F1; the turn continues while the button is held
Direction of the turn follows the stickdiffersthe stick is read only when $59 is 0 or ±1. At a full facing the hold simply flips the machine to the other side, whatever the stick does ($9816)
64 hostages in totalconfirmed$0CEE + $0CEF is compared with #$40 at $9683
Hostages are held in sheds that have to be blown openlive$B652 sets $0CF4,x; the shed body switches from shape 71 to 72
Eighteen hostages per barracksdiffers$BE52 puts 16 in each of sheds 1 to 3 and 8 in shed 4, which starts open
At most 16 hostages ride at onceconfirmed$9D18 refuses to board when $0CF1 holds 16
Hostages walk to the helicopter and climb inlive$9B7D; boarding at a signed distance of +8/+9 or −11/−10
The helicopter can run its own people overconfirmeda hostage at −9 to +7 with the machine down and still is killed ($9E14)
Status bar counts killed, aboard and deliveredlivethree BCD counters written by $A978; $0CFC, $0CFE, $0CFD
Tanks attack from the far side of the borderliveactor type 3, handler $A0BB, five gun elevations
Jets appear after the first sortieconfirmedthe type-9 cap at $A3B6 is 0 at difficulty 0 and rises to 1 and 2
Drones appear on the third sortietracedactor type 10, handler $B73D, released from sortie 2 on, one at a time, homing on the helicopter. The shared emulator stayed on sortie 0 for the whole watch, so this rests on the code and the shape, not on seeing one
Three helicopters per gamelivethe life count is sortie_number ($0CFF). A sortie ends only when the helicopter is destroyed: the settle counter $62 runs to 6 ($A875), sets sortie_end_delay ($63), the main loop adds 3 a pass until it goes negative ($968E) and jumps to sortie_start, which increments $0CFF ($964B) after the message. $0CFF = 3 at $9621 is THE END. Live: a full-speed crash with $0CFF = 2 ran the whole chain and ended the game
No points are scored, only hostages countedconfirmedthere is no score variable; the only counters are the three on the bar
Sorties are announced as FIRST / SECOND / THIRD SORTIElivemessage table at $8AD7, drawn into a raster text window by $8A8B
THE END and MAGNIFICENT! end messagesconfirmedsame table, shown from $96E4 and $96E8
RUN/STOP pauseslive$98BC reads the KERNAL scan code $3F from $C5
Q ends the gameopenno read of the Q key was found. The game's only keyboard input is the RUN/STOP poll at $98BC, reached through $C5, and the only other keyboard code is the KERNAL scan the raster handler calls. Searched: every $DC00/$DC01/$C5/$CB/$0277 access in $8000-$BFFF, and every immediate comparison against the matrix code for Q ($3E) and its PETSCII ($51)

Beyond the documentation

Open questions

How the analysed state was reached

How to get from the contributor's own copy to the analysed state. Someone else must be able to follow this exactly.

The image

work/choplifter.d64, a 174,848-byte disk image. The directory shows a disk named WWW.C64HQ.COM holding one program:

NameTypeBlocks
CHOPLIFTERprg66

The program loads at $0801 and ends at $4904. $0801 is a one-line BASIC program, SYS 2064, and $0810 is a 24-byte copy loop that moves $0900-$48FF up to $8000-$BFFF, writes $36 to $01 and jumps to $959C.

What it copies is a 16 KB cartridge image. $8000/$8001 and $8002/$8003 are the cartridge cold and warm start vectors, both $9593, and $8004-$8008 holds the CBM80 signature that makes a C64 start a cartridge on reset. The disk file is that cartridge with a loader bolted on the front, and the loader enters at $959C rather than at the cartridge's own $9593. There is no trainer, no cracktro and no decompression. Nothing identifies a cracking group.

The game's own title screen is the only build marker: BRODERBUND SOFTWARE PRESENTS, CHOPLIFTER!, DAN GORLIN, DANE BIGHAM, (C) 1982.

From power-on to play

  1. Attach the image to drive 8 and autostart it. On a PAL C64 with a 1541 the load takes about 40 seconds. Autostart leaves the emulator in warp mode; turn warp off before timing anything.
  2. The title screen appears and stays. There is no attract demonstration.
  3. Press fire on joystick port 1. Play starts at once, with the helicopter parked at the base.

Getting fire into the machine is the awkward part of this game. The vice-mcp vice_joystick_set tool only drives CIA1 port A ($DC00, control port 2); called for the other port it reports success and changes nothing, and vice_keyboard_matrix did not move $DC01 either. Choplifter reads control port 1 at $DC01. The way in is to make CIA1 port B an output and write the port directly:

python3 - <<'PY'
import sys, time
sys.path.insert(0, 'kit/scripts')
from vice import connect, stick_arm, stick, FIRE, UP, LEFT
rpc = connect()
stick_arm(rpc)          # DDRB = $1F, so writes to $DC01 are what the game reads
stick(rpc, FIRE); time.sleep(0.2); stick(rpc, 0)
PY

stick_arm, stick and stick_release are in kit/scripts/vice.py. Bits are the standard ones, active low: 1 up, 2 down, 4 left, 8 right, 16 fire. Leaving bits 5-7 as inputs keeps the keyboard columns the game shares that read with.

Snapshots

FileState
work/play-inflight.vsffirst sortie, helicopter airborne over the base, nothing carried. This is the snapshot everything downstream is read from.

Saved without ROMs. The 64 KB RAM image then starts at file offset 209. Checked against two bytes of the game's own code at $9760 and $8010 rather than against $0000/$0001 or the screen. $8000-$BFFF in the snapshot is byte for byte identical to the copy on the disk, so the program never modifies itself and the cartridge image can be read either way.

Steady state

| Range | What | |---|---| | $0000-$00FF | the game's zero page variables | | $0400-$07E7 | video matrix, bank 0 copy | | $0800-$08FF | the disk loader's BASIC line and copy loop, dead after the copy | | $0A00-$0B47 | bitmap row address tables, low bytes then high bytes | | $0B50-$0D02 | actor records and game state | | $2000-$3F3F | bitmap, bank 0 copy | | $4400-$47E7 | video matrix, bank 1 copy | | $5000-$57FF | character set for the top bar in bank 1 | | $6000-$7F3F | bitmap, bank 1 copy | | $8000-$8008 | cartridge header | | $8009-$809E | 75 shape pointers | | $80A1-$8A8A | 75 shape bitmaps | | $8A8B-$BFFE | the engine: code, tables and text |

Everything from $0900 to $48FF still holds the loader's copy of the cartridge when play begins, and the bitmap and the row tables are laid down on top of it. Nothing reads it again.

The loader, in a paragraph

$0810 is the entire loader: a 16 KB lda ($FE),y / sta ($FC),y copy from $0900 to $8000, lda #$36 / sta $01 to swap the BASIC ROM out for the RAM the top half of the cartridge needs, and jmp $959C. It does not decompress, patch, or check anything, and it is not annotated further.

The symbol map for this game is symbols.json; the listing behind the Source tab is listing.json. Write-ups, facts and symbol maps are CC BY-SA 4.0. The game itself is not hosted here.