Dynamic-Pokemon-Expansion/scripts/insert.py
2019-08-25 03:14:34 -04:00

543 lines
19 KiB
Python

#!/usr/bin/env python3
import os
import subprocess
import shutil
import sys
from datetime import datetime
import _io
OFFSET_TO_PUT = 0x1800000
SOURCE_ROM = "BPRE0.gba"
ROM_NAME = "test.gba"
if sys.platform.startswith('win'):
PathVar = os.environ.get('Path')
Paths = PathVar.split(';')
PATH = ''
for candidatePath in Paths:
if 'devkitARM' in candidatePath:
PATH = candidatePath
break
if PATH == '':
PATH = 'C://devkitPro//devkitARM//bin'
if os.path.isdir(PATH) is False:
print('Devkit not found.')
sys.exit(1)
PREFIX = 'arm-none-eabi-'
OBJDUMP = os.path.join(PATH, PREFIX + 'objdump')
NM = os.path.join(PATH, PREFIX + 'nm')
AS = os.path.join(PATH, PREFIX + 'as')
else: # Linux, OSX, etc.
PREFIX = 'arm-none-eabi-'
OBJDUMP = (PREFIX + 'objdump')
NM = (PREFIX + 'nm')
AS = (PREFIX + 'as')
OUTPUT = 'build/output.bin'
BYTE_REPLACEMENT = 'bytereplacement'
HOOKS = 'hooks'
REPOINTS = 'repoints'
GENERATED_REPOINTS = 'generatedrepoints'
REPOINT_ALL = 'repointall'
ROUTINE_POINTERS = 'routinepointers'
FUNCTION_REWRITES = 'functionrewrites'
SPECIAL_INSERTS = 'special_inserts.asm'
SPECIAL_INSERTS_OUT = 'build/special_inserts.bin'
def ExtractPointer(byteList: [bytes]):
pointer = 0
for a in range(len(byteList)):
pointer += (int(byteList[a])) << (8 * a)
return pointer
def GetTextSection() -> int:
try:
# Dump sections
out = subprocess.check_output([OBJDUMP, '-t', 'build/linked.o'])
lines = out.decode().split('\n')
# Find text section
text = filter(lambda x: x.strip().endswith('.text'), lines)
section = (list(text))[0]
# Get the offset
offset = int(section.split(' ')[0], 16)
return offset
except:
print("Error: The insertion process could not be completed.\n"
+ "The linker symbol file was not found.")
sys.exit(1)
def GetSymbols(subtract=0) -> {str: int}:
out = subprocess.check_output([NM, 'build/linked.o'])
lines = out.decode().split('\n')
ret = {}
for line in lines:
parts = line.strip().split()
if len(parts) < 3:
continue
if parts[1].lower() not in {'t', 'd'}:
continue
offset = int(parts[0], 16)
ret[parts[2]] = offset - subtract
return ret
def Hook(rom: _io.BufferedReader, space: int, hookAt: int, register=0):
# Align 2
if hookAt & 1:
hookAt -= 1
rom.seek(hookAt)
register &= 7
if hookAt % 4:
data = bytes([0x01, 0x48 | register, 0x00 | (register << 3), 0x47, 0x0, 0x0])
else:
data = bytes([0x00, 0x48 | register, 0x00 | (register << 3), 0x47])
space += 0x08000001
data += (space.to_bytes(4, 'little'))
rom.write(bytes(data))
def FunctionWrap(rom: _io.BufferedReader, space: int, hookAt: int, numParams: int, isReturning: int):
# Align 2
if hookAt & 1:
hookAt -= 1
rom.seek(hookAt)
numParams = numParams - 1
if numParams < 4:
data = bytes([0x10, 0xB5, 0x3, 0x4C, 0x0, 0xF0, 0x3, 0xF8, 0x10, 0xBC,
(isReturning + 1), 0xBC, (isReturning << 3), 0x47, 0x20, 0x47])
else:
k = numParams - 3
data = bytes([0x10, 0xB5, 0x82, 0xB0])
for i in range(k + 2):
data += bytes([i + 2, 0x9C, i, 0x94])
data += bytes([0x0, 0x9C, numParams - 1, 0x94, 0x1, 0x9C, numParams, 0x94, 0x2, 0xB0, k + 8, 0x4C,
0x0, 0xF0, (k << 1) + 13, 0xF8, 0x82, 0xB0, numParams, 0x9C, 0x1, 0x94, numParams - 1,
0x9C, 0x0, 0x94])
for i in reversed(range(k + 2)):
data += bytes([i, 0x9C, i+2, 0x94])
data += bytes([0x2, 0xB0, 0x10, 0xBC, isReturning + 1, 0xBC, isReturning << 3, 0x47, 0x20, 0x47])
space += 0x08000001
data += (space.to_bytes(4, 'little'))
rom.write(bytes(data))
def Repoint(rom: _io.BufferedReader, space: int, repointAt: int, slideFactor=0):
rom.seek(repointAt)
space += (0x08000000 + slideFactor)
data = (space.to_bytes(4, 'little'))
rom.write(bytes(data))
# These offsets contain the word 0x8900000 - the attack data from
# Mr. DS's rombase. In order to maintain as much compatibility as
# possible, the data at these offsets is never modified.
IGNORED_OFFSETS = [0x3986C0, 0x3986EC, 0xDABDF0]
def RealRepoint(rom: _io.BufferedReader, offsetTuples: [(int, int, str)]):
pointerList = []
pointerDict = {}
for tup in offsetTuples: # Format is (Double Pointer, New Pointer, Symbol)
offset = tup[0]
rom.seek(offset)
pointer = ExtractPointer(rom.read(4))
pointerList.append(pointer)
pointerDict[pointer] = (tup[1] + 0x08000000, tup[2])
offset = 0
offsetList = []
while offset < 0xFFFFFD:
if offset in IGNORED_OFFSETS:
offset += 4
continue
rom.seek(offset)
word = ExtractPointer(rom.read(4))
rom.seek(offset)
for pointer in pointerList:
if word == pointer:
offsetList.append((offset, pointerDict[pointer][1]))
rom.write(bytes(pointerDict[pointer][0].to_bytes(4, 'little')))
break
offset += 4
return offsetList
def ReplaceBytes(rom: _io.BufferedReader, offset: int, data: str):
ar = offset
words = data.split()
for i in range(0, len(words)):
rom.seek(ar)
intByte = int(words[i], 16)
rom.write(bytes(intByte.to_bytes(1, 'big')))
ar += 1
def TryProcessFileInclusion(line: str, definesDict: dict) -> bool:
if line.startswith('#include "'):
try:
path = line.split('"')[1].strip()
with open(path, 'r') as file:
for line in file:
if line.startswith('#define '):
try:
lineList = line.strip().split()
title = lineList[1]
if len(lineList) == 2 or lineList[2].startswith('//') or lineList[2].startswith('/*'):
define = True
else:
define = lineList[2]
definesDict[title] = define
except IndexError:
print('Error reading define on line"' + line.strip() + '" in file "' + path + '".')
except Exception as e:
print('Error including file on line "' + line.strip() + '".')
print(e)
return True # Inclusion line; don't read otherwise
return False
def TryProcessConditionalCompilation(line: str, definesDict: dict, conditionals: [(str, bool)]) -> bool:
line = line.strip()
upperLine = line.upper()
numWordsOnLine = len(line.split())
if upperLine.startswith('#IFDEF ') and numWordsOnLine > 1:
condition = line.strip().split()[1]
conditionals.insert(0, (condition, True)) # Insert at front
return True
elif upperLine.startswith('#INFDEF ') and numWordsOnLine > 1:
condition = line.strip().split()[1]
conditionals.insert(0, (condition, False)) # Insert at front
return True
elif upperLine == '#ELSE':
if len(conditionals) >= 1: # At least one statement was pushed before
condition = conditionals.pop(0)
if condition[1] is True:
conditionals.insert(0, (condition[0], False)) # Invert old statement
else:
conditionals.insert(0, (condition[0], True)) # Invert old statement
return True
elif upperLine == '#ENDIF':
conditionals.pop(0) # Remove first element (last pushed)
return True
else:
for condition in conditionals:
definedType = condition[1]
condition = condition[0]
if definedType is True: # From #ifdef
if condition not in definesDict:
return True # If something isn't defined then skip the line
else: # From #ifndef
if condition in definesDict:
return True # If something is defined then skip the line
return False
def main():
startTime = datetime.now()
try:
shutil.copyfile(SOURCE_ROM, ROM_NAME)
except FileNotFoundError:
print('Error: Insertion could not be completed.\n'
+ 'Could not find source rom: "' + SOURCE_ROM + '".\n'
+ 'Please make sure a rom with this name exists in the root.')
sys.exit(1)
except PermissionError:
print('Error: Insertion could not be completed.\n'
+ '"' + ROM_NAME + '" is currently in use by another application.'
+ '\nPlease free it up before trying again.')
sys.exit(1)
with open(ROM_NAME, 'rb+') as rom:
print("Inserting code.")
table = GetSymbols(GetTextSection())
rom.seek(OFFSET_TO_PUT)
with open(OUTPUT, 'rb') as binary:
rom.write(binary.read())
binary.close()
# Adjust symbol table
for entry in table:
table[entry] += OFFSET_TO_PUT
# Insert byte changes
if os.path.isfile(BYTE_REPLACEMENT):
with open(BYTE_REPLACEMENT, 'r') as replacelist:
definesDict = {}
conditionals = []
for line in replacelist:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
offset = int(line[:8], 16) - 0x08000000
try:
ReplaceBytes(rom, offset, line[9:].strip())
except ValueError: #Try loading from the defines dict if unrecognizable character
newNumber = definesDict[line[9:].strip()]
try:
newNumber = int(newNumber)
except ValueError:
newNumber = int(newNumber, 16)
newNumber = str(hex(newNumber)).split('0x')[1]
ReplaceBytes(rom, offset, newNumber)
# Do Special Inserts
if os.path.isfile(SPECIAL_INSERTS) and os.path.isfile(SPECIAL_INSERTS_OUT):
with open(SPECIAL_INSERTS, 'r') as file:
offsetList = []
for line in file:
if line.strip().startswith('.org '):
offsetList.append(int(line.split('.org ')[1].split(',')[0], 16))
offsetList.sort()
with open(SPECIAL_INSERTS_OUT, 'rb') as binFile:
for offset in offsetList:
originalOffset = offset
dataList = ""
if offsetList.index(offset) == len(offsetList) - 1:
while True:
try:
binFile.seek(offset)
dataList += hex(binFile.read(1)[0]) + ' '
except IndexError:
break
offset += 1
else:
binFile.seek(offset)
word = ExtractPointer(binFile.read(4))
while word != 0xFFFFFFFF:
binFile.seek(offset)
dataList += hex(binFile.read(1)[0]) + ' '
offset += 1
if offset in offsetList: # Overlapping data
break
word = ExtractPointer(binFile.read(4))
ReplaceBytes(rom, originalOffset, dataList.strip())
# Read hooks from a file
if os.path.isfile(HOOKS):
with open(HOOKS, 'r') as hookList:
definesDict = {}
conditionals = []
for line in hookList:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
symbol, address, register = line.split()
offset = int(address, 16) - 0x08000000
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
Hook(rom, code, offset, int(register))
# Read repoints from a file
if os.path.isfile(REPOINTS):
with open(REPOINTS, 'r') as repointList:
definesDict = {}
conditionals = []
for line in repointList:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
if len(line.split()) == 2:
symbol, address = line.split()
offset = int(address, 16) - 0x08000000
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
Repoint(rom, code, offset)
if len(line.split()) == 3:
symbol, address, slide = line.split()
offset = int(address, 16) - 0x08000000
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
Repoint(rom, code, offset, int(slide))
symbolsRepointed = set()
if os.path.isfile(GENERATED_REPOINTS):
with open(GENERATED_REPOINTS, 'r') as repointList:
for line in repointList:
if line.strip().startswith('#') or line.strip() == '':
continue
symbol, address = line.split()
offset = int(address)
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
symbolsRepointed.add(symbol)
Repoint(rom, code, offset)
else:
with open(GENERATED_REPOINTS, 'w') as repointList:
repointList.write('##This is a generated file at runtime. Do not modify it!\n')
if os.path.isfile(REPOINT_ALL):
offsetsToRepointTogether = []
with open(REPOINT_ALL, 'r') as repointList:
definesDict = {}
conditionals = []
for line in repointList:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
symbol, address = line.split()
offset = int(address, 16) - 0x08000000
if symbol in symbolsRepointed:
continue
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
offsetsToRepointTogether.append((offset, code, symbol))
if offsetsToRepointTogether != []:
offsets = RealRepoint(rom, offsetsToRepointTogether) # Format is [(offset, symbol), ...]
output = open(GENERATED_REPOINTS, 'a')
for tup in offsets:
output.write(tup[1] + ' ' + str(tup[0]) + '\n')
output.close()
# Read routine repoints from a file
if os.path.isfile(ROUTINE_POINTERS):
with open(ROUTINE_POINTERS, 'r') as pointerlist:
definesDict = {}
conditionals = []
for line in pointerlist:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
symbol, address = line.split()
offset = int(address, 16) - 0x08000000
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
Repoint(rom, code, offset, 1)
# Read routine rewrite wrapper from a file
if os.path.isfile(FUNCTION_REWRITES):
with open(FUNCTION_REWRITES, 'r') as frwlist:
definesDict = {}
conditionals = []
for line in frwlist:
if TryProcessFileInclusion(line, definesDict):
continue
if TryProcessConditionalCompilation(line, definesDict, conditionals):
continue
if line.strip().startswith('#') or line.strip() == '':
continue
symbol, address, numParams, isReturning = line.split()
offset = int(address, 16) - 0x08000000
try:
code = table[symbol]
except KeyError:
print('Symbol missing:', symbol)
continue
FunctionWrap(rom, code, offset, int(numParams), int(isReturning))
width = max(map(len, table.keys())) + 1
if os.path.isfile('offsets.ini'):
offsetIni = open('offsets.ini', 'r+')
else:
offsetIni = open('offsets.ini', 'w')
offsetIni.truncate()
for key in sorted(table.keys()):
fstr = ('{:' + str(width) + '} {:08X}')
offsetIni.write(fstr.format(key + ':', table[key] + 0x08000000) + '\n')
offsetIni.close()
print('Inserted in ' + str(datetime.now() - startTime) + '.')
if __name__ == '__main__':
main()