10. Disk routines#
Lode Runner contains a copy of DOS 3.3's RWTS in order for it to access the disk. Because it does
not access files, but rather just sectors of data, Lode Runner does not need the file manager part
of DOS. So, the area B600-BFFF is taken by DOS routines.
The standard DOS I/O Control Block (IOB) and Device Characteristics Table (DCT) are used. Further details can be read in Beneath Apple DOS.
There is one patch that seems to have been applied. DOS has a routine called FORMDSK
located at BE0D which is supposed to jump to DSKFORM at BE46, which
formats a disk. Lode Runner's copy of DOS changes the jump target to 8E00,
Lode Runner's FORMAT_PATCH routine.
The reason for this is to prevent formatting the Load Runner master disk via the level editor.
The routine moves to track 0, and keeps reading bytes off the disk until either
it finds a sequence of D4 D5 D6, or times out. If it finds the sequence, we've
detected that this disk we're trying to format is the Lode Runner master disk, and
we should error out and go back to the level editor.
Otherwise we're OK to proceed with disk formatting.
SEEKABS EQU $B9A0 DSKFORM EQU $BEAF
ORG $8E00 FORMAT_PATCH: LDA #$44 STA $0478 ; Used by DOS as current track. LDA #$00 JSR SEEKABS ; DOS routine to move disk to track in A. NOP NOP NOP NOP NOP NOP LDA #$20 STA $4F .await_D4_D5_D6: DEY BNE .read_byte1 DEC $4F BNE .read_byte1 JMP DSKFORM ; DOS routine to format disk HEX EA EA .read_byte1: LDA $C08C,X BPL .read_byte1 .check_for_D4: CMP #$D4 BNE .await_D4_D5_D6 NOP .read_byte2: LDA $C08C,X BPL .read_byte2 .check_for_D5: CMP #$D5 BNE .check_for_D4 NOP .read_byte3: LDA $C08C,X BPL .read_byte3 CMP #$D6 BNE .check_for_D5 LDA $C088,X ; Turn motor off JSR DONT_MANIPULATE_MASTER_DISK JMP START_LEVEL_EDITOR
DOS_IOB EQU $B7E8 IOB_SLOTNUMx16 EQU $B7E9 IOB_DRIVE_NUM EQU $B7EA IOB_VOLUME_NUMBER_EXPECTED EQU $B7EB IOB_TRACK_NUMBER EQU $B7EC IOB_SECTOR_NUMBER EQU $B7ED IOB_DEVICE_CHARACTERISTICS_TABLE_PTR EQU $B7EE ; 2 bytes IOB_READ_WRITE_BUFFER_PTR EQU $B7F0 ; 2 bytes IOB_UNUSED EQU $B7F2 IOB_BYTE_COUNT_FOR_PARTIAL_SECTOR EQU $B7F3 IOB_COMMAND_CODE EQU $B7F4 IOB_RETURN_CODE EQU $B7F5 IOB_LAST_ACCESS_VOLUME EQU $B7F6 IOB_LAST_ACCESS_SLOTx16 EQU $B7F7 IOB_LAST_ACCESS_DRIVE EQU $B7F8 DCT_DEVICE_TYPE EQU $B7FB DCT_PHASES_PER_TRACK EQU $B7FC DCT_MOTOR_ON_TIME_COUNT EQU $B7FD ; 2 bytes
DCT_DEVICE_TYPE, DCT_MOTOR_ON_TIME_COUNT, DCT_PHASES_PER_TRACK, DOS_IOB, IOB_BYTE_COUNT_FOR_PARTIAL_SECTOR, IOB_COMMAND_CODE, IOB_DEVICE_CHARACTERISTICS_TABLE_PTR, IOB_DRIVE_NUM, IOB_LAST_ACCESS_DRIVE, IOB_LAST_ACCESS_SLOTx16, IOB_LAST_ACCESS_VOLUME, IOB_READ_WRITE_BUFFER_PTR, IOB_RETURN_CODE, IOB_SECTOR_NUMBER, IOB_SLOTNUMx16, IOB_TRACK_NUMBER, IOB_UNUSED, IOB_VOLUME_NUMBER_EXPECTEDACCESS_HI_SCORE_DATA_FROM_DISK reads or writes—depending on A, where 1 is read and 2
is write—the high score table from
disk at track 12 sector 15 into HI_SCORE_TABLE. We then compare
the 11 bytes of HI_SCORE_DATA_MARKER to where they are supposed to be in the table.
If the marker doesn't match, then we return 0, indicating that the disk doesn't have
a high score table.
If the marker does match, but the very last byte in the table is nonzero, then we return 1,
indicating that this is a master disk (so its level data shouldn't be touched),
otherwise we return -1, this being a data disk.
ORG $63A8 HI_SCORE_DATA_MARKER: ; Spells out "LODE RUNNER". HEX CC CF C4 C5 A0 D2 D5 CE CE C5 D2
ORG $6359 ACCESS_HI_SCORE_DATA_FROM_DISK: SUBROUTINE STA IOB_COMMAND_CODE LDA #$0C STA IOB_TRACK_NUMBER LDA #$0F STA IOB_SECTOR_NUMBER LDA #<HI_SCORE_DATA STA IOB_READ_WRITE_BUFFER_PTR LDA #>HI_SCORE_DATA STA IOB_READ_WRITE_BUFFER_PTR+1 LDA #$00 STA IOB_VOLUME_NUMBER_EXPECTED LDY #<DOS_IOB LDA #>DOS_IOB JSR INDIRECT_RWTS BCC .no_error JMP RESET_GAME .no_error: LDY #$0A LDA #$00 STA MASK0 ; temp storage .loop: LDA HI_SCORE_DATA+244,Y EOR HI_SCORE_DATA_MARKER,Y ORA MASK0 STA MASK0 DEY BPL .loop LDA MASK0 BEQ .all_zero_data LDA #$00 RTS .all_zero_data: LDA #$01 LDX $1FFF BNE .end LDA #$FF .end: RTS
ACCESS_HI_SCORE_DATA_FROM_DISKRECORD_HI_SCORE_DATA_TO_DISK records the player's score to disk if the player's score
belongs on the high score list. It also handles getting the player's initials.
HIGH_SCORE_INITIALS_INDEX EQU $824D HI_SCORE_TARGET_INDEX EQU $56 ; aliased with TMP_GUARD_ROW
ORG $84C8 RECORD_HI_SCORE_DATA_TO_DISK: SUBROUTINE LDA $9D BEQ .end LDA SCORE ORA SCORE+1 ORA SCORE+2 ORA SCORE+3 BEQ .end LDA #$01 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; read table ; Return value of 0 means the hi score marker wasn't present, ; so don't write the hi score table. BEQ .end LDY #$01 .loop: LDX HI_SCORE_TABLE_OFFSETS,Y LDA LEVELNUM CMP HI_SCORE_DATA+3,X ; level BCC .next BNE .record_it LDA SCORE+3 CMP HI_SCORE_DATA+4,X BCC .next BNE .record_it LDA SCORE+2 CMP HI_SCORE_DATA+5,X BCC .next BNE .record_it LDA SCORE+1 CMP HI_SCORE_DATA+6,X BCC .next BNE .record_it LDA SCORE CMP HI_SCORE_DATA+7,X BCC .next BNE .record_it .next: INY CPY #$0B BCC .loop .end: RTS .record_it: CPY #$0A BEQ .write_here STY HI_SCORE_TARGET_INDEX ; Move the table rows to make room at index HI_SCORE_TARGET_INDEX LDY #$09 .loop2: LDX HI_SCORE_TABLE_OFFSETS,Y ; Move 8 bytes of hi score data LDA #$08 STA ROW_COUNT ; temporary counter .loop3: LDA HI_SCORE_DATA,X STA HI_SCORE_DATA+8,X INX DEC ROW_COUNT BNE .loop3 CPY HI_SCORE_TARGET_INDEX BEQ .write_here DEY BNE .loop2 .write_here: LDX HI_SCORE_TABLE_OFFSETS,Y LDA #$A0 STA HI_SCORE_DATA,X STA HI_SCORE_DATA+1,X STA HI_SCORE_DATA+2,X LDA LEVELNUM STA HI_SCORE_DATA+3,X LDA SCORE+3 STA HI_SCORE_DATA+4,X LDA SCORE+2 STA HI_SCORE_DATA+5,X LDA SCORE+1 STA HI_SCORE_DATA+6,X LDA SCORE STA HI_SCORE_DATA+7,X STY WIPE0 ; temporary LDA HI_SCORE_TABLE_OFFSETS,Y STA .rd_loc+1 STA .wr_loc+1 JSR HI_SCORE_SCREEN LDA #$40 STA DRAW_PAGE LDA WIPE0 CLC ADC #$04 STA GAME_ROWNUM LDA #$07 STA GAME_COLNUM LDX #$00 STX HIGH_SCORE_INITIALS_INDEX .get_initial_from_player: LDX HIGH_SCORE_INITIALS_INDEX .rd_loc: LDA HI_SCORE_DATA,X ; fixed up to add offset from above JSR CHAR_TO_SPRITE_NUM JSR WAIT_FOR_KEY STA KBDSTRB CMP #$8D BEQ .return_pressed CMP #$88 ; backspace/back arrow BNE .other_key_pressed ; backspace pressed LDX KBD_ENTRY_INDEX BEQ .beep ; can't backspace/back arrow past the beginning DEC HIGH_SCORE_INITIALS_INDEX DEC GAME_COLNUM JMP .get_initial_from_player .other_key_pressed: CMP #$95 ; fwd arrow BNE .check_for_allowed_chars LDX KBD_ENTRY_INDEX CPX #$02 BEQ .beep ; can't fwd arrow past the end INC GAME_COLNUM INC KBD_ENTRY_INDEX JMP .get_initial_from_player .check_for_allowed_chars CMP #$AE ; period allowed BEQ .put_char CMP #$A0 ; space allowed BEQ .put_char CMP #$C1 BCC .beep ; can't be less than 'A' CMP #$DB BCS .beep ; can't be greater than 'Z' .put_char LDY KBD_ENTRY_INDEX .wr_loc: STA HI_SCORE_DATA,Y ; fixed up to add offset from above JSR PUT_CHAR INC KBD_ENTRY_INDEX LDA KBD_ENTRY_INDEX CMP #$03 BCC .get_initial_from_player DEC KBD_ENTRY_INDEX DEC GAME_COLNUM JMP .get_initial_from_player .beep: JSR BEEP JMP .get_initial_from_player .return_pressed: LDA #$20 STA DRAW_PAGE LDA #$02 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; write hi score table JMP LONG_DELAY ORG $824D KBD_ENTRY_INDEX: HEX 60
KBD_ENTRY_INDEX, RECORD_HI_SCORE_DATA_TO_DISKORG $8106 BAD_DATA_DISK: SUBROUTINE JSR CLEAR_HGR2 LDA #$40 STA DRAW_PAGE LDA #$00 STA GAME_COLNUM STA GAME_ROWNUM ; "DISKETTE IN DRIVE IS NOT A\r" ; "LODE RUNNER DATA DISK." JSR PUT_STRING HEX C4 C9 D3 CB C5 D4 D4 C5 A0 C9 CE A0 C4 D2 C9 D6 HEX C5 A0 C9 D3 A0 CE CF D4 A0 C1 8D CC CF C4 C5 A0 HEX D2 D5 CE CE C5 D2 A0 C4 C1 D4 C1 A0 C4 C9 D3 CB HEX AE 00 JMP HIT_KEY_TO_CONTINUE
BAD_DATA_DISKORG $8098 DONT_MANIPULATE_MASTER_DISK: SUBROUTINE JSR CLEAR_HGR2 LDA #$40 STA DRAW_PAGE LDA #$00 STA GAME_COLNUM STA GAME_ROWNUM ; "USER NOT ALLOWED TO\r" ; "MANIPULATE MASTER DISKETTE." JSR PUT_STRING HEX D5 D3 C5 D2 A0 CE CF D4 A0 C1 CC CC CF D7 C5 C4 HEX A0 D4 CF 8D CD C1 CE C9 D0 D5 CC C1 D4 C5 A0 CD HEX C1 D3 D4 C5 D2 A0 C4 C9 D3 CB C5 D4 D4 C5 AE 00 ; fallthrough to HIT_KEY_TO_CONTINUE
DONT_MANIPULATE_MASTER_DISKThe level editor has a routine to check for a valid data disk, meaning it has a
high score table and is not the master disk. In case of a disk that is not a valid
data disk, we abort the current editor operation, dumping the user right into the
level editor by jumping to START_LEVEL_EDITOR. Otherwise we jump to
RETURN_FROM_SUBROUTINE, which apparently saved a byte over having a local RTS
instruction.
ORG $807F CHECK_FOR_VALID_DATA_DISK: SUBROUTINE LDA #$01 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; read table CMP #$00 ; bad table BNE .check_for_master_disk JSR BAD_DATA_DISK JMP START_LEVEL_EDITOR .check_for_master_disk: CMP #$01 ; master disk BNE RETURN_FROM_SUBROUTINE JSR DONT_MANIPULATE_MASTER_DISK JMP START_LEVEL_EDITOR
CHECK_FOR_VALID_DATA_DISKInitializing a disk first DOS formats it. This zeros out all data on all tracks
and sectors. Once that's done, we write track 0 sector 0 with the data from
DISK_BOOT_SECTOR_DATA. Then we read the Volume Table of Contents (VTOC) at track
17 sector 0, which will contain all zeros because of the initial format. We then
stick SAVED_VTOC_DATA in the disk buffer and write it to the VTOC. We do the
same thing with the catalog sector at track 17 sector 15 and
SAVED_FILE_DESCRIPTIVE_ENTRY_DATA.
The final step is to create a blank sector at track 12 sector 15, with the special
"LODE RUNNER" marker HI_SCORE_DATA_MARKER near the end.
ORG $1DB2 DISK_BOOT_SECTOR_DATA: HEX 01 20 58 FC 20 93 FE 20 89 FE A0 00 B9 34 08 F0 HEX 0E 20 F0 FD C9 8D D0 04 A9 09 85 24 C8 D0 ED A6 HEX 2B 9D 88 C0 8A 4A 4A 4A 4A 09 C0 8D 33 08 20 0C HEX FD 4C 00 C6 8D 8D 8D 8D 8D 8D 8D CC CF C4 C5 A0 HEX D2 D5 CE CE C5 D2 A0 C4 C1 D4 C1 A0 C4 C9 D3 CB HEX BA 8D AD AD AD AD AD AD AD AD AD AD AD AD AD AD HEX AD AD AD AD AD AD AD AD 8D 8D C4 C9 D3 CB C5 D4 HEX D4 C5 A0 D7 C9 CC CC A0 CE CF D4 A0 C2 CF CF D4 HEX 8D 8D A0 C9 CE D3 C5 D2 D4 A0 CE C5 D7 A0 C4 C9 HEX D3 CB A0 C1 CE C4 8D A0 C8 C9 D4 A0 C1 A0 CB C5 HEX D9 A0 D4 CF A0 D2 C5 C2 CF CF D4 8D 8D A0 A0 A0 HEX A0 A0 A0 A0 A0 A0 A0 00 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ORG $8250 SAVED_VTOC_DATA: HEX 60 02 11 0F 04 00 00 FE 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 HEX 00 00 00 00 00 00 00 00 7A 00 00 00 00 00 00 00 HEX 00 FF FF 00 00 23 0F 00 01 ORG $8289 SAVED_FILE_DESCRIPTIVE_ENTRY_DATA: HEX 22 ; Track of first track/sector list sector (T34) HEX 0F ; Sector of first track/sector list sector (S15) HEX 88 ; File type and flags: locked, S-type file ; File name: "^H^H^H^H^H^H^HLODE RUNNER DATA DISK " HEX 88 88 88 88 88 88 88 CC CF C4 C5 A0 D2 D5 CE CE HEX C5 D2 A0 C4 C1 D4 C1 A0 C4 C9 D3 CB A0 A0
ORG $7D5D EDITOR_INITIALIZE_DISK: SUBROUTINE ; "\r" ; ">>INITIALIZE\r" ; " THIS FORMATS THE DISKETTE\r" ; " FOR USER CREATED LEVELS.\r" ; " (CAUTION. IT ERASES THE\r" ; " ENTIRE DISKETTE FIRST)\r" ; "\r" ; " ARE YOU SURE (Y/N) " JSR PUT_STRING HEX 8D BE BE C9 CE C9 D4 C9 C1 CC C9 DA C5 8D A0 A0 HEX D4 C8 C9 D3 A0 C6 CF D2 CD C1 D4 D3 A0 D4 C8 C5 HEX A0 C4 C9 D3 CB C5 D4 D4 C5 8D A0 A0 C6 CF D2 A0 HEX D5 D3 C5 D2 A0 C3 D2 C5 C1 D4 C5 C4 A0 CC C5 D6 HEX C5 CC D3 AE 8D A0 A0 A8 C3 C1 D5 D4 C9 CF CE AE HEX A0 C9 D4 A0 C5 D2 C1 D3 C5 D3 A0 D4 C8 C5 8D A0 HEX A0 A0 C5 CE D4 C9 D2 C5 A0 C4 C9 D3 CB C5 D4 D4 HEX C5 A0 C6 C9 D2 D3 D4 A9 8D 8D A0 A0 C1 D2 C5 A0 HEX D9 CF D5 A0 D3 D5 D2 C5 A0 A8 D9 AF CE A9 A0 00 JSR EDITOR_WAIT_FOR_KEY CMP #$D9 ; Y BNE .end NOP ; NOP x 15 NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP LDA DISK_LEVEL_LOC PHA ; Format the disk LDA #DISK_ACCESS_FORMAT JSR ACCESS_COMPRESSED_LEVEL_DATA ; Write the boot sector (T0S0) LDA #<DISK_BOOT_SECTOR_DATA STA IOB_READ_WRITE_BUFFER_PTR LDA #>DISK_BOOT_SECTOR_DATA STA IOB_READ_WRITE_BUFFER_PTR+1 LDA #$00 STA IOB_SECTOR_NUMBER STA IOB_TRACK_NUMBER LDA #$02 STA IOB_COMMAND_CODE JSR ACCESS_DISK_OR_RESET_GAME ; write T0S0 with DISK_BOOT_SECTOR_DATA. ; Read the VTOC (T17S0) LDA #$E0 STA DISK_LEVEL_LOC ; ends up being T17S0 (the VTOC) LDA #DISK_ACCESS_READ JSR ACCESS_COMPRESSED_LEVEL_DATA ; Copy from SAVED_VTOC_DATA to DISK_BUFFER and write it. LDY #$37 .loop: LDA SAVED_VTOC_DATA+1,Y STA DISK_BUFFER,Y DEY BPL .loop LDA #$02 JSR ACCESS_COMPRESSED_LEVEL_DATA ; Read the first catalog sector (T17S15) LDA #$EF STA DISK_LEVEL_LOC LDA #DISK_ACCESS_READ JSR ACCESS_COMPRESSED_LEVEL_DATA ; Copy from SAVED_FILE_DESCRIPTIVE_ENTRY_DATA the first file descriptive ; entry to DISK_BUFFER and write it. LDY #$20 .loop2: LDA SAVED_FILE_DESCRIPTIVE_ENTRY_DATA,Y STA DISK_BUFFER+11,Y DEY BPL .loop2 ; Write it back LDA #DISK_ACCESS_WRITE JSR ACCESS_COMPRESSED_LEVEL_DATA ; Read the high score sector LDA #$01 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; Copy from HI_SCORE_DATA_MARKER and write it. LDY #$0A .loop3: LDA HI_SCORE_DATA_MARKER,Y STA $1FF4,Y DEY BPL .loop3 ; Write it back LDA #$02 JSR ACCESS_HI_SCORE_DATA_FROM_DISK PLA STA DISK_LEVEL_LOC .end: JMP EDITOR_COMMAND_LOOP
EDITOR_INITIALIZE_DISKACCESS_HI_SCORE_DATA_FROM_DISK, DISK_ACCESS_FORMAT, DISK_ACCESS_READ, DISK_ACCESS_WRITE, DISK_BOOT_SECTOR_DATA, EDITOR_COMMAND_LOOP, EDITOR_WAIT_FOR_KEY, HI_SCORE_DATA_MARKER, IOB_COMMAND_CODE, IOB_READ_WRITE_BUFFER_PTR, IOB_SECTOR_NUMBER, IOB_TRACK_NUMBER, PUT_STRING, SAVED_FILE_DESCRIPTIVE_ENTRY_DATA, SAVED_VTOC_DATATo clear the high score table from a disk, we first read the sector where the high score table is supposed to be, and check to see if the buffer is a good high score table. If so, we zero out the first 80 bytes (the 10 high score entries) and write that back to disk.
If the disk didn't contain a good high score table, we display the BAD_DATA_DISK message
and abort.
ORG $7E75 EDITOR_CLEAR_HIGH_SCORES: SUBROUTINE ; "\r" ; ">>CLEAR SCORE FILE\r" ; " THIS CLEARS THE HIGH\r" ; " SCORE FILE OF ALL\r" ; " ENTRIES.\r" ; "\r" ; " ARE YOU SURE (Y/N) " JSR PUT_STRING HEX 8D BE BE C3 CC C5 C1 D2 A0 D3 C3 CF D2 C5 A0 C6 HEX C9 CC C5 8D A0 A0 D4 C8 C9 D3 A0 C3 CC C5 C1 D2 HEX D3 A0 D4 C8 C5 A0 C8 C9 C7 C8 8D A0 A0 D3 C3 CF HEX D2 C5 A0 C6 C9 CC C5 A0 CF C6 A0 C1 CC CC 8D A0 HEX A0 C5 CE D4 D2 C9 C5 D3 AE 8D 8D A0 A0 C1 D2 C5 HEX A0 D9 CF D5 A0 D3 D5 D2 C5 A0 A8 D9 AF CE A9 A0 HEX 00 JSR EDITOR_WAIT_FOR_KEY CMP #$D9 ; 'Y' BNE .end LDA #$01 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; read table CMP #$00 BNE .good_disk JSR BAD_DATA_DISK JMP START_LEVEL_EDITOR .good_disk: LDY #$4F LDA #$00 .loop: STA HI_SCORE_DATA,Y DEY BPL .loop LDA #$02 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; write table .end: JMP EDITOR_COMMAND_LOOP
10.1 Initialization#
INDIRECT_TARGET EQU $36 ; Init with DEFAULT_INDIRECT_TARGET DISABLE_INTS_CALL_RWTS_PTR EQU $38 ; Init with DISABLE_INTS_CALL_RWTS DISABLE_INTS_CALL_RWTS EQU $B7B5
DISABLE_INTS_CALL_RWTS, DISABLE_INTS_CALL_RWTS_PTR, INDIRECT_TARGETORG $63A5 INDIRECT_RWTS: SUBROUTINE JMP (INDIRECT_TARGET) ORG $8E50 DEFAULT_INDIRECT_TARGET: SUBROUTINE JMP DISABLE_INTS_CALL_RWTS
GUARD_PATTERN_OFFSET EQU $97
ORG $6056 INIT_GAME_DATA: LDA #0 STA SCORE STA SCORE+1 STA SCORE+2 STA SCORE+3 STA GUARD_PATTERN_OFFSET STA WIPE_MODE ; WIPE_MODE = SCORE = $97 = 0 STA $53 STA $AB STA $A8 ; $53 = $AB = $A8 = 0 LDA #$9B ; 155 STA $A9 ; $A9 = 155 LDA #5 STA LIVES ; LIVES = 5 LDA GAME_MODE LSR ; if GAME_MODE was 0 or 1 (i.e. not displaying high score screen or splash screen), ; play the game. BEQ .put_status_and_start_game ; We were displaying the high score screen or splash screen LDA #1 JSR ACCESS_HI_SCORE_DATA_FROM_DISK ; Read hi score data CMP #$00 BNE .set_rwts_target JSR BAD_DATA_DISK JMP RESET_GAME .set_rwts_target: LDA $1FFF BNE .use_dos_target LDA INDIRECT_TARGET LDX INDIRECT_TARGET+1 BNE .store_rwts_addr .use_dos_target: LDA DISABLE_INTS_CALL_RWTS_PTR LDX DISABLE_INTS_CALL_RWTS_PTR+1 .store_rwts_addr: STA RWTS_ADDR STX RWTS_ADDR+1 .put_status_and_start_game: JSR PUT_STATUS STA TXTPAGE1
ORG $6CA7 GUARD_PATTERNS_LIST: HEX 00 00 00 HEX 00 01 01 HEX 01 01 01 HEX 01 03 01 HEX 01 03 03 HEX 03 03 03 HEX 03 03 07 HEX 03 07 07 HEX 07 07 07 HEX 07 07 0F HEX 07 0F 0F HEX 0F 0F 0F
The game loop, which runs in both attract mode and in play mode, effectively implements the following flowchart:

ORG $609F .start_game: LDX #$01 JSR LOAD_LEVEL LDA #$00 STA KEY_COMMAND STA KEY_COMMAND_LR LDA GAME_MODE LSR ; if GAME_MODE was 0 or 1 (i.e. not displaying high score screen ; or splash screen), play the game. BEQ .play_game ; When GAME_MODE is 2: JSR WAIT_KEY LDA PLAYER_COL STA GAME_COLNUM LDA PLAYER_ROW STA GAME_ROWNUM LDA #SPRITE_PLAYER JSR WAIT_FOR_KEY_WITH_CURSOR_PAGE_1 .play_game: LDX #$00 STX DIG_DIRECTION STX NOTE_INDEX LDA GUARD_PATTERN_OFFSET CLC ADC GUARD_COUNT ; GUARD_COUNT + $97 can't be greater than 8. TAY LDX TIMES_3_TABLE,Y ; X = 3 * Y (goes up to Y=8) LDA GUARD_PATTERNS_LIST,X STA GUARD_PATTERNS LDA GUARD_PATTERNS_LIST+1,X STA GUARD_PATTERNS+1 LDA GUARD_PATTERNS_LIST+2,X STA GUARD_PATTERNS+2 LDY GUARD_PATTERN_OFFSET LDA $621D,Y STA GUARD_GOLD_TIMER_START_VALUE .game_loop: JSR MOVE_PLAYER LDA ALIVE BEQ .died JSR PLAY_SOUND LDA GOLD_COUNT BNE .check_player_reached_top JSR ENABLE_NEXT_LEVEL_LADDERS .check_player_reached_top: LDA PLAYER_ROW BNE .not_at_top LDA PLAYER_Y_ADJ CMP #$02 BNE .not_at_top ; Reached top of screen LDA GOLD_COUNT BEQ .level_cleared CMP #$FF BEQ .level_cleared ; level cleared if GOLD_COUNT == 0 or -1. .not_at_top: JSR HANDLE_TIMERS LDA ALIVE BEQ .died JSR PLAY_SOUND JSR MOVE_GUARDS LDA ALIVE BEQ .died BNE .game_loop .level_cleared: INC LEVELNUM INC DISK_LEVEL_LOC INC LIVES BNE .lives_incremented DEC LIVES ; LIVES doesn't overflow. .lives_incremented: ; Increment score by 1500, playing an ascending tune while doing so. LDX #$0F STX SCRATCH_5C .loop2: LDY #$01 LDA #$00 JSR ADD_AND_UPDATE_SCORE ; SCORE += 100 JSR APPEND_LEVEL_CLEARED_NOTE JSR APPEND_LEVEL_CLEARED_NOTE JSR APPEND_LEVEL_CLEARED_NOTE DEC SCRATCH_5C BNE .loop2 .start_game_: JMP .start_game .died: DEC LIVES JSR PUT_STATUS_LIVES JSR LOAD_SOUND_DATA HEX 02 40 02 40 03 50 03 50 04 60 04 60 05 70 05 70 HEX 06 80 06 80 07 90 07 90 08 A0 08 A0 09 B0 09 B0 HEX 0A C0 0A C0 0B D0 0B D0 0C E0 0C E0 0D F0 0D F0 HEX 00 .play_died_tune: JSR PLAY_SOUND BCS .play_died_tune LDA GAME_MODE LSR BEQ .restore_enable_sound ; If GAME_MODE is 0 or 1 LDA LIVES BNE .start_game_ ; We can still play. ; Game over JSR RECORD_HI_SCORE_DATA_TO_DISK JSR SPINNING_GAME_OVER BCS .key_pressed
ADD_AND_UPDATE_SCORE, ALIVE, APPEND_LEVEL_CLEARED_NOTE, DIG_DIRECTION, ENABLE_NEXT_LEVEL_LADDERS, GAME_COLNUM, GAME_MODE, GAME_ROWNUM, GOLD_COUNT, GUARD_COUNT, GUARD_GOLD_TIMER_START_VALUE, GUARD_PATTERNS, GUARD_PATTERNS_LIST, GUARD_PATTERN_OFFSET, HANDLE_TIMERS, KEY_COMMAND, KEY_COMMAND_LR, LEVELNUM, LIVES, LOAD_LEVEL, LOAD_SOUND_DATA, MOVE_GUARDS, MOVE_PLAYER, NOTE_INDEX, PLAYER_COL, PLAYER_ROW, PLAYER_Y_ADJ, PLAY_SOUND, PUT_STATUS_LIVES, RECORD_HI_SCORE_DATA_TO_DISK, SCORE, SCRATCH_5C, TIMES_3_TABLE, WAIT_FOR_KEY_WITH_CURSOR_PAGE_1, WAIT_KEYORG $6214 TIMES_3_TABLE: HEX 00 03 06 09 0C 0F 12 15 18
ORG $8C35 TABLE0: HEX 80 80 80 80 80 80 80 80 80 80 80 80 80 80 TABLE1: HEX C0 AA D5 AA D5 AA D5 AA D5 AA D5 AA D5 80 TABLE2: HEX 90 80 80 80 80 80 80 80 80 80 80 80 80 82 TABLE3: HEX 90 AA D1 A2 D5 A8 85 A8 C5 A2 D4 A2 95 82 TABLE4: HEX 90 82 91 A2 C5 A8 80 88 C5 A2 94 A0 90 82 TABLE5: HEX 90 82 90 A2 C4 A8 80 88 C5 A2 94 A0 90 82 TABLE6: HEX 90 82 90 A2 C4 A8 81 88 C4 A2 D4 A0 95 82 TABLE7: HEX 90 A2 D1 A2 C4 88 80 88 C4 A2 84 A0 85 82 TABLE8: HEX 90 82 91 A2 C4 88 80 88 C4 AA 84 A0 85 82 TABLE9: HEX 90 82 91 A2 C4 88 80 88 C4 8A 84 A0 91 82 TABLE10: HEX 90 AA 91 A2 C4 A8 85 A8 85 82 D4 A2 91 82 ORG $8CCF ADDRESS_TABLE: WORD TABLE0-13 WORD TABLE1-13 WORD TABLE2-13 WORD TABLE3-13 WORD TABLE4-13 WORD TABLE5-13 WORD TABLE6-13 WORD TABLE7-13 WORD TABLE8-13 WORD TABLE9-13 WORD TABLE10-13
ORG $8B1A SPINNING_GAME_OVER: SUBROUTINE LDA #$01 STA ANIM_COUNT LDA #$20 STA HGR_PAGE .loop: JSR ANIM5 JSR ANIM4 JSR ANIM3 JSR ANIM2 JSR ANIM1 JSR ANIM0 JSR ANIM1 JSR ANIM2 JSR ANIM3 JSR ANIM4 JSR ANIM5 JSR ANIM10 JSR ANIM9 JSR ANIM8 JSR ANIM7 JSR ANIM6 JSR ANIM7 JSR ANIM8 JSR ANIM9 JSR ANIM10 LDA ANIM_COUNT CMP #100 BCC .loop JSR ANIM5 JSR ANIM4 JSR ANIM3 JSR ANIM2 JSR ANIM1 JSR ANIM0 CLC RTS ORG $8B7A ANIM0: JSR SHOW_ANIM_LINE HEX 00 01 02 03 04 05 06 07 08 09 0A 02 01 00 ANIM1: JSR SHOW_ANIM_LINE HEX 00 00 01 02 03 04 05 07 09 0A 02 01 00 00 ANIM2: JSR SHOW_ANIM_LINE HEX 00 00 00 01 02 03 04 09 0A 02 01 00 00 00 ANIM3: JSR SHOW_ANIM_LINE HEX 00 00 00 00 01 02 03 0A 02 01 00 00 00 00 ANIM4: JSR SHOW_ANIM_LINE HEX 00 00 00 00 00 01 03 0A 01 00 00 00 00 00 ANIM5: JSR SHOW_ANIM_LINE HEX 00 00 00 00 00 00 01 01 00 00 00 00 00 00 ANIM6: JSR SHOW_ANIM_LINE HEX 00 01 02 0A 09 08 07 06 05 04 03 02 01 00 ANIM7: JSR SHOW_ANIM_LINE HEX 00 00 01 02 0A 09 07 05 04 03 02 01 00 00 ANIM8: JSR SHOW_ANIM_LINE HEX 00 00 00 01 02 0A 09 04 03 02 01 00 00 00 ANIM9: JSR SHOW_ANIM_LINE HEX 00 00 00 00 01 02 0A 03 02 01 00 00 00 00 ANIM10: JSR SHOW_ANIM_LINE HEX 00 00 00 00 00 01 0A 03 01 00 00 00 00 00
ORG $8CE5 SHOW_ANIM_LINE: SUBROUTINE PLA STA TMP_PTR PLA STA TMP_PTR+1 ; store "return" addr ; Fill 14 rows of pixel data from row 0x51 (81) through 0x5E (94). LDY #$50 STY GAME_ROWNUM BNE .next ; unconditional .loop: JSR ROW_TO_ADDR LDY #$00 LDA (TMP_PTR),Y ASL TAX LDA ADDRESS_TABLE,X STA .loop2+1 LDA ADDRESS_TABLE+1,X ; groups of 14 bytes STA .loop2+2 LDY #$0D ; Copy 13 bytes of pixel data onto screen from ; addr+14 to addr+26 .loop2: LDA $8D08,Y ; fixed up from above STA (ROW_ADDR),Y ; pixel data INY CPY #$1B BCC .loop2 ; Y < 27 ; Next row .next: JSR INCREMENT_TMP_PTR INC GAME_ROWNUM LDY GAME_ROWNUM CPY #$5F BCC .loop LDX ANIM_COUNT LDY #$FF .delay: DEY BNE .delay DEX BNE .delay INC ANIM_COUNT LDA INPUT_MODE CMP #KEYBOARD_MODE BEQ .check_for_keypress LDA BUTN1 BMI .input_detected LDA BUTN0 BMI .input_detected .check_for_keypress: LDA KBD BMI .input_detected RTS ; Skip the rest of the big animation. .input_detected: PLA PLA SEC LDA KBD STA KBDSTRB RTS ORG $8D4B ANIM_COUNT: HEX 9D ORG $8D4C INCREMENT_TMP_PTR: SUBROUTINE INC TMP_PTR BNE .end INC TMP_PTR+1 .end: RTS
ANIM_COUNT, INCREMENT_TMP_PTR, SHOW_ANIM_LINEADDRESS_TABLE, BUTN0, BUTN1, GAME_ROWNUM, INPUT_MODE, KBD, KBDSTRB, ROW_ADDR, ROW_TO_ADDR, TMP_PTR