mirror of
https://gitlab.com/ryandwyer/perfect-dark
synced 2026-09-08 19:22:27 -04:00
Build ROM from C source
* ROM is mismatching but functionally equivalent. * ROM does not shift, so any edits must use the same amount of bytecode. * Asset files such as stage setup and lang are not included yet (they are copied from the base ROM).
This commit is contained in:
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
def main():
|
||||
fd = open('build/ntsc-final/pd.z64', 'wb+')
|
||||
write_binary(fd, 0, get_header())
|
||||
write_binary(fd, 0x40, get_rspboot())
|
||||
write_binary(fd, 0x1000, get_boot())
|
||||
write_binary(fd, 0x3050, get_library())
|
||||
write_binary(fd, 0x39850, get_setup())
|
||||
write_binary(fd, 0x4e850, get_rarezip())
|
||||
write_binary(fd, 0x4fc40, get_gamezips())
|
||||
write_binary(fd, 0x157810, get_unknown())
|
||||
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_binary(fd, 0xed83a0, get_files())
|
||||
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')
|
||||
return binary
|
||||
|
||||
def get_rspboot():
|
||||
return getfilecontents('extracted/ntsc-final/ucode/rspboot.bin')
|
||||
|
||||
def get_boot():
|
||||
return getfilecontents('build/ntsc-final/ucode/boot.bin')
|
||||
|
||||
def get_library():
|
||||
return zip('build/ntsc-final/ucode/library.bin')
|
||||
|
||||
def get_setup():
|
||||
return zip('build/ntsc-final/ucode/setup.bin')
|
||||
|
||||
def get_rarezip():
|
||||
return getfilecontents('build/ntsc-final/ucode/rarezip.bin')
|
||||
|
||||
def get_gamezips():
|
||||
return getfilecontents('build/ntsc-final/ucode/gamezips.bin')
|
||||
|
||||
def get_unknown():
|
||||
return getfrombaserom(0x157810, 0x69ab78)
|
||||
|
||||
def get_fonts():
|
||||
return getfrombaserom(0x7f2388, 0x17ec8)
|
||||
|
||||
def get_sfxctl():
|
||||
return getfilecontents('extracted/ntsc-final/audio/sfx.ctl')
|
||||
|
||||
def get_sfxtbl():
|
||||
return getfilecontents('extracted/ntsc-final/audio/sfx.tbl')
|
||||
|
||||
def get_seqctl():
|
||||
return getfilecontents('extracted/ntsc-final/audio/music.ctl')
|
||||
|
||||
def get_seqtbl():
|
||||
return getfilecontents('extracted/ntsc-final/audio/music.tbl')
|
||||
|
||||
def get_midi():
|
||||
return getfilecontents('extracted/ntsc-final/audio/midi.bin')
|
||||
|
||||
def get_files():
|
||||
return getfrombaserom(0xed83a0, 0xe8d3a0)
|
||||
|
||||
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.ntsc-final.z64', 'rb')
|
||||
fd.seek(offset)
|
||||
binary = fd.read(len)
|
||||
fd.close()
|
||||
return binary
|
||||
|
||||
def zip(filename):
|
||||
return subprocess.check_output(['tools/rarezip', filename])
|
||||
|
||||
main()
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys;
|
||||
|
||||
class Tool:
|
||||
|
||||
def ROL(self, i, b):
|
||||
return ((i << b) | (i >> (32 - b))) & 0xffffffff
|
||||
|
||||
def R4(self, b):
|
||||
return b[0]*0x1000000 + b[1]*0x10000 + b[2]*0x100 + b[3]
|
||||
|
||||
def crc(self, f):
|
||||
seed = 0xdf26f436
|
||||
t1 = t2 = t3 = t4 = t5 = t6 = seed
|
||||
|
||||
f.seek(0x0710 + 0x40)
|
||||
lookup = f.read(0x100)
|
||||
|
||||
f.seek(0x1000)
|
||||
for i in range(0x1000, 0x101000, 4):
|
||||
d = self.R4(f.read(4))
|
||||
|
||||
if ((t6 + d) & 0xffffffff) < t6:
|
||||
t4 += 1
|
||||
t4 &= 0xffffffff
|
||||
|
||||
t6 += d
|
||||
t6 &= 0xffffffff
|
||||
|
||||
t3 ^= d
|
||||
|
||||
r = self.ROL(d, d & 0x1F)
|
||||
|
||||
t5 += r
|
||||
t5 &= 0xffffffff
|
||||
|
||||
if t2 > d:
|
||||
t2 ^= r
|
||||
else:
|
||||
t2 ^= t6 ^ d
|
||||
|
||||
o = i & 0xFF
|
||||
temp = self.R4(lookup[o:o + 4])
|
||||
t1 += temp ^ d
|
||||
t1 &= 0xffffffff
|
||||
|
||||
crc1 = t6 ^ t4 ^ t3
|
||||
crc2 = t5 ^ t2 ^ t1
|
||||
|
||||
return crc1 & 0xffffffff, crc2 & 0xffffffff
|
||||
|
||||
fd = open(sys.argv[1], 'rb')
|
||||
|
||||
# Read existing CRC
|
||||
fd.seek(0x10)
|
||||
old = [
|
||||
int.from_bytes(fd.read(4), 'big'),
|
||||
int.from_bytes(fd.read(4), 'big'),
|
||||
]
|
||||
|
||||
# Calculate new CRC
|
||||
tool = Tool()
|
||||
new = tool.crc(fd)
|
||||
fd.close()
|
||||
|
||||
if '--verbose' in sys.argv:
|
||||
print('Old CRCs: %08x %08x' % (old[0], old[1]))
|
||||
print('New CRCs: %08x %08x' % (new[0], new[1]))
|
||||
|
||||
if new != old and '--write' in sys.argv:
|
||||
fd = open(sys.argv[1], 'r+b')
|
||||
fd.seek(0x10)
|
||||
fd.write(new[0].to_bytes(4, 'big'))
|
||||
fd.write(new[1].to_bytes(4, 'big'))
|
||||
fd.close()
|
||||
-239
@@ -1,239 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys, zlib
|
||||
|
||||
class Injector:
|
||||
|
||||
vacancies = [
|
||||
(0x00ed83a0, 0x01d5ca00), # Normal files spot
|
||||
(0x0002ea70, 0x00039850), # Unused space 1
|
||||
(0x00157810, 0x001a15c0), # Unused space 2
|
||||
]
|
||||
|
||||
def rarezip(self, buffer):
|
||||
length = len(buffer)
|
||||
|
||||
header = bytes([0x11, 0x73])
|
||||
lendata = bytes([length >> 16])
|
||||
lendata += bytes([(length >> 8) & 0xff])
|
||||
lendata += bytes([length & 0xff])
|
||||
|
||||
obj = zlib.compressobj(level=9, wbits=-15)
|
||||
return header + lendata + obj.compress(buffer) + obj.flush()
|
||||
|
||||
def rareunzip(self, compressed):
|
||||
return zlib.decompress(compressed[5:], wbits=-15)
|
||||
|
||||
def load(self, romfile):
|
||||
fp = open(romfile, 'rb')
|
||||
rombuffer = fp.read()
|
||||
fp.close()
|
||||
|
||||
self.rompart1 = rombuffer[0:0x2ea70]
|
||||
self.rompart2 = rombuffer[0x0004e850:0x00157810]
|
||||
self.rompart3 = rombuffer[0x001a15c0:0x00ed83a0]
|
||||
self.rompart4 = rombuffer[0x01d5ca00:]
|
||||
|
||||
fp = open('build/ntsc-final/ucode/setup.bin', 'rb')
|
||||
setup = fp.read()
|
||||
fp.close()
|
||||
|
||||
self.globaltop = setup[0:0x28080]
|
||||
self.globalbot = setup[0x2a000:]
|
||||
|
||||
self.files = []
|
||||
i = 0
|
||||
|
||||
while i <= 0x7dd:
|
||||
romnameaddr = int.from_bytes(rombuffer[0x01d5ca00 + i * 4:0x01d5ca00 + i * 4 + 4], 'big') + 0x01d5ca00
|
||||
romdataaddr = int.from_bytes(setup[0x28080 + i * 4:0x28080 + i * 4 + 4], 'big')
|
||||
|
||||
name = ''
|
||||
while rombuffer[romnameaddr] != 0:
|
||||
name = name + chr(rombuffer[romnameaddr])
|
||||
romnameaddr += 1
|
||||
|
||||
self.files.append({
|
||||
'name': name,
|
||||
'romaddr': romdataaddr,
|
||||
'data': None,
|
||||
})
|
||||
i += 1
|
||||
|
||||
self.files.sort(key=lambda file: file['romaddr'])
|
||||
|
||||
for index, file in enumerate(self.files):
|
||||
start = file['romaddr']
|
||||
end = self.files[index + 1]['romaddr'] if index < 0x7dd else 0x01d5ca00
|
||||
file['data'] = rombuffer[start:end]
|
||||
|
||||
def transform(self):
|
||||
# Replace file contents
|
||||
for file in self.files:
|
||||
if self.isFilePointless(file['name']):
|
||||
file['data'] = None
|
||||
continue
|
||||
|
||||
# Zipped file
|
||||
try:
|
||||
fp = open('build/ntsc-final/files/%s' % file['name'], 'rb')
|
||||
contents = fp.read()
|
||||
fp.close()
|
||||
except:
|
||||
continue
|
||||
|
||||
file['data'] = self.align(contents)
|
||||
|
||||
# Calculate new ROM addresses
|
||||
vacancyid = 0
|
||||
romaddr = self.vacancies[0][0]
|
||||
vacend = self.vacancies[0][1]
|
||||
|
||||
for file in self.files:
|
||||
if file['name'] == '':
|
||||
continue
|
||||
|
||||
filelen = len(file['data'])
|
||||
available = vacend - romaddr
|
||||
|
||||
if filelen > available:
|
||||
vacancyid += 1
|
||||
romaddr = self.vacancies[vacancyid][0]
|
||||
vacend = self.vacancies[vacancyid][1]
|
||||
available = vacend - romaddr
|
||||
|
||||
file['romaddr'] = romaddr
|
||||
romaddr += filelen
|
||||
|
||||
def isFilePointless(self, name):
|
||||
return False
|
||||
|
||||
def align(self, buffer):
|
||||
length = len(buffer)
|
||||
|
||||
if length % 0x10 == 0:
|
||||
return buffer
|
||||
|
||||
over = length % 0x10
|
||||
pad = 0x10 - over
|
||||
|
||||
buffer += (0).to_bytes(1, 'big') * pad
|
||||
|
||||
return buffer
|
||||
|
||||
def compile(self):
|
||||
buffer = self.rompart1
|
||||
assert(len(buffer) == 0x2ea70)
|
||||
buffer += self.compileVacancy(1)
|
||||
assert(len(buffer) == 0x39850)
|
||||
buffer += self.compileGlobals()
|
||||
assert(len(buffer) == 0x4e850)
|
||||
buffer += self.rompart2
|
||||
assert(len(buffer) == 0x157810)
|
||||
buffer += self.compileVacancy(2)
|
||||
assert(len(buffer) == 0x1a15c0)
|
||||
buffer += self.rompart3
|
||||
assert(len(buffer) == 0xed83a0)
|
||||
buffer += self.compileVacancy(0)
|
||||
assert(len(buffer) == 0x01d5ca00)
|
||||
buffer += self.rompart4
|
||||
assert(len(buffer) == 0x2000000)
|
||||
return buffer
|
||||
|
||||
def compileGlobals(self):
|
||||
buffer = self.globaltop
|
||||
|
||||
for file in self.files:
|
||||
buffer += file['romaddr'].to_bytes(4, 'big')
|
||||
|
||||
buffer += (0x01d5ca00).to_bytes(4, 'big')
|
||||
buffer += (0).to_bytes(4, 'big')
|
||||
buffer += self.globalbot
|
||||
|
||||
buffer = self.rarezip(buffer)
|
||||
|
||||
available = 0x15000 - len(buffer)
|
||||
buffer += (0).to_bytes(1, 'big') * available
|
||||
return buffer
|
||||
|
||||
def compileVacancy(self, vacid):
|
||||
buffer = bytes()
|
||||
vacstart = self.vacancies[vacid][0]
|
||||
vacend = self.vacancies[vacid][1]
|
||||
|
||||
for file in self.files:
|
||||
if file['romaddr'] >= vacstart and file['romaddr'] < vacend:
|
||||
buffer += file['data']
|
||||
|
||||
available = vacend - vacstart - len(buffer)
|
||||
buffer += (0).to_bytes(1, 'big') * available
|
||||
|
||||
return buffer
|
||||
|
||||
def ROL(self, i, b):
|
||||
return ((i << b) | (i >> (32 - b))) & 0xffffffff
|
||||
|
||||
def R4(self, b):
|
||||
return b[0]*0x1000000 + b[1]*0x10000 + b[2]*0x100 + b[3]
|
||||
|
||||
def crc(self, f):
|
||||
seed = 0xdf26f436
|
||||
t1 = t2 = t3 = t4 = t5 = t6 = seed
|
||||
|
||||
f.seek(0x0710 + 0x40)
|
||||
lookup = f.read(0x100)
|
||||
|
||||
f.seek(0x1000)
|
||||
for i in range(0x1000, 0x101000, 4):
|
||||
d = self.R4(f.read(4))
|
||||
|
||||
if ((t6 + d) & 0xffffffff) < t6:
|
||||
t4 += 1
|
||||
t4 &= 0xffffffff
|
||||
|
||||
t6 += d
|
||||
t6 &= 0xffffffff
|
||||
|
||||
t3 ^= d
|
||||
|
||||
r = self.ROL(d, d & 0x1F)
|
||||
|
||||
t5 += r
|
||||
t5 &= 0xffffffff
|
||||
|
||||
if t2 > d:
|
||||
t2 ^= r
|
||||
else:
|
||||
t2 ^= t6 ^ d
|
||||
|
||||
o = i & 0xFF
|
||||
temp = self.R4(lookup[o:o + 4])
|
||||
t1 += temp ^ d
|
||||
t1 &= 0xffffffff
|
||||
|
||||
crc1 = t6 ^ t4 ^ t3
|
||||
crc2 = t5 ^ t2 ^ t1
|
||||
|
||||
return crc1 & 0xffffffff, crc2 & 0xffffffff
|
||||
|
||||
injector = Injector()
|
||||
injector.load(sys.argv[1])
|
||||
injector.transform()
|
||||
|
||||
filename = 'build/ntsc-final/pd.z64'
|
||||
|
||||
fp = open(filename, 'wb')
|
||||
fp.write(injector.compile())
|
||||
fp.close()
|
||||
|
||||
fp = open(filename, 'rb')
|
||||
crcs = injector.crc(fp)
|
||||
fp.seek(0)
|
||||
buffer = fp.read()
|
||||
fp.close()
|
||||
|
||||
fp = open(filename, 'r+b')
|
||||
fp.seek(0x10)
|
||||
fp.write(crcs[0].to_bytes(4, 'big'))
|
||||
fp.write(crcs[1].to_bytes(4, 'big'))
|
||||
fp.close()
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
"""
|
||||
mkgamezips - Creates the ucode/gamezips.bin from ucode/game.bin
|
||||
|
||||
game.bin is the compiled game code from ld. This game code is split into
|
||||
4KB chunks. Each chunk is individually zipped.
|
||||
|
||||
The format of the gamezips binary is:
|
||||
* Array of offsets to each zip, where each offset is 4 bytes and relative to the
|
||||
start of the gamezips segment.
|
||||
* After the array of offsets comes the data it points to. Each data consists of:
|
||||
* 2 bytes of probable garbage data (set to 0x0000 by this script)
|
||||
* Zip data (starting with 0x1173001000)
|
||||
* Optional single byte to align it to the next 2 byte boundary. The added
|
||||
byte is probable garbage data (set to 0x00 by this script).
|
||||
"""
|
||||
def main():
|
||||
zips = get_zips()
|
||||
|
||||
fd = open('build/ntsc-final/ucode/gamezips.bin', 'wb')
|
||||
pos = len(zips) * 4 + 4
|
||||
|
||||
# Write pointer array
|
||||
for zip in zips:
|
||||
fd.write(pos.to_bytes(4, byteorder='big'))
|
||||
pos += 2 + len(zip)
|
||||
if pos % 2 == 1:
|
||||
pos += 1
|
||||
|
||||
# Last pointer points to end
|
||||
fd.write(pos.to_bytes(4, byteorder='big'))
|
||||
|
||||
# Write data
|
||||
for index, zip in enumerate(zips):
|
||||
if pos % 2 == 1:
|
||||
fd.write(b'\x00')
|
||||
pos += 1
|
||||
|
||||
fd.write(b'\x00\x00')
|
||||
fd.write(zip)
|
||||
pos += len(zip)
|
||||
|
||||
fd.close()
|
||||
|
||||
def get_filecontents(filename):
|
||||
fd = open(filename, 'rb')
|
||||
binary = fd.read()
|
||||
fd.close()
|
||||
return binary
|
||||
|
||||
def get_zips():
|
||||
binary = get_filecontents('build/ntsc-final/ucode/game.bin')
|
||||
parts = [binary[i:i+0x1000] for i in range(0, len(binary), 0x1000)]
|
||||
return [zip(part) for part in parts]
|
||||
|
||||
def zip(binary):
|
||||
fd = open('build/part.bin', 'wb')
|
||||
fd.write(binary)
|
||||
fd.close()
|
||||
|
||||
zipped = subprocess.check_output(['tools/rarezip', 'build/part.bin'])
|
||||
os.remove('build/part.bin')
|
||||
return zipped
|
||||
|
||||
main()
|
||||
+2
-18
@@ -2,22 +2,6 @@
|
||||
|
||||
size=$(stat --format="%s" $1)
|
||||
|
||||
printf "0: 1173 %.6x" $size | xxd -r -g0 > $1.tmp
|
||||
|
||||
cat $1 | tools/gzip --no-name --best | head --bytes=-8 | tail --bytes=+11 >> $1.tmp
|
||||
|
||||
# Pad to 0x10 boundary
|
||||
compsize=$(stat --format="%s" $1.tmp)
|
||||
over=$((compsize % 0x10))
|
||||
|
||||
if [ "$over" -gt 0 ]; then
|
||||
while [ "$over" -lt 16 ]; do
|
||||
echo -ne \\x00 >> $1.tmp
|
||||
over=$((over + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
cat $1.tmp
|
||||
|
||||
rm -f $1.tmp
|
||||
printf "0: 1173 %.6x" $size | xxd -r -g0
|
||||
|
||||
cat $1 | tools/gzip --no-name --best | head --bytes=-8 | tail --bytes=+11
|
||||
|
||||
Reference in New Issue
Block a user