mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-12-10 17:52:58 +00:00
Add PowerPC/4MB rebuilding
This commit is contained in:
parent
9046e888d6
commit
7fbbea569d
@ -6,6 +6,7 @@ class NamedTupleStruct(struct.Struct):
|
||||
|
||||
def __init__(self, *args, name=None, fields=None, **kwargs):
|
||||
self.__namedtuple = namedtuple(name, fields)
|
||||
self._fields = self.__namedtuple._fields
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __tuplify(self, *args, **kwargs):
|
||||
|
@ -1,5 +1,269 @@
|
||||
from os import path
|
||||
import shlex
|
||||
import ast
|
||||
import re
|
||||
import struct
|
||||
import glob
|
||||
|
||||
from .lowlevel import ConfigInfo
|
||||
|
||||
from . import dispatcher
|
||||
|
||||
|
||||
MAPNAMES = ['sup', 'usr', 'cpu', 'ovl']
|
||||
BATNAMES = ['ibat0', 'ibat1', 'ibat2', 'ibat3', 'dbat0', 'dbat1', 'dbat2', 'dbat3']
|
||||
|
||||
|
||||
class CodeLine(dict):
|
||||
def __getattr__(self, attrname):
|
||||
return self[attrname]
|
||||
|
||||
def __setattr__(self, attrname, attrval):
|
||||
self[attrname] = attrval
|
||||
|
||||
|
||||
def iter_configinfo_names():
|
||||
yield 'Configfile'
|
||||
|
||||
n = 1
|
||||
while True:
|
||||
yield 'Configfile-%d' % n
|
||||
n += 1
|
||||
|
||||
|
||||
def is_safe(expr):
|
||||
chexpr = re.sub(r'\b(0x[0-9a-f]+|0b[01]+|\b[1-9][0-9]*|\b0\b)', '', expr.lower())
|
||||
for char in chexpr:
|
||||
if char not in '+-|&()':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sub_constants(expr):
|
||||
K = dict(PMDT_InvalidAddress=0xA00, PMDT_Available=0xA01)
|
||||
|
||||
for k, v in K.items():
|
||||
expr = re.sub(r'\b%s\b' % k, '(%s)' % hex(v), expr)
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
def parse_configinfo(src_path):
|
||||
linelist = []
|
||||
chunks = {'': linelist} # must sort as first
|
||||
|
||||
with open(src_path) as f:
|
||||
for line in f:
|
||||
words = shlex.split(line, comments=True, posix=True)
|
||||
if len(words) == 0: continue
|
||||
|
||||
if len(words) == 1 and words[0].startswith('[') and words[0].endswith(']'):
|
||||
linelist = []
|
||||
chunks[words[0][1:-1]] = linelist
|
||||
continue
|
||||
|
||||
worddict = CodeLine()
|
||||
linelist.append(worddict)
|
||||
for word in words:
|
||||
k, sep, v = word.partition('=')
|
||||
if sep: worddict[k] = v
|
||||
|
||||
# do some cleanup: replace all instances of BASE with ROMImageBaseOffset
|
||||
base = '-0x30C000' # bad fallback, don't skip ROMImageBaseOffset
|
||||
lines = chunks['']
|
||||
for words in lines:
|
||||
for k, v in words.items():
|
||||
if k == 'ROMImageBaseOffset':
|
||||
base = v
|
||||
|
||||
base = '(%s)' % base
|
||||
|
||||
for header, lines in chunks.items():
|
||||
for words in lines:
|
||||
for k, v in list(words.items()):
|
||||
if k == 'BootstrapVersion':
|
||||
words[k] = v.encode('mac_roman')
|
||||
continue
|
||||
|
||||
if k == 'brpn':
|
||||
v2 = re.sub(r'\bBASE\b', base, v)
|
||||
is_relative = (v != v2)
|
||||
if is_safe(v2):
|
||||
words[k] = is_relative, eval(v2)
|
||||
continue
|
||||
|
||||
v2 = re.sub(r'\bBASE\b', base, v)
|
||||
v2 = sub_constants(v2)
|
||||
if is_safe(v2):
|
||||
words[k] = eval(v2)
|
||||
|
||||
return chunks
|
||||
|
||||
|
||||
def insert_and_assert(binary, insertee, offset):
|
||||
existing = binary[offset:offset+len(insertee)]
|
||||
if existing != insertee and any(existing):
|
||||
raise ValueError('inserting over something else')
|
||||
|
||||
binary[offset:offset+len(insertee)] = insertee
|
||||
|
||||
|
||||
def checksum_image(binary, ofs):
|
||||
# ugly, but iterating the right was is painfully slow
|
||||
|
||||
byte_lanes = [sum(binary[i::8]) for i in range(8)]
|
||||
|
||||
zeroed_byte_lanes = list(byte_lanes)
|
||||
for j in range(ofs, ofs+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')
|
||||
|
||||
return allsums
|
||||
|
||||
|
||||
def build(src):
|
||||
raise dispatcher.WrongFormat
|
||||
if not path.exists(path.join(src, 'Configfile')) or path.exists(path.join(src, 'Configfile-1')): raise dispatcher.WrongFormat
|
||||
|
||||
cilist = []
|
||||
for ciname in iter_configinfo_names():
|
||||
try:
|
||||
cilist.append(parse_configinfo(path.join(src, ciname)))
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
break
|
||||
|
||||
if len(cilist) == 0: raise dispatcher.WrongFormat
|
||||
|
||||
# This will typically contain the emulator, which I can't reliably extract
|
||||
try:
|
||||
with open(path.join(src, 'EverythingElse'), 'rb') as f:
|
||||
rom = bytearray(f.read())
|
||||
except FileNotFoundError:
|
||||
rom = bytearray(0x400000)
|
||||
|
||||
# Now we go through every configinfo and insert it (oh hell)
|
||||
for ci in reversed(cilist):
|
||||
fields = {key: 0 for key in ConfigInfo._fields}
|
||||
lowmem = bytearray()
|
||||
pagemap = bytearray()
|
||||
segptrs = [bytearray(128) for _ in MAPNAMES]
|
||||
batmap = bytearray() # will be padded to 128
|
||||
batptrs = [0 for _ in MAPNAMES]
|
||||
|
||||
for header, lines in ci.items():
|
||||
if header == '':
|
||||
for words in lines:
|
||||
for k, v in words.items():
|
||||
if k in fields:
|
||||
fields[k] = v
|
||||
|
||||
elif header == 'LowMemory':
|
||||
for words in lines:
|
||||
lowmem.extend(struct.pack('>LL', words.address, words.value))
|
||||
|
||||
elif header == 'PageMappingInfo':
|
||||
for words in lines:
|
||||
if 'segment_ptr_here' in words:
|
||||
mapidx = MAPNAMES.index(words.map.lower())
|
||||
struct.pack_into('>LL', segptrs[mapidx], 8 * words.segment_ptr_here, len(pagemap), words.segment_register)
|
||||
|
||||
elif 'special_pmdt' in words:
|
||||
key = 'PageMap%sOffset' % words.special_pmdt.upper()
|
||||
fields[key] = len(pagemap)
|
||||
|
||||
elif 'pmdt_page_offset' in words:
|
||||
long2 = words.phys_page << 12 | words.attr
|
||||
pagemap.extend(struct.pack('>HHL', words.pmdt_page_offset, words.pages_minus_1, long2))
|
||||
|
||||
elif header == 'BatMappingInfo':
|
||||
for words in lines:
|
||||
if 'bat_ptr_here' in words:
|
||||
batidx = BATNAMES.index(words.bat_ptr_here.lower())
|
||||
mapidx = MAPNAMES.index(words.map.lower())
|
||||
fourbits = len(batmap) // 8
|
||||
shift = 4 * (7 - batidx)
|
||||
batptrs[mapidx] |= fourbits << shift
|
||||
|
||||
elif 'bepi' in words:
|
||||
ubat = lbat = 0
|
||||
|
||||
ubat |= words.bepi & 0xFFFE0000 # trailing zeroes hopefully
|
||||
ubat |= words.bl_128k << 2
|
||||
ubat |= words.vs << 1
|
||||
ubat |= words.vp
|
||||
|
||||
lbat |= words.unk23 << 8
|
||||
lbat |= words.wim << 4
|
||||
lbat |= words.ks << 3
|
||||
lbat |= words.ku << 2
|
||||
lbat |= words.pp
|
||||
|
||||
is_relative, brpn = words.brpn # special case in parse_configinfo
|
||||
if is_relative: lbat |= 0x200
|
||||
lbat = (lbat + brpn) & 0xFFFFFFFF
|
||||
|
||||
batmap.extend(struct.pack('>LL', ubat, lbat))
|
||||
|
||||
# Get the awkward array data into the struct
|
||||
fields['SegMap32SupInit'] = segptrs[0]
|
||||
fields['SegMap32UsrInit'] = segptrs[1]
|
||||
fields['SegMap32CPUInit'] = segptrs[2]
|
||||
fields['SegMap32OvlInit'] = segptrs[3]
|
||||
fields['BatMap32SupInit'] = batptrs[0]
|
||||
fields['BatMap32UsrInit'] = batptrs[1]
|
||||
fields['BatMap32CPUInit'] = batptrs[2]
|
||||
fields['BatMap32OvlInit'] = batptrs[3]
|
||||
fields['BATRangeInit'] = batmap
|
||||
|
||||
# Great, now we'll neaten up fields and blat it out
|
||||
lowmem.extend(b'\0\0\0\0')
|
||||
|
||||
flat = bytearray(0x1000)
|
||||
ptr = len(flat)
|
||||
|
||||
ptr -= len(lowmem)
|
||||
insert_and_assert(flat, lowmem, ptr)
|
||||
fields['MacLowMemInitOffset'] = ptr
|
||||
|
||||
ptr -= len(pagemap)
|
||||
insert_and_assert(flat, pagemap, ptr)
|
||||
fields['PageMapInitOffset'] = ptr
|
||||
fields['PageMapInitSize'] = len(pagemap)
|
||||
|
||||
insert_and_assert(flat, ConfigInfo.pack(**fields), 0)
|
||||
|
||||
# Insert the ConfigInfo struct!
|
||||
configinfo_offset = -fields['ROMImageBaseOffset'] # this var used below
|
||||
insert_and_assert(rom, flat, configinfo_offset)
|
||||
|
||||
# Now insert other things as directed by the struct
|
||||
described = [
|
||||
('Mac68KROM', 'Mac68KROM'),
|
||||
('ExceptionTable', 'ExceptionTable'),
|
||||
('HWInitCode', 'HWInit'),
|
||||
('KernelCode', 'NanoKernel'),
|
||||
('OpenFWBundle', 'OpenFW'),
|
||||
]
|
||||
|
||||
for basename, filename in described:
|
||||
blob_offset = fields[basename + 'Offset']
|
||||
if blob_offset == 0: continue
|
||||
|
||||
matches = glob.glob(glob.escape(path.join(src, filename)) + '*')
|
||||
if matches:
|
||||
match = min(matches) # try for * before *.src
|
||||
blob = dispatcher.build_path(match)
|
||||
insert_and_assert(rom, blob, configinfo_offset + blob_offset)
|
||||
|
||||
# let's do a cheeky checksum!
|
||||
cksum = checksum_image(rom, configinfo_offset)
|
||||
insert_and_assert(rom, cksum, configinfo_offset) # overwrites start of ConfigInfo
|
||||
|
||||
return bytes(rom)
|
||||
|
@ -1,7 +1,7 @@
|
||||
from .lowlevel import SuperMarioHeader, ConfigInfo
|
||||
|
||||
import struct
|
||||
|
||||
import shlex
|
||||
import os
|
||||
from os import path
|
||||
|
||||
@ -22,7 +22,7 @@ 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= # Offset of base of Hardware Init 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
|
||||
@ -124,7 +124,7 @@ def dump_configinfo(binary, offset, push_line):
|
||||
if key == 'InterruptHandlerKind':
|
||||
value = '0x%02X' % raw_value
|
||||
elif key == 'BootstrapVersion':
|
||||
value = repr(raw_value)[1:]
|
||||
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'
|
||||
@ -201,6 +201,7 @@ def dump_configinfo(binary, offset, push_line):
|
||||
attr_s = 'PMDT_InvalidAddress'
|
||||
elif attr == 0xA01:
|
||||
attr_s = 'PMDT_Available'
|
||||
# elif attr &
|
||||
else:
|
||||
attr_s = '0x%03X' % attr
|
||||
|
||||
@ -214,7 +215,7 @@ def dump_configinfo(binary, offset, push_line):
|
||||
if i == s.PageMapKDPOffset: push_line('special_pmdt=kdp')
|
||||
if i == s.PageMapEDPOffset: push_line('special_pmdt=edp')
|
||||
|
||||
push_line('pmdt_page_offset=0x%04X pages_minus_1=0x%04X phys_page=%s attr=%s' % (pgidx, pgcnt, paddr_s, attr_s))
|
||||
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('')
|
||||
|
||||
@ -237,17 +238,22 @@ def dump_configinfo(binary, offset, push_line):
|
||||
vp = u & 1
|
||||
|
||||
brpn = l >> 17
|
||||
wimg = [(l > 6) & 1, (l > 5) & 1, (l > 4) & 1, (l > 3) & 1]
|
||||
pp = [(l > 1) & 1, l & 1]
|
||||
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(11)
|
||||
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('bepi=0x%08X bl=%s vs=%s vp=%d brpn=%s wimg=0b%d%d%d%d pp=0b%d%d' % (bepi << 17, bl_s, vs, vp, brpn_s, *wimg, *pp))
|
||||
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('')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user