--- title: "Hexload III" id: 56687 type: "computer_media" slug: "hexload-iii" url: "http://localhost/computer_media/hexload-iii/" markdown_url: "http://localhost/computer_media/hexload-iii.md" published_at: "2024-09-22T23:31:42+00:00" modified_at: "2026-03-30T21:21:48+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/HexLoad3.jpg" excerpt: "A memory hex editor built from a compact machine code routine — browse, write, insert, and delete bytes at any address using familiar hex notation." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_type: "Cassette" download_url: "https://archive.org/download/timex-sinclair-software-archive/Hexload%20III%20%28198x%29%28TS1000%29%28Cassette%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/09/HexLoad3.jpg" media_type_tags: "Programming" --- # Hexload III This program implements a hex editor for ZX81/TS1000 memory, allowing the user to list, write, insert, and delete bytes at arbitrary addresses using hexadecimal notation. The machine code routine embedded in the REM statement at line 1 (stored from address 16393 onward) provides the core editing engine; BASIC acts purely as a menu and input layer. Address entry uses a clever two-byte POKE calculation at lines 640–650, converting ASCII hex digit pairs into raw byte values via the formula `16*CODE + CODE – 476`. RAND USR calls at lines 30, 140, 240, 340, 410, 500 dispatch into the machine code at specific offsets to perform list, write, insert, delete, save, and load operations. The CLEAR statement at line 660 is used to free string space after each input rather than to reset variables, a common ZX81 memory-management idiom. *** ## Program Analysis ### Program Structure The program is organized as a set of numbered entry points, each implementing one editing operation, plus a shared address-input subroutine: | Line(s) | Function | | --- | --- | | 1 | REM — stores the machine code routine | | 10–30 | LIST mode: display memory from address | | 100–150 | WRITE mode: accept hex byte string, poke into memory | | 200–250 | INSERT mode: insert byte(s) into memory | | 300–340 | DELETE mode: delete byte(s) from memory | | 400–420 | Save routine: dimension output buffer, save to tape | | 500–520 | Load/quit: calls USR 16669, then CLEAR and STOP | | 600–670 | Shared address-input subroutine | The user selects a mode by running the program from a specific line number (e.g., `RUN 10` for LIST, `RUN 100` for WRITE). There is no top-level menu; the entry point *is* the mode selector. ### Machine Code in the REM Statement The REM at line 1 contains approximately 170 bytes of Z80 machine code beginning at address 16393 (0x4009, the byte immediately after the REM token). Several distinct routines are embedded at fixed offsets, each called by a separate `RAND USR` invocation: | USR Address | Decimal Offset in REM | Purpose | | --- | --- | --- | | 16539 | 146 | LIST — dump memory bytes in hex | | 16589 | 196 | WRITE — write input hex string to memory | | 16635 | 242 | Return buffer size for DIM at line 400 | | 16651 | 258 | Save helper | | 16669 | 276 | Load helper | | 16687 | 294 | INSERT — insert bytes into memory block | | 16732 | 339 | DELETE — remove bytes from memory block | The machine code manipulates system variables and string pointers directly. The two-byte address of the current edit target is stored at fixed locations within the ZX81 memory map (around 0x4093–0x4099, i.e. addresses 16531–16537), shared between BASIC and machine code via POKEs at lines 640–650. ### Hex Address Input Subroutine (Lines 600–670) The subroutine beginning at line 600 reads a four-character hex string and converts it into a 16-bit address stored via two POKEs. The formula used is: - `POKE A+1, 16*CODE A$ + CODE A$(2) - 476` — high byte from first two hex digits - `POKE A, 16*CODE A$(3) + CODE A$(4) - 476` — low byte from last two hex digits The constant 476 is derived from the ASCII/ZX81 character codes: for the digit ‘0’ (code 28 on ZX81), the value `16*28 + 28 = 476` represents the baseline for “00”, so subtracting 476 normalises the result to zero. Letters A–F would follow the same arithmetic using their respective character codes. The address is stored in little-endian order (low byte first), consistent with Z80 convention. The variable `A` is pre-loaded before the `GOSUB 610` call: line 320 sets `A=16535` for DELETE mode, while line 600 sets `A=16533` for all other modes, pointing POKEs at different target locations within the shared data area. ### Key BASIC Idioms - **`RAND USR` instead of `PRINT USR`**: avoids printing the return value on screen and discards it safely. - **`RUN USR 16732` at line 340**: uses `RUN` (not `RAND`), which on the ZX81 is a synonym for `RANDOMIZE` — the distinction here is purely cosmetic but unusual. - **`CLEAR` at line 660**: called after each INPUT to reclaim string storage without resetting numeric variables or the GOSUB return stack; a standard ZX81 memory-pressure technique. - **Loop via `GOTO`**: lines 150 and 250 create write/insert loops by jumping back to the INPUT line, relying on `CLEAR` to prevent string space exhaustion during repeated entry. - **`DIM O$(USR 16635)` at line 400**: uses the return value of a machine code routine to dynamically size a string array, a neat way to let the MC determine the required buffer length. ### Notable Techniques The machine code makes use of Z80 block-move instructions (`LDIR`, opcode `ED B0`) for INSERT and DELETE operations, visible in the REM bytes as the sequence `\ED\B0` appearing multiple times. This allows efficient shifting of memory blocks without byte-by-byte loops. The RST 8 instruction (`CF`) appears several times in the machine code — this is the ZX81 character-output routine, used by the LIST function to display hex digits directly to the screen without returning to BASIC. ### Bugs and Anomalies - The hex conversion formula assumes ZX81 character codes for digits 0–9 and, implicitly, letters A–F. If a non-hex character is entered, the POKE value will be incorrect but no error is raised — there is no input validation. - The `GOTO 120` and `GOTO 220` loops have no exit condition in BASIC; the user must break with BREAK or the machine code must halt execution itself. - Line 420 `SAVE "HEXLD%3"` saves the program to tape. The `%3` is a zmakebas escape for an inverse character; in context this forms part of the filename. ## Source Code ``` 1 REM \F5\E6\F0\1F\1F\1F\1F\C6\1C\D7\F1\E6\0F\C6\1C\D7\C9\82\40\E4\2C\37\75\37\75\2A\99\40\22\97\40\54\5D\2A\95\40\A7\ED\52\19\30\1F\7C\CD\82\40\7D\CD\82\40\AF\D7\7E\CD\82\40\CB\76\20\04\AF\D7\7E\D7\3E\76\D7\23\22\95\40\18\DB\CF\14\2A\10\40\23\46\23\CB\28\28\F4\ED\5B\95\40\23\7E\87\87\87\87\23\86\C6\24\12\13\ED\53\95\40\E5\2A\99\40\ED\52\E1\30\04\ED\53\99\40\10\E1\C9\2A\99\40\ED\5B\93\40\A7\ED\52\22\97\40\44\4D\C9\2A\10\40\11\06\00\19\EB\2A\93\40\ED\4B\97\40\ED\B0\C9\2A\10\40\11\06\00\19\ED\5B\93\40\ED\4B\97\40\ED\B0\C9\2A\10\40\23\4E\23\46\CB\28\CB\19\20\02\CF\15\C5\2A\99\40\ED\5B\95\40\A7\ED\52\23\44\4D\E1\ED\5B\99\40\19\22\99\40\EB\ED\B8\CD\CD\40\C9\2A\99\40\ED\5B\97\40\D5\A7\ED\52\44\4D\E1\23\ED\5B\95\40\ED\B0\1B\ED\53\99\40\CF\12\3B\3C\3D\3E\26\27\28\29\2A 10 PRINT " LIST " 20 GOSUB 600 30 RAND USR 16539 100 PRINT "WRITE" 110 GOSUB 600 120 INPUT A$ 130 PRINT A$;" "; 140 RAND USR 16589 150 GOTO 120 200 PRINT "INSERT" 210 GOSUB 600 220 INPUT A$ 230 PRINT A$;" "; 240 RAND USR 16687 250 GOTO 220 300 PRINT "DELETE" 310 GOSUB 600 320 LET A=16535 330 GOSUB 610 340 RUN USR 16732 400 DIM O$(USR 16635) 410 RAND USR 16651 420 SAVE "HEXLD%3" 500 RAND USR 16669 510 CLEAR 520 STOP 600 LET A=16533 610 PRINT "ADDRESS "; 620 INPUT A$ 630 PRINT A$ 640 POKE A+1,16*CODE A$+CODE A$(2)-476 650 POKE A,16*CODE A$(3)+CODE A$(4)-476 660 CLEAR 670 RETURN ```