diff --git a/bbb2.system.s b/bbb2.system.s new file mode 100644 index 0000000..98fb2e6 --- /dev/null +++ b/bbb2.system.s @@ -0,0 +1,740 @@ +;;; Disassembly of BYE.SYSTEM (Bird's Better Bye) +;;; Additions by Joshua Bell inexorabletash@gmail.com + + .setcpu "65C02" + .include "apple2.inc" + .include "prodos.inc" + + .feature c_comments + +;;; Miscellaneous + +COL80HPOS := $57B + +;;; I/O Soft Switches / Firmware + +RAMRDOFF := $C002 ; If 80STORE Off: Read Main Mem $0200-$BFFF +RAMRDON := $C003 ; If 80STORE Off: Read Aux Mem $0200-$BFFF +RAMWRTOFF := $C004 ; If 80STORE Off: Write Main Mem $0200-$BFFF +RAMWRTON := $C005 ; If 80STORE Off: Write Aux Mem $0200-$BFFF +ALTZPOFF := $C008 ; Main Stack and Zero Page +ALTZPON := $C009 ; Aux Stack and Zero Page +ROMINNW := $C082 ; Read ROM; no write +ROMINWB1 := $C089 ; Read ROM; write RAM bank 1 + +SLOT3 := $C300 + +;;; Monitor + +SETTXT := $FB39 +TABV := $FB5B +SETPWRC := $FB6F +BELL1 := $FBDD +HOME := $FC58 +COUT := $FDED +SETINV := $FE80 +SETNORM := $FE84 + +;;; ASCII/Key codes +ASCII_TAB := $9 +ASCII_DOWN := $A ; down arrow +ASCII_UP := $B ; up arrow +ASCII_CR := $D +ASCII_RIGHT := $15 ; right arrow +ASCII_SYN := $16 ; scroll text window up +ASCII_ETB := $17 ; scroll text window down +ASCII_EM := $19 ; move cursor to upper left +ASCII_ESCAPE := $1B + +;;; ------------------------------------------------------------ + +.define HI(char) (char|$80) + +.macro HIASCII arg + .repeat .strlen(arg), i + .byte .strat(arg, i) | $80 + .endrep +.endmacro + +.macro HIASCIIZ arg + HIASCII arg + .byte 0 +.endmacro + + +;;; ------------------------------------------------------------ + +;;; ProDOS Technical Reference Manual, 5.1.5.2: +;;; +;;; ProDOS MLI call $65, the QUIT call, moves addresses $D100 through +;;; $D3FF from the second 4K bank of RAM of the language card to +;;; $1000, and executes a JMP to $1000. What initially resides in that +;;; area is Apple's dispatcher code. + +;;; ------------------------------------------------------------ +;;; Entry point +;;; ------------------------------------------------------------ + + ;; Loads at $2000 but executed at $1000. + .org $2000 + + jmp install_and_quit + install_src := * + +;;; ------------------------------------------------------------ +;;; Selector +;;; ------------------------------------------------------------ + + .org $1000 +.proc bbb + + prefix := $280 ; length-prefixed + + filenames := $1400 ; each is length + 15 bytes + read_buffer := $2000 ; Also, start location for launched SYS files + + mark_params := $60 + mark_ref_num := $61 + mark_position := $62 ; 3-bytes + next_device_num := $65 ; next device number to try + + current_entry := $67 ; index of current entry + num_entries := $68 ; length of |filenames| + curr_len := $69 ; length of current entry name + curr_ptr := $6C ; address of current entry name (in |filenames|) + + prefix_depth := $6B ; 0 = root + + entry_length := $6E + entries_per_block := $6F + file_count := $70 ; 2 bytes + + entry_num := $72 + page_start := $73 ; index of first entry shown on screen + + max_entries := 128 ; max # of entries; more are ignored + types_table := $74 ; high bit clear = dir, set = sys + + top_row := 2 ; first row used on screen + bottom_row := 21 ; last row used on screen + +;;; ------------------------------------------------------------ + + cld ; ProDOS protocol for QUIT routine + lda ROMINNW ; Page in ROM for reads, writes ignored + + lda #$A0 + jsr SLOT3 ; Activate 80-Column Firmware + + ;; Update system bitmap + ldx #BITMAP_SIZE-1 ; zero it all out +: stz BITMAP,x + dex + bpl :- + inc BITMAP+BITMAP_SIZE-1 ; protect ProDOS global page + lda #%11001111 ; protect zp, stack, text page 1 + sta BITMAP + + ;; Find device + lda #2 + sta mark_params + ldx DEVCNT ; max device num + stx next_device_num + lda DEVNUM + bne check_device + +next_device: + ldx next_device_num + lda DEVLST,x + cpx #1 + bcs :+ + ldx DEVCNT + inx +: dex + stx next_device_num + +check_device: + sta on_line_params_unit + MLI_CALL ON_LINE, on_line_params + bcs next_device + + stz prefix_depth + lda prefix+1 + and #$0F + beq next_device + adc #2 + tax + + ;; Resize prefix to length x and open the directory for reading +.proc resize_prefix_and_open + stx prefix + lda #'/' + sta prefix+1 + sta prefix,x + stz prefix+1,x + + jsr do_open + bcc :+ + + ;; Open failed + lda prefix_depth ; root? + beq next_device + jsr pop_prefix ; no, but failed; go up a level + stx prefix + jmp keyboard_loop + + ;; Open succeeded +: inc prefix_depth + stz num_entries + lda #DirectoryHeader::size + sta read_params_request + stz read_params_request+1 + jsr do_read + bcs finish_read2 + + ;; Store entry_length/entries_per_block/file_count + ldx #3 +: lda read_buffer + DirectoryHeader::entry_length,x + sta entry_length,x + dex + bpl :- + + sta read_params_request + lda #1 + sta entry_num + stz mark_position+1 + stz mark_position+2 + + lda file_count + ora file_count+1 + bne next_file_entry ; any files? + +finish_read2: + bra finish_read + +next_file_entry: + bit file_count+1 ; wrap around? + bmi finish_read2 + + + ;; TODO: The math here is mysterious; understand and document +floop: lda mark_position+1 + and #$FE + sta mark_position+1 + ldy entry_num + lda #0 + cpy entries_per_block + bcc :+ + tay + sty entry_num + + inc mark_position+1 +carry: inc mark_position+1 +: dey + clc + bmi :+ + adc entry_length + bcc :- + bcs carry + +: adc #4 + sta mark_position + MLI_CALL SET_MARK, mark_params + bcs finish_read2 + jsr do_read + bcs finish_read2 + + inc entry_num + lda read_buffer + FileEntry::storage_type + and #$F0 ; mask off storage_type + beq floop ; inactive file entry + dec file_count + bne :+ + dec file_count+1 + + ;; Check read access +: ror read_buffer + FileEntry::access + bcc next_file_entry + + ;; Check file type + lda read_buffer + FileEntry::file_type + cmp #FileType::Directory + beq :+ + cmp #FileType::System + bne next_file_entry + + ;; Check to see if we have room +: ldx num_entries + cpx #max_entries + bcs finish_read + + ;; Store type + sta types_table,x + + ;; Copy name + jsr update_curr_ptr + ldy #$0F ; name length + 1 (includes length byte) +: lda read_buffer,y + sta (curr_ptr),y + dey + bpl :- + iny ; Y = 0 + and #$0F ; mask off name length (remove storage_type) + sta (curr_ptr),y ; store length + + ;; Next + inc num_entries + bne next_file_entry + +next: jmp next_device + +finish_read: + MLI_CALL CLOSE, close_params + ;; fall through +.endproc + +;;; ------------------------------------------------------------ + +.proc draw_screen + jsr SETTXT ; TEXT + jsr HOME ; HOME + lda #23 ; VTAB 23 + jsr TABV + + ;; Print help text + lda #20 ; HTAB 20 + sta CH + ldy #0 ; index into string buffer + jsr cout_string + + ;; Draw prefix + jsr home + ldx #0 +: lda prefix+1,x + beq :+ + jsr ascii_cout + inx + bne :- + +: stz current_entry + stz page_start + lda num_entries + beq keyboard_loop ; no entries (empty directory) + + row_count := $6A + + cmp #bottom_row ; more entries than fit? + bcc :+ + lda #(bottom_row - top_row + 1) +: sta row_count + lda #2 + sta WNDTOP + sta WNDLFT + lda #22 + sta WNDWDTH + sta WNDBTM +loop: jsr draw_current_line + inc current_entry + dec row_count + bne loop + stz current_entry + beq draw_current_line_inv +.endproc + +;;; ------------------------------------------------------------ + + ;; Remove level from prefix; returns new length in X +.proc pop_prefix + ldx prefix +loop: dex + lda prefix,x + cmp #'/' + bne loop + cpx #1 + bne done + ldx prefix +done: + ;; Fall through... +.endproc + +handy_rts: + rts + +;;; ------------------------------------------------------------ + +.proc down_common + lda current_entry + inc a + cmp num_entries ; past the limit? + bcs handy_rts ; yes, just redraw + sta current_entry ; go to next + + lda CV + cmp #bottom_row ; at the bottom? + bne handy_rts + inc page_start ; yes, adjust page and + lda #ASCII_ETB ; scroll screen down + jmp COUT ; implicit rts +.endproc + +.proc on_down + jsr down_common + bra draw_current_line_inv +.endproc + +;;; ------------------------------------------------------------ + +.proc on_up + ldx current_entry + beq draw_current_line_inv ; first one? just redraw + dec current_entry ; go to previous + + lda CV + cmp #top_row ; at the top? + bne draw_current_line_inv ; if not, just draw + dec page_start ; yes, adjust page and + lda #ASCII_SYN ; scroll screen up + jsr cout + ;; fall through +.endproc + +;;; ------------------------------------------------------------ + +.proc on_alpha +loop: lda KBD + and #$5F ; make ASCII and uppercase + ldy #1 + cmp (curr_ptr),y ; key < first char ? + bcc draw_current_line_inv + + jsr down_common + bra loop +.endproc + +;;; ------------------------------------------------------------ + +draw_current_line_inv: + jsr SETINV + jsr draw_current_line + ;; fall through + +;;; ------------------------------------------------------------ + +.proc keyboard_loop + lda KBD + bpl keyboard_loop + sta KBDSTRB + jsr SETNORM + ldx num_entries + beq :+ ; no up/down/return if empty + + ;; BUG: overwrites A + pha + jsr draw_current_line + pla + + cmp #HI(ASCII_CR) + beq on_return + cmp #HI(ASCII_DOWN) + beq on_down + cmp #HI(ASCII_UP) + beq on_up + + cmp #HI('A') + bcs on_alpha + +: cmp #HI(ASCII_TAB) + beq next_drive + cmp #HI(ASCII_ESCAPE) + bne keyboard_loop + ;; fall through +.endproc + +;;; ------------------------------------------------------------ + +.proc on_escape + jsr pop_prefix ; leaves length in X + dec prefix_depth + bra resize_prefix_and_open_jmp +.endproc + +;;; ------------------------------------------------------------ + +next_drive: + jmp next_device + +inc_resize_prefix_and_open: + inx + +resize_prefix_and_open_jmp: + jmp resize_prefix_and_open + +;;; ------------------------------------------------------------ + +.proc on_return + MLI_CALL SET_PREFIX, set_prefix_params + bcs next_drive + ldx current_entry + jsr update_curr_ptr + + ldx prefix +: iny + lda (curr_ptr),y + inx + sta prefix,x + cpy curr_len + bcc :- + stx prefix + + ldy current_entry + lda types_table,y + bpl inc_resize_prefix_and_open ; is directory??? + ;; nope, system file, so... + + ;; fall through +.endproc + +;;; ------------------------------------------------------------ + +.proc launch_sys_file + jsr SETTXT + jsr HOME + lda #HI(ASCII_RIGHT) ; Right arrow ??? + jsr COUT + + jsr do_open + bcs next_drive + lda #$FF ; Load up to $FFFF bytes + sta read_params_request + sta read_params_request+1 + jsr do_read + php + MLI_CALL CLOSE, close_params + plp + bcs next_drive + jmp read_buffer ; Invoke the loaded code +.endproc + +;;; ------------------------------------------------------------ + +.proc cout_string +loop: lda help_string,y + beq handy_rts2 + jsr COUT + iny + bra loop +.endproc + +;;; ------------------------------------------------------------ + +;; Compute address/length of curr_ptr/curr_len +;; Call with entry index in X. Returns with Y = 0 + +.proc update_curr_ptr + stz curr_ptr+1 + txa + asl a + rol curr_ptr+1 + asl a + rol curr_ptr+1 + asl a + rol curr_ptr+1 + asl a + rol curr_ptr+1 + sta curr_ptr + lda #>filenames + clc + adc curr_ptr+1 + sta curr_ptr+1 + ldy #0 + lda (curr_ptr),y + sta curr_len +.endproc + +handy_rts2: + rts + +;;; ------------------------------------------------------------ + +.proc draw_current_line + lda #2 ; hpos = 2 + sta COL80HPOS + + ldx current_entry ; vpos = entry - page_start + 2 + txa + sec + sbc page_start + inc a + inc a + jsr TABV + + lda types_table,x + bmi name ; is sys file? + + ;; Draw folder glyph + stz COL80HPOS + lda INVFLG + pha + ldy #(folder_string - string_start) ; Draw folder glyphs + jsr cout_string + pla + sta INVFLG + + ;; Draw the name +name: jsr space + jsr update_curr_ptr +loop: iny + lda (curr_ptr),y + jsr ascii_cout + cpy curr_len + bcc loop + +space: lda #HI(' ') + bne cout ; implicit RTS +.endproc + +home: lda #HI(ASCII_EM) ; move cursor to top left + + ;; Sets high bit before calling COUT +ascii_cout: + ora #$80 +cout: jmp COUT + +;;; ------------------------------------------------------------ + +.proc do_open + MLI_CALL OPEN, open_params + lda open_params_ref_num + sta read_params_ref_num + sta mark_ref_num + rts +.endproc + +.proc do_read + MLI_CALL READ, read_params + rts +.endproc + +;;; ------------------------------------------------------------ + + string_start := * +.proc help_string + HIASCIIZ "RETURN: Select | TAB: Chg Vol | ESC: Back" +.endproc + + ;; Mousetext sequence: Enable, folder left, folder right, disable +.proc folder_string + .byte $0F,$1B,$D8,$D9,$18,$0E + .byte 0 ; null terminated +.endproc + +;;; ------------------------------------------------------------ + +.proc open_params +params: .byte 3 +path: .addr prefix +buffer: .addr $1C00 +ref_num:.byte 0 +.endproc + open_params_ref_num := open_params::ref_num + +.proc close_params +params: .byte 1 +ref_num:.byte 0 +.endproc + +.proc on_line_params +params: .byte 2 +unit: .byte $60 +buffer: .addr prefix+1 +.endproc + on_line_params_unit := on_line_params::unit + +.proc set_prefix_params +params: .byte 1 +path: .addr prefix +.endproc + +.proc read_params +params: .byte 4 +ref_num:.byte 1 +buffer: .word read_buffer +request:.word 0 +trans: .word 0 +.endproc + read_params_ref_num := read_params::ref_num + read_params_request := read_params::request + +;;; ------------------------------------------------------------ + + .assert read_params::request - bbb <= $300, error, "Must fit in $300 bytes" + + .res $13FF-*-2, 0 + .byte $48,$AD ; 72, 173 ??? +.endproc + + + .assert .sizeof(bbb) = $3FF, error, "Expected size is $3FF" + +;;; ------------------------------------------------------------ +;;; Installer +;;; ------------------------------------------------------------ + + .org $2402 + +.proc install_and_quit + jsr install + MLI_CALL QUIT, params + +.proc params +params: .byte 4 +type: .byte 0 +res1: .word 0 +res2: .byte 0 +res3: .addr 0 +.endproc +.endproc + +;;; ------------------------------------------------------------ + +.proc install + src := install_src + end := install_src + .sizeof(bbb) + dst := $D100 ; Install location in ProDOS (bank 2) + + src_ptr := $19 + dst_ptr := $1B + + sta ALTZPOFF + lda ROMIN + lda ROMIN + lda #>src + sta src_ptr+1 + lda #dst + sta dst_ptr+1 + lda #end + bne loop + lda src_ptr + cmp #