2020-06-03 16:12:29 +09:00
|
|
|
#Written by JoshuaMK 2020
|
2020-08-18 10:38:37 +09:00
|
|
|
#Start.dol EclipseCodes -m ARENA --codehook 802A80D0 -o -vv
|
2020-06-03 16:12:29 +09:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import shutil
|
2020-07-22 14:32:25 +09:00
|
|
|
import random
|
2020-08-17 05:04:47 +09:00
|
|
|
import argparse
|
2020-06-03 16:12:29 +09:00
|
|
|
|
2020-08-17 05:04:47 +09:00
|
|
|
from kernel import *
|
2020-08-18 10:38:37 +09:00
|
|
|
from access import *
|
2020-06-03 16:12:29 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
_VERSION_ = "v5.0.0"
|
2020-07-22 14:32:25 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
def determine_codehook(dolFile: DolFile, codehandler: CodeHandler):
|
|
|
|
if codehandler.hookAddress == None:
|
|
|
|
assert_code_hook(dolFile, codehandler, GCNVIHOOK, WIIVIHOOK)
|
2020-07-21 15:00:39 +09:00
|
|
|
else:
|
2020-08-18 10:38:37 +09:00
|
|
|
insert_code_hook(dolFile, codehandler, codehandler.hookAddress)
|
2020-07-21 15:00:39 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
def assert_code_hook(dolFile: DolFile, codehandler: CodeHandler, gcnhook: bytes, wiihook: bytes):
|
|
|
|
for offset, address, size in dolFile.textSections:
|
|
|
|
dolFile.seek(address, 0)
|
|
|
|
sample = dolFile.read(size)
|
2020-07-21 15:00:39 +09:00
|
|
|
|
|
|
|
result = sample.find(gcnhook)
|
|
|
|
if result >= 0:
|
2020-08-18 10:38:37 +09:00
|
|
|
dolFile.seek(address, 0)
|
|
|
|
dolFile.seek(result, 1)
|
2020-07-21 15:00:39 +09:00
|
|
|
else:
|
|
|
|
result = sample.find(wiihook)
|
|
|
|
if result >= 0:
|
2020-08-18 10:38:37 +09:00
|
|
|
dolFile.seek(address, 0)
|
|
|
|
dolFile.seek(result, 1)
|
2020-07-21 15:00:39 +09:00
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
sample = dolFile.read(4)
|
2020-07-21 15:00:39 +09:00
|
|
|
while sample != b'\x4E\x80\x00\x20':
|
2020-08-18 10:38:37 +09:00
|
|
|
sample = dolFile.read(4)
|
2020-07-21 15:00:39 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
dolFile.seek(-4, 1)
|
|
|
|
codehandler.hookAddress = dolFile.tell()
|
2020-07-21 15:00:39 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
insert_code_hook(dolFile, codehandler, codehandler.hookAddress)
|
2020-07-21 15:00:39 +09:00
|
|
|
return
|
|
|
|
|
|
|
|
parser.error('Failed to find a hook address. Try using option --codehook to use your own address')
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
def insert_code_hook(dolFile: DolFile, codehandler: CodeHandler, address: int):
|
|
|
|
dolFile.seek(address)
|
2020-07-21 15:00:39 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
if dolFile.read(4) != b'\x4E\x80\x00\x20':
|
2020-07-21 15:00:39 +09:00
|
|
|
parser.error("Codehandler hook given is not a blr")
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
dolFile.seek(-4, 1)
|
|
|
|
dolFile.insert_branch(codehandler.startAddress, address, lk=0)
|
2020-07-21 15:00:39 +09:00
|
|
|
|
2020-08-17 05:04:47 +09:00
|
|
|
def sort_file_args(fileA, fileB):
|
2020-06-03 16:12:29 +09:00
|
|
|
if os.path.splitext(fileA)[1].lower() == '.dol':
|
|
|
|
dolFile = fileA
|
2020-07-21 16:07:13 +09:00
|
|
|
gctFile = fileB
|
2020-06-03 16:12:29 +09:00
|
|
|
elif os.path.splitext(fileB)[1].lower() == '.dol':
|
|
|
|
dolFile = fileB
|
|
|
|
gctFile = fileA
|
|
|
|
else:
|
2020-07-21 16:07:13 +09:00
|
|
|
parser.error('No dol file was passed\n')
|
|
|
|
return dolFile, gctFile
|
2020-06-03 16:12:29 +09:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-08-18 10:38:37 +09:00
|
|
|
parser = argparse.ArgumentParser(prog='GeckoLoader ' + _VERSION_,
|
2020-06-03 16:12:29 +09:00
|
|
|
description='Process files and allocations for GeckoLoader',
|
|
|
|
allow_abbrev=False)
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
parser.add_argument('dolFile', help='DOL file')
|
|
|
|
parser.add_argument('codelist', help='Folder or Gecko GCT|TXT file')
|
2020-06-03 16:12:29 +09:00
|
|
|
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')
|
2020-06-26 15:56:07 +09:00
|
|
|
parser.add_argument('-o', '--optimize',
|
|
|
|
help='''Optimizes the codelist by directly patching qualifying
|
|
|
|
ram writes into the dol file, and removing them from the codelist''',
|
|
|
|
action='store_true')
|
2020-08-18 10:38:37 +09:00
|
|
|
parser.add_argument('-p', '--protect',
|
|
|
|
help='''Targets and nullifies the standard codehandler provided by loaders and Dolphin Emulator,
|
|
|
|
only applies when the ARENA is used (Can be forced using option (-m|--movecodes))''',
|
|
|
|
action='store_true')
|
|
|
|
parser.add_argument('--dest',
|
|
|
|
help='Target path to put the modified DOL, can be a folder or file',
|
|
|
|
metavar='PATH')
|
|
|
|
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
version = _VERSION_.rjust(9, ' ')
|
|
|
|
logo = [' ',
|
|
|
|
' ╔═════════════════════════════════════════════════╗ ',
|
|
|
|
' ║ ║ ',
|
|
|
|
' ║ ┌───┬───┬───┬┐┌─┬───┬┐ ┌───┬───┬───┬───┬───┐ ║ ',
|
|
|
|
' ║ │┌─┐│┌──┤┌─┐│││┌┤┌─┐││ │┌─┐│┌─┐├┐┌┐│┌──┤┌─┐│ ║ ',
|
|
|
|
' ║ ││ └┤└──┤│ └┤└┘┘││ │││ ││ │││ ││││││└──┤└─┘│ ║ ',
|
|
|
|
' ║ ││┌─┤┌──┤│ ┌┤┌┐│││ │││ ┌┤│ ││└─┘│││││┌──┤┌┐┌┘ ║ ',
|
|
|
|
' ║ │└┴─│└──┤└─┘│││└┤└─┘│└─┘│└─┘│┌─┐├┘└┘│└──┤││└┐ ║ ',
|
|
|
|
' ║ └───┴───┴───┴┘└─┴───┴───┴───┴┘ └┴───┴───┴┘└─┘ ║ ',
|
|
|
|
' ║ ║ ',
|
|
|
|
' ║ ┌┬───┬───┬┐ ┌┬┐ ┌┬───┬─┐┌─┬┐┌─┐ ║ ',
|
|
|
|
' ║ ││┌─┐│┌─┐││ │││ ││┌─┐│ └┘ │││┌┘ ║ ',
|
|
|
|
' ║ │││ ││└──┤└─┘││ │││ ││┌┐┌┐│└┘┘ ║ ',
|
|
|
|
' ║ ┌──┐┌┐│││ │├──┐│┌─┐││ ││└─┘││││││┌┐│ ┌──┐ ║ ',
|
|
|
|
' ║ └──┘│└┘│└─┘│└─┘││ ││└─┘│┌─┐││││││││└┐└──┘ ║ ',
|
|
|
|
' ║ └──┴───┴───┴┘ └┴───┴┘ └┴┘└┘└┴┘└─┘ ║ ',
|
|
|
|
f' ║ {version} ║ ',
|
|
|
|
' ╚═════════════════════════════════════════════════╝ ',
|
|
|
|
' ']
|
|
|
|
for line in logo:
|
|
|
|
print(line)
|
|
|
|
sys.exit(0)
|
2020-06-03 16:12:29 +09:00
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if args.alloc:
|
|
|
|
try:
|
2020-08-18 10:38:37 +09:00
|
|
|
_allocation = int(args.alloc, 16)
|
|
|
|
except ValueError:
|
2020-06-03 16:12:29 +09:00
|
|
|
parser.error('The allocation was invalid\n')
|
|
|
|
else:
|
|
|
|
_allocation = None
|
|
|
|
|
|
|
|
if args.codehook:
|
2020-08-18 10:38:37 +09:00
|
|
|
if 0x80000000 > int(args.codehook, 16) >= 0x81800000:
|
2020-06-03 16:12:29 +09:00
|
|
|
parser.error('The codehandler hook address was beyond bounds\n')
|
|
|
|
else:
|
|
|
|
try:
|
2020-08-18 10:38:37 +09:00
|
|
|
_codehook = int(args.codehook, 16)
|
|
|
|
except ValueError:
|
2020-06-03 16:12:29 +09:00
|
|
|
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'
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
#dolFile, gctFile = sort_file_args(args.fileA, args.fileB)
|
2020-06-03 16:12:29 +09:00
|
|
|
|
|
|
|
try:
|
|
|
|
if not os.path.isdir('BUILD'):
|
|
|
|
os.mkdir('BUILD')
|
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
if not os.path.isfile(args.dolFile):
|
2020-07-21 16:07:13 +09:00
|
|
|
parser.error('File "' + dolFile + '" does not exist')
|
2020-06-03 16:12:29 +09:00
|
|
|
|
2020-08-18 10:38:37 +09:00
|
|
|
if not os.path.exists(args.codelist):
|
2020-07-21 16:07:13 +09:00
|
|
|
parser.error('File/folder "' + gctFile + '" does not exist')
|
2020-06-03 16:12:29 +09:00
|
|
|
|
2020-07-22 14:32:25 +09:00
|
|
|
tmpdir = ''.join(random.choice('1234567890-_abcdefghijklomnpqrstuvwxyz') for i in range(6)) + '-GeckoLoader'
|
|
|
|
|
|
|
|
if not os.path.isdir(tmpdir):
|
|
|
|
os.mkdir(tmpdir)
|
2020-08-18 10:38:37 +09:00
|
|
|
|
|
|
|
with open(resource_path(os.path.join('bin', os.path.normpath(codehandlerFile))), 'rb') as handler:
|
|
|
|
codehandler = CodeHandler(handler)
|
|
|
|
codehandler.allocation = _allocation
|
|
|
|
codehandler.hookAddress = _codehook
|
|
|
|
codehandler.includeAll = args.txtcodes
|
|
|
|
|
|
|
|
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile:
|
|
|
|
geckoKernel = KernelLoader(kernelfile)
|
|
|
|
|
|
|
|
if (args.init is not None):
|
|
|
|
geckoKernel.initAddress = args.init.lstrip("0x").upper()
|
|
|
|
|
|
|
|
geckoKernel.codeLocation = args.movecodes
|
|
|
|
geckoKernel.verbosity = args.verbose
|
|
|
|
geckoKernel.quiet = args.quiet
|
|
|
|
|
|
|
|
with open(os.path.normpath(args.dolFile), 'rb') as dol:
|
|
|
|
dolFile = DolFile(dol)
|
|
|
|
|
|
|
|
codehandler.optimizeList = args.optimize
|
|
|
|
|
|
|
|
if args.protect and args.movecodes == "ARENA":
|
|
|
|
geckoKernel.protect_game(codehandler)
|
|
|
|
|
|
|
|
if args.dest:
|
|
|
|
if os.path.splitext(args.dest)[1] == "":
|
|
|
|
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/'), os.path.basename(args.dolFile)))
|
|
|
|
else:
|
|
|
|
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/')))
|
|
|
|
else:
|
|
|
|
dest = os.path.normpath(os.path.join(os.getcwd(), "BUILD", os.path.basename(args.dolFile)))
|
|
|
|
|
|
|
|
if not os.path.exists(dest) and os.path.dirname(dest) not in ('', '/'):
|
|
|
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
|
|
|
|
|
|
geckoKernel.build(args.codelist, dolFile, codehandler, tmpdir, dest)
|
2020-06-03 16:12:29 +09:00
|
|
|
|
2020-07-22 14:32:25 +09:00
|
|
|
shutil.rmtree(tmpdir)
|
2020-08-18 10:38:37 +09:00
|
|
|
sys.exit(0)
|
2020-06-03 16:12:29 +09:00
|
|
|
|
|
|
|
except FileNotFoundError as err:
|
|
|
|
parser.error(err)
|
|
|
|
sys.exit(1)
|