XFERSYS/CMD is a one-shot upgrade utility shipped with TRSDOS 1.3 for the Model III. Its job is to take a TRSDOS 1.1 or TRSDOS 1.2 diskette in Drive 0 and produce a TRSDOS 1.3 diskette in Drive 1, copying over the SYSTEM files (BOOT/SYS, SYS0/SYS through SYS7/SYS, the RENUMBER overlay area, and the directory-track files) while preserving anything from the source diskette that the user would expect to survive an upgrade — most notably the protection counter and the 1.1-format serial number — and re-indexing the resulting destination directory so that the end-record-numbers point to the correct last record of each file.
The program is loaded by the SYS1/SYS command interpreter into the SYSHI overlay area at 5200H (TRSDOS 1.3 reserves 4E00H–51FFH as the SYSLOW overlay area for SYS1–SYS5/SYS10, and 5200H upward as the SYSHI overlay area for SYS6+ and standalone CMD utilities). At entry, the supervisor has already read the program file from disk and parked the user's command-line tail in HL; XFERSYS does not consume any command-line arguments, so HL is ignored after entry. On exit, the program returns to the DOS READY prompt either via JP SYS1IN (402DH) on success, or via JP ABORT (4030H) on a Q response, an aborted prompt, or a 1.3-already-installed condition, or via JP ERROR (4409H) on any disk-I/O failure encountered along the way.
The high-level flow is: prompt the user for confirmation; READ track 0 sector 1 of the source (BOOT/SYS) into BUFFR; sniff byte 254 (the version byte of TRSDOS 1.x boot sectors) to determine source-disk version; if the source is already 1.3, abort; if the source is a SYSTEM disk, copy three blocks of sectors (BOOT/SYS through track 0, then track 15 RENUMBER, then track 18 onward); call CORDIR to fix every directory entry's ERN value (TRSDOS 1.3 stores ERN one less than TRSDOS 1.1/1.2 did); then walk the destination's HIT one logical-file at a time, re-creating each SYSTEM file's directory entry on the destination via OPEN+INIT+GET/PUT+CLOSE; finally display "Xfersys Complete~" and exit.
Memory Map
| Address Range | Label | Contents |
|---|---|---|
| 5200H–5204H | START | Entry point — sets the CLEAR-RAM flag at CFLAG (42B4H) so that subsequent overlays load fresh. |
| 5205H–525BH | START1 | Display the title prompt and read the user's Y/Q response. Falls through with HL pointing at BUFFR after the read sector. |
| 525CH–526DH | START2 | Read the protection-counter byte from BUFFR+34 (1.2 location), version-test against 12H, and (for 1.2) skip the serial-number copy. |
| 526EH–5270H | NOPE | 1.1-only path — call MOVSER to capture the 10-byte serial number out of BUFFR+243. |
| 5271H–5290H | VER12 | Branch on system-disk flag — if SYSTEM-disk, call MOVBOT then copy the BOOT/SYS, RENUMBER, and SYS-files sector ranges via three calls to MOVE. |
| 5291H–52ABH | NOTSYS | For 1.1/1.2 sources, call CORDIR to fix the directory; then re-read the destination HIT and copy it into BUFFR for the per-file walk. |
| 52ACH–5344H | START4 | Per-system-file outer loop — find next HIT entry via GETFIL, build NAME/EXT:0 read-DCB and NAME/EXT:1 write-DCB strings, OPEN both, branch into the GET/PUT loop. |
| 5345H–5357H | STAR10 | Sequential GET/PUT loop that copies the file body byte-for-byte until source EOF. |
| 5358H–5384H | STAR11 | Close the destination, mark its directory entry SYSTEM-invisible (OR 4EH into the attribute byte), zero the UPD password, and write the directory back. |
| 5385H–539FH | GETFIL | Walk the saved HIT looking for the next non-zero entry; CALL RDDIR for it; clear the HIT entry so we don't process it twice. |
| 53A0H–53F2H | CORDIR | Re-read the source HIT, copy it to BUFF2, and walk every entry; for each one whose EOF byte is non-zero, decrement the ERN and write the directory back. |
| 53F3H–53FBH | MEND | Exit-on-success — display "Xfersys Complete~" and JP SYS1IN. |
| 53FCH–5406H | MOVBOT | Subroutine — push AF, copy just the boot sector (track 0 sector 1, B=1) via MOVE, restore AF, and return. |
| 5407H–5475H | MOVE | Subroutine — copy B sectors starting at track/sector DE from drive 0 to drive 1 via XREAD/XWRITE, with special-case handling for the boot sector (overwrites the destination's protection counter and serial number from saved values). |
| 5476H–5486H | MOVSER | Subroutine — copy the 10-byte serial number from BUFFR+243 into SERBUF, set the SERFLG self-modifying-byte to FFH, and return. |
| 5487H–5494H | SETPRO | Subroutine — point IY at SCAFLG (42FFH), DEC the byte at (IY) to set the protection-override flag, then clear IY and return. (TRSDOS 1.3 checks (IY)≠0 in the OPEN path to bypass the protection-counter check.) |
| 5495H–551EH | TITLE | Display strings, packed back-to-back: TITLE prompt, ALREDY message, COMPLT message. |
| 5528H–5559H | DCB1 | Read DCB storage (50 bytes, DCBSIZ from SYS0.GBL). |
| 555AH–5590H | DCB2 | Write DCB storage (55 bytes, DCBSIZ+5). |
| 5591H–559AH | SERBUF | 10-byte serial-number buffer. |
| 559BH | COUNT | 1-byte protection-counter storage (load file ends here). |
| 5600H–56FFH | BUFFR | 256-byte general-purpose sector buffer (uninitialized DEFS — not in load file). 256-byte alignment via ORG $,256. |
| 5700H–57FFH | SBUFF | 256-byte source-side file buffer (passed as the buffer pointer to OPEN). Uninitialized DEFS. |
| 5800H–58FFH | DBUFF | 256-byte destination-side file buffer (passed as the buffer pointer to INIT). Uninitialized DEFS. |
Self-Modifying Workspace Variables
XFERSYS uses four self-modifying-code (SMC) operand bytes inside its own code space. Each is the 1-byte operand of an LD A,0 or LD BC,0 instruction; storing into the labelled address rewrites the instruction so that the next time it executes, A (or BC) is loaded with the most recently stored value. These labels live inside a transient overlay and are not stable across other CMD-utility loads — they exist only while XFERSYS is the active program in the SYSHI area.
| Address | Label | Size | Description |
|---|---|---|---|
| 5263H | SVER | 1 | Operand byte of the LD A,0 at 5262H. Holds the source-disk version (12H = TRSDOS 1.2, anything else = TRSDOS 1.1) so the program can re-read it later at NOTSYS without going back to the boot sector. Written at 5238H. |
| 5272H | SYSFLG | 1 | Operand byte of the LD A,0 at 5271H. Holds the system-disk flag (0 = data disk; non-zero = system disk, computed as BUFF1+SYSOFF (= 4300H+E0H = 43E0H)'s extent-pointer byte INCremented). Written at 5247H. Read by VER12's OR A + conditional CALL/JR sequence. |
| 53E6H | LFNSAV | 2 | Operand bytes of the LD BC,0 at 53E5H. Holds the (LFN-in-B, drive-1-in-C) pair captured at CORDI3 entry so it can be reloaded after the directory READ has clobbered BC. Written at 53C6H (LD (LFNSAV),BC). |
| 543CH | SERFLG | 1 | Operand byte of the LD A,0 at 543BH inside MOVE. Initially 00H (skip serial-number copy); set to FFH by MOVSER (5481H) so that on the boot-sector pass through MOVE, the saved 10-byte serial number is patched into BUFFR before the destination write. |
Major Routines
| Address | Label | Function / Entry-Exit |
|---|---|---|
| 5200H | START | Program entry point. Sets the CLEAR-RAM flag at CFLAG (42B4H) so the next SYS1 load reinitializes the overlay area. Falls through to START1. |
| 5205H | START1 | Display the title prompt, accept a 1-character Y/Q response. Loops back here on no-response or non-Y/Q. JP ABORT on Q. Continues with the destination-disk version check. |
| 525CH | START2 | Re-entry point after version=1.3 check. Read protection counter from BUFFR+34 (1.2 location) and store in COUNT. Test version against 12H to decide whether to skip the serial-number copy. |
| 526EH | NOPE | 1.1 path — CALL MOVSER to copy the 1.1-format serial number out of BUFFR+243. |
| 5271H | VER12 | System-disk branch. If source is a SYSTEM disk, copy boot via MOVBOT then call MOVE three times (BOOT-SYS area, RENUMBER area, files area). If data disk, fall through to NOTSYS. |
| 5291H | NOTSYS | Common path — re-read source version; if not 1.3, call CORDIR. Re-read destination HIT and copy it into BUFFR as the per-file walk's master list. |
| 52ACH | START4 | Top of the per-system-file outer loop. CALL GETFIL; if EOF, JP MEND. Build NAME:0 and NAME:1 strings into DCB1/DCB2. |
| 52C5H | START5 | Inner loop copying up to 8 filename bytes from the directory entry into both DCB strings, terminated by a SPACE. |
| 52D9H | START6/START7 | Filename-copy loop exit. POP the saved directory pointer. |
| 52F1H | START8 | Inner loop copying up to 3 extension bytes after the "/" separator. |
| 5303H | START9 | Drive-specifier emit — ":0" for the source DCB, ":1" for the destination DCB; both DCB strings terminated with 0DH. |
| 5345H | STAR10 | GET/PUT byte-copy loop. CALL GET on DCB1; if EOF, JR STAR11. CALL PUT on DCB2; loop. |
| 5358H | STAR11 | End-of-file branch. CLOSE the write DCB, RDDIR for the destination's directory entry, OR 4EH into attribute byte, zero the UPD password word, WRDIR. |
| 5385H | GETFIL | Scan saved-HIT for next non-zero LFN. SCF+RET if exhausted (caller sees CARRY=1). |
| 5393H | GETFI2 | Match found — clear the HIT byte, then RDDIR with B=LFN, C=0 (drive 0). |
| 53A0H | CORDIR | Re-read source HIT, copy BUFF1 to BUFF2 (working copy), then loop through MAXFIL entries. |
| 53B8H | CORDI1 | Top of the CORDIR HIT-walk loop. |
| 53BCH | CORDI2 | Bottom-of-loop pointer advance. |
| 53C0H | CORDI3 | Active entry — SAVE registers, save (LFN,drive) into LFNSAV, RDDIR, examine the EOF byte; if non-zero, DEC the ERN and write the directory back. |
| 53EEH | CORDI4 | RSTR registers, JR CORDI2 to continue. |
| 53F3H | MEND | Display COMPLT, JP SYS1IN. |
| 53FCH | MOVBOT | Boot-sector-only copy subroutine. PUSH AF / DE=1 / B=1 / CALL MOVE / POP AF / RET. |
| 5407H | MOVE | Multi-sector copy subroutine: read B sectors from drive 0 to BUFFR (5600H+), then write them back to drive 1, with mid-loop track wrapping at sector 13H (TRACK+1=29H wait — actually 19 sectors per track in 1.x doublestep, but TRSDOS 1.3 still uses 18 phys + 1 hold so the test is CP TRACK+1 = CP 13H which is 18+1 ⇒ end-of-track at sector 13H). |
| 5476H | MOVSER | Serial-number capture: LDIR 9 bytes (SERLTH+1 = 8+1) from BUFFR+243 to SERBUF; set SERFLG to FFH; RET. |
| 5487H | SETPRO | Protection-override: point IY at SCAFLG (42FFH); DEC (IY); clear IY; RET. |
DCB / Directory-Entry Field Reference
XFERSYS reaches into directory entries via offsets defined in SYS0.GBL. The relevant offsets used in this overlay:
| Offset | Equate | Used by |
|---|---|---|
| +03H | DEOF | CORDI3 (53D2H) reads the EOF byte to decide whether to fix the ERN. |
| +05H | DNAME | START4 (52B7H) sets DE = DNAME and ADDs HL,DE to land HL at the start of the 8-byte filename inside the directory entry. |
| +06H | SYSTEM | BIT-position constant (= 6) used at 52B2H by BIT SYSTEM,(HL) to test the SYSTEM bit of the HIT byte. (SYSTEM in SYS0.GBL is the bit number, not an offset.) |
| +07H | LFN | STAR11 (5358H) reads (IX+LFN) from the destination DCB after CLOSE. (Here LFN is an IX-offset equate via the symbolic reference DCB2+LFN.) |
| +09H | LRL | START4 (5337H) reads (DCB1+LRL) to size the destination's INIT call. |
| +0DH | DEXT | START7 (52DAH) sets DE = DEXT and ADDs HL,DE to land HL at the 3-byte extension. |
| +10H | DUPD | STAR11 (5373H) sets DE = DUPD and ADDs HL,DE to land at the UPD password word, which is then zeroed. |
| +14H | DERN | CORDI3 (53D9H) sets DE = DERN and ADDs HL,DE to land at the ERN field; HL is then DEC'd, the 16-bit ERN read into BC, decremented, and written back. |
| +E0H | SYSOFF | START2 (5243H) reads (BUFF1+SYSOFF) = 43E0H, the system-extent-pointer byte in the source's BOOT/SYS image. |
External Routines Called
XFERSYS calls into the following SYS0/SYS1/SYS7 resident routines. All of these are stable across XFERSYS execution because XFERSYS does not load any other overlays:
| Address | Module | Purpose |
|---|---|---|
| 0013H | ROM | String compare (used by START4 at 5348H). |
| 001BH | ROM | String length / utility (used by START4 at 5350H). |
| 0040H | ROM | KEYN — line-input from the keyboard. B = max chars; HL = buffer. |
| 01C9H | SYS0 | CLS — clear the screen. |
| 021BH | SYS0 | PRINT — display the message at HL until a 03H terminator. |
| 402DH | SYS0 | SYS1IN — DOS READY exit (success path from MEND). |
| 4030H | SYS0 | ABORT — error-already-displayed exit (used at START1 on Q response and at 5259H after the ALREDY message). |
| 4409H | SYS0 | ERROR — DOS error-display exit (used after every disk-I/O failure). |
| 4420H | SYS0 | INIT — open a file new-or-existing; HL = buffer, DE = DCB, B = LRL. |
| 4424H | SYS0 | OPEN — open an existing file; HL = buffer, DE = DCB. |
| 4428H | SYS0 | CLOSE — close the file whose DCB is in DE. |
| 4675H | SYS0 | XREAD — non-error-checked sector read; HL = buffer, C = drive, DE = (sector,track). |
| 45F7H | SYS0 | XWRITE — non-error-checked sector write; HL = buffer, C = drive, DE = (sector,track). |
| 4A67H | SYS0 | RDDIR — read the directory record for LFN in B from drive in C; on return DE points at the entry. |
| 4A7BH | SYS0 | WRDIR — write the directory record back. |
| 4ABAH | SYS0 | RDHIT — read the HIT (Hash Index Table) sector for the drive in C into BUFF1. |
Cross-References and Analysis Notes
A few items observed during this disassembly that future readers should be aware of:
- The shipped binary's
SAVEmacro is 3-byte, not 4-byte. The MACRO.LIB used at build time definesSAVEasPUSH BC / PUSH DE / PUSH HL(noPUSH AF) andRSTRas the symmetric 3-byte pop. This is verified by binary inspection at MOVE1 (540EH): the bytes areC5 D5 E5, notC5 D5 E5 F5. Some other TRSDOS 1.3 sources use a 4-byte form; XFERSYS uses the 3-byte form. RSTR is correspondingly 3 bytes (POP HL / POP DE / POP BC). - BUFFR is at 5600H, not contiguous with 559BH. The source forces a 256-byte alignment via
ORG $,256after COUNT, so BUFFR / SBUFF / DBUFF land at 5600H / 5700H / 5800H. The high byte alone identifies which page-aligned buffer is being addressed at any given moment, which simplifies several MOVE-internal pointer adjustments. - SYSTEM is a bit number, not an offset. The
BIT SYSTEM,(HL)at 52B2H uses SYSTEM=06 (bit 6 of the HIT byte) to test whether the directory entry is a SYSTEM file. This is distinct from how SYSTEM appears in the FCB type byte (where it's a flag combined into bits 0–7). - The version byte at offset 254 / offset 252. TRSDOS 1.x boot sectors store the version (e.g., 11H, 12H, 13H) at byte 254. The protection counter LSB is at byte 252 in 1.1 and at byte 34 (C1LOC, from SYS7.GBL = 22H) in 1.2 — XFERSYS reads both depending on the SVER comparison.
- SCAFLG protection-override. SETPRO writes a non-zero value into the byte at SCAFLG (42FFH, from SYS0.GBL) by doing
LD YL,SCAFLG.LSB. / LD YH,SCAFLG.MSB. / DEC (IY). SYS0's OPEN and INIT paths check this byte; when non-zero, the protection counter check is bypassed so XFERSYS can open files on a 1.1/1.2 source without tripping the protection logic. - Serial-number layout. SERLTH (from SYS0.GBL) = 08, so the LDIR count in MOVSER is
SERLTH+1 = 9bytes, and the matching write in MOVE21 (5440H) also moves 9 bytes from SERBUF back into BUFFR+S1SLOC (where S1SLOC, from SYS7.GBL = F4H = 244 dec). The S1SLOC equate replaces the literal "+243" you'd expect from the read side; the +1 in the count covers a leading flag byte in the serial block.