TRS-80 DOS - LDOS 5.3.1 for the Model I - Disassembled
Page Index
Overview & History
Overview
LDOS 5.3.1 for the Model I is a Disk Operating System originally developed by Logical Systems, Inc. and, in this final release, maintained and published by MISOSYS, Inc. (1991). The system disk carries the banner "LDOS - THE LOGICAL DISK OPERATING SYSTEM - VER 5.3.1" and the header "Copyright 1991 MISOSYS, Inc., All rights reserved."
The operating system is built around a resident core, SYS0/SYS (SYSRES), which stays permanently in memory, plus a set of system overlays (SYS1/SYS through SYS12/SYS) that are loaded from the system drive on demand. Two additional system files, BOOT/SYS and DIR/SYS, are written to a diskette by the FORMAT utility and are never copied from one disk to another; DOS maintains them automatically. Because overlays are only pulled in when their functions are needed, unneeded overlays can be purged to free space on a working system diskette.
On the Model I, LDOS drives the WD1771 Floppy Disk Controller through memory-mapped I/O (command/status at 37ECH, track at 37EDH, sector at 37EEH, and data at 37EFH, with the drive-select and interrupt-status latches at 37E1H and 37E0H). System services and overlay loading are reached through a single Z80 instruction, RST 28H (see "How Service Calls Work" below).
One of LDOS's defining features is media and code interchangeability: a diskette created under LDOS on a Model I or Model III can be read or written on either machine, and assembly-language programs written to the LDOS SVC table are portable across LDOS installations regardless of the host computer. The bundled LBASIC language allows most standard Microsoft BASIC programs to run unmodified.
History
LDOS was created by Logical Systems, Inc. and first released in 1981 as LDOS 5.1 for the TRS-80 Model I and Model III. It was the product of a collaboration among three companies: MISOSYS (Roy Soltoff, of Alexandria, Virginia), Galactic Software Ltd. (Bill Schroeder, of Mequon, Wisconsin), and Lobo Drives (of Goleta, California) - and the "LDOS" name originally stood for "Lobo Drives Operating System." The development team credited in the original manual comprised Bill Schroeder (project leader), Roy Soltoff (systems analyst), Chuck Jensen, Doug Kennedy, Dick Konop, and Tim Mann.
LDOS set itself apart from Radio Shack's own Model I and III TRSDOS by providing full media interchangeability between the two machines, allowing programs to be written or patched to run on both, and by supporting hard drives. On the strength of that hard-drive support, Tandy licensed LDOS as its official hard-drive operating system and sold it through the Radio Shack catalog as RS26-2213, and Logical Systems was subsequently chosen to write TRSDOS 6 for the Model 4.
The 5.1 line was maintained by Logical Systems through updates 5.1.3 and 5.1.4 (1981-1983). Roy Soltoff's company, MISOSYS, later took over maintenance of the Model I/III product, releasing LDOS 5.3.0 in 1987 and LDOS 5.3.1 in 1991 - the final release of the line. The 5.3 series expanded the date range to accept dates from January 1, 1980 through December 31, 1999, added a modification time stamp to each directory entry, and brought the on-disk directory convention into line with LS-DOS 6.3. The disk disassembled on this page is that final LDOS 5.3.1 release.
Disassembly Highlights
This page serves as the index for the commented disassembly of the LDOS 5.3.1 system and related files. Every program on the distribution disk has now been disassembled, TED, the text editor, included.
How Service Calls Work
In LDOS, the RST 28H (Restart 28 Hex) instruction is the central gateway to operating-system services and overlay management. It is a single-byte Z80 instruction that acts as a high-speed trap into the resident kernel (SYS0/SYS), letting programs invoke disk operations, file services, and system extensions without needing to know the physical addresses of those routines, which may shift between releases.
When an RST 28H is executed, the CPU vectors through ROM address 0028H into the resident DOS vector at 400CH, which jumps to the supervisor-call dispatcher at 4BCDH. The request being made is encoded in the A register:
- Request Code Evaluation: The value in the Accumulator (A register) is treated as a request code.
- Resident vs. Overlay Service: If the high bit of the code is set (code >= 80H), the dispatcher saves and disarms the DEBUG-trap flag (at 4315H) and calls the overlay loader (at 4BF5H). The loader finds the matching SYS overlay in the directory, reads it into the transient region at 4E00H, and patches the dispatcher's self-modified CALL with the overlay's entry point. The overlay runs and returns, after which the trap flag is restored.
- Resident Return Path: If the high bit is clear (code < 80H), the request is typically handled resident - most often a simple return (code 1) or an error path at 4BEF.
- Overlay Tracking: Location 430EH holds the number of the overlay currently resident in the transient area, so the loader can avoid reloading an overlay that is already present.
In addition to this internal supervisor mechanism, LDOS (DOS version 5) offers a user-loadable SVC table - a jump table placed in high memory by the SYSTEM (SVC) command - that gives assembly-language programs a stable, machine-independent set of entry points. The SVC table requires the KI/DVR keyboard driver to be active.
Resident SVC Vector Table
The resident core exposes a fixed vector table (addresses 4420H-4454H) that overlays and applications call for essential services. These vector addresses are stable across the Model I DOSes, which is what makes SVC-level program compatibility possible. The confirmed vectors on this LDOS 5.3.1 build are:
| Vector | Address | Purpose |
|---|---|---|
| @INIT | 4420H | Open or initialize a file/logical device (serviced by SYS2/SYS). |
| @OPEN | 4424H | Open an existing file. |
| @CLOSE | 4428H | Close a file or logical device (serviced by SYS3/SYS). |
| @KILL | 442CH | Delete a file and release its space (serviced by SYS10/SYS). |
| @LOAD | 4430H | Load a program load-module into memory. |
| @RUN | 4433H | Load and execute a program. |
| @READ | 4436H | Read a logical record from an open file. |
| @WRITE | 4439H | Write a logical record to an open file. |
| @VER | 443CH | Write and verify a record. |
| @REW | 443FH | Rewind a file to its first record. |
| @POSN | 4442H | Position a file to a given record. |
| @BKSP | 4445H | Backspace one record. |
| @PEOF | 4448H | Position to the end of file. |
| @DSPLY | 4467H | Display a string to the video (*DO). |
| 446AH | Send a string to the printer (*PR). | |
| @DIV | 4451H | Arithmetic divide helper. |
Known RST 28H Request Codes
Request codes with the high bit set select an overlay service. The overlay loader at 4BF5H turns a code into a SYS file number by taking its low nibble and subtracting two, and the overlay itself masks the code with 70H to pick a sub-function, so 84H is SYS2 sub-function 00H, 94H is SYS2 sub-function 10H, and so on. A code whose sub-function no overlay recognises simply returns, which makes it a way of loading an overlay and nothing more. The codes decoded so far in the SYS0/SYS, SYS1/SYS, SYS2/SYS, SYS3/SYS, SYS4/SYS, SYS5/SYS, SYS6/SYS, SYS7/SYS, SYS8/SYS, SYS9/SYS, SYS10/SYS, SYS11/SYS, SYS12/SYS, FORMAT/CMD and BACKUP/CMD disassemblies are:
| Code (A) | Function |
|---|---|
| 84H | Load SYS2/SYS and return. The code masks to sub-function 00H, which the SYS2/SYS entry dispatcher at 4E00H matches against none of its five sub-functions, so it falls onto the RET at 4E16H and the overlay does nothing at all. The whole effect is that SYS2/SYS becomes the resident overlay, recorded in OVRLY$ at 430EH, so a later request with the same low nibble is satisfied from memory without a directory read. Issued by SYS6/SYS COPY through the stub at 57C5H before a one-drive copy starts swapping diskettes, and by FORMAT/CMD at 7277H before it touches the disk. COPY prints its own Insert SOURCE disk prompt; this request does not. |
| 85H | Load SYS3/SYS and return, the same construction as 84H. The code masks to sub-function 00H, which SYS3/SYS does not recognise, so the overlay returns immediately and the only effect is that SYS3/SYS is made resident ahead of the diskette swapping. Issued by SYS6/SYS COPY through the stub at 57CBH. |
| 87H @DEBUG | SYS5/SYS Loads and enters the DEBUG machine-language monitor. The resident @DEBUG launcher at 44B4H issues RST 28H code 87H; the overlay takes a full register snapshot, then runs its interactive command loop. Breakpoints re-enter DEBUG through the RST 30H hook at 400FH. |
| 88H | Command library A dispatch - run a built-in DOS command; Register B holds the command code (issued by the SYS1 command interpreter; serviced by SYS6/SYS). |
| 89H | Library B command dispatch. Register B selects one of 13 SYS7/SYS command overlays (15 command codes), each loaded to 5200H by the resident overlay loader at 4BF5H. Commands: AUTO, DATE, TIME, CREATE, DEBUG, VERIFY, SYSGEN, FREE, SYSTEM, BUILD, ATTRIB, DUMP, PURGE, FORMS, SETCOM. See SYS7/SYS. |
| 8BH | Load the extended DEBUG command set (issued by the DEBUG command in SYS7/SYS when its EXT option is given, and by SYS5/SYS; serviced by SYS9/SYS). The overlay lowers HIGH$ by 03D8H, copies itself into the reserved space as a resident module named EXTDBG, and points the resident unrecognised-command vector at 4310H at the copy. DEBUG OFF and RESET in SYS7/SYS undo it by restoring 4876H to that vector. |
| 8AH | Load SYS8/SYS and return, the same construction as 84H and 85H. Sub-function 00H is not recognised, so the overlay returns at once and SYS8/SYS is simply made resident. Issued by SYS6/SYS COPY through the stub at 57C8H. The three stubs at 57C5H, 57C8H and 57CBH sit nose to tail in the COPY overlay and are CALLed separately from 531FH, 53C3H and 5424H; because the RST 28H dispatcher discards the address the RST pushed, each two-instruction stub returns to the caller of that stub and needs no RET of its own. |
| 93H | Return to DOS (DOS Ready). |
| 94H | @OPEN - open or initialize a file or logical device; also performs the drive-availability check for @CKDRV (serviced by SYS2/SYS, which hashes the filename to a one-byte directory HIT code and the password to a 16-bit value). |
| 95H | @CLOSE - close a file or logical device; for a disk file this finalizes the directory entry (end-of-file, ending record number, and modification date/time stamp) and releases any pre-allocated but unused granules back to the free space (serviced by SYS3/SYS). |
| 96H | @ERROR - display a system error message; the message text is decompressed from a packed 56-word dictionary and may be followed by the offending device or file specification and the caller's return address (serviced by SYS4/SYS). |
| 9AH | Allocate file space - give an open disk file another granule when a write runs past the space it already owns. The overlay walks the file's directory extent chain, tries first to grow the last extent with the granule that physically follows it, and otherwise claims a free granule from the Granule Allocation Table and builds a new extent, creating and linking a secondary extended directory record when all four extent slots of a record are in use (serviced by SYS8/SYS). |
| 9CH @KILL | @KILL - delete a file or close out a logical device. For a disk file the overlay checks that it was opened at an access level permitting removal, releases every granule of every extent back into the Granule Allocation Table, clears bit 4 of each directory record's attribute byte, zeroes the record's Hash Index Table byte, follows any FXDE link to a secondary extended record, writes both tables back and clears the caller's File Control Block. For a device it rebuilds the control block as a re-openable *XX specification (serviced by SYS10/SYS). |
| 9DH | Execute a compiled SYSTEM/JCL job (issued by SYS6/SYS DO at 5301H; serviced by SYS11/SYS). The overlay saves the real *KI driver address out of KIDCB$ at 4016H into KIJCL$ at 43BEH and puts its own job-file reader in its place, so the ordinary ROM line-input routine collects each line of the job into INBUF$ as though it had been typed, and hands it to the command interpreter through @CMNDI. |
| 9EH @RAMDIR | @RAMDIR - build a table of directory records in memory (serviced by SYS12/SYS as sub-function 10H). Register B holds the drive number, Register Pair HL the caller's buffer, and Register C selects the reply: 0FFH returns the kilobytes in use followed by the kilobytes free, 00H returns one 22-byte record for every visible file on the disk ended by a 2BH marker, and any other value returns the single directory record whose number is one greater than the value given. Each record is a fifteen-character blank-padded file specification, the access level, the end-of-file byte, the logical record length, the ending record number and the file size in kilobytes. There is no resident vector for this code; callers issue the RST 28H themselves. |
| A3H | Command-line execute - hands a command line (Register Pair HL) to DOS for execution through the resident stub at 4451H (LD A,0A3H; RST 28H). Used to re-run a stored command by SYS6/SYS SPOOL and SYS7/SYS AUTO. This is not @DIV; @DIV is the resident routine at 44C4H. |
| A5H | @FNAME - build the printable file specification (NAME/EXT:d) for a directory record into a caller-supplied buffer (serviced by SYS3/SYS). |
| ADH | @EXIT launch stub - reports to SYS11/SYS that a program run by a job has exited normally, so that the next line of the job is read and executed. One of the two codes written over the @EXIT and @ABORT vectors at 402DH when a job starts (with BDH). |
| AEH @DODIR | @DODIR - the abbreviated directory service, loaded by the resident @DODIR vector at 4463H and serviced by SYS12/SYS as sub-function 20H. Register C holds the drive number, Register Pair HL the caller's buffer and Register B a function code: 0 displays the directory four file specifications to a line and fifteen lines to a screen, 1 copies eighteen raw directory bytes per file into the buffer and ends the list with 0FFH, 2 and 3 do the same but keep only files whose extension matches the three-character mask in the first three bytes of the buffer (a dollar sign matches anything), and 4 returns the disk name, the disk date, the disk capacity in kilobytes and the free space in kilobytes. Both scans walk the Hash Index Table rather than reading every directory sector. |
| BCH | Resident module/driver control-block lookup - Register Pair DE points at the module or device name; the service walks the chain of modules installed above HIGH$ (4049H), matching each header's name, and returns the control-block address in Register Pair DE (NZ with error 8 if not resident). Issued by SYS6/SYS LINK and ROUTE and by SYS7/SYS SYSTEM (5AF6H), FORMS (53BAH) and SETCOM (54FFH); serviced by SYS10/SYS at 4F28H, which reaches it as sub-function 30H. |
| BDH | @EXIT launch stub - reports to SYS11/SYS that a program run by a job has aborted, which ends the job with the message Job aborted. The second of the two codes written over the exit vectors when a job starts (with ADH). |
| C3H | @FSPEC - parse a file specification. |
| 0D4H | Filename hash - Register Pair HL points at an eleven-byte packed name and extension; the service exclusive-ORs and rotates them into the one-byte Hash Index Table hash, forces a zero result to 1 so that it can never look like a free slot, returns it in Register A and leaves it in the name-hash cell at 51E1H. Masked with 70H it is sub-function 50H of SYS2/SYS, the same routine @OPEN uses internally. Issued by SYS6/SYS RENAME through the trampoline at 52D0H, which then updates the Hash Index Table itself using the SYS2/SYS entry points at 51BDH and 51BEH. |
| 0E4H | Password hash - hashes an eight-character password to the 16-bit value stored in a directory record, returned in Register Pair HL. Masked with 70H it is sub-function 60H of SYS2/SYS at 4E27H: the eight bytes are folded in reverse order into a running value seeded with 0FFFFH. Eight blanks hash to 4296H and the default master password PASSWORD hashes to 42E0H. Issued by SYS7/SYS ATTRIB at 532EH and PURGE at 54E5H. |
| CDH @DOKEY | Keyboard request issued by the resident @DOKEY vector at 44BEH. While a Job Control Language job is running this reaches SYS11/SYS at 4ECFH. Because the supervisor discards the return address the RST 28H pushed, the overlay can read the calling program's own stack frame: finding the ROM line-input routine's return address 05E3H there means the request is part of collecting a command line, which the job answers from the job file; anything else is chained to the real keyboard driver. |
| DDH | Keyboard request while a job's INPUT directive is being answered. SYS11/SYS writes 0DDH over the request-code operand at 44BFH inside the @DOKEY vector so that every keyboard request goes straight to the operator, and sets it back to 0CDH once the reply is terminated. |
Additional codes (including B3H, D3H, E3H and C4H) are implemented in the various SYS overlays and are decoded as each overlay is analyzed.
BOOT/SYS
Purpose:
The LDOS 5.3.1 bootstrap program written to a diskette by FORMAT and SYSGEN. The Model I boot ROM loads track 0, sector 0 of drive 0 to 4200H and jumps to it; that sector reads the resident system loader's directory record from the directory cylinder, streams the file as an LDOS load module, and transfers to its entry point. Disk access is direct to the memory-mapped WD1771 (Seek 1BH, Read Sector 88H, Force Interrupt 0D0H). The 1792-byte file also stores an alternate drive-geometry-aware boot sector, a Drive Code Table (DCT) initialization-template table, the day/month name tables, the "LDOS531 07/14/91" identification string, and a secondary loader.
Disassembly:
A disassembly of BOOT/SYS can be found here.
SYS0/SYS
Purpose:
SYS0/SYS is not an overlay - it is the resident part of the operating system (SYSRES). It remains permanently in memory and must be present on any disk used to boot the system, but may be removed from disks not used for booting.
From the disassembly, SYS0/SYS spans 3C00H through 50FFH and contains the sign-on text and block-graphics "LDOS" logo, the RST/DOS vectors at 400CH, the device control blocks for *KI/*DO/*PR and *JL/*SI/*SO, the clock and interrupt workspace and task-vector table, the SVC vector table (4420H-4454H), the low-level WD1771 disk I/O engine, the record-positioning and file-access routines, the directory read/write and granule-allocation code, the 16-bit multiply/divide primitives, the RST 28H supervisor dispatcher and overlay loader, and the boot-time initialization and date/time prompt code.
Role:
It provides the fundamental, non-changing operating-system services:
- Low-level disk I/O (reading and writing raw sectors via the memory-mapped WD1771).
- Basic file-system primitives (directory search, granule allocation, record positioning, open/read/write/close support).
- Interrupt handling for the real-time clock and the task scheduler.
- Fixed entry points for character output, file services, and program exit/error handling through the resident vector table.
Disassembly:
A detailed, commented disassembly of SYS0/SYS can be found here.
SYS1/SYS
Purpose:
SYS1/SYS contains the DOS command interpreter, together with the routines that service the @FEXT (default file-extension), @FSPEC (file-specification parsing), and @PARAM (parameter-list parsing) system vectors. It holds the built-in command-name table (APPEND through VERIFY) and routes each typed command either directly within the overlay or to the Library A/B command overlays through RST 28H request codes 88H and 89H. This overlay must be available on all SYSTEM disks.
Disassembly:
A disassembly of SYS1/SYS can be found here.
SYS2/SYS
Purpose:
SYS2/SYS opens and initializes disk files and logical devices. It also contains the routines that check the availability of a disk pack (servicing the @CKDRV vector) and the routines that hash file specifications and passwords. This overlay must reside on all SYSTEM disks; along with SYS3/SYS it must remain on any booting disk if a SYSGEN configuration file is to be loaded.
Disassembly:
A disassembly of SYS2/SYS can be found here.
SYS3/SYS
Purpose:
SYS3/SYS contains all of the system routines needed to close files and logical devices, plus the routines that service the @FNAME system vector. This overlay must not be eliminated, and together with SYS2/SYS must remain on any booting disk if a SYSGEN configuration file is to be loaded.
When a disk file is closed, SYS3/SYS finalizes its directory entry - flushing any buffered record, updating the end-of-file byte and ending record number, and, on drives whose dating is enabled, stamping the modification date and time - and then releases any pre-allocated but unused granules back to the Granule Allocation Table, unlinking and de-hashing any secondary (FXDE) directory record that becomes empty. Closing a logical device instead rebuilds its control block into a re-openable device specification. The @FNAME service (RST 28H code A5H) builds the printable NAME/EXT:d specification for a directory record into a caller-supplied buffer.
Disassembly:
A disassembly of SYS3/SYS can be found here.
SYS4/SYS
Purpose:
SYS4/SYS holds the system error dictionary - the text of messages such as "File not in directory" and "Directory read error." It is the overlay that services the @ERROR system call (RST 28H request code 96H): it turns a numeric error code into readable text, frames it as "*** Error code = NN, message ***", and displays it. If this overlay is purged, all system errors instead produce the terse message "SYS ERROR." Because it occupies only a single granule, it is recommended that it be kept.
To fit its 46 messages into one granule, SYS4/SYS does not store them as literal strings. It keeps a dictionary of 56 English words once, a compact per-error table that lists each message as a short sequence of word numbers, and a one-byte index that locates each message; every dictionary word is marked by setting the high bit on its final character. When an error is displayed, the routine can also append the identity of the offending object - either <Device=*XX> for a logical device or <File=NAME/EXT:D> for a disk file read from the directory - and a "Referenced at X'NNNN'" line giving the address from which the failing service was called. Option bits in the caller's accumulator choose how much of this framing is produced and whether control returns to the caller or aborts to DOS.
Disassembly:
A disassembly of SYS4/SYS can be found here.
SYS5/SYS
Purpose:
SYS5/SYS is the LDOS 5.3.1 DEBUG overlay for the Model I. It provides an interactive machine-language monitor: display and edit of the full Z80 register set (captured on entry into the resident DEBUG save area DBGSV$ at 405DH-407CH), hexadecimal and ASCII memory examination and patching, execution breakpoints planted as RST 30H (F7H) opcodes, and single-step or trace execution driven by a built-in instruction-length decoder.
DEBUG is loaded at 4E00H by the resident RST 28H overlay loader (request code 87H) and re-entered by the RST 30H breakpoint hook at 400FH. It performs no disk I/O of its own; it drives the screen and keyboard through the Model I ROM (0033H, 002BH, 0049H) and reuses the resident video DCB cursor field at 4020H. The command set is single-key: g (Go), r (register modify), m (memory modify), d (set memory window), ; and - (scroll), a and h (ASCII/hex display), s and x (screen layout), c (step over), i (step into), and u (live update).
Disassembly:
A disassembly of SYS5/SYS can be found here.
SYS6/SYS - Library A command set
Purpose:
SYS6/SYS contains the algorithms and routines that service the primary command library - the commands identified as "Library A" by the LIB command. Very limited use can be made of DOS if this overlay is removed from a working SYSTEM disk. It also contains the DO command; if SYS6/SYS is purged there is no purpose in keeping the JCL processor (SYS11/SYS).
The SYS6 file contains 15 overlay routines:
- 1: MEMORY
- 2: DIR
- 3: REMOVE
- 4: LIB
- 5: DO
- 6: SPOOL
- 7: COPY
- 8: LIST
- 9: RENAME
- 10: SYSTEM (status/drives)
- 11: LINK
- 12: Command overlay
- 13: ROUTE
- 14: Command overlay (device driver/filter)
- 15: Command overlay (disk-swap operation)
Disassembly:
A disassembly of SYS6/SYS can be found here.
SYS7/SYS - Library B command set (ATTRIB, AUTO, CREATE, DATE, DUMP, PURGE, FORMS, SETCOM, SYSTEM, ...)
Purpose:
SYS7/SYS contains the routines that service the secondary command library - the commands identified as "Library B" by the LIB command. These are more specialized functions; a great deal of use can be made of DOS even without this overlay, so it may be purged with the PURGE command if its commands are not needed on a working SYSTEM diskette.
Disassembly:
A disassembly of SYS7/SYS can be found here.
SYS8/SYS
Purpose:
On DOS version 5 (LDOS for the Model I), SYS8/SYS is the routine that dynamically allocates file space as files are written. It must be present on any working SYSTEM diskette, and must be on the boot disk if a configuration file is to be loaded.
From the disassembly, SYS8/SYS is a single-granule overlay of 662 bytes spanning 4E00H through 5095H, entered through RST 28H request code 9AH with Register Pair IX addressing the caller's File Control Block and Register Pair BC holding the file-relative granule number that is wanted. It reads the file's directory record and adds up the granules described by the four 2-byte extent entries at DIR+16H through DIR+1DH, following an FEH marker at DIR+1EH to any secondary extended directory record, until the running total covers the granule requested.
When the chain falls short the overlay reads the Granule Allocation Table into a buffer at 5100H and favours contiguity: it works out the granule that physically follows the file's current last extent and, if that granule's bit is clear, simply grows the existing extent rather than spending a new entry. Only when that granule is taken, when the extent has reached its 32-granule maximum, or when the next cylinder lies outside the cylinder map does it scan the table for any cylinder with free space, making two passes so a search starting part way along the disk wraps round and covers every cylinder. If all four extent slots of a record are in use, a free slot is taken from the Hash Index Table, given the same name hash as the primary record, and built into a secondary extended directory record linked from its parent. A full disk returns error 27 and a full directory returns error 30.
Disassembly:
A disassembly of SYS8/SYS can be found here.
SYS9/SYS
Purpose:
SYS9/SYS contains the routines that service the extended debugging commands available after a DEBUG (EXT) is performed. It may be purged if the extended debugging commands are not needed; if SYS5/SYS has also been purged, keeping SYS9/SYS serves no purpose.
From the disassembly, almost none of SYS9/SYS runs where it is loaded. The first thirty-eight bytes are an installer: they check that the resident unrecognised-command vector at 4310H still holds its default 4876H, so a second DEBUG (EXT) cannot install the extension twice, lower HIGH$ at 4049H by 03D8H bytes, copy the remaining 984 bytes of the overlay into the space reserved, and point 4310H at the copy. The overlay then returns and the extension lives on in high memory.
What is copied is a properly formed resident module: an 18H relative-jump signature, a link word filled in with the previous value of HIGH$, a name length and the six characters EXTDBG. That is the layout the module lookup in SYS10/SYS walks, so the extension can be found by name like any driver or filter. The code is position-independent by construction, every one of its sixty internal transfers being a relative jump and every absolute address it names belonging to the ROM, the resident core, or the SYS5/SYS DEBUG overlay whose keyboard parser and hexadecimal display routines it borrows for all of its input and output.
Thirteen commands are added. Seven work on memory: b block move, f fill, e and t examine and alter as hexadecimal and as text, l and w search for a byte and a two-byte value, and v compare two blocks stopping at the first difference. Three concern control and hardware: j steps the saved program counter over a byte, q reads and writes Z80 ports, and o leaves DEBUG. One, n, walks from one record of an LDOS load module to the next. The digits 0 to 7 open raw disk sector access on that drive, with read, write and deleted-data-mark write, and @ prints a memory range to the printer.
Disassembly:
A disassembly of SYS9/SYS can be found here.
SYS10/SYS
Purpose:
SYS10/SYS contains the procedures that service the request to delete a file (the @KILL function). It should remain on working SYSTEM diskettes.
From the disassembly, SYS10/SYS is 371 bytes spanning 4E00H through 4F72H and is the exact counterpart of SYS8/SYS: where SYS8/SYS claims granules and builds extent entries as a file grows, SYS10/SYS gives every one of them back. Entered through RST 28H request code 9CH with Register Pair DE addressing the caller's File Control Block, it first refuses the request with error 37 unless the file was opened at access level 0 or 1, full or remove access. It then reads the Granule Allocation Table into a buffer at 5000H and walks the four extent entries, clearing the allocation bit of every granule each extent describes and rolling forward onto the next cylinder whenever a granule number reaches the number a cylinder holds.
Each directory record is then marked free by clearing bit 4 of its attribute byte, rewritten, and de-hashed by zeroing its byte in the Hash Index Table; an FEH marker at DIR+1EH sends the whole sequence round again for the secondary extended record it names. Only once the chain is exhausted is the Granule Allocation Table written back, and the caller's 32-byte File Control Block is then cleared. Deleting a logical device instead simply rebuilds the control block as the re-openable specification *XX.
The overlay carries a second, unrelated service. Request code 0BCH masks to sub-function 30H and selects the resident module and driver control-block lookup at 4F28H, which walks the chain of modules installed above HIGH$ comparing names and returns the matching module's control block in Register Pair DE. That is the service behind the "not resident" and "not installed" messages produced by SYS6/SYS LINK and ROUTE and by SYS7/SYS SYSTEM, FORMS and SETCOM.
Disassembly:
A disassembly of SYS10/SYS can be found here.
SYS11/SYS
Purpose:
SYS11/SYS contains the procedures that perform the Job Control Language (JCL) execution phase. It may be removed from working disks if no JCL functions will be invoked. Because the DO command that launches JCL resides in SYS6/SYS, keeping SYS11/SYS serves no purpose if SYS6/SYS has been purged.
From the disassembly, SYS11/SYS occupies 4E00H through 5180H and 5187H through 51FBH, the six bytes between being an uninitialised scratch area that the load module deliberately steps over. The DO command compiles a JCL source file and issues RST 28H request code 9DH to start the compiled job; this overlay then sees it through to the end.
The central idea is a small one. A job is a list of commands the operator would otherwise type, so the overlay makes the job file be the keyboard: it saves the real *KI driver address out of KIDCB$ at 4016H into KIJCL$ at 43BEH and writes in its place a two-instruction routine that returns the next byte of the job file. The ordinary ROM line-input routine then collects a line of the job into INBUF$ without any idea that it is not reading the keyboard. Because a job runs programs that finish or fail, six bytes are also copied over the @EXIT, @ABORT and @DVRHK vectors at 402DH so that a program exiting re-enters the overlay with request code 0ADH and one aborting re-enters it with 0BDH.
Later keyboard requests arrive through @DOKEY as request code 0CDH. Since the supervisor discards the return address the RST 28H pushed, the overlay can read the calling program's own stack frame: it looks fourteen bytes down for 05E3H, the address the ROM line-input routine's CALL 0049H leaves behind while waiting for a key, and answers only those requests from the job file. Ten bytes down lie that routine's buffer counts, whose equality shows a fresh line is starting.
Ten run-time directives are held in a keyword table of seven-byte entries at 51B5H - ABORT, ALERT, DELAY, EXIT, FLASH, KEYIN, PAUSE, STOP, WAIT and INPUT - each five characters of name followed by the address of its handler. KEYIN is what gives JCL its branching: it displays a prompt, reads one key, and scans forward through the job file for a line beginning with two slashes and that key, or three slashes which matches anything. ALERT plays a sequence of numbers as alternating tones and silences through bit 0 of the cassette port, with bracketed groups repeated.
Disassembly:
A disassembly of SYS11/SYS can be found here.
SYS12/SYS
Purpose:
SYS12/SYS contains the routines that service the @DODIR and @RAMDIR directory vectors. These routines are used by the MiniDOS filter and may also be used by other application programs that provide a directory display. It can be removed if the two "mini" directory routines are not needed.
From the disassembly, SYS12/SYS occupies 4E00H through 50FDH, 766 bytes in one contiguous piece, and deposits that same length as the two-byte load block 02FEH at 4BC9H. Entry is at 4E00H with the request code still in Register A; masking it with 70H gives 20H for @DODIR, which the resident vector at 4463H raises as code AEH, and 10H for @RAMDIR, which callers raise directly as code 9EH because that service has no resident vector of its own.
Neither service reads the directory record by record. Both read the Hash Index Table, the second sector of the directory cylinder, into the scratch page at 5100H and step a record number through it in slot-major order, adding 20H at a time so that all eight slots of one directory sector are visited before the sector number is advanced. Only slots whose hash byte is non-zero are looked up, and a self-modifying compare remembers which directory sector already sits in the shared buffer at 4200H, so a run of eight records costs one disk read.
@DODIR offers five functions. Function 0 displays file specifications four to a line and fifteen lines to a screen, pausing for a keypress and clearing the screen when one fills; function 1 copies eighteen raw directory bytes per file into the caller's buffer and ends the list with 0FFH; functions 2 and 3 repeat those two with an extension mask filter in which a dollar sign matches any character; and function 4 returns the disk name and date copied straight out of the Granule Allocation Table together with the capacity and the free space. @RAMDIR instead produces fixed 22-byte records - a fifteen-character blank-padded file specification, the access level, the end-of-file byte, the logical record length, the ending record number and the file size in kilobytes - ending the table with a 2BH marker that each new record overwrites.
Free space is counted by rotating each Granule Allocation Table byte right with a one shifted in from the left, which tests one granule bit per pass and ends naturally when the byte has become 0FFH. The number of cylinders to examine comes from the configuration byte at Granule Allocation Table offset CCH, which holds the cylinder count less 35, and sectors are turned into kilobytes by adding two and dividing by four. Before any of this, every entry seeks the drive and watches the WD1771 index-pulse status bit go clear, set and clear again, which proves a diskette is actually turning; twenty clock ticks without that cycle returns error 8, device not available.
Disassembly:
A disassembly of SYS12/SYS can be found here.
BACKUP/CMD
Purpose:
BACKUP/CMD duplicates data from one disk to another. It supports mirror-image and file-by-file (class) backups, with parameters to select, exclude, and filter the files that are copied.
BACKUP is a single transient load module that carries three programs in one image and only ever runs one of them. The command line is parsed by a front end assembled to run at 5A00H, and the two backup engines are assembled to run at that same address: the mirror-image engine is stored at 5E00H and the file-by-file engine at 6300H. When the front end has decided which kind of backup is required, the selector at 6C00H block-moves the chosen engine down over the front end that has just finished with it and jumps to 5A00H. Everything above the moved engine, from the rounded-up end address kept at 5285H to the top of memory reported by HIGH$ at 4049H, becomes the sector and directory work space, which is why the file will not run in a machine whose high memory has been pushed down too far.
A mirror-image backup is decided before any data moves. The engine compares ten bytes at offsets CEH to D7H of the two Granule Allocation Tables - the master-password hash followed by the eight-character disk name, which together are the Pack ID the manual describes - and on a mismatch asks Different pack IDs! Abort backup ?. It then walks the two lock-out maps at GAT offset 60H and fails with Backup aborted, destination not mirror-image if any cylinder that carries data on the source is locked out on the destination. The copy itself runs a cylinder at a time in three passes, displaying Loading cylinder, Dumping cylinder and Verifying cylinder, each with the cylinder number rewritten in place through a leading 1DH that returns the cursor to the start of the line. Because the transfer is timed against the drive rather than the clock, the engine saves the resident interrupt vector byte at 4012H and replaces it with a RET for the duration, which is the reason the utility ends with Note: Real time clock no longer accurate.
A backup by class or a backup reconstruct copies file by file. The engine walks the Hash Index Table rather than reading every directory sector, rejecting free, secondary, system and invisible slots against the SYS and INV switches, then applies the partspec and its wildcard character, the MOD flag, the DATE range, and the NEW and OLD tests, and finally the QUERY prompt, which displays the file specification with its modification date and a plus sign when the modification flag is set and accepts Y, N, C or ENTER. Dates are formatted through the three-letter month table at 6A14H, and the year is taken either from the three low bits of the old-format date byte or, when bit [drive] of the resident dating flag byte at 475DH says the pack is in the x.3 extended format, from the five-bit extended year in the word at directory offset 12H. Creating a SYSTEM disk from a formatted DATA disk is the other half of the FORMAT finding about Hash Index Table ordering: BACKUP must claim the thirteen further slots the /SYS overlays occupy, checking all fifteen against one table, and refuses with Can't create SYSTEM disk - directory slots in use! when they are already taken.
The X parameter lets a backup run with no system disk in drive 0, and BACKUP verifies that this is safe before agreeing to it. It reads the word at 4BDFH inside the resident supervisor-call dispatcher, which is the operand the overlay loader keeps for its own address; if it still holds 4BF5H then no overlays have been made resident and the utility refuses with This backup requires residency of SYS's: 2, 3, 8, & 10.. Otherwise it uses that address as a base and tests four bytes below it, one for each of the four overlays the file-by-file path depends on. All disk work is done through a private copy of the drive's Drive Control Table entry reached with a JP (IY), the same technique FORMAT/CMD uses, with the ten-byte block located through the resident GETDCT routine at 478FH.
The manual states flatly that BACKUP ignores any password protection on a file, and the disassembly shows how. A ten-byte string at 63E0H holds a full stop, the eight characters RS0LT0FF and a colon, and the routine at 6819H appends all ten to every filespec the utility builds, immediately before the drive digit. In an LDOS filespec a full stop introduces the password field, so every file is opened as NAME/EXT.RS0LT0FF:d. That also settles what the constant 113DH inside SYS2/SYS is: the open service hashes the supplied password and compares the result against it, granting access on a match without consulting the directory at all. Running these eight characters through that hash routine produces 113DH exactly, and the same routine reproduces both known constants, 4296H for eight blanks and 42E0H for the default master password, so the identification is certain rather than inferred. The plaintext is the name of the MISOSYS author, Roy Soltoff, with each letter O written as a zero.
BACKUP issues only two supervisor requests of its own, as two adjacent three-byte tail-call stubs at 6C6BH and 6C6EH: code 0E4H to hash the master password typed in answer to Master password ?, and code 84H to make SYS2/SYS resident. It reads and writes the drive's extended-dating flag at 475DH through two self-modified CB instructions whose second byte is computed from the drive number, so one instruction body serves all eight drives.
Disassembly:
A disassembly of BACKUP/CMD can be found here.
DATECONV/CMD
Purpose:
DATECONV/CMD updates LDOS disks created under version 5.1.4 or earlier to the 5.3 style of dating. LDOS 5.3 expanded the date range to 1980-1999 and added a modification time stamp to each directory entry, bringing the on-disk convention into line with LS-DOS 6.3; DATECONV converts older disks so their date/time information is interpreted correctly. The command syntax is DATECONV :d, where :d names the drive to convert.
DATECONV is a transient utility loaded at 5200H. It parses the drive from the command line, confirms the pack is mounted and not write-protected through the resident @CKDRV service, and reads the directory cylinder named by the Drive Code Table. It then reads sector 0 of that cylinder (the Granule Allocation Table) and tests bit 3 of the high byte of the GAT configuration word: that bit marks a disk already in the x.3 extended-dating format. On an older non-x.3 disk that is a bootable SYSTEM disk lacking the required x.3 system files, DATECONV refuses with SYSTEM disk conversion requires a 5.3 or 6.3 disk!, matching the manual's instruction to first move x.3 system files onto such a disk with BACKUP. On a disk already in the x.3 format it converts only files whose user access-password word is still blank (the hash 4296H), so that genuine 5.3 time stamps are protected.
For every in-use primary directory record that carries an old-style date, DATECONV copies the record's three-bit year field into the extended-dating word - the directory field that under LDOS 5.1.4 held the user access-password hash and under 5.3 carries the packed modification time and extended year. It counts each file it updates, writes every modified directory sector back with the deleted-data address mark LDOS uses for directory sectors, sets bit 3 of the GAT configuration word to mark the whole pack as an x.3 dated disk, and prints the number of files updated. Pressing BREAK during the scan aborts the conversion. DATECONV issues no RST 28H request of its own; every service it uses is a Model I ROM low-core routine or a resident SYS0/SYS entry point, so no overlay loads while it runs.
Disassembly:
A disassembly of DATECONV/CMD can be found here.
FDUBL/CMD
Purpose:
FDUBL/CMD installs the double-density disk driver used with double-density hardware modifications on the Model I under DOS version 5. It patches the floppy driver so the Radio Shack interface (fitted with a supported double-density board) can read and write double-density diskettes.
The driver is installed as a named resident module. FDUBL runs at 5200H, builds the requested variant of the driver inside its own image, relocates it into high memory below HIGH$ (4049H) and lowers HIGH$ to protect it. The module carries a standard LDOS resident-module header and is named $FDD, so it can be located by name through the module lookup service, RST 28H request code 0BCH. FDUBL then walks all eight Drive Control Table entries at 4700H and rewrites the driver address inside each drive's JP vector, so every disk operation on every eligible unit is routed through the new module instead of through the resident single-density driver at 45FBH in SYS0.
Two incompatible doubler designs are supported. The default is the Radio Shack Double-Density Adapter Kit, whose density and write-precompensation latches are driven by the three high-order bits of a write to the controller sector register at 37EEH, decoded on the adapter board. The PERCOM parameter selects the Percom Doubler instead, whose latch is set by writing 0FEH or 0FFH to the controller command register at 37ECH and then cancelling that command with a Force Interrupt. The Percom board implements no write precompensation, so FDUBL patches its precompensation routine out of the driver and re-bases the module when that variant is built. A TANDY parameter exists only so that naming both designs on one command line can be rejected.
The resident module is a modified copy of the SYS0 floppy driver. Drive selection, track positioning and Type I command issue still go through the resident routines at 45E0H, 461CH, 462AH, 462DH, 4659H and 4662H, and controller status is still translated to an LDOS error number by the resident decoder at 46F4H, whose base error number FDUBL writes directly at 46F6H. What the module adds is the density latch write before each operation, an unrolled data request polling loop fast enough for the doubled MFM data rate, a density-dependent choice of the deleted data address mark write command (0ABH for the WD1771 in single density, 0A9H for the WD1791 in double density), and an error recovery path that inverts the drive's density flag and reloads the drive geometry so that a diskette recorded in the other density is still readable.
The distributed file carries the applied patch FDUBL1, which writes the correct entry displacement into the module header for the Percom build. Without it the relocated Percom module entered its driver 21 bytes past the operation dispatcher, so FDUBL1 is what makes Percom mode work.
Disassembly:
A disassembly of FDUBL/CMD can be found here.
FORMAT/CMD
Purpose:
FORMAT/CMD writes the track, sector, and directory information onto a diskette, organizing it into cylinders, tracks, and sectors so DOS can access it. This is the utility that creates the BOOT/SYS and DIR/SYS system files on a new disk.
FORMAT is built in two phases that occupy the same memory. Its transfer address enters a front end which reads cylinder 0 sector 2 of drive 0, the system configuration sector, for the machine's default stepping rate and its per-drive default cylinder counts, parses the command line through @PARAM, asks the operator for whatever is still unanswered, checks that the target drive is present, ready and unprotected, and then jumps into the format engine. The engine expands its track image upward from the address the front end was itself executing at, which is why it tests HIGH$ before it starts: the buffer can be as long as 10673 bytes.
Nothing about the format is computed on the fly. Four packed templates describe the four combinations of drive size and density, each as a run-length stream with five escape codes that expands into the literal byte sequence the WD1771 Write Track command lays on the surface. A template carries the sectors per track, the cylinder-to-cylinder skew, the sector interleave table with negative wrap markers, the stream itself, and behind it the list of sector numbers the verify pass walks in physical order. Marker bytes in the stream record where each sector identifier field lands, and FORMAT fills the cylinder, side and sector into those positions before every write.
FORMAT never uses the resident disk primitives for the format pass. It takes a ten-byte copy of the target drive's Drive Code Table entry, points Register Pair IY at the copy, and reaches the drive's own driver through the JP in its first three bytes, so it can alter the side select bit, the geometry and the current cylinder without the DOS ever seeing it. The head is positioned only by Restore and Step In; no seek is ever issued. A cylinder that fails verification has its granule allocation map byte set to 0FFH, and that map is copied wholesale into the lock-out map the FREE command displays.
The two boot sectors FORMAT writes to cylinder 0 are carried complete in its image, both assembled to run at 4200H where the Model I boot ROM places sector 0. The single density copy works the WD1771 directly and assumes ten sectors on a track; the double density copy goes through the Drive Code Table, reads the Granule Allocation Table first to learn the geometry, and is the one SOLE brings into use. Each is patched with the directory cylinder and the stepping rate before it is written. Both look for the resident system loader in slot 0 of sector 4 of the directory cylinder, which pins down the Hash Index Table ordering: directory record n is slot 0 of sector 2 plus n, so a DATA disk reserves two slots and a SYSTEM disk fifteen in total, those two plus thirteen for SYS0/SYS through SYS12/SYS.
FORMAT issues only two RST 28H requests, both to SYS2. Code 84H carries no sub-function and SYS2 rejects it with an immediate RET, so its only effect is to make SYS2 resident before the disk is touched; code 0E4H is the password hash, and because the two share a low nibble the second is served from memory without a further directory read. Everything else is ROM low core or a resident SYS0 vector.
Disassembly:
A disassembly of FORMAT/CMD can be found here.
PATCH/CMD
Purpose:
PATCH/CMD installs byte-level corrections into an existing disk file - normally a load module such as a /CMD program or a /SYS overlay - and is the standard mechanism for applying the official fixes MISOSYS distributed as /FIX files. A patch file (or an inline command-line patch) is a series of verb lines: X'nnnn'= patches by memory load address, appending a named load module to the file that loads over that address and can later be removed with the YANK parameter; Drr,bb= directly modifies record rr, byte bb in place, optionally guarded by a matching Frr,bb= find line and removable with the REMOVE parameter; and Lnn selects a library command module in SYS6/SYS7 to patch. Lines beginning with a period are comments.
Disassembly:
A disassembly of PATCH/CMD can be found here.
SOLE/CMD
Purpose:
SOLE/CMD is a Model I utility that installs a double-density (DDEN) boot driver under DOS version 5, allowing the system to boot from a double-density system diskette on suitably equipped Model I hardware.
Disassembly:
A disassembly of SOLE/CMD can be found here.
KI/DVR
Purpose:
KI/DVR is the DOS version 5 keyboard driver. It provides Type Ahead, Screen Print, key repeat, and special <clear>-key control functions, and establishes <shift><0> as a CAPS-lock key. It is a required foundation for many DOS features: SPOOL, the SYSTEM (SVC) table, KSM, MiniDOS, and LCOMM all depend on it. Using the driver with the TYPE option is strongly recommended.
From the disassembly, KI/DVR is not a SYS overlay but a transient installer, loaded and entered at 5200H by the SET library command. It carries no copyright load record - the copyright string sits inside the loaded image - and fills one contiguous block, 5200H through 573CH (1341 bytes). It relocates up to three resident modules into high memory below HIGH$ (4049H): $KI, the keyboard scan, debounce, translation and key-repeat engine, installed always; $TA, a 128-character type-ahead ring buffer with its own interrupt task, installed only with the TYPE option; and $JKL, the screen-print filter, installed only with the JKL option. Each module is hooked in front of the previous keyboard driver through KIDCB$ at 4016H, or through KIJCL$ at 43BEH when a Job Control Language job holds the real driver address - the same cell SYS11/SYS saves and restores for request code 9DH.
The four options are read by @PARAM against a table at 56FCH, in which every keyword appears both spelled out and abbreviated to its first letter. TYPE and JKL are switches: @PARAM writes 0FFFFH into the operand of an LD BC that gates the corresponding install. DELAY=d and RATE=r store a value into the operand of an LD BC in the resident driver; the pre-set values are 20 (DELAY, floored at 10) and 2 (RATE, floored at 1). The resident $KI driver scans the eight keyboard matrix rows at 3801H-3840H, keeping an eight-byte debounce state table at 4036H-403DH, resolves each key through two sixteen-byte translation tables, and times the first-repeat delay and repeat rate against TIMER$ (4040H). The screen-print module walks video RAM 3C00H-3FFFH and sends each line to *PR through the ROM @PRT vector.
A refinement to the SYS0/SYS resident reference follows from the driver code: the KI/DVR-active flag is DFLAG$ (441FH) bit 4, not bit 3; bit 1 is type-ahead active and bit 2 is screen-print active. KFLAG$ (4423H) bit 5 is the CAPS-lock flag (matching the DOS manual POKE), bit 6 selects the special-key translation table and bit 7 signals that the type-ahead buffer holds data. KI/DVR issues no RST 28H request of its own, so no request-code rows change.
Disassembly:
A disassembly of KI/DVR can be found here.
PR/FLT
Purpose:
PR/FLT is the DOS version 5 printer filter. It lets the user format printed output - controlling page length, margins, line spacing, and character translation - by filtering data on its way to the *PR device.
From the disassembly, PR/FLT is neither a SYS overlay nor a command but a transient installer, loaded and entered at 5200H by the FILTER library command with Register Pair DE holding the device's DCB address and Register Pair HL holding the parameter text. Only a type-1FH copyright record precedes the load records; the transfer address is 5200H and the image fills 5200H-550CH and 5517H-5629H, 1056 bytes, with a deliberate 10-byte unloaded gap at 550DH-5516H. The installer relocates a 293-byte block, 5505H-5629H, into high memory below HIGH$ (4049H) and registers it under the module name $FF. It refuses to run on a device whose DCB type byte marks it inactive (bit 3), routed (bit 4) or incapable of output (bit 1 clear), and on a system where the filter is already installed.
Installation lowers HIGH$ by 0125H only once. The base address of the block is recorded in the resident cell 4DF6H, and a later installation finds that cell non-zero and re-uses the same memory, which is the mechanism behind the DOS manual's statement that re-installing the filter occupies the same allocation. Seventeen operands inside the module are adjusted from the relocation table at 54E3H. The module header at 5505H is the standard LDOS form - an 18H signature byte that is simultaneously the JR opcode of the entry vector, a link word, a name length and the name - and the ten bytes following the name are its control block: an eight-byte copy of the printer DCB, through which every pass-through call is made, and a two-byte pointer back to the real DCB.
The page geometry does not live in the module. PAGE, the current line number and LINES are held in DCB+03H, DCB+04H and DCB+05H of the printer's own Device Control Block, both PAGE and LINES stored with one added; the character-formatting parameters (column counter, XLATE pair, INDENT, flag byte, CHARS, MARGIN) occupy the seven bytes at 5517H-551DH. Those two areas are exactly the ten-byte structure the FORMS command in SYS7/SYS overlay 12 reads and rewrites after asking SYS10/SYS for the $FF control block with RST 28H request code 0BCH, following the word at control block + 8 to reach the DCB. The flag byte's bit 3, which SYS7 FORMS preserves and applies as a plus-one bias, is the SLINE parameter: set for the Model III convention of 67 lines with the first line numbered 1, clear for the Model I convention of 66 lines numbered from 0.
PR/FLT also carries a complete Model I printer driver of its own at module base + 3AH. It is substituted into the DCB whenever that DCB still points at PRSV$ (43BCH), the DOS's saved printer driver, and it is what makes the PORT= parameter possible: the installer rewrites its LD A,(37E8H) and LD (37E8H),A into IN A,(port) and OUT (port),A followed by a NOP. That driver masks the printer status with 0F0H and reports a result of 0F0H as ready, which is why a machine with no printer attached does not lock up, and reports 30H as the genuine ready state. The parameters are read by @PARAM against a 21-entry table at 5430H in which every keyword appears spelled out and abbreviated; five of the value cells are ordinary storage at 54D9H-54E2H and the other five are 16-bit instruction operands inside the installer itself.
The filter and the print spooler in SYS6/SYS overlay 6 know about each other in both directions, and reading the two together settles both cells. If the filter goes in first, SPOOL tests DFLAG$ bit 3, takes the $FF module base from 4DF6H, walks the header's name-length byte to reach the control block, follows the word at control block + 8 to the real printer DCB, and when that is the device it is about to spool it hooks itself into the filter's private DCB instead of the device DCB. If the spooler goes in first, the block at 533FH in PR/FLT does the mirror-image job: 4DF0H gives the spooler's control block, the word at its offset 8 gives the address of the cell holding the despooler's output DCB pointer, and PR/FLT replaces that pointer with its own private DCB, so the despooler hands the printer driver the same control block the filter uses without passing the queued text through the filter twice.
Three refinements to the SYS0/SYS resident reference follow. DFLAG$ (441FH) bit 3 is the forms-filter-installed flag: PR/FLT aborts with "PR/FLT already active!" when it is set and sets it itself at 524BH, which completes the correction begun by KI/DVR, whose own flag is bit 4 - SPOOL's test of that KI/DVR bit reads as bit 3 only because it is applied to an RRCA-rotated copy of DFLAG$. The resident cells 4DF0H and 4DF6H, which SYS0/SYS loads as zero and never touches, belong to transient installers, and they are not laid out alike: 4DF6H holds the $FF module BASE, while 4DF0H holds the spooler's CONTROL BLOCK address, its base plus 0AH, because the name "SPOOL" is five characters long. PR/FLT issues no RST 28H request of its own, so no request-code rows change.
Disassembly:
A disassembly of PR/FLT can be found here.
BASIC/CMD
Purpose:
BASIC/CMD is LBASIC, the enhanced Disk BASIC interpreter supplied with LDOS. It extends standard Microsoft BASIC and allows most standard Microsoft BASIC programs to run unmodified. It uses the overlay files BASIC/OV1 through BASIC/OV4 for certain extended features.
It is not a complete interpreter. The Model I Level II BASIC ROM at 0000H-2FFFH already contains the tokenizer, the expression evaluator, the statement executor and the whole floating-point library, and it was built with a block of RAM hooks reserved for a disk extension. BASIC/CMD is that extension: it supplies the code behind every disk keyword the ROM knows the name of but has no body for, and it takes over the ROM's inner character-fetch loop so it can add keywords the ROM has never heard of. The file is a single LDOS /CMD load module with the transfer address 52CCH, loading in two pieces - 159 bytes at 4E00H-4E9EH and 5116 contiguous bytes at 5200H-65FBH.
The 4E00H piece is never executed where it lands. It is two blocks of JP instructions that the initialization code copies away and then abandons: 147 bytes to 4152H-41E4H, the ROM's forty-nine-slot Disk BASIC entry-point table, and 12 bytes to 4000H-400BH, the RAM hooks behind RST 08H, RST 10H, RST 18H and RST 20H. Three of those four RST hooks are pointed back at the ROM routines they normally reach; the fourth, RST 10H, is diverted to BASIC/CMD's own routine at 6579H instead of the ROM's CHRGTR at 1D78H, which is what lets LBASIC recognise its own keywords. Loading into 4E00H is safe because no numbered SYS overlay is resident while a /CMD program's load records are being placed, and once the copies are made BASIC/CMD re-uses the area as the load address for BASIC/OV3.
LBASIC accepts four command-line parameters through the resident @PARAM service at 4476H: MEM (or M) sets the highest address BASIC may use, FILES (or F) sets the number of disk file buffers to build, BLK (or B) selects variable-length record blocking, and EXT (or E) controls the extended-feature handler. A version gate reads OSVER$ at 403EH and refuses to run below 5.3, displaying "Use LDOS 5.3 or later!". Two other messages, "Parameter error" and "Out of memory", cover the failure paths.
CMD"N", CMD"X" and CMD"V" load BASIC/OV1, BASIC/OV2 and BASIC/OV4 with @RUN at 4433H, replacing LBASIC's own image at 5200H. Any other CMD string is treated as an LDOS command: BASIC/CMD copies it to INBUF$ at 4318H, block-moves the whole of BASIC's low memory up under the stack, lowers HIGH$ to protect it, rewrites @EXIT, @ABORT and @ERROR to point at relocated stubs, and hands the line to @CMNDI at 4405H, restoring everything when the command finishes. BASIC/OV3, which holds the long error-message text and the Sort routine, is treated differently again because it is a subroutine rather than a program: it loads at 4E00H and BASIC/CMD uses OVRLY$ at 430EH as its "already resident" marker, entering the overlay with no disk access at all when that byte holds 01H. BASIC/CMD issues no RST 28H supervisor request of its own; everything it asks of the operating system goes through a Level II ROM entry point or a resident SYS0 vector.
Disassembly:
A disassembly of BASIC/CMD can be found here.
BASIC/OV1
Purpose:
BASIC/OV1 is the DOS version 5 LBASIC overlay that provides the CMD"N" renumber feature. It implements the statement CMD"N ! line,newline,inc,last", which renumbers the program in memory and, unlike a DOS-level line move, correctly adjusts every internal line-number reference in GOTO, GOSUB, THEN, ELSE, RESUME, RUN, ON...GOTO/GOSUB and ERL. The parameters line and last bound the old line range, newline is the new starting number, inc the increment, and the optional leading "!" skips the pre-renumber error scan.
The overlay is loaded to 5200H with @RUN, replacing LBASIC's own image, and entered with Register Pair HL at the parameter string. It assigns each line its new number by storing it in that line's link word, validates that references resolve and that numbers stay in ascending order below 65529, rewrites the reference digits line by line into a work buffer and splices each line back into the program, then commits the new numbers, rebuilds the line links and reloads BASIC/CMD. Both BASIC/CMD and BASIC/OV1 must be present on the disk.
Disassembly:
A disassembly of BASIC/OV1 can be found here.
BASIC/OV2
Purpose:
BASIC/OV2 is the DOS version 5 LBASIC overlay that provides the cross-reference CMD"X" feature. It implements the statement CMD"X devspec/filespec -V|=var,-L|=lnum,<title>", which lists every variable and every line number reference in the BASIC program currently in memory, each followed by the numbers of the lines that mention it. The banner identifies it as "BASIC Cross Reference Listing Utility - Version 1.22", copyright 1987 MISOSYS, Inc. DOS version 6 drops the CMD"X" statement and supplies the same facility as the separate BREF/CMD utility, run from DOS Ready.
The listing goes to the video display unless a device or file specification is given, and a disk file receives the default extension /TXT. The -V and -L switches restrict the listing to variables or to line numbers, an =name or =number restricts it to a single entry, and text between angle brackets becomes a title printed at the head of every page. Both BASIC/CMD and BASIC/OV2 must be present on the disk.
The overlay is a standard LDOS load module of 1647 bytes: one type-1FH copyright record followed by seven type-01H load records and a type-02H transfer record, giving a contiguous image at 5200H-581BH with the entry at 5200H. Code occupies 5200H-5720H and data 5721H-581BH; the work cells at 581CH-5866H and the 256-byte listing buffer at 5900H are uninitialised RAM which the overlay simply claims. It is loaded over LBASIC's own image at 5200H by @RUN at 4433H, exactly as BASIC/OV1 and BASIC/OV4 are, and is entered with Register Pair HL pointing at the parameter text inside INBUF$ at 4318H.
There is no sort table in the overlay. One complete pass is made over the program text for every entry printed: each pass keeps the lowest reference that is still higher than the entry printed last, together with the line numbers of every line that mentions it, and the listing ends when a pass finds nothing. The scanner reads each line through Register Pair IY as (IY+04H), skips string literals, REM text, DATA item lists and numeric constants, and collects a variable as its first letter, second character, type declaration character and opening parenthesis, which is exactly the identity Level II BASIC gives a variable. A line number is collected only after one of the seven tokens that may precede one - THEN, ELSE, RESUME, GOTO, GOSUB, RUN and ERL - and is right-justified in five characters with leading zeros so that one character comparison orders both names and numbers.
BASIC/OV2 issues no RST 28H supervisor request of its own. It reaches the operating system through the resident vectors @DSPLY 4467H, @FSPEC 441CH, @FEXT 4473H, @INIT 4420H, @CLOSE 4428H, @ERROR 4409H, @KITSK 4300H and @RUN 4433H, reads and clears KFLAG$ at 4423H directly, and uses the Level II ROM entry points $PUT 001BH, @KBD 002BH, $KEY 0049H, MAKINT 0A9AH, FOUINI 1034H and FOUT2 0FD9H. It exits by reloading BASIC/CMD with an asterisk in INBUF$ at 4318H, so the interpreter returns with the program and the variables intact.
Disassembly:
A disassembly of BASIC/OV2 can be found here.
BASIC/OV3
Purpose:
BASIC/OV3 is the one LBASIC overlay that must be on the disk whenever DOS version 5 BASIC is running. The other three are loaded on demand by a single CMD statement and replace LBASIC's own image while they run; BASIC/OV3 is a subroutine library that LBASIC calls back into repeatedly, which is why the MISOSYS BASIC Reference Manual says it must always be present when programming in BASIC. It carries six services behind one entry point: the long error message dictionary, the translator that maps an LDOS error number onto a BASIC one, the CMD"O" string array sort, the machinery that keeps the variables alive across a chained load, and the two DOS version 5 single-key program line edit commands, M n1,n2 and C n1,n2.
Alone among the four overlays it loads at 4E00H rather than 5200H, occupying 4E00H-5281H with the transfer address 4E00H, so it sits above BASIC/CMD's own image instead of replacing it. That is what allows it to be called as a subroutine and return. BASIC/CMD tracks it with OVRLY$ at 430EH and skips the disk access entirely when that byte holds 01H. The module is 1229 bytes on disk: one type 1FH copyright record, five type 01H load records and a type 02H transfer record, giving a contiguous 1154-byte image with no gaps and no applied patch records. The top of the image does overlap BASIC/CMD's own banner, message and parameter table block at 5200H-5281H, which is safe because none of that data is referenced once BASIC has started.
Entry is always at 4E00H with a function selector in the alternate accumulator, because BASIC/CMD's loader at 54B6H needs the primary accumulator for OVRLY$ and for the @LOAD error code. The six selectors are 01H expand a BASIC error number into message text, called from 5A42H; 02H translate an LDOS error number, called from 644FH; 03H sort a string array, called from the CMD"O" stub at 5834H; 04H retain the variables across a chained load, called from the RUN"filespec",V and LOAD"filespec",V path at 5EB0H; and 05H and 06H, the Move and Copy line commands, both called from the READY prompt line collector at 6459H through its one-letter abbreviation table at 656BH.
The error dictionary is built exactly the way SYS4 builds the DOS error dictionary. Messages are not stored as strings: a dictionary of seventy-seven English words at 50D6H holds each word once with bit 7 set on its final character, an index table at 5057H holds one entry per message as a run of word numbers with bit 7 set on the last, and the expander walks both tables counting bit-7 bytes, so neither table needs offsets. Forty-nine messages are built from 428 bytes of dictionary and 127 bytes of index, and a third table at 502DH maps the forty-two LDOS error numbers onto them. Twenty-one different LDOS numbers all report Disk I/O error, which is precisely why the manual recommends CMD"E" for the real reason; the byte CMD"E" reads, 65F7H, is written here at 4E6CH and nowhere else.
Selector 04H is the missing half of BASIC/CMD's chained load. It voids every DEF FN definition pointer, lifts any string constant still living inside the program text into string space through ROM GETSPA, moves the whole variable block up under the stack with LDDR, lowers STKTOP to protect it, and fills in the five 16-bit operand cells at 5F5EH, 5F65H, 5F71H, 5F7EH and 5F84H that ship as zero in the BASIC/CMD image and that no BASIC/CMD instruction ever writes. It also writes 0D6H to the chain state byte 65F4H, the only place that value is ever set. BASIC/OV3 issues no RST 28H supervisor request of its own and calls no resident LDOS routine: OVRLY$ at 430EH is the only operating system cell it touches, and every external call it makes is to a Level II ROM entry point.
Disassembly:
A disassembly of BASIC/OV3 can be found here.
BASIC/OV4
Purpose:
BASIC/OV4 is the DOS version 5 LBASIC overlay that dumps a list of the currently active variables. It backs the statement CMD"V [*DO | *PR] [-S] [-A] [=x]", which lists every scalar variable, every element of every array and every user defined function that exists in the BASIC workspace at the moment the statement is executed. It can be used while a program is running, after it has been stopped with BREAK, or after it has ended, because the overlay reads the interpreter's own variable tables rather than the program text. *DO selects the video display and is the default and *PR selects the line printer; -S restricts the dump to scalars, -A restricts it to arrays, and =x restricts it to variables whose first letter is x. Both BASIC/CMD and BASIC/OV4 must be present on the disk. The banner in the image identifies it as "BASIC Active Variable Dump - Version 4.1", although no instruction in the overlay ever displays that banner.
The overlay is a standard LDOS load module of 960 bytes: one type 1FH copyright record, five type 01H load records and a type 02H transfer record. It is the only one of the four BASIC overlays whose image is not contiguous, occupying 5200H-5569H and 5574H-557EH with a ten byte hole at 556AH-5573H. That hole is deliberate: it is the block of five 16-bit subscript counters, and the overlay zeroes all ten bytes itself before it starts on an array rather than carrying ten zeros on disk. The transfer address is 5200H. It is loaded over LBASIC's own image by @RUN at 4433H exactly as BASIC/OV1 and BASIC/OV2 are, entered with Register Pair HL pointing at the parameter text one byte into INBUF$ at 4318H, and it runs on LBASIC's stack without switching to its own. Because the image stops at 557EH, everything of BASIC/CMD above that address survives while the overlay runs, including the RST 10H replacement at 6579H and the targets of the whole forty-nine slot Disk BASIC vector table.
BASIC/OV4 carries no floating point formatter and no string handling of its own. For every variable it finds it builds a fragment of BASIC source text in a work buffer above its image: a quotation mark, three blanks, the variable's name and type declaration character, the characters space equals space, a closing quotation mark, and then the same reference a second time as live source. It hands that fragment to the Level II ROM PRINT loop, entering it at 2069H, two bytes into LPRINT, so that the device code already in Register A is planted in PRTFLG at 409CH on the way in. The interpreter evaluates the reference and prints the value, which is how a single overlay of 885 bytes displays integers, singles, doubles and strings without knowing anything about any of them. A user defined function is handled the other way round: its tokenized definition is copied out of the program text and expanded back into readable keywords by the ROM untokenizer BUFLIN, entered at 2B85H so that the destination comes from Register Pair BC instead of from BUFPNT at 40A7H, which under LBASIC points into BASIC/CMD's own initialization code and therefore inside this overlay's image.
An array is enumerated by an odometer over the five subscript counters. The dimension sizes in an array header are stored last dimension first, so the pointer starts at the far end of that table and steps backwards through it as the subscript list is built from left to right, while the counter pointer steps forwards. Each element rewinds the output buffer to the position saved on the stack for the current subscript, so only the digits change from line to line. An array declared with six or more dimensions cannot be represented by five counters and produces the message "* Too many subscripts *" instead of a listing. The keyboard is polled after every line: shift and at-sign holds the display and BREAK abandons the dump, in both cases through the ROM routines at 002BH and 0049H rather than through KFLAG$.
BASIC/OV4 issues no RST 28H supervisor request of its own. It reaches the operating system through the resident vectors @RUN 4433H, @DSPLY 4467H, @PRINT 446AH, @LOGOT 447BH and @DIV 44C4H, and it reads VARTAB 40F9H, ARYTAB 40FBH and STREND 40FDH directly. It exits the same way BASIC/OV1 and BASIC/OV2 do, by moving "BASIC/CMD" into CFCB$ 4480H, writing an asterisk into INBUF$ 4318H and jumping to @RUN, so the interpreter returns with the program and the variables intact. The byte immediately before that filespec is an ASCII L that is not copied, the same shared source fragment artefact that appears in BASIC/OV1 and BASIC/OV2.
Disassembly:
A disassembly of BASIC/OV4 can be found here.
TED/CMD
Purpose:
TED is the full screen text editor supplied on the LDOS 5.3.1 distribution disk, and it is the one program on that disk which is an ordinary application rather than a part of the operating system: nothing else on the disk calls it and it issues no RST 28H supervisor request of its own. The manual describes it as a "quick" editor that "was not designed to be a full featured word processor", and the code agrees. There is no word wrap, no formatting and no undo. What there is, is a text buffer that fills all of free memory, four-directional cursor movement, insert and overstrike entry, five kinds of deletion, six block operations, search and replace, a directory query, and disk LOAD and FILE, every one of them driven from a single keystroke. The command is TED [filename[/TXT]]; a file named on the command line is loaded automatically because initialisation copies the filespec into the prompt buffer and plants a CTRL L keystroke for the main loop to find, and a command line beginning with an asterisk suppresses the clearing of the text buffer, so TED * re-enters the editor over whatever the previous run left in memory. The status line banner reads TED 1.2 - (c) 1991 MISOSYS, Inc while the copyright record ahead of the code reads Copyright (c) 1987 MISOSYS, Inc., All rights reserved.
TED will not run without KI/DVR, the DOS version 5 keyboard driver installed with SET *KI TO KI/DVR. The transfer address at 529FH tests bit 4 of DFLAG$ at 441FH and aborts with TED requires KI/DVR! if the driver is absent, and initialisation then sets bit 6 of KFLAG$ at 4423H to select the driver's special-key translation table, resetting it again on the way out. The whole command set is built on the codes that table returns, held as twenty-two three-byte entries at 525DH, each a key code followed by the address of its handler. The four arrows return 81H left, 84H right, 88H down and 82H up; the shifted arrows return 91H, 94H and 92H for the beginning of the line, the end of the line and the top of the text; the end of the text is CTRL Z, 1AH, because on the Model I keyboard the DOS 5 control prefix is SHIFT with the down arrow, which is the key sequence the TED manual writes as SHIFT down arrow then Z; and the exit key, CLEAR with SHIFT and =, returns 0BDH.
The screen is divided in three. Video rows 0 to 13, 3C00H to 3F7FH, are the text area of fourteen lines by 64 characters; video row 14 is a rule of underscores; and video row 15 is the status line, which carries the banner when nothing is happening and every prompt, query and error message when something is. Alongside it TED keeps a line table at 5B96H, fourteen two-byte text addresses giving the first character of each display row, rebuilt by every redraw, and it is that table rather than any calculation that turns a cursor row and column back into a text address. The text buffer runs from 5D00H up to HIGH$, which TED reads once at start-up and never lowers, and it holds a plain run of characters ending in a zero byte with 0DH for a newline. The two block markers the operator plants are stored in the text as ordinary characters, 0FEH and 0FFH, which is why the manual warns that they occupy text positions and will be written to the disk file if they are still there when the text is filed; the display shows a newline as the small graphics block 84H, a begin marker as the graphic left bracket 0B7H and an end marker as the graphic right bracket 0BBH.
Every command that throws text away asks Press <ENTER> to confirm first, and the routine that does the asking discards the caller's return address when the answer is anything else, so a command abandons itself without the caller testing anything. A block move is implemented as a copy followed by a delete, using the same code as block copy and then the same code as block delete, which is exactly what the manual means by "essentially one of copying and automatic deleting". The marked block is found by searching forward for the end marker and then backwards for the begin marker, so a stray begin marker earlier in the text cannot be picked up, and the cursor is then tested against the pair: copy and move refuse with Cursor! when the cursor is inside the block, while delete refuses when it is outside, because for delete the cursor is what says which of several marked blocks is meant.
LOAD and FILE reach the disk through the resident vectors and nothing else. A filespec is parsed by @FSPEC at 441CH, given the default extension /TXT by @FEXT at 4473H, and the file is opened with @OPEN at 4424H or created with @INIT at 4420H at a logical record length of zero, which the DOS reads as 256 bytes. Both transfers then walk the File Control Block's own buffer address up the text buffer by adding one to its high byte between records. A LOAD knows the size of the file before it reads a byte, from the ending record number and the end-of-file byte offset in the directory, so a file that will not fit is refused with No room! without disturbing the text already in the buffer, and a LOAD appends to the end of the text rather than replacing it. A disk error is not allowed to reach the screen: TED sets bit 7 of the SYS1 status byte at 4758H so that SYS4/SYS builds the message text without displaying it, has it returned into a buffer of its own, and puts it on the status line in TED's own format.
The module is a study in packing. Six instructions carry their working values in their own operand fields instead of in variables: the main loop alone holds the saved stack pointer at 540EH, the cursor row and column at 5422H, the pending keystroke at 5426H and the text address of the cursor at 5438H, while the end-of-text pointer lives at 58C2H and the text buffer ceiling at 58C5H. The end-of-text pointer is the subtle one, because the image loads with zero in that field and the value is first established by the line display routine, which records the address of the terminating zero byte every time a redraw reaches the end of the text. There are ten places where a branch or a command table entry lands one byte inside another instruction so that an index prefix or an immediate opcode is stepped over and the bytes that follow read as a different instruction, which is how three status message setters fit into eleven bytes at 5324H and how CTRL L and CTRL F share an entry point at 5642H. Two fragments are unreachable: three bytes at 5508H that no branch names, and a complete nineteen-byte subroutine at 5831H that nothing calls and that loads its divisor into Register C while the resident @DIV routine at 44C4H takes it in Register A.
Disassembly:
A disassembly of TED/CMD can be found here.