mirror of
https://github.com/AxioDL/PrimeAPI.git
synced 2026-03-30 11:38:46 -07:00
273 lines
8.1 KiB
Python
273 lines
8.1 KiB
Python
from Stream import *
|
|
import os
|
|
|
|
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):
|
|
return self.textSecOffsets[2] != 0
|
|
|
|
def apply_patch(self, patchFile, outFile):
|
|
# todo - make this more flexible so it can work with multiple versions of the game...
|
|
assert(self.valid)
|
|
patchFile = open(patchFile, "rb")
|
|
patchData = bytearray( patchFile.read() )
|
|
amtToPad = ((len(patchData) + 31) & ~31) - len(patchData)
|
|
patchData.extend(bytearray(amtToPad))
|
|
patchFile.close()
|
|
|
|
self.textSecOffsets[2] = self.textSecOffsets[1] + self.textSecSizes[1]
|
|
self.textSecSizes[2] = len(patchData)
|
|
self.textSecAddresses[2] = 0x80001800
|
|
|
|
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 > 2 and offset != 0:
|
|
offset += len(patchData)
|
|
|
|
outStream.write_long(offset)
|
|
|
|
for dataSecIdx in range(0, 11):
|
|
offset = self.dataSecOffsets[dataSecIdx]
|
|
|
|
if offset != 0:
|
|
offset += len(patchData)
|
|
|
|
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
|
|
for textSecIdx in range(0, 7):
|
|
if textSecIdx == 2:
|
|
outStream.write_bytes(patchData)
|
|
else:
|
|
srcStream.goto(self.textSecOffsets[textSecIdx])
|
|
data = srcStream.read_bytes(self.textSecSizes[textSecIdx])
|
|
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)
|
|
|
|
# Patch one more instruction to call LinkCustomCode(), then save
|
|
outStream.goto(0x1D54)
|
|
outStream.write_long(0x4BFFCA1D)
|
|
outStream.save_file(outFile)
|
|
|
|
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) |