Lode Runner: Apple II reverse engineering

11. Level editor#

⟨tables [560]⟩+=
    ORG     $7C4D
EDITOR_KEYS:
    ; P (Play level)
    ; C (Clear level)
    ; E (Edit level)
    ; M (Move level)
    ; I (Initialize disk)
    ; S (clear high Scores)
    HEX     D0 C3 C5 CD C9 D3 00    ; P C E M I S
EDITOR_ROUTINE_ADDRESS:
    WORD    EDITOR_PLAY_LEVEL-1
    WORD    EDITOR_CLEAR_LEVEL-1
    WORD    EDITOR_EDIT_LEVEL-1
    WORD    EDITOR_MOVE_LEVEL-1
    WORD    EDITOR_INITIALIZE_DISK-1
    WORD    EDITOR_CLEAR_HIGH_SCORES-1
⟨level editor [562]⟩=
    ORG     $7B84
LEVEL_EDITOR:
    SUBROUTINE

    LDA     #$00
    STA     SCORE
    STA     SCORE+1
    STA     SCORE+2
    STA     SCORE+3

    LDA     INDIRECT_TARGET
    STA     RWTS_ADDR
    LDA     INDIRECT_TARGET+1
    STA     RWTS_ADDR+1

    LDA     #$05
    STA     LIVES
    STA     GAME_MODE
    LDA     INPUT_MODE
    STA     READ_SAVED_INPUT_MODE+1

    LDA     #KEYBOARD_MODE
    STA     INPUT_MODE

    STA     TXTPAGE1

    LDA     DISK_LEVEL_LOC
    CMP     #$96
    BCC     START_LEVEL_EDITOR
    LDA     #$00
    STA     DISK_LEVEL_LOC

START_LEVEL_EDITOR:
    JSR     CLEAR_HGR1
    LDA     #$20
    STA     DRAW_PAGE
    LDA     #$00
    STA     GAME_COLNUM
    STA     GAME_ROWNUM

    ; "  LODE RUNNER BOARD EDITOR\r
    ; "----------------------------\r
    ; "  <ESC> ABORTS ANY COMMAND\r"
    JSR     PUT_STRING
    HEX     A0 A0 CC CF C4 C5 A0 D2 D5 CE CE C5 D2 A0 C2 CF
    HEX     C1 D2 C4 A0 C5 C4 C9 D4 CF D2 8D AD AD AD AD AD
    HEX     AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD
    HEX     AD AD AD AD AD AD AD 8D A0 A0 BC C5 D3 C3 BE A0
    HEX     C1 C2 CF D2 D4 D3 A0 C1 CE D9 A0 C3 CF CD CD C1
    HEX     CE C4 8D 00

EDITOR_COMMAND_LOOP:
    LDA     GAME_ROWNUM
    CMP     #$09
    BCS     START_LEVEL_EDITOR

    ; "\r"
    ; "COMMAND>"
    JSR     PUT_STRING
    HEX     8D C3 CF CD CD C1 CE C4 BE 00

    JSR     EDITOR_WAIT_FOR_KEY
    LDX     #$00

.loop2:
    LDY     EDITOR_KEYS,X
    BEQ     EDITOR_COMMAND_LOOP_BEEP
    CMP     EDITOR_KEYS,X
    BEQ     .end
    INX
    BNE     .loop2

EDITOR_COMMAND_LOOP_BEEP:
    JSR     BEEP
    JMP     EDITOR_COMMAND_LOOP

.end:
    TXA
    ASL
    TAX
    LDA     EDITOR_ROUTINE_ADDRESS+1,X
    PHA
    LDA     EDITOR_ROUTINE_ADDRESS,X
    PHA
    RTS

Clearing a level involves getting the target level number from the user, waiting for the user to insert a valid data disk, and then writing zeros to the target level on disk.

⟨editor clear level [564]⟩=
    ORG     $7C8E
EDITOR_CLEAR_LEVEL:
    SUBROUTINE

    ; "\r"
    ; ">>CLEAR LEVEL"
    JSR     PUT_STRING
    HEX     8D BE BE C3 CC C5 C1 D2 A0 CC C5 D6 C5 CC 00

    JSR     GET_LEVEL_FROM_KEYBOARD
    BCS     .beep
    JSR     CHECK_FOR_VALID_DATA_DISK

    LDY     #$00
    TYA
.loop:
    STA     DISK_BUFFER,Y
    INY
    BNE     .loop

    LDA     #DISK_ACCESS_WRITE
    JSR     ACCESS_COMPRESSED_LEVEL_DATA      ; write level
    JMP     EDITOR_COMMAND_LOOP

.beep:
    JMP     EDITOR_COMMAND_LOOP_BEEP

Moving a level involves getting the source and target level numbers from the user, waiting for the user to insert the source data disk, reading the source level, waiting for the user to insert the target data disk, and then writing the current level data to the target level on disk.

⟨defines [566]⟩+=
    ORG     $824F
EDITOR_LEVEL_ENTRY:
    HEX     0F
Used in ⟨*⟩
Defines EDITOR_LEVEL_ENTRY
⟨editor move level [568]⟩=
    ORG     $7CD8
EDITOR_MOVE_LEVEL:
    SUBROUTINE

    ; "\r"
    ; ">>MOVE LEVEL"
    JSR     PUT_STRING
    HEX     8D BE BE CD CF D6 C5 A0 CC C5 D6 C5 CC 00

    JSR     GET_LEVEL_FROM_KEYBOARD
    BCS     .beep
    STY     EDITOR_LEVEL_ENTRY      ; source level

    ; " TO LEVEL"
    JSR     PUT_STRING
    HEX     A0 D4 CF A0 CC C5 D6 C5 CC 00

    JSR     GET_LEVEL_FROM_KEYBOARD
    BCS     .beep
    STY     SAVED_VTOC_DATA         ; convenient place for target level

    ; "\r"
    ; "  SOURCE DISKETTE"
    JSR     PUT_STRING
    HEX     8D A0 A0 D3 CF D5 D2 C3 C5 A0 C4 C9 D3 CB C5 D4 D4 C5 00
 
    JSR     EDITOR_WAIT_FOR_KEY
    ; Deny and dump user back to editor if not valid data disk
    JSR     CHECK_FOR_VALID_DATA_DISK
    LDA     EDITOR_LEVEL_ENTRY              ; source level
    STA     DISK_LEVEL_LOC
    LDA     #DISK_ACCESS_READ
    JSR     ACCESS_COMPRESSED_LEVEL_DATA      ; read source level

    ; "\r"
    ; "  DESTINATION DISKETTE"
    JSR     PUT_STRING
    HEX     8D A0 A0 C4 C5 D3 D4 C9 CE C1 D4 C9 CF CE A0 C4 C9 D3 CB C5 D4 D4 C5 00

    JSR     EDITOR_WAIT_FOR_KEY
    ; Deny and dump user back to editor if not valid data disk
    JSR     CHECK_FOR_VALID_DATA_DISK
    LDA     SAVED_VTOC_DATA                 ; target level
    STA     DISK_LEVEL_LOC
    LDA     #DISK_ACCESS_WRITE
    JSR     ACCESS_COMPRESSED_LEVEL_DATA      ; write target level
    JMP     EDITOR_COMMAND_LOOP

.beep:
    JMP     EDITOR_COMMAND_LOOP_BEEP
⟨editor play level [570]⟩=
    ORG     $7C60
EDITOR_PLAY_LEVEL:
    SUBROUTINE

    ; "\r"
    ; ">>PLAY LEVEL"
    JSR     PUT_STRING
    HEX     8D BE BE D0 CC C1 D9 A0 CC C5 D6 C5 CC 00

    JSR     GET_LEVEL_FROM_KEYBOARD
    BCS     .beep

READ_SAVED_INPUT_MODE:
    LDA     #$00
    STA     INPUT_MODE
    LDA     #GAME_MODE_PLAY_IN_EDITOR
    STA     GAME_MODE
    LDA     #$01
    STA     $9D
    LDA     DISK_LEVEL_LOC
    BEQ     .init_game_data_
    LSR     $9D

.init_game_data_:
    JMP     INIT_GAME_DATA

.beep:
    JMP     EDITOR_COMMAND_LOOP_BEEP
Defines EDITOR_PLAY_LEVEL
⟨editor edit level [572]⟩=
    ORG     $7CBC
EDITOR_EDIT_LEVEL:
    SUBROUTINE

    ; "\r"
    ; ">>EDIT LEVEL"
    JSR     PUT_STRING
    HEX     8D BE BE C5 C4 C9 D4 A0 CC C5 D6 C5 CC 00

    JSR     GET_LEVEL_FROM_KEYBOARD
    BCS     .beep
    JMP     EDIT_LEVEL

.beep:
    JMP     EDITOR_COMMAND_LOOP_BEEP
⟨editor edit level [574]⟩+=
    ORG     $7F01
EDIT_LEVEL:
    SUBROUTINE

    JSR     CLEAR_HGR2
    LDA     #$40
    STA     DRAW_PAGE
    JSR     PUT_STATUS_DRAW
    LDA     #$20
    STA     DRAW_PAGE

    JSR     CHECK_FOR_VALID_DATA_DISK
    LDX     #$01
    STX     $AD
    DEX
    JSR     LOAD_LEVEL
    BCC     .start_editing
    JMP     EDITOR_COMMAND_LOOP_BEEP

.start_editing:
    LDA     #$00
    STA     GAME_COLNUM
    STA     GAME_ROWNUM

EDIT_LEVEL_KEY_LOOP:
    JSR     GET_KEY_FOR_EDIT_LEVEL
    CMP     #$BA
    BCS     .store_sprite_num       ; key >= '9'+1
    CMP     #$B0
    BCC     .store_sprite_num       ; key < '0'
    ; key is digit

    AND     #$0F
    STA     SPRITE_NUM
    LDY     GAME_ROWNUM
    <<set active row pointer [[PTR1]] for [[Y]]>>
    LDY     GAME_COLNUM
    LDA     SPRITE_NUM
    EOR     (PTR1),Y
    BEQ     .store_sprite
    LSR     $AD

.store_sprite:
    LDA     SPRITE_NUM
    STA     (PTR1),Y
    JSR     DRAW_SPRITE_PAGE1
    JMP     EDIT_LEVEL_KEY_LOOP

.store_sprite_num:
    STA     SPRITE_NUM
    LDY     #$FF

.loop2:
    INY
    LDA     LEVEL_EDIT_KEY_TABLE,Y
    BEQ     EDIT_LEVEL_BEEP

    CMP     SPRITE_NUM
    BNE     .loop2

    TYA
    ASL
    TAY
    LDA     LEVEL_EDIT_KEY_FUNCTIONS+1,Y
    PHA
    LDA     LEVEL_EDIT_KEY_FUNCTIONS,Y
    PHA 
    RTS

EDIT_LEVEL_BEEP:
    JSR     BEEP
    JMP     EDIT_LEVEL_KEY_LOOP
⟨get key for edit level [576]⟩=
    ORG     $814B
GET_KEY_FOR_EDIT_LEVEL:
    SUBROUTINE

    LDY     GAME_ROWNUM
    <<set active row pointer [[PTR1]] for [[Y]]>>
    LDY     GAME_COLNUM
    LDA     (PTR1),Y
    JSR     WAIT_FOR_KEY_WITH_CURSOR_PAGE_1
    STA     KBDSTRB
    RTS
⟨tables [578]⟩+=
    ORG     $8162
LEVEL_EDIT_KEY_TABLE:
    ; J (left)
    ; I (up)
    ; K (right)
    ; M (down)
    ; ctrl-S (save)
    ; right arrow
    ; left arrow
    ; ctrl-Q
    HEX     CA C9 CB CD 93 95 88 91 00
LEVEL_EDIT_KEY_FUNCTIONS:
    WORD    LEVEL_EDIT_LEFT-1
    WORD    LEVEL_EDIT_UP-1
    WORD    LEVEL_EDIT_RIGHT-1
    WORD    LEVEL_EDIT_DOWN-1
    WORD    LEVEL_EDIT_SAVE-1
    WORD    LEVEL_EDIT_RIGHT_ARROW-1
    WORD    LEVEL_EDIT_LEFT_ARROW-1
    WORD    LEVEL_EDIT_Q-1
Used in ⟨*⟩
Defines LEVEL_EDIT_KEY_FUNCTIONS, LEVEL_EDIT_KEY_TABLE
⟨level editor key functions [580]⟩=
    ORG     $7F74
LEVEL_EDIT_UP:
    SUBROUTINE

    LDA     GAME_ROWNUM
    BEQ     EDIT_LEVEL_BEEP         ; nope!

    DEC     GAME_ROWNUM
    BPL     EDIT_LEVEL_KEY_LOOP     ; unconditional

LEVEL_EDIT_LEFT:
    SUBROUTINE

    LDA     GAME_COLNUM
    BEQ     EDIT_LEVEL_BEEP         ; nope!

    DEC     GAME_COLNUM
    BPL     EDIT_LEVEL_KEY_LOOP     ; unconditional

LEVEL_EDIT_RIGHT:
    SUBROUTINE

    LDA     GAME_COLNUM
    CMP     #MAX_GAME_COL
    BCS     EDIT_LEVEL_BEEP         ; nope!

    INC     GAME_COLNUM
    BNE     EDIT_LEVEL_KEY_LOOP     ; unconditional

LEVEL_EDIT_DOWN:
    SUBROUTINE

    LDA     GAME_ROWNUM
    CMP     #MAX_GAME_ROW
    BCS     EDIT_LEVEL_BEEP         ; nope!

    INC     GAME_ROWNUM
    BNE     EDIT_LEVEL_KEY_LOOP     ; unconditional

LEVEL_EDIT_SAVE1:
    SUBROUTINE

    LDA     GAME_ROWNUM
    PHA
    LDA     GAME_COLNUM
    PHA
    LDA     #$01
    JSR     ACCESS_HI_SCORE_DATA_FROM_DISK
    CMP     #$00
    BNE     .check_for_master_disk

    JSR     BAD_DATA_DISK
    JMP     .end

.check_for_master_disk:
    CMP     #$01
    BNE     .save_data

    JSR     DONT_MANIPULATE_MASTER_DISK
    JMP     .end

.save_data:
    JSR     COMPRESS_AND_SAVE_LEVEL_DATA
    PLA
    STA     GAME_COLNUM
    PLA
    STA     GAME_ROWNUM
    LDA     #$01
    STA     $AD
    RTS

.end:
    LDA     #$00
    STA     GAME_COLNUM
    STA     GAME_ROWNUM
    JMP     EDIT_LEVEL_KEY_LOOP

LEVEL_EDIT_SAVE:
    SUBROUTINE

    JSR     LEVEL_EDIT_SAVE1
    JMP     EDIT_LEVEL_KEY_LOOP

LEVEL_EDIT_RIGHT_ARROW:
    SUBROUTINE

    LDA     DISK_LEVEL_LOC
    CMP     #$95

LEVEL_EDIT_RIGHT_ARROW_CHECK:
    BEQ     EDIT_LEVEL_BEEP
    JSR     LEVEL_EDIT_CHANGE_LEVEL
    INC     DISK_LEVEL_LOC
    INC     LEVELNUM
    JMP     EDIT_LEVEL

LEVEL_EDIT_LEFT_ARROW:
    SUBROUTINE

    LDA     DISK_LEVEL_LOC
    BEQ     LEVEL_EDIT_RIGHT_ARROW_CHECK
    JSR     LEVEL_EDIT_CHANGE_LEVEL
    DEC     LEVELNUM
    DEC     DISK_LEVEL_LOC
    JMP     EDIT_LEVEL

LEVEL_EDIT_Q:
    SUBROUTINE

    JSR     LEVEL_EDIT_CHANGE_LEVEL
    JMP     START_LEVEL_EDITOR

LEVEL_EDIT_CHANGE_LEVEL:
    SUBROUTINE

    LDA     $AD
    BNE     .end
    JSR     CLEAR_HGR2
    LDA     #$40
    STA     DRAW_PAGE
    LDA     #$00
    STA     GAME_COLNUM
    STA     GAME_ROWNUM

    ; "LEVEL HAS BEEN CHANGED BUT\r"
    ; "NOT SAVED. DO YOU WISH TO\r"
    ; "SAVE MODIFIED LEVEL (Y/N) "
    JSR     PUT_STRING
    HEX     CC C5 D6 C5 CC A0 C8 C1 D3 A0 C2 C5 C5 CE A0 C3
    HEX     C8 C1 CE C7 C5 C4 A0 C2 D5 D4 8D CE CF D4 A0 D3
    HEX     C1 D6 C5 C4 AE A0 C4 CF A0 D9 CF D5 A0 D7 C9 D3
    HEX     C8 A0 D4 CF 8D D3 C1 D6 C5 A0 CD CF C4 C9 C6 C9
    HEX     C5 C4 A0 CC C5 D6 C5 CC A0 A8 D9 AF CE A9 A0 00

    JSR     BEEP
    STA     TXTPAGE2

.loop:
    LDA     #$00
    JSR     WAIT_FOR_KEY
    STA     KBDSTRB
    CMP     #$CE        ; 'N'
    BEQ     .end

    CMP     #$D9        ; 'Y'
    BNE     .loop

    JSR     LEVEL_EDIT_SAVE1

.end:
    STA     TXTPAGE1
    LDA     #$00
    STA     GAME_COLNUM
    STA     GAME_ROWNUM
    RTS