TRS-80 DOS - TRSDOS v1.3 - SYS02/SYS (Open/Init) Disassembled

This is a reconstructed and annotated disassembly of the TRSDOS 1.3 (Model III) SYS2 overlay. All source labels, comments, equate names, and routine descriptions are derived from the original Tandy source code (SYS2/SRC).

SYS2 loads from Track 16, Sector 7 into memory at 4E00H–5150H (825 bytes of code + 25 bytes of variables). It provides three SVC functions dispatched via RST 28H: OPEN (VECT10 = 90H), INIT (VECT20 = A0H), and SETTDC (VECT40 = C0H, password trap door code calculation).

Self-modifying code locations (EQU $+1 pattern): HCODE (4E75H), ATTRIB (4FCFH), S2LRL (500EH), PRIV (5011H), BLKBUF (501FH–5020H).

Disassembly

4E00H - START — Input Jump Vectors

ORG 4E00H
4E00START
AND 0F0H
Mask off low 4 bits of the SVC function code in Register A, leaving only the vector group identifier (bits 7–4).
4E02
CP 90H
Compare against VECT10 (90H) = OPEN function.
4E04
If match, jump to S2OPEN — open an existing file.
4E07
CP 0A0H
Compare against VECT20 (A0H) = INIT function.
4E09
If match, jump to S2INIT — initialize (create) a file.
4E0C
CP 0C0H
Compare against VECT40 (C0H) = SETTDC function.
4E0E
If match, jump to SETTDC — calculate a trap door code from a password.
4E11
RET
Unsupported function code; return to caller.

4E12H - S2OPEN / REOPEN — Open an Existing File

S2OPEN = Open an existing file. Entry: B = LRL (0 if not blocked), HL = blocking buffer address, DE = DCB (contains file name). Exit: Z set if open was good, A = error code.
REOPEN = EQU for S2OPEN (same address). Used when INIT calls back into the open logic.

4E12S2OPEN
Call SAVERN in SYS0 to save registers without DCB ownership check.

4E15H - OPENR — Open Inner Routine

Inner open routine, also called by S2INIT to attempt opening an existing file before creating a new one.

4E15OPENR
XOR A
Clear A.
4E16
Reset FLAG1 (overlay-in-memory flag) to 0, forcing the current overlay to be reloaded on next SVC call. This is necessary because SYS2 modifies its own code via self-modifying variables.
4E19
LD A,B
Get the requested Logical Record Length from Register B.
4E1A
Store the LRL into the self-modifying LD A,nn instruction at 500DH (S2LRL EQU $+1).
4E1D
Store the user blocking buffer address into the self-modifying LD DE,nnnn at 501EH (BLKBUF EQU $+1).
4E20
4E22
PUSH IX
POP HL
Transfer IX (DCB pointer) to HL for filespec parsing.
4E23
Call CRACK to parse the filespec from the DCB into NAME, EXT, PWORD, DRV, and SID fields.
4E26
RET NZ
Return with error if CRACK detected an invalid filespec.
4E27
LD HL,5141H
HL points to the NAME buffer (8 bytes filename + 3 bytes extension = 11 bytes).
4E2A
Call HASH to generate a hash code for the 11-byte name+extension. Returns hash in A (always non-zero).
4E2D
Store the hash code into the self-modifying CP nn instruction at 4E74H (HCODE EQU $+1). This allows the HIT scan loop to compare against the target hash without using a register.
4E30
LD DE,5139H
DE points to the PWORD buffer (8 bytes).
4E33
Call SETTDC to calculate a Trap Door Code from the password. Returns 16-bit TDC in HL.
4E36
Store the TDC as the Update password trap door code.
4E39
Store the same TDC as the Access password trap door code (both start equal; directory comparison will distinguish them).

Initialize the side number from the parsed filespec.

4E3C
Get the parsed side number from the SID variable (FFH = not specified).
4E3F
INC A
Test if SID was FFH (INC FFH = 00H, sets Z flag).
4E40
If no side was specified, skip to NOSIDE to use the global default.
4E42
XOR A
Clear A (= side 0).
4E43
Set the current side to 0 in the CSIDE system variable.

4E46H - NOSIDE — Initialize the Drive Number

Initializes the drive search from either the user-specified drive or the system default.

4E46NOSIDE
Get the parsed drive number from the DRV variable (FFH = not specified).
4E49
LD C,A
C = requested drive number.
4E4A
INC A
Test if DRV was FFH.
4E4B
If a specific drive was supplied, jump to OPENL to begin searching.
4E4D
No drive specified; get the default drive number from DIFULT.
4E50
LD C,A
C = default drive.

4E51H - OPENL — Check Drive(s) for the Requested File

Main loop that searches each drive for the file by reading the HIT (Hash Index Table) and scanning for a matching hash code.

4E51OPENL
Call CHKDRV to verify drive C exists and is ready.
4E54
If drive check failed, jump to OPNODR to try the next drive.
4E56
Call RDHIT in SYS0 to read the Hash Index Table sector into BUFF1 (4300H).
4E59
RET NZ
Return if disk read error.

Save BC, DE, HL before the LDIR block copy.

4E5A
PUSH BC
Save BC (drive/counter).
4E5B
PUSH DE
Save DE (DCB pointer).
4E5C
PUSH HL
Save HL.
4E5D
LD HL,4300H
HL = BUFF1 (sector buffer where RDHIT loaded the HIT).
4E60
LD DE,4D00H
DE = BUFF2 (working copy destination).
4E63
LD BC,0100H
BC = SECTOR (256 bytes).
4E66
LDIR
Copy the entire HIT sector from BUFF1 to BUFF2 so BUFF1 is free for directory reads.

Restore HL, DE, BC.

4E68
POP HL
Restore HL.
4E69
POP DE
Restore DE.
4E6A
POP BC
Restore BC.
4E6B
LD B,50H
B = MAXFIL (80 decimal) — maximum number of files per directory.
4E6D
LD HL,4D00H
HL = BUFF2 (the working copy of the HIT).

Top of the HIT scan loop. Scans up to 80 entries looking for a matching hash code.

4E70OPEN2
LD A,(HL)
Fetch the next HIT entry (hash code byte).
4E71
OR A
Test if the entry is zero (unused slot).
4E72
If zero, this slot is empty; skip to OPEN3 to advance to the next entry.
4E74HCODE
CP nn
Compare against the target file's hash code. Self-modifying: the immediate byte at 4E75H (HCODE) was set at 4E2DH.
4E76
If hash codes match, jump to OPEN4 to read the directory entry and verify the full filename.
4E78OPEN3
INC L
Advance to the next HIT entry (entries are consecutive bytes in the 256-byte sector).
4E79
Loop until all MAXFIL entries checked.

End of HIT scan loop. If we fall through, the file was not found on this drive.

4E7BH - OPNODR — File Not Found on This Drive

Reached when the file's hash was not found in the current drive's HIT. If no specific drive was requested, advances to the next drive; otherwise reports File Not Found.

4E7BOPNODR
Get the user-specified drive number (FFH if global search).
4E7E
INC A
Test if DRV was FFH (global search).
4E7F
If a specific drive was requested and not found, jump to OPNONE for File Not Found error.
4E81
INC C
Bump drive number to try the next drive.
4E82
Get the maximum drive number in the system.
4E85
CP C
Compare MAXDRV against the next drive to try.
4E86
If more drives remain (MAXDRV >= C), loop back to OPENL.

4E88H - OPNONE / OPERR0 — Error Exit with Drive 0 Select

OPNONE = File Not Found error exit point
OPERR0 = Common error exit: pushes error code, selects drive 0, pops and returns. Called from multiple error paths with A pre-loaded.

4E88OPNONE
LD A,18H
A = EFNF (24 = File Not Found).
4E8AOPERR0
PUSH AF
Save the error code on the stack.
4E8B
LD C,00H
C = drive 0.
4E8D
Call DSEL in SYS0 to select drive 0 (restore to a known state on error).
4E90
POP AF
Restore the error code.
4E91
RET
Return to caller with NZ set (A is non-zero).

4E92H - OPERR1 — Drive Not in System Error

Sets error code EDNS (Drive Not in System) and falls into OPERR0.

4E92OPERR1
LD A,02H
A = EDNS (2 = Drive Not in System).
4E94
Jump to OPERR0 to select drive 0 and return.

4E96H - OPEN6 — Directory Name Mismatch Cleanup

Reached when the directory entry name/extension didn't match the requested file. Pops the saved registers from the OPEN4/OPEN4A pushes and returns to the HIT scan loop at OPEN3.

4E96OPEN6
POP BC
Discard saved B (name length residual).
4E97
POP HL
Discard saved HL (directory entry pointer).
4E98
POP BC
Restore BC (C = drive, B = hash counter).
4E99
POP HL
Restore HL (HIT table pointer).
4E9A
Jump to OPEN3 to advance to the next HIT entry.

4E9CH - OPEN4 — Valid Hash Code Found, Read Directory Entry

A matching hash code was found in the HIT. Read the corresponding directory entry from disk to verify the full filename matches.

4E9COPEN4
PUSH HL
Save HL (pointer into HIT table).
4E9D
PUSH BC
Save BC (C = drive number).
4E9E
LD B,L
B = L (the HIT index = Logical File Number).
4E9F
Call RDDIR in SYS0 to read the directory entry for LFN in B into BUFF1. Returns HL pointing to the directory entry.
4EA2
If read succeeded, jump to OPEN4A to check the filename.
4EA4
POP BC
Read failed; clean up stack.
4EA5
POP HL
Restore HIT pointer.
4EA6
RET
Return with disk error code in A.

4EA7H - OPEN4A — Check the File's Name and Extension

Compares the 11-byte name+extension from the directory entry against the parsed NAME buffer to confirm a true match (not just a hash collision).

4EA7OPEN4A
PUSH HL
Save HL (directory entry pointer).
4EA8
PUSH BC
Save BC (C = drive, B = LFN).
4EA9
LD A,05H
A = DNAME (5) — offset to filename within the directory entry.
4EAB
ADD A,L
Add offset to the LSB of the directory pointer.
4EAC
LD L,A
HL now points to the filename field in the directory entry.
4EAD
LD DE,5141H
DE = NAME buffer (the parsed filename + extension).
4EB0
LD B,0BH
B = 11 (8 bytes name + 3 bytes extension).

Top of the name comparison loop (11 characters).

4EB2OPEN5
LD A,(DE)
Get the next character from the parsed NAME buffer.
4EB3
CP (HL)
Compare against the directory entry's filename character.
4EB4
If mismatch, jump to OPEN6 to clean up and try the next HIT entry.
4EB6
INC HL
Advance directory name pointer.
4EB7
INC DE
Advance parsed name pointer.
4EB8
Loop for all 11 characters.

All 11 characters matched — this is the requested file. Now check file protection.

4EBAH - File Found — Check Protection Level

After confirming the filename match: saves the drive number, sets up IY to point to SCAFLG (the SCA protection override flag at 42FFH), and enters the protection checking logic.

4EBA
POP BC
Restore BC (C = drive, B = LFN).
4EBB
LD A,C
A = drive number.
4EBC
Save the confirmed drive number into the DRV variable.
4EBF
LD IYL,0FFH
IYL = FFH. This is the low byte of SCAFLG's address (42FFH).
4EC2
POP HL
Restore HL (directory entry pointer).
4EC3
POP AF
Discard saved drive number from stack (was pushed at OPEN4).
4EC4
POP AF
Discard saved HIT pointer from stack (was pushed at OPEN4).
4EC5
LD IYH,42H
IYH = 42H, so IY = 42FFH = address of SCAFLG (SCA protection override flag).
4EC8
PUSH BC
Save BC (C = drive, B = LFN).
4EC9
PUSH HL
Save HL (directory entry pointer).
4ECA
LD B,00H
B = 0 = full access (PNONE). This is the starting privilege assumption.

Check if the SCA protection override flag is set (bit 0 of SCAFLG at 42FFH). If set, skip all password checking and grant full access.

4ECC
BIT 0,(IY+00H)
Test bit 0 of (IY+0) = SCAFLG. If set, protection checking is bypassed.
4ED0
RES 0,(IY+00H)
Always clear the flag (one-shot override).
4ED4
If the flag was set, jump to PROT3 to grant access.

Protection flag was not set. Check the file's protection level from the directory entry attributes.

4ED6
LD A,(HL)
Get the file's attribute byte (first byte of directory entry).
4ED7
AND 07H
Mask for protection level bits (PNOACC mask = bits 2–0).
4ED9
CP 07H
Compare against PNOACC (7 = No Access).
4EDB
If No Access, jump to PROT2 for File Access Denied error.
4EDD
LD C,A
Save the protection level in C.
4EDE
LD A,10H
A = DUPD (16) — offset to the Update password TDC in the directory entry.
4EE0
ADD A,L
Add offset to directory pointer LSB.
4EE1
LD L,A
HL now points to the Update TDC in the directory entry.
4EE2
DE = the user-supplied password TDC.
4EE6
LD A,(HL)
Get LSB of the Update TDC from the directory.
4EE7
INC HL
Point to MSB of Update TDC.
4EE8
PUSH HL
Save pointer (now at MSB of Update TDC).
4EE9
LD H,(HL)
H = MSB of Update TDC from directory.
4EEA
LD L,A
L = LSB of Update TDC. HL = directory's Update TDC.
4EEB
OR A
Clear carry for SBC.
4EEC
SBC HL,DE
Compare: directory Update TDC minus user-supplied TDC.
4EEE
POP HL
Restore pointer to MSB of Update TDC.
4EEF
If Update password matched, jump to PROT3 with full access (B still = 0).

Update password didn't match. Try the Access password (one directory entry field later).

4EF1
INC HL
HL now points to the LSB of the Access TDC in the directory.
4EF2
LD B,C
B = protection level from attribute byte. This becomes the privilege level (limited access).
4EF3
PUSH AF
Save AF.
4EF4
LD A,(HL)
Get LSB of Access TDC from directory.
4EF5
INC HL
Point to MSB.
4EF6
LD H,(HL)
H = MSB of Access TDC.
4EF7
LD L,A
HL = directory's Access TDC.
4EF8
POP AF
Restore AF.
4EF9
OR A
Clear carry.
4EFA
SBC HL,DE
Compare: directory Access TDC minus user-supplied TDC.
4EFC
If Access password also doesn't match, jump to PROT2 for File Access Denied.

Access password matched. Check for BASIC execute-only special handling.

4EFE
Get the BASIC-calling flag from BASICG (non-zero = not called from BASIC).
4F01
OR A
Test the flag.
4F02
If not called from BASIC, skip the BASIC protection logic.
4F04
LD A,B
A = protection level.
4F05
CP 06H
Compare against PEXEC (6 = Execute Only).
4F07
If protection is below Execute Only (i.e., more permissive), skip to PROT3.
4F09
DEC B
Demote the privilege from EXEC to READ level.
4F0A
XOR A
Clear A.
4F0B
LD (5200H),A
Set DOPROT = 0 in BASIC's workspace (clear BASIC's internal protection flag).
4F0E
DEC A
A = FFH.
4F0F
LD (5201H),A
Set PROTFL = FFH in BASIC's workspace (flag the file as protection-restricted for BASIC).
4F12
Jump to PROT3 to continue with the demoted access level.

4F14H - PROT2 — File Access Denied

Reached when no password matched and the file has access restrictions. Returns error code EFAD.

4F14PROT2
POP HL
Discard saved directory entry pointer.
4F15
POP BC
Discard saved LFN/drive.
4F16
LD A,19H
A = EFAD (25 = File Access Denied).
4F18
OR A
Force NZ to signal error.
4F19
RET
Return with error.

4F1AH - PROT3 — File Access Granted

Reached when the file passed protection checking. Saves the privilege level and jumps to SETDCB to construct the file's DCB.

4F1APROT3
POP HL
Restore HL (directory entry pointer).
4F1B
LD A,B
A = privilege level (0=full, higher=more restricted).
4F1C
Store the privilege level into the self-modifying LD A,nn at 5010H (PRIV EQU $+1).
4F1F
POP BC
Restore BC (B = LFN, C = drive).
4F20
Jump to SETDCB to build the DCB from the directory entry.

4F23H - S2INIT — Initialize (Create) a File

Initialize a file: attempts to OPEN it first; if not found, creates a new directory entry. Entry: B = LRL (0 if not blocked), HL = blocking buffer, DE = DCB. Exit: Z set if good, C set if new file created, A = error code.

4F23S2INIT
Call SAVERN in SYS0 to save registers.
4F26
Attempt to open the file using the OPENR inner routine.
4F29
If open returned an error, jump to S2INI1 to handle it.

File already exists and was opened successfully. Clear the end-of-file markers and set the LRL in the DCB.

4F2B
LD (IX+0CH),00H
Clear DCB+0CH (ERNL = End Record Number low byte).
4F2F
LD (IX+0DH),00H
Clear DCB+0DH (ERNH = End Record Number high byte).
4F33
LD (IX+08H),00H
Clear DCB+08H (EOF byte offset).
4F37
Get the requested LRL from the self-modifying variable.
4F3A
LD (IX+09H),A
Store the LRL into DCB+09H (LRL field).
4F3D
XOR A
A = 0. Set Z flag for successful return.
4F3E
RET
Return — file opened successfully (Z set, NC).

4F3FH - S2INI1 — INIT Error Handler (File Not Found = Create New File)

Handles the error from the OPEN attempt. If the error was File Not Found, proceeds to create a new file. Any other error is passed through.

4F3FS2INI1
CP 18H
Compare error code against EFNF (24 = File Not Found).
4F41
RET NZ
If any other error, return it to the caller.
4F42
LD A,10H
A = 10H (attribute: user file, visible, no protection). This is stored in the self-modifying ATTRIB variable.
4F44
Set the default attribute byte in the self-modifying LD (HL),nn at 4FCEH (ATTRIB EQU $+1).
4F47
Get the parsed drive number.
4F4A
LD C,A
C = drive number.
4F4B
INC A
Test for FFH (global search).
4F4C
If a specific drive was given, jump to INITL.
4F4E
Get the default drive number.
4F51
LD C,A
C = default drive.

4F52H - INITL — Search for Available Disk Space

Loop that checks each drive for available space by reading the GAT (Granule Allocation Table) and looking for a track that isn't full.

4F52INITL
Check if drive C exists and is ready.
4F55
If drive doesn't exist, jump to INNODR to try next drive.
4F57
If drive is write-protected, jump to INNODR.
4F59
Call RDGAT in SYS0 to read the Granule Allocation Table into BUFF2 (4D00H).
4F5C
RET NZ
Return if disk read error.
4F5D
LD HL,4D00H
HL = BUFF2 (the GAT sector in memory).
4F60
LD B,28H
B = MAXTRK (40 decimal) — number of tracks on a standard disk.

Scan the GAT for a track that isn't full.

4F62INITLL
LD A,(HL)
Get the next GAT allocation byte.
4F63
CP 3FH
Compare against USDTRK (3FH = all granules allocated, track full).
4F65
If track not full, jump to INITLM to proceed with creating the file on this drive.
4F67
INC HL
Advance to next track's GAT byte.
4F68
Loop for all 40 tracks.
4F6A
All tracks full on this drive; jump to INNODR to try the next drive.

4F6CH - INITLM — Disk Has Space, Read the HIT

The GAT shows available space. Now read the HIT to find an unused directory slot.

4F6CINITLM
Read the HIT sector into BUFF1.
4F6F
RET NZ
Return if disk error.

Copy HIT from BUFF1 to BUFF2.

4F70
PUSH BC
Save BC.
4F71
PUSH DE
Save DE.
4F72
PUSH HL
Save HL.
4F73
LD HL,4300H
HL = BUFF1 (HIT just read).
4F76
LD DE,4D00H
DE = BUFF2.
4F79
LD BC,0100H
BC = SECTOR (256).
4F7C
LDIR
Copy HIT sector.
4F7E
POP HL
Restore HL.
4F7F
POP DE
Restore DE.
4F80
POP BC
Restore BC.
4F81
Call GETENT to find an available (zero) entry in the HIT copy at BUFF2.
4F84
If found, jump to INIT2 to set up the new directory entry.

4F86H - INNODR / INNONE — Directory Full or Drive Not Available

INNODR = This drive's directory is full (or drive unavailable). If no specific drive was requested, try the next one.
INNONE = All drives exhausted — Disk Full error.

4F86INNODR
Get the user-specified drive.
4F89
INC A
Test for FFH (global search).
4F8A
If a specific drive was requested, jump to INNONE for Disk Full error.
4F8C
INC C
Next drive number.
4F8D
Get maximum drive number.
4F90
CP C
Compare MAXDRV against next drive.
4F91
If more drives remain, loop back to INITL.
4F93INNONE
LD A,1BH
A = EDSFL (27 = Disk Full).
4F95
Jump to OPERR0 to select drive 0 and return with error.

4F98H - INIT2 — Update the Hash Index Table with New File

An available HIT slot was found. Store the new file's hash code in the HIT, write it back to disk, then build the directory entry.

4F98INIT2
LD B,L
B = L = the available LFN (Logical File Number) from GETENT.
4F99
Get the file's hash code from the self-modifying variable.
4F9C
LD (HL),A
Store the hash code in the HIT slot.

Copy the modified HIT from BUFF2 back to BUFF1 for writing.

4F9D
PUSH BC
Save BC.
4F9E
PUSH DE
Save DE.
4F9F
PUSH HL
Save HL.
4FA0
LD HL,4D00H
HL = BUFF2 (modified HIT).
4FA3
LD DE,4300H
DE = BUFF1 (write source).
4FA6
LD BC,0100H
BC = SECTOR (256).
4FA9
LDIR
Copy modified HIT to BUFF1.
4FAB
POP HL
Restore HL.
4FAC
POP DE
Restore DE.
4FAD
POP BC
Restore BC.
4FAE
Call WRHIT in SYS0 to write the HIT from BUFF1 back to disk.
4FB1
RET NZ
Return if write error.
4FB2
Call RDDIR to read the directory sector for LFN in B. Returns HL pointing to the directory entry in BUFF1.
4FB5
RET NZ
Return if read error.
4FB6
PUSH HL
Save HL (directory entry pointer).

Check whether a password was supplied. Compare the Update TDC against MASTPW (the TDC that an all-spaces password produces: 5CEFH).

4FB7
Get the computed Update TDC.
4FBA
LD DE,5CEFH
DE = MASTPW (5CEFH) — the trap door code produced by a blank (all spaces) password. This is a constant computed from 8 spaces through the SETTDC algorithm.
4FBD
OR A
Clear carry for SBC.
4FBE
SBC HL,DE
Compare: is the Update TDC the same as a blank password?
4FC0
POP HL
Restore directory entry pointer.
4FC1
PUSH HL
Re-save it (needed later).
4FC2
If the password was blank (no password given), jump to ATT1 to use the default attribute (no protection).

A password was supplied. Upgrade the attribute to include maximum protection level.

4FC4
Get the current attribute byte from the self-modifying variable.
4FC7
AND 0F8H
Mask off the protection level bits (clear bits 2–0).
4FC9
OR 06H
Set protection level to 6 (PEXEC = Execute only / maximum protection).
4FCB
Store the upgraded attribute back.

Write the directory entry fields for the new file.

4FCEATT1
LD (HL),nn
Self-modifying: stores the attribute byte (ATTRIB at 4FCFH) into the directory entry's attribute field. The nn value was set above.
4FD0
INC HL
Advance to directory+01H.
4FD1
Get the current month from the system clock.
4FD4
LD (HL),A
Store the month in the directory entry (date field).
4FD5
INC HL
Advance to directory+02H.
4FD6
Get the current year from the system clock.
4FD9
LD (HL),A
Store the year in the directory entry.
4FDA
INC HL
Advance to directory+03H (DEOF).
4FDB
LD (HL),00H
Set EOF byte offset = 0.
4FDD
INC HL
Advance to directory+04H (DLRL).
4FDE
Get the requested LRL.
4FE1
LD (HL),A
Store LRL in the directory entry.
4FE2
INC HL
Advance to directory+05H (DNAME).
4FE3
PUSH BC
Save BC for the LDIR.
4FE4
LD BC,000FH
BC = 15 bytes (8 name + 3 ext + 2 update TDC + 2 access TDC).
4FE7
EX DE,HL
Swap: DE = directory pointer, HL will be source.
4FE8
LD HL,5141H
HL = NAME buffer (contains name, ext, UPDTDC, ACCTDC in order).
4FEB
LDIR
Copy the 15 bytes (name + extension + both password TDCs) into the directory entry.
4FED
EX DE,HL
Swap back: HL = directory pointer (now past the TDCs).
4FEE
LD (HL),00H
Clear ERN low byte (directory+14H).
4FF0
INC HL
Advance.
4FF1
LD (HL),00H
Clear ERN high byte (directory+15H).
4FF3
INC HL
Advance to the segment descriptors (directory+16H = DSEG).
4FF4
LD B,1AH
B = NUMSEG (26 decimal) — number of segment descriptor bytes.

Fill all 26 segment descriptor bytes with FFH (= no extents allocated).

4FF6INIT3
LD (HL),0FFH
Store FFH (unused extent marker).
4FF8
INC HL
Advance to next extent byte.
4FF9
Loop for all NUMSEG bytes.
4FFB
POP BC
Restore BC (B = LFN, C = drive).
4FFC
Call WRDIR in SYS0 to write the directory entry from BUFF1 back to disk.
4FFF
POP HL
Restore HL (directory entry pointer).
5000
RET NZ
Return if write error.
5001
Call SETDCB to construct the open DCB from the newly created directory entry.
5004
SCF
Set Carry flag to signal that a new file was created (as opposed to an existing file being opened).
5005
RET
Return (Z set from SETDCB, C set from SCF).

5006H - SETDCB — Set Up a File's DCB

Construct an open DCB from the directory entry. Entry: B = LFN, C = drive, HL = directory entry, IX = DCB. Exit: IX = filled DCB, Z set.

5006SETDCB
EX DE,HL
DE = directory entry pointer; HL will be set to DCB.
5007
5009
PUSH IX
POP HL
HL = IX = DCB pointer.
500A
LD (HL),80H
DCB+00H (TYPE): Set bit 7 = file is open (80H).
500C
INC HL
Advance to DCB+01H (PROT/access mode).
500DS2LRL
LD A,nn
Self-modifying: S2LRL value (LRL). The immediate at 500EH was set at 4E1AH.
500F
OR A
Test if LRL = 0 (which means 256-byte sectors). Sets/clears Z flag.
5010PRIV
LD A,nn
Self-modifying: PRIV value (privilege level). The immediate at 5011H was set at 4F1CH. Note: this LD does not change flags, so Z reflects the LRL test above.
5012
If LRL was 0 (sector I/O), skip the blocked-access flag.
5014
OR 80H
Set bit 7 in the access mode byte (= blocked I/O).
5016SETDC2
OR 20H
Set bit 5 (= current sector not yet loaded / position flag). Source: SETDC2.
5018
LD (HL),A
Store the access mode byte into DCB+01H (PROT).
5019
INC HL
Advance to DCB+02H (DADDH — reserved).
501A
LD (HL),00H
Clear the reserved byte.
501C
INC HL
Advance to DCB+03H (BUFL).
501D
PUSH DE
Save DE (directory pointer).
501EBLKBUF
LD DE,nnnn
Self-modifying: BLKBUF (blocking buffer address). The 2-byte immediate at 501FH was set at 4E1DH.
5021
LD (HL),E
Store buffer address low byte into DCB+03H (BUFL).
5022
INC HL
Advance to DCB+04H (BUFH).
5023
LD (HL),D
Store buffer address high byte into DCB+04H (BUFH).
5024
INC HL
Advance to DCB+05H (OFFSET).
5025
POP DE
Restore DE (directory pointer).
5026
LD (HL),00H
Clear byte address / offset (DCB+05H).
5028
INC HL
Advance to DCB+06H (DRVNUM).
5029
LD (HL),C
Store drive number into DCB+06H.
502A
INC HL
Advance to DCB+07H (LFN).
502B
LD (HL),B
Store Logical File Number into DCB+07H.
502C
INC HL
Advance to DCB+08H (EOF).
502D
LD A,03H
A = DEOF (3) — offset to EOF byte in the directory entry.
502F
ADD A,E
Add to directory pointer LSB.
5030
LD E,A
DE now points to the directory's DEOF field.
5031
LD A,(DE)
Get the EOF byte from the directory.
5032
LD (HL),A
Store EOF byte into DCB+08H.
5033
INC HL
Advance to DCB+09H (LRL).
5034
INC DE
DE now points to directory's DLRL field.
5035
LD A,(DE)
Get the LRL from the directory entry.
5036
LD (HL),A
Store LRL into DCB+09H.
5037
INC HL
Advance to DCB+0AH (NRNL).
5038
LD (HL),00H
Clear NRN low byte (current record number = 0).
503A
INC HL
Advance to DCB+0BH (NRNH).
503B
LD (HL),00H
Clear NRN high byte.
503D
INC HL
Advance to DCB+0CH (ERNL).
503E
LD A,10H
A = DERN-4 = 16. Since DE currently points to directory+04H, adding 16 reaches directory+20 (DERN field).
5040
ADD A,E
Add offset to directory pointer.
5041
LD E,A
DE now points to directory's DERN (End Record Number).
5042
LD A,(DE)
Get ERN low byte from directory.
5043
LD (HL),A
Store into DCB+0CH (ERNL).
5044
INC HL
Advance to DCB+0DH (ERNH).
5045
INC DE
Advance directory pointer to ERN high byte.
5046
LD A,(DE)
Get ERN high byte from directory.
5047
LD (HL),A
Store into DCB+0DH (ERNH).
5048
INC HL
Advance to DCB+0EH (SIDE).
5049
Get the current side number from the system variable.
504C
LD (HL),A
Store side into DCB+0EH.
504D
INC HL
Advance to DCB+0FH (SPARE).
504E
LD (HL),00H
Clear spare byte (reserved for future use).
5050
INC HL
Advance to DCB+10H (EXTBEG — start of extent data).
5051
INC DE
DE now points to directory's DSEG (segment descriptors, directory+22).
5052
EX DE,HL
Swap: HL = directory extents, DE = DCB extent area.
5053
LD BC,001AH
BC = NUMSEG (26) — number of extent descriptor bytes to copy.
5056
LDIR
Copy the 26 extent bytes from the directory entry into the DCB.
5058
LD A,(IX+09H)
Get the LRL from DCB+09H.
505B
OR A
Test if LRL = 0 (which means 256-byte sector I/O).
505C
If LRL = 0 (sector I/O), skip setting the variable-length flag.
505E
SET 7,(IX+01H)
Set bit 7 of DCB+01H (PROT) to flag variable-length records.
5062SETD1
XOR A
A = 0. Set Z flag for successful return.
5063
RET
Return (Z set = success).

5064H - CRACK — Crack a File Specification

Parse a filespec string (pointed to by HL) into its component fields: NAME (8), EXT (3), PWORD (8), DRV, and SID. Format: <name>/<ext>.<password>:<drive>. Entry: HL = filespec. Exit: Z if valid, NZ if invalid.

5064CRACK
LD A,0FFH
A = FFH (= no side specified).
5066
Initialize SID to FFH (global/default side).
5069
LD B,0BH
B = 11 (8 name + 3 ext bytes to clear).
506B
LD DE,5141H
DE = NAME buffer.
506E
LD A,20H
A = 20H (space character).

Initialize the NAME + EXT buffer to spaces.

5070CRACK1
LD (DE),A
Store space.
5071
INC DE
Next byte.
5072
Loop for all 11 bytes.
5074
LD B,08H
B = 8 (password field length).
5076
LD DE,5139H
DE = PWORD buffer.

Initialize the PWORD buffer to spaces.

5079CRACKP
LD (DE),A
Store space.
507A
INC DE
Next byte.
507B
Loop for all 8 bytes.
507D
LD A,0FFH
A = FFH.
507F
Initialize DRV to FFH (global/default drive search).
5082
LD DE,5141H
DE = NAME buffer.
5085
LD B,08H
B = 8 (max filename length).
5087
Call GETFLD to extract the filename field. Returns: A = terminating character, B = residual count.
508A
LD C,A
Save the terminating character in C.
508B
LD A,B
A = residual (how many chars were NOT used).
508C
CP 08H
If B still equals 8, no characters were extracted (empty name).
508E
If at least one character was extracted, jump to CRACK2.
5090
LD A,13H
A = EBFN (19 = Bad File Name).
5092
OR A
Force NZ to signal error.
5093
RET
Return with error.

Process optional extension (after '/' separator).

5094CRACK2
LD A,C
A = terminating character from the name field.
5095
CP 2FH
Is it '/' (extension separator)?
5097
If not, skip to CRACK3.
5099
LD DE,5149H
DE = EXT buffer (3 bytes, at NAME+8).
509C
LD B,03H
B = 3 (max extension length).
509E
Extract the extension field.

Process optional password (after '.' separator).

50A1CRACK3
CP 2EH
Is the terminator '.' (password separator)?
50A3
If not, skip to CRACK4.
50A5
LD DE,5139H
DE = PWORD buffer.
50A8
LD B,08H
B = 8 (max password length).
50AA
Extract the password field.

Process optional drive number (after ':' separator).

50ADCRACK4
CP 3AH
Is the terminator ':' (drive separator)?
50AF
If not, jump to CRACK7 to finish.
50B1
LD A,(HL)
Get the drive digit character.
50B2
SUB 30H
Convert ASCII digit to binary ('0'=30H).
50B4
If below '0', invalid drive number.
50B6
LD C,A
C = drive number.
50B7
Get maximum drive number.
50BA
CP C
Compare MAXDRV against requested drive.
50BB
LD A,C
Restore drive number to A.
50BC
If MAXDRV >= requested drive, it's valid.
50BECRACK5
LD A,02H
A = EDNS (2 = Drive Not in System).
50C0
Jump to OPERR0 to select drive 0 and return error.
50C3CRACK6
Store the validated drive number in DRV.
50C6
INC HL
Skip past the drive digit.
50C7
LD A,(HL)
Get next character (possible side specifier).
50C8
INC HL
Advance past it.

Check for side specifier ('#') and return.

50C9CRACK7
CP 23H
Is the character '#' (side specifier)? (Note: side handling is incomplete in TRSDOS 1.3.)
50CBCRACK9
XOR A
A = 0. Set Z flag for successful parse.
50CC
RET
Return (Z = valid filespec).

50CDH - GETFLD — Extract an Alphanumeric Field from a String

Extract up to B alphanumeric characters from the string at HL into the buffer at DE. The first character must be alphabetic (A–Z); subsequent characters can be alphanumeric (A–Z, 0–9). Entry: B = max length, HL = source, DE = destination. Exit: A = terminating character, B = length residual.

50CDGETFLD
LD A,(HL)
Get the first character.
50CE
INC HL
Advance source pointer.
50CF
Jump to GETFL3 — the first character must be alphabetic.

Subsequent characters can be alphanumeric.

50D1GETFL2
LD A,(HL)
Get next character.
50D2
INC HL
Advance source pointer.
50D3
CP 30H
Is it below '0' (30H)?
50D5
RET C
If below '0', it's a control/special character; return with it as the terminator.
50D6
CP 3AH
Is it below ':' (3AH)? (i.e., is it '0'–'9'?)
50D8
If numeric, jump to GETFL4 to store it.

Alphabetic check (also first-character entry point).

50DAGETFL3
CP 41H
Is it below 'A' (41H)?
50DC
RET C
If below 'A', it's not alphabetic; return with it as the terminator.
50DD
CP 5BH
Is it above 'Z' (5AH)? (5BH = 'Z'+1)
50DF
RET NC
If >= 5BH, it's not alphabetic; return.

Valid character; store it and continue.

50E0GETFL4
LD (DE),A
Store the character in the destination buffer.
50E1
INC DE
Advance destination pointer.
50E2
Loop until B characters have been extracted or a terminator is found.
50E4
LD A,(HL)
Field is full but there may be more characters; get the next one.
50E5
INC HL
Advance past it.
50E6
RET
Return with the overflow character as the terminator.

50E7H - HASH — Generate a Hash Code

Generate a non-zero 8-bit hash code from an 11-byte filename+extension string. Uses XOR and RLCA (rotate left through carry) to accumulate the hash. Entry: HL = filename/ext (11 bytes). Exit: A = hash code (guaranteed non-zero).

50E7HASH
LD B,0BH
B = 11 (name + extension length).
50E9
LD C,00H
C = 0 (hash accumulator).

HASH2: Main hash loop.

50EBHASH2
LD A,(HL)
Get next character.
50EC
INC HL
Advance pointer.
50ED
XOR C
XOR the character with the running hash.
50EE
RLCA
Rotate left (circular) to spread bits.
50EF
LD C,A
Update the hash accumulator.
50F0
Loop for all 11 characters.
50F2
LD A,C
A = final hash code.
50F3
OR A
Test if zero.
50F4
RET NZ
If non-zero, return.
50F5
INC A
Force A = 1 (zero hash codes are not allowed because zero means “empty slot” in the HIT).
50F6
RET
Return with non-zero hash code.

50F7H - GETENT — Get an Available HIT Entry

Scan the HIT copy in BUFF2 for the first zero (available) entry. Entry: none. Exit: Z set if found (HL = available entry), NZ if directory space full (A = EDSF error code).

50F7GETENT
LD HL,4D00H
HL = BUFF2 (start of HIT copy).

GETEN2: Scan loop.

50FAGETEN2
LD A,(HL)
Get next HIT entry.
50FB
OR A
Test if zero (available slot).
50FC
RET Z
If zero, return with Z set and HL pointing to the available slot. L = LFN.
50FD
INC HL
Advance to next slot.
50FE
LD A,L
A = low byte of pointer.
50FF
CP 51H
Compare against MAXFIL+1 (81 = past the last valid slot, since BUFF2 is at 4D00H and MAXFIL=80, valid slots are 4D00H–4D4FH, so L goes from 00H to 50H).
5101
If L < 51H, keep scanning.
5103
LD A,1AH
A = EDSF (26 = Directory Space Full).
5105
OR A
Force NZ to signal error.
5106
RET
Return with error.

5107H - SETTDC — Calculate a Trap Door Code

Calculate a 16-bit Trap Door Code (TDC) from an 8-byte password. The algorithm processes the password bytes in reverse order, using a combination of XOR, rotate, subtract, and shift operations to produce a non-reversible hash. Entry: DE = password array (8 bytes). Exit: HL = 16-bit TDC (guaranteed non-zero).

5107SETTDC
LD HL,0FFFFH
Initialize HL = FFFFH (seed value for the TDC).
510A
LD B,08H
B = 8 (password length, processing in reverse).
510C
LD A,E
A = low byte of password pointer.
510D
ADD A,07H
Add 7 to point to the last (8th) byte of the password.
510F
LD E,A
Update E.
5110
If no carry, jump to SETTD2.
5112
INC D
If carry, propagate to high byte of pointer.

Main TDC computation loop. Processes each password byte through a series of bit manipulation operations.

5113SETTD2
LD A,(DE)
Get the current password character (from the end, working backward).
5114
PUSH DE
Save the password pointer.
5115
LD D,A
D = password character.
5116
LD E,H
E = current TDC high byte.
5117
LD A,L
A = current TDC low byte.
5118
AND 07H
Isolate the bottom 3 bits of the TDC low byte.
511A
511B
RRCA
RRCA
Rotate right twice (3 bits move from positions 2–0 to positions 7–5, effectively shifting them to the top).
511C
RRCA
Third rotate: bottom 3 bits are now in positions 7–5.
511D
XOR L
XOR with the original TDC low byte to mix the bits.
511E
LD L,A
L = mixed low byte.
511F
LD H,00H
H = 0. HL now holds only the mixed low byte.
5121
SBC HL,DE
HL = HL - DE - carry. Subtract the password character and old TDC high byte.
5123
ADD HL,HL
HL = HL * 2.
5124
ADD HL,HL
HL = HL * 4.
5125
XOR H
XOR A (which held the mixed low) with the new H.
5126
XOR D
XOR with the password character.
5127
LD D,A
D = result (new high byte of working value).
5128
LD A,L
A = new L.
5129
ADD HL,HL
HL = HL * 2 (cumulative *8 from the original).
512A
XOR H
XOR A with the shifted H.
512B
XOR E
XOR with E (the old TDC high byte).
512C
LD E,A
E = result (new low byte of working value).
512D
EX DE,HL
Swap DE and HL: HL now holds the new TDC, DE is free.
512E
POP DE
Restore password pointer.
512F
DEC DE
Move to the previous password character.
5130
Loop for all 8 characters.
5132
5133
LD A,H
OR L
Test if the final TDC is zero (a zero TDC is not allowed).
5134
RET NZ
If non-zero, return with the TDC in HL.
5135
INC L
Force L = 1 to avoid a zero TDC.
5136
RET
Return with HL = 0001H.

5137H - SYS2 Variables

5137SID
DEFB 0
SID — Requested side number (FFH = any/default, 0–1 = specific side).
5138DRV
DEFB 0
DRV — Requested drive number (FFH = any/default, 0–3 = specific drive).
5139PWORD
DEFS 8
PWORD — Password Field (8 bytes, 5139H–5140H).
5141NAME
DEFS 8
NAME — Filename Field (8 bytes, 5141H–5148H).
5149EXT
DEFS 3
EXT — Extension Field (3 bytes, 5149H–514BH).
514CUPDTDC
DEFS 2
UPDTDC — Update password Trap Door Code (2 bytes, 514CH–514DH).
514EACCTDC
DEFS 2
ACCTDC — Access password Trap Door Code (2 bytes, 514EH–514FH).
END 4E00H