mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-06-25 20:29:43 +00:00
Rebuild SuperMario ROMs cleanly
This commit is contained in:
parent
ce6a1e747f
commit
19ec05be87
|
@ -53,21 +53,20 @@ def parse_configinfo(src_path):
|
||||||
linelist = []
|
linelist = []
|
||||||
chunks = {'': linelist} # must sort as first
|
chunks = {'': linelist} # must sort as first
|
||||||
|
|
||||||
with open(src_path) as f:
|
for line in dispatcher.build_path(src_path).decode('utf8').split('\n'):
|
||||||
for line in f:
|
words = shlex.split(line, comments=True, posix=True)
|
||||||
words = shlex.split(line, comments=True, posix=True)
|
if len(words) == 0: continue
|
||||||
if len(words) == 0: continue
|
|
||||||
|
|
||||||
if len(words) == 1 and words[0].startswith('[') and words[0].endswith(']'):
|
if len(words) == 1 and words[0].startswith('[') and words[0].endswith(']'):
|
||||||
linelist = []
|
linelist = []
|
||||||
chunks[words[0][1:-1]] = linelist
|
chunks[words[0][1:-1]] = linelist
|
||||||
continue
|
continue
|
||||||
|
|
||||||
worddict = CodeLine()
|
worddict = CodeLine()
|
||||||
linelist.append(worddict)
|
linelist.append(worddict)
|
||||||
for word in words:
|
for word in words:
|
||||||
k, sep, v = word.partition('=')
|
k, sep, v = word.partition('=')
|
||||||
if sep: worddict[k] = v
|
if sep: worddict[k] = v
|
||||||
|
|
||||||
# do some cleanup: replace all instances of BASE with ROMImageBaseOffset
|
# do some cleanup: replace all instances of BASE with ROMImageBaseOffset
|
||||||
base = '-0x30C000' # bad fallback, don't skip ROMImageBaseOffset
|
base = '-0x30C000' # bad fallback, don't skip ROMImageBaseOffset
|
||||||
|
@ -132,6 +131,8 @@ def checksum_image(binary, ofs):
|
||||||
def build(src):
|
def build(src):
|
||||||
if not path.exists(path.join(src, 'Configfile')) or path.exists(path.join(src, 'Configfile-1')): raise dispatcher.WrongFormat
|
if not path.exists(path.join(src, 'Configfile')) or path.exists(path.join(src, 'Configfile-1')): raise dispatcher.WrongFormat
|
||||||
|
|
||||||
|
print('powerpc', src)
|
||||||
|
|
||||||
cilist = []
|
cilist = []
|
||||||
for ciname in iter_configinfo_names():
|
for ciname in iter_configinfo_names():
|
||||||
try:
|
try:
|
||||||
|
@ -143,8 +144,7 @@ def build(src):
|
||||||
|
|
||||||
# This will typically contain the emulator, which I can't reliably extract
|
# This will typically contain the emulator, which I can't reliably extract
|
||||||
try:
|
try:
|
||||||
with open(path.join(src, 'EverythingElse'), 'rb') as f:
|
rom = bytearray(dispatcher.build_path(path.join(src, 'EverythingElse')))
|
||||||
rom = bytearray(f.read())
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
rom = bytearray(0x400000)
|
rom = bytearray(0x400000)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,125 @@
|
||||||
|
import shlex
|
||||||
|
import ast
|
||||||
|
import struct
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
from . import lowlevel
|
||||||
from . import dispatcher
|
from . import dispatcher
|
||||||
|
|
||||||
|
|
||||||
|
ALIGN = 16
|
||||||
|
REV_COMBO_FIELDS = {v: k for (k, v) in lowlevel.COMBO_FIELDS.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def checksum(binary):
|
||||||
|
binary[:4] = bytes(4)
|
||||||
|
binary[0x30:0x40] = bytes(16)
|
||||||
|
|
||||||
|
lanes = [sum(binary[i::4]) & 0xFFFFFFFF for i in range(4)]
|
||||||
|
struct.pack_into('>LLLL', binary, 0x30, *lanes)
|
||||||
|
|
||||||
|
oneword = (sum(binary[::2])*256 + sum(binary[1::2])) & 0xFFFFFFFF
|
||||||
|
struct.pack_into('>L', binary, 0, oneword)
|
||||||
|
|
||||||
|
|
||||||
def build(src):
|
def build(src):
|
||||||
raise dispatcher.WrongFormat
|
if not path.exists(path.join(src, 'Romfile')): raise dispatcher.WrongFormat
|
||||||
|
|
||||||
|
romfile = dispatcher.build_path(path.join(src, 'Romfile')).decode('utf8').split('\n')
|
||||||
|
|
||||||
|
rsrc_list = []
|
||||||
|
for l in romfile:
|
||||||
|
words = shlex.split(l, comments=True, posix=True)
|
||||||
|
|
||||||
|
thisdict = {}
|
||||||
|
for word in words:
|
||||||
|
a, sep, b = word.partition('=')
|
||||||
|
if sep == '=':
|
||||||
|
if a == 'type':
|
||||||
|
b = b.encode('mac_roman')
|
||||||
|
elif a in ('id', 'rom_size'):
|
||||||
|
b = ast.literal_eval(b)
|
||||||
|
|
||||||
|
thisdict[a] = b
|
||||||
|
|
||||||
|
if 'rom_size' in thisdict:
|
||||||
|
rom_size = thisdict['rom_size']
|
||||||
|
elif 'type' in thisdict:
|
||||||
|
rsrc_list.append(thisdict)
|
||||||
|
|
||||||
|
rom = bytearray(b'kc' * (rom_size // 2))
|
||||||
|
|
||||||
|
maincode = dispatcher.build_path(path.join(src, 'MainCode'))
|
||||||
|
rom[:len(maincode)] = maincode
|
||||||
|
|
||||||
|
try:
|
||||||
|
decldata = dispatcher.build_path(path.join(src, 'DeclData'))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if decldata: rom[-len(decldata):] = decldata
|
||||||
|
|
||||||
|
# now blat in the resources
|
||||||
|
free = len(maincode) + 16
|
||||||
|
prev_ent_ptr = 0
|
||||||
|
bogus_off = 0x5C
|
||||||
|
|
||||||
|
for r in rsrc_list:
|
||||||
|
data = dispatcher.build_path(path.join(src, r['src']))
|
||||||
|
|
||||||
|
data_ptr = lowlevel.pad(free + 16, ALIGN)
|
||||||
|
mm_ptr = data_ptr - 12
|
||||||
|
ent_ptr = lowlevel.pad(data_ptr + len(data), ALIGN)
|
||||||
|
|
||||||
|
mm = lowlevel.FakeMMHeader.pack(
|
||||||
|
MagicKurt=b'Kurt',
|
||||||
|
MagicC0A00000=0xC0A00000,
|
||||||
|
dataSizePlus12=len(data) + 12,
|
||||||
|
bogusOff=bogus_off,
|
||||||
|
)
|
||||||
|
rom[mm_ptr-4:mm_ptr-4+len(mm)] = mm
|
||||||
|
|
||||||
|
rom[data_ptr:data_ptr+len(data)] = data
|
||||||
|
|
||||||
|
combo = r.get('combo', 'AllCombos')
|
||||||
|
combo = REV_COMBO_FIELDS.get(combo, None)
|
||||||
|
if combo is None:
|
||||||
|
combo = ast.literal_eval(combo) << 56
|
||||||
|
|
||||||
|
ent = lowlevel.ResEntry.pack(
|
||||||
|
combo=combo,
|
||||||
|
offsetToNext=prev_ent_ptr,
|
||||||
|
offsetToData=data_ptr,
|
||||||
|
rsrcType=r['type'],
|
||||||
|
rsrcID=r['id'],
|
||||||
|
rsrcAttr=0x58,
|
||||||
|
rsrcName=r['name'].encode('mac_roman'),
|
||||||
|
)
|
||||||
|
# snip off pascal string padding
|
||||||
|
ent = ent[:0x18 + ent[0x17]]
|
||||||
|
|
||||||
|
rom[ent_ptr:ent_ptr+len(ent)] = ent
|
||||||
|
|
||||||
|
free = ent_ptr + len(ent)
|
||||||
|
prev_ent_ptr = ent_ptr
|
||||||
|
bogus_off += 8
|
||||||
|
|
||||||
|
head = lowlevel.ResHeader.pack(
|
||||||
|
offsetToFirst=prev_ent_ptr,
|
||||||
|
maxValidIndex=4,
|
||||||
|
comboFieldSize=8,
|
||||||
|
comboVersion=1,
|
||||||
|
headerSize=12,
|
||||||
|
)
|
||||||
|
rom[len(maincode):len(maincode)+len(head)] = head
|
||||||
|
|
||||||
|
# now set the size
|
||||||
|
fields = lowlevel.SuperMarioHeader.unpack_from(rom)._asdict()
|
||||||
|
fields['RomRsrc'] = len(maincode)
|
||||||
|
fields['RomSize'] = len(rom)
|
||||||
|
lowlevel.SuperMarioHeader.pack_into(rom, 0, **fields)
|
||||||
|
|
||||||
|
# now set the checksums!
|
||||||
|
checksum(rom)
|
||||||
|
|
||||||
|
return bytes(rom)
|
||||||
|
|
|
@ -50,6 +50,7 @@ def is_supermario(binary):
|
||||||
|
|
||||||
|
|
||||||
def extract_decldata(binary):
|
def extract_decldata(binary):
|
||||||
|
print(hex(binary.rfind(PAD) + len(PAD)))
|
||||||
return binary[binary.rfind(PAD) + len(PAD):]
|
return binary[binary.rfind(PAD) + len(PAD):]
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,63 +100,62 @@ def dump(binary, dest_dir):
|
||||||
|
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
|
||||||
header = SuperMarioHeader.unpack_from(binary)
|
with open(path.join(dest_dir, 'Romfile'), 'w') as f:
|
||||||
|
print(HEADER_COMMENT + '\n', file=f)
|
||||||
|
print('rom_size=%s\n' % hex(len(binary)), file=f)
|
||||||
|
|
||||||
main_code = clean_maincode(binary[:header.RomRsrc])
|
header = SuperMarioHeader.unpack_from(binary)
|
||||||
with open(path.join(dest_dir, 'MainCode'), 'wb') as f:
|
|
||||||
f.write(main_code)
|
|
||||||
|
|
||||||
decldata = extract_decldata(binary)
|
main_code = clean_maincode(binary[:header.RomRsrc])
|
||||||
with open(path.join(dest_dir, 'DeclData'), 'wb') as f:
|
dispatcher.dump(main_code, path.join(dest_dir, 'MainCode'))
|
||||||
f.write(decldata)
|
|
||||||
|
|
||||||
# now for the tricky bit: resources :(
|
decldata = extract_decldata(binary)
|
||||||
f = open(path.join(dest_dir, 'Rsrcfile'), 'w')
|
if decldata:
|
||||||
print(HEADER_COMMENT + '\n', file=f)
|
dispatcher.dump(decldata, path.join(dest_dir, 'DeclData'))
|
||||||
|
|
||||||
unavail_filenames = set(['', '.pef'])
|
# now for the tricky bit: resources :(
|
||||||
|
unavail_filenames = set(['', '.pef'])
|
||||||
|
|
||||||
for i, offset in enumerate(extract_resource_offsets(binary)):
|
for i, offset in enumerate(extract_resource_offsets(binary)):
|
||||||
rsrc_dir = path.join(dest_dir, 'Rsrc')
|
rsrc_dir = path.join(dest_dir, 'Rsrc')
|
||||||
os.makedirs(rsrc_dir, exist_ok=True)
|
os.makedirs(rsrc_dir, exist_ok=True)
|
||||||
|
|
||||||
entry = ResEntry.unpack_from(binary, offset)
|
entry = ResEntry.unpack_from(binary, offset)
|
||||||
mmhead = FakeMMHeader.unpack_from(binary, entry.offsetToData - FakeMMHeader.size)
|
mmhead = FakeMMHeader.unpack_from(binary, entry.offsetToData - FakeMMHeader.size)
|
||||||
|
|
||||||
# assert entry.
|
# assert entry.
|
||||||
assert mmhead.MagicKurt == b'Kurt'
|
assert mmhead.MagicKurt == b'Kurt'
|
||||||
assert mmhead.MagicC0A00000 == 0xC0A00000
|
assert mmhead.MagicC0A00000 == 0xC0A00000
|
||||||
|
|
||||||
data = binary[entry.offsetToData:][:mmhead.dataSizePlus12 - 12]
|
data = binary[entry.offsetToData:][:mmhead.dataSizePlus12 - 12]
|
||||||
report_combo_field = COMBO_FIELDS.get(entry.combo, '0b' + bin(entry.combo >> 56)[2:].zfill(8))
|
report_combo_field = COMBO_FIELDS.get(entry.combo, '0b' + bin(entry.combo >> 56)[2:].zfill(8))
|
||||||
|
|
||||||
# create a friendly ascii filename for the resource
|
# create a friendly ascii filename for the resource
|
||||||
filename = '%s_%d' % (sanitize_macroman(entry.rsrcType), entry.rsrcID)
|
filename = '%s_%d' % (sanitize_macroman(entry.rsrcType), entry.rsrcID)
|
||||||
if len(entry.rsrcName) > 0 and entry.rsrcName != b'Main': # uninformative artifact of rom build
|
if len(entry.rsrcName) > 0 and entry.rsrcName != b'Main': # uninformative artifact of rom build
|
||||||
filename += '_' + sanitize_macroman(entry.rsrcName)
|
filename += '_' + sanitize_macroman(entry.rsrcName)
|
||||||
if report_combo_field != 'AllCombos':
|
if report_combo_field != 'AllCombos':
|
||||||
filename += '_' + report_combo_field.replace('AppleTalk', 'AT')
|
filename += '_' + report_combo_field.replace('AppleTalk', 'AT')
|
||||||
filename = filename.strip('_')
|
filename = filename.strip('_')
|
||||||
while '__' in filename: filename = filename.replace('__', '_')
|
while '__' in filename: filename = filename.replace('__', '_')
|
||||||
if data.startswith(b'Joy!peff'): filename += '.pef'
|
if data.startswith(b'Joy!peff'): filename += '.pef'
|
||||||
while filename in unavail_filenames: filename = '_' + filename
|
while filename in unavail_filenames: filename = '_' + filename
|
||||||
|
|
||||||
unavail_filenames.add(filename)
|
unavail_filenames.add(filename)
|
||||||
|
|
||||||
with open(path.join(rsrc_dir, filename), 'wb') as f2:
|
with open(path.join(rsrc_dir, filename), 'wb') as f2:
|
||||||
f2.write(data)
|
f2.write(data)
|
||||||
|
|
||||||
filename = path.join('Rsrc', filename)
|
filename = path.join('Rsrc', filename)
|
||||||
|
|
||||||
# Now, just need to dream up a data format
|
# Now, just need to dream up a data format
|
||||||
report = ''
|
report = ''
|
||||||
report = ljustspc(report + 'type=' + quodec(entry.rsrcType), 12)
|
report = ljustspc(report + 'type=' + quodec(entry.rsrcType), 12)
|
||||||
report = ljustspc(report + 'id=' + str(entry.rsrcID), 24)
|
report = ljustspc(report + 'id=' + str(entry.rsrcID), 24)
|
||||||
report = ljustspc(report + 'name=' + quodec(entry.rsrcName), 48)
|
report = ljustspc(report + 'name=' + quodec(entry.rsrcName), 48)
|
||||||
report = ljustspc(report + 'src=' + shlex.quote(filename), 84)
|
report = ljustspc(report + 'src=' + shlex.quote(filename), 84)
|
||||||
if report_combo_field != 'AllCombos':
|
if report_combo_field != 'AllCombos':
|
||||||
report = ljustspc(report + 'combo=' + report_combo_field, 0)
|
report = ljustspc(report + 'combo=' + report_combo_field, 0)
|
||||||
report = report.rstrip()
|
report = report.rstrip()
|
||||||
|
|
||||||
print(report, file=f)
|
|
||||||
|
|
||||||
|
print(report, file=f)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user