--- title: "Header Reader" id: 71194 type: "computer_media" slug: "header-reader" url: "http://localhost/computer_media/header-reader/" markdown_url: "http://localhost/computer_media/header-reader.md" published_at: "2026-08-31T08:20:11+00:00" modified_at: "2026-08-31T08:20:12+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2022/04/20230809-041332.jpg" excerpt: "Intercept and decode tape header blocks in real time using a hand-coded machine language routine that reads the EAR port directly." 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/" indiv: - name: "Nazir A. Pashtoon" slug: "nazir-a-pashtoon" taxonomy: "indiv" url: "http://localhost/indiv/nazir-a-pashtoon/" genre: - name: "Tape Directory" slug: "tape-directory" taxonomy: "genre" url: "http://localhost/type/tape-directory/" media_type: "Program" programmers: - name: "Nazir A. Pashtoon" slug: "nazir-a-pashtoon" taxonomy: "indiv" url: "http://localhost/indiv/nazir-a-pashtoon/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Header%20Reader%20%281984%29%28Pashtoon%2C%20Nazir%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "1984" media_type_tags: "Tape Directory" --- # Header Reader Header Reader loads a 45-byte machine code routine into RAM starting at address 32765 and uses it to intercept and decode tape header blocks from the EAR port. The machine code temporarily disables interrupts, reads the header data byte by byte into memory, then returns control to BASIC for analysis. Once a header is captured, the program parses the 17-byte standard header structure: the type byte (0=BASIC, 1=number array, 2=character array, 3=code), a 10-character filename, block length, load address, and for BASIC programs, the autostart line number and variable area offset. The DEF FN g(x) function reads two consecutive PEEK values to reconstruct 16-bit little-endian integers for length and address fields. A type value of 4 is used as a sentinel to detect that no valid header was found, triggering a flashing error message. *** ### Program Structure The program is organized into four logical sections: 1. **Initialization (lines 30–90):** Clears memory, sets up addresses, defines a helper function, and POKEs a 45-byte machine code routine into RAM at address 32765. 2. **Header capture (lines 100–140):** Prompts the user, calls the machine code via `RANDOMIZE USR j`, and reads back the type byte. 3. **BASIC program decoding (lines 150–340):** Parses and prints filename, program length, variable area size, and autostart line for type-0 (BASIC) headers. 4. **Array/code decoding and repeat loop (lines 500–1020):** Subroutine at line 500 handles types 1–3; lines 1000–1020 handle errors and offer a retry. ### Machine Code Routine The 45 bytes POKEd starting at address `j` (32765) form a hand-assembled Z80 routine. Key observations from the DATA bytes: - `62,0` — `LD A,0`: loads accumulator with 0. - `55` — `SCF`: sets the carry flag, signaling to the ROM loader that this is a LOAD (not VERIFY) operation. - `221,33,42,128` — `LD IX,#802A`: points IX to address 32810 (the `head` buffer area). - `17,17,0` — `LD DE,17`: loads DE with 17 (the standard header block length). - `243` — `DI`: disables maskable interrupts. - `245` — `PUSH AF`: preserves the carry/flag state. - EAR port reads (`219,255` = `IN A,(255)`) and bit manipulation (`203,255`, `211,255`) handle raw tape signal sampling. - `251` — `EI`: re-enables interrupts before returning. - `201` — `RET`: returns to BASIC. The sentinel value 4 is pre-POKEd into `head` (address 32810) at line 110. If the machine code fails to overwrite it with a valid header type byte, the BASIC check at line 150 catches this condition and displays the error message. ### Memory Map | Address | Variable | Contents | | --- | --- | --- | | 32765 | `j` | Machine code entry point (45 bytes) | | 32810 | `head` (j+45) | Header buffer: type byte + 10-char name + length/address fields | | 32810+11 | `a+11` | 16-bit block length (little-endian) | | 32810+13 | `a+13` | 16-bit load/autostart address | | 32810+15 | `a+15` | 16-bit variable offset within BASIC file | ### Key BASIC Idioms - `DEF FN g(x)= PEEK x+256* PEEK (x+1)` at line 50 is a standard idiom for reading a 16-bit little-endian word from two consecutive memory addresses. - `PAUSE 0` followed by `IF INKEY$="y"` at line 1020 is the standard efficient keypress-wait pattern. - `CLEAR 32764` at line 30 protects the machine code area from being overwritten by BASIC’s memory allocator by moving `RAMTOP` below it. - The variable `a` is set equal to `head` at line 205, then used in `FN g(a+11)` etc., allowing the function argument arithmetic to be slightly more readable than using `head` directly. ### Header Type Dispatch The standard Spectrum tape header type byte is decoded as follows: | Type byte | Meaning | Handling | | --- | --- | --- | | 0 | BASIC program | Falls through to lines 300–340 after `IF hdtype THEN GO SUB 500` is false | | 1 | Number array | `GO SUB 500`; `b$` set to “Number array” | | 2 | Character array | `GO SUB 500`; `b$` set to “Character array” | | 3 | Binary code block | `GO SUB 500`; prints start address | | 4 | Sentinel (no header read) | Error message at line 1000 | ### Notable Techniques and Anomalies - The autostart detection at line 330 uses the heuristic `IF hdadr>10000`. The actual ROM convention uses 32768+ (bit 15 set) to indicate no autostart, so values between 10001 and 32767 would be incorrectly reported as “no autostart.” A threshold of 32768 would be strictly correct. - The variable name `N` is used in the DATA POKE loop (line 80) while the loop control variable is declared as lowercase `n` at line 70. On the Spectrum, variable names in FOR loops are case-sensitive for display but the interpreter treats them as equivalent single-letter names, so this is harmless but visually inconsistent. - The subroutine at line 500 leaves `b$` as an empty string for type 3 (binary code), so line 530 prints “Program is a ” with an empty `b$` before line 540 handles the code-specific output separately — a minor cosmetic inconsistency. - The machine code uses the ROM’s tape loading conventions (carry flag set = LOAD, IX = destination, DE = byte count) while bypassing the full ROM loader, reading the EAR port more directly for header-only capture. ## Source Code ``` 10 REM 20 REM \* NAP AUG.,10,1984 30 CLEAR 32764 40 LET j=32765:LET head=j+45 50 DEF FN g(x)= PEEK x+256* PEEK (x+1) 60 RESTORE 70 FOR n=0 TO 44:READ code 80 POKE (j+N),code:NEXT n 90 DATA 62,0,55,221,33,42,128,17,17,0,243,245,219,255,203,255,211,255,219,244,50,41,128,62,1,211,244,241,205,252,0,58,41,128,211,244,219,255,203,191,211,255,251,201,0 100 CLS 110 POKE head,4 120 PRINT AT 18,8;"start the tape" 130 RANDOMIZE USR j 140 CLS :LET hdtype= PEEK head 150 IF hdtype=4 THEN GO TO 1000 160 LET h$="" 170 FOR n=1 TO 10 180 LET h$=h$+ CHR$ PEEK (head+n) 190 NEXT n 200 PRINT "Prog. Name > ";h$:PRINT :PRINT 205 LET a=head 210 LET hdlen= FN g(a+11):LET hdadr= FN g(a+13):LET hdvars= FN g(a+15) 220 LET vars=hdlen-hdvars 230 IF hdtype THEN GO SUB 500:GO TO 1010 300 PRINT "Program is in BASIC":PRINT 310 PRINT "Program area >";hdvars;" bytes":PRINT 320 PRINT "Variables area >";vars;" bytes":PRINT 330 IF hdadr>10000 THEN PRINT "There is no autostart !":GO TO 1010 340 PRINT "Line # for autostart >";hdadr:GO TO 1010 500 LET b$="" 510 IF hdtype=1 THEN LET b$="Number array" 520 IF hdtype=2 THEN LET b$="Character array" 530 PRINT "Program is a ";b$:PRINT 540 IF hdtype=3 THEN PRINT "Binary code ,start adr >";hdadr:PRINT 550 PRINT "Block length is >";hdlen;" bytes" 560 RETURN 1000 PRINT FLASH 1; AT 18,8;"This is not a tape header" 1010 PRINT FLASH 0; AT 19,1;"Are you game for another try ?(y/n)" 1020 PAUSE 0:IF INKEY$="y" THEN GO TO 100 ```