--- title: "MC-MOVE" id: 54574 type: "computer_media" slug: "mc-move" url: "http://localhost/computer_media/mc-move/" markdown_url: "http://localhost/computer_media/mc-move.md" published_at: "2024-06-02T17:44:59+00:00" modified_at: "2026-03-30T21:43:36+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A BASIC-driven Z80 machine code relocator that rewrites embedded 16-bit addresses opcode-by-opcode, letting you move assembled routines to a new memory location safely." 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-nsge.png" media_type_tags: "Programming" --- # MC-MOVE This program relocates Z80 machine code from one absolute memory address to another by scanning each byte of the source region and rewriting any embedded 16-bit addresses that fall within the code block. The routine works by identifying Z80 opcode prefixes (ED, DD, FD, CB) and consulting hard-coded DATA tables of opcodes that carry immediate 16-bit address operands, then adjusting those addresses by the relocation delta. It protects ROM calls by leaving any address below the start of the code block unchanged. The author notes a throughput of roughly 100 bytes per minute and tested it successfully on a 4,000-byte program, but explicitly warns that data tables and workspace areas within the scanned range must be excluded to avoid corruption. *** ## Program Analysis ### Program Structure The program is divided into two logical sections. Lines `5`–`80` display explanatory text about the tool’s capabilities and limitations, then halt with `STOP` so the user can read before proceeding. Lines `100`–`499` contain the actual relocation engine, entered by running `GO TO 100` (or `RUN 100` as the prompt suggests). Line `500` saves the program with autostart at line `5`, and line `520` verifies the saved file. ### Input and Initialization Line `100` initializes a flag variable `xx` to zero and defines a two-byte little-endian PEEK function: `DEF FN P(x)=PEEK x+256*PEEK (x+1)`. Lines `102`–`110` collect three parameters: `b` (start of old location), `e` (end of old location), and `n` (start of new location). Entering `9` at any prompt causes an immediate `STOP`. Line `115` validates that neither source nor destination start below address `26710` (the typical start of the ZX Spectrum RAM area above the system variables) and that `e>=b`, issuing a `BEEP` and looping back on bad input. ### Main Scan Loop Line `120` computes the relocation delta `d=b-n` (positive when moving to a lower address) and sets the loop pointer `i=b`. Each iteration of the loop, controlled by lines `125`–`350`, reads the byte at address `i` into `s`, writes it to the new location at `i-d`, then classifies the opcode to determine how many additional bytes to consume and whether any embedded address needs to be rewritten. Line `127` prints the current address as a progress indicator using `PRINT AT 6,6;i`, which is the source of the slow throughput (~100 bytes/minute) since screen output is expensive. ### Opcode Classification Logic The classification uses a cascade of range and value checks, combined with DATA table lookups: - **Line `135`:** Opcodes in the range 63–193 are treated as single-byte instructions with no operands; the loop advances by 1 via line `300`. - **Line `140`–`155` (ED prefix):** If `s=237` (0xED), the DATA table at line `440` is searched for the second byte. A match indicates a 16-bit operand follows; `FN P(i+2)` reads it and `GO SUB 400` adjusts it if within the code block. Advance by 4 bytes via line `350`. - **Lines `170`–`200` (DD/FD prefix):** If `s=221` or `253` (0xDD / 0xFD), two DATA tables are checked. Line `430` lists five second-byte opcodes that carry a 16-bit address (advance 4); line `420` lists eleven that advance only 2 bytes; anything else advances 3 bytes via line `320`. - **Lines `220`–`250` (unprefixed opcodes):** Line `450` (24 entries) lists opcodes with an 8-bit immediate — advance 2 bytes via line `310`. Line `460` (26 entries) lists opcodes with a 16-bit address operand; these are read with `FN P(i+1)`, adjusted via `GO SUB 400`, and advance 3 bytes via line `320`. ### Address Adjustment Subroutine (Line 400) The subroutine at line `400` takes the candidate address in `q`. If `q0 THEN LET s(1)=u: LET s(2)=v` — handles the unprefixed 16-bit opcodes from the DATA `460` table, where the address starts at offset +1 rather than +2, so the bytes go into `s(1)`/`s(2)` instead. ### DATA Tables | Line | Contents | Purpose | | --- | --- | --- | | `420` | 9,25,35,40,41,57,225,227,229,233,249 | DD/FD second bytes: no address operand, advance 2 | | `430` | 33,34,42,54,229 | DD/FD second bytes: 16-bit address, advance 4 | | `440` | 67,75,83,91,99,107,115,123 | ED second bytes (block load/store): 16-bit address, advance 4 | | `450` | 24 entries | Unprefixed opcodes with 8-bit immediate, advance 2 | | `460` | 26 entries | Unprefixed opcodes with 16-bit address, advance 3 | ### Notable Techniques - The `DEF FN P(x)` function neatly encapsulates little-endian 16-bit PEEK in a single expression, called repeatedly throughout the address-adjustment logic. - Using `DIM s(3)` at line `140` inside the loop re-initializes the array to zeros on every iteration, providing implicit clearing of the operand buffer without explicit POKE or assignment overhead. - The variable `xx` doubles as both an uninitialized-DATA sentinel (reset to `0` after each DATA loop) and as a flag to the subroutine at line `412` to switch the byte-packing target from `s(2)/s(3)` to `s(1)/s(2)`. - Progress display with `PRINT AT 6,6;i` overwrites the same screen position each iteration, giving a live counter without scrolling. ### Bugs and Anomalies - Line `140` reads `s(1)=PEEK (i+1)` and then checks `IF s=203` — but at this point `s` holds the original opcode byte (from line `130`), not `s(1)`. The intention is clearly to check whether the second byte is `0xCB` (a CB-prefixed instruction), but the condition should read `IF s(1)=203`. As written, this branch can never be taken unless the opcode itself happens to be 203. - The range check at line `135` (`PEEK i>=63 AND PEEK i<=193`) performs two additional memory reads even though `s` already holds `PEEK i` from line `130`; it would be more efficient to write `IF s>=63 AND s<=193`. - The CB prefix (0xCB) is not explicitly handled as a two-byte prefix group. Opcodes 0xCB nn are two bytes with no 16-bit address, and will fall through to the DATA `450`/`460` tables; since 0xCB (203) does appear in the DATA `450` list, most CB-prefixed instructions will advance 2 bytes, which is correct for the majority of cases. - The `RESTORE` calls before each DATA loop are necessary because BASIC DATA reads are sequential and the pointer must be reset; this is correctly handled throughout. ## Source Code ``` 5 PRINT "mc-move";" by John Leary 12/84": PRINT 10 PRINT "The routine at line 100 alters absolute addresses for relocat- ing machine code." 15 PRINT : PRINT "It cannot handle data tables or workspaces; you must exclude such areas." 20 PRINT : PRINT "It is not fast; it moves about 100 bytes per minute." 30 PRINT : PRINT "I used it on a 4000 byte programwithout finding any errors." 40 PRINT : PRINT "Addresses below start of code (line 102) are not altered. Thisprotects ROM calls." 50 PRINT : PRINT "Run 100 to proceed" 80 STOP 100 LET xx=0: DEF FN P(x)=PEEK x+256*PEEK (x+1) 101 PRINT "Input 9 to STOP" 102 INPUT "start of old location ";b: PRINT b: IF b=9 THEN STOP 105 INPUT "end of old location ";e: PRINT e: IF e=9 THEN STOP 110 INPUT "start of new location ";n: PRINT n: IF n=9 THEN STOP 115 IF b<26710 OR n<26710 OR ee THEN STOP 127 PRINT AT 6,6;i 130 LET s=PEEK i: POKE i-d,s 135 IF PEEK i>=63 AND PEEK i<=193 THEN GO TO 300 140 DIM s(3): LET s(1)=PEEK (i+1): IF s=203 THEN GO TO 310 142 LET s(2)=PEEK (i+2): LET s(3)=PEEK (i+3) 145 IF s<>237 THEN GO TO 170 150 RESTORE 440: FOR j=1 TO 8: READ z: IF z=s(1) THEN LET q=FN P(i+2): GO SUB 400: GO TO 350 155 NEXT j: GO TO 310 170 IF s<>221 AND s<>253 THEN GO TO 220 175 IF s(1)=203 THEN GO TO 350 180 RESTORE 430: FOR j=1 TO 5: READ z: IF z=s(1) THEN LET q=FN P(i+2): GO SUB 400: GO TO 350 185 NEXT j: RESTORE 420: FOR j=1 TO 11: READ z 190 IF z=s(1) THEN GO TO 310 200 NEXT j: GO TO 320 220 RESTORE 450: FOR j=1 TO 24: READ z: IF z=s THEN GO TO 310 225 NEXT j 230 RESTORE 460: FOR j=1 TO 26: READ xx 240 IF xx=s THEN LET q=FN P(i+1): GO SUB 400: LET xx=0: GO TO 320 250 NEXT j: LET xx=0 300 LET i=i+1: GO TO 125 310 POKE i-d+1,s(1): LET i=i+2: GO TO 125 320 POKE i-d+1,s(1): POKE i-d+2,s(2): LET i=i+3: GO TO 125 350 POKE i-d+1,s(1): POKE i-d+2,s(2): POKE i-d+3,s(3): LET i=i+4: GO TO 125 400 IF q0 THEN LET s(1)=u: LET s(2)=v 415 RETURN 420 DATA 9,25,35,40,41,57,225,227,229,233,249 430 DATA 33,34,42,54,229 440 DATA 67,75,83,91,99,107,115,123 450 DATA 6,14,16,22,24,30,32,38,40,46,48,54,56,62,198,206,211,214,219,222,230,238,246,254 460 DATA 1,17,33,34,42,49,50,58,194,195,196,202,204,205,210,212,218,220,226,228,234,236,242,244,250,252 499 STOP 500 SAVE "mc-move" LINE 5: STOP 520 VERIFY "mc-move" ```