TRS-80 DOS - VTOS 4.0.2 for the Model I - SYS3/SYS Disassembled

Page Customization

Introduction/Summary:

VTOS 4.0.2 SYS3/SYS Disassembly - File Close, Extent Chain, and GAT Deallocation Overlay (Model I)

SYS3/SYS is the VTOS 4.0 overlay responsible for file close operations, extent chain management, and Granule Allocation Table (GAT) deallocation. It occupies the overlay address space from 4E00H to 5061H and is loaded on demand by the resident DOS when an SVC request code targeting SYS3 is issued via RST 28H.

The overlay handles three SVC request codes: 10H (file close), 30H (directory-to-filespec conversion), and 00H (FCB initialization for unopened files). The file close operation is the most complex, encompassing directory entry updates with EOF offset and extent chain pointers, date and time stamping using the system clock at 4044H-4046H, extent chain traversal to count allocated sectors, comparison of allocated versus used sectors, and sector-by-sector deallocation of excess extents with GAT bitmap updates.

A filespec builder routine at 4F87H constructs human-readable filespec strings in the format FILENAME/EXT:D from directory entries. The GAT bit manipulation routine at 4FF6H uses self-modifying code to dynamically construct a Z80 CB-prefix RES instruction, allowing any bit position (0-7) to be cleared in a single byte. Four directory/GAT I/O helper routines handle sector reads and writes with error codes and write-verification.

SYS3 uses two separate buffers: 5100H for directory sector I/O (distinct from the SYS0 directory buffer at 4200H) and 4200H for GAT sector I/O (shared with SYS2). This dual-buffer approach allows simultaneous access to both directory and GAT data during the deallocation loop.

Variable and Memory Locations

AddressBytesPurpose
4044H-4046H3System date: day (4044H), month (4045H), year (4046H). Read by close routine for directory date stamping. Day uses internal base 50H format.
4200H256GAT sector buffer. Shared with SYS2. Read/written by 502FH and 5041H.
430FH1System mode flags. Bit 2 (kill-file active) tested at 4E55H and 4E5DH to control date stamping and kill processing.
4F4BH1Self-modifying: LD B,nn operand. Stores HIT entry count during continuation entry cleanup (written by 4F3CH).
4FC3H1Self-modifying: LD A,nn operand. Stores ASCII drive digit for filespec builder (written by 4F8CH).
5001H1Self-modifying: second byte of CB-prefix RES instruction. Stores computed RES n,B opcode (written by 4FFDH).
5100H256Directory sector buffer. Used by SYS3 read/write directory helpers (5003H, 5016H). Separate from the SYS0 directory buffer at 4200H.

Major Routines

AddressName / Purpose
4E00HSVC Dispatcher
Routes request codes: 10H to close file, 30H to filespec builder, 00H to FCB initializer.
4E0DHClose File
Validates FCB, reads directory entry, updates EOF offset and extent pointers, performs date stamping, writes directory back to disk.
4E63HDate Encoder
Reads system date from 4044H-4046H, encodes month/day/year into directory entry flags and date bytes.
4E91HExtent Chain Walker
Traverses extent chain counting total allocated sectors. Follows FEH continuation pointers across directory entries.
4EDEHSector Release Loop
Deallocates excess sectors by clearing GAT bits and freeing continuation directory entries. Uses self-modifying RES instruction for bit manipulation.
4F74HClose/Kill Finalization
Re-reads original directory entry and transfers IX (FCB address) to DE for return to caller.
4F87HFilespec Builder
Constructs FILENAME/EXT:D string from directory entry. Uses self-modifying code for drive digit.
4FCBHRequest 30H Handler
Validates directory entry and calls filespec builder for directory-to-filespec conversion.
4FD8HFCB Initializer
Sets FCB status to 2AH (reference state), stores drive config pointers and default buffer address.
4FF6HGAT Bit Clear
Clears a single bit in a GAT allocation byte using dynamically constructed RES n,B instruction.
5003HRead Directory Sector
Reads one directory sector into buffer at 5100H. Returns error code 14H on failure.
5016HWrite Directory Sector
Writes directory sector from 5100H buffer with write-verify. Returns error code 15H on failure.
502FHRead GAT Sector
Reads GAT sector (track 0, sector 1) into buffer at 4200H. Returns error code 16H on failure.
5041HWrite GAT Sector
Writes GAT sector from 4200H buffer with write-verify. Returns error code 17H on failure.
505AHHL Offset Add
Adds Register A to Register Pair HL with carry propagation. Preserves A.

Cross-References

SYS3 calls these SYS0 resident routines: 4768H (write sector), 4772H (write-verify), 4797H (read directory sector by info byte), 4988H (FCB validation and directory sector setup), 49E9H/49EBH (overlay call context setup), 4B10H (directory entry validation), 4B1FH (directory entry write/update), 4B45H (read sector from disk), 4B65H (set up track/sector for directory I/O), 4B7BH (multiply A by sectors-per-granule), and 4BA9H (16-bit subtract/compare). SYS3 makes no direct ROM calls; all disk I/O is through SYS0 wrappers.

Error Codes

CodeSourceMeaning
0FH4F6BHNon-fatal directory write error (tolerated during close)
14H5013HDirectory read error
15H502AHDirectory write error
16H503EHGAT read error
17H5055HGAT write error

Self-Modifying Code Locations

WriterTargetPurpose
4F3CH4F4BHStores HIT entry count as LD B,nn operand for continuation entry processing loop.
4F8CH4FC3HStores ASCII drive digit ('0'-'7') as LD A,nn operand for filespec string construction.
4FFDH5001HStores computed RES opcode byte (CB-prefix second byte) to dynamically construct RES n,B for GAT bit clearing.

Disassembly:

4E00H - SVC Request Dispatcher

Entry point for SYS3 overlay. Register A contains the full SVC request code from the RST 28H dispatcher. The routine masks out the overlay number (lower nibble) to isolate the function code in bits 4-6, then routes to the appropriate handler: 10H dispatches to the file close routine at 4E0DH, 30H dispatches to the directory-to-filespec handler at 4FCBH, and 00H falls through to a RET which returns to the SYS0 overlay context setup that redirects to the FCB initializer at 4FD8H.

4E00
AND A,70H E6 70
Mask Register A with 70H to isolate the function code in bits 4-6. This strips the overlay number from the lower nibble and the high bit, leaving only the request type.
4E02
CP A,10H FE 10
Compare Register A against 10H (file close request). If the function code is 10H, the Z FLAG is set.
4E04
If the Z FLAG (Zero) has been set because the request code is 10H (close file), JUMP to 4E0DH to begin the file close operation.
4E07
CP A,30H FE 30
Compare Register A against 30H (directory-to-filespec request). If the function code is 30H, the Z FLAG is set.
4E09
If the Z FLAG (Zero) has been set because the request code is 30H (filespec build), JUMP to 4FCBH to handle the directory-to-filespec conversion.
4E0C
RET C9
Return to the caller for any unrecognized request code. For request 00H (FCB initialization), this returns to the SYS0 overlay dispatcher which routes to the FCB initializer at 4FD8H.

4E0DH - Close File - Validate FCB and Update Directory Entry

Main file close routine. Validates the FCB, reads the directory entry, compares the current EOF offset and extent pointers against the directory copy, and updates the directory if they differ. If the kill-file flag (bit 2 of IX+00H) is set, the routine branches to date stamping and then to the extent chain walker for deallocation. Register DE points to the FCB on entry.

4E0D
LD A,(DE) 1A
Load Register A with the FCB status byte from the address in DE. The FCB status byte at offset +00H contains file state flags.
4E0E
AND A,0C0H E6 C0
Mask with 0C0H to isolate bits 6 and 7 (file open indicators). If both bits are clear, the file is not formally open and requires only FCB initialization, not a full close.
4E10
If the Z FLAG (Zero) has been set because bits 6-7 are both clear (file not open), JUMP to 4FD8H to perform FCB initialization instead of a full close operation.
4E13
GOSUB to the SYS0 overlay context setup routine at 49EBH (alternate entry point). This saves registers and sets IX to the FCB base address from DE.
4E16
GOSUB to the SYS0 FCB validation routine at 4988H. This verifies the FCB is within valid memory bounds and sets up the directory sector parameters. Returns Z on success, NZ with error code on failure.
4E19
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the FCB validation failed, return with the error code in Register A.
4E1A
LD B,(IX+07H) DD 46 07
Load Register B with the directory entry info byte from FCB offset +07H. This encodes the directory sector number and entry position within that sector.
4E1D
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset +06H.
4E20
GOSUB to the SYS0 directory entry validation routine at 4B10H. This reads the directory sector into the buffer at 4200H and positions HL to the directory entry. BC contains the directory entry encoding and drive number.
4E23
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory read failed, return with the error code in Register A.

Directory Entry Comparison
HL now points to the start of the directory entry in the 4200H buffer. The following code compares the FCB's current EOF offset and extent chain pointers against the directory's stored values to detect changes.

4E24
LD A,03H 3E 03
Load Register A with 03H as an offset value. Directory entry offset +03H holds the EOF byte offset.
4E26
ADD A,L 85
ADD 03H to Register L to advance HL to directory entry offset +03H (EOF byte offset field).
4E27
LD L,A 6F
Store the result into Register L. HL now points to the EOF byte offset field at entry+03H.
4E28
PUSH HL E5
Save HL (pointing to entry+03H) onto the stack for later use when updating the directory entry.
4E29
LD A,(IX+08H) DD 7E 08
Load Register A with the FCB's current EOF byte offset from IX+08H. A value of 00H means the file ends on a sector boundary.
4E2C
CP A,(HL) BE
Compare the FCB's EOF offset (Register A) against the directory entry's stored EOF offset at (HL). If they differ, the directory needs updating.
4E2D
If the NZ FLAG (Not Zero) has been set because the EOF offsets differ, JUMP to 4E43H to update the directory entry with the current FCB values.
4E2F
LD A,11H 3E 11
Load Register A with 11H as an offset. Adding 11H to the current L (pointing to entry+03H) advances to entry+14H, which holds the extent chain end pointer (low byte).
4E31
ADD A,L 85
ADD 11H to Register L to advance to directory entry offset +14H.
4E32
LD L,A 6F
Store the result into Register L. HL now points to the extent chain end pointer low byte at entry+14H.
4E33
LD A,(IX+0CH) DD 7E 0C
Load Register A with the FCB's total sector count low byte from IX+0CH.
4E36
CP A,(HL) BE
Compare the FCB's sector count low byte against the directory entry's extent end pointer low byte at entry+14H.
4E37
If the NZ FLAG (Not Zero) has been set because the low bytes differ, JUMP to 4E43H to update the directory entry.
4E39
INC L 2C
INCrement Register L to advance to entry+15H (extent chain end pointer high byte).
4E3A
LD A,(IX+0DH) DD 7E 0D
Load Register A with the FCB's total sector count high byte from IX+0DH.
4E3D
CP A,(HL) BE
Compare the FCB's sector count high byte against the directory entry's extent end pointer high byte at entry+15H.
4E3E
If the NZ FLAG (Not Zero) has been set because the high bytes differ, JUMP to 4E43H to update the directory entry.

No Changes Detected
All three comparisons matched (EOF offset, extent pointer low, extent pointer high). The directory entry is already current, so skip the update and proceed to check the kill-file flag.

4E40
POP AF F1
Discard the saved HL value from the stack (the entry+03H pointer is no longer needed since no update is required). POP AF is used as a quick discard since the value in AF is not needed.
4E41
JUMP to 4E5DH to test the kill-file flag and proceed with extent chain analysis without writing the directory.

Directory Update Required
At least one of the three values (EOF offset, extent pointer low, extent pointer high) has changed. Write all three current FCB values into the directory entry buffer.

4E43
POP HL E1
Restore HL from the stack. HL points to the directory entry's EOF byte offset field at entry+03H.
4E44
LD A,(IX+08H) DD 7E 08
Load Register A with the FCB's current EOF byte offset from IX+08H.
4E47
LD (HL),A 77
Store the FCB's EOF byte offset into the directory entry at offset +03H.
4E48
LD A,11H 3E 11
Load Register A with 11H to advance from entry+03H to entry+14H.
4E4A
ADD A,L 85
ADD 11H to Register L to advance to the extent chain end pointer at entry+14H.
4E4B
LD L,A 6F
Store the result into Register L. HL now points to entry+14H.
4E4C
LD A,(IX+0CH) DD 7E 0C
Load Register A with the FCB's total sector count low byte from IX+0CH.
4E4F
LD (HL),A 77
Store the sector count low byte into the directory entry at offset +14H.
4E50
INC L 2C
INCrement Register L to advance to entry+15H.
4E51
LD A,(IX+0DH) DD 7E 0D
Load Register A with the FCB's total sector count high byte from IX+0DH.
4E54
LD (HL),A 77
Store the sector count high byte into the directory entry at offset +15H.
4E55
BIT 2,(IX+00H) DD CB 00 56
Test bit 2 of the FCB status byte at IX+00H. Bit 2 is the kill-file active flag, set by SYS2 during file deletion operations. If the bit is set, the close operation should include date stamping and proceed to extent deallocation.
4E59
If the Z FLAG (Zero) has been set because bit 2 is clear (normal close, not a kill), JUMP to 4E8BH to write the updated directory entry without date stamping and return.
4E5B
JUMP to 4E63H to perform date stamping before writing the directory entry. This path is taken when the kill-file flag is set.

Kill-File Flag Check (No Update Path)
This entry point is reached when the directory entry did not need updating (all three comparisons matched at 4E2CH/4E36H/4E3DH). The kill-file flag is still checked to determine whether extent chain analysis is needed.

4E5D
BIT 2,(IX+00H) DD CB 00 56
Test bit 2 of the FCB status byte at IX+00H (kill-file active flag). Same test as 4E55H but reached via the no-update path.
4E61
If the Z FLAG (Zero) has been set because bit 2 is clear (normal close), JUMP to 4E91H to begin extent chain traversal. On a normal close, the extent chain is walked to check for excess allocated sectors.

4E63H - Date Encoder - Stamp Directory Entry with Current Date

Reads the system date from 4044H-4046H and encodes it into the directory entry's flags and date bytes. Sets bit 6 (modified flag) and bit 4 (date present flag) in the directory entry's attribute byte at offset +01H. The day value uses an internal format with base 50H (SUB 50H converts to a 0-based value). The month is rotated left three times into bits 5-7, and the year is stored in the lower 5 bits of the attribute byte.

4E63
PUSH HL E5
Save HL onto the stack. HL currently points to entry+15H (the last extent pointer byte that was just written).
4E64
LD A,L 7D
Load Register A with the current L value.
4E65
AND A,0E0H E6 E0
Mask with 0E0H to isolate the upper 3 bits of L, which gives the base offset of the current directory entry within the sector. Each entry is 32 bytes, so the upper 3 bits of the low byte identify which entry slot.
4E67
LD L,A 6F
Store the base offset into Register L. HL now points to the start of the directory entry (entry+00H).
4E68
INC L 2C
INCrement Register L to advance to entry+01H (the flags/attribute byte).
4E69
SET 6,(HL) CB F6
Set bit 6 of the directory entry attribute byte at (HL). Bit 6 is the modified/dirty flag, indicating the file has been changed since last access.
4E6B
LD DE,4044H 11 44 40
Point Register Pair DE to 4044H, the system date area. The three bytes at 4044H-4046H hold day, month, and year respectively.
4E6E
SET 4,(HL) CB E6
Set bit 4 of the directory entry attribute byte at (HL). Bit 4 is the date-present flag, indicating that a valid date has been stored in this entry.
4E70
LD A,(DE) 1A
Load Register A with the day value from system date address 4044H.
4E71
OR A,A B7
OR Register A with itself to test if the day value is zero. A day value of zero indicates the system date has not been set.
4E72
If the Z FLAG (Zero) has been set because the day value is zero (date not set), JUMP to 4E8AH to skip the date encoding. The modified and date-present flags were already set but no actual date values will be stored.
4E74
SUB A,50H D6 50
SUBtract 50H from Register A to convert the day from its internal format (base 50H) to a zero-based value. For example, day 1 is stored internally as 51H, so 51H - 50H = 01H.
4E76
PUSH BC C5
Save Register Pair BC onto the stack (preserves directory entry encoding and drive number).
4E77
LD B,A 47
Copy the adjusted day value into Register B for temporary storage while the month is processed.
4E78
INC DE 13
INCrement DE to advance from 4044H (day) to 4045H (month).
4E79
LD A,(DE) 1A
Load Register A with the month value (1-12) from system date address 4045H.
4E7A
RLCA 07
Rotate Register A Left. First of three rotations to shift the month value into bits 5-7 of the byte.
4E7B
RLCA 07
Rotate Register A Left. Second rotation.
4E7C
RLCA 07
Rotate Register A Left. Third rotation. The month value is now in bits 5-7 of Register A (e.g., month 3 = March becomes 60H).
4E7D
OR A,B B0
OR the shifted month value with the adjusted day value in Register B. This combines month (bits 5-7) and day (bits 0-4) into a single byte.
4E7E
INC L 2C
INCrement Register L to advance from entry+01H (flags) to entry+02H (date byte).
4E7F
LD (HL),A 77
Store the combined month/day byte into the directory entry at offset +02H.
4E80
DEC L 2D
DECrement Register L to move back to entry+01H (flags/attribute byte) for year encoding.
4E81
INC DE 13
INCrement DE to advance from 4045H (month) to 4046H (year).
4E82
LD A,(DE) 1A
Load Register A with the year value (0-31) from system date address 4046H.
4E83
LD B,A 47
Copy the year value into Register B for temporary storage.
4E84
LD A,(HL) 7E
Load Register A with the current flags/attribute byte from the directory entry at offset +01H. This byte contains the modified flag (bit 6), date-present flag (bit 4), and potentially other flags in the upper bits.
4E85
AND A,0E0H E6 E0
Mask with 0E0H to preserve only the upper 3 bits (bits 5-7) of the attribute byte. This clears bits 0-4, making room for the year value while preserving the extended-extents flag (bit 7), and the bit 6 (modified) and bit 5 flags.
4E87
OR A,B B0
OR the preserved upper flags with the year value in Register B. The year (0-31) occupies bits 0-4 of the attribute byte.
4E88
LD (HL),A 77
Store the updated attribute byte (upper flags + year) back into the directory entry at offset +01H.
4E89
POP BC C1
Restore Register Pair BC from the stack (directory entry encoding and drive number).
4E8A
POP HL E1
Restore HL from the stack. HL points back to the extent pointer area of the directory entry.

Write Updated Directory Entry
The directory entry has been updated with the current EOF offset, extent pointers, and date. Now write it back to disk.

4E8B
PUSH HL E5
Save HL onto the stack (preserves directory entry position for post-write operations).
4E8C
GOSUB to the SYS0 directory entry write routine at 4B1FH. This writes the modified directory sector back to disk. Returns Z on success, NZ with error code on failure.
4E8F
POP HL E1
Restore HL from the stack.
4E90
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory write failed, return with the error code in Register A.

4E91H - Extent Chain Walker - Count Total Allocated Sectors

Traverses the file's extent chain in the directory entry, counting the total number of allocated sectors. Each extent is a 2-byte pair where the second byte's lower 5 bits encode the sector count minus 1. Special values FEH and FFH serve as continuation pointer and end-of-chain markers respectively. The total allocated sector count in DE is compared against the FCB's used sector count to determine if excess sectors need deallocation. HL points to the extent chain area of the directory entry on entry.

4E91
INC L 2C
INCrement Register L to advance past the extent end pointer high byte at entry+15H to entry+16H, the start of the extent chain data.
4E92
PUSH HL E5
Save HL (pointing to the start of the extent chain at entry+16H) onto the stack.
4E93
LD A,L 7D
Load Register A with the current L value (entry+16H offset).
4E94
SUB A,15H D6 15
SUBtract 15H from Register A to compute the offset to entry+01H (the flags/attribute byte). From entry+16H, subtracting 15H gives entry+01H.
4E96
LD L,A 6F
Store the result into Register L. HL now points to the directory entry's flags/attribute byte at entry+01H.
4E97
BIT 7,(HL) CB 7E
Test bit 7 of the flags/attribute byte at entry+01H. Bit 7 is the extended-extents flag, indicating that the extent chain uses a format that exceeds the standard single-entry storage.
4E99
POP HL E1
Restore HL from the stack. HL points back to the start of the extent chain at entry+16H.
4E9A
If the NZ FLAG (Not Zero) has been set because bit 7 is set (extended extents), JUMP to 4F74H to finalize without deallocation. Extended-extent files are handled differently and skip the sector release logic.
4E9D
LD DE,0000H 11 00 00
Set Register Pair DE to 0000H. DE will accumulate the total count of allocated sectors as the extent chain is traversed.

Extent Chain Traversal Loop
Walk through the extent chain, reading 2-byte pairs. For each extent, extract the sector count from the second byte's lower 5 bits (plus 1) and add to the running total in DE. FEH marks a continuation pointer, FFH marks end-of-chain.

4EA0
LD A,(HL) 7E
Load Register A with the first byte of the current extent pair. This byte encodes the granule position (bits 5-7) and sector count minus 1 (bits 0-4) in its high and low portions respectively.
4EA1
INC L 2C
INCrement Register L to advance to the second byte of the extent pair.
4EA2
CP A,0FEH FE FE
Compare Register A against 0FEH. Values FEH and FFH are special markers: FEH is a continuation pointer, FFH is end-of-chain. If A >= FEH, the CARRY FLAG is clear (NC).
4EA4
If the NO CARRY FLAG is set because A >= FEH (either a continuation pointer or end-of-chain marker), JUMP to 4EB2H to handle the special case.

Normal Extent Entry
The first byte was a regular extent entry (value below FEH). Read the second byte, extract the sector count from its lower 5 bits, increment by 1 (since the stored value is count minus 1), and add to the running total in DE.

4EA6
LD A,(HL) 7E
Load Register A with the second byte of the extent pair.
4EA7
INC L 2C
INCrement Register L to advance past this 2-byte extent pair to the start of the next pair.
4EA8
AND A,1FH E6 1F
Mask with 1FH to isolate the lower 5 bits, which encode the sector count minus 1 for this extent (range 0-31, representing 1-32 sectors).
4EAA
INC A 3C
INCrement Register A by 1 to convert from count-minus-1 to actual sector count.
4EAB
ADD A,E 83
ADD the sector count for this extent to the running total low byte in Register E.
4EAC
LD E,A 5F
Store the updated total back into Register E.
4EAD
If the NO CARRY FLAG is set (no overflow from the ADD), LOOP BACK to 4EA0H to process the next extent pair.
4EAF
INC D 14
INCrement Register D by 1 to propagate the carry from the ADD A,E into the high byte of the 16-bit running total.
4EB0
LOOP BACK to 4EA0H to continue processing extent pairs.

Special Extent Markers
The first byte was FEH (continuation pointer) or FFH (end-of-chain). Determine which one and take the appropriate action.

4EB2
If the NZ FLAG (Not Zero) has been set because A was FFH (not equal to FEH from the CP at 4EA2H which set Z for FEH), JUMP to 4EBFH to handle end-of-chain. If Z is set, A was exactly FEH (continuation pointer).

Continuation Pointer (FEH)
The current extent entry is a continuation pointer. The second byte contains the directory entry index of the next continuation entry. Read that entry and continue the chain from its extent data at offset +16H.

4EB4
LD B,(HL) 46
Load Register B with the second byte of the FEH pair, which is the directory entry index of the continuation entry.
4EB5
GOSUB to the SYS0 directory entry validation routine at 4B10H to read the continuation directory entry. Register B contains the directory entry index, Register C still contains the drive number. HL is repositioned to the continuation entry.
4EB8
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory read failed, return with the error code in Register A.
4EB9
LD A,L 7D
Load Register A with the current L value (pointing to the start of the continuation entry).
4EBA
ADD A,16H C6 16
ADD 16H to advance to the extent chain data area at offset +16H within the continuation entry.
4EBC
LD L,A 6F
Store the result into Register L. HL now points to the extent chain data at entry+16H of the continuation entry.
4EBD
LOOP BACK to 4EA0H to continue processing extent pairs from the continuation entry.

End of Extent Chain (FFH)
The extent chain has been fully traversed. DE contains the total number of allocated sectors. Compare this against the FCB's used sector count to determine if deallocation is needed.

4EBF
PUSH HL E5
Save HL onto the stack (preserves the current position in the extent chain for the deallocation loop).
4EC0
LD L,(IX+0CH) DD 6E 0C
Load Register L with the FCB's used sector count low byte from IX+0CH.
4EC3
LD H,(IX+0DH) DD 66 0D
Load Register H with the FCB's used sector count high byte from IX+0DH. HL now contains the 16-bit count of sectors actually used by the file.
4EC6
LD A,08H 3E 08
Load Register A with 08H. This parameter is passed to the SYS0 directory info read routine to request the directory geometry information.
4EC8
GOSUB to the SYS0 routine at 4797H to read the directory information byte. Returns the directory sector info in A, which encodes the sectors-per-track or granule geometry.
4ECB
AND A,1FH E6 1F
Mask with 1FH to isolate the lower 5 bits of the directory info byte, extracting the granule-related geometry value.
4ECD
GOSUB to the HL offset add utility at 505AH. This adds the value in Register A to HL, advancing HL by the geometry offset. A is preserved across this call.
4ED0
NOP 00
No operation. Patched-out instruction; the original code at this address has been replaced with a NOP.
4ED1
INC A 3C
INCrement Register A by 1. Combined with the geometry value, this produces the divisor for the allocated-vs-used sector comparison.
4ED2
GOSUB to the SYS0 16-bit subtract/compare routine at 4BA9H. This compares or adjusts the values in HL and DE to determine the difference between allocated and used sectors.
4ED5
XOR A,A AF
Set Register A to ZERO and clear all flags. This clears the carry flag before the SBC instruction that follows.
4ED6
EX DE,HL EB
Exchange DE and HL. DE now contains the used sector count, HL contains the allocated sector count (or the adjusted comparison result).
4ED7
SBC HL,DE ED 52
SUBtract DE (used sectors) from HL (allocated sectors) with borrow. The result in HL is the number of excess allocated sectors. If HL = 0 or the result is negative, no deallocation is needed.
4ED9
EX DE,HL EB
Exchange DE and HL again. DE now contains the excess sector count (number of sectors to deallocate).
4EDA
POP HL E1
Restore HL from the stack. HL points to the current position in the extent chain (the FFH end marker area).
4EDB
If the Z FLAG (Zero) has been set because the excess count is zero (allocated equals used), JUMP to 4F74H to finalize without deallocation.
4EDE
If the CARRY FLAG has been set because the used count exceeds the allocated count (which should not occur in normal operation), JUMP to 4F74H to finalize without deallocation.

4EE1H - Sector Release Loop - Deallocate Excess Sectors via GAT

Deallocates excess allocated sectors one at a time by clearing the corresponding bits in the GAT. For each sector, the routine extracts the granule position and sector offset from the last extent entry, reads the directory sector into the SYS3 buffer at 5100H, loads the GAT sector into 4200H, clears the appropriate allocation bit using the self-modifying RES instruction at 4FF6H, and updates the extent chain. When an extent entry is fully released, continuation directory entries are freed by zeroing their first byte, clearing the HIT entry, and writing back the GAT. DE contains the number of sectors to deallocate on entry, and HL points to the end of the extent chain.

4EE1
DEC L 2D
DECrement Register L to move back from the FFH end-marker to the last extent pair's second byte.
4EE2
DEC L 2D
DECrement Register L again to point to the last extent pair's first byte.
4EE3
GOSUB to the SYS3 directory sector read routine at 5003H. This reads the current directory sector into the buffer at 5100H, providing a second copy separate from the SYS0 buffer at 4200H.
4EE6
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory read failed, return with error code 14H.

Sector Deallocation Loop Start
Process one sector at a time. Extract the granule number and sector position from the current extent entry, then use those to locate and clear the corresponding GAT bit.

4EE7
PUSH DE D5
Save Register Pair DE (remaining sector count to deallocate) onto the stack.
4EE8
LD A,(HL) 7E
Load Register A with the first byte of the current extent pair. The upper 3 bits (bits 5-7) encode the granule position.
4EE9
AND A,0E0H E6 E0
Mask with 0E0H to isolate the upper 3 bits (granule position indicator).
4EEB
RLCA 07
Rotate Register A Left. First of three rotations to shift the granule position from bits 5-7 down to bits 0-2.
4EEC
RLCA 07
Rotate Register A Left. Second rotation.
4EED
RLCA 07
Rotate Register A Left. Third rotation. The granule position (0-7) is now in the lower 3 bits of Register A.
4EEE
LD E,A 5F
Store the granule position into Register E for later use as the bit number for GAT bit clearing.
4EEF
LD A,(HL) 7E
Reload Register A with the first byte of the extent pair.
4EF0
AND A,1FH E6 1F
Mask with 1FH to isolate the lower 5 bits (sector offset within the granule, stored as count minus 1).
4EF2
ADD A,E 83
ADD the sector offset to the granule position in Register E. This computes the combined granule+sector index used to locate the GAT entry.
4EF3
LD E,A 5F
Store the combined index back into Register E.
4EF4
LD A,08H 3E 08
Load Register A with 08H as a parameter for the directory info read routine.
4EF6
GOSUB to the SYS0 directory info read routine at 4797H. Returns the directory geometry information in Register A.
4EF9
RLCA 07
Rotate Register A Left. First of three rotations to extract the sectors-per-granule value from the geometry byte.
4EFA
RLCA 07
Rotate Register A Left. Second rotation.
4EFB
RLCA 07
Rotate Register A Left. Third rotation.
4EFC
AND A,07H E6 07
Mask with 07H to isolate the lower 3 bits, giving the sectors-per-granule value minus 1.
4EFE
INC A 3C
INCrement Register A by 1 to get the actual sectors-per-granule count.
4EFF
GOSUB to the SYS0 granule multiply routine at 4B7BH. This multiplies Register A by the sectors-per-granule value to compute the total sector offset for the granule. The result is used to determine which GAT byte and bit to clear.
4F02
DEC L 2D
DECrement Register L to move from the extent pair's first byte to the preceding byte, which in the 5100H buffer corresponds to the GAT track index.
4F03
ADD A,(HL) 86
ADD the value at (HL) to Register A. This adds the track-relative base to the computed granule offset, producing the absolute GAT byte index.
4F04
LD D,A 57
Store the GAT byte index into Register D.
4F05
PUSH HL E5
Save HL onto the stack (preserves the directory buffer position).
4F06
PUSH BC C5
Save Register Pair BC onto the stack (preserves directory entry encoding and drive number).
4F07
LD H,51H 26 51
Load Register H with 51H, setting up the high byte for accessing the directory buffer at 5100H.
4F09
LD L,D 6A
Load Register L with Register D (the GAT byte index). HL now points to the corresponding byte in the 5100H buffer.
4F0A
LD B,(HL) 46
Load Register B with the current GAT allocation byte from the buffer. Each bit in this byte represents one granule's allocation status.
4F0B
LD A,E 7B
Load Register A with Register E (the granule bit position, 0-7).
4F0C
GOSUB to the GAT bit clear routine at 4FF6H. This dynamically constructs and executes a RES n,B instruction to clear the specified bit in Register B, marking the granule as free.
4F0F
LD (HL),B 70
Store the modified GAT byte (with the granule bit cleared) back into the 5100H buffer.
4F10
POP BC C1
Restore Register Pair BC from the stack.
4F11
POP HL E1
Restore HL from the stack (directory buffer position).
4F12
INC L 2C
INCrement Register L to advance back to the extent pair's first byte.
4F13
DEC (HL) 35
DECrement the sector count byte at (HL). This reduces the remaining sector count in the current extent by one, reflecting the deallocation of one sector.
4F14
LD A,(HL) 7E
Load Register A with the updated sector count byte.
4F15
INC A 3C
INCrement Register A by 1. This compensates for the stored value being count-minus-1: if the DEC brought it to FFH (-1), INC makes it 00H; if it brought it to a valid count-minus-1, INC tests whether the extent is now empty.
4F16
AND A,1FH E6 1F
Mask with 1FH to isolate the lower 5 bits (sector count field). If the result is zero, the entire extent has been deallocated.
4F18
POP DE D1
Restore Register Pair DE from the stack (remaining sectors to deallocate).
4F19
If the NZ FLAG (Not Zero) has been set because the extent still has remaining sectors (not fully released), JUMP to 4F61H to decrement the deallocation counter and continue the loop.

Extent Fully Released
The current extent entry has been completely deallocated. Mark it with FFH end-of-chain markers and check whether this position is at the start of a continuation directory entry (offset +15H), in which case the continuation entry itself needs to be freed.

4F1B
LD (HL),0FFH 36 FF
Store FFH into the current extent position (first byte of the pair), marking it as end-of-chain.
4F1D
DEC L 2D
DECrement Register L to move to the preceding byte.
4F1E
LD (HL),0FFH 36 FF
Store FFH into the preceding byte as well, creating a double-FFH end-of-chain marker.
4F20
DEC L 2D
DECrement Register L to point to the new last extent pair's second byte (backing up in the chain).
4F21
LD A,L 7D
Load Register A with the current L value.
4F22
AND A,1FH E6 1F
Mask with 1FH to isolate the position within the 32-byte directory entry.
4F24
CP A,15H FE 15
Compare against 15H. Offset +15H is just before the start of the extent chain at +16H. If the current position has backed up to offset +15H, the entire extent chain in this continuation entry has been freed.
4F26
If the NZ FLAG (Not Zero) has been set because the position is not at offset +15H (more extents remain in this entry), JUMP to 4F61H to continue the deallocation loop.

Continuation Entry Cleanup
The extent chain in this continuation directory entry has been completely freed. Delete the continuation entry by zeroing its first byte, update the HIT (Hash Index Table) entry, and write back the GAT.

4F28
XOR A,L AD
XOR Register A (which is 15H) with Register L. Since A contained 15H (from the AND/CP above), this effectively clears the lower 5 bits of L while preserving the upper bits, computing the base address of the directory entry (entry+00H).
4F29
LD L,A 6F
Store the entry base offset into Register L. HL now points to entry+00H (the status/name byte).
4F2A
BIT 7,(HL) CB 7E
Test bit 7 of the entry's first byte at offset +00H. Bit 7 set indicates this is a continuation entry (not a primary file entry).
4F2C
If the Z FLAG (Zero) has been set because bit 7 is clear (this is a primary entry, not a continuation), JUMP to 4F61H without deleting it. Primary entries must not be deleted during deallocation.
4F2E
LD (HL),00H 36 00
Store 00H into the entry's first byte at offset +00H, marking the continuation directory entry as deleted/free.
4F30
GOSUB to the SYS0 directory write routine at 4B1FH to write the modified directory sector (with the zeroed continuation entry) back to disk.
4F33
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory write failed, return with the error code.

Update HIT Entry
Clear the HIT (Hash Index Table) entry corresponding to the deleted continuation directory entry. The HIT byte position is computed from the directory entry encoding in Register B.

4F34
LD A,B 78
Load Register A with Register B (the directory entry encoding byte for the continuation entry).
4F35
AND A,0E0H E6 E0
Mask with 0E0H to isolate the upper 3 bits (entry position within the sector), giving the 32-byte entry slot number shifted left by 5.
4F37
INC A 3C
INCrement Register A by 1. This converts the entry slot to the HIT byte offset within the sector. The HIT is stored in the directory sector at offset +01H relative to the entry's sector position.
4F38
LD L,A 6F
Store the HIT offset into Register L.
4F39
LD H,42H 26 42
Load Register H with 42H. HL now points to the HIT byte in the SYS0 directory buffer at 4200H.
4F3B
LD A,(HL) 7E
Load Register A with the current HIT byte value. This is saved to the self-modifying code location for later restoration during the GAT update.
4F3C
LD (4F4BH),A 32 4B 4F
Self-Modifying Code
Store the HIT byte value into the immediate operand of the LD B,nn instruction at 4F4BH. This saves the original HIT entry count for use after the GAT read/write cycle.
4F3F
GOSUB to the GAT sector read routine at 502FH. This reads the GAT sector into the buffer at 4200H. Note: this overwrites the directory data that was in the 4200H buffer, which is why SYS3 maintains a separate directory buffer at 5100H.
4F42
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the GAT read failed, return with error code 16H.
4F43
LD L,B 68
Load Register L with Register B (the directory entry encoding). This positions HL into the GAT buffer at 4200H + entry_index to clear the allocation bit for this entry.
4F44
LD (HL),00H 36 00
Store 00H into the GAT byte at (HL), clearing the allocation entry for the freed continuation directory entry.
4F46
GOSUB to the GAT sector write routine at 5041H. This writes the modified GAT sector back to disk with write-verification.
4F49
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the GAT write failed, return with error code 17H.
4F4A
LD B,00H 06 00
Self-Modifying Code
Load Register B with 00H. The immediate operand at 4F4BH was patched by the LD (4F4BH),A at 4F3CH with the saved HIT entry count. At runtime, B receives the original HIT value.
4F4C
GOSUB to the SYS0 directory entry validation routine at 4B10H. This re-reads the directory sector into the 4200H buffer (which was overwritten by the GAT read/write cycle). Register B contains the entry index restored from the self-modifying code.
4F4F
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory re-read failed, return with the error code.
4F50
LD A,B 78
Load Register A with Register B (the directory entry encoding).
4F51
AND A,0E0H E6 E0
Mask with 0E0H to isolate the entry slot position (upper 3 bits).
4F53
ADD A,1FH C6 1F
ADD 1FH to advance to the last byte of the 32-byte directory entry (offset +1FH). This positions to the end of the extent chain area.
4F55
LD L,A 6F
Store the offset into Register L. HL now points to the last byte of the directory entry in the 4200H buffer.
4F56
LD (HL),0FFH 36 FF
Store FFH into the last byte of the entry, marking the end of the extent chain.
4F58
DEC L 2D
DECrement Register L to move to the second-to-last byte.
4F59
LD (HL),0FFH 36 FF
Store FFH into the preceding byte as well, creating the double-FFH end-of-chain marker at the end of the freed entry.
4F5B
PUSH HL E5
Save HL onto the stack.
4F5C
GOSUB to the SYS0 directory write routine at 4B1FH to write the modified directory sector (with the FFH markers) back to disk.
4F5F
POP HL E1
Restore HL from the stack.
4F60
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory write failed, return with the error code.

Deallocation Counter Update
One sector has been deallocated. Decrement the remaining count and loop back if more sectors need freeing.

4F61
DEC DE 1B
DECrement Register Pair DE by 1. DE is the remaining count of excess sectors to deallocate.
4F62
LD A,D 7A
Load Register A with the high byte of the remaining count.
4F63
OR A,E B3
OR Register A with Register E. If the result is zero, both bytes of DE are zero and all excess sectors have been deallocated.
4F64
If the NZ FLAG (Not Zero) has been set because DE is not zero (more sectors to deallocate), LOOP BACK to 4EE7H to process the next sector.

Deallocation Complete
All excess sectors have been freed. Write the final directory entry update and the directory sector from the 5100H buffer.

4F66
GOSUB to the SYS0 directory write routine at 4B1FH to write the final updated directory entry back to disk.
4F69
If the Z FLAG (Zero) has been set because the directory write succeeded, JUMP to 4F70H to write the directory sector buffer.
4F6B
CP A,0FH FE 0F
Compare the error code in Register A against 0FH. Error code 0FH is a non-fatal directory write error that is tolerated during close operations.
4F6D
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the error code is not 0FH (a different, fatal error), return with the error code.
4F6E
JUMP to 4F74H to finalize the close operation. Error 0FH is tolerated; the close proceeds despite the non-fatal write error.
4F70
GOSUB to the SYS3 directory sector write routine at 5016H. This writes the directory sector from the 5100H buffer back to disk with write-verification.
4F73
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory sector write failed, return with error code 15H.

4F74H - Close/Kill Finalization - Read Original Directory and Prepare FCB Return

Final phase of the close/kill operation. This routine reads the directory sector for the file's original directory entry (using the FCB's stored drive and directory info), then copies the IX (FCB) pointer to DE for return to the caller. This ensures the caller receives the FCB address in DE for any post-close operations.

4F74
LD A,B 78
Load Register A with Register B (the current directory entry encoding from the most recent directory operation).
4F75
LD B,(IX+07H) DD 46 07
Load Register B with the FCB's original directory entry info byte from offset +07H. This is the directory position where the file's primary entry resides, which may differ from the current B value if continuation entries were processed.
4F78
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset +06H.
4F7B
XOR A,B A8
XOR the previous directory entry encoding (Register A) with the FCB's directory info (Register B). If they are the same, the result is zero, meaning the correct directory sector is already loaded in the buffer.
4F7C
AND A,1FH E6 1F
Mask with 1FH to isolate the sector number bits. The entry position within a sector does not matter for determining whether a new sector needs loading; only the sector number matters.
4F7E
If the NZ FLAG (Not Zero) has been set because the current and FCB directory sectors differ, GOSUB to the SYS0 directory entry validation routine at 4B10H to read the correct directory sector for the file's primary entry. If the sectors are the same, this call is skipped.
4F81
RET NZ C0
If the NZ FLAG (Not Zero) has been set because the directory read failed, return with the error code in Register A.
4F82
PUSH IX DD E5
Save the IX register (FCB base address) onto the stack.
4F84
POP DE D1
Restore the value into Register Pair DE. This is a standard IX-to-DE transfer technique: PUSH IX / POP DE copies the FCB base address into DE for return to the caller.
4F85
NOP 00
No operation. Patched-out instruction (two consecutive NOPs suggest a removed 2-byte instruction).
4F86
NOP 00
No operation.

4F87H - Build Filespec String from Directory Entry

Constructs a human-readable filespec string from a directory entry. The output format is FILENAME/EXT:D followed by a 03H load-type marker byte. Register B contains the directory entry encoding, Register C contains the drive number, and Register DE points to the output buffer where the filespec string will be written. HL is used internally to read from the directory entry in the sector buffer at 4200H.

4F87
LD A,C 79
Load Register A with the drive number from Register C.
4F88
AND A,07H E6 07
Mask with 07H to isolate the drive number (0-7).
4F8A
OR A,30H F6 30
OR with 30H to convert the drive number to its ASCII digit character (30H = '0', so drive 0 becomes '0', drive 1 becomes '1', etc.).
4F8C
LD (4FC3H),A 32 C3 4F
Self-Modifying Code
Store the ASCII drive digit into the immediate operand of the LD A,nn instruction at 4FC3H. When that instruction executes later, it will load the correct drive character for insertion into the filespec string.
4F8F
LD H,42H 26 42
Load Register H with 42H, setting up the high byte for accessing the directory entry in the sector buffer at 4200H page.
4F91
LD A,B 78
Load Register A with Register B (the directory entry encoding byte).
4F92
AND A,0E0H E6 E0
Mask with 0E0H to isolate the entry position (upper 3 bits), which gives the entry's base offset within the sector.
4F94
OR A,05H F6 05
OR with 05H to advance to offset +05H within the directory entry, which is the start of the 8-byte filename field.
4F96
LD L,A 6F
Store the computed offset into Register L. HL now points to the filename field (entry+05H) in the sector buffer at 4200H page.
4F97
PUSH HL E5
Save HL (pointing to the filename start) onto the stack for later use when reading the extension.
4F98
LD B,08H 06 08
Load Register B with 08H as a loop counter. The filename field is 8 bytes long.

Loop Start
Copy the filename from the directory entry to the output buffer, skipping trailing spaces.

4F9A
LD A,(HL) 7E
Load Register A with the current filename character from the directory entry.
4F9B
CP A,20H FE 20
Compare Register A against 20H (ASCII space). If the character is a space, the filename has ended (space-padded).
4F9D
If the Z FLAG (Zero) has been set because the character is a space, JUMP to 4FA4H to stop copying filename characters and proceed to the extension.
4F9F
LD (DE),A 12
Store the filename character into the output buffer at the address in DE.
4FA0
INC HL 23
INCrement HL to advance to the next character in the directory entry's filename field.
4FA1
INC DE 13
INCrement DE to advance to the next position in the output buffer.
4FA2
DECrement B and loop back to 4F9AH if not zero. Continue copying filename characters until all 8 bytes are processed or a space is found. Loop End
4FA4
POP HL E1
Restore HL from the stack. HL points back to the filename start (entry+05H).
4FA5
LD A,L 7D
Load Register A with the current L value (pointing to entry+05H).
4FA6
ADD A,08H C6 08
ADD 08H to advance from the filename start (entry+05H) to the extension field (entry+0DH). The 8-byte filename field runs from +05H to +0CH, so +05H + 08H = +0DH.
4FA8
LD L,A 6F
Store the computed offset into Register L. HL now points to the 3-byte extension field at entry+0DH.
4FA9
LD A,(HL) 7E
Load Register A with the first extension character from the directory entry.
4FAA
CP A,20H FE 20
Compare Register A against 20H (ASCII space). If the first extension character is a space, the file has no extension.
4FAC
If the Z FLAG (Zero) has been set because the extension is empty (space), JUMP to 4FBEH to skip the extension and write the drive separator directly.
4FAE
LD A,2FH 3E 2F
Load Register A with 2FH (ASCII '/'), the extension separator character in TRS-80 filespecs.
4FB0
LD (DE),A 12
Store the '/' separator into the output buffer.
4FB1
INC DE 13
INCrement DE to advance to the next position in the output buffer.
4FB2
LD B,03H 06 03
Load Register B with 03H as a loop counter. The extension field is 3 bytes long.

Loop Start
Copy the file extension characters to the output buffer, stopping at spaces.

4FB4
LD A,(HL) 7E
Load Register A with the current extension character from the directory entry.
4FB5
CP A,20H FE 20
Compare Register A against 20H (ASCII space). If the character is a space, the extension has ended.
4FB7
If the Z FLAG (Zero) has been set because the character is a space, JUMP to 4FBEH to finish the extension and write the drive number.
4FB9
LD (DE),A 12
Store the extension character into the output buffer.
4FBA
INC HL 23
INCrement HL to advance to the next extension character.
4FBB
INC DE 13
INCrement DE to advance to the next position in the output buffer.
4FBC
DECrement B and loop back to 4FB4H if not zero. Continue copying extension characters until all 3 bytes are processed or a space is found. Loop End
4FBE
LD A,3AH 3E 3A
Load Register A with 3AH (ASCII ':'), the drive separator character in TRS-80 filespecs.
4FC0
LD (DE),A 12
Store the ':' drive separator into the output buffer.
4FC1
INC DE 13
INCrement DE to advance to the next position in the output buffer.
4FC2
LD A,00H 3E 00
Self-Modifying Code
Load Register A with 00H. The immediate operand at 4FC3H was patched by the LD (4FC3H),A at 4F8CH with the ASCII drive digit character. At runtime, A receives the correct drive character (e.g., '0', '1').
4FC4
LD (DE),A 12
Store the drive digit character into the output buffer.
4FC5
INC DE 13
INCrement DE to advance past the drive digit.
4FC6
LD A,03H 3E 03
Load Register A with 03H. This is a load-type marker byte that terminates the filespec string, indicating that a program load address follows.
4FC8
LD (DE),A 12
Store the load-type marker (03H) into the output buffer after the filespec string.
4FC9
XOR A,A AF
Set Register A to ZERO and clear all flags. Z FLAG is set to indicate success.
4FCA
RET C9
Return to the caller with Z FLAG set (success). The output buffer now contains the complete filespec string: FILENAME/EXT:D followed by the 03H load marker.

4FCBH - Request 30H Handler - Directory Lookup and Filespec Build

Handles SVC request 30H (directory-to-filespec conversion). Validates the directory entry by reading it from disk, then calls the filespec builder to construct the filespec string. Register DE points to the output buffer, and the directory entry encoding is already set up in BC from the caller.

4FCB
PUSH DE D5
Save Register Pair DE (output buffer pointer from the caller) onto the stack.
4FCC
PUSH HL E5
Save Register Pair HL onto the stack.
4FCD
GOSUB to the SYS0 directory entry validation routine at 4B10H. This reads the directory sector into the buffer and positions HL to the directory entry. BC contains the directory entry encoding and drive number.
4FD0
If the NZ FLAG (Not Zero) has been set because the directory read failed, JUMP to 4FD5H to restore registers and return with the error code.
4FD2
GOSUB to the filespec builder routine at 4F87H. This constructs the filespec string from the directory entry and writes it to the output buffer pointed to by DE. Returns with Z FLAG set on success.
4FD5
POP HL E1
Restore Register Pair HL from the stack.
4FD6
POP DE D1
Restore Register Pair DE from the stack. DE contains the original output buffer pointer.
4FD7
RET C9
Return to the caller. The Z/NZ flag from the directory read or filespec build indicates success or failure.

4FD8H - FCB Initializer for Unopened Files

Reached when the close operation detects that the FCB's status byte has bits 6-7 both clear (file is not formally open). Instead of performing a full close, this routine initializes the FCB with a basic configuration: status byte 2AH, the drive config table entry pointer, and a default buffer address. This allows the FCB to be used for file lookups and other operations that do not require a full open.

4FD8
GOSUB to the SYS0 overlay context setup routine at 49E9H (primary entry point). This saves registers and sets IX to the FCB base address from DE.
4FDB
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset +06H.
4FDE
LD B,(IX+07H) DD 46 07
Load Register B with the directory entry info byte from FCB offset +07H.
4FE1
LD A,04H 3E 04
Load Register A with 04H. This value is not used before the NOPs, suggesting it was part of a patched-out operation.
4FE3
NOP 00
No operation. Patched-out instruction (three consecutive NOPs suggest a removed 3-byte instruction, such as a CALL).
4FE4
NOP 00
No operation.
4FE5
NOP 00
No operation.
4FE6
LD (IX+00H),2AH DD 36 00 2A
Store 2AH into the FCB status byte at IX+00H. The value 2AH (binary 00101010) sets bits 1, 3, and 5, configuring the FCB as initialized but not formally opened for I/O. This is the reference state that allows directory lookups without full file access.
4FEA
LD (IX+01H),C DD 71 01
Store the drive number (Register C) into FCB offset +01H. This records the drive config table entry pointer (low byte) in the FCB.
4FED
LD (IX+02H),B DD 70 02
Store the directory entry info (Register B) into FCB offset +02H. This records the drive config table entry pointer (high byte) in the FCB.
4FF0
LD (IX+03H),03H DD 36 03 03
Store 03H into FCB offset +03H. This sets the sector buffer high byte to 03H, establishing a default buffer address at 0300H for the unopened FCB.
4FF4
XOR A,A AF
Set Register A to ZERO and clear all flags. Z FLAG is set to indicate success.
4FF5
RET C9
Return to the caller with Z FLAG set (success). The FCB has been initialized with status 2AH, drive config pointers, and a default buffer address.

4FF6H - GAT Bit Clear - Free a Granule in the Allocation Bitmap

Clears a single bit in a GAT allocation byte to mark a granule as free. Register A contains the bit position (0-7), and Register B contains the GAT byte to modify. The routine builds a Z80 CB-prefix RES n,B instruction using self-modifying code, where n is derived from the bit position in A. The RES instruction encoding places the bit number in bits 3-5 of the second opcode byte, with the register field in bits 0-2 (B=000) and the operation type in bits 6-7 (10=RES). The modified byte is returned in B.

4FF6
AND A,07H E6 07
Mask Register A with 07H to ensure the bit position is in the range 0-7.
4FF8
RLCA 07
Rotate Register A Left. First of three rotations to shift the bit number into bits 3-5 of the byte, matching the CB-prefix RES instruction encoding where bits 3-5 select which bit to reset.
4FF9
RLCA 07
Rotate Register A Left. Second rotation.
4FFA
RLCA 07
Rotate Register A Left. Third rotation. The bit number is now in bits 3-5 of Register A.
4FFB
OR A,80H F6 80
OR with 80H to set bit 7. This produces the second byte of a CB-prefix RES instruction: the base opcode for RES n,B is 80H + (n shifted left 3), so OR 80H combined with the shifted bit number creates the correct opcode byte.
4FFD
LD (5001H),A 32 01 50
Self-Modifying Code
Store the computed RES opcode byte into address 5001H, which is the second byte of the CB-prefix instruction at 5000H. This dynamically creates the instruction RES n,B where n is the bit position to clear.
5000
RES 0,B CB 80
Self-Modifying Code
Execute the dynamically constructed RES instruction. The initial value shown (RES 0,B = CB 80) is the default; at runtime, the second byte (80H at 5001H) has been overwritten with the correct opcode for the target bit. This clears the specified bit in Register B, marking the corresponding granule as free in the GAT allocation byte.
5002
RET C9
Return to the caller. Register B now contains the modified GAT byte with the specified granule bit cleared.

5003H - Read Directory Sector into Buffer at 5100H

Reads one directory sector from disk into the SYS3 directory buffer at 5100H. Uses the SYS0 directory I/O setup routine to configure the FDC track and sector registers, then calls the sector read routine. This buffer is separate from the SYS0 directory buffer at 4200H, allowing SYS3 to maintain both directory and GAT data simultaneously. Returns Z on success, NZ with error code 14H on failure.

5003
PUSH DE D5
Save Register Pair DE onto the stack (preserves the caller's deallocation counter or other context).
5004
PUSH HL E5
Save Register Pair HL onto the stack (preserves the caller's directory entry pointer).
5005
GOSUB to the SYS0 directory I/O setup routine at 4B65H. This configures the FDC track and sector registers based on the current directory parameters in BC.
5008
LD E,00H 1E 00
Load Register E with 00H. This is the sector offset parameter for the read operation (sector 0 relative to the configured track).
500A
LD HL,5100H 21 00 51
Point Register Pair HL to 5100H, the SYS3 directory sector buffer where the read data will be stored.
500D
GOSUB to the SYS0 sector read routine at 4B45H. This reads 256 bytes from the disk into the buffer at 5100H. On success, Z FLAG is set.
5010
POP HL E1
Restore Register Pair HL from the stack.
5011
POP DE D1
Restore Register Pair DE from the stack.
5012
RET Z C8
If the Z FLAG (Zero) has been set because the sector read succeeded, return to the caller with Z FLAG set (success).
5013
LD A,14H 3E 14
Load Register A with error code 14H (directory read error).
5015
RET C9
Return to the caller with NZ FLAG set (from the LD A instruction) and error code 14H in Register A.

5016H - Write Directory Sector from Buffer at 5100H

Writes the SYS3 directory sector buffer at 5100H back to disk with write-verify. Uses the same directory I/O setup as the read routine, followed by a write and read-after-write verification. The verify check compares the FDC status against 06H to detect write/CRC errors. Returns Z on success, NZ with error code 15H on failure.

5016
PUSH DE D5
Save Register Pair DE onto the stack.
5017
PUSH HL E5
Save Register Pair HL onto the stack.
5018
GOSUB to the SYS0 directory I/O setup routine at 4B65H to configure the FDC track and sector registers.
501B
LD E,00H 1E 00
Load Register E with 00H (sector offset parameter).
501D
LD HL,5100H 21 00 51
Point Register Pair HL to the SYS3 directory sector buffer at 5100H.
5020
GOSUB to the SYS0 write sector routine at 4768H. This writes the 256-byte buffer to the disk at the configured track and sector.
5023
If the NZ FLAG (Not Zero) has been set because the write operation failed, JUMP to 502AH to set the error code and return.
5025
GOSUB to the SYS0 write-verify routine at 4772H. This performs a read-after-write verification to ensure the data was written correctly.
5028
CP A,06H FE 06
Compare the FDC status returned by the verify against 06H. Status 06H indicates a CRC or record-not-found error during the verification read. If A is less than 06H, the CARRY FLAG is set and the verify passed.
502A
LD A,15H 3E 15
Load Register A with error code 15H (directory write error). This overwrites the FDC status, so the specific write failure reason is lost, but the caller knows a directory write error occurred.
502C
POP HL E1
Restore Register Pair HL from the stack.
502D
POP DE D1
Restore Register Pair DE from the stack.
502E
RET C9
Return to the caller. The Z/NZ flag from the CP 06H or write failure indicates success or failure, with error code 15H in A on failure.

502FH - Read GAT Sector into Buffer at 4200H

Reads the GAT (Granule Allocation Table) sector from disk into the buffer at 4200H. The GAT is always at sector 1 on the system track (E=01H). Returns Z on success, NZ with error code 16H on failure.

502F
PUSH BC C5
Save Register Pair BC onto the stack (preserves directory encoding and drive number).
5030
PUSH DE D5
Save Register Pair DE onto the stack.
5031
GOSUB to the SYS0 directory I/O setup routine at 4B65H to configure the FDC for the system track.
5034
LD E,01H 1E 01
Load Register E with 01H. The GAT sector is sector 1 on the system track.
5036
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the GAT sector buffer where the read data will be stored.
5039
GOSUB to the SYS0 sector read routine at 4B45H. This reads the 256-byte GAT sector into the buffer at 4200H.
503C
POP DE D1
Restore Register Pair DE from the stack.
503D
POP BC C1
Restore Register Pair BC from the stack.
503E
LD A,16H 3E 16
Load Register A with error code 16H (GAT read error). This is pre-loaded so it is ready if the return is NZ.
5040
RET C9
Return to the caller. The Z/NZ flag from the 4B45H read indicates success or failure. On failure, A contains error code 16H.

5041H - Write GAT Sector from Buffer at 4200H

Writes the GAT sector buffer at 4200H back to disk with write-verify. The GAT is always at sector 1 on the system track. Returns Z on success, NZ with error code 17H on failure.

5041
PUSH BC C5
Save Register Pair BC onto the stack.
5042
PUSH DE D5
Save Register Pair DE onto the stack.
5043
GOSUB to the SYS0 directory I/O setup routine at 4B65H to configure the FDC for the system track.
5046
LD E,01H 1E 01
Load Register E with 01H. The GAT sector is sector 1 on the system track.
5048
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the GAT sector buffer.
504B
GOSUB to the SYS0 write sector routine at 4768H. This writes the 256-byte GAT buffer to disk.
504E
If the NZ FLAG (Not Zero) has been set because the write failed, JUMP to 5055H to set the error code and return.
5050
GOSUB to the SYS0 write-verify routine at 4772H. This performs a read-after-write verification of the GAT sector.
5053
CP A,06H FE 06
Compare the FDC verification status against 06H. Status below 06H indicates a successful verify.
5055
LD A,17H 3E 17
Load Register A with error code 17H (GAT write error).
5057
POP DE D1
Restore Register Pair DE from the stack.
5058
POP BC C1
Restore Register Pair BC from the stack.
5059
RET C9
Return to the caller. The Z/NZ flag indicates success or failure. On failure, A contains error code 17H.

505AH - HL Offset Add Utility

A small utility routine that adds the value in Register A to Register Pair HL, with proper carry propagation from L to H. Used by the extent chain walker to advance HL by a computed offset. Register A is preserved across this call (saved and restored via PUSH AF / POP AF).

505A
PUSH AF F5
Save Register A and flags onto the stack. Register A is preserved across this utility call.
505B
ADD A,L 85
ADD the offset value in Register A to Register L. If the result exceeds 255, the CARRY FLAG is set.
505C
LD L,A 6F
Store the sum back into Register L.
505D
If the NO CARRY FLAG is set (no overflow from L), JUMP to 5060H to skip the H increment.
505F
INC H 24
INCrement Register H by 1 to propagate the carry from the ADD A,L operation. This handles the case where L overflowed past FFH.
5060
POP AF F1
Restore Register A and flags from the stack. A is back to its original value before the utility call.
5061
RET C9
Return to the caller. HL has been advanced by the offset that was in A, with proper carry propagation.