MCUtil

Date: 198x
Type: Program
Platform(s): TS 2068

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.

LinesModule
1050–1400Review MC in blocks (navigable grid)
1410–1530Decimal address → Low/High byte splitter
1540–1780Dec code pair → decimal value converter
1790–2000Machine code loader (POKE loop)
2040–2470Relocate machine code block
2480–2550RAMTOP display
2560–2850Main menu
2860–2950Hex conversion subroutine
2970–3280Input & calculate (N / S / E)
3350–3380RAMTOP PEEK subroutine
3410–3450Memory array builder (DIM M$)
3490–3560UDG cursor definition
3570–3700List MC by address with hex column
9958–9999Self-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-1 or NS-S-1 iterations).
  • If the gap equals or exceeds the block: zero the entire original block (E-S iterations).

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+1 and then checks IF E<S THEN GO TO 3175, but line 3175 does not exist; this would generate an error. However, the guard at line 3200 already prevents E<S from 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=1 as a guard, but the subroutine at 2970 is entered for menu option 1 before Q is 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 if Q$ contains non-numeric text, VAL Q$ will return 0 rather than raising an error, silently POKEing 0.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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.

People

No people associated with this content.

Scroll to Top