1
0
Fork 0

Light code refactoring

This commit is contained in:
JoshuaMK 2020-12-06 01:53:18 -06:00
parent 651448b6dd
commit 8e1bd5d38b
2 changed files with 231 additions and 168 deletions

View file

@ -1,4 +1,4 @@
#Written by JoshuaMK 2020 # Written by JoshuaMK 2020
import atexit import atexit
import logging import logging
@ -49,6 +49,7 @@ __version__ = "v7.1.0"
TMPDIR = Path(tempfile.mkdtemp(prefix="GeckoLoader-")) TMPDIR = Path(tempfile.mkdtemp(prefix="GeckoLoader-"))
@atexit.register @atexit.register
def clean_tmp_resources(): def clean_tmp_resources():
tmpfolder = TMPDIR.parent tmpfolder = TMPDIR.parent
@ -56,81 +57,84 @@ def clean_tmp_resources():
if entry.name.startswith("GeckoLoader-"): if entry.name.startswith("GeckoLoader-"):
shutil.rmtree(entry, ignore_errors=True) shutil.rmtree(entry, ignore_errors=True)
class GeckoLoaderCli(CommandLineParser): class GeckoLoaderCli(CommandLineParser):
def __init__(self, name, version=None, description=''): def __init__(self, name, version=None, description=''):
super().__init__(prog=(f"{name} {version}"), description=description, allow_abbrev=False) super().__init__(prog=(f"{name} {version}"),
description=description, allow_abbrev=False)
self.__version__ = version self.__version__ = version
self.__doc__ = description self.__doc__ = description
self.add_argument('dolfile', help='DOL file') self.add_argument('dolfile', help='DOL file')
self.add_argument('codelist', help='Folder or Gecko GCT|TXT file') self.add_argument('codelist', help='Folder or Gecko GCT|TXT file')
self.add_argument('-a', '--alloc', self.add_argument('-a', '--alloc',
help='Define the size of the code allocation in hex, only applies when using the ARENA space', help='Define the size of the code allocation in hex, only applies when using the ARENA space',
metavar ='SIZE') metavar='SIZE')
self.add_argument('-i', '--init', self.add_argument('-i', '--init',
help='Define where GeckoLoader is initialized in hex', help='Define where GeckoLoader is initialized in hex',
metavar='ADDRESS') metavar='ADDRESS')
self.add_argument('-tc', '--txtcodes', self.add_argument('-tc', '--txtcodes',
help='''["ACTIVE", "ALL"] What codes get parsed when a txt file is used. help='''["ACTIVE", "ALL"] What codes get parsed when a txt file is used.
"ALL" makes all codes get parsed, "ALL" makes all codes get parsed,
"ACTIVE" makes only activated codes get parsed. "ACTIVE" makes only activated codes get parsed.
"ACTIVE" is the default''', "ACTIVE" is the default''',
default='ACTIVE', default='ACTIVE',
metavar='TYPE') metavar='TYPE')
self.add_argument('--handler', self.add_argument('--handler',
help='''["MINI", "FULL"] Which codehandler gets used. "MINI" uses a smaller codehandler help='''["MINI", "FULL"] Which codehandler gets used. "MINI" uses a smaller codehandler
which only supports (0x, 2x, Cx, and E0 types) and supports up to which only supports (0x, 2x, Cx, and E0 types) and supports up to
600 lines of gecko codes when using the legacy codespace. 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. "FULL" is the default''', in the legacy codespace. "FULL" is the default''',
default='FULL', default='FULL',
choices=['MINI', 'FULL'], choices=['MINI', 'FULL'],
metavar='TYPE') metavar='TYPE')
self.add_argument('--hooktype', self.add_argument('--hooktype',
help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended, help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended,
although "PAD" can work just as well. "VI" is the default''', although "PAD" can work just as well. "VI" is the default''',
default='VI', default='VI',
choices=['VI', 'GX', 'PAD'], choices=['VI', 'GX', 'PAD'],
metavar='HOOK') metavar='HOOK')
self.add_argument('--hookaddress', self.add_argument('--hookaddress',
help='Choose where the codehandler hooks to in hex, overrides auto hooks', help='Choose where the codehandler hooks to in hex, overrides auto hooks',
metavar='ADDRESS') metavar='ADDRESS')
self.add_argument('-o', '--optimize', self.add_argument('-o', '--optimize',
help='''Optimizes the codelist by directly patching qualifying help='''Optimizes the codelist by directly patching qualifying
ram writes into the dol file, and removing them from the codelist''', ram writes into the dol file, and removing them from the codelist''',
action='store_true') action='store_true')
self.add_argument('-p', '--protect', self.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''', only applies when the ARENA is used''',
action='store_true') action='store_true')
self.add_argument('--dest', self.add_argument('--dest',
help='Target path to put the modified DOL, can be a folder or file', help='Target path to put the modified DOL, can be a folder or file',
metavar='PATH') metavar='PATH')
self.add_argument('--checkupdate', self.add_argument('--checkupdate',
help='''Checks to see if a new update exists on the GitHub Repository releases page, help='''Checks to see if a new update exists on the GitHub Repository releases page,
this option overrides all other commands.''', this option overrides all other commands.''',
action='store_true') action='store_true')
self.add_argument('--splash', self.add_argument('--splash',
help='''Print the splash screen, this option overrides help='''Print the splash screen, this option overrides
all other commands excluding --checkupdate''', all other commands excluding --checkupdate''',
action='store_true') action='store_true')
self.add_argument('--encrypt', self.add_argument('--encrypt',
help='Encrypts the codelist on compile time, helping to slow the snoopers', help='Encrypts the codelist on compile time, helping to slow the snoopers',
action='store_true') action='store_true')
self.add_argument('-q', '--quiet', self.add_argument('-q', '--quiet',
help='Print nothing to the console', help='Print nothing to the console',
action='store_true') action='store_true')
self.add_argument('-v', '--verbose', self.add_argument('-v', '--verbose',
help='Print extra info to the console', help='Print extra info to the console',
default=0, default=0,
action='count') action='count')
def __str__(self) -> str: def __str__(self) -> str:
return self.__doc__ return self.__doc__
def print_splash(self): def print_splash(self):
helpMessage = 'Try option -h for more info on this program'.center(64, ' ') helpMessage = 'Try option -h for more info on this program'.center(
64, ' ')
version = self.__version__.rjust(9, ' ') version = self.__version__.rjust(9, ' ')
logo = [' ', logo = [' ',
@ -149,17 +153,18 @@ class GeckoLoaderCli(CommandLineParser):
' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ', ' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ',
' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ', ' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ',
' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ', ' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ',
f'{version}', f'{version}',
' ╚═══════════════════════════════════════════════════════════╝ ', ' ╚═══════════════════════════════════════════════════════════╝ ',
' ', ' ',
' GeckoLoader is a cli tool for allowing extended ', ' GeckoLoader is a cli tool for allowing extended ',
' gecko code space in all Wii and GC games. ', ' gecko code space in all Wii and GC games. ',
' ', ' ',
f'{helpMessage}', f'{helpMessage}',
' '] ' ']
for line in logo: for line in logo:
print(color_text(line, [('', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT)) print(color_text(
line, [('', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT))
def check_updates(self): def check_updates(self):
repoChecker = Updater('JoshuaMKW', 'GeckoLoader') repoChecker = Updater('JoshuaMKW', 'GeckoLoader')
@ -167,39 +172,57 @@ class GeckoLoaderCli(CommandLineParser):
tag, status = repoChecker.get_newest_version() tag, status = repoChecker.get_newest_version()
if status is False: if status is False:
self.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False) self.error(color_text(tag + '\n', defaultColor=TREDLIT),
print_usage=False)
print('') print('')
if LooseVersion(tag) > LooseVersion(self.__version__): 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(
print(color_text(f' :: Current version is "{self.__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT)) 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__): elif LooseVersion(tag) < LooseVersion(self.__version__):
print(color_text(' :: No update available', defaultColor=TGREENLIT)) 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)) print(color_text(
f' :: Current version is "{self.__version__}(dev)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
else: else:
print(color_text(' :: No update available', defaultColor=TGREENLIT)) 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(color_text(
f' :: Current version is "{self.__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
print('') print('')
def _validate_args(self, args) -> tuple: 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: if args.alloc:
try: try:
_allocation = int(args.alloc, 16) _allocation = int(args.alloc, 16)
except ValueError: except ValueError:
self.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT)) self.error(color_text(
'The allocation was invalid\n', defaultColor=TREDLIT))
else: else:
_allocation = None _allocation = None
if args.hookaddress: if args.hookaddress:
if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000: if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000:
self.error(color_text('The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT)) self.error(color_text(
'The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT))
else: else:
try: try:
_codehook = int(args.hookaddress, 16) _codehook = int(args.hookaddress, 16)
except ValueError: except ValueError:
self.error(color_text('The codehandler hook address was invalid\n', defaultColor=TREDLIT)) self.error(color_text(
'The codehandler hook address was invalid\n', defaultColor=TREDLIT))
else: else:
_codehook = None _codehook = None
@ -208,57 +231,65 @@ class GeckoLoaderCli(CommandLineParser):
elif args.handler == CodeHandler.Types.FULL: elif args.handler == CodeHandler.Types.FULL:
codeHandlerFile = Path('bin/codehandler.bin') codeHandlerFile = Path('bin/codehandler.bin')
else: else:
self.error(color_text(f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT)) self.error(color_text(
f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT))
if not os.path.isfile(args.dolfile): if not dolFile.is_file():
self.error(color_text(f'File "{args.dolfile}" does not exist\n', defaultColor=TREDLIT)) self.error(color_text(
f'File "{dolFile}" does not exist\n', defaultColor=TREDLIT))
if not os.path.exists(args.codelist):
self.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT))
return _allocation, _codehook, codeHandlerFile 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}
def _exec(self, args, tmpdir): def _exec(self, args, tmpdir):
_allocation, _codehook, codeHandlerFile = self._validate_args(args) context = self._validate_args(args)
try: try:
with open(args.dolfile, "rb") as dol: with context["dol"].open("rb") as dol:
dolFile = DolFile(dol) dolFile = DolFile(dol)
with resource_path(str(codeHandlerFile)).open("rb") as handler: with resource_path(context["codehandler"]).open("rb") as handler:
codeHandler = CodeHandler(handler) codeHandler = CodeHandler(handler)
codeHandler.allocation = _allocation codeHandler.allocation = context["allocation"]
codeHandler.hookAddress = _codehook codeHandler.hookAddress = context["hookaddress"]
codeHandler.hookType = args.hooktype codeHandler.hookType = context["hooktype"]
codeHandler.includeAll = args.txtcodes.lower() == 'all' codeHandler.includeAll = context["includeall"]
codeHandler.optimizeList = args.optimize codeHandler.optimizeList = context["optimize"]
with resource_path("bin/geckoloader.bin").open("rb") as kernelfile: with resource_path("bin/geckoloader.bin").open("rb") as kernelfile:
geckoKernel = KernelLoader(kernelfile, cli) 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 args.init is not None: if not context["destination"].parent.exists():
geckoKernel.initAddress = int(args.init, 16) context["destination"].parent.mkdir(parents=True, exist_ok=True)
geckoKernel.verbosity = args.verbose geckoKernel.build(context["codepath"], dolFile,
geckoKernel.quiet = args.quiet codeHandler, TMPDIR, context["destination"])
geckoKernel.encrypt = args.encrypt
geckoKernel.protect = args.protect
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 not dest.parent.exists():
dest.parent.mkdir(parents=True, exist_ok=True)
geckoKernel.build(Path(args.codelist), dolFile, codeHandler, TMPDIR, dest)
except FileNotFoundError as e: except FileNotFoundError as e:
self.error(color_text(e, defaultColor=TREDLIT)) self.error(color_text(e, defaultColor=TREDLIT))
class GUI(object): class GUI(object):
class Dialogs: class Dialogs:
@ -289,8 +320,10 @@ class GUI(object):
if not get_program_folder("GeckoLoader").exists(): if not get_program_folder("GeckoLoader").exists():
get_program_folder("GeckoLoader").mkdir() get_program_folder("GeckoLoader").mkdir()
hdlr = logging.FileHandler(get_program_folder("GeckoLoader") / "error.log") hdlr = logging.FileHandler(
formatter = logging.Formatter("\n%(levelname)s (%(asctime)s): %(message)s") get_program_folder("GeckoLoader") / "error.log")
formatter = logging.Formatter(
"\n%(levelname)s (%(asctime)s): %(message)s")
hdlr.setFormatter(formatter) hdlr.setFormatter(formatter)
self.log.addHandler(hdlr) self.log.addHandler(hdlr)
@ -298,18 +331,19 @@ class GUI(object):
if dialog_type == "aboutqt": if dialog_type == "aboutqt":
QtWidgets.QMessageBox.aboutQt(self.app.activeWindow()) QtWidgets.QMessageBox.aboutQt(self.app.activeWindow())
elif dialog_type == "aboutGeckoLoader": elif dialog_type == "aboutGeckoLoader":
desc = "".join([ "GeckoLoader is a cross platform tool designed to give ", desc = "".join(["GeckoLoader is a cross platform tool designed to give ",
"the user the most efficient codespace usage possible.\n\n ", "the user the most efficient codespace usage possible.\n\n ",
"This application supports various features, such as ", "This application supports various features, such as ",
"pre-patching codes, dynamic codehandler hooks, codespace ", "pre-patching codes, dynamic codehandler hooks, codespace ",
"extension through memory reallocation, multiple patching ", "extension through memory reallocation, multiple patching ",
"of a single DOL, and more.\n\n", "of a single DOL, and more.\n\n",
f"Current running version: {self.version}\n\n" f"Current running version: {self.version}\n\n"
"Copyright (c) 2020\n\n", "Copyright (c) 2020\n\n",
"JoshuaMK <joshuamkw2002@gmail.com> \n\n", "JoshuaMK <joshuamkw2002@gmail.com> \n\n",
"All rights reserved." ]) "All rights reserved."])
QtWidgets.QMessageBox.about(self.app.activeWindow(), "About GeckoLoader", desc) QtWidgets.QMessageBox.about(
self.app.activeWindow(), "About GeckoLoader", desc)
elif dialog_type == "Preferences": elif dialog_type == "Preferences":
self.uiprefs.show() self.uiprefs.show()
else: else:
@ -322,10 +356,10 @@ class GUI(object):
def _open_dol(self) -> tuple: def _open_dol(self) -> tuple:
if self.dolPath is None: # Just start in the home directory if self.dolPath is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
@ -338,21 +372,21 @@ class GUI(object):
else: else:
return False, "The file does not exist!" return False, "The file does not exist!"
def _load_codes(self, isFolder: bool=False) -> tuple: def _load_codes(self, isFolder: bool = False) -> tuple:
if not isFolder: if not isFolder:
if self.codePath[0] is None: # Just start in the home directory if self.codePath[0] is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(Path.home()), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(Path.home()),
"Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0]) "Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(self.codePath[0].parent), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(self.codePath[0].parent),
"Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0]) "Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0])
else: else:
if self.codePath[0] is None: # Just start in the home directory if self.codePath[0] is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(Path.home()), fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(Path.home()),
QtWidgets.QFileDialog.ShowDirsOnly)) QtWidgets.QFileDialog.ShowDirsOnly))
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(self.codePath[0].parent), fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(self.codePath[0].parent),
QtWidgets.QFileDialog.ShowDirsOnly)) QtWidgets.QFileDialog.ShowDirsOnly))
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
@ -371,10 +405,10 @@ class GUI(object):
def _open_dest(self) -> tuple: def _open_dest(self) -> tuple:
if self.dolPath is None: # Just start in the home directory if self.dolPath is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
@ -387,10 +421,10 @@ class GUI(object):
def _load_session(self) -> tuple: def _load_session(self) -> tuple:
if self.sessionPath is None: if self.sessionPath is None:
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(Path.home()), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(Path.home()),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(self.sessionPath.parent), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(self.sessionPath.parent),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
@ -422,7 +456,8 @@ class GUI(object):
self.uiexSettings.encryptCodes.setChecked(p["encrypt"]) self.uiexSettings.encryptCodes.setChecked(p["encrypt"])
self.uiexSettings.codehookLineEdit.setText(p["hookAddress"]) self.uiexSettings.codehookLineEdit.setText(p["hookAddress"])
self.uiexSettings.kernelHookLineEdit.setText(p["initAddress"]) self.uiexSettings.kernelHookLineEdit.setText(p["initAddress"])
self.uiexSettings.verbositySelect.setCurrentIndex(p["verbosity"]) self.uiexSettings.verbositySelect.setCurrentIndex(
p["verbosity"])
return True, None return True, None
@ -463,7 +498,7 @@ class GUI(object):
cPickle.dump(p, session) cPickle.dump(p, session)
except cPickle.PicklingError as e: except cPickle.PicklingError as e:
return False, str(e) return False, str(e)
return True, None return True, None
except (IOError, PermissionError) as e: except (IOError, PermissionError) as e:
return False, str(e) return False, str(e)
@ -489,7 +524,7 @@ class GUI(object):
except IndexError: except IndexError:
self.ui.set_edit_fields() self.ui.set_edit_fields()
return return
if status is False and msg is not None: if status is False and msg is not None:
reply = QtWidgets.QErrorMessage(self) reply = QtWidgets.QErrorMessage(self)
reply.setWindowTitle("I/O Failure") reply.setWindowTitle("I/O Failure")
@ -534,7 +569,7 @@ class GUI(object):
else: else:
# Input validation # Input validation
if (p.get("qtstyle") in list(QtWidgets.QStyleFactory.keys()) or if (p.get("qtstyle") in list(QtWidgets.QStyleFactory.keys()) or
p.get("qtstyle") == "Default"): p.get("qtstyle") == "Default"):
self.prefs["qtstyle"] = p.get("qtstyle") self.prefs["qtstyle"] = p.get("qtstyle")
if p.get("darktheme") in (True, False): if p.get("darktheme") in (True, False):
@ -549,15 +584,16 @@ class GUI(object):
self.prefs.get("qtstyle"), self.prefs.get("qtstyle"),
flags=QtCore.Qt.MatchFixedString)) flags=QtCore.Qt.MatchFixedString))
self.uiprefs.qtdarkButton.setChecked(self.prefs.get("darktheme")) self.uiprefs.qtdarkButton.setChecked(
self.prefs.get("darktheme"))
self.update_theme() self.update_theme()
except FileNotFoundError: except FileNotFoundError:
self.log.warning("No preferences file found; using defaults.") self.log.warning("No preferences file found; using defaults.")
def save_prefs(self): def save_prefs(self):
datapath = get_program_folder("GeckoLoader") datapath = get_program_folder("GeckoLoader")
self.prefs["qtstyle"] = str(self.uiprefs.qtstyleSelect.currentText()) self.prefs["qtstyle"] = str(self.uiprefs.qtstyleSelect.currentText())
self.prefs["darktheme"] = self.uiprefs.qtdarkButton.isChecked() self.prefs["darktheme"] = self.uiprefs.qtdarkButton.isChecked()
@ -568,7 +604,8 @@ class GUI(object):
self.log.exception(e) self.log.exception(e)
def load_qtstyle(self, style, first_style_load=False): def load_qtstyle(self, style, first_style_load=False):
self.style_log.append([self.app.style, self.uiprefs.qtstyleSelect.currentText()]) self.style_log.append(
[self.app.style, self.uiprefs.qtstyleSelect.currentText()])
if len(self.style_log) > 2: if len(self.style_log) > 2:
self.style_log.pop(0) self.style_log.pop(0)
@ -606,14 +643,16 @@ class GUI(object):
_status = True _status = True
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(resource_path(Path("bin/icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(
str(resource_path(Path("bin/icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
if _status is False: if _status is False:
reply = QtWidgets.QErrorMessage() reply = QtWidgets.QErrorMessage()
reply.setWindowIcon(icon) reply.setWindowIcon(icon)
reply.setWindowTitle("Response Error") reply.setWindowTitle("Response Error")
reply.setText(self._remove_ansi(_errpipe.getvalue())) reply.setText(self._remove_ansi(_errpipe.getvalue()))
reply.setInformativeText("Make sure you have an internet connection") reply.setInformativeText(
"Make sure you have an internet connection")
reply.setIcon(QtWidgets.QMessageBox.Warning) reply.setIcon(QtWidgets.QMessageBox.Warning)
reply.setStandardButtons(QtWidgets.QMessageBox.Ok) reply.setStandardButtons(QtWidgets.QMessageBox.Ok)
reply.exec_() reply.exec_()
@ -621,7 +660,8 @@ class GUI(object):
reply = QtWidgets.QMessageBox() reply = QtWidgets.QMessageBox()
reply.setWindowIcon(icon) reply.setWindowIcon(icon)
reply.setWindowTitle("Update Info") reply.setWindowTitle("Update Info")
reply.setText(self._remove_ansi(_outpipe.getvalue()).strip("\n") + "\n\nYou can find all GeckoLoader releases at:\nhttps://github.com/JoshuaMKW/GeckoLoader/releases") reply.setText(self._remove_ansi(_outpipe.getvalue()).strip(
"\n") + "\n\nYou can find all GeckoLoader releases at:\nhttps://github.com/JoshuaMKW/GeckoLoader/releases")
reply.setIcon(QtWidgets.QMessageBox.Information) reply.setIcon(QtWidgets.QMessageBox.Information)
reply.setStandardButtons(QtWidgets.QMessageBox.Ok) reply.setStandardButtons(QtWidgets.QMessageBox.Ok)
reply.exec_() reply.exec_()
@ -631,9 +671,11 @@ class GUI(object):
textbox.setText(textbox.text().strip()) textbox.setText(textbox.text().strip())
if len(textbox.text()) > 0: if len(textbox.text()) > 0:
_depth = len(hex(mask)[2:]) _depth = len(hex(mask)[2:])
_address = int(textbox.text(), 16) << ((_depth - len(textbox.text())) << 2) _address = int(textbox.text(), 16) << (
(_depth - len(textbox.text())) << 2)
aligned = hex(((_address & mask) | _or) >> ((_depth - len(textbox.text())) << 2))[2:].upper()
aligned = hex(((_address & mask) | _or) >> (
(_depth - len(textbox.text())) << 2))[2:].upper()
textbox.setText(aligned) textbox.setText(aligned)
@staticmethod @staticmethod
@ -642,50 +684,73 @@ class GUI(object):
return ansi_escape.sub('', msg) return ansi_escape.sub('', msg)
def connect_signals(self): def connect_signals(self):
self.ui.actionPreferences.triggered.connect(lambda: self.show_dialog("Preferences")) self.ui.actionPreferences.triggered.connect(
self.ui.actionAbout_Qt.triggered.connect(lambda: self.show_dialog("aboutqt")) lambda: self.show_dialog("Preferences"))
self.ui.actionAbout_GeckoLoader.triggered.connect(lambda: self.show_dialog("aboutGeckoLoader")) self.ui.actionAbout_Qt.triggered.connect(
self.ui.actionCheck_Update.triggered.connect(lambda: self.display_update()) lambda: self.show_dialog("aboutqt"))
self.ui.actionAbout_GeckoLoader.triggered.connect(
lambda: self.show_dialog("aboutGeckoLoader"))
self.ui.actionCheck_Update.triggered.connect(
lambda: self.display_update())
self.ui.actionOpen.triggered.connect(lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_SESSION)) self.ui.actionOpen.triggered.connect(
lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_SESSION))
self.ui.actionClose.triggered.connect(lambda: self.close_session()) self.ui.actionClose.triggered.connect(lambda: self.close_session())
self.ui.actionSave_As.triggered.connect(lambda: self.file_dialog_exec(GUI.Dialogs.SAVE_SESSION_AS)) self.ui.actionSave_As.triggered.connect(
self.ui.actionSave.triggered.connect(lambda: self.file_dialog_exec(GUI.Dialogs.SAVE_SESSION)) lambda: self.file_dialog_exec(GUI.Dialogs.SAVE_SESSION_AS))
self.ui.actionSave.triggered.connect(
lambda: self.file_dialog_exec(GUI.Dialogs.SAVE_SESSION))
self.ui.dolButton.clicked.connect(lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_DOL)) self.ui.dolButton.clicked.connect(
self.ui.gctFileButton.clicked.connect(lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_GCT)) lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_DOL))
self.ui.gctFolderButton.clicked.connect(lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_FOLDER)) self.ui.gctFileButton.clicked.connect(
self.ui.destButton.clicked.connect(lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_DEST)) lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_GCT))
self.ui.gctFolderButton.clicked.connect(
lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_FOLDER))
self.ui.destButton.clicked.connect(
lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_DEST))
self.ui.dolTextBox.textChanged.connect(lambda: self.ui.set_edit_fields()) self.ui.dolTextBox.textChanged.connect(
self.ui.gctFolderTextBox.textChanged.connect(lambda: self.ui.set_edit_fields()) lambda: self.ui.set_edit_fields())
self.ui.gctFileTextBox.textChanged.connect(lambda: self.ui.set_edit_fields()) self.ui.gctFolderTextBox.textChanged.connect(
self.ui.destTextBox.textChanged.connect(lambda: self.ui.set_edit_fields()) lambda: self.ui.set_edit_fields())
self.ui.gctFileTextBox.textChanged.connect(
lambda: self.ui.set_edit_fields())
self.ui.destTextBox.textChanged.connect(
lambda: self.ui.set_edit_fields())
self.ui.allocLineEdit.textChanged.connect(lambda: self._enforce_mask(self.ui.allocLineEdit, 0xFFFFFC)) self.ui.allocLineEdit.textChanged.connect(
lambda: self._enforce_mask(self.ui.allocLineEdit, 0xFFFFFC))
self.ui.exOptionsButton.clicked.connect(lambda: self.show_dialog("Advanced Settings")) self.ui.exOptionsButton.clicked.connect(
lambda: self.show_dialog("Advanced Settings"))
self.ui.compileButton.clicked.connect(lambda: self._exec_api()) self.ui.compileButton.clicked.connect(lambda: self._exec_api())
self.uiprefs.buttonBox.accepted.connect(self.save_prefs) self.uiprefs.buttonBox.accepted.connect(self.save_prefs)
self.uiprefs.qtstyleSelect.currentIndexChanged.connect(lambda: self.load_qtstyle(self.uiprefs.qtstyleSelect.currentText())) self.uiprefs.qtstyleSelect.currentIndexChanged.connect(
lambda: self.load_qtstyle(self.uiprefs.qtstyleSelect.currentText()))
self.uiprefs.qtdarkButton.clicked.connect(lambda: self.update_theme()) self.uiprefs.qtdarkButton.clicked.connect(lambda: self.update_theme())
self.uiexSettings.codehookLineEdit.textChanged.connect(lambda: self._enforce_mask(self.uiexSettings.codehookLineEdit, 0x817FFFFC, 0x80000000)) self.uiexSettings.codehookLineEdit.textChanged.connect(lambda: self._enforce_mask(
self.uiexSettings.kernelHookLineEdit.textChanged.connect(lambda: self._enforce_mask(self.uiexSettings.kernelHookLineEdit, 0x817FFFFC, 0x80000000)) self.uiexSettings.codehookLineEdit, 0x817FFFFC, 0x80000000))
self.uiexSettings.kernelHookLineEdit.textChanged.connect(lambda: self._enforce_mask(
self.uiexSettings.kernelHookLineEdit, 0x817FFFFC, 0x80000000))
def _exec_api(self): def _exec_api(self):
if sys.platform == "win32": if sys.platform == "win32":
self.ui.responses.appendPlainText(f"| Session {self.compileCount} |".center(84, "=") + "\n") self.ui.responses.appendPlainText(
f"| Session {self.compileCount} |".center(84, "=") + "\n")
else: else:
self.ui.responses.appendPlainText(f"| Session {self.compileCount} |".center(76, "=") + "\n") self.ui.responses.appendPlainText(
f"| Session {self.compileCount} |".center(76, "=") + "\n")
self.compileCount += 1 self.compileCount += 1
if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "": if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "":
dol = self.ui.dolTextBox.text().strip() dol = self.ui.dolTextBox.text().strip()
else: else:
self.ui.responses.appendPlainText("DOL is missing, please add the path to your codes in the respective textbox" + "\n\n") self.ui.responses.appendPlainText(
"DOL is missing, please add the path to your codes in the respective textbox" + "\n\n")
return return
if self.ui.gctFileTextBox.isEnabled and self.ui.gctFileTextBox.text().strip() != "": if self.ui.gctFileTextBox.isEnabled and self.ui.gctFileTextBox.text().strip() != "":
@ -693,7 +758,8 @@ class GUI(object):
elif self.ui.gctFolderTextBox.isEnabled and self.ui.gctFolderTextBox.text().strip() != "": elif self.ui.gctFolderTextBox.isEnabled and self.ui.gctFolderTextBox.text().strip() != "":
gct = self.ui.gctFolderTextBox.text().strip() gct = self.ui.gctFolderTextBox.text().strip()
else: else:
self.ui.responses.appendPlainText("GCT is missing, please add the path to your codes in the respective textbox" + "\n\n") self.ui.responses.appendPlainText(
"GCT is missing, please add the path to your codes in the respective textbox" + "\n\n")
return return
alloc = self.ui.allocLineEdit.text().strip() alloc = self.ui.allocLineEdit.text().strip()
@ -705,18 +771,21 @@ class GUI(object):
optimize = self.uiexSettings.optimizeCodes.isChecked() optimize = self.uiexSettings.optimizeCodes.isChecked()
protect = self.uiexSettings.protectCodes.isChecked() protect = self.uiexSettings.protectCodes.isChecked()
encrypt = self.uiexSettings.encryptCodes.isChecked() encrypt = self.uiexSettings.encryptCodes.isChecked()
verbosity = int(self.uiexSettings.verbositySelect.currentText().strip()) verbosity = int(
self.uiexSettings.verbositySelect.currentText().strip())
dest = self.ui.destTextBox.text().strip() dest = self.ui.destTextBox.text().strip()
argslist = [ dol, gct, "-t", txtInclude, "--handler", codeHandlerType, "--hooktype", hookType ] argslist = [dol, gct, "-t", txtInclude, "--handler",
codeHandlerType, "--hooktype", hookType]
if alloc != "": if alloc != "":
argslist.append("-a") argslist.append("-a")
argslist.append(hex(int(alloc, 16) & 0xFFFFFC)[2:].upper()) argslist.append(hex(int(alloc, 16) & 0xFFFFFC)[2:].upper())
if hookAddress != "": if hookAddress != "":
if int(hookAddress, 16) < 0x80000000: if int(hookAddress, 16) < 0x80000000:
self.ui.responses.appendPlainText("The specified code hook is invalid" + "\n") self.ui.responses.appendPlainText(
"The specified code hook is invalid" + "\n")
return return
argslist.append("--hookaddress") argslist.append("--hookaddress")
@ -724,7 +793,8 @@ class GUI(object):
if initAddress != "": if initAddress != "":
if int(initAddress, 16) < 0x80000000: if int(initAddress, 16) < 0x80000000:
self.ui.responses.appendPlainText("The specified initialization address is invalid" + "\n") self.ui.responses.appendPlainText(
"The specified initialization address is invalid" + "\n")
return return
argslist.append("-i") argslist.append("-i")
@ -735,7 +805,8 @@ class GUI(object):
argslist.append("--dest") argslist.append("--dest")
argslist.append(dest) argslist.append(dest)
else: else:
self.ui.responses.appendPlainText("The destination file path is not a valid DOL file\n") self.ui.responses.appendPlainText(
"The destination file path is not a valid DOL file\n")
return return
if optimize: if optimize:
@ -751,7 +822,7 @@ class GUI(object):
argslist.append("-" + "v"*verbosity) argslist.append("-" + "v"*verbosity)
else: else:
argslist.append("-q") argslist.append("-q")
args = self.cli.parse_args(argslist) args = self.cli.parse_args(argslist)
_outpipe = StringIO() _outpipe = StringIO()
@ -769,12 +840,12 @@ class GUI(object):
if _status is False: if _status is False:
_msg = f"Arguments failed! GeckoLoader couldn't execute the job\n\nArgs: {args.__repr__()}\n\nstderr: {self._remove_ansi(_errpipe.getvalue())}" _msg = f"Arguments failed! GeckoLoader couldn't execute the job\n\nArgs: {args.__repr__()}\n\nstderr: {self._remove_ansi(_errpipe.getvalue())}"
self.ui.responses.appendPlainText(_outpipe.getvalue() + "\n\n" + _msg.strip() + "\n") self.ui.responses.appendPlainText(_msg.strip() + "\n")
else: else:
for line in self._remove_ansi(_outpipe.getvalue()).split("\n"): for line in self._remove_ansi(_outpipe.getvalue()).split("\n"):
_msg += line.lstrip() + "\n" _msg += line.lstrip() + "\n"
self.ui.responses.appendPlainText(_msg.strip() + "\n") self.ui.responses.appendPlainText(_msg.strip() + "\n")
def run(self): def run(self):
if sys.platform != "win32": if sys.platform != "win32":
datapath = Path.home() / ".GeckoLoader" datapath = Path.home() / ".GeckoLoader"
@ -783,7 +854,7 @@ class GUI(object):
if not datapath.is_dir(): if not datapath.is_dir():
datapath.mkdir() datapath.mkdir()
self.app = QtWidgets.QApplication(sys.argv) self.app = QtWidgets.QApplication(sys.argv)
self.default_qtstyle = self.app.style().objectName() self.default_qtstyle = self.app.style().objectName()
self.ui = MainWindow(self.version) self.ui = MainWindow(self.version)
@ -808,8 +879,10 @@ class GUI(object):
self.ui.show() self.ui.show()
sys.exit(self.app.exec_()) sys.exit(self.app.exec_())
if __name__ == "__main__": if __name__ == "__main__":
cli = GeckoLoaderCli('GeckoLoader', __version__, description='Dol editing tool for allocating extended codespace') cli = GeckoLoaderCli('GeckoLoader', __version__,
description='Dol editing tool for allocating extended codespace')
if len(sys.argv) == 1: if len(sys.argv) == 1:
cli.print_splash() cli.print_splash()

View file

@ -1,17 +1,7 @@
import logging
import os
import pickle as cPickle
import re
import signal
import subprocess
import sys
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from children_ui import PrefWindow from children_ui import PrefWindow
from dolreader import DolFile
from fileutils import resource_path from fileutils import resource_path
from kernel import CodeHandler, KernelLoader
class MainWindow(QtWidgets.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
def __init__(self, version: str): def __init__(self, version: str):
@ -66,7 +56,7 @@ class MainWindow(QtWidgets.QMainWindow):
font.setWeight(42) font.setWeight(42)
self.setFont(font) self.setFont(font)
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(resource_path(os.path.join("bin", "icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(str(resource_path("bin/icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.setWindowIcon(icon) self.setWindowIcon(icon)
#Top level widget #Top level widget