1
0
Fork 0
This repository has been archived on 2024-02-06. You can view files and clone it, but cannot push or open issues or pull requests.
GeckoLoader/kernel.py

704 lines
30 KiB
Python
Raw Normal View History

import os
import random
import re
import sys
import time
import functools
from io import BytesIO
import tools
from fileutils import *
from dolreader import DolFile, SectionCountFullError, UnmappedAddressError
try:
import chardet
except ImportError as IE:
print(IE)
sys.exit(1)
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
value = func(*args, **kwargs)
end = time.perf_counter()
print(tools.color_text(f'\n :: Completed in {(end - start):0.4f} seconds!\n', defaultColor=tools.TGREENLIT))
return value
2020-09-28 17:02:59 +09:00
return wrapper
class InvalidGeckoCodeError(Exception): pass
class GCT(object):
2020-10-04 16:30:11 +09:00
def __init__(self, f):
self.codeList = BytesIO(f.read())
2020-09-28 17:02:59 +09:00
self.rawLineCount = tools.stream_size(self.codeList) >> 3
self.lineCount = self.rawLineCount - 2
2020-09-28 17:02:59 +09:00
self.size = tools.stream_size(self.codeList)
f.seek(0)
@staticmethod
2020-10-04 16:30:11 +09:00
def determine_codelength(codetype, info: bytes) -> int:
if codetype.startswith(b'\x06'):
bytelength = int.from_bytes(info, byteorder='big', signed=False)
padding = get_alignment(bytelength, 8)
return 0x8 + bytelength + padding
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09')
or codetype.startswith(b'\x18') or codetype.startswith(b'\x18')):
return 0x16
elif (codetype.startswith(b'\xC2') or codetype.startswith(b'\xC4')
or codetype.startswith(b'\xC3') or codetype.startswith(b'\xC5')
or codetype.startswith(b'\xD2') or codetype.startswith(b'\xD4')
or codetype.startswith(b'\xD3') or codetype.startswith(b'\xD5')):
return 0x8 + (int.from_bytes(info, byteorder='big', signed=False) << 3)
elif (codetype.startswith(b'\xF2') or codetype.startswith(b'\xF3')
or codetype.startswith(b'\xF4') or codetype.startswith(b'\xF5')):
return 0x8 + (int.from_bytes(info[:2], byteorder='big', signed=False) << 3)
elif codetype.startswith(b'\xF6'):
return 0x8 + (int.from_bytes(info[:4], byteorder='big', signed=False) << 3)
else:
return 0x8
def optimize_codelist(self, dolFile: DolFile):
2020-10-04 16:30:11 +09:00
codelist = b'\x00\xD0\xC0\xDE'*2
skipcodes = 0
2020-08-27 15:50:12 +09:00
2020-10-04 16:30:11 +09:00
self.codeList.seek(8)
while codetype := self.codeList.read(4):
info = self.codeList.read(4)
address = 0x80000000 | (int.from_bytes(codetype, byteorder='big', signed=False) & 0x01FFFFFF)
try:
if skipcodes <= 0:
if (codetype.startswith(b'\x00') or codetype.startswith(b'\x01')
or codetype.startswith(b'\x10') or codetype.startswith(b'\x11')):
dolFile.seek(address)
counter = int.from_bytes(info[:-2], byteorder='big', signed=False)
value = info[2:]
while counter + 1 > 0:
dolFile.write(value[1:])
counter -= 1
continue
elif (codetype.startswith(b'\x02') or codetype.startswith(b'\x03')
or codetype.startswith(b'\x12') or codetype.startswith(b'\x13')):
dolFile.seek(address)
counter = int.from_bytes(info[:-2], byteorder='big', signed=False)
value = info[2:]
while counter + 1 > 0:
dolFile.write(value)
counter -= 1
continue
elif (codetype.startswith(b'\x04') or codetype.startswith(b'\x05')
or codetype.startswith(b'\x14') or codetype.startswith(b'\x15')):
dolFile.seek(address)
dolFile.write(info)
continue
elif (codetype.startswith(b'\x06') or codetype.startswith(b'\x07')
or codetype.startswith(b'\x16') or codetype.startswith(b'\x17')):
dolFile.seek(address)
arraylength = int.from_bytes(info, byteorder='big', signed=False)
padding = get_alignment(arraylength, 8)
while arraylength > 0:
value = self.codeList.read(1)
dolFile.write(value)
arraylength -= 1
self.codeList.seek(padding, 1)
continue
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09')
or codetype.startswith(b'\x18') or codetype.startswith(b'\x19')):
dolFile.seek(address)
value = int.from_bytes(info, byteorder='big', signed=False)
2020-09-28 17:02:59 +09:00
data = read_uint16(self.codeList)
size = data & 0x3000
counter = data & 0xFFF
address_increment = read_uint16(self.codeList)
value_increment = read_uint32(self.codeList)
while counter + 1 > 0:
if size == 0:
write_ubyte(dolFile, value)
dolFile.seek(-1, 1)
elif size == 1:
write_uint16(dolFile, value)
dolFile.seek(-2, 1)
elif size == 2:
write_uint32(dolFile, value)
dolFile.seek(-4, 1)
else:
raise ValueError('Size type {} does not match 08 codetype specs'.format(size))
dolFile.seek(address_increment, 1)
value += value_increment
counter -= 1
if value > 0xFFFFFFFF:
value -= 0x100000000
continue
elif (codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')
or codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')):
dolFile.seek(address)
2020-11-06 19:14:27 +09:00
dolFile.insert_branch(int.from_bytes(info, byteorder='big', signed=False), dolFile.tell())
continue
if codetype.hex().startswith('2') or codetype.hex().startswith('3'):
skipcodes += 1
elif codetype.startswith(b'\xE0'):
skipcodes -= 1
elif codetype.startswith(b'\xF0'):
codelist += b'\xF0\x00\x00\x00\x00\x00\x00\x00'
break
2020-10-04 16:30:11 +09:00
self.codeList.seek(-8, 1)*5
length = GCT.determine_codelength(codetype, info)
while length > 0:
codelist += self.codeList.read(1)
length -= 1
except (RuntimeError, UnmappedAddressError):
self.codeList.seek(-8, 1)
length = GCT.determine_codelength(codetype, info)
codelist += self.codeList.read(length)
self.codeList = BytesIO(codelist)
self.size = len(self.codeList.getbuffer())
class CodeHandler(object):
2020-11-06 19:14:27 +09:00
class Types:
MINI = "MINI"
FULL = "FULL"
def __init__(self, f):
self._rawData = BytesIO(f.read())
'''Get codelist pointer'''
2020-09-28 17:02:59 +09:00
self._rawData.seek(0xFA)
codelistUpper = self._rawData.read(2).hex()
self._rawData.seek(0xFE)
codelistLower = self._rawData.read(2).hex()
self.codeListPointer = int(codelistUpper[2:] + codelistLower[2:], 16)
2020-09-28 17:02:59 +09:00
self.handlerLength = tools.stream_size(self._rawData)
self.initAddress = 0x80001800
self.startAddress = 0x800018A8
2020-08-24 19:10:23 +09:00
self.wiiVIHook = b'\x7C\xE3\x3B\x78\x38\x87\x00\x34\x38\xA7\x00\x38\x38\xC7\x00\x4C'
self.gcnVIHook = b'\x7C\x03\x00\x34\x38\x83\x00\x20\x54\x85\x08\x3C\x7C\x7F\x2A\x14\xA0\x03\x00\x00\x7C\x7D\x2A\x14\x20\xA4\x00\x3F\xB0\x03\x00\x00'
2020-08-24 19:10:23 +09:00
self.wiiGXDrawHook = b'\x3C\xA0\xCC\x01\x38\x00\x00\x61\x3C\x80\x45\x00\x98\x05\x80\x00'
self.gcnGXDrawHook = b'\x38\x00\x00\x61\x3C\xA0\xCC\x01\x3C\x80\x45\x00\x98\x05\x80\x00'
self.wiiPADHook = b'\x3A\xB5\x00\x01\x3A\x73\x00\x0C\x2C\x15\x00\x04\x3B\x18\x00\x0C'
self.gcnPADHook = b'\x3A\xB5\x00\x01\x2C\x15\x00\x04\x3B\x18\x00\x0C\x3B\xFF\x00\x0C'
self.allocation = None
self.hookAddress = None
2020-08-24 19:10:23 +09:00
self.hookType = None
self.geckoCodes = None
self.includeAll = False
self.optimizeList = False
if self.handlerLength < 0x900:
2020-11-06 19:14:27 +09:00
self.type = CodeHandler.Types.MINI
else:
2020-11-06 19:14:27 +09:00
self.type = CodeHandler.Types.FULL
f.seek(0)
2020-10-20 13:15:33 +09:00
def init_gct(self, gctFile: str, tmpdir: str=""):
if '.' in gctFile:
if os.path.splitext(gctFile)[1].lower() == '.txt':
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp:
temp.write(bytes.fromhex('00D0C0DE'*2 + self.parse_input(gctFile) + 'F000000000000000'))
temp.seek(0)
self.geckoCodes = GCT(temp)
elif os.path.splitext(gctFile)[1].lower() == '.gct':
with open(gctFile, 'rb') as gct:
self.geckoCodes = GCT(gct)
else:
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp:
temp.write(b'\x00\xD0\xC0\xDE'*2)
for file in os.listdir(gctFile):
if os.path.isfile(os.path.join(gctFile, file)):
if os.path.splitext(file)[1].lower() == '.txt':
temp.write(bytes.fromhex(self.parse_input(os.path.join(gctFile, file))))
elif os.path.splitext(file)[1].lower() == '.gct':
with open(os.path.join(gctFile, file), 'rb') as gct:
temp.write(gct.read()[8:-8])
else:
print(tools.color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=tools.TYELLOWLIT))
temp.write(b'\xF0\x00\x00\x00\x00\x00\x00\x00')
temp.seek(0)
self.geckoCodes = GCT(temp)
2020-10-04 16:30:11 +09:00
def parse_input(self, geckoText) -> str:
with open(r'{}'.format(geckoText), 'rb') as gecko:
result = chardet.detect(gecko.read())
encodeType = result['encoding']
with open(r'{}'.format(geckoText), 'r', encoding=encodeType) as gecko:
geckoCodes = ''
state = None
for line in gecko.readlines():
if line in ('', '\n'):
continue
if state is None:
2020-08-24 19:10:23 +09:00
if line.startswith('$') or line.startswith('['):
state = 'Dolphin'
else:
state = 'OcarinaM'
try:
if state == 'OcarinaM':
2020-08-25 21:06:56 +09:00
if self.includeAll:
geckoLine = re.findall(r'[A-F0-9]{8}[\t\f ][A-F0-9]{8}', line, re.IGNORECASE)[0]
else:
geckoLine = re.findall(r'(?:\*\s*)([A-F0-9]{8}[\t\f ][A-F0-9]{8})', line, re.IGNORECASE)[0]
else:
geckoLine = re.findall(r'(?<![$\*])[A-F0-9]{8}[\t\f ][A-F0-9]{8}', line, re.IGNORECASE)[0]
except IndexError:
continue
geckoCodes += geckoLine.replace(' ', '').strip()
return geckoCodes
@staticmethod
2020-09-28 17:02:59 +09:00
def encrypt_key(key: int) -> int:
b1 = key & 0xFF
b2 = (key >> 8) & 0xFF
b3 = (key >> 16) & 0xFF
b4 = (key >> 24) & 0xFF
b3 ^= b4
b2 ^= b3
b1 ^= b2
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
2020-10-04 16:30:11 +09:00
def encrypt_codes(self, key: int):
self.geckoCodes.codeList.seek(0)
i = 0
while True:
try:
packet = read_uint32(self.geckoCodes.codeList)
self.geckoCodes.codeList.seek(-4, 1)
write_uint32(self.geckoCodes.codeList, (packet^key) & 0xFFFFFFFF)
2020-08-24 19:24:50 +09:00
key += (i << 3) & 0xFFFFFFFF
if key > 0xFFFFFFFF:
key -= 0x100000000
i += 1
except:
break
2020-09-28 17:02:59 +09:00
def find_variable_data(self, variable) -> int:
self._rawData.seek(0)
if self._rawData.read(4) == variable:
return self._rawData.tell() - 4
while sample := self._rawData.read(4):
if sample == variable:
return self._rawData.tell() - 4
return None
def set_hook_instruction(self, dolFile: DolFile, address: int, varOffset: int, lk=0):
self._rawData.seek(varOffset)
dolFile.seek(address)
ppc = read_uint32(dolFile)
if ((((ppc >> 24) & 0xFF) > 0x47 and ((ppc >> 24) & 0xFF) < 0x4C) or (((ppc >> 24) & 0xFF) > 0x3F and ((ppc >> 24) & 0xFF) < 0x44)):
to, conditional = dolFile.extract_branch_addr(address)
if conditional:
raise NotImplementedError("Hooking to a conditional non spr branch is unsupported")
write_uint32(self._rawData, (to - (self.initAddress + varOffset)) & 0x3FFFFFD | 0x48000000 | lk)
else:
write_uint32(self._rawData, ppc)
def set_variables(self, dolFile: DolFile):
varOffset = self.find_variable_data(b'\x00\xDE\xDE\xDE')
if varOffset is None:
2020-10-04 16:30:11 +09:00
raise RuntimeError(tools.color_text("Variable codehandler data not found\n", defaultColor=tools.TREDLIT))
self.set_hook_instruction(dolFile, self.hookAddress, varOffset, 0)
self._rawData.seek(varOffset + 4)
write_uint32(self._rawData, ((self.hookAddress + 4) - (self.initAddress + (varOffset + 4))) & 0x3FFFFFD | 0x48000000 | 0)
class KernelLoader(object):
def __init__(self, f, cli: tools.CommandLineParser=None):
self._rawData = BytesIO(f.read())
2020-09-28 17:02:59 +09:00
self._initDataList = None
self._gpModDataList = None
self._gpDiscDataList = None
self._gpKeyAddrList = None
self._cli = cli
2020-09-28 17:02:59 +09:00
self.patchJob = None
self.initAddress = None
2020-08-18 12:23:10 +09:00
self.protect = False
self.verbosity = 0
self.quiet = False
self.encrypt = False
2020-11-06 19:14:27 +09:00
def error(self, msg: str):
if self._cli is not None:
2020-11-06 19:14:27 +09:00
self._cli.error(msg)
else:
print(msg)
2020-11-06 19:14:27 +09:00
sys.exit(1)
def set_variables(self, entryPoint: list, baseOffset: int=0):
self._rawData.seek(0)
2020-09-28 17:02:59 +09:00
if self._gpModDataList is None:
return
2020-09-28 17:02:59 +09:00
while sample := self._rawData.read(2):
if sample == b'GH':
self._rawData.seek(-2, 1)
2020-09-28 17:02:59 +09:00
write_uint16(self._rawData, self._gpModDataList[0])
elif sample == b'GL':
self._rawData.seek(-2, 1)
2020-09-28 17:02:59 +09:00
write_uint16(self._rawData, baseOffset + self._gpModDataList[1])
elif sample == b'IH':
self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[0])
elif sample == b'IL':
self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[1])
elif sample == b'KH':
self._rawData.seek(-2, 1)
2020-09-28 17:02:59 +09:00
write_uint16(self._rawData, self._gpKeyAddrList[0])
elif sample == b'KL':
self._rawData.seek(-2, 1)
2020-09-28 17:02:59 +09:00
write_uint16(self._rawData, baseOffset + self._gpKeyAddrList[1])
def complete_data(self, codeHandler: CodeHandler, initpoint: list):
2020-09-28 17:02:59 +09:00
_upperAddr, _lowerAddr = ((self.initAddress >> 16) & 0xFFFF, self.initAddress & 0xFFFF)
_key = random.randrange(0x100000000)
self._rawData.seek(0)
2020-09-28 17:02:59 +09:00
while sample := self._rawData.read(4):
if sample == b'HEAP': #Found keyword "HEAP". Goes with the resize of the heap
self._rawData.seek(-4, 1)
gpModInfoOffset = self._rawData.tell()
2020-09-28 17:02:59 +09:00
if _lowerAddr + gpModInfoOffset > 0x7FFF: #Absolute addressing
gpModUpperAddr = _upperAddr + 1
else:
2020-09-28 17:02:59 +09:00
gpModUpperAddr = _upperAddr
if codeHandler.allocation == None:
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8
write_uint32(self._rawData, codeHandler.allocation)
elif sample == b'LSIZ': #Found keyword "LSIZ". Goes with the size of the loader
self._rawData.seek(-4, 1)
write_uint32(self._rawData, len(self._rawData.getbuffer()))
elif sample == b'HSIZ': #Found keyword "HSIZ". Goes with the size of the codehandler
self._rawData.seek(-4, 1)
write_sint32(self._rawData, codeHandler.handlerLength)
elif sample == b'CSIZ': #Found keyword "CSIZ". Goes with the size of the codes
self._rawData.seek(-4, 1)
write_sint32(self._rawData, codeHandler.geckoCodes.size)
elif sample == b'HOOK': #Found keyword "HOOK". Goes with the codehandler hook
self._rawData.seek(-4, 1)
write_uint32(self._rawData, codeHandler.hookAddress)
elif sample == b'CRPT': #Found keyword "CRPT". Boolean of the encryption
self._rawData.seek(-4, 1)
write_bool(self._rawData, self.encrypt, 4)
elif sample == b'CYPT': #Found keyword "CYPT". Encryption Key
self._rawData.seek(-4, 1)
gpKeyOffset = self._rawData.tell()
2020-09-28 17:02:59 +09:00
if _lowerAddr + gpKeyOffset > 0x7FFF: #Absolute addressing
gpKeyUpperAddr = _upperAddr + 1
else:
2020-09-28 17:02:59 +09:00
gpKeyUpperAddr = _upperAddr
2020-09-28 17:02:59 +09:00
write_uint32(self._rawData, CodeHandler.encrypt_key(_key))
2020-09-28 17:02:59 +09:00
self._gpModDataList = (gpModUpperAddr, gpModInfoOffset)
self._gpKeyAddrList = (gpKeyUpperAddr, gpKeyOffset)
2020-09-28 17:02:59 +09:00
self.set_variables(initpoint, _lowerAddr)
if self.encrypt:
2020-10-04 16:30:11 +09:00
codeHandler.encrypt_codes(_key)
2020-11-06 19:14:27 +09:00
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple:
self.complete_data(codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF])
self._rawData.seek(0, 2)
self._rawData.write(codeHandler._rawData.getvalue() + codeHandler.geckoCodes.codeList.getvalue())
self._rawData.seek(0)
2020-09-28 17:02:59 +09:00
_kernelData = self._rawData.getvalue()
try:
dolFile.append_text_sections([(_kernelData, self.initAddress)])
except SectionCountFullError:
try:
dolFile.append_data_sections([(_kernelData, self.initAddress)])
except SectionCountFullError:
2020-10-04 16:30:11 +09:00
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT))
2020-11-06 19:14:27 +09:00
return False, 'There are no unused sections left for GeckoLoader to use!'
2020-10-04 16:30:11 +09:00
2020-09-28 17:02:59 +09:00
dolFile.entryPoint = self.initAddress
2020-11-06 19:14:27 +09:00
return True, None
2020-11-06 19:14:27 +09:00
def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple:
codeHandler._rawData.seek(0)
codeHandler.geckoCodes.codeList.seek(0)
2020-09-28 17:02:59 +09:00
_handlerData = codeHandler._rawData.getvalue() + codeHandler.geckoCodes.codeList.getvalue()
try:
dolFile.append_text_sections([(_handlerData, codeHandler.initAddress)])
except SectionCountFullError:
try:
dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)])
except SectionCountFullError:
2020-10-04 16:30:11 +09:00
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT))
2020-11-06 19:14:27 +09:00
return False, 'There are no unused sections left for GeckoLoader to use!'
return True, None
def protect_game(self, codeHandler: CodeHandler):
2020-09-28 17:02:59 +09:00
_oldpos = codeHandler.geckoCodes.codeList.tell()
2020-10-04 16:30:11 +09:00
protectdata = (b'\xC0\x00\x00\x00\x00\x00\x00\x17',
b'\x7C\x08\x02\xA6\x94\x21\xFF\x70',
b'\x90\x01\x00\x08\xBC\x61\x00\x0C',
b'\x48\x00\x00\x0D\x00\xD0\xC0\xDE',
b'\x00\xD0\xDE\xAD\x7F\xE8\x02\xA6',
b'\x3B\xDF\x00\x08\x3C\x60\x80\x00',
b'\x38\x80\x11\x00\x38\xA0\x00\x00',
b'\x60\x63\x1E\xF8\x7C\x89\x03\xA6',
b'\x38\x80\x00\x00\x7D\x03\x22\x14',
b'\x54\xE9\x06\x3E\x89\x08\x00\x08',
b'\x7D\x3F\x48\xAE\x38\xE7\x00\x01',
b'\x7C\x08\x48\x40\x41\x82\x00\x0C',
b'\x60\xA7\x00\x00\x48\x00\x00\x04',
b'\x54\xE8\x06\x3E\x28\x08\x00\x03',
b'\x41\x81\x00\x10\x38\x84\x00\x01',
b'\x42\x00\xFF\xCC\x48\x00\x00\x2C',
b'\x38\xA0\x00\x08\x7C\x84\x1A\x14',
b'\x7C\xA9\x03\xA6\x38\x60\x00\x00',
b'\x38\x84\xFF\xFF\x54\x66\x07\xBE',
b'\x7C\xDE\x30\xAE\x38\x63\x00\x01',
b'\x9C\xC4\x00\x01\x42\x00\xFF\xF0',
b'\xB8\x61\x00\x0C\x80\x01\x00\x08',
b'\x38\x21\x00\x90\x7C\x08\x03\xA6',
2020-10-04 16:30:11 +09:00
b'\x4E\x80\x00\x20\x00\x00\x00\x00')
codeHandler.geckoCodes.codeList.seek(-8, 2)
for chunk in protectdata:
codeHandler.geckoCodes.codeList.write(chunk)
codeHandler.geckoCodes.codeList.write(b'\xF0\x00\x00\x00\x00\x00\x00\x00')
codeHandler.geckoCodes.codeList.seek(0, 2)
codeHandler.geckoCodes.size = codeHandler.geckoCodes.codeList.tell()
2020-09-28 17:02:59 +09:00
codeHandler.geckoCodes.codeList.seek(_oldpos)
@timer
def build(self, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump):
2020-11-06 19:14:27 +09:00
_oldStart = dolFile.entryPoint
2020-10-04 16:30:11 +09:00
'''Initialize our codes'''
2020-10-20 13:15:33 +09:00
codeHandler.init_gct(gctFile, tmpdir)
2020-10-04 16:30:11 +09:00
if codeHandler.geckoCodes is None:
self.error(tools.color_text('Valid codelist not found. Please provide a .txt/.gct file, or a folder of .txt/.gct files\n', defaultColor=tools.TREDLIT))
2020-11-06 19:14:27 +09:00
2020-10-04 16:30:11 +09:00
if self.protect and self.patchJob == "ARENA":
self.protect_game(codeHandler)
if self.patchJob == 'AUTO':
if codeHandler.initAddress + codeHandler.handlerLength + codeHandler.geckoCodes.size > 0x80002FFF:
self.patchJob = 'ARENA'
else:
self.patchJob = 'LEGACY'
2020-08-18 12:23:10 +09:00
2020-10-04 16:30:11 +09:00
'''Get entrypoint (or BSS midpoint) for insert'''
2020-10-04 16:30:11 +09:00
if self.initAddress:
try:
dolFile.resolve_address(self.initAddress)
2020-11-06 19:14:27 +09:00
self.error(tools.color_text(f'Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections', defaultColor=tools.TREDLIT))
2020-10-04 16:30:11 +09:00
except RuntimeError:
pass
else:
self.initAddress = dolFile.seek_nearest_unmapped(dolFile.bssAddress, len(self._rawData.getbuffer()) + codeHandler.handlerLength + codeHandler.geckoCodes.size)
self._rawData.seek(0)
2020-10-04 16:30:11 +09:00
if codeHandler.optimizeList:
codeHandler.geckoCodes.optimize_codelist(dolFile)
2020-10-04 16:30:11 +09:00
'''Is codelist optimized away?'''
2020-10-04 16:30:11 +09:00
if codeHandler.geckoCodes.codeList.getvalue() == b'\x00\xD0\xC0\xDE\x00\xD0\xC0\xDE\xF0\x00\x00\x00\x00\x00\x00\x00':
with open(dump, 'wb') as final:
dolFile.save(final)
2020-10-04 16:30:11 +09:00
2020-11-06 19:14:27 +09:00
if not self.quiet:
if self.verbosity >= 3:
dolFile.print_info()
print('-'*64)
if self.verbosity >= 1:
print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT))
2020-10-04 16:30:11 +09:00
return
2020-11-06 19:14:27 +09:00
2020-10-04 16:30:11 +09:00
if self.patchJob == 'LEGACY':
legacy = True
2020-10-04 16:30:11 +09:00
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
hooked = determine_codehook(dolFile, codeHandler, True)
if hooked:
2020-11-06 19:14:27 +09:00
_status, _msg = self.patch_legacy(codeHandler, dolFile)
2020-10-04 16:30:11 +09:00
else:
legacy = False
hooked = determine_codehook(dolFile, codeHandler, False)
if hooked:
2020-11-06 19:14:27 +09:00
_status, _msg = self.patch_arena(codeHandler, dolFile)
2020-10-04 16:30:11 +09:00
if not hooked:
self.error(tools.color_text('Failed to find a hook address. Try using option --codehook to use your own address\n', defaultColor=tools.TREDLIT))
2020-11-06 19:14:27 +09:00
elif _status is False:
self.error(tools.color_text(_msg + '\n', defaultColor=tools.TREDLIT))
2020-10-04 16:30:11 +09:00
elif codeHandler.allocation < codeHandler.geckoCodes.size:
2020-11-06 19:14:27 +09:00
self.error(tools.color_text('Allocated codespace was smaller than the given codelist\n', defaultColor=tools.TYELLOW))
2020-10-04 16:30:11 +09:00
with open(dump, 'wb') as final:
2020-09-28 17:02:59 +09:00
dolFile.save(final)
2020-10-04 16:30:11 +09:00
if self.quiet:
return
2020-10-04 16:30:11 +09:00
if codeHandler.allocation > 0x70000:
print(tools.color_text(f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codeHandler.allocation:X}', defaultColor=tools.TYELLOW))
2020-10-04 16:30:11 +09:00
elif codeHandler.allocation > 0x40000:
print(tools.color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=tools.TYELLOWLIT))
if self.verbosity >= 3:
dolFile.print_info()
print('-'*64)
if self.verbosity >= 2:
print('')
2020-11-06 19:14:27 +09:00
if legacy is False:
2020-10-04 16:30:11 +09:00
info = [f' :: Start of game modified to address 0x{self.initAddress:X}',
2020-11-06 19:14:27 +09:00
f' :: Game function "__start()" located at address 0x{_oldStart:X}',
2020-10-04 16:30:11 +09:00
f' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}',
f' :: Codehandler is of type "{codeHandler.type}"',
2020-11-06 19:14:27 +09:00
f' :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used',
f' :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used']
2020-10-04 16:30:11 +09:00
else:
2020-11-06 19:14:27 +09:00
info = [f' :: Game function "__start()" located at address 0x{_oldStart:X}',
2020-10-04 16:30:11 +09:00
f' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}',
f' :: Codehandler is of type "{codeHandler.type}"',
2020-11-06 19:14:27 +09:00
f' :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used',
f' :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used']
2020-10-04 16:30:11 +09:00
for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
elif self.verbosity >= 1:
print('')
info = [f' :: GeckoLoader set at address 0x{self.initAddress:X}',
f' :: Legacy size is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
f' :: Codehandler is of type "{codeHandler.type}"']
for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
2020-11-06 19:14:27 +09:00
def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False) -> bool:
if codeHandler.hookAddress is None:
2020-08-24 19:10:23 +09:00
if not assert_code_hook(dolFile, codeHandler):
return False
if hook:
codeHandler.set_variables(dolFile)
2020-08-24 19:10:23 +09:00
insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
2020-10-20 13:15:33 +09:00
2020-08-24 19:10:23 +09:00
return True
2020-11-06 19:14:27 +09:00
def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler) -> bool:
for _, address, size, _, _ in dolFile.textSections:
2020-09-28 17:02:59 +09:00
dolFile.seek(address)
sample = dolFile.read(size)
2020-08-24 19:10:23 +09:00
if codeHandler.hookType == 'VI':
result = sample.find(codeHandler.gcnVIHook)
elif codeHandler.hookType == 'GX':
result = sample.find(codeHandler.gcnGXDrawHook)
elif codeHandler.hookType == 'PAD':
result = sample.find(codeHandler.gcnPADHook)
else:
raise NotImplementedError(tools.color_text(f'Unsupported hook type specified ({codeHandler.hookType})', defaultColor=tools.TREDLIT))
2020-08-24 19:10:23 +09:00
if result >= 0:
2020-09-28 17:02:59 +09:00
dolFile.seek(address + result)
else:
2020-08-24 19:10:23 +09:00
if codeHandler.hookType == 'VI':
result = sample.find(codeHandler.wiiVIHook)
elif codeHandler.hookType == 'GX':
result = sample.find(codeHandler.wiiGXDrawHook)
elif codeHandler.hookType == 'PAD':
result = sample.find(codeHandler.wiiPADHook)
else:
raise NotImplementedError(tools.color_text(f'Unsupported hook type specified ({codeHandler.hookType})', defaultColor=tools.TREDLIT))
2020-08-24 19:10:23 +09:00
if result >= 0:
2020-09-28 17:02:59 +09:00
dolFile.seek(address + result)
else:
continue
2020-09-28 17:02:59 +09:00
while (sample := read_uint32(dolFile)) != 0x4E800020:
pass
dolFile.seek(-4, 1)
codeHandler.hookAddress = dolFile.tell()
2020-08-24 19:10:23 +09:00
return True
return False
def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int):
dolFile.seek(address)
ppc = read_uint32(dolFile)
if ((ppc >> 24) & 0xFF) > 0x3F and ((ppc >> 24) & 0xFF) < 0x48:
raise NotImplementedError(tools.color_text('Hooking the codehandler to a conditional non spr branch is unsupported', defaultColor=tools.TREDLIT))
dolFile.seek(-4, 1)
2020-08-24 19:10:23 +09:00
dolFile.insert_branch(codeHandler.startAddress, address, lk=0)