From 2a7ac8a3d958e9b8cc6c70541d430fff07b0589f Mon Sep 17 00:00:00 2001 From: JoshuaMKW <60854312+JoshuaMKW@users.noreply.github.com> Date: Wed, 19 Aug 2020 06:21:15 -0500 Subject: [PATCH] Added a simple update finder, and further cleaned code --- GeckoLoader.py | 221 +++++++++++++++-------- access.py | 68 ------- imports/access.py | 132 ++++++++++++++ dolreader.py => imports/dolreader.py | 119 ++++++------ kernel.py => imports/kernel.py | 258 +++++++++++++-------------- imports/versioncheck.py | 25 +++ 6 files changed, 480 insertions(+), 343 deletions(-) delete mode 100644 access.py create mode 100644 imports/access.py rename dolreader.py => imports/dolreader.py (67%) rename kernel.py => imports/kernel.py (64%) create mode 100644 imports/versioncheck.py diff --git a/GeckoLoader.py b/GeckoLoader.py index 3bde76b..a47082e 100644 --- a/GeckoLoader.py +++ b/GeckoLoader.py @@ -1,25 +1,54 @@ #Written by JoshuaMK 2020 -#Start.dol EclipseCodes -m ARENA --codehook 802A80D0 -o -vv import sys import os -import re -import shutil import random +import shutil import argparse +from distutils.version import LooseVersion + +sys.path.extend([os.path.join(os.path.dirname(__file__), 'imports')]) + from kernel import * from access import * +from versioncheck import Updater -_VERSION_ = "v5.0.0" +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 -def determine_codehook(dolFile: DolFile, codehandler: CodeHandler): - if codehandler.hookAddress == None: - assert_code_hook(dolFile, codehandler, GCNVIHOOK, WIIVIHOOK) +except ImportError: + TRESET = '' + TGREEN = '' + TGREENLIT = '' + TYELLOW = '' + TYELLOWLIT = '' + TRED = '' + TREDLIT = '' + +__version__ = 'v5.1.0' + +def resource_path(relative_path: str): + """ 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 determine_codehook(dolFile: DolFile, codeHandler: CodeHandler): + if codeHandler.hookAddress == None: + assert_code_hook(dolFile, codeHandler, GCNVIHOOK, WIIVIHOOK) else: - insert_code_hook(dolFile, codehandler, codehandler.hookAddress) + insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress) -def assert_code_hook(dolFile: DolFile, codehandler: CodeHandler, gcnhook: bytes, wiihook: bytes): +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) @@ -36,26 +65,26 @@ def assert_code_hook(dolFile: DolFile, codehandler: CodeHandler, gcnhook: bytes, else: continue - sample = dolFile.read(4) - while sample != b'\x4E\x80\x00\x20': - sample = dolFile.read(4) + sample = read_uint32(dolFile) + while sample != 0x4E800020: + sample = read_uint32(dolFile) dolFile.seek(-4, 1) - codehandler.hookAddress = dolFile.tell() + codeHandler.hookAddress = dolFile.tell() - insert_code_hook(dolFile, codehandler, codehandler.hookAddress) + insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress) return - parser.error('Failed to find a hook address. Try using option --codehook to use your own address') + parser.error(color_text('Failed to find a hook address. Try using option --codehook to use your own address\n', defaultColor=TREDLIT)) -def insert_code_hook(dolFile: DolFile, codehandler: CodeHandler, address: int): +def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int): dolFile.seek(address) - if dolFile.read(4) != b'\x4E\x80\x00\x20': - parser.error("Codehandler hook given is not a blr") + if read_uint32(dolFile) != 0x4E800020: + parser.error(color_text("Codehandler hook given is not a blr\n", defaultColor=TREDLIT)) dolFile.seek(-4, 1) - dolFile.insert_branch(codehandler.startAddress, address, lk=0) + dolFile.insert_branch(codeHandler.startAddress, address, lk=0) def sort_file_args(fileA, fileB): if os.path.splitext(fileA)[1].lower() == '.dol': @@ -65,15 +94,15 @@ def sort_file_args(fileA, fileB): dolFile = fileB gctFile = fileA else: - parser.error('No dol file was passed\n') + parser.error(color_text('No dol file was passed\n', defaultColor=TREDLIT)) return dolFile, gctFile if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='GeckoLoader ' + _VERSION_, - description='Process files and allocations for GeckoLoader', - allow_abbrev=False) + parser = CommandLineParser(prog='GeckoLoader ' + __version__, + description='Process files and allocations for GeckoLoader', + allow_abbrev=False) - parser.add_argument('dolFile', help='DOL file') + parser.add_argument('dolfile', help='DOL file') parser.add_argument('codelist', help='Folder or Gecko GCT|TXT file') parser.add_argument('-a', '--alloc', help='Define the size of the code allocation in hex, only applies when using the ARENA space', @@ -95,17 +124,17 @@ if __name__ == "__main__": default='active', metavar='TYPE') parser.add_argument('--handler', - help='''Which codehandler gets used. "MINI" uses a smaller codehandler + 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 + "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''', + 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', @@ -119,36 +148,76 @@ if __name__ == "__main__": ram writes into the dol file, and removing them from the codelist''', action='store_true') parser.add_argument('-p', '--protect', - help='''Targets and nullifies the standard codehandler provided by loaders and Dolphin Emulator, + 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') + parser.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') if len(sys.argv) == 1: - version = _VERSION_.rjust(9, ' ') + version = __version__.rjust(9, ' ') + + if os.path.normpath(os.path.join(os.path.expanduser('~'), "AppData", "Roaming")) in os.path.dirname(__file__): + helpMessage = 'Try the command: GeckoLoader -h'.center(64, ' ') + else: + if os.path.splitext(__file__)[1].lower() == ".py": + helpMessage = 'Try the command: python GeckoLoader.py -h'.center(64, ' ') + else: + helpMessage = 'Try the command: .\GeckLoader.exe -h'.center(64, ' ') + logo = [' ', - ' ╔═════════════════════════════════════════════════╗ ', - ' ║ ║ ', - ' ║ ┌───┬───┬───┬┐┌─┬───┬┐ ┌───┬───┬───┬───┬───┐ ║ ', - ' ║ │┌─┐│┌──┤┌─┐│││┌┤┌─┐││ │┌─┐│┌─┐├┐┌┐│┌──┤┌─┐│ ║ ', - ' ║ ││ └┤└──┤│ └┤└┘┘││ │││ ││ │││ ││││││└──┤└─┘│ ║ ', - ' ║ ││┌─┤┌──┤│ ┌┤┌┐│││ │││ ┌┤│ ││└─┘│││││┌──┤┌┐┌┘ ║ ', - ' ║ │└┴─│└──┤└─┘│││└┤└─┘│└─┘│└─┘│┌─┐├┘└┘│└──┤││└┐ ║ ', - ' ║ └───┴───┴───┴┘└─┴───┴───┴───┴┘ └┴───┴───┴┘└─┘ ║ ', - ' ║ ║ ', - ' ║ ┌┬───┬───┬┐ ┌┬┐ ┌┬───┬─┐┌─┬┐┌─┐ ║ ', - ' ║ ││┌─┐│┌─┐││ │││ ││┌─┐│ └┘ │││┌┘ ║ ', - ' ║ │││ ││└──┤└─┘││ │││ ││┌┐┌┐│└┘┘ ║ ', - ' ║ ┌──┐┌┐│││ │├──┐│┌─┐││ ││└─┘││││││┌┐│ ┌──┐ ║ ', - ' ║ └──┘│└┘│└─┘│└─┘││ ││└─┘│┌─┐││││││││└┐└──┘ ║ ', - ' ║ └──┴───┴───┴┘ └┴───┴┘ └┴┘└┘└┴┘└─┘ ║ ', - f' ║ {version} ║ ', - ' ╚═════════════════════════════════════════════════╝ ', + ' ╔═══════════════════════════════════════════════════════════╗ ', + ' ║ ║ ', + ' ║ ┌───┐┌───┐┌───┐┌┐┌─┐┌───┐┌┐ ┌───┐┌───┐┌───┐┌───┐┌───┐ ║ ', + ' ║ │┌─┐││┌──┘│┌─┐││││┌┘│┌─┐│││ │┌─┐││┌─┐│└┐┌┐││┌──┘│┌─┐│ ║ ', + ' ║ ││ └┘│└──┐││ └┘│└┘┘ ││ ││││ ││ ││││ ││ │││││└──┐│└─┘│ ║ ', + ' ║ ││┌─┐│┌──┘││ ┌┐│┌┐│ ││ ││││ ┌┐││ │││└─┘│ │││││┌──┘│┌┐┌┘ ║ ', + ' ║ │└┴─││└──┐│└─┘││││└┐│└─┘││└─┘││└─┘││┌─┐│┌┘└┘││└──┐│││└┐ ║ ', + ' ║ └───┘└───┘└───┘└┘└─┘└───┘└───┘└───┘└┘ └┘└───┘└───┘└┘└─┘ ║ ', + ' ║ ║ ', + ' ║ ┌┐┌───┐┌───┐┌┐ ┌┐┌┐ ┌┐┌───┐┌─┐┌─┐┌┐┌─┐ ║ ', + ' ║ │││┌─┐││┌─┐│││ ││││ │││┌─┐││ └┘ ││││┌┘ ║ ', + ' ║ ││││ │││└──┐│└─┘│││ ││││ │││┌┐┌┐││└┘┘ ║ ', + ' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ', + ' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ', + ' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ', + 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(line) + print(color_text(line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT)) + sys.exit(0) + elif '--check-update' in sys.argv: + repoChecker = Updater('JoshuaMKW', 'GeckoLoader') + + tag, status = repoChecker.get_newest_version() + + print('') + + if status is False: + parser.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False) + + if LooseVersion(tag) > LooseVersion(__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 "{__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT)) + elif LooseVersion(tag) < LooseVersion(__version__): + print(color_text(' :: No update available', defaultColor=TGREENLIT)) + print(color_text(f' :: Current version is "{__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 "{__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT)) + + print('') sys.exit(0) args = parser.parse_args() @@ -157,84 +226,78 @@ if __name__ == "__main__": try: _allocation = int(args.alloc, 16) except ValueError: - parser.error('The allocation was invalid\n') + parser.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT)) else: _allocation = None if args.codehook: if 0x80000000 > int(args.codehook, 16) >= 0x81800000: - parser.error('The codehandler hook address was beyond bounds\n') + parser.error(color_text('The codeHandler hook address was beyond bounds\n', defaultColor=TREDLIT)) else: try: _codehook = int(args.codehook, 16) except ValueError: - parser.error('The codehandler hook address was invalid\n') + parser.error(color_text('The codeHandler hook address was invalid\n', defaultColor=TREDLIT)) else: _codehook = None if args.handler: if args.handler == 'MINI': - codehandlerFile = 'codehandler-mini.bin' + codeHandlerFile = 'codehandler-mini.bin' else: - codehandlerFile = 'codehandler.bin' + codeHandlerFile = 'codehandler.bin' else: - codehandlerFile = 'codehandler.bin' - - #dolFile, gctFile = sort_file_args(args.fileA, args.fileB) + codeHandlerFile = 'codehandler.bin' try: - if not os.path.isdir('BUILD'): - os.mkdir('BUILD') - - if not os.path.isfile(args.dolFile): - parser.error('File "' + dolFile + '" does not exist') + 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('File/folder "' + gctFile + '" does not exist') + parser.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT)) tmpdir = ''.join(random.choice('1234567890-_abcdefghijklomnpqrstuvwxyz') for i in range(6)) + '-GeckoLoader' - if not os.path.isdir(tmpdir): - os.mkdir(tmpdir) - - 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', 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): + 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: + with open(os.path.normpath(args.dolfile), 'rb') as dol: dolFile = DolFile(dol) - codehandler.optimizeList = args.optimize + codeHandler.optimizeList = args.optimize 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))) + 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))) + 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) - + if not os.path.exists(os.path.abspath(tmpdir)): + os.mkdir(tmpdir) + + geckoKernel.build(parser, args.codelist, dolFile, codeHandler, tmpdir, dest) + shutil.rmtree(tmpdir) sys.exit(0) - except FileNotFoundError as err: - parser.error(err) - sys.exit(1) + except FileNotFoundError as e: + parser.error(color_text(e + '\n', defaultColor=TREDLIT)) diff --git a/access.py b/access.py deleted file mode 100644 index ec2bc54..0000000 --- a/access.py +++ /dev/null @@ -1,68 +0,0 @@ -import struct -import sys -import os - -def read_sbyte(f): - return struct.unpack("b", f.read(1))[0] - -def write_sbyte(f): - struct.unpack("b", f.read(1)) - -def read_sint16(f): - return struct.unpack(">h", f.read(4))[0] - -def write_sint16(f, val): - f.write(struct.pack(">h", val)) - -def read_sint32(f): - return struct.unpack(">i", f.read(4))[0] - -def write_sint32(f, val): - f.write(struct.pack(">i", val)) - -def read_float(f): - return struct.unpack(">f", f.read(4))[0] - -def write_float(f, val): - f.write(struct.pack(">f", val)) - -def read_double(f): - return struct.unpack(">d", f.read(4))[0] - -def write_double(f, val): - f.write(struct.pack(">d", val)) - -def read_ubyte(f): - return struct.unpack("B", f.read(1))[0] - -def write_ubyte(f): - struct.unpack("B", f.read(1)) - -def read_uint16(f): - return struct.unpack(">H", f.read(4))[0] - -def write_uint16(f, val): - f.write(struct.pack(">H", val)) - -def read_uint32(f): - return struct.unpack(">I", f.read(4))[0] - -def write_uint32(f, val): - f.write(struct.pack(">I", val)) - -def read_float(f): - return struct.unpack(">f", f.read(4))[0] - -def write_float(f, val): - f.write(struct.pack(">f", val)) - -def read_double(f): - return struct.unpack(">d", f.read(4))[0] - -def write_double(f, val): - f.write(struct.pack(">d", val)) - -def resource_path(relative_path: str): - """ 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) \ No newline at end of file diff --git a/imports/access.py b/imports/access.py new file mode 100644 index 0000000..c516e14 --- /dev/null +++ b/imports/access.py @@ -0,0 +1,132 @@ +import struct +import sys +import os +from argparse import ArgumentParser + +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 read_sbyte(f): + return struct.unpack("b", f.read(1))[0] + +def write_sbyte(f): + struct.unpack("b", f.read(1)) + +def read_sint16(f): + return struct.unpack(">h", f.read(4))[0] + +def write_sint16(f, val): + f.write(struct.pack(">h", val)) + +def read_sint32(f): + return struct.unpack(">i", f.read(4))[0] + +def write_sint32(f, val): + f.write(struct.pack(">i", val)) + +def read_float(f): + return struct.unpack(">f", f.read(4))[0] + +def write_float(f, val): + f.write(struct.pack(">f", val)) + +def read_double(f): + return struct.unpack(">d", f.read(4))[0] + +def write_double(f, val): + f.write(struct.pack(">d", val)) + +def read_ubyte(f): + return struct.unpack("B", f.read(1))[0] + +def write_ubyte(f): + struct.unpack("B", f.read(1)) + +def read_uint16(f): + return struct.unpack(">H", f.read(4))[0] + +def write_uint16(f, val): + f.write(struct.pack(">H", val)) + +def read_uint32(f): + return struct.unpack(">I", f.read(4))[0] + +def write_uint32(f, val): + f.write(struct.pack(">I", val)) + +def read_float(f): + return struct.unpack(">f", f.read(4))[0] + +def write_float(f, val): + f.write(struct.pack(">f", val)) + +def read_double(f): + return struct.unpack(">d", f.read(4))[0] + +def write_double(f, val): + f.write(struct.pack(">d", val)) + +def color_text(text: str, textToColor: list=[('', None)], defaultColor: str=None): + currentColor = None + formattedText = '' + + for char in text: + handled = False + for itemPair in textToColor: + if (char in itemPair[0] or '\*' in itemPair[0]) and itemPair[1] is not None: + if currentColor != itemPair[1]: + formattedText += TRESET + formattedText += itemPair[1] + currentColor = itemPair[1] + handled = True + + elif defaultColor is not None: + formattedText += TRESET + formattedText += defaultColor + currentColor = defaultColor + + elif currentColor is not None: + formattedText += TRESET + currentColor = None + + if handled: + break + + formattedText += char + + return formattedText + TRESET + +class CommandLineParser(ArgumentParser): + + def error(self, message: str, prefix: str=None, print_usage=True, exit=True): + if print_usage: + self.print_usage(sys.stderr) + + if prefix is None: + if exit: + self.exit(2, f'{self.prog}: error: {message}\n') + else: + self._print_message(f'{self.prog}: error: {message}\n') + else: + if exit: + self.exit(2, f'{prefix} {message}\n') + else: + self._print_message(f'{prefix} {message}\n') \ No newline at end of file diff --git a/dolreader.py b/imports/dolreader.py similarity index 67% rename from dolreader.py rename to imports/dolreader.py index 6a875b0..0d2dad7 100644 --- a/dolreader.py +++ b/imports/dolreader.py @@ -4,53 +4,43 @@ from access import * class DolFile: def __init__(self, f): - self.rawData = BytesIO(f.read()) - fileoffset = 0 - addressoffset = 0x48 - sizeoffset = 0x90 + self._rawData = BytesIO(f.read()) + self.fileOffsetLoc = 0 + self.fileAddressLoc = 0x48 + self.fileSizeLoc = 0x90 + self.fileEntryLoc = 0xE0 self.textSections = [] self.dataSections = [] self.maxTextSections = 7 self.maxDataSections = 11 - nomoretext = False - nomoredata = False - - self._current_end = None + self._currentEnd = None # Read text and data section addresses and sizes for i in range(18): - f.seek(fileoffset + (i << 2)) + f.seek(self.fileOffsetLoc + (i << 2)) offset = read_uint32(f) - f.seek(addressoffset + (i << 2)) + f.seek(self.fileAddressLoc + (i << 2)) address = read_uint32(f) - f.seek(sizeoffset + (i << 2)) + f.seek(self.fileSizeLoc + (i << 2)) size = read_uint32(f) - if i <= 6: - if offset == 0: - nomoretext = True - elif not nomoretext: + if offset != 0: + if i < self.maxTextSections: self.textSections.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: + else: self.dataSections.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.entryPoint = read_uint32(f) - self.bss = BytesIO(self.rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize]) + self._bssData = BytesIO(self._rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize]) - self.currAddr = self.textSections[0][1] - self.seek(self.currAddr) + self._currAddr = self.textSections[0][1] + self.seek(self._currAddr) f.seek(0) # Internal function for @@ -78,48 +68,48 @@ class DolFile: # 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: + if self._currAddr + size > self._currentEnd: raise RuntimeError("Read goes over current section") - self.currAddr += size - return self.rawData.read(size) + 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: + if self._currAddr + len(data) > self._currentEnd: raise RuntimeError("Write goes over current section") - self.rawData.write(data) - self.currAddr += len(data) + 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._rawData.seek(offset + (where-gc_start)) - self.currAddr = where - self._current_end = gc_start + gc_size + self._currAddr = where + self._currentEnd = 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)) + 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 + self._currAddr += where + self._currentEnd = gc_start + gc_size else: raise RuntimeError("Unsupported whence type '{}'".format(whence)) def tell(self): - return self.currAddr + return self._currAddr def save(self, f): f.seek(0) - f.write(self.rawData.getbuffer()) + f.write(self._rawData.getbuffer()) def get_size(self): - oldpos = self.rawData.tell() - self.rawData.seek(0, 2) - size = self.rawData.tell() - self.rawData.seek(oldpos) + oldpos = self._rawData.tell() + self._rawData.seek(0, 2) + size = self._rawData.tell() + self._rawData.seek(oldpos) return size def get_alignment(self, alignment): @@ -131,10 +121,10 @@ class DolFile: return 0 def align(self, alignment): - oldpos = self.rawData.tell() - self.rawData.seek(0, 2) - self.rawData.write(bytes.fromhex("00" * self.get_alignment(alignment))) - self.rawData.seek(oldpos) + oldpos = self._rawData.tell() + self._rawData.seek(0, 2) + self._rawData.write(bytes.fromhex("00" * self.get_alignment(alignment))) + self._rawData.seek(oldpos) def append_text_sections(self, sections_list: list): offset = len(self.textSections) << 2 @@ -143,15 +133,15 @@ class DolFile: return False '''Write offset to each section in DOL file header''' - self.rawData.seek(offset) + self._rawData.seek(offset) for section_offset in sections_list: - self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file + self._rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file - self.rawData.seek(0x48 + offset) + self._rawData.seek(0x48 + offset) '''Write in game memory addresses for each section in DOL file header''' for section_addr in sections_list: - self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game + self._rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game '''Get size of GeckoLoader + gecko codes, and the codehandler''' size_list = [] @@ -162,9 +152,9 @@ class DolFile: size_list.append(sections_list[i][1] - section_offset[1]) '''Write size of each section into DOL file header''' - self.rawData.seek(0x90 + offset) + self._rawData.seek(0x90 + offset) for size in size_list: - self.rawData.write(size.to_bytes(4, byteorder='big', signed=False)) + self._rawData.write(size.to_bytes(4, byteorder='big', signed=False)) return True @@ -175,15 +165,15 @@ class DolFile: return False '''Write offset to each section in DOL file header''' - self.rawData.seek(offset) + self._rawData.seek(offset) for section_offset in sections_list: - self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file + write_uint32(self._rawData, section_offset[1]) #offset in file - self.rawData.seek(0x64 + offset) + self._rawData.seek(0x64 + offset) '''Write in game memory addresses for each section in DOL file header''' for section_addr in sections_list: - self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game + write_uint32(self._rawData, section_addr[0]) #absolute address in game '''Get size of GeckoLoader + gecko codes, and the codehandler''' size_list = [] @@ -194,18 +184,17 @@ class DolFile: size_list.append(sections_list[i][1] - section_offset[1]) '''Write size of each section into DOL file header''' - self.rawData.seek(0xAC + offset) + self._rawData.seek(0xAC + offset) for size in size_list: - self.rawData.write(size.to_bytes(4, byteorder='big', signed=False)) + write_uint32(self._rawData, size) return True def set_entry_point(self, address): - oldpos = self.rawData.tell() - self.rawData.seek(0xE0) - self.rawData.write(bytes.fromhex('{:08X}'.format(address))) - self.rawData.seek(oldpos) - + oldpos = self._rawData.tell() + self._rawData.seek(self.fileEntryLoc) + write_uint32(self._rawData, address) + self._rawData.seek(oldpos) def insert_branch(self, to, _from, lk=0): self.write(((to - _from) & 0x3FFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False)) diff --git a/kernel.py b/imports/kernel.py similarity index 64% rename from kernel.py rename to imports/kernel.py index 2513b32..575e028 100644 --- a/kernel.py +++ b/imports/kernel.py @@ -13,27 +13,6 @@ 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 = '' - HEAP = b'HEAP' LOADERSIZE = b'LSIZ' HANDLERSIZE = b'HSIZ' @@ -48,6 +27,11 @@ IL = b'IL' WIIVIHOOK = b'\x7C\xE3\x3B\x78\x38\x87\x00\x34\x38\xA7\x00\x38\x38\xC7\x00\x4C' 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' +def resource_path(relative_path: str): + """ 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_alignment(number, align: int): if number % align != 0: return align - (number % align) @@ -166,18 +150,18 @@ class GCT: data = self.codeList.read(2).hex() size = int(data[:-3], 16) counter = int(data[1:], 16) - address_increment = int.from_bytes(self.codeList.read(2), byteorder='big', signed=False) - value_increment = int.from_bytes(self.codeList.read(4), byteorder='big', signed=False) + address_increment = read_uint16(self.codeList) + value_increment = read_uint32(self.codeList) while counter + 1 > 0: if size == 0: - dolFile.write(value.to_bytes(length=1, byteorder='big', signed=False)) + write_ubyte(dolFile, value) dolFile.seek(-1, 1) elif size == 1: - dolFile.write(value.to_bytes(length=2, byteorder='big', signed=False)) + write_uint16(dolFile, value) dolFile.seek(-2, 1) elif size == 2: - dolFile.write(value.to_bytes(length=4, byteorder='big', signed=False)) + write_uint32(dolFile, value) dolFile.seek(-4, 1) else: raise ValueError('Size type {} does not match 08 codetype specs'.format(size)) @@ -224,7 +208,7 @@ class GCT: class CodeHandler: def __init__(self, f): - self.rawData = BytesIO(f.read()) + self._rawData = BytesIO(f.read()) '''Get codelist pointer''' f.seek(0xFA) @@ -288,7 +272,7 @@ class CodeHandler: class KernelLoader: def __init__(self, f): - self.rawData = BytesIO(f.read()) + self._rawData = BytesIO(f.read()) self.initDataList = None self.gpModDataList = None self.gpDiscDataList = None @@ -308,26 +292,26 @@ class KernelLoader: while sample: if sample == DH: tmp.seek(-2, 1) - tmp.write(self.gpDiscDataList[0]) + write_uint16(tmp, self.gpDiscDataList[0]) elif sample == DL: tmp.seek(-2, 1) - tmp.write((lowerAddr + self.gpDiscDataList[1]).to_bytes(2, byteorder='big', signed=False)) + write_uint16(tmp, lowerAddr + self.gpDiscDataList[1]) elif sample == GH: tmp.seek(-2, 1) - tmp.write(self.gpModDataList[0]) + write_uint16(tmp, self.gpModDataList[0]) elif sample == GL: tmp.seek(-2, 1) - tmp.write((lowerAddr + self.gpModDataList[1]).to_bytes(2, byteorder='big', signed=False)) + write_uint16(tmp, lowerAddr + self.gpModDataList[1]) elif sample == IH: tmp.seek(-2, 1) - tmp.write(entryPoint[0]) + write_uint16(tmp, entryPoint[0]) elif sample == IL: tmp.seek(-2, 1) - tmp.write(entryPoint[1]) + write_uint16(tmp, entryPoint[1]) sample = tmp.read(2) - def figure_loader_data(self, tmp, codehandler: CodeHandler, dolFile: DolFile, entrypoint: str, initpoint: list): - upperAddr, lowerAddr = entrypoint[:int(len(entrypoint)/2)], entrypoint[int(len(entrypoint)/2):] + def figure_loader_data(self, tmp, codeHandler: CodeHandler, dolFile: DolFile, initpoint: list): + upperAddr, lowerAddr = self.initAddress[:int(len(self.initAddress)/2)], self.initAddress[int(len(self.initAddress)/2):] tmp.seek(0) sample = tmp.read(4) @@ -335,42 +319,45 @@ class KernelLoader: 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 = (int(upperAddr, 16) + 1).to_bytes(2, byteorder='big', signed=False) + gpModUpperAddr = int(upperAddr, 16) + 1 else: - gpModUpperAddr = int(upperAddr, 16).to_bytes(2, byteorder='big', signed=False) - if codehandler.allocation == None: - codehandler.allocation = (codehandler.handlerLength + codehandler.geckoCodes.size + 7) & 0xFFFFFFF8 - tmp.write(codehandler.allocation.to_bytes(4, byteorder='big', signed=False)) + gpModUpperAddr = int(upperAddr, 16) + + if codeHandler.allocation == None: + codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & 0xFFFFFFF8 + + write_uint32(tmp, codeHandler.allocation) elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader tmp.seek(-4, 1) - tmp.write(get_size(self.rawData).to_bytes(4, byteorder='big', signed=False)) + write_uint32(tmp, get_size(self._rawData)) - elif sample == HANDLERSIZE: #Found keyword "HSIZ". Goes with the size of the codehandler + 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)) + write_sint32(tmp, codeHandler.handlerLength) 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)) + write_sint32(tmp, codeHandler.geckoCodes.size) elif sample == CODEHOOK: tmp.seek(-4, 1) - if codehandler.hookAddress == None: - tmp.write(b'\x00\x00\x00\x00') + if codeHandler.hookAddress == None: + write_uint32(tmp, 0) else: - tmp.write(codehandler.hookAddress.to_bytes(4, byteorder='big', signed=False)) + write_uint32(tmp, codeHandler.hookAddress) sample = tmp.read(4) gpDiscOffset = get_size(tmp, -4) if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing - gpDiscUpperAddr = (int(upperAddr, 16) + 1).to_bytes(2, byteorder='big', signed=False) + gpDiscUpperAddr = int(upperAddr, 16) + 1 else: - gpDiscUpperAddr = int(upperAddr, 16).to_bytes(2, byteorder='big', signed=False) + gpDiscUpperAddr = int(upperAddr, 16) self.gpModDataList = (gpModUpperAddr, gpModInfoOffset) self.gpDiscDataList = (gpDiscUpperAddr, gpDiscOffset) @@ -378,45 +365,45 @@ class KernelLoader: self.fill_loader_data(tmp, initpoint, int(lowerAddr, 16)) tmp.seek(0, 2) - codehandler.rawData.seek(0) - codehandler.geckoCodes.codeList.seek(0) + codeHandler._rawData.seek(0) + codeHandler.geckoCodes.codeList.seek(0) - tmp.write(codehandler.rawData.read() + codehandler.geckoCodes.codeList.read()) + tmp.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read()) - def patch_arena(self, codehandler: CodeHandler, dolFile: DolFile, entrypoint: str, tmp): - tmp.write(self.rawData.getbuffer()) + def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile, tmp): + tmp.write(self._rawData.getbuffer()) geckoloader_offset = dolFile.get_size() - self.figure_loader_data(tmp, codehandler, dolFile, entrypoint, - [((dolFile.entryPoint >> 16) & 0xFFFF).to_bytes(2, byteorder='big', signed=False), - (dolFile.entryPoint & 0xFFFF).to_bytes(2, byteorder='big', signed=False)]) + + self.figure_loader_data(tmp, codeHandler, dolFile, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF]) + tmp.seek(0) - dolFile.rawData.seek(0, 2) - dolFile.rawData.write(tmp.read()) + dolFile._rawData.seek(0, 2) + dolFile._rawData.write(tmp.read()) dolFile.align(256) - status = dolFile.append_text_sections([(int(entrypoint, 16), geckoloader_offset)]) + status = dolFile.append_text_sections([(int(self.initAddress, 16), geckoloader_offset)]) if status is True: - dolFile.set_entry_point(int(entrypoint, 16)) + dolFile.set_entry_point(int(self.initAddress, 16)) return status - def patch_legacy(self, codehandler: CodeHandler, dolFile: DolFile, tmp): + def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile, tmp): handlerOffset = dolFile.get_size() - dolFile.rawData.seek(0, 2) - codehandler.rawData.seek(0) - codehandler.geckoCodes.codeList.seek(0) + dolFile._rawData.seek(0, 2) + codeHandler._rawData.seek(0) + codeHandler.geckoCodes.codeList.seek(0) - dolFile.rawData.write(codehandler.rawData.read() + codehandler.geckoCodes.codeList.read()) + dolFile._rawData.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read()) dolFile.align(256) - status = dolFile.append_text_sections([(codehandler.initAddress, handlerOffset)]) + status = dolFile.append_text_sections([(codeHandler.initAddress, handlerOffset)]) return status - def protect_game(self, codehandler: CodeHandler): - oldpos = codehandler.geckoCodes.codeList.tell() + def protect_game(self, codeHandler: CodeHandler): + oldpos = codeHandler.geckoCodes.codeList.tell() protectdata = [b'\xC0\x00\x00\x00\x00\x00\x00\x17', b'\x7C\x08\x02\xA6\x94\x21\xFF\x80', @@ -444,33 +431,39 @@ class KernelLoader: b'\x4E\x80\x00\x20\x00\x00\x00\x00', b'\xF0\x00\x00\x00\x00\x00\x00\x00'] - codehandler.geckoCodes.codeList.seek(-8, 2) + codeHandler.geckoCodes.codeList.seek(-8, 2) for chunk in protectdata: - codehandler.geckoCodes.codeList.write(chunk) - codehandler.geckoCodes.codeList.seek(oldpos) + codeHandler.geckoCodes.codeList.write(chunk) + codeHandler.geckoCodes.codeList.seek(oldpos) - def build(self, gctFile, dolFile: DolFile, codehandler: CodeHandler, tmpdir, dump): + def build(self, parser: CommandLineParser, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump): beginTime = time.time() + if not os.path.isdir(tmpdir): + os.mkdir(tmpdir) + with open(os.path.join(tmpdir, 'tmp.bin'), 'wb+') as tmp, open(dump, 'wb+') as final: if dolFile.get_size() < 0x100: - shutil.rmtree(tmpdir) - parser.error('DOL header is corrupted. Please provide a clean file') + parser.error(color_text('DOL header is corrupted. Please provide a clean file\n', defaultColor=TREDLIT), exit=False) + return '''Initialize our codes''' + foundData = False + 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 + codehandler.gecko_parser(gctFile, codehandler.includeAll) + 'F000000000000000')) + temp.write(bytes.fromhex('00D0C0DE'*2 + codeHandler.gecko_parser(gctFile, codeHandler.includeAll) + 'F000000000000000')) temp.seek(0) - codehandler.geckoCodes = GCT(temp) + codeHandler.geckoCodes = GCT(temp) + foundData = True elif os.path.splitext(gctFile)[1].lower() == '.gct': - with open(r'{}'.format(gctFile), 'rb') as gct: - codehandler.geckoCodes = GCT(gct) - else: - parser.error('No valid gecko code file found') + with open(gctFile, 'rb') as gct: + codeHandler.geckoCodes = GCT(gct) + foundData = True + else: with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp: temp.write(bytes.fromhex('00D0C0DE'*2)) @@ -478,24 +471,28 @@ class KernelLoader: 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(codehandler.gecko_parser(os.path.join(gctFile, file), codehandler.includeAll))) + temp.write(bytes.fromhex(codeHandler.gecko_parser(os.path.join(gctFile, file), codeHandler.includeAll))) + foundData = True elif os.path.splitext(file)[1].lower() == '.gct': with open(os.path.join(gctFile, file), 'rb') as gct: temp.write(gct.read()[8:-8]) + foundData = True else: - print(TYELLOW + ' :: WARNING: {} is not a .txt or .gct file'.format(file) + TRESET) + print(color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=TYELLOWLIT)) temp.write(bytes.fromhex('F000000000000000')) temp.seek(0) - codehandler.geckoCodes = GCT(temp) + codeHandler.geckoCodes = GCT(temp) - + if not foundData: + parser.error(color_text('No valid gecko code file found\n', defaultColor=TREDLIT), exit=False) + return - if self.protect and self.build == "ARENA": - self.protect_game(codehandler) + if self.protect and self.codeLocation == "ARENA": + self.protect_game(codeHandler) if self.codeLocation == 'AUTO': - if codehandler.initAddress + codehandler.handlerLength + codehandler.geckoCodes.size > 0x80002FFF: + if codeHandler.initAddress + codeHandler.handlerLength + codeHandler.geckoCodes.size > 0x80002FFF: self.codeLocation = 'ARENA' else: self.codeLocation = 'LEGACY' @@ -503,85 +500,84 @@ class KernelLoader: '''Get entrypoint (or BSS midpoint) for insert''' if self.initAddress: - dump_address = self.initAddress.lstrip("0x").upper() try: - dolFile.resolve_address(int(dump_address, 16)) - print(TYELLOW + '\n :: WARNING: Init address specified for GeckoLoader (0x{}) clobbers existing dol sections'.format(dump_address) + TRESET) + dolFile.resolve_address(int(self.initAddress, 16)) + print(color_text(f'\n :: WARNING: Init address specified for GeckoLoader (0x{self.initAddress}) clobbers existing dol sections', defaultColor=TYELLOW)) except RuntimeError: pass else: - dump_address = '{:08X}'.format(dolFile.seek_safe_address((dolFile.bssOffset + (dolFile.bssSize >> 1)) & 0xFFFFFF00, - get_size(self.rawData) + codehandler.handlerLength + codehandler.geckoCodes.size)) - self.rawData.seek(0) + self.initAddress = '{:08X}'.format(dolFile.seek_safe_address((dolFile.bssOffset + (dolFile.bssSize >> 1)) & 0xFFFFFF00, + get_size(self._rawData) + codeHandler.handlerLength + codeHandler.geckoCodes.size)) + self._rawData.seek(0) '''Is insertion legacy?''' - if codehandler.geckoCodes.size <= 0x10: + if codeHandler.geckoCodes.size <= 0x10: dolFile.save(final) if self.verbosity >= 1: - print(TGREENLIT + '\n :: All codes have been successfully pre patched' + TRESET) + print(color_text('\n :: All codes have been successfully pre patched', defaultColor=TGREENLIT)) return if self.codeLocation == 'LEGACY': - codehandler.allocation = 0x80003000 - (codehandler.initAddress + codehandler.handlerLength) - status = self.patch_legacy(codehandler, dolFile, tmp) + codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength) + status = self.patch_legacy(codeHandler, dolFile, tmp) if status is False: - determine_codehook(dolFile, codehandler) + determine_codehook(dolFile, codeHandler) legacy = True else: - status = self.patch_arena(codehandler, dolFile, dump_address, tmp) + status = self.patch_arena(codeHandler, dolFile, tmp) legacy = False if status is False: - shutil.rmtree(tmpdir) - parser.error(TREDLIT + 'Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET) + parser.error(color_text('Not enough text sections to patch the DOL file! Potentially due to previous mods?\n', defaultColor=TREDLIT), exit=False) + return dolFile.save(final) - if codehandler.allocation < codehandler.geckoCodes.size: - print(TYELLOW + '\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run' + TRESET) - + if codeHandler.allocation < codeHandler.geckoCodes.size: + print(color_text('\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run', defaultColor=TYELLOW)) + if self.quiet: return - if codehandler.allocation > 0x70000: - print(TYELLOW + f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codehandler.allocation:X}' + TRESET) + if codeHandler.allocation > 0x70000: + print(color_text(f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codeHandler.allocation:X}', defaultColor=TYELLOW)) - elif codehandler.allocation > 0x40000: - print(TYELLOWLIT + f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codehandler.allocation:X}' + TRESET) + elif codeHandler.allocation > 0x40000: + print(color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=TYELLOWLIT)) if self.verbosity >= 2: print('') if legacy == False: - info = [TGREENLIT + f' :: GeckoLoader set at address 0x{dump_address.upper()}, start of game modified to address 0x{dump_address.upper()}', - f' :: Game function "__init_registers" located at address 0x{dolFile.entryPoint:X}'.format(), - f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}', - f' :: Codehandler is of type "{codehandler.type}"', - f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used' + TRESET] - if codehandler.hookAddress is not None: - info.insert(2, f' :: Codehandler hooked at 0x{codehandler.hookAddress:08X}') + info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}, start of game modified to address 0x{self.initAddress.upper()}', + f' :: Game function "__init_registers()" located at address 0x{dolFile.entryPoint:X}', + f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}', + f' :: Codehandler is of type "{codeHandler.type}"', + f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used'] + if codeHandler.hookAddress is not None: + info.insert(2, f' :: Codehandler hooked at 0x{codeHandler.hookAddress:08X}') else: - info = [TGREENLIT + f' :: Game function "__init_registers" located at address 0x{dolFile.entryPoint:X}', - f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}', - f' :: Codehandler is of type "{codehandler.type}"', - f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used' + TRESET] - if codehandler.hookAddress is not None: - info.insert(1, f' :: Codehandler hooked at 0x{codehandler.hookAddress:08X}') + info = [f' :: Game function "__init_registers()" located at address 0x{dolFile.entryPoint:X}', + f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}', + f' :: Codehandler is of type "{codeHandler.type}"', + f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used'] + if codeHandler.hookAddress is not None: + info.insert(1, f' :: Codehandler hooked at 0x{codeHandler.hookAddress:08X}') for bit in info: - print(bit) + print(color_text(bit, defaultColor=TGREENLIT)) elif self.verbosity >= 1: print('') if legacy == False: - info = [TGREENLIT + f' :: GeckoLoader set at address 0x{dump_address.upper()}', - f' :: Codehandler is of type "{codehandler.type}"', - f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}' + TRESET] + info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}', + f' :: Codehandler is of type "{codeHandler.type}"', + f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}'] else: - info = [TGREENLIT + f' :: Codehandler is of type "{codehandler.type}"', - f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}' + TRESET] + info = [f' :: Codehandler is of type "{codeHandler.type}"', + f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}'] for bit in info: - print(bit) + print(color_text(bit, defaultColor=TGREENLIT)) - print(TGREENLIT + f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n' + TRESET) + print(color_text(f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n', defaultColor=TGREENLIT)) diff --git a/imports/versioncheck.py b/imports/versioncheck.py new file mode 100644 index 0000000..13b8f4a --- /dev/null +++ b/imports/versioncheck.py @@ -0,0 +1,25 @@ +import requests +from bs4 import BeautifulSoup + +class Updater: + + def __init__(self, owner: str, repository: str): + self.owner = owner + self.repo = repository + self.gitReleases = 'https://github.com/{}/{}/releases' + + def request_release_data(self): + '''Returns "soup" data of the repository releases tab''' + return requests.get(self.gitReleases.format(self.owner, self.repo)) + + def get_newest_version(self): + '''Returns newest release version''' + try: + response = self.request_release_data() + response.raise_for_status() + soup = BeautifulSoup(response.text, 'html.parser') + return soup.find('span', {'class': 'css-truncate-target'}).get_text(strip=True), True + except requests.HTTPError as e: + return f'HTTP request failed with error code ({response.status_code})', False + except requests.ConnectionError: + return 'Request failed, ensure you have a working internet connection and try again', False \ No newline at end of file