Files

389 lines
12 KiB
Python

from Stream import *
import os
import struct
def list_as_hex(list):
return [("0x%08X" % value) for value in list]
def extend_sign_bit(value, numBits):
signBit = 1 << (numBits - 1)
return (value & (signBit - 1)) - (value & signBit)
# Relocation type definitions
R_INVALID = -1
R_PPC_NONE = 0
R_PPC_ADDR32 = 1
R_PPC_ADDR24 = 2
R_PPC_ADDR16 = 3
R_PPC_ADDR16_LO = 4
R_PPC_ADDR16_HI = 5
R_PPC_ADDR16_HA = 6
R_PPC_ADDR14 = 7
R_PPC_ADDR14_BRTAKEN = 8
R_PPC_ADDR14_BRNTAKEN = 9
R_PPC_REL24 = 10
R_PPC_REL14 = 11
R_DOLPHIN_NOP = 201
R_DOLPHIN_SECTION = 202
R_DOLPHIN_END = 203
class DolFile:
def __init__(self):
self.valid = False
def read(self, filename):
self.filename = filename
stream = InputStream(filename, BIG_ENDIAN)
# Read DOL Header
self.textSecOffsets = []
for textSecIdx in range(0, 7):
self.textSecOffsets.append( stream.read_long() )
self.dataSecOffsets = []
for dataSecIdx in range(0, 11):
self.dataSecOffsets.append( stream.read_long() )
self.textSecAddresses = []
for textSecIdx in range(0, 7):
self.textSecAddresses.append( stream.read_long() )
self.dataSecAddresses = []
for dataSecIdx in range(0, 11):
self.dataSecAddresses.append( stream.read_long() )
self.textSecSizes = []
for textSecIdx in range(0, 7):
self.textSecSizes.append( stream.read_long() )
self.dataSecSizes = []
for dataSecIdx in range(0, 11):
self.dataSecSizes.append( stream.read_long() )
self.bssAddress = stream.read_long()
self.bssSize = stream.read_long()
self.entryPoint = stream.read_long()
self.valid = True
# Fetch game build number
buildStrOffset = stream.data.find(b'Build v') + 7
buildStrEnd = stream.data.find(b' ', buildStrOffset)
self.buildVersionStr = stream.data[buildStrOffset:buildStrEnd].decode()
self.buildVersion = float(self.buildVersionStr)
def is_patched(self, bootstrapSectionIdx):
return self.textSecOffsets[bootstrapSectionIdx] != 0
def patch_rel24(self, buffer, offset, basePatchAddress, targetAddress):
instruction = struct.unpack(">I", buffer[offset:offset+4])[0] & ~0x3FFFFFC
relAddr = targetAddress - (basePatchAddress + offset)
instruction |= (relAddr & 0x3FFFFFC)
buffer[offset:offset+4] = struct.pack(">I", instruction)
def patch_hi_lo(self, buffer, offsetHi, offsetLo, targetAddress):
instrHi = struct.unpack(">I", buffer[offsetHi:offsetHi+4])[0] & 0xFFFF0000
instrLo = struct.unpack(">I", buffer[offsetLo:offsetLo+4])[0] & 0xFFFF0000
addrHi = (targetAddress & 0xFFFF0000)
addrLo = (targetAddress & 0x0000FFFF)
if addrLo >= 0x8000:
addrHi += 0x10000
instrHi |= (addrHi >> 16)
instrLo |= addrLo
buffer[offsetHi:offsetHi+4] = struct.pack(">I", instrHi)
buffer[offsetLo:offsetLo+4] = struct.pack(">I", instrLo)
def apply_patch(self, bootstrapHookTarget, bootstrapSectionIdx, bootstrapAddr, outputModuleName, patchFile, outFile):
# Find the addresses of functions we need to call from the patch code
addrDVDOpen = self.get_symbol("DVDOpen")
addrDVDReadAsyncPrio = self.get_symbol("DVDReadAsyncPrio")
addrDVDClose = self.get_symbol("DVDClose")
addrOSLink = self.get_symbol("OSLink")
addrNew = self.get_symbol("__nwa__FUlPCcPCc")
addrHook = self.get_symbol(bootstrapHookTarget)
failed = []
if addrDVDOpen == None: failed.append("DVDOpen")
if addrDVDReadAsyncPrio == None: failed.append("DVDReadAsyncPrio")
if addrDVDClose == None: failed.append("DVDClose")
if addrOSLink == None: failed.append("OSLink")
if addrNew == None: failed.append("__nwa__FUlPCcPCc")
if addrHook == None: failed.append(bootstrapHookTarget)
if len(failed) > 0:
print("Failed to apply DOL bootstrap patch! The following symbols are missing: %s" % ", ".join(failed))
return False
# Read patch data into memory
assert(self.valid)
patchFile = open(patchFile, "rb")
patchData = bytearray( patchFile.read() )
amtToPad = ((len(patchData) + 31) & ~31) - len(patchData)
patchData.extend(bytearray(amtToPad))
patchFile.close()
# Pick address to write the patch code to
address = bootstrapAddr & ~31
print("Adding patch to 0x%08X" % address)
# Relocate addresses in the patch code to account for the address it's placed at in memory
self.patch_hi_lo(patchData, 0x04, 0x08, address + 0xD0)
self.patch_hi_lo(patchData, 0x20, 0x24, address + 0xE0)
self.patch_hi_lo(patchData, 0x40, 0x44, address + 0xD1)
self.patch_hi_lo(patchData, 0x64, 0x68, address)
self.patch_hi_lo(patchData, 0x74, 0x78, address + 0xD0)
self.patch_hi_lo(patchData, 0x90, 0x94, address + 0xD9)
self.patch_rel24(patchData, 0x2C, address, addrDVDOpen)
self.patch_rel24(patchData, 0x4C, address, addrNew)
self.patch_rel24(patchData, 0x70, address, addrDVDReadAsyncPrio)
self.patch_rel24(patchData, 0x88, address, addrDVDClose)
self.patch_rel24(patchData, 0x9C, address, addrNew)
self.patch_rel24(patchData, 0xAC, address, addrOSLink)
self.patch_rel24(patchData, 0xCC, address, addrHook)
# Place rel filename in the buffer at the end of the patch and remove any unused space
relFilename = outputModuleName + ".rel" + '\0'
patchData[0xE0:] = relFilename.encode()
# Update DOL header for write
patchOffset = 0
for i in range(bootstrapSectionIdx-1, -1, -1):
if self.textSecOffsets[i] > 0 and self.textSecOffsets[i] > patchOffset:
patchOffset = self.textSecOffsets[i] + self.textSecSizes[i]
patchOffset = ((patchOffset + 31) & ~31)
patchSize = ((len(patchData) + 31) & ~31)
padSize = patchSize - len(patchData)
self.textSecOffsets[bootstrapSectionIdx] = patchOffset
self.textSecSizes[bootstrapSectionIdx] = patchSize
self.textSecAddresses[bootstrapSectionIdx] = address
srcStream = InputStream(self.filename, BIG_ENDIAN)
outStream = OutputStream(BIG_ENDIAN)
# Write DOL header
for textSecIdx in range(0, 7):
offset = self.textSecOffsets[textSecIdx]
if textSecIdx > bootstrapSectionIdx and offset != 0:
offset += patchSize
outStream.write_long(offset)
for dataSecIdx in range(0, 11):
offset = self.dataSecOffsets[dataSecIdx]
if offset != 0:
offset += patchSize
outStream.write_long(offset)
for textSecIdx in range(0, 7):
outStream.write_long(self.textSecAddresses[textSecIdx])
for dataSecIdx in range(0, 11):
outStream.write_long(self.dataSecAddresses[dataSecIdx])
for textSecIdx in range(0, 7):
outStream.write_long(self.textSecSizes[textSecIdx])
for dataSecIdx in range(0, 11):
outStream.write_long(self.dataSecSizes[dataSecIdx])
outStream.write_long(self.bssAddress)
outStream.write_long(self.bssSize)
outStream.write_long(self.entryPoint)
for padIdx in range(0, 7):
outStream.write_long(0)
# Write section data
foundAnyCallers = False
foundMultipleCallers = False
for textSecIdx in range(0, 7):
if textSecIdx == bootstrapSectionIdx:
outStream.write_bytes(patchData)
for i in range(0, padSize):
outStream.write_byte(0)
else:
srcStream.goto(self.textSecOffsets[textSecIdx])
data = srcStream.read_bytes(self.textSecSizes[textSecIdx])
if textSecIdx <= 1:
# Look for instructions calling the target function so we can
# patch it to call our bootstrap function instead.
patchedSection = False
baseAddr = self.textSecAddresses[textSecIdx]
buffer = bytearray(data)
for offset in range(0, len(buffer), 4):
instr = struct.unpack(">I", buffer[offset:offset+4])[0]
opcode = (instr >> 26) & 0x3F
if opcode is 18:
LI = instr & 0x3FFFFFC
AA = True if ((instr & 0x2) != 0) else False
branchAddr = (baseAddr + offset + LI) if not AA else LI
if branchAddr == addrHook:
if foundAnyCallers:
foundMultipleCallers = True
else:
# The bootstrap function starts at offset 0x10 within the patch data
self.patch_rel24(buffer, offset, baseAddr, address + 0x10)
foundAnyCallers = True
patchedSection = True
if patchedSection:
data = bytes(buffer)
outStream.write_bytes(data)
for dataSecIdx in range(0, 11):
srcStream.goto(self.dataSecOffsets[dataSecIdx])
data = srcStream.read_bytes(self.dataSecSizes[dataSecIdx])
outStream.write_bytes(data)
# Check if there was a problem with the caller patching
if not foundAnyCallers:
print("Patch failed; no callers of bootstrap hook found.")
return False
elif foundMultipleCallers:
print("Warning: Multiple callers of bootstrap hook were found. Only the first will be patched.");
# Save
outStream.save_file(outFile)
return True
def load_symbols(self, symbolDir):
# Load symbols
symbolFileName = "%s\\v%s.lst" % (symbolDir, self.buildVersionStr)
if not os.path.isfile(symbolFileName):
print("Failed to find a symbol list file for the provided build of the game (v%s)" % self.buildVersionStr)
return False
symbolFile = open("%s\\v%s.lst" % (symbolDir, self.buildVersionStr))
self.symbols = {}
while True:
line = symbolFile.readline()
if not line: break
split = line.strip().split(" ")
self.symbols[split[1]] = int(split[0], 0)
return True
def generate_patches(self, origSymName, newSymName):
# Read all instructions in all text sections and find branches to the given address.
# Only unconditional branch instructions supported for now. More later.
# Note: newSymbolName should be unmangled.
PPC_OPCODE_bx = 18
out = []
# Find the address of the symbol.
if origSymName in self.symbols:
origSymAddress = self.symbols[origSymName]
# Check for potential unmangled name, which can happen if extern "C" is used (line in SDK libraries)
else:
funcNameEnd = origSymName.find("__F")
if funcNameEnd != -1:
unmangledName = origSymName[0:funcNameEnd]
if unmangledName in self.symbols:
origSymAddress = self.symbols[unmangledName]
else:
print("*** Warning: Failed to resolve symbol '%s'. There will be no relocations generated for it. ***" % origSymName)
return out
stream = InputStream(self.filename)
origSymAddress = self.symbols[origSymName]
for textSecIdx in range(0, 7):
# Grab offset/address, check if valid
secOffset = self.textSecOffsets[textSecIdx]
if secOffset is 0: continue
secAddress = self.textSecAddresses[textSecIdx]
secSize = self.textSecSizes[textSecIdx]
secEnd = secOffset + secSize
stream.goto(secOffset)
# Parse all instructions in this code block
while stream.tell() < secEnd and not stream.eof():
address = secAddress + (stream.tell() - secOffset)
instruction = stream.read_long()
opcode = (instruction >> 26) & 0x3F
# bx
if opcode == PPC_OPCODE_bx:
LI = extend_sign_bit(instruction & 0x3FFFFFC, 24)
AA = (instruction >> 1) & 0x1
target = LI
if AA is 0: target = target + address
if target == origSymAddress:
patch = {}
patch['address'] = address
patch['type'] = R_PPC_REL24
patch['symbol'] = newSymName
out.append(patch)
for dataSecIdx in range(0, 11):
# Grab offset/address, check if valid
secOffset = self.dataSecOffsets[dataSecIdx]
if secOffset is 0: continue
secAddress = self.dataSecAddresses[dataSecIdx]
secSize = self.dataSecSizes[dataSecIdx]
secEnd = secOffset + secSize
stream.goto(secOffset)
# Look for 32-bit address references
while stream.tell() < secEnd and not stream.eof():
address = secAddress + (stream.tell() - secOffset)
value = stream.read_long()
if value == origSymAddress:
patch = {}
patch['address'] = address
patch['type'] = R_PPC_ADDR32
patch['symbol'] = newSymName
out.append(patch)
return out
def get_symbol(self, symName):
if symName in self.symbols:
return self.symbols[symName]
else:
return None
def get_section_index(self, address):
allSecAddresses = self.textSecAddresses + self.dataSecAddresses
allSecSizes = self.textSecSizes + self.dataSecSizes
for secIdx in range(0, 18):
secBegin = allSecAddresses[secIdx]
secEnd = secBegin + allSecSizes[secIdx]
if address >= secBegin and address < secEnd:
return secIdx
# If we're at this point then the address is invalid
assert(False)
return -1
def print_header_info(self):
print("Text Section Offsets: %s" % list_as_hex(self.textSecOffsets))
print("Data Section Offsets: %s" % list_as_hex(self.dataSecOffsets))
print("Text Section Addresses: %s" % list_as_hex(self.textSecAddresses))
print("Data Section Addresses: %s" % list_as_hex(self.dataSecAddresses))
print("BSS Address: 0x%08X" % self.bssAddress)
print("BSS Size: 0x%08X" % self.bssSize)
print("Entry Point: 0x%08X" % self.entryPoint)