#!/usr/bin/env python3 import os import re import subprocess def main(): fd = open(bdir() + '/pd.z64', 'wb+') # The retail ROM contains truncated duplicates of some segments. # For example, the real boot segment is at 0x1000 - 0x3050, but the tail end # of it is repeated at 0x2ea6c - 0x30a60. The truncated parts are not read # by the ROM; they are likely a side effect of Rare's linker copying things # around in the ROM. if os.environ['ROMID'] == 'ntsc-final': write_binary(fd, 0x2ea1c, get_boot()) write_binary(fd, 0x30a6c, get_lib()[:0x8df0]) else: write_binary(fd, 0x30a20, get_lib()[:0x52]) write_binary(fd, 0x2ea22, get_boot()) write_binary(fd, 0x30a72, get_lib()[:0x8df0]) write_binary(fd, 0x157120, get_unknown()) write_binary(fd, 0, get_header()) write_binary(fd, 0x40, get_rspboot()) write_binary(fd, 0x1000, get_boot()) write_binary(fd, 0x3050, get_lib()) write_binary(fd, 0x39850, get_gamedata()) write_binary(fd, 0x4e850, get_inflate()) write_binary(fd, 0x4fc40, get_gamezips()) write_binary(fd, 0x7d0a40, get_mpconfigs()) write_binary(fd, 0x7d1c20, get_mpstrings('E')) write_binary(fd, 0x7d5320, get_mpstrings('J')) write_binary(fd, 0x7d8a20, get_mpstrings('P')) write_binary(fd, 0x7dc120, get_mpstrings('G')) write_binary(fd, 0x7df820, get_mpstrings('F')) write_binary(fd, 0x7e2f20, get_mpstrings('S')) write_binary(fd, 0x7e6620, get_mpstrings('I')) write_binary(fd, 0x7f2388, get_fonts()) write_binary(fd, 0x80a250, get_sfxctl()) write_binary(fd, 0x839dd0, get_sfxtbl()) write_binary(fd, 0xcfbf30, get_seqctl()) write_binary(fd, 0xd05f90, get_seqtbl()) write_binary(fd, 0xe82000, get_midi()) write_files(fd) write_binary(fd, 0x1d5ca00, get_filenames()) write_binary(fd, 0x1d65f40, get_textures()) fd.close() def write_binary(fd, address, binary): fd.seek(address) fd.write(binary) def get_header(): binary = bytearray() binary.extend(b'\x80\x37\x12\x40') # Identifier binary.extend(b'\x00\x00\x00\x0f') # Clock rate binary.extend(b'\x80\x00\x10\x00') # Program counter binary.extend(b'\x00\x00\x14\x49') # Release address binary.extend(b'\x00\x00\x00\x00') # CRC 1 binary.extend(b'\x00\x00\x00\x00') # CRC 2 binary.extend(b'\x00\x00\x00\x00') binary.extend(b'\x00\x00\x00\x00') binary.extend(b'Perfect Dark ') binary.extend(b'\x00\x00\x00\x00') binary.extend(b'\x00\x00\x00') binary.extend(b'NPDE') binary.extend(b'\x01' if os.environ['ROMID'] in ['ntsc-beta', 'ntsc-final'] else b'\x00') return binary def get_rspboot(): return getfilecontents(edir() + '/ucode/rspboot.bin') def get_boot(): return getfilecontents(bdir() + '/ucode/boot.bin') def get_lib(): return zip(bdir() + '/ucode/lib.bin') def get_gamedata(): return zip(bdir() + '/ucode/gamedata.bin') def get_inflate(): return getfilecontents(bdir() + '/ucode/inflate.bin') def get_gamezips(): return getfilecontents(bdir() + '/ucode/gamezips.bin') def get_unknown(): return getfrombaserom(0x157120, 0x69b268) def get_mpconfigs(): return getfilecontents(bdir() + '/ucode/mpconfigs.bin') def get_mpstrings(lang): return getfilecontents(bdir() + '/ucode/mpstrings%s.bin' % lang) def get_fonts(): return getfrombaserom(0x7f2388, 0x17ec8) def get_sfxctl(): return getfilecontents(edir() + '/audio/sfx.ctl') def get_sfxtbl(): return getfilecontents(edir() + '/audio/sfx.tbl') def get_seqctl(): return getfilecontents(edir() + '/audio/music.ctl') def get_seqtbl(): return getfilecontents(edir() + '/audio/music.tbl') def get_midi(): return getfilecontents(edir() + '/audio/sequences.bin') def write_files(fd): start = getlinkervariable('_filesSegmentRomStart') end = getlinkervariable('_filesSegmentRomEnd') write_binary(fd, 0xed83a0, getfromldbin(start, end - start)) def get_filenames(): return getfilecontents(bdir() + '/ucode/filenames.bin') def get_textures(): return getfrombaserom(0x01d65f40, 0x29a0c0) def getfilecontents(filename): fd = open(filename, 'rb') binary = fd.read() fd.close() return binary def getfrombaserom(offset, len): fd = open('pd.%s.z64' % os.environ['ROMID'], 'rb') fd.seek(offset) binary = fd.read(len) fd.close() return binary def getfromldbin(offset, len): fd = open(bdir() + '/pd.bin', 'rb') fd.seek(offset) binary = fd.read(len) fd.close() return binary def zip(filename): return subprocess.check_output(['tools/rarezip', filename]) def bdir(): return 'build/%s' % os.environ['ROMID'] def edir(): return 'extracted/%s' % os.environ['ROMID'] def getlinkervariable(varname): if 'TOOLCHAIN' in os.environ: cmd = '%s-objdump' % os.environ['TOOLCHAIN'] else: cmd = 'mips64-elf-objdump' objdump = subprocess.check_output([cmd, bdir() + '/pd.elf', '-t']).decode('utf-8') matches = re.findall(r'^([0-9a-f]+) .*? %s$' % varname, objdump, re.MULTILINE) return int(matches[0], 16) main()