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
ADDRafter four POKEs have already incremented it by 4, so the displayed address is 4 beyond the first byte POKEd. Line 165 compensates withADDR-4for LPRINT but the PRINT at line 140 already shows the original address, creating an inconsistency. - Line 24 calls
GO SUB 655for the review command but does not follow it withGO 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.
Content
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

