MC-MOVE

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 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 580 display explanatory text about the tool’s capabilities and limitations, then halt with STOP so the user can read before proceeding. Lines 100499 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 102110 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 125350, 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 140155 (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 170200 (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 220250 (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 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 412IF 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

LineContentsPurpose
4209,25,35,40,41,57,225,227,229,233,249DD/FD second bytes: no address operand, advance 2
43033,34,42,54,229DD/FD second bytes: 16-bit address, advance 4
44067,75,83,91,99,107,115,123ED second bytes (block load/store): 16-bit address, advance 4
45024 entriesUnprefixed opcodes with 8-bit immediate, advance 2
46026 entriesUnprefixed 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.

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

    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.

People

No people associated with this content.

Scroll to Top