Model I TRSDOS v2.3 SYS0/SYS Explained

This is a complete annotated disassembly of TRSDOS v2.3's SYS0/SYS file for the TRS-80 Model I. SYS0/SYS is the resident DOS kernel, loaded at boot time and remaining in memory at all times. It occupies the address range 400CH-4F26H (with a 256-byte overlay buffer at 4D00H-4DFFH that is not code). The program entry point is 4E00H (cold boot).

SYS0 provides the core services of TRSDOS: RST vector dispatch, the file I/O system (open, read, write, position, rewind, close), low-level disk I/O through the WD1771 Floppy Disk Controller, the RST 28H overlay loading mechanism, the program loader, Granule Allocation Table (GAT) management for disk space allocation, the real-time clock and date display, and the cold boot initialization sequence including RAM detection and the NEG-encoded boot banner.

TRSDOS does quite a bit of sector work by using a buffer and then writing out the buffer when done. The buffer is usually set up being pointed to by the IX register, as that is 1 of 2 registers upon which math can be done. In the case of the buffered sector, the DCB (Device Control Block, also referred to as the FCB or File Control Block) is structured as follows:

  • IX+00H = special DCB code area
  • IX+01H = special codes. Lowest 3 bits are the permission flags. Bit 4 signals that the buffer has been updated / is available for use. Bit 5 forces a read on the next file access. Bit 6 signifies the need for a POSITION call. Bit 7 specifies physical or logical I/O.
  • IX+02H = Reserved / Not Used
  • IX+03H = the LSB of the DCB Sector Buffer Address
  • IX+04H = the MSB of the DCB Sector Buffer Address
  • IX+05H = the next record address -OR - current byte offset in sector buffer to the current record
  • IX+06H = the drive number
  • IX+07H = the DIRECTORY SECTOR OFFSET (i.e., directory sector number with the HIT Index) or the OVERFLOW pointer
  • IX+08H = EOF offset held in the DCB
  • IX+09H = record size in bytes
  • IX+0AH = the LSB of the next sector number
  • IX+0BH = the MSB of the next sector number
  • IX+0CH = the LSB of the EOF sector held in the DCB -or- The number of records
  • IX+0DH = the MSB of the EOF sector held in the DCB -or- The number of records
  • IX+0EH = the track number of the lst GAP
  • IX+0FH = Granule count

Key System Variables and Storage Locations

Address RangePurpose
3C2EH-3C31H
(4 bytes)
Video RAM: hexadecimal diagnostic display position
3C35H-3C3CH
(8 bytes)
Video RAM: clock display position (HH:MM:SS)
4012H-4014H
(3 bytes)
RST 10H vector (JP 4518H - keyboard scan handler)
4015H-401CH
(8 bytes)
Boot sector drive parameter data (per-drive, source for 4358H copy)
4016H-4017H
(2 bytes)
System return address (set to 43D8H at boot)
4041H-4041H
(1 byte)
Seconds counter (binary, 0-59)
4042H-4042H
(1 byte)
Minutes counter (binary, 0-59)
4043H-4043H
(1 byte)
Hours counter (binary, 0-23)
4044H-4044H
(1 byte)
Year counter (binary)
4045H-4045H
(1 byte)
Day counter (binary, 1-31)
4046H-4046H
(1 byte)
Month counter (binary, 1-12)
4047H-4048H
(2 bytes)
High memory limit pointer (initialized to 5200H at boot)
4049H-404AH
(2 bytes)
Detected RAM top address (from cold boot detection)
404CH-404CH
(1 byte)
System capability flags (bit 7 = keyboard handler installed, bit 6 = disk I/O installed)
4059H-405AH
(2 bytes)
Disk I/O vector target (JP 4669H)
405BH-405CH
(2 bytes)
Character output vector target (JP 4560H)
4200H-42FFH
(256 bytes)
Directory sector buffer
4300H+
(varies)
DOS work area base
4304H-4307H
(4 bytes)
Track cache table (one byte per drive, indexed by drive number)
430AH-430BH
(2 bytes)
Saved FCB pointer during overlay calls
430CH-430DH
(2 bytes)
Saved overlay return address
430EH-430EH
(1 byte)
Currently loaded overlay ID (SVC code of resident overlay)
430FH-430FH
(1 byte)
System mode flag (01H = command mode, bit 7 = execute-after-load)
4315H-4315H
(1 byte)
BREAK key enable flag (00H = disabled, non-zero = enabled)
4318H-4337H
(32 bytes)
DOS command input buffer
4358H+
(32/drive)
Drive parameter table (8 bytes params + 8 bytes status + 16 bytes FFH per drive)
43B8H-43C9H
(18 bytes)
Boot sector parameter copies
43CCH-43FFH
(52 bytes)
Work area (cleared to zero during boot)
43D8H-43D8H
(entry)
DOS Ready / command loop entry point
44A1H-44A1H
(1 byte)
Overlay execution state flag
44A7H-44A7H
(1 byte)
Overlay directory entry info
44AAH-44ABH
(2 bytes)
Overlay cache markers (0000H = invalidated)
44AEH-44AFH
(2 bytes)
Overlay starting sector address on disk
4D00H-4DFFH
(256 bytes)
Overlay/sector buffer area (not code - used for overlay loading and directory I/O)

Self-Modifying Code Locations

WriterTargetPurpose
4641H4645HDrive command byte in SET instruction
4672H4693HFDC command byte in LD (HL),xxH
467BH46D3HDRQ polling opcode (IN vs OUT direction)
4682H46A7HData transfer loop code
478EH485EHWrite verify flag
457FH45A6HCurrent timer callback slot pointer
48E7H499AHSector offset in ADD instruction at 4999H
4933H499AHRemaining sector count (same target as above)
496AH499AHSector offset within extent (same target)
499FH49AEHTrack index in ADD instruction at 49ADH
49BDH4ABEHDirectory entry info byte for GAT write
4B64H4B68HBIT opcode for GAT bit test/set (dynamic BIT n,B)
4BE8H4BFCHOverlay entry point address in CALL instruction at 4BFBH
4BF1H4C00HBREAK key state saved/restored across overlay calls

Major Routines

AddressPurpose
400CHRST 28H Vector
JP to overlay loader at 4BA2H
400FHRST 30H Vector
JP to DEBUG/execution hook
4400HDOS Ready
Command entry point
4424HOpen Existing File
SVC stub for file open
4430HOpen/Create and Load
SVC stub for file load
4437HRead Record
JP to read handler at 476DH
4518HKeyboard Scan
RST 10H handler
4560HCharacter Output
RST 18H handler
45AFHTimer Interrupt
Clock cascade and display update
4600HDrive Select
FDC drive selection with track cache
4647HSeek Track
FDC seek with step rate control
4669HDisk I/O Core
FDC sector read/write with DRQ polling
4737HPosition to Record
Seek to logical record in file
4756HRewind File
Reset file position to beginning
476DHRead Record
Read one logical record from file
478BHWrite Record
Write one logical record to file
4892HFile Open Check
Verify file is open, set up overlay call context
48B9HEOF Check
Compare current position against end-of-file
48DCHSector Position Calc
Walk granule chain to find physical sector
4993HPhysical Disk Params
Convert logical sector to track/sector/drive
4A18HGAT Allocation
Search GAT for free granules, allocate disk space
4A6EHGAT Free Search
Scan GAT bytes for free granule bits
4B35HRead with Retry
Sector read with head recalibration on soft error
4B5DHGAT Bit Test
Self-modifying BIT instruction for granule test
4B6AH24-Bit Multiply
HL:A = DE x C
4B84H16-Bit Divide
HL / A, remainder in A
4BA2HOverlay Loader
RST 28H dispatcher with overlay caching
4C06HLoad Program
Load and optionally execute program file
4C39HProgram Loader Core
Parse type 01H/02H/20H+ load format blocks
4CB4HTime Display
Convert binary time to HH:MM:SS at video RAM 3C35H
4CD2HDate Display
Convert binary date to MM/DD/YY
4CD9HHex Display
Display 16-bit value as 4 hex digits at video RAM 3C2EH
4E00HCold Boot
Program entry - DI, IM1, SP init, RAM detection
4E23HBoot Continuation
Vector install, drive init, banner, auto-execute

RST 28H SVC Code Encoding

The overlay system uses RST 28H with Register A containing a Supervisor Call (SVC) code. The encoding is:

Bit(s)Description
Bit 7Overlay Flag
Must be set (1) for overlay calls. If clear, dispatches to BREAK handler at 4312H.
Bits 4-6Function Number
Selects which routine within the overlay to execute (0-7).
Bits 0-3Overlay Number
Selects which SYS file (0-15). Values 0-7 map directly to directory entries; values 8-15 have 18H added to the entry number.

TRSDOS 2.3 Error Codes Referenced

Code / HexDescription
16 / 10HInvalid directory entry number
17 / 11HDisk read error (directory sector)
18 / 12HDisk write error (directory sector)
20 / 14HSeek error / track out of range
21 / 15HWrite/verify failure on directory sector
27 / 1BHDisk full (no free granules in GAT)
28 / 1CHPosition at end of file
29 / 1DHPosition past end of file
34 / 22HInvalid program module type
37 / 25HAccess denied (file type not loadable)
38 / 26HFile not open
95 / 5FHFile not found during program load
99 / 63HMemory write error (non-writable address)
100 / 64HMemory write verify error

Dissassembly

400CH - DOS Jump Table and RST Vectors

This is the main entry point table for TRSDOS 2.3. The ROM vectors at RST 28H (0028H) and RST 30H (0030H) jump into this table. Application programs call DOS services by jumping to fixed addresses in this table, which then redirect to the actual handler routines within SYS0. The table starts at 400CH because addresses 4000H-400BH are reserved for BASIC RST hooks.

400C
JUMP to 4BA2H, the RST 28H handler (overlay loader/dispatcher). This is the primary DOS system call vector. The ROM's RST 28H instruction at 0028H jumps here via JP 400CH. Register A holds the SVC function code that determines which overlay to load and which routine within it to execute.
400F
JUMP to 44B4H, the RST 30H handler (error handler / DEBUG entry). The ROM's RST 30H instruction at 0030H jumps here via JP 400FH. This pushes AF, loads A with an SVC code, and calls the overlay dispatcher to invoke the error/debug overlay.
4012
Jump point to interrupt service routine
4015
DEFS 8
Keyboard DCB. This value is loaded by Level II
401D
DEFS 8
Video DCB. This value is loaded by Level II
4025
DEFS 8
Printer DCB. This value is loaded by Level II

402DH - DOS Ready and Error Exit Vectors

These vectors provide the standard exit paths for DOS commands and overlays. Programs that complete successfully jump to 402DH. Programs that encounter errors use 4030H (error already displayed) or call through the error handler overlay.

402D
No-Error Exit / DOS Ready. JUMP to 4400H, the DOS Ready handler. Programs concluding with no error jump here. DOS resets its internal state and returns to the TRSDOS READY prompt.
4030
LD A,A3H 3E A3
Load Register A with A3H, the SVC function code for the error-already-displayed exit handler. SVC A3H decodes as: function 5, file 0, directory sector 5 — this targets a routine in the DOS command overlay that handles returning to READY after an error message has already been shown.
4032
RST 28H EF
Invoke the RST 28H supervisor call with function code A3H (loaded above). This loads and executes the error-already-displayed exit routine from the appropriate overlay.
4033
JUMP to 44BBH, the I/O Dispatcher. This is the entry point for the character I/O routing system that processes overlay module chain entries.

Local Storage Area

4036
DEFS 7
Table to hold the keyboard row history
403D
DEFB 00
Video display byte used by Level II
403E
DEFB 21H
Reserved
403F
DEFB 01H
Reserved
4040
DEFB 00
Clock Interrupt Index
4041
DEFB 00
Seconds
4042
DEFB 00
Minutes
4043
DEFB 00
Hours
4047
DEFW 0000
Utility program load address
4049
DEFW 0000
Memory size
404B
DEFB 00
Current interrupt byte status
404C
DEFB 00
Interrupt subroutine mask

Interrupt task list

404D
DEFW 4537H
Address of interrupt service routine for interrupt bit 0
404F
DEFW 4537H
Address of interrupt service routine for interrupt bit 1
4051
DEFW 4537H
Address of interrupt service routine for interrupt bit 2
4053
DEFW 4537H
Address of interrupt service routine for interrupt bit 3
4055
DEFW 4537H
Address of interrupt service routine for interrupt bit 4
4057
DEFW 4537H
Address of interrupt service routine for interrupt bit 5
4059
DEFW 4537H
Address of interrupt service routine for interrupt bit 6
405B
DEFW 4537H
Address of interrupt service routine for interrupt bit 7

System Sector Buffer

4200
DEFS 256
System Sector Buffer Area

Track History Table

4300
DEFB 11H
Last Track Addressed on Drive 0
4301
DEFB 11H
Last Track Addressed on Drive 1
4302
DEFB 11H
Last Track Addressed on Drive 2
4303
DEFB 11H
Last Track Addressed on Drive 3

Directory Track List Table

4304
DEFB 11H
Directory Track for Drive 0
4305
DEFB 11H
Directory Track for Drive 1
4306
DEFB 11H
Directory Track for Drive 2
4307
DEFB 11H
Directory Track for Drive 3

Unit Flags and Addresses

4308
DEFB 00H
Last Disk Drive Selected
4309
DEFB 01H
Unit mask for the last drive selected
430A
DEFB 00H
DCB/Buffer address, current operation
430B
DEFB 00H
430C
DEFB 00H
DCB address for current operation
430D
DEFB 00H
430E
DEFB 02H
Last Overlay Load Request
430F
DEFB 00H
DEBUG Load Flag
4310
DEFB 00H
Reserved
4311
DEFB 00H
Reserved
4312
JUMP to 4BA0H to return with zero status. This will be JP 5BA4H if disk basic is loaded

DEBUG related

4315
DEFB 00H
This byte will be either 0 if DEBUG is off, or C3 if DEBUG is on
4316
DEFW 00H
This byte will be the address of the nucleus to load DEBUG
4318
-43BF
Command Line Buffer

4398H - Routine to wait for Disk Controller to Become NOT Busy

Waits for the WD1771 Floppy Disk Controller to complete its current operation by polling the Busy bit (bit 0) of the FDC Status Register at 37ECH. Returns only when the FDC is no longer busy. Called before issuing new FDC commands to ensure the controller is ready.

4398
LD A,(37ECH) 3A EC 37
[LOOP START] Fetch the FDC Status Register from memory-mapped address 37ECH (WD1771 Command/Status Register) into Register A. This register reflects the current state of the floppy disk controller.
76543210
Not
Ready
ProtectedHead
Loaded
Seek
Error
CRC
Error
Track 00IndexBusy
439B
BIT 0,A CB 47
Test Bit 0 (Busy flag) of the FDC Status Register value in Register A. If Bit 0 is 1, the FDC is still executing a command and the Z FLAG is cleared (NZ). If Bit 0 is 0, the FDC is idle and the Z FLAG is set.
439D
RET Z C8
If the Z FLAG has been set (Bit 0 is clear, meaning the FDC is no longer busy), RETURN to the caller. The FDC is ready to accept a new command.
439E
If the FDC is still busy (NZ), LOOP BACK to 4398H to read the status register again. This tight loop continues until the Busy bit clears. [LOOP END]

43ACH - Drive Select and Status Read

Activates the currently selected drive by writing the drive select value to the drive select latch at 37E1H, then reads and returns the FDC Status Register. The drive select value is fetched from the system variable at 4309H, which holds the current drive's select latch pattern (01H=Drive 0, 02H=Drive 1, 04H=Drive 2, 08H=Drive 3).

43AC
LD A,(4309H) 3A 09 43
Fetch the current drive select value from 4309H into Register A. This byte holds the bit pattern for the drive select latch (01H=Drive 0, 02H=Drive 1, 04H=Drive 2, 08H=Drive 3). It was set during the most recent drive selection operation at 4600H.
43AF
LD (37E1H),A 32 E1 37
Write the drive select value from Register A to the Drive Select Latch at memory-mapped address 37E1H. This activates the selected drive's electronics, enabling the FDC to communicate with it. On the Model I Expansion Interface, 37E0H-37E3H all map to the same drive select latch.
43B2
LD A,(37ECH) 3A EC 37
Read the FDC Status Register from memory-mapped address 37ECH into Register A. After selecting the drive, this captures the current status (busy, write protect, head loaded, etc.) for the caller to examine.
43B5
RET C9
RETURN to the caller with the FDC Status Register value in Register A.

43C0H - Storage Locations

43C0
Device List

43D1H - Decrement BC If IX+08H Is Non-Zero

A small utility that checks the byte at FCB offset +08H (the sector count / EOF marker in the File Control Block pointed to by IX). If the value is zero, it returns immediately. If non-zero, it decrements Register Pair BC (typically the remaining sector count) and returns. This is used during file positioning to handle the last-sector adjustment when the EOF offset is non-zero.

43D1
LD A,(IX+08H) DD 7E 08
Fetch the byte at FCB offset +08H (the EOF byte offset within the last sector) from the File Control Block pointed to by IX. A value of 00H means the file ends exactly on a sector boundary; any other value indicates the file's last sector is partially filled.
43D4
OR A B7
OR Register A with itself to set the flags. If A is 00H (EOF on sector boundary), the Z FLAG is set. If A is non-zero (partial last sector), the NZ FLAG is set.
43D5
RET Z C8
If the Z FLAG has been set (the EOF byte offset is zero, meaning the file ends exactly on a sector boundary), RETURN without modifying BC. No adjustment is needed.
43D6
DEC BC 0B
DECrement Register Pair BC by 1. BC holds the remaining sector count for the file. Since the last sector is only partially filled (IX+08H is non-zero), the effective full-sector count is one less than the total. This adjustment ensures the file position calculations account for the partial final sector.
43D7
RET C9
RETURN to the caller with BC adjusted (decremented by 1) to reflect the partial last sector.

43D8H - Keyboard Scan and New Keypress Detection

Scans the Model I keyboard matrix by reading the 7 keyboard row addresses (3801H through 3880H) and comparing them against the stored keyboard image at 4036H-403CH. Detects newly-pressed keys by XORing the current state with the previous state. Returns with D containing a count of changed rows and, if a new keypress is found, jumps to the key decode routine. This routine is called by the interrupt handler to provide debounced keyboard input.

43D8
LD HL,4036H 21 36 40
Point Register Pair HL to 4036H, the start of the keyboard image buffer (7 bytes at 4036H-403CH). This buffer stores the previous state of each keyboard row so that newly-pressed keys can be detected by comparison.
43DB
LD BC,3801H 01 01 38
Load Register Pair BC with 3801H, the first keyboard row address. The Model I keyboard is memory-mapped at 3801H (Row 0: @ABCDEFG) through 3880H (Row 7: SHIFT). Register C starts at 01H and is rotated left through the row addresses.
43DE
LD D,00H 16 00
Load Register D with 00H to initialize the changed-row counter. D will be incremented for each keyboard row that has no new keypresses, serving as an index.

[LOOP START] The following loop iterates through all 7 keyboard rows, comparing the current hardware state against the stored image to detect newly-pressed keys.

43E0
LD A,(BC) 0A
Read the current keyboard row state from the hardware address pointed to by Register Pair BC (starting at 3801H). Each bit represents one key in the row: 1=pressed, 0=not pressed (active HIGH after the hardware inverts the active-LOW signals).
43E1
LD E,A 5F
Copy the current keyboard row state from Register A to Register E for safekeeping. E now holds the raw current state of this keyboard row.
43E2
XOR (HL) AE
XOR the current row state (Register A) with the previous row state stored in the keyboard image buffer at (HL). The result has 1-bits only where the state has CHANGED since the last scan (key newly pressed or newly released).
43E3
LD (HL),E 73
Store the current keyboard row state (Register E) into the keyboard image buffer at (HL), updating the stored image for the next comparison cycle.
43E4
AND E A3
AND the changed-bits mask (Register A from the XOR) with the current row state (Register E). This isolates only the bits for keys that are BOTH changed AND currently pressed — i.e., newly pressed keys. Keys that were released are filtered out.
43E5
If the NZ FLAG has been set (at least one key in this row was newly pressed), JUMP forward to 43EFH to process the new keypress. Register A holds the bitmask of newly-pressed keys in this row.
43E7
INC D 14
INCrement Register D (the row counter) by 1. No new keypress was found in this row, so advance the counter to track which row we are scanning.
43E8
INC L 2C
INCrement Register L by 1 to advance the keyboard image buffer pointer from one row entry to the next (4036H to 4037H, etc.).
43E9
RLC C CB 01
Rotate Register C left through carry. C started at 01H (3801H = Row 0). After RLC: 02H (3802H = Row 1), then 04H (3804H = Row 2), 08H (3808H = Row 3), 10H (3810H = Row 4), 20H (3820H = Row 5), 40H (3840H = Row 6), 80H (3880H = Row 7). The carry flag is cleared after each rotate since C starts below 80H. When C reaches 80H and is rotated again, carry is set and C becomes 01H again.
43EB
If the Sign flag is positive (P), meaning bit 7 of the last result is clear and we have not yet scanned all 7 rows, LOOP BACK to 43E0H to scan the next keyboard row. The JP P condition is met as long as the RLC operation has not rotated C past 80H. [LOOP END]
43EE
RET C9
RETURN to the caller. All 7 keyboard rows have been scanned with no new keypresses detected. Register D contains 07H (incremented once per row).

43EFH - New Keypress Handler

Called from the keyboard scan loop at 43D8H when a newly-pressed key is detected. Register A holds the bitmask of newly-pressed keys, Register D holds the row number (0-6), and Register BC points to the keyboard hardware row. This routine waits for the key to stabilize (debounce), then checks if it is still pressed before dispatching to the ROM's key decode routine at 03FBH.

43EF
LD E,A 5F
Copy the new keypress bitmask from Register A to Register E. A contains the bits identifying which key(s) in the current row were newly pressed.
43F0
PUSH BC C5
Save Register Pair BC (the keyboard hardware row address) onto the stack. It will be needed after the debounce delay to re-read the same row.
43F1
LD BC,04DFH 01 DF 04
Load Register Pair BC with 04DFH, a debounce delay count. This value is passed to the ROM delay loop at 0060H to wait approximately 5-10 milliseconds for the key contact to stabilize.
43F4
GOSUB to ROM routine at 0060H to execute a delay loop. The delay count in BC determines the pause duration. This provides keyboard debouncing — waiting for the mechanical key contacts to settle before re-reading the row.
43F7
POP BC C1
Restore Register Pair BC (the keyboard hardware row address) from the stack.
43F8
LD A,(BC) 0A
Re-read the current keyboard row state from the hardware address in BC. After the debounce delay, this captures the stabilized key state.
43F9
AND E A3
AND the stabilized row state (Register A) with the new-keypress bitmask (Register E). If the key is still pressed after the debounce delay, the result is non-zero. If the key was a bounce artifact and is no longer pressed, the result is zero.
43FA
RET Z C8
If the Z FLAG has been set (the key is no longer pressed after debouncing — it was a bounce), RETURN to the caller without processing a keypress. The detected press was spurious.
43FB
JUMP to ROM routine at 03FBH, the key decode and dispatch handler. Register A contains the validated keypress bitmask and Register D contains the keyboard row number. The ROM routine translates these into an ASCII character code and processes the keypress.

4400H - DOS Ready Handler and SVC Dispatch Stubs

This area at 4400H-4478H contains the DOS Ready handler, the error handler vectors, and the main DOS service jump table. Many entries are SVC stubs consisting of LD A,xxH followed by RST 28H, which load an overlay function code and invoke the overlay dispatcher. Other entries are direct JP instructions to routines within SYS0. The disassembler has partially misinterpreted this area because it did not account for the 3-byte SVC stub pattern (3E xx EF). The addresses below reflect the corrected structure.

The area from 4400H to 440CH contains the DOS Ready handler and error entry stubs. Note that 4400H is the target of JP 4400H at 402DH (the No-Error Exit vector). The code here is a series of SVC stubs that load overlay function codes and dispatch via RST 28H.

4400
LD A,93H 3E 93
Dos Ready Entry. Load Register A with 93H, the SVC function code for the DOS Ready / command processor overlay. SVC 93H decodes as: function 4, file 2, directory sector 5 — this targets the TRSDOS command processor overlay. This is the primary entry point reached when a program exits normally via JP 402DH.
4402
RST 28H EF
Invoke the RST 28H supervisor call with the function code 93H loaded above. This triggers the overlay loader at 4BA2H, which loads the command processor overlay and transfers control to it. The command processor displays the TRSDOS READY prompt and waits for user input.

The following entries at 4403H-440CH are additional SVC entry stubs used by the DOS error handling system. Note that 4405H (LD A,93H) appears to be a duplicate entry point that also routes to the DOS Ready overlay, while 4406H routes to the B3H overlay function for different error handling.

4403
LD A,93H 3E 93
Load Register A with 93H (DOS Ready overlay function code). This is a secondary entry point to the same DOS Ready overlay as 4400H.
4405
RST 28H EF
Invoke RST 28H with SVC 93H. Same action as the entry at 4402H.
4406
LD A,B3H 3E B3
Load Register A with B3H, the SVC function code for the error handler overlay (function 5, file 2, directory sector 5). This entry point is used when an error has been detected and the error overlay needs to display a message and return to DOS Ready.
4408
RST 28H EF
Invoke RST 28H with SVC B3H to load and execute the error handler overlay.
4409
LD A,B3H 3E B3
Dos Error Exit. Load Register A with B3H, the SVC function code for the error handler overlay. This is the standard DOS error exit vector — programs that encounter errors jump here (or to 4409H via the documented entry point) to have the error displayed and return to DOS Ready.
440B
RST 28H EF
Invoke RST 28H with SVC B3H to display the error and return to DOS Ready.
440C
JUMP to ROM routine at 00B0H, the BASIC warm start entry point. This transfers control back to BASIC after the error overlay has completed. The ROM routine at 00B0H re-initializes the BASIC interpreter state and displays the READY prompt.

440DH - DOS Service Jump Table

The main DOS service vector table. Application programs call these fixed addresses to access DOS functions. Each entry is either a JP instruction to a handler within SYS0, or a LD A,xxH / RST 28H pair that loads an overlay. The addresses from 440DH through 4478H constitute the public API of TRSDOS 2.3.

440D
Enter Debug. JUMP to 44B4H, the error/debug handler entry that pushes AF and invokes an overlay for debugging.
4410
Enqueue Timer Callback. JUMP to 4596H. Registers A (callback slot number) and DE (callback address) specify which timer slot to install. The interrupt handler at 4518H dispatches these callbacks on each timer tick.
4413
Dequeue Timer Callback. JUMP to 4593H to remove a timer callback. Register A holds the callback slot number to clear.
4416
Keep Drives Spinning. JUMP to 45A5H. This re-reads the motor-on timer for the currently selected drive and resets the countdown, preventing the drive motor from being turned off by the timer interrupt.
4419
Dos-Call. JUMP to 458EH. Execute a DOS command from within a running program and return to the caller when done. Register Pair HL points to the command string.
441C
LD A,C3H 3E C3
Extract Filespec. Load Register A with C3H, the SVC function code for the filespec extraction overlay (function 6, file 0, directory sector 5). This parses a filename from the command line.
441E
RST 28H EF
Invoke RST 28H with SVC C3H to load and execute the filespec extraction overlay.
441F
RET C9
RETURN to the caller after the filespec extraction overlay has completed. Register Pair DE points to the parsed filename data and the Z flag indicates success.
4420
LD A,A4H 3E A4
Open File (New or Existing). Load Register A with A4H, the SVC function code for the file open overlay (function 5, file 0, directory sector 6).
4422
RST 28H EF
Invoke RST 28H with SVC A4H to open a file. Register Pair DE points to the FCB, HL points to the filespec.
4423
RET C9
RETURN to the caller. Z flag set = success, NZ = error with code in A.
4424
LD A,94H 3E 94
Open Existing File. Load Register A with 94H, the SVC function code for opening an existing file only (function 4, file 2, directory sector 6). Fails if the file does not exist.
4426
RST 28H EF
Invoke RST 28H with SVC 94H.
4427
RET C9
RETURN to the caller. Z flag set = success, NZ = error with code in A.
4428
LD A,95H 3E 95
Close File. Load Register A with 95H, the SVC function code for the file close overlay (function 4, file 2, directory sector 7). Flushes any buffered data and updates the directory entry.
442A
RST 28H EF
Invoke RST 28H with SVC 95H to close the file.
442B
RET C9
RETURN to the caller.
442C
LD A,A5H 3E A5
Kill File. Load Register A with A5H, the SVC function code for the file kill/delete overlay (function 5, file 0, directory sector 7). Removes the file and frees its allocated granules.
442E
RST 28H EF
Invoke RST 28H with SVC A5H to delete the file.
442F
RET C9
RETURN to the caller.
4430
NOP 00
No operation. Padding byte before the Load Program vector. This aligns the entry point at 4431H.
4431
Open/Create and Load File. JUMP to 4C16H, the file open and program loading handler. Opens the specified file and prepares to load its contents into memory.
4434
Load Program. JUMP to 4C06H, the program loader. Reads a /CMD format file into memory and optionally transfers control to its entry point.
4437
Read Record. JUMP to 476DH. Reads the next logical record from the file associated with the FCB pointed to by IX. Returns the data in the caller's buffer.
443A
Write Record. JUMP to 478BH. Writes a logical record to the file associated with the FCB pointed to by IX from the caller's buffer.
443D
Write with Verify. JUMP to 47A8H. Same as Write Record but performs a read-after-write verification to ensure data integrity.
4440
Rewind File. JUMP to 4756H. Repositions the FCB to the start of the file (sector 0, byte offset 0).
4443
Position to Record. JUMP to 4700H. Positions the FCB to a specified logical record number within the file. BC holds the target record number.
4446
Position to Eof. JUMP to 4737H. Positions the FCB to the end of the file for appending.
4449
JP C35FH C3 5F C3
Special Vector. JUMP to C35FH. This address is outside the normal DOS range and appears to be a placeholder or a vector that is patched during initialization. The target C35FH would be in high RAM if RAM extends that far.

4467H - Display and Utility Vectors

Continuation of the DOS service jump table with display output, clock/date conversion, and overlay-based utility functions.

4467
RST 08H ⇒ 44H CF 44
Display Message to Screen. RST 08H checks if the next byte (44H, ASCII 'D') matches the character in Register A. This is part of a dispatch mechanism — but more precisely, 4467H is a 2-byte entry that is called as a subroutine. The RST 08H / DB 44H sequence is the TRSDOS way of embedding inline data after a restart instruction. The byte 44H is consumed as a parameter.
4469
NOP 00
Padding byte.
446A
Display Message to Printer. JUMP to 44DFH, the printer string output routine. Register Pair HL points to the null-terminated (03H) string to print.
446D
Convert Time to Hh:Mm:Ss. JUMP to 4CB7H. Reads the system clock bytes at 4041H-4043H and converts them to a displayable HH:MM:SS string at a screen location.
4470
Convert Date to Mm/Dd/Yy. JUMP to 4CD2H. Reads the system date bytes at 4044H-4046H and converts them to a displayable MM/DD/YY string.
4473
LD A,D3H 3E D3
Insert Default Extension. Load Register A with D3H, the SVC function code for the default filename extension overlay (function 6, file 2, directory sector 5).
4475
RST 28H EF
Invoke RST 28H with SVC D3H to insert the default filename extension into the filespec.
4476
LD A,E3H 3E E3
Additional Svc Function. Load Register A with E3H, the SVC function code (function 7, file 0, directory sector 5). This invokes an additional overlay utility function.
4478
RST 28H EF
Invoke RST 28H with SVC E3H.

4480H - Filename and Password Template

This area contains the default FCB filename template used for file operations. The format is the standard TRSDOS filespec layout: 8-character filename, a "/" separator, 3-character extension, a "." separator, and an 8-character password field. Following the template is the default drive specifier area with a colon and padding, plus the system password and access flags.

4480
DEFM "NAMENAME/EXT" 4E 41 4D 45 4E 41 4D 45 2F 45 58 54
Default Fcb Filename Template
12 bytes at 4480H-448BH. This is the placeholder filename/extension template: "NAMENAME" (8 chars) followed by "/" separator and "EXT" (3 chars). When a file operation is initiated, the actual filename is copied over this template area.
448C
DEFM ".PASSWORD" 2E 50 41 53 53 57 4F 52 44
Default Password Template
9 bytes at 448CH-4494H. The "." separator followed by the placeholder password "PASSWORD" (8 chars). Overwritten with the actual file password when needed.
4495
DEFM ":N" 3A 4E
Drive Specifier
2 bytes at 4495H-4496H. The ":" drive separator followed by "N" as a placeholder drive letter. Overwritten with the actual drive number during filespec parsing.
4497–44A0
DEFB (10 bytes) 20 20 20 20 20 20 20 20 80 00
Padding and Flags
8 space characters (20H) for padding, followed by 80H (file attribute flags — bit 7 set indicates system/hidden) and 00H (end marker). This completes the default FCB template.

44A1H - System State Variables

A block of system state variables used by the overlay loader and various DOS functions. These are initialized during boot and updated during operation.

44A1
DEFB 00H 00
44A1H
Overlay execution state flag. Cleared (00H) during boot at 4BC5H. Set to track whether the DOS is currently executing an overlay command.
44A2–44A3
DEFB 00H,00H 00 00
44A2H-44A3H
Reserved bytes, initialized to 00H.
44A4
DEFB 4DH 4D
44A4H
Overlay page boundary marker. The value 4DH (77 decimal) indicates the high byte of the overlay load area (4D00H). Overlays are loaded at page 4DH (address 4D00H-4DFFH and beyond).
44A5–44AB
DEFB (7 bytes) 00 00 00 00 00 FF FF
44A5H-44ABH
System state bytes. 44A5H-44A9H are cleared (00H). 44AAH-44ABH are set to FFH, used as initialization markers for the overlay load address tracking.
44AC–44AF
DEFB FFH,FFH,00H,00H FF FF 00 00
44ACH-44AFH
Additional overlay state. 44ACH-44ADH = FFFFH (no overlay cached), 44AEH-44AFH = 0000H (overlay load address, filled during CMD file loading at 4BDCH).

44B0H - Error Handler Entry (SVC 86H / 87H)

Two error handler entry points that save the current flags and invoke overlay functions via RST 28H. The entry at 44B0H uses SVC 86H and the entry at 44B4H uses SVC 87H. These are the primary mechanisms for dispatching to the error display and debug overlays. After the overlay completes, execution continues at 44B8H which extracts the overlay's return information from IX.

44B0
PUSH AF F5
Save Register Pair AF (Accumulator and Flags) onto the stack. The flags contain the error condition information that the error overlay will need.
44B1
LD A,86H 3E 86
Load Register A with 86H, the SVC function code for the error display overlay (function 4, file 0, directory sector 8).
44B3
RST 28H EF
Invoke RST 28H with SVC 86H. This loads the error display overlay and transfers control to it. The overlay displays the error message corresponding to the error code that was on the stack.
44B4
PUSH AF F5
Save Register Pair AF onto the stack. This is the entry point for the RST 30H / DEBUG handler (targeted by JP 44B4H at 400FH and 440DH).
44B5
LD A,87H 3E 87
Load Register A with 87H, the SVC function code for the debug/secondary error overlay (function 4, file 0, directory sector 9).
44B7
RST 28H EF
Invoke RST 28H with SVC 87H. This loads the debug overlay.
44B8
PUSH HL E5
Save Register Pair HL onto the stack (preserving the caller's HL value).
44B9
POP IX DD E1
Pop the top of the stack into IX. This transfers the HL value just pushed into IX, so IX now points to the same address that HL contained. This is a common Z80 pattern for loading IX from HL (since there is no direct LD IX,HL instruction).
44BB
LD L,(IX+01H) DD 6E 01
I/O Dispatcher Entry. Fetch the byte at IX+01H (offset 1 in the overlay's dispatch table) into Register L. This is the low byte of the routine address that the overlay has set up for the I/O operation to dispatch to.
44BE
LD H,(IX+02H) DD 66 02
Fetch the byte at IX+02H (offset 2 in the overlay's dispatch table) into Register H. This is the high byte of the dispatch routine address. HL now contains the complete target address.
44C1
LD A,(IX+00H) DD 7E 00
Fetch the byte at IX+00H (offset 0 — the module type/command byte) into Register A. This identifies what kind of dispatch record this is.
44C4
CP 10H FE 10
Compare Register A against 10H. If the module type byte is 10H, this indicates a chain entry — the dispatch record points to another dispatch table rather than a final handler. If below 10H, it is a direct handler address.
44C6
If the Z FLAG has been set (module type = 10H, meaning this is a chain entry), LOOP BACK to 44B8H to follow the chain. HL points to the next dispatch table, which is loaded into IX and the process repeats until a non-chain entry is found.
44C8
If the NO CARRY FLAG has been set (module type >= 10H but not equal to 10H, meaning an invalid or unrecognized module type), JUMP to 47AEH which is the open-for-write handler. This handles FCB initialization for write operations when the dispatch type indicates a file-write context.
44CB
LD A,B 78
Load Register A with the value from Register B. B holds the caller's parameter (typically a function sub-code or character to process).
44CC
CP 02H FE 02
Compare Register A against 02H. This tests the sub-function code: values below 02H are input operations, 02H and above are output operations.
44CE
JP (HL) E9
JUMP to the address in Register Pair HL. This is the final dispatch — HL contains the handler address extracted from the I/O module dispatch table. The carry flag from the CP 02H comparison tells the handler whether this is an input or output request.

44CFH - Display String to Screen

Displays a null-terminated string (terminated by 03H) to the screen using the ROM character output routine at 0033H. Register Pair HL points to the start of the string on entry. Each character is output via CALL 0033H until a carriage return (0DH) is encountered, at which point the routine returns. The 03H terminator causes an immediate return without printing.

44CF
PUSH HL E5
Save Register Pair HL (pointer to the string to display) onto the stack. HL will be modified during the loop and needs to be preserved for the caller.
44D0
LD A,(HL) 7E
[LOOP START] Fetch the next character from the string at the address pointed to by HL into Register A.
44D1
CP 03H FE 03
Compare Register A against 03H (the string terminator byte, ETX / End-of-Text). If Register A equals 03H, the Z FLAG is set, indicating the end of the string.
44D3
If the Z FLAG has been set (the 03H terminator was found), JUMP forward to 44DDH to restore HL and return. [STRING END]
44D5
GOSUB to ROM routine at 0033H to display the character in Register A at the current cursor position and advance the cursor. This is the Level II BASIC character output routine.
44D8
INC HL 23
INCrement Register Pair HL by 1 to advance the string pointer to the next character.
44D9
CP 0DH FE 0D
Compare Register A (the character just displayed) against 0DH (carriage return). If a CR was output, the Z FLAG is set.
44DB
If the NZ FLAG has been set (the character was NOT a carriage return), LOOP BACK to 44D0H to fetch and display the next character. [LOOP END]
44DD
POP HL E1
Restore Register Pair HL (the original string pointer) from the stack.
44DE
RET C9
RETURN to the caller. The string has been displayed on screen. HL is restored to its original value.

44DFH - Display String to Printer

Displays a null-terminated string (terminated by 03H) to the printer using the ROM printer output routine at 003BH. Identical in structure to the screen display routine at 44CFH but uses CALL 003BH instead of CALL 0033H for printer output.

44DF
PUSH HL E5
Save Register Pair HL (pointer to the string to print) onto the stack.
44E0
LD A,(HL) 7E
[LOOP START] Fetch the next character from the string at (HL) into Register A.
44E1
CP 03H FE 03
Compare Register A against 03H (string terminator). If equal, the Z FLAG is set.
44E3
If the Z FLAG has been set (terminator found), JUMP to 44EDH to return.
44E5
GOSUB to ROM routine at 003BH to send the character in Register A to the printer. This is the Level II BASIC printer output routine.
44E8
INC HL 23
INCrement Register Pair HL to advance to the next character in the string.
44E9
CP 0DH FE 0D
Compare Register A against 0DH (carriage return).
44EB
If the NZ FLAG has been set (not a carriage return), LOOP BACK to 44E0H to continue printing. [LOOP END]
44ED
POP HL E1
Restore Register Pair HL from the stack.
44EE
RET C9
RETURN to the caller. The string has been sent to the printer.
44EF
NOP
Empty

4500H - Timer Callback Address Table

A data table of 12 two-byte addresses at 4500H-4517H. Each pair represents a timer callback slot that the interrupt handler dispatches on the heartbeat tick. Slots 0-3 (4500H-4507H) are dispatched by the heartbeat handler at 4560H. Slots 4-11 (4508H-4517H) are the extended callback table. Default entries point to 45A3H (a simple RET) or other handler addresses. The values shown are the initial boot state; these are overwritten at runtime when timer callbacks are installed via CALL 4410H.

4500–4517
DEFW (12 entries) A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45 A3 45
Timer Callback Address Table
Twelve 2-byte entries (24 bytes). Each entry is a little-endian address pointing to a timer callback routine. All 12 entries are initialized to 45A3H, which points to a default handler (AND D / LD B,L — effectively a NOP placeholder). When a program installs a timer callback via CALL 4410H, the appropriate entry is overwritten with the handler's address. The interrupt handler at 4560H dispatches these on each heartbeat tick.

4518H - Interrupt Service Routine (RST 38H Handler)

The main interrupt service routine, reached via RST 38H (0038H) which jumps to 4012H, which in turn jumps here. This routine first reads the drive select latch to check for active drive motors, then scans the timer callback table at 404BH to dispatch any pending callbacks. If a callback slot's timer has expired, it calls the registered handler. Also checks for the BREAK key and optionally dispatches through the BREAK handler vector at 4312H.

4518
PUSH HL E5
Save Register Pair HL onto the stack. The ISR must preserve all registers it uses since it interrupts whatever code is running.
4519
PUSH AF F5
Save Register Pair AF (Accumulator and Flags) onto the stack.
451A
LD A,(37E0H) 3A E0 37
Read the Drive Select Latch at memory-mapped address 37E0H into Register A. This captures which drive (if any) currently has its motor running and is selected. This value is used to manage motor timeout callbacks.
451D
LD HL,404BH 21 4B 40
Point Register Pair HL to 404BH, the start of the timer callback slot table (8 two-byte entries at 404BH-405CH). Each entry pairs a motor-status byte with a callback flag.
4520
LD (HL),A 77
Store the current drive select latch value (Register A) into the first byte of the callback table at 404BH. This records which drive is currently active for the motor timeout logic.
4521
INC L 2C
INCrement Register L by 1 to advance to 404CH, the motor timeout mask byte.
4522
AND (HL) A6
AND the drive select latch value (Register A) with the motor timeout mask at (HL). If the result is non-zero, the currently active drive has an active timeout callback pending.
4523
If the Z FLAG has been set (no motor timeout callback is pending for the active drive), JUMP forward to 452DH to check the BREAK key instead.
4525
INC L 2C
INCrement Register L to advance to the next callback slot entry.
4526
RRA 1F
Rotate Register A right through carry. This shifts the drive select bits right, moving the next drive's bit into the carry flag position to check if it has an active callback.
4527
If the CARRY FLAG has been set (the current bit position represents a drive with an active motor timeout), JUMP forward to 4538H to dispatch that drive's timer callback handler.
4529
INC L 2C
INCrement Register L to advance past the callback address bytes to the next slot.
452A
OR A B7
OR Register A with itself to test if any more drive bits remain to check. If A is zero, all drives have been scanned.
452B
If the NZ FLAG has been set (more drive bits to check), LOOP BACK to 4525H to check the next drive's callback status.
452D
LD A,(3840H) 3A 40 38
Read the keyboard row 6 at memory-mapped address 3840H into Register A. This row contains the BREAK key (among others: ENTER, CLEAR, BREAK, arrow keys, SPACE).
4530
AND 04H E6 04
AND Register A with 04H to isolate Bit 2, the BREAK key. If the BREAK key is pressed, bit 2 is set and the result is non-zero (04H). If not pressed, the result is zero.
4532
If the NZ FLAG has been set (the BREAK key IS pressed), JUMP forward to 454FH to process the BREAK key interrupt.
4534
POP AF F1
Restore Register Pair AF from the stack.
4535
POP HL E1
Restore Register Pair HL from the stack.
4536
EI FB
Re-enable interrupts. Interrupts were disabled when the Z80 entered the ISR via RST 38H.
4537
RET C9
RETURN from the interrupt service routine. Execution resumes at the point where the interrupt occurred.

4538H - Timer Callback Dispatcher

Dispatches a timer callback whose slot was identified by the ISR at 4518H. Saves all registers, reads the 2-byte callback address from the slot table entry pointed to by HL, and calls the handler. After the handler returns, restores all registers and loops back to check for more active callbacks.

4538
PUSH AF F5
Save Register A and Flags onto the stack before dispatching the callback.
4539
PUSH BC C5
Save Register Pair BC onto the stack.
453A
PUSH DE D5
Save Register Pair DE onto the stack.
453B
PUSH HL E5
Save Register Pair HL (pointer into the callback slot table) onto the stack.
453C
PUSH IX DD E5
Save Register Pair IX onto the stack.
453E
LD DE,4547H 11 47 45
Load Register Pair DE with 4547H, the return address for the callback. This address (the cleanup code at 4547H) will be pushed onto the stack so that when the callback handler executes RET, control returns to 4547H.
4541
PUSH DE D5
PUSH the return address (4547H) onto the stack. This sets up the callback return path.
4542
LD E,(HL) 5E
Fetch the low byte of the callback handler address from the slot table entry at (HL) into Register E.
4543
INC L 2C
INCrement Register L to point to the high byte of the callback address.
4544
LD D,(HL) 56
Fetch the high byte of the callback handler address into Register D. DE now holds the complete callback handler address.
4545
EX DE,HL EB
Exchange DE and HL. HL now holds the callback handler address, and DE holds the table pointer.
4546
JP (HL) E9
JUMP to the callback handler address in HL. Since the return address 4547H was pushed onto the stack at 4541H, the handler's RET will return to 4547H.
4547
POP IX DD E1
Restore Register Pair IX from the stack (callback handler has returned).
4549
POP HL E1
Restore Register Pair HL (pointer into callback slot table).
454A
POP DE D1
Restore Register Pair DE.
454B
POP BC C1
Restore Register Pair BC.
454C
POP AF F1
Restore Register A and Flags.
454D
JUMP back to 4529H in the ISR to continue scanning for more active timer callbacks.

454FH - BREAK Key Handler

Processes a BREAK key detection from the interrupt handler. Checks if BREAK handling is enabled by examining 4315H. If disabled (00H), returns normally. If enabled, clears the BREAK enable flag, reads the BREAK handler address from 4316H-4317H, and transfers control to it by replacing the ISR's return address on the stack.

454F
LD HL,4315H 21 15 43
Point Register Pair HL to 4315H, the BREAK key enable flag. If this byte is 00H, BREAK handling is disabled. If non-zero, BREAK handling is active and the handler address at 4316H-4317H is used.
4552
XOR A AF
Set Register A to 00H and clear all flags.
4553
CP (HL) BE
Compare Register A (00H) against the byte at (HL) which is the BREAK enable flag at 4315H. If 4315H contains 00H (BREAK disabled), the Z FLAG is set.
4554
If the Z FLAG has been set (BREAK handling is disabled), JUMP back to 4534H in the ISR to restore registers and return normally. The BREAK keypress is ignored.
4556
LD (HL),A 77
Store 00H (Register A) into 4315H, disabling BREAK handling. This prevents re-entrant BREAK processing — the BREAK handler is a one-shot that must be re-enabled by the application.
4557
INC L 2C
INCrement Register L to advance HL from 4315H to 4316H, which holds the low byte of the BREAK handler address.
4558
LD A,(HL) 7E
Fetch the low byte of the BREAK handler address from 4316H into Register A.
4559
INC L 2C
INCrement Register L to advance to 4317H, which holds the high byte of the BREAK handler address.
455A
LD H,(HL) 66
Fetch the high byte of the BREAK handler address from 4317H into Register H.
455B
LD L,A 6F
Load Register L with the low byte of the handler address (from Register A). HL now contains the complete BREAK handler address.
455C
POP AF F1
Pop the saved AF from the stack (the flags that were pushed at the start of the ISR at 4519H).
455D
EX (SP),HL E3
Replace the return address on the stack. The top of the stack currently holds the saved HL from 4518H (which is the interrupted program's return address). EX (SP),HL swaps it with the BREAK handler address in HL. Now the stack holds the BREAK handler address, and HL holds the original return address (which is discarded).
455E
EI FB
Re-enable interrupts before transferring to the BREAK handler.
455F
RET C9
RETURN — but instead of returning to the interrupted code, this "returns" to the BREAK handler address that was placed on the stack at 455DH. The BREAK handler takes over execution.

4560H - Heartbeat Timer Tick Handler

Called periodically by the timer interrupt to dispatch the four primary timer callback slots (slots 8-11 at offsets 08H-0BH in the timer table) and increment the master tick counter at 4040H. The tick counter is used for motor timeout timing. After dispatching callbacks 8-11, increments 4040H and masks the result with 07H to produce a 3-bit sub-tick counter, which is then used to index into the timer callback table for additional dispatching via the generic callback mechanism at 457BH.

4560
LD A,08H 3E 08
Load Register A with 08H, the callback slot index for the first of four timer callbacks to dispatch. Slot 08H indexes into the timer callback address table at 4500H (slot 8 = offset 10H from 4500H = 4510H).
4562
GOSUB to 457BH, the timer callback dispatcher, with slot index 08H in Register A. This reads the callback address from the table and calls it.
4565
LD A,09H 3E 09
Load Register A with 09H for callback slot 9.
4567
GOSUB to 457BH to dispatch callback slot 9.
456A
LD A,0AH 3E 0A
Load Register A with 0AH for callback slot 10.
456C
GOSUB to 457BH to dispatch callback slot 10.
456F
LD A,0BH 3E 0B
Load Register A with 0BH for callback slot 11.
4571
GOSUB to 457BH to dispatch callback slot 11.
4574
LD HL,4040H 21 40 40
Point Register Pair HL to 4040H, the master tick counter. This byte is incremented on every heartbeat interrupt and wraps modulo 256.
4577
INC (HL) 34
INCrement the master tick counter at 4040H by 1.
4578
LD A,(HL) 7E
Fetch the updated tick counter value into Register A.
4579
AND 07H E6 07
AND Register A with 07H to extract the low 3 bits of the tick counter, producing a value 0-7. This sub-tick counter cycles through all 8 values over 8 heartbeats, used to time-share additional callback dispatching.

Fall through to 457BH with A = sub-tick index (0-7). This dispatches one additional callback per heartbeat on a round-robin basis.

457BH - Timer Callback Table Dispatcher

Generic callback dispatcher. Register A holds a callback slot index (0-11). The slot index is doubled (since each table entry is 2 bytes) and used to index into the timer callback address table at 4500H. The 2-byte address is read from the table, loaded into IX and then into HL, and finally executed via JP (HL). The callback's own code reads its parameters from the table entry pointed to by IX.

457B
RLCA 07
Rotate Register A left (multiply by 2). Each callback table entry is 2 bytes, so the slot index must be doubled to get the byte offset into the table.
457C
LD L,A 6F
Load Register L with the doubled index. This is the low byte of the pointer into the callback table.
457D
LD H,45H 26 45
Load Register H with 45H. Combined with L, HL now points to address 4500H + (slot * 2) — the callback address entry in the timer callback table at 4500H-4517H.
457F
LD (45A6H),HL 22 A6 45
Store the current callback table pointer (HL) into 45A6H for later retrieval. [SELF-MODIFYING CODE] The dequeue routine at 458EH reads this value back to identify which callback slot is being serviced.
4582
LD E,(HL) 5E
Fetch the low byte of the callback handler address from the table entry at (HL) into Register E.
4583
INC L 2C
INCrement Register L to point to the high byte of the callback address.
4584
LD D,(HL) 56
Fetch the high byte of the callback handler address into Register D. DE now holds the callback handler's address.
4585
PUSH DE D5
Push the callback handler address (DE) onto the stack.
4586
POP IX DD E1
Pop the callback handler address into IX. IX now points to the callback handler routine. This allows the handler to reference its own parameters via IX offsets if needed.
4588
EX DE,HL EB
Exchange DE and HL. HL now holds the callback handler address.
4589
LD E,(HL) 5E
Fetch the first byte at the callback handler address into Register E. This reads the handler's own code or data.
458A
INC HL 23
INCrement HL to point to the second byte of the callback handler.
458B
LD D,(HL) 56
Fetch the second byte of the callback handler into Register D. If the handler uses an indirect jump table, DE now holds the actual execution address.
458C
EX DE,HL EB
Exchange DE and HL. HL now holds the address read from the handler's first two bytes.
458D
JP (HL) E9
JUMP to the address in HL — the callback handler's execution entry point. The handler runs and eventually returns to the caller of 457BH (typically the heartbeat handler at 4560H).

458EH - DOS-CALL / Dequeue Callback / Enqueue Callback

Three related entry points for managing the timer callback system. 458EH is the DOS-CALL entry (removes a callback and prepares for DOS command execution). 4593H is the dequeue entry that removes a callback by slot number. 4596H is the enqueue entry that installs a new callback. Register A holds the callback slot number (0-11) and DE holds the callback handler address (for enqueue).

458E
POP DE D1
Dos-Call Entry. Pop the return address from the stack into DE. This removes the caller's return address so that after the DOS command completes, control returns to the DOS Ready prompt rather than the caller. DE holds the discarded return address.
458F
LD A,(45A6H) 3A A6 45
Fetch the current callback slot pointer (low byte) from 45A6H into Register A. This was stored by the dispatcher at 457FH and identifies which callback slot is currently being serviced.
4592
RRCA 0F
Rotate Register A right (divide by 2). This converts the byte offset back to a slot index number since each table entry is 2 bytes.
4593
LD DE,45A3H 11 A3 45
Dequeue Entry. Load Register Pair DE with 45A3H, the address of the default NOP handler. Storing this address in a callback slot effectively removes (dequeues) the callback by replacing it with a do-nothing handler.
4596
CP 0CH FE 0C
Enqueue Entry. Compare the slot index in Register A against 0CH (12 decimal). There are only 12 callback slots (0-11). If A >= 12, the slot number is invalid.
4598
RET NC D0
If the NO CARRY FLAG has been set (slot index >= 12, meaning the slot number is out of range), RETURN immediately without modifying any callback. Invalid slot numbers are silently rejected.
4599
RLCA 07
Rotate Register A left (multiply slot index by 2) to get the byte offset into the callback address table.
459A
LD L,A 6F
Load Register L with the byte offset.
459B
LD H,45H 26 45
Load Register H with 45H. HL now points to the callback table entry at 4500H + (slot * 2).
459D
DI F3
Disable interrupts to prevent the ISR from reading a half-updated callback address during the write.
459E
LD (HL),E 73
Store the low byte of the callback handler address (Register E) into the table entry at (HL).
459F
INC L 2C
INCrement Register L to advance to the high byte position.
45A0
LD (HL),D 72
Store the high byte of the callback handler address (Register D) into the table.
45A1
EI FB
Re-enable interrupts. The callback address is now fully written and consistent.
45A2
RET C9
RETURN to the caller. The callback has been enqueued (or dequeued with the default handler address).

45A3H - Default Callback Handler (NOP)

The default timer callback handler. When no real callback is installed in a slot, the slot points here. This is just a 2-byte data entry (A2H, 45H = address 45A2H in little-endian) that the callback dispatcher at 457BH reads as an indirect address. The pointed-to address 45A2H contains RET, so the callback does nothing.

45A3
DEFB A2H,45H A2 45
Default Callback Handler Pointer
2-byte address entry (little-endian: 45A2H) pointing to the RET instruction at 45A2H. When the callback dispatcher reads this entry, it jumps to 45A2H which immediately returns. This is the "do nothing" callback that all unused timer slots point to.

45A5H - Keep Drives Spinning

Resets the motor-off countdown for the currently selected drive by re-reading the drive's motor timer callback entry and re-installing it. This prevents the timer interrupt from turning off the drive motor while a multi-sector operation is in progress. Called via the DOS service vector at 4416H.

45A5
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H. [SELF-MODIFYING CODE] The immediate operand at 45A6H-45A7H is overwritten by the callback dispatcher at 457FH with the current callback table pointer. At runtime, HL is loaded with the address of the active callback slot entry, not 0000H.
45A8
LD E,(HL) 5E
Fetch the low byte of the callback handler address from the slot table entry at (HL) into Register E.
45A9
INC HL 23
INCrement HL to point to the high byte of the callback address.
45AA
LD D,(HL) 56
Fetch the high byte of the callback handler address into Register D. DE now holds the callback handler address.
45AB
EX DE,HL EB
Exchange DE and HL. HL now points to the callback handler, DE holds the table pointer.
45AC
POP DE D1
Pop the return address from the stack into DE (discarding it — this is part of the keep-spinning chain that redirects execution).
45AD
JUMP back to 459DH to re-install the callback (DI / LD (HL),E / INC L / LD (HL),D / EI / RET). This refreshes the callback entry, resetting the motor timer.

45AFH - Drive Motor Timer Callback

The timer callback handler for drive motor management. This is installed as a callback handler and pointed to by entries in the timer callback table. It uses IX+02H as a prescaler counter: the callback fires on every timer tick, but only performs the actual motor countdown every 2 ticks (the prescaler). On the active tick, it increments the seconds/minutes/hours counters at 4041H-4046H (the system clock) using the calendar limit table at 45EFH. The clock update cascade handles seconds rolling to minutes, minutes to hours, hours to days, and days to months using the day-per-month table.

45AF
DEFB B2H,45H B2 45
Callback Dispatch Pointer
2-byte address entry (little-endian: 45B2H) that the callback dispatcher at 457BH reads and jumps to. Points to the actual handler code at 45B1H+1 = 45B2H.
45B1
DEC B 05
This byte (05H) is actually part of the callback parameter block, not an executable instruction. The callback dispatcher reads past it. The real entry point is 45B2H.
45B2
DEC (IX+02H) DD 35 02
DECrement the prescaler counter at IX+02H. This counter starts at 02H (set at 45B6H) and counts down. When it reaches zero, the actual clock update fires. This divides the timer tick rate by 2.
45B5
RET NZ C0
If the NZ FLAG has been set (the prescaler has not yet reached zero), RETURN without updating the clock. The next tick will decrement it again.
45B6
LD (IX+02H),05H DD 36 02 05
Reset the prescaler counter at IX+02H to 05H. Wait — this is 05H, not 02H. This means the actual prescaler divides by 5, not 2. The clock counter at 4041H is incremented once every 5 timer ticks.
45BA
LD B,03H 06 03
Load Register B with 03H, the number of time fields to cascade through: seconds (4041H), minutes (4042H), hours (4043H). B serves as the loop counter for the time increment cascade.
45BC
LD HL,4041H 21 41 40
Point Register Pair HL to 4041H, the seconds counter. The three time counters are at consecutive addresses: 4041H=seconds, 4042H=minutes, 4043H=hours.
45BF
LD DE,45EFH 11 EF 45
Point Register Pair DE to 45EFH, the calendar limit table. This table holds the maximum values for each time field: 45EFH=60 (seconds max), 45F0H=60 (minutes max), etc.

[LOOP START] The following loop increments the current time field, checks if it has reached its maximum, and if so, resets it to zero and carries to the next field (seconds->minutes->hours).

45C2
INC (HL) 34
INCrement the current time field at (HL) by 1. On the first iteration, this increments the seconds counter at 4041H.
45C3
LD A,(DE) 1A
Fetch the maximum value for this time field from the calendar limit table at (DE) into Register A. For seconds, this is 3CH (60 decimal).
45C4
SUB (HL) 96
SUBtract the current time field value at (HL) from the maximum value in Register A. If the field has not reached its maximum, the result is positive (NZ). If the field equals the maximum, the result is zero (Z).
45C5
RET NZ C0
If the NZ FLAG has been set (the time field has NOT reached its maximum), RETURN. No carry to the next field is needed. For example, if seconds went from 35 to 36, we just return.
45C6
LD (HL),A 77
Store 00H (the result of the subtraction when field = max) back into the time field at (HL). This resets the field to zero (e.g., seconds rolls from 59 back to 0).
45C7
INC L 2C
INCrement Register L to advance HL to the next time field (seconds->minutes, minutes->hours).
45C8
INC E 1C
INCrement Register E to advance DE to the next calendar limit entry.
45C9
DECrement Register B and LOOP BACK to 45C2H if not zero. B started at 3, so this loops through seconds, minutes, and hours. [LOOP END]

If we reach here, all three time fields cascaded (seconds, minutes, and hours all rolled over). This means a full day has passed. Now the code cascades into the date counters: days, months, years.

45CB
INC L 2C
INCrement Register L to skip past the hours field and point to the day-of-month counter at 4044H.
45CC
INC (HL) 34
INCrement the day-of-month counter at 4044H by 1.
45CD
INC L 2C
INCrement Register L to point to the month counter at 4045H.
45CE
LD A,(HL) 7E
Fetch the current month from 4045H into Register A (1-12).
45CF
DEC L 2D
DECrement Register L to point back to the day-of-month counter at 4044H.
45D0
DEC A 3D
DECrement the month value by 1 to convert from 1-based (Jan=1) to 0-based (Jan=0) for use as a table index into the days-per-month table.
45D1
ADD A,E 83
ADD the month index to Register E (which points into the calendar limit table). This calculates the offset to the days-per-month entry for the current month in the table at 45F2H-45FEH.
45D2
LD E,A 5F
Load Register E with the calculated table offset. DE now points to the days-per-month entry for the current month.
45D3
LD A,(DE) 1A
Fetch the maximum day for the current month from the calendar limit table into Register A (e.g., 31 for January, 28 for February).
45D4
CP (HL) BE
Compare the maximum day (Register A) against the current day at (HL). If the current day has not exceeded the maximum, carry is clear (NC).
45D5
RET NC D0
If the NO CARRY FLAG has been set (current day <= maximum days in month), RETURN. The day has not rolled over past the end of the month.
45D6
LD A,(HL) 7E
Fetch the current day-of-month value into Register A.
45D7
CP 1EH FE 1E
Compare the current day against 1EH (30 decimal). This is a validity check — if the day counter has somehow reached 30 or more, it needs to roll over to the next month.
45D9
If the NO CARRY FLAG has been set (day >= 30), JUMP to 45E1H to reset the day and advance to the next month.
45DB
DEC L 2D
DECrement Register L to point to the hours counter at 4043H.
45DC
LD A,(HL) 7E
Fetch the hours value from 4043H into Register A.
45DD
INC L 2C
INCrement Register L back to 4044H (day counter).
45DE
AND 03H E6 03
AND the hours value with 03H. This extracts the low 2 bits for a leap year check related to February. If the result is zero, it might be a leap year adjustment point.
45E0
RET Z C8
If the Z FLAG has been set (the hours AND 03H = 0), RETURN. This is a simplified leap year compensation — on certain cycles, February is given an extra day.
45E1
LD (HL),01H 36 01
Reset the day-of-month counter at 4044H to 01H (the 1st of the month).
45E3
INC L 2C
INCrement Register L to point to the month counter at 4045H.
45E4
INC (HL) 34
INCrement the month counter at 4045H by 1.
45E5
LD A,(HL) 7E
Fetch the updated month value into Register A.
45E6
SUB 0DH D6 0D
SUBtract 0DH (13 decimal) from the month. If the month was incremented to 13 (past December), the result is 0 and the carry flag is clear. If the month is still 1-12, the subtraction produces a negative result (carry set).
45E8
RET C D8
If the CARRY FLAG has been set (month is still 1-12, meaning no year rollover), RETURN. The month was advanced but we are still within the same year.
45E9
LD (HL),01H 36 01
Reset the month counter at 4045H to 01H (January). The year has rolled over.
45EB
INC L 2C
INCrement Register L to point to the year counter at 4046H.
45EC
DEC L 2D
DECrement Register L — wait, this is INC L followed immediately by DEC L, which nets to no change. HL still points to 4045H. This appears to be a bug or leftover code.
45ED
INC (HL) 34
INCrement the year counter. Given the INC/DEC issue above, this actually increments 4045H (month) again instead of 4046H (year). This is a known bug in TRSDOS 2.3 — the year counter at 4046H is never properly incremented on year rollover.
45EE
RET C9
RETURN from the clock update routine.

45EFH - Calendar Limit Table

A data table containing the maximum values for each time and date field, used by the clock cascade routine at 45B2H. The first three bytes are time limits (seconds, minutes, hours). The remaining bytes are the days-per-month table for months 1-12.

45EF
DEFB 3CH
Maximum number of 60 for SECONDS
45F0
DEFB 3CH
Maximum number of 60 for MINUTES
45F1
DEFB 18H
Maximum number of 24 for HOURS
45F2
DEFB 1FH
Maximum number of 31 for JANUARY
45F3
DEFB 1CH
Maximum number of 28 for FEBRUARY
45F4
DEFB 1FH
Maximum number of 31 for MARCH
45F5
DEFB 1EH
Maximum number of 30 for APRIL
45F6
DEFB 1FH
Maximum number of 31 for MAY
45F7
DEFB 1EH
Maximum number of 30 for JUNE
45F8
DEFB 1FH
Maximum number of 31 for JULY
45F9
DEFB 1FH
Maximum number of 31 for AUGUST
45FA
DEFB 1EH
Maximum number of 30 for SEPTEMBER
45FB
DEFB 1FH
Maximum number of 31 for OCTOBER
45FC
DEFB 1EH
Maximum number of 30 for NOVEMBER
45FD
DEFB 1FH
Maximum number of 31 for DECEMBER

4600H - Drive Select with Track Cache

Selects a drive for disk I/O operations by managing the track position cache. Register C holds the drive select latch value (01H=Drive 0, 02H=Drive 1, etc.). The routine checks if the requested drive is already selected (comparing against 4308H). If so, it returns the cached track position. If a different drive is being selected, it saves the current track register value to the old drive's cache entry and loads the new drive's cached track into the FDC Track Register at 37EDH. This avoids unnecessary restore-to-track-0 operations when switching between drives.

4600
PUSH HL E5
Save Register Pair HL onto the stack. HL will be used to index into the track position cache table.
4601
LD HL,4308H 21 08 43
Point Register Pair HL to 4308H, the last-selected drive identifier. This byte stores the drive select latch value for the drive that was most recently selected.
4604
LD A,(HL) 7E
Fetch the last-selected drive value from 4308H into Register A.
4605
CP C B9
Compare the last-selected drive (Register A) against the requested drive (Register C). If they are the same, the Z FLAG is set — no drive switch is needed.
4606
LD A,(HL) 7E
Re-fetch the last-selected drive value into Register A (needed for the cache table indexing below regardless of match result — this is a re-read, not a bug).
4607
If the NZ FLAG has been set (the requested drive is DIFFERENT from the currently selected drive), JUMP forward to 460DH to perform the drive switch, saving and restoring track cache entries.
4609
INC L 2C
INCrement Register L to advance HL from 4308H to 4309H, which holds the cached drive select command byte for the current drive.
460A
LD A,(HL) 7E
Fetch the cached drive select command byte from 4309H into Register A.
460B
JUMP forward to 4625H to activate the drive using the cached command byte, skipping the track save/restore logic since the drive hasn't changed.
460D
LD (HL),C 71
Store the new drive select value (Register C) into 4308H, updating the last-selected drive identifier to reflect the newly requested drive.
460E
LD H,43H 26 43
Load Register H with 43H. Combined with L (which will be set from the drive select value), HL will point into the track cache table in the 43xxH page.
4610
LD L,A 6F
Load Register L with the OLD drive select value (Register A). HL now points to the old drive's track cache entry at 4300H + old_drive_select.
4611
GOSUB to 4398H, the FDC busy-wait loop. Wait for the FDC to finish any pending operation before reading/writing the Track Register.
4614
LD A,(37EDH) 3A ED 37
Fetch the current value of the FDC Track Register at 37EDH into Register A. This is the track number where the old drive's head is currently positioned.
4617
LD (HL),A 77
Store the old drive's current track position into the track cache table at (HL). This preserves the track position so it can be restored when this drive is reselected later.
4618
LD A,C 79
Load Register A with the new drive select value from Register C.
4619
LD L,A 6F
Load Register L with the new drive select value. HL now points to the new drive's track cache entry at 4300H + new_drive_select.
461A
LD A,(HL) 7E
Fetch the new drive's cached track position from the track cache table into Register A.
461B
LD (37EDH),A 32 ED 37
Write the cached track position into the FDC Track Register at 37EDH. This tells the FDC where the new drive's head is positioned, avoiding an unnecessary restore-to-track-0 operation.
461E
LD A,C 79
Load Register A with the new drive select value from Register C again.
461F
GOSUB to 4639H, the drive-number-to-FDC-command builder. Converts the drive select latch value in Register A to the appropriate FDC drive select command byte and stores it in the system variable at 4309H.
4622
LD (4309H),A 32 09 43
Store the computed FDC drive select command byte into 4309H, the current drive command cache.
4625
LD L,A 6F
Load Register L with the drive command byte from Register A. L is used as a temporary holder while the FDC status is checked.
4626
LD A,(37ECH) 3A EC 37
Read the FDC Status Register at 37ECH into Register A to check the current controller state.
4629
RLCA 07
Rotate Register A left. This moves Bit 7 (the Not Ready flag) into the Carry flag for easy testing.
462A
LD A,L 7D
Load Register A with the drive command byte from Register L (restoring it after the status check).
462B
LD (37E1H),A 32 E1 37
Write the drive command byte to the Drive Select Latch at 37E1H. This activates the new drive's motor and selects it for FDC operations.
462E
If the NO CARRY FLAG has been set (the FDC was ready - Not Ready bit was 0), JUMP forward to 4638H to return immediately. The drive is already spinning and ready.
4630
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H to initialize a motor spin-up delay counter. This creates a countdown loop that waits for the drive motor to reach operating speed.
4633
DEC HL 2B
Loop Start
DECrement Register Pair HL by 1.
4634
LD A,H 7C
Load Register A with Register H (high byte of the counter).
4635
OR L B5
OR Register A with Register L. If HL has reached 0000H, the result is zero and the Z FLAG is set.
4636
If the NZ FLAG has been set (HL has not yet reached zero), LOOP BACK to 4633H to continue the delay. This loop counts down from FFFFH (65535) to 0000H, providing approximately 0.5 seconds of delay at 1.774 MHz for the drive motor to spin up. Loop End
4638
POP HL E1
Restore Register Pair HL from the stack (saved at 4600H).
4639
RET C9
RETURN to the caller. The requested drive is now selected with the correct track position loaded into the FDC Track Register.

463AH - Drive Number to FDC Command Byte Builder

Converts a drive select latch value (01H, 02H, 04H, 08H) in Register A to an FDC drive select command byte using the formula: extract the drive number (0-7) from bits 0-2, shift it left 3 positions, OR with C7H. The result encodes the drive number in the FDC command format. Also patches the self-modifying code at 4645H with the computed command byte for the BIT instruction test.

463A
AND 07H E6 07
AND Register A with 07H to isolate the low 3 bits of the drive select value. For standard drives: 01H AND 07H = 01H (Drive 0), 02H AND 07H = 02H (Drive 1), etc.
463C
RLCA 07
Rotate Register A left (multiply by 2). First shift of the 3-bit drive number.
463D
RLCA 07
Rotate Register A left again (multiply by 4 total).
463E
RLCA 07
Rotate Register A left again (multiply by 8 total). The drive number is now shifted into bits 3-5 of Register A, which is the bit position the FDC drive select latch uses.
463F
OR C7H F6 C7
OR Register A with C7H. This sets bits 0-2 (stepping rate = 111 = 15ms, the slowest rate) and bits 6-7 (head load + motor on). The result is the complete FDC drive select command byte.
4641
LD (4645H),A 32 45 46
Store the computed command byte into 4645H. Self-Modifying Code
This overwrites the operand of the SET/BIT instruction at 4645H, allowing subsequent code to test the correct drive bit.
4644
XOR A AF
Set Register A to 00H and clear all flags. This clears A in preparation for the BIT test at 4645H.
4645
SET 0,A CB C7
SET Bit 0 of Register A. Self-Modifying Code
The second byte (C7H) was overwritten at 4641H with the FDC command byte. At runtime, this instruction is actually a different SET or BIT operation depending on the drive number, producing the appropriate drive select bit pattern in Register A.
4647
RET C9
RETURN to the caller. Register A contains the FDC command byte with the drive number encoded.

4648H - Seek to Track and Set Sector

Performs a seek operation to position the drive head at the specified track and loads the sector register. On entry: C = drive select value, D = target track number, E = target sector number. Calls the drive select routine at 4600H to activate the drive and load the track cache, then compares the current track with the target. If they differ, issues a Seek command (1FH) to the FDC to move the head to the target track. Also loads DE into the FDC Sector Register at 37EEH.

4648
GOSUB to 4600H, the drive select with track cache routine. Register C holds the drive select value. On return, the drive is selected, its motor is running, and the FDC Track Register contains the cached track position.
464B
GOSUB to 4669H, a short delay / NOP stub (this address contains three NOP bytes followed by a CALL to 43ACH and a busy-wait). This provides a timing delay between drive select and FDC register access.
464E
LD A,(37EDH) 3A ED 37
Fetch the current FDC Track Register value from 37EDH into Register A. This is where the head is currently positioned (from the track cache).
4651
CP D BA
Compare the current track position (Register A) against the target track (Register D). If they are equal, the Z FLAG is set — no seek is needed.
4652
LD (37EEH),DE ED 53 EE 37
Store Register Pair DE into the FDC Sector Register at 37EEH. E (the target sector) goes to 37EEH, and D (the target track) goes to 37EFH (the FDC Data Register). For a Seek command, the Data Register must contain the target track number.
4656
RET Z C8
If the Z FLAG has been set (current track = target track, no seek needed), RETURN immediately. The head is already on the correct track.
4657
LD A,1FH 3E 1F
Load Register A with 1FH, the FDC Seek command with head load enabled and 15ms stepping rate (the slowest/safest rate). Seek command format: 0001 hVr1r0 where h=1, V=1, r1r0=11.
4659
PUSH AF F5
Save the Seek command byte and flags onto the stack.
465A
GOSUB to 4669H for another timing delay before issuing the FDC command.
465D
POP AF F1
Restore the Seek command byte (1FH) from the stack into Register A.
465E
LD (37ECH),A 32 EC 37
Write the Seek command (1FH) to the FDC Command Register at 37ECH. The FDC begins moving the head to the track specified in the Data Register (37EFH). The Seek command uses the Track Register as the starting position and the Data Register as the destination.
4661
RET C9
RETURN to the caller. The Seek command has been issued. The caller must wait for the FDC to complete the seek (via 4398H) before proceeding.

4662H - Seek with Extended Delay

Calls the seek routine at 4658H (which issues the Seek command) and then performs additional NOP-based timing delays. The PUSH AF / POP AF / NOP / NOP sequence provides a brief additional wait after the seek command is issued.

4662
GOSUB to 4658H to issue the Seek command (which calls 4669H, then writes 1FH to the FDC Command Register).
4665
PUSH AF F5
Save AF onto the stack (timing padding).
4666
POP AF F1
Restore AF from the stack (timing padding — 11 T-states of delay).
4667
NOP 00
No operation (4 T-states of delay).
4668
NOP 00
No operation (4 T-states of delay).

4669H - FDC Timing Delay and Status Check

A short timing delay followed by a call to the drive select/status routine at 43ACH and an FDC busy-wait. The three NOP bytes at 4669H-466BH provide initial timing, then the drive select latch is refreshed and the FDC status is polled until not busy. This is called between FDC operations to ensure proper command timing.

4669
NOP 00
No operation (timing delay).
466A
NOP 00
No operation (timing delay).
466B
NOP 00
No operation (timing delay).
466A
GOSUB to 43ACH, the drive select and status read routine. This writes the current drive select value to 37E1H and reads the FDC Status Register into Register A.
466D
BIT 0,A CB 47
Test Bit 0 (Busy flag) of the FDC Status Register in Register A. If the FDC is still executing a command, the Z FLAG is cleared (NZ).
466F
RET Z C8
If the Z FLAG has been set (FDC is not busy), RETURN. The controller is ready for the next command.
4670
If the FDC is still busy, LOOP BACK to 466AH to re-read the status and check again.

4672H - Disk Read/Write Sector Core

The core disk I/O routine that performs the actual sector read or write operation. On entry, Register A holds the FDC command byte (88H for Read Sector, A8H for Read with verify, A9H for Write Sector). This routine stores the command, sets up the inline parameter block, issues the command to the FDC, and manages the byte-by-byte data transfer between the FDC Data Register (37EFH) and the memory buffer. Uses self-modifying code to store parameters and switch between read and write transfer loops.

4672
LD (4693H),A 32 93 46
Store the FDC command byte (Register A) into 4693H. Self-Modifying Code
This overwrites the operand of the LD (HL),xxH instruction at 4693H, so that when the code reaches that point, it writes the correct FDC command to the Command Register at 37ECH.
4675
EX (SP),HL E3
Exchange the top of the stack with HL. The top of the stack is the return address (the address after the CALL that invoked this routine). HL receives the return address, while the old HL value is pushed. The bytes following the CALL at the caller are an inline parameter block.
4676
PUSH BC C5
Save Register Pair BC onto the stack.
4677
LD B,(HL) 46
Fetch the first parameter byte from the inline block: the retry count. This is read from the byte immediately after the CALL instruction in the caller's code.
4678
INC HL 23
INCrement HL to point to the second parameter byte.
4679
LD A,(HL) 7E
Fetch the second parameter byte: the DRQ polling opcode. This byte is either 0FH (RRCA — used for read operations to check bit 1 via carry) or another value for write operations.
467A
INC HL 23
INCrement HL to point to the third parameter byte.
467B
LD (46D3H),A 32 D3 46
Store the DRQ polling opcode into 46D3H. Self-Modifying Code
This patches the instruction at 46D3H in the data transfer loop to use the correct polling method for reads vs writes.
467E
LD A,(HL) 7E
Fetch the third parameter byte: the low byte of the data transfer handler address.
467F
INC HL 23
INCrement HL to point to the fourth parameter byte.
4680
LD H,(HL) 66
Fetch the fourth parameter byte: the high byte of the data transfer handler address into Register H.
4681
LD L,A 6F
Load Register L with the low byte of the handler address. HL now holds the data transfer loop address (either the read loop at 46A7H or the write loop address).
4682
LD (46A7H),HL 22 A7 46
Store the data transfer loop address into 46A7H. Self-Modifying Code
This patches the operand of the LD instruction or JP instruction at 46A7H to point to the correct data transfer loop for reads or writes.
4685
POP HL E1
Restore the original HL value from the stack (this was the caller's HL, pushed when EX (SP),HL was executed at 4675H).
4686
EX (SP),HL E3
Exchange again: push the original HL back and pull the adjusted return address (now pointing past the inline parameter block) off the stack. This fixes up the return address so that when this routine returns, it resumes after the parameter block in the caller.
4687
Loop Start
GOSUB to 4647H to activate the current drive (refreshes the drive select latch and returns the command byte in A). This is the start of the retry loop.
468A
GOSUB to 4669H for timing delay and FDC busy-wait. Ensures the FDC is ready before issuing the read/write command.
468D
PUSH BC C5
Save Register Pair BC (B = retry count, C = drive info) onto the stack.
468E
PUSH DE D5
Save Register Pair DE (D = track, E = sector) onto the stack.
468F
PUSH HL E5
Save Register Pair HL (pointer to the memory buffer for data transfer) onto the stack.
4690
LD HL,37ECH 21 EC 37
Point Register Pair HL to 37ECH, the FDC Command Register. This address will be used to write the FDC command.
4693
LD (HL),00H 36 00
Write the FDC command byte to the Command Register at 37ECH. Self-Modifying Code
The operand 00H was overwritten at 4672H with the actual FDC command (88H for Read Sector, A9H for Write Sector, etc.). This issues the disk operation command to the WD1771 controller.
4695
LD DE,37EFH 11 EF 37
Load Register Pair DE with 37EFH, the FDC Data Register address. Data bytes are transferred to/from this address during the sector read or write operation.
4698
POP BC C1
Restore the buffer address from the stack into BC (it was pushed as HL at 468FH, but popped into BC here). BC now points to the memory buffer.
4699
PUSH BC C5
Re-save the buffer address (now in BC) onto the stack for potential retry use.
469A
POP BC C1
Immediately pop it back — this POP/PUSH/POP sequence is a timing delay (36 T-states total) to allow the FDC command to start processing.
469B
PUSH BC C5
Push the buffer address onto the stack again (preserved for retry).
469C
DI F3
Disable interrupts. The FDC data transfer requires precise timing — the Z80 must service DRQ within 48 microseconds for single-density operation. Interrupts cannot be allowed during the transfer.
469D
JUMP forward to 46A2H to enter the DRQ polling loop.
469F
RRCA 0F
Rotate Register A right through carry. This shifts Bit 1 (DRQ) into the Carry flag for testing. Self-Modifying Code
The opcode 0FH at this location may be patched by the parameter setup at 467BH depending on the operation type.
46A0
If the NO CARRY FLAG has been set (DRQ is not asserted — the FDC does not have a byte ready/waiting), JUMP forward to 46AEH to check if the operation is complete.
46A2
LD A,(HL) 7E
Read the FDC Status Register at 37ECH (HL points to 37ECH) into Register A. This polls for the DRQ (Data Request) bit indicating a byte is ready for transfer.
46A3
BIT 1,A CB 4F
Test Bit 1 (DRQ - Data Request) of the FDC Status Register. If DRQ is set (1), the FDC has a byte ready to transfer. If DRQ is clear (0), no data is available yet.
46A5
If the Z FLAG has been set (DRQ is NOT set — no byte ready), LOOP BACK to 469FH to continue polling. The loop checks for both DRQ and command completion.

At this point, DRQ is asserted — the FDC has a byte ready. The following instructions perform the actual data transfer. The code at 46A7H is self-modifying: for reads, it reads from (DE) = 37EFH and stores to the buffer; for writes, it reads from the buffer and writes to (DE) = 37EFH.

46A7
DI F3
Self-Modifying Code
The bytes at 46A7H-46AAH were patched at 4682H with the data transfer loop code. For a READ operation, the patched code would be: LD A,(DE) / LD (BC),A / INC BC — reading a byte from the FDC Data Register (DE=37EFH) and storing it in the memory buffer (BC). For a WRITE operation: LD A,(BC) / LD (DE),A / INC BC — reading from the buffer and sending to the FDC. The bytes shown (F3 00 00 03) are the initial values before patching.
46AB
JUMP back to 46A2H to poll for the next DRQ and transfer the next byte. This loop continues until all 256 bytes of the sector have been transferred.
46AE
EI FB
Re-enable interrupts. The data transfer is complete or the command has finished.
46AF
LD A,(HL) 7E
Read the final FDC Status Register value from 37ECH into Register A. This captures the completion status of the read/write operation.
46B0
AND 7CH E6 7C
AND Register A with 7CH (binary 01111100) to isolate the error bits: Bit 6 (Write Protect), Bit 5 (Record Type), Bit 4 (Record Not Found), Bit 3 (CRC Error), Bit 2 (Lost Data). Bits 0 (Busy) and 1 (DRQ) are masked out as they are not error conditions. Bit 7 (Not Ready) is also masked.
46B2
LD (HL),D0H 36 D0
Write D0H (Force Interrupt command) to the FDC Command Register at 37ECH. This immediately terminates any in-progress FDC operation and resets the controller to an idle state. D0H = Force Interrupt with no conditions (just stop).
46B4
POP HL E1
Restore the buffer address from the stack into HL.
46B5
POP DE D1
Restore Register Pair DE (D = track, E = sector) from the stack.
46B6
POP BC C1
Restore Register Pair BC (B = retry count, C = drive info) from the stack.
46B7
If the Z FLAG has been set (AND 7CH produced zero, meaning NO error bits are set), JUMP forward to 46DCH to return success. The read/write completed without errors.

Error Path
If we reach here, one or more error bits were set in the FDC status. The code now determines what kind of error occurred and whether to retry.

46B9
BIT 2,A CB 57
Test Bit 2 (Lost Data) of the error status. Lost Data means the Z80 did not read/write the FDC Data Register fast enough — a timing error that is often transient.
46BB
If the NZ FLAG has been set (Lost Data error detected), JUMP to 46D0H to attempt a retry.
46BD
CP 20H FE 20
Compare the error status against 20H (Bit 5 = Record Type / Deleted Data). This checks if the only "error" is a deleted data address mark, which is not a true error but an informational flag.
46BF
If the Z FLAG has been set (the only flag is Deleted Data mark), JUMP to 46D2H. This is treated as a non-fatal status — the sector was read successfully but contained a deleted data mark.
46C1
PUSH AF F5
Save the error status onto the stack before performing the restore (reseek) operation.
46C2
LD A,FFH 3E FF
Load Register A with FFH. This is written to 4308H to invalidate the drive cache, forcing a full reselect on the next attempt.
46C4
LD (4308H),A 32 08 43
Store FFH into 4308H, the last-selected drive identifier. Setting it to FFH ensures that the next call to 4600H treats the drive as unselected and performs a full track cache reload.
46C7
GOSUB to 4600H to reselect the drive. Since 4308H was set to FFH, this performs a full drive switch with track cache reload.
46CA
LD A,0BH 3E 0B
Load Register A with 0BH, the FDC Restore command with head load and 15ms stepping. Restore command: 0000 1011 = head load on, verify off, rate=11 (15ms).
46CC
GOSUB to 4658H to issue the Restore command. This moves the head back to Track 0, then re-seeks to the target track — a full recalibration in case the head position was lost.
46CF
POP AF F1
Restore the error status from the stack into Register A.
46D0
DECrement Register B (retry counter) by 1. If not zero (retries remain), LOOP BACK to 4687H to attempt the read/write operation again. The total number of retries was set by the inline parameter at the caller.
46D2
LD B,A 47
Copy the error/status byte from Register A to Register B for further analysis.
46D3
LD A,00H 3E 00
Self-Modifying Code
The operand 00H was patched at 467BH. For sector read, this becomes the read-loop entry setup; for sector write, it is different. After the retry loop exhausts, this loads a result code.
46D5
RRC B CB 08
Rotate Register B right through carry. This shifts the error status bits to convert the FDC error bits into an error code index.
46D7
If the CARRY FLAG has been set (the low bit of the error status was 1), JUMP to 46DCH. This selects one error code path.
46D9
INC A 3C
INCrement Register A by 1, advancing the error code index.
46DA
JUMP back to 46D5H to shift the next error bit and check again. This loop converts the bit position of the highest-priority error into a numeric error code.
46DC
POP BC C1
Restore Register Pair BC (saved at 4676H).
46DD
RET C9
RETURN to the caller. Register A contains 00H for success, or a non-zero error code identifying the type of disk error encountered.

46DEH - Read Sector Entry Point

Entry point for reading a single disk sector. Loads the FDC Read Sector command (88H) into Register A and calls the disk I/O core at 4672H with an inline parameter block specifying the retry count, DRQ polling method, and data transfer loop address for read operations. On entry: C = drive select value, D = track, E = sector, HL = buffer address.

46DE
LD A,88H 3E 88
Load Register A with 88H, the FDC Read Sector command. Read Sector: 10001000 — bit 7-5=100 (Read), bit 4=m=1 (IBM format), bit 3=b=0 (non-IBM sector length), bit 2=E=0 (no extra delay), bits 1-0=00 (unused).
46E0
GOSUB to 4672H, the disk I/O core, with the Read Sector command in Register A. The bytes following this CALL are an inline parameter block that 4672H reads via EX (SP),HL.

The following bytes at 46E3H-46E8H are the inline parameter block consumed by the disk I/O core at 4672H. They are NOT executable instructions.

46E3
DEFB 0AH 0A
Retry Count
0AH (10 decimal): attempt the read operation up to 10 times before reporting an error.
46E4
DEFB 01H,1AH,02H 01 1A 02
Drq Polling Opcode and Transfer Loop Address
01H = DRQ polling instruction opcode. 1A02H (little-endian: 021AH) is not used as shown — the actual parameter encoding provides the data transfer loop address for read mode.

46E7H - Read Sector with Verify Entry Point

Entry point for reading a sector with post-read verification. Uses FDC command A8H (Read Sector with side compare). Similar to 46DEH but with different FDC command flags.

46E7
LD A,A8H 3E A8
Load Register A with A8H, the FDC Read Sector command with verify flags. 10101000 — Read with additional flags set for verification mode.
46E9
GOSUB to 4672H, the disk I/O core, with the Read-with-verify command in A.
46EC
DEFB 05H 05
Retry Count
05H (5): attempt the operation up to 5 times.
46ED
DEFB 09H,0AH,12H 09 0A 12
Drq Polling and Transfer Parameters
Inline parameter block for the verify-read operation.

46F0H - Write Sector Entry Point

Entry point for writing a single disk sector. Loads the FDC Write Sector command (A9H) into Register A and calls the disk I/O core with write-specific inline parameters. On entry: C = drive select value, D = track, E = sector, HL = buffer address.

46F0
LD A,A9H 3E A9
Load Register A with A9H, the FDC Write Sector command. Write Sector: 10101001 — bit 7-5=101 (Write), bit 4=m=0 (single record), bit 3=b=1 (IBM format), bit 2=E=0 (no extra delay), bits 1-0=01 (normal data address mark).
46F2
JUMP back to 46E9H to call the disk I/O core (4672H) using the verify-read parameter block. This reuses the same retry count and parameters as the verify-read entry, but with the Write Sector command in A instead of A8H.

46F4H - Read Sector (Alternate Entry)

Another Read Sector entry point that uses the same 88H command but calls the I/O core at a slightly different point (4672H directly).

46F4
LD A,88H 3E 88
Load Register A with 88H, the FDC Read Sector command (same as 46DEH).
Command Code: 88H (10001000)Read Sector
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Function
100mbE00Command=Read Sector
Bit 7-5: Read Command (100)
m: 1=Multiple Records, 0=Single Record
b: 1=IBM format, 0=Non-IBM Format
E: 1=Enable HLD, HLT, and 10ms Delay, 0=Assume Head Already Engaged, no Delay
Remainder: Unused (00)
46F6
GOSUB to 4672H with the Read Sector command.
46F9
DEFB 05H 05
Retry Count
05H (5 retries). The inline parameters continue at 46FAH but the listing shows the remaining bytes there.

46FAH - Disk I/O Inline Parameters and File Operation Core

The bytes at 46FAH-46FFH complete the inline parameter block for the alternate read entry at 46F4H, followed by the start of the file operation routines. At 4700H begins the core file positioning system that manages record-level I/O through the File Control Block (FCB) structure pointed to by IX.

46FA
DEFB 01H,1AH 01 1A
Inline Parameters
Continuation of the parameter block for the read entry at 46F4H. These bytes are consumed by the disk I/O core at 4672H.
46FC-46FF
DEFB CDH,92H,48H CD 92 48
Inline Continuation
The remaining parameter bytes. The sequence CD 92 48 also happens to be CALL 4892H when interpreted as code — and indeed this is where execution resumes after the inline parameters are consumed. This dual-purpose encoding is a space-saving technique.

4700H - Position FCB to Specified Record

Positions the file to a specified logical record number. On entry: IX points to the FCB, BC holds the target record number. The routine validates the FCB, checks if the file supports random positioning (bit 7 of IX+01H), calculates the target sector and byte offset from the record number, and calls the sector-level positioning routines to seek to the correct location. Called via the DOS service vector at 4443H.

4700
GOSUB to 4892H, the FCB validation routine. This checks that the FCB pointed to by IX is open and valid. If bit 7 of the first byte (IX+00H) is set, the FCB is in an error state and the routine exits via a different path. On success, DE points to the directory entry and A/flags reflect the FCB state.
4703
LD L,(IX+0AH) DD 6E 0A
Fetch the low byte of the current sector count from FCB offset +0AH into Register L. IX+0AH/+0BH hold the current sector position within the file (the logical sector number, 0-based).
4706
LD H,(IX+0BH) DD 66 0B
Fetch the high byte of the current sector count from FCB offset +0BH into Register H. HL now holds the current sector position (16-bit value).
4709
BIT 7,(IX+01H) DD CB 01 7E
Test Bit 7 of FCB offset +01H (the extended flags byte). Bit 7 indicates whether the file uses a fixed record length (set) or is a sequential byte-stream file (clear). If set, the record number in BC must be converted using the record size at IX+09H.
470D
If the Z FLAG has been set (Bit 7 is clear — sequential file, no fixed record length), JUMP to 4717H to store the position directly without record-size conversion. BC is used as-is for the sector position.
470F
LD H,B 60
Load Register H with Register B (high byte of the target record number).
4710
LD L,C 69
Load Register L with Register C (low byte of the target record number). HL now holds the target record number.
4711
LD A,(IX+09H) DD 7E 09
Fetch the record length from FCB offset +09H into Register A. This is the number of bytes per logical record (e.g., 80H for 128-byte records, FFH for 255-byte records).
4714
GOSUB to 4B6AH, the 16-bit multiply routine. Multiplies HL (record number) by A (record length). The result in HL is the byte offset within the file. This converts from record-based to byte-based positioning.
4717
LD (IX+05H),A DD 77 05
Store Register A into FCB offset +05H, the byte offset within the current sector. After the multiply, A holds the remainder (byte position within the sector).
471A
PUSH BC C5
Save Register Pair BC onto the stack.
471B
GOSUB to 4878H, the FCB position validation routine. Checks that the new position is within the file's boundaries. Returns Z=success, NZ=error.
471E
POP BC C1
Restore Register Pair BC from the stack.
471F
RET NZ C0
If the NZ FLAG has been set (position is outside file boundaries), RETURN with the error code in Register A.
4720
LD (IX+0AH),C DD 71 0A
Store Register C (low byte of sector position) into FCB offset +0AH, updating the current sector position low byte.
4723
LD (IX+0BH),B DD 70 0B
Store Register B (high byte of sector position) into FCB offset +0BH, updating the current sector position high byte.
4726
SET 5,(IX+01H) DD CB 01 EE
SET Bit 5 of FCB offset +01H. This flag indicates that the sector position has been changed and the sector buffer may need to be refreshed from disk before the next read/write operation.
472A
GOSUB to 48B9H, the EOF boundary check routine. Compares the new position against the file's EOF marker to determine if we are within or past the end of file.
472D
RET NZ C0
If the NZ FLAG has been set (position is valid, not at or past EOF), RETURN successfully.
472E
GOSUB to 48DCH, the granule/directory traversal routine. Walks the granule chain in the directory to locate the disk sector corresponding to the current file position.
4731
RET NZ C0
If the NZ FLAG has been set (error during granule traversal — corrupt directory or bad granule chain), RETURN with error code in A.
4732
GOSUB to 4647H to activate the drive (refreshes drive select and returns command byte). This ensures the correct drive is selected for the upcoming sector access.
4735
XOR A AF
Set Register A to 00H (success code) and clear all flags.
4736
RET C9
RETURN to the caller with A = 00H indicating successful positioning.

4737H - Position FCB to End of File (EOF)

Positions the FCB to the end of the file for appending data. Reads the current sector count from the FCB and optionally adjusts based on the EOF byte offset at IX+08H. Called via the DOS service vector at 4446H.

4737
GOSUB to 4892H, the FCB validation routine.
473A
LD C,(IX+0AH) DD 4E 0A
Fetch the low byte of the current sector count from FCB offset +0AH into Register C.
473D
LD B,(IX+0BH) DD 46 0B
Fetch the high byte of the current sector count from FCB offset +0BH into Register B. BC now holds the total number of sectors in the file.
4740
BIT 7,(IX+01H) DD CB 01 7E
Test Bit 7 of FCB offset +01H (fixed record length flag).
4744
If the Z FLAG has been set (sequential file, no fixed records), JUMP to 4753H to decrement BC and position to the last sector.
4746
LD A,(IX+09H) DD 7E 09
Fetch the record length from FCB offset +09H into Register A.
4749
OR A B7
OR Register A with itself to test if the record length is zero.
474A
If the Z FLAG has been set (record length is 0 — treating as byte-level access), JUMP to 4753H.
474C
NEG ED 44
NEGate Register A (A = 0 - A = 256 - record_length). This computes the complement, which is used to calculate how many bytes remain unused in the last sector.
474E
ADD A,(IX+05H) DD 86 05
ADD the current byte offset within the sector (IX+05H) to the negated record length. If this overflows (carry set), the last sector has room for another record.
4751
If the CARRY FLAG has been set (there is room for another record in the current sector), JUMP back to 4717H to store the position and proceed.
4753
DEC BC 0B
DECrement Register Pair BC by 1. This adjusts the sector count to point to the last valid sector (0-based index).
4754
JUMP back to 4717H to store the adjusted position into the FCB and validate it.

4756H - Rewind File (Position to Start)

Positions the FCB to the very beginning of the file (sector 0, byte offset 0). Called via the DOS service vector at 4440H.

4756
GOSUB to 4892H, the FCB validation routine.
4759
LD BC,0000H 01 00 00
Load Register Pair BC with 0000H — sector position zero (the beginning of the file).
475C
XOR A AF
Set Register A to 00H — byte offset zero within the sector.
475D
JUMP back to 4717H to store position (BC=0000H, A=00H) into the FCB, establishing the file pointer at the very start.

475FH - Position to Last Sector (EOF via Directory)

Positions the FCB to the file's last sector by reading the sector count from the FCB directory entry fields at IX+0CH/+0DH and adjusting with the EOF byte check via 43D1H.

475F
GOSUB to 4892H, the FCB validation routine.
4762
LD C,(IX+0CH) DD 4E 0C
Fetch the low byte of the file's total sector count from FCB offset +0CH into Register C. IX+0CH/+0DH hold the total number of sectors allocated to the file (from the directory entry).
4765
LD B,(IX+0DH) DD 46 0D
Fetch the high byte of the total sector count from FCB offset +0DH into Register B.
4768
GOSUB to 43D1H, the EOF byte adjustment routine. If IX+08H (EOF byte offset) is non-zero, decrements BC by 1 to account for the partial last sector.
476B
JUMP back to 4717H to store the adjusted sector position into the FCB.

476DH - Read Record from File

Reads a logical record from the file into the caller's buffer. On entry: IX points to the FCB, HL points to the destination buffer. The routine validates the FCB, checks the record length mode, and transfers data byte by byte from the file. For fixed-record files (bit 7 of IX+01H set), reads IX+09H bytes. For byte-stream files, reads one sector at a time. Calls the low-level byte-read routine at 47BBH for each byte. Called via the DOS service vector at 4437H.

476D
GOSUB to 4892H, the FCB validation routine. Verifies the FCB is open and in a valid state.
4770
GOSUB to 4878H, the FCB position validation routine. Checks whether a sector needs to be loaded from disk into the buffer before reading can proceed.
4773
RET NZ C0
If the NZ FLAG has been set (error during position validation — e.g., past EOF or disk error), RETURN with the error code in Register A.
4774
BIT 7,(IX+01H) DD CB 01 7E
Test Bit 7 of FCB offset +01H (fixed record length flag). If set, the file uses fixed-length records and IX+09H specifies the record size.
4778
If the Z FLAG has been set (sequential file, no fixed record length), JUMP to 480EH to handle byte-stream reading (read next sector into buffer).
477B
LD B,(IX+09H) DD 46 09
Fetch the record length from FCB offset +09H into Register B. B serves as the byte counter for the read loop.
477E
PUSH HL E5
Loop Start
Save Register Pair HL (destination buffer pointer) onto the stack before reading a byte.
477F
PUSH BC C5
Save Register Pair BC (B=remaining byte count) onto the stack.
4780
GOSUB to 47BBH, the read-one-byte routine. This reads the next byte from the file's sector buffer and advances the file position. Returns the byte in Register A.
4783
POP BC C1
Restore Register Pair BC (byte counter).
4784
POP HL E1
Restore Register Pair HL (destination buffer pointer).
4785
RET NZ C0
If the NZ FLAG has been set (error during byte read — disk error or unexpected EOF), RETURN with error code in A.
4786
LD (HL),A 77
Store the byte just read (Register A) into the destination buffer at (HL).
4787
INC HL 23
INCrement HL to advance the destination buffer pointer to the next byte.
4788
DECrement Register B (remaining byte count) and LOOP BACK to 477EH if not zero. Continue reading bytes until the full record has been transferred. Loop End
478A
RET C9
RETURN to the caller. A complete record of IX+09H bytes has been read into the buffer starting at the original HL.

478BH - Write Record to File

Writes a logical record from the caller's buffer to the file. On entry: IX points to the FCB, HL points to the source buffer, A = 00H for normal write. For fixed-record files, writes IX+09H bytes; for byte-stream files, writes one sector. Stores the verify flag at 485EH (self-modifying code) to control whether a read-after-write verification is performed. Called via the DOS service vector at 443AH.

478B
GOSUB to 4892H, the FCB validation routine.
478E
LD (485EH),A 32 5E 48
Store Register A (the verify flag: 00H=no verify, non-zero=verify) into 485EH. Self-Modifying Code
This patches the operand at 485EH used by the write-completion code at 485DH to determine whether to perform read-after-write verification.
4791
BIT 7,(IX+01H) DD CB 01 7E
Test Bit 7 of FCB offset +01H (fixed record length flag).
4795
If the Z FLAG has been set (sequential file), JUMP to 483EH to handle sector-level write operations.
4798
LD B,(IX+09H) DD 46 09
Fetch the record length from FCB offset +09H into Register B (byte counter).
479B
LD A,(HL) 7E
Loop Start
Fetch the next byte to write from the source buffer at (HL) into Register A.
479C
INC HL 23
INCrement HL to advance the source buffer pointer.
479D
PUSH HL E5
Save the source buffer pointer onto the stack.
479E
PUSH BC C5
Save the byte counter (BC) onto the stack.
479F
GOSUB to 47DAH, the write-one-byte routine. Writes the byte in Register A to the file's sector buffer and advances the file position. If the sector buffer is full, flushes it to disk.
47A2
POP BC C1
Restore the byte counter.
47A3
POP HL E1
Restore the source buffer pointer.
47A4
RET NZ C0
If the NZ FLAG has been set (write error), RETURN with the error code in A.
47A5
DECrement B and LOOP BACK to 479BH if not zero. Continue writing until the full record is written. Loop End
47A7
RET C9
RETURN to the caller. The complete record has been written.

47A8H - Write Record with Verify

Same as Write Record at 478BH but forces read-after-write verification by setting A to a non-zero value before calling the write handler. Called via the DOS service vector at 443DH.

47A8
GOSUB to 4892H, the FCB validation routine.
47AB
INC A 3C
INCrement Register A by 1. Since A was cleared by the XOR A in 4892H's success path, this sets A to 01H — a non-zero verify flag.
47AC
JUMP back to 478EH to store the non-zero verify flag and proceed with the write operation. The non-zero value ensures read-after-write verification will be performed.

47AEH - Open File for Write (Initialize FCB)

Initializes the FCB for write access by setting the random-access flag (bit 7 of IX+01H), validating the caller's access level, and preparing the initial file position. Register B holds the caller's function code and C holds the access flags. This is reached from the I/O dispatcher at 44C8H when the dispatch type indicates a file-write context.

47AE
GOSUB to 4892H, the FCB validation routine.
47B1
SET 7,(IX+01H) DD CB 01 FE
SET Bit 7 of FCB offset +01H, enabling the fixed record length mode for this file. This indicates the file will be accessed using record-based I/O rather than byte-stream I/O.
47B5
LD A,B 78
Load Register A with the function code from Register B.
47B6
CP 02H FE 02
Compare the function code against 02H. If the function code is 02H or greater, the caller is requesting write access.
47B8
LD A,C 79
Load Register A with the access flags from Register C. C contains the record length for the file operations.
47B9
If the NO CARRY FLAG has been set (function code >= 02H, write access requested), JUMP to 47DAH to begin the write-one-byte handler. The record length in A is passed as the initial data byte.

47BBH - Read One Byte from File

Reads a single byte from the file at the current position. Checks the EOF boundary, loads the sector from disk if needed (bit 5 of IX+01H indicates the buffer needs refreshing), calculates the byte's address within the sector buffer, reads it, and advances the position by one byte. Returns the byte in Register A with Z flag set on success.

47BB
GOSUB to 48B9H, the EOF boundary check. Returns NZ if within file, Z with error 1CH if at/past EOF.
47BE
RET NZ C0
If the NZ FLAG has been set (error — at or past EOF), RETURN with error code in A.
47BF
BIT 5,(IX+01H) DD CB 01 6E
Test Bit 5 of FCB offset +01H. Bit 5 is the sector buffer needs refresh flag — when set, the current sector must be loaded from disk before reading.
47C3
If the Z FLAG has been set (bit 5 is clear — buffer is already current), JUMP to 47C9H to skip the disk read and go directly to the buffer access.
47C5
GOSUB to 480EH, the load sector into buffer routine. Reads the current file sector from disk into the FCB's sector buffer.
47C8
RET NZ C0
If the NZ FLAG has been set (disk read error), RETURN with error code in A.
47C9
GOSUB to 4883H, the buffer address calculator. Computes the actual memory address within the FCB's sector buffer where the current byte lives. Returns DE pointing to the byte and C containing the byte offset.
47CC
XOR A AF
Set Register A to 00H (pre-clear in case the load below replaces it).
47CD
LD A,(DE) 1A
Fetch the byte at (DE) — the current file byte from the sector buffer — into Register A. This is the byte being read.
47CE
PUSH AF F5
Save the data byte (AF) onto the stack. The flags from the XOR A are preserved to ensure Z flag is set for success.
47CF
INC (IX+05H) DD 34 05
INCrement the byte offset within the current sector at FCB offset +05H by 1. This advances the file position by one byte.
47D2
If the NZ FLAG has been set (the byte offset did NOT wrap from FFH to 00H), JUMP to 47D8H to return. The position is still within the same sector.
47D4
SET 5,(IX+01H) DD CB 01 EE
SET Bit 5 of FCB offset +01H. The byte offset wrapped from FFH to 00H, meaning we have crossed a sector boundary. Setting this flag tells the next read/write to load the next sector from disk.
47D8
POP AF F1
Restore the data byte and flags from the stack. A = the byte that was read, Z flag is set (success).
47D9
RET C9
RETURN to the caller. Register A contains the byte read from the file. Z flag is set indicating success.

47DAH - Write One Byte to File

Writes a single byte to the file at the current position. Checks if the sector buffer needs to be flushed (bit 5 of IX+01H), writes the byte to the buffer, sets the dirty flag, and advances the position. If the sector boundary is crossed, triggers the EOF check and sector flush sequence.

47DA
PUSH AF F5
Save Register A (the byte to write) and Flags onto the stack.
47DB
BIT 5,(IX+01H) DD CB 01 6E
Test Bit 5 of FCB offset +01H (sector buffer needs refresh flag).
47DF
If the Z FLAG has been set (buffer is current, no flush needed), JUMP to 47E9H to write the byte directly.
47E1
GOSUB to 482DH, the flush sector buffer routine. Writes the current dirty buffer to disk and loads the next sector. Clears bit 5 of IX+01H.
47E4
If the Z FLAG has been set (flush succeeded), JUMP to 47E9H to proceed with the write.
47E6
EX (SP),HL E3
Exchange the top of the stack (the saved AF with the byte to write) with HL. This discards the byte and prepares to return with an error.
47E7
POP HL E1
Pop the error state from the stack. The write byte is discarded.
47E8
RET C9
RETURN with the error code from the flush operation in A (NZ).
47E9
GOSUB to 4883H, the buffer address calculator. Returns DE pointing to the byte position within the sector buffer.
47EC
POP AF F1
Restore the byte to write from the stack into Register A.
47ED
LD (DE),A 12
Store the byte (Register A) into the sector buffer at (DE). The data is now in the buffer awaiting flush to disk.
47EE
INC DE 13
INCrement DE (not functionally necessary since this is a single-byte write, but part of the standard pattern).
47EF
SET 4,(IX+01H) DD CB 01 E6
SET Bit 4 of FCB offset +01H, the buffer dirty flag. This marks the sector buffer as modified, indicating it must be written to disk before it can be discarded or overwritten.
47F3
INC (IX+05H) DD 34 05
INCrement the byte offset within the current sector (IX+05H) by 1.
47F6
GOSUB to 48B9H, the EOF boundary check. Determines if the new position has reached or passed the end of the allocated file space.
47F9
If the NZ FLAG has been set (not at EOF — position is within the file), JUMP to 4801H to update the EOF byte offset and return.
47FB
BIT 6,(IX+01H) DD CB 01 76
Test Bit 6 of FCB offset +01H. Bit 6 indicates whether the file is open for extend (can allocate new sectors beyond the current allocation).
47FF
If the NZ FLAG has been set (file CAN be extended), JUMP to 4804H to check if we need to allocate a new sector.
4801
LD (IX+08H),C DD 71 08
Store Register C (the current byte offset, returned by the buffer calculator at 4883H) into FCB offset +08H, updating the EOF byte offset. This tracks where the last valid byte is within the last sector.
4804
LD A,C 79
Load Register A with the byte offset (Register C).
4805
OR A B7
OR Register A with itself. If the byte offset is 00H (we've crossed a sector boundary), the Z FLAG is set.
4806
If the NZ FLAG has been set (byte offset is non-zero, still within the current sector), JUMP to 482BH to return success.
4808
SET 5,(IX+01H) DD CB 01 EE
SET Bit 5 of FCB offset +01H (sector buffer needs refresh). The byte offset wrapped to 00H, meaning we've crossed a sector boundary and the next read/write needs a new sector loaded.
480C
JUMP to 483EH to handle the sector-write (flush the current dirty buffer to disk).

480EH - Load Sector into FCB Buffer / Access Check

Validates the file access mode and loads the current sector from disk into the FCB's sector buffer. Checks the access mode bits at IX+01H to ensure the file is open for the requested type of access (read requires mode < 06H, write requires mode < 05H). Returns error 25H (access denied) if the access mode is insufficient.

480E
LD A,(IX+01H) DD 7E 01
Fetch the FCB access mode flags from offset +01H into Register A.
4811
AND 07H E6 07
AND with 07H to isolate the low 3 bits (the access mode: read=01, write=02, read/write=03, etc.).
4813
CP 06H FE 06
Compare the access mode against 06H. Modes 06H and 07H are restricted/system modes.
4815
If the CARRY FLAG has been set (access mode < 06H, meaning normal read or write access), JUMP to 481BH to proceed with loading the sector.
4817
LD A,25H 3E 25
Load Register A with 25H, the Access Denied error code.
4819
OR A B7
OR A with itself to set the NZ flag (ensuring the error return path is taken by the caller's RET NZ).
481A
RET C9
RETURN with A = 25H (Access Denied) and NZ flag set.
481B
GOSUB to 48B9H, the EOF boundary check.
481E
RET NZ C0
If NZ (error), RETURN.
481F
GOSUB to 482DH to flush the current buffer (if dirty) and read the next sector.
4822
RET NZ C0
If NZ (disk error), RETURN.
4823
INC (IX+0AH) DD 34 0A
INCrement the low byte of the sector counter at FCB offset +0AH. This advances the file position to the next sector.
4826
If NZ (no carry from low byte), JUMP to 482BH to return success.
4828
INC (IX+0BH) DD 34 0B
INCrement the high byte of the sector counter at FCB offset +0BH (carry from low byte).
482B
XOR A AF
Set Register A to 00H (success) and clear all flags (Z flag set).
482C
RET C9
RETURN with A = 00H and Z flag set (success).

482DH - Flush Buffer and Load Next Sector

Writes the current dirty sector buffer to disk (if modified), clears bit 5 of IX+01H (buffer-needs-refresh flag), and reads the next sector from disk. Used during sequential file access when crossing sector boundaries.

482D
GOSUB to 48DCH, the granule/directory traversal routine. Locates the physical disk sector corresponding to the current file position by walking the granule allocation chain.
4830
RET NZ C0
If NZ (error during granule traversal), RETURN.
4831
RES 5,(IX+01H) DD CB 01 AE
RESET Bit 5 of FCB offset +01H, clearing the buffer needs refresh flag. The buffer will be current after the upcoming disk read.
4835
LD L,(IX+03H) DD 6E 03
Fetch the low byte of the sector buffer address from FCB offset +03H into Register L.
4838
LD H,(IX+04H) DD 66 04
Fetch the high byte of the sector buffer address from FCB offset +04H into Register H. HL now points to the FCB's 256-byte sector buffer in RAM.
483B
JUMP to 46DDH to perform the actual disk read. This reads the physical sector (track/sector determined by the granule traversal) into the buffer at HL.

483EH - Write Sector from FCB Buffer

Validates write access mode, traverses the granule chain to find the physical sector, writes the buffer to disk, and optionally performs read-after-write verification. The verify flag at 485EH (self-modifying code) controls whether verification occurs.

483E
LD A,(IX+01H) DD 7E 01
Fetch the FCB access mode flags from offset +01H.
4841
AND 07H E6 07
Isolate the low 3 access mode bits.
4843
CP 05H FE 05
Compare against 05H. Write access requires mode < 05H.
4845
If CARRY (mode < 05H, write access granted), JUMP to 484BH.
4847
LD A,25H 3E 25
Load A with 25H (Access Denied error code).
4849
OR A B7
Set NZ flag for error return.
484A
RET C9
RETURN with Access Denied error.
484B
GOSUB to 48DCH to traverse the granule chain and locate the physical sector.
484E
RET NZ C0
If NZ (error), RETURN.
484F
LD L,(IX+03H) DD 6E 03
Fetch the sector buffer address low byte from IX+03H.
4852
LD H,(IX+04H) DD 66 04
Fetch the sector buffer address high byte from IX+04H. HL points to the buffer.
4855
GOSUB to 46E6H to write the sector from the buffer at HL to the disk at the track/sector determined by the granule traversal.
4858
RET NZ C0
If NZ (write error), RETURN.
4859
RES 4,(IX+01H) DD CB 01 A6
RESET Bit 4 of FCB offset +01H, clearing the buffer dirty flag. The buffer has been successfully written to disk.
485D
LD A,00H 3E 00
Self-Modifying Code
The operand 00H was patched at 478EH with the verify flag. If non-zero, a read-after-write verification will be performed.
485F
OR A B7
Test the verify flag. If zero, Z flag is set (no verify). If non-zero, NZ flag is set (verify needed).
4860
If the NZ FLAG has been set (verify requested), GOSUB to 46F3H to perform a read-after-write verification. This re-reads the sector just written and compares it against the buffer.
4863
RET NZ C0
If NZ (verification failed), RETURN with error.
4864
GOSUB to 48B9H for the EOF boundary check.
4867
If NZ (not at EOF), JUMP to 486FH to continue.
4869
BIT 6,(IX+01H) DD CB 01 76
Test Bit 6 of IX+01H (file open for extend).
486D
If NZ (can extend), JUMP back to 4823H to increment the sector counter.
486F
INC HL 23
INCrement HL (advance buffer pointer past the sector data).
4870
LD (IX+00H),L DD 75 0C
Store Register L into FCB offset +0CH. Note: the disassembly shows DD 75 0C which is LD (IX+0CH),L — updating the low byte of the total sector count in the directory entry portion of the FCB.
4873
LD (IX+00H),H DD 74 0D
Store Register H into FCB offset +0DH — updating the high byte of the total sector count.
4876
JUMP back to 4823H to increment the sector position and return success.

4878H - FCB Position Validation

Checks if the FCB's current state requires a sector write before proceeding. Tests bits 4 (dirty) and 7 (error) of IX+01H. If both are set, the buffer is dirty and an error occurred — routes to the write handler at 483EH. Otherwise returns success.

4878
LD A,(IX+01H) DD 7E 01
Fetch the FCB flags byte from offset +01H into Register A.
487B
AND 90H E6 90
AND with 90H to isolate Bit 7 (error/random flag) and Bit 4 (dirty flag). If both are set, the result is 90H.
487D
CP 90H FE 90
Compare against 90H. Z flag set means both bits 7 and 4 are set — the buffer is dirty and requires flushing.
487F
If Z (both flags set), JUMP to 483EH to write the dirty buffer to disk.
4881
XOR A AF
Set A to 00H (success) and set Z flag.
4882
RET C9
RETURN with success (A=00H, Z set).

4883H - Calculate Buffer Byte Address

Computes the memory address of the current byte within the FCB's sector buffer. Takes the byte offset from IX+05H, adds it to the buffer base address from IX+03H/+04H, and returns the result in DE. Also returns the byte offset in C.

4883
LD A,(IX+05H) DD 7E 05
Fetch the byte offset within the current sector from FCB offset +05H into Register A. This is a value 00H-FFH representing the position within the 256-byte sector.
4886
LD C,A 4F
Copy the byte offset to Register C for the caller's use.
4887
ADD A,(IX+03H) DD 86 03
ADD the low byte of the buffer base address (IX+03H) to the byte offset in Register A. This computes the low byte of the target address.
488A
LD E,A 5F
Store the computed low byte into Register E.
488B
LD A,(IX+04H) DD 7E 04
Fetch the high byte of the buffer base address from IX+04H.
488E
ADC 00H CE 00
ADD the carry from the low byte addition to the high byte. This handles the case where the byte offset + buffer base low byte overflows past FFH.
4890
LD D,A 57
Store the computed high byte into Register D. DE now holds the complete address of the current byte within the sector buffer.
4891
RET C9
RETURN. DE = address of current byte in buffer, C = byte offset within sector.

4892H - Verify File Open and Set Up Overlay Call Context

Called at the entry of every file I/O vector (Read Record at 476DH, Write Record at 478BH, Position to Record at 4737H, Rewind at 4756H, Position to EOF at 475FH). This routine checks whether the file is open by testing bit 7 of the FCB status byte at (DE). If the file is open, it saves the current overlay return address and FCB pointer, then sets up the stack so that execution returns through the restore routine at 48B3H. If the file is not open, it returns error code 26H.

4892
LD A,(DE) 1A
Fetch the FCB status byte from the address pointed to by Register Pair DE. DE points to the start of the FCB (offset +00H), which holds the status/error flags. Bit 7 of this byte indicates whether the file is open (1 = open, 0 = not open).
4893
RLCA 07
Rotate Register A left through carry. This moves bit 7 (the file-open flag) into the Carry flag for testing.
4894
If the NO CARRY FLAG is set (bit 7 of the status byte was 0, meaning the file is not open), JUMP to 48AEH to return error code 26H (file not open).

File Is Open
The file is confirmed open. The following code saves the current overlay context (return address and FCB pointer) so that after the file I/O operation completes, the overlay system can be restored to its prior state. This is necessary because file I/O routines may load overlays that overwrite the currently loaded overlay.

4896
POP AF F1
Restore Register Pair AF from the stack. This pops the return address of the CALL 4892H instruction's caller, but since EX (SP),HL follows, this is part of the stack manipulation to access the caller's return address.
4897
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack. This retrieves the return address of the routine that called the file I/O vector (the user's return address) into HL, while pushing the current HL onto the stack.
4898
LD (430CH),HL 22 0C 43
Store Register Pair HL (the user's return address) to memory location 430CH. This saves the overlay return address so it can be restored after the file I/O operation completes. 430CH is the saved overlay return address storage.
489B
LD (430AH),DE ED 53 0A 43
Store Register Pair DE (the FCB pointer) to memory location 430AH. This saves the FCB pointer into the overlay context save area at 430A-430BH for later restoration.
489F
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack. This restores the original HL value from the stack and puts the saved return address back on the stack.
48A0
PUSH DE D5
Save Register Pair DE (FCB pointer) onto the stack.
48A1
EX (SP),IX DD E3
Exchange the IX register with the value on top of the stack. This saves the current IX value onto the stack and loads IX with the FCB pointer (DE) that was just pushed. After this, IX points to the FCB for use by the file I/O routines.
48A3
PUSH HL E5
Save Register Pair HL onto the stack (preserving the caller's HL value).
48A4
PUSH DE D5
Save Register Pair DE onto the stack (preserving the FCB pointer).
48A5
PUSH BC C5
Save Register Pair BC onto the stack (preserving the caller's BC value).
48A6
PUSH HL E5
Save Register Pair HL onto the stack again. This creates a slot on the stack that will be replaced by the return address of the restore routine (48B3H).
48A7
LD HL,48B3H 21 B3 48
Point Register Pair HL to 48B3H, which is the register restore routine. This address will be placed on the stack as a synthetic return address.
48AA
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack. This replaces the HL copy on the stack with 48B3H (the restore routine address), and restores HL to its original value. When the file I/O routine returns, it will return to 48B3H instead of the original caller.
48AB
PUSH AF F5
Save Register Pair AF onto the stack (preserving the caller's AF value, including the function code).
48AC
XOR A AF
Set Register A to ZERO and clear all flags. This sets the Z flag to indicate no error, so the file I/O routine sees a clean entry state.
48AD
RET C9
RETurn to the caller (the file I/O vector routine). The Z flag is set (no error), and the stack has been set up so that when the I/O routine eventually returns, execution passes through the restore routine at 48B3H.

48AEH - File Not Open Error Return

Reached when the FCB status byte indicates the file is not open. Returns error code 26H with the NZ flag set to signal an error to the caller.

48AE
POP AF F1
Restore Register Pair AF from the stack, discarding the saved return address to balance the stack after the CALL to 4892H.
48AF
LD A,26H 3E 26
Load Register A with 26H, which is the TRSDOS error code for "file not open" (decimal 38).
48B1
OR A B7
OR Register A with itself. Since A = 26H (non-zero), this sets the NZ flag to signal an error condition to the caller.
48B2
RET C9
RETurn to the caller with error code 26H in Register A and the NZ flag set.

48B3H - Register Restore After File I/O

This is the synthetic return address placed on the stack by the setup routine at 4892H. When a file I/O routine (read, write, position, etc.) completes and returns, it lands here. This routine restores all registers that were saved during the setup, returning control to the original caller with registers intact.

48B3
POP BC C1
Restore Register Pair BC from the stack (the caller's original BC value, saved at 48A5H).
48B4
POP DE D1
Restore Register Pair DE from the stack (the FCB pointer, saved at 48A4H).
48B5
POP HL E1
Restore Register Pair HL from the stack (the caller's original HL value, saved at 48A3H).
48B6
POP IX DD E1
Restore the IX register from the stack (the caller's original IX value, saved via EX (SP),IX at 48A1H).
48B8
RET C9
RETurn to the original caller of the file I/O vector. The stack now holds the user's original return address, so execution returns to the code that originally called the DOS service vector.

48B9H - Check Current Position Against EOF

Compares the FCB's current sector position (IX+0AH/0BH) and byte offset (IX+05H) against the total sector count (IX+0CH/0DH) and EOF byte offset (IX+08H) to determine if the file position is at or past the end of file. Returns with the Z flag set and the Carry flag clear if the position is within the file. Returns error code 1CH or 1DH if the position is invalid.

48B9
LD L,(IX+0AH) DD 6E 0A
Load Register L with the low byte of the current sector position from FCB offset IX+0AH.
48BC
LD H,(IX+0BH) DD 66 0B
Load Register H with the high byte of the current sector position from FCB offset IX+0BH. Register Pair HL now holds the 16-bit current sector number.
48BF
LD C,(IX+05H) DD 4E 05
Load Register C with the byte offset within the current sector from FCB offset IX+05H (range 00H-FFH).
48C2
LD A,H 7C
Load Register A with the high byte of the current sector position (from Register H) for comparison.
48C3
CP (IX+0DH) DD BE 0D
Compare Register A (high byte of current sector) against the high byte of the total sector count at FCB offset IX+0DH. If they are not equal, the current sector is either before or after the last sector.
48C6
If the NZ FLAG (Not Zero) is set (high bytes differ), JUMP to 48D7H to check whether we are before or past EOF.
48C8
LD A,L 7D
Load Register A with the low byte of the current sector position (from Register L) for comparison.
48C9
CP (IX+0CH) DD BE 0C
Compare Register A (low byte of current sector) against the low byte of the total sector count at FCB offset IX+0CH.
48CC
If the NZ FLAG (Not Zero) is set (low bytes differ), JUMP to 48D7H to check whether we are before or past EOF.

On the Last Sector
The current sector number matches the total sector count, meaning we are on the last sector of the file. Now check the byte offset within this sector against the EOF byte offset to see if we are at or past the end of the file data.

48CE
DEC C 0D
DECrement Register C (the byte offset within the current sector) by 1. This adjusts for a zero-based comparison against the EOF byte offset.
48CF
GOSUB to 4B97H, the EOF byte comparison subroutine. This compares the adjusted byte offset (C) against the EOF byte offset stored at IX+08H. Returns with flags set based on the comparison.
48D2
If the NZ FLAG (Not Zero) is set (byte offset does not match EOF exactly), JUMP to 48D7H to determine the error type.
48D4
OR 1CH F6 1C
OR Register A with 1CH. Since A was zero from the CALL, this sets A to 1CH (position error - before start of file) and sets the NZ flag. This indicates the position is exactly at EOF.
48D6
RET C9
RETurn to the caller with error code 1CH in Register A (at EOF position).
48D7
LD A,1DH 3E 1D
Load Register A with 1DH, the TRSDOS error code for "position past end of file."
48D9
RET NC D0
If the NO CARRY FLAG is set (current position is past the last sector), RETurn with error code 1DH (past EOF). The Carry flag was set by the earlier CP instruction if the current sector was less than the total count.
48DA
XOR A AF
Set Register A to ZERO and clear all flags. The current position is before the last sector, so there is no error - the position is within the file.
48DB
RET C9
RETurn to the caller with A = 0 and the Z flag set, indicating the position is valid (within the file).

48DCH - Calculate Sector Position in Granule Chain

Given the current sector position in the FCB (IX+0AH/0BH), this routine calculates the directory entry, granule, and sector within the granule that corresponds to the position. It walks the extent chain in the directory to find which extent and granule contain the target sector, then computes the physical disk parameters needed to read or write the sector. The results are stored in self-modifying code locations for use by the disk I/O routines.

48DC
LD L,(IX+0AH) DD 6E 0A
Load Register L with the low byte of the current sector position from FCB offset IX+0AH.
48DF
LD H,(IX+0BH) DD 66 0B
Load Register H with the high byte of the current sector position from FCB offset IX+0BH. Register Pair HL now holds the 16-bit target sector number within the file.
48E2
LD A,05H 3E 05
Load Register A with 05H. This is the divisor for the 16-bit division that follows - each directory extent entry can hold up to 5 granules, so dividing the sector position by the number of sectors per extent gives the extent index.
48E4
GOSUB to 4B84H, the 16-bit division routine. Divides HL by A (05H). Returns: HL = quotient (extent-relative sector index), A = remainder.
48E7
LD (49AAH),A 32 AA 49
Self-Modifying Code
Store the remainder (A) to memory location 49AAH. This value is the granule counter offset used later at 49A9H in the ADD instruction. The remainder represents the sector offset within the current extent group.
48EA
PUSH IX DD E5
Save the IX register (FCB pointer) onto the stack.
48EC
EX (SP),HL E3
Exchange Register Pair HL (the quotient from the division) with the value on top of the stack (the IX/FCB pointer). HL now holds the FCB pointer, and the quotient is on the stack.
48ED
LD BC,000FH 01 0F 00
Load Register Pair BC with 000FH (decimal 15). This is the offset to the first extent entry within the FCB. The directory extent data starts at FCB+0FH.
48F0
ADD HL,BC 09
ADD Register Pair BC (000FH) to Register Pair HL (FCB base address). HL now points to the first extent entry in the FCB's directory data area (FCB+0FH).
48F1
POP BC C1
Restore Register Pair BC from the stack. BC now holds the quotient from the division at 48E4H - the number of complete extents to skip past.
48F2
LD A,05H 3E 05
Load Register A with 05H, the number of extent entries per directory sector. This serves as a countdown for walking through the extent chain.
48F4
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H, initializing the accumulated sector count to zero.

Extent Walk Loop
This loop walks through the extent entries in the directory data. Each extent entry is 3 bytes: the first byte contains a granule count (bits 0-4) and track/granule info (bits 5-7), and the next two bytes hold the starting granule address. The loop accumulates sector counts to find which extent contains the target sector.

48F7
PUSH AF F5
Save Register A (extent entry counter) onto the stack.
48F8
DEC HL 2B
DECrement Register Pair HL by 1, pointing to the granule count byte of the current extent entry (the byte before the 2-byte granule address).
48F9
LD A,(HL) 7E
Fetch the extent descriptor byte from the address pointed to by HL. This byte encodes the number of granules in this extent (bits 0-4) plus additional info in bits 5-7.
48FA
INC HL 23
INCrement Register Pair HL by 1, pointing back to the start of the 2-byte granule address field.
48FB
INC A 3C
INCrement Register A by 1. If the extent descriptor was FFH (empty/unused extent), A becomes 00H and the Z flag is set, indicating the end of the extent chain.
48FC
If the Z FLAG (Zero) is set (extent descriptor was FFH, meaning no more extents), JUMP to 4909H to advance to the next extent entry slot.
48FE
PUSH HL E5
Save Register Pair HL (pointer to extent's granule address) onto the stack.
48FF
LD H,D 62
Load Register H with Register D (high byte of accumulated sector count).
4900
LD L,E 6B
Load Register L with Register E (low byte of accumulated sector count). HL now equals DE (the accumulated sector count so far).
4901
XOR A AF
Set Register A to ZERO and clear the Carry flag, preparing for a 16-bit subtraction.
4902
SBC HL,BC ED 42
SUBtract Register Pair BC (the target extent count) from Register Pair HL (the accumulated count) with borrow. If HL >= BC, the Carry flag is clear, meaning we have reached or passed the target extent.
4904
If the CARRY FLAG is set (accumulated count is still less than the target), JUMP to 4915H to add this extent's granule count to the accumulated total and continue the search.
4906
POP HL E1
Restore Register Pair HL from the stack (pointer to the current extent's granule address).
4907
If the Z FLAG is set (accumulated count exactly matches the target), JUMP to 496AH to process this extent as the one containing the target sector.

4909H - Advance to Next Extent Entry

Advances the extent pointer to the next 3-byte entry in the directory. If all 5 extent entries in the current directory sector have been exhausted, falls through to 4928H to load the next directory sector via the extent chain continuation.

4909
INC HL 23
INCrement Register Pair HL by 1. Skip past the 2-byte granule address field to point to the start of the next extent entry (each entry is 3 bytes: 1 descriptor + 2 address bytes).
490A
POP AF F1
Restore Register A from the stack. A contains the remaining extent entry counter (decremented from 05H).
490B
DEC A 3D
DECrement Register A (the extent entry counter) by 1.
490C
If the Z FLAG (Zero) is set (all 5 extent entries in this directory sector have been processed), JUMP to 4928H to follow the continuation pointer to the next directory sector.
490E
LD E,(HL) 5E
Load Register E with the low byte of the next extent entry's granule address from (HL).
490F
INC HL 23
INCrement Register Pair HL to point to the high byte of the granule address.
4910
LD D,(HL) 56
Load Register D with the high byte of the next extent entry's granule address from (HL). Register Pair DE now holds the starting sector address for this extent.
4911
INC HL 23
INCrement Register Pair HL to point to the descriptor byte of the following extent entry.
4912
INC HL 23
INCrement Register Pair HL again, advancing past the descriptor byte to the granule address of the following extent entry. HL is now positioned for the next iteration.
4913
LOOP BACK to 48F7H to process the next extent entry in the chain.

4915H - Accumulate Extent Granule Count

When the target sector has not yet been reached, this routine adds the current extent's granule count to the running total and continues the extent walk loop. It also handles the case where the accumulated total has been exceeded, requiring backtracking into the current extent.

4915
INC H 24
INCrement Register H by 1. This is part of a calculation to determine the granule count from the extent descriptor byte that was loaded earlier.
4916
LD A,L 7D
Load Register A with Register L (the low byte of the subtraction result from SBC HL,BC at 4902H).
4917
POP HL E1
Restore Register Pair HL from the stack (pointer to the current extent's granule address, saved at 48FEH).
4918
If the NZ FLAG (Not Zero) is set (the carry-over from the subtraction indicates the target is still further ahead), JUMP back to 4909H to advance to the next extent entry.

Target Is Within This Extent
The target sector falls within the current extent. The following code calculates the exact position within the extent by examining the granule boundaries.

491A
PUSH DE D5
Save Register Pair DE (accumulated sector count) onto the stack.
491B
LD E,A 5F
Load Register E with Register A (the sector offset within the extent from the subtraction result).
491C
LD A,(HL) 7E
Fetch the extent descriptor byte from the address pointed to by HL. Bits 0-4 contain the number of sectors in this granule allocation.
491D
AND 1FH E6 1F
MASK Register A with 1FH (0001 1111) to extract the granule count field from bits 0-4 of the extent descriptor. This gives the number of sectors in this extent allocation.
491F
ADD A,E 83
ADD Register E (the sector offset) to Register A (the granule count). This computes the position within the extent.
4920
LD A,E 7B
Load Register A with Register E (the sector offset value again). This is the value to be used if the position falls within this extent.
4921
POP DE D1
Restore Register Pair DE from the stack (the accumulated sector count).
4922
If the NO CARRY FLAG is set (the position is within this extent's sector range), JUMP back to 4909H to advance to the next entry. The target was not in this specific granule allocation.
4924
NEG ED 44
Negate Register A (two's complement). This computes the sector offset from the end of the extent, converting it to a forward offset within the extent.
4926
JUMP to 496AH to finalize the sector position calculation using the computed offset in Register A.

4928H - Follow Directory Extent Chain Continuation

When all 5 extent entries in the current directory sector have been processed and the target sector has not been found, this routine follows the continuation pointer to the next directory sector containing more extent entries for this file.

4928
GOSUB to 49B5H, the directory sector read routine. This reads the next directory sector in the file's extent chain. On return, BC holds the continuation sector address and DE holds the data pointer.
492B
RET NZ C0
If the NZ FLAG (Not Zero) is set (an error occurred reading the directory sector), RETurn with the error code in Register A.
492C
PUSH HL E5
Save Register Pair HL onto the stack.
492D
LD H,B 60
Load Register H with Register B (high byte of continuation sector address from the directory read).
492E
LD L,C 69
Load Register L with Register C (low byte of continuation sector address). HL now holds the next sector's starting address.
492F
XOR A AF
Set Register A to ZERO and clear the Carry flag for the subtraction.
4930
SBC HL,DE ED 52
SUBtract Register Pair DE (data pointer) from Register Pair HL (continuation address). The result gives the number of remaining sectors in this chain segment.
4932
LD A,L 7D
Load Register A with Register L (the low byte of the remaining sector count).
4933
LD (499AH),A 32 9A 49
Self-Modifying Code
Store Register A (the remaining sector count) to memory location 499AH. This patches the ADD instruction at 4999H to include the correct offset when computing the final sector position.
4936
POP HL E1
Restore Register Pair HL from the stack.
4937
PUSH DE D5
Save Register Pair DE (data pointer) onto the stack.
4938
PUSH IX DD E5
Save the IX register (FCB pointer) onto the stack.
493A
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack (IX value). HL now holds the FCB base address.
493B
LD DE,000EH 11 0E 00
Load Register Pair DE with 000EH (decimal 14), the offset to the extent data area within the FCB.
493E
ADD HL,DE 19
ADD Register Pair DE (000EH) to Register Pair HL (FCB base). HL now points to the extent data area at FCB+0EH.
493F
POP DE D1
Restore Register Pair DE from the stack (this was the IX value from the EX at 493AH).
4940
LD B,05H 06 05
Load Register B with 05H, the number of extent entries per directory sector (5 entries of 3 bytes each).

Extent Entry Search Loop
This loop searches through the 5 extent entries in the newly loaded directory sector to find the one that matches the continuation chain pointer. It compares the starting sector address in each entry against the target address.

4942
LD A,(HL) 7E
Fetch the low byte of the current extent entry's sector address from (HL).
4943
INC HL 23
INCrement Register Pair HL to point to the high byte of the sector address.
4944
CP E BB
Compare Register A (low byte of extent sector address) against Register E (low byte of target sector address).
4945
If the NZ FLAG (Not Zero) is set (low bytes do not match), JUMP to 494DH to try the next extent entry.
4947
LD A,(HL) 7E
Fetch the high byte of the current extent entry's sector address from (HL).
4948
XOR D AA
XOR Register A with Register D (high byte of target sector address). If bits 5-7 match (same track group), the result in bits 5-7 will be zero.
4949
AND E0H E6 E0
MASK the XOR result with E0H (1110 0000) to test only the upper 3 bits (the track group identifier). If these bits match, the result is zero.
494B
If the Z FLAG (Zero) is set (the track group bits match), JUMP to 4965H - the matching extent entry has been found.
494D
DEC B 05
DECrement Register B (the extent entry counter) by 1.
494E
If the Z FLAG (Zero) is set (all 5 entries checked without a match), JUMP to 4955H to shift the entries and make room for the new extent data.
4950
INC HL 23
INCrement Register Pair HL to skip past the high byte of the address.
4951
INC HL 23
INCrement HL again to skip the descriptor byte.
4952
INC HL 23
INCrement HL again to point to the low byte of the next entry's sector address. Each extent entry is 3 bytes (2 address + 1 descriptor).
4953
LOOP BACK to 4942H to check the next extent entry.

4955H - Shift Extent Entries and Insert New Entry

When no matching extent entry is found in the current set of 5 entries, this routine shifts the existing entries down by one position (using LDDR) to make room at the beginning for the new extent data being chained in.

4955
PUSH DE D5
Save Register Pair DE (target sector address) onto the stack.
4956
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now points to the end of the extent entry area, and HL is free for calculation.
4957
LD HL,FFFCH 21 FC FF
Load Register Pair HL with FFFCH (-4 in two's complement). This is the backward offset to calculate the destination for the block move.
495A
ADD HL,DE 19
ADD Register Pair DE to Register Pair HL. HL now points 4 bytes before the end of the extent area, which is the destination for the LDDR block move.
495B
LD BC,000CH 01 0C 00
Load Register Pair BC with 000CH (decimal 12). This is the number of bytes to move - 4 extent entries of 3 bytes each = 12 bytes.
495E
LDDR ED B8
Block move (decrementing). Copies BC (12) bytes from source (DE) to destination (HL), both decrementing. This shifts the last 4 extent entries down by one position, making room at the start for a new entry.
4960
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now points to the start of the vacated first entry position.
4961
POP BC C1
Restore Register Pair BC from the stack (the target sector address that was in DE before the PUSH at 4955H).
4962
POP DE D1
Restore Register Pair DE from the stack.
4963
JUMP to 498AH to store the new extent entry data at the vacated position.

4965H - Matching Extent Entry Found

The extent entry matching the continuation chain pointer has been located. This routine updates the entry with the new track information and falls through to the sector position finalization code.

4965
LD (HL),D 72
Store Register D (high byte of the target sector address) into the current extent entry's high address byte at (HL). This updates the extent entry with the new chain information.
4966
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now points to the extent entry, and HL holds the target sector address.
4967
POP AF F1
Restore Register A from the stack, discarding the saved extent counter.
4968
JUMP to 4993H to compute the final physical disk parameters from the sector position.

496AH - Compute Sector Offset Within Extent

The target extent has been identified. This routine computes the exact sector offset within the extent and then calculates the physical disk parameters (track, sector, drive) needed for the disk I/O operation. The results are stored into self-modifying code locations used by the disk I/O core.

496A
LD (499AH),A 32 9A 49
Self-Modifying Code
Store Register A (the sector offset within the extent) to memory location 499AH. This patches the ADD instruction at 4999H with the correct sector offset for the final position calculation.
496D
LD B,(HL) 46
Load Register B with the extent descriptor byte from (HL). This byte contains the granule/sector count information for this extent.
496E
DEC HL 2B
DECrement Register Pair HL to point to the high byte of the extent's sector address.
496F
LD C,(HL) 4E
Load Register C with the high byte of the extent's sector address from (HL).
4970
INC HL 23
INCrement Register Pair HL back to the descriptor byte position.
4971
POP AF F1
Restore Register A from the stack (the extent entry counter that was saved at 48F7H).
4972
CPL 2F
Complement Register A (invert all bits). This converts the remaining entry count into a consumed entry count.
4973
ADD 04H C6 04
ADD 04H to Register A. Combined with the CPL, this computes (5 - remaining_count - 1) = the index of the matched entry. The result tells how many 3-byte entries to skip to reach the target.
4975
If the NO CARRY FLAG is set (the result fits without carry), JUMP to 4991H to continue with the position calculation.
4977
INC A 3C
INCrement Register A by 1, adjusting the entry index for the multiplication that follows.
4978
RLCA 07
Rotate Register A left. This begins the multiply-by-3 operation (each extent entry is 3 bytes).
4979
RLCA 07
Rotate Register A left again. After two left rotates, A has been effectively multiplied by 4. But since we need times 3, additional adjustment follows.

497AH - Block Move Extent Data Into Position

Shifts extent entry data within the FCB directory area using a backward block copy (LDDR) to position the entries correctly after the extent chain walk.

497A
PUSH BC C5
Save Register Pair BC (extent sector address) onto the stack.
497B
PUSH DE D5
Save Register Pair DE onto the stack.
497C
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now holds the source pointer for the block move.
497D
LD HL,FFFCH 21 FC FF
Load Register Pair HL with FFFCH (-4 in two's complement), the backward offset for the destination calculation.
4980
ADD HL,DE 19
ADD Register Pair DE to Register Pair HL. HL now points 4 bytes before DE, establishing the destination for the block move.
4981
LD B,00H 06 00
Load Register B with 00H. B is the high byte of the byte count for the block move.
4983
LD C,A 4F
Load Register C with Register A (the number of bytes to move, computed from the extent entry index). BC now holds the block move byte count.
4984
LDDR ED B8
Block move (decrementing). Copies BC bytes from the source address (DE) to the destination address (HL), both decrementing. This shifts the extent entries into the correct position within the FCB directory area.
4986
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now points to the beginning of the repositioned extent data.
4987
POP DE D1
Restore Register Pair DE from the stack.
4988
POP BC C1
Restore Register Pair BC from the stack (extent sector address).
4989
NOP 00
No operation. Padding byte (likely a remnant of an earlier version or alignment).

498AH - Store Extent Entry Data

Writes the extent entry data (sector address and descriptor) into the FCB extent area at the position pointed to by HL. Then falls through to the physical disk parameter calculation.

498A
LD (HL),B 70
Store Register B (high byte of extent sector address / descriptor) to (HL).
498B
DEC HL 2B
DECrement Register Pair HL to point to the next byte position.
498C
LD (HL),C 71
Store Register C (low byte of extent sector address) to (HL).
498D
DEC HL 2B
DECrement Register Pair HL to the next position.
498E
LD (HL),D 72
Store Register D to (HL).
498F
DEC HL 2B
DECrement Register Pair HL.
4990
LD (HL),E 73
Store Register E to (HL). The 4 bytes (E, D, C, B) have been written in descending order to fill the extent entry slot.
4991
LD H,B 60
Load Register H with Register B. Begin building the sector address in HL from the extent entry data.
4992
LD L,C 69
Load Register L with Register C. HL now holds the extent starting sector address (BC).

4993H - Compute Physical Disk Parameters

Converts the logical sector address in HL into physical disk parameters: track number, sector number within the track, and drive number. These values are stored into self-modifying code locations at 49AEH and 49ADH for use by the disk I/O core routines. Also loads the drive number from the FCB at IX+06H.

4993
LD A,H 7C
Load Register A with Register H (the high byte of the sector address).
4994
RLCA 07
Rotate Register A left through carry (bit 7 goes to carry and bit 0).
4995
RLCA 07
Rotate Register A left again.
4996
RLCA 07
Rotate Register A left a third time. The upper 3 bits of H have been rotated to the lower 3 bit positions. These 3 bits encode the track group number.
4997
AND 07H E6 07
MASK Register A with 07H (0000 0111) to isolate the 3-bit track group number.
4999
ADD 00H C6 00
Self-Modifying Code
ADD the value at 499AH to Register A. The operand at 499AH was patched by the code at 48E7H or 496AH with the sector offset within the extent group. This adds the offset to the track group base to compute the absolute track number.
499B
LD E,A 5F
Load Register E with Register A (the computed track number).
499C
AND FEH E6 FE
MASK Register A with FEH (1111 1110) to clear bit 0. This isolates the track number without the sector bit (which was in the LSB).
499E
RRCA 0F
Rotate Register A right through carry. This divides the masked track value by 2, producing the track index for the disk parameter lookup.
499F
LD (49AEH),A 32 AE 49
Self-Modifying Code
Store Register A (the track index) to memory location 49AEH. This patches the ADD instruction at 49ADH with the track number for the disk I/O routines.
49A2
LD A,E 7B
Load Register A with Register E (the computed track number including the sector bit).
49A3
AND 01H E6 01
MASK Register A with 01H to isolate bit 0, the sector position indicator within the track (even/odd sector).
49A5
LD E,A 5F
Load Register E with Register A (the sector position bit: 0 or 1).
49A6
RLCA 07
Rotate Register A left. Multiply the sector bit by 2.
49A7
RLCA 07
Rotate Register A left again. The sector bit has been shifted left by 2 positions (multiplied by 4).
49A8
ADD A,E 83
ADD Register E (the original sector bit) to Register A (the bit shifted left by 2). This computes sector_bit * 5, converting the half-track indicator into a sector offset within the track's 5-sector group.
49A9
ADD 00H C6 00
Self-Modifying Code
ADD the value at 49AAH to Register A. The operand at 49AAH was patched at 48E7H or 496AH with the sector offset. This adjusts the sector number within the track.
49AB
LD E,A 5F
Load Register E with Register A (the final sector number within the track).
49AC
LD A,L 7D
Load Register A with Register L (the low byte of the original sector address).
49AD
ADD 00H C6 00
Self-Modifying Code
ADD the value at 49AEH to Register A. The operand at 49AEH was patched at 499FH with the track index. This computes the final track number for the disk I/O operation.
49AF
LD D,A 57
Load Register D with Register A (the final track number). Register Pair DE now holds the physical disk address: D = track, E = sector.
49B0
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset IX+06H. The disk I/O routines use C to select the drive.
49B3
XOR A AF
Set Register A to ZERO and clear all flags. Z flag is set to indicate no error.
49B4
RET C9
RETurn to the caller with D = track number, E = sector number, C = drive number, and Z flag set (no error).

49B5H - Read Directory Sector for File

Reads the directory sector that contains the file's directory entry. Uses the FCB's directory entry info (IX+07H) to locate the correct directory sector and position within it. Returns with HL pointing to the directory data buffer and DE pointing to the extent data within the entry. Also returns the continuation chain pointer in BC.

49B5
PUSH BC C5
Save Register Pair BC onto the stack.
49B6
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H, initializing the sector offset to zero.
49B9
LD B,(IX+07H) DD 46 07
Load Register B with the directory entry info byte from FCB offset IX+07H. This encodes the directory sector and entry position within the sector for this file.
49BC
LD A,B 78
Load Register A with Register B (the directory entry info byte) for manipulation.
49BD
LD (4ABEH),A 32 BE 4A
Self-Modifying Code
Store Register A (the directory entry info) to memory location 4ABEH. This saves the directory entry byte for later use by the GAT/directory sector write routine at 4AB6H.
49C0
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset IX+06H.
49C3
GOSUB to 4AC1H, the directory sector read subroutine. This reads the directory sector identified by B (directory entry info) from drive C into the directory buffer. Returns with HL pointing to the buffer.
49C6
LD BC,0016H 01 16 00
Load Register Pair BC with 0016H (decimal 22). This is the offset to the extent/granule data within the directory entry. Each directory entry has 22 bytes of header information before the extent data begins.
49C9
ADD HL,BC 09
ADD Register Pair BC (0016H) to Register Pair HL (directory buffer base). HL now points to the extent data area of the directory entry.
49CA
EX DE,HL EB
Exchange Register Pairs DE and HL. DE now points to the extent data, HL holds the original sector offset (0000H).
49CB
POP BC C1
Restore Register Pair BC from the stack.
49CC
RET NZ C0
If the NZ FLAG is set (the directory sector read at 4AC1H returned an error), RETurn with the error code in Register A.

Directory Sector Read Successfully
The directory sector has been read. Now walk the extent entries in the directory to find the file's granule chain. Each extent entry is 2 bytes (sector address) followed by a 1-byte descriptor. The last extent in the chain has FFH or FEH as the status byte.

49CD
LD A,(DE) 1A
Fetch the extent status byte from the address pointed to by DE (the current extent entry in the directory).
49CE
CP FEH FE FE
Compare Register A against FEH. A value of FEH or FFH indicates the end of the extent chain: FEH means a continuation pointer follows, FFH means end of file extents.
49D0
If the NO CARRY FLAG is set (A >= FEH, meaning this is a chain terminator or continuation pointer), JUMP to 49F1H to handle the end of the extent chain.
49D2
INC DE 13
INCrement Register Pair DE to point to the high byte of the extent's sector address.
49D3
LD A,(DE) 1A
Fetch the high byte of the extent's sector address from (DE).
49D4
PUSH HL E5
Save Register Pair HL (accumulated position) onto the stack.
49D5
AND 1FH E6 1F
MASK Register A with 1FH (0001 1111) to extract the granule count from the lower 5 bits of the sector address byte. This gives the number of sectors in this extent.
49D7
INC A 3C
INCrement Register A by 1. Granule counts are stored 0-based, so add 1 to get the actual count.
49D8
ADD A,L 85
ADD Register L (low byte of accumulated position) to Register A. This accumulates the total sector count across extents.
49D9
LD L,A 6F
Load Register L with Register A (the new accumulated count low byte).
49DA
If the NO CARRY FLAG is set (no overflow from the 8-bit addition), JUMP over the high byte increment.
49DC
INC H 24
INCrement Register H (high byte of accumulated position) to account for the carry from the low byte addition.
49DD
PUSH HL E5
Save Register Pair HL (new accumulated position) onto the stack.
49DE
DEC HL 2B
DECrement Register Pair HL by 1. This adjusts the accumulated count for a less-than comparison.
49DF
XOR A AF
Set Register A to ZERO and clear the Carry flag for the subtraction.
49E0
SBC HL,BC ED 42
SUBtract Register Pair BC (the target sector position) from Register Pair HL (accumulated count - 1). If HL >= BC, the target has been reached or passed.
49E2
POP HL E1
Restore Register Pair HL (accumulated position) from the stack.
49E3
If the NO CARRY FLAG is set (accumulated count >= target), JUMP to 49E9H - the target extent has been found.
49E5
INC DE 13
INCrement Register Pair DE to advance past the current extent entry to the next one.
49E6
POP AF F1
Restore Register A from the stack (discarding the saved accumulated position from 49D4H to balance the stack).
49E7
LOOP BACK to 49CDH to process the next extent entry in the directory.

Target Extent Found
The extent containing the target sector has been located. Extract the physical sector address from the extent entry.

49E9
POP HL E1
Restore Register Pair HL from the stack (the accumulated position saved at 49D4H).
49EA
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now points to the extent entry in the directory, DE holds the accumulated count.
49EB
LD A,(HL) 7E
Fetch the high byte of the extent's sector address from (HL). This contains both the track group bits (5-7) and the sector offset (0-4).
49EC
DEC HL 2B
DECrement Register Pair HL to point to the low byte of the extent's sector address.
49ED
LD L,(HL) 6E
Load Register L with the low byte of the extent's sector address from (HL).
49EE
LD H,A 67
Load Register H with Register A (the high byte of the extent's sector address). HL now holds the full 16-bit sector address for this extent.
49EF
XOR A AF
Set Register A to ZERO and clear all flags. Z flag is set to indicate no error.
49F0
RET C9
RETurn to the caller with HL = sector address, and Z flag set (no error). The caller will use this address for the physical disk I/O.

49F1H - Handle Extent Chain End or Continuation

Handles the case where the extent chain status byte is FEH (continuation pointer to another directory sector) or FFH (absolute end of extents). FEH means follow the pointer to load the next directory sector. FFH means the file has been extended and needs a new extent allocation via the overlay system.

49F1
PUSH BC C5
Save Register Pair BC (target sector count) onto the stack.
49F2
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now points to the extent status byte in the directory.
49F3
If the NZ FLAG is set (the status byte was FFH, not FEH - since the CP FEH at 49CEH sets Z only for FEH), JUMP to 49F9H to handle the absolute end of extents via an overlay call.

Continuation Pointer (FEH)
The status byte is FEH, meaning a continuation pointer follows. The next byte contains the directory entry number of the next sector in the chain. Load that and re-enter the directory read routine.

49F5
INC HL 23
INCrement Register Pair HL to point past the FEH status byte to the continuation directory entry number.
49F6
LD B,(HL) 46
Load Register B with the continuation directory entry number from (HL). This is the next directory entry in the file's extent chain.
49F7
JUMP back to 49BCH to read the next directory sector using the continuation entry number in B. This follows the chain to the next set of extent entries.
49F9
GOSUB to 4A00H, the overlay-based extent allocation routine. This loads an overlay to allocate additional disk space for the file and extend the extent chain.
49FC
POP BC C1
Restore Register Pair BC (target sector count) from the stack.
49FD
RET NZ C0
If the NZ FLAG is set (the overlay allocation returned an error), RETurn with the error code.
49FE
JUMP back to 49B5H to re-read the directory sector after the extent has been extended. The new extent data is now in the directory, so the search can continue.

4A00H - Overlay-Based Extent Allocation

Calls the overlay system to allocate a new extent (disk space) for a file that has grown beyond its current allocation. Uses the RST 28H SVC mechanism to invoke the appropriate overlay.

4A00
PUSH BC C5
Save Register Pair BC onto the stack.
4A01
LD B,(IX+07H) DD 46 07
Load Register B with the directory entry info byte from FCB offset IX+07H.
4A04
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset IX+06H.
4A07
GOSUB to 4AF0H, which reads the directory sector for this file into the 4D00H overlay/buffer area. Returns with HL pointing to the directory entry.
4A0A
POP BC C1
Restore Register Pair BC from the stack.
4A0B
RET NZ C0
If the NZ FLAG is set (the directory read returned an error), RETurn with the error code.
4A0C
PUSH HL E5
Save Register Pair HL (directory entry pointer) onto the stack.
4A0D
LD H,B 60
Load Register H with Register B (high byte of target count).
4A0E
LD L,C 69
Load Register L with Register C (low byte of target count). HL now holds the target sector count.
4A0F
XOR A AF
Set Register A to ZERO and clear the Carry flag for the subtraction.
4A10
SBC HL,DE ED 52
SUBtract Register Pair DE from Register Pair HL. This computes the number of additional sectors needed to reach the target position.
4A12
LD B,H 44
Load Register B with Register H (high byte of sectors needed).
4A13
LD C,L 4D
Load Register C with Register L (low byte of sectors needed). BC now holds the number of sectors to allocate.
4A14
INC BC 03
INCrement Register Pair BC by 1 to include the current sector in the allocation count.
4A15
POP DE D1
Restore Register Pair DE from the stack (directory entry pointer from 4A0CH).
4A16
INC DE 13
INCrement Register Pair DE to advance to the next byte of the directory entry.
4A17
INC BC 03
INCrement Register Pair BC by 1 again, adding one more sector for safety margin in the allocation.

4A18H - Extent Allocation - GAT Sector Search

Searches the Granule Allocation Table (GAT) to find free granules for the file extent allocation. Walks through the directory entry's extent slots and the GAT sectors at 4D00H to locate and allocate free disk space. This routine handles the complex task of finding contiguous free granules, updating the directory entries, and writing the updated GAT back to disk.

4A18
LD A,(IX+07H) DD 7E 07
Fetch the directory entry info byte from FCB offset IX+07H into Register A.
4A1B
AND 07H E6 07
MASK Register A with 07H (0000 0111) to extract the lower 3 bits, which encode the directory entry number within the sector (0-7).
4A1D
RLCA 07
Rotate Register A left. Begin computing the byte offset to this entry's extent data within the 4D00H buffer.
4A1E
RLCA 07
Rotate Register A left again. After two left rotations, A = entry_number * 4, which starts the offset calculation for the 32-byte directory entries.
4A1F
LD L,A 6F
Load Register L with Register A (the partial offset). This will be used to index into the 4D00H buffer.
4A20
LD H,4DH 26 4D
Load Register H with 4DH. HL now points into the 4D00H overlay/buffer area at the computed offset. The directory sector data was loaded here by the earlier CALL to 4AF0H.
4A22
PUSH BC C5
Save Register Pair BC (sectors to allocate count) onto the stack.
4A23
LD A,E 7B
Load Register A with Register E (low byte of the directory entry data pointer).
4A24
AND 1EH E6 1E
MASK Register A with 1EH (0001 1110) to extract the extent slot position. This identifies which of the extent entry slots in the directory entry is currently active.
4A26
CP 16H FE 16
Compare Register A against 16H (decimal 22). If the extent slot position equals 16H, all 5 extent slots (at offsets 16H, 19H, 1CH, 1FH, 22H) have been used, so a new directory sector is needed.
4A28
If the Z FLAG is set (all extent slots exhausted), JUMP to 4A6EH to search for a free GAT entry.
4A2A
DEC E 1D
DECrement Register E to back up to the previous byte in the extent data.
4A2B
DEC E 1D
DECrement Register E again. DE now points to the descriptor byte of the last used extent entry.
4A2C
LD A,(DE) 1A
Fetch the extent descriptor byte from (DE). Bits 0-4 contain the granule count for the last allocated extent.
4A2D
AND 1FH E6 1F
MASK Register A with 1FH to extract the granule count from the descriptor byte.
4A2F
INC A 3C
INCrement Register A to convert from 0-based to 1-based count.
4A30
LD C,A 4F
Load Register C with Register A (the granule count + 1).
4A31
CP 20H FE 20
Compare Register A against 20H (decimal 32). If the granule count equals 32, this extent is at maximum capacity and a new extent slot is needed.
4A33
If the Z FLAG is set (extent at maximum 32 granules), JUMP to 4A54H to advance to the next extent slot or GAT search.
4A35
LD A,(DE) 1A
Fetch the extent descriptor byte from (DE) again to get the track/granule bits.
4A36
AND E0H E6 E0
MASK Register A with E0H (1110 0000) to extract the upper 3 bits (track group identifier).
4A38
RLCA 07
Rotate A left to shift the track group bits.
4A39
RLCA 07
Rotate A left again.
4A3A
RLCA 07
Rotate A left a third time. The 3 track group bits are now in positions 0-2.
4A3B
ADD A,C 81
ADD Register C (the granule count) to Register A (the track group base). This computes the next granule position after the last allocated one.
4A3C
LD B,A 47
Load Register B with Register A (the target granule position in the GAT).
4A3D
SRL B CB 38
Shift Register B right logically (divide by 2). This converts the granule position to a byte offset within the GAT.
4A3F
AND 01H E6 01
MASK Register A with 01H to determine the bit position within the GAT byte (even or odd granule).
4A41
LD C,A 4F
Load Register C with Register A (the bit position: 0 = low nibble, 1 = high nibble of the GAT byte).
4A42
DEC DE 1B
DECrement Register Pair DE to point to the low byte of the extent's sector address.
4A43
LD A,(DE) 1A
Fetch the low byte of the extent's starting sector address from (DE).
4A44
INC DE 13
INCrement Register Pair DE back to the descriptor byte position.
4A45
ADD A,B 80
ADD Register B (the byte offset in GAT) to Register A (the starting sector). This computes the GAT byte offset for the next free granule to check.
4A46
LD L,A 6F
Load Register L with Register A (the GAT byte offset).
4A47
LD H,4DH 26 4D
Load Register H with 4DH. HL now points to the target byte in the GAT sector at 4D00H.
4A49
CP 23H FE 23
Compare Register A against 23H (decimal 35). If the GAT offset is past the end of the GAT data for this track, all granules on this track have been used.
4A4B
If the NO CARRY FLAG is set (offset >= 23H, past end of GAT track data), JUMP to 4A54H to search a different track.
4A4D
LD A,C 79
Load Register A with Register C (the bit position within the GAT byte).
4A4E
LD B,(HL) 46
Load Register B with the GAT byte from (HL). Each bit in this byte represents one granule (1 = allocated, 0 = free).
4A4F
GOSUB to 4B5DH, the bit test routine. Tests whether the granule at position A within GAT byte B is free (bit = 0). Returns Z if the bit is clear (granule free).
4A52
If the Z FLAG is set (the granule is free), JUMP to 4A9CH to allocate it.
4A54
INC E 1C
INCrement Register E to advance to the next extent slot in the directory entry.
4A55
INC E 1C
INCrement Register E again. Each extent slot is 3 bytes, but we are advancing by 2 here (the third increment is implicit from the pointer setup).
4A56
LD A,E 7B
Load Register A with Register E (the new extent slot position).
4A57
AND 1EH E6 1E
MASK Register A with 1EH to check the slot index.
4A59
CP 1EH FE 1E
Compare Register A against 1EH. If the slot position has reached 1EH, we have passed the last extent slot and need to chain to another directory sector.
4A5B
If the NZ FLAG is set (we have not exhausted all slots), JUMP to 4A6EH to continue the GAT search with the next extent slot.
4A5D
GOSUB to 4AB6H, the directory sector write routine. Write the current directory sector back to disk before loading the next one.
4A60
POP BC C1
Restore Register Pair BC (sectors to allocate count) from the stack.
4A61
RET NZ C0
If the NZ FLAG is set (the write returned an error), RETurn with the error code.
4A62
PUSH BC C5
Save Register Pair BC (allocation count) back onto the stack.
4A63
GOSUB to 4A6BH, the SVC stub for the GAT/directory write overlay (SVC code B4H). This invokes the overlay to handle directory chain extension.
4A66
POP BC C1
Restore Register Pair BC (allocation count) from the stack.
4A67
RET NZ C0
If the NZ FLAG is set (the overlay returned an error), RETurn with the error code.
4A68
JUMP to 49B5H to re-read the directory sector after the chain extension. The process continues from the beginning of the extent walk.

4A6BH - SVC Stub: GAT/Directory Write Overlay

A compact 2-byte SVC stub that invokes the GAT/directory write overlay via the RST 28H mechanism. SVC code B4H decodes to: function 5, file 2, directory sector 6.

4A6B
LD A,B4H 3E B4
Load Register A with B4H, the SVC code for the GAT/directory write overlay. B4H = 1011 0100 binary: bits 5-7 = 101 (function 5), bits 3-4 = 10 (file 2), bits 0-2 = 100 (directory sector 6).
4A6D
RST 28H EF
Invoke the overlay dispatcher via RST 28H. This loads and executes the GAT/directory write overlay, which handles extending the directory extent chain and writing the updated data to disk.

4A6EH - GAT Free Granule Search

Searches through the GAT data in the 4D00H buffer to find a free granule. Scans two pages of GAT data (page 0 starting at 4D00H, page 1 starting at 4D00H with L reset to 00H after the first pass). Each byte in the GAT represents 2 granules (one per nibble). When a free granule is found, jumps to the allocation routine at 4A9CH.

4A6E
LD B,02H 06 02
Load Register B with 02H. This is the page counter - the GAT is scanned in two passes.
4A70
LD A,L 7D
Load Register A with Register L (the current byte offset within the GAT page).
4A71
CP 23H FE 23
Compare Register A against 23H (decimal 35), the number of GAT data bytes per track entry.
4A73
If the NO CARRY FLAG is set (offset >= 23H, past end of track GAT data), JUMP to 4A7CH to advance to the next GAT page or return an error.
4A75
LD A,(HL) 7E
Fetch the GAT byte from (HL). Each bit represents one granule (1 = allocated, 0 = free).
4A76
INC A 3C
INCrement Register A by 1. If the GAT byte was FFH (all 8 granules allocated), A becomes 00H and Z is set.
4A77
If the NZ FLAG is set (the GAT byte is not FFH, meaning at least one granule is free), JUMP to 4A89H to find which specific granule is free.
4A79
INC L 2C
INCrement Register L to advance to the next GAT byte.
4A7A
LOOP BACK to 4A70H to check the next GAT byte.
4A7C
LD L,00H 2E 00
Load Register L with 00H, resetting the offset to the start of the GAT page for the second pass.
4A7E
DECrement Register B (page counter) and if not zero, LOOP BACK to 4A70H to scan the second GAT page.

No Free Granules Found
Both GAT pages have been scanned without finding a free granule. The disk is full. Write the current directory sector and return a "disk full" error.

4A80
POP BC C1
Restore Register Pair BC from the stack (discard the saved allocation count).
4A81
GOSUB to 4AB6H to write the directory sector back to disk.
4A84
RET NZ C0
If the NZ FLAG is set (the write returned an error), RETurn with the disk write error code.
4A85
LD A,1BH 3E 1B
Load Register A with 1BH, the TRSDOS error code for "disk full" (decimal 27).
4A87
OR A B7
OR Register A with itself. Since A = 1BH (non-zero), this sets the NZ flag to signal an error.
4A88
RET C9
RETurn to the caller with error code 1BH (disk full) and NZ flag set.

4A89H - Allocate Free Granule in GAT

A free granule has been found in the GAT byte at (HL). This routine identifies the specific free bit, marks it as allocated, updates the extent entry in the directory, and continues allocating until the required number of sectors has been satisfied.

4A89
LD A,FFH 3E FF
Load Register A with FFH. This will be stored as a chain terminator at the current extent entry position to mark the end of the allocation chain.
4A8B
LD (DE),A 12
Store FFH (end-of-chain marker) at the address pointed to by DE (the extent status byte in the directory entry).
4A8C
LD C,00H 0E 00
Load Register C with 00H. This initializes the granule count within the new extent entry.
4A8F
LD A,C 79
Load Register A with Register C (the current bit position within the GAT byte to test).
4A90
GOSUB to 4B5DH, the bit test routine. Tests whether granule at position C in GAT byte B is free. Returns Z if the bit is clear (free).
4A93
If the Z FLAG is set (the granule is free), JUMP to 4A9CH to allocate it.
4A95
LD A,(DE) 1A
Fetch the current extent status byte from (DE).
4A96
ADD 20H C6 20
ADD 20H to Register A. This advances the track group bits (bits 5-7) by one position, moving to the next group of granules.
4A98
LD (DE),A 12
Store the updated extent status byte back to (DE).
4A99
INC C 0C
INCrement Register C (the granule position counter).
4A9A
LOOP BACK to 4A8FH to test the next granule position.

4A9CH - Mark Granule as Allocated

Marks the found free granule as allocated in the GAT by setting the appropriate bit, updates the extent entry, and checks if more sectors need to be allocated. If more are needed, loops back to continue the allocation.

4A9C
LD A,C 79
Load Register A with Register C (the granule bit position).
4A9D
GOSUB to 4639H, the bit set routine. Converts the granule position in A to a bitmask for the GAT byte. Returns with A = bitmask.
4AA0
OR (HL) B6
OR Register A (the bitmask) with the GAT byte at (HL). This sets the bit for the allocated granule.
4AA1
LD (HL),A 77
Store the updated GAT byte back to (HL). The granule is now marked as allocated.
4AA2
DEC E 1D
DECrement Register E to point to the previous byte in the extent data.
4AA3
LD A,(DE) 1A
Fetch the byte from (DE) to check the extent low address byte.
4AA4
INC A 3C
INCrement Register A. If the byte was FFH (uninitialized/new entry), A becomes 00H.
4AA5
If the NZ FLAG is set (byte was not FFH, meaning the address is already initialized), JUMP over the initialization.
4AA7
LD A,L 7D
Load Register A with Register L (the GAT byte offset). This becomes the low byte of the extent's sector address for a new entry.
4AA8
LD (DE),A 12
Store Register A (the GAT offset) to (DE), setting the extent's low sector address byte.
4AA9
INC E 1C
INCrement Register E to advance back to the status byte of the extent entry.
4AAA
LD A,(DE) 1A
Fetch the extent status byte from (DE).
4AAB
INC A 3C
INCrement Register A. This updates the granule count in the status byte (bits 0-4 count granules allocated so far in this extent).
4AAC
LD (DE),A 12
Store the incremented status byte back to (DE).
4AAD
POP BC C1
Restore Register Pair BC (remaining sectors to allocate) from the stack.
4AAE
DEC BC 0B
DECrement Register Pair BC (remaining allocation count) by 1. One sector has been allocated.
4AAF
PUSH BC C5
Save Register Pair BC (updated remaining count) back onto the stack.
4AB0
LD A,B 78
Load Register A with Register B (high byte of remaining count).
4AB1
OR C B1
OR Register C with Register A. If both are zero, all sectors have been allocated.
4AB2
If the NZ FLAG is set (more sectors still needed), JUMP back to 4A2CH to continue the allocation loop.
4AB5
POP BC C1
Restore Register Pair BC from the stack (now zero, allocation complete). Clean up the stack.

4AB6H - Write Directory Sector to Disk

Writes the current directory sector from the 4D00H buffer back to disk. Loads the drive number from FCB offset IX+06H and calls the disk write routine.

4AB6
LD C,(IX+06H) DD 4E 06
Load Register C with the drive number from FCB offset IX+06H.
4AB9
GOSUB to 4B03H, the write-directory-sector-from-4D00H routine. Writes the directory sector from the 4D00H buffer to the disk drive specified in C.
4ABC
RET NZ C0
If the NZ FLAG is set (the write returned an error), RETurn with the error code in Register A.
4ABD
LD B,00H 06 00
Load Register B with 00H, setting up for the directory entry info value.
4ABF
JUMP to 4AD6H to continue with the GAT sector write operation.

4AC1H - Read Directory Sector into Buffer

Reads a directory sector from disk into the directory buffer. The directory sector to read is identified by Register B (directory entry info byte) and the drive by Register C. Returns HL pointing to the directory entry within the buffer.

4AC1
PUSH BC C5
Save Register Pair BC (B = directory entry info, C = drive) onto the stack.
4AC2
PUSH DE D5
Save Register Pair DE onto the stack.
4AC3
GOSUB to 4B1EH, which decodes the directory entry info byte in B to determine the directory track/sector and offset within the sector. Returns HL = directory buffer pointer, E = sector offset.
4AC6
If the NZ FLAG is set (the decode returned an error or invalid entry), JUMP to 4AD1H to return error code 11H.
4AC8
PUSH HL E5
Save Register Pair HL (directory buffer pointer) onto the stack.
4AC9
LD L,00H 2E 00
Load Register L with 00H. Set the buffer offset to 0, so the entire sector is read from the beginning.
4ACB
GOSUB to 4B35H, the read-sector-into-buffer routine. Reads the directory sector from disk into the buffer at the address in HL.
4ACE
POP HL E1
Restore Register Pair HL (directory buffer pointer) from the stack.
4ACF
If the Z FLAG is set (read successful), JUMP to 4AD3H to return normally.
4AD1
LD A,11H 3E 11
Load Register A with 11H, the TRSDOS error code for "disk read error" (decimal 17).
4AD3
POP DE D1
Restore Register Pair DE from the stack.
4AD4
POP BC C1
Restore Register Pair BC from the stack.
4AD5
RET C9
RETurn to the caller. A = 0 with Z flag set on success, or A = error code with NZ flag on failure. HL points to the directory entry within the buffer.

4AD6H - Write Directory and GAT Sectors

Writes the directory sector from the buffer area to disk, then writes the GAT sector. Used after extent allocation operations to commit both the updated directory entries and the updated granule allocation table.

4AD6
PUSH BC C5
Save Register Pair BC (B = directory entry info) onto the stack.
4AD7
PUSH DE D5
Save Register Pair DE onto the stack.
4AD8
GOSUB to 4B1EH to decode the directory entry info byte and determine the track/sector address.
4ADB
If the NZ FLAG is set (decode error), JUMP to 4AEDH to return without writing.
4ADD
LD L,00H 2E 00
Load Register L with 00H, setting the buffer offset to 0.
4ADF
GOSUB to 46EFH, the disk write routine. Writes the sector from the buffer to the disk.
4AE2
If the NZ FLAG is set (write error), JUMP to 4AEBH to set the write error code.
4AE4
GOSUB to 46F3H, the write verify routine. Reads back the sector just written and compares it to verify the write was successful.
4AE7
CP 06H FE 06
Compare Register A (the verify result) against 06H. A value of 06H indicates a successful verify (the FDC returned a clean status).
4AE9
If the Z FLAG is set (verify successful), JUMP to 4AEDH to return normally.
4AEB
LD A,12H 3E 12
Load Register A with 12H, the TRSDOS error code for "disk write error" (decimal 18).
4AED
POP DE D1
Restore Register Pair DE from the stack.
4AEE
POP BC C1
Restore Register Pair BC from the stack.
4AEF
RET C9
RETurn to the caller. A = 0 with Z flag on success, or A = error code with NZ flag on failure.

4AF0H - Read Directory Sector into 4D00H Buffer

Reads the file's directory sector into the 4D00H overlay/buffer area. This is used by the extent allocation routines that need the directory data in a known location.

4AF0
PUSH DE D5
Save Register Pair DE onto the stack.
4AF1
PUSH HL E5
Save Register Pair HL onto the stack.
4AF2
GOSUB to 4B55H, the track cache lookup routine. Retrieves the cached track position for drive C.
4AF5
LD E,00H 1E 00
Load Register E with 00H, setting the sector number to 0 (the directory sector on each track).
4AF7
LD HL,4D00H 21 00 4D
Point Register Pair HL to 4D00H, the overlay/buffer area where the directory sector will be loaded.
4AFA
GOSUB to 4B35H, the read-sector-into-buffer routine. Reads the directory sector into the buffer at 4D00H.
4AFD
POP HL E1
Restore Register Pair HL from the stack.
4AFE
POP DE D1
Restore Register Pair DE from the stack.
4AFF
RET Z C8
If the Z FLAG is set (read successful), RETurn with A = 0.
4B00
LD A,14H 3E 14
Load Register A with 14H, the TRSDOS error code for "seek error / track out of range" (decimal 20).
4B02
RET C9
RETurn with error code 14H and NZ flag set.

4B03H - Write Sector from 4D00H Buffer

Writes a sector from the 4D00H buffer to disk with verify. Similar to 4AF0H but performs a write-and-verify operation instead of a read.

4B03
PUSH DE D5
Save Register Pair DE onto the stack.
4B04
PUSH HL E5
Save Register Pair HL onto the stack.
4B05
GOSUB to 4B55H, the track cache lookup routine. Retrieves the cached track position for drive C.
4B08
LD E,00H 1E 00
Load Register E with 00H (sector 0 - the directory sector).
4B0A
LD HL,4D00H 21 00 4D
Point Register Pair HL to 4D00H, the source buffer for the write.
4B0D
GOSUB to 46EFH, the disk write routine. Writes the sector from 4D00H to disk.
4B10
If the NZ FLAG is set (write error), JUMP to 4B19H to set error code 15H.
4B12
GOSUB to 46F3H, the write verify routine. Verifies the write by reading back the sector.
4B15
CP 06H FE 06
Compare the verify result against 06H (successful verify status).
4B17
If the Z FLAG is set (verify successful), JUMP to 4B1BH to return normally.
4B19
LD A,15H 3E 15
Load Register A with 15H, an error code indicating a write/verify failure on the directory sector.
4B1B
POP HL E1
Restore Register Pair HL from the stack.
4B1C
POP DE D1
Restore Register Pair DE from the stack.
4B1D
RET C9
RETurn to the caller.

4B1EH - Decode Directory Entry Info Byte

Decodes the directory entry info byte (from IX+07H or passed in B) to determine the directory buffer address and sector offset. The entry info byte encodes both the directory sector number (bits 5-7) and the entry position within the sector (bits 0-2).

4B1E
GOSUB to 4B55H, the track cache lookup routine. Retrieves the cached track number for drive C into Register D.
4B21
LD A,B 78
Load Register A with Register B (the directory entry info byte).
4B22
AND E0H E6 E0
MASK Register A with E0H (1110 0000) to extract the directory sector offset from bits 5-7.
4B24
LD L,A 6F
Load Register L with Register A (the sector offset, positioned in the upper 3 bits).
4B25
LD H,42H 26 42
Load Register H with 42H. HL now points to 42xxH, where xx is the directory offset. The directory buffer area begins at 4200H.
4B27
XOR B A8
XOR Register A with Register B. Since A contained only bits 5-7 and B is the full info byte, the XOR isolates bits 0-4 (the entry number within the sector and additional info).
4B28
CP 08H FE 08
Compare Register A against 08H. If the entry number portion is less than 8, it is a valid directory entry position.
4B2A
If the NO CARRY FLAG is set (entry number >= 8, which is invalid), JUMP to 4B31H to return error code 10H.
4B2C
ADD 02H C6 02
ADD 02H to Register A (the entry number). This computes the directory track number (directory tracks start at track 2 on a TRSDOS disk).
4B2E
LD E,A 5F
Load Register E with Register A (the directory track/sector number).
4B2F
XOR A AF
Set Register A to ZERO and clear all flags. Z flag set to indicate success.
4B30
RET C9
RETurn with HL = directory buffer pointer, E = sector number, D = track number, A = 0, Z flag set (success).
4B31
LD A,10H 3E 10
Load Register A with 10H, an error code indicating an invalid directory entry number.
4B33
OR A B7
OR Register A with itself to set the NZ flag (error condition).
4B34
RET C9
RETurn with error code 10H and NZ flag set.

4B35H - Read Sector with Retry

Reads a sector from disk into the buffer pointed to by HL. Includes retry logic: if the first read fails with a non-fatal error, it re-reads the directory/sector header and retries. D = track, E = sector, C = drive, HL = buffer address.

4B35
GOSUB to 46DDH, the sector read routine. Reads the sector specified by D (track) and E (sector) from drive C into the buffer at HL.
4B38
SUB 06H D6 06
SUBtract 06H from Register A (the FDC status code). If the status was 06H (successful read), A becomes 0 and Z is set.
4B3A
RET Z C8
If the Z FLAG is set (read successful, status was 06H), RETurn with A = 0.
4B3B
ADD 06H C6 06
ADD 06H back to Register A, restoring the original status code.
4B3D
RET NZ C0
If the NZ FLAG is set (a non-recoverable error), RETurn with the error code. This catches status codes other than 06H and 00H.

Retry on Soft Error
The first read returned status 00H (which after SUB 06H and ADD 06H results in A = 0 with Z set from the ADD). This indicates a possible soft error. Retry by re-reading the sector header first, then re-reading the data.

4B3E
PUSH DE D5
Save Register Pair DE (track/sector) onto the stack.
4B3F
LD DE,0000H 11 00 00
Load Register Pair DE with 0000H. D = track 0, E = sector 0. Read the sector header/GAT from track 0 as part of the error recovery.
4B42
GOSUB to 46DDH to re-read sector 0 of track 0 (the GAT sector). This serves as a head recalibration.
4B45
POP DE D1
Restore Register Pair DE (original track/sector) from the stack.
4B46
RET NZ C0
If the NZ FLAG is set (the recalibration read also failed), RETurn with the error code. The disk has a hardware problem.
4B47
PUSH HL E5
Save Register Pair HL (buffer pointer) onto the stack.
4B48
INC HL 23
INCrement HL to point past the first byte of the buffer.
4B49
INC HL 23
INCrement HL again to point to the third byte.
4B4A
LD D,(HL) 56
Load Register D with the track number from the sector header data that was just read. The GAT sector contains track mapping information.
4B4B
LD A,04H 3E 04
Load Register A with 04H, which is the base offset for the track cache table at 4300H.
4B4D
ADD A,C 81
ADD Register C (drive number) to Register A. This computes the offset into the track cache table for the current drive.
4B4E
LD L,A 6F
Load Register L with Register A (the cache table offset).
4B4F
LD H,43H 26 43
Load Register H with 43H. HL now points to 43xxH, the track cache entry for this drive (table base at 4304H).
4B51
LD (HL),D 72
Store Register D (the track number from the sector header) into the track cache entry. This updates the cached track position for this drive.
4B52
POP HL E1
Restore Register Pair HL (original buffer pointer) from the stack.
4B53
LOOP BACK to 4B35H to retry the original sector read now that the head has been recalibrated.

4B55H - Track Cache Lookup

Looks up the cached track position for the drive specified in Register C. Returns the track number in Register D. The track cache is a small table at 4304H+ indexed by drive number, avoiding unnecessary seek operations.

4B55
LD A,04H 3E 04
Load Register A with 04H, the base offset within the 4300H area for the track cache table.
4B57
ADD A,C 81
ADD Register C (drive number, 0-3) to Register A. This computes the index into the track cache table.
4B58
LD L,A 6F
Load Register L with Register A (the cache table index).
4B59
LD H,43H 26 43
Load Register H with 43H. HL now points to 43xxH, the track cache entry for drive C.
4B5B
LD D,(HL) 56
Load Register D with the cached track number from (HL).
4B5C
RET C9
RETurn with D = cached track number for the specified drive.

4B5DH - GAT Bit Test/Set Utility

Converts a granule position number (0-7) in Register A to a BIT test instruction opcode and executes it dynamically via self-modifying code. The bit position is used to test or set a specific granule's allocation status in a GAT byte held in Register B. Returns Z if the tested bit is clear (granule free), NZ if set (granule allocated).

4B5D
AND 07H E6 07
MASK Register A with 07H to ensure the bit position is in range 0-7.
4B5F
RLCA 07
Rotate A left. Begin computing the CB-prefix BIT opcode for the desired bit position.
4B60
RLCA 07
Rotate A left again.
4B61
RLCA 07
Rotate A left a third time. The bit number is now in bits 3-5, which is the position field of the CB BIT/SET/RES instruction encoding.
4B62
OR 40H F6 40
OR Register A with 40H. This sets bit 6, converting the value into a CB-prefix BIT instruction opcode (CB 40-7F range tests bits 0-7 of a register).
4B64
LD (4B68H),A 32 68 4B
Self-Modifying Code
Store Register A (the constructed BIT opcode) to memory location 4B68H. This patches the instruction at 4B67H-4B68H to execute the correct BIT test on Register B.
4B67
BIT 0,B CB 40
Self-Modifying Code
The second byte of this instruction (40H at 4B68H) is overwritten at runtime by the computed BIT opcode. The instruction actually executed is BIT n,B where n is the granule bit position. Returns Z if the bit is clear (granule free), NZ if set (allocated).
4B69
RET C9
RETurn with Z/NZ flags reflecting whether the tested granule bit is clear or set.

4B6AH - 24-Bit Multiply (HL:A = DE x C)

Performs a 24-bit unsigned multiplication: multiplies the 16-bit value in DE by the 8-bit value in C. Returns the 24-bit result in HL (high 16 bits) and A (low 8 bits). Uses the standard shift-and-add algorithm.

4B6A
PUSH BC C5
Save Register Pair BC onto the stack.
4B6B
EX DE,HL EB
Exchange Register Pairs DE and HL. HL now holds the original DE (the multiplicand), and DE holds the original HL.
4B6C
LD C,A 4F
Load Register C with Register A (saving the multiplier for the shift-and-add loop).
4B6D
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H, initializing the result accumulator to zero.
4B70
LD A,L 7D
Load Register A with Register L (low byte of accumulator). A serves as the extended low byte of the 24-bit result.
4B71
LD B,08H 06 08
Load Register B with 08H (8 iterations for the 8-bit multiplier in C).
4B73
ADD HL,HL 29
ADD HL to itself (shift HL left by 1 bit). This is the high-word left shift of the accumulator.
4B74
RLA 17
Rotate A left through carry. This extends the shift into the low byte, propagating the carry from ADD HL,HL.
4B75
RLC C CB 01
Rotate Register C (the multiplier) left through carry. The high bit of C goes into carry, determining whether to add the multiplicand.
4B77
If the NO CARRY FLAG is set (this bit of the multiplier is 0), JUMP to 4B7CH to skip the addition.
4B79
ADD HL,DE 19
ADD Register Pair DE (the multiplicand) to Register Pair HL (the accumulator). This adds the multiplicand for this bit position.
4B7A
ADC 00H CE 00
ADD the carry from the ADD HL,DE to Register A (the extended low byte). This propagates overflow from the 16-bit addition into the 24-bit result.
4B7C
DECrement Register B (iteration counter) and if not zero, LOOP BACK to 4B73H for the next bit.
4B7E
LD C,A 4F
Load Register C with Register A (the low 8 bits of the 24-bit result).
4B7F
LD A,L 7D
Load Register A with Register L.
4B80
LD L,H 6C
Load Register L with Register H. Shift the result bytes down: HL becomes the upper 16 bits.
4B81
LD H,C 61
Load Register H with Register C (the low 8 bits). The 24-bit result is now arranged with H = low byte, L = middle byte, A = high byte (effectively a byte rotation of the result).
4B82
POP BC C1
Restore Register Pair BC from the stack.
4B83
RET C9
RETurn with the 24-bit multiplication result in HL:A.

4B84H - 16-Bit Divide (HL / A)

Divides the 16-bit value in HL by the 8-bit value in Register A. Returns the 16-bit quotient in HL and the remainder in A. Uses the standard restoring division algorithm with 16 iterations.

4B84
PUSH DE D5
Save Register Pair DE onto the stack.
4B85
LD D,A 57
Load Register D with Register A (the divisor). D holds the divisor throughout the division loop.
4B86
LD E,10H 1E 10
Load Register E with 10H (decimal 16). This is the iteration counter for the 16-bit division (one iteration per bit of the dividend).
4B88
XOR A AF
Set Register A to ZERO. A serves as the running remainder during the division.
4B89
ADD HL,HL 29
ADD HL to itself (shift the dividend left by 1 bit). The high bit of HL goes into the Carry flag.
4B8A
RLA 17
Rotate A left through carry. This shifts the high bit of HL into the low bit of A (the remainder), building up the remainder one bit at a time.
4B8B
If the CARRY FLAG is set (remainder overflowed past 8 bits), JUMP to 4B90H to subtract the divisor (the quotient bit is definitely 1).
4B8D
CP D BA
Compare Register A (remainder) against Register D (divisor). If A >= D, the quotient bit is 1.
4B8E
If the CARRY FLAG is set (remainder < divisor), JUMP to 4B92H to skip the subtraction. The quotient bit is 0.
4B90
SUB D 92
SUBtract Register D (divisor) from Register A (remainder). The quotient bit is 1.
4B91
INC L 2C
INCrement Register L. This sets the low bit of L to 1, recording the quotient bit. Since HL was shifted left at 4B89H, the new bit 0 was 0; setting it to 1 records that the divisor fit.
4B92
DEC E 1D
DECrement Register E (iteration counter) by 1.
4B93
If the NZ FLAG is set (more iterations remain), LOOP BACK to 4B89H for the next bit.
4B95
POP DE D1
Restore Register Pair DE from the stack.
4B96
RET C9
RETurn with HL = quotient, A = remainder.

4B97H - EOF Byte Offset Comparison

Compares the current byte offset (in Register C) against the EOF byte offset (IX+08H) to determine if the current position is at or past the end of file data within the last sector. Used by the EOF check routine at 48B9H.

4B97
LD A,(IX+08H) DD 7E 08
Fetch the EOF byte offset from FCB offset IX+08H. This value indicates the last valid byte position within the final sector of the file (00H = the sector is completely full, ending on a sector boundary).
4B9A
DEC A 3D
DECrement Register A by 1. Adjust the EOF offset for comparison against the zero-based current position.
4B9B
SUB C 91
SUBtract Register C (current byte offset) from Register A (adjusted EOF offset). If A >= C, the position is within the file data. If A < C, the position is past the end.
4B9C
CCF 3F
Complement the Carry flag. This inverts the carry from the SUB, so Carry is set when the position is within the file (A was >= C), and clear when past EOF.
4B9D
INC BC 03
INCrement Register Pair BC by 1. Adjust BC for the caller's needs (advancing the position counter).
4B9E
RET C9
RETurn with the Carry and Zero flags set based on the EOF comparison.

4B9FH - Padding / No-Error Return Stub

A short utility stub that returns with A = 0 and Z flag set (no error). The NOP at 4B9FH is padding.

4B9F
NOP 00
No operation. Padding byte.
4BA0
XOR A AF
Set Register A to ZERO and clear all flags. Z flag set to indicate no error.
4BA1
RET C9
RETurn with A = 0 and Z flag set (no error).

4BA2H - RST 28H Overlay Loader

This is the main overlay dispatcher, reached via the RST 28H vector at 400CH (JP 4BA2H). Register A contains the SVC (Supervisor Call) code that identifies which overlay to load and which function within it to execute. The SVC code is decoded as: bits 0-2 = directory sector minus 2, bits 3-4 = system file number within that sector, bits 5-7 = function number within the SYS file. If bit 7 of A is clear, it is not an overlay call but a direct transfer to the BREAK handler vector at 4312H. If bit 7 is set, the overlay is loaded from disk into the 4D00H buffer area and execution transfers to the overlay's entry point.

4BA2
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack. HL now holds the return address (the address after the RST 28H instruction in the caller), and the caller's HL is saved on the stack.
4BA3
POP HL E1
POP the caller's original HL value from the stack. The return address has been discarded - the overlay will handle returning to the correct location via the saved context at 430AH/430CH.
4BA4
OR A B7
OR Register A with itself. This tests bit 7 by setting the Sign flag: if bit 7 is set (A >= 80H), the P flag is clear (Sign is negative); if bit 7 is clear (A < 80H), the P flag is set (Sign is positive).
4BA5
If the P FLAG (Positive/Sign) is set (bit 7 of A was clear, meaning this is not an overlay call), JUMP to 4312H, the BREAK key handler vector. Non-overlay RST 28H calls are dispatched to the BREAK handler.

Overlay Call Processing
Bit 7 is set, confirming this is an overlay call. The SVC code in A identifies the overlay to load. First, check if the requested overlay is already loaded in memory (overlay caching) to avoid unnecessary disk I/O.

4BA8
PUSH HL E5
Save Register Pair HL (caller's original HL) onto the stack.
4BA9
LD H,A 67
Load Register H with Register A (the SVC code). H temporarily holds the full SVC code for comparison.
4BAA
LD A,(430EH) 3A 0E 43
Fetch the currently loaded overlay ID from memory location 430EH. This is the SVC code of the overlay that is currently resident in the 4D00H buffer.
4BAD
XOR H AC
XOR Register A (current overlay ID) with Register H (requested SVC code). If the lower 4 bits match (same overlay file), the result's lower 4 bits are zero.
4BAE
AND 0FH E6 0F
MASK the XOR result with 0FH to test only the lower 4 bits (the overlay file identifier). If the result is zero, the same overlay file is already loaded.
4BB0
LD A,H 7C
Load Register A with Register H (the requested SVC code). Restore A for use below.
4BB1
LD (430EH),A 32 0E 43
Store Register A (the requested SVC code) to 430EH, updating the currently loaded overlay ID regardless of whether a new load is needed.
4BB4
If the Z FLAG is set (the lower 4 bits matched, same overlay already loaded), JUMP to 4BEDH to skip the disk load and go directly to the overlay execution setup.

Load New Overlay From Disk
The requested overlay is not currently loaded. Save registers, compute the directory sector and entry for the overlay file, and load it from disk into the 4D00H buffer area.

4BB6
PUSH DE D5
Save Register Pair DE onto the stack.
4BB7
PUSH BC C5
Save Register Pair BC onto the stack.
4BB8
AND 0FH E6 0F
MASK Register A with 0FH to extract the overlay file number (bits 0-3 of the SVC code). This identifies which directory sector and entry contains the overlay.
4BBA
CP 08H FE 08
Compare Register A against 08H. Overlay numbers 0-7 are in the first set of directory sectors; 8-15 require an offset adjustment.
4BBC
If the CARRY FLAG is set (overlay number < 8), JUMP to 4BC0H to use the number directly.
4BBE
ADD 18H C6 18
ADD 18H (decimal 24) to Register A. For overlay numbers 8-15, this adjusts the directory entry offset to account for the layout of system files across directory sectors.
4BC0
LD B,A 47
Load Register B with Register A (the directory entry info byte for the overlay file).
4BC1
LD (44A7H),A 32 A7 44
Store Register A (the directory entry info) to memory location 44A7H. This saves the overlay's directory entry for later reference.
4BC4
XOR A AF
Set Register A to ZERO.
4BC5
LD (44A1H),A 32 A1 44
Store zero to 44A1H, the overlay execution state flag. This indicates the overlay has not yet been executed.
4BC8
SBC HL,HL ED 62
SUBtract HL from itself with carry. Since A was just set to 0 (clearing carry), this sets HL to 0000H.
4BCA
LD (44AAH),HL 22 AA 44
Store 0000H to 44AAH, clearing the overlay cache markers. This invalidates any cached overlay information.
4BCD
LD C,A 4F
Load Register C with Register A (00H). C = drive 0 (system drive) for loading the overlay.
4BCE
GOSUB to 4AC1H, the directory sector read routine. Reads the directory sector containing the overlay file's entry from drive 0. B = directory entry info, C = drive 0. Returns HL pointing to the directory entry.
4BD1
If the NZ FLAG is set (the directory read failed), JUMP to 4409H, the DOS Error Exit routine. A fatal error loading an overlay terminates to the DOS error handler.
4BD4
LD A,L 7D
Load Register A with Register L (low byte of directory buffer pointer).
4BD5
ADD 16H C6 16
ADD 16H (decimal 22) to Register A. This advances past the 22-byte directory entry header to the extent data area.
4BD7
LD L,A 6F
Load Register L with Register A. HL now points to the extent data for this overlay file's directory entry.
4BD8
LD A,(HL) 7E
Fetch the first extent byte from (HL). This is the low byte of the starting sector address for the overlay.
4BD9
INC L 2C
INCrement Register L to point to the high byte of the extent's sector address.
4BDA
LD H,(HL) 66
Load Register H with the high byte of the sector address from (HL).
4BDB
LD L,A 6F
Load Register L with Register A (the low byte). HL now holds the starting sector address of the overlay file on disk.
4BDC
LD (44AEH),HL 22 AE 44
Store Register Pair HL (the overlay's starting sector address) to 44AEH, the overlay load address variable.
4BDF
LD DE,44A0H 11 A0 44
Load Register Pair DE with 44A0H, the address of the overlay parameter block. This block is used by the program loader to control the loading process.
4BE2
GOSUB to 4C39H, the program/overlay loader core. This reads the overlay file from disk into the 4D00H buffer area using the parameters at 44A0H and the sector address from 44AEH.
4BE5
If the NZ FLAG is set (the overlay load failed), JUMP to 4409H, the DOS Error Exit. A fatal error.
4BE8
LD (4BFCH),HL 22 FC 4B
Self-Modifying Code
Store Register Pair HL (the overlay's entry point address, returned by the loader) to memory location 4BFCH. This patches the CALL instruction at 4BFBH with the correct entry point for the overlay.
4BEB
POP BC C1
Restore Register Pair BC from the stack.
4BEC
POP DE D1
Restore Register Pair DE from the stack.

Execute the Overlay
The overlay is now loaded at 4D00H and the entry point has been stored. Disable the BREAK key during overlay execution, then call the overlay's entry point. The SVC code in A tells the overlay which function to perform.

4BED
POP HL E1
Restore Register Pair HL (the caller's original HL) from the stack.
4BEE
LD A,(4315H) 3A 15 43
Fetch the BREAK key enable flag from memory location 4315H. A non-zero value means the BREAK key is enabled.
4BF1
LD (4C00H),A 32 00 4C
Self-Modifying Code
Store the current BREAK key enable state to 4C00H. This saves the BREAK state so it can be restored after the overlay returns.
4BF4
XOR A AF
Set Register A to ZERO.
4BF5
LD (4315H),A 32 15 43
Store zero to 4315H, disabling the BREAK key during overlay execution. This prevents the user from interrupting an overlay mid-operation, which could leave the disk in an inconsistent state.
4BF8
LD A,(430EH) 3A 0E 43
Fetch the currently loaded overlay ID from 430EH. This is the SVC code that was stored earlier, and it will be passed to the overlay as the function selector.
4BFB
CALL 0000H CD 00 00
Self-Modifying Code
GOSUB to the overlay's entry point. The target address at 4BFCH was patched by the code at 4BE8H with the actual entry point address of the loaded overlay. The overlay receives the SVC code in Register A and performs the requested function.
4BFE
PUSH AF F5
Save Register Pair AF (the overlay's return status) onto the stack.
4BFF
LD A,00H 3E 00
Self-Modifying Code
Load Register A with the saved BREAK key state from 4C00H. This byte was patched at 4BF1H with the original BREAK enable flag.
4C01
LD (4315H),A 32 15 43
Restore the BREAK key enable flag at 4315H to its original state before the overlay call.
4C04
POP AF F1
Restore Register Pair AF (the overlay's return status) from the stack.
4C05
RET C9
RETurn to the code that set up the overlay call. For SVC stubs (like those at 4400H, 441CH, etc.), this returns to the instruction after the RST 28H. The overlay's result in A and the flags determine the caller's next action.

4C06H - Load Program File

Entry point for loading a program file into memory. Reached via JP 4C06H at the 4434H jump table entry. Opens the file specified in the command buffer, loads it into RAM at the address specified in the file's directory entry, and optionally transfers execution to the loaded program if the system flag at 430FH has bit 7 set.

4C06
PUSH HL E5
Save Register Pair HL onto the stack. HL contains the return address for the caller.
4C07
GOSUB to 4430H, the "Open/create and load file" SVC stub. This opens the program file and loads it into memory at the address specified in the file's directory entry.
4C0A
If the NZ FLAG is set (the load failed), JUMP to 4409H, the DOS Error Exit routine.
4C0D
EX (SP),HL E3
Exchange Register Pair HL with the value on top of the stack. HL now holds the original return address, and the load address (returned by the loader) is on the stack.
4C0E
LD A,(430FH) 3A 0F 43
Fetch the system flags byte from memory location 430FH. Bit 7 controls whether to execute the loaded program or return to the caller.
4C11
OR A B7
OR Register A with itself to set the Sign flag based on bit 7.
4C12
If the M FLAG (Minus/Sign) is set (bit 7 of 430FH = 1, meaning "execute after load"), JUMP to 400FH which is the RST 30H vector. This transfers control to the loaded program through the DEBUG/execution hook.
4C15
RET C9
RETurn to the caller. The program has been loaded but not executed (return-to-caller mode).

4C16H - Open and Load File from Disk

Opens an existing file and loads it into RAM. Reached via JP 4C16H at the 4431H jump table entry. Uses the 4D00H buffer as the FCB area and calls the file open and program load routines.

4C16
LD B,00H 06 00
Load Register B with 00H. B = 0 specifies that no special access flags are needed when opening the file.
4C18
LD HL,4D00H 21 00 4D
Point Register Pair HL to 4D00H, the overlay/buffer area. This will be used as the FCB work area for opening the file.
4C1B
GOSUB to 4424H, the "Open existing file" SVC stub. Opens the file specified in the command line buffer. Returns Z on success, NZ on error.
4C1E
If the Z FLAG is set (file opened successfully), JUMP to 4C29H to proceed with loading.
4C20
SET 6,A CB F7
SET bit 6 of Register A (the error code). This modifies the error code to indicate that the error occurred during a file open for program load.
4C22
CP 58H FE 58
Compare Register A against 58H. If the modified error code is not 58H (which would be a specific "file not found for load" error), return the error.
4C24
RET NZ C0
If the NZ FLAG is set (error code is not 58H), RETurn with the error code.
4C25
LD A,5FH 3E 5F
Load Register A with 5FH, a specific error code for "file not found during program load" (decimal 95).
4C27
OR A B7
OR Register A with itself to set the NZ flag.
4C28
RET C9
RETurn with error code 5FH and NZ flag set.

File Opened Successfully
The file is open. Now verify it is a loadable program type and load it into RAM at the address specified in its directory entry.

4C29
INC DE 13
INCrement Register Pair DE to point to the next byte of the directory entry data returned by the open call.
4C2A
LD A,(DE) 1A
Fetch the file type byte from the directory entry. This indicates the type of file (system, BASIC, machine code, etc.).
4C2B
DEC DE 1B
DECrement Register Pair DE back to the original position.
4C2C
AND 07H E6 07
MASK Register A with 07H to extract the file type field (bits 0-2). Valid loadable program types are typically 02H-05H.
4C2E
CP 06H FE 06
Compare Register A against 06H. Types 00H-05H are valid loadable types; type 06H and above are not loadable.
4C30
If the CARRY FLAG is set (file type < 06H, a valid loadable type), JUMP to 4C39H to proceed with loading.
4C32
LD A,(430FH) 3A 0F 43
Fetch the system flags from 430FH. Check if special load permission is granted for non-standard file types.
4C35
RLCA 07
Rotate Register A left, moving bit 7 into carry. Bit 7 of 430FH indicates special load permission.
4C36
LD A,25H 3E 25
Load Register A with 25H, the error code for "access denied" (decimal 37). Prepare for a potential error return.
4C38
RET C D8
If the CARRY FLAG is set (bit 7 of 430FH was set, meaning no special permission), RETurn with error code 25H. The file type is not loadable and permission is not granted.

4C39H - Program Loader Core

The core file loading routine. Reads the program file sector by sector from disk into RAM at the address specified in the directory entry. Handles the TRSDOS load format: type 01H = data block (count, address, data), type 02H = entry point, type 20H+ = skip block. Returns with HL = entry point address and Z flag set on success.

4C39
LD BC,4DFFH 01 FF 4D
Load Register Pair BC with 4DFFH. B = 4DH (the page for sector data), C = FFH (the byte offset counter, initialized to end-of-sector to force an immediate sector read on the first byte fetch).
4C3C
GOSUB to 4C8CH, the "get next byte from file" routine. Reads the next byte of the program file, automatically loading new sectors as needed. Returns the byte in Register A.
4C3F
CP 01H FE 01
Compare Register A against 01H. Type 01H indicates a data block header: the next bytes specify a byte count, load address, and data.
4C41
If the Z FLAG is set (type 01H data block), JUMP to 4C67H to process the data block.
4C43
CP 02H FE 02
Compare Register A against 02H. Type 02H indicates an entry point record: the next 2 bytes specify the execution start address.
4C45
If the Z FLAG is set (type 02H entry point), JUMP to 4C5AH to read the entry point address.
4C47
CP 20H FE 20
Compare Register A against 20H. Types 20H and above indicate comment/filler blocks that should be skipped.
4C49
If the CARRY FLAG is set (type < 20H but not 01H or 02H), JUMP to 4C4FH. This is an unrecognized block type but still has a length to skip.
4C4B
LD A,22H 3E 22
Load Register A with 22H, the error code for "invalid module type" (decimal 34).
4C4D
OR A B7
OR Register A with itself to set the NZ flag.
4C4E
RET C9
RETurn with error code 22H (invalid module type) and NZ flag set.

Skip Unknown Block
The block type is unrecognized but has a standard length header. Read the length byte and skip that many bytes.

4C4F
GOSUB to 4C8CH to read the next byte (the block length).
4C52
LD B,A 47
Load Register B with Register A (the block length - number of bytes to skip).
4C53
GOSUB to 4C8CH to read and discard the next byte of the block.
4C56
DECrement Register B and if not zero, LOOP BACK to 4C53H to skip the remaining bytes in the block.
4C58
JUMP back to 4C3CH to process the next block in the file.

4C5AH - Process Entry Point Record (Type 02H)

Reads the entry point record: a 2-byte header followed by a 2-byte entry point address. The entry point is returned in HL for use by the caller.

4C5A
GOSUB to 4C8CH to read the next byte (the record length byte, which is always 02H for an entry point record).
4C5D
GOSUB to 4C8CH to read the low byte of the entry point address.
4C60
LD L,A 6F
Load Register L with Register A (low byte of entry point address).
4C61
GOSUB to 4C8CH to read the high byte of the entry point address.
4C64
LD H,A 67
Load Register H with Register A (high byte of entry point address). HL now holds the program's execution start address.
4C65
XOR A AF
Set Register A to ZERO and clear all flags. Z flag set to indicate successful load.
4C66
RET C9
RETurn with HL = entry point address, A = 0, Z flag set (success). The caller can now transfer execution to HL if desired.

4C67H - Process Data Block (Type 01H)

Reads a data block record: byte count, 2-byte load address, then the data bytes. Each data byte is written to memory at the load address and verified. If a write fails (possibly ROM or non-existent memory), returns an error.

4C67
GOSUB to 4C8CH to read the block length byte.
4C6A
LD B,A 47
Load Register B with Register A (the number of data bytes in this block).
4C6B
GOSUB to 4C8CH to read the low byte of the load address.
4C6E
LD L,A 6F
Load Register L with Register A (low byte of load address).
4C6F
DEC B 05
DECrement Register B by 1 (the load address bytes are included in the block length count).
4C70
GOSUB to 4C8CH to read the high byte of the load address.
4C73
LD H,A 67
Load Register H with Register A (high byte of load address). HL now holds the target address in RAM for this data block.
4C74
DEC B 05
DECrement Register B by 1 again (second address byte counted).

Data Transfer Loop
Read each data byte from the file and store it to the target address in RAM. After storing, verify that the byte was written correctly by reading it back. This detects writes to ROM or non-existent memory.

4C75
GOSUB to 4C8CH to read the next data byte from the file.
4C78
LD (HL),A 77
Store Register A (the data byte) to the target address in RAM pointed to by HL.
4C79
CP (HL) BE
Compare Register A against the value at (HL). This verifies the write by reading back the stored byte. If the memory is ROM or absent, the values will not match.
4C7A
If the NZ FLAG is set (write verification failed - the byte read back does not match), JUMP to 4C81H to try a workaround.
4C7C
INC HL 23
INCrement Register Pair HL to point to the next target address.
4C7D
DECrement Register B and if not zero, LOOP BACK to 4C75H to store the next data byte.
4C7F
JUMP back to 4C3CH to process the next block in the file.

Write Verification Failed
The data byte could not be written to the target address. Try complementing the byte and writing it, then check again. This handles the case where the memory might be write-protected or the value at the address happens to be the complement of what was expected.

4C81
LD A,(HL) 7E
Fetch the value currently at the target address (HL).
4C82
CPL 2F
Complement Register A (invert all bits).
4C83
LD (HL),A 77
Store the complemented value to (HL).
4C84
CP (HL) BE
Compare Register A against the value at (HL). If the complemented value sticks, the memory is writable - the original write just had a coincidental read-back match failure.
4C85
LD A,63H 3E 63
Load Register A with 63H. This is error code 63H for "memory write error" (decimal 99) - the address is not writable RAM.
4C87
RET NZ C0
If the NZ FLAG is set (the complement also failed to write, confirming non-writable memory), RETurn with error code 63H.
4C88
LD A,64H 3E 64
Load Register A with 64H, error code for "memory write verify error" (decimal 100) - the memory is writable but the original value did not stick.
4C8A
OR A B7
OR Register A with itself to set the NZ flag.
4C8B
RET C9
RETurn with error code 64H and NZ flag set.

4C8CH - Get Next Byte From Program File

Reads the next sequential byte from the program file being loaded. Maintains a sector buffer at 4D00H (page 4DH) with C as the byte offset within the sector. When C overflows past FFH (the sector is exhausted), automatically reads the next sector from disk. Returns the byte in Register A.

4C8C
INC C 0C
INCrement Register C (the byte offset within the current sector) by 1. If C was FFH, it wraps to 00H and sets the Z flag, indicating the sector buffer is exhausted.
4C8D
If the NZ FLAG is set (the sector buffer still has bytes remaining), JUMP to 4CA3H to fetch the next byte directly from the buffer.

Sector Buffer Exhausted
The current sector has been fully consumed (C wrapped from FFH to 00H). Read the next sector from disk into the 4D00H buffer using the file I/O read routine.

4C8F
PUSH DE D5
Save Register Pair DE onto the stack.
4C90
EX (SP),IX DD E3
Exchange IX with the value on the stack. This saves IX and loads DE (the FCB pointer) into IX so that the sector read routine can access the FCB.
4C92
PUSH HL E5
Save Register Pair HL (the current load address) onto the stack.
4C93
PUSH DE D5
Save Register Pair DE (the original IX value) onto the stack.
4C94
PUSH BC C5
Save Register Pair BC (B = page, C = offset) onto the stack.
4C95
GOSUB to 481BH, the sector read routine for sequential file access. Reads the next sector of the file into the buffer. Uses IX as the FCB pointer.
4C98
POP BC C1
Restore Register Pair BC from the stack.
4C99
POP DE D1
Restore Register Pair DE from the stack.
4C9A
POP HL E1
Restore Register Pair HL from the stack.
4C9B
POP IX DD E1
Restore the IX register from the stack (the original IX value saved via EX (SP),IX).
4C9D
If the Z FLAG is set (sector read successful), JUMP to 4CA3H to fetch the first byte of the new sector.

Sector Read Error During Load
The sector read failed. Pop the program loader's return address from the stack (to unwind back to the original caller) and return the error.

4C9F
POP BC C1
Discard the return address of the CALL 4C8CH from the stack, unwinding back to the program loader's caller.
4CA0
OR 40H F6 40
OR Register A (the error code) with 40H. This sets bit 6, marking the error as occurring during a program load operation.
4CA2
RET C9
RETurn to the program loader's caller with the modified error code and NZ flag set.

Fetch Byte From Buffer
The sector buffer at 4D00H+C contains the current byte. Load it from page B (= 4DH) at offset C.

4CA3
PUSH BC C5
Save Register Pair BC onto the stack (B = page 4DH, C = current offset).
4CA4
LD B,4DH 06 4D
Load Register B with 4DH. B is used as the high byte of the buffer address (page 4DH = 4D00H).
4CA6
LD A,(BC) 0A
Fetch the byte from the address BC (= 4D00H + C offset). This reads the current byte from the sector buffer.
4CA7
POP BC C1
Restore Register Pair BC from the stack.
4CA8
RET C9
RETurn with the byte in Register A.

4CA9H - Clock/Timer/Date Display Routines

4CA9H - Timer Interrupt Dispatch Table

A 2-byte address table used by the real-time clock interrupt handler. This contains the entry point address for the time display conversion routine at 4CACH.

4CA9
DEFW 4CACH AC 4C
Address word: 4CACH (low byte ACH, high byte 4CH). This points to the clock tick cascade routine at 4CACH.

4CABH - Decrement Timer Tick Counter

Called from the real-time clock interrupt. Decrements the prescaler counter at IX+02H. When the counter reaches zero, it reloads the prescaler value (02H for a 2:1 divide) and falls through to the time-to-ASCII conversion routine. This scales the hardware timer interrupt rate down to once per second.

4CAB
DEC B 05
DECrement Register B by 1. B acts as a secondary prescaler counter.
4CAC
DEC (IX+02H) DD 35 02
DECrement the byte at IX+02H (the clock prescaler counter) by 1. This counter divides the interrupt rate by its initial value.
4CAF
RET NZ C0
If the NZ FLAG is set (the prescaler has not yet counted down to zero), RETurn. No display update needed on this tick.
4CB0
LD (IX+02H),05H DD 36 02 05
Reload the prescaler counter at IX+02H with 05H. The prescaler divides the interrupt rate by 5, so this routine updates the display every 5 interrupts.

4CB4H - Convert Time to HH:MM:SS Display

Converts the binary time values stored at 4043H (hours), 4042H (minutes), 4041H (seconds) into ASCII digits and writes them to the video RAM at 3C35H-3C3CH in HH:MM:SS format. The colon separators are embedded in the display. Also handles date conversion when called via 4CD2H.

4CB4
LD HL,3C35H 21 35 3C
Point Register Pair HL to 3C35H, the video RAM address where the time display starts. On the Model I, 3C00H is the start of video RAM, and 3C35H is the clock display position on the screen.
4CB7
LD DE,4043H 11 43 40
Point Register Pair DE to 4043H, the hours byte of the time-of-day clock. The time is stored as: 4043H = hours, 4042H = minutes, 4041H = seconds (decreasing order).
4CBA
LD C,3AH 0E 3A
Load Register C with 3AH, the ASCII code for colon (:). This is the separator character between hours, minutes, and seconds.
4CBC
LD B,03H 06 03
Load Register B with 03H. This is the field counter: 3 fields (hours, minutes, seconds) to convert and display.

Binary-to-ASCII Conversion Loop
For each time field: read the binary value from (DE), divide it into tens and units digits by repeated subtraction, write the two ASCII digits to video RAM at (HL), and insert the separator character. DE decrements through hours → minutes → seconds.

4CBE
LD A,(DE) 1A
Fetch the current time field value (hours, minutes, or seconds) from the address pointed to by DE.
4CBF
DEC DE 1B
DECrement Register Pair DE to point to the next time field (hours → minutes → seconds, since they are stored in descending address order).
4CC0
LD (HL),2FH 36 2F
Store ASCII '/' (2FH) to video RAM at (HL). This is one less than ASCII '0' (30H), serving as the starting point for the tens digit. The INC at 4CC2H will immediately bring it to '0'.
4CC2
INC (HL) 34
INCrement the byte at (HL), advancing the tens digit by 1 (from '/' to '0', then '0' to '1', etc.).
4CC3
SUB 0AH D6 0A
SUBtract 10 (0AH) from Register A (the time field value). This removes one tens-digit worth of the value.
4CC5
If the NO CARRY FLAG is set (A >= 0, more tens digits to subtract), LOOP BACK to 4CC2H to increment the tens digit again.
4CC7
ADD 3AH C6 3A
ADD 3AH (58 decimal) to Register A. Since A went negative by at most -10 (from SUB 0AH) and the units digit is in the range 0-9, adding 3AH converts the negative remainder back to ASCII '0'-'9'. (If A = -1, then -1 + 3AH = 39H = '9'; if A = -10, then -10 + 3AH = 30H = '0').
4CC9
INC HL 23
INCrement Register Pair HL to the next video RAM position (the units digit position).
4CCA
LD (HL),A 77
Store Register A (the ASCII units digit) to video RAM at (HL).
4CCB
INC HL 23
INCrement Register Pair HL to the next video RAM position (the separator position).
4CCC
DEC B 05
DECrement Register B (field counter) by 1.
4CCD
RET Z C8
If the Z FLAG is set (all 3 fields have been converted and displayed), RETurn. The time display is complete.
4CCE
LD (HL),C 71
Store Register C (the separator character: colon 3AH for time, slash 2FH for date) to video RAM at (HL).
4CCF
INC HL 23
INCrement Register Pair HL to the next video RAM position.
4CD0
LOOP BACK to 4CBEH to convert and display the next time field.

4CD2H - Convert Date to MM/DD/YY Display

Converts the binary date values at 4046H (month), 4045H (day), 4044H (year) into ASCII and writes them to video RAM. Uses the same conversion loop as the time display but with a slash (/) separator and starting from the date storage area.

4CD2
LD DE,4046H 11 46 40
Point Register Pair DE to 4046H, the month byte of the stored date. The date is stored as: 4046H = month, 4045H = day, 4044H = year.
4CD5
LD C,2FH 0E 2F
Load Register C with 2FH, the ASCII code for slash (/). This is the separator character between month, day, and year fields.
4CD7
JUMP to 4CBCH to enter the binary-to-ASCII conversion loop. HL still points to the display position (set by the caller or continuing from the time display). The loop converts and displays three date fields with slash separators.

4CD9H - Hexadecimal Display Routine

Reads a 16-bit value from the stack (the caller's return environment) and displays it as 4 hexadecimal digits at video RAM address 3C2EH. This is used for diagnostic display of addresses or values. The IN A,(4CH) at the start reads the cassette port (possibly for timing or status), and the stack frame provides the value to display.

4CD9
IN A,(4CH) DB 4C
Read from I/O port 4CH (the cassette data port). The result is discarded; this may be used for timing synchronization or to acknowledge an interrupt.
4CDB
LD HL,0012H 21 12 00
Load Register Pair HL with 0012H (decimal 18). This is the stack frame offset to reach the value to be displayed.
4CDE
ADD HL,SP 39
ADD the Stack Pointer to Register Pair HL. HL now points to the 16-bit value within the stack frame that should be displayed.
4CDF
LD E,(HL) 5E
Load Register E with the low byte of the value from the stack frame.
4CE0
INC HL 23
INCrement Register Pair HL to point to the high byte.
4CE1
LD D,(HL) 56
Load Register D with the high byte of the value. DE now holds the 16-bit value to display.
4CE2
LD HL,3C2EH 21 2E 3C
Point Register Pair HL to 3C2EH, the video RAM position where the hex display will be written.
4CE5
LD A,D 7A
Load Register A with Register D (the high byte of the value). Display the high byte first (big-endian display).
4CE6
GOSUB to 4CEAH to display the high byte (D) as two hex digits at the current video position.
4CE9
LD A,E 7B
Load Register A with Register E (the low byte of the value). Now display the low byte.

Display One Byte as Two Hex Digits
The following code converts one byte in A to two ASCII hex digits and writes them to consecutive video RAM positions at (HL). The high nibble is displayed first, then the low nibble.

4CEA
PUSH AF F5
Save Register A (the full byte) onto the stack. The low nibble will be processed after the high nibble.
4CEB
RRA 1F
Rotate Register A right through carry. Shift the high nibble toward the low nibble position.
4CEC
RRA 1F
Rotate A right again.
4CED
RRA 1F
Rotate A right a third time.
4CEE
RRA 1F
Rotate A right a fourth time. The original high nibble is now in the low nibble position (bits 0-3).
4CEF
GOSUB to 4CF3H to convert the nibble in A to an ASCII hex character and write it to video RAM.
4CF2
POP AF F1
Restore Register A (the original byte) from the stack. The low nibble is now ready for conversion.
4CF3
AND 0FH E6 0F
MASK Register A with 0FH to isolate the low nibble (0-15).
4CF5
ADD 30H C6 30
ADD 30H (ASCII '0') to Register A. This converts values 0-9 to ASCII '0'-'9'. Values 10-15 will produce 3AH-3FH, which are not the correct hex letters yet.
4CF7
CP 3AH FE 3A
Compare Register A against 3AH (ASCII ':'). If A < 3AH, the digit is 0-9 and the ASCII code is correct.
4CF9
If the CARRY FLAG is set (A < 3AH, digit 0-9), JUMP to 4CFDH to write the character.
4CFB
ADD 07H C6 07
ADD 07H to Register A. This converts 3AH-3FH to 41H-46H (ASCII 'A'-'F'), completing the hex digit conversion for values 10-15.
4CFD
LD (HL),A 77
Store Register A (the ASCII hex character) to video RAM at (HL).
4CFE
INC HL 23
INCrement Register Pair HL to the next video RAM position.
4CFF
RET C9
RETurn to the caller.

4E00H - Cold Boot Entry Point (PROGRAM ENTRY)

This is the program entry point of the TRSDOS 2.3 SYS0 file, executed immediately after the boot sector loads SYS0 from disk. Disables interrupts, sets interrupt mode 1, initializes the stack pointer, and performs RAM size detection by writing and reading back a test pattern at 1KB page boundaries starting from the top of memory (FFFFH) downward. The detected RAM top address is stored at 4049H. After detection, jumps to the final boot setup at 4F20H.

4E00
DI F3
Disable all maskable interrupts. Critical during initialization to prevent any interrupt handler from executing before the interrupt vectors and system variables are set up.
4E01
IM 1 ED 56
Set the Z80 to Interrupt Mode 1. In this mode, the CPU executes RST 38H on any maskable interrupt, which is the standard mode for the TRS-80 Model I.
4E03
LD SP,41FCH 31 FC 41
Initialize the Stack Pointer to 41FCH. This is within the DOS work area, providing a temporary stack for boot initialization. The stack grows downward from 41FCH.
4E06
LD HL,5200H 21 00 52
Load Register Pair HL with 5200H, the initial high memory pointer. This is stored as the default user memory high address.
4E09
LD (4047H),HL 22 47 40
Store Register Pair HL (5200H) to 4047H, the high memory limit pointer. Programs are loaded below this address.
4E0C
LD HL,FFFFH 21 FF FF
Load Register Pair HL with FFFFH, the highest possible memory address. RAM detection starts here and works downward.

RAM Size Detection Loop
Tests whether RAM exists at the address in HL by writing the complement of the current byte and reading it back. If the written value persists, RAM exists at that address. If not (ROM or absent memory), the loop moves down by 1KB (400H) pages until writable RAM is found.

4E0F
LD A,(HL) 7E
Fetch the current byte at address (HL) into Register A.
4E10
LD B,A 47
Save the original byte value in Register B so it can be restored.
4E11
CPL 2F
Complement Register A (invert all bits). This creates a test pattern that is guaranteed to differ from the original value.
4E12
LD (HL),A 77
Write the complemented value to memory at (HL).
4E13
CP (HL) BE
Compare Register A with the value at (HL). If the complemented value was successfully written and read back, this address is writable RAM.
4E14
LD (HL),B 70
Restore the original byte value from Register B to memory at (HL), undoing the test write.
4E15
If the Z FLAG is set (the test value was read back correctly, confirming writable RAM exists), JUMP to 4E1DH to store the RAM top address.
4E17
LD A,H 7C
Load Register A with Register H (the high byte of the current test address).
4E18
SUB 04H D6 04
SUBtract 04H from Register A. This moves the test address down by 1KB (0400H bytes) to the next page boundary.
4E1A
LD H,A 67
Load Register H with Register A (the decremented high byte). HL now points 1KB lower.
4E1B
LOOP BACK to 4E0FH to test the next 1KB page.
4E1D
LD (4049H),HL 22 49 40
Store Register Pair HL (the highest writable RAM address) to 4049H, the system's detected RAM top pointer.
4E20
JUMP to 4F20H, which stores the DOS ready vector and then continues the boot process at 4E23H.

4E23H - Boot Continuation: System Initialization

Continues the boot process after RAM detection. Copies interrupt vectors and drive parameters from the boot sector data area (401xH-402xH) to the DOS work area (43xxH). Initializes the 4-drive parameter table at 4358H+ (8 bytes per drive from boot sector, followed by 8 bytes of zeros, then 16 bytes of FFH). Installs RST vector hooks, disables the BREAK key, clears the command buffer, sets the timer interrupt, displays the boot banner, and enters the command loop.

4E23
LD (43B8H),HL 22 B8 43
Store Register Pair HL to 43B8H. HL still holds the RAM top address from the detection or the value passed by the boot jump. This stores the system's stack base.
4E26
LD (43C2H),HL 22 C2 43
Store Register Pair HL to 43C2H, the second copy of the stack/memory limit.
4E29
LD HL,(401BH) 2A 1B 40
Fetch the word at 401BH from the boot sector parameter area. This contains an interrupt/system vector address.
4E2C
LD (43C0H),HL 22 C0 43
Store it to 43C0H in the DOS work area.
4E2F
LD HL,(401EH) 2A 1E 40
Fetch the word at 401EH (another boot sector parameter).
4E32
LD (43BAH),HL 22 BA 43
Store it to 43BAH.
4E35
LD (43C6H),HL 22 C6 43
Store it to 43C6H (duplicate copy).
4E38
LD HL,(4023H) 2A 23 40
Fetch the word at 4023H.
4E3B
LD (43C4H),HL 22 C4 43
Store it to 43C4H.
4E3E
LD HL,(4026H) 2A 26 40
Fetch the word at 4026H.
4E41
LD (43BCH),HL 22 BC 43
Store it to 43BCH.
4E44
LD (43CAH),HL 22 CA 43
Store it to 43CAH (duplicate copy).
4E47
LD HL,(402BH) 2A 2B 40
Fetch the word at 402BH.
4E4A
LD (43C8H),HL 22 C8 43
Store it to 43C8H. This completes the copy of boot sector parameters into the DOS work area at 43B8H-43C9H.
4E4D
LD HL,43CCH 21 CC 43
Point Register Pair HL to 43CCH, the start of a work area to be cleared.
4E50
LD (HL),00H 36 00
Store 00H to (HL), clearing the first byte.
4E52
INC L 2C
INCrement Register L. Since the area is within page 43H, this advances through 43CCH-43FFH.
4E53
If the NZ FLAG is set (L has not wrapped to 00H, i.e., still within 43xxH page), JUMP to 4E55H. Note: This is a 2-byte NOP (JR to the next instruction) - the condition is always true until L wraps, but the next instruction is the target anyway. This clears 43CCH through 43FFH.

Drive Parameter Table Initialization
Initialize the 4-drive parameter table at 4358H. For each of up to 4 drives: copy 8 bytes of drive parameters from the boot sector (starting at 4015H), zero 8 bytes of status, then fill 16 bytes with FFH (empty directory markers).

4E55
LD A,03H 3E 03
Load Register A with 03H. This is the drive counter minus 1 (for drives 0-3, but this may indicate only 3 iterations if the first drive was already handled, or a 0-based index).
4E57
LD HL,4015H 21 15 40
Point Register Pair HL to 4015H, the start of drive parameter data in the boot sector area.
4E5A
LD DE,4358H 11 58 43
Point Register Pair DE to 4358H, the DOS drive parameter table destination.
4E5D
PUSH AF F5
Save Register A (drive counter) onto the stack.
4E5E
LD BC,0008H 01 08 00
Load Register Pair BC with 0008H (8 bytes to copy from boot sector parameters).
4E61
LDIR ED B0
Block copy 8 bytes from (HL) to (DE), both incrementing. Copies the drive parameter data from the boot sector to the DOS table.
4E63
XOR A AF
Set Register A to ZERO.
4E64
LD B,08H 06 08
Load Register B with 08H (8 zero bytes to store as the drive status area).
4E66
LD (DE),A 12
Store 00H to (DE), clearing one byte of the status area.
4E67
INC DE 13
INCrement Register Pair DE to the next byte.
4E68
DECrement Register B and if not zero, LOOP BACK to 4E66H. Zeros out 8 bytes of status data.
4E6A
LD A,FFH 3E FF
Load Register A with FFH (empty directory marker).
4E6C
LD B,10H 06 10
Load Register B with 10H (16 bytes of FFH to write as the directory extent cache).
4E6E
LD (DE),A 12
Store FFH to (DE).
4E6F
INC DE 13
INCrement Register Pair DE.
4E70
DECrement Register B and if not zero, LOOP BACK to 4E6EH. Fills 16 bytes with FFH.
4E72
POP AF F1
Restore Register A (drive counter) from the stack.
4E73
DEC A 3D
DECrement Register A (drive counter) by 1.
4E74
If the NZ FLAG is set (more drives to initialize), JUMP to 4E76H. Note: this is another 2-byte NOP (JR to the next instruction). The actual loop control is below, but the assembler generated this as a placeholder.

System Vector Installation
Install the RST vector hooks and interrupt service routine addresses into the RAM-based vector table at 4012H-405BH. These vectors allow TRSDOS to intercept key system calls (keyboard scan, character output, file I/O) through the RST instruction mechanism.

4E76
LD A,(37ECH) 3A EC 37
Fetch the byte at 37ECH. This is a system configuration byte in the ROM parameter area (or a bootstrap parameter). Its value is read but not used - the XOR A that follows discards it.
4E79
XOR A AF
Set Register A to ZERO. Discard the value just read from 37ECH.
4E7A
LD (4315H),A 32 15 43
Store 00H to 4315H, the BREAK key enable flag. This disables the BREAK key during boot.
4E7D
LD HL,404CH 21 4C 40
Point Register Pair HL to 404CH, the system capability flags byte. This byte controls which DOS features are enabled.
4E80
LD (HL),A 77
Store 00H to 404CH, clearing all capability flags initially.
4E81
LD A,C3H 3E C3
Load Register A with C3H, which is the Z80 opcode for JP (unconditional jump). This is used to construct JP instructions at the RST vector locations.
4E83
LD DE,4518H 11 18 45
Load Register Pair DE with 4518H, the address of the RST 10H handler (keyboard scan routine).
4E86
LD (4013H),DE ED 53 13 40
Store 4518H to 4013H-4014H, setting the RST 10H vector target address. RST 10H at 4012H-4014H becomes JP 4518H.
4E8A
LD (4012H),A 32 12 40
Store C3H (JP opcode) to 4012H, completing the RST 10H vector: JP 4518H.
4E8D
LD DE,4560H 11 60 45
Load Register Pair DE with 4560H, the address of the RST 18H handler (character output routine).
4E90
LD (405BH),DE ED 53 5B 40
Store 4560H to 405BH-405CH, setting the vector for the character output handler at 405AH (JP 4560H).
4E94
SET 7,(HL) CB FE
SET bit 7 of the byte at (HL) (404CH, the capability flags). Bit 7 indicates the keyboard interrupt handler is installed.
4E96
LD DE,4669H 11 69 46
Load Register Pair DE with 4669H, the address of the disk I/O handler.
4E99
LD (4059H),DE ED 53 59 40
Store 4669H to 4059H-405AH, setting the disk I/O vector.
4E9D
SET 6,(HL) CB F6
SET bit 6 of 404CH (capability flags). Bit 6 indicates the disk I/O handler is installed.
4E9F
LD A,01H 3E 01
Load Register A with 01H.
4EA1
LD (430FH),A 32 0F 43
Store 01H to 430FH, the system mode flag. Value 01H indicates the system is in "command mode" (ready to accept user commands).
4EA4
LD DE,45AFH 11 AF 45
Load Register Pair DE with 45AFH, the address of the real-time clock interrupt service routine.
4EA7
LD A,07H 3E 07
Load Register A with 07H, the RST 38H sub-function code for installing a timer interrupt handler.
4EA9
GOSUB to 4410H, which installs the timer interrupt handler at DE (45AFH) using function code 07H. This enables the real-time clock display update.

Display Boot Banner
Display the "TRSDOS - DISK OPERATING SYSTEM - VER 2.3" boot banner on screen. The banner text is NEG-encoded at 4EEDH to obscure it from casual inspection. Each byte is negated (NEG) to recover the ASCII character, then output via the ROM character display routine at 0033H.

4EAC
LD HL,4EEDH 21 ED 4E
Point Register Pair HL to 4EEDH, the start of the NEG-encoded boot banner text.
4EAF
LD A,(HL) 7E
Fetch the next encoded banner byte from (HL).
4EB0
NEG ED 44
Negate Register A (two's complement). This decodes the encoded byte back to the original ASCII character. For example, ACH → NEG → 54H = 'T'.
4EB2
CALL 0033H CD 33 00
GOSUB to 0033H, the Model I ROM character display routine. Outputs the character in A to the current cursor position on screen.
4EB5
INC HL 23
INCrement Register Pair HL to point to the next encoded byte.
4EB6
CP 0DH FE 0D
Compare Register A (the decoded character) against 0DH (carriage return). The banner ends with a CR character.
4EB8
If the NZ FLAG is set (not yet at the end of the banner), LOOP BACK to 4EAFH to decode and display the next character.

Check for Auto-Execute Command
After displaying the banner, check if the boot disk has an auto-execute command stored in directory sector 11H at offset E0H. If so, copy it to the command buffer and execute it. If the auto-command is just a CR (0DH, empty), go directly to the DOS Ready prompt.

4EBA
CALL 002BH CD 2B 00
GOSUB to 002BH, the Model I ROM keyboard scan routine. Waits for a keypress or returns the last key pressed. This gives the user a brief moment to intervene before auto-execution.
4EBD
CP 0DH FE 0D
Compare Register A (the key pressed) against 0DH (ENTER key). If ENTER was pressed, skip auto-execution and go directly to the DOS Ready prompt.
4EBF
If the Z FLAG is set (ENTER was pressed), JUMP to 4400H, the DOS Ready/command entry point. This bypasses auto-execution.
4EC2
LD C,00H 0E 00
Load Register C with 00H (drive 0, the boot drive).
4EC4
LD D,11H 16 11
Load Register D with 11H (track 17, decimal). This is the directory track on a TRSDOS disk.
4EC6
LD E,00H 1E 00
Load Register E with 00H (sector 0 on the directory track).
4EC8
LD HL,4200H 21 00 42
Point Register Pair HL to 4200H, the directory buffer. The auto-command sector will be read here.
4ECB
GOSUB to 4B35H, the read-sector-with-retry routine. Reads directory sector 0 of track 17 from drive 0 into the 4200H buffer.
4ECE
If the NZ FLAG is set (the read failed), JUMP to 4409H, the DOS Error Exit.
4ED1
LD A,(42E0H) 3A E0 42
Fetch the first byte of the auto-execute command from 42E0H (offset E0H within the directory sector buffer at 4200H).
4ED4
CP 0DH FE 0D
Compare Register A against 0DH (carriage return). If the auto-command starts with CR, it is empty (no auto-execute).
4ED6
If the Z FLAG is set (empty auto-command), JUMP to 4400H, the DOS Ready prompt.
4ED9
LD HL,42E0H 21 E0 42
Point Register Pair HL to 42E0H, the source of the auto-execute command string within the directory buffer.
4EDC
LD DE,4318H 11 18 43
Point Register Pair DE to 4318H, the DOS command input buffer where the auto-command will be copied.
4EDF
LD BC,0020H 01 20 00
Load Register Pair BC with 0020H (32 bytes). This is the maximum length of the auto-execute command string.
4EE2
LDIR ED B0
Block copy 32 bytes from the directory sector (42E0H) to the command buffer (4318H). The entire auto-execute command is transferred.
4EE4
LD HL,4318H 21 18 43
Point Register Pair HL to 4318H, the command buffer containing the auto-execute command.
4EE7
GOSUB to 4467H, the DOS command parser. Parses and begins execution of the command in the buffer at HL.
4EEA
JUMP to 4405H, the DOS Ready prompt with error display. This returns to the command loop after the auto-execute command completes.

4EEDH - NEG-Encoded Boot Banner Data

This is the boot banner text, stored as NEG-encoded bytes to prevent the string from being trivially visible in a hex dump. Each byte is the two's complement (NEG) of the corresponding ASCII character. The display routine at 4EAFH reads each byte, applies NEG to recover the ASCII value, and outputs it via the ROM display routine. The decoded text reads: TRSDOS - DISK OPERATING SYSTEM - VER 2.3 followed by a line feed (0AH) and carriage return (0DH).

4EED
DEFM (NEG-encoded) "TRSDOS" AC AE AD BC B1 AD
NEG-encoded bytes: ACH→'T', AEH→'R', ADH→'S', BCH→'D', B1H→'O', ADH→'S'. Decodes to "TRSDOS".
4EF3
DEFM (NEG-encoded) " - " E0 D3 E0
NEG-encoded bytes: E0H→' ', D3H→'-', E0H→' '. Decodes to " - ".
4EF6
DEFM (NEG-encoded) "DISK" BC B7 AD B5
NEG-encoded bytes: BCH→'D', B7H→'I', ADH→'S', B5H→'K'. Decodes to "DISK".
4EFA
DEFM (NEG-encoded) " OPERATING SYSTEM" E0 B1 B0 BB AE BF AC B7 B2 B9 E0 AD A7 AD AC BB B3
Decodes to " OPERATING SYSTEM".
4F0B
DEFM (NEG-encoded) " - VER 2.3" E0 D3 E0 AA BB AE E0 CE D2 CD
Decodes to " - VER 2.3".
4F15
DEFM (NEG-encoded) " " + LF + CR E0 E0 F6 F3
E0H→' ', E0H→' ', F6H→0AH (line feed), F3H→0DH (carriage return). The CR terminates the banner display loop at 4EB6H.

4F20H - Final Boot Jump

Reached from the RAM detection code at 4E20H. Stores the DOS Ready vector address (43D8H) into the system return address at 4016H, then jumps to the main boot continuation at 4E23H to complete system initialization.

4F20
LD HL,43D8H 21 D8 43
Load Register Pair HL with 43D8H, the address of the DOS Ready / command loop entry point in the work area.
4F23
LD (4016H),HL 22 16 40
Store Register Pair HL (43D8H) to 4016H, the system return address variable. This ensures that warm restarts and error exits return to the DOS Ready prompt at 43D8H.
4F26
JUMP to 4E23H to continue the boot initialization process (vector installation, drive parameter setup, banner display, and command loop entry).