; sbfs.asm ; Reading the SplitBit Filesystem. ; ; This belongs to CosmOS, which is the thing that needs it. Programs outside CosmOS may ; include it, and the test programs do, but when CosmOS becomes a repository of its own ; this file goes with it and anything left behind takes a copy. ; ; The other implementation of this format is SplitDisk on the host. Nothing is shared ; between them but the written specification, so anything that changes here has to change ; there in the same breath. ; ; The disk's buffer is registered as bank 3, and every block read lands there and is then ; blitted where it is wanted. The CPU never touches the buffer directly, because nothing ; can: memory a device brings is reachable only through the controller. ; ; Written by Anachronaut #Program ; Registers the disk's buffer, reads the superblock, and checks that the disk really is ; one of ours. Q is zero if the disk is ready to be read. sbfsMount: INIA 0d3 OUTA 0xE3 INIA 0x20 OUTA 0xE2 INIA 0x03 OUTA 0xE8 ; RegisterBank: bank 3 is the disk's buffer. SETD.0 SbfsBlock RSTA STA.0 INCD.0 STA.0 ; Block 0, the superblock. CALL sbfsReadBlock BRQ sbfsMountRead RET ; The read failed, and Q says so. sbfsMountRead: SETD.1 SbfsBuffer CALL sbfsBufferOut ; "SBFS", or this is not a disk of ours. A blank image is all zeroes, so without this ; an unformatted disk would look like a formatted one with no files on it. SETD.0 SbfsBuffer SETD.2 SbfsMagic INIA 0d4 SETD.1 SbfsCount STA.1 sbfsMagicLoop: CALL sbfsSameByte BRQ sbfsMagicSame RET ; Q is the difference, which is not zero. sbfsMagicSame: INCD.0 INCD.2 LDA.1 DECA STA.1 BNA sbfsMagicLoop sbfsMagicDone: ; The version has to be one we understand. SETD.0 SbfsBuffer DPUP.0 0d04 LDA.0 INIB 0d1 XOR BRQ sbfsGeometry RET ; A version we do not know. sbfsGeometry: ; Where the directory is and how big it is. The superblock carries both so that disks ; of different sizes can have different directories without a new format. SETD.0 SbfsBuffer DPUP.0 0d08 SETD.1 SbfsDirStart CALL sbfsCopyWord SETD.0 SbfsBuffer DPUP.0 0d10 SETD.1 SbfsDirBlocks CALL sbfsCopyWord SETD.0 SbfsBuffer DPUP.0 0d06 SETD.1 SbfsDiskBlocks CALL sbfsCopyWord RSTA RSTB CCF ADD ; Q is zero: mounted. RET ; Finds a file by name. DP0 points at a name, ending in a zero byte. Q is zero if it was ; found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe it. sbfsFind: SETD.1 SbfsWanted CALL sbfsKeepName ; Start at the first directory block and work through them all. SETD.0 SbfsDirStart SETD.1 SbfsBlock CALL sbfsCopyWord SETD.0 SbfsDirBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsLeft STA.1 ; Only the low byte: a directory of 256 blocks is 2048 files. sbfsFindBlock: CALL sbfsReadBlock BRQ sbfsFindLoaded RET ; The read failed. sbfsFindLoaded: SETD.1 SbfsBuffer CALL sbfsBufferOut ; Eight entries to a block, thirty two bytes each. SETD.2 SbfsBuffer INIA 0d8 SETD.1 SbfsCount STA.1 sbfsFindEntry: LDA.2 INIB 0x01 AND BRQ sbfsFindNext ; The in use bit is down, so this entry is free. CALL sbfsMatchEntry BRQ sbfsFindFound sbfsFindNext: DPUP.2 0d32 LDA.1 DECA STA.1 BNA sbfsFindEntry sbfsFindNextBlock: SETD.0 SbfsBlock CALL sbfsStepWord SETD.1 SbfsLeft LDA.1 DECA STA.1 BRA sbfsFindMissing BRI sbfsFindBlock sbfsFindMissing: ; Nothing of that name. Q has to be something other than zero to say so. RSTA INIB 0d1 CCF ADD RET sbfsFindFound: ; DP2 is on the entry. Keep it in DP3 while the pieces are taken out of it: DP3 is the ; pointer a CALL does not put back, so it is the only one that survives a subroutine. ; Copying it into DP0 has to be done here rather than in a routine of its own, for the ; same reason: a routine that set DP0 would have the assignment undone by its own RET. PSHD.2 POPD.3 PSHD.3 POPD.0 DPUP.0 0d01 SETD.1 SbfsFileStart CALL sbfsCopyWord PSHD.3 POPD.0 DPUP.0 0d03 SETD.1 SbfsFileBlocks CALL sbfsCopyWord PSHD.3 POPD.0 DPUP.0 0d05 LDA.0 SETD.1 SbfsFileTail STA.1 RSTA RSTB CCF ADD ; Q is zero: found. RET ; ---- Walking the directory ---- ; ; sbfsFind answers a question about one name. Listing what is on a disk is a different ; job: it needs the directory walked rather than searched. sbfsFirst starts a walk and ; sbfsNext steps it, and each of them stops on an entry that is in use, skipping the free ; ones on the way past. ; ; Q is zero while there is an entry to look at. When it is, SbfsName holds the name with ; a zero byte after it, and SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe the ; file exactly the way sbfsFind leaves them. Q is something else when the directory is ; finished, and also when a block could not be read, which are the same answer to the ; question "is there another one" and different answers to "why not". ; ; A walk keeps a directory block in SbfsBuffer between calls, so anything else that goes ; to the disk in the middle of one ends it. Take what is wanted out of an entry before ; asking the disk for anything else. sbfsFirst: SETD.0 SbfsDirStart SETD.1 SbfsWalkBlock CALL sbfsCopyWord SETD.0 SbfsDirBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsWalkLeft STA.1 ; Only the low byte, the same as sbfsFind. ; Nothing is loaded yet. Saying the block in hand is empty makes the scan fetch the ; first one, so there is one way into a block rather than two. RSTA SETD.0 SbfsWalkCount STA.0 BRI sbfsWalkScan sbfsNext: ; The pointer and the count were stepped when the last entry was handed out, so there ; is nothing to do here but carry on looking. BRI sbfsWalkScan sbfsWalkScan: ; Anything left in the block in hand? SETD.0 SbfsWalkCount LDA.0 BNA sbfsWalkEntry ; No. Take the next directory block, if the directory has one. SETD.0 SbfsWalkLeft LDA.0 BRA sbfsWalkEnd DECA STA.0 SETD.0 SbfsWalkBlock SETD.1 SbfsBlock CALL sbfsCopyWord CALL sbfsReadBlock BRQ sbfsWalkLoaded RET ; The read failed, and Q says so. sbfsWalkLoaded: SETD.1 SbfsBuffer CALL sbfsBufferOut ; That one is in hand now, so the walk's block number moves on to the following one. SETD.0 SbfsWalkBlock CALL sbfsStepWord ; Eight entries to a block, starting at the top of the buffer. SETD.0 SbfsBuffer SETD.1 SbfsWalkPointer STD.0.1 INIA 0d8 SETD.0 SbfsWalkCount STA.0 BRI sbfsWalkScan sbfsWalkEntry: ; Where this entry is. Kept in memory rather than in DP3, so that a walk does not ; quietly take the one pointer a caller can carry things across a call in. SETD.1 SbfsWalkPointer LDD.2.1 SETD.1 SbfsWalkAt STD.2.1 ; Step past it now, so that the walk has moved on whether this one is taken or skipped. ; Getting that wrong in only one of the two paths is how a walk repeats an entry ; forever, and it repeats the interesting ones rather than the boring ones. SETD.0 SbfsWalkCount LDA.0 DECA STA.0 PSHD.2 POPD.0 DPUP.0 0d32 SETD.1 SbfsWalkPointer STD.0.1 ; Is it a file? A free entry is not one, and there can be free entries in the middle of ; a directory, so this is a skip rather than a stop. LDA.2 INIB 0x01 AND BRQ sbfsWalkScan CALL sbfsWalkTake RSTA RSTB CCF ADD ; Q is zero: here is an entry. RET sbfsWalkEnd: RSTA INIB 0d1 CCF ADD ; Q is one: the directory is finished. RET ; Takes the pieces out of the entry the walk stopped on. Everything lands in memory, ; because that is the only place a subroutine can leave anything. sbfsWalkTake: SETD.1 SbfsWalkAt LDD.0.1 DPUP.0 0d01 SETD.1 SbfsFileStart CALL sbfsCopyWord SETD.1 SbfsWalkAt LDD.0.1 DPUP.0 0d03 SETD.1 SbfsFileBlocks CALL sbfsCopyWord SETD.1 SbfsWalkAt LDD.0.1 DPUP.0 0d05 LDA.0 SETD.1 SbfsFileTail STA.1 ; The name. Twenty two bytes of it, padded with zeroes rather than terminated, so it is ; copied into a buffer with a twenty third byte that nothing ever writes. That byte is ; what makes a name that fills the field into a string that can be printed. SETD.1 SbfsWalkAt LDD.0.1 DPUP.0 0d06 SETD.1 SbfsName INIA 0d22 SETD.2 SbfsCount STA.2 sbfsWalkName: LDA.0 STA.1 INCD.0 INCD.1 SETD.2 SbfsCount LDA.2 DECA STA.2 BNA sbfsWalkName RET ; Compares the name in the entry at DP2 with the one kept in SbfsWanted. Q is zero if ; they are the same. Names are padded with zeroes rather than terminated, so a name that ; fills the field has no terminator to look for, which is why the count is what stops it. sbfsMatchEntry: PSHD.2 POPD.0 DPUP.0 0d06 ; The name inside the entry. SETD.2 SbfsWanted INIA 0d22 SETD.1 SbfsCount STA.1 sbfsMatchLoop: CALL sbfsSameByte BRQ sbfsMatchSame RET ; They differ, and Q is the difference. sbfsMatchSame: LDA.0 BRA sbfsMatchYes ; Both ended in the same place. INCD.0 INCD.2 LDA.1 DECA STA.1 BNA sbfsMatchLoop ; Twenty two the same is the same name, so falling out is a match. sbfsMatchYes: RSTA RSTB CCF ADD RET ; Copies the name at DP0 into DP1, twenty two bytes of it, padding with zeroes the way an ; entry is padded so that the two can be compared as they stand. sbfsKeepName: INIA 0d22 SETD.2 SbfsCount STA.2 RSTB ; B stays zero once the name has ended. sbfsKeepLoop: LDA.0 BRA sbfsKeepPad STA.1 INCD.0 BRI sbfsKeepStep sbfsKeepPad: STB.1 sbfsKeepStep: INCD.1 LDA.2 DECA STA.2 BNA sbfsKeepLoop sbfsKeepDone: RET ; Reads the file that sbfsFind found into Data Memory at DP1. Q is zero if it worked. ; ; Whole blocks are copied whole, including the last one, so the bytes after the end of a ; file are whatever else was in that block on the disk. SbfsFileTail says where the file ; actually stops, and it is the caller's business to respect it. ; ; A file of more than 255 blocks is not handled here. That is 64K, which is the whole of ; Data Memory, so it could not be read into it anyway. sbfsRead: PSHD.1 POPD.3 ; Where it goes. DP3 survives a CALL, and nothing called here ; touches it. SETD.0 SbfsFileStart SETD.1 SbfsBlock CALL sbfsCopyWord SETD.0 SbfsFileBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsLeft STA.1 SETD.0 SbfsFileTail LDA.0 BRA sbfsReadCounted ; No part block on the end. SETD.0 SbfsLeft LDA.0 INCA STA.0 ; One more block for the tail. sbfsReadCounted: SETD.0 SbfsLeft LDA.0 BRA sbfsReadDone ; An empty file has nothing to read. sbfsReadLoop: CALL sbfsReadBlock BRQ sbfsReadGot RET ; The read failed, and Q says so. sbfsReadGot: PSHD.3 POPD.1 CALL sbfsBufferOut ; On by a whole block. DPUP carries one byte at a time, so 256 takes two of them. DPUP.3 0xFF DPUP.3 0x01 SETD.0 SbfsBlock CALL sbfsStepWord SETD.0 SbfsLeft LDA.0 DECA STA.0 BNA sbfsReadLoop sbfsReadDone: RSTA RSTB CCF ADD ; Q is zero: read. RET ; ---- Writing ---- ; Where the first block that can hold a file is: past the superblock and the directory. sbfsFirstData: SETD.0 SbfsCandidate SETD.2 SbfsDirStart CALL sbfsSetWord SETD.0 SbfsCandidate SETD.2 SbfsDirBlocks CALL sbfsAddWord RET ; SbfsEntryStart and SbfsEntryEnd describe the file in the entry at DP2. The end is one ; past the last block it holds, and a file of nothing holds none, so its end is its start. sbfsEntryBounds: PSHD.2 POPD.3 PSHD.3 POPD.0 DPUP.0 0d01 SETD.1 SbfsEntryStart CALL sbfsCopyWord SETD.0 SbfsEntryEnd SETD.2 SbfsEntryStart CALL sbfsSetWord PSHD.3 POPD.0 DPUP.0 0d03 SETD.1 SbfsScratch CALL sbfsCopyWord SETD.0 SbfsEntryEnd SETD.2 SbfsScratch CALL sbfsAddWord PSHD.3 POPD.0 DPUP.0 0d05 LDA.0 BRA sbfsBoundsDone ; No part block on the end. SETD.0 SbfsEntryEnd SETD.2 SbfsOne CALL sbfsAddWord sbfsBoundsDone: RET ; Finds a run of SbfsWantBlocks free blocks and puts where it begins in SbfsFileStart. ; Q is zero if there was room. ; ; First fit, walking the directory, because with files laid down contiguously the ; directory already says which blocks are spoken for. There is no allocation table to ; consult and none to keep right. sbfsAllocate: CALL sbfsFirstData sbfsAllocTry: SETD.0 SbfsCandEnd SETD.2 SbfsCandidate CALL sbfsSetWord SETD.0 SbfsCandEnd SETD.2 SbfsWantBlocks CALL sbfsAddWord ; Past the end of the disk means there is nowhere left to look. SETD.0 SbfsDiskBlocks SETD.2 SbfsCandEnd CALL sbfsCompareWord BRC sbfsAllocNoRoom ; The disk is smaller than where this run would end. SETD.0 SbfsDirStart SETD.1 SbfsBlock CALL sbfsCopyWord SETD.0 SbfsDirBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsLeft STA.1 sbfsAllocBlock: CALL sbfsReadBlock BNQ sbfsAllocFailed SETD.1 SbfsBuffer CALL sbfsBufferOut SETD.2 SbfsBuffer INIA 0d8 SETD.1 SbfsCount STA.1 sbfsAllocEntry: LDA.2 INIB 0x01 AND BRQ sbfsAllocNext ; A free entry holds nothing, so it is in nobody's way. CALL sbfsEntryBounds ; The comparisons need DP2 for their own purposes, so where this entry is goes on the ; Stack for the duration. Working it out again afterwards was the first way this was ; written, and it was both longer and wrong. PSHD.2 ; Two runs overlap when each begins before the other ends. SETD.0 SbfsCandidate SETD.2 SbfsEntryEnd CALL sbfsCompareWord BNC sbfsAllocClear SETD.0 SbfsEntryStart SETD.2 SbfsCandEnd CALL sbfsCompareWord BNC sbfsAllocClear ; They do overlap, so try again from the far end of whatever is in the way. POPD.2 SETD.0 SbfsCandidate SETD.2 SbfsEntryEnd CALL sbfsSetWord BRI sbfsAllocTry sbfsAllocClear: POPD.2 sbfsAllocNext: DPUP.2 0d32 SETD.1 SbfsCount LDA.1 DECA STA.1 BNA sbfsAllocEntry SETD.0 SbfsBlock CALL sbfsStepWord SETD.1 SbfsLeft LDA.1 DECA STA.1 BNA sbfsAllocBlock ; Nothing was in the way, so this is where it goes. SETD.0 SbfsFileStart SETD.2 SbfsCandidate CALL sbfsSetWord RSTA RSTB CCF ADD RET sbfsAllocNoRoom: sbfsAllocFailed: RSTA INIB 0d1 CCF ADD RET ; The two byte number at DP0 becomes the one at DP2. Written destination first, so that a ; call reads the way an assignment does. sbfsCopyWord goes the other way, source first, ; which is a difference worth keeping in mind: getting it backwards here quietly destroyed ; a value that had just been worked out. sbfsSetWord: LDA.2 STA.0 INCD.2 INCD.0 LDA.2 STA.0 RET ; The two byte number at DP0 becomes itself less the one at DP2. sbfsSubWord: DPUP.0 0d01 DPUP.2 0d01 LDA.0 LDB.2 CCF SUB MVQA STA.0 DPDN.0 0d01 DPDN.2 0d01 LDA.0 LDB.2 SUB ; Borrows in from the low half. MVQA STA.0 RET ; How many blocks a file of SbfsFileBlocks and SbfsFileTail actually occupies, into ; SbfsWantBlocks. A file of nothing occupies none. sbfsFileExtent: SETD.0 SbfsWantBlocks SETD.2 SbfsFileBlocks CALL sbfsSetWord SETD.0 SbfsFileTail LDA.0 BRA sbfsExtentDone SETD.0 SbfsWantBlocks SETD.2 SbfsOne CALL sbfsAddWord sbfsExtentDone: RET ; Makes a file. DP0 names it, and SbfsFileBlocks with SbfsFileTail say how big it is, ; which has to be settled before it is made because nothing here can grow one afterwards. ; Q is zero if it was made, and then SbfsFileStart says where its blocks are. sbfsCreate: SETD.1 SbfsWanted CALL sbfsKeepName CALL sbfsFileExtent CALL sbfsAllocate BNQ sbfsCreateFailed ; Walk the directory for an entry nobody is using. SETD.0 SbfsDirStart SETD.1 SbfsBlock CALL sbfsCopyWord SETD.0 SbfsDirBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsLeft STA.1 sbfsCreateBlock: CALL sbfsReadBlock BNQ sbfsCreateFailed SETD.1 SbfsBuffer CALL sbfsBufferOut SETD.2 SbfsBuffer INIA 0d8 SETD.1 SbfsCount STA.1 sbfsCreateEntry: LDA.2 INIB 0x01 AND BRQ sbfsCreateFill ; This one is free. DPUP.2 0d32 SETD.1 SbfsCount LDA.1 DECA STA.1 BNA sbfsCreateEntry SETD.0 SbfsBlock CALL sbfsStepWord SETD.1 SbfsLeft LDA.1 DECA STA.1 BNA sbfsCreateBlock sbfsCreateFull: RSTA INIB 0d1 CCF ADD ; Every entry is taken. RET sbfsCreateFill: ; DP2 is on the entry. Fill it in, then put the whole block back on the disk. PSHD.2 POPD.3 INIA 0x01 STA.3 ; In use. PSHD.3 POPD.1 DPUP.1 0d01 SETD.0 SbfsFileStart CALL sbfsCopyWord PSHD.3 POPD.1 DPUP.1 0d03 SETD.0 SbfsFileBlocks CALL sbfsCopyWord PSHD.3 POPD.1 DPUP.1 0d05 SETD.0 SbfsFileTail LDA.0 STA.1 PSHD.3 POPD.1 DPUP.1 0d06 SETD.0 SbfsWanted INIA 0d22 SETD.2 SbfsCount STA.2 sbfsCreateName: LDA.0 STA.1 INCD.0 INCD.1 LDA.2 DECA STA.2 BNA sbfsCreateName SETD.1 SbfsBuffer CALL sbfsBufferIn CALL sbfsWriteBlock BNQ sbfsCreateFailed ; The free count is a note rather than the truth, but it should still be kept right. RSTA SETD.0 SbfsBlock STA.0 INCD.0 STA.0 CALL sbfsReadBlock BNQ sbfsCreateFailed SETD.1 SbfsBuffer CALL sbfsBufferOut SETD.0 SbfsBuffer DPUP.0 0d12 SETD.2 SbfsWantBlocks CALL sbfsSubWord SETD.1 SbfsBuffer CALL sbfsBufferIn CALL sbfsWriteBlock RET sbfsCreateFailed: RSTA INIB 0d1 CCF ADD RET ; Writes a file that sbfsCreate made, from Data Memory at DP1. Q is zero if it worked. ; ; Whole blocks go out whole, so whatever follows the file in Data Memory ends up in the ; unused tail of its last block. That is harmless, since the tail in the entry says where ; the file stops, but it does mean a block can hold a little of whatever was next to it. sbfsWriteFile: PSHD.1 POPD.3 SETD.0 SbfsBlock SETD.2 SbfsFileStart CALL sbfsSetWord CALL sbfsFileExtent SETD.0 SbfsWantBlocks DPUP.0 0d01 LDA.0 SETD.1 SbfsLeft STA.1 BRA sbfsWriteDone ; A file of nothing has nothing to write. sbfsWriteLoop: PSHD.3 POPD.1 CALL sbfsBufferIn CALL sbfsWriteBlock BNQ sbfsWriteFailed DPUP.3 0xFF DPUP.3 0x01 SETD.0 SbfsBlock CALL sbfsStepWord SETD.0 SbfsLeft LDA.0 DECA STA.0 BNA sbfsWriteLoop sbfsWriteDone: RSTA RSTB CCF ADD RET sbfsWriteFailed: RSTA INIB 0d1 CCF ADD RET ; Copies a whole block from Data Memory at DP1 into the disk's buffer, which is the other ; way round from sbfsBufferOut. sbfsBufferIn: INIA 0d1 OUTA 0xE0 PSHD.1 POPA POPB OUTB 0xE1 OUTA 0xE2 INIA 0d3 OUTA 0xE3 RSTA OUTA 0xE4 OUTA 0xE5 INIA 0x01 OUTA 0xE6 RSTA OUTA 0xE7 INIA 0x01 OUTA 0xE8 RET ; Puts the disk's buffer down as the block named by SbfsBlock. Q is zero if it worked. sbfsWriteBlock: SETD.0 SbfsBlock LDA.0 OUTA 0x20 INCD.0 LDA.0 OUTA 0x21 INIA 0x02 OUTA 0x22 INA 0x23 INIB 0x02 AND RET ; Reads the block named by SbfsBlock into the disk's buffer. Q is zero if it worked. sbfsReadBlock: SETD.0 SbfsBlock LDA.0 OUTA 0x20 INCD.0 LDA.0 OUTA 0x21 INIA 0x01 OUTA 0x22 INA 0x23 INIB 0x02 AND ; Q is the error bit, so zero means it worked. RET ; Copies the disk's buffer, a whole block of it, to Data Memory at DP1. sbfsBufferOut: INIA 0d3 OUTA 0xE0 RSTA OUTA 0xE1 OUTA 0xE2 INIA 0d1 OUTA 0xE3 PSHD.1 POPA POPB OUTB 0xE4 OUTA 0xE5 INIA 0x01 OUTA 0xE6 RSTA OUTA 0xE7 ; A whole block. INIA 0x01 OUTA 0xE8 RET ; Two bytes from DP0 to DP1, most significant first, the way every number on a SplitBit ; disk is stored. sbfsCopyWord: LDA.0 STA.1 INCD.0 INCD.1 LDA.0 STA.1 RET ; Adds one to the two byte number at DP0. sbfsStepWord: DPUP.0 0d01 LDA.0 INCA STA.0 BNC sbfsStepDone ; It did not wrap, so the high byte is untouched. DPDN.0 0d01 LDA.0 INCA STA.0 sbfsStepDone: RET ; The two byte number at DP0 becomes itself plus the one at DP2. Only memory changes, so ; it survives the return: a routine cannot hand back a pointer or a register. sbfsAddWord: DPUP.0 0d01 DPUP.2 0d01 LDA.0 LDB.2 CCF ADD MVQA STA.0 DPDN.0 0d01 DPDN.2 0d01 LDA.0 LDB.2 ADD ; Carries in from the low half. Nothing between the two touches it. MVQA STA.0 RET ; Compares the two byte number at DP0 with the one at DP2. Q is zero if they are equal, ; and the Carry Flag is set if the one at DP0 is the smaller. Both come back, because ; neither Q nor the Status register is put back by a return. sbfsCompareWord: LDA.0 LDB.2 CCF SUB ; The high bytes settle it unless they are the same. BNQ sbfsCompareDone INCD.0 INCD.2 LDA.0 LDB.2 CCF SUB sbfsCompareDone: RET ; The byte at DP0 against the byte at DP2. Q is zero if they are the same. sbfsSameByte: LDA.0 LDB.2 XOR RET #Data SbfsMagic: "SBFS" SbfsDirStart: 0x00 0x00 SbfsDirBlocks: 0x00 0x00 SbfsFileStart: 0x00 0x00 SbfsFileBlocks: 0x00 0x00 SbfsFileTail: 0x00 SbfsBlock: 0x00 0x00 SbfsDiskBlocks: 0x00 0x00 SbfsWantBlocks: 0x00 0x00 SbfsCandidate: 0x00 0x00 SbfsCandEnd: 0x00 0x00 SbfsEntryStart: 0x00 0x00 SbfsEntryEnd: 0x00 0x00 SbfsScratch: 0x00 0x00 SbfsOne: 0x00 0x01 SbfsCount: 0x00 SbfsLeft: 0x00 ; ---- What a walk through the directory keeps between calls ---- SbfsWalkBlock: 0x00 0x00 SbfsWalkLeft: 0x00 SbfsWalkCount: 0x00 SbfsWalkPointer: 0x00 0x00 SbfsWalkAt: 0x00 0x00 ; Twenty three bytes for a name of twenty two, so that the last one is a zero nothing ; ever writes over and the name is always a string. SbfsName: #Reserve 0d23 SbfsWanted: #Reserve 0d22 SbfsBuffer: #Reserve 0d256