Update ACIA; Check EOF; Search for file; Spool or Exec file; Load and save block; Load block header; Load and save byte; Update CRC - 801 bytes (4.8%)
- §1. Update ACIA
- §2. Process byte after being read from tape or ROMFS
- §3. fsReadProgress2FoundCarrierTone
- §4. fsReadProgress3FoundSyncByte
- §5. fsReadProgress4FoundBlockData
- §6. fsReadProgress5FinishedReadingBlockData
- §7. Check for EOF on an open file
- §8. searchForFile
- §9. setBlockFlagsAndExit
- §10. Close a SPOOL / EXEC file
- §11. Close EXEC file and optionally open a new EXEC file
- §12. bgetReadBlockAndHeader
- §13. Set Load Address and Load Block
- §14. searchForBlock
- §15. checkForROMBlockMarker
- §16. clearCatalogueStatusBadROM
- §17. readTapeBlockHeader
- §18. Read block header
- §19. Read byte from tape or ROM
- §20. Update CRC
- §21. Set state ready for loading block data (or reset)
- §22. Set state ready for loading block data or searching (or reset state)
- §23. Save a block to tape
- §24. saveByteAToTapeAndUpdateCRC
- §25. saveChecksumToTape
- §26. Save a byte to tape
- §27. waitFiveSeconds
- §28. Delay for the (temporary) interblock gap time
- §29. Wait for A/10 seconds, or until ESCAPE is pressed
If necessary, read or write a byte to cassette, or read a byte from ROMFS. Can either be called from interrupt routine, or when loading from cassette.
.updateACIA = $f588 DEC .fsGotACharacterToReadOrWriteFlag Used when writing to tape, to decrement the value from zero to 255 indicating the byte has been written (which happens below) LDA .tapeOrROMSwitch filing system flag 0=tape; 2=ROMFS BEQ .updateACIATape if (tape filing system active) then branch deal with reading a byte from ROM / PHROM JSR .readByteFromROMFSorPHROM read ROMFS data ROM or PHROM TAY Y=A CLC clear carry flag BCC .postReadByte ALWAYS branch .updateACIATape = $f596 deal with reading or writing to tape LDA .acia6850StatusRegister ACIA status register PHA save A on stack AND #2 clear all but bit 1 BEQ .readByteFromTape if (transmit data register full, i.e. not ready to send) then branch (read from tape) LDY .tapeSendingFlag check we are sending to tape BEQ .readByteFromTape if (not sending to tape) then branch (read from tape) send a byte to tape PLA get back A LDA .fsCharacterJustReadOrCharToWrite get character to send STA .acia6850DataRegister ACIA transmit data register RTS read a byte from tape .readByteFromTape = $f5a9 LDY .acia6850DataRegister read ACIA receive data register PLA get back A LSR } LSR } shift bit 2 to carry LSR } (data carrier detect) fall through
§2. Process byte after being read from tape or ROMFS.
This is called from the 100Hz interrupt routine. It updates the read progress state as needed, and stores the byte for later processing in .readBlockHeader and .readTapeBlockHeader. See .readBlockHeader. See .readTapeBlockHeader. The progress in reading a block is tracked by a progress byte (0-5): See .fsReadProgressState 0 - exit 1 - looking for carrier tone 2 - found carrier tone, waiting for sync byte $2A 3 - found sync byte $2A, now reading header 4 - have read the header with non-zero block data length, now reading actual block data 5 - finished reading data in block On Entry: Y = byte just read Carry set if it's the tape filing system and 'data carrier detect' bit is set
.postReadByte = $f5b0 LDX .fsReadProgressState read progress state BEQ .exit31 if (.fsReadProgressState = 0) then branch (exit) DEX X=X-1 BNE .fsReadProgress2FoundCarrierTone if (.fsReadProgressState > 1) then branch deal with progress state = 1 (looking for carrier tone) BCC .exit31 if (reading from ROM OR carrier tone from cassette not detected) then branch (exit) LDY #2 Y=2 BNE .storeTapeProgress ALWAYS branch (store progress and return)
§3. fsReadProgress2FoundCarrierTone.
.fsReadProgress2FoundCarrierTone = $f5bd DEX X=X-1 BNE .fsReadProgress3FoundSyncByte if (.fsReadProgressState > 2) then branch deal with progress state = 2 (found carrier tone, waiting for it to finish) BCS .exit31 if (carrier tone from cassette still detected) then branch (exit) TYA A=Y=byte read JSR .zeroChecksumAndFSFlag initialise CRC checksum to zero LDY #3 Y=3 CMP #.fsSynchronisationByte check for the sync byte $2A BEQ .storeTapeProgress if (found sync byte) then branch (store state and return) sync byte not found, revert back to progress state 1 JSR .flipRelayOffAndOnThenSetToReadFromTape control cassette system LDY #1 Y=1 BNE .storeTapeProgress ALWAYS branch
§4. fsReadProgress3FoundSyncByte.
.fsReadProgress3FoundSyncByte = $f5d3 DEX X=X-1 BNE .fsReadProgress4FoundBlockData if (.fsReadProgressState > 3) then branch deal with progress state = 3 (found sync byte $2A, reading block header) BCS + if (carrier detected) then branch STY .fsCharacterJustReadOrCharToWrite store byte just read BEQ .exit31 if (zero) then branch (exit) + LDA #$80 A=$80 STA .fsGotACharacterToReadOrWriteFlag note that we have just read a byte BNE .exit31 ALWAYS branch (exit)
§5. fsReadProgress4FoundBlockData.
.fsReadProgress4FoundBlockData = $f5e2 DEX X=X-1 BNE .fsReadProgress5FinishedReadingBlockData if (.fsReadProgressState > 4) then branch deal with progress state = 4 (found non-zero length block, reading block data) if carrier detected then reset progress to zero BCS .resetACIAAndResetProgress if (carry set) then branch (reset and return) TYA A=Y=byte just read JSR .updateCRC perform CRC LDY .fsTempStorage get length of block read INC .fsTempStorage increment length of block read BIT .fsCharacterJustReadOrCharToWrite check if bit 7 set (meaning searching, not loading) BMI .checkForEndOfBlock if (searching) then branch send byte to second processor if needed, or store byte in regular memory JSR .checkLoadAddressIsForSecondProcessor check load address is ok also puts value of A (the byte just read) into X BEQ + if (load address is not for second processor) then branch STX .tubeULADataRegister3 Write byte to Tube. This triggers an NMI on the co-processor. BNE .checkForEndOfBlock ALWAYS branch + TXA A=X restore value STA (.loadAddressLow),Y store byte at current load address .checkForEndOfBlock = $f600 INY Y=Y+1 CPY .fsBlockLengthLow block length BNE .exit31 if (not equal) then branch (exit) reached end of block data LDA #1 A=1 STA .fsTempStorage loop counter LDY #5 Y=5 BNE .storeTapeProgress ALWAYS branch (store progress and return)
§6. fsReadProgress5FinishedReadingBlockData.
.fsReadProgress5FinishedReadingBlockData = $f60e this first section reads two more bytes (one byte at a time) by using .fsTempStorage (which was initialised to 1 on the previous progress state above) as a loop counter. TYA A=Y=byte read JSR .updateCRC update CRC DEC .fsTempStorage decrement loop counter (1,0) BPL .exit31 exit if loop is not done .resetACIAAndResetProgress = $f616 JSR .resetACIA reset ACIA LDY #0 Y=0 .storeTapeProgress = $f61b STY .fsReadProgressState progress state .exit31 = $f61d RTS
§7. Check for EOF on an open file.
On Entry: X = file handle On Exit: Preserves A,Y X is 0 if EOF reached
.checkEOFEntryPoint = $f61e PHA save A on stack TYA PHA save Y on stack TXA } TAY } Y=X=file handle LDA #3 A=3 (meaning check for file being open for read or write) JSR .checkFileIsOpen confirm file is open (otherwise BRK with 'Channel' error) LDA .fsStatusByte tape / ROM FS status byte AND #%01000000 mask for just the EOF flag TAX X=A PLA get back A TAY Y=A PLA get back A RTS
.searchForFile = $f631 LDA #0 } STA .currentBlockNumberLow } reset current block number STA .currentBlockNumberHigh } .searchForSpecifiedBlock = $f637 LDA .currentBlockNumberLow current block number (low byte) PHA save A on stack STA .nextBlockNumberLow save as next block number (low byte) LDA .currentBlockNumberHigh current block number (high byte) PHA save A on stack STA .nextBlockNumberHigh save as next block number (high byte) JSR .safePrintFollowingMessage print message following call !text "Searching" !text .charRETURN,0 LDA #$FF A=$FF (Meaning 'Must match filename and block number') JSR .searchForBlockCheckFilingSystem read data from tape filing system/ROMFS PLA get back A STA .currentBlockNumberHigh current block number high PLA get back A STA .currentBlockNumberLow current block number low LDA .nextBlockNumberLow next block number low ORA .nextBlockNumberHigh next block number high BNE + STA .currentBlockNumberLow zero current block number low STA .currentBlockNumberHigh zero current block number high LDA .checksumIsValidFlag checksum result BNE + LDX #(.fsFilename-1) - .vduVariablesStart JSR .copyToSoughtFilename copy filename from .fsFilename to .filenameToSearchFor + LDA .tapeOrROMSwitch filing system flag 0=tape filing system; 2=ROMFS BEQ .setBlockFlagsAndExit if (tape filing system active) then branch BVS .setBlockFlagsAndExit if (V set) then branch .fileNotFoundError = $f674 BRK !byte $D6 Error number !text "File not found",0
.setBlockFlagsAndExit = $f685 LDY #$FF Y=$FF STY .fsLastBlockReadFlagsCopy copy of last read block flag RTS
§10. Close a SPOOL / EXEC file.
.closeSpoolOrExecFile = $f68b LDA #0 A=0, Z=0 fall through...
§11. Close EXEC file and optionally open a new EXEC file.
On Entry: A = 0 Z = 0 means close any open EXEC file. Z = 1 means open the exec file whose filename is in address XY.
.closeAndOptionallyOpenANewEXECFile = $f68d PHP save flags on stack STY .tempWorkspaceE6 .tempWorkspaceE6 = Y LDY .execFileHandle file handle of EXEC file to be closed STA .execFileHandle record EXEC file closed (A=0) BEQ + if (closing an exec file) then branch JSR .OSFIND close EXEC file (A=0; Y=file handle) + LDY .tempWorkspaceE6 Y = original Y PLP get back flags BEQ + if (zero flag set on entry) then branch (exit) LDA #$40 A is value for opening an input file JSR .OSFIND open an EXEC file TAY Y=A BEQ .fileNotFoundError If (Y ===0) then branch ('File not found') STA .execFileHandle store EXEC file handle + RTS
.bgetReadBlockAndHeader = $f6ac LDX #(.bgetFilename-1) - .vduVariablesStart JSR .copyToSoughtFilename copy filename from .bgetFilename to .filenameToSearchFor JSR .readBlockHeader read block header. On exit V is clear for last block of ROMFS. .checkLockedFlag = $f6b4 LDA .fsBlockFlagByte block flag LSR move bit 0 into carry to check for a 'locked' file BCC .setLoadAddressAndLoadBlock if (not locked) then branch (skip next instruction) JMP .fileLocked 'locked' file routine
§13. Set Load Address and Load Block.
.setLoadAddressAndLoadBlock = $f6bd LDA .nextBGETBlockLow Expected BGET file block number low STA .currentBlockNumberLow current block number low LDA .nextBGETBlockHigh expected BGET file block number high STA .currentBlockNumberHigh current block number high LDA #<.tapeOrRS423InputBuffer } STA .loadAddressLow } LDA #>.tapeOrRS423InputBuffer } STA .loadAddressMid1 } set load address to $FFFF0A00 LDA #$FF } (i.e. .tapeOrRS423InputBuffer) STA .loadAddressMid2 } STA .loadAddressHigh } JSR .setStateForLoadingBlockDataOrReset set progress to read block data JSR .loadBlock load block from tape BNE .searchForBlock if (non zero) then branch LDA .tapeOrRS423InputBuffer + $FF get last character from input buffer STA .lastCharacterOfCurrentlyResidentBlock last character currently resident block JSR .incrementBlockNumbers inc. current block number STX .nextBGETBlockLow expected BGET file block number low STY .nextBGETBlockHigh expected BGET file block number high LDX #2 X = loop counter - LDA .fsBlockLengthLow,X read bytes from block flag/block length STA .tapeInputCurrentBlockSizeLow,X store into current values of above DEX X=X-1 BPL - until X=-1 ($FF) BIT .blockFlagOfCurrentlyResidentBlock block flag of currently resident block BPL + if (not last block) then branch (don't print newline) JSR .printReturnSafely print newline if needed + JMP .cancelTapeOperationAndMotor
.searchForBlock = $f702 JSR .searchForSpecifiedBlock search for a specified block BNE .checkLockedFlag ALWAYS branch (check for locked condition)
.checkForROMBlockMarker = $f707 CMP #.fsSynchronisationByte check for synchronising byte $2A (at start of first block) BEQ .startOfBlock if (found synchronising byte for first block) then branch CMP #.romFSMiddleBlockHeaderByte check for header byte for a middle block BNE .clearCatalogueStatusBadROM if (middle block header not found) then branch (error 'Bad ROM') INC .fsBlockNumberLow block number BNE + INC .fsBlockNumberHigh block number high + LDX #$FF X=$FF BIT .allBitsSet set N and V flags BNE .clearChecksumTapeProgress ALWAYS branch
§16. clearCatalogueStatusBadROM.
.clearCatalogueStatusBadROM = $f71e LDA #%11110111 } JSR .clearTapeStatusBits } clear current catalogue status [this could just call JSR .clearCatalogueStatus instead of the LDA/JSR instructions above, for a 2 byte saving] .badROMError = $f723 BRK cause error !byte $D7 error number !text "Bad ROM" message !byte 0 terminator
.readTapeBlockHeader = $f72d LDY #$FF JSR .storeFileHandleAndMotorOn switch Motor on LDA #1 A=1 STA .fsReadProgressState start the read progress state JSR .flipRelayOffAndOnThenSetToReadFromTape control serial system - JSR .checkForEscapeDuringCassetteOperation confirm ESC not set and tape filing system not executing LDA #3 A=3 CMP .fsReadProgressState progress state BNE - back until .fsReadProgressState=3 .startOfBlock = $f742 LDY #0 Y=0 JSR .setChecksumBytesToY zero checksum bytes read filename - JSR .readByteFromTapeOrROM get character from file and do CRC BVC .reachedEndOfHeader if (V clear) then branch STA .fsFilename,Y store BEQ + or if A=0 then branch INY Y=Y+1 CPY #11 check Y against 11 BNE - if (Y != 11) then branch (go back for next character) DEY Y=10 + read rest of header LDX #12 X=loop counter (from 12 to 31) to read rest of header - JSR .readByteFromTapeOrROM get character from file and do CRC BVC .reachedEndOfHeader if (V clear) then branch STA .fsFilename,X store byte INX X=X+1 CPX #$1F check X for 31 BNE - if (X is not 31) then loop back .reachedEndOfHeader = $f766 TYA } TAX } X=Y=length of filename LDA #0 A=0 STA .fsFilename,Y store terminator LDA .tapeChecksumLow } ORA .tapeChecksumHigh } Check if the CRC value is zero STA .checksumIsValidFlag Checksum result is zero if valid .clearChecksumTapeProgress = $f773 JSR .zeroChecksumAndFSFlag reset checksum etc STY .fsReadProgressState reset progress (Y=0) TXA A=X=length of filename BNE .exit32 The tape filename is always non-zero in length so: ALWAYS branch (exit) [In which case, RTS would be shorter]
On Exit: V = clear if final block
.readBlockHeader = $f77b LDA .tapeOrROMSwitch filing system flag 0=tape; 2=ROMFS BEQ .readTapeBlockHeader if (tape filing system active) then branch Read ROM file header See NAUG Section 17.5.6, Page 317 for ROM data format - JSR .readByteFromROMFSorPHROM read ROMFS data ROM or PHROM CMP #.romFSFinalBlockHeaderByte check for ROM file terminator BNE .checkForROMBlockMarker if (not terminator) then branch ROM file terminator found LDA #%00001000 A=isolating bit 3 (catalogue status) AND .fsStatusByte check catalogue status BEQ + if (clear) then branch (skip next instruction) JSR .saveFlagsPrintCR print CR safely + JSR .readByteFromROMOrPHROM get byte from data ROM BCC - if (carry clear) then branch (loop back and try again) CLV clear overflow flag RTS
§19. Read byte from tape or ROM.
.readByteFromTapeOrROM = $f797 LDA .tapeOrROMSwitch filing system flag 0=tape; 2=ROMFS BEQ + if (tape filing system active) then branch deal with ROMFS read byte TXA } PHA } TYA } save X and Y PHA } JSR .readByteFromROMFSorPHROM read ROMFS data ROM or PHROM STA .fsCharacterJustReadOrCharToWrite store character just read LDA #$FF Top bit set STA .fsGotACharacterToReadOrWriteFlag note that we have just read a byte set 'flag' to $FF to avoid waiting for byte to be read (c.f. from tape) PLA } TAY } PLA } restore X and Y TAX } + JSR .waitForByteToReadOrWrite check for Escape and loop till bit 7 of FS buffer flag=1 fall through...
A cyclic redundancy check is updated one byte at a time. Pseudo-code is as follows. HL = high and low bytes of the CRC. A = byte on input. H = A EOR H FOR X = 1 TO 8 Carry=bit 7 of H IF (Carry = 1) THEN HL=HL EOR $0810 HL=(HL*2 + Carry) AND $FFFF NEXT X On Entry: A = byte just written or read Preserves A, X and Y
.updateCRC = $f7b0 PHP save flags on stack PHA save A on stack SEC } Set top bit of CRC bit counter ROR .tapeCRCBitCounter } This acts as a loop counter EOR .tapeChecksumHigh } STA .tapeChecksumHigh } H = A EOR H - LDA .tapeChecksumHigh ROL Carry = bit 7 (A = A * 2) BCC + ROR A = A / 2 = H EOR #$08 } STA .tapeChecksumHigh } LDA .tapeChecksumLow } HL=HL EOR $0810 EOR #$10 } STA .tapeChecksumLow } SEC set carry flag + ROL .tapeChecksumLow } ROL .tapeChecksumHigh } HL=(HL*2 + Carry) AND &FFFF LSR .tapeCRCBitCounter shift loop counter right BNE - if (eight bits not processed) then branch PLA get back A PLP get back flags .exit32 = $f7d4 RTS
§21. Set state ready for loading block data (or reset).
Set the character just read to zero, and the progress state to 4. Together this signifies we are ready to load block data. If V is clear, then we instead reset to progress state zero. On Entry: If V clear, then reset to state zero otherwise set to state 4, ready to read the block data bytes
.setStateForLoadingBlockDataOrReset = $f7d5 LDA #0 A=0 fall through...
§22. Set state ready for loading block data or searching (or reset state).
Store the character just read. If all is well (V set and non-zero block length), then progress state is set to 4. Otherwise we revert back to progress state zero. On Entry: A is $FF when searching, $00 when loading V clear means reset progress state to zero .fsBlockLengthLow/High holds the block length
.setCharAThenProgressState4OrReset = $f7d7 STA .fsCharacterJustReadOrCharToWrite store character just read LDX #0 X=0 STX .fsTempStorage BVC + LDA .fsBlockLengthLow block length ORA .fsBlockLengthHigh block length high BEQ + if (block length is zero) then branch LDX #4 X=4 (block length not zero) + STX .fsReadProgressState save progress state RTS
When saving to tape, we record a five second delay before the first block, then a delay specified by the interblock gap for all subsequent blocks. After all blocks are recorded there is a further five second delay.
.saveABlockToTape = $f7ec PHP save flags on stack LDX #3 X=loop counter LDA #0 A=value to store - STA .fsSpareByteA,X clear the four spare bytes of file system block DEX X=X-1 BPL - LDA .fsBlockNumberLow block number ORA .fsBlockNumberHigh block number high BNE .waitForInterblockGap if (block number is not zero) then branch JSR .waitFiveSeconds generate a 5 second delay BEQ + ALWAYS branch .waitForInterblockGap = $f804 JSR .generateInterBlockGapDelay generate delay set by interblock gap initialise and save value $2A to tape + LDA #.fsSynchronisationByte A=synchronisation byte (byte to save to tape) STA .fsCharacterJustReadOrCharToWrite store character to write JSR .zeroChecksumAndFSFlag initialise checksum and file system buffer flag JSR .activateRequestToSend request send to tape JSR .waitForByteToReadOrWrite wait for byte to be saved to tape DEY Y=Y-1 save filename - INY Y=Y+1 LDA .filenameToSearchFor,Y move sought filename STA .fsFilename,Y into filename block JSR .saveByteAToTapeAndUpdateCRC transfer byte to tape filing system and do CRC BNE - if (filename not yet complete) then branch (do it again) save rest of header: load address (4 bytes) execution address (4 bytes) block number (2 bytes) block length (2 bytes) block flag byte (1 byte) spare bytes (4 bytes) LDX #.fsLoadAddressLow - .fsFilename X=offset to load address - LDA .fsFilename,X get filename byte JSR .saveByteAToTapeAndUpdateCRC transfer byte to tape filing system and do CRC INX X=X+1 CPX #.fsChecksumLow - .fsFilename until X reaches the end of the header block (just before the start of the two checksum bytes) BNE - JSR .saveChecksumToTape save checksum to TAPE reset buffer flag LDA .fsBlockLengthLow block length ORA .fsBlockLengthHigh block length high BEQ .saveFinalTwoBytes if (block length is zero) then branch LDY #0 Y=0 JSR .setChecksumBytesToY zero checksum bytes - LDA (.tapeSaveStartAddressLow),Y get a data byte to save JSR .checkLoadAddressIsForSecondProcessor check if load address is from second processor BEQ + if (not from second processor) then branch LDX .tubeULADataRegister3 read byte from second processor + TXA A=X JSR .saveByteAToTapeAndUpdateCRC transfer byte to tape filing system and do CRC INY Y=Y+1 CPY .fsBlockLengthLow check block length BNE - if (block not done yet) then branch (loop back) JSR .saveChecksumToTape save checksum to TAPE .saveFinalTwoBytes = $f855 JSR .waitForByteToReadOrWrite save byte JSR .waitForByteToReadOrWrite save byte JSR .resetACIA reset ACIA LDA #1 A=1 JSR .generateDelay generate 0.1 second delay PLP get back flags JSR .updateBlockFlagAndDisplayProgress update block flag, PRINT filename (and address if reqd) BIT .fsBlockFlagByte block flag BPL + if (this is NOT last block) then branch PHP save flags on stack JSR .waitFiveSeconds generate a 5 second delay JSR .beepCancelAndPrintReturn sound bell and cancel PLP get back flags + RTS
§24. saveByteAToTapeAndUpdateCRC.
.saveByteAToTapeAndUpdateCRC = $f875 JSR .saveByteAToTape save byte JMP .updateCRC update CRC
.saveChecksumToTape = $f87b LDA .tapeChecksumHigh JSR .saveByteAToTape save byte to buffer, transfer to tape filing system and reset flag LDA .tapeChecksumLow fall through...
On Entry: A = character to write On Exit: A is preserved
.saveByteAToTape = $f882 STA .fsCharacterJustReadOrCharToWrite store character to write .waitForByteToReadOrWrite = $f884 JSR .checkForEscapeDuringCassetteOperation confirm ESC not set and tape filing system not executing BIT .fsGotACharacterToReadOrWriteFlag check if we have read / written a byte BPL .waitForByteToReadOrWrite loop back until we have read / written a byte byte is now read / written LDA #0 A=0 STA .fsGotACharacterToReadOrWriteFlag clear flag, we no longer have a byte LDA .fsCharacterJustReadOrCharToWrite get character just read RTS
.waitFiveSeconds = $f892 LDA #50 Set A for 50 tenths of a second delay BNE .generateDelay ALWAYS branch. generate delay 100ms * A (= 5 seconds)
§28. Delay for the (temporary) interblock gap time.
.generateInterBlockGapDelay = $f896 LDA .tapeInterBlockGap get current interblock flag fall through...
§29. Wait for A/10 seconds, or until ESCAPE is pressed.
On Entry: A = amount of delay in 10ths of a second
.generateDelay = $f898 LDX #5 X=loop counter .delayLoop = $f89a STA .verticalSyncCounter set vsync counter - JSR .checkForEscapeDuringCassetteOperation check for ESCAPE BIT .verticalSyncCounter check vsync counter (decremented 50 times a second) BPL - if (not negative) then branch back DEX X-- BNE .delayLoop RTS