Major overhaul, added multiple features
This commit is contained in:
parent
70a708ca05
commit
a02e6c55c8
6 changed files with 923 additions and 0 deletions
522
GeckoLoader.py
Normal file
522
GeckoLoader.py
Normal file
|
@ -0,0 +1,522 @@
|
|||
#Written by JoshuaMK 2020
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import shutil
|
||||
import dolreader
|
||||
|
||||
from io import BytesIO, RawIOBase
|
||||
|
||||
try:
|
||||
import argparse
|
||||
import chardet
|
||||
except ImportError as IE:
|
||||
print(IE)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
colorama.init()
|
||||
TRESET = Style.RESET_ALL
|
||||
TGREEN = Fore.GREEN
|
||||
TGREENLIT = Style.BRIGHT + Fore.GREEN
|
||||
TYELLOW = Fore.YELLOW
|
||||
TYELLOWLIT = Style.BRIGHT + Fore.YELLOW
|
||||
TRED = Fore.RED
|
||||
TREDLIT = Style.BRIGHT + Fore.RED
|
||||
|
||||
except ImportError:
|
||||
TRESET = ''
|
||||
TGREEN = ''
|
||||
TGREENLIT = ''
|
||||
TYELLOW = ''
|
||||
TYELLOWLIT = ''
|
||||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
def resource_path(relative_path):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
def get_size(file, offset=0):
|
||||
""" Return a file's size in bytes """
|
||||
file.seek(0, 2)
|
||||
return(bytes.fromhex('{:08X}'.format(file.tell() + offset)))
|
||||
|
||||
def getFileAlignment(file, alignment):
|
||||
""" Return file alignment, 0 = aligned, non zero = misaligned """
|
||||
size = int.from_bytes(get_size(file), byteorder='big', signed=False)
|
||||
|
||||
if size % alignment != 0:
|
||||
return alignment - (size % alignment)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def alignFile(file, alignment):
|
||||
""" Align a file to be the specified size """
|
||||
file.write(bytes.fromhex("00" * getFileAlignment(file, alignment)))
|
||||
|
||||
class GCT(object):
|
||||
|
||||
def __init__(self, f):
|
||||
self.codelist = BytesIO(f.read())
|
||||
self.rawlinecount = int.from_bytes(get_size(f), byteorder='big', signed=True) >> 3
|
||||
self.linecount = self.rawlinecount - 2
|
||||
self.size = int.from_bytes(get_size(f), byteorder='big', signed=True)
|
||||
f.seek(0)
|
||||
|
||||
class CodeHandler(object):
|
||||
|
||||
def __init__(self, f, gctFile, isText):
|
||||
self.codehandler = BytesIO(f.read())
|
||||
|
||||
'''Get codelist pointer'''
|
||||
f.seek(0xFA, 0)
|
||||
codelistUpper = f.read(2).hex()
|
||||
f.seek(0xFE, 0)
|
||||
codelistLower = f.read(2).hex()
|
||||
|
||||
self.codelistpointer = int(codelistUpper[2:] + codelistLower[2:], 16)
|
||||
self.handlerlength = int.from_bytes(get_size(f), byteorder='big', signed=True)
|
||||
self.initaddress = 0x80001800
|
||||
self.startaddress = 0x800018A8
|
||||
|
||||
if self.handlerlength < 0x900:
|
||||
self.type = "Mini"
|
||||
else:
|
||||
self.type = "Full"
|
||||
|
||||
if isText == True:
|
||||
self.geckocodes = self.geckoParser(gctFile, args.txtcodes)
|
||||
else:
|
||||
with open(r'{}'.format(gctFile), 'rb') as gct:
|
||||
self.geckocodes = GCT(gct)
|
||||
|
||||
f.seek(0)
|
||||
|
||||
def geckoParser(self, geckoText, parseAll):
|
||||
geckoMagic = '00D0C0DE00D0C0DE'
|
||||
geckoTerminate = 'F000000000000000'
|
||||
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:
|
||||
data = gecko.readlines()
|
||||
geckoCodes = ''
|
||||
|
||||
for line in data:
|
||||
if parseAll.lower() == 'all':
|
||||
geckoLine = re.findall(r'[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
elif parseAll.lower() == 'active':
|
||||
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
else:
|
||||
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
|
||||
geckoLine = ''.join(geckoLine)
|
||||
geckoLine = re.sub(r'\s+', '', geckoLine)
|
||||
geckoCodes = geckoCodes + geckoLine.replace('*', '')
|
||||
|
||||
with open(os.path.join('tmp', 'gct.bin'), 'wb+') as code:
|
||||
code.write(bytes.fromhex(geckoMagic + geckoCodes + geckoTerminate))
|
||||
code.seek(0)
|
||||
gct = GCT(code)
|
||||
|
||||
return gct
|
||||
|
||||
def build(gctFile, dolFile, codehandlerFile, size):
|
||||
global isText, _allocation, _codehook
|
||||
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as code, open(r'{}'.format(dolFile), 'rb') as dol, open(resource_path(os.path.join('bin', r'{}'.format(codehandlerFile))), 'rb') as handler, open(os.path.join('tmp', 'tmp.bin'), 'wb+') as tmp, open(os.path.join('BUILD', os.path.basename(dolFile)), 'wb+') as final:
|
||||
|
||||
if int(get_size(dol).hex(), 16) < 0x100:
|
||||
shutil.rmtree('tmp')
|
||||
parser.error('DOL header is corrupted. Please provide a clean file')
|
||||
|
||||
dol.seek(0)
|
||||
|
||||
'''Initialize the new DOL file'''
|
||||
|
||||
final.write(dol.read())
|
||||
final.seek(0)
|
||||
|
||||
dolfile = dolreader.DolFile(final)
|
||||
|
||||
'''Initialize our codehandler + codes'''
|
||||
|
||||
codehandler = CodeHandler(handler, gctFile, isText)
|
||||
|
||||
'''Get entrypoint (or BSS midpoint) for insert'''
|
||||
|
||||
if args.init:
|
||||
dump_address = args.init.lstrip("0x").upper()
|
||||
else:
|
||||
dump_address = '{:08X}'.format(dolfile._bssoffset + (dolfile._bsssize >> 1))[:-2] + '00'
|
||||
|
||||
'''Is insertion legacy?'''
|
||||
|
||||
if args.movecodes == 'LEGACY':
|
||||
_allocation = '{:X}'.format(0x80003000 - (codehandler.initaddress + codehandler.handlerlength))
|
||||
patchLegacyHandler(codehandler, tmp, dolfile)
|
||||
legacy = True
|
||||
elif args.movecodes == 'ARENA':
|
||||
patchGeckoLoader(code, codehandler, tmp, dolfile, dump_address)
|
||||
legacy = False
|
||||
else: #Auto decide area
|
||||
if codehandler.initaddress + codehandler.handlerlength + codehandler.geckocodes.size > 0x80002FFF:
|
||||
patchGeckoLoader(code, codehandler, tmp, dolfile, dump_address)
|
||||
legacy = False
|
||||
else:
|
||||
_allocation = '{:X}'.format(0x80003000 - (codehandler.initaddress + codehandler.handlerlength))
|
||||
patchLegacyHandler(codehandler, tmp, dolfile)
|
||||
legacy = True
|
||||
|
||||
dolfile.save(final)
|
||||
|
||||
if int(_allocation, 16) < codehandler.geckocodes.size:
|
||||
print(TYELLOW + '\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run' + TRESET)
|
||||
|
||||
if args.quiet:
|
||||
return
|
||||
|
||||
if int(_allocation, 16) > int('70000', 16):
|
||||
print(TYELLOW + '\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{}'.format(_allocation.upper().lstrip('0')) + TRESET)
|
||||
|
||||
elif int(_allocation, 16) > int('40000', 16):
|
||||
print(TYELLOWLIT + '\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{}'.format(_allocation.upper().lstrip('0')) + TRESET)
|
||||
|
||||
if args.verbose >= 2:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}, start of game modified to address 0x{}'.format(dump_address.upper().lstrip('0'), dump_address.upper().lstrip('0')),
|
||||
' :: Game function "_init_registers" located at address 0x{:X}'.format(dolfile._init),
|
||||
' :: Codehandler hooked at 0x{}'.format(_codehook.upper().lstrip('0')),
|
||||
' :: Code allocation is 0x{}; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.geckocodes.size),
|
||||
' :: Codehandler is of type "{}"'.format(codehandler.type),
|
||||
' :: Of the 7 text sections in this DOL file, {} were already used'.format(len(dolfile._text)) + TRESET]
|
||||
else:
|
||||
info = [TGREENLIT + ' :: Game function "_init_registers" located at address 0x{:X}'.format(dolfile._init),
|
||||
' :: Codehandler hooked at 0x{}'.format(_codehook.upper().lstrip('0')),
|
||||
' :: Code allocation is 0x{}; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.geckocodes.size),
|
||||
' :: Codehandler is of type "{}"'.format(codehandler.type),
|
||||
' :: Of the 7 text sections in this DOL file, {} were already used'.format(len(dolfile._text)) + TRESET]
|
||||
|
||||
for bit in info:
|
||||
print(bit)
|
||||
|
||||
elif args.verbose >= 1:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}'.format(dump_address.upper()),
|
||||
' :: Codehandler is of type "{}"'.format(args.handler),
|
||||
' :: Code allocation is 0x{} in hex; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.handlerlength) + TRESET]
|
||||
else:
|
||||
info = [TGREENLIT + ' :: Codehandler is of type "{}"'.format(args.handler),
|
||||
' :: Code allocation is 0x{} in hex; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.handlerlength) + TRESET]
|
||||
|
||||
for bit in info:
|
||||
print(bit)
|
||||
return
|
||||
|
||||
def patchGeckoLoader(fLoader, codehandler, tmp, dolfile, entrypoint):
|
||||
tmp.write(fLoader.read())
|
||||
geckoloader_offset = dolfile.getsize()
|
||||
figureLoaderData(tmp, fLoader, codehandler, entrypoint,
|
||||
[bytes.fromhex('{:X}'.format(dolfile._init)[:4]), bytes.fromhex('{:X}'.format(dolfile._init)[4:])])
|
||||
tmp.seek(0)
|
||||
dolfile._rawdata.seek(0, 2)
|
||||
dolfile._rawdata.write(tmp.read())
|
||||
dolfile.align(256)
|
||||
assertTextSections(dolfile, 6, [[int(entrypoint, 16), geckoloader_offset]])
|
||||
|
||||
'''Write game entry in DOL file header'''
|
||||
dolfile.setInitPoint(int(entrypoint, 16))
|
||||
|
||||
def patchLegacyHandler(codehandler, tmp, dolfile):
|
||||
handler_offset = dolfile.getsize()
|
||||
dolfile._rawdata.seek(0, 2)
|
||||
dolfile._rawdata.write(codehandler.codehandler.read() + codehandler.geckocodes.codelist.read())
|
||||
dolfile.align(256)
|
||||
assertTextSections(dolfile, 6, [[0x80001800, handler_offset]])
|
||||
determineCodeHook(dolfile, codehandler)
|
||||
|
||||
def assertTextSections(dolfile, textsections, sections_list):
|
||||
offset = len(dolfile._text) << 2
|
||||
if len(sections_list) + len(dolfile._text) <= 7:
|
||||
'''Write offset to each section in DOL file header'''
|
||||
dolfile._rawdata.seek(offset)
|
||||
for section_offset in sections_list:
|
||||
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(section_offset[1]))) #offset in file
|
||||
|
||||
dolfile._rawdata.seek(0x48 + offset)
|
||||
|
||||
'''Write in game memory addresses for each section in DOL file header'''
|
||||
for section_addr in sections_list:
|
||||
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(section_addr[0]))) #absolute address in game
|
||||
|
||||
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
|
||||
size_list = []
|
||||
for i, section_offset in enumerate(sections_list, start=1):
|
||||
if i > len(sections_list) - 1:
|
||||
size_list.append(dolfile.getsize() - section_offset[1])
|
||||
else:
|
||||
size_list.append(sections_list[i][1] - section_offset[1])
|
||||
|
||||
'''Write size of each section into DOL file header'''
|
||||
dolfile._rawdata.seek(0x90 + offset)
|
||||
for size in size_list:
|
||||
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(size)))
|
||||
else:
|
||||
shutil.rmtree('tmp')
|
||||
parser.error(TREDLIT + 'Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET)
|
||||
|
||||
def figureLoaderData(tmp, fLoader, codehandler, entrypoint, initpoint):
|
||||
global _allocation, _codehook
|
||||
|
||||
upperAddr, lowerAddr = entrypoint[:int(len(entrypoint)/2)], entrypoint[int(len(entrypoint)/2):]
|
||||
|
||||
tmp.seek(0)
|
||||
sample = tmp.read(4)
|
||||
while sample:
|
||||
if sample == HEAP: #Found keyword "HEAP". Goes with the resize of the heap
|
||||
tmp.seek(-4, 1)
|
||||
gpModInfoOffset = tmp.tell()
|
||||
if int(lowerAddr, 16) + gpModInfoOffset > 0x7FFF: #Absolute addressing
|
||||
gpModUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
|
||||
else:
|
||||
gpModUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16)))
|
||||
if _allocation == None:
|
||||
_allocation = '{:08X}'.format(codehandler.handlerlength + codehandler.geckocodes.size)
|
||||
tmp.write(bytes.fromhex(_allocation))
|
||||
|
||||
elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(get_size(fLoader))
|
||||
|
||||
elif sample == HANDLERSIZE: #Found keyword "HSIZ". Goes with the size of the codehandler
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(codehandler.handlerlength.to_bytes(4, byteorder='big', signed=True))
|
||||
|
||||
elif sample == CODESIZE: #Found keyword "CSIZ". Goes with the size of the codes
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(codehandler.geckocodes.size.to_bytes(4, byteorder='big', signed=True))
|
||||
|
||||
elif sample == CODEHOOK:
|
||||
tmp.seek(-4, 1)
|
||||
if _codehook == None:
|
||||
tmp.write(b'\x00\x00\x00\x00')
|
||||
else:
|
||||
tmp.write(bytes.fromhex(_codehook))
|
||||
|
||||
sample = tmp.read(4)
|
||||
|
||||
gpDiscOffset = int.from_bytes(get_size(tmp, -4), byteorder="big", signed=False)
|
||||
|
||||
if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing
|
||||
gpDiscUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
|
||||
else:
|
||||
gpDiscUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16)))
|
||||
|
||||
fillLoaderData(tmp, initpoint, lowerAddr, [gpModUpperAddr, gpModInfoOffset], [gpDiscUpperAddr, gpDiscOffset])
|
||||
tmp.seek(0, 2)
|
||||
tmp.write(codehandler.codehandler.read() + codehandler.geckocodes.codelist.read())
|
||||
|
||||
def fillLoaderData(tmp, _init, lowerAddr, gpModInfo, gpDiscInfo):
|
||||
tmp.seek(0)
|
||||
sample = tmp.read(2)
|
||||
while sample:
|
||||
if sample == DH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(gpDiscInfo[0])
|
||||
elif sample == DL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gpDiscInfo[1])))
|
||||
elif sample == GH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(gpModInfo[0])
|
||||
elif sample == GL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gpModInfo[1])))
|
||||
elif sample == IH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(_init[0])
|
||||
elif sample == IL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(_init[1])
|
||||
sample = tmp.read(2)
|
||||
|
||||
def sortArgFiles(fileA, fileB):
|
||||
global isText
|
||||
if os.path.splitext(fileA)[1].lower() == '.dol':
|
||||
dolFile = fileA
|
||||
elif os.path.splitext(fileB)[1].lower() == '.dol':
|
||||
dolFile = fileB
|
||||
else:
|
||||
parser.error('No dol file was passed\n')
|
||||
|
||||
if os.path.splitext(fileA)[1].lower() == '.gct':
|
||||
gctFile = fileA
|
||||
isText = False
|
||||
elif os.path.splitext(fileA)[1].lower() == '.txt':
|
||||
gctFile = fileA
|
||||
isText = True
|
||||
elif os.path.splitext(fileB)[1].lower() == '.gct':
|
||||
gctFile = fileB
|
||||
isText = False
|
||||
elif os.path.splitext(fileB)[1].lower() == '.txt':
|
||||
gctFile = fileB
|
||||
isText = True
|
||||
else:
|
||||
parser.error('Neither a gct or gecko text file was passed\n')
|
||||
return dolFile, gctFile
|
||||
|
||||
def determineCodeHook(dolfile, codehandler):
|
||||
global GCNVIHOOK, WIIVIHOOK, _codehook
|
||||
if _codehook == None:
|
||||
assertCodeHook(dolfile, codehandler, GCNVIHOOK, WIIVIHOOK)
|
||||
else:
|
||||
insertCodeHook(dolfile, codehandler, int(_codehook, 16))
|
||||
|
||||
def assertCodeHook(dolfile, codehandler, GCNVIHOOK, WIIVIHOOK):
|
||||
for offset, address, size in dolfile._text:
|
||||
dolfile.seek(address, 0)
|
||||
sample = dolfile.read(size)
|
||||
if sample.find(GCNVIHOOK) != -1 or sample.find(WIIVIHOOK):
|
||||
sample = dolfile.read(4)
|
||||
while sample != b'4E800020':
|
||||
sample = dolfile.read(4)
|
||||
dolfile.seek(-4, 1)
|
||||
insertCodeHook(dolfile, codehandler, dolfile.tell())
|
||||
|
||||
def insertCodeHook(dolfile, codehandler, address):
|
||||
dolfile.seek(address)
|
||||
if dolfile.read(4) == bytes.fromhex('4E800020'):
|
||||
lk = 1
|
||||
else:
|
||||
parser.error("Codehandler hook given is not a blr")
|
||||
dolfile.seek(-4, 1)
|
||||
dolfile.write(((codehandler.startaddress - address) & 0x3FFFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
isText = False
|
||||
if not os.path.isdir('tmp'):
|
||||
os.mkdir('tmp')
|
||||
|
||||
parser = argparse.ArgumentParser(prog='GeckoLoader',
|
||||
description='Process files and allocations for GeckoLoader',
|
||||
allow_abbrev=False)
|
||||
|
||||
parser.add_argument('file', help='First file')
|
||||
parser.add_argument('file2', help='Second file')
|
||||
parser.add_argument('-a', '--alloc',
|
||||
help='Define the size of the code allocation in hex, only applies when using the ARENA space',
|
||||
metavar ='SIZE')
|
||||
parser.add_argument('-i', '--init',
|
||||
help='Define where geckoloader is injected in hex',
|
||||
metavar='ADDRESS')
|
||||
parser.add_argument('-m', '--movecodes',
|
||||
help='''Choose if geckoloader moves the codes to OSArenaHi,
|
||||
or the legacy space. Default is "AUTO",
|
||||
which auto decides where to insert the codes''',
|
||||
default='AUTO',
|
||||
choices=['AUTO', 'LEGACY', 'ARENA'],
|
||||
metavar='TYPE')
|
||||
parser.add_argument('-tc', '--txtcodes',
|
||||
help='''What codes get parsed when a txt file is used.
|
||||
"ALL" makes all codes get parsed,
|
||||
"ACTIVE" makes only activated codes get parsed.''',
|
||||
default='active',
|
||||
metavar='TYPE')
|
||||
parser.add_argument('--handler',
|
||||
help='''Which codehandler gets used. "MINI" uses a smaller codehandler
|
||||
which only supports (0x, 2x, Cx, and E0 types) and supports up to
|
||||
600 lines of gecko codes when using the legacy codespace.
|
||||
"FULL" is the standard codehandler, supporting up to 350 lines of code
|
||||
in the legacy codespace.
|
||||
"MINI" should only be considered if using the legacy codespace''',
|
||||
default='FULL',
|
||||
choices=['MINI', 'FULL'],
|
||||
metavar='TYPE')
|
||||
parser.add_argument('--codehook',
|
||||
help='''Choose where the codehandler hooks to, needs to exist at a blr instruction''',
|
||||
metavar='ADDRESS')
|
||||
parser.add_argument('-q', '--quiet',
|
||||
help='Print nothing to the console',
|
||||
action='store_true')
|
||||
parser.add_argument('-v', '--verbose',
|
||||
help='Print extra info to the console',
|
||||
default=0,
|
||||
action='count')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.alloc:
|
||||
try:
|
||||
_allocation = '{:08X}'.format(int(args.alloc.lstrip('0x'), 16))
|
||||
except:
|
||||
parser.error('The allocation was invalid\n')
|
||||
else:
|
||||
_allocation = None
|
||||
|
||||
if args.codehook:
|
||||
if int(args.codehook, 16) < 0x80000000 or int(args.codehook, 16) >= 0x81800000:
|
||||
parser.error('The codehandler hook address was beyond bounds\n')
|
||||
else:
|
||||
try:
|
||||
_codehook = '{:08X}'.format(int(args.codehook.lstrip('0x'), 16))
|
||||
except:
|
||||
parser.error('The codehandler hook address was invalid\n')
|
||||
else:
|
||||
_codehook = None
|
||||
|
||||
if args.handler:
|
||||
if args.handler == 'MINI':
|
||||
codehandlerFile = 'codehandler-mini.bin'
|
||||
else:
|
||||
codehandlerFile = 'codehandler.bin'
|
||||
else:
|
||||
codehandlerFile = 'codehandler.bin'
|
||||
|
||||
dolFile, gctFile = sortArgFiles(args.file, args.file2)
|
||||
|
||||
HEAP = b'HEAP'
|
||||
LOADERSIZE = b'LSIZ'
|
||||
HANDLERSIZE = b'HSIZ'
|
||||
CODESIZE = b'CSIZ'
|
||||
CODEHOOK = b'HOOK'
|
||||
DH = b'DH'
|
||||
DL = b'DL'
|
||||
GH = b'GH'
|
||||
GL = b'GL'
|
||||
IH = b'IH'
|
||||
IL = b'IL'
|
||||
|
||||
|
||||
WIIVIHOOK = b'7CE33B783887003438A7003838C7004C'
|
||||
GCNVIHOOK = b'7C030034388300205485083C7C7F2A14A00300007C7D2A1420A4003FB0030000'
|
||||
|
||||
try:
|
||||
if not os.path.isdir('BUILD'):
|
||||
os.mkdir('BUILD')
|
||||
|
||||
if not os.path.isfile(dolFile):
|
||||
parser.error(dolFile + ' Does not exist')
|
||||
|
||||
if not os.path.isfile(gctFile):
|
||||
parser.error(gctFile + ' Does not exist')
|
||||
|
||||
time1 = time.time()
|
||||
|
||||
build(gctFile, dolFile, codehandlerFile, _allocation)
|
||||
|
||||
shutil.rmtree('tmp')
|
||||
if not args.quiet:
|
||||
print(TGREENLIT + '\n :: Compiled in {:0.4f} seconds!\n'.format(time.time() - time1) + TRESET)
|
||||
|
||||
except FileNotFoundError as err:
|
||||
parser.error(err)
|
||||
sys.exit(1)
|
BIN
bin/codehandler-mini.bin
Normal file
BIN
bin/codehandler-mini.bin
Normal file
Binary file not shown.
BIN
bin/codehandler.bin
Normal file
BIN
bin/codehandler.bin
Normal file
Binary file not shown.
BIN
bin/geckoloader.bin
Normal file
BIN
bin/geckoloader.bin
Normal file
Binary file not shown.
180
dolreader.py
Normal file
180
dolreader.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
import struct
|
||||
from io import BytesIO, RawIOBase
|
||||
|
||||
|
||||
def read_ubyte(f):
|
||||
return struct.unpack("B", f.read(1))[0]
|
||||
|
||||
def read_uint32(f):
|
||||
return struct.unpack(">I", f.read(4))[0]
|
||||
|
||||
def write_uint32(f, val):
|
||||
f.write(struct.pack(">I", val))
|
||||
|
||||
class DolFile(object):
|
||||
def __init__(self, f):
|
||||
self._rawdata = BytesIO(f.read())
|
||||
fileoffset = 0
|
||||
addressoffset = 0x48
|
||||
sizeoffset = 0x90
|
||||
|
||||
self._text = []
|
||||
self._data = []
|
||||
|
||||
nomoretext = False
|
||||
nomoredata = False
|
||||
|
||||
self._current_end = None
|
||||
|
||||
# Read text and data section addresses and sizes
|
||||
for i in range(18):
|
||||
f.seek(fileoffset+i*4)
|
||||
offset = read_uint32(f)
|
||||
f.seek(addressoffset+i*4)
|
||||
address = read_uint32(f)
|
||||
f.seek(sizeoffset+i*4)
|
||||
size = read_uint32(f)
|
||||
|
||||
if i <= 6:
|
||||
if offset == 0:
|
||||
nomoretext = True
|
||||
elif not nomoretext:
|
||||
self._text.append((offset, address, size))
|
||||
# print("text{0}".format(i), hex(offset), hex(address), hex(size))
|
||||
else:
|
||||
#datanum = i - 7
|
||||
if offset == 0:
|
||||
nomoredata = True
|
||||
elif not nomoredata:
|
||||
self._data.append((offset, address, size))
|
||||
# print("data{0}".format(datanum), hex(offset), hex(address), hex(size))
|
||||
|
||||
f.seek(0xD8)
|
||||
self._bssoffset = read_uint32(f)
|
||||
self._bsssize = read_uint32(f)
|
||||
self._init = read_uint32(f)
|
||||
|
||||
self.bss = BytesIO(self._rawdata.getbuffer()[self._bssoffset:self._bssoffset+self._bsssize])
|
||||
|
||||
self._curraddr = self._text[0][1]
|
||||
self.seek(self._curraddr)
|
||||
f.seek(0)
|
||||
|
||||
# Internal function for
|
||||
def _resolve_address(self, gc_addr):
|
||||
for offset, address, size in self._text:
|
||||
if address <= gc_addr < address+size:
|
||||
return offset, address, size
|
||||
for offset, address, size in self._data:
|
||||
if address <= gc_addr < address+size:
|
||||
return offset, address, size
|
||||
|
||||
raise RuntimeError("Unmapped address: {0}".format(hex(gc_addr)))
|
||||
|
||||
# Unsupported: Reading an entire dol file
|
||||
# Assumption: A read should not go beyond the current section
|
||||
def read(self, size):
|
||||
if self._curraddr + size >= self._current_end:
|
||||
raise RuntimeError("Read goes over current section")
|
||||
|
||||
self._curraddr += size
|
||||
return self._rawdata.read(size)
|
||||
|
||||
# Assumption: A write should not go beyond the current section
|
||||
def write(self, data):
|
||||
if self._curraddr + len(data) >= self._current_end:
|
||||
raise RuntimeError("Write goes over current section")
|
||||
|
||||
self._rawdata.write(data)
|
||||
self._curraddr += len(data)
|
||||
|
||||
def seek(self, where, whence=0):
|
||||
if whence == 0:
|
||||
offset, gc_start, gc_size = self._resolve_address(where)
|
||||
self._rawdata.seek(offset + (where-gc_start))
|
||||
|
||||
self._curraddr = where
|
||||
self._current_end = gc_start + gc_size
|
||||
elif whence == 1:
|
||||
offset, gc_start, gc_size = self._resolve_address(self._curraddr + where)
|
||||
self._rawdata.seek(offset + ((self._curraddr + where)-gc_start))
|
||||
|
||||
self._curraddr += where
|
||||
self._current_end = gc_start + gc_size
|
||||
else:
|
||||
raise RuntimeError("Unsupported whence type '{}'".format(whence))
|
||||
|
||||
def tell(self):
|
||||
return self._curraddr
|
||||
|
||||
def save(self, f):
|
||||
f.seek(0)
|
||||
f.write(self._rawdata.getbuffer())
|
||||
|
||||
def getsize(self):
|
||||
oldpos = self._rawdata.tell()
|
||||
self._rawdata.seek(0, 2)
|
||||
size = self._rawdata.tell()
|
||||
self._rawdata.seek(oldpos)
|
||||
return size
|
||||
|
||||
def getalignment(self, alignment):
|
||||
size = self.getsize()
|
||||
|
||||
if size % alignment != 0:
|
||||
return alignment - (size % alignment)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def setInitPoint(self, address):
|
||||
oldpos = self._rawdata.tell()
|
||||
self._rawdata.seek(0xE0)
|
||||
self._rawdata.write(bytes.fromhex('{:08X}'.format(address)))
|
||||
self._rawdata.seek(oldpos)
|
||||
|
||||
def align(self, alignment):
|
||||
oldpos = self._rawdata.tell()
|
||||
self._rawdata.seek(0, 2)
|
||||
self._rawdata.write(bytes.fromhex("00" * self.getalignment(alignment)))
|
||||
self._rawdata.seek(oldpos)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage (reading some enemy info from the Pikmin 2 demo from US demo disc 17)
|
||||
|
||||
def read_string(f):
|
||||
start = f.tell()
|
||||
length = 0
|
||||
while f.read(1) != b"\x00":
|
||||
length += 1
|
||||
if length > 100:
|
||||
break
|
||||
|
||||
f.seek(start)
|
||||
return f.read(length)
|
||||
|
||||
entries = []
|
||||
|
||||
with open("main.dol", "rb") as f:
|
||||
dol = DolFile(f)
|
||||
|
||||
start = 0x804ac478 # memory address to start of enemy info table.
|
||||
|
||||
for i in range(100):
|
||||
dol.seek(start+0x34*i, 0)
|
||||
|
||||
# string offset would normally be pointing to a location in RAM and thus
|
||||
# wouldn't be suitable as a file offset but because the seek function of DolFile
|
||||
# takes into account the memory address at which the data sections of the dol file
|
||||
# is loaded, we can use the string offset directly..
|
||||
stringoffset = read_uint32(dol)
|
||||
identifier = read_ubyte(dol)
|
||||
dol.seek(stringoffset, 0)
|
||||
name = read_string(dol)
|
||||
|
||||
entries.append((identifier,i, name, hex(stringoffset)))
|
||||
|
||||
entries.sort(key=lambda x: x[0])
|
||||
for val in entries:
|
||||
print(hex(val[0]), val)
|
221
loader.cpp
Normal file
221
loader.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*Credits to riidefi for hook code, cache asm, and teaching me C*/
|
||||
|
||||
#define dcbst(_val) asm volatile("dcbst 0, %0" \
|
||||
: \
|
||||
: "r"(_val))
|
||||
#define dcbf(_val) asm volatile("dcbf 0, %0" \
|
||||
: \
|
||||
: "r"(_val))
|
||||
#define icbi(_val) asm volatile("icbi 0, %0" \
|
||||
: \
|
||||
: "r"(_val))
|
||||
|
||||
#define call(addr) ((void (*)(...))addr)
|
||||
#define MEM1_START 0x80000000
|
||||
#define MEM1_END 0x81800000
|
||||
#define CODEHANDLER 0x800018A8
|
||||
#define GCT_MAGIC 0x00D0C0DE
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned char u8;
|
||||
typedef int s32;
|
||||
typedef short s16;
|
||||
typedef char s8;
|
||||
|
||||
__attribute__((noreturn)) int main();
|
||||
|
||||
struct CodeList {
|
||||
u16 mBaseASM;
|
||||
u16 mUpperBase;
|
||||
u16 mOffsetASM;
|
||||
u16 mLowerOffset;
|
||||
};
|
||||
|
||||
struct Info {
|
||||
const u32 allocsize;
|
||||
const u32 loaderSize;
|
||||
const u32 handlerSize;
|
||||
const u32 codeSize;
|
||||
const u32* codehandlerHook;
|
||||
const u32 wiiVIHook[4];
|
||||
const u32 gcnVIHook[8];
|
||||
};
|
||||
|
||||
struct DiscInfo {
|
||||
const u8 mDiscID;
|
||||
const u16 mGameCode;
|
||||
const u8 mRegionCode;
|
||||
const u16 mMakerCode;
|
||||
const u8 mDiscNumber;
|
||||
const u8 mDiscVersion;
|
||||
const u8 mAudioStreaming;
|
||||
const u8 mStreamBufferSize;
|
||||
const u8 _00[12];
|
||||
const u32 mWiiMagic;
|
||||
const u32 mGCNMagic;
|
||||
const u32 _01[2];
|
||||
u32 mRAMSize;
|
||||
const u32 _02[2];
|
||||
u32* mHeapPointer;
|
||||
u32 mHeapMirror;
|
||||
u32 mFstSize;
|
||||
u32 mData[0x30D0 / 4];
|
||||
u32 mWiiHeap;
|
||||
};
|
||||
|
||||
Info gpModInfo = {
|
||||
0, /*This is the code allocation*/
|
||||
0, /*This is the size of the GeckoLoader*/
|
||||
0, /*This is the size of the codehandler*/
|
||||
0, /*This is the size of the GeckoLoader + the codelist*/
|
||||
0, /*This is the codehandler hook address*/
|
||||
{ 0x7CE33B78, 0x38870034, 0x38A70038, 0x38C7004C },
|
||||
{ 0x7C030034, 0x38830020, 0x5485083C, 0x7C7F2A14, 0xA0030000, 0x7C7D2A14, 0x20A4003F, 0xB0030000 },
|
||||
};
|
||||
|
||||
DiscInfo* gpDiscResources = (DiscInfo*)MEM1_START;
|
||||
|
||||
static inline void flushAddr(void* addr)
|
||||
{
|
||||
dcbf(addr);
|
||||
icbi(addr);
|
||||
}
|
||||
|
||||
static inline void directWrite(u32* addr, u32 value)
|
||||
{
|
||||
*addr = value;
|
||||
flushAddr(addr);
|
||||
}
|
||||
|
||||
/*This constructs a branch instruction. &TO = ((TO - FROM) & MAX_OFFSET) | BRANCH_TYPE | !!isLink*/
|
||||
static inline void directBranchEx(void* addr, void* ptr, bool lk)
|
||||
{
|
||||
directWrite((u32*)(addr), ((((u32)(ptr) - (u32)(addr)) & 0x3ffffff) | 0x48000000 | !!lk));
|
||||
}
|
||||
|
||||
static inline u32* findArrayInstance(u32* start, const u32 end, u32 arrayLength, const u32* hookData)
|
||||
{
|
||||
u32 index = 0;
|
||||
|
||||
/*Loop through the games RAM, make sure we don't find our own hook data by accident*/
|
||||
for (u32 i = 0; (u32)&start[i] < end; ++i) {
|
||||
/*If the data matches, increase the index counter and continue search,
|
||||
else set index to 0 and continue searching*/
|
||||
if (start[i] == hookData[index])
|
||||
++index;
|
||||
else
|
||||
index = 0;
|
||||
|
||||
/*If the data has matched the whole array, return the address of the match*/
|
||||
if (index >= (arrayLength) && ((u32)&start[i] < (u32)&gpModInfo || (u32)&start[i] > (u32)&gpModInfo + sizeof(Info)))
|
||||
return (u32*)&start[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline u32* findU32Instance(u32* start, u32 end, u32 hookData)
|
||||
{
|
||||
for (u32 i = 0; (u32)&start[i] < end; ++i) {
|
||||
if (start[i] == hookData) {
|
||||
return (u32*)&start[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*Find VI hook for Game*/
|
||||
static inline u32* findVIHook(u32* start, const u32 end)
|
||||
{
|
||||
const u32* hookData;
|
||||
u32 arrayLength;
|
||||
|
||||
/*If the game is built for the Wii, set the hookdata to be the Wii variant*/
|
||||
if (gpDiscResources->mWiiMagic) {
|
||||
hookData = (const u32*)gpModInfo.wiiVIHook;
|
||||
arrayLength = sizeof(gpModInfo.wiiVIHook) / sizeof(u32);
|
||||
} else /*The game is built for the GCN, set the hookdata to be the GCN variant*/
|
||||
{
|
||||
hookData = (const u32*)gpModInfo.gcnVIHook;
|
||||
arrayLength = sizeof(gpModInfo.gcnVIHook) / sizeof(u32);
|
||||
}
|
||||
return findArrayInstance(start, end, arrayLength, hookData);
|
||||
}
|
||||
|
||||
/*Call this after findFunction, finds the address of the first instance
|
||||
of value hookInstruction, and hooks it to the pointer hookTo*/
|
||||
static inline void hookFunction(u32* start, u32 hookInstruction, u32 hookTo, bool isLink)
|
||||
{
|
||||
int i = 0;
|
||||
while (start[i] != hookInstruction) {
|
||||
++i;
|
||||
}
|
||||
directBranchEx((u32*)(&start[i]), (void*)(hookTo), isLink);
|
||||
}
|
||||
|
||||
/*Reallocate the games internal memory heap based on the console
|
||||
the game is for, to make space for our codes*/
|
||||
static inline void setHeap(u32 alloc)
|
||||
{
|
||||
if (gpDiscResources->mWiiMagic) {
|
||||
gpDiscResources->mHeapPointer = (u32*)((u32)gpDiscResources->mWiiHeap - alloc);
|
||||
gpDiscResources->mWiiHeap = (u32)gpDiscResources->mHeapPointer;
|
||||
} else {
|
||||
gpDiscResources->mHeapPointer = (u32*)((u32)gpDiscResources->mHeapPointer - alloc);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void memCopy(u32* to, u32* from, s32 size)
|
||||
{
|
||||
for (s32 i = 0; i < size; ++i) {
|
||||
*to++ = *from++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void flushCacheRange(u8* addr, s32 size)
|
||||
{
|
||||
if ((u32)addr & 31) size += 32;
|
||||
else size += 31;
|
||||
|
||||
for (u32 i = 0; i < (size >> 5); ++i) {
|
||||
flushAddr((void*)addr);
|
||||
addr += 32;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool initMods(DiscInfo* gpDiscResources)
|
||||
{
|
||||
setHeap(gpModInfo.allocsize); /*Reallocate the internal heap*/
|
||||
|
||||
/*Change codelist pointer to the new address in the allocation*/
|
||||
CodeList* codelistPointer = (CodeList*)((u32)&gpModInfo + sizeof(gpModInfo) + 0xFC);
|
||||
codelistPointer->mUpperBase = (((u32)gpDiscResources->mHeapPointer + gpModInfo.handlerSize) >> 16) & 0xFFFF;
|
||||
codelistPointer->mLowerOffset = ((u32)gpDiscResources->mHeapPointer + gpModInfo.handlerSize) & 0xFFFF;
|
||||
|
||||
/*Copy codelist to the new allocation*/
|
||||
memCopy(gpDiscResources->mHeapPointer, (u32*)((u32)&gpModInfo + sizeof(gpModInfo) + 4), (gpModInfo.handlerSize + gpModInfo.codeSize) >> 2);
|
||||
|
||||
/*Update the cache, so that the instructions fully update*/
|
||||
flushAddr(&codelistPointer->mBaseASM);
|
||||
|
||||
if (!gpModInfo.codehandlerHook || *gpModInfo.codehandlerHook != 0x4E800020) {
|
||||
u32* functionAddr = findVIHook((u32*)MEM1_START, MEM1_END);
|
||||
if (functionAddr == nullptr)
|
||||
return false;
|
||||
|
||||
hookFunction(functionAddr, 0x4E800020, (u32)gpDiscResources->mHeapPointer + 0xA8, false);
|
||||
} else {
|
||||
directBranchEx((void*)gpModInfo.codehandlerHook, (void*)((u32)gpDiscResources->mHeapPointer + 0xA8), false);
|
||||
}
|
||||
|
||||
flushCacheRange((u8*)gpDiscResources->mHeapPointer, gpModInfo.handlerSize + gpModInfo.codeSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
if ((gpDiscResources->mWiiMagic || gpDiscResources->mGCNMagic) && initMods(gpDiscResources) == true)
|
||||
call((void*)((u32)(gpDiscResources->mHeapPointer) + 0xA8))(); /*Call the codehandler if successful*/
|
||||
|
||||
call(0xDEADBEEF)(); /*Call the game start*/
|
||||
}
|
Reference in a new issue