MCUtil is a machine code utility suite providing seven tools for inspecting, entering, relocating, and converting machine code in memory. The program displays memory contents as a navigable block grid (up to 160 bytes) with joystick support via STICK, uses UDG characters (defined by POKEing addresses 65368–65391) to render a three-character cursor overlay with OVER 1, and performs hex conversion by hand using CHR$ arithmetic rather than a built-in hex function. A separate decimal-to-machine-code address splitter computes the high and low bytes of any 16-bit address, and a relocation routine handles both upward and downward block moves with careful loop-bound calculations to avoid overwriting source data. Lines 9958–9999 contain a self-modifying BASIC renumberer that patches GO TO/GO SUB target line numbers in both their display text and the hidden floating-point number stored inline by the Spectrum’s tokenizer.
Program Structure
The program is organized as a menu dispatcher at line 2560 that routes to seven functional modules. Lines below 2560 contain the modules; lines above 2560 through 3700 hold shared subroutines; and lines 9958–9999 are a standalone BASIC renumberer appended at the top of the line-number space to avoid interfering with renumbering itself.
| Lines | Module |
|---|---|
| 1050–1400 | Review MC in blocks (navigable grid) |
| 1410–1530 | Decimal address → Low/High byte splitter |
| 1540–1780 | Dec code pair → decimal value converter |
| 1790–2000 | Machine code loader (POKE loop) |
| 2040–2470 | Relocate machine code block |
| 2480–2550 | RAMTOP display |
| 2560–2850 | Main menu |
| 2860–2950 | Hex conversion subroutine |
| 2970–3280 | Input & calculate (N / S / E) |
| 3350–3380 | RAMTOP PEEK subroutine |
| 3410–3450 | Memory array builder (DIM M$) |
| 3490–3560 | UDG cursor definition |
| 3570–3700 | List MC by address with hex column |
| 9958–9999 | Self-modifying BASIC renumberer |
Memory Array and Block Display
The block viewer (lines 1050–1400) stores up to 160 bytes as a string array M$ dimensioned to (N,4) at line 3410, where each element holds the decimal string representation of a POKEd byte (e.g., "255"). The display arranges bytes in a 20-column × 8-row grid; the cursor position (X,Y) maps to a byte index via SA = 8*X + INT(Y/4) + 1, reflecting the four-character-wide fields.
Three UDG characters \a\b\c (chars 144–146) are defined at lines 3520–3550 by POKEing 24 bytes starting at address 65368. They form a three-part cursor bracket drawn with PRINT AT X,Y; OVER 1;"\a\b\c", using OVER 1 (XOR mode) so the cursor can be erased by redrawing it without restoring the background explicitly.
Joystick and Keyboard Navigation
Lines 1360–1390 implement dual-input navigation combining STICK (port 2 joystick) with cursor keys 5/6/7/8. The STICK return value is decoded by checking ranges: values 1/5/9 = up, 2/6/10 = down, ST>7 = right, ST>3 AND ST<7 = left. Boundary clamping uses Boolean arithmetic rather than IF statements; for example, X = X + (X<0) - (...overflow condition...) keeps X within the valid grid row range, with separate logic for the final partial row controlled by XLIM and YLIM.
Hex Conversion Subroutine
Lines 2890–2950 convert a decimal byte value stored in M$(I) to a two-character hex string in H$. It splits the value into high and low nibbles, then converts each nibble to its ASCII character: digits 0–9 via CHR$(nibble+48) and letters A–F via CHR$(nibble+55). There is no built-in hex function, so this manual approach is the standard workaround.
Relocation Routine
The relocator (lines 2040–2470) handles both upward and downward moves. If the new address NS is below the source start S, it copies forward (low-to-high addresses, lines 2270–2300) to avoid overwriting unread source bytes; if NS > S, it copies backward (high-to-low, lines 2350–2380). After copying, the vacated region is zeroed. The zero-fill loop bound is computed with a conditional expression:
- If the gap is smaller than the block: zero only the gap (
S-NS-1orNS-S-1iterations). - If the gap equals or exceeds the block: zero the entire original block (
E-Siterations).
This avoids zeroing bytes that were never part of the original block when the destination is far from the source.
Input and Calculation Subroutine
Lines 2970–3280 accept any two of three values — number of bytes N, starting address S, and ending address E — and derive the third. The logic at lines 3110–3270 handles all combinations. A guard at line 3110 detects the degenerate case where both N$ and S$ are empty and redirects to the error handler at 3280. The subroutine shares a Q variable with the menu to enforce the 160-byte limit only for the block viewer (option 1).
RAMTOP Subroutine
Lines 3350–3380 read RAMTOP from Spectrum system variables at addresses 23730 (low byte) and 23731 (high byte), combining them as RT = 256*RH + RL. The relocation routine at lines 3460–3480 (reachable but not called from the menu) would lower RAMTOP by POKEing the updated value back to the same addresses.
Self-Modifying BASIC Renumberer (Lines 9958–9999)
This module walks the BASIC program area using the PROG system variable at address 23635. It uses two inline functions: FN A(X) reads a little-endian 16-bit word (low byte first) and FN B(S) reads a big-endian 16-bit word (high byte first), used to read line lengths and line numbers respectively.
The first pass (9962–9975) scans all lines below 9958 for GO TO (token 236) and GO SUB (token 237) statements, collecting the memory address and current target line number of each into the string T$ in 9-character records: 5-character address + 4-character line number.
The second pass (9976–9987) walks the program again, renumbering lines starting at 1000 in steps of 10 by POKEing the new line number into the two header bytes of each line. For each line, it checks T$ for any GO TO/GO SUB targeting that line and calls the subroutine at 9990 if found.
The patch subroutine (9990–9999) updates both the display text of the target number (4 bytes via CODE(STR$ BASE)(J)) and the Spectrum’s hidden inline floating-point representation (5 bytes starting 6 bytes after the token), reconstructing the binary float from the new line number using LN to determine the exponent byte.
Bugs and Anomalies
- Line 3230 recalculates
N = E-S+1and then checksIF E<S THEN GO TO 3175, but line 3175 does not exist; this would generate an error. However, the guard at line 3200 already preventsE<Sfrom reaching line 3230 in practice. - Lines 3390–3400 (
LET S$="RT+1-N") and 3460–3480 (lower RAMTOP) are defined but never called from any menu option or subroutine in the listing. - Line 3000 references
Q=1as a guard, but the subroutine at 2970 is entered for menu option 1 beforeQis set at line 3000; for other options the condition is false, which is the intended behavior. - The “ADDESS” typo (missing R) at lines 2200 and 2210 appears in both screen and LPRINT output.
- Line 1300 validates
Q$as an integer between 0 and 255, but ifQ$contains non-numeric text,VAL Q$will return 0 rather than raising an error, silently POKEing 0.
Content
Source Code
1000 REM ***********************
1010 REM MCUTIL
1020 REM ***********************
1030 BORDER 5
1040 GO TO 2560
1050 REM ***********************
1060 REM LIST BY BLOCK
1070 REM ***********************
1080 LET T$="1"
1090 GO SUB 3010
1100 GO SUB 3410
1110 GO SUB 3490
1120 CLS
1130 LET XLIM= INT ((N-1)/8)
1140 LET YLIM=4*(N-1-8*XLIM)
1150 FOR I=1 TO N
1160 PRINT M$(I);
1170 LPRINT M$(I);
1180 NEXT I
1190 PRINT AT 21,0;"\{20}\{1}\{20}\{1}ENTER E TO EXIT M TO MODIFY\{20}\{1} \{20}\{0}\{20}\{0}"\{20}\{0}
1200 LET X=0:LET Y=0:LET X1=0:LET Y1=0
1210 PRINT AT X,Y; OVER 1;"\a\b\c"
1220 LET SA=8*(X)+ INT (Y/4)+1
1230 LET SAO=8*(X1)+ INT (Y1/4)+1
1240 PRINT AT X1,Y1;M$(SAO)
1250 PRINT AT X,Y; OVER 1;"\a\b\c"
1260 PRINT AT 20,12;"CURSOR ADDRESS ";S+SA-1
1270 IF INKEY$="E" THEN GO TO 2560
1280 IF INKEY$ <>"M" THEN GO TO 1330
1290 INPUT "ENTER NEW BYTE: ";Q$
1300 IF Q$="" OR VAL Q$<0 OR VAL Q$>255 OR VAL Q$ <> INT VAL Q$ THEN GO TO 1330
1310 POKE (S+SA-1), VAL Q$:LET M$(SA)=Q$
1320 PRINT AT X,Y;M$(SA)
1330 LET ST=STICK (1,2)
1350 LET X1=X:LET Y1=Y
1360 LET X=X+(ST=2 OR ST=6 OR ST=10 OR INKEY$="6")-(ST=1 OR ST=5 OR ST=9 OR INKEY$="7")
1370 LET X=X+(X<0)-((X>XLIM AND Y <=YLIM) OR (X>XLIM-1 AND Y>YLIM))
1380 LET Y=Y+4*((ST>7 OR INKEY$="8")-((ST>3 AND ST<7) OR INKEY$="5"))
1390 LET Y=Y+4*((Y<0)-((Y>28 AND X<(XLIM)) OR (Y>YLIM AND X=XLIM)))
1400 GO TO 1220
1410 REM ***********************
1420 REM DECIMAL TO MC
1430 REM ***********************
1440 CLS
1450 PRINT "\{20}\{1}DECIMAL TO MC\{20}\{0}"
1460 PRINT
1470 INPUT "ENTER ADDRESS (DECIMAL): ";Q$
1480 LET H= INT ((VAL Q$)/256)
1490 LET L= VAL Q$-256*H
1500 PRINT "ADDRESS: ";Q$; TAB 16;"L: ";L; TAB 24;"H: ";H
1510 INPUT "NEW ADDR?"'"HIT ENTER TO RET TO MENU ";Q$
1520 IF Q$="" THEN GO TO 2560
1530 GO TO 1480
1540 REM ***********************
1550 REM DEC CODE TO DEC VALUE
1560 REM ***********************
1570 CLS
1580 PRINT
1590 PRINT AT 0,0;"\{20}\{1}DEC. CODE TO DEC. CONVERSION\{20}\{0}"
1600 LPRINT AT 0,0;"\{20}\{1}DEC. CODE TO DEC. CONVERSION\{20}\{0}"
1610 PRINT
1620 LPRINT
1630 PRINT AT 3,0;"LOW"; AT 3,5;"HIGH"; AT 3,12;"ADDR OR VALUE"
1640 LPRINT AT 3,0;"LOW"; AT 3,5;"HIGH"; AT 3,12;"ADDR OR VALUE"
1650 PRINT
1660 LPRINT
1670 INPUT "HIT ENTER TO RETURN TO MENU..."'"DECIMAL CODE: LOW ";L$
1680 IF L$="" THEN GO TO 2560
1690 PRINT L$;
1700 LPRINT L$
1710 INPUT "DECIMAL CODE: HIGH ";H$
1720 IF H$="" THEN GO TO 2560
1730 PRINT TAB 5;H$;
1740 LPRINT TAB 5;H$
1750 LET A=256*(VAL H$)+ VAL L$
1760 PRINT TAB 12;A
1770 LPRINT TAB 12;A
1780 GO TO 1670
1790 REM ***********************
1800 REM MC LOADER
1810 REM ***********************
1820 CLS
1830 PRINT "\{20}\{1}MC LOADER\{20}\{0}"
1840 GO SUB 2970
1850 CLS
1860 PRINT "INPUT CODE (DEC) FOR EA ADDR"
1870 LET SUM=0
1880 FOR I=1 TO N
1890 PRINT INK 3; VAL (S$)+I-1;
1900 INPUT C$
1910 IF C$="" THEN CLS :GO TO 2560
1920 IF VAL C$>255 THEN GO TO 1900
1930 PRINT TAB (10- LEN C$);C$
1940 POKE (VAL (S$)+I-1), VAL C$
1950 LET SUM=SUM+ VAL C$
1960 NEXT I
1970 PRINT
1980 PRINT "CHECKSUM= ";SUM
1990 PRINT "HIT ANY KEY TO RETURN TO MENU":PAUSE 4E4
2000 GO TO 2560
2010 REM ***********************
2020 REM RELOCATE MC
2030 REM ***********************
2040 CLS
2050 PRINT "RELOCATE MACHINE CODE"
2060 PRINT
2070 GO SUB 2970
2080 PRINT "CURRENT STARTING ADDRESS OF"'"BLOCK TO BE RELOCATED IS ";S$
2090 PRINT
2100 LPRINT "CURRENT STARTING ADDRESS OF"'"BLOCK TO BE RELOCATED IS ";S$
2110 LPRINT
2120 PRINT "ENDING ADDRESS OF BLOCK"'"TO BE RELOCATED ";E
2130 LPRINT "ENDING ADDRESS OF BLOCK"'"TO BE RELOCATED ";E
2140 PRINT
2150 LPRINT
2160 INPUT "ENTER NEW START ADDRESS ";T$
2170 IF T$="" THEN GO TO 2560
2180 LET NS= VAL T$
2190 IF NS<1 OR NS <> INT NS THEN GO TO 2160
2200 PRINT "NEW STARTING ADDESS IS ";NS
2210 LPRINT "NEW STARTING ADDESS IS ";NS
2220 PRINT
2230 LPRINT
2240 PRINT "BLOCK BEING RELOCATED"
2250 PRINT
2260 IF S<NS THEN GO TO 2350
2270 FOR I=0 TO E-S
2280 LET C= PEEK (S+I)
2290 POKE (NS+I),C
2300 NEXT I
2310 FOR I=0 TO ((S-NS-1)*((S-NS) <=(E-S)))+((E-S)*((S-NS)>(E-S)))
2320 POKE (E-I),0
2330 NEXT I
2340 GO TO 2450
2350 FOR I=0 TO E-S
2360 LET C= PEEK (E-I)
2370 POKE (E+NS-S-I),C
2380 NEXT I
2390 PRINT "NOW ZEROING VACATED MEMORY LOCATIONS"
2400 FOR I=0 TO ((NS-S-1)*((NS-S) <=(E-S)))+((E-S)*((NS-S)>(E-S)))
2410 POKE (S+I),0
2420 IF I/200= INT (I/200) THEN PRINT "STILL RUNNING"
2430 NEXT I
2440 PRINT
2450 PRINT "THE BLOCK OF MACHINE CODE HAS"'"BEEN RELOCATED TO START AT"'NS
2460 PAUSE 600
2470 GO TO 2560
2480 REM ***********************
2490 REM RAMTOP
2500 REM ***********************
2510 CLS
2520 GO SUB 3350
2530 PRINT "RAMTOP IS AT ";RT
2540 PAUSE 600
2550 GO TO 2560
2560 REM ***********************
2570 REM MENU
2580 REM ***********************
2590 CLS
2600 PRINT TAB 10;"\{20}\{1}\ \ \ \m\e\n\u\ \ \ \{20}\{0}"
2610 PRINT
2620 PRINT
2630 PRINT "1...REVIEW MC IN BLOCKS"
2640 PRINT
2650 PRINT "2...REVIEW MC WITH ADDRESSES"
2660 PRINT
2670 PRINT "3...CONVERT DECIMAL TO MC"
2680 PRINT
2690 PRINT "4...CONVERT DEC CODE TO DEC VAL"
2700 PRINT
2710 PRINT "5...MACH CODE LOADER"
2720 PRINT
2730 PRINT "6...RELOCATE MACH CODE"
2740 PRINT
2750 PRINT "7...RAMTOP LOCATION"
2760 PRINT
2770 INPUT "ENTER SELECTION ";Q
2780 CLS :IF Q=1 THEN PRINT "REVIEW MC IN BLOCKS":PRINT :PRINT "JOYSTICK IS ALLOWED IN PORT 2":GO TO 1050
2790 IF Q=2 THEN GO TO 3570
2800 IF Q=3 THEN PRINT "CONVERT DECIMAL TO MC":PRINT :GO TO 1410
2810 IF Q=4 THEN PRINT "CONVERT DEC CODE TO DEC VALUE":PRINT :GO TO 1540
2820 IF Q=5 THEN PRINT "MACHINE CODE LOADER":PRINT :GO TO 1790
2830 IF Q=6 THEN GO TO 2040
2840 IF Q=7 THEN GO TO 2480
2850 GO TO 2560
2860 REM ******************
2870 REM HEX CALCULATION
2880 REM ******************
2890 LET P= INT (VAL M$(I)/16)
2900 LET Q= VAL M$(I)-16*P
2910 IF P <=9 THEN LET H$= CHR$ (P+48)
2920 IF P>9 THEN LET H$= CHR$ (P+55)
2930 IF Q <=9 THEN LET H$=H$+ CHR$ (Q+48)
2940 IF Q>9 THEN LET H$=H$+ CHR$ (Q+55)
2950 RETURN
2970 REM ***********************
2980 REM INPUT AND CALC ROUTINE
2990 REM ***********************
3000 IF Q=1 THEN PRINT AT 5,0;"MAXIMUM OF 160 BYTES"
3010 PRINT AT 10,0;"ENTER ANY 2 OF 3 ITEMS"
3020 PRINT AT 12,0;"NUMBER OF BYTES:"
3030 PRINT AT 14,0;"STARTING ADDRESS:"
3040 PRINT AT 16,0;"ENDING ADDRESS:"
3050 PRINT AT 18,5;"HIT ENTER IF UNKNOWN"
3060 INPUT "ENTER NUMBER OF BYTES ";N$:IF N$="" THEN GO TO 3100
3070 LET N= VAL N$:IF N<1 OR N <> INT N THEN GO TO 3060
3080 IF Q=1 AND N>160 THEN GO TO 3060
3090 PRINT AT 12,17;N
3100 INPUT "ENTER STARTING ADDRESS ";S$
3110 IF N$="" AND S$="" THEN GO TO 3280
3120 IF S$="" THEN GO TO 3160
3130 LET S= VAL S$:IF S<1 OR S <> INT S THEN GO TO 3100
3140 PRINT AT 14,18;S
3150 IF N$ <>"" THEN LET E=S+N-1:PRINT AT 16,16;E:PRINT AT 18,0;"CREATING MEMORY ARRAY ":PAUSE 180:RETURN
3160 INPUT "ENTER ENDING ADDRESS ";E$:IF E$="" THEN GO TO 3280
3170 LET E= VAL E$
3180 IF E<1 OR E <> INT E THEN GO TO 3250
3190 IF S$="" THEN LET S=E-N+1
3200 IF E<S THEN PRINT AT 20,0; FLASH 1;"END ADDR MUST BE > START ADDR":PAUSE 180:PRINT AT 20,0," ": GO TO 3160
3210 PRINT AT 16,16;E
3220 PRINT AT 14,18;S
3230 LET N=E-S+1:IF E<S THEN GO TO 3175
3240 IF Q=1 AND N>160 THEN PRINT FLASH 1; AT 5,0;"MAXIMUM OF 160 BYTES":PAUSE 180:PRINT AT 14,18;" "; AT 16,16;" ":GO TO 3000
3250 PRINT AT 12,17;N
3260 PRINT AT 12,17;N
3270 PRINT AT 18,0;"CREATING MEMORY ARRAY ":PAUSE 180:RETURN
3280 PRINT FLASH 1; AT 10,0;"ENTER ANY 2 OF 3 ITEMS":PAUSE 180
3290 PRINT AT 12,17;" "; AT 14,18;" "; AT 16,16;" ":GO TO 3010
3300 GO SUB 3350
3310 IF S$="" THEN LET S$="RT+1"
3320 PRINT "STARTING ADDRESS IS "; VAL S$
3330 PRINT
3340 RETURN
3350 LET RL= PEEK 23730
3360 LET RH= PEEK 23731
3370 LET RT=256*RH+RL
3380 RETURN
3390 LET S$="RT+1-N"
3400 RETURN
3410 DIM M$(N,4)
3420 FOR I=1 TO N
3430 LET M$(I)= STR$ PEEK (S-1+I)
3440 NEXT I
3450 RETURN
3460 GO SUB 3350
3470 PRINT "RAMTOP BEING LOWERED TO ";RT-N:POKE 23730,(RT-N)-256* INT ((RT-N)/256):POKE 23731, INT ((RT-N)/256)
3480 RETURN
3490 REM ***********************
3500 REM CREATE CURSOR
3510 REM ***********************
3515 RESTORE
3520 FOR I=65368 TO 65391
3530 READ D:POKE I,D
3540 NEXT I
3550 DATA 255,128,128,128,128,128,128,255,255,0,0,0,0,0,0,255,255,1,1,1,1,1,1,255
3560 RETURN
3570 REM ***********************
3580 REM LIST BY ADDRESS
3590 REM ***********************
3600 CLS :PRINT "\{20}\{1}LIST MC BY ADDRESS\{20}\{0}"
3610 GO SUB 2970
3615 GO SUB 3410
3620 CLS :PRINT "ADDR"; TAB 7;"DEC"; TAB 13;"HEX":PRINT
3630 FOR I=1 TO N
3640 PRINT S-1+I; TAB 7;M$(I);
3650 GO SUB 2860
3660 PRINT TAB 13;H$
3670 LPRINT S-1+I; TAB 7;M$(I); TAB 13;H$
3680 NEXT I
3690 PRINT AT 21,0;"HIT ANY KEY TO RETURN TO MENU":PAUSE 4E4
3700 GO TO 2560
9958 CLS :PRINT "RENUMBERING STARTING AT 1000"
9959 LET T$="":LET X=23635
9960 DEF FN A(X)= PEEK X+256* PEEK (X+1)
9961 DEF FN B(S)=256* PEEK S+ PEEK (S+1)
9962 LET S= FN A(X)
9963 LET LINE= FN B(S):IF LINE >=9958 THEN GO TO 9976
9964 LET LENGTH= FN A(S+2)
9965 IF PEEK (S+4)=234 THEN GO TO 9969
9966 FOR I=S+4 TO S+LENGTH+2
9967 IF PEEK I=236 OR PEEK I=237 THEN GO SUB 9971
9968 NEXT I
9969 LET S=S+LENGTH+4
9970 GO TO 9963
9971 IF PEEK (I+5)=14 THEN GO TO 9974
9972 PRINT "N0N-STANDARD COMMAND, LINE ";LINE
9973 STOP
9974 LET T$=T$+ STR$ I+ CHR$ PEEK (I+1)+ CHR$ PEEK (I+2)+ CHR$ PEEK (I+3)+ CHR$ PEEK (I+4)
9975 RETURN
9976 LET BASE=1000:LET X=23635
9977 LET S= FN A(X)
9978 LET LINE= FN B(S):IF LINE >=9958 THEN BEEP 5,0:STOP
9979 LET LENGTH= FN A(S+2)
9980 FOR I=1 TO LEN T$ STEP 9
9981 IF VAL T$(I+5 TO I+8)=LINE THEN GO SUB 9990
9982 NEXT I
9983 POKE S, INT (BASE/256)
9984 POKE S+1,BASE-256* INT (BASE/256)
9985 LET BASE=BASE+10
9986 LET S=S+LENGTH+4
9987 GO TO 9978
9990 FOR J=1 TO 4
9991 POKE (VAL T$(I TO I+4)+J), CODE (STR$ BASE)(J)
9992 NEXT J
9993 LET BYTE1=128+ INT (LN BASE/ LN 2+1)
9994 LET BYTE2=BASE*65536/(2^(BYTE1-128))
9995 LET MEMORY= VAL T$(I TO I+4)
9996 POKE MEMORY+6,BYTE1
9997 POKE MEMORY+7, INT (BYTE2/256)-128
9998 POKE MEMORY+8,BYTE2-256* INT (BYTE2/256)
9999 RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
