Light code refactoring
This commit is contained in:
parent
651448b6dd
commit
8e1bd5d38b
2 changed files with 231 additions and 168 deletions
387
GeckoLoader.py
387
GeckoLoader.py
|
@ -1,4 +1,4 @@
|
|||
#Written by JoshuaMK 2020
|
||||
# Written by JoshuaMK 2020
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
|
@ -49,6 +49,7 @@ __version__ = "v7.1.0"
|
|||
|
||||
TMPDIR = Path(tempfile.mkdtemp(prefix="GeckoLoader-"))
|
||||
|
||||
|
||||
@atexit.register
|
||||
def clean_tmp_resources():
|
||||
tmpfolder = TMPDIR.parent
|
||||
|
@ -56,81 +57,84 @@ def clean_tmp_resources():
|
|||
if entry.name.startswith("GeckoLoader-"):
|
||||
shutil.rmtree(entry, ignore_errors=True)
|
||||
|
||||
|
||||
class GeckoLoaderCli(CommandLineParser):
|
||||
|
||||
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.__doc__ = description
|
||||
|
||||
self.add_argument('dolfile', help='DOL file')
|
||||
self.add_argument('codelist', help='Folder or Gecko GCT|TXT file')
|
||||
self.add_argument('-a', '--alloc',
|
||||
help='Define the size of the code allocation in hex, only applies when using the ARENA space',
|
||||
metavar ='SIZE')
|
||||
help='Define the size of the code allocation in hex, only applies when using the ARENA space',
|
||||
metavar='SIZE')
|
||||
self.add_argument('-i', '--init',
|
||||
help='Define where GeckoLoader is initialized in hex',
|
||||
metavar='ADDRESS')
|
||||
help='Define where GeckoLoader is initialized in hex',
|
||||
metavar='ADDRESS')
|
||||
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,
|
||||
"ACTIVE" makes only activated codes get parsed.
|
||||
"ACTIVE" is the default''',
|
||||
default='ACTIVE',
|
||||
metavar='TYPE')
|
||||
default='ACTIVE',
|
||||
metavar='TYPE')
|
||||
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
|
||||
600 lines of gecko codes when using the legacy codespace.
|
||||
"FULL" is the standard codehandler, supporting up to 350 lines of code
|
||||
in the legacy codespace. "FULL" is the default''',
|
||||
default='FULL',
|
||||
choices=['MINI', 'FULL'],
|
||||
metavar='TYPE')
|
||||
default='FULL',
|
||||
choices=['MINI', 'FULL'],
|
||||
metavar='TYPE')
|
||||
self.add_argument('--hooktype',
|
||||
help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended,
|
||||
help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended,
|
||||
although "PAD" can work just as well. "VI" is the default''',
|
||||
default='VI',
|
||||
choices=['VI', 'GX', 'PAD'],
|
||||
metavar='HOOK')
|
||||
default='VI',
|
||||
choices=['VI', 'GX', 'PAD'],
|
||||
metavar='HOOK')
|
||||
self.add_argument('--hookaddress',
|
||||
help='Choose where the codehandler hooks to in hex, overrides auto hooks',
|
||||
metavar='ADDRESS')
|
||||
help='Choose where the codehandler hooks to in hex, overrides auto hooks',
|
||||
metavar='ADDRESS')
|
||||
self.add_argument('-o', '--optimize',
|
||||
help='''Optimizes the codelist by directly patching qualifying
|
||||
help='''Optimizes the codelist by directly patching qualifying
|
||||
ram writes into the dol file, and removing them from the codelist''',
|
||||
action='store_true')
|
||||
action='store_true')
|
||||
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''',
|
||||
action='store_true')
|
||||
action='store_true')
|
||||
self.add_argument('--dest',
|
||||
help='Target path to put the modified DOL, can be a folder or file',
|
||||
metavar='PATH')
|
||||
help='Target path to put the modified DOL, can be a folder or file',
|
||||
metavar='PATH')
|
||||
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.''',
|
||||
action='store_true')
|
||||
action='store_true')
|
||||
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''',
|
||||
action='store_true')
|
||||
action='store_true')
|
||||
self.add_argument('--encrypt',
|
||||
help='Encrypts the codelist on compile time, helping to slow the snoopers',
|
||||
action='store_true')
|
||||
help='Encrypts the codelist on compile time, helping to slow the snoopers',
|
||||
action='store_true')
|
||||
self.add_argument('-q', '--quiet',
|
||||
help='Print nothing to the console',
|
||||
action='store_true')
|
||||
help='Print nothing to the console',
|
||||
action='store_true')
|
||||
self.add_argument('-v', '--verbose',
|
||||
help='Print extra info to the console',
|
||||
default=0,
|
||||
action='count')
|
||||
|
||||
help='Print extra info to the console',
|
||||
default=0,
|
||||
action='count')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__doc__
|
||||
|
||||
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, ' ')
|
||||
|
||||
logo = [' ',
|
||||
|
@ -149,17 +153,18 @@ class GeckoLoaderCli(CommandLineParser):
|
|||
' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ',
|
||||
' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ',
|
||||
' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ',
|
||||
f' ║ {version} ║ ',
|
||||
f' ║ {version} ║ ',
|
||||
' ╚═══════════════════════════════════════════════════════════╝ ',
|
||||
' ',
|
||||
' GeckoLoader is a cli tool for allowing extended ',
|
||||
' gecko code space in all Wii and GC games. ',
|
||||
' ',
|
||||
f'{helpMessage}',
|
||||
f'{helpMessage}',
|
||||
' ']
|
||||
|
||||
for line in logo:
|
||||
print(color_text(line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT))
|
||||
print(color_text(
|
||||
line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT))
|
||||
|
||||
def check_updates(self):
|
||||
repoChecker = Updater('JoshuaMKW', 'GeckoLoader')
|
||||
|
@ -167,39 +172,57 @@ class GeckoLoaderCli(CommandLineParser):
|
|||
tag, status = repoChecker.get_newest_version()
|
||||
|
||||
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('')
|
||||
|
||||
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))
|
||||
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))
|
||||
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(color_text(
|
||||
f' :: Current version is "{self.__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
|
||||
|
||||
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:
|
||||
try:
|
||||
_allocation = int(args.alloc, 16)
|
||||
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:
|
||||
_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))
|
||||
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))
|
||||
self.error(color_text(
|
||||
'The codehandler hook address was invalid\n', defaultColor=TREDLIT))
|
||||
else:
|
||||
_codehook = None
|
||||
|
||||
|
@ -208,57 +231,65 @@ class GeckoLoaderCli(CommandLineParser):
|
|||
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))
|
||||
self.error(color_text(
|
||||
f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT))
|
||||
|
||||
if not os.path.isfile(args.dolfile):
|
||||
self.error(color_text(f'File "{args.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))
|
||||
if not dolFile.is_file():
|
||||
self.error(color_text(
|
||||
f'File "{dolFile}" 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):
|
||||
_allocation, _codehook, codeHandlerFile = self._validate_args(args)
|
||||
|
||||
context = self._validate_args(args)
|
||||
|
||||
try:
|
||||
with open(args.dolfile, "rb") as dol:
|
||||
with context["dol"].open("rb") as 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.allocation = _allocation
|
||||
codeHandler.hookAddress = _codehook
|
||||
codeHandler.hookType = args.hooktype
|
||||
codeHandler.includeAll = args.txtcodes.lower() == 'all'
|
||||
codeHandler.optimizeList = args.optimize
|
||||
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 args.init is not None:
|
||||
geckoKernel.initAddress = int(args.init, 16)
|
||||
if not context["destination"].parent.exists():
|
||||
context["destination"].parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
geckoKernel.verbosity = args.verbose
|
||||
geckoKernel.quiet = args.quiet
|
||||
geckoKernel.encrypt = args.encrypt
|
||||
geckoKernel.protect = args.protect
|
||||
geckoKernel.build(context["codepath"], dolFile,
|
||||
codeHandler, TMPDIR, context["destination"])
|
||||
|
||||
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:
|
||||
self.error(color_text(e, defaultColor=TREDLIT))
|
||||
|
||||
|
||||
class GUI(object):
|
||||
|
||||
class Dialogs:
|
||||
|
@ -289,8 +320,10 @@ class GUI(object):
|
|||
if not get_program_folder("GeckoLoader").exists():
|
||||
get_program_folder("GeckoLoader").mkdir()
|
||||
|
||||
hdlr = logging.FileHandler(get_program_folder("GeckoLoader") / "error.log")
|
||||
formatter = logging.Formatter("\n%(levelname)s (%(asctime)s): %(message)s")
|
||||
hdlr = logging.FileHandler(
|
||||
get_program_folder("GeckoLoader") / "error.log")
|
||||
formatter = logging.Formatter(
|
||||
"\n%(levelname)s (%(asctime)s): %(message)s")
|
||||
hdlr.setFormatter(formatter)
|
||||
self.log.addHandler(hdlr)
|
||||
|
||||
|
@ -298,18 +331,19 @@ class GUI(object):
|
|||
if dialog_type == "aboutqt":
|
||||
QtWidgets.QMessageBox.aboutQt(self.app.activeWindow())
|
||||
elif dialog_type == "aboutGeckoLoader":
|
||||
desc = "".join([ "GeckoLoader is a cross platform tool designed to give ",
|
||||
"the user the most efficient codespace usage possible.\n\n ",
|
||||
"This application supports various features, such as ",
|
||||
"pre-patching codes, dynamic codehandler hooks, codespace ",
|
||||
"extension through memory reallocation, multiple patching ",
|
||||
"of a single DOL, and more.\n\n",
|
||||
desc = "".join(["GeckoLoader is a cross platform tool designed to give ",
|
||||
"the user the most efficient codespace usage possible.\n\n ",
|
||||
"This application supports various features, such as ",
|
||||
"pre-patching codes, dynamic codehandler hooks, codespace ",
|
||||
"extension through memory reallocation, multiple patching ",
|
||||
"of a single DOL, and more.\n\n",
|
||||
f"Current running version: {self.version}\n\n"
|
||||
"Copyright (c) 2020\n\n",
|
||||
"JoshuaMK <joshuamkw2002@gmail.com> \n\n",
|
||||
"All rights reserved." ])
|
||||
"Copyright (c) 2020\n\n",
|
||||
"JoshuaMK <joshuamkw2002@gmail.com> \n\n",
|
||||
"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":
|
||||
self.uiprefs.show()
|
||||
else:
|
||||
|
@ -322,10 +356,10 @@ class GUI(object):
|
|||
def _open_dol(self) -> tuple:
|
||||
if self.dolPath is None: # Just start in the home directory
|
||||
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
|
||||
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
|
||||
return False, None
|
||||
|
@ -338,21 +372,21 @@ class GUI(object):
|
|||
else:
|
||||
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 self.codePath[0] is None: # Just start in the home directory
|
||||
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
|
||||
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:
|
||||
if self.codePath[0] is None: # Just start in the home directory
|
||||
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
|
||||
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
|
||||
return False, None
|
||||
|
@ -371,10 +405,10 @@ class GUI(object):
|
|||
def _open_dest(self) -> tuple:
|
||||
if self.dolPath is None: # Just start in the home directory
|
||||
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
|
||||
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
|
||||
return False, None
|
||||
|
@ -387,10 +421,10 @@ class GUI(object):
|
|||
def _load_session(self) -> tuple:
|
||||
if self.sessionPath is None:
|
||||
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
|
||||
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
|
||||
return False, None
|
||||
|
@ -422,7 +456,8 @@ class GUI(object):
|
|||
self.uiexSettings.encryptCodes.setChecked(p["encrypt"])
|
||||
self.uiexSettings.codehookLineEdit.setText(p["hookAddress"])
|
||||
self.uiexSettings.kernelHookLineEdit.setText(p["initAddress"])
|
||||
self.uiexSettings.verbositySelect.setCurrentIndex(p["verbosity"])
|
||||
self.uiexSettings.verbositySelect.setCurrentIndex(
|
||||
p["verbosity"])
|
||||
|
||||
return True, None
|
||||
|
||||
|
@ -463,7 +498,7 @@ class GUI(object):
|
|||
cPickle.dump(p, session)
|
||||
except cPickle.PicklingError as e:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
return True, None
|
||||
except (IOError, PermissionError) as e:
|
||||
return False, str(e)
|
||||
|
@ -489,7 +524,7 @@ class GUI(object):
|
|||
except IndexError:
|
||||
self.ui.set_edit_fields()
|
||||
return
|
||||
|
||||
|
||||
if status is False and msg is not None:
|
||||
reply = QtWidgets.QErrorMessage(self)
|
||||
reply.setWindowTitle("I/O Failure")
|
||||
|
@ -534,7 +569,7 @@ class GUI(object):
|
|||
else:
|
||||
# Input validation
|
||||
if (p.get("qtstyle") in list(QtWidgets.QStyleFactory.keys()) or
|
||||
p.get("qtstyle") == "Default"):
|
||||
p.get("qtstyle") == "Default"):
|
||||
self.prefs["qtstyle"] = p.get("qtstyle")
|
||||
|
||||
if p.get("darktheme") in (True, False):
|
||||
|
@ -549,15 +584,16 @@ class GUI(object):
|
|||
self.prefs.get("qtstyle"),
|
||||
flags=QtCore.Qt.MatchFixedString))
|
||||
|
||||
self.uiprefs.qtdarkButton.setChecked(self.prefs.get("darktheme"))
|
||||
self.uiprefs.qtdarkButton.setChecked(
|
||||
self.prefs.get("darktheme"))
|
||||
self.update_theme()
|
||||
|
||||
except FileNotFoundError:
|
||||
self.log.warning("No preferences file found; using defaults.")
|
||||
|
||||
|
||||
def save_prefs(self):
|
||||
datapath = get_program_folder("GeckoLoader")
|
||||
|
||||
|
||||
self.prefs["qtstyle"] = str(self.uiprefs.qtstyleSelect.currentText())
|
||||
self.prefs["darktheme"] = self.uiprefs.qtdarkButton.isChecked()
|
||||
|
||||
|
@ -568,7 +604,8 @@ class GUI(object):
|
|||
self.log.exception(e)
|
||||
|
||||
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:
|
||||
self.style_log.pop(0)
|
||||
|
@ -606,14 +643,16 @@ class GUI(object):
|
|||
_status = True
|
||||
|
||||
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:
|
||||
reply = QtWidgets.QErrorMessage()
|
||||
reply.setWindowIcon(icon)
|
||||
reply.setWindowTitle("Response Error")
|
||||
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.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||||
reply.exec_()
|
||||
|
@ -621,7 +660,8 @@ class GUI(object):
|
|||
reply = QtWidgets.QMessageBox()
|
||||
reply.setWindowIcon(icon)
|
||||
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.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||||
reply.exec_()
|
||||
|
@ -631,9 +671,11 @@ class GUI(object):
|
|||
textbox.setText(textbox.text().strip())
|
||||
if len(textbox.text()) > 0:
|
||||
_depth = len(hex(mask)[2:])
|
||||
_address = int(textbox.text(), 16) << ((_depth - len(textbox.text())) << 2)
|
||||
|
||||
aligned = hex(((_address & mask) | _or) >> ((_depth - len(textbox.text())) << 2))[2:].upper()
|
||||
_address = int(textbox.text(), 16) << (
|
||||
(_depth - len(textbox.text())) << 2)
|
||||
|
||||
aligned = hex(((_address & mask) | _or) >> (
|
||||
(_depth - len(textbox.text())) << 2))[2:].upper()
|
||||
textbox.setText(aligned)
|
||||
|
||||
@staticmethod
|
||||
|
@ -642,50 +684,73 @@ class GUI(object):
|
|||
return ansi_escape.sub('', msg)
|
||||
|
||||
def connect_signals(self):
|
||||
self.ui.actionPreferences.triggered.connect(lambda: self.show_dialog("Preferences"))
|
||||
self.ui.actionAbout_Qt.triggered.connect(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.actionPreferences.triggered.connect(
|
||||
lambda: self.show_dialog("Preferences"))
|
||||
self.ui.actionAbout_Qt.triggered.connect(
|
||||
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.actionSave_As.triggered.connect(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.actionSave_As.triggered.connect(
|
||||
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.gctFileButton.clicked.connect(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.dolButton.clicked.connect(
|
||||
lambda: self.file_dialog_exec(GUI.Dialogs.LOAD_DOL))
|
||||
self.ui.gctFileButton.clicked.connect(
|
||||
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.gctFolderTextBox.textChanged.connect(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.dolTextBox.textChanged.connect(
|
||||
lambda: self.ui.set_edit_fields())
|
||||
self.ui.gctFolderTextBox.textChanged.connect(
|
||||
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.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.uiexSettings.codehookLineEdit.textChanged.connect(lambda: self._enforce_mask(self.uiexSettings.codehookLineEdit, 0x817FFFFC, 0x80000000))
|
||||
self.uiexSettings.kernelHookLineEdit.textChanged.connect(lambda: self._enforce_mask(self.uiexSettings.kernelHookLineEdit, 0x817FFFFC, 0x80000000))
|
||||
self.uiexSettings.codehookLineEdit.textChanged.connect(lambda: self._enforce_mask(
|
||||
self.uiexSettings.codehookLineEdit, 0x817FFFFC, 0x80000000))
|
||||
self.uiexSettings.kernelHookLineEdit.textChanged.connect(lambda: self._enforce_mask(
|
||||
self.uiexSettings.kernelHookLineEdit, 0x817FFFFC, 0x80000000))
|
||||
|
||||
def _exec_api(self):
|
||||
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:
|
||||
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
|
||||
|
||||
if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "":
|
||||
dol = self.ui.dolTextBox.text().strip()
|
||||
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
|
||||
|
||||
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() != "":
|
||||
gct = self.ui.gctFolderTextBox.text().strip()
|
||||
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
|
||||
|
||||
alloc = self.ui.allocLineEdit.text().strip()
|
||||
|
@ -705,18 +771,21 @@ class GUI(object):
|
|||
optimize = self.uiexSettings.optimizeCodes.isChecked()
|
||||
protect = self.uiexSettings.protectCodes.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()
|
||||
|
||||
argslist = [ dol, gct, "-t", txtInclude, "--handler", codeHandlerType, "--hooktype", hookType ]
|
||||
argslist = [dol, gct, "-t", txtInclude, "--handler",
|
||||
codeHandlerType, "--hooktype", hookType]
|
||||
|
||||
if alloc != "":
|
||||
argslist.append("-a")
|
||||
argslist.append(hex(int(alloc, 16) & 0xFFFFFC)[2:].upper())
|
||||
|
||||
|
||||
if hookAddress != "":
|
||||
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
|
||||
|
||||
argslist.append("--hookaddress")
|
||||
|
@ -724,7 +793,8 @@ class GUI(object):
|
|||
|
||||
if initAddress != "":
|
||||
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
|
||||
|
||||
argslist.append("-i")
|
||||
|
@ -735,7 +805,8 @@ class GUI(object):
|
|||
argslist.append("--dest")
|
||||
argslist.append(dest)
|
||||
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
|
||||
|
||||
if optimize:
|
||||
|
@ -751,7 +822,7 @@ class GUI(object):
|
|||
argslist.append("-" + "v"*verbosity)
|
||||
else:
|
||||
argslist.append("-q")
|
||||
|
||||
|
||||
args = self.cli.parse_args(argslist)
|
||||
|
||||
_outpipe = StringIO()
|
||||
|
@ -769,12 +840,12 @@ class GUI(object):
|
|||
|
||||
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())}"
|
||||
self.ui.responses.appendPlainText(_outpipe.getvalue() + "\n\n" + _msg.strip() + "\n")
|
||||
self.ui.responses.appendPlainText(_msg.strip() + "\n")
|
||||
else:
|
||||
for line in self._remove_ansi(_outpipe.getvalue()).split("\n"):
|
||||
_msg += line.lstrip() + "\n"
|
||||
self.ui.responses.appendPlainText(_msg.strip() + "\n")
|
||||
|
||||
|
||||
def run(self):
|
||||
if sys.platform != "win32":
|
||||
datapath = Path.home() / ".GeckoLoader"
|
||||
|
@ -783,7 +854,7 @@ class GUI(object):
|
|||
|
||||
if not datapath.is_dir():
|
||||
datapath.mkdir()
|
||||
|
||||
|
||||
self.app = QtWidgets.QApplication(sys.argv)
|
||||
self.default_qtstyle = self.app.style().objectName()
|
||||
self.ui = MainWindow(self.version)
|
||||
|
@ -808,8 +879,10 @@ class GUI(object):
|
|||
self.ui.show()
|
||||
sys.exit(self.app.exec_())
|
||||
|
||||
|
||||
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:
|
||||
cli.print_splash()
|
||||
|
|
12
main_ui.py
12
main_ui.py
|
@ -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 children_ui import PrefWindow
|
||||
from dolreader import DolFile
|
||||
from fileutils import resource_path
|
||||
from kernel import CodeHandler, KernelLoader
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, version: str):
|
||||
|
@ -66,7 +56,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
font.setWeight(42)
|
||||
self.setFont(font)
|
||||
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)
|
||||
|
||||
#Top level widget
|
||||
|
|
Reference in a new issue