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 line300. - Line
140–155(ED prefix): Ifs=237(0xED), the DATA table at line440is searched for the second byte. A match indicates a 16-bit operand follows;FN P(i+2)reads it andGO SUB 400adjusts it if within the code block. Advance by 4 bytes via line350. - Lines
170–200(DD/FD prefix): Ifs=221or253(0xDD / 0xFD), two DATA tables are checked. Line430lists five second-byte opcodes that carry a 16-bit address (advance 4); line420lists eleven that advance only 2 bytes; anything else advances 3 bytes via line320. - Lines
220–250(unprefixed opcodes): Line450(24 entries) lists opcodes with an 8-bit immediate — advance 2 bytes via line310. Line460(26 entries) lists opcodes with a 16-bit address operand; these are read withFN P(i+1), adjusted viaGO SUB 400, and advance 3 bytes via line320.
Address Adjustment Subroutine (Line 400)
The subroutine at line 400 takes the candidate address in q. If q<b it returns immediately, preserving ROM calls and absolute references outside the code block. Otherwise, it computes the relocated address q-d, splits it into low byte u and high byte v, and stores them in s(2) and s(3). The special case at line 412 — IF xx<>0 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 line140inside 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
xxdoubles as both an uninitialized-DATA sentinel (reset to0after each DATA loop) and as a flag to the subroutine at line412to switch the byte-packing target froms(2)/s(3)tos(1)/s(2). - Progress display with
PRINT AT 6,6;ioverwrites the same screen position each iteration, giving a live counter without scrolling.
Bugs and Anomalies
- Line
140readss(1)=PEEK (i+1)and then checksIF s=203— but at this pointsholds the original opcode byte (from line130), nots(1). The intention is clearly to check whether the second byte is0xCB(a CB-prefixed instruction), but the condition should readIF 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 thoughsalready holdsPEEK ifrom line130; it would be more efficient to writeIF 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/460tables; since 0xCB (203) does appear in the DATA450list, most CB-prefixed instructions will advance 2 bytes, which is correct for the majority of cases. - The
RESTOREcalls before each DATA loop are necessary because BASIC DATA reads are sequential and the pointer must be reset; this is correctly handled throughout.
Content
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 e<b THEN PRINT "Bad Input-Try again": BEEP 1,.1: GO TO 102
120 LET d=b-n: LET i=b
125 IF i>e 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 q<b THEN RETURN
410 LET v=INT ((q-d)/256): LET u=q-d-256*v: LET s(2)=u: LET s(3)=v
412 IF xx<>0 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"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
