#Written by JoshuaMK 2020 import argparse import os import random import shutil import sys import atexit from distutils.version import LooseVersion from dolreader import DolFile from kernel import CodeHandler, KernelLoader from tools import CommandLineParser, color_text from versioncheck import Updater 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 = '' __version__ = 'v6.2.0' def resource_path(relative_path: str): """ Get absolute path to resource, works for dev and for PyInstaller """ base_path = os.path.dirname(os.path.realpath(sys.argv[0])) return os.path.join(base_path, relative_path) TMPDIR = resource_path(''.join(random.choice('1234567890-_abcdefghijklomnpqrstuvwxyz') for i in range(6)) + '-GeckoLoader') def sort_file_args(fileA, fileB): if os.path.splitext(fileA)[1].lower() == '.dol': dolFile = fileA gctFile = fileB elif os.path.splitext(fileB)[1].lower() == '.dol': dolFile = fileB gctFile = fileA else: parser.error(color_text('No dol file was passed\n', defaultColor=TREDLIT)) return dolFile, gctFile @atexit.register def clean_tmp_resources(): if os.path.isdir(TMPDIR): shutil.rmtree(TMPDIR) class GeckoLoaderCli(CommandLineParser): def __init__(self, name, version=None, description=''): super().__init__(prog=(name+' '+version), description=description, allow_abbrev=False) self.__version__ = version self.__doc__ = description self.add_argument('dolfile', help='DOL file') self.add_argument('codelist', help='Folder or Gecko GCT|TXT file') self.add_argument('-a', '--alloc', help='Define the size of the code allocation in hex, only applies when using the ARENA space', metavar ='SIZE') self.add_argument('-i', '--init', help='Define where GeckoLoader is initialized in hex', metavar='ADDRESS') self.add_argument('-m', '--movecodes', help='''["AUTO", "LEGACY", "ARENA"] 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') self.add_argument('-tc', '--txtcodes', help='''["ACTIVE", "ALL"] What codes get parsed when a txt file is used. "ALL" makes all codes get parsed, "ACTIVE" makes only activated codes get parsed. "ACTIVE" is the default''', default='ACTIVE', metavar='TYPE') self.add_argument('--handler', help='''["MINI", "FULL"] 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. "FULL" is the default''', default='FULL', choices=['MINI', 'FULL'], metavar='TYPE') self.add_argument('--hooktype', help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended, although "PAD" can work just as well. "VI" is the default''', default='VI', choices=['VI', 'GX', 'PAD'], metavar='HOOK') self.add_argument('--hookaddress', help='Choose where the codehandler hooks to in hex, overrides auto hooks', metavar='ADDRESS') self.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') self.add_argument('-p', '--protect', help='''Targets and nullifies the standard codehandler provided by loaders and Dolphin Emulator, only applies when the ARENA is used''', action='store_true') self.add_argument('--dest', help='Target path to put the modified DOL, can be a folder or file', metavar='PATH') self.add_argument('--check-update', help='''Checks to see if a new update exists on the GitHub Repository releases page, this option overrides all other commands.''', action='store_true') self.add_argument('--encrypt', help='Encrypts the codelist on compile time, helping to slow the snoopers', action='store_true') self.add_argument('-q', '--quiet', help='Print nothing to the console', action='store_true') self.add_argument('-v', '--verbose', help='Print extra info to the console', default=0, action='count') def __str__(self): return self.__doc__ def print_splash(self): helpMessage = 'Try option -h for more info on this program'.center(64, ' ') version = self.__version__.rjust(9, ' ') logo = [' ', ' ╔═══════════════════════════════════════════════════════════╗ ', ' ║ ║ ', ' ║ ┌───┐┌───┐┌───┐┌┐┌─┐┌───┐┌┐ ┌───┐┌───┐┌───┐┌───┐┌───┐ ║ ', ' ║ │┌─┐││┌──┘│┌─┐││││┌┘│┌─┐│││ │┌─┐││┌─┐│└┐┌┐││┌──┘│┌─┐│ ║ ', ' ║ ││ └┘│└──┐││ └┘│└┘┘ ││ ││││ ││ ││││ ││ │││││└──┐│└─┘│ ║ ', ' ║ ││┌─┐│┌──┘││ ┌┐│┌┐│ ││ ││││ ┌┐││ │││└─┘│ │││││┌──┘│┌┐┌┘ ║ ', ' ║ │└┴─││└──┐│└─┘││││└┐│└─┘││└─┘││└─┘││┌─┐│┌┘└┘││└──┐│││└┐ ║ ', ' ║ └───┘└───┘└───┘└┘└─┘└───┘└───┘└───┘└┘ └┘└───┘└───┘└┘└─┘ ║ ', ' ║ ║ ', ' ║ ┌┐┌───┐┌───┐┌┐ ┌┐┌┐ ┌┐┌───┐┌─┐┌─┐┌┐┌─┐ ║ ', ' ║ │││┌─┐││┌─┐│││ ││││ │││┌─┐││ └┘ ││││┌┘ ║ ', ' ║ ││││ │││└──┐│└─┘│││ ││││ │││┌┐┌┐││└┘┘ ║ ', ' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ', ' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ', ' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ', f' ║ {version} ║ ', ' ╚═══════════════════════════════════════════════════════════╝ ', ' ', ' GeckoLoader is a cli tool for allowing extended ', ' gecko code space in all Wii and GC games. ', ' ', f'{helpMessage}', ' '] for line in logo: print(color_text(line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT)) sys.exit(0) def check_updates(self): repoChecker = Updater('JoshuaMKW', 'GeckoLoader') tag, status = repoChecker.get_newest_version() print('') if status is False: self.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False) if LooseVersion(tag) > LooseVersion(self.__version__): print(color_text(f' :: A new update is live at {repoChecker.gitReleases.format(repoChecker.owner, repoChecker.repo)}', defaultColor=TYELLOWLIT)) print(color_text(f' :: Current version is "{self.__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT)) elif LooseVersion(tag) < LooseVersion(self.__version__): print(color_text(' :: No update available', defaultColor=TGREENLIT)) print(color_text(f' :: Current version is "{self.__version__}(dev)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT)) else: print(color_text(' :: No update available', defaultColor=TGREENLIT)) print(color_text(f' :: Current version is "{self.__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT)) print('') sys.exit(0) if __name__ == "__main__": parser = GeckoLoaderCli('GeckoLoader ' + __version__, __version__, description='Dol editing tool for allocating extended codespace') if len(sys.argv) == 1: parser.print_splash() elif '--check-update' in sys.argv: parser.check_updates() args = parser.parse_args() if args.alloc: try: _allocation = int(args.alloc, 16) except ValueError: parser.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT)) else: _allocation = None if args.hookaddress: if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000: parser.error(color_text('The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT)) else: try: _codehook = int(args.hookaddress, 16) except ValueError: parser.error(color_text('The codehandler hook address was invalid\n', defaultColor=TREDLIT)) else: _codehook = None if args.handler == 'MINI': codeHandlerFile = 'codehandler-mini.bin' else: codeHandlerFile = 'codehandler.bin' try: if not os.path.isfile(args.dolfile): parser.error(color_text(f'File "{args.dolfile}" does not exist\n', defaultColor=TREDLIT)) if not os.path.exists(args.codelist): parser.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT)) with open(os.path.normpath(args.dolfile), 'rb') as dol: dolFile = DolFile(dol) 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.hookType = args.hooktype codeHandler.includeAll = args.txtcodes.lower() == 'all' codeHandler.optimizeList = args.optimize with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile: geckoKernel = KernelLoader(kernelfile, parser) if args.init is not None: geckoKernel.initAddress = int(args.init, 16) geckoKernel.patchJob = args.movecodes geckoKernel.verbosity = args.verbose geckoKernel.quiet = args.quiet geckoKernel.encrypt = args.encrypt geckoKernel.protect = args.protect 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(), "geckoloader-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) if not os.path.isdir(TMPDIR): os.mkdir(TMPDIR) geckoKernel.build(args.codelist, dolFile, codeHandler, TMPDIR, dest) sys.exit(0) except FileNotFoundError as e: parser.error(color_text(e + '\n', defaultColor=TREDLIT))