1
0
Fork 0

Added a simple update finder, and further cleaned code

This commit is contained in:
JoshuaMKW 2020-08-19 06:21:15 -05:00
parent 927835e760
commit 2a7ac8a3d9
6 changed files with 480 additions and 343 deletions

View file

@ -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))

View file

@ -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)

132
imports/access.py Normal file
View file

@ -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')

View file

@ -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))

View file

@ -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))

25
imports/versioncheck.py Normal file
View file

@ -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