; Fenders "3-sector" loader disassembly, 20070526 bkw ; Double-density version ; Note: the double-density loader doesn't actually fit in 3 sectors. ; It uses sectors 1-3 and 720. ; First 3 sectors of a DD disk are still only 128 bytes/sector. ; At boot, the OS boot code loads the first 3 sectors and jumps to ; the loaded code... which then loads sector 720 (a proper 256-byte DD ; sector), which contains the rest of the code. ; I haven't done a very thorough job of reverse-engineering the DD ; version of the loader. Its structure is similar to that of the SD ; loader (fenders.dasm). processor 6502 ;;; Equates: ;; OS ROM entry points SIOV .equ $e459 COLDSV .equ $e477 KEYBDV .equ $e420 ; K: handler device table keyb_get_lo .equ KEYBDV+4 ; pointer to "get byte" routine, minus 1 keyb_get_hi .equ KEYBDV+5 ; (used by get_key) ;; OS zero page BOOTQ .equ $09 SAVMSC .equ $58 ZROFRE .equ $80 ;; OS and FMS page 2 RAM variables COLDST .equ $0244 SDMCTL .equ $022f SDLSTL .equ $0230 SDLSTH .equ $0231 RUNAD .equ $02e0 INITAD .equ $02e2 ;; DCB, used for sector I/O parameters by SIOV (called by read_sector) DDEVIC .equ $0300 DUNIT .equ $0301 DCOMND .equ $0302 DSTATS .equ $0303 DBUFLO .equ $0304 DBUFHI .equ $0305 DTIMLO .equ $0306 DBYTLO .equ $0308 DBYTHI .equ $0309 DAUX1 .equ $030a DAUX2 .equ $030b ;; Hardware registers COLPF1 .equ $d017 VCOUNT .equ $d40b ;; Local variables (zero page) end_address .equ $45 save_pos .equ $49 menu_counter .equ $b0 dir_sector_lo .equ $b1 tmp_dlistl .equ $b2 tmp_dlisth .equ $b3 menu_ptr_lo .equ $b4 menu_ptr_hi .equ $b5 dest_ptr .equ $43 start_sector_lo_tbl .equ $c0 start_sector_hi_tbl .equ $e0 ;; Local variables (non zero page) buffer .equ $0b10 sector_link_hi .equ $0c0d ; buffer + $fd sector_link_lo .equ $0c0e ; buffer + $fe sector_byte_count .equ $0c0f ; buffer + $ff ;;; Bootable disk image starts here: .org $0700 ;;; Standard Atari boot disk header (6 bytes) boot_record: .byte $00 ; ignored .byte $03 ; number of sectors to read .word boot_record ; load address .word COLDSV ; init address, don't think this gets used ;;; Actual code starts here: boot_continuation: OFFSET_COLDST_1_DD .equ *-boot_record+1 OFFSET_COLDST_2_DD .equ *-boot_record+5 LDY #$00 ; 0 . STY COLDST INY STY BOOTQ STY DUNIT DEC DTIMLO set_dbl_density: LDA #$4e ; 78 N STA DCOMND LDA #$40 ; 64 @ STA DSTATS LDA #$0c ; 12 . STA DBYTLO LDA #$00 ; 0 . STA DBYTHI LDA #buffer STA DBUFHI JSR SIOV BMI set_dbl_density LDA #$04 ; 4 . STA buffer+5 LDA #$01 ; 1 . STA buffer+6 LDA #$00 ; 0 . STA buffer+7 LDA #$4f ; 79 O STA DCOMND LDA #$80 ; 128 . STA DSTATS JSR SIOV BMI set_dbl_density ; The bootloader code is 640 bytes long. First 384 bytes were loaded ; from the 3 boot sectors already; the rest lives in sector 720, which ; we have to load before running it: read_sec_720: LDA #$52 ; 82 R STA DCOMND LDA #$40 ; 64 @ STA DSTATS LDA #$80 ; 128 . STA DBUFLO LDA #$08 ; 8 . STA DBUFHI LDA #$00 ; 0 . STA DBYTLO LDA #$01 ; 1 . STA DBYTHI LDA #$d0 ; 208 . STA DAUX1 LDA #$02 ; 2 . STA DAUX2 JSR SIOV BMI read_sec_720 ; setup display list LDA SDLSTL STA tmp_dlistl LDA SDLSTH STA tmp_dlisth LDA #$00 ; 0 . STA SDMCTL LDA #display_list ; 9 . STA SDLSTH ; init menu LDA #$6c ; 108 l STA menu_ptr_lo LDA #$09 ; 9 . STA menu_ptr_hi LDA #$69 ; 105 i STA dir_sector_lo read_dir_sector: LDA dir_sector_lo STA DAUX1 LDA #$01 ; 1 . STA DAUX2 JSR read_sector INC dir_sector_lo DEX do_dirent: LDA buffer,X BEQ dir_done BMI next_dirent AND #$01 ; 1 . BNE next_dirent INC menu_counter LDY menu_counter LDA buffer+3,X STA start_sector_lo_tbl,Y LDA buffer+4,X STA start_sector_hi_tbl,Y TYA CLC ADC #$a0 ; 160 . LDY #$03 ; 3 . STA (menu_ptr_lo),Y INY LDA #$8e ; 142 . STA (menu_ptr_lo),Y INY next_char: INY LDA buffer+5,X INX SEC SBC #$20 ; 32 STA (menu_ptr_lo),Y CPY #$10 ; 16 . BNE next_char CLC LDA menu_ptr_lo ADC #$14 ; 20 . STA menu_ptr_lo BCC skip_ptr_hi INC menu_ptr_hi skip_ptr_hi: LDA menu_counter CMP #$14 ; 20 . BEQ dir_done next_dirent: TXA AND #$f0 ; 240 . CLC ADC #$10 ; 16 . TAX ASL BCC do_dirent BCS read_dir_sector dir_done: LDA #$22 ; 34 " STA SDMCTL wait_vcount_0: LDA VCOUNT BNE wait_vcount_0 wait_for_input: JSR get_key SEC SBC #$40 ; 64 @ CMP menu_counter BEQ load_file BCS wait_for_input load_file: TAX LDA start_sector_lo_tbl,X STA DAUX1 LDA start_sector_hi_tbl,X STA DAUX2 LDA #$68 ; 104 h STA menu_ptr_lo LDA #$09 ; 9 . STA menu_ptr_hi L0834: DEX BEQ print_loading_msg CLC LDA menu_ptr_lo ADC #$14 ; 20 . STA menu_ptr_lo BCC L0834 INC menu_ptr_hi BNE L0834 print_loading_msg: LDY #$00 ; 0 . next_msg_byte: LDA loading_msg,Y STA (SAVMSC),Y INY CPY #$09 ; 9 . BNE next_msg_byte print_filename: LDA (menu_ptr_lo),Y STA (SAVMSC),Y INY CPY #$15 ; 21 . BNE print_filename LDA #$00 ; 0 . STA SDMCTL LDA tmp_dlistl STA SDLSTL LDA tmp_dlisth STA SDLSTH LDA #$22 ; 34 " STA SDMCTL wait_vcount_again: LDA VCOUNT BNE wait_vcount_again LDY #$00 ; 0 . TYA clear_zp: STA ZROFRE,Y INY BPL clear_zp ; the "JSR try_read" below MUST be located at $087f. ; The JSR opcode is the last byte loaded in the 3-sector boot loader, ; and its operand is the first 2 bytes loaded from sector 720! .if *<>$087f .echo "Code offsets have changed, fix me (", *, "should be $087f)" .err .endif JSR try_read ; cut here! DEX read_segment: JSR get_next_byte STA dest_ptr JSR get_next_byte STA dest_ptr+1 AND dest_ptr CMP #$ff ; 255 . BEQ read_segment JSR get_next_byte STA end_address JSR get_next_byte STA end_address+1 load_byte: JSR get_next_byte STA (dest_ptr),Y INC dest_ptr BNE check_seg_done INC dest_ptr+1 BEQ check_for_init check_seg_done: LDA end_address CMP dest_ptr LDA end_address+1 SBC dest_ptr+1 BCS load_byte check_for_init: LDA INITAD ORA INITAD+1 BEQ read_segment STX save_pos JSR do_init LDX save_pos LDY #$00 ; 0 . STY INITAD STY INITAD+1 BEQ read_segment do_init: JMP (INITAD) get_next_byte: ; self-modifying code changes immediate CPX operand CPX #$fd ; 253 . BNE return_next_byte LDA DAUX1 ORA DAUX2 BNE try_read OFFSET_SCREENOFF_DD .equ *-boot_record LDA SDMCTL JMP (RUNAD) read_sector: LDA #$31 ; 49 1 STA DDEVIC LDA #$52 ; 82 R STA DCOMND LDA #buffer ; 11 . STA DBUFHI LDA #$00 ; 0 . STA DBYTLO LDA #$01 ; 1 . STA DBYTHI try_read: LDA #$40 ; 64 @ STA DSTATS JSR SIOV BMI try_read LDA sector_link_hi AND #$03 ; 3 . STA DAUX2 LDA sector_link_lo STA DAUX1 OFFSET_ROTCOLOR_DD .equ *-boot_record LDA COLPF1 LDA sector_byte_count STA get_next_byte+1 LDY #$00 ; 0 . LDX #$00 ; 0 . return_next_byte: LDA buffer,X INX RTS get_key: LDA keyb_get_hi PHA LDA keyb_get_lo PHA RTS loading_msg: .byte $00,$00,$2c,$6f,$61,$64,$69,$6e ; "..,oadin" .byte $67,$00,$00 ; "g.." display_list: .byte $70,$70,$70,$47 ; "pppG" .byte screen .byte $70,$06,$06,$06,$06,$06,$06 ; "p......" .byte $06,$06,$06,$06,$06,$06,$06,$06 ; "........" .byte $06,$06,$06,$06,$06,$06,$41 ; "......A" .byte display_list OFFSET_TITLE_DD .equ *-boot_record screen: .byte $00,$00,$00,$00,$61,$74,$61 ; ".....ata" .byte $72,$69,$00,$61,$72,$63,$61,$64 ; "ri.arcad" .byte $65,$00,$00,$00,$00,$00,$00,$00 ; "e......." .byte $00,$00,$00,$00,$00,$00,$00,$00 ; "........" .byte $00,$00,$00,$00,$00,$00,$00,$00 ; "........" .byte $00 ; "."