prodos-drivers/selectors/buhbye.system.s

702 lines
19 KiB
ArmAsm
Raw Normal View History

;;; Enhancements to the 80-column ProDOS 8 Selector ("BYE") found in
;;; ProDOS 1.9 and 2.0.x.
;;;
2017-12-12 18:52:01 +00:00
;;; Modifications by Joshua Bell inexorabletash@gmail.com
;;; * alpha key advances to next matching filename
;;; * replaced directory enumeration (smaller, per PDTRM)
2019-10-02 05:08:21 +00:00
;;; * installs, then chains to next .SYSTEM file
2017-12-12 04:15:51 +00:00
.setcpu "65C02"
2019-10-02 05:08:21 +00:00
.linecont +
.feature string_escapes
2017-12-12 04:15:51 +00:00
.include "apple2.inc"
2019-10-02 05:08:21 +00:00
.include "apple2.mac"
2019-10-04 04:23:00 +00:00
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
2019-10-05 01:10:01 +00:00
.include "../inc/ascii.inc"
2017-12-12 04:15:51 +00:00
2019-10-02 05:08:21 +00:00
;;; ************************************************************
.include "../inc/driver_preamble.inc"
2019-10-02 05:08:21 +00:00
;;; ************************************************************
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
;;; 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.
;;; ------------------------------------------------------------
2017-12-13 04:56:51 +00:00
;;; Installer
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
max_size = $300
2017-12-13 04:56:51 +00:00
2019-10-02 05:08:21 +00:00
.proc maybe_install_driver
2017-12-13 04:56:51 +00:00
src := install_src
end := install_src + install_size
dst := $D100 ; Install location in ProDOS (bank 2)
src_ptr := $19
dst_ptr := $1B
2019-10-05 01:10:01 +00:00
sta ALTZPOFF ; TODO: Necessary?
2017-12-13 04:56:51 +00:00
lda ROMIN ; write bank 2
lda ROMIN
lda #<src ; src_ptr = src
sta src_ptr
lda #>src
sta src_ptr+1
lda #<dst ; dst_ptr = dst
sta dst_ptr
lda #>dst
sta dst_ptr+1
loop: lda (src_ptr) ; *src_ptr = *dst_ptr
sta (dst_ptr)
inc src_ptr ; src_ptr++
bne :+
inc src_ptr+1
: inc dst_ptr ; dst_ptr++
bne :+
inc dst_ptr+1
: lda src_ptr+1 ; src_ptr == end ?
cmp #>end
bne loop
lda src_ptr
cmp #<end
bne loop
2019-10-05 01:10:01 +00:00
sta ALTZPOFF ; TODO: Necessary?
2017-12-13 04:56:51 +00:00
sta ROMINWB1
sta ROMINWB1
2019-10-02 05:08:21 +00:00
rts
2017-12-13 04:56:51 +00:00
.endproc
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
;;; Selector
;;; ------------------------------------------------------------
2019-10-02 05:08:21 +00:00
install_src := *
pushorg $1000
2017-12-12 22:54:27 +00:00
.proc selector
2017-12-12 04:15:51 +00:00
2017-12-13 04:56:51 +00:00
prefix := $280 ; length-prefixed
2017-12-12 04:15:51 +00:00
filenames := $1400 ; each is length + 15 bytes
read_buffer := $2000 ; Also, start location for launched SYS files
2017-12-12 18:58:22 +00:00
;; Device/Prefix enumeration
cur_device_num := $65 ; device number to try
2017-12-12 18:58:22 +00:00
prefix_depth := $6B ; 0 = root
;; Directory enumeration
2017-12-12 12:45:28 +00:00
entry_pointer := $60 ; 2 bytes
block_entries := $62
active_entries := $63 ; 2 bytes
2017-12-12 04:15:51 +00:00
entry_length := $6E
entries_per_block := $6F
file_count := $70 ; 2 bytes
2017-12-12 18:58:22 +00:00
;; Found entries
current_entry := $67 ; index of current entry
num_entries := $68 ; length of |filenames| (max 128)
curr_len := $69 ; length of current entry name
curr_ptr := $6C ; address of current entry name (in |filenames|)
2017-12-12 04:15:51 +00:00
types_table := $74 ; high bit clear = dir, set = sys
2017-12-12 18:58:22 +00:00
;; Entry display
page_start := $73 ; index of first entry shown on screen
row_count := $6A ; number of rows in this page
2017-12-12 04:15:51 +00:00
top_row := 2 ; first row used on screen
bottom_row := 21 ; last row used on screen
2017-12-12 18:58:22 +00:00
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
cld ; ProDOS protocol for QUIT routine
lda ROMIN2 ; Page in ROM for reads, writes ignored
2017-12-12 04:15:51 +00:00
bit KBDSTRB ; Clear keyboard strobe
2017-12-12 04:15:51 +00:00
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
ldx DEVCNT
: lda DEVLST,x
and #%11110000
cmp DEVNUM
beq check_device
dex
bra :-
2017-12-12 04:15:51 +00:00
next_device:
ldx cur_device_num
dex
bpl :+
2017-12-12 04:15:51 +00:00
ldx DEVCNT
: lda DEVLST,x
2017-12-12 04:15:51 +00:00
check_device:
stx cur_device_num
2017-12-12 04:15:51 +00:00
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 #'/'
2017-12-12 12:45:28 +00:00
sta prefix+1 ; ensure prefix is at least '/'
sta prefix,x ; and ends with '/'
stz prefix+1,x ; and is null terminated
stz num_entries
2017-12-12 18:52:01 +00:00
;;; Enumerate directory
;;; Algorithm from ProDOS Technical Reference Manual B.2.5
2017-12-12 04:15:51 +00:00
2017-12-12 18:52:01 +00:00
;; Open the directory
2017-12-12 04:15:51 +00:00
jsr do_open
bcc :+
;; Open failed
2017-12-12 12:45:28 +00:00
fail: lda prefix_depth ; root?
2017-12-12 04:15:51 +00:00
beq next_device
2017-12-12 12:45:28 +00:00
jsr pop_prefix ; and go up a level
bra resize_prefix_and_open
2017-12-12 04:15:51 +00:00
;; Open succeeded
: inc prefix_depth
2017-12-12 12:45:28 +00:00
2017-12-12 18:52:01 +00:00
;; Read a block (512 bytes) into buffer
2017-12-12 12:45:28 +00:00
stz read_params_request
lda #2
sta read_params_request+1
2017-12-12 04:15:51 +00:00
jsr do_read
2017-12-12 12:45:28 +00:00
bcs fail
2017-12-12 04:15:51 +00:00
2017-12-12 12:45:28 +00:00
;; Store entry_length (byte), entries_per_block (byte), file_count (word)
2017-12-12 04:15:51 +00:00
ldx #3
2019-10-02 05:08:21 +00:00
: lda read_buffer + SubdirectoryHeader::entry_length,x
2017-12-12 04:15:51 +00:00
sta entry_length,x
dex
bpl :-
2017-12-12 18:52:01 +00:00
;; Any entries?
lda file_count
ora file_count+1
beq close_dir
;; Skip header entry
2017-12-12 12:45:28 +00:00
clc
2017-12-12 18:52:01 +00:00
lda #<(read_buffer+4) ; 4 bytes for prev/next pointers
2017-12-12 12:45:28 +00:00
adc entry_length
sta entry_pointer
lda #>(read_buffer+4)
adc #0 ; TODO: Can skip this if entry_length << 256
sta entry_pointer+1
2017-12-12 04:15:51 +00:00
2017-12-12 18:52:01 +00:00
;; Prepare to process entry two (first "entry" is header)
2017-12-12 12:45:28 +00:00
lda #2
sta block_entries
2017-12-12 04:15:51 +00:00
2017-12-12 12:45:28 +00:00
while_loop:
2017-12-12 18:52:01 +00:00
;; Check if entry is active
2017-12-12 12:45:28 +00:00
lda (entry_pointer)
beq done_entry
2017-12-12 04:15:51 +00:00
;; Check file type
2017-12-12 12:45:28 +00:00
ldy #FileEntry::file_type
lda (entry_pointer),y
2019-10-02 05:08:21 +00:00
cmp #FT_DIRECTORY
2017-12-13 04:40:31 +00:00
beq store_entry
2019-10-02 05:08:21 +00:00
cmp #FT_SYSTEM
2017-12-12 18:52:01 +00:00
bne done_active_entry
2017-12-12 04:15:51 +00:00
2017-12-13 04:40:31 +00:00
store_entry:
2017-12-12 04:15:51 +00:00
;; Store type
2017-12-12 12:45:28 +00:00
ldx num_entries
2017-12-12 04:15:51 +00:00
sta types_table,x
2017-12-12 18:52:01 +00:00
;; Copy name into |filenames|
jsr update_curr_ptr ; current entry in X
ldy #15 ; max name length (length byte copied too)
2017-12-12 12:45:28 +00:00
: lda (entry_pointer),y
2017-12-12 04:15:51 +00:00
sta (curr_ptr),y
dey
bpl :-
; storage_type/name_length in A
2017-12-12 18:52:01 +00:00
and #%00001111 ; mask off name_length (remove storage_type)
sta (curr_ptr) ; store length
2017-12-12 04:15:51 +00:00
inc num_entries
2017-12-12 18:52:01 +00:00
done_active_entry:
2017-12-12 12:45:28 +00:00
dec file_count
bpl :+
dec file_count+1
:
done_entry:
2017-12-12 18:52:01 +00:00
;; Seen all active entries?
2017-12-12 12:45:28 +00:00
lda file_count
ora file_count+1
beq close_dir
2017-12-12 18:52:01 +00:00
;; Seen all entries in this block?
2017-12-12 12:45:28 +00:00
lda block_entries
cmp entries_per_block
bne next_in_block
2017-12-12 18:52:01 +00:00
;; Grab next block
next_block:
jsr do_read ; read another block
2017-12-12 12:45:28 +00:00
bcs fail
2017-12-12 18:52:01 +00:00
lda #1 ; first entry in non-key block
2017-12-12 12:45:28 +00:00
sta block_entries
2017-12-12 18:52:01 +00:00
lda #<(read_buffer+4) ; 4 bytes for prev/next pointers
2017-12-12 12:45:28 +00:00
sta entry_pointer
lda #>(read_buffer+4)
sta entry_pointer+1
bra end_while
2017-12-12 18:52:01 +00:00
;; Next entry in current block
2017-12-12 12:45:28 +00:00
next_in_block:
clc
lda entry_pointer
adc entry_length
sta entry_pointer
bcc :+
inc entry_pointer+1
:
2017-12-12 12:45:28 +00:00
inc block_entries
end_while:
;; Check to see if we have room
bit num_entries ; max is 128
bpl while_loop
close_dir:
2017-12-12 04:15:51 +00:00
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 COL80HPOS
ldy #(help_string - text_resources)
2017-12-12 04:15:51 +00:00
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
2017-12-13 04:40:31 +00:00
beq selection_loop_keyboard_loop ; no entries (empty directory)
2017-12-12 04:15:51 +00:00
2017-12-13 04:40:31 +00:00
;; Draw entries
2017-12-12 04:15:51 +00:00
cmp #bottom_row ; more entries than fit?
bcc :+
lda #(bottom_row - top_row + 1)
: sta row_count
2017-12-12 18:58:22 +00:00
lda #top_row
2017-12-12 04:15:51 +00:00
sta WNDTOP
sta WNDLFT
2017-12-12 18:58:22 +00:00
lda #bottom_row+1
2017-12-12 04:15:51 +00:00
sta WNDWDTH
sta WNDBTM
loop: jsr draw_current_line
inc current_entry
dec row_count
bne loop
stz current_entry
2017-12-13 04:40:31 +00:00
beq selection_loop
2017-12-12 04:15:51 +00:00
.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 on_down
jsr down_common
2017-12-13 04:40:31 +00:00
bra selection_loop
2017-12-12 04:15:51 +00:00
.endproc
;;; ------------------------------------------------------------
.proc on_up
2017-12-13 04:40:31 +00:00
ldx current_entry ; first one?
beq selection_loop
dec current_entry ; go to previous
2017-12-12 04:15:51 +00:00
lda CV
cmp #top_row ; at the top?
2017-12-13 04:40:31 +00:00
bne selection_loop
2017-12-12 04:15:51 +00:00
dec page_start ; yes, adjust page and
lda #ASCII_SYN ; scroll screen up
jsr COUT
2017-12-12 04:15:51 +00:00
;; fall through
.endproc
;;; ------------------------------------------------------------
2017-12-13 04:40:31 +00:00
.proc selection_loop
2017-12-12 04:15:51 +00:00
jsr SETINV
jsr draw_current_line
2017-12-13 04:40:31 +00:00
keyboard_loop:
2017-12-12 04:15:51 +00:00
lda KBD
bpl keyboard_loop
sta KBDSTRB
jsr SETNORM
cmp #HI(ASCII_TAB)
beq next_drive
cmp #HI(ASCII_ESCAPE)
beq on_escape
2017-12-12 04:15:51 +00:00
ldx num_entries
beq keyboard_loop ; if empty, no navigation
pha
jsr draw_current_line
pla
2017-12-12 04:15:51 +00:00
cmp #HI(ASCII_CR)
beq on_return
cmp #HI(ASCII_DOWN)
beq on_down
cmp #HI(ASCII_UP)
beq on_up
;; fall through
.endproc
2017-12-13 04:40:31 +00:00
selection_loop_keyboard_loop := selection_loop::keyboard_loop
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
2017-12-12 04:15:51 +00:00
.proc on_alpha
loop: jsr down_common
jsr draw_current_line
lda KBD
and #$5F ; make ASCII and uppercase
ldy #1
cmp (curr_ptr),y ; key = first char ?
2017-12-13 04:40:31 +00:00
beq selection_loop
bra loop
2017-12-12 04:15:51 +00:00
.endproc
2017-12-12 13:17:39 +00:00
;;; ------------------------------------------------------------
.proc on_escape
jsr pop_prefix ; leaves length in X
dec prefix_depth
bra resize_prefix_and_open_jmp
.endproc
;;; ------------------------------------------------------------
2017-12-12 13:00:29 +00:00
.proc down_common
lda current_entry
inc a
cmp num_entries ; past the limit?
bcc :+
pla ; yes - abort subroutine
pla
2017-12-13 04:40:31 +00:00
bra selection_loop
2017-12-12 13:00:29 +00:00
: 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
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
next_drive: ; relay for branches
2017-12-12 04:15:51 +00:00
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
lda (curr_ptr)
2017-12-12 04:15:51 +00:00
sta curr_len
ldy #0 ; needed by caller
;; fall through
2017-12-12 04:15:51 +00:00
.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 - text_resources) ; Draw folder glyphs
2017-12-12 04:15:51 +00:00
jsr cout_string
pla
sta INVFLG
;; Work around MouseText deactivation bug on Franklin ACE 2X00/500
;; The Apple IIe/IIc/IIgs and the Franklin ACE 2X00/500 all use the
;; screen hole address $4FB to store a video firmware operating
;; mode byte, e.g. whether or not MouseText output is active. For
;; the Apple IIe and IIgs, see the Apple IIe Technical Reference
;; Manual, Appendix J Monitor Firmware Listing for the definition.
;; (Bit 0 is set when MouseText is inactive and clear when active.)
;; The Apple IIc also uses bit 0 the same way, but bits 3 and 7 are
;; also normally set. The Franklin Ace 2X00 and 500 set bit 6
;; when MouseText mode is activated by printing $1B, but fails to
;; clear it when printing $18, so MouseText remains active. This
;; seems to be a bug in the firmware. Work around it by detecting
;; the unique byte signature left behind and resetting it.
lda $4FB ; $4FB = $30 normally (V flag clear)
cmp #$40 ; $4FB = $70 if MT is active (V flag set)
bne :+ ; $4FB = $40 if you try to disable MT!
lda #$30 ; Bug in firmware? We're not sure yet.
sta $4FB ; Any way, set it manually.
:
2017-12-12 04:15:51 +00:00
;; 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
rts
.endproc
.proc do_read
MLI_CALL READ, read_params
rts
.endproc
;;; ------------------------------------------------------------
text_resources := *
2017-12-12 04:15:51 +00:00
.proc help_string
2019-10-02 05:08:21 +00:00
scrcode "RETURN: Select | TAB: Chg Vol | ESC: Back"
.byte 0
2017-12-12 04:15:51 +00:00
.endproc
;; Mousetext sequence: Enable, folder left, folder right, disable
.proc folder_string
.byte $0F,$1B,$D8,$D9,$18,$0E
.byte 0 ; null terminated
.endproc
;;; ------------------------------------------------------------
2019-10-02 05:08:21 +00:00
DEFINE_OPEN_PARAMS open_params, prefix, $1C00
2017-12-12 04:15:51 +00:00
open_params_ref_num := open_params::ref_num
2019-10-02 05:08:21 +00:00
DEFINE_CLOSE_PARAMS close_params
2017-12-12 04:15:51 +00:00
2019-10-02 05:08:21 +00:00
DEFINE_ON_LINE_PARAMS on_line_params, $60, prefix+1
on_line_params_unit := on_line_params::unit_num
2017-12-12 04:15:51 +00:00
2019-10-02 05:08:21 +00:00
DEFINE_SET_PREFIX_PARAMS set_prefix_params, prefix
2017-12-12 04:15:51 +00:00
2019-10-02 05:08:21 +00:00
DEFINE_READ_PARAMS read_params, read_buffer, 0
2017-12-12 04:15:51 +00:00
read_params_ref_num := read_params::ref_num
2019-10-02 05:08:21 +00:00
read_params_request := read_params::request_count
2017-12-12 04:15:51 +00:00
;;; ------------------------------------------------------------
.endproc
.assert .sizeof(selector) <= max_size, error, .sprintf("Must fit in $300 bytes, is: $%x", .sizeof(selector))
install_size = .sizeof(selector)
2019-10-02 05:08:21 +00:00
poporg
;;; ************************************************************
.include "../inc/driver_postamble.inc"
2019-10-02 05:08:21 +00:00
;;; ************************************************************