; Modified problock driver to be used as a virtual floppy disk
; The loader needs to setup $3000 to hold the two index blocks for the 
; image file and then pretends to be a 280 block floppy
;
; Not relocatable, hard coded to be at bank 4, at $3600 (256k required)
;
; 
;  By Robert Justice
;
;            .TITLE "Apple /// Virtual Floppy Image Driver"

            .segment "RAM"
            .setcpu "6502"

            .export  VDrvLAddr
            .export  VDrvSize
            .export  DInit
            .export  BlockioStb    ; entry point for blockio stub
            .export  StartStub
            .export  EndStub
            .export  StubLen



VDrvLAddr   = $3600               ; org for this code
VDrvSize    = EndCode - StartCode

IndxBlk1    = VDrvLAddr - $600          ; Index block 1
IndxBlk2    = VDrvLAddr - $400          ; Index block 2
Buffer      = VDrvLAddr - $200          ; Buffer for card block read/writes

; this needs to be in an unused bit of non banked memory
BlockioStb  = $1940          ; entry point for blockio stub 
BankSave    = BlockioStb + (Bank_Tmp -StartStub)
SectioStb   = BlockioStb + (SectIOT - StartStub)    ; entry point for sectio stub
StubLen     = EndStub - StartStub


IBBUFP      = $85
IBCMD       = $87
BUF         = $9b            ; monitor diskio routines buffer

;
; SOS Equates
;
ExtPG       = $1401          ; Driver extended bank address offset
EReg        = $FFDF          ; Environment register
E_IFR       = $FFED          ; VIA E Interrupt Flag Register
E_IER       = $FFEE          ; VIA E Interrupt Enable Register
Bank_Reg    = $FFEF          ; Bank register
CWrtOff     = $C0DA          ; Character loading off
CWrtOn      = $C0DB          ; Character loading on

XDNFERR     = $10            ; Device not found

;
; Extra zero page variables
;
ScreenBase  = $E5            ; 2 bytes lb,hb for save/restore screenholes
Pointer     = $E7            ; 2 byte pointer for signature check

D_TRACK     = $16d8          ; for softcard sectorio
D_SECTOR    = $16d9          ; these are the inputs
D_UNITNUM   = $16da
D_BUFL      = $16db
D_BUFH      = $16dc
D_BUFX      = $16dd
D_COMMAND   = $16de

OrigDriver  = $9cc5          ; Softcard floppy driver entry

ScanStart   = $04            ; Slot number to start scan from

            .SEGMENT    "RAM"
            .org    VDrvLAddr
StartCode   = *

            jmp     BlockIO             ; entry point for BLOCKIO - 3600
            jmp     SectIO              ; entry point for SectorIO - 3603
Slot:       .byte   0                   ; slot for card
EnvSave:    .byte   0                   ; save current environment state
OrigBlk:    .byte   0                   ; temp location for original block num
                                        ; only patch blks <256, so 8bits is enough

;------------------------------------
;
; Local storage locations
;
;------------------------------------

TmpScrH:    .res    $10, $00            ; Storage for screenholes, slot0 & current slot
TmpZero:    .res    $30, $00            ; Storage to save zero page locations
                                        ; we set the prodos call data in here and then swap
                                        ; it in before we call the interface
ProCommand  = TmpZero + $22             ; Command        - $42
ProUnit     = TmpZero + $23             ; Unit number    - $43
ProBuf      = TmpZero + $24             ; buffer pointer - $44
ProBlock    = TmpZero + $26             ; Block number   - $46

ProBufOff   = $44                       ; buffer pointer - $44

Signature:  .byte $FF, $20, $FF, $00    ; Disk card signature for disk controller
            .byte $FF, $03

DInit:      lda     EReg                ; save current environment so we can
            sta     EnvSave             ;  restore on exit
            ora     #$c0                ; enable Cxxx I/O and set 1MHz, grappler+ softsp needs this
            sta     EReg
            lda     #ScanStart          ; load starting scan slot
            ora     #$C0                ; Form a $Cs00 address, where s = slot #
            sta     Pointer+1
            lda     #$00                
            sta     Pointer
            sta     Pointer+ExtPG       ; zero out the xbyte

CheckNext:  ldy     #$05                ; We check all 3 sig bytes, starting from last
@1:         lda     (Pointer),y
            cmp     Signature,Y
            bne     NoMatch             ; No device if bytes don't match
            dey
            dey
            bpl     @1
            
            ldy     #$ff                ; $CxFF - check last byte
            lda     (Pointer),y
            cmp     #$00
            beq     NoMatch             ; if $00, is a Disk II 16 sector device, error
            cmp     #$ff
            bne     Match               ; if its not $ff (Disk II 13 sector device)
                                        ; Then we found an intelligent disk controller :-)
            
NoMatch:    dec     Pointer+1           ; try next slot
            lda     Pointer+1
            and     #$07
            bne     CheckNext           ; Check next slot
            beq     NoDevice            ; Otherwise we did not find one :-(     
            
Match:      sta     ProDrvAdr+1         ; Set card driver entry low byte
            lda     Pointer+1
            sta     ProDrvAdr+2         ; Set card driver entry high byte
            and     #$07
            sta     Slot
            lda     EnvSave
            sta     EReg
            lda     #0
            rts

NoDevice:   lda     EnvSave
            sta     EReg
            lda     #XDNFERR            ; Device not found
            rts

;
; jsr to card firmware driver
; We update the address based on the slot and firmware CxFF byte
;
ProDriver:  sei                         ; disable interrupts while changing things
            jsr     SaveMem             ; save and swap in card screen hole & zeropage
            jsr     GoSlow
ProDrvAdr:  jsr     $0000               ; call device entry
            sei                         ; Keep interrupts off incase card firmware reenabled
            jsr     GoFast
            jsr     RestMem             ; save and swap out card screen hole & zeropage
            lda     #$18                ; Clear CB1 & CB2 flags - VBL
            sta     E_IFR               ; this seems more for mame, its a little different
            cli                         ; enable interrupts again
            rts

;
; Blockio routine. This replicates the rom routine behaviour
; x = block number msb
; a = block number lsb
; IBCMD = command, 1=read , write?
; IBBUFP = dest pointer
;
; For the Softcard ///, the blockio routine is only used to load in
; the boot block 0 and then the next 24 blocks with the 6502 IO routines and CPM
;
; We'll patch the two blocks to redirect them to our new routines here
; as we read them.
;
; Block 0 - offset $df -> 00 13  (new blockio)
; Block 3 - offset $1b4 -> 16 13 (new sectio) 
;

BlockIO:    cpx     #0                  ; check lk num msb
            beq     Low                 ; block is <256
            tax
            lda     IndxBlk2,X
            sta     ProBlock
            lda     IndxBlk2+256,X
            sta     ProBlock+1
            jmp     HDone

Low:        sta     OrigBlk             ; store for patch check
            tax                         ; map the block number
            lda     IndxBlk1,X
            sta     ProBlock            ; and store for prodos call
            lda     IndxBlk1+256,X
            sta     ProBlock+1

HDone:      lda     EReg                ; save current environment so we can
            sta     EnvSave             ;  restore on exit
            lda     IBCMD
            sta     ProCommand
            lda     IBBUFP
            sta     BUF
            lda     IBBUFP+1
            sta     BUF+1
            jsr     SetProUnit
            jsr     SetProBuf
            lda     EReg                ; enable I/O
            ora     #$40
            sta     EReg
            jsr     ProDriver

            jsr     CheckPatch          ; check if we need to patch it

            ldy     #0                  ; copy the buffer over
looppt1:    lda     Buffer,y            ; using extended addressing
            sta     (BUF),y             ;1940.50.60
            iny
            bne     looppt1

            inc     BUF+1
looppt2:    lda     Buffer+256,y
            sta     (BUF),y
            iny
            bne     looppt2
            
            dec     BUF+1

            lda     EnvSave
            sta     EReg

            lda     #0                 ; dummy no error
            clc

            rts

; check and patch the block to redirect here
; and also repatch the blockio and sectio stubs
; as these are located in memory that get loaded from
; the floppy
CheckPatch: lda     OrigBlk
            bne     nxtchk
            ldy     #$df               ; offset for blockio jsr address in boot block
            lda     #<BlockioStb
            sta     Buffer,Y
            iny
            lda     #>BlockioStb
            sta     Buffer,Y
            bne     donep

nxtchk:     cmp     #3
            bne     donep
            ldy     #$b4               ; offset for sectio jsr address in block 3 (1b4)
            lda     #<SectioStb
            sta     Buffer+256,y
            iny
            lda     #>SectioStb
            sta     Buffer+256,y

            ldy     #0                 ; also in blk3, we patch in the blockio & sectio stub
@p1:        lda     StartStub,y        ; as these would get overwritten with this blk read
            sta     Buffer+256+$40,y
            iny
            cpy     #StubLen
            bne     @p1

donep:      rts

;
; Sectio routine. This replicates the softcard floppy sector routine
; 256 byte sectors are mapped to the appropriate blocks
;
; Inputs
; D_TRACK         .eq     $16d8
; D_SECTOR        .eq     $16d9
; D_UNITNUM       .eq     $16da
; D_BUFL          .eq     $16db
; D_BUFH          .eq     $16dc
; D_BUFX          .eq     $16dd
; D_COMMAND       .eq     $16de
;
;
SectMap:    .byte   0,4,0,4,1,5,1,5,2,6,2,6,3,7,3,7 ;phys to blk
SectHalf:   .byte   0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1 ;first 256 or second 256 in block


SectIO:     lda     EReg                ; save current environment reg so we can
            sta     EnvSave             ;  restore on exit
            
            lda     D_TRACK             ; get track number to convert to block num
            asl                         ; multiply x 8
            asl
            asl
            bcc     Lt256               ; blknum < 256
            ldx     D_SECTOR            ; use sector number as index into map
            clc
            adc     SectMap,x
            tax                         ; then map the block number to image file blocks
            lda     IndxBlk2,X
            sta     ProBlock            ; and store for prodos call
            lda     IndxBlk2+256,X
            sta     ProBlock+1
            jmp     HDone2

Lt256:      ldx     D_SECTOR            ; use sector number as index into map
            adc     SectMap,x
            tax                         ; then map the block number to image file blocks
            lda     IndxBlk1,X
            sta     ProBlock            ; and store for prodos call
            lda     IndxBlk1+256,X
            sta     ProBlock+1

HDone2:     lda     #1                  ; read the block regardless of write or read
            sta     ProCommand          
            jsr     SetProUnit
            jsr     SetProBuf
            lda     EReg                ; enable Cxxx I/O
            ora     #$40
            sta     EReg
            jsr     ProDriver           ; get block
            bcs     DskError

            lda     D_BUFL              ; dest pointer lsb
            sta     BUF                 ; rom diskio buf lsb
            lda     D_BUFH              ; dest pointer msb
            sta     BUF+1               ; rom diskio buf msb
            lda     D_BUFX              ; dest pointer xbyte
            sta     $149c               ; rom diskio buf xbyte

            lda     D_COMMAND           ; now check if its a read/write 1=read, 0=write
            lsr     a
            bcc     SectWrite           ; yes

;fall through for read
            ldy     #0
            ldx     D_SECTOR            ; use sector number as index into map
            lda     SectHalf,x
            beq     firsthr

secondhr:   lda     Buffer+256,y        ; copy half block buffer into sector buffer
            sta     (BUF),Y
            iny
            bne     secondhr
            beq     done2

firsthr:    lda     Buffer,y            ; copy half block buffer into sector buffer
            sta     (BUF),Y
            iny
            bne     firsthr
            beq     done2

SectWrite:  ldy     #0
            ldx     D_SECTOR            ; use sector number as index into map
            lda     SectHalf,x
            beq     firsthw

secondhw:   lda     (BUF),Y             ; copy sector into block buffer
            sta     Buffer+256,y
            iny
            bne     secondhw
            beq     cont

firsthw:    lda     (BUF),Y             ; copy sector into block buffer
            sta     Buffer,y
            iny
            bne     firsthw

cont:       lda     #2                  ; write
            sta     ProCommand
            jsr     ProDriver           ; write block buffer back
            bcs     DskError

done2:      lda     EnvSave
            sta     EReg

            lda     #0                  ; set no error
            clc

            rts

DskError:   lda    #$27                 ; I/O error?
            sec
            rts


;
; Throttle back to 1 MHz
;
GoSlow:     pha
            php
            lda     EReg
            ora     #$80
            sta     EReg
            plp
            pla
            rts

;
; Throttle up to 2 MHz
;
GoFast:     pha
            php
            lda     EReg
            and     #$7F
            sta     EReg
            plp
            pla
            rts

;
; Save current screenholes and restore peripheral card values (current slot + slot0)
; and save zeropage $42-$47
;
SaveMem:    lda     E_IER
            and     #$18                ; See if either CB1 or CB2 interrupts were enabled
            beq     SkipVbl             ; No, they were not, skip the wait for vbl
            
                                        ; now wait for 2xVBL before we muck with the screenholes
            lda     #$18                ; Clear CB2 flag - VBL
            sta     E_IFR
VWait:      bit     E_IFR               ; Wait for vertical retrace
            beq     VWait
                                        ; wait for another one to ensure font data is loaded
            lda     #$18                ; Clear CB2 flag - VBL
            sta     E_IFR
VWait2:     bit     E_IFR               ; Wait for vertical retrace
            beq     VWait2

SkipVbl:    bit     CWrtOff             ; disable font loading

            jsr     SwapScrH            ; Swap in the screenhole data with ours
            jsr     SwapZero            ; swap zeropage
            rts

;
; Save Card screen holes and restore original values
; and restore zeropage $42-$47
;
RestMem:    pha
            php                         ; keep carry error indication
            jsr     SwapZero            ; swap zeropage
            jsr     SwapScrH            ; Swap back the original screenhole data            
            plp
            pla
            rts

;            
; Swap screenholes with driver values 
; we do Slot 0 and current slot values
; This nice code is from Peter Ferrie, thanks 
;
SwapScrH:   lda     #$07                ; Init ZP Screenbase, X and A
            sta     ScreenBase+1
            lda     #$00
            sta     ScreenBase
            sta     ScreenBase+ExtPG
            ldx     #$0F
            lda     #$F8
@loop:
            tay                         ; A holds screen page index at loop entry
            lda     (ScreenBase),Y   
            pha                         ; Save current screen hole byte on stack.
            lda     TmpScrH,X
            sta     (ScreenBase),Y      ; Restore screen hole byte from array 
            pla
            sta     TmpScrH,X           ; Copy saved screen hole byte into array.
            dex
            bmi     done                ; Exit when array is full (all 16 bytes are copied, X<0).
            txa                         ; TXA/LSR tests whether array index is odd or even
            lsr                         ; and sets carry accordingly (1 = odd).
            tya                         ; Bring screen index into A for manipulation
            eor     Slot                ; Cycle page index between $x8 and $x8+n as long as N in 1..7
            bcc     @loop               ; Take branch every other loop, using array index odd/even 
                                        ;  (carry still valid from TXA/LSR)
            eor     #$80                ; Cycle page index between $F8 and $78
            bpl     @loop               ; If flipping from $78->$F8 (now negative), continue
            dec     ScreenBase+1        ; Go to next page counting down ($07->$06, etc.)
            bne     @loop               ; Always -- equiv. to BRA or JMP.  Never reaches 0.
done:       rts

;
; Swap zeropage with driver values
;
SwapZero:   ldx     #$2f
@loop2:     lda     $20,X   
            pha                         ; Save current zeropage byte on stack.
            lda     TmpZero,X
            sta     $20,X               ; Restore zeropage byte from temp storage 
            pla
            sta     TmpZero,X           ; Copy saved zeropage byte into temp storage.
            dex
            bpl     @loop2
            rts

;
; Set Prodos unit number, assume unit0 only
;
SetProUnit: lda     Slot                ; create the slot/unit byte
            asl                         ; 7 6 5 4 3 2 1 0
            asl                         ; D S S S 0 0 0 0
            asl
            asl
            sta     ProUnit
            rts

;
; Set Prodos buffer pointer
;
SetProBuf:  lda     #<Buffer
            sta     ProBuf
            lda     #>Buffer
            sta     ProBuf+1
            lda     #0                  ; turn off extended addressing
            sta     ProBufOff+ExtPG
            rts

;
;Blockio stub
; This hides in non banked memory
;
StartStub:  ldy     Bank_Reg           ;save current bank
            sty     BankSave
            ldy     #4                 ;set virtual disk driver bank
            sty     Bank_Reg
            jsr     BlockIO            ;go read/write the block
            ldy     BankSave           ;restore bank
            sty     Bank_Reg
            rts
Bank_Tmp:   .byte   0                  ;temp holder for calling bank
;
;Sector IO stub
; This hides in non banked memory
; we only catch unit 0 (D1) as virtual drive
; other wise we pass to the orignal floppy driver
;
SectIOT:    lda     D_UNITNUM
            beq     drive1
            jmp     OrigDriver         ;continue on with original driver

drive1:     ldy     Bank_Reg           ;save current bank
            sty     BankSave
            ldy     #4                 ;set virtual disk driver bank
            sty     Bank_Reg
            jsr     SectIO             ;go read/write the sector
            ldy     BankSave           ;restore bank
            sty     Bank_Reg
            rts
EndStub     =*




EndCode     = *

            .END