swap68/swap68

151 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
import struct
import binascii
import functools
syserr32k = '\xA9\xC9' * 0x4000
def hexlit(string):
hexes = ''.join(c for c in string.lower() if c in '0123456789abcdef')
return binascii.unhexlify(hexes)
def identify_branch_islands(binary):
lst = []
for i in range(0, len(binary) - 15, 16):
if binary[i:i+2] == b'\x60\xFF':
if not any(binary[i+6:i+16]):
targ = i + 2 + struct.unpack_from('>l', binary, i + 2)[0]
if targ % 2 == 0:
lst.append((i, targ))
return lst
# Find a unique match in binary2 (branches etc excepted).
# Return a (start, stop) tuple of the unique match, or throw a ValueError
@functools.lru_cache() # minor speedup
def identify_same_code(binary1, offset1, binary2):
THANGS = [
(b'\x4e\x56', 2, 'LINK.W A6,#t'),
(b'\x60\x00', 2, 'BRA t'),
(b'\x61\x00', 2, 'BSR t'),
(b'\x60\xff', 4, 'BRA.L t'),
(b'\x61\xff', 4, 'BSR.L t'),
(b'\x4e\xba', 2, 'JSR t'),
(b'\x4e\xfa', 2, 'JMP t'),
(b'\x48\x7a', 2, 'PEA t'),
(b'\x41\xfa', 2, 'LEA t, A0'),
(b'\x43\xfa', 2, 'LEA t, A1'),
(b'\x45\xfa', 2, 'LEA t, A2'),
(b'\x47\xfa', 2, 'LEA t, A3'),
(b'\x49\xfa', 2, 'LEA t, A4'),
(b'\x4b\xfa', 2, 'LEA t, A5'),
(b'\x4d\xfa', 2, 'LEA t, A6'),
(b'\x4f\xfa', 2, 'LEA t, A7'),
]
matches = range(0, len(binary2) - 1, 2)
mask = bytearray()
while len(matches) > 1:
offender = binary1[offset1+len(mask):offset1+len(mask)+2]
if len(offender) < 2: break
for known, n_unknown, human in THANGS:
if offender.startswith(known):
mask.extend(b'\xFF' * len(known))
mask.extend(b'\x00' * n_unknown)
break
else:
mask.extend(b'\xFF' * len(offender))
nu_matches = []
for m in matches:
if all((binary2[m+i] == binary1[offset1+i] or mask[i] == 0) for i in range(len(mask))):
nu_matches.append(m)
matches = nu_matches
if len(matches) != 1:
raise ValueError('could not match')
return matches[0], matches[0] + len(mask)
def command_line():
import argparse
can_do_list = sorted(l[4:] for l in globals() if l.startswith('Swap'))
parser = argparse.ArgumentParser(description='''
Move Managers from one Mac 68k ROM image to another. Supported:
''' + ' '.join(can_do_list))
parser.add_argument('dest', help='Recipient MainCode image')
parser.add_argument('base', help='Base MainCode image')
parser.add_argument('donor', help='Donor MainCode image')
parser.add_argument('swap', nargs='*', choices=can_do_list, metavar='mgr', help='Which Managers? (listed above)')
args = parser.parse_args()
base = open(args.base, 'rb').read()
donor = open(args.donor, 'rb').read()
dest = base
for s in args.swap:
dest = globals()['Swap' + s](dest, donor)
open(args.dest, 'wb').write(dest)
def SwapGoNative(base, donor):
def identify_caller(binary):
a = binary.index(hexlit('303C 4E2B A9C9')) # MOVE.W #dsGNLoadFail,D0; _SysError
a -= 8
assert binary[a:a+2] == hexlit('61FF') # BSR.L
return a
def identify(binary):
a = identify_caller(binary) + 2
start = a + struct.unpack_from('>l', binary, a)[0]
stop = binary.index(hexlit('21C8 005C 4E75'), start) # MOVE.L A0,$005C; RTS
stop += 8
return start, stop
# Get donor region, plus branch islands within 32k on either side
# (Because any of these islands may be reached by a JSR)
dstart, dstop = kstart, kstop = identify(donor)
islands = identify_branch_islands(donor)
islands = [i for i in islands if kstart - 0x8000 <= i[0] < kstop + 0x8000]
if islands:
kstart = min(kstart, islands[0][0])
kstop = max(kstop, islands[-1][0]+16)
# kstart will be moved to len(nubase)
nubase = bytearray(base)
while len(nubase) % 16: nubase.append(0)
delta = len(nubase) - kstart
nubase.extend(donor[kstart:kstop]) # try to redact some of this ala SysError
caller = identify_caller(base) + 2 # BSR.L
struct.pack_into('>l', nubase, caller, dstart + delta - caller)
for src, dest in islands:
src += delta
src += 2 # make calculations easier
assert src +4 < len(nubase)
dest, _ = identify_same_code(donor, dest, base)
struct.pack_into('>l', nubase, src, dest - src)
return bytes(nubase)
command_line()