--- title: "Header Readers" id: 71073 type: "computer_media" slug: "header-readers" url: "http://localhost/computer_media/header-readers/" markdown_url: "http://localhost/computer_media/header-readers.md" published_at: "2026-08-27T19:34:18+00:00" modified_at: "2026-08-27T19:40:39+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/08/header-readers.png" alt: "Header Readers screen" excerpt: "A dual-mode tape utility that reads and decodes file headers in real time, revealing program type, length, auto-start line, and array variable names via embedded machine code routines." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Tape Directory" slug: "tape-directory" taxonomy: "genre" url: "http://localhost/type/tape-directory/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Header%20Readers%20%28198x%29%28-%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2026/08/header-readers.png" alt: "Header Readers screen" media_type_tags: "Tape Directory" --- # Header Readers Header Readers is a dual-mode tape header analysis utility that reads and decodes the metadata blocks written at the start of tape-saved files. The program offers two operating modes: a streaming list mode (lines 10–270) that continuously reads and displays headers one after another, and a detailed single-header mode (lines 500–950) that decodes each field individually including program area size, variable area size, auto-start line, array variable names, and starting addresses. Both modes rely on short machine code routines POKEd into RAM at address 30000 (or 29957) to interface directly with the tape hardware. The list mode encodes its machine code as a space-delimited decimal string in `n$` and uses `VAL` slicing to extract and POKE each byte, while the 2068 mode READs its routine from DATA statements. A menu at line 1000 lets the user choose between the two modes at startup. *** ### Program Structure The listing is divided into three functional blocks, selected from a menu at line 1000: 1. **Lines 10–270** — Streaming list mode: continuously reads tape headers and prints a compact table showing filename, length, and type remarks. 2. **Lines 500–950** — Single-header detail mode: reads one header at a time and decodes every field with descriptive labels, with an option to COPY the output or loop for another header. 3. **Lines 960–990** — A raw hex/decimal dump of the 17 bytes stored from address 30001, reachable from the detail mode flow (after line 900) but never explicitly branched to from the menu. The entry point is line 1000, which presents a two-option menu. `RUN` on line 1040 restarts the whole program if neither option is chosen. Line 9900 contains a `SAVE` command to preserve the program. ### Machine Code Injection — List Mode Lines 40–60 install a machine code routine at address 30000. The bytes are encoded as a space-delimited decimal string in `n$`. The loop at line 60 iterates 16 times, each iteration calling `VAL n$( TO PI)` — using `PI` (≈3.14159) as the upper bound of a string slice, which `VAL` truncates to 3, effectively reading the first three characters of the remaining string — and then advances `n$` forward by 4 characters with `n$=a$(4 TO )`. Note the reference to `a$` here when the variable was named `n$`, which is a bug: the slice should read `n$(4 TO )`, so the loop as written will either error or produce incorrect POKEs depending on the value of `a$` at runtime. The `DEF FN p(x)` at line 40 also establishes `p=30016` as a base pointer into the header buffer, used throughout lines 130–240 to PEEK decoded header fields. ### Machine Code Injection — Detail Mode Lines 510–580 use `READ`/`DATA` to POKE 44 bytes into addresses 29957–30000. The routine is invoked via `RANDOMIZE USR 29957` at line 630. After the call, the decoded header byte at address 30001 holds the file type, and addresses 30002–30017 hold the filename and parameter fields. Line 540 contains a `DATA` value of `c` (an unquoted variable reference) and `NOT PI` (which evaluates to 0) mixed with numeric literals. The `c` reference is suspicious — it would be read as the current value of the variable `c` during the FOR/READ loop, which is the loop control variable itself. This is almost certainly a typo for a numeric literal (likely `0` or another byte value). Line 610 does `POKE 30001,4` to pre-initialize the type byte to an invalid value (4 = “not a header”), then calls `CLEAR` with no argument — on most implementations this resets variables, which would undo the carefully POKEd routine. This line appears anomalous. ### Header Field Decoding Both modes decode the standard 17-byte tape header format. The field layout assumed by the program is: | Offset | Field | Mode | | --- | --- | --- | | +0 | File type (0=BASIC, 1=Num Array, 2=Char Array, 3=Code) | Both | | +1 to +10 | Filename (10 chars) | Both | | +11/+12 | Data length (16-bit LE) | List mode | | +12/+13 | Data length (detail: 30012/30013) | Detail mode | | +13/+14 | Parameter 1 (start address or auto-start line) | List mode | | +14/+15 | Parameter 1 (detail: 30014/30015) | Detail mode | | +15/+16 | Parameter 2 (program area length for BASIC) | List mode | | +16/+17 | Parameter 2 (detail: 30016/30017) | Detail mode | ### Array Variable Name Decoding In the list mode (lines 220–230), array variable names are recovered by reading the descriptor byte at `p+14`, dividing by 32 to get a “tier” value `t`, and computing the character code as `s - 32*t + 96`. If `t=2` or `t=6`, a `$` suffix is appended for string arrays. In the detail mode (lines 850–860), the approach is different: for numeric arrays (type 1), two candidate names are computed as `CHR$(PEEK 30015 - 64)` and `CHR$(PEEK 30015 - 32)`, joined with ” OR “; for character arrays (type 2), `CHR$(PEEK 30015 - 128)+"$"` and `CHR$(PEEK 30015 - 96)+"$"`. ### Notable BASIC Idioms - `NOT PI` evaluates to 0 (since PI is non-zero, NOT returns 0), used pervasively as a readable zero literal. - `SGN PI` evaluates to 1 (PI > 0), used as the FOR loop start value in line 60. - `VAL "number"` in `GO TO` and `CLEAR` statements is a standard memory-saving idiom. - `TAB PI - LEN STR$ i` at line 980 uses PI (≈3) to right-align single and double digit numbers in a column of width 3. - `PAUSE NOT PI` (line 90) pauses for 0 frames, which combined with the subsequent keypress check acts as a wait-for-key idiom. - Line 750 uses `OVER 1` to overlay the filename `a$` on an underline of underscores, producing an underlined appearance. ### Bugs and Anomalies - **Line 60 — wrong variable name:** The loop advances the string with `n$=a$(4 TO )` but the string being built is `n$`. This should be `n$=n$(4 TO )`. As written, this will read from `a$` (likely empty or undefined), corrupting the POKEd machine code bytes after the first iteration. - **Line 540 — bare `c` in DATA:** The DATA statement contains the token `c` as a value. During the READ loop, `c` is the loop variable, so the byte POKEd will equal the current loop index rather than a fixed opcode. This is almost certainly a missing numeric literal. - **Line 610 — bare `CLEAR`:**`CLEAR` without an argument resets all variables. This executes after POKEing machine code into RAM, but the machine code occupies fixed numeric addresses so survives `CLEAR`; however, it also resets the RAMTOP, which may reclaim the POKEd area depending on implementation. - **Line 900 — typo “Lenght”:** The label prints as “Lenght:” instead of “Length:”. - **Line 960 — unreachable from menu:** The raw byte dump block at lines 960–990 is never branched to from the main menu or from the detail mode’s normal flow at line 910 (which goes to the INPUT prompt). It appears to be dead code or a debug remnant. ## Source Code ``` 5 RUN VAL "999" 10 REM tape analyser 20 BORDER NOT PI:PAPER NOT PI:INK 7 30 CLEAR VAL "29999" 40 DEF FN p(x)= PEEK x+256* PEEK (x+1):LET p=30016 50 LET n$="221 33 64117 17 17 0175 55205 86 5216195 48117" 60 FOR i= SGN PI TO VAL "16":POKE 29999+i, VAL n$( TO PI):LET n$=a$(4 TO ):NEXT i 70 PRINT INVERSE 1;" *Tape Analyser*", 80 PRINT " NAME"; TAB VAL "10";"Length Remarks"'':PLOT NOT PI, VAL "156":DRAW VAL "255", NOT PI 90 PRINT #1;" Start tape, then press any key":PAUSE NOT PI 100 PRINT #1; AT 1, NOT PI,, 110 RANDOMIZE USR 30000 120 LET n$="" 130 LET type= PEEK p:LET l= FN p(p+11):FOR i=p+1 TO p+10:LET n$=n$+ CHR$ PEEK i:NEXT i 140 LET l$= STR$ l:LET l$=" "(1 TO 6- LEN l$)+l$ 150 PRINT n$;l$;" "; 160 IF type>3 THEN PRINT "Read Error":GO TO 110 170 IF type<3 THEN GO TO 210 180 LET s= FN p(p+13) 190 IF s=16384 AND l=6192 THEN PRINT "SCREEN$ ":GO TO 210 200 PRINT "CODE at: ";s:GO TO 110 210 IF type=0 THEN GO TO 240 220 LET s= PEEK (p+14):LET t= INT (s/32):LET l$= CHR$ (s-32*t+96):IF t=2 OR t=6 THEN LET l$=l$+"$" 230 PRINT "Array: ";l$:GO TO 110 240 LET l= FN p(p+13):LET s= FN p(p+15) 250 PRINT "Basic"; 260 IF l<10000 THEN PRINT " LINE ";l; 270 PRINT :GO TO 110 500 REM "2068hdrrdr"-2068 Header Reader. 510 BORDER NOT PI:PAPER NOT PI:INK 7:CLEAR VAL "29956":RESTORE VAL "500" 520 FOR a=29957 TO 30000 530 READ c:POKE a,c:NEXT a 540 DATA 175,55,221,33,49,117,17,c, NOT PI,243 550 DATA 245,219,255,203,255,211,255,219,244,50 560 DATA 48,117,62,1,211,244,241,205,252,0 570 DATA 58,48,117,211,244,219,255,203,191,211 580 DATA 255,251,201,0 610 POKE 30001,4:CLEAR 620 PRINT AT 10,8; "Start the tape-" 630 RANDOMIZE USR 29957 640 CLS 650 LET type= PEEK 30001 660 IF type>3 THEN PRINT AT 10,9; FLASH 1;"Not a Header":GO TO 930 670 LET a$="" 680 FOR i=0 TO 9 690 LET a$=a$+ CHR$ PEEK (30002+i) 700 NEXT i 710 LET len= VAL "PEEK 30012+256* PEEK 30013" 720 LET start= VAL "PEEK 30014+256* PEEK 30015" 730 LET pa= VAL "PEEK 30016+256* PEEK 30017" 740 LET var=len-pa 750 PRINT "Name:__________; "; OVER 1; AT 0,6;a$'' 760 IF type THEN GO TO 830 770 PRINT "Program type:BASIC."'' 780 PRINT "Program area:";pa;"bytes."'' 790 PRINT "Variables area:";var;"bytes."'' 800 IF start>9999 THEN PRINT "no auto-start.":GO TO 910 810 PRINT "auto-start at line";start;"." 820 GO TO 910 830 LET t$="Code Block." 840 IF start=16384 AND len=6912 THEN LET t$="Screen." 850 IF type=1 THEN LET t$="Numeric Array.":LET n$= CHR$ (PEEK 30015-64)+" OR "+ CHR$ (PEEK 30015-32) 860 IF type=2 THEN LET t$="Character Array.":LET n$= CHR$ (PEEK 30015-128)+"$ OR "+ CHR$ (PEEK 30015-96)+"$" 870 PRINT "type:";t$'' 880 IF type=1 OR type=2 THEN PRINT "Array Variable Name:";n$'' 890 IF type=3 THEN PRINT "Starting Address:";start'' 900 PRINT "Lenght:";len;"bytes." 910 INPUT "Copy(y/n)?";r$ 920 IF r$="Y" OR r$="y" THEN COPY 930 INPUT "Read another header(y/n)?";r$ 940 IF r$="Y" OR r$="y" THEN GO TO 620 950 STOP 960 CLS :PRINT "Bytes Contents"; OVER 1; AT NOT PI, NOT PI;"____ ________"'' 970 FOR i=1 TO 17 980 PRINT TAB PI- LEN STR$ i;i; TAB 12- LEN STR$ PEEK (30000+i); PEEK (30000+i) 990 NEXT i:GO TO 910 1000 PAPER VAL "6":BORDER VAL "6":INK VAL "9":CLS 1010 PRINT '''' TAB VAL "10";"Header Readers"'''" 1 LIST Headers"''" 2 1 AT a time" 1020 INPUT "Select 1, 2, ";i:IF i= VAL "1" THEN GO TO VAL "10" 1030 IF i= VAL "2" THEN GO TO VAL "500" 1040 RUN 9900 SAVE "HdrReads 2" LINE 1 ```