diff --git a/tbxi/powerpc_build.py b/tbxi/powerpc_build.py index 0b94a18..acf70d0 100644 --- a/tbxi/powerpc_build.py +++ b/tbxi/powerpc_build.py @@ -53,21 +53,20 @@ 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 + for line in dispatcher.build_path(src_path).decode('utf8').split('\n'): + 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 + 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 + 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 @@ -132,6 +131,8 @@ def checksum_image(binary, ofs): def build(src): if not path.exists(path.join(src, 'Configfile')) or path.exists(path.join(src, 'Configfile-1')): raise dispatcher.WrongFormat + print('powerpc', src) + cilist = [] for ciname in iter_configinfo_names(): try: @@ -143,8 +144,7 @@ def build(src): # 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()) + rom = bytearray(dispatcher.build_path(path.join(src, 'EverythingElse'))) except FileNotFoundError: rom = bytearray(0x400000) diff --git a/tbxi/supermario_build.py b/tbxi/supermario_build.py index 92c5275..4a46ba6 100644 --- a/tbxi/supermario_build.py +++ b/tbxi/supermario_build.py @@ -1,5 +1,125 @@ +import shlex +import ast +import struct +from os import path + +from . import lowlevel 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): - 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) diff --git a/tbxi/supermario_dump.py b/tbxi/supermario_dump.py index 3012f86..6249f62 100644 --- a/tbxi/supermario_dump.py +++ b/tbxi/supermario_dump.py @@ -50,6 +50,7 @@ def is_supermario(binary): def extract_decldata(binary): + print(hex(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) - 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]) - with open(path.join(dest_dir, 'MainCode'), 'wb') as f: - f.write(main_code) + header = SuperMarioHeader.unpack_from(binary) - decldata = extract_decldata(binary) - with open(path.join(dest_dir, 'DeclData'), 'wb') as f: - f.write(decldata) + main_code = clean_maincode(binary[:header.RomRsrc]) + dispatcher.dump(main_code, path.join(dest_dir, 'MainCode')) - # now for the tricky bit: resources :( - f = open(path.join(dest_dir, 'Rsrcfile'), 'w') - print(HEADER_COMMENT + '\n', file=f) + decldata = extract_decldata(binary) + if decldata: + 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)): - rsrc_dir = path.join(dest_dir, 'Rsrc') - os.makedirs(rsrc_dir, exist_ok=True) + for i, offset in enumerate(extract_resource_offsets(binary)): + rsrc_dir = path.join(dest_dir, 'Rsrc') + os.makedirs(rsrc_dir, exist_ok=True) - entry = ResEntry.unpack_from(binary, offset) - mmhead = FakeMMHeader.unpack_from(binary, entry.offsetToData - FakeMMHeader.size) + entry = ResEntry.unpack_from(binary, offset) + mmhead = FakeMMHeader.unpack_from(binary, entry.offsetToData - FakeMMHeader.size) - # assert entry. - assert mmhead.MagicKurt == b'Kurt' - assert mmhead.MagicC0A00000 == 0xC0A00000 - - data = binary[entry.offsetToData:][:mmhead.dataSizePlus12 - 12] - report_combo_field = COMBO_FIELDS.get(entry.combo, '0b' + bin(entry.combo >> 56)[2:].zfill(8)) + # assert entry. + assert mmhead.MagicKurt == b'Kurt' + assert mmhead.MagicC0A00000 == 0xC0A00000 + + data = binary[entry.offsetToData:][:mmhead.dataSizePlus12 - 12] + report_combo_field = COMBO_FIELDS.get(entry.combo, '0b' + bin(entry.combo >> 56)[2:].zfill(8)) - # create a friendly ascii filename for the resource - filename = '%s_%d' % (sanitize_macroman(entry.rsrcType), entry.rsrcID) - if len(entry.rsrcName) > 0 and entry.rsrcName != b'Main': # uninformative artifact of rom build - filename += '_' + sanitize_macroman(entry.rsrcName) - if report_combo_field != 'AllCombos': - filename += '_' + report_combo_field.replace('AppleTalk', 'AT') - filename = filename.strip('_') - while '__' in filename: filename = filename.replace('__', '_') - if data.startswith(b'Joy!peff'): filename += '.pef' - while filename in unavail_filenames: filename = '_' + filename + # create a friendly ascii filename for the resource + filename = '%s_%d' % (sanitize_macroman(entry.rsrcType), entry.rsrcID) + if len(entry.rsrcName) > 0 and entry.rsrcName != b'Main': # uninformative artifact of rom build + filename += '_' + sanitize_macroman(entry.rsrcName) + if report_combo_field != 'AllCombos': + filename += '_' + report_combo_field.replace('AppleTalk', 'AT') + filename = filename.strip('_') + while '__' in filename: filename = filename.replace('__', '_') + if data.startswith(b'Joy!peff'): filename += '.pef' + 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: - f2.write(data) + with open(path.join(rsrc_dir, filename), 'wb') as f2: + f2.write(data) - filename = path.join('Rsrc', filename) + filename = path.join('Rsrc', filename) - # Now, just need to dream up a data format - report = '' - report = ljustspc(report + 'type=' + quodec(entry.rsrcType), 12) - report = ljustspc(report + 'id=' + str(entry.rsrcID), 24) - report = ljustspc(report + 'name=' + quodec(entry.rsrcName), 48) - report = ljustspc(report + 'src=' + shlex.quote(filename), 84) - if report_combo_field != 'AllCombos': - report = ljustspc(report + 'combo=' + report_combo_field, 0) - report = report.rstrip() - - print(report, file=f) + # Now, just need to dream up a data format + report = '' + report = ljustspc(report + 'type=' + quodec(entry.rsrcType), 12) + report = ljustspc(report + 'id=' + str(entry.rsrcID), 24) + report = ljustspc(report + 'name=' + quodec(entry.rsrcName), 48) + report = ljustspc(report + 'src=' + shlex.quote(filename), 84) + if report_combo_field != 'AllCombos': + report = ljustspc(report + 'combo=' + report_combo_field, 0) + report = report.rstrip() + print(report, file=f)