--- title: "HEXLOADER" id: 54572 type: "computer_media" slug: "hexloader" url: "http://localhost/computer_media/hexloader/" markdown_url: "http://localhost/computer_media/hexloader.md" published_at: "2024-06-02T17:43:30+00:00" modified_at: "2026-03-30T21:42:58+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/06/SCR-20260330-nqxn.png" excerpt: "Enter hex bytes directly into memory with automatic address tracking, running checksum management, and a review mode that reads back every POKEd location." 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: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_contents: - id: 51214 title: "CATS Library Tape 8" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-8/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20260330-nqxn.png" - url: "http://localhost/wp-content/uploads/2024/06/SCR-20260330-nrat.png" media_type_tags: "Programming" --- # HEXLOADER This program is a hexadecimal machine-code loader that lets the user POKE bytes into memory by entering 2, 4, 6, or 8 hex-digit strings at a sequentially advancing address. It maintains a running checksum that can be zeroed, inspected, altered, or sent to the printer, and it prints both a 4-digit hex address and the entered bytes to both the screen and an LPRINT-capable printer (such as the ZX Printer or compatible). A decimal-to-hex conversion subroutine (lines 301–340 and 901–940) converts the current address into four ASCII character codes by repeated division rather than using any built-in hex function, since no such function exists in the dialect. The program also includes a review mode (line 655) that reads back POKEd memory over a user-specified range and displays each byte as hex and decimal. On startup it saves both the BASIC loader and a separate CODE block starting at address 23756 for 756 bytes, consistent with a machine-code extension stored in the ZX Spectrum’s new-variables area. *** ## Program Analysis ### Program Structure The program is organized into a main input loop and a set of subroutines and entry points. Below is a high-level map of the line ranges: | Lines | Purpose | | --- | --- | | 10–36 | Initialization and main dispatch loop | | 140–180 | 8-hex-digit (4-byte) POKE handler | | 201–208 | 6-hex-digit (3-byte) POKE handler | | 231–238 | 4-hex-digit (2-byte) POKE handler | | 251–258 | 2-hex-digit (1-byte) POKE handler | | 300 | Address increment subroutine | | 301–340 | Convert `ADDR` to 4 hex ASCII codes in H, I, J, K | | 350–370 | PRINT hex address (uses H, I, J, K) | | 380 | LPRINT hex address | | 501–540 | Checksum accumulator (GO SUB 500 target; entry at 501) | | 600–650 | Debug dump subroutine (not called in normal flow) | | 655–730 | Review mode: display memory range as hex + decimal | | 800–840 | Convert PEEK N byte to two hex ASCII codes in L, M | | 901–940 | Convert `NN` to 4 hex ASCII codes (duplicate of 301–340) | | 1000–1090 | Help/info screen; ends with PAUSE 0 then RUN | | 9200–9210 | Stop, catalog/load, and SAVE routines | ### Main Input Loop Lines 10–36 form the program’s core. Line 10 initializes `ADDR` using `PEEK 23641+PEEK 23642*256` as a default (E LINE system variable) and resets the checksum `CS` to 0. Line 15 assigns numeric values 10–15 to variables `A`–`F`, which appear intended for hexadecimal digit substitution, although they are not actually referenced in the hex parsing — the program instead uses `VAL A$(n)` directly on numeric characters. Line 20 displays a comprehensive prompt menu and reads `A$`. Lines 21–29 handle single-character commands (`S`, `P`, `PP`, `R`, `C`, `L`, `A`) before the hex entry path. Line 29 acts as a length validator, rejecting strings with odd lengths or lengths outside 2–8 before dispatching via lines 30–36. Note that the dispatch at lines 32 and 34 uses `GO TO` rather than `GO SUB`, so control falls through to line 20 via explicit `GO TO 20` at the end of each handler. ### Hex Parsing Technique Hex bytes are entered as pairs of characters within `A$`. Each pair is decoded with the idiom `VAL A$(n)*16+VAL A$(n+1)`, which works correctly only for digit characters 0–9. There is no handling for alphabetic hex digits A–F; entering a letter such as `3F` would cause a BASIC error because `VAL "F"` is invalid. The menu says “USE CAPS” but the program actually restricts usable values to `00`–`99` in decimal disguise, meaning it silently misbehaves for any byte whose nibble is A–F. This is a functional bug for a hex loader. ### Address-to-Hex Conversion Because the dialect has no built-in hex formatting, subroutines at lines 301–340 and 901–940 implement a manual base-16 conversion by successive division of the address by 4096, 256, 16, and remainder. The resulting nibble values (0–15) are converted to ASCII codes: digits 0–9 become ASCII 48–57 (`+48`) and letters A–F become ASCII 65–70 (`+55`). The zero case is handled separately (lines 326–329, 926–929) to avoid being absorbed into the `H<>0` guard. The two subroutines (301 and 901) are functionally identical but use different input variables (`ADDR` vs. `NN`), representing a missed opportunity to share code. ### Checksum Handling The checksum subroutine nominally begins at line 500 but the first executable line is 501. This means `GO SUB 500` (called from each handler via line 170, 207, 237, 257) jumps to a non-existent line, causing BASIC to execute the next line found (501). This is an intentional and well-known technique. The checksum accumulates raw byte values; no modular reduction is applied, so `CS` grows unboundedly as a floating-point number. ### Review Mode The review subroutine (lines 655–730) iterates from a user-supplied `START` to `END` address. For each address `N`, it calls subroutine 901 (via a temporary copy in `NN`) to format the address as hex, prints it via subroutine 350, then calls subroutine 800 to format `PEEK N` as two hex digits in `L` and `M`, and finally prints both hex and decimal representations. Subroutine 800 independently re-implements the nibble-to-ASCII logic for a single byte. ### GO SUB 500 Gap Anomaly The checksum subroutine is entered with `GO SUB 500` (lines 170, 207, 237, 257) but line 500 does not exist; the first line in that block is 501. As noted above, this is standard BASIC practice: the interpreter finds the next available line (501) and begins execution there. It is not a bug. ### Notable Bugs and Anomalies - Variables `A`–`F` (set to 10–15 at line 15) are never used in hex digit parsing; the hex-to-byte conversion only works for digit characters, not A–F hex letters. - Line 160 prints `ADDR` after four POKEs have already incremented it by 4, so the displayed address is 4 beyond the first byte POKEd. Line 165 compensates with `ADDR-4` for LPRINT but the PRINT at line 140 already shows the original address, creating an inconsistency. - Line 24 calls `GO SUB 655` for the review command but does not follow it with `GO TO 20`; execution falls through to line 25, which checks for `"C"`, and continues from there rather than looping cleanly. - The debug subroutine at lines 600–650 is never called from normal program flow; it appears to be a development aid left in place. - Line 1030 uses `FREE` (the `~` keyword token) to display free memory alongside system variable data. ### Save Mechanism Line 9210 saves the BASIC program with `LINE 9205` as the auto-start line, which performs a `LOAD "C" CODE` followed by patching system variables at addresses 23606–23607 before calling `RUN 1000`. A separate CODE file named `"C"` is saved starting at address 23756 for 756 bytes — consistent with storing a machine-code extension in the new-variables area just above the system variables. ## Source Code ``` 10 INPUT "E LINE=";VAL "PEEK 23641+PEEK 23642*256"'"ENTER START ADDRESS IN DECIMAL"';ADDR: LET CS=0 15 LET A=10: LET B=11: LET C=12: LET D=13: LET E=14: LET F=15 20 GO SUB 301: INPUT "ENTER:"'"2,4,6,OR 8 PLACE HEX"'"""S"" TO STOP";TAB 24;"CHECKSUM"'"""P"" TO CHANGE ADDRESS";TAB 25;VAL "CS"'"""PP"" TO PRINT ADDRESS"'"""L"" TO LPRINT CHECKSUM"'"""C"" TO ZERO CHECKSUM"'"""A"" TO CHANGE CHECKSUM"'"""R"" TO REVIEW POKED DATA ";A$: LET Z$=A$ 21 IF A$="S" THEN STOP 22 IF A$="P" THEN INPUT "NEW ADDRESS ";ADDR: GO TO 20 23 IF A$="PP" THEN PRINT ADDR: GO TO 20 24 IF A$="R" THEN GO SUB 655 25 IF A$="C" THEN LET CS=0 26 IF A$="L" THEN LPRINT "CHECKSUM=";CS 27 IF A$="A" THEN INPUT "ENTER NEW VALUE FOR CHECKSUM ";CS 29 IF LEN A$>8 OR LEN A$<2 OR LEN A$=1 OR LEN A$=3 OR LEN A$=5 OR LEN A$=7 THEN GO TO 20 30 IF LEN A$=8 THEN GO SUB 140 32 IF LEN A$=6 THEN GO TO 200 34 IF LEN A$=4 THEN GO TO 230 36 IF LEN A$=2 THEN GO TO 250 140 PRINT ADDR;: LET V1=VAL A$(1)*16+VAL A$(2): POKE ADDR,V1: GO SUB 300 145 LET V2=VAL A$(3)*16+VAL A$(4): POKE ADDR,V2: GO SUB 300 150 LET V3=VAL A$(5)*16+VAL A$(6): POKE ADDR,V3: GO SUB 300 155 LET V4=VAL A$(7)*16+VAL A$(8): POKE ADDR,V4: GO SUB 300 160 PRINT TAB 5;;: GO SUB 350: PRINT TAB 9;A$;TAB 17;V1;TAB 21;V2;TAB 25;V3;TAB 29;V4 165 LPRINT ADDR-4;TAB 5;;: GO SUB 380: LPRINT TAB 9;A$;TAB 17;V1;TAB 21;V2;TAB 25;V3;TAB 29;V4 170 GO SUB 500 180 GO TO 20 201 PRINT ADDR;: LET V1=VAL A$(1)*16+VAL A$(2): POKE ADDR,V1: GO SUB 300 202 LET V2=VAL A$(3)*16+VAL A$(4): POKE ADDR,V2: GO SUB 300 203 LET V3=VAL A$(5)*16+VAL A$(6): POKE ADDR,V3: GO SUB 300 205 PRINT TAB 6;;: GO SUB 350: PRINT TAB 11;A$;TAB 18;V1;TAB 22;V2;TAB 26;V3 206 LPRINT ADDR-3;TAB 6;;: GO SUB 380: LPRINT TAB 11;A$;TAB 17;V1;TAB 21;V2;TAB 25;V3 207 GO SUB 500 208 GO TO 20 231 PRINT ADDR;: LET V1=VAL A$(1)*16+VAL A$(2): POKE ADDR,V1: GO SUB 300 232 LET V2=VAL A$(3)*16+VAL A$(4): POKE ADDR,V2: GO SUB 300 235 PRINT TAB 6;;: GO SUB 350: PRINT TAB 11;A$;TAB 18;V1;TAB 22;V2 236 LPRINT ADDR-2;TAB 6;;: GO SUB 380: LPRINT TAB 11;A$;TAB 17;V1;TAB 21;V2 237 GO SUB 500 238 GO TO 20 251 PRINT ADDR;: LET V1=VAL A$(1)*16+VAL A$(2): POKE ADDR,V1 255 PRINT TAB 6;;: GO SUB 350: PRINT TAB 11;A$;TAB 18;V1 256 LPRINT ADDR;TAB 6;;: GO SUB 380: LPRINT TAB 11;A$;TAB 17;V1 257 GO SUB 300: GO SUB 500 258 GO TO 20 300 LET ADDR=ADDR+1: RETURN 301 LET DR=ADDR: LET H=INT (DR/4096): LET DR=DR-H*4096 302 LET I=INT (DR/256): LET DR=DR-I*256 303 LET J=INT (DR/16): LET DR=DR-J*16 304 LET K=DR 320 IF H<10 AND H<>0 THEN LET H=H+48 322 IF I<10 AND I<>0 THEN LET I=I+48 323 IF J<10 AND J<>0 THEN LET J=J+48 324 IF K<10 AND K<>0 THEN LET K=K+48 326 IF H=0 THEN LET H=48 327 IF I=0 THEN LET I=48 328 IF J=0 THEN LET J=48 329 IF K=0 THEN LET K=48 330 IF H>=10 AND H<=15 THEN LET H=H+55 331 IF I>=10 AND I<=15 THEN LET I=I+55 332 IF J>=10 AND J<=15 THEN LET J=J+55 333 IF K>=10 AND K<=15 THEN LET K=K+55 340 RETURN 350 PRINT CHR$ H;CHR$ I;CHR$ J;CHR$ K; 370 RETURN 380 LPRINT CHR$ H;CHR$ I;CHR$ J;CHR$ K;: RETURN 501 IF LEN A$=2 THEN LET CS=CS+V1 520 IF LEN A$=4 THEN LET CS=CS+V1+V2 525 IF LEN A$=6 THEN LET CS=CS+V1+V2+V3 530 IF LEN A$=8 THEN LET CS=CS+V1+V2+V3+V4 540 RETURN 600 PRINT "ADDR ";ADDR 602 PRINT "A$ ";Z$ 603 PRINT "LEN A$ ";LEN Z$ 615 IF LEN A$=2 THEN PRINT "V1 ";V1 620 IF LEN A$=4 THEN PRINT "V1 ";V1'"V2 ";V2 625 IF LEN A$=6 THEN PRINT "V1 ";V1'"V2 ";V2'"V3 ";V3 630 IF LEN A$=8 THEN PRINT "V1 ";V1'"V2 ";V2'"V3 ";V3'"V4 ";V4 640 PRINT "CHR$ H ";CHR$ H'"CHR$ I ";CHR$ I'"CHR$ J ";CHR$ J'"CHR$ K ";CHR$ K 650 RETURN 655 INPUT "ENTER REVIEW START ADDRESS ";START 656 INPUT "ENTER REVIEW END ADDRESS ";END 701 FOR N=START TO END: LET NN=N: PRINT N;: PRINT TAB 6;: GO SUB 901: GO SUB 350 710 GO SUB 800: PRINT TAB 11;CHR$ L;CHR$ M;TAB 14;PEEK N 720 NEXT N 730 RETURN 800 IF INT ((PEEK N)/16)<=9 THEN LET L=INT ((PEEK N)/16)+48 810 IF INT ((PEEK N)/16)>9 THEN LET L=INT ((PEEK N)/16)+55 820 LET M=PEEK N-(INT ((PEEK N)/16)*16) 830 IF M>9 THEN LET M=M+55 835 IF M<=9 THEN LET M=M+48 840 RETURN 901 LET H=INT (NN/4096): LET NN=NN-H*4096 902 LET I=INT (NN/256): LET NN=NN-I*256 903 LET J=INT (NN/16): LET NN=NN-J*16 904 LET K=NN 920 IF H<10 AND H<>0 THEN LET H=H+48 922 IF I<10 AND I<>0 THEN LET I=I+48 923 IF J<10 AND J<>0 THEN LET J=J+48 924 IF K<10 AND K<>0 THEN LET K=K+48 926 IF H=0 THEN LET H=48 927 IF I=0 THEN LET I=48 928 IF J=0 THEN LET J=48 929 IF K=0 THEN LET K=48 930 IF H>=10 AND H<=15 THEN LET H=H+55 931 IF I>=10 AND I<=15 THEN LET I=I+55 932 IF J>=10 AND J<=15 THEN LET J=J+55 933 IF K>=10 AND K<=15 THEN LET K=K+55 940 RETURN 1000 PRINT "THIS PROGRAM IS A HEXLOADER IN BASIC WITH BOLD CODE LOCATED IN THE NEW VARIABLES AREA." 1005 PRINT '"USE CAPS" 1010 PRINT '"USE RUN AND ENTER TO START FROM OTHER LOCATIONS (AFTER BREAK) AND THEN READ THE PROMPTS." 1020 PRINT '"THE LINES WILL BE LPRINTED IF THE 2040 PRINTER SWITCH IS ON." 1030 PRINT '"E LINE ";PEEK 23641+PEEK 23642*256;TAB 22;"FREE "; FREE 1040 PRINT '"GO TO 9210 TO SAVE" 1050 PRINT '"LOAD """"" 1060 PRINT '"GO TO 1000 TO REPEAT THIS SCREEN" 1080 PRINT '"HIT ANY KEY TO START." 1090 PAUSE 0: RUN 9200 STOP 9204 CAT "C.bin",: POKE 23606,204: POKE 23607,91: RUN 1000 9205 LOAD "C"CODE : POKE 23606,204: POKE 23607,91: RUN 1000 9210 SAVE "HEXLOADER" LINE 9205: SAVE "C"CODE 23756,756 ```