This program catalogs the contents of a cassette tape by reading file headers directly from memory, displaying the file type (BASIC, Array, or Code), filename, byte length, and load/autostart address for each block found. It uses a machine code routine POKEd into RAM at address 65451 (near the top of a 64K address space) to intercept the tape-loading mechanism, with the header data examined at address 65475. The directory can be printed to a printer via channel 3, saved to tape as a character array, or reloaded for later review. A hex-string-to-machine-code loader at lines 9100–9110 converts two ASCII hex digits at a time into byte values using arithmetic on character codes, with a checksum validation step to catch transcription errors.
Program Analysis
Program Structure
The program is organized around a menu loop at lines 100–130, with distinct functional modules reached via computed GO TO targets. The main tape-reading loop runs from lines 15–80, the printer toggle is at 200–240, the save routine at 300–360, the load routine at 600–690, and the machine code installer at 9000–9110. Lines 9900–9999 handle self-save/verify and cleanup.
| Lines | Function |
|---|---|
| 10–80 | Tape header capture and display loop |
| 100–130 | Main menu and dispatch |
| 200–240 | Printer toggle |
| 300–360 | Save directory to tape |
| 400–420 | Keypress subroutine (wait-for-release, then wait-for-press) |
| 500–510 | Tape name/side initialisation |
| 600–690 | Load and review saved directory |
| 9000–9110 | Machine code hex-string installer with checksums |
| 9990 | Tape error handler |
| 9998–9999 | Self-save/verify sequence |
Machine Code Usage
The installer routine at lines 9000–9110 encodes two separate Z80 routines as hex strings in a$. The first (40 bytes, 80 hex chars) is loaded at address 65451; RANDOMIZE USR 65451 at line 9020 executes it immediately as a test. The second routine (24 bytes, 48 hex chars) is also placed at 65451 (overwriting the first), and is the one used at runtime by line 20.
The hex-to-byte conversion in the subroutine at line 9110 uses the expression (CODE a$(x)-(48 AND CODE a$(x)<58)-(55 AND CODE a$(x)>64)) to convert each ASCII hex digit: subtracting 48 for digit characters (‘0’–’9’) and 55 for letter characters (‘A’–’F’), yielding values 0–15. Two such nibbles are combined with *16+.
Checksum validation at line 9100 sums the ASCII codes of all characters in a$. Expected values (4735 and 2887) are hard-coded, guarding against transcription errors when the program is entered by hand.
At line 9040, a secondary block of 8 bytes is POKEd into address 65286 using offsets read from a DATA statement, with a separate data checksum check (expected sum 133). The byte 254 is POKEd as a sentinel before the offset-addressed 255 bytes are placed.
Header Parsing at Runtime
After RANDOMIZE USR 65451 triggers the tape-reading machine code, line 20 reads the captured header from address ix=65475. The type byte at ix+1 is used to select the label string: 0=BASIC, 1 or 2=Array, 3=Code. The filename occupies 10 bytes at ix+2 to ix+11. Byte length is taken from a two-byte little-endian word at ix+12/ix+13, and the load/autostart parameter from ix+14/ix+15.
For array types (t=1 or t=2), line 60 reconstructs the variable name: it reads the byte at ix+15, subtracts 32 for a numeric array (t=1) or 96 for a string array (t=2), and appends $ only for string arrays (t=2), along with ().
Display Formatting with z$
The string z$ is constructed at line 10 as CHR$ 19+CHR$ 1+CHR$ 19+CHR$ 0, which represents ink colour control sequences (bright ink on, bright ink off). Slicing z$( TO 2) gives the “bright on” prefix and z$(3 TO ) gives the “bright off” suffix, so field labels are displayed in bright ink while values are in normal ink.
Key BASIC Idioms
- Conditional string concatenation using
("string" AND condition)— produces the string if true, empty string if false. - Computed
GO TOat line 130 using arithmetic on Boolean results ofk$comparisons to select the branch target. - The keypress subroutine (lines 400–420) first waits for any currently held key to be released, then waits for a new keypress — a standard debounce idiom.
POKE 23692,0at line 70 resets the scroll counter (system variable SCRCT) to prevent the “scroll?” prompt interrupting output.INPUT INKEY$at lines 360 and 600 clears any key already in the buffer before waiting for tape operations.ON ERR GO TO(rendered as{in the source) provides structured error handling throughout;ON ERR RESET(©) at line 120 clears the error handler within the menu.
Directory Storage
Each parsed header entry is accumulated into b$ (line 70), separated by double carriage returns. The tape name and side, set in the subroutine at lines 500–510, are stored at the start of b$ followed by CHR$ 255 as a delimiter; k holds the length of this prefix. On save (line 330), b$ is copied into a DIM’d character array c$() matching its current length before being saved with SAVE … DATA c$(). On load, the delimiter is located by scanning for CHR$ 255 (lines 610–630) to recover k.
Self-Save Sequence
Line 9999 saves the BASIC program itself with SAVE "directory" LINE 9998 so it autoruns to line 9998 on load, then saves the machine code block at address 65253 (length 222 bytes) as a separate CODE file. Line 9998 then loads that CODE block back and jumps to the main menu. Line 9900 uses DELETE 9000,9988 to remove the installer lines before the self-save, keeping the runtime image clean.
Bugs and Anomalies
- At line 130, the arithmetic uses both addition and subtraction of line-number offsets in a single expression to select the menu branch. The expression relies on exactly one
k$comparison being true; if none match, the result isGO TO 120, repeating the keypress wait — a safe default. - The
GO TO 9988at line 9080 targets a non-existent line; execution will fall through to line 9900, which clears and deletes the installer lines — an intentional technique. - Line 9989 (
GO TO 9999) appears to be reachable only if line 9900’sDELETEsomehow did not reach line 9989 itself; in practice it serves as a forward jump past the error handler at 9990 into the self-save at 9999.
Content
Source Code
1 REM *********************** Cassette Directory 1986 Michael E. Carver ***********************
10 ON ERR GO TO 100: CLS : GO SUB 500: LET z$=CHR$ 19+CHR$ 1+CHR$ 19+CHR$ 0: CLS : IF print THEN PRINT #3;"Directory of ";b$( TO k)''
15 CLS : ON ERR GO TO 100: PRINT AT 2,11;"BREAK/CAPS will return to MENU";#1;"Start tape, then press any key.": GO SUB 400: CLS
20 RANDOMIZE USR 65451: LET ix=65475: LET t=PEEK (ix+1): LET a$=z$( TO 2)+("BASIC" AND NOT t)+("Array" AND (t=1 OR t=2))+("Code" AND t=3)+":"+z$(3 TO )+" "
30 FOR n=ix+2 TO ix+11: LET a$=a$+CHR$ (PEEK n): NEXT n: LET a$=a$+" "+z$( TO 2)+"Bytes:"+z$(3 TO )+" "+STR$ (PEEK (ix+12)+256*PEEK (ix+13))
40 LET s=(PEEK (ix+14)+256*PEEK (ix+15)): LET a$=a$+CHR$ 13+" "+(z$( TO 2)+"Loads at"+z$(3 TO )+" "+STR$ s AND t=3)+(z$( TO 2)+("Autostart at"+z$(3 TO )+" "+STR$ s AND (NOT t AND s<=9999)))
50 IF NOT t AND s>9999 THEN LET a$=a$+z$( TO 2)+"No Autostart"+z$(3 TO )
60 IF (t=1 OR t=2) THEN LET a$=a$+z$( TO 2)+"Variable"+z$(3 TO )+" "+CHR$ (PEEK (ix+15)-(32 AND t=1)-(96 AND t=2))+("$" AND t=2)+"()"
70 POKE 23692,0: PRINT a$'': LET b$=b$+a$+CHR$ 13+CHR$ 13: IF print THEN PRINT #3;a$''
80 GO TO 20
100 CLS : PRINT AT 2,6;"CASSETTE DIRECTORY"
110 PRINT AT 8,0;"CREATE DIRECTORY...............1"''"PRINTER SWITCH.................2"''"SAVE TO TAPE...................3"''"CONTINUE DIRECTORY.............4"''"LOAD FROM TAPE.................5"''"REVIEW DIRECTORY...............6"
120 GO SUB 400: ON ERR RESET
130 GO TO 120-(110 AND k$="1")+(80 AND k$="2")+(180 AND k$="3")-(105 AND k$="4")+(480 AND k$="5")+(490 AND k$="6")
200 ON ERR GO TO 100: CLS : PRINT AT 1,8;"PRINTER SWITCH"
210 PRINT AT 5,10;"PRINTER "; FLASH 1;;("ON " AND print);("OFF " AND NOT print); FLASH 0;AT 20,0;"Press ""P"" to toggle switch";TAB 6;"""M"" for Menu"
220 GO SUB 400
230 IF k$="p" OR k$="P" THEN LET print=NOT print: GO TO 210
240 GO TO 220-(120 AND (k$="m" OR k$="M"))
300 ON ERR GO TO 100: CLS : PRINT ''"Ready to save Directory: "
310 PRINT " ";b$( TO k);'"to tape."
320 INPUT "Save as? ";n$: IF LEN n$>10 THEN LET n$=n$( TO 10)
330 DIM c$(LEN b$): LET c$=b$: PRINT ''"Saving """;n$;"""": SAVE n$ DATA c$()
340 PRINT #1;"Verify (y or n)?": GO SUB 400
350 IF k$="n" OR k$="N" THEN GO TO 100
360 INPUT INKEY$: PRINT ''"verifying """;n$;"""";''"Rewind tape";#1;"Start tape, then press any key.": GO SUB 400: INPUT INKEY$: ON ERR GO TO 9990: VERIFY n$ DATA c$(): GO TO 100
400 IF INKEY$<>"" THEN GO TO 400
410 IF INKEY$="" THEN GO TO 410
420 LET k$=INKEY$: RETURN
500 INPUT "Tape name? ";k$: LET a$=k$: INPUT "Side? ";k$: LET a$=a$+" Side "+k$: LET b$=a$+CHR$ 255: LET k=LEN b$-1
510 RETURN
600 POKE 23692,23: CLS : ON ERR GO TO 100: INPUT "Load name? ";n$: PRINT ''"Loading """;n$;"""";#1;"Start tape, then press any key.": GO SUB 400: INPUT INKEY$: IF LEN n$>10 THEN LET n$=n$( TO 10)
605 ON ERR GO TO 9990: LOAD n$ DATA c$(): LET b$=c$
610 ON ERR GO TO 100: CLS : FOR j=1 TO LEN b$: IF b$(j)=CHR$ 255 THEN GO TO 630
620 NEXT j
630 LET k=j-1
640 IF print THEN PRINT #3;"Directory of ";b$( TO k);'';b$(k+2 TO )
650 PRINT "Directory of ";b$( TO k);'';b$(k+2 TO )
660 PRINT #1;"Press ""M"" for MENU"
670 GO SUB 400
680 IF k$<>"M" AND k$<>"m" THEN GO TO 670
690 GO TO 100
9000 CLEAR 65252: LET a$="F33E01D3F4DBFFCBFFD3FF21E50011E58001C600EDB0AFD3FFD3F4FB21E58011E5FE01C600EDB0C9"
9010 GO SUB 9100: IF LEN a$<>80 OR check<>4735 THEN PRINT FLASH 1;"Error in a$ Line 9000 ----- Please Correct": STOP
9020 LET address=65451: GO SUB 9110: RANDOMIZE USR 65451
9030 DATA 14,15,7,15,10,47,11,14: RESTORE 9030: LET data=0: FOR x=1 TO 8: READ y: LET data=data+y: NEXT x: IF data<>133 THEN PRINT FLASH 1;"Error in Line 9030 ----- Please Correct": STOP
9040 RESTORE 9030: LET address=65286: POKE address,254: FOR x=1 TO 8: READ y: LET address=address+y: POKE address,255: NEXT x
9050 LET a$="AF3700DD21C3FF11150008CDFFFE2AC3FF7DFE00C8C3ABFF"
9060 GO SUB 9100: IF LEN a$<>48 OR check<>2887 THEN PRINT FLASH 1;"Error in a$ Line 9040 ----- Please Correct": STOP
9070 LET address=65451: GO SUB 9110
9080 GO TO 9988
9100 LET check=0: FOR x=1 TO LEN a$: LET check=check+CODE a$(x): NEXT x: RETURN
9110 FOR x=1 TO LEN a$-1 STEP 2: POKE address+INT ((x-1)/2),(CODE a$(x)-(48 AND CODE a$(x)<58)-(55 AND CODE a$(x)>64))*16+CODE a$(x+1)-(48 AND CODE a$(x+1)<58)-(55 AND CODE a$(x+1)>64): NEXT x: RETURN
9900 CLEAR : DELETE 9000,9988
9989 GO TO 9999
9990 ON ERR GO TO 100: CLS : PRINT FLASH 1;AT 5,8;"Tape Loading Error"; FLASH 0;'''TAB 8;"Please Attempt Again";#1;"Press any key for Menu": GO SUB 400: GO TO 100
9998 CLEAR 65252: LOAD "directory"CODE 65253: LET print=0: GO TO 100
9999 CLS : PRINT "Saving directory": SAVE "directory" LINE 9998: SAVE "directory"CODE 65253,222: CLS : PRINT "Ready to Verify -- Please Rewind Tape -- Start Tape and Press anyKey.": GO SUB 400: CLEAR : VERIFY "directory": VERIFY "directory"CODE : LET print=0: GO TO 100
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
