Classic Computer Magazine Archive COMPUTE! ISSUE 87 / AUGUST 1987 / PAGE 97

ML Relocator

Samuel Ford

Here's a solution to a common problem—wanting to use two machine language programs that use the same memory space. For intermediate and advanced machine language programmers.

Sooner or later, most Commodore 64 programmers accumulate a collection of machine language (ML) programs that perform various useful tasks. One may be a BASIC enhancement, another may be a renumbering utility, and so forth. Inevitably, the day comes when you want to use two such programs at the same time, only to discover that both of them load into the same area of the computer's memory. Such conflicts are fairly common on the 64 because it contains a safe memory area (locations 49152-53247) reserved especially for that purpose.

Relocating BASIC programs is a snap: In fact, Commodore computers are designed to relocate a BASIC program automatically as part of the normal loading process. Machine language programs, on the other hand, usually work at one and only one location. Trying to relocate an ML program by hand can be a complicated process, since you need to adjust the targets of all JSR and JMP instructions that refer to locations in the program.

"ML Relocator" automates most of the process of relocating an ML program, allowing you to use favorite programs and utilities even if their original memory location is devoted to some other purpose. Type in and save Programs 1 and 2.

Program 1 is designed for machine language programs that do not occupy any part of BASIC program space (locations 2048-40959). In order to use the program, you must know the normal starting and ending address of the program you wish to relocate, plus the new starting address. If you're not sure of the starting and ending addresses of a program stored on disk, this short program will print them on the screen:

100 PRINT"FILENAME" : INPUT F$
110 OPEN 2, 8, 2, " 0 : " + F$ + ", P, R"
120 GET #2, LO$ : GET #2, HI$
130 ADR = (ASC(LO$ + CHR$(0)) + {SPACE}256 * ASC(HI$ + CHR$(0)))
140 PRINT"STARTING ADDRESS";ADR
150 PRINT"CALCULATING SIZE…"
160 GET #2, X$ : IF ST = 0 THEN ADR = ADR + 1 : GOTO 160
170 PRINT "ENDING ADDRESS : "; ADR
180 CLOSE 2

Program 1 assumes that the program you wish to relocate is already in memory at its normal location. Load the ML program, as usual, before loading Program 1. Be sure to type NEW and press RETURN after loading the ML code, to reset the BASIC pointers.

When you run Program 1, it prints the prompt SOURCE: START, END. Type the program's normal starting and ending addresses, separating the two numbers with a comma. You may enter the addresses in either decimal or hexadecimal (base 16) numbers. To enter an address in hexadecimal, type a dollar sign ($) in front of the number. For instance, you would type $C000 to enter the hexadecimal equivalent of decimal 49152. After you enter those addresses, the program prints the prompt TARGET: START, indicating that you should enter the new address where you wish the ML code to begin.

The next step is very important. The program prompts you to enter areas of the ML program which you wish to skip. These areas may include variables, data tables, and anything else which is not executable ML code. This assumes, of course, that you have enough familiarity with the program to know where those areas are. If the program is one that you wrote, you already know where its data areas reside. For other programs, unless you have documentation about the program's structure, you will need to examine the code with a machine language monitor or disassembler.

Program 1 can remember the locations of up to 20 areas to skip. When specifying such an area, enter its starting and ending addresses. If the area is only one byte long, enter the address of that byte, type a comma, and press RETURN without specifying an ending address.

When you finish entering the locations of areas to skip, enter a comma and press RETURN without typing anything else.

After you perform these steps, the program adjusts the code in the areas you specified. The length of time this takes depends on the length of the program involved. When this is done, you are given the option to relocate the program (enter Y or N as prompted). If you respond with no, the program ends, leaving the adjusted ML program in its original location. If you respond with yes, the program moves the adjusted program to its new location. At that point you can execute the relocated program in place or save it using a machine language monitor or similar means.

Relocating Before You Relocate

Program 2 is designed for relocating ML programs that normally reside somewhere in BASIC's program space. Such a program cannot be relocated by Program 1 because the program itself may corrupt the code you wish to relocate. Program 2 can lower the top or raise the bottom of BASIC, creating a safe space for the ML code to reside.

When you run Program 2, it prints the current bottom and top of BASIC program space—usually 2048 ($0801) and 40960 ($A000), respectively. Enter the new addresses you wish BASIC program space to have; then press RETURN. BASIC is reconfigured, and you may use Program 1 as described above.

No Magic Cure-All

ML Relocator is actually a very simple program. It runs through the specified memory area performing a partial disassembly of the code, looking for any ML instruction that uses absolute addressing. When it finds such an instruction, it checks whether the instruction's target address falls within the boundaries of the program it is relocating. If so, it calculates an address offset equal to the target address minus the source address.

As mentioned above, it's imperative that you tell the program which data areas to skip. If you don't, the program likely will become confused, mistaking data bytes for instructions that need adustment. If the data becomes garbled, the relocated program probably won't work at all.

In addition to raw data areas, there are some programming techniques which can't be handled by this simple method of relocation. For example, many programs set up a table of two-byte vectors which are used as the target for indirect JMP instructions or similar purposes. Another limitation has to do with zero-page indirect addressing, a very popular addressing mode in 6502/6510 machine language programming. For instance, consider this series of instructions:

LDA #$00
STA $FD
LDA #$C1
STA $FE
LDA #$41
LDY #$00
STA ($FD), Y

These instructions store the character a (ASCII 65, hex $41) in location $C100, which we'll assume to be a variable located within the program to be relocated. The first four instructions store the address $C100 in two consecutive locations in the zero page (lowest 256 bytes) of the 64's memory. The last instruction references the location $C100 indirectly, using the address stored in locations 253-254 ($FD–$FE). Because the address $C100 never appears as a complete address in this code, ML Relocator can't tell that it needs adjustment.

In this and similar circumstances, you have no alternative but to examine the code with a monitor and adjust the addressing by hand. ML Relocator can take much of the drudgery out of relocating a program, but it's not a magic cure-all.

For instructions on entering these programs, please refer to "COMPUTE!'s Guide to Typing In Programs" elsewhere in this issue.

Program 1: ML Relocator

EG 5 REM COPYRIGHT 1987 COMPUTE! PUBLICATIONS, INC. {3 SPACES} ALL RIGHTS RESERVED.
FR 10 PRINT"{CLR} {5 SPACES} {RVS} ML RELOCATOR {OFF} {DOWN}"
HC 12 PRINT"{5 SPACES} COPYRIGHT 1987" : PRINT"COMPUTE! PUBLICATIONS, INC."
BP 14 PRINT"{3 SPACES} ALL RIGHTS RESERVED."
GG 16 FOR X = 1 TO 1000 : NEXT
MA 20 DIM CD(255), TB(20, 1)
DC 30 PRINT"{DOWN} SETTING UP…" ; : FOR A = 0 TO 255 : READ {SPACE} CD(A) : NEXT : PRINT"DONE"
AB 40 INPUT"{DOWN} SOURCE : START, END" ; CS$, CE$
AP 41 N$ = CS$ : GOSUB 500 : CS = N
GE 42 N$ = CE$ : GOSUB 500 : CE = N
JG 50 INPUT"TARGET : {5 SPACES} START" ; TS$
GS 51 N$ = TS$ : GOSUB 500 : TS = N
FS 60 NT = 0 : PRINT"{DOWN} ENTER SECTIONS OF MEMORY TO SKIP, {7 SPACES} END WITH COMMA {DOWN}"
CJ 70 PRINT"TABLE" NT + 1; : INPUT {SPACE} A$, B$
SS 72 IF A$ = "" THEN 100
JX 74 IF B$ = "" THEN B$ = A$
SS 76 N$ = A$ : GOSUB 500 : A = N : N$ = B$ : GOSUB 500 : B = N
RQ 80 IF B = 0 THEN B = A
JQ 85 IF A<CS OR A>CE OR B<CS {SPACE} OR B>CE OR B<A THEN 70
FS 90 NT = NT + 1 : TB(NT, 0) = A : TB(NT, 1) = B : GOTO 70
KH 100 PRINT "{DOWN} WORKING…"
BC 110 IF NT<2 THEN 120
JP 112 FOR A = NT TO 2 STEP-1 : FOR B = 1 TO A-1
MJ 114 IF TB(A, 0)<TB(B, 0) THEN T = TB(A, 0) : TB(A, 0) = TB(B, 0) : TB(B, 0) = T
CP 116 NEXT B, A
JQ 120 TP = 0 : PT = CS : OF = TS - CS
XH 130 IF NT = TP THEN 140
PS 135 Z1 = TP + 1 : IF PT = > TB(Z1, 0) AND PT < = TB(Z1, 1) THEN {SPACE} PT = TB(Z1, 1) + 1 : TP = TP + 1
PG 140 Z1 = CD(PEEK(PT)) : IF Z1 < 2 THEN 160
PH 150 Z2 = PEEK(PT + 1) + PEEK(PT + 2) * 256
RR 155 IFZ2 = > CSANDZ2 < = CETHENZ2 = Z2 + OF : POKEPT + 2, Z2/256 : POKEPT + 1, Z2 - INT(Z2/256) * 256
XD 160 PT = PT + Z1 + 1 : IF PT < CE THEN 130
SF 170 PRINT "{DOWN} FINISHED. " ; : 
EB 180 INPUT "RELOCATE (Y/N)" ; A$ : IF A$ < > "Y" AND A$ < >"N" THEN 180
PS 190 IF A$ = "N" THEN END
KX 200 PRINT"{DOWN}WORKING …" ;
AJ 210 IF TS > CS THEN FOR A = CE {SPACE} TO CS STEP -1 : POKE A + OF, PEEK (A) : NEXT
HD 220 IF TS < CS THEN FOR A = CS{SPACE} TO CE : POKE A + OF, PEEK(A) : NEXT
RH 230 PRINT "DONE" : END
PQ 500 N1$ = LEFT$(N$, 1) : N$ = MID$(N$, 2)
XX 510 IF N1$ < > "$" THEN N = VAL(N1$ + N$) : RETURN
QF 520 N = 0 : FOR N1 = 1 TO 4 : N2 = ASC(MID$(N$, N1, 1)) - 55 : N2 = N2 - 7 * (N2 < 10)
CS 530 N = N + N2 * 16 ↑ (4 - N1) : NEXT : RETURN
CS 1000 DATA 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 2, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 2, 2, 0
QH 1010 DATA 2, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 2, 2, 0
GE 1020 DATA 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 2, 2, 0
FH 1030 DATA 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0 , 0, 1 , 1, 0, 0, 2, 0, 0, 0, 2, 2, 0
XK 1040 DATA 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 2, 0, 0, 0, 2, 0, 0
CE 1050 DATA 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 2, 0, 0, 2, 2, 2, 0
ED 1060 DATA 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 2, 2, 0
BX 1070 DATA 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 2, 2, 2, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 2, 0, 0, 0, 2, 2, 0

Program 2: BASIC Relocator

EG 5 REM COPYRIGHT 1987 COMPUTE! PUBLICATIONS, INC. {5 SPACES} ALL RIGHTS RESERVED.
JC 10 PRINT "{CLR}{4 SPACES} {RVS} BASIC RELOCATOR {OFF}{DOWN}"
CK 12 PRINT "{4 SPACES} COPYRIGHT 1987" : PRINT "COMPUTE! PUBLICATIONS, INC."
QP 14 PRINT "{2 SPACES}ALL RIGHTS RESERVED."
GG 16 FOR X = 1 TO 1000 : NEXT
FR 20 SB = PEEK(43) + PEEK(44)*256 : EB = PEEK(55) + PEEK(56)*256
GR 30 PRINT"{DOWN}CURRENT START : "SB"{LEFT}, " : N = SB : GOSUB 2000 : PRINTHX$ : SB$ = HX$
RP 40 PRINT "CURRENT{3 SPACES}END : "EB" {LEFT}, " ; : N = EB : GOSUB 2000 : PRINTHX$ : EB$ = HX$
FD 50 PRINT "{DOWN} NEW START? " SB$ "{7 LEFT}" ; : INPUT N$ : GOSUB 1000 : NS = N
PC 55 PRINT "NEW{3 SPACES} END? {SPACE} "EB$" {7 LEFT}" ; : INPUT N$ : GOSUB 1000 : NE = N
MA 60 IF NE < NS THEN PRINT" {DOWN} TRY AGAIN" : GOTO 50
JS 70 PRINT"{DOWN}COMPLETED."
DK 80 PRINT"{3 DOWN}POKE43, "NS-INT(NS/256)*256" {LEFT} : POKE44, "INT (NS/256)" {LEFT} : ";
HR 81 PRINT"POKE55, "NE-INT (NE/256) * 256 "{LEFT} : POKE56, " INT (NE/256) "{LEFT} : POKE" NS" {LEFT}, 0 : NEW" ;
GC 90 POKE 631, 145 : POKE 632, 145 : POKE 633, 13 : POKE 198, 3
GG 999 END
PP 1000 N1$ = LEFT$ (N$, 1) : N$ = MID$(N$, 2)
HS 1010 IF NI$ <> "$" THEN N = VAL(N1$ + N$) : RETURN
PS 1020 N = 0 : FOR N1 = 1 TO 4 : N2 = ASC(MID$(N$, N1, 1))-55 : N2 = N2 - 7 * (N2 < 10)
SR 1030 N = N + N2 * 16 ↑ (4 - N1) : NEST : RETURN
DB 2000 H$ = "0123456789ABCDEF" : HX$ = "$"
CC 2010 FOR N1 = 3 TO 0 STEP -1 : N2 = 16 ↑ N1 : N3 = INT (N/N2) : N = N - N3 * N2
DB 2020 HX$ = HX$ + MID$ (H$, N3 + 1, 1) : NEXT : RETURN