368 lines
15 KiB
Python
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)
|