def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple: self.complete_data( codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF]) self._rawData.seek(0, 2) self._rawData.write(codeHandler._rawData.getvalue() + codeHandler.geckoCodes.codeList.getvalue()) self._rawData.seek(0) _kernelData = self._rawData.getvalue() try: dolFile.append_text_sections([(_kernelData, self.initAddress)]) except SectionCountFullError: try: dolFile.append_data_sections([(_kernelData, self.initAddress)]) except SectionCountFullError: self.error( tools.color_text( "There are no unused sections left for GeckoLoader to use!\n", defaultColor=tools.TREDLIT)) dolFile.entryPoint = self.initAddress return True, None
def _exec(self, args, tmpdir): context = self._validate_args(args) try: with context["dol"].open("rb") as dol: dolFile = DolFile(dol) with resource_path(context["codehandler"]).open("rb") as handler: codeHandler = CodeHandler(handler) codeHandler.allocation = context["allocation"] codeHandler.hookAddress = context["hookaddress"] codeHandler.hookType = context["hooktype"] codeHandler.includeAll = context["includeall"] codeHandler.optimizeList = context["optimize"] with resource_path("bin/geckoloader.bin").open("rb") as kernelfile: geckoKernel = KernelLoader(kernelfile, cli) geckoKernel.initAddress = context["initaddress"] geckoKernel.verbosity = context["verbosity"] geckoKernel.quiet = context["quiet"] geckoKernel.encrypt = context["encrypt"] geckoKernel.protect = context["protect"] if not context["destination"].parent.exists(): context["destination"].parent.mkdir(parents=True, exist_ok=True) geckoKernel.build(context["codepath"], dolFile, codeHandler, TMPDIR, context["destination"]) except FileNotFoundError as e: self.error(color_text(e, defaultColor=TREDLIT))
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))
def wrapper(*args, **kwargs): start = time.perf_counter() value = func(*args, **kwargs) end = time.perf_counter() print( tools.color_text( f"\n :: Completed in {(end - start):0.4f} seconds!\n", defaultColor=tools.TGREENLIT)) return value
def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler) -> bool: for section in dolFile.textSections: dolFile.seek(section["address"]) sample = dolFile.read(section["size"]) if codeHandler.hookType == "VI": result = sample.find(codeHandler.gcnVIHook) elif codeHandler.hookType == "GX": result = sample.find(codeHandler.gcnGXDrawHook) elif codeHandler.hookType == "PAD": result = sample.find(codeHandler.gcnPADHook) else: raise NotImplementedError( tools.color_text( f"Unsupported hook type specified ({codeHandler.hookType})", defaultColor=tools.TREDLIT)) if result >= 0: dolFile.seek(section["address"] + result) else: if codeHandler.hookType == "VI": result = sample.find(codeHandler.wiiVIHook) elif codeHandler.hookType == "GX": result = sample.find(codeHandler.wiiGXDrawHook) elif codeHandler.hookType == "PAD": result = sample.find(codeHandler.wiiPADHook) else: raise NotImplementedError( tools.color_text( f"Unsupported hook type specified ({codeHandler.hookType})", defaultColor=tools.TREDLIT)) if result >= 0: dolFile.seek(section["address"] + result) else: continue while (sample := read_uint32(dolFile)) != 0x4E800020: pass dolFile.seek(-4, 1) codeHandler.hookAddress = dolFile.tell() return True
def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int): dolFile.seek(address) ppc = read_uint32(dolFile) if ((ppc >> 24) & 0xFF) > 0x3F and ((ppc >> 24) & 0xFF) < 0x48: raise NotImplementedError( tools.color_text( "Hooking the codehandler to a conditional non spr branch is unsupported", defaultColor=tools.TREDLIT)) dolFile.seek(-4, 1) dolFile.insert_branch(codeHandler.startAddress, address, lk=0)
def set_variables(self, dolFile: DolFile): varOffset = self.find_variable_data(b"\x00\xDE\xDE\xDE") if varOffset is None: raise RuntimeError( tools.color_text("Variable codehandler data not found\n", defaultColor=tools.TREDLIT)) self.set_hook_instruction(dolFile, self.hookAddress, varOffset, 0) self._rawData.seek(varOffset + 4) write_uint32(self._rawData, ((self.hookAddress + 4) - (self.initAddress + (varOffset + 4))) & 0x3FFFFFD | 0x48000000 | 0)
def init_gct(self, gctFile: Path, tmpdir: Path = None): if tmpdir is not None: _tmpGct = tmpdir / "gct.bin" else: _tmpGct = Path("gct.bin") if gctFile.suffix.lower() == ".txt": with _tmpGct.open("wb+") as temp: temp.write( bytes.fromhex("00D0C0DE" * 2 + self.parse_input(gctFile) + "F000000000000000")) temp.seek(0) self.geckoCodes = GCT(temp) elif gctFile.suffix.lower() == ".gct": with gctFile.open("rb") as gct: self.geckoCodes = GCT(gct) elif gctFile.suffix == "": with _tmpGct.open("wb+") as temp: temp.write(b"\x00\xD0\xC0\xDE" * 2) for file in gctFile.iterdir(): if file.is_file(): if file.suffix.lower() == ".txt": temp.write(bytes.fromhex(self.parse_input(file))) elif file.suffix.lower() == ".gct": with file.open("rb") as gct: temp.write(gct.read()[8:-8]) else: print( tools.color_text( f" :: HINT: {file} is not a .txt or .gct file", defaultColor=tools.TYELLOWLIT)) temp.write(b"\xF0\x00\x00\x00\x00\x00\x00\x00") temp.seek(0) self.geckoCodes = GCT(temp) else: raise NotImplementedError( f"Parsing file type `{gctFile.suffix}' as a GCT is unsupported" )
def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple: codeHandler._rawData.seek(0) codeHandler.geckoCodes.codeList.seek(0) _handlerData = codeHandler._rawData.getvalue( ) + codeHandler.geckoCodes.codeList.getvalue() try: dolFile.append_text_sections([(_handlerData, codeHandler.initAddress)]) except SectionCountFullError: try: dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)]) except SectionCountFullError: self.error( tools.color_text( "There are no unused sections left for GeckoLoader to use!\n", defaultColor=tools.TREDLIT)) return True, None
def check_updates(self): repoChecker = Updater('JoshuaMKW', 'GeckoLoader') tag, status = repoChecker.get_newest_version() if status is False: self.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False) print('') 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('')
def build(self, gctFile: Path, dolFile: DolFile, codeHandler: CodeHandler, tmpdir: Path, dump: Path): _oldStart = dolFile.entryPoint """Initialize our codes""" codeHandler.init_gct(gctFile, tmpdir) if codeHandler.geckoCodes is None: self.error( tools.color_text( "Valid codelist not found. Please provide a .txt/.gct file, or a folder of .txt/.gct files\n", defaultColor=tools.TREDLIT)) if self.protect: self.protect_game(codeHandler) """Get entrypoint (or BSS midpoint) for insert""" if self.initAddress: try: dolFile.resolve_address(self.initAddress) self.error( tools.color_text( f"Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections", defaultColor=tools.TREDLIT)) except UnmappedAddressError: pass else: self.initAddress = dolFile.seek_nearest_unmapped( dolFile.bssAddress, len(self._rawData.getbuffer()) + codeHandler.handlerLength + codeHandler.geckoCodes.size) self._rawData.seek(0) if codeHandler.optimizeList: codeHandler.geckoCodes.optimize_codelist(dolFile) """Is codelist optimized away?""" if codeHandler.geckoCodes.codeList.getvalue( ) == b"\x00\xD0\xC0\xDE\x00\xD0\xC0\xDE\xF0\x00\x00\x00\x00\x00\x00\x00": with dump.open("wb") as final: dolFile.save(final) if not self.quiet: if self.verbosity >= 3: dolFile.print_info() print("-" * 64) if self.verbosity >= 1: print( tools.color_text( "\n :: All codes have been successfully pre patched", defaultColor=tools.TGREENLIT)) return hooked = determine_codehook(dolFile, codeHandler, False) if hooked: _status, _msg = self.patch_arena(codeHandler, dolFile) else: self.error( tools.color_text( "Failed to find a hook address. Try using option --codehook to use your own address\n", defaultColor=tools.TREDLIT)) if _status is False: self.error( tools.color_text(_msg + "\n", defaultColor=tools.TREDLIT)) elif codeHandler.allocation < codeHandler.geckoCodes.size: self.error( tools.color_text( "Allocated codespace was smaller than the given codelist\n", defaultColor=tools.TYELLOW)) with dump.open("wb") as final: dolFile.save(final) if self.quiet: return if self.verbosity >= 3: dolFile.print_info() print("-" * 64) if self.verbosity >= 2: print("") info = [ f" :: Start of game modified to address 0x{self.initAddress:X}", f" :: Game function `__start()' located at address 0x{_oldStart:X}", f" :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}", f" :: Codehandler hooked at 0x{codeHandler.hookAddress:X}", f" :: Codehandler is of type `{codeHandler.type}'", f" :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used", f" :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used" ] for bit in info: print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) elif self.verbosity >= 1: print("") info = [ f" :: GeckoLoader set at address 0x{self.initAddress:X}", f" :: Legacy size is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}", f" :: Codehandler is of type `{codeHandler.type}'" ] for bit in info: print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
def _validate_args(self, args) -> dict: dolFile = Path(args.dolfile).resolve() codeList = Path(args.codelist).resolve() if args.dest: dest = Path(args.dest).resolve() if dest.suffix == "": dest = dest / args.dolfile.name else: dest = Path.cwd() / "geckoloader-build" / args.dolfile.name if args.alloc: try: _allocation = int(args.alloc, 16) except ValueError: self.error( color_text('The allocation was invalid\n', defaultColor=TREDLIT)) else: _allocation = None if args.hookaddress: if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000: self.error( color_text( 'The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT)) else: try: _codehook = int(args.hookaddress, 16) except ValueError: self.error( color_text( 'The codehandler hook address was invalid\n', defaultColor=TREDLIT)) else: _codehook = None if args.handler == CodeHandler.Types.MINI: codeHandlerFile = Path('bin/codehandler-mini.bin') elif args.handler == CodeHandler.Types.FULL: codeHandlerFile = Path('bin/codehandler.bin') else: self.error( color_text(f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT)) if not dolFile.is_file(): self.error( color_text(f'File "{dolFile}" does not exist\n', defaultColor=TREDLIT)) if not codeList.exists(): self.error( color_text(f'File/folder "{codeList}" does not exist\n', defaultColor=TREDLIT)) return { "dol": dolFile, "codepath": codeList, "codehandler": codeHandlerFile, "destination": dest, "allocation": _allocation, "hookaddress": _codehook, "hooktype": args.hooktype, "initaddress": None if args.init is None else int(args.init, 16), "includeall": args.txtcodes.lower() == "all", "optimize": args.optimize, "protect": args.protect, "encrypt": args.encrypt, "verbosity": args.verbose, "quiet": args.quiet }