HEXLOADER

This file is part of and CATS Library Tape 8. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 2068

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:

LinesPurpose
10–36Initialization and main dispatch loop
140–1808-hex-digit (4-byte) POKE handler
201–2086-hex-digit (3-byte) POKE handler
231–2384-hex-digit (2-byte) POKE handler
251–2582-hex-digit (1-byte) POKE handler
300Address increment subroutine
301–340Convert ADDR to 4 hex ASCII codes in H, I, J, K
350–370PRINT hex address (uses H, I, J, K)
380LPRINT hex address
501–540Checksum accumulator (GO SUB 500 target; entry at 501)
600–650Debug dump subroutine (not called in normal flow)
655–730Review mode: display memory range as hex + decimal
800–840Convert PEEK N byte to two hex ASCII codes in L, M
901–940Convert NN to 4 hex ASCII codes (duplicate of 301–340)
1000–1090Help/info screen; ends with PAUSE 0 then RUN
9200–9210Stop, 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 AF, 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 0099 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 AF (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.

Content

Appears On

The power-user's tape. Assemble and disassemble Z80 code, manage databases with Quicksort, trace BASIC program flow, or decode resistor color codes — Tape 8 is an essential toolkit for the serious TS 2068 programmer.

Related Products

Related Articles

Related Content

Image Gallery

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.

People

No people associated with this content.

Scroll to Top