TRS-80 DOS - LDOS 5.3.1 for the Model I - SYS3/SYS Disassembled

Page Customization

Summary:

LDOS 5.3.1 SYS3/SYS Disassembly - File/Device Close and @FNAME (Model I)

SYS3/SYS is the LDOS 5.3.1 system overlay that closes files and logical devices and that services the @FNAME system vector. It is loaded on demand into the transient overlay region at 4E00H by the RST 28H supervisor dispatcher in the resident core (SYS0/SYS) and returns to that dispatcher when finished. This overlay must not be eliminated from a working system disk, and together with SYS2/SYS it must remain on any booting disk from which a SYSGEN configuration file is to be loaded.

When an open disk file is closed, SYS3/SYS finalizes that file's directory record: it flushes any buffered record still waiting to be written, copies the file's end-of-file byte and ending-record-number from the File Control Block (FCB) into the directory entry, and - on LDOS 5.3 disks whose dating is enabled - stamps the entry with the current modification date and time. It then reconciles the granules the file actually needs against the granules that were pre-allocated to it, releasing any excess granules back to the Granule Allocation Table (GAT) and, when a whole secondary directory record (an extended directory entry, or FXDE) becomes empty, unlinking that FXDE and clearing its Hash Index Table (HIT) slot. Finally it rebuilds the caller's FCB into a printable file specification of the form NAME/EXT:d. Closing a logical device instead rebuilds the FCB into the two-character device specification *XX.

The @FNAME service (RST 28H request code A5H) reads a named directory record and constructs the same NAME/EXT:d file-specification string into a caller-supplied buffer, sharing the file-specification builder used by the close path.

On the Model I the physical disk I/O is performed by the resident WD1771 engine in SYS0/SYS through the memory-mapped floppy-disk controller (command/status at 37ECH); SYS3/SYS itself performs no direct controller access, reaching the media only through the SYS0 directory-read, directory-write, sector-read, and sector-write primitives.

Variable and Buffer List

The following table lists the self-modifying operand cells and work buffers that SYS3/SYS uses. Addresses in the 40xxH-4Dxx resident range belong to SYS0/SYS and are cross-referenced there.

Address RangePurpose
4BC9H-4BCAH
2 bytes
Resident load block (A6H 02H) deposited by the overlay loader into the SYS0 padding at 4BBC-4BCA, ahead of the execute-drive-operation routine at 4BCBH. It is data only and is not executed on the SYS3 path.
4E9DH
1 byte
Self-modifying operand for the BIT b,A instruction at 4E9CH. It is patched at 4E96H so the test selects the current drive's bit within the 475DH extended-dating flag byte.
4F9DH
1 byte
Self-modifying operand for the LD B,nn instruction at 4F9CH. It is patched at 4F8EH with the parent directory record number so the FXDE unlink logic can re-read that parent record.
504DH
1 byte
Self-modifying operand for the RES b,B instruction at 504CH. It is patched at 5049H so the correct granule bit is cleared in the GAT byte.
5014H
1 byte
Self-modifying operand for the LD A,nn instruction at 5013H. It is patched at 4FDDH with the ASCII drive digit that terminates the file specification (the ":d" suffix).
4200H-42FFH
256 bytes
The shared SYS0 directory/sector buffer. SYS3/SYS reads and writes the HIT sector here and reads directory-record sectors here through the SYS0 primitives.
5100H-51FFH
256 bytes
The GAT sector work buffer. The Granule Allocation Table sector is read here (504FH), modified in place as granules are freed, and written back (5062H).

Major Routine List

AddressName and Purpose
4E00HOverlay Entry and Request Dispatch
Masks the RST 28H request code in Register A with 70H and routes 10H (@CLOSE) to 4E0CH and 20H (@FNAME) to 501CH, returning for any other value.
4E0CHClose an Open Disk File
Routes device FCBs to 5029H, then for a disk file reads the directory record, flushes the buffer, and copies the EOF byte and ending-record-number from the FCB into the directory entry.
4E69HDirectory Modification Date and Time Stamp
Packs the system date into the directory entry's date bytes and, when the drive's dating flag is set, packs the modification time and extended year into the reused access-password field.
4ECFHRewrite Directory Record and Measure Granule Usage
Writes the finalized directory record, walks the extent and FXDE chain to total the allocated granules, computes how many the file actually needs, and reads the GAT when excess granules must be released.
4F2EHFree One Extent's Granules from the GAT
Computes each granule's absolute position from the extent's cylinder and granule fields, clears its GAT bit, shrinks the extent, and unlinks and de-hashes an FXDE record that becomes empty.
4FB2HFinish Granule Release and Commit
Loops until all excess granules are freed, rewrites the directory record and the GAT sector, re-reads the primary directory record, and points DE at the FCB for the file-specification builder.
4FD8HBuild the File Specification String
Constructs the printable specification NAME/EXT:d, terminated by 03H, from a directory record into the buffer addressed by DE. Shared by the close path and by @FNAME.
501CH@FNAME Service Entry
Reads the requested directory record and calls the file-specification builder to return the file specification to the caller's buffer.
5029HClose a Logical Device
Rebuilds the FCB into the two-character device specification *XX terminated by 03H.
5042HGAT Bit-Clear Helper
Self-modifies a RES instruction so the granule bit selected by Register A is cleared in Register B.
504FHRead the Granule Allocation Table Sector
Reads sector 0 of the directory track (the GAT) into 5100H, returning error 14H on failure.
5062HWrite the Granule Allocation Table Sector
Writes the GAT buffer at 5100H back to sector 0 with a deleted-data address mark and verifies it, returning error 15H on failure.
507BHRead the Hash Index Table Sector
Reads sector 1 of the directory track (the HIT) into 4200H, returning error 16H on failure.
508DHWrite the Hash Index Table Sector
Writes the HIT buffer at 4200H back to sector 1 with a deleted-data address mark and verifies it, returning error 17H on failure.

Cross-Reference Notes

SYS3/SYS is invoked by the resident RST 28H dispatcher in SYS0/SYS for request code 95H (@CLOSE) and request code A5H (@FNAME); both codes are masked with 70H at the overlay entry to select the close (10H) or file-name (20H) sub-function. The overlay calls the following resident SYS0/SYS routines: the file-operation prologue at 49F1H and its secondary entry 49F3H, the buffer-flush routine at 4993H, the directory-record read 4B10H (DIRRD) and write 4B1FH (DIRWR), the directory-sector reader 4B45H (RDSSEC), the directory-track locator 4B65H, the deleted-data-mark sector writer 4768H (WRPROT) and sector verifier 4772H (VERSEC), the DCT geometry-byte reader 479CH (DCTBYT), the divide helper at 44C4H, and the multiply/divide primitive at 4B7BH. It reads the SYS0 clock workspace bytes at 4042H-4046H (minutes, hours, year, day, month) and the SYS2-maintained extended-dating flag byte at 475DH.

Disassembly:

4BC9H - Resident Load Block

A two-byte block the overlay loader deposits into the SYS0 padding area at 4BBC-4BCA, immediately ahead of the resident execute-drive-operation routine at 4BCBH. On the SYS3 path these two bytes are never executed; they are shown as data. Each LDOS overlay writes a different pair here (SYS1 wrote F9H 03H, SYS2 wrote 00H 04H, SYS3 writes A6H 02H).

4BC9
DEFB A6H A6
First byte of the resident load block written into the SYS0 padding at 4BC9H. It is data, not an instruction, on the SYS3 path.
4BCA
DEFB 02H 02
Second byte of the resident load block written into the SYS0 padding at 4BCAH. It is data, not an instruction, on the SYS3 path.

4E00H - Overlay Entry and Request Dispatch

Execution enters here from the SYS0 RST 28H dispatcher with the service request code still in Register A. The code is masked and compared to route the request to the close routine or the @FNAME routine.

4E00
AND 70H E6 70
Register A holds the RST 28H request code passed by the caller (95H for @CLOSE, A5H for @FNAME). Mask it to bits 6-4 so related request codes collapse onto a single sub-function selector, leaving A as 10H, 20H, or another value.
4E02
CP 10H FE 10
Compare the masked selector in Register A against 10H. If Register A equals 10H (the close sub-function, from request code 95H), the Z FLAG is set; otherwise the NZ FLAG is set.
4E04
If the Z FLAG is set (Register A was 10H, an @CLOSE request), JUMP to 4E0CH to close the file or device.
4E06
CP 20H FE 20
Compare the masked selector in Register A against 20H. If Register A equals 20H (the file-name sub-function, from request code A5H), the Z FLAG is set; otherwise the NZ FLAG is set.
4E08
If the Z FLAG is set (Register A was 20H, an @FNAME request), JUMP to 501CH to build the file specification for the requested directory record.
4E0B
RET C9
The request code is neither 10H nor 20H, so this overlay has nothing to do for it. Return to the SYS0 dispatcher.

4E0CH - Close an Open Disk File

Register DE points to the caller's File Control Block (FCB). This routine separates logical devices from disk files, reads the file's directory record, flushes any pending buffered record, and copies the end-of-file byte and ending-record-number from the FCB into the directory entry.

4E0C
LD A,(DE) 1A
Register DE points to the FCB being closed. Load Register A with the FCB type/attribute byte at FCB+00, which is 80H for an open disk file, 10H for an open logical device, or 2AH once already closed.
4E0D
AND 0C0H E6 C0
Isolate bits 7 and 6 of the FCB type byte in Register A. An open disk file (80H) leaves bit 7 set and yields a non-zero result; a logical device (10H) or a closed FCB (2AH) yields zero.
4E0F
If the Z FLAG is set (the FCB is not an open disk file, so it is a logical device), JUMP to 5029H to close it as a device.
4E12
GOSUB to the SYS0 file-operation prologue secondary entry at 49F3H, which validates the FCB pointed to by Register DE and leaves Index Register IX pointing at that FCB for the indexed accesses that follow.
4E15
LD B,(IX+07H) DD 46 07
Load Register B with FCB+07, the directory record number that locates this file's entry within the directory (the directory record read next).
4E18
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06, the drive number on which this file resides.
4E1B
GOSUB to the SYS0 directory-read routine (DIRRD) at 4B10H, which reads the directory sector holding record number B on drive C into the 4200H buffer and returns Register HL pointing at that 32-byte directory record.
4E1E
RET NZ C0
If the NZ FLAG is set, the directory read failed and Register A holds the error code. Return that error to the dispatcher.
4E1F
BIT 4,(HL) CB 66
Register HL points to the directory record's attribute byte (DIR+0). Test bit 4, the record-in-use flag. If it is clear the entry is not active and the Z FLAG is set.
4E21
RET Z C8
If the Z FLAG is set the directory record is not in use, so there is nothing to finalize. Return to the dispatcher.
4E22
PUSH HL E5
Save Register HL, the pointer to the directory record, across the buffer flush.
4E23
PUSH BC C5
Save Register B (the directory record number) and Register C (the drive number) across the buffer flush.
4E24
GOSUB to the SYS0 buffer-flush routine at 4993H, which writes out any partially filled record still held in this file's buffer so the media reflects all data before the file is closed.
4E27
POP BC C1
Restore Register B (directory record number) and Register C (drive number) after the flush.
4E28
POP HL E1
Restore Register HL, the pointer to the directory record, after the flush.
4E29
RET NZ C0
If the NZ FLAG is set the flush failed and Register A holds the error code. Return that error to the dispatcher.
4E2A
LD A,03H 3E 03
Load Register A with the offset 03H, the position of the end-of-file byte within a directory record (DIR+3).
4E2C
ADD A,L 85
Add the low byte of the directory-record pointer (Register L) to the offset in Register A so Register A now addresses DIR+3 within the current record.
4E2D
LD L,A 6F
Store the computed low byte back into Register L so Register HL now points at DIR+3, the directory entry's end-of-file byte.
4E2E
LD A,(IX+08H) DD 7E 08
Load Register A with FCB+08, the current end-of-file byte offset that the FCB accumulated while the file was open.
4E31
PUSH HL E5
Save Register HL, the pointer to DIR+3, so it can be reused whether or not the directory needs updating.
4E32
CP (HL) BE
Compare the FCB end-of-file byte in Register A against the directory entry's stored end-of-file byte at DIR+3. If they are equal the Z FLAG is set; otherwise the NZ FLAG is set.
4E33
If the NZ FLAG is set the end-of-file byte has changed, so the directory record must be rewritten. JUMP to 4E49H to store the new values.
4E35
LD A,11H 3E 11
Load Register A with 11H, the distance from DIR+3 to DIR+20, the ending-record-number low byte within the directory record.
4E37
ADD A,L 85
Add the low byte of the current pointer (Register L, at DIR+3) to 11H in Register A to address DIR+20.
4E38
LD L,A 6F
Store the result into Register L so Register HL points at DIR+20, the ending-record-number low byte of the directory entry.
4E39
LD A,(IX+0CH) DD 7E 0C
Load Register A with FCB+0C, the low byte of the file's ending record number held in the FCB.
4E3C
CP (HL) BE
Compare the FCB ending-record-number low byte in Register A against the directory entry's DIR+20. If they are equal the Z FLAG is set; otherwise the NZ FLAG is set.
4E3D
If the NZ FLAG is set the ending-record-number low byte has changed, so the record must be rewritten. JUMP to 4E49H to store the new values.
4E3F
INC L 2C
Advance Register L so Register HL points at DIR+21, the ending-record-number high byte of the directory entry.
4E40
LD A,(IX+0DH) DD 7E 0D
Load Register A with FCB+0D, the high byte of the file's ending record number held in the FCB.
4E43
CP (HL) BE
Compare the FCB ending-record-number high byte in Register A against the directory entry's DIR+21. If they are equal the Z FLAG is set; otherwise the NZ FLAG is set.
4E44
If the NZ FLAG is set the ending-record-number high byte has changed, so the record must be rewritten. JUMP to 4E49H to store the new values.
4E46
POP AF F1
The FCB values already match the directory entry, so discard the saved DIR+3 pointer from the stack (popped into Register AF, which is not needed).
4E47
Nothing in the file's size fields changed. JUMP to 4E63H to decide whether a modification date/time stamp is still requested.
4E49
POP HL E1
Restore Register HL, the pointer to DIR+3, so the changed size fields can be written into the directory record.
4E4A
LD A,(IX+08H) DD 7E 08
Load Register A with FCB+08, the file's current end-of-file byte.
4E4D
LD (HL),A 77
Store the end-of-file byte into DIR+3, updating the directory entry's recorded end-of-file position.
4E4E
LD A,11H 3E 11
Load Register A with 11H, the distance from DIR+3 to DIR+20 (ending-record-number low).
4E50
ADD A,L 85
Add the low byte of the DIR+3 pointer (Register L) to 11H in Register A to address DIR+20.
4E51
LD L,A 6F
Store the result into Register L so Register HL points at DIR+20.
4E52
LD A,(IX+0CH) DD 7E 0C
Load Register A with FCB+0C, the ending-record-number low byte from the FCB.
4E55
LD (HL),A 77
Store the ending-record-number low byte into DIR+20 of the directory entry.
4E56
INC L 2C
Advance Register L so Register HL points at DIR+21, the ending-record-number high byte.
4E57
LD A,(IX+0DH) DD 7E 0D
Load Register A with FCB+0D, the ending-record-number high byte from the FCB.
4E5A
LD (HL),A 77
Store the ending-record-number high byte into DIR+21 of the directory entry.
4E5B
BIT 2,(IX+00H) DD CB 00 56
Test bit 2 of the FCB type byte at FCB+00, the flag that requests a modification date/time stamp when the file is closed. If the flag is clear the Z FLAG is set.
4E5F
If the Z FLAG is set no date/time stamp is requested, so JUMP directly to 4ECFH to write the updated directory record.
4E61
A date/time stamp is requested. JUMP to 4E69H to pack the modification date and time into the directory record before it is written.
4E63
BIT 2,(IX+00H) DD CB 00 56
On the path where the size fields did not change, test bit 2 of the FCB type byte at FCB+00 again to see whether a modification date/time stamp is still requested. If the flag is clear the Z FLAG is set.
4E67
If the Z FLAG is set nothing in the record changed and no stamp is requested, so the record need not be rewritten. JUMP to 4ED5H to move on to reconciling the file's granule allocation.

4E69H - Directory Modification Date and Time Stamp

Register HL points within the directory record. This routine sets the modification flag in the entry, packs the current system date (month, day, and low year bits) into the LDOS date bytes, and, when the drive's extended-dating bit is set, packs the modification time and the extended year into the field that LDOS 5.3 reclaimed from the old access-password hash. The system clock is kept in the SYS0 workspace bytes at 4042H (minutes), 4043H (hours), 4044H (year), 4045H (day), and 4046H (month).

4E69
PUSH HL E5
Register HL points at DIR+21 (the ending-record-number high byte just handled). Save it so it can be restored after the date/time fields, which lie elsewhere in the record, are written.
4E6A
LD A,L 7D
Copy the low byte of the directory-record pointer from Register L into Register A to compute the record's base offset.
4E6B
AND 0E0H E6 E0
Clear the low 5 bits of Register A. Because each directory record is 32 bytes and aligned, this yields the offset of DIR+0, the start of the record.
4E6D
LD L,A 6F
Store the base offset back into Register L so Register HL points at DIR+0, the attribute byte.
4E6E
INC L 2C
Advance Register L so Register HL points at DIR+1, the byte holding the flags in its high nibble and the modification month in its low nibble.
4E6F
SET 6,(HL) CB F6
Set bit 6 of DIR+1, the flag that marks this directory entry as carrying a modification date/time stamp.
4E71
LD DE,4044H 11 44 40
Point Register DE at the SYS0 clock workspace byte 4044H, which holds the current year.
4E74
LD A,(DE) 1A
Load Register A with the current year value from 4044H.
4E75
PUSH AF F5
Save the year value on the stack; it is needed again for the extended year field if that path is taken.
4E76
PUSH BC C5
Save Register B (directory record number) and Register C (drive number) across the date/time packing.
4E77
AND 07H E6 07
Keep only the low 3 bits of the year in Register A. These are the compatibility year bits that the original LDOS date format stores in the second date byte.
4E79
LD B,A 47
Hold the 3 low year bits in Register B while the day is fetched.
4E7A
INC DE 13
Advance Register DE to 4045H, the current day of the month.
4E7B
LD A,(DE) 1A
Load Register A with the current day value from 4045H.
4E7C
RLCA 07
Rotate Register A left one bit, beginning to move the day value up into bits 7-3.
4E7D
RLCA 07
Rotate Register A left a second bit.
4E7E
RLCA 07
Rotate Register A left a third bit, so the 5-bit day now occupies bits 7-3 of Register A.
4E7F
OR B B0
Merge the 3 low year bits held in Register B into bits 2-0 of Register A, forming the packed day-and-year-low byte.
4E80
INC L 2C
Advance Register L so Register HL points at DIR+2, the remaining date byte.
4E81
LD (HL),A 77
Store the packed day-and-year-low byte from Register A into DIR+2.
4E82
DEC L 2D
Move Register L back so Register HL again points at DIR+1.
4E83
INC DE 13
Advance Register DE to 4046H, the current month.
4E84
LD A,(DE) 1A
Load Register A with the current month value from 4046H.
4E85
LD B,A 47
Hold the month value in Register B while DIR+1 is read.
4E86
LD A,(HL) 7E
Load Register A with the current DIR+1 byte, whose high nibble holds the entry flags (including bit 6 just set).
4E87
AND 0F0H E6 F0
Keep only the high-nibble flag bits in Register A, clearing the low-nibble month field so a fresh month can be inserted.
4E89
OR B B0
Merge the month value (1-12) held in Register B into the low nibble of Register A.
4E8A
LD (HL),A 77
Store the combined flags-and-month byte from Register A back into DIR+1.
4E8B
LD A,C 79
Copy the drive number from Register C into Register A to build a per-drive bit test.
4E8C
AND 07H E6 07
Keep only the drive number 0-7 in Register A.
4E8E
LD B,A 47
Copy the drive number into Register B to use as the loop counter.
4E8F
INC B 04
Increment Register B to (drive number + 1) so the loop below executes once more than the drive number.
4E90
LD A,3FH 3E 3F
Seed Register A with 3FH before accumulating 8 per loop pass.
4E92
ADD A,08H C6 08
Loop Start
Add 8 to Register A on each pass so that after the loop Register A holds 3FH + 8 times (drive + 1), which equals 47H + 8 times the drive number - the opcode byte for BIT (drive),A.
4E94
Loop End
DECrement Register B and LOOP BACK to 4E92H until Register B reaches zero.
4E96
LD (4E9DH),A 32 9D 4E
Self-Modifying Code
Store the value in Register A into the operand byte at 4E9DH, which is the second byte of the BIT instruction at 4E9CH. This rewrites that instruction from BIT 0,A into BIT (drive),A, so the following test selects the current drive's bit.
4E99
LD A,(475DH) 3A 5D 47
Load Register A with the SYS2-maintained extended-dating flag byte at 475DH, which holds one enable bit per drive indicating whether that disk uses the LDOS 5.3 modification date/time stamp.
4E9C
BIT 0,A CB 47
Self-Modifying Code
As patched at 4E96H this is BIT (drive),A. Test the current drive's extended-dating bit within the 475DH flag byte held in Register A. If the bit is set the NZ FLAG is set.
4E9E
If the NZ FLAG is set the drive uses extended dating, so JUMP to 4EA4H to pack the modification time and extended year.
4EA0
POP BC C1
Extended dating is not enabled for this drive; restore Register B (directory record number) and Register C (drive number).
4EA1
POP AF F1
Restore the saved year value into Register AF; it is not needed because the extended field is skipped.
4EA2
Skip the extended time stamp. JUMP to 4ECEH to restore the record pointer.
4EA4
LD A,L 7D
Copy the low byte of the DIR+1 pointer from Register L into Register A to compute the address of the extended date/time field.
4EA5
ADD A,11H C6 11
Add 11H to Register A so it addresses DIR+12H (the field that in LDOS 5.3 holds the modification time, reclaimed from the old access-password hash).
4EA7
LD L,A 6F
Store the result into Register L so Register HL points at DIR+12H.
4EA8
LD DE,(4042H) ED 5B 42 40
Load Register DE from the clock workspace word at 4042H, so Register E holds the current minutes (4042H) and Register D holds the current hours (4043H).
4EAC
LD A,D 7A
Copy the hours from Register D into Register A.
4EAD
LD D,E 53
Copy the minutes from Register E into Register D to prepare the minute value for shifting.
4EAE
LD E,00H 1E 00
Clear Register E so the low bits shifted out of the minutes can accumulate at its top.
4EB0
LD B,03H 06 03
Load Register B with 3, the number of bit positions to shift the DE pair.
4EB2
SRL D CB 3A
Loop Start
Shift Register D (the minutes) right one bit, moving bit 0 into the CARRY FLAG.
4EB4
RR E CB 1B
Rotate the CARRY FLAG into the top of Register E, so the DE pair is shifted right one bit overall and the low minute bits collect at the top of Register E.
4EB6
Loop End
DECrement Register B and LOOP BACK to 4EB2H until the minutes have been shifted right 3 bits (into Register D) with their low 3 bits gathered at the top of Register E.
4EB8
RLCA 07
Rotate Register A (the hours) left one bit toward bits 7-3.
4EB9
RLCA 07
Rotate Register A left a second bit.
4EBA
RLCA 07
Rotate Register A left a third bit so the hours occupy bits 7-3 of Register A.
4EBB
OR D B2
Merge the shifted-down minute high bits held in Register D into the low bits of Register A, forming the high byte of the packed time.
4EBC
LD D,A 57
Store the packed time high byte from Register A into Register D.
4EBD
POP BC C1
Restore Register B (directory record number) and Register C (drive number).
4EBE
POP AF F1
Restore the saved year value into Register A for the extended-year computation.
4EBF
CP 0CH FE 0C
Compare the year in Register A against 0CH (12). Stored years 0-11 represent 2000-2011, and stored years 12 and up represent 1980-1999. If the year is 12 or greater the NO CARRY FLAG is set.
4EC1
If the NO CARRY FLAG is set (year 12 or greater, a 1980s or 1990s year), skip the century adjustment and JUMP to 4EC5H.
4EC3
ADD A,64H C6 64
For years 0-11 add 100 (64H) so that the 2000-2011 range sorts above 1999 before the offset is taken.
4EC5
SUB 50H D6 50
Subtract 80 (50H) so the year becomes an offset counted from 1980.
4EC7
AND 1FH E6 1F
Keep the low 5 bits of Register A, the extended year offset (0-31, spanning 1980 through 2011).
4EC9
OR E B3
Merge the year offset with the low minute bits held at the top of Register E, forming the low byte of the packed time/year word.
4ECA
LD E,A 5F
Store the packed low byte from Register A into Register E.
4ECB
LD (HL),D 72
Store the packed time high byte from Register D into DIR+12H.
4ECC
INC HL 23
Advance Register HL to DIR+13H.
4ECD
LD (HL),E 73
Store the packed time/year low byte from Register E into DIR+13H, completing the modification stamp.
4ECE
POP HL E1
Restore Register HL, the directory-record pointer saved at 4E69H, so the record can be written and its extents examined.

4ECFH - Rewrite Directory Record and Measure Granule Usage

The finalized directory record is written back to disk. The routine then walks the file's extent fields, following the FXDE chain into any secondary directory records, to total the granules currently allocated to the file, compares that total against the granules the file actually needs, and reads the GAT when excess granules must be released.

4ECF
PUSH HL E5
Register HL points into the directory record. Save it across the directory write.
4ED0
GOSUB to the SYS0 directory-write routine (DIRWR) at 4B1FH, which writes the buffer's directory sector back to disk with a deleted-data address mark.
4ED3
POP HL E1
Restore Register HL, the pointer into the directory record.
4ED4
RET NZ C0
If the NZ FLAG is set the directory write failed and Register A holds the error code. Return that error to the dispatcher.
4ED5
INC L 2C
Advance Register L so Register HL points at DIR+22, the first extent field (its first byte, the starting cylinder).
4ED6
PUSH HL E5
Save the extent pointer while the DIR+1 flag byte is tested.
4ED7
LD A,L 7D
Copy the low byte of the extent pointer from Register L into Register A.
4ED8
SUB 15H D6 15
Subtract 15H from Register A to convert the DIR+22 offset back to the DIR+1 offset within the record.
4EDA
LD L,A 6F
Store the result into Register L so Register HL points at DIR+1, the flag/month byte.
4EDB
BIT 7,(HL) CB 7E
Test bit 7 of the DIR+1 flag byte. When this flag is set the file's allocation is left unchanged on close and the granule-release logic below is bypassed; if it is clear the Z FLAG is set.
4EDD
POP HL E1
Restore Register HL, the pointer to the first extent field at DIR+22.
4EDE
If the NZ FLAG is set the allocation must not be trimmed, so JUMP to 4FC5H to finish without releasing any granules.
4EE1
LD DE,0000H 11 00 00
Clear Register DE to zero; it accumulates the running total of granules allocated to the file across all extents.
4EE4
LD A,(HL) 7E
Loop Start
Register HL points at an extent's first byte. Load Register A with that byte, the starting cylinder of this extent (or a terminator value at the end of the extent list).
4EE5
INC L 2C
Advance Register L so Register HL points at the extent's second byte, which packs the starting granule and the granule count.
4EE6
CP 0FEH FE FE
Compare the byte in Register A against 0FEH. A value of 0FEH marks a link to a secondary (FXDE) directory record and 0FFH marks the end of the extent list; either sets the NO CARRY FLAG.
4EE8
If the NO CARRY FLAG is set the value is a terminator (0FEH or 0FFH), so no more extents follow in this record. JUMP to 4EF6H.
4EEA
LD A,(HL) 7E
Load Register A with the extent's second byte, whose bits 7-5 are the starting granule and bits 4-0 are the contiguous granule count minus one.
4EEB
INC L 2C
Advance Register L so Register HL points at the next extent's first byte.
4EEC
AND 1FH E6 1F
Keep bits 4-0 of Register A, the contiguous granule count minus one for this extent.
4EEE
INC A 3C
Add one to Register A to obtain the actual number of granules in this extent.
4EEF
ADD A,E 83
Add this extent's granule count to the running total's low byte in Register E.
4EF0
LD E,A 5F
Store the updated running-total low byte back into Register E.
4EF1
If the addition produced no carry, LOOP BACK to 4EE4H to count the next extent.
4EF3
INC D 14
The low-byte total overflowed, so increment the running total's high byte in Register D.
4EF4
LOOP BACK to 4EE4H to count the next extent.
4EF6
Register A still holds the terminator byte. If it is not 0FEH (so it is 0FFH, no further records), the extent count is complete. JUMP to 4F03H.
4EF8
LD B,(HL) 46
The terminator was 0FEH, a link to a secondary directory record. Register HL points at DIR+31; load Register B with the directory-entry code of that FXDE record.
4EF9
GOSUB to the SYS0 directory-read routine (DIRRD) at 4B10H to read the secondary FXDE directory record numbered in Register B; Register HL returns pointing at that record.
4EFC
RET NZ C0
If the NZ FLAG is set the read of the secondary record failed and Register A holds the error code. Return it to the dispatcher.
4EFD
LD A,L 7D
Copy the low byte of the secondary-record pointer from Register L into Register A.
4EFE
ADD A,16H C6 16
Add 16H to Register A to address the secondary record's first extent field (its DIR+22).
4F00
LD L,A 6F
Store the result into Register L so Register HL points at the secondary record's first extent.
4F01
LOOP BACK to 4EE4H to continue totalling granules from the secondary record's extents.
4F03
PUSH HL E5
Save the current extent pointer while the number of granules the file needs is computed.
4F04
LD L,(IX+0CH) DD 6E 0C
Load Register L with FCB+0C, the low byte of the file's ending record number.
4F07
LD H,(IX+0DH) DD 66 0D
Load Register H with FCB+0D, the high byte of the ending record number, so Register HL holds the count of full logical records in the file.
4F0A
LD A,08H 3E 08
Load Register A with 8, the index of the DCT geometry byte DCT+8.
4F0C
GOSUB to the SYS0 DCT geometry reader (DCTBYT) at 479CH to read DCT+8 for the current drive; its bits 4-0 hold the number of sectors per granule.
4F0F
AND 1FH E6 1F
Keep bits 4-0 of Register A, the sectors-per-granule for this drive.
4F11
PUSH AF F5
Save the sectors-per-granule value for reuse as the divisor.
4F12
ADD A,L 85
Add sectors-per-granule to the ending-record-number low byte in Register L, biasing the record count so the division below rounds up to whole granules.
4F13
LD L,A 6F
Store the biased low byte back into Register L.
4F14
If the bias produced no carry, skip the high-byte increment and JUMP to 4F17H.
4F16
INC H 24
The low-byte bias overflowed, so increment the ending-record-number high byte in Register H.
4F17
POP AF F1
Restore the sectors-per-granule value into Register A.
4F18
INC A 3C
Increment Register A to form the divisor used to convert the biased record count into a granule count.
4F19
GOSUB to the SYS0 divide helper at 44C4H, dividing the biased ending record number in Register HL by the divisor in Register A; Register HL returns the number of granules the file's data actually needs.
4F1C
XOR A AF
Set Register A to ZERO and clear the CARRY FLAG in preparation for the subtraction.
4F1D
EX DE,HL EB
Exchange Registers DE and HL, so Register HL now holds the total allocated granules that were accumulated in DE and Register DE holds the granules needed.
4F1E
SBC HL,DE ED 52
Subtract the granules-needed (Register DE) from the granules-allocated (Register HL); Register HL becomes the count of excess, pre-allocated granules. The Z FLAG is set if there is no excess, and the CARRY FLAG is set if fewer are allocated than needed.
4F20
EX DE,HL EB
Exchange back so Register DE holds the excess granule count and Register HL holds the granules-needed.
4F21
POP HL E1
Restore the extent pointer saved at 4F03H.
4F22
If the Z FLAG is set there is no excess allocation, so nothing needs freeing. JUMP to 4FC5H to finish.
4F25
If the CARRY FLAG is set the file needs at least as many granules as are allocated, so there is nothing to release. JUMP to 4FC5H to finish.
4F28
GOSUB to 504FH to read the Granule Allocation Table sector into the 5100H buffer so the freed granules' bits can be cleared.
4F2B
RET NZ C0
If the NZ FLAG is set the GAT read failed and Register A holds error code 14H. Return it to the dispatcher.
4F2C
JUMP to 4F6FH to begin releasing the excess granules, working from the last extent backward.

4F2EH - Free One Extent's Granules from the GAT

This is the granule-release worker. For the granule at the top of the current extent it computes the absolute cylinder and the granule position within that cylinder, clears that granule's bit in the GAT buffer, and shrinks the extent by one granule. When an extent empties its fields are cleared to FFFFH, and when an emptied extent belongs to a secondary (FXDE) directory record that record is marked free, de-hashed from the HIT, and unlinked from its parent. The loop is first entered at 4F6FH from 4F2CH.

4F2E
PUSH DE D5
Loop Start
Register DE holds the remaining count of excess granules to free. Save it while one granule is released.
4F2F
LD A,(HL) 7E
Register HL points at the current extent's second byte. Load Register A with it; bits 7-5 are the extent's starting granule and bits 4-0 are the granule count minus one.
4F30
AND 0E0H E6 E0
Keep bits 7-5 of Register A, the extent's starting granule number (still in the high bits).
4F32
RLCA 07
Rotate Register A left one bit toward the low end.
4F33
RLCA 07
Rotate Register A left a second bit.
4F34
RLCA 07
Rotate Register A left a third bit so the starting granule number now sits in bits 2-0.
4F35
LD E,A 5F
Store the starting granule number into Register E.
4F36
LD A,(HL) 7E
Reload Register A with the extent's second byte.
4F37
AND 1FH E6 1F
Keep bits 4-0 of Register A, the granule count minus one for this extent.
4F39
ADD A,E 83
Add the starting granule (Register E) to the count-minus-one in Register A, giving the granule offset of the last (top) granule in this extent, measured from the extent's starting cylinder.
4F3A
LD E,A 5F
Store that top-granule offset into Register E.
4F3B
LD A,08H 3E 08
Load Register A with 8, the index of the DCT geometry byte DCT+8.
4F3D
GOSUB to the SYS0 DCT geometry reader (DCTBYT) at 479CH to read DCT+8, whose bits 7-5 hold the number of granules per track for this drive.
4F40
RLCA 07
Rotate Register A left one bit to begin moving the granules-per-track field down.
4F41
RLCA 07
Rotate Register A left a second bit.
4F42
RLCA 07
Rotate Register A left a third bit so the granules-per-track field now sits in bits 2-0.
4F43
AND 07H E6 07
Keep bits 2-0 of Register A, the granules-per-track value.
4F45
INC A 3C
Add one to obtain the actual number of granules per cylinder.
4F46
LD D,A 57
Store the granules-per-cylinder into Register D.
4F47
LD A,04H 3E 04
Load Register A with 4, the index of the DCT geometry byte DCT+4.
4F49
GOSUB to the SYS0 DCT geometry reader (DCTBYT) at 479CH to read DCT+4, whose bit 5 indicates double-sided operation.
4F4C
BIT 5,A CB 6F
Test bit 5 of DCT+4 in Register A, the double-sided flag. If it is clear (single-sided) the Z FLAG is set.
4F4E
LD A,D 7A
Load Register A with the granules-per-cylinder value from Register D.
4F4F
If the Z FLAG is set the drive is single-sided, so the granules-per-cylinder value stands. JUMP to 4F52H.
4F51
RLCA 07
The drive is double-sided, so rotate Register A left once to double the granules-per-cylinder count.
4F52
GOSUB to the SYS0 divide primitive at 4B7BH, dividing the top-granule offset in Register E by the granules-per-cylinder in Register A. The quotient (the number of whole cylinders into the extent) returns in Register A and the remainder (the granule's position within its cylinder) returns in Register E.
4F55
DEC L 2D
Move Register L back so Register HL points at the extent's first byte, the starting cylinder.
4F56
ADD A,(HL) 86
Add the extent's starting cylinder to the whole-cylinder quotient in Register A, giving the absolute cylinder number that holds the granule to be freed.
4F57
LD D,A 57
Store the absolute cylinder number into Register D; the GAT is indexed by cylinder.
4F58
PUSH HL E5
Save the extent pointer (at the starting-cylinder byte) while the GAT byte is updated.
4F59
PUSH BC C5
Save Registers B and C while the GAT byte is updated.
4F5A
LD H,51H 26 51
Load Register H with 51H, the high byte of the GAT work buffer at 5100H.
4F5C
LD L,D 6A
Load Register L with the cylinder number in Register D, so Register HL addresses 5100H plus the cylinder - the GAT byte whose bits track that cylinder's granules.
4F5D
LD B,(HL) 46
Load Register B with the GAT byte for this cylinder, in which a set bit marks an allocated granule.
4F5E
LD A,E 7B
Load Register A with the granule's position within its cylinder (Register E), the bit number to clear.
4F5F
GOSUB to the GAT bit-clear helper at 5042H, which self-modifies a RES instruction to clear the granule bit selected by Register A within the GAT byte in Register B, freeing that granule.
4F62
LD (HL),B 70
Write the updated GAT byte from Register B back into the 5100H buffer.
4F63
POP BC C1
Restore Registers B and C.
4F64
POP HL E1
Restore the extent pointer to the starting-cylinder byte.
4F65
INC L 2C
Advance Register L so Register HL points at the extent's second byte (the starting-granule and count byte).
4F66
DEC (HL) 35
Decrement the extent's second byte, reducing its granule count by one to reflect the granule just freed.
4F67
LD A,(HL) 7E
Load Register A with the updated extent byte.
4F68
INC A 3C
Add one to Register A to test the extent's remaining granule count.
4F69
AND 1FH E6 1F
Keep bits 4-0 of Register A. A non-zero result means the extent still holds granules; a zero result means this extent has just been emptied. The Z FLAG reflects that test.
4F6B
POP DE D1
Restore the remaining excess-granule count into Register DE.
4F6C
DEC DE 1B
Decrement the remaining excess-granule count by one for the granule just freed (this does not disturb the Z FLAG set at 4F69H).
4F6D
If the NZ FLAG is set (from 4F69H) the extent still holds granules, so it need not be cleared. JUMP to 4FB2H to check whether more granules remain to free.
4F6F
LD (HL),0FFH 36 FF
The extent is empty (on the first entry from 4F2CH this clears the trailing link byte). Store 0FFH into the byte at Register HL.
4F71
DEC L 2D
Move Register L back to the preceding byte of this extent field (or link) pair.
4F72
LD (HL),0FFH 36 FF
Store 0FFH into that byte too, so the two-byte extent field (or the FXDE link) now reads FFFFH, meaning empty or no link.
4F74
DEC L 2D
Move Register L back to the second byte of the preceding extent field.
4F75
LD A,L 7D
Copy the low byte of the pointer from Register L into Register A.
4F76
AND 1FH E6 1F
Keep the low 5 bits of Register A, the offset within the 32-byte directory record.
4F78
CP 15H FE 15
Compare the offset against 15H. Reaching 15H means the pointer has backed up past DIR+22, so all four extent fields of this record have been cleared. If so the Z FLAG is set.
4F7A
If the NZ FLAG is set extents remain in this record, so JUMP to 4FB2H to continue freeing.
4F7C
XOR L AD
Exclusive-OR Register L into Register A (which holds 15H); because Register L is the record base plus 15H, this clears the offset and leaves Register A holding the record's base offset.
4F7D
LD L,A 6F
Store the base offset into Register L so Register HL points at DIR+0 of this record.
4F7E
BIT 7,(HL) CB 7E
Test bit 7 of the DIR+0 attribute byte, which is set for a secondary (FXDE) directory record and clear for a primary record. If it is clear the Z FLAG is set.
4F80
If the Z FLAG is set this is the primary directory record, which is never freed, so JUMP to 4FB2H.
4F82
LD (HL),00H 36 00
This is a secondary FXDE record whose extents are all gone. Store 00H into DIR+0 to mark the record unused.
4F84
GOSUB to the SYS0 directory-write routine (DIRWR) at 4B1FH to write the now-freed secondary record back to disk.
4F87
RET NZ C0
If the NZ FLAG is set the write failed and Register A holds the error code. Return it to the dispatcher.
4F88
LD A,B 78
Load Register A with the freed FXDE record's directory-entry code, held in Register B.
4F89
AND 0E0H E6 E0
Keep bits 7-5 of Register A, the record's slot-base bits within the directory buffer.
4F8B
INC A 3C
Advance Register A to the byte just past that slot base.
4F8C
LD L,A 6F
Store the offset into Register L so Register HL addresses that byte in the directory buffer.
4F8D
LD A,(HL) 7E
Load Register A with that directory byte, the parent record number that owns this FXDE.
4F8E
LD (4F9DH),A 32 9D 4F
Self-Modifying Code
Store the parent record number into the operand byte at 4F9DH, which is the immediate value of the LD B instruction at 4F9CH, so the parent record can be re-read after the HIT is updated.
4F91
GOSUB to 507BH to read the Hash Index Table sector into the 4200H buffer.
4F94
RET NZ C0
If the NZ FLAG is set the HIT read failed and Register A holds error code 16H. Return it to the dispatcher.
4F95
LD L,B 68
Load Register L with the FXDE record number in Register B, indexing that record's byte within the HIT buffer at 4200H.
4F96
LD (HL),00H 36 00
Store 00H into the FXDE's HIT byte, removing its hash-index entry so the freed record is no longer found by name.
4F98
GOSUB to 508DH to write the updated HIT sector back to disk.
4F9B
RET NZ C0
If the NZ FLAG is set the HIT write failed and Register A holds error code 17H. Return it to the dispatcher.
4F9C
LD B,00H 06 00
Self-Modifying Code
As patched at 4F8EH, this loads Register B with the parent record number so the parent directory record can be re-read.
4F9E
GOSUB to the SYS0 directory-read routine (DIRRD) at 4B10H to read the parent directory record numbered in Register B into the buffer.
4FA1
RET NZ C0
If the NZ FLAG is set the parent read failed and Register A holds the error code. Return it to the dispatcher.
4FA2
LD A,B 78
Load Register A with the parent record number in Register B.
4FA3
OR 1FH F6 1F
Set the low 5 bits of Register A so it addresses DIR+31, the last byte of the parent record's slot in the buffer.
4FA5
LD L,A 6F
Store the offset into Register L so Register HL points at the parent's DIR+31.
4FA6
LD (HL),0FFH 36 FF
Store 0FFH into DIR+31 of the parent record, clearing the directory-entry code that pointed at the freed FXDE.
4FA8
DEC L 2D
Move Register L back so Register HL points at the parent's DIR+30, the FXDE link byte.
4FA9
LD (HL),0FFH 36 FF
Store 0FFH into DIR+30, marking the parent record as having no secondary (FXDE) record linked.
4FAB
DEC L 2D
Move Register L back to DIR+29 of the parent record.
4FAC
PUSH HL E5
Save the parent-record pointer across the directory write.
4FAD
GOSUB to the SYS0 directory-write routine (DIRWR) at 4B1FH to write the parent record back with its FXDE link removed.
4FB0
POP HL E1
Restore the parent-record pointer.
4FB1
RET NZ C0
If the NZ FLAG is set the parent write failed and Register A holds the error code. Return it to the dispatcher.

4FB2H - Finish Granule Release and Commit

The release loop converges here. While excess granules remain it re-enters the worker; once all are freed it writes the shrunken directory record and the updated GAT sector back to disk, re-reads the file's primary directory record, and points Register DE at the FCB so the file specification can be rebuilt into it.

4FB2
LD A,D 7A
Load Register A with the high byte of the remaining excess-granule count in Register DE.
4FB3
OR E B3
Combine with the low byte in Register E. If the excess-granule count in Register DE is zero the Z FLAG is set.
4FB4
If the NZ FLAG is set granules still need freeing, so JUMP to 4F2EH to release the next one.
4FB7
GOSUB to the SYS0 directory-write routine (DIRWR) at 4B1FH to write the directory record, whose extent fields now reflect the released granules, back to disk.
4FBA
If the Z FLAG is set the directory write succeeded, so JUMP to 4FC1H to commit the GAT.
4FBC
CP 0FH FE 0F
Compare the write status in Register A against 0FH. This is the non-fatal directory condition that is allowed to fall through to completion.
4FBE
RET NZ C0
If the NZ FLAG is set the status is a genuine error, so return it to the dispatcher.
4FBF
The status was 0FH, treated as non-fatal, so JUMP to 4FC5H to finish and rebuild the file specification.
4FC1
GOSUB to 5062H to write the updated GAT sector from the 5100H buffer back to disk, committing the freed granules to the disk's free space.
4FC4
RET NZ C0
If the NZ FLAG is set the GAT write failed and Register A holds error code 15H. Return it to the dispatcher.
4FC5
LD A,(IX+07H) DD 7E 07
Load Register A with FCB+07, the file's primary directory record number.
4FC8
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06, the drive number.
4FCB
XOR B A8
Exclusive-OR Register B (the directory record currently held in the buffer, which may be a secondary FXDE record) into Register A to detect a difference from the primary record number.
4FCC
AND 1FH E6 1F
Keep the low 5 bits of the difference. A non-zero result means the buffer does not currently hold the primary directory record and sets the NZ FLAG.
4FCE
LD B,(IX+07H) DD 46 07
Load Register B with FCB+07, the primary directory record number, ready for a re-read.
4FD1
If the NZ FLAG is set the buffer holds a different record, so GOSUB to the SYS0 directory-read routine (DIRRD) at 4B10H to reload the primary directory record so the file name can be read from it.
4FD4
RET NZ C0
If the NZ FLAG is set the re-read failed and Register A holds the error code. Return it to the dispatcher.
4FD5
PUSH IX DD E5
Push Index Register IX, which points at the FCB, so its value can be transferred to Register DE.
4FD7
POP DE D1
Pop the FCB address into Register DE, making the FCB itself the destination buffer for the file specification built next.

4FD8H - Build the File Specification String

Register HL addresses the directory record for the file and Register DE addresses the destination buffer. This routine copies the name and extension from the directory record and appends the drive suffix, producing the printable specification NAME/EXT:d terminated by an 03H byte. It is shared by the close path (which builds the specification back into the FCB) and by the @FNAME service.

4FD8
LD A,C 79
Load Register A with the drive number in Register C.
4FD9
AND 07H E6 07
Keep the drive number 0-7 in Register A.
4FDB
OR 30H F6 30
Set bits to convert the drive number into its ASCII digit, so 0-7 becomes the characters 0 through 7.
4FDD
LD (5014H),A 32 14 50
Self-Modifying Code
Store the ASCII drive digit into the operand byte at 5014H, which is the immediate value of the LD A instruction at 5013H, so the drive suffix written at the end of the string carries the correct digit.
4FE0
LD H,42H 26 42
Load Register H with 42H, the high byte of the directory buffer at 4200H.
4FE2
LD A,B 78
Load Register A with the directory record number in Register B to locate its slot in the buffer.
4FE3
AND 0E0H E6 E0
Keep bits 7-5 of Register A, the record's slot-base offset within the buffer.
4FE5
OR 05H F6 05
Add offset 5 so Register A addresses DIR+5, the start of the 8-character file name.
4FE7
LD L,A 6F
Store the offset into Register L so Register HL points at the file name within the directory record.
4FE8
PUSH HL E5
Save the pointer to the file name so the extension can be located afterward.
4FE9
LD B,08H 06 08
Load Register B with 8, the maximum number of file-name characters to copy.
4FEB
LD A,(HL) 7E
Loop Start
Load Register A with the next file-name character from the directory record.
4FEC
CP 20H FE 20
Compare the character in Register A against 20H (a space). File names are blank-padded, so a space marks the end of the name; if so the Z FLAG is set.
4FEE
If the Z FLAG is set the name is finished, so JUMP to 4FF5H to handle the extension.
4FF0
LD (DE),A 12
Store the file-name character from Register A into the destination buffer addressed by Register DE.
4FF1
INC HL 23
Advance Register HL to the next source character in the directory record.
4FF2
INC DE 13
Advance Register DE to the next position in the destination buffer.
4FF3
Loop End
DECrement Register B and LOOP BACK to 4FEBH until all 8 name characters have been examined.
4FF5
POP HL E1
Restore Register HL to the start of the file name in the directory record.
4FF6
LD A,L 7D
Copy the low byte of the name pointer from Register L into Register A.
4FF7
ADD A,08H C6 08
Add 8 to Register A so it addresses DIR+13, the start of the 3-character extension (the name field is 8 bytes long).
4FF9
LD L,A 6F
Store the offset into Register L so Register HL points at the extension field.
4FFA
LD A,(HL) 7E
Load Register A with the first extension character.
4FFB
CP 20H FE 20
Compare it against 20H (a space). A space in the first extension position means the file has no extension; if so the Z FLAG is set.
4FFD
If the Z FLAG is set there is no extension, so JUMP to 500FH to append the drive suffix.
4FFF
LD A,2FH 3E 2F
Load Register A with 2FH, the ASCII slash / that separates the name from the extension.
5001
LD (DE),A 12
Store the slash into the destination buffer addressed by Register DE.
5002
INC DE 13
Advance Register DE past the slash.
5003
LD B,03H 06 03
Load Register B with 3, the maximum number of extension characters to copy.
5005
LD A,(HL) 7E
Loop Start
Load Register A with the next extension character from the directory record.
5006
CP 20H FE 20
Compare it against 20H (a space), which marks the end of a blank-padded extension; if so the Z FLAG is set.
5008
If the Z FLAG is set the extension is finished, so JUMP to 500FH to append the drive suffix.
500A
LD (DE),A 12
Store the extension character from Register A into the destination buffer addressed by Register DE.
500B
INC HL 23
Advance Register HL to the next extension character in the directory record.
500C
INC DE 13
Advance Register DE to the next destination position.
500D
Loop End
DECrement Register B and LOOP BACK to 5005H until all 3 extension characters have been examined.
500F
LD A,3AH 3E 3A
Load Register A with 3AH, the ASCII colon : that introduces the drive suffix.
5011
LD (DE),A 12
Store the colon into the destination buffer addressed by Register DE.
5012
INC DE 13
Advance Register DE past the colon.
5013
LD A,00H 3E 00
Self-Modifying Code
As patched at 4FDDH, this loads Register A with the ASCII drive digit that completes the ":d" suffix.
5015
LD (DE),A 12
Store the drive digit into the destination buffer addressed by Register DE.
5016
INC DE 13
Advance Register DE past the drive digit.
5017
LD A,03H 3E 03
Load Register A with 03H, the end-of-string terminator that LDOS uses to mark the end of a file specification.
5019
LD (DE),A 12
Store the 03H terminator into the destination buffer addressed by Register DE.
501A
XOR A AF
Set Register A to ZERO, signalling success, and set the Z FLAG.
501B
RET C9
Return to the caller with the completed file specification in the destination buffer.

501CH - @FNAME Service Entry

This is the entry reached for the @FNAME request (RST 28H code A5H). Register DE addresses the caller's buffer and Registers B and C select the directory record number and drive. The routine reads that directory record and calls the file-specification builder to return the file name to the caller.

501C
PUSH DE D5
Register DE points at the caller's destination buffer. Save it across the directory read.
501D
PUSH HL E5
Save Register HL across the directory read.
501E
GOSUB to the SYS0 directory-read routine (DIRRD) at 4B10H to read the directory record numbered in Register B on the drive in Register C; Register HL returns pointing at that record.
5021
If the NZ FLAG is set the directory read failed, so skip building the name and JUMP to 5026H.
5023
GOSUB to the file-specification builder at 4FD8H to construct the NAME/EXT:d string for this record into the caller's buffer addressed by Register DE.
5026
POP HL E1
Restore Register HL.
5027
POP DE D1
Restore Register DE, the caller's buffer pointer.
5028
RET C9
Return to the SYS0 dispatcher with the file specification (or the read error) delivered to the caller.

5029H - Close a Logical Device

Reached when the FCB being closed is a logical device rather than an open disk file. The routine rebuilds the FCB into the two-character device specification of the form *XX, terminated by 03H, restoring it to a form that can be re-opened.

5029
GOSUB to the SYS0 file-operation prologue at 49F1H, which validates the FCB pointed to by Register DE and leaves Index Register IX pointing at that FCB.
502C
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06, the first character of the device name held there while the device was open.
502F
LD B,(IX+07H) DD 46 07
Load Register B with FCB+07, the second character of the device name held there while the device was open.
5032
LD (IX+00H),2AH DD 36 00 2A
Store 2AH (the ASCII asterisk *) into FCB+00, marking the FCB as a closed device specification that begins with an asterisk.
5036
LD (IX+01H),C DD 71 01
Store the first device-name character from Register C into FCB+01, the character after the asterisk.
5039
LD (IX+02H),B DD 70 02
Store the second device-name character from Register B into FCB+02.
503C
LD (IX+03H),03H DD 36 03 03
Store 03H into FCB+03, the end-of-string terminator, completing the *XX device specification.
5040
XOR A AF
Set Register A to ZERO to signal success and set the Z FLAG.
5041
RET C9
Return to the SYS0 dispatcher; the device is closed.

5042H - GAT Bit-Clear Helper

A small self-modifying helper that clears one granule bit in a GAT byte. Register A selects the bit and Register B holds the GAT byte; the routine rewrites its own RES instruction so the selected bit is the one cleared.

5042
AND 07H E6 07
Keep the low 3 bits of Register A, the granule bit number (0-7) to clear within the GAT byte.
5044
RLCA 07
Rotate Register A left one bit, beginning to position the bit number into the RES instruction's opcode field.
5045
RLCA 07
Rotate Register A left a second bit.
5046
RLCA 07
Rotate Register A left a third bit, so the bit number now occupies bits 5-3, matching the encoding of RES b,B.
5047
OR 80H F6 80
Set bit 7 of Register A to form the second byte of the CB-prefixed instruction, so Register A now holds the opcode for RES (bit),B.
5049
LD (504DH),A 32 4D 50
Self-Modifying Code
Store the computed opcode from Register A into the operand byte at 504DH, which is the second byte of the CB-prefixed instruction at 504CH, converting RES 0,B into RES (bit),B.
504C
RES 0,B CB 80
Self-Modifying Code
As patched at 5049H, this clears the selected granule bit in Register B, freeing that granule within the cylinder's GAT byte.
504E
RET C9
Return to the caller with the granule bit cleared in Register B.

504FH - Read the Granule Allocation Table Sector

Reads sector 0 of the directory track - the Granule Allocation Table - into the 5100H work buffer so its granule bits can be examined and cleared. Returns error 14H on a read failure.

504F
PUSH DE D5
Save Register DE across the sector read.
5050
PUSH HL E5
Save Register HL across the sector read.
5051
GOSUB to the SYS0 directory-track locator at 4B65H, which establishes the directory-track context for the current drive so the following sector read targets the directory track.
5054
LD E,00H 1E 00
Load Register E with 0, selecting sector 0 of the directory track, which holds the Granule Allocation Table.
5056
LD HL,5100H 21 00 51
Point Register HL at the 5100H work buffer, the destination for the GAT sector.
5059
GOSUB to the SYS0 directory-sector reader (RDSSEC) at 4B45H to read sector E (the GAT) into the buffer addressed by Register HL.
505C
POP HL E1
Restore Register HL after the read.
505D
POP DE D1
Restore Register DE after the read.
505E
RET Z C8
If the Z FLAG is set the GAT read succeeded, so return to the caller.
505F
LD A,14H 3E 14
The read failed, so load Register A with error code 14H, the GAT read error.
5061
RET C9
Return to the caller with the error code in Register A and the NZ FLAG set.

5062H - Write the Granule Allocation Table Sector

Writes the GAT work buffer at 5100H back to sector 0 of the directory track with a deleted-data address mark, then verifies it. Directory-track sectors carry a deleted-data address mark, which is how LDOS distinguishes them. Returns error 15H on failure.

5062
PUSH DE D5
Save Register DE across the sector write.
5063
PUSH HL E5
Save Register HL across the sector write.
5064
GOSUB to the SYS0 directory-track locator at 4B65H to establish the directory-track context for the current drive.
5067
LD E,00H 1E 00
Load Register E with 0, selecting sector 0 (the GAT) as the write target.
5069
LD HL,5100H 21 00 51
Point Register HL at the 5100H work buffer, the source of the GAT data to be written.
506C
GOSUB to the SYS0 protected-sector writer (WRPROT) at 4768H, which writes the buffer to sector E with a deleted-data address mark as required for directory-track sectors.
506F
If the NZ FLAG is set the write failed, so skip the verify and JUMP to 5076H to report the error.
5071
GOSUB to the SYS0 sector verifier (VERSEC) at 4772H to confirm the GAT sector was written correctly.
5074
CP 06H FE 06
Compare the verify status in Register A against 06H to set the result flags for the caller.
5076
LD A,15H 3E 15
Load Register A with error code 15H, the GAT write error, which is returned when the write or verify indicated failure.
5078
POP HL E1
Restore Register HL after the write.
5079
POP DE D1
Restore Register DE after the write.
507A
RET C9
Return to the caller with the write/verify status in the flags.

507BH - Read the Hash Index Table Sector

Reads sector 1 of the directory track - the Hash Index Table - into the 4200H directory buffer so a freed record's hash entry can be cleared. Returns error 16H on a read failure.

507B
PUSH BC C5
Save Registers B and C across the sector read.
507C
PUSH DE D5
Save Register DE across the sector read.
507D
GOSUB to the SYS0 directory-track locator at 4B65H to establish the directory-track context for the current drive.
5080
LD E,01H 1E 01
Load Register E with 1, selecting sector 1 of the directory track, which holds the Hash Index Table.
5082
LD HL,4200H 21 00 42
Point Register HL at the 4200H directory buffer, the destination for the HIT sector.
5085
GOSUB to the SYS0 directory-sector reader (RDSSEC) at 4B45H to read sector E (the HIT) into the buffer addressed by Register HL.
5088
POP DE D1
Restore Register DE after the read.
5089
POP BC C1
Restore Registers B and C after the read.
508A
LD A,16H 3E 16
Load Register A with error code 16H, the HIT read error, which is meaningful only when the read set the NZ FLAG.
508C
RET C9
Return to the caller; the flags from the read indicate success or the 16H error.

508DH - Write the Hash Index Table Sector

Writes the HIT buffer at 4200H back to sector 1 of the directory track with a deleted-data address mark, then verifies it. Returns error 17H on failure.

508D
PUSH BC C5
Save Registers B and C across the sector write.
508E
PUSH DE D5
Save Register DE across the sector write.
508F
GOSUB to the SYS0 directory-track locator at 4B65H to establish the directory-track context for the current drive.
5092
LD E,01H 1E 01
Load Register E with 1, selecting sector 1 (the HIT) as the write target.
5094
LD HL,4200H 21 00 42
Point Register HL at the 4200H directory buffer, the source of the HIT data to be written.
5097
GOSUB to the SYS0 protected-sector writer (WRPROT) at 4768H, which writes the HIT buffer to sector E with a deleted-data address mark.
509A
If the NZ FLAG is set the write failed, so skip the verify and JUMP to 50A1H to report the error.
509C
GOSUB to the SYS0 sector verifier (VERSEC) at 4772H to confirm the HIT sector was written correctly.
509F
CP 06H FE 06
Compare the verify status in Register A against 06H to set the result flags for the caller.
50A1
LD A,17H 3E 17
Load Register A with error code 17H, the HIT write error, which is returned when the write or verify indicated failure.
50A3
POP DE D1
Restore Register DE after the write.
50A4
POP BC C1
Restore Registers B and C after the write.
50A5
RET C9
Return to the caller with the write/verify status in the flags. This is the end of the SYS3/SYS overlay image (END 4E00H).