tbxi/tbxi/powerpc_dump.py

368 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. If a second '=' is present, it indicates the name of a file to
insert at this location.
"""
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
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
Mac68KROMSize= # [38] Number of bytes in Macintosh 68K ROM
ExceptionTableOffset= # [3C] Offset of base of PowerPC Exception Table Code
ExceptionTableSize= # [40] Number of bytes in PowerPC Exception Table Code
HWInitCodeOffset= # [44] Offset of base of Hardware Init Code
HWInitCodeSize= # [48] Number of bytes in Hardware Init Code
KernelCodeOffset= # [4C] Offset of base of NanoKernel Code
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
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, filename_dict, 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 raw_value == 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')
if key in filename_dict:
value += '=' + shlex.quote(filename_dict[key])
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 dump(orig_binary, dest_dir):
if not is_powerpc(orig_binary): raise dispatcher.WrongFormat
os.makedirs(dest_dir, exist_ok=True)
# We will zero out parts as we go along extracting them
binary = bytearray(orig_binary)
ci_loc = []; ci_struct = [];
for i in list(find_configinfo(binary)):
ci_loc.append(i)
ci_struct.append(ConfigInfo.unpack_from(binary, i))
extract_and_zero(binary, i, i + 0x1000) # Keep it out of EverythingElse
fields = []
for field in ['Mac68KROM', 'ExceptionTable', 'HWInitCode', 'KernelCode', 'OpenFWBundle', 'ROMImageBase']:
start = ci_loc[0] + ci_struct[0]._asdict()[field + 'Offset']
if field == 'ROMImageBase': # This will contain everything not encompassed by
stop = len(binary)
else:
stop = start + ci_struct[0]._asdict()[field + 'Size']
fields.append((start, stop, field))
# Special case: the "EverythingElse" gets searched for last (emulator not yet extractable)
fields = sorted(fields[:-1]) + fields[-1:]
filename_dict = {} # Maps 'KernelCode' etc to a filename
for start, stop, field in fields:
# ConfigInfo is known to lie about these fields
if field in 'HWInitCode KernelCode OpenFWBundle':
stop = binary.find(bytes(1024), start)
# Always grab a multiple of 4 bytes
while stop % 4 != 0: stop += 1
# Grab the fragment, and zero where it came from
fragment = extract_and_zero(binary, start, stop)
# Nothing to see here
if len(fragment) == 0 or not any(fragment): continue
filename = field.replace('Code', '').replace('Bundle', '').replace('Kern', 'NanoKern').replace('ROMImageBase', 'EverythingElse')
if field == 'KernelCode':
vers = get_nk_version(fragment)
if vers: filename += '-' + vers
filename_dict[field + 'Offset'] = filename
dispatcher.dump(fragment, path.join(dest_dir, filename))
# Finally, write out ConfigInfo with paths to the files that we create
for i, cioffset in enumerate(ci_loc, 1):
filename = 'Configfile-%d' % i
with open(path.join(dest_dir, filename), 'w') as f:
push_line = lambda x: print(x, file=f)
dump_configinfo(orig_binary, cioffset, filename_dict, push_line)