perfect_dark/tools/packrom

153 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import re
import subprocess
import sys
"""
packrom - performs code compression, writing of garbage data (required for a
matching ROM), ROM truncation to 32MB, and filling the tail end of the ROM with
0xff bytes.
Usage:
packrom <rom>
"""
def zip(binary):
filename = bdir() + '/tmp.bin';
fd = open(filename, 'wb')
fd.write(binary)
fd.close()
zipped = subprocess.check_output(['tools/rarezip', filename])
os.remove(filename)
return zipped
def bdir():
return 'build/%s' % os.environ['ROMID']
def edir():
return 'extracted/%s' % os.environ['ROMID']
def get_start(locations, segname):
return next(filter(lambda l: l['name'] == segname, locations))['addr']
def get_end(locations, start):
best = 0xffffffff
for location in locations:
if location['addr'] > start and location['addr'] < best:
best = location['addr']
return best;
def attempt(fd, locations, segname, payload, constname):
# Get location to write to
start = get_start(locations, segname + 'zip')
end = get_end(locations, start)
# Check it'll fit
allocation = end - start
if len(payload) > allocation:
print('The %s segment is too big after compression to fit the allocation of 0x%x. In ld/pd.ld, increase the value of %s to 0x%x or higher.' % (
segname, allocation, constname, len(payload)
))
exit(1)
# Write it
fd.seek(start)
fd.write(payload)
def get_segment(fd, locations, segname):
start = get_start(locations, segname)
end = get_end(locations, start)
fd.seek(start)
return fd.read(end - start)
# lib is compressed from offset 0x2000 onwards
def pack_lib(fd, locations):
lib = get_segment(fd, locations, 'lib')
zipped = lib[0:0x2000] + zip(lib[0x2000:])
attempt(fd, locations, 'lib', zipped, 'ROMALLOCATION_LIB')
def pack_data(fd, locations):
data = get_segment(fd, locations, 'data')
zipped = zip(data)
attempt(fd, locations, 'data', zipped, 'ROMALLOCATION_DATA')
def pack_game(fd, locations):
fd2 = open(bdir() + '/segments/gamezips.bin', 'rb')
zips = fd2.read()
fd2.close()
attempt(fd, locations, 'game', zips, 'ROMALLOCATION_GAME')
def get_locations():
fd = open(bdir() + '/pd.map', 'r')
ldmap = fd.read()
fd.close()
matches = re.findall(r'^\.(\S+)\s+0x[0-9a-f]+\s+0x[0-9a-f]+\s+load address\s+0x([0-9a-f]+)', ldmap, re.MULTILINE)
def make_numeric(match):
return {'addr': int(match[1], 16), 'name': match[0]}
return list(map(make_numeric, matches))
def write_garbage_part(fd, addr, filename):
fd2 = open(edir() + '/' + filename, 'rb')
binary = fd2.read()
fd2.close()
fd.seek(addr)
fd.write(binary)
def write_garbage(fd):
if os.environ['ROMID'] == 'pal-final':
write_garbage_part(fd, 0x2eb21, 'garbage1.bin')
write_garbage_part(fd, 0x158038, 'garbage2.bin')
elif os.environ['ROMID'] == 'ntsc-final':
write_garbage_part(fd, 0x2ea6c, 'garbage1.bin')
write_garbage_part(fd, 0x157800, 'garbage2.bin')
else:
write_garbage_part(fd, 0x2ea22, 'garbage1.bin')
write_garbage_part(fd, 0x1574a0, 'garbage2.bin')
def fill_tail(fd):
fd2 = open(bdir() + '/pd.map', 'r')
ldmap = fd2.read()
fd2.close()
match = re.findall(r'^\s*0x([0-9a-f]+)\s+_accessingpakSegmentRomEnd', ldmap, re.MULTILINE)
pos = int(match[0], 16)
fd.seek(pos)
while pos < 1024 * 1024 * 32:
fd.write(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
pos += 0x10
def main():
locations = get_locations()
fd = open(sys.argv[1], 'rb+')
write_garbage(fd)
pack_lib(fd, locations)
pack_data(fd, locations)
pack_game(fd, locations)
fill_tail(fd)
# Truncate to 32MB
fd.seek(0)
fd.truncate(1024 * 1024 * 32)
fd.close()
main()