mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-06-14 02:29:33 +00:00
b5af802944
The Pippin ROM has lower checksums than expected, by about 4%. Without recovering the missing data, this probably cannot be replicated, so we just detect it and live with it.
373 lines
15 KiB
Python
373 lines
15 KiB
Python
from .lowlevel import SuperMarioHeader, ConfigInfo
|
|
|
|
import struct
|
|
import shlex
|
|
import os
|
|
from os import path
|
|
|
|
from . import dispatcher
|
|
|
|
|
|
PAD = b'kc' * 100
|
|
|
|
HEADER_COMMENT = """
|
|
Automated dump of the ConfigInfo page of a Power Mac ROM
|
|
(at least one per ROM)
|
|
|
|
The first section contains the simple structure fields. The [LowMemory]
|
|
section instructs the kernel to set low-memory globals. The
|
|
[PageMappingInfo] section lists parts of ConfigInfo that tell the kernel
|
|
how to lay out the PowerPC page table. (Hint: lines not starting with a
|
|
tab are pointers into an array). The [BatMappingInfo] section similarly
|
|
tells the kernel how to lay out the Block Allocation Table registers.
|
|
|
|
Fields encoding the offset of a ROM component are computed from the base
|
|
of ConfigInfo, but for clarity are expressed here relative to the "BASE"
|
|
of ROM. ConfigInfo itself will be placed at -ROMImageBaseOffset.
|
|
"""
|
|
|
|
HEADER_COMMENT = '\n'.join('# ' + l if l else '' for l in HEADER_COMMENT.strip().split('\n'))
|
|
|
|
CONFIGINFO_TEMPLATE = """
|
|
ROMImageBaseOffset= # [28] Offset of Base of total ROM image [./EverythingElse placed here]
|
|
ROMImageSize= # [2C] Number of bytes in ROM image
|
|
ROMImageVersion= # [30] ROM Version number for entire ROM
|
|
|
|
# ROM component Info (offsets are from base of ConfigInfo page)
|
|
Mac68KROMOffset= # [34] Offset of base of Macintosh 68K ROM [./Mac68KROM placed here]
|
|
Mac68KROMSize= # [38] Number of bytes in Macintosh 68K ROM
|
|
|
|
ExceptionTableOffset= # [3C] Offset of base of PowerPC Exception Table Code [./ExceptionTable placed here]
|
|
ExceptionTableSize= # [40] Number of bytes in PowerPC Exception Table Code
|
|
|
|
HWInitCodeOffset= # [44] Offset of base of Hardware Init Code [./HWInit placed here]
|
|
HWInitCodeSize= # [48] Number of bytes in Hardware Init Code
|
|
|
|
KernelCodeOffset= # [4C] Offset of base of NanoKernel Code [./NanoKernel placed here]
|
|
KernelCodeSize= # [50] Number of bytes in NanoKernel Code
|
|
|
|
EmulatorCodeOffset= # [54] Offset of base of Emulator Code
|
|
EmulatorCodeSize= # [58] Number of bytes in Emulator Code
|
|
|
|
OpcodeTableOffset= # [5C] Offset of base of Opcode Table
|
|
OpcodeTableSize= # [60] Number of bytes in Opcode Table
|
|
|
|
# Offsets within the Emulator Data Page.
|
|
BootstrapVersion= # [64] Bootstrap loader version info
|
|
############## VALUES SPECIFIC TO THIS 68K EMULATOR VERSION ##############
|
|
BootVersionOffset= # [74] offset within EmulatorData of BootstrapVersion
|
|
ECBOffset= # [78] offset within EmulatorData of ECB
|
|
IplValueOffset= # [7C] offset within EmulatorData of IplValue
|
|
|
|
# Offsets within the Emulator Code.
|
|
EmulatorEntryOffset= # [80] offset within Emulator Code of entry point
|
|
KernelTrapTableOffset= # [84] offset within Emulator Code of KernelTrapTable
|
|
|
|
# Interrupt Passing Masks.
|
|
TestIntMaskInit= # [88] initial value for test interrupt mask
|
|
ClearIntMaskInit= # [8C] initial value for clear interrupt mask
|
|
PostIntMaskInit= # [90] initial value for post interrupt mask
|
|
##################### END OF EMULATOR-SPECIFIC VALUES ####################
|
|
LA_InterruptCtl= # [94] logical address of Interrupt Control I/O page
|
|
InterruptHandlerKind= # [98] kind of handler to use
|
|
|
|
LA_InfoRecord= # [9C] logical address of InfoRecord page
|
|
LA_KernelData= # [A0] logical address of KernelData page
|
|
LA_EmulatorData= # [A4] logical address of EmulatorData page
|
|
LA_DispatchTable= # [A8] logical address of Dispatch Table
|
|
LA_EmulatorCode= # [AC] logical address of Emulator Code
|
|
|
|
# Address Space Mapping.
|
|
PageAttributeInit= # [B4] default WIMG/PP settings for PTE creation
|
|
|
|
# Only needed for Smurf
|
|
SharedMemoryAddr= # [35C] physical address of Mac/Smurf shared message mem
|
|
|
|
PA_RelocatedLowMemInit= # [360] physical address of RelocatedLowMem
|
|
|
|
OpenFWBundleOffset= # [364] Offset of base of OpenFirmware PEF Bundle [./OpenFW placed here]
|
|
OpenFWBundleSize= # [368] Number of bytes in OpenFirmware PEF Bundle
|
|
|
|
LA_OpenFirmware= # [36C] logical address of Open Firmware
|
|
PA_OpenFirmware= # [370] physical address of Open Firmware
|
|
LA_HardwarePriv= # [374] logical address of HardwarePriv callback
|
|
""".strip()
|
|
|
|
|
|
def extract_and_zero(binary, start, stop):
|
|
ret = binary[start:stop]
|
|
binary[start:stop] = bytes(stop - start)
|
|
return ret
|
|
|
|
|
|
def find_configinfo(binary):
|
|
# Find a ConfigIngo struct by checking every possible
|
|
# place for a valid checksum. Ugly but quick.
|
|
|
|
byte_lanes = [sum(binary[i::8]) for i in range(8)]
|
|
|
|
for i in range(0, len(binary), 0x100):
|
|
zeroed_byte_lanes = list(byte_lanes)
|
|
for j in range(i, i+40):
|
|
zeroed_byte_lanes[j % 8] -= binary[j]
|
|
|
|
sum32 = [lane % (1<<32) for lane in zeroed_byte_lanes]
|
|
|
|
sum64 = sum(lane << (k * 8) for (k, lane) in enumerate(reversed(zeroed_byte_lanes)))
|
|
sum64 %= 1 << 64
|
|
|
|
allsums = b''.join(x.to_bytes(4, byteorder='big') for x in sum32)
|
|
allsums += sum64.to_bytes(8, byteorder='big')
|
|
|
|
# if i == 0x30d000: # for figuring out pippin in future
|
|
# print(*['%02X' % x for x in allsums])
|
|
# print(*['%02X' % x for x in binary[i:i+len(allsums)]])
|
|
|
|
if binary[i:i+len(allsums)] == allsums:
|
|
break
|
|
else:
|
|
# Hack for Pippin ROM, which has bad checksum
|
|
for i in range(0x300000, len(binary), 0x100):
|
|
if binary[i+0x64:].startswith(b'Boot '):
|
|
break
|
|
else:
|
|
return # failed!
|
|
|
|
# Which structs share the BootstrapVersion signature?
|
|
for j in range(0, len(binary), 0x100):
|
|
if binary[i+0x64:i+0x74] == binary[j+0x64:j+0x74]:
|
|
yield j
|
|
|
|
|
|
def dump_configinfo(binary, offset, push_line):
|
|
s = ConfigInfo.unpack_from(binary, offset)
|
|
|
|
push_line(HEADER_COMMENT + '\n')
|
|
|
|
# First section (no [header]):
|
|
# Raw key=value lines not resembling the struct in PCCInfoRecordsPriv.h
|
|
for line in CONFIGINFO_TEMPLATE.split('\n'):
|
|
if '=' in line:
|
|
key, _, remainder = line.partition('=')
|
|
raw_value = getattr(s, key)
|
|
if key == 'InterruptHandlerKind':
|
|
value = '0x%02X' % raw_value
|
|
elif key == 'BootstrapVersion':
|
|
value = shlex.quote(raw_value.decode('mac_roman'))
|
|
elif key.endswith('Offset') and key.startswith(('Mac68KROM', 'ExceptionTable', 'HWInitCode', 'KernelCode', 'EmulatorCode', 'OpcodeTable', 'OpenFWBundle')):
|
|
if getattr(s, key.replace('Offset', 'Size')) == 0:
|
|
value = '0x00000000'
|
|
else:
|
|
value = 'BASE0x%+X' % (raw_value - s.ROMImageBaseOffset)
|
|
else:
|
|
value = '0x%08X' % raw_value
|
|
|
|
value = value.replace('0x-', '-0x').replace('0x+', '+0x')
|
|
|
|
nuline = key + '=' + value
|
|
while remainder.startswith(' ') and len(nuline) + len(remainder) > len(line):
|
|
remainder = remainder[1:]
|
|
nuline += remainder
|
|
line = nuline
|
|
|
|
push_line(line)
|
|
|
|
push_line('')
|
|
|
|
# Now dump the more structured parts of the ConfigInfo
|
|
mapnames = ['sup', 'usr', 'cpu', 'ovl']
|
|
|
|
segmaps = [[],[],[],[]]
|
|
for i, blob in enumerate((s.SegMap32SupInit, s.SegMap32UsrInit, s.SegMap32CPUInit, s.SegMap32OvlInit)):
|
|
for j in range(0, len(blob), 8):
|
|
tpl = struct.unpack_from('>LL', blob, j)
|
|
segmaps[i].append(tpl)
|
|
|
|
def print_seg_ptrs_for_offset(segmap_offset):
|
|
for header, list16 in zip(mapnames, segmaps):
|
|
for seg_i, (seg_offset, seg_reg) in enumerate(list16):
|
|
if seg_offset == segmap_offset:
|
|
push_line('segment_ptr_here=0x%X map=%s segment_register=0x%08X' % (seg_i, header, seg_reg))
|
|
|
|
batmaps = [[],[],[],[]]
|
|
for i, blob in enumerate((s.BatMap32SupInit, s.BatMap32UsrInit, s.BatMap32CPUInit, s.BatMap32OvlInit)):
|
|
for j in reversed(range(0, 32, 4)):
|
|
batmaps[i].append(((blob >> j) & 0xF) * 8)
|
|
|
|
last_used_batmap = max(y for x in batmaps for y in x)
|
|
|
|
def print_bat_ptrs_for_offset(batmap_offset):
|
|
for header, list8 in zip(mapnames, batmaps):
|
|
for bat_offset, bat_name in zip(list8, ['ibat0', 'ibat1', 'ibat2', 'ibat3', 'dbat0', 'dbat1', 'dbat2', 'dbat3']):
|
|
if bat_offset == batmap_offset:
|
|
push_line('bat_ptr_here=%s map=%s' % (bat_name, header))
|
|
|
|
lowmem = []
|
|
lmoffset = s.MacLowMemInitOffset
|
|
while any(binary[offset+lmoffset:][:4]):
|
|
key, val = struct.unpack_from('>LL', binary, offset+lmoffset)
|
|
lowmem.append((key, val))
|
|
lmoffset += 8
|
|
|
|
push_line('[LowMemory]')
|
|
for key, val in lowmem:
|
|
push_line('address=0x%08X value=0x%08X' % (key, val))
|
|
push_line('')
|
|
|
|
push_line('[PageMappingInfo]')
|
|
if s.PageMapInitSize or any(s.SegMap32SupInit + s.SegMap32UsrInit + s.SegMap32CPUInit + s.SegMap32OvlInit):
|
|
push_line('# Constants: PMDT_InvalidAddress = 0xA00, PMDT_Available = 0xA01')
|
|
|
|
pagemapinit = binary[offset:][s.PageMapInitOffset:][:s.PageMapInitSize]
|
|
|
|
for i in range(0, len(pagemapinit), 8):
|
|
print_seg_ptrs_for_offset(i)
|
|
|
|
pgidx, pgcnt, word2 = struct.unpack_from('>HHL', pagemapinit, i)
|
|
attr = word2 & 0xFFF
|
|
|
|
if attr == 0xA00:
|
|
attr_s = 'PMDT_InvalidAddress'
|
|
elif attr == 0xA01:
|
|
attr_s = 'PMDT_Available'
|
|
# elif attr &
|
|
else:
|
|
attr_s = '0x%03X' % attr
|
|
|
|
paddr = word2 >> 12
|
|
if 'Rel' in attr_s:
|
|
paddr_s = 'BASE+0x%05X' % ((paddr + offset) & 0xFFFFF)
|
|
else:
|
|
paddr_s = '0x%05X' % paddr
|
|
|
|
if i == s.PageMapIRPOffset: push_line('special_pmdt=irp')
|
|
if i == s.PageMapKDPOffset: push_line('special_pmdt=kdp')
|
|
if i == s.PageMapEDPOffset: push_line('special_pmdt=edp')
|
|
|
|
push_line('\tpmdt_page_offset=0x%04X pages_minus_1=0x%04X phys_page=%s attr=%s' % (pgidx, pgcnt, paddr_s, attr_s))
|
|
|
|
push_line('')
|
|
|
|
push_line('[BatMappingInfo]')
|
|
if any(s.BATRangeInit) or s.BatMap32SupInit or s.BatMap32UsrInit or s.BatMap32CPUInit or s.BatMap32OvlInit:
|
|
for i in range(0, len(s.BATRangeInit), 8):
|
|
if i > last_used_batmap * 8: break
|
|
|
|
print_bat_ptrs_for_offset(i)
|
|
|
|
u, l = struct.unpack_from('>LL', s.BATRangeInit, i)
|
|
|
|
is_relative = l & 0x200
|
|
if is_relative:
|
|
l = (offset + l) & 0xFFFFFFFF - is_relative
|
|
|
|
bepi = u >> 17
|
|
bl = (u >> 2) & 0x7FF
|
|
vs = (u >> 1) & 1
|
|
vp = u & 1
|
|
|
|
brpn = l >> 17
|
|
unk23 = (l >> 8) & 1
|
|
wim = (l >> 4) & 0x7
|
|
ks = (l >> 3) & 1
|
|
ku = (l >> 2) & 1
|
|
pp = l & 0x3
|
|
|
|
bl_s = '0b' + bin(bl)[2:].zfill(6)
|
|
wim_s = bin(wim)[2:].zfill(3)
|
|
pp_s = bin(pp)[2:].zfill(2)
|
|
|
|
if is_relative:
|
|
brpn_s = 'BASE+0x%06X' % (brpn << 17)
|
|
else:
|
|
brpn_s = '0x%08X' % (brpn << 17)
|
|
|
|
push_line('\tbepi=0x%08X bl_128k=%s vs=%d vp=%d brpn=%s unk23=%d wim=0b%s ks=%d ku=%d pp=0b%s' % (bepi << 17, bl_s, vs, vp, brpn_s, unk23, wim_s, ks, ku, pp_s))
|
|
|
|
push_line('')
|
|
|
|
|
|
|
|
def is_powerpc(binary):
|
|
return (len(binary) == 0x400000) and (PAD in binary[:0x300000])
|
|
|
|
|
|
def get_nk_version(nk):
|
|
if nk.startswith(b'\x48\x00\x00\x0C'):
|
|
# v2 NK has structured header
|
|
return 'v%02X.%02X' % (nk[4], nk[5])
|
|
|
|
for i in range(0, len(nk) - 8, 4):
|
|
if nk[i:i+2] == b'\x39\x80': # li r12, ???
|
|
if nk[i+4:i+8] == b'\xB1\x81\x0F\xE4': # sth r12, 0xFE4(r1)
|
|
return 'v%02X.%02X' % (nk[i+2], nk[i+3]) # return the ???
|
|
|
|
|
|
def extract_plausible_thing(binary, start):
|
|
stop = binary.find(bytes(1024), start) # check, because kernel is often absent or wrong size
|
|
if stop > start:
|
|
while stop % 4 != 0: stop += 1
|
|
return extract_and_zero(binary, start, stop)
|
|
|
|
|
|
def dump(binary, dest_dir):
|
|
if not is_powerpc(binary): raise dispatcher.WrongFormat
|
|
|
|
os.makedirs(dest_dir, exist_ok=True)
|
|
|
|
cioffsets = list(find_configinfo(binary))
|
|
|
|
# We will zero out parts as we go along extracting them
|
|
binary = bytearray(binary)
|
|
|
|
for i, cioffset in enumerate(cioffsets, 1):
|
|
filename = 'Configfile'
|
|
if len(cioffsets) > 1: filename += '-' + str(i)
|
|
|
|
with open(path.join(dest_dir, filename), 'w') as f:
|
|
push_line = lambda x: print(x, file=f)
|
|
dump_configinfo(binary, cioffset, push_line)
|
|
|
|
best_cioffset = cioffsets[0]
|
|
best_ci = ConfigInfo.unpack_from(binary, best_cioffset)
|
|
|
|
for cioffset in cioffsets:
|
|
extract_and_zero(binary, cioffset, cioffset + 0x1000)
|
|
|
|
supermario = extract_and_zero(binary,
|
|
best_cioffset + best_ci.Mac68KROMOffset,
|
|
best_cioffset + best_ci.Mac68KROMOffset + best_ci.Mac68KROMSize)
|
|
dispatcher.dump(supermario, path.join(dest_dir, 'Mac68KROM'))
|
|
|
|
xtbl = extract_and_zero(binary,
|
|
best_cioffset + best_ci.ExceptionTableOffset,
|
|
best_cioffset + best_ci.ExceptionTableOffset + best_ci.ExceptionTableSize)
|
|
if any(xtbl):
|
|
# xtbl_len = len(xtbl)
|
|
# while not any(xtbl[xtbl_len-4:xtbl_len]): xtbl_len -= 4
|
|
# xtbl = xtbl[xtbl_len:]
|
|
with open(path.join(dest_dir, 'ExceptionTable'), 'wb') as f:
|
|
f.write(xtbl)
|
|
|
|
nk = extract_plausible_thing(binary, min(0x310000, best_cioffset + best_ci.KernelCodeOffset))
|
|
if nk:
|
|
name = 'NanoKernel'
|
|
vers = get_nk_version(nk)
|
|
if vers: name += '-' + vers
|
|
|
|
with open(path.join(dest_dir, name), 'wb') as f:
|
|
f.write(nk)
|
|
|
|
hwinit = extract_plausible_thing(binary, best_cioffset + best_ci.HWInitCodeOffset)
|
|
if hwinit:
|
|
with open(path.join(dest_dir, 'HWInit'), 'wb') as f:
|
|
f.write(hwinit)
|
|
|
|
openfw = extract_plausible_thing(binary, best_cioffset + best_ci.OpenFWBundleOffset)
|
|
if openfw:
|
|
with open(path.join(dest_dir, 'OpenFW'), 'wb') as f:
|
|
f.write(openfw)
|
|
|
|
with open(path.join(dest_dir, 'EverythingElse'), 'wb') as f:
|
|
f.write(binary)
|