LDOS 5.3.1 for the Model III - SYS8/SYS Disassembled

Page Customization

Introduction/Summary

LDOS 5.3.1 SYS8/SYS Disassembly - @ALLOC, Dynamic File Space Allocation (Model III)

SYS8/SYS gives an open disk file its space while the file is written. SYS0/SYS keeps up to five extents of a file in its control block; when the record it positions to lies in a granule that none of them holds, it issues request 9AH (its 4AB3H, called from 4A98H) and the supervisor dispatcher loads SYS8/SYS into the overlay area at 4E00H and calls it with Index Register IX = the file control block and Register Pair BC = the granule wanted, counted from the file's first granule (0). SYS8/SYS reads the file's directory records and counts the granules of their extents up to the one wanted; when the file has fewer granules, it takes the missing ones from the granule allocation table, writes the table and the directory record back, and counts again. The manual says only that this overlay is needed to allocate file space when files are written and must be on every working system diskette. Request 8AH (function 00H) loads SYS8/SYS and returns at once (4E00H).

The file is a load module of 735 bytes: a record of type 1FH with the text Copyright 1991 MISOSYS, Inc., All rights reserved, three load blocks for 4E00H-5095H, a load block for the two bytes 4BC8H-4BC9H (0296H, the 662 bytes that SYS8/SYS takes up in the overlay area) and the transfer address record for 4E00H.

What @ALLOC Returns

SYS8/SYS returns to SYS0/SYS at 4A9BH, after the CALL 4AB3H (the dispatcher drops the return address of the RST 28H itself).

RegisterContents
Z FLAGSet on success with Register A = the granule's offset in its extent (0-31). On failure the NZ FLAG with the error number in Register A: 11H directory read error, 12H directory write error or 0FH write protected (from DIRWR), 14H GAT read error, 15H GAT write error, 16H HIT read error, 17H HIT write error, 1BH disk space full, 1EH directory full.
DEThe file's granule where the extent starts.
NO CARRY FLAGA slot of the control block held the extent and was brought up to date in place (its granule byte, whose count may have grown); Register Pair HL = the extent: Register H its granule byte, Register L its cylinder (4E45H).
CARRY FLAGNo slot held the extent: the second to fourth slots have moved on one slot and the fifth is dropped (4E34H); Register Pair BC = the extent (Register B the granule byte, Register C the cylinder) and Register Pair HL = the granule byte of the second slot, which SYS0/SYS (4AD5H) fills with the extent and its first granule.

The extent slots of the file control block are five groups at +0EH-+1FH: a cylinder byte and a granule byte at +0EH, +12H, +16H, +1AH and +1EH, the word before each of the last four holding the file's granule where that extent starts (the first slot starts at granule 0).

Extents and Directory Records

The space of a file is described by the extents of its directory record, the record whose number is at +07H of the file control block (+06H holds the drive). Directory record n is the 32 byte slot n AND E0H of sector (n AND 1FH) + 2 of the directory cylinder; DIRRD (4B10H) reads that sector into 4300H.

BytesWhat SYS8/SYS reads or writes
+00HThe attributes. An extension record SYS8/SYS makes gets 90H: bit 7 an extension record, bit 4 a record in use (4FAAH).
+01HIn an extension record: the number of the record it extends.
+16H-+1DHFour extents of two bytes: a cylinder byte, then a granule byte whose bits 7-5 are the extent's first granule on that cylinder and bits 4-0 the number of its granules less one, so an extent holds 1-32 granules. A cylinder byte of FFH is an extent not in use, the end of the file's space.
+1EH-+1FHFEH and the number of an extension record where the file's extents go on, or FFH when there is none.

An extent may run on over the end of its cylinder: its granules are counted on from its first granule over the granules of a cylinder (4ED9H). The granules of a cylinder are bits 7-5 of byte +08H of the drive code table plus one, doubled when bit 5 of byte +04H says the drive has two sides (5033H).

How Space Is Found

The granule allocation table, sector 0 of the directory cylinder, is read into 5100H-51FFH: byte 5100H + c is cylinder c (00H-CAH), and bit n set is granule n of that cylinder in use. The number of granules to allocate is the granule wanted less the granules the file has, plus one. For each one (4ED0H):

  • When the last extent in use holds fewer than 32 granules and the granule after it is free, that granule is taken and the extent's count goes up by one, so a file written on its own stays in one extent.
  • Otherwise the next extent of the record is started (4F24H): the table is searched for a cylinder whose byte is not FFH, from the cylinder at 4767H (01H: nothing changes it) to the drive's last cylinder, then once more from cylinder 0, and the first free granule of that cylinder starts the extent. No free granule anywhere is error 1BH, disk space full, after the granules taken so far are written back.
  • When the four extents of the record are full, the table and the record are written back and an extension record is made (4F86H): the hash index table (sector 1) is read into 4300H, a free record is found from the column of the file's own record on (4FDEH), given the file's hash byte so that it counts as in use, and built with no extents; the record it extends gets FEH and its number at +1EH-+1FH. No free record is error 1EH, directory full.

When the granules are all taken the table is written back with the deleted data mark and verified (4F7AH), then the directory sector of the record read last, and the extents are counted again from the file's first record. The search for a new extent starts at a fixed cylinder; the code at 4EABH still builds a random number from the refresh register and TIMER$ (4288H) and fetches the number of cylinders, which this release does not use: the 5.1.4 update notes give a patch (for the Model I D5 CD C1 44 D1 6C; on the Model III, where @MULT is 444EH, D5 CD 4E 44 D1 6C) that makes the search start at a random cylinder again, and in this file it fits over 4EBEH-4EC3H.

About This Page

The Model III file is the Model I file of the same release with the same code at the same addresses; its 11 different bytes are operands that name the Model III resident area: TIMER$ 4288H (4EAFH; Model I 4040H), DIVEA 4B7AH (4EF5H), MULTEA 4B6BH (4FEFH), DIRCYL 4B64H (5041H, 5054H, 506DH, 507FH; on the Model I these routines are one byte higher), the system sector buffer 4300H (5072H, 5084H; Model I 4200H), and the overlay length block at 4BC8H-4BC9H (Model I 4BC9H-4BCAH).

Every byte of the file is shown, in address order: the load block at 4BC8H first, then 4E00H-5095H. SYS8/SYS changes seven of its own bytes while it runs, all operands (the table of Variables): the offset it returns (4E4AH), the number of cylinders (4F36H), the record read last (4F82H), the new record (4FD9H), the number of columns of the hash index table (5008H), and the bit numbers of a BIT n,B and a SET n,A (5025H, 5031H).

Memory Map

Load Blocks in File Order

The four load blocks of the file in the order the overlay loader (4C8CH) reads them, after the copyright record. The transfer address is 4E00H.

AddressContents
4E00H-4EFFH256 bytes
4F00H-4FFFH256 bytes
5000H-5095H150 bytes
4BC8H-4BC9H2 bytes

Sections of the Disassembly

AddressContents
4BC8HThe Overlay Length (Data)
4E00H@ALLOC: Entry and the Extent Slots of the File Control Block
4E4CHWalk the File's Extents
4E97HAllocate: The Granules Missing, Then Grow the Last Extent
4F24HA New Extent at the First Free Granule; Allocate One Granule
4F86HMake and Link an Extension Record
4FDEHFind a Free Directory Record in the Hash Index Table
501AHTest and Set a Granule's Bit
5033HDouble a Count for a Drive with Two Sides
503FHRead and Write the Granule Allocation Table and the Hash Index Table

Memory SYS8/SYS Uses

AddressContents
4288HTIMER$, read for the random number that this release does not use (4EAFH).
4300H-43FFHThe system sector buffer: the directory sector that DIRRD reads (4E5AH, 4FA6H, 4FCDH), and the hash index table (4F89H).
444EH@MULT, not called in this release: the 5.1.4 patch for a random start calls it at 4EBFH.
4767HThe fifth byte of WRSECT, 01H: the cylinder where the search for a free granule starts (4EBFH).
4768H, 4772HWRPROT and VERSEC: write a system sector with the deleted data mark and verify it (505CH, 5061H, 5087H, 508CH).
479CHDCTBYT: a byte of the drive code table: +04H (5036H), +06H (4EB9H, 4F2AH), +07H (4FE1H), +08H (4EE8H).
4B10H, 4B1FHDIRRD and DIRWR: read and write the directory sector of a record (4E5AH, 4F83H, 4FA6H, 4FC5H, 4FCDH, 4FDAH).
4B45H, 4B64HRDSSEC and DIRCYL: read a sector of the directory cylinder (5049H, 5075H) and find that cylinder.
4B6BH, 4B7AHMULTEA and DIVEA: 8 bit multiply (4FEFH) and divide (4EF5H).
4BC8H-4BC9HThe length of the system file in the overlay area: 0296H for SYS8/SYS.
4E00H-5095HSYS8/SYS in the overlay area.
5100H-51FFHThe granule allocation table of the file's drive, read and written by SYS8/SYS (503FH, 5052H).

Variables

The entry points of SYS8/SYS, then the data of the file, the operands it changes while it runs, and the cells outside the file that it uses, by address.

Entry Points

AddressNameContents
4E00H@ALLOCRequest 9AH: find the extent that holds granule Register Pair BC of the file at Index Register IX, allocating space first when needed, and bring the control block's extent slots up to date.
4E4CHCount the granules of the file's extents to the one wanted; allocate and count again when the file is too short.
4E97HAllocate the granules missing: grow the last extent, or start the next one.
4F24HStart an extent at the first free granule; 4F60H takes one granule into an extent.
4F7AHWrite the allocation table and the directory sector of the record read last.
4F86HMake an extension record and link it.
4FDEHFind a free directory record in the hash index table (5004H: search from Register Pair HL).
501AHTest bit Register A of Register B; 5027H: Register A = the bit for granule Register A.
5033HDouble Register A for a drive with two sides.
503FHRead the granule allocation table into 5100H; 5052H write it.
506BHRead the hash index table into 4300H; 507DH write it.

Data in the File

Address
Size
Contents
4BC8H-4BC9H
2 bytes
DEFW 0296H
4BC8H-4BC9H: 0296H (662), the number of bytes from 4E00H that SYS8/SYS takes up (4E00H-5095H). Loading SYS8/SYS puts it over the length the system file loaded before it left here.

Operands Changed While It Runs

Address
Size
Contents
4E4AH
1 byte
The operand of LD A,00H at 4E49H: the granule's offset in its extent, stored by 4E12H and returned in Register A.
4F36H
1 byte
The operand of CP 00H at 4F35H: the number of cylinders of the drive, stored by 4F2FH; the search for a free granule stops there.
4F82H
1 byte
The operand of LD B,00H at 4F81H: the number of the directory record read last by the walk (the file's record or an extension record), stored by 4E54H; written back by 4F81H, and read by 4FAEH and 4FC9H as the record an extension record extends.
4FD9H
1 byte
The operand of LD (HL),00H at 4FD8H: the number of the new extension record, stored by 4F9AH, put in +1FH of the record it extends.
5008H
1 byte
The operand of CP 00H at 5007H: the number of columns of the hash index table in use, the directory sectors less 2, stored by 4FF8H.
5025H
1 byte
The second byte of BIT 0,B at 5024H (40H in the file): 40H + 8n, stored by 5021H, makes it BIT n,B.
5031H
1 byte
The second byte of SET 0,A at 5030H (C7H in the file): C7H + 8n, stored by 502CH, makes it SET n,A.

Cells Outside the File That SYS8/SYS Uses

Address
Size
Contents
IX +06H
1 byte
The file control block's drive, read into Register C for every disk call (4E57H, 4E98H and on).
IX +07H
1 byte
The number of the file's directory record, where the walk starts (4E50H); its column in the hash index table is where the search for a free record starts (4F8DH) and its byte there is the hash the extension record gets (4F9EH).
IX +0EH-+1FH
18 bytes
The five extent slots of the file control block, brought up to date at 4E45H or moved on at 4E3DH.
4288H
1 byte
TIMER$, the count of the timer interrupts: the high byte of a random number this release does not use (4EAFH).
4300H-43FFH
256 bytes
The system sector buffer: a directory sector (record at Register Pair HL from DIRRD), or the hash index table, one byte per record, 00H for a free record.
4767H
1 byte
01H, the first cylinder of the search for a free granule (4EBFH).
5100H-51FFH
256 bytes
The granule allocation table: byte 5100H + c for cylinder c (00H-CAH), bit n set when granule n is in use.

Disassembly

4BC8H - The Overlay Length (Data)

Every system file from SYS1/SYS to SYS12/SYS carries a load block for the two bytes 4BC8H-4BC9H in the resident area of SYS0/SYS. They hold the length of the file's part in the overlay area: for SYS8/SYS 0296H, the 662 bytes 4E00H-5095H. In the file this block is the last load block, after the three blocks of 4E00H-5095H and before the transfer address record (4E00H). The two bytes are data, not instructions.

4BC8-4BC9
DEFW 0296H 96 02
4BC8H-4BC9H: 0296H (662), the number of bytes from 4E00H that SYS8/SYS takes up (4E00H-5095H). Loading SYS8/SYS puts it over the length the system file loaded before it left here.

4E00H - @ALLOC: Entry and the Extent Slots of the File Control Block

SYS0/SYS issues request 9AH (its 4AB3H) when the record it positions to lies in a granule that none of the five extent slots of the file control block holds; the supervisor dispatcher loads SYS8/SYS into 4E00H and calls 4E00H with Index Register IX = the file control block and Register Pair BC = the file's granule wanted (counted from 0). 4E4CH finds the extent that holds that granule in the file's directory records, giving the file more granules first when it has too few. The slots of the control block are five pairs of a cylinder byte and a granule byte at +0EH, +12H, +16H, +1AH and +1EH, with the starting granule of the second to fifth slot in the word before each pair (the first slot starts at granule 0). The slot that holds the extent gets the extent's granule byte again (its count may have grown); when no slot holds it, the second to fourth slots move on by one slot, the fifth is dropped, and SYS0/SYS (4AD5H) fills the second slot. Request 8AH (function 00H), which COPY issues, only loads the file and returns.

4E00
AND 70H E6 70
Keep bits 6-4 of Register A, the request code (AND 70H): the function, 10H for request 9AH (@ALLOC), 00H for request 8AH.
4E02
CP 10H FE 10
Compare Register A (the function) against 10H, the only function SYS8/SYS carries out.
4E04
If the Z FLAG is set (function 10H), JUMP to 4E07H to find or allocate the granule wanted.
4E06
RET C9
RETurn for any other function (request 8AH loads SYS8/SYS into the overlay area and nothing more) with the NZ FLAG.
4E07
GOSUB to 4E4CH to find the extent that holds granule Register Pair BC of the file, allocating granules when needed: Register Pair HL = the extent (Register H its granule byte, Register L its cylinder), Register Pair DE = the file's granule where the extent starts.
4E0A
RET NZ C0
RETurn if the NZ FLAG is set (a read or write error, the disk full or the directory full) with the error number in Register A.
4E0B
PUSH HL E5
Save Register Pair HL (the extent) to the stack while Register Pair HL computes the offset.
4E0C
LD H,B 60
Copy Register B (the high byte of the granule wanted) into Register H.
4E0D
LD L,C 69
Copy Register C (the low byte of the granule wanted) into Register L: Register Pair HL = the granule wanted.
4E0E
XOR A AF
Set Register A to 00H (XOR A), which clears the CARRY FLAG for the SBC below.
4E0F
SBC HL,DE ED 52
SUBtract Register Pair DE (the extent's first granule) from Register Pair HL: the granule's offset in its extent, 0-31.
4E11
LD A,L 7D
Copy Register L (the offset in the extent) into Register A.
4E12
LD (4E4AH),A 32 4A 4E
Store Register A (the offset) at 4E4AH, the operand of the LD A at 4E49H, which returns it to SYS0/SYS.
4E15
POP HL E1
Restore Register Pair HL (the extent: Register H the granule byte, Register L the cylinder) from the stack.
4E16
PUSH DE D5
Save Register Pair DE (the extent's first granule) to the stack: 4E48H returns it in Register Pair DE.
4E17
PUSH IX DD E5
Save Index Register IX (the address of the file control block) to the stack.
4E19
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack (the Stack Pointer is not changed): Register Pair HL = the file control block and the extent goes on the stack.
4E1A
LD DE,000EH 11 0E 00
Load Register Pair DE with 000EH, the offset of the first extent slot's cylinder byte in the control block.
4E1D
ADD HL,DE 19
ADD Register Pair DE to Register Pair HL: Register Pair HL = the control block +0EH, the cylinder byte of the first slot.
4E1E
POP DE D1
Restore the extent into Register Pair DE from the stack: Register D = its granule byte, Register E = its cylinder.
4E1F
LD B,05H 06 05
Load Register B with 05H, the count of the five extent slots of the control block to look at.
4E21
LD A,(HL) 7E
Fetch the slot's cylinder byte at Register Pair HL into Register A. 4E32H loops back here for the next slot.
4E22
INC HL 23
INCrement Register Pair HL to the slot's granule byte, the byte after its cylinder byte.
4E23
CP E BB
Compare Register A (the slot's cylinder) against Register E (the extent's cylinder).
4E24
If the NZ FLAG is set (another cylinder), JUMP to 4E2CH to go on with the next slot.
4E26
LD A,(HL) 7E
Fetch the slot's granule byte at Register Pair HL into Register A: bits 7-5 its first granule, bits 4-0 its count less one.
4E27
XOR D AA
Exclusive OR Register D (the extent's granule byte) into Register A: the bits in which the two differ are set.
4E28
AND E0H E6 E0
Keep bits 7-5 of Register A (AND E0H): 00H with the Z FLAG when the slot starts at the same granule of the cylinder.
4E2A
If the Z FLAG is set (the slot holds this extent), JUMP to 4E45H to store the extent's granule byte with its new count.
4E2C
DEC B 05
DECrement Register B, the count of the slots still to look at: the Z FLAG after the fifth slot.
4E2D
If the Z FLAG is set (no slot holds the extent), JUMP to 4E34H to free the second slot for it.
4E2F
INC HL 23
INCrement Register Pair HL past the granule byte, to the low byte of the next slot's starting granule.
4E30
INC HL 23
INCrement Register Pair HL to the high byte of the next slot's starting granule.
4E31
INC HL 23
INCrement Register Pair HL to the next slot's cylinder byte (4 bytes after the last one).
4E32
LOOP BACK to 4E21H to look at the next extent slot of the control block.
4E34
PUSH DE D5
Save Register Pair DE (the extent: Register D the granule byte, Register E the cylinder) to the stack.
4E35
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair DE = the control block +1FH, the last byte of the fifth slot, where the LDDR stores first.
4E36
LD HL,FFFCH 21 FC FF
Load Register Pair HL with FFFCH (minus 4), the size of one slot of the control block.
4E39
ADD HL,DE 19
ADD Register Pair DE to Register Pair HL: Register Pair HL = the control block +1BH, the last byte of the fourth slot, copied first.
4E3A
LD BC,000CH 01 0C 00
Load Register Pair BC with 000CH, the 12 bytes +10H-+1BH: the second, third and fourth slots with their starting granules.
4E3D
LDDR ED B8
Copy (DE) = (HL) for Register Pair BC bytes, HL and DE counting down: +10H-+1BH move to +14H-+1FH, the second to fourth slots become the third to fifth and the fifth slot is dropped.
4E3F
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair HL = the control block +13H, where the LDDR left Register Pair DE: the granule byte of the second slot, now free.
4E40
POP BC C1
Restore the extent into Register Pair BC from the stack: Register B = its granule byte, Register C = its cylinder.
4E41
XOR A AF
Set Register A to 00H (XOR A), which sets the Z FLAG: the granule was found.
4E42
SCF 37
Set the CARRY FLAG: SYS0/SYS (4AB1H) is to store the extent and its first granule in the second slot at Register Pair HL.
4E43
JUMP to 4E48H to return the extent's first granule and the granule's offset in the extent.
4E45
LD (HL),D 72
Store Register D (the extent's granule byte, with its count as the directory record has it) at Register Pair HL, the slot's granule byte.
4E46
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair HL = the extent again (Register H the granule byte, Register L the cylinder).
4E47
XOR A AF
Set Register A to 00H (XOR A): the Z FLAG is set and the CARRY FLAG cleared, the slot was brought up to date in place.
4E48
POP DE D1
Restore Register Pair DE (the extent's first granule, saved at 4E16H) from the stack. POP does not change the flags.
4E49
LD A,00H 3E 00
Load Register A with the granule's offset in its extent, the operand at 4E4AH (00H in the file) stored by 4E12H. LD does not change the flags.
4E4B
RET C9
RETurn to SYS0/SYS at 4A9BH, after its CALL 4AB3H (the dispatcher drops the RST 28H's own return address), with the Z FLAG, Register A = the offset and Register Pair DE = the extent's first granule; with the NO CARRY FLAG Register Pair HL = the extent, with the CARRY FLAG Register Pair BC = the extent and Register Pair HL = the free second slot.

4E4CH - Walk the File's Extents

Counts the granules of the file's extents in its directory record (+16H-+1DH, four extents of a cylinder byte and a granule byte: bits 7-5 the first granule on the cylinder, bits 4-0 the granules less one) until the extent that holds granule Register Pair BC. A cylinder byte of FEH (in +1EH, after the fourth extent) links to an extension record, whose number is in +1FH; the count goes on there. A cylinder byte of FFH is an extent not in use, the end of the file's space: then 4E97H allocates the granules up to the one wanted and the walk starts again. The number of the record read last is kept in the operand at 4F82H.

4E4C
PUSH BC C5
Save Register Pair BC (the file's granule wanted) to the stack, for the DIRRD below that uses Register Pairs BC and HL.
4E4D
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H, the granules counted so far: the first extent starts at the file's granule 0.
4E50
LD B,(IX+07H) DD 46 07
Fetch the number of the file's directory record from Index Register IX +07H (the control block) into Register B.
4E53
LD A,B 78
Copy Register B (the directory record to read) into Register A. 4E8EH comes here with an extension record of the file.
4E54
LD (4F82H),A 32 82 4F
Store Register A at 4F82H, the operand of the LD B at 4F81H: the record that 4F7AH writes back and 4F86H links an extension record from.
4E57
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4E5A
GOSUB to 4B10H (DIRRD) to read the directory sector of record Register B of drive Register C into 4300H: Register Pair HL = the record, or the NZ FLAG with Register A = 11H. Register Pair DE is kept.
4E5D
LD BC,0016H 01 16 00
Load Register Pair BC with 0016H, the offset of the first extent in a directory record.
4E60
ADD HL,BC 09
ADD Register Pair BC to Register Pair HL: Register Pair HL = the record +16H, the first extent's cylinder byte. ADD of a pair does not change the Z FLAG.
4E61
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair DE = the first extent of the record, Register Pair HL = the granules counted so far.
4E62
POP BC C1
Restore Register Pair BC (the file's granule wanted) from the stack. POP does not change the flags.
4E63
RET NZ C0
RETurn if the NZ FLAG is set (the directory sector could not be read) with Register A = 11H, error 17: directory read error.
4E64
LD A,(DE) 1A
Fetch the extent's cylinder byte at Register Pair DE into Register A. 4E7EH loops back here for the next extent.
4E65
CP FEH FE FE
Compare Register A against FEH: FEH is the link to an extension record, FFH an extent not in use; both leave the NO CARRY FLAG.
4E67
If the NO CARRY FLAG is set (FEH or FFH), JUMP to 4E88H to follow the link or allocate more granules.
4E69
INC DE 13
INCrement Register Pair DE to the extent's granule byte, the byte after its cylinder byte.
4E6A
LD A,(DE) 1A
Fetch the extent's granule byte at Register Pair DE into Register A: bits 7-5 its first granule, bits 4-0 its granules less one.
4E6B
PUSH HL E5
Save Register Pair HL (the granules counted before this extent: the file's granule where it starts) to the stack.
4E6C
AND 1FH E6 1F
Keep bits 4-0 of Register A (AND 1FH): the extent's granules less one.
4E6E
INC A 3C
INCrement Register A: the number of granules in the extent, 1-32.
4E6F
ADD A,L 85
ADD Register L (the low byte of the granules counted) to Register A (the extent's granules).
4E70
LD L,A 6F
Copy Register A (the low byte of the new count) into Register L.
4E71
If the NO CARRY FLAG is set (no carry into the high byte), JUMP to 4E74H.
4E73
INC H 24
INCrement Register H, the carry into the high byte: Register Pair HL = the granules counted to the end of this extent.
4E74
PUSH HL E5
Save Register Pair HL (the granules counted to the end of this extent) to the stack.
4E75
DEC HL 2B
DECrement Register Pair HL: the file's granule number of the extent's last granule.
4E76
XOR A AF
Set Register A to 00H (XOR A), which clears the CARRY FLAG for the SBC below.
4E77
SBC HL,BC ED 42
SUBtract Register Pair BC (the granule wanted) from Register Pair HL: the NO CARRY FLAG when the granule wanted is in this extent.
4E79
POP HL E1
Restore Register Pair HL (the granules counted to the end of this extent) from the stack. POP does not change the flags.
4E7A
If the NO CARRY FLAG is set (the granule wanted is in this extent), JUMP to 4E80H to return the extent.
4E7C
INC DE 13
INCrement Register Pair DE to the next extent's cylinder byte, the byte after this extent's granule byte.
4E7D
POP AF F1
Remove the count saved at 4E6BH from the stack, into Register Pair AF, whose value is not used.
4E7E
LOOP BACK to 4E64H to count the next extent of the record.
4E80
POP HL E1
Restore Register Pair HL from the stack: the file's granule number where the extent starts.
4E81
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair DE = the extent's first granule, Register Pair HL = its granule byte in the record.
4E82
LD A,(HL) 7E
Fetch the extent's granule byte at Register Pair HL into Register A.
4E83
DEC HL 2B
DECrement Register Pair HL to the extent's cylinder byte, the byte before its granule byte.
4E84
LD L,(HL) 6E
Fetch the extent's cylinder byte at Register Pair HL into Register L.
4E85
LD H,A 67
Copy Register A (the granule byte) into Register H: Register Pair HL = the extent, Register H the granule byte and Register L the cylinder.
4E86
XOR A AF
Set Register A to 00H (XOR A), which sets the Z FLAG: the extent was found.
4E87
RET C9
RETurn with the Z FLAG, Register Pair HL = the extent and Register Pair DE = its first granule: to 4E0AH, or to 4E93H when 4F21H jumped here.
4E88
PUSH BC C5
Save Register Pair BC (the file's granule wanted) to the stack. The Z FLAG from 4E65H is kept: set for FEH.
4E89
EX DE,HL EB
Exchange Register Pairs DE and HL: Register Pair HL = the FEH or FFH in the record, Register Pair DE = the granules the file has up to here.
4E8A
If the NZ FLAG is set (FFH: the file has no more space), JUMP to 4E90H to allocate the granules missing.
4E8C
INC HL 23
INCrement Register Pair HL past the FEH (byte +1EH of the record) to byte +1FH, the number of the extension record.
4E8D
LD B,(HL) 46
Fetch the extension record's number at Register Pair HL into Register B.
4E8E
JUMP to 4E53H to read the extension record and go on counting; 4E62H takes the granule wanted back from the stack.
4E90
GOSUB to 4E97H to allocate the granules from the file's end (Register Pair DE) up to the one wanted (Register Pair BC) at the free extent at Register Pair HL: the NZ FLAG with an error number on failure.
4E93
POP BC C1
Restore Register Pair BC (the file's granule wanted) from the stack. POP does not change the flags.
4E94
RET NZ C0
RETurn if the NZ FLAG is set (the allocation failed) with the error number in Register A.
4E95
JUMP to 4E4CH to walk the file's extents again from the first record: now the granule wanted is in one of them.

4E97H - Allocate: The Granules Missing, Then Grow the Last Extent

Entered with Register Pair BC = the granule wanted, Register Pair DE = the granules the file has, Register Pair HL = the FFH where the record's space ends (an extent not in use, or +1EH when all four are in use). The granule allocation table is read into 5100H. The granules to allocate are the granule wanted less the granules the file has, plus one. While the last extent holds fewer than 32 granules and the granule after it (counted on from its first granule, over the cylinders) is free, it is added to that extent; otherwise the next extent of the record is started at a free granule (4F24H). When the four extents are full the table and the record are written back, an extension record is made and linked (4F86H), and the walk starts again.

4E97
PUSH BC C5
Save Register Pair BC (the file's granule wanted) to the stack while Register C is the drive.
4E98
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4E9B
GOSUB to 503FH to read the granule allocation table of drive Register C into 5100H-51FFH: the NZ FLAG with Register A = 14H on failure.
4E9E
POP BC C1
Restore Register Pair BC (the file's granule wanted) from the stack. POP does not change the flags.
4E9F
RET NZ C0
RETurn if the NZ FLAG is set (the table could not be read) with Register A = 14H, error 20: GAT read error.
4EA0
PUSH HL E5
Save Register Pair HL (the FFH where the record's space ends) to the stack.
4EA1
LD H,B 60
Copy Register B (the high byte of the granule wanted) into Register H.
4EA2
LD L,C 69
Copy Register C (the low byte of the granule wanted) into Register L: Register Pair HL = the granule wanted.
4EA3
XOR A AF
Set Register A to 00H (XOR A), which clears the CARRY FLAG for the SBC below.
4EA4
SBC HL,DE ED 52
SUBtract Register Pair DE (the granules the file has) from Register Pair HL: the granules missing, less one.
4EA6
LD B,H 44
Copy Register H (the high byte of the result) into Register B.
4EA7
LD C,L 4D
Copy Register L (the low byte of the result) into Register C.
4EA8
INC BC 03
INCrement Register Pair BC: the number of granules to allocate, at least one.
4EA9
POP DE D1
Restore Register Pair DE from the stack: the cylinder byte of the free extent, or the link byte +1EH when the four extents are in use.
4EAA
INC DE 13
INCrement Register Pair DE to the next byte: the free extent's granule byte (+17H, +19H, +1BH or +1DH), or +1FH.
4EAB
LD A,R ED 5F
Copy Register R, the memory refresh register, into Register A: a value that changes all the time. 4EABH-4EBCH are the start of the random choice of the cylinder where the search begins; this release starts at a fixed cylinder instead (4EBEH) and keeps these rows unused.
4EAD
RLA 17
Rotate Register A (the refresh count) left through the CARRY FLAG (RLA): the low byte of the random number.
4EAE
LD L,A 6F
Copy Register A (the low byte of the random number) into Register L. 4EC2H loads Register L again, so in this release the value is not used.
4EAF
LD A,(4288H) 3A 88 42
Fetch TIMER$, the count of the timer interrupts, from 4288H into Register A: the high byte of the random number.
4EB2
LD H,A 67
Copy Register A (TIMER$) into Register H: Register Pair HL = the random number. 4EC4H loads Register H again, so in this release the value is not used.
4EB3
PUSH BC C5
Save Register Pair BC (the granules to allocate) to the stack while Register C is the drive.
4EB4
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4EB7
LD A,06H 3E 06
Load Register A with 06H, the offset of the highest cylinder in a drive code table.
4EB9
GOSUB to 479CH (DCTBYT) to fetch byte +06H of drive Register C's code table into Register A: the highest cylinder.
4EBC
INC A 3C
INCrement Register A (the highest cylinder): the number of cylinders of the drive. In this release Register A is put aside at 4EBEH and not used; 4F24H fetches the count again.
4EBD
POP BC C1
Restore Register Pair BC (the granules to allocate) from the stack.
4EBE
PUSH AF F5
Save Register Pair AF (Register A = the number of cylinders) to the stack while Register A fetches the start of the search. The 5.1.4 update notes give a six byte patch that brings back the random start, written for the Model I as D5 CD C1 44 D1 6C (PUSH DE / CALL 44C1H, the Model I @MULT / POP DE / LD L,H); on the Model III the same patch is D5 CD 4E 44 D1 6C, calling @MULT at 444EH; in this file it fits over 4EBEH-4EC3H, between the POP BC and the LD H,51H, where @MULT of the random number by the number of cylinders leaves in Register H a cylinder from 0 to the last.
4EBF
LD A,(4767H) 3A 67 47
Fetch the byte at 4767H into Register A: 01H, the fifth byte of SYS0/SYS's WRSECT primitive, which nothing changes: the cylinder where the search for a free granule starts. The update notes say space is allocated from the lower numbered cylinders on.
4EC2
LD L,A 6F
Copy Register A (01H, the first cylinder to search) into Register L, replacing the random number's low byte.
4EC3
POP AF F1
Restore Register Pair AF from the stack: Register A = the number of cylinders again, which is not used.
4EC4
LD H,51H 26 51
Load Register H with 51H: Register Pair HL = 5101H, the byte of cylinder 1 in the allocation table at 5100H.
4EC6
PUSH BC C5
Save Register Pair BC (the granules to allocate) to the stack: the loop takes it back for each granule.
4EC7
LD A,E 7B
Copy Register E (the low byte of the address of the free extent's granule byte) into Register A.
4EC8
AND 1EH E6 1E
Keep bits 4-1 of Register A (AND 1EH): the byte's place in the record, made even: 16H for the first extent.
4ECA
CP 16H FE 16
Compare Register A against 16H, the place of the first extent: the record has no extent in use.
4ECC
If the Z FLAG is set (no extent to grow), JUMP to 4F24H to start the first extent at a free granule from cylinder 1.
4ECE
DEC E 1D
DECrement Register E (the record does not cross a page): Register Pair DE moves back towards the last extent in use.
4ECF
DEC E 1D
DECrement Register E again: Register Pair DE = the granule byte of the last extent in use.
4ED0
LD A,(DE) 1A
Fetch the extent's granule byte at Register Pair DE into Register A. 4F76H comes back here for each further granule.
4ED1
AND 1FH E6 1F
Keep bits 4-0 of Register A (AND 1FH): the extent's granules less one.
4ED3
INC A 3C
INCrement Register A: the number of granules in the extent, 1-32.
4ED4
LD C,A 4F
Copy Register A (the extent's granules) into Register C.
4ED5
CP 20H FE 20
Compare Register A against 20H, 32 granules, the most one extent can hold.
4ED7
If the Z FLAG is set (the extent is full), JUMP to 4F0DH to go on with the next extent of the record.
4ED9
LD A,(DE) 1A
Fetch the extent's granule byte at Register Pair DE into Register A again.
4EDA
AND E0H E6 E0
Keep bits 7-5 of Register A (AND E0H): the extent's first granule on its cylinder.
4EDC
RLCA 07
Rotate Register A left one bit (RLCA): first of three rotations that bring bits 7-5 to bits 2-0.
4EDD
RLCA 07
Rotate Register A left one bit (RLCA): second of the three rotations.
4EDE
RLCA 07
Rotate Register A left one bit (RLCA): bits 7-5 are now bits 2-0, the first granule on the cylinder.
4EDF
ADD A,C 81
ADD Register C (the extent's granules) to Register A: the granule after the extent, counted from the start of the extent's cylinder.
4EE0
PUSH DE D5
Save Register Pair DE (the address of the extent's granule byte) to the stack.
4EE1
LD E,A 5F
Copy Register A (the granule after the extent) into Register E, the dividend for DIVEA.
4EE2
LD A,08H 3E 08
Load Register A with 08H, the offset of the granule layout in a drive code table.
4EE4
PUSH BC C5
Save Register Pair BC (Register C = the extent's granules) to the stack while Register C is the drive.
4EE5
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4EE8
GOSUB to 479CH (DCTBYT) for byte +08H of the drive's table: bits 7-5 the granules on a track less one, bits 4-0 the sectors of a granule less one.
4EEB
RLCA 07
Rotate Register A left one bit (RLCA): first of three rotations that bring bits 7-5 to bits 2-0.
4EEC
RLCA 07
Rotate Register A left one bit (RLCA): second of the three rotations.
4EED
RLCA 07
Rotate Register A left one bit (RLCA): the third rotation.
4EEE
AND 07H E6 07
Keep bits 2-0 of Register A (AND 07H): the granules on a track less one.
4EF0
INC A 3C
INCrement Register A: the granules on a track (one side of a cylinder).
4EF1
GOSUB to 5033H to double Register A when the drive has two sides: the granules on a cylinder.
4EF4
POP BC C1
Restore Register Pair BC (Register C = the extent's granules) from the stack.
4EF5
GOSUB to 4B7AH (DIVEA) to divide Register E by Register A: Register A = the cylinders to step on, Register E = the granule on that cylinder.
4EF8
LD B,A 47
Copy Register A (the cylinders to step on from the extent's cylinder) into Register B.
4EF9
LD C,E 4B
Copy Register E (the granule on that cylinder, 0-7) into Register C.
4EFA
POP DE D1
Restore Register Pair DE (the address of the extent's granule byte) from the stack.
4EFB
DEC DE 1B
DECrement Register Pair DE to the extent's cylinder byte, the byte before its granule byte.
4EFC
LD A,(DE) 1A
Fetch the extent's cylinder at Register Pair DE into Register A.
4EFD
INC DE 13
INCrement Register Pair DE back to the extent's granule byte.
4EFE
ADD A,B 80
ADD Register B (the cylinders to step on) to Register A: the cylinder of the granule after the extent.
4EFF
LD L,A 6F
Copy Register A (that cylinder) into Register L.
4F00
LD H,51H 26 51
Load Register H with 51H: Register Pair HL = 5100H + the cylinder, its byte in the allocation table.
4F02
CP CBH FE CB
Compare Register A (the cylinder) against CBH: the table holds a byte for each cylinder from 00H to CAH.
4F04
If the NO CARRY FLAG is set (no byte for that cylinder), JUMP to 4F0DH: the extent cannot grow.
4F06
LD A,C 79
Copy Register C (the granule on that cylinder) into Register A, the bit to test.
4F07
LD B,(HL) 46
Fetch the cylinder's byte of the allocation table at Register Pair HL into Register B: a bit set for each granule in use.
4F08
GOSUB to 501AH to test bit Register A of Register B: the Z FLAG when the granule after the extent is free.
4F0B
If the Z FLAG is set (it is free), JUMP to 4F60H to allocate it and add it to the extent.
4F0D
INC E 1C
INCrement Register E: Register Pair DE moves on to the next extent of the record.
4F0E
INC E 1C
INCrement Register E again: Register Pair DE = the next extent's granule byte, or +1FH after the fourth.
4F0F
LD A,E 7B
Copy Register E (the low byte of that address) into Register A.
4F10
AND 1EH E6 1E
Keep bits 4-1 of Register A (AND 1EH): the byte's place in the record, made even.
4F12
CP 1EH FE 1E
Compare Register A against 1EH: the place of the link, past the fourth extent.
4F14
If the NZ FLAG is set (an extent of the record is left), JUMP to 4F24H to start it at a free granule.
4F16
GOSUB to 4F7AH to write back the allocation table and the record: its four extents are full.
4F19
POP BC C1
Restore Register Pair BC (the granules still to allocate) from the stack. POP does not change the flags.
4F1A
RET NZ C0
RETurn if the NZ FLAG is set (a write failed) with the error number in Register A.
4F1B
PUSH BC C5
Save Register Pair BC (the granules still to allocate) to the stack again.
4F1C
GOSUB to 4F86H to make an extension record for the file and link it from the record's +1EH-+1FH.
4F1F
POP BC C1
Restore Register Pair BC (the granules still to allocate) from the stack. POP does not change the flags.
4F20
RET NZ C0
RETurn if the NZ FLAG is set (the directory is full, or a read or write failed) with the error number in Register A.
4F21
JUMP to 4E4CH to walk the extents again. Register Pair BC holds the granules still to allocate here, not the granule wanted: the walk returns when the file has that many, or allocates up to that count first; either way its RET goes to 4E93H, which takes the granule wanted back and walks again.

4F24H - A New Extent at the First Free Granule; Allocate One Granule

Register Pair DE = the granule byte of a free extent of the record, Register Pair HL = the allocation table byte where the search starts (cylinder 1, or the cylinder after the last extent). The search runs to the drive's last cylinder and then once more from cylinder 0, looking for a byte that is not FFH (all granules in use); none found is error 1BH, disk space full, after the granules allocated so far are written back. The first free granule of that cylinder starts the extent: its number goes into bits 7-5 of the granule byte. 4F60H marks the granule in use in the table, gives a new extent its cylinder, counts the granule into the extent and loops back to 4ED0H until the granules missing are allocated.

4F24
LD A,06H 3E 06
Load Register A with 06H, the offset of the highest cylinder in a drive code table.
4F26
PUSH BC C5
Save Register Pair BC (the granules still to allocate) to the stack while Register C is the drive.
4F27
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4F2A
GOSUB to 479CH (DCTBYT) to fetch byte +06H of drive Register C's code table into Register A: the highest cylinder.
4F2D
INC A 3C
INCrement Register A: the number of cylinders on the drive.
4F2E
POP BC C1
Restore Register Pair BC (the granules still to allocate) from the stack.
4F2F
LD (4F36H),A 32 36 4F
Store Register A (the number of cylinders) at 4F36H, the operand of the CP at 4F35H, where each pass of the search stops.
4F32
LD B,02H 06 02
Load Register B with 02H: two passes, from Register L to the last cylinder and then from cylinder 0.
4F34
LD A,L 7D
Copy Register L (the cylinder to look at) into Register A. 4F3EH and 4F42H loop back here.
4F35
CP 00H FE 00
Compare Register A against the number of cylinders, the operand at 4F36H (00H in the file) stored by 4F2FH.
4F37
If the NO CARRY FLAG is set (past the last cylinder), JUMP to 4F40H to end this pass.
4F39
LD A,(HL) 7E
Fetch the cylinder's allocation table byte at Register Pair HL into Register A: a bit set for each granule in use.
4F3A
INC A 3C
INCrement Register A: 00H with the Z FLAG when the byte is FFH, every granule of the cylinder in use.
4F3B
If the NZ FLAG is set (a granule of the cylinder is free), JUMP to 4F4DH to start the extent there.
4F3D
INC L 2C
INCrement Register L to the next cylinder's byte in the allocation table.
4F3E
LOOP BACK to 4F34H to look at the next cylinder.
4F40
LD L,00H 2E 00
Load Register L with 00H: the second pass starts at the byte of cylinder 0.
4F42
DECrement Register B and LOOP BACK to 4F34H for the second pass of the search.
4F44
POP BC C1
Restore Register Pair BC (the granules still to allocate) from the stack: both passes found no free granule.
4F45
GOSUB to 4F7AH to write back the allocation table and the record with the granules allocated so far.
4F48
RET NZ C0
RETurn if the NZ FLAG is set (a write failed) with the error number in Register A.
4F49
LD A,1BH 3E 1B
Load Register A with 1BH, error 27: disk space full.
4F4B
OR A B7
Set the flags from Register A (OR A): the NZ FLAG is set, Register A not being 00H.
4F4C
RET C9
RETurn with the NZ FLAG and Register A = 1BH, error 27: disk space full.
4F4D
LD A,FFH 3E FF
Load Register A with FFH, the start value of the new extent's granule byte.
4F4F
LD (DE),A 12
Store Register A (FFH) at Register Pair DE, the new extent's granule byte: 4F5AH adds 20H for each granule passed over and 4F6FH adds one, leaving the first granule in bits 7-5 and 00H (one granule) in bits 4-0.
4F50
LD C,00H 0E 00
Load Register C with 00H, the first granule of the cylinder to test.
4F52
LD B,(HL) 46
Fetch the cylinder's allocation table byte at Register Pair HL into Register B.
4F53
LD A,C 79
Copy Register C (the granule to test) into Register A. 4F5EH loops back here.
4F54
GOSUB to 501AH to test bit Register A of Register B: the Z FLAG when the granule is free.
4F57
If the Z FLAG is set (the granule is free), JUMP to 4F60H to allocate it.
4F59
LD A,(DE) 1A
Fetch the new extent's granule byte at Register Pair DE into Register A.
4F5A
ADD A,20H C6 20
ADD 20H to Register A: the first granule in bits 7-5 moves on by one granule.
4F5C
LD (DE),A 12
Store Register A at Register Pair DE, the new extent's granule byte.
4F5D
INC C 0C
INCrement Register C to the next granule of the cylinder.
4F5E
LOOP BACK to 4F53H to test the next granule of the cylinder.
4F60
LD A,C 79
Copy Register C (the free granule on the cylinder) into Register A. 4F0BH comes here with the granule after an extent.
4F61
GOSUB to 5027H: Register A = the granule's bit in the allocation table byte (1 shifted left by Register A).
4F64
OR (HL) B6
OR the cylinder's allocation table byte at Register Pair HL into Register A.
4F65
LD (HL),A 77
Store Register A at Register Pair HL, the cylinder's allocation table byte: the granule is now in use.
4F66
DEC E 1D
DECrement Register E: Register Pair DE = the extent's cylinder byte, the byte before its granule byte.
4F67
LD A,(DE) 1A
Fetch the extent's cylinder byte at Register Pair DE into Register A.
4F68
INC A 3C
INCrement Register A: 00H with the Z FLAG when the cylinder byte is FFH, a new extent.
4F69
If the NZ FLAG is set (the extent has its cylinder), JUMP to 4F6DH to count the granule.
4F6B
LD A,L 7D
Copy Register L (the cylinder of the free granule) into Register A.
4F6C
LD (DE),A 12
Store Register A at Register Pair DE, the cylinder byte: the new extent starts on this cylinder.
4F6D
INC E 1C
INCrement Register E back to the extent's granule byte, the byte after its cylinder byte.
4F6E
LD A,(DE) 1A
Fetch the extent's granule byte at Register Pair DE into Register A.
4F6F
INC A 3C
INCrement Register A: one granule more in bits 4-0 of the granule byte.
4F70
LD (DE),A 12
Store Register A at Register Pair DE, the extent's granule byte with its new count.
4F71
POP BC C1
Restore Register Pair BC (the granules still to allocate) from the stack.
4F72
DEC BC 0B
DECrement Register Pair BC: one granule fewer to allocate.
4F73
PUSH BC C5
Save Register Pair BC (the granules still to allocate) to the stack again.
4F74
LD A,B 78
Copy Register B (the high byte of the count) into Register A.
4F75
OR C B1
OR Register C (the low byte) into Register A: the Z FLAG when no granule is left to allocate.
4F76
If the NZ FLAG is set (more granules to allocate), JUMP to 4ED0H to try the granule after this extent next.
4F79
POP BC C1
Restore Register Pair BC (0000H) from the stack: the granules missing are all allocated.
4F7A
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C. 4F16H and 4F45H call here.
4F7D
GOSUB to 5052H to write the allocation table at 5100H back to drive Register C and verify it: the NZ FLAG with Register A = 15H on failure.
4F80
RET NZ C0
RETurn if the NZ FLAG is set (the table could not be written) with Register A = 15H, error 21: GAT write error.
4F81
LD B,00H 06 00
Load Register B with the number of the record last read, the operand at 4F82H (00H in the file) stored by 4E54H.
4F83
JUMP to 4B1FH (DIRWR) to write the directory sector at 4300H, which holds record Register B, back to drive Register C and verify it; its RET returns the Z FLAG, or the NZ FLAG with the error number.

4F86H - Make and Link an Extension Record

The record's four extents are full, so the file goes on in an extension record. The hash index table (sector 1 of the directory cylinder) is read into 4300H and a free record looked for (4FDEH), from the column of the file's own record on; none free is error 1EH, directory full. The new record gets the file's hash byte in the table (so it counts as in use), and the table is written back. The new record is built in its directory sector: 90H (bit 7 an extension record, bit 4 in use), the number of the record it extends, twenty bytes of 00H and ten of FFH (four extents not in use and no link), and written. Then the record it extends is read again and gets FEH and the new record's number in +1EH-+1FH.

4F86
LD C,(IX+06H) DD 4E 06
Fetch the file's drive from Index Register IX +06H (the control block) into Register C.
4F89
GOSUB to 506BH to read the hash index table (sector 1 of the directory cylinder) of drive Register C into 4300H: the NZ FLAG with Register A = 16H on failure.
4F8C
RET NZ C0
RETurn if the NZ FLAG is set (the table could not be read) with Register A = 16H, error 22: HIT read error.
4F8D
LD A,(IX+07H) DD 7E 07
Fetch the number of the file's directory record from Index Register IX +07H into Register A.
4F90
AND 1FH E6 1F
Keep bits 4-0 of Register A (AND 1FH): the record's sector less 2, its column in the hash index table, where the search starts.
4F92
GOSUB to 4FDEH to find a free directory record: the Z FLAG with Register Pair HL = its byte in the hash index table, Register L = the record's number.
4F95
LD A,1EH 3E 1E
Load Register A with 1EH, error 30: directory full. LD does not change the flags.
4F97
RET NZ C0
RETurn if the NZ FLAG is set (no free record) with Register A = 1EH, error 30: directory full.
4F98
LD B,L 45
Copy Register L (the new record's number) into Register B, for the DIRRD and DIRWR below.
4F99
LD A,L 7D
Copy Register L (the new record's number) into Register A.
4F9A
LD (4FD9H),A 32 D9 4F
Store Register A (the new record's number) at 4FD9H, the operand of the LD (HL) at 4FD8H, which links the new record.
4F9D
LD D,H 54
Copy Register H (42H, the page of the hash index table at 4300H) into Register D.
4F9E
LD E,(IX+07H) DD 5E 07
Fetch the number of the file's directory record from Index Register IX +07H into Register E: Register Pair DE = the file's byte in the hash index table.
4FA1
LD A,(DE) 1A
Fetch the file's hash byte at Register Pair DE into Register A.
4FA2
LD (HL),A 77
Store Register A (the file's hash byte) at Register Pair HL: the new record is now in use in the hash index table.
4FA3
GOSUB to 507DH to write the hash index table at 4300H back and verify it: the Z FLAG on success, else the NZ FLAG with Register A = 17H.
4FA6
If the Z FLAG is set (written), GOSUB to 4B10H (DIRRD) to read the directory sector of the new record Register B into 4300H: Register Pair HL = the record.
4FA9
RET NZ C0
RETurn if the NZ FLAG is set (a write or read failed) with the error number in Register A (17H, or 11H from DIRRD).
4FAA
LD (HL),90H 36 90
Store 90H at Register Pair HL, byte +00H of the new record: bit 7 an extension record, bit 4 a record in use.
4FAC
INC L 2C
INCrement Register L to byte +01H of the new record (the record does not cross a page).
4FAD
PUSH BC C5
Save Register Pair BC (Register B = the new record, Register C = the drive) to the stack while Register B counts.
4FAE
LD A,(4F82H) 3A 82 4F
Fetch the number of the record the walk read last, the operand at 4F82H, into Register A: the record the new one extends.
4FB1
LD (HL),A 77
Store Register A at Register Pair HL, byte +01H of the new record: the number of the record it extends.
4FB2
INC L 2C
INCrement Register L to byte +02H of the new record, the first byte to clear.
4FB3
LD B,14H 06 14
Load Register B with 14H, the count of the 20 bytes +02H-+15H of the new record.
4FB5
LD (HL),00H 36 00
Store 00H at Register Pair HL, the next byte of +02H-+15H. 4FB8H loops back here.
4FB7
INC L 2C
INCrement Register L to the next byte of the new record.
4FB8
DECrement Register B and LOOP BACK to 4FB5H until bytes +02H-+15H are 00H.
4FBA
PUSH HL E5
Save Register Pair HL (the new record's +16H, its first extent) to the stack.
4FBB
LD B,0AH 06 0A
Load Register B with 0AH, the count of the 10 bytes +16H-+1FH: the four extents and the link.
4FBD
LD (HL),FFH 36 FF
Store FFH at Register Pair HL: an extent not in use, and at +1EH no link. 4FC0H loops back here.
4FBF
INC L 2C
INCrement Register L to the next byte of the new record.
4FC0
DECrement Register B and LOOP BACK to 4FBDH until bytes +16H-+1FH are FFH.
4FC2
POP DE D1
Restore the address of the new record's +16H into Register Pair DE, taking it off the stack.
4FC3
INC DE 13
INCrement Register Pair DE to the new record's +17H. Nothing uses Register Pair DE after this.
4FC4
POP BC C1
Restore Register Pair BC (Register B = the new record, Register C = the drive) from the stack.
4FC5
GOSUB to 4B1FH (DIRWR) to write the directory sector with the new record back and verify it: the NZ FLAG with the error number on failure.
4FC8
RET NZ C0
RETurn if the NZ FLAG is set (the sector could not be written) with the error number in Register A.
4FC9
LD A,(4F82H) 3A 82 4F
Fetch the number of the record the walk read last, the operand at 4F82H, into Register A: the record to link from.
4FCC
LD B,A 47
Copy Register A (the record to link from) into Register B.
4FCD
GOSUB to 4B10H (DIRRD) to read the directory sector of record Register B into 4300H: Register Pair HL = the record.
4FD0
RET NZ C0
RETurn if the NZ FLAG is set (the sector could not be read) with Register A = 11H, error 17: directory read error.
4FD1
LD A,L 7D
Copy Register L (the low byte of the record's address) into Register A.
4FD2
ADD A,1EH C6 1E
ADD 1EH to Register A: the low byte of the record's +1EH, the link.
4FD4
LD L,A 6F
Copy Register A into Register L: Register Pair HL = the record +1EH.
4FD5
LD (HL),FEH 36 FE
Store FEH at Register Pair HL, the record's +1EH: the file goes on in an extension record.
4FD7
INC L 2C
INCrement Register L to the record's +1FH, the number of the extension record.
4FD8
LD (HL),00H 36 00
Store the new record's number, the operand at 4FD9H (00H in the file) stored by 4F9AH, at Register Pair HL (+1FH).
4FDA
GOSUB to 4B1FH (DIRWR) to write the directory sector with the linked record back and verify it.
4FDD
RET C9
RETurn with the Z FLAG on success, or with the NZ FLAG and the error number from DIRWR.

4FDEH - Find a Free Directory Record in the Hash Index Table

Entered with Register A = the column of the hash index table to start at (the sector of the file's own record less 2) and the table in 4300H-43FFH. A byte of the table is the hash of the record at that place: byte n is record n, record n AND E0H of directory sector (n AND 1FH) + 2, and 00H is a record not in use. The number of columns that hold records is the sectors of a directory cylinder less 2 (the allocation table and the hash index table), worked out from the drive code table and stored at 5008H. The first pass looks down each column (rows 00H, 20H ... E0H) from the start column to the last; the second pass starts again at 40H, row 2 of column 0, and so never offers records 00H and 20H. Returns the Z FLAG with Register Pair HL = the free byte (Register L = the free record's number), or the NZ FLAG when none is free.

4FDE
PUSH AF F5
Save Register Pair AF (Register A = the column to start at) to the stack while Register A works out the number of columns.
4FDF
LD A,07H 3E 07
Load Register A with 07H, the offset of the sector layout in a drive code table.
4FE1
GOSUB to 479CH (DCTBYT) to fetch byte +07H of drive Register C's code table into Register A: bits 4-0 the highest sector on a track, bits 7-5 the heads less one.
4FE4
PUSH DE D5
Save Register Pair DE to the stack while Register D and Register E work out the count.
4FE5
LD D,A 57
Copy Register A (byte +07H of the drive code table) into Register D.
4FE6
AND 1FH E6 1F
Keep bits 4-0 of Register A (AND 1FH): the highest sector on a track.
4FE8
LD E,A 5F
Copy Register A (the highest sector) into Register E.
4FE9
INC E 1C
INCrement Register E: the number of sectors on a track.
4FEA
XOR D AA
Exclusive OR Register D (byte +07H) into Register A (its bits 4-0): Register A = bits 7-5 of byte +07H alone, the heads less one.
4FEB
RLCA 07
Rotate Register A left one bit (RLCA): first of three rotations that bring bits 7-5 to bits 2-0.
4FEC
RLCA 07
Rotate Register A left one bit (RLCA): second of the three rotations.
4FED
RLCA 07
Rotate Register A left one bit (RLCA): bits 2-0 now hold the heads less one.
4FEE
INC A 3C
INCrement Register A (the heads less one): Register A = the number of heads of the drive.
4FEF
GOSUB to 4B6BH (MULTEA) to multiply Register A (the heads) by Register E (the sectors on a track): Register A = the sectors of a cylinder on one side.
4FF2
GOSUB to 5033H to double Register A when bit 5 of byte +04H of the drive code table says the drive has two sides: Register A = the sectors of the directory cylinder.
4FF5
POP DE D1
Restore Register Pair DE from the stack (5033H used Register D).
4FF6
SUB 02H D6 02
SUBtract 02H from Register A: the sectors of the directory cylinder that hold records, less the allocation table (sector 0) and the hash index table (sector 1). This is the number of columns of the hash index table in use.
4FF8
LD (5008H),A 32 08 50
Store Register A (the number of columns) at 5008H, the operand of the CP at 5007H, where each column search stops.
4FFB
POP AF F1
Restore Register Pair AF from the stack: Register A = the column to start at.
4FFC
LD L,A 6F
Copy Register A (the column to start at) into Register L. Register H is 42H, the page of the hash index table read into 4300H: Register Pair HL = row 0 of the start column.
4FFD
GOSUB to 5004H for the first pass, from the start column to the last column in use.
5000
RET Z C8
RETurn if the Z FLAG is set (a free record found) with Register Pair HL = its byte in the hash index table.
5001
LD L,3FH 2E 3F
Load Register L with 3FH, so that the INC L below makes it 40H: the second pass starts at row 2 of column 0 (records 00H and 20H are never offered).
5003
INC L 2C
INCrement Register L: from 3FH to 40H for the second pass, or to the next column (row 0) when 5016H jumps here.
5004
LD A,L 7D
Copy Register L (the byte of the hash index table to look at) into Register A. 4FFDH calls here and 5012H loops back here.
5005
AND 1FH E6 1F
Keep bits 4-0 of Register A (AND 1FH): the column, the directory sector less 2.
5007
CP 00H FE 00
Compare Register A (the column) against the number of columns in use, the operand at 5008H (00H in the file) stored by 4FF8H.
5009
If the NO CARRY FLAG is set (past the last column in use), JUMP to 5018H to return the NZ FLAG: no free record in this pass.
500B
LD A,(HL) 7E
Fetch the hash index table byte at Register Pair HL into Register A: 00H for a record not in use.
500C
OR A B7
Set the flags from Register A (OR A): the Z FLAG when the byte is 00H.
500D
RET Z C8
RETurn if the Z FLAG is set (the record is free) with Register Pair HL = its byte, Register L = the record's number.
500E
LD A,L 7D
Copy Register L (the byte just looked at) into Register A.
500F
ADD A,20H C6 20
ADD 20H to Register A: the next row of the same column, the next record of the same directory sector.
5011
LD L,A 6F
Copy Register A (the next row) into Register L.
5012
If the NO CARRY FLAG is set (row E0H not passed yet), LOOP BACK to 5004H to look at that row.
5014
CP 1FH FE 1F
Compare Register A (after the carry the column alone, row 0) against 1FH, the last column the table can have.
5016
If the NZ FLAG is set (not the last column), JUMP to 5003H to go on with the next column.
5018
OR A B7
Set the flags from Register A (OR A): Register A is the column, 1FH or at least the number of columns in use, so the NZ FLAG is set.
5019
RET C9
RETurn with the NZ FLAG: no free record in this pass. After the second pass this return goes to 4F95H, which gives error 1EH.

501AH - Test and Set a Granule's Bit

501AH builds the instruction BIT n,B at 5024H for bit n = Register A (bits 2-0) by storing 40H + 8n at its second byte 5025H, and runs it: the Z FLAG is set when bit n of Register B is 0, the granule free. 5027H builds SET n,A at 5030H by storing C7H + 8n at 5031H and runs it on Register A = 00H: Register A = the granule's bit, 1 shifted left n times.

501A
AND 07H E6 07
Keep bits 2-0 of Register A (AND 07H): the number n of the bit to test, the granule on its cylinder.
501C
RLCA 07
Rotate Register A left one bit (RLCA): first of three rotations that make n into 8 x n.
501D
RLCA 07
Rotate Register A left one bit (RLCA): second of the three rotations.
501E
RLCA 07
Rotate Register A left one bit (RLCA): Register A = 8 x n, the bit field of a CB instruction.
501F
OR 40H F6 40
OR 40H into Register A: 40H + 8n, the second byte of BIT n,B.
5021
LD (5025H),A 32 25 50
Store Register A (40H + 8n) at 5025H, the second byte of the BIT 0,B at 5024H: it becomes BIT n,B.
5024
BIT 0,B CB 40
Test bit n of Register B (the cylinder's allocation table byte), the bit stored at 5025H (40H, bit 0, in the file): the Z FLAG when the granule is free.
5026
RET C9
RETurn with the Z FLAG for a free granule, the NZ FLAG for one in use.
5027
RLCA 07
Rotate Register A (the granule n on its cylinder, 0-7) left one bit (RLCA): first of three rotations that make n into 8 x n.
5028
RLCA 07
Rotate Register A left one bit (RLCA): second of the three rotations.
5029
RLCA 07
Rotate Register A left one bit (RLCA): Register A = 8 x n, the bit field of a CB instruction.
502A
OR C7H F6 C7
OR C7H into Register A: C7H + 8n, the second byte of SET n,A.
502C
LD (5031H),A 32 31 50
Store Register A (C7H + 8n) at 5031H, the second byte of the SET 0,A at 5030H: it becomes SET n,A.
502F
XOR A AF
Set Register A to 00H (XOR A), so that only the granule's bit will be set.
5030
SET 0,A CB C7
Set bit n of Register A, the bit stored at 5031H (C7H, bit 0, in the file): Register A = the granule's bit in its allocation table byte.
5032
RET C9
RETurn to 4F64H with Register A = the granule's bit, to be ORed into its allocation table byte.

5033H - Double a Count for a Drive with Two Sides

Register A = a count for one side of a cylinder. When bit 5 of byte +04H of the file's drive code table (drive Register C) is set, the drive has two sides and the count is doubled. Register D is used and not kept.

5033
LD D,A 57
Copy Register A (the count for one side) into Register D to keep it.
5034
LD A,04H 3E 04
Load Register A with 04H, the offset of the second drive specification byte in a drive code table.
5036
GOSUB to 479CH (DCTBYT) to fetch byte +04H of drive Register C's code table into Register A.
5039
BIT 5,A CB 6F
Test bit 5 of Register A, set when the drive has two sides: the Z FLAG for one side.
503B
LD A,D 7A
Copy Register D (the count for one side) back into Register A. LD does not change the flags.
503C
RET Z C8
RETurn if the Z FLAG is set (one side) with the count unchanged in Register A.
503D
ADD A,A 87
ADD Register A to itself: the count doubled, for both sides of the cylinder.
503E
RET C9
RETurn with the doubled count in Register A: the count for both sides of the cylinder.

503FH - Read and Write the Granule Allocation Table and the Hash Index Table

Four routines for the two system sectors of the directory cylinder of drive Register C. 503FH reads sector 0, the granule allocation table, into 5100H-51FFH (error 14H, GAT read error); 5052H writes it back from 5100H with the deleted data mark and verifies it (error 15H, GAT write error). 506BH reads sector 1, the hash index table, into 4300H-43FFH (error 16H, HIT read error); 507DH writes it back from 4300H and verifies it (error 17H, HIT write error). A verify of a sector with the deleted data mark returns 06H, so 06H is success. Each returns the Z FLAG on success; Register A then holds the error number of the routine, which the caller does not use.

503F
PUSH DE D5
Save Register Pair DE to the stack while Register Pair DE names the sector.
5040
PUSH HL E5
Save Register Pair HL to the stack while Register Pair HL points at the buffer.
5041
GOSUB to 4B64H (DIRCYL) to load Register D with the directory cylinder of drive Register C (byte +09H of its code table).
5044
LD E,00H 1E 00
Load Register E with 00H, sector 0 of the directory cylinder: the granule allocation table.
5046
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H, the buffer for the allocation table, the page after the overlay's code.
5049
GOSUB to 4B45H (RDSSEC) to read cylinder Register D, sector Register E of drive Register C into 5100H: the Z FLAG when it was read and carries the deleted data mark.
504C
POP HL E1
Restore the caller's Register Pair HL from the stack (it was 5100H only inside this routine).
504D
POP DE D1
Restore Register Pair DE from the stack. POP does not change the flags.
504E
RET Z C8
RETurn if the Z FLAG is set (the table was read).
504F
LD A,14H 3E 14
Load Register A with 14H, error 20: GAT read error.
5051
RET C9
RETurn with the NZ FLAG and Register A = 14H.
5052
PUSH DE D5
Save Register Pair DE to the stack while Register Pair DE names the sector.
5053
PUSH HL E5
Save Register Pair HL to the stack while Register Pair HL points at the buffer.
5054
GOSUB to 4B64H (DIRCYL) to load Register D with the directory cylinder of drive Register C.
5057
LD E,00H 1E 00
Load Register E with 00H, sector 0 of the directory cylinder: the granule allocation table.
5059
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H, the allocation table as SYS8/SYS changed it.
505C
GOSUB to 4768H (WRPROT) to write the buffer at 5100H to cylinder Register D, sector Register E of drive Register C with the deleted data mark: the NZ FLAG with an error number on failure.
505F
If the NZ FLAG is set (the write failed), JUMP to 5066H to return error 15H.
5061
GOSUB to 4772H (VERSEC) to read back cylinder Register D, sector Register E of drive Register C without storing it: Register A = 06H for a good sector with the deleted data mark.
5064
CP 06H FE 06
Compare Register A (the verify result) against 06H: the Z FLAG when the sector was written well.
5066
LD A,15H 3E 15
Load Register A with 15H, error 21: GAT write error. LD does not change the flags. 505FH jumps here with the NZ FLAG.
5068
POP HL E1
Restore the caller's Register Pair HL from the stack (it was 5100H only inside this routine).
5069
POP DE D1
Restore Register Pair DE from the stack. POP does not change the flags.
506A
RET C9
RETurn with the Z FLAG on success, or the NZ FLAG and Register A = 15H.
506B
PUSH BC C5
Save Register Pair BC (Register C = the drive) to the stack.
506C
PUSH DE D5
Save Register Pair DE to the stack while Register Pair DE names the sector.
506D
GOSUB to 4B64H (DIRCYL) to load Register D with the directory cylinder of drive Register C.
5070
LD E,01H 1E 01
Load Register E with 01H, sector 1 of the directory cylinder: the hash index table.
5072
LD HL,4300H 21 00 43
Load Register Pair HL with 4300H, the system sector buffer, which also holds the directory sectors that DIRRD reads.
5075
GOSUB to 4B45H (RDSSEC) to read cylinder Register D, sector Register E of drive Register C into 4300H: the Z FLAG when it was read and carries the deleted data mark. Register Pair HL comes back as 4300H.
5078
POP DE D1
Restore the caller's Register Pair DE from the stack, replacing the directory cylinder and sector 01H.
5079
POP BC C1
Restore Register Pair BC from the stack. POP does not change the flags.
507A
LD A,16H 3E 16
Load Register A with 16H, error 22: HIT read error. LD does not change the flags.
507C
RET C9
RETurn with the Z FLAG on success, or the NZ FLAG and Register A = 16H; Register Pair HL = 4300H.
507D
PUSH BC C5
Save Register Pair BC (Register C = the drive) to the stack.
507E
PUSH DE D5
Save Register Pair DE to the stack while Register Pair DE names the sector.
507F
GOSUB to 4B64H (DIRCYL) to load Register D with the directory cylinder of drive Register C.
5082
LD E,01H 1E 01
Load Register E with 01H, sector 1 of the directory cylinder: the hash index table.
5084
LD HL,4300H 21 00 43
Load Register Pair HL with 4300H, the hash index table as SYS8/SYS changed it.
5087
GOSUB to 4768H (WRPROT) to write the buffer at 4300H to cylinder Register D, sector Register E of drive Register C with the deleted data mark: the NZ FLAG with an error number on failure.
508A
If the NZ FLAG is set (the write failed), JUMP to 5091H to return error 17H.
508C
GOSUB to 4772H (VERSEC) to read back cylinder Register D, sector Register E of drive Register C without storing it: Register A = 06H for a good sector with the deleted data mark.
508F
CP 06H FE 06
Compare Register A (the verify result) against 06H: the Z FLAG when the sector was written well.
5091
LD A,17H 3E 17
Load Register A with 17H, error 23: HIT write error. LD does not change the flags. 508AH jumps here with the NZ FLAG.
5093
POP DE D1
Restore the caller's Register Pair DE from the stack, replacing the directory cylinder and sector 01H.
5094
POP BC C1
Restore Register Pair BC from the stack. POP does not change the flags.
5095
RET C9
RETurn with the Z FLAG on success, or the NZ FLAG and Register A = 17H. Register Pair HL = 4300H.