mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-12-21 15:29:26 +00:00
93c5d270cd
Most TNT-era ROMs use this feature of RomLayout. Heuristically detect, report and rebuild these ROMs.
157 lines
4.4 KiB
Python
157 lines
4.4 KiB
Python
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):
|
|
if not path.exists(path.join(src, 'Romfile')): raise dispatcher.WrongFormat
|
|
|
|
romfile = dispatcher.build(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', 'offset'):
|
|
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))
|
|
free_map = bytearray(b'X' * (rom_size // ALIGN))
|
|
|
|
def rom_insert(offset, binary, letter=' '):
|
|
letter = letter.encode('ascii')
|
|
|
|
if offset + len(binary) > len(rom):
|
|
raise IndexError('ROM too small to insert %r at %s' % (letter, hex(offset)))
|
|
rom[offset:offset+len(binary)] = binary
|
|
|
|
# Populate the informational array of letters
|
|
# Only capitals can be overwritten
|
|
start = offset // ALIGN
|
|
stop = (offset + len(binary) - 1) // ALIGN + 1
|
|
|
|
for i in range(start, stop):
|
|
if free_map[i] != 20:
|
|
if free_map[i:i+1].upper() != free_map[i:i+1]:
|
|
raise ValueError('Tried to insert %r over %r at %s' % (letter, free_map[i], hex(offset)))
|
|
|
|
free_map[i:i+1] = letter
|
|
|
|
def find_free(length):
|
|
length = (length + ALIGN - 1) // ALIGN
|
|
return free_map.index(b'X' * length) * ALIGN
|
|
|
|
maincode = dispatcher.build(path.join(src, 'MainCode'))
|
|
rom_insert(0, maincode, 'm')
|
|
|
|
head_ptr = find_free(16)
|
|
rom_insert(head_ptr, b'fake header', 'H')
|
|
|
|
try:
|
|
decldata = dispatcher.build(path.join(src, 'DeclData'))
|
|
except FileNotFoundError:
|
|
pass
|
|
else:
|
|
rom_insert(len(rom) - len(decldata), decldata, 'd')
|
|
|
|
# now blat in the resources
|
|
ent_ptr = 0
|
|
bogus_off = 0x5C
|
|
|
|
for r in rsrc_list:
|
|
data = dispatcher.build(path.join(src, r['src']))
|
|
|
|
# First place the data, including the fake MemMgr header
|
|
if 'offset' in r:
|
|
ofs = r['offset']
|
|
else:
|
|
ofs = find_free(16 + len(data))
|
|
|
|
mm_ptr = ofs + 4
|
|
data_ptr = ofs + 16
|
|
|
|
# And insert that
|
|
mm = lowlevel.FakeMMHeader.pack(
|
|
MagicKurt=b'Kurt',
|
|
MagicC0A00000=0xC0A00000,
|
|
dataSizePlus12=len(data) + 12,
|
|
bogusOff=bogus_off,
|
|
)
|
|
rom_insert(mm_ptr - 4, mm + data, 'r')
|
|
|
|
# Calculate the independently-placed entry struct
|
|
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=ent_ptr, # this is the previous one
|
|
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]]
|
|
|
|
# Place the entry struct
|
|
ent_ptr = find_free(len(ent))
|
|
rom_insert(ent_ptr, ent, 'e')
|
|
|
|
bogus_off += 8
|
|
|
|
head = lowlevel.ResHeader.pack(
|
|
offsetToFirst=ent_ptr,
|
|
maxValidIndex=4,
|
|
comboFieldSize=8,
|
|
comboVersion=1,
|
|
headerSize=12,
|
|
)
|
|
rom_insert(head_ptr, head, 'h')
|
|
|
|
# now set the size
|
|
fields = lowlevel.SuperMarioHeader.unpack_from(rom)._asdict()
|
|
fields['RomRsrc'] = head_ptr
|
|
fields['RomSize'] = len(rom)
|
|
lowlevel.SuperMarioHeader.pack_into(rom, 0, **fields)
|
|
|
|
# now set the checksums!
|
|
checksum(rom)
|
|
|
|
return bytes(rom)
|