├── .gitignore ├── CHOPGFX ├── CHOPGFXHI ├── CHOPLIFTER.po ├── DiskImageParts ├── .DS_Store ├── BASIC.SYSTEM │ ├── BASIC.SYSTEM#FF2000 │ └── _FileInformation.txt ├── BITSY.BOOT │ ├── BITSY.BOOT#FF2000 │ └── _FileInformation.txt ├── PRODOS │ ├── PRODOS#FF0000 │ └── _FileInformation.txt └── QUIT.SYSTEM │ ├── QUIT.SYSTEM#FF2000 │ └── _FileInformation.txt ├── Makefile ├── README.md ├── V2Make.scpt ├── cadius ├── choplifter.s ├── linkerConfig ├── linkerConfigLoader ├── loader.s └── zeropage.s /.gitignore: -------------------------------------------------------------------------------- 1 | /Screenshot5.xcf 2 | /Screenshot4.xcf 3 | /Screenshot3.xcf 4 | /Screenshot2.xcf 5 | /Screenshot1.xcf 6 | /loader.lst 7 | /FullDump 8 | /ChoplifterOriginal 9 | /CHOPLIFTER\#060300 10 | /Choplifter.xcodeproj 11 | /CHOPLIFTER.SYSTEM\#FF2000 12 | /choplifter.lst 13 | /CHOP1 14 | /CHOP0 15 | /CHOP.SYSTEM\#FF2000 16 | /.DS_Store 17 | -------------------------------------------------------------------------------- /CHOPGFX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/CHOPGFX -------------------------------------------------------------------------------- /CHOPGFXHI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/CHOPGFXHI -------------------------------------------------------------------------------- /CHOPLIFTER.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/CHOPLIFTER.po -------------------------------------------------------------------------------- /DiskImageParts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/.DS_Store -------------------------------------------------------------------------------- /DiskImageParts/BASIC.SYSTEM/BASIC.SYSTEM#FF2000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/BASIC.SYSTEM/BASIC.SYSTEM#FF2000 -------------------------------------------------------------------------------- /DiskImageParts/BASIC.SYSTEM/_FileInformation.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/BASIC.SYSTEM/_FileInformation.txt -------------------------------------------------------------------------------- /DiskImageParts/BITSY.BOOT/BITSY.BOOT#FF2000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/BITSY.BOOT/BITSY.BOOT#FF2000 -------------------------------------------------------------------------------- /DiskImageParts/BITSY.BOOT/_FileInformation.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/BITSY.BOOT/_FileInformation.txt -------------------------------------------------------------------------------- /DiskImageParts/PRODOS/PRODOS#FF0000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/PRODOS/PRODOS#FF0000 -------------------------------------------------------------------------------- /DiskImageParts/PRODOS/_FileInformation.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/PRODOS/_FileInformation.txt -------------------------------------------------------------------------------- /DiskImageParts/QUIT.SYSTEM/QUIT.SYSTEM#FF2000: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/QUIT.SYSTEM/QUIT.SYSTEM#FF2000 -------------------------------------------------------------------------------- /DiskImageParts/QUIT.SYSTEM/_FileInformation.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/DiskImageParts/QUIT.SYSTEM/_FileInformation.txt -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # Choplifter Reverse Engineer 4 | # 5 | # Created by Quinn Dunki on May 5, 2024 6 | # https://blondihacks.com 7 | # 8 | 9 | 10 | CL65=cl65 11 | CAD=./cadius 12 | ADDR=800 13 | LOADERADDR=300 14 | VOLNAME=CHOPLIFTER 15 | IMG=DiskImageParts 16 | PGM=choplifter 17 | EXECNAME=CHOP.SYSTEM\#FF2000 18 | 19 | all: clean diskimage loader $(PGM) emulate 20 | 21 | $(PGM): 22 | @PATH=$(PATH):/usr/local/bin; $(CL65) -C linkerConfig -t apple2 --start-addr $(ADDR) -l$(PGM).lst $(PGM).s 23 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) CHOP0 24 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) CHOP1 25 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) CHOPGFX 26 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) CHOPGFXHI 27 | rm -f $(PGM).o 28 | 29 | diskimage: 30 | $(CAD) CREATEVOLUME $(VOLNAME).po $(VOLNAME) 143KB 31 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) $(IMG)/PRODOS/PRODOS#FF0000 32 | 33 | clean: 34 | rm -f $(PGM) 35 | rm -f $(PGM).o 36 | 37 | emulate: 38 | osascript V2Make.scpt $(PROJECT_DIR) $(VOLNAME) 39 | 40 | loader: 41 | @PATH=$(PATH):/usr/local/bin; $(CL65) -C linkerConfigLoader -t apple2 --start-addr $(LOADERADDR) -lloader.lst loader.s -o $(EXECNAME) 42 | $(CAD) ADDFILE $(VOLNAME).po /$(VOLNAME) $(EXECNAME) 43 | rm -f $(LOADEREXEC) 44 | rm -f loader.o 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Choplifter Reverse-Engineer 2 | 3 | This is a full reverse engineer of the Apple II game Choplifter, written by Dan Gorlin in 1982. It was done clean-room style beginning only with the binary. I had no additional information about this game other than the disk image. 4 | 5 | The source code here is fully documented and will build and run to a version of Choplifter that is binary-identical to the original, except for Dan Gorlin's custom floppy loader. In order to modernize this a bit, I wrote a new loader for it based on ProDOS, and this version boots Choplifter from ProDOS instead. Otherwise, it is identical. 6 | 7 | For a full writeup about this reverse-engineer and how it was done (along with lots more information about this source code), see my blog post here: 8 | 9 | [https://blondihacks.com/reversing-choplifter](https://blondihacks.com/reversing-choplifter) 10 | 11 | A quick note on building this– the makefile ends by executing an AppleScript to launch Virtual II and boot the disk image. This is all obviously very Mac-specific and probably somewhat dependent on my local environment. If the build gets to the end and fails on the AppleScript (V2Make.Scpt) don't worry, the build completed and your disk image is good. You can remove the "emulate" target from the makefile if you're building on another platform, or if the AppleScript doesn't work for you. 12 | 13 | This reverse engineer was complete by me, Quinn Dunki, on May 12, 2024, but this is of course still Dan's game and it is a brilliant piece of work. Reverse engineering it only *increased* my admiration of it. I doubt anyone would say that about most of what I've written in my career. 😁 14 | 15 | Thanks Dan, for writing one of the best games on the platform, and I hope you don't mind that I did this to it. 16 | 17 | -------------------------------------------------------------------------------- /V2Make.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/V2Make.scpt -------------------------------------------------------------------------------- /cadius: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blondie7575/ChoplifterReverse/ac8efac5a7ab5d805de0cd9ebf43623860e993b2/cadius -------------------------------------------------------------------------------- /linkerConfig: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | LORAM: start = $0800, size = $1800, file = "CHOP0"; 3 | HIRAM: start = $6000, size = $6000, file = "CHOP1"; 4 | } 5 | 6 | SEGMENTS { 7 | LOCODE: load = LORAM, type = rw; 8 | HICODE: load = HIRAM, type = rw; 9 | 10 | STARTUP: load = LORAM, type = ro, define = yes; 11 | ONCE: load = LORAM, type = ro, optional = yes; 12 | INIT: load = LORAM, type = rw, optional = yes; 13 | BSS: load = LORAM, type = bss, define = yes; 14 | } 15 | -------------------------------------------------------------------------------- /linkerConfigLoader: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | MAIN: start = $2000, size = $1000; 3 | } 4 | 5 | SEGMENTS { 6 | STARTUP: load = MAIN, type = ro, define = yes; 7 | ONCE: load = MAIN, type = ro, optional = yes; 8 | INIT: load = MAIN, type = rw, optional = yes; 9 | BSS: load = MAIN, type = bss, define = yes; 10 | CODE: load = MAIN, type = ro, define = yes; 11 | } 12 | -------------------------------------------------------------------------------- /loader.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; loader 3 | ; A very simplistic code loader for my reverse-engineered Choplifter 4 | ; 5 | ; Created by Quinn Dunki on May 5, 2024 6 | ; 7 | 8 | .segment "STARTUP" 9 | 10 | MAINENTRY = $2000 ; Mandated by ProDOS for SYSTEM programs 11 | LOADBUFFER = $4000 ; Use HGR2 as a loading buffer 12 | PRODOS = $bf00 ; MLI entry point 13 | 14 | .org $2000 15 | 16 | main: 17 | ; Open the low code file 18 | jsr PRODOS 19 | .byte $c8 20 | .addr fileOpenCode0 21 | bne ioError 22 | 23 | ; Load low code at $800 24 | jsr PRODOS 25 | .byte $ca 26 | .addr fileRead0 27 | bne ioError 28 | 29 | ; Close the file 30 | jsr PRODOS 31 | .byte $cc 32 | .addr fileClose 33 | 34 | ; Open the high code file 35 | jsr PRODOS 36 | .byte $c8 37 | .addr fileOpenCode1 38 | bne ioError 39 | 40 | ; Load high code at $6000 41 | jsr PRODOS 42 | .byte $ca 43 | .addr fileRead1 44 | bne ioError 45 | 46 | ; Close the file 47 | jsr PRODOS 48 | .byte $cc 49 | .addr fileClose 50 | 51 | ; Open the graphics file 52 | jsr PRODOS 53 | .byte $c8 54 | .addr fileOpenGfx 55 | bne ioError 56 | 57 | ; Load graphics at $a102 58 | jsr PRODOS 59 | .byte $ca 60 | .addr fileReadGfx 61 | bne ioError 62 | 63 | ; Close the file 64 | jsr PRODOS 65 | .byte $cc 66 | .addr fileClose 67 | 68 | ; We have another page of graphics that has to load on top of ProDOS, so load it in HGR2 first 69 | jsr PRODOS 70 | .byte $c8 71 | .addr fileOpenGfxHi 72 | bne ioError 73 | 74 | ; Load high graphics at $5000 temporarily 75 | jsr PRODOS 76 | .byte $ca 77 | .addr fileReadGfxHi 78 | bne ioError 79 | 80 | ; Close the file 81 | jsr PRODOS 82 | .byte $cc 83 | .addr fileClose 84 | 85 | ; We're done with ProDOS for good now, so we can stomp on it with the last page of graphics 86 | ; that Choplifter expects to find there 87 | ldx #$00 88 | 89 | copyBytesLoop: 90 | lda $5000,x 91 | sta $bef0,x 92 | inx 93 | cpx #$70 94 | bne copyBytesLoop 95 | 96 | jmp initVectors 97 | 98 | ioError: 99 | brk 100 | 101 | initVectors: 102 | ; Prepare game flow vectors. These are things that Dan's loader would have done 103 | ; but have been lost in this reverse engineer because I've converted it to ProDOS 104 | ; None of these vectors ever change during gameplay, so making them an indirection 105 | ; was probably a development and debugging tool. 106 | lda #$c7 ; Initialize game start vector 107 | sta $2a ; ZP_LOADERVECTOR_L 108 | lda #$09 109 | sta $2b ; ZP_LOADERVECTOR_H 110 | lda #$1f 111 | sta $28 ; ZP_STARTTITLE_JMP_L 112 | lda #$08 113 | sta $29 ; ZP_STARTTITLE_JMP_H 114 | lda #$5f 115 | sta $3a ; ZP_GAMESTART_JMP_L 116 | lda #$0b 117 | sta $3b ; ZP_GAMESTART_JMP_H 118 | lda #$13 119 | sta $3c ; ZP_NEWSORTIE_JMP_L 120 | lda #$0c 121 | sta $3d ; ZP_NEWSORTIE_JMP_H 122 | lda #$9b 123 | sta $4e ; ZP_GAMEINIT_L 124 | lda #$0b 125 | sta $4f ; ZP_GAMEINIT_H 126 | lda #$92 127 | sta $24 ; ZP_INDIRECT_JMP_L 128 | lda #$0c 129 | sta $25 ; ZP_INDIRECT_JMP_H 130 | 131 | ; Give ourselves a stub in $300 because Choplifter is about to erase this area of memory 132 | lda #$20 ; jsr jumpStartGraphicsDeadCode 133 | sta $300 134 | lda #$09 135 | sta $301 136 | lda #$90 137 | sta $302 138 | 139 | ; Jump into Choplifter loader entry, and Dan's code is none the wiser, partying like it's 1982. 140 | lda #$4c ; jmp $0800 141 | sta $303 142 | lda #$00 143 | sta $304 144 | lda #$08 145 | sta $305 146 | 147 | ; Jump into our new stub, and this loader now ceases to exist 148 | jmp $0300 149 | 150 | 151 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 152 | 153 | fileOpenCode0: 154 | .byte 3 155 | .addr codePath0 156 | .addr LOADBUFFER 157 | .byte 0 ; Result (file handle) 158 | .byte 0 ; Padding 159 | 160 | fileRead0: 161 | .byte 4 162 | .byte 1 ; File handle (we know it's gonna be 1) 163 | .addr $800 164 | .word $1800 165 | fileRead0Len: 166 | .word 0 ; Result (bytes read) 167 | 168 | fileOpenCode1: 169 | .byte 3 170 | .addr codePath1 171 | .addr LOADBUFFER 172 | .byte 0 ; Result (file handle) 173 | .byte 0 ; Padding 174 | 175 | fileRead1: 176 | .byte 4 177 | .byte 1 ; File handle (we know it's gonna be 1) 178 | .addr $6000 179 | .word $4100 ; Covers all code up to bottom of graphics area 180 | fileRead1Len: 181 | .word 0 ; Result (bytes read) 182 | 183 | fileOpenGfx: 184 | .byte 3 185 | .addr gfxPath 186 | .addr LOADBUFFER 187 | .byte 0 ; Result (file handle) 188 | .byte 0 ; Padding 189 | 190 | fileReadGfx: 191 | .byte 4 192 | .byte 1 ; File handle (we know it's gonna be 1) 193 | .addr $a102 194 | .word $1ded ; Don't step on ProDOS when loading graphics 195 | fileReadGfxLen: 196 | .word 0 ; Result (bytes read) 197 | 198 | fileOpenGfxHi: 199 | .byte 3 200 | .addr gfxPathHi 201 | .addr LOADBUFFER 202 | .byte 0 ; Result (file handle) 203 | .byte 0 ; Padding 204 | 205 | fileReadGfxHi: 206 | .byte 4 207 | .byte 1 ; File handle (we know it's gonna be 1) 208 | .addr $5000 209 | .word $70 ; This little piece would step on ProDOS 210 | fileReadGfxHiLen: 211 | .word 0 ; Result (bytes read) 212 | 213 | fileClose: 214 | .byte 1 215 | .byte 1 ; File handle (we know it's gonna be 1) 216 | 217 | 218 | .macro pstring Arg 219 | .byte .strlen(Arg), Arg 220 | .endmacro 221 | 222 | 223 | codePath0: 224 | pstring "/CHOPLIFTER/CHOP0" 225 | codePath1: 226 | pstring "/CHOPLIFTER/CHOP1" 227 | gfxPath: 228 | pstring "/CHOPLIFTER/CHOPGFX" 229 | gfxPathHi: 230 | pstring "/CHOPLIFTER/CHOPGFXHI" 231 | -------------------------------------------------------------------------------- /zeropage.s: -------------------------------------------------------------------------------- 1 | ; Zero page allocations. Since Choplifter was a strictly cold-boot-from-floppy game, 2 | ; it has total control of the machine. There is no ProDOS, AppleSoft, or anything else 3 | ; that it has to share the zero page with. As such, it uses basically all of it, as 4 | ; the 6502 Gods intended. The zero page is supposed to be treated as 256 registers, 5 | ; and boy-howdy does Choplifter indeed treat it that way. 6 | 7 | ZP_VELX_16_L = $00 ; Our X velocity as a 16-bit signed (low byte) Positive is to the right 8 | ZP_VELX_16_H = $01 ; Our X velocity as a 16-bit signed (high byte) Positive is to the right 9 | ZP_VELY_16_L = $02 ; Our Y velocity as a 16-bit signed (low byte) Positive is up. 10 | ZP_VELY_16_H = $03 ; Our Y velocity as a 16-bit signed (high byte) Positive is up. 11 | CHOP_POS_X_L = $04 ; 16-bit X position of chopper in screenspace (low byte) 12 | CHOP_POS_X_H = $05 ; 16-bit X position of chopper in screenspace (high byte) 13 | CHOP_POS_Y = $06 ; Y position of chopper in screenspace, bottom relative. Sitting on ground is $07 14 | CHOP_GROUND = $07 ; Offset from 0 to sit chopper on the ground (always $16 for chopper) 15 | 16 | 17 | ; Helicopter physics state 18 | ZP_TURN_STATE = $08 ; Current sprite rotate-in-place frame. -5 is full left, 0 is facing camera, +5 is full right. 19 | ZP_ACCELX = $09 ; $00 = on ground Current acceleration accumulated by stick position 20 | ; $01 = drift left 21 | ; $05 = full left 22 | ; $fb = full right 23 | ZP_STICKX = $0A ; $01 = drift left Stick input from pilot 24 | ; $05 = full left 25 | ; $fb = full right (-5) 26 | ZP_ACCELY = $0B ; $08 = Normal gravity, $0f = Full thrust up 27 | ZP_AIRBORNE = $0C ; $01 if we're airborne. $00 means touched down, but not landed 28 | ZP_LANDED = $0D ; $01 if we're fully landed. $00 if we're airborne or not sitting on ground properly yet 29 | ZP_LANDED_BASE = $0E ; $01 if we're landed on the home base pad 30 | ZP_GROUNDING = $0F ; Set to $01 when we first intersect ground 31 | ZP_DYING = $10 ; $01 during death animation 32 | 33 | ZP_SINK_Y = $11 ; How far into the ground we've sunk (used for crashing) 34 | ZP_DEATHTIMER = $12 ; Counts to $ff during death animation 35 | ZP_BTN0DOWN = $13 ; $ff if joystick button 0 is down 36 | 37 | ZP_OFFSETPTR0_L = $14 ; Pointer to render offset table, buffer 0 (low byte) 38 | ZP_OFFSETPTR0_H = $15 ; Pointer to render offset table, buffer 0 (high byte) 39 | ZP_OFFSETPTR1_L = $16 ; Pointer to render offset table, buffer 1 (low byte) 40 | ZP_OFFSETPTR1_H = $17 ; Pointer to render offset table, buffer 1 (high byte) 41 | ZP_OFFSET_ROW0 = $18 ; Current position in render offset table (buffer 0) 42 | ZP_OFFSET_ROW1 = $19 ; Current position in render offset table (buffer 1) 43 | 44 | ZP_SPRITE_PTR_L = $1A ; Current sprite art pointer (low byte) 45 | ZP_SPRITE_PTR_H = $1B ; Current sprite art pointer (high byte) 46 | ZP_RENDERPOS_XL = $1C ; 16-bit X position parameter for rendering (low byte) 47 | ZP_RENDERPOS_XH = $1D ; 16-bit X position parameter for rendering (high byte) 48 | ZP_RENDERPOS_Y = $1E ; Y position parameter for rendering 49 | 50 | ZP_POS_SCRATCH0 = $1F ; A scratch for calculating render offsets 51 | ZP_POS_SCRATCH1 = $20 ; A scratch for calculating render offsets 52 | ZP_POS_SCRATCH2 = $21 ; A scratch for calculating render offsets 53 | 54 | ZP_INDIRECT_JMP_L = $24 ; An indirect jump vector used in the main loop (low byte) 55 | ZP_INDIRECT_JMP_H = $25 ; An indirect jump vector used in the main loop (high byte) 56 | ZP_STARTTITLE_JMP_L = $28 ; An indirect jump vector used to start the title sequence (high byte) 57 | ZP_STARTTITLE_JMP_H = $29 ; An indirect jump vector used to start the title sequence (low byte) 58 | 59 | ZP_LOADERVECTOR_L = $2A ; Vector loader jumps to when loading finished (low byte). Always $09c7 60 | ZP_LOADERVECTOR_H = $2B ; Vector loader jumps to when loading finished (high byte). Always $09c7 61 | 62 | ZP_GAMESTART_JMP_L = $3A ; Vector to game start, used in demo mode (low byte) 63 | ZP_GAMESTART_JMP_H = $3B ; Vector to game start, used in demo mode (high byte) 64 | ZP_NEWSORTIE_JMP_L = $3C ; Vector used to start a new sortie (low byte) 65 | ZP_NEWSORTIE_JMP_H = $3D ; Vector used to start a new sortie (low byte) 66 | ZP_GAMEINIT_L = $4E ; Vector used in game init (low byte) 67 | ZP_GAMEINIT_H = $4F ; Vector used in game init (high byte) 68 | 69 | ; Scratch values are used generically for arithmetic and local data within functions. 70 | ; Occasionally these are persistent past a function's scope, but never for long. These 71 | ; are always treated as temporaries. 72 | ZP_SCRATCH56 = $56 73 | ZP_SCRATCH57 = $57 74 | ZP_SCRATCH58 = $58 75 | ZP_SCRATCH59 = $59 76 | ZP_SCRATCH5A = $5A 77 | ZP_SCRATCH5B = $5B 78 | ZP_SCRATCH5C = $5C 79 | ZP_SCRATCH16_L = $5D ; A 16-bit scratch for wide math and pointers (low byte) 80 | ZP_SCRATCH16_H = $5E ; A 16-bit scratch for wide math and pointers (high byte) 81 | ZP_SCRATCH5F = $5F 82 | ZP_SCRATCH60 = $60 83 | 84 | ZP_SCRATCH61 = $61 85 | ZP_SCRATCH62 = $62 86 | ZP_SCRATCH63 = $63 87 | ZP_SCRATCH64 = $64 88 | ZP_SCRATCH65 = $65 89 | ZP_SCRATCH66 = $66 90 | ZP_SCRATCH67 = $67 91 | ZP_SCRATCH68 = $68 92 | ZP_SCRATCH69 = $69 93 | ZP_SCRATCH6A = $6A 94 | ; Curiously, zero page location $6B is completely unused in the game 95 | ZP_SCRATCH6C = $6C 96 | ZP_SCRATCH6D = $6D 97 | ZP_SCRATCH6E = $6E 98 | ZP_SCRATCH6F = $6F 99 | ZP_SCRATCH70 = $70 100 | ZP_SCRATCH71 = $71 101 | 102 | ZP_SCROLLPOS_L = $72 ; Current horizontal scroll position of playfield. 16 bits, 0 at far end (low byte) 103 | ZP_SCROLLPOS_H = $73 ; Current horizontal scroll position of playfield. 16 bits, 0 at far end (high byte) 104 | ZP_SKY_BACK_H = $74 ; Height of sky area when erasing a sprite 105 | ZP_LAND_BACK_H = $75 ; How deep into the ground a sprite is during erasure 106 | ZP_RND = $76 ; Next random number to be generated 107 | ZP_FRAME_COUNT = $77 ; A frame counter, like a timer. Resets often though. 108 | ZP_BUFFER = $78 ; $00 = Using double buffer 1, $ff = Double buffer 2 109 | ZP_GAMEACTIVE = $79 ; $ff if gameplay is underway, or $00 if player has lost control 110 | 111 | ZP_CURR_ENTITY = $7A ; ID of current entity being updated 112 | ZP_NEXT_ENTITY = $7B ; Next entity to update (local offset in entityTable) 113 | ZP_FREE_ENTITY = $7C ; First unallocated entity 114 | 115 | ZP_SCREEN_Y = $80 ; Y position of current screenspace rendering (0 is screen bottom) 116 | ZP_SCREEN_X_L = $81 ; 16-bit X position of current screenspace rendering (low byte) 117 | ZP_SCREEN_X_H = $82 ; 16-bit X position of current screenspace rendering (high byte) 118 | 119 | ZP_HGRPTR_L = $83 ; Pointer to start of a high-res row (low byte) 120 | ZP_HGRPTR_H = $84 ; Pointer to start of a high-res row (high byte) 121 | 122 | ZP_CURR_X_BYTE = $85 ; X position of current rendering in bytes (rounded up, see below for remainder) 123 | ZP_CURR_X_BIT = $86 ; Current rendering X position remainder (bit within byte to render at) 124 | ZP_CURR_X_BYTEC = $87 ; A cache of ZP_CURR_X_BYTE 125 | ZP_CURR_Y = $88 ; Y position of current rendering 126 | 127 | ZP_RENDER_CURRBIT = $89 ; Current horizontal bit within byte in VRAM while rendering 128 | ZP_RENDER_CURRBYTE = $8A ; Current horiztonal byte in VRAM while rendering 129 | ZP_RENDER_CURR_W = $8B ; Current width of image data while rendering, in pixels 130 | ZP_PALETTE = $8C ; Palette bit to use when blitting 131 | 132 | ZP_PAGEMASK = $8D ; Page flipping state as a bit mask. Holds $00 and $60 on alternate page flips. This is the bit pattern difference between $2000 and $4000 address ranges. Clever! 133 | 134 | ZP_IMAGE_W = $8E ; Width of current rendering image, in pixels 135 | ZP_IMAGE_H = $8F ; Height of current rendering image, in pixels 136 | 137 | ZP_DRAWSCRATCH0 = $90 ; Used as a scratch in various drawing routines 138 | ZP_DRAWSCRATCH1 = $91 ; Used as a scratch in various drawing routines 139 | ZP_DRAWEND = $93 ; Used as end point for rendering rows or bytes in various routines 140 | ZP_BITSCRATCH = $94 ; Scratch value for bit masks in rendering routines 141 | ZP_PARAM_PTR_L = $95 ; Pointer to inline params (low byte) 142 | ZP_PARAM_PTR_H = $96 ; Pointer to inline params (high byte) 143 | ZP_PIXELS = $97 ; Stores new pixels to write in various rendering routines 144 | ZP_PIXEL_MASK = $98 ; A mask for pixels within a byte 145 | ZP_PIXELSCRATCH = $99 ; Scratch value for pixels used during rendering 146 | ZP_SPAN_COUNTER = $9D ; A span counter used during rendering 147 | 148 | ZP_INLINE_RTS_L = $9E ; Return address for MLI-convention calling vector (low byte) 149 | ZP_INLINE_RTS_H = $9F ; Return address for MLI-convention calling vector (high byte) 150 | 151 | ZP_REGISTER_A = $A0 ; A stackless way to save/restore accumulator 152 | ZP_REGISTER_X = $A1 ; A stackless way to save/restore X register 153 | ZP_REGISTER_Y = $A2 ; A stackless way to save/restore Y register 154 | 155 | ZP_PSEUDORTS_L = $A6 ; Indirect jump used in parameter unpacking (low byte) 156 | ZP_PSEUDORTS_H = $A7 ; Indirect jump used in parameter unpacking (high byte) 157 | 158 | ZP_LEFTCLIP = $A9 ; Amount of sprite to clip from the left, in pixels. Used for rendering at screen edges 159 | ZP_RIGHTCLIP = $AA ; Amount of sprite to clip from the right, in pixels. Used for rendering at screen edges 160 | ZP_TOPCLIP = $AB ; Amount to clip off the top of thing we're rendering. Used for sinking/sliding effects 161 | ZP_BOTTOMCLIP = $AC ; Amount to clip off the bottom of thing we're rendering. Used for sinking/sliding effects 162 | ZP_CLIPBITS = $AD ; A bit value related to clipping. Exact purpose unclear, but possibly related to clipping on sub-byte boundaries 163 | ZP_LEFTCLIP_BYTES = $AE ; Amount of sprite to clip from the left, in bytes. 164 | ZP_SOURCE_IMGPTR_L = $AF ; Pointer into source image during rendering (low byte) 165 | ZP_SOURCE_IMGPTR_H = $B0 ; Pointer into source image during rendering (high byte) 166 | ZP_IMAGE_W_BYTES = $B1 ; Width of current animating image, in bytes 167 | 168 | ZP_SPRITE_TILT_OFFSET = $B2 ; Offset amount for rendering tilted sprites 169 | ZP_SPRITE_TILT_SIGN = $B3 ; Offset direction for rendering tilted sprites. $ff or $01 170 | 171 | ZP_UNUSEDB4 = $B4 ; Curiously this is initialized to $00 but then never used in the game 172 | ZP_UNUSEDB5 = $B5 ; Curiously this is initialized to $00 but then never used in the game 173 | ZP_UNUSEDB6 = $B6 ; A scratch value only used in dead code (an abandoned attempt to calculated sprite tilt) 174 | ZP_UNUSEDB7 = $B7 ; A scratch value only used in dead code (an abandoned attempt to calculated sprite tilt) 175 | ZP_UNUSEDB8 = $B8 ; A scratch value only used in dead code (an abandoned attempt to calculated sprite tilt) 176 | 177 | ZP_SPRITE_TILT = $B9 ; Sets tilt of rendered sprites $00 = left tilt, $ff = right tilt 178 | 179 | ZP_UNUSEDBA = $BA ; A tilt parameter for sprite rendering that ended up being unused in the end 180 | ZP_REGISTER = $BB ; A scratch value used to save and restore random registers in a couple of places 181 | 182 | ZP_CURRTILT_OFFSET = $BC ; Current offset during tilted sprite rendering 183 | ZP_RENDER_CURR_Y = $BD ; Current Y-position while rendering (bottom relative) 184 | ZP_OFFSET_REMAINING = $BE ; Offset remaining while rendering tilted 185 | ZP_TILTSCRATCH = $BF ; A scratch value used in tilted sprite rendering (the real one that shipped) 186 | 187 | ZP_WORLD_X_L = $C0 ; 16-bit worldspace X position for current rendering (low byte) 188 | ZP_WORLD_X_H = $C1 ; 16-bit worldspace X position for current rendering (high byte) 189 | ZP_WORLD_Y = $C2 ; Worldspace Y position for current rendering 190 | 191 | ZP_SPRITEANIM_PTR_L = $C3 ; Pointer to a graphic to animate (low byte) 192 | ZP_SPRITEANIM_PTR_H = $C4 ; Pointer to a graphic to animate (high byte) 193 | 194 | ZP_SCREENTOP = $C5 ; Y value of the top of the scroll window. Always $c0 now, but the game likely had vertical scrolling at one point 195 | ZP_SCREENBOTTOM = $C6 ; Y value of the bottom of the scroll window. Always $00 now, but the game likely had vertical scrolling at one point 196 | ZP_LOCALSCROLL_L = $C7 ; A pointer to a scroll position used indirectly for some rendering (low byte). Always points to ZP_SCROLLPOS anyway. 197 | ZP_LOCALSCROLL_H = $C8 ; A pointer to a scroll position used indirectly for some rendering (high byte). Always points to ZP_SCROLLPOS anyway. 198 | --------------------------------------------------------------------------------