;open/read/write binary file in ProDOS filesystem ;copyright (c) Peter Ferrie 2013-16 !cpu 6502 !to "prorwts",plain *=$800 enable_floppy = 0 ;set to 1 to enable floppy drive support override_adr = 0 ;set to 1 to require an explicit load address enable_write = 0 ;set to 1 to enable write support ;file must exist already and its size cannot be altered ;writes occur in multiples of block size (256 bytes for floppy, 512 bytes for HDD) allow_multi = 1 ;set to 1 to allow multiple floppies allow_subdir = 0 ;set to 1 to allow opening subdirectories to access files might_exist = 0 ;set to 1 if file is not known to always exist already ;makes use of status to indicate success or failure allow_aux = 0 ;set to 1 to allow read/write directly to/from aux memory load_high = 0 ;load into banked RAM instead of main RAM lc_bank = 1 ;load into specified bank (1 or 2) if load_high=1 !if enable_floppy=1 { tmpsec = $3c reqsec = $3d } ;enable_floppy A1L = $3c A1H = $3d A2L = $3e A2H = $3f !if enable_write=1 { A3L = $40 A3H = $41 } ;enable_write !if enable_floppy=1 { curtrk = $40 } ;enable_floppy command = $42 ;ProDOS constant unit = $43 ;ProDOS constant adrlo = $44 ;ProDOS constant adrhi = $45 ;ProDOS constant bloklo = $46 ;ProDOS constant blokhi = $47 ;ProDOS constant secsize = $46 secsize1 = $47 secsize2 = $48 !if might_exist=1 { status = $f3 ;returns non-zero on error } !if allow_aux=1 { auxreq = $f4 ;set to 1 to read/write aux memory, else main memory is used } sizelo = $f5 ;must set if writing sizehi = $f6 ;must set if writing entries = $f7 ;total number of entries reqcmd = $f8 ;used if enable_write=1, 1=read, 2=write; if allow_multi=1, bit 7 selects drive ldrlo = $f9 ;used if override_adr=1 ldrhi = $fa ;used if override_adr=1 namlo = $fb namhi = $fc !if enable_floppy=1 { step = $fd ;state for stepper motor tmptrk = $fe ;temporary copy of current track phase = $ff ;current phase for seek !if enable_write=1 { !if load_high=1 { reloc = $d000 dirbuf = $d400 encbuf = $d600 } else { ;load_high reloc = $bc00 dirbuf = $ba00 encbuf = $b900 } ;load_high } else { ;enable_write !if load_high=1 { reloc = $d000 dirbuf = $d300 } else { ;load_high reloc = $bd00 dirbuf = $bb00 } ;load_high } ;enable_write } else { ;enable_floppy !if load_high=1 { reloc = $d000 dirbuf = $d200 } else { ;load_high reloc = $be00 dirbuf = $bc00 } ;load_high } ;enable_floppy init jsr $fe93 jsr $fe89 lda $bf30 sta x80_parms+1 sta unrunit+1 and #$70 pha !if enable_floppy=1 { ora #$80 sta unrseek+1 ora #8 sta unrdrvoff+1 tax inx stx unrdrvon+1 inx !if allow_multi=1 { stx unrdrvsel+1 } ;allow_multi inx inx stx unrread1+1 stx unrread2+1 stx unrread3+1 !if enable_write=1 { stx unrread4+1 stx unrread5+1 stx unrread6+1 stx unrread7+1 stx unrread8+1 inx stx unrlatch1+1 stx unrlatch2+1 stx unrlatch3+1 inx stx unrlatchin+1 inx stx unrlatchout+1 } ;enable_write } ;enable_floppy ldx #1 stx namlo inx stx namhi jsr $bf00 !byte $c7 !word c7_parms ldx $200 dex stx sizelo bmi plus05 readblock jsr $bf00 !byte $80 !word x80_parms sta A2L lda #<(readbuff+4) sta A1L lda #>(readbuff+4) sta A1H inextent ldy #0 lda (A1L), y pha and #$d0 ;watch for subdirectory entries cmp #$d0 bne plus01 lda (A1L), y and #$0f tax iny minus01 lda (A1L), y cmp (namlo), y beq ifoundname ;match failed, move to next directory in this block, if possible minus02 plus01 pla clc lda A1L adc #$27 sta A1L bcc plus02 ;there can be only one page crossed, so we can increment instead of adc inc A1H plus02 inc A2L lda A2L cmp #$0d bcc inextent ;read next directory block when we reach the end of this block ldy readbuff+2 ldx readbuff+3 bcs plus03 ifoundname iny dex bne minus01 lda (namlo), y cmp #'/' bne minus02 tya eor #$ff adc sizelo sta sizelo clc tya adc namlo sta namlo pla and #$20 bne plus04 ldy #$12 lda (A1L), y tax dey lda (A1L), y tay !if enable_floppy=1 { sty unrblocklo+1 stx unrblockhi+1 } ;enable_floppy sty unrhddblocklo+1 stx unrhddblockhi+1 plus03 sty x80_parms+4 stx x80_parms+5 plus04 lda sizelo bne readblock plus05 pla lsr lsr lsr lsr ora #$c0 sta slot+2 sta unrentry+2 !if enable_floppy=1 { ldx #>unrelocdsk ldy #unrelochdd ldy #(dirbuf+4) sta A1H nextent ldy #0 lda (A1L), y and #$f0 !if might_exist=1 { ;skip deleted entries without counting beq plus09 } ;might_exist !if allow_subdir=1 { ;subdirectory entries are seedlings ;but we need to distinguish between them later cmp #$d0 beq savetype } ;allow_subdir ;watch for seedling and saplings only cmp #$30 bcs plus08 ;remember type savetype !if allow_subdir=1 { asl asl } else { ;allow_subdir cmp #$20 } ;allow_subdir php ;match name lengths before attempting to match names lda (A1L), y and #$0f tax inx !byte $2c minus06 lda (A1L), y cmp (namlo), y beq foundname ;match failed, check if any directory entries remain plp plus08 !if might_exist=1 { inc A2H lda A2H cmp entries ;lock if entry not found bne plus09 inc status rts } ;might_exist ;move to next directory in this block, if possible plus09 clc lda A1L adc #$27 sta A1L bcc plus10 ;there can be only one page crossed, so we can increment instead of adc inc A1H plus10 inc A2L lda A2L cmp #$0d bcc nextent ;read next directory block when we reach the end of this block lda dirbuf+2 ldx dirbuf+3 jsr readdirsec beq firstent foundname iny dex bne minus06 !if enable_write=1 { ldy reqcmd dey php beq plus12 ;round requested size up to nearest sector ;and cache requested size if writing ldx sizehi beq plus11 lda sizelo beq plus12 plus11 inx plus12 } ;enable_write ;cache EOF (file size) ldy #$15 lda (A1L), y sta sizelo iny lda (A1L), y sta sizehi !if enable_write=1 { plp beq plus15 ;round file size up to nearest sector ;and check against requested size if writing tay beq plus13 lda sizelo beq plus14 ldy #0 plus13 sty sizelo inc sizehi ;set read size to min(length, requested size) plus14 cpx sizehi bcs plus15 stx sizehi plus15 } ;enable_write ;cache AUX_TYPE (load offset for binary files) !if override_adr=0 { !if allow_subdir=1 { pla tax } else { ;allow_subdir plp } ;allow_subdir ldy #$1f lda (A1L), y pha iny lda (A1L), y pha !if allow_subdir=1 { txa pha } ;allow_subdir } ;override_adr ;cache KEY_POINTER (loaded backwards) ldy #$12 lda (A1L), y tax sta dirbuf+256 dey lda (A1L), y sta dirbuf ldy #0 sty entries ;read index block in case of sapling !if allow_subdir=1 { plp bpl plus16 php jsr readdirsec plp } else { ;allow_subdir !if override_adr=1 { plp } ;override_adr bcc plus16 jsr readdirsec } ;allow_subdir ;restore load offset plus16 !if override_adr=1 { ldx ldrhi lda ldrlo } else { ;override_adr pla tax pla } ;override_adr !if enable_write=1 { ldy reqcmd } ;enable_write !if allow_subdir=1 { ;check file type and fake size and load address for subdirectories bcc plus17 ldy #2 sty sizehi !if enable_write=1 { dey } ;enable_write ldx #>dirbuf lda #dirbuf sty adrhi ;convert block number to track/sector seekread pha and #7 cmp #4 and #3 php asl plp rol sta reqsec txa lsr pla ror lsr lsr sta phase ;set read size to min(first size, $100) and then read address ldy #0 lda secsize2 bne plus19 ldy secsize1 plus19 sty secsize dec secsize2 jsr readadr ;if track does not match, then seek ldx curtrk cpx phase beq checksec jsr seek ;force sector mismatch lda #$ff ;match or read sector checksec jsr cmpsec ;return if less than one sector requested tya bne readret ;return if only one sector requested lda secsize1 cmp secsize2 beq readret sta secsize inc adrhi inc reqsec inc reqsec cmpsecrd jsr readadr cmpsec !if enable_write=1 { ldy command dey bne encsec } ;enable_write cmp reqsec bne cmpsecrd ;read sector data readdata jsr readd5aa eor #$ad ;zero A if match ;; bne * ;lock if read failure unrread1=unrelocdsk+(*-reloc) minus07 ldx $c0ec bpl minus07 eor nibtbl-$96, x sta bit2tbl-$aa, y iny bne minus07 unrread2=unrelocdsk+(*-reloc) minus08 ldx $c0ec bpl minus08 eor nibtbl-$96, x sta (adrlo), y ;the real address iny cpy secsize bne minus08 ldy #0 minus09 ldx #$a9 minus10 inx beq minus09 lda (adrlo), y lsr bit2tbl-$aa, x rol lsr bit2tbl-$aa, x rol sta (adrlo), y iny cpy secsize bne minus10 readret rts !if enable_write=1 { encsec iny minus11 ldx #$aa minus12 dey lda (adrlo), y lsr rol bit2tbl-$aa, x lsr rol bit2tbl-$aa, x sta encbuf, y lda bit2tbl-$aa, x and #$3f sta bit2tbl-$aa, x inx bne minus12 tya bne minus11 cmpsecwr jsr readadr cmp reqsec bne cmpsecwr ;skip tail #$DE #$AA #$EB some #$FFs ... ldy #$24 minus13 dey bpl minus13 ;write sector data tya ldx #$56 unrlatchout=unrelocdsk+(*-reloc) sta $c0ef unrread4=unrelocdsk+(*-reloc) ora $c0ec ldy #4 cmp $ea ;3 cycles cmp ($ea,x) ;6 cycles minus14 jsr writenib1 dey bne minus14 ldy #(prolog_e-prolog) cmp $ea ;3 cycles minus15 lda prolog-1, y jsr writenib3 dey bne minus15 tya !byte $c5 ;(1 cycle), turns into cmp $ea and 3 cycles minus16 nop ;2 cycles cmp $ea ;3 cycles eor bit2tbl-1, x tay lda xlattbl, y unrlatch1=unrelocdsk+(*-reloc) sta $c0ed unrread5=unrelocdsk+(*-reloc) lda $c0ec lda bit2tbl-1, x dex bne minus16 !byte $c5 ;(1 cycle), turns into cmp $ea and 3 cycles minus17 nop ;2 cycles cmp $ea ;3 cycles eor encbuf, x tay lda xlattbl, y unrlatch2=unrelocdsk+(*-reloc) sta $c0ed unrread6=unrelocdsk+(*-reloc) lda $c0ec lda encbuf, x inx bne minus17 tax lda xlattbl, x nop ;2 cycles nop ;2 cycles jsr writenib4 ldy #(epilog_e-epilog) cmp $ea ;3 cycles minus18 lda epilog-1, y jsr writenib3 dey bne minus18 unrlatchin=unrelocdsk+(*-reloc) lda $c0ee unrread7=unrelocdsk+(*-reloc) lda $c0ec rts writenib1 cmp ($ea,x) ;6 cycles writenib2 cmp ($ea,x) ;6 cycles writenib3 cmp $ea ;3 cycles writenib4 unrlatch3=unrelocdsk+(*-reloc) sta $c0ed unrread8=unrelocdsk+(*-reloc) ora $c0ec rts prolog !byte $ad, $aa, $d5 prolog_e epilog !byte $ff, $eb, $aa, $de epilog_e } ;enable_write !if enable_write=1 { !align 255,0 } ;enable_write bit2tbl = * nibtbl = bit2tbl+86 !if enable_write=1 { xlattbl = nibtbl+106 dataend = xlattbl+64 } else { ;enable_write dataend = nibtbl+106 } ;enable_write ;hack to error out when code is too large for current address !if reloc<$c000 { !if dataend>$c000 { !serious "code is too large" } } } ;enable_floppy } ;reloc unrelochdd !pseudopc reloc { ;read volume directory key block hddopendir lda #0 sta adrlo unrhddblocklo=unrelochdd+(*-reloc) lda #2 unrhddblockhi=unrelochdd+(*-reloc) ldx #0 jsr hddreaddirsec !if enable_floppy=1 { !if (*-hddopendir) < (readdir-opendir) { ;essential padding to match offset with floppy version !fill (readdir-opendir)-(*-hddopendir), $ea } } ;enable_floppy ;include volume directory header in count hddreaddir !if might_exist=1 { ldx dirbuf+37 inx stx entries } !if allow_subdir=1 { lda #0 } ;allow_subdir !if might_exist=1 { sta status sta A2H } ;might_exist hddfirstent sta A2L lda #<(dirbuf+4) sta A1L lda #>(dirbuf+4) sta A1H hddnextent ldy #0 lda (A1L), y and #$f0 !if might_exist=1 { ;skip deleted entries without counting beq plus26 } ;might_exist !if allow_subdir=1 { ;subdirectory entries are seedlings ;but we need to distinguish between them later cmp #$d0 beq hddsavetype } ;allow_subdir ;watch for seedling and saplings only cmp #$30 bcs plus25 ;remember type hddsavetype !if allow_subdir=1 { asl asl } else { ;allow_subdir cmp #$20 } ;allow_subdir php ;match name lengths before attempting to match names lda (A1L), y and #$0f tax inx !byte $2c minus27 lda (A1L), y cmp (namlo), y beq hddfoundname ;match failed, check if any directory entries remain plp plus25 !if might_exist=1 { inc A2H lda A2H cmp entries ;lock if entry not found bne plus26 inc status rts } ;move to next directory in this block, if possible plus26 clc lda A1L adc #$27 sta A1L bcc plus27 ;there can be only one page crossed, so we can increment instead of adc inc A1H plus27 inc A2L lda A2L cmp #$0d bcc hddnextent ;read next directory block when we reach the end of this block lda dirbuf+2 ldx dirbuf+3 jsr hddreaddirsec bcc hddfirstent hddfoundname iny dex bne minus27 !if enable_write=1 { ldy reqcmd dey php beq plus30 ;round requested size up to nearest block ;and cache requested size if writing lda sizehi tax lsr bcc plus28 inx plus28 cpx #2 bcc plus29 lda sizelo beq plus30 plus29 ldx #2 plus30 } ;enable_write ;cache EOF (file size) ldy #$15 lda (A1L), y sta sizelo iny lda (A1L), y sta sizehi !if enable_write=1 { plp beq plus34 ;round file size up to nearest block ;and check against requested size if writing tay lsr bcc plus31 iny plus31 cpy #2 bcc plus32 lda sizelo beq plus33 plus32 lda #0 sta sizelo ldy #2 plus33 sty sizehi ;set read size to min(length, requested size) cpx sizehi bcs plus34 stx sizehi plus34 } ;enable_write ;cache AUX_TYPE (load offset for binary files) !if override_adr=0 { !if allow_subdir=1 { pla tax } else { ;allow_subdir plp } ;allow_subdir ldy #$1f lda (A1L), y pha iny lda (A1L), y pha !if allow_subdir=1 { txa pha } ;allow_subdir } ;override_adr ;cache KEY_POINTER (loaded backwards) ldy #$12 lda (A1L), y tax sta dirbuf+256 dey lda (A1L), y sta dirbuf ldy #0 sty entries ;read index block in case of sapling !if allow_subdir=1 { plp bpl plus35 php jsr hddreaddirsec plp } else { ;allow_subdir !if override_adr=1 { plp } ;override_adr bcc plus35 jsr hddreaddirsec } ;allow_subdir ;restore load offset plus35 !if override_adr=1 { ldx ldrhi lda ldrlo } else { ;override_adr pla tax pla } ;override_adr !if allow_subdir=1 { ;check file type and fake size and load address for subdirectories bcc plus36 ldy #2 sty sizehi !if enable_write=1 { dey sty reqcmd } ;enable_write ldx #>dirbuf lda #dirbuf sta adrhi lda #0 sta adrlo plus37 php ;fetch data block and read it ldy entries inc entries lda dirbuf, y ldx dirbuf+256, y jsr hddseekread plp inc adrhi inc adrhi dec sizehi dec sizehi bne hddreadfile bcc plus38 lda sizelo bne hddreadfile !if allow_aux=1 { hddreaddone sta $c002 sta $c004 } ;allow_aux rts plus38 pla sta A1L pla sta A1H dec adrhi dec adrhi pla tay beq plus39 dey minus28 lda (adrlo), y sta (A1L), y iny bne minus28 inc A1H inc adrhi plus39 lda sizelo !if allow_aux=1 { beq hddreaddone } else { beq plus40 } ;allow_aux minus29 lda (adrlo), y sta (A1L), y iny cpy sizelo bne minus29 !if allow_aux=1 { beq hddreaddone } else { plus40 rts } ;allow_aux hddreaddirsec ldy #1 sty command ldy #>dirbuf sty adrhi hddseekread sta bloklo stx blokhi unrunit=unrelochdd+(*-reloc) lda #$d1 sta unit unrentry=unrelochdd+(*-reloc) jmp $d1d1 } readbuff !byte $D3,$C1,$CE,$A0,$C9,$CE,$C3,$AE