OSBYTE 140, 141, 143; OSARGS entry; OSFILE entry; OSFIND entry; OSBGET entry; OSBPUT entry; File system vector table; FSC entry point; Setup tape options; Load and save file; Copy filename; *RUN; *CAT; *OPT; Search for block; Open a file; Save a block to tape - 1107 bytes (6.7%)


§1. The tape (and ROM) block format.

 The cassette filing system uses a variant of the 1976 Kansas City standard.
 See https://en.wikipedia.org/wiki/Kansas_City_standard

 Tape signals are a sequence of binary tones, either 2400Hz (representing a set bit) or
 1200Hz (representing a clear bit). These tones can be recorded and read at a speed of
 either 1200 baud or 300 baud.

 Files are stored on tape as a sequence of 'blocks', each holding (up to) 256 bytes of
 program data. The first block is written with a five second carrier tone (2400 Hz) at the
 start. Subsequent blocks start with a shorter carrier tone. Each block continues as
 follows:

 $2A               - synchronisation byte (%00101010 alternating ones and zeroes)
                   - (see .fsSynchronisationByte)
 <1 to 10 bytes>   - filename
 $00               - filename terminator
 <4 bytes>         - load address
 <4 bytes>         - execution address
 <2 bytes>         - block number
 <2 bytes>         - block length
 <1 byte>          - block flag (bit 7 set for the final block; bit 0 means *RUN only)
 <4 bytes>         - TAPE: four $00 bytes (unused)
                   - ROMFS: address of end byte of file
 <2 bytes>         - CRC on header (all bytes before here except the synchronisation byte)
 <0 to 256 bytes>  - data
 <2 bytes>         - CRC on data (all data bytes)

 The ROM filing system uses a similar format of block, but with shortcuts to save ROM
 space. The first block is as above. Subsequent blocks just have a single byte header of
 $23. The end of all the ROM's data is marked with a single byte $2B.

 For an example of what the cassette sounds like, see http://playuef.8bitkick.cc

§2. OSBYTE 140 - Select TAPE file system, and OSBYTE 141 - Select ROM file system.

 On Entry:
       A = 140 to select TAPE filing system
       A = 141 to select ROM filing system
.osbyte140EntryPoint = $f135
.osbyte141EntryPoint = $f135
    EOR #140                                            A=0 for *TAPE; A=1 for *ROM
.setTapeOrROMFS = $f137
    ASL                                                 double it
    STA .tapeOrROMSwitch                                store it in filing system flag store
    CPX #3                                              compare with 300 baud
    JMP .setupTapeBaudRate                              

§3. Setup cassette options.

 Called after power on, hard or soft BREAK.

 See .tapeOptionsByte for the details of the options.

 On Entry:
       Z set for 1200 baud
       Z clear for 300 baud
.setupTapeOptions = $f140
    PHP                                                 save flags
    LDA #%10100001                                      set sequential access abort if
                                                        error, no messages
    STA .tapeOptionsByte                                set load/save retry if error, short
                                                        messages
    LDA #25                                             set interblock gap (2.5 seconds)
    STA .tapeSequentialAccessInterBlockGap              and store it
    PLP                                                 get back flags
    fall through...

§4. Set tape baud.

 On Entry:
       Z set for 300 baud
       Z clear for 1200 baud
.setupTapeBaudRate = $f14b
    PHP                                                 push flags
    LDA #6                                              get close files command to FSCV
    JSR .passToCurrentFilingSystem                      and FSCV
    LDX #6                                              
    PLP                                                 get back flags
    BEQ +                                               if (Z set on entry) then branch
    DEX                                                 decrement X
+
    STX .tapeBaudRate                                   set current baud rate: X=5 means
                                                        1200 baud; X=6 means 300 baud


    Reset the file based vectors (FILEV; ARGSV; BGETV; BPUTV; GBPBV; FINDV; FSCV) to default
    values
    LDX #14                                             Loop counter
-
    LDA .defaultFileSystemVectors - 1,X                 }
    STA .vectorFILEV - 1,X                              } Reset vectors for file related
                                                        } operations
    DEX                                                 }
    BNE -                                               }

    STX .fsReadProgressState                            progress = 0
    LDX #.romServiceCallVectorsClaimed                  set X to make a Paged ROM service
                                                        call 'Vectors Claimed'
                                                        used after a new filing system
                                                        overwrites the current set of
                                                        vectors.
    fall through...

§5. OSBYTE 143 - Pass service commands to sideways ROMs.

 On Entry:
       X = command number, see .romServiceCallAbsoluteWorkspaceClaim
 On Exit:
       A = X = zero if a ROM chooses to handle the call; or 255 otherwise
       Y = result from ROM
.osbyte143EntryPoint = $f168
    LDA .currentlySelectedROM                           get current ROM number
    PHA                                                 store it
    TXA                                                 command in A
    LDX #15                                             set X=15
                                                        send commands loop
-
    INC .romTypeTable,X                                 } read bit 7 of rom type
    DEC .romTypeTable,X                                 } which indicates if it has a
                                                        } service routine
                                                          (all user ROMs except BASIC should
                                                        have a service routine)
    BPL +                                               if (not service routine) then branch
                                                        (skips the BASIC ROM)
    STX .currentlySelectedROM                           store ROM number
    STX .romSelectRegister                              switch the ROM into the memory map
    JSR .romServiceEntry                                and jump to the service entry
    TAX                                                 on exit put A in X
    BEQ .romChecksFinished                              if (zero, i.e. command recognised by
                                                        ROM) then branch (restore current
                                                        ROM selection and exit)
    LDX .currentlySelectedROM                           point to next lower ROM
+
    DEX                                                 
    BPL -                                               and go round loop again

.romChecksFinished = $f186
    PLA                                                 get back original ROM number
    STA .currentlySelectedROM                           store it in Ram copy
    STA .romSelectRegister                              switch the original ROM into the
                                                        memory map
    TXA                                                 put X back in A
    RTS                                                 

§6. OSARGS - Read or write a file's attributes.

 On Entry:
       In this default implementation, we only need to cover TAPE and ROM filing systems.
       We only implement A=0,Y=0, which returns the filing system number in A.
 On Exit:
       A = 0   no filing system present
       A = 1   1200 baud tape filing system
       A = 2   300 baud tape filing system
       A = 3   ROM filing system

 [ Note: Beyond the scope of this OS, implemented by optional Paged ROMs:
       A = 4   Disc file system
       A = 5   Econet (Networking) file system
       A = 6   Telesoftware (Teletext/Prestel) file system
       A = 7   IEEE filing system
       A = 8   ADFS filing system
       A = 9   Host filing system
       A = 10  Videodisc filing system

 See http://www.sprow.co.uk/bbc/library/fsids.txt for more filing system ids.
 ]
.osargsEntryPoint = $f18e
    ORA #0                                              
    BNE .exit27                                         if (A != 0) then branch (return)
    CPY #0                                              
    BNE .exit27                                         if (Y != 0) then branch (return)
    LDA .tapeBaudRate                                   get current baud rate:
                                                        
                                                          1200 ; 300  ;  ROM
                                                          baud ; baud ;
                                                          tape ; tape ;
                                                            5  ;   6  ;   5

    [Incidentally, this would do the same thing as below, but using four fewer bytes:
    ORA .tapeOrROMSwitch                                    5  ;   6  ;   7
    AND #3                                                  1  ;   2  ;   3
    RTS]

    AND #$FB                                                1  ;   2  ;   1
    ORA .tapeOrROMSwitch                                    1  ;   2  ;   3
    ASL                                                     2  ;   4  ;   6
    ORA .tapeOrROMSwitch                                    2  ;   4  ;   6
    LSR                                                     1  ;   2  ;   3
.exit27 = $f1a2
    RTS                                                 

§7. Filing system routine table.

 The FSC routine (see below) jumps to the relevant address depending on the accumulator.
.fileSystemControlRoutineTable = $f1a3
    !word .starOptEntryPoint - 1                        *OPT
    !word .checkEOFEntryPoint - 1                       check EOF
    !word .starRunEntryPoint - 1                        */
    !word .badCommandError - 1                          'Bad command' if ROMs and FS don't
                                                        want it
    !word .starRunEntryPoint - 1                        *RUN
    !word .starCatEntryPoint - 1                        *CAT
    !word .osbyte119EntryPoint - 1                      osbyte 119 (close SPOOL and EXEC
                                                        files, to get ready for a new FS)

§8. FSC - File System Control.

 On Entry
       A is reason code. Only values 0-6 are handled:

         A=0    A *OPT command has been issued. X and Y are the 2 parameters
         A=1    EOF is being checked. On Entry: X=File handle On Exit: X=FF means EOF else 00
         A=2    A */ command has been issued
         A=3    An unrecognised OS command has been issued. X,Y point at command
         A=4    A *RUN command has been issued X,Y point at filename
         A=5    A *CAT cammand has been issued X,Y point to rest of command
         A=6    New filing system is about to take over. Close *SPOOL and *EXEC files

 Just for reference, these are not implemented in OS (to be implemented by Filing System
 Paged ROMs):
         A=7    return file handle range (X=minimum; Y=maximum)
         A=8    OS has received a star command
         A=9    *EX command
         A=10   *INFO
         A=11   *RUN command for library
         A=12   *RENAME command

 See NAUG Section 16.1.7, Page 256-7
.fscEntryPoint = $f1b1
    CMP #7                                              }
    BCS .exit27                                         } if A>=7 then branch (exit)
    STX .fsTempStorage                                  save X
    ASL                                                 A=A*2
    TAX                                                 X=A to get offset
    LDA .fileSystemControlRoutineTable + 1,X            get high byte of address
    PHA                                                 push it
    LDA .fileSystemControlRoutineTable,X                get low byte of address
    PHA                                                 push it
    LDX .fsTempStorage                                  restore X
    RTS                                                 this now jumps to (the address
                                                        got from the table) + 1

§9. *LOAD or *RUN a file.

loading.png

 On Entry:
       A=0     *RUN the file
       A=$FF   *LOAD the named file and read its catalogue information

 See:

 See:

.loadFile = $f1c4
    PHP                                                 save flags on stack
    PHA                                                 save A on stack
    JSR .claimSerialSystemForLoadSave                   claim serial system for cassette
                                                        we can't use the cassette and the
                                                        RS-423 system at the same time
                                                        so we select the cassette here.
    LDA .fsExecutionAddressLow                          execution address low
    PHA                                                 save A on stack
    JSR .searchForFile                                  search for file
    PLA                                                 get back A
    BEQ .checkFileAttributes                            if (A = 0) then branch
    LDX #3                                              X=3, loop counter
    LDA #$FF                                            A=$FF
-
    PHA                                                 save A on stack
    LDA .fsLoadAddressLow,X                             get load address
    STA .loadAddressLow,X                               store it as current load address
    PLA                                                 get back A
    AND .loadAddressLow,X                               
    DEX                                                 X=X-1
    BPL -                                               until all 4 bytes copied

    [peculiarly, the code makes a special point to check for a load
     address of $FFFFFFFF and signals an error if it occurs. Why?]
    CMP #$FF                                            check if all the bytes of the load
                                                        address are $FF
    BNE .checkFileAttributes                            if (address is valid, i.e. not all
                                                        four bytes $FF) then branch
                                                        (continue)

    handle error because load address is $FFFFFFFF
    JSR .beepAndCancelTapeOperation                     sound bell, reset ACIA and motor off
    JMP .brkBadAddress                                  show 'Bad address' error

§10. checkFileAttributes.

.checkFileAttributes = $f1ed
    LDA .fsBlockFlagByte                                block flag
    LSR                                                 set carry from bit 0 (file locked)
    PLA                                                 get back A
    BEQ .checkAndSetEscapeEffect                        if (A = 0) then branch (entry is
                                                        from star run)
    BCC .loadOrRun                                      if (carry clear, i.e. not locked)
                                                        then branch
    fall through...

§11. File locked.

 A locked file is one with bit zero set on the block flag byte (see .fsBlockFlagByte)
 Locked files can only be *RUN, not *LOADed. This is a form of copy protection on cassette
 tapes.

 See Chapter 18: Tape and ROM Filing systems for data format.
.fileLocked = $f1f6
    JSR .cancelTapeOperationAndMotor                    

    BRK                                                 
    !byte $D5                                           error number
    !text "Locked",0                                    'Locked' error message and zero
                                                        terminator

§12. checkAndSetEscapeEffect.

.checkAndSetEscapeEffect = $f202
    BCC .loadOrRun                                      if (carry clear) then branch (load)
    LDA #3                                              A=3
    STA .escapeAndBreakEffect                           store to cause ESCAPE disable and
                                                        memory clear on break

.loadOrRun = $f209
    LDA #%00110000                                      check bits 4 and 5

    See .tapeCurrentOptionsByte for description of the options
    AND .tapeCurrentOptionsByte                         check for 'ignore errors'
    BEQ +                                               if (ignore errors) then branch
    LDA .checksumIsValidFlag                            get checksum result
    BNE .checksumFail                                   if (checksum failed) then branch
+
    TYA                                                 A=Y
    PHA                                                 save A on stack
    JSR .startSendToSecondProcessor                     send to second processor if present
    PLA                                                 get back A
    TAY                                                 Y=A
    JSR .setStateForLoadingBlockDataOrReset             set state to read block data
.checksumFail = $f21d
    JSR .loadBlock                                      load block from tape
    BNE .retryAfterFailure                              if (not found) then branch (retry)
    JSR .incrementBlockNumbers                          increment current block number

+
    BIT .fsBlockFlagByte                                block flag
    BMI .reachedFinalBlock                              if (bit 7 set, i.e. this is the
                                                        final block) then branch
    JSR .incrementLoadOrSaveAddress                     increment current load address
    JSR .readBlockHeader                                read block header
    BNE .loadOrRun                                      if (non zero) then branch (loop back)
    fall through...

§13. Reached final block.

 Stores the tape file length (low/high) and two zeros into the OSFILE parameter block. Note
 the bugs below though.

 John Kortink found a couple of bugs in this routine.
 See http://mdfs.net/Archive/BBCMicro/2006/10/14/174712.htm

 People have unwittingly hit the second of these bugs:
 See https://stardot.org.uk/forums/viewtopic.php?f=54&t=13481
 See http://www.retrosoftware.co.uk/forum/viewtopic.php?p=5687#p7655

 The first affects the ROM filing system only. In the previously executed .filenameDone
 routine (See .filenameDone), the length of the file (.tapeFileLengthLow/High) only gets
 set if long messages are enabled, so the length returned from this routine may be wrong.

 The second (and more serious) affects both TAPE and ROM filing systems. The parameter block
 address (.osfileBlockAddressLow/High) is normally set up in .osfileEntryPoint
 (see .osfileEntryPoint) but has not been set up if entered via a *RUN command. So four bytes
 of memory at an arbitrary address can be corrupted here! We may just about silently get away
 with it sometimes (cross fingers) if a previous OSFILE operation happens to have set them
 up to a benign value. For example, if we CHAIN a BASIC program first, then *RUN works.
.reachedFinalBlock = $f232
    LDY #10                                             Y=10
    LDA .tapeFileLengthLow                              file length counter low
    STA (.osfileBlockAddressLow),Y                      OSFILE parameter block
    INY                                                 Y=Y+1
    LDA .tapeFileLengthHigh                             file length counter high
    STA (.osfileBlockAddressLow),Y                      OSFILE parameter block
    LDA #0                                              A=0
    INY                                                 Y=Y+1
    STA (.osfileBlockAddressLow),Y                      OSFILE parameter block
    INY                                                 Y=Y+1
    STA (.osfileBlockAddressLow),Y                      OSFILE parameter block
    PLP                                                 get back flags

.beepCancelAndPrintReturn = $f246
    JSR .beepAndCancelTapeOperation                     bell, reset ACIA and motor

.printReturnSafely = $f249
    BIT .currentBlockHasDataErrorFlag                   current block flag
    BMI .exit28                                         

.saveFlagsPrintCR = $f24d
    PHP                                                 save flags on stack
    JSR .safePrintFollowingMessage                      print message following call (in
                                                        this case a newline character)
    !byte .charRETURN, 0                                message
    PLP                                                 restore flags from stack
.exit28 = $f254
    RTS                                                 

§14. retryAfterFailure.

.retryAfterFailure = $f255
    JSR .searchForSpecifiedBlock                        search for a specified block
    BNE .loadOrRun                                      ALWAYS branch (try again)

§15. Copy filename from XY.

 On Entry:
       X and Y     source filename address (Low/High)
 On Exit:
       A = 0
       The filename is copied into the buffer at .filenameToSearchFor
.getFilenameFromXY = $f25a
    STX .stringInputBufferAddressLow                    OS filename/command line pointer
    STY .stringInputBufferAddressHigh                   OS filename/command line pointer
    LDY #0                                              Y=0
    JSR .gsinitForFilenameParsing                       initialise string
    LDX #0                                              X=0
-
    JSR .gsreadEntryPoint                               GSREAD call
    BCS .terminateFilename                              if (end of character string) then
                                                        branch
    BEQ .brkBadStringJumper                             if (zero found) then break ('Bad
                                                        string' error)
    STA .filenameToSearchFor,X                          store character in tape filename area
    INX                                                 X=X+1
    CPX #11                                             check X with 11
    BNE -                                               if (X != 11) then branch (loop back)
.brkBadStringJumper = $f274
    JMP .brkBadString                                   Bad String error

§16. terminateFilename.

.terminateFilename = $f277
    LDA #0                                              terminate filename with 0
    STA .filenameToSearchFor,X                          
    RTS                                                 

§17. OSFILE.

 default handling for OSFILE (for cassette and ROM filing system)

 parameter block located by XY
   0-1     Address of Filename terminated by CR
   2-4     Load Address of File
   6-9     Execution Address of File
   10-13   Start address of data for write operations or length of file for read operations
   14-17   End address of Data (i.e. byte AFTER last byte to be written) or file attributes

 On Entry:
   Action is determined by value in A

   A=0     Save section of memory as named file, write catalogue information
   A=1     Write catalogue information for named file             (not supported in this FS)
   A=2     Write the LOAD       address (only) for the named File (not supported in this FS)
   A=3     Write the EXECUTION  address (only) for the named File (not supported in this FS)
   A=4     Write the ATTRIBUTES for the named File                (not supported in this FS)
   A=5     Read the catalogue information and put file type in A  (not supported in this FS)
   A=6     Delete the named file                                  (not supported in this FS)
   A=$FF   Load the named file and read its catalogue information

   XY = address of osfile block

 See .saveFileToTape for PDF showing the code path and how bytes are written to tape.
 See .loadFile for PDFs showing the code path and how bytes are read from tape and ROM.
.osfileEntryPoint = $f27d
    PHA                                                 save action on stack
    STX .osfileBlockAddressLow                          osfile block pointer low
    STY .osfileBlockAddressHigh                         osfile block pointer high
    LDY #0                                              Y=0
    LDA (.osfileBlockAddressLow),Y                      get address of filename (low byte)
    TAX                                                 store in X
    INY                                                 Y=1
    LDA (.osfileBlockAddressLow),Y                      get address of filename (high byte)
    TAY                                                 store in Y
    JSR .getFilenameFromXY                              get filename from buffer at XY
    LDY #2                                              Y=2

    loop to copy load and execution addresses to .loadAddressLow
-
    LDA (.osfileBlockAddressLow),Y                      copy parameter block (load and exec
                                                        addresses)...
    STA .fsLoadAddressLow-2,Y                           ...     to .fsLoadAddressLow
    STA .loadAddressLow-2,Y                             ... and to .loadAddressLow
    INY                                                 increment y
    CPY #10                                             compare with limit
    BNE -                                               if (not done yet) then branch (loop
                                                        back)

    get action code, and branch to save if A=0
    PLA                                                 get back A (action)
    BEQ .saveFileToTape                                 if (A == 0) then branch (save file)

    if action is not $FF then exit
    CMP #$FF                                            check A with 255
    BNE .exit28                                         if (A != 255) then branch (exit,
                                                        as cassette / ROM filing systems
                                                        have no other options)
    load a file from cassette or ROM
    JMP .loadFile                                       load file (with A=255; Y=0)

§18. Save a file to tape.

   See Chapter 18: Tape and ROM Filing systems for Tape/ROM format.

 On Entry:
       A = 0
       Y = 10 (offset in OSFILE Block)

 See:

  for the code path and steps for writing to tape.
.saveFileToTape = $f2a7
    STA .fsBlockNumberLow                               zero block number low
    STA .fsBlockNumberHigh                              zero block number high

    copy start address, end address into zero page copy
    Note Y=10 (loop counter) at this point
-
    LDA (.osfileBlockAddressLow),Y                      read from .OSFILE parameter block
    STA .tapeSaveStartAddressLow-10,Y                   store
    INY                                                 data start and data end address
    CPY #10 + 8                                         check for Y=18
    BNE -                                               if (not finished) then branch (loop
                                                        back)

    TXA                                                 A=X
    BEQ .brkBadStringJumper                             if (no filename found) then branch
                                                        ('Bad string' error)

    JSR .claimSerialSystemForLoadSave                   claim serial system for load/save on
                                                        cassette
    JSR .promptToRecordOnTape                           prompt to start recording
    LDA #0                                              A=0 (meaning read bytes from second
                                                        processor, aka:
                                                             'multiple single byte transfer:
                                                        2nd processor to I/O processor')
    JSR .secondProcessorTransfer                        start transfer from 2nd processor
                                                        (if present)
    JSR .setupForCassetteWrite                          set up tape for write operation

.saveToTapeLoop = $f2c8
    calculate length to read
    SEC                                                 set carry
    LDX #$FD                                            X = loop counter (loops three times:
                                                        X=$FD, $FE, $FF)

    loop to work out the amount of file remaining to save, to calculate block length
    length = end address - current address
-
    LDA .tapeSaveEndAddressLow - $00FD,X                read end address
    SBC .tapeSaveStartAddressLow - $00FD,X              subtract current start address
    STA .fsBlockLengthLow - $00FD,X                     store at .fsBlockLengthLow/High and
                                                        .fsBlockFlagByte.
                                                        we don't care about overwriting
                                                        .fsBlockFlagByte with a length
                                                        calculation since we are about to
                                                        set it anyway.
    INX                                                 X=X+1
    BNE -                                               

    at this point X = 0 (meaning by default it's not the last block, unless we overwrite X
    below)

    if high byte of length is non-zero, then save the full block length of $0100
    TAY                                                 Y=A (to set the zero flag if zero)
    BNE .setFullBlockLength                             if (highest byte of subtracted value
                                                        is non zero) then branch

    check the low and mid bytes of the length to see if this this the last block
    CPX .fsBlockLengthLow                               } compare X with zero
    LDA #$01                                            } calculate $0100 - block length
    SBC .fsBlockLengthHigh                              }
    BCC .setFullBlockLength                             if (fsBlockLength >= $0100) then
                                                        branch

    yes, this is the last block
    LDX #$80                                            X=$80 (set last block flag)
    BNE .skipSettingBlockLength                         ALWAYS branch

.setFullBlockLength = $f2e8
    LDA #1                                              }
    STA .fsBlockLengthHigh                              } block length = $0100 (X=0 at this
                                                        } point)
    STX .fsBlockLengthLow                               }

.skipSettingBlockLength = $f2f0
    STX .fsBlockFlagByte                                store the block flag
    JSR .saveABlockToTape                               write block to tape
    BMI .exit29                                         if (negative) then branch (exit)
    JSR .incrementLoadOrSaveAddress                     increment start save address by a
                                                        page
    INC .fsBlockNumberLow                               block number
    BNE .saveToTapeLoop                                 if (not 0) then branch (loop back
                                                        again)
    INC .fsBlockNumberHigh                              block number high
    BNE .saveToTapeLoop                                 ALWAYS branch (since block number
                                                        high byte should never wrap around)
                                                        (loop back again)

§19. starRunEntryPoint.

.starRunEntryPoint = $f305
    JSR .getFilenameFromXY                              get filename from buffer at XY
    LDX #$FF                                            X=$FF
    STX .fsExecutionAddressLow                          store in low byte of execution
                                                        address
    JSR .loadFile                                       load file (with A=0)
    BIT .tubePresentFlag                                check to see if Tube is present
    BPL +                                               if (Tube is not present) then branch
    LDA .fsExecutionAddressMid2                         execution address extend
    AND .fsExecutionAddressHigh                         execution address extend
    CMP #$FF                                            check if all bits set
    BNE .sendExecAddressToRunFromTube                   if (top two bytes of exec address
                                                        are not both $FF) then branch
                                                        (execute on the Tube)
+
    JMP (.fsExecutionAddressLow)                        RUN file

§20. sendExecAddressToRunFromTube.

.sendExecAddressToRunFromTube = $f322
    LDX #<.fsExecutionAddressLow                        }
    LDY #>.fsExecutionAddressHigh                       } point to execution address
    LDA #4                                              Tube service call 4
    JMP .secondProcessorCall                            and issue to Tube to run file

§21. starCatEntryPoint.

.starCatEntryPoint = $f32b
    LDA #%00001000                                      A=8 (set catalogue state)
    JSR .setTapeStatusBits                              set ('OR' in) status bits
    JSR .claimSerialSystemForLoadSave                   claim serial system for cassette
    LDA #0                                              A=0 (don't match filename and block
                                                        number)
    JSR .searchForBlockCheckFilingSystem                read data from tape/ROMFS
    JSR .cancelTapeOperation                            

.clearCatalogueStatus = $f33b
    LDA #%11110111                                      clear bit 3 of file system status
                                                        bit (catalogue status)
    fall through...

§22. Clear tape status bits.

 On Entry:
       A has the bits set that are to be cleared from the tape status byte

 On Exit:
       X and Y preserved
.clearTapeStatusBits = $f33d
    AND .fsStatusByte                                   
.storeTapeStatusByte = $f33f
    STA .fsStatusByte                                   
.exit29 = $f341
    RTS                                                 

§23. setTapeEOFStatus.

.setTapeEOFStatus = $f342
    LDA #%01000000                                      set bit 6 of cassette status (EOF)
    fall through...

§24. Set tape status bits.

 On Entry:
       A has the bits set that are to be set on the tape status byte

 On Exit:
       X and Y preserved
.setTapeStatusBits = $f344
    ORA .fsStatusByte                                   
    BNE .storeTapeStatusByte                            ALWAYS branch (store status byte)

§25. Search for the next block, checking for Tape or ROM.

 Used when loading a file or cataloguing a directory.

 On Entry:
       A = $FF - must match the current filename and block number
       A = $00 - match anything (e.g. for catalogue)

 On Exit:
       V clear if final block
.searchForBlockCheckFilingSystem = $f348
    PHA                                                 save A on stack
    LDA .tapeOrROMSwitch                                filing system flag 0=Tape; 2=ROMFS
    BEQ .searchForBlockReadHeaderAndCompare             if (tape filing system active) then
                                                        branch

    deal with search on ROM FS
    JSR .setInitialSpeechPHROMNumber                    set current Filing System ROM/PHROM
    JSR .readByteFromROMOrPHROM                         get byte from data ROM check type
    BCC .searchForBlockReadHeaderAndCompare             if (carry clear) then branch
    CLV                                                 clear overflow flag
    BVC .pullAAndReturn                                 ALWAYS branch

§26. searchForBlockReadHeaderAndCompare.

.searchForBlockReadHeaderAndCompare = $f359
    JSR .readBlockHeader                                read block header
    LDA .fsBlockNumberLow                               block number
    STA .currentBlockNumberLow                          current block number low
    LDA .fsBlockNumberHigh                              block number high
    STA .currentBlockNumberHigh                         current block number high
    LDX #$FF                                            X=$FF
    STX .fsLastBlockReadFlagsCopy                       copy of last read block flag
    INX                                                 X=0
    STX .currentBlockHasDataErrorFlag                   reset data error flag to zero
    BEQ +                                               ALWAYS branch

.searchForBlockLoop = $f370
    JSR .incrementBlockNumbers                          increment current block number
    JSR .readBlockHeader                                read block header

+
    LDA .tapeOrROMSwitch                                get filing system flag 0=tape;
                                                        2=ROMFS
    BEQ +                                               if (tape filing system is active)
                                                        then branch
    BVC .pullAAndReturn                                 if (final block, on ROMFS) then
                                                        branch (exit)

+
    PLA                                                 get back A (match flag)
    PHA                                                 save A on stack
    BEQ .finishReadingBlockAndContinue                  if (A == 0, i.e. match anything)
                                                        then branch
    JSR .compareFilenames                               check filename header block matches
                                                        searched Fn
    BNE .messageAndFinishBlock                          if (filenames don't match) then
                                                        branch
    LDA #%00110000                                      
    AND .tapeCurrentOptionsByte                         check bits 4/5 of current options,
                                                        checking for 'ignore errors'
    BEQ .pullAAndReturn                                 if (ignore errors) then branch
    LDA .fsBlockNumberLow                               block number
    CMP .nextBlockNumberLow                             next block number low
    BNE .messageAndFinishBlock                          
    LDA .fsBlockNumberHigh                              block number high
    CMP .nextBlockNumberHigh                            next block number high
    BNE .messageAndFinishBlock                          

.pullAAndReturn = $f39a
    PLA                                                 get back A
    RTS                                                 

.messageAndFinishBlock = $f39c
    LDA .tapeOrROMSwitch                                filing system flag 0=tape; 2=ROMFS
    BEQ .finishReadingBlockAndContinue                  if (tape filing system active) then
                                                        branch

.skipMessaging = $f3a1
    JSR .setROMOrPHROMAddress                           set ROM address

.resetBlockNumberAndContinue = $f3a4
    LDA #$FF                                            A=$FF to reset block number
    STA .fsBlockNumberLow                               block number low
    STA .fsBlockNumberHigh                              block number high
    BNE .searchForBlockLoop                             ALWAYS branch

§27. Finish reading block and continue.

 On Entry:
       V set means reset file system state (occurs when reading the last block of a ROMFS file)
.finishReadingBlockAndContinue = $f3ae
    BVC +                                               if (V clear) then branch
    LDA #$FF                                            A=$FF (searching)
    JSR .setCharAThenProgressState4OrReset              update to progress state 4 (if block
                                                        length > 0)
+
    LDX #0                                              X=0
    JSR .checkForChecksumError                          report 'Data?' if necessary
    LDA .tapeOrROMSwitch                                get current filing system
    BEQ +                                               if (tape filing system) then branch
    BIT .tapeCurrentOptionsByte                         check current options (bit 6)
    BVC .skipMessaging                                  if (no long messages) then branch
                                                        (back)
+
    BIT .fsBlockFlagByte                                block flag
    BMI .resetBlockNumberAndContinue                    if (-ve) then branch
    BPL .searchForBlockLoop                             ALWAYS branch (loop back and do it
                                                        again)

§28. OSFIND - Open or close a file.

 When opening a file, this returns a file handle ('channel') number:

   1 = input file on tape
   2 = output file on tape
   3 = input file from ROMFS

 On Entry:
       A determines action
       Y may contain file handle (channel number)
       or XY is the address of the filename terminated by $0D

       A=0    closes file in channel Y; if Y = 0 closes all files
       A=$40  open a file for input  (reading) XY points to filename
       A=$80  open a file for output (writing) XY points to filename
       A=$C0  open a file for both input and output (random Access)
              (not supported by TAPE and ROM filing systems)

 On Exit:
       A = file handle of opened file or zero on error
       X,Y are preserved
.osfindEntryPoint = $f3ca
    STA .fsTempStorage                                  save action in temporary store
    TXA                                                 }
    PHA                                                 }
    TYA                                                 } save X and Y on the stack
    PHA                                                 }
    LDA .fsTempStorage                                  recall action
    BNE .openAFile                                      if (A is non zero) then branch (open
                                                        a specific file)

.closeOneFile = $f3d4
    TYA                                                 A=Y
    BNE +                                               if (A is not zero) then branch
                                                        (close specified file)
    JSR .osbyte119EntryPoint                            close all SPOOL/EXEC files using
                                                        OSBYTE 119
    JSR .closeFileThatsOpenForOutput                    close file open for output
-
    LSR .fsStatusByte                                   shift right (bit zero into carry)
    ASL .fsStatusByte                                   shift left (clear bit zero)
    BCC .finishWithError                                if (carry clear, i.e. no input file
                                                        open) then branch
+
    LSR                                                 A contains file handle so shift bit
                                                        0 into carry
    BCS -                                               if (carry set) then branch (close
                                                        input file)
    LSR                                                 shift bit 1 into carry
    BCS .closeOutputFile                                if (carry set) then branch (close
                                                        output file)
    JMP .channelError                                   report 'Channel error'
                                                        as tape filing system can only
                                                        support one input and one output file

.closeOutputFile = $f3ec
    JSR .closeFileThatsOpenForOutput                    close file open for output
.finishWithError = $f3ef
    JMP .restoreRegistersAndExit                        and exit

§29. Open a file.

 A file is opened for input or output. The file handle ('channel') is returned in A.

 File handles returned:
   1 = input file on tape
   2 = output file on tape
   3 = input file from ROMFS

 On Entry:
       XY = address of zero terminated filename
       .fsTempStorage - action from .OSFIND (See .osfindEntryPoint)
           bit 6 set to open for input
           bit 7 set to open for output
           (opening both for input and output is not supported on either tape or ROMFS)

 On Exit:
       A = file handle ('channel')
.openAFile = $f3f2
    JSR .getFilenameFromXY                              get filename from buffer at XY
    BIT .fsTempStorage                                  get action (OSFIND value of A on
                                                        entry)
    BVC .openAFileForOutputOnly                         if (bit 6 not set, i.e. it's to be
                                                        an output only file) then branch

    open a file for input
    LDA #0                                              it's an input file
    STA .bgetBufferOffset                               BGET buffer offset for next byte
    STA .nextBGETBlockLow                               Expected BGET file block number low
    STA .nextBGETBlockHigh                              expected BGET file block number high
    LDA #%00111110                                      }
    JSR .clearTapeStatusBits                            } clear 'input file open' bit; clear
                                                        } 'EOF reached'; clear 'EOF warning
                                                        } given'
    JSR .claimSerialSystemForSequentialAccess           claim serial system and set OPTions
    PHP                                                 save flags on stack
    JSR .searchForFile                                  search for file
    JSR .checkLockedFlag                                check protection bit of block status
                                                        and respond
    PLP                                                 get back flags
    LDX #$FF                                            X=loop counter

-
    INX                                                 X=X+1
    LDA .fsFilename,X                                   copy file name character
    STA .bgetFilename,X                                 and store as BGET filename
    BNE -                                               until end of filename (zero
                                                        terminator)

    LDA #%00000001                                      A=1 to set bit for 'input file open'
    JSR .setTapeStatusBits                              set status bit
    LDA .tapeInputCurrentBlockSizeLow                   tape filing system currently
                                                        resident file block length low
    ORA .tapeInputCurrentBlockSizeHigh                  tape filing system currently
                                                        resident file block length high
    BNE +                                               if (block length is no zero) then
                                                        branch
    JSR .setTapeEOFStatus                               set tape filing system status bit 6
                                                        (EOF reached)
+
    LDA #1                                              }
    ORA .tapeOrROMSwitch                                } get input file handle, 1 = tape;
                                                        } 3=ROMFS
    BNE .returnAWithXYRestoredFromStack                 ALWAYS branch (restore registers and
                                                        exit)

§30. Open a file for output.

 On Entry:
       X is length of filename
.openAFileForOutputOnly = $f436
    TXA                                                 A=X
    BNE +                                               if (filename length is non-zero)
                                                        then branch
    JMP .brkBadString                                   output 'Bad String' error

+
    LDX #$FF                                            X=$FF
.copyFilenameLoop = $f43e
    INX                                                 X=X+1
    LDA .filenameToSearchFor,X                          sought filename
    STA .tapeBlockHeaderStart,X                         write it to BPUT file header block
    BNE .copyFilenameLoop                               until A=0 (zero terminator at end of
                                                        filename)

    LDA #$FF                                            A=$FF
    LDX #8                                              X=8
-
    STA .tapeBlockLoadAddressLow-1,X                    set load and exec address to $FF
    DEX                                                 X=X-1
    BNE -                                               

    TXA                                                 A=0
    LDX #.tapeBlockNumberLow - .tapeBlockHeaderStart    X=loop counter from $14 to $1E
-
    STA .tapeBlockHeaderStart,X                         BPUT file header block
    INX                                                 X=X+1
    CPX #.bgetBufferOffset - .tapeBlockHeaderStart      this zeros BPUT block
    BNE -                                               

    ROL .tapeBlockLengthHigh                            
    JSR .claimSerialSystemForLoadSave                   claim serial system for cassette
                                                        load/save
    JSR .promptToRecordOnTape                           prompt to start recording
    JSR .cancelTapeOperationAndMotor                    
    LDA #%00000010                                      } set the tape status bit
    JSR .setTapeStatusBits                              } for 'output file open'
    LDA #2                                              A=2=file handle of (tape) file open
                                                        for output

.returnAWithXYRestoredFromStack = $f46f
    STA .fsTempStorage                                  store value of A temporarily

.restoreRegistersAndExit = $f471
    PLA                                                 }
    TAY                                                 }
    PLA                                                 } Restore X and Y registers
    TAX                                                 }
    LDA .fsTempStorage                                  recall temporary store into A
.exit30 = $f477
    RTS                                                 

§31. closeFileThatsOpenForOutput.

.closeFileThatsOpenForOutput = $f478
    LDA #%00000010                                      }
    AND .fsStatusByte                                   } clear all of tape status byte
                                                        } except bit 1
    BEQ .exit30                                         if (output file not open) then
                                                        branch (exit)

    an output file is open
    LDA #0                                              A=0
    STA .tapeBlockLengthHigh                            setting block length to current
                                                        value of BPUT offset
    LDA #$80                                            A=$80 (last block)
    LDX .bputBufferOffset                               get BPUT buffer offset
    STX .tapeBlockLengthLow                             setting block length to current
                                                        value of BPUT offset
    STA .tapeBlockFlagByte                              mark current block as last
    JSR .saveSequentialAccessBlockToTape                save block to tape
    LDA #%11111101                                      }
    JMP .clearTapeStatusBits                            } clear bit 1 of the tape status (no
                                                        } file is open for output) and return

§32. saveSequentialAccessBlockToTape.

 Sequential access writes bytes instead of files. (As used by BGET and BPUT in BASIC).
.saveSequentialAccessBlockToTape = $f496
    JSR .claimSerialSystemForSequentialAccess           claim serial system and set OPTions

    LDX #17                                             loop counter
-
    LDA .tapeBlockLoadAddressLow,X                      }
    STA .fsLoadAddressLow,X                             } copy header block
    DEX                                                 X=X-1
    BPL -                                               loop back until X=255

    set load address to "FFFF0900"
    STX .tapeSaveStartAddressMid2                       store $FF
    STX .tapeSaveStartAddressHigh                       store $FF
    INX                                                 increment X (to zero)
    STX .tapeSaveStartAddressLow                        store $00
    LDA #$09                                            
    STA .tapeSaveStartAddressMid1                       store $09

    Copy
    LDX #(.tapeBlockFilename-1) - .vduVariablesStart    
    JSR .copyToSoughtFilename                           copy filename from
                                                        .tapeBlockFilename to
                                                        .filenameToSearchFor
    STA .fsLastBlockReadFlagsCopy                       set copy of last read block flag to
                                                        zero
    JSR .zeroFileHandleAndMotorOn                       switch Motor On
    JSR .setupForCassetteWrite                          set up tape filing system for write
                                                        operation
    JSR .saveABlockToTape                               write block to tape

    increment block number
    INC .tapeBlockNumberLow                             block number low
    BNE +                                               
    INC .tapeBlockNumberHigh                            block number high
+
    RTS                                                 

§33. OSBGET - get byte from file.

 On Entry:
       Y contains channel number (file handle)
 On Exit:
       X and Y are preserved
       C=0 indicates valid character
       A contains character (or error) A=$FE End Of File

 See:

 See:

.osbgetEntryPoint = $f4c9
    TXA                                                 A=X
    PHA                                                 save A on stack
    TYA                                                 A=Y
    PHA                                                 save A on stack
    LDA #1                                              A=1 (check file is open for reading)
    JSR .checkFileIsOpen                                check conditions for OSBGET are OK
    LDA .fsStatusByte                                   tape/ROM FS status byte
    ASL                                                 shift bit 7 into carry (EOF warning
                                                        given)
    BCS .eofErrorMessage                                if (carry set) then branch
    ASL                                                 shift bit 6 into carry
    BCC .notEOF                                         if (carry clear, i.e. EOF not
                                                        reached) then branch
    LDA #%10000000                                      }
    JSR .setTapeStatusBits                              } set bit 7 of status byte ('EOF
                                                        } warning given')
    LDA #$FE                                            A = return value = $FE (end of file)
    BCS .storeFileStatus                                if (carry set) then branch

.notEOF = $f4e3
    LDX .bgetBufferOffset                               BGET buffer offset for next byte
    INX                                                 X=X+1
    CPX .tapeInputCurrentBlockSizeLow                   tape filing system currently
                                                        resident file block length low
    BNE .readFromFSBufferAndContinue                    if (not end of block) then branch
                                                        (read a byte from buffer)

    BIT .blockFlagOfCurrentlyResidentBlock              block flag of currently resident
                                                        block
    BMI .setEOF                                         if (this is the last block) then
                                                        branch
    LDA .lastCharacterOfCurrentlyResidentBlock          last character of currently resident
                                                        block
    PHA                                                 save A on stack
    JSR .claimSerialSystemForSequentialAccess           claim serial system and set OPTions
    PHP                                                 save flags on stack
    JSR .bgetReadBlockAndHeader                         read in a new block
    PLP                                                 get back flags
    PLA                                                 get back A
    STA .fsTempStorage                                  store return value: last byte of
                                                        current block
    CLC                                                 clear carry flag
    BIT .blockFlagOfCurrentlyResidentBlock              block flag of currently resident
                                                        block
    BPL .incrementAndFinish                             if (not last block) then branch
    LDA .tapeInputCurrentBlockSizeLow                   get block length
    ORA .tapeInputCurrentBlockSizeHigh                  OR with high byte
    BNE .incrementAndFinish                             if (block size not zero) then branch
    JSR .setTapeEOFStatus                               set tape filing system status bit 6
                                                        (EOF reached)
    BNE .incrementAndFinish                             if (not EOF) then branch

.setEOF = $f513
    JSR .setTapeEOFStatus                               set tape filing system status bit 6
                                                        (EOF reached)

.readFromFSBufferAndContinue = $f516
    DEX                                                 X=X-1
    CLC                                                 clear carry flag
    LDA .tapeOrRS423InputBuffer,X                       read byte from cassette buffer

.storeFileStatus = $f51b
    STA .fsTempStorage                                  store return value temporarily
.incrementAndFinish = $f51d
    INC .bgetBufferOffset                               BGET buffer offset for next byte
    JMP .restoreRegistersAndExit                        exit via .restoreRegistersAndExit

§34. eofErrorMessage.

.eofErrorMessage = $f523
    BRK                                                 
    !byte $DF                                           error number
    !text "EOF",0                                       string and zero terminator

§35. OSBPUT - Write a byte to file.

 This is for the TAPE filing system only. The OS supplies filing system code for TAPE and
 ROM filing systems, and obviously you can't write into the ROM filing system.

 On Entry:
       Y contains channel number (file handle)
       A contains byte to be written

 See .saveFileToTape for PDF showing the code path and how bytes are written to tape.
.osbputEntryPoint = $f529
    STA .tapeLastBputValue                              store A in temporary store
    TXA                                                 }
    PHA                                                 } save XY on the stack
    TYA                                                 }
    PHA                                                 }
    LDA #2                                              A=2 (meaning check file is open for
                                                        writing)
    JSR .checkFileIsOpen                                check conditions necessary for
                                                        OSBPUT are OK
    LDX .bputBufferOffset                               BPUT buffer offset for next byte
    LDA .tapeLastBputValue                              get back original value of A
    STA .tapeOrRS423OutputBuffer,X                      Write into cassette or RS-423 buffer
    INX                                                 X=X+1
    BNE +                                               if (not zero, buffer is not full)
                                                        then branch
    JSR .saveSequentialAccessBlockToTape                buffer is full so save block to tape
    JSR .cancelTapeOperationAndMotor                    
+
    INC .bputBufferOffset                               BPUT buffer offset for next byte
    LDA .tapeLastBputValue                              get back A
    JMP .returnAWithXYRestoredFromStack                 and exit

§36. *OPT X,Y.

 On Entry:

        X,Y
   *OPT 0,0  restore *OPT default message values
   *OPT 1,0  turn off filing system messages
   *OPT 1,1  turn on filing system messages (normal)
   *OPT 1,2  turn on filing system messages (extended)
   *OPT 2,0  ignore errors, though messages may be displayed
   *OPT 2,1  on error prompt for retry
   *OPT 2,2  on error, abort
   *OPT 3,n  set interblock gaps to n/10 seconds (for tape SAVE operations)
             if n>127 then set default value
.starOptEntryPoint = $f54d
    TXA                                                 A=X
    BEQ .restoreDefaultOPTs                             if (*OPT 0) then branch (set
                                                        defaults)
    CPX #3                                              
    BEQ .tapeSetInterblockGap                           if (*OPT 3) then branch (to set
                                                        interblock gap)
    CPY #3                                              
    BCS .issueBadCommand                                if (Y > 2) then branch ('Bad
                                                        command' error)
    DEX                                                 
    BEQ .opt1                                           if (*OPT 1) then branch (set filing
                                                        system message level)
    DEX                                                 
    BEQ .opt2                                           if (*OPT 2) then branch (response
                                                        level to errors)
.issueBadCommand = $f55e
    JMP .badCommandError                                issue 'Bad command' error

§37. opt1.

 Set filing system message level
.opt1 = $f561
    LDA #%00110011                                      set lower two bits of each nybble as
                                                        a MASK
                                                        this allows us to set the top two
                                                        bits of each nybble (message type)
                                                        and
                                                        still keep the existing values for
                                                        the bottom two bits
                                                        of each nybble (ignore/retry/abort)
    INY                                                 }
    INY                                                 } Y=Y+3 (offset into
                                                        } .tapeOptByteTable)
    INY                                                 }
    BNE .setOptWithMask                                 ALWAYS branch

§38. opt2.

 Set error levels
.opt2 = $f568
    LDA #%11001100                                      setting top two bits of each nybble
                                                        as a MASK
                                                        allows us to set the bottom two bits
                                                        of each nybble (ignore/retry/abort)
                                                        and
                                                        still keep the existing values for
                                                        the top two bits
                                                        of each nybble (message type)
.setOptWithMask = $f56a
    INY                                                 Y=Y+1 (offset into .tapeOptByteTable)
    AND .tapeOptionsByte                                keep bits set in MASK for keeping
                                                        current tape options
.setOPTValueY = $f56d
    ORA .tapeOptByteTable,Y                             OR with table value to set new
                                                        options
    STA .tapeOptionsByte                                store it
    RTS                                                 

§39. tapeSetInterblockGap.

.tapeSetInterblockGap = $f573
    TYA                                                 A=Y (interblock gap)
    BMI +                                               if (A > 127) use default value
    BNE .useGapDontUseDefaultValue                      if (A != 0) then branch (don't use
                                                        default value)
+
    LDA #25                                             A=25 (2.5 seconds by default)
.useGapDontUseDefaultValue = $f57a
    STA .tapeSequentialAccessInterBlockGap              sequential block gap
    RTS                                                 

§40. restoreDefaultOPTs.

.restoreDefaultOPTs = $f57e
    TAY                                                 Y=A=0
    BEQ .setOPTValueY                                   ALWAYS branch

§41. Tape opt byte table.

 Options are stored in top four bits (LOAD/SAVE) and bottom four bits (sequential access)
 See .tapeCurrentOptionsByte.

   bit 0/4 = Abort bit
   bit 1/5 = Retry bit
   bit 2/6 } Message type: 00 = no messages
   bit 3/7 }             : 10 = short messages
           }             : 11 = long messages

 0000     Ignore errors         no messages
 0001     Abort if error        no messages
 0010     Retry after error     no messages
 1000     Ignore error          short messages
 1001     Abort if error        short messages
 1010     Retry after error     short messages
 1100     Ignore error          long messages
 1101     Abort if error        long messages
 1110     Retry after error     long messages
.tapeOptByteTable = $f581
    !byte %10100001                                     default value:
                                                        LOAD/SAVE: Retry; short messages
                                                        SEQUENTIAL: Abort; no messages
    !byte %00000000                                     ignore error
    !byte %00100010                                     retry if error
    !byte %00010001                                     abort if error
    !byte %00000000                                     no messages
    !byte %10001000                                     short messages
    !byte %11001100                                     long messages