From 12d031d2ec6902c8abc0f872a37417da25c42c0c Mon Sep 17 00:00:00 2001 From: JoshuaMKW Date: Fri, 6 Nov 2020 04:14:27 -0600 Subject: [PATCH] New GUI, lots of code restructuring --- GeckoLoader.py | 805 +++++++++++++++++++++++++++++++++++++++------- fileutils.py | 33 +- installer.cs | 203 ------------ kernel.py | 83 ++--- main_ui.py | 832 ++++++++++++++++++++++++++++++++++++++++++++++++ versioncheck.py | 2 +- 6 files changed, 1592 insertions(+), 366 deletions(-) delete mode 100644 installer.cs create mode 100644 main_ui.py diff --git a/GeckoLoader.py b/GeckoLoader.py index ce7f3f9..22ad58f 100644 --- a/GeckoLoader.py +++ b/GeckoLoader.py @@ -1,15 +1,26 @@ #Written by JoshuaMK 2020 import argparse -import os -import random -import shutil -import sys import atexit +import logging +import os +import pickle as cPickle +import re +import shutil +import signal +import sys +import tempfile +from contextlib import redirect_stdout, redirect_stderr from distutils.version import LooseVersion +from io import StringIO +from PyQt5 import QtCore, QtGui, QtWidgets + +from children_ui import PrefWindow, SettingsWindow from dolreader import DolFile +from fileutils import resource_path, get_program_folder from kernel import CodeHandler, KernelLoader +from main_ui import MainWindow from tools import CommandLineParser, color_text from versioncheck import Updater @@ -34,30 +45,16 @@ except ImportError: TRED = '' TREDLIT = '' -__version__ = 'v6.2.0' +__version__ = "v6.3.0" -def resource_path(relative_path: str = ""): - """ Get absolute path to resource, works for dev and for PyInstaller """ - base_path = os.path.dirname(os.path.realpath(sys.argv[0])) - return os.path.join(base_path, relative_path) - -TMPDIR = resource_path(''.join(random.choice('1234567890-_abcdefghijklomnpqrstuvwxyz') for i in range(6)) + '-GeckoLoader') - -def sort_file_args(fileA, fileB): - if os.path.splitext(fileA)[1].lower() == '.dol': - dolFile = fileA - gctFile = fileB - elif os.path.splitext(fileB)[1].lower() == '.dol': - dolFile = fileB - gctFile = fileA - else: - parser.error(color_text('No dol file was passed\n', defaultColor=TREDLIT)) - return dolFile, gctFile +TMPDIR = tempfile.mkdtemp("GeckoLoader-") @atexit.register def clean_tmp_resources(): - if os.path.isdir(TMPDIR): - shutil.rmtree(TMPDIR) + tmpfolder = os.path.dirname(TMPDIR) + for entry in os.listdir(tmpfolder): + if entry.startswith("GeckoLoader-"): + shutil.rmtree(entry, ignore_errors=True) class GeckoLoaderCli(CommandLineParser): @@ -69,34 +66,34 @@ class GeckoLoaderCli(CommandLineParser): 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('-m', '--movecodes', - help='''["AUTO", "LEGACY", "ARENA"] Choose if GeckoLoader moves the codes to OSArenaHi, - or the legacy space. Default is "AUTO", - which auto decides where to insert the codes''', - default='AUTO', - choices=['AUTO', 'LEGACY', 'ARENA'], - metavar='TYPE') + help='''["AUTO", "LEGACY", "ARENA"] Choose if GeckoLoader moves the codes to OSArenaHi, + or the legacy space. Default is "AUTO", + which auto decides where to insert the codes''', + default='AUTO', + choices=['AUTO', 'LEGACY', 'ARENA'], + metavar='TYPE') self.add_argument('-tc', '--txtcodes', - 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') + 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') self.add_argument('--handler', - 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') + 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') self.add_argument('--hooktype', 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''', @@ -117,16 +114,17 @@ class GeckoLoaderCli(CommandLineParser): self.add_argument('--dest', help='Target path to put the modified DOL, can be a folder or file', metavar='PATH') - self.add_argument('--check-update', + self.add_argument('--checkupdate', help='''Checks to see if a new update exists on the GitHub Repository releases page, this option overrides all other commands.''', action='store_true') + self.add_argument('--splash', + help='''Print the splash screen, this option overrides + all other commands excluding --checkupdate''', + action='store_true') self.add_argument('--encrypt', help='Encrypts the codelist on compile time, helping to slow the snoopers', action='store_true') - self.add_argument('--disablecolor', - help='Disables the colorization of text output', - action='store_true') self.add_argument('-q', '--quiet', help='Print nothing to the console', action='store_true') @@ -135,7 +133,7 @@ class GeckoLoaderCli(CommandLineParser): default=0, action='count') - def __str__(self): + def __str__(self) -> str: return self.__doc__ def print_splash(self): @@ -169,8 +167,6 @@ class GeckoLoaderCli(CommandLineParser): for line in logo: print(color_text(line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT)) - - sys.exit(0) def check_updates(self): repoChecker = Updater('JoshuaMKW', 'GeckoLoader') @@ -193,89 +189,656 @@ class GeckoLoaderCli(CommandLineParser): print(color_text(f' :: Current version is "{self.__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT)) print('') - sys.exit(0) -if __name__ == "__main__": - parser = GeckoLoaderCli('GeckoLoader', __version__, description='Dol editing tool for allocating extended codespace') - - if len(sys.argv) == 1: - parser.print_splash() - elif '--check-update' in sys.argv: - parser.check_updates() - - args = parser.parse_args() - - if args.alloc: - try: - _allocation = int(args.alloc, 16) - except ValueError: - parser.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT)) - else: - _allocation = None - - if args.hookaddress: - if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000: - parser.error(color_text('The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT)) - else: + def _validate_args(self, args) -> tuple: + if args.alloc: try: - _codehook = int(args.hookaddress, 16) + _allocation = int(args.alloc, 16) except ValueError: - parser.error(color_text('The codehandler hook address was invalid\n', defaultColor=TREDLIT)) - else: - _codehook = None + self.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT)) + else: + _allocation = None - if args.handler == 'MINI': - codeHandlerFile = 'codehandler-mini.bin' - else: - codeHandlerFile = 'codehandler.bin' + if args.hookaddress: + if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000: + self.error(color_text('The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT)) + else: + try: + _codehook = int(args.hookaddress, 16) + except ValueError: + self.error(color_text('The codehandler hook address was invalid\n', defaultColor=TREDLIT)) + else: + _codehook = None + + if args.handler == CodeHandler.Types.MINI: + codeHandlerFile = 'codehandler-mini.bin' + elif args.handler == CodeHandler.Types.FULL: + codeHandlerFile = 'codehandler.bin' + else: + self.error(color_text(f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT)) - try: if not os.path.isfile(args.dolfile): - parser.error(color_text(f'File "{args.dolfile}" does not exist\n', defaultColor=TREDLIT)) + self.error(color_text(f'File "{args.dolfile}" does not exist\n', defaultColor=TREDLIT)) if not os.path.exists(args.codelist): - parser.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT)) + self.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT)) - with open(os.path.normpath(args.dolfile), 'rb') as dol: - dolFile = DolFile(dol) + return _allocation, _codehook, codeHandlerFile - 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.hookType = args.hooktype - codeHandler.includeAll = args.txtcodes.lower() == 'all' - codeHandler.optimizeList = args.optimize - - with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile: - geckoKernel = KernelLoader(kernelfile, parser) - - if args.init is not None: - geckoKernel.initAddress = int(args.init, 16) - - geckoKernel.patchJob = args.movecodes - geckoKernel.verbosity = args.verbose - geckoKernel.quiet = args.quiet - geckoKernel.encrypt = args.encrypt - geckoKernel.protect = args.protect + def _exec(self, args, tmpdir): + if not os.path.isabs(args.dolfile): + args.dolfile = os.path.abspath(args.dolfile) + + if not os.path.isabs(args.codelist): + args.codelist = os.path.abspath(args.codelist) 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))) + if not os.path.isabs(args.dest): + args.dest = os.path.abspath(args.dest) + + _allocation, _codehook, codeHandlerFile = self._validate_args(args) + + try: + with open(os.path.normpath(args.dolfile), 'rb') as dol: + dolFile = DolFile(dol) + + 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.hookType = args.hooktype + codeHandler.includeAll = args.txtcodes.lower() == 'all' + codeHandler.optimizeList = args.optimize + + with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile: + geckoKernel = KernelLoader(kernelfile, cli) + + if args.init is not None: + geckoKernel.initAddress = int(args.init, 16) + + geckoKernel.patchJob = args.movecodes + geckoKernel.verbosity = args.verbose + geckoKernel.quiet = args.quiet + geckoKernel.encrypt = args.encrypt + geckoKernel.protect = args.protect + + if args.dest: + if not os.path.isabs(args.dest): + if os.path.splitext(args.dest)[1] == "": + dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('.').lstrip('\\').lstrip('/'), os.path.basename(args.dolfile))) + else: + dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('.').lstrip('\\').lstrip('/'))) + else: + if os.path.splitext(args.dest)[1] == "": + dest = os.path.normpath(os.path.join(args.dest.lstrip('.').lstrip('\\').lstrip('/'), os.path.basename(args.dolfile))) + else: + dest = os.path.normpath(os.path.join(args.dest.lstrip('.').lstrip('\\').lstrip('/'))) else: - dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/'))) + dest = os.path.normpath(os.path.join(os.getcwd(), "geckoloader-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) + + except FileNotFoundError as e: + self.error(color_text(e, defaultColor=TREDLIT)) + +class GUI(object): + + class Dialogs: + LOAD_DEST = 0 + LOAD_GCT = 1 + LOAD_FOLDER = 2 + LOAD_DOL = 3 + LOAD_SESSION = 4 + SAVE_SESSION = 5 + SAVE_SESSION_AS = 6 + + def __init__(self, cli: GeckoLoaderCli): + self.cli = cli + self.app = None + self.ui = None + self.uiprefs = None + self.uiexSettings = None + self.dolPath = None + self.codePath = [None, None] + self.destPath = None + self.sessionPath = None + self.prefs = {"qtstyle": "Default", "darktheme": False} + self.style_log = [] + + self.log = logging.getLogger(f"GeckoLoader {self.cli.__version__}") + + hdlr = logging.FileHandler(os.path.join(get_program_folder("GeckoLoader"), "error.log")) + formatter = logging.Formatter("\n%(levelname)s (%(asctime)s): %(message)s") + hdlr.setFormatter(formatter) + self.log.addHandler(hdlr) + + def show_dialog(self, dialog_type=None): + 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", + f"Current running version: {self.cli.__version__}\n\n" + "Copyright (c) 2020\n\n", + "JoshuaMK \n\n", + "All rights reserved." ]) + + QtWidgets.QMessageBox.about(self.app.activeWindow(), "About GeckoLoader", desc) + elif dialog_type == "Preferences": + self.uiprefs.show() else: - dest = os.path.normpath(os.path.join(os.getcwd(), "geckoloader-build", os.path.basename(args.dolfile))) + self.uiexSettings.show() - if not os.path.exists(dest) and os.path.dirname(dest) not in ('', '/'): - os.makedirs(os.path.dirname(dest), exist_ok=True) + 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", os.path.expanduser("~"), + "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", os.path.split(self.dolPath)[0], + "Nintendo DOL Executable (*.dol);;All files (*)")[0]) - if not os.path.isdir(TMPDIR): - os.mkdir(TMPDIR) + if fname == "" or fname is None: # Make sure we have something to open + return False, None + else: + self.dolPath = os.path.normpath(fname) + + if os.path.isfile(self.dolPath): + self.ui.dolTextBox.setText(self.dolPath) + return True, None + else: + return False, "The file does not exist!" + + 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", os.path.expanduser("~"), + "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", os.path.split(self.codePath[0])[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", os.path.expanduser("~"), + QtWidgets.QFileDialog.ShowDirsOnly)) + else: # Start in the last directory used by the user + fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", os.path.split(self.codePath[0])[0], + QtWidgets.QFileDialog.ShowDirsOnly)) + + if fname == "" or fname is None: # Make sure we have something to open + return False, None + else: + self.codePath = [os.path.normpath(fname), isFolder] + + if not isFolder: + self.ui.gctFileTextBox.setText(self.codePath[0]) + self.ui.gctFolderTextBox.setText("") + else: + self.ui.gctFileTextBox.setText("") + self.ui.gctFolderTextBox.setText(self.codePath[0]) + + return True, None + + 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", os.path.expanduser("~"), + "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", os.path.split(self.dolPath)[0], + "Nintendo DOL Executable (*.dol);;All files (*)")[0]) + + if fname == "" or fname is None: # Make sure we have something to open + return False, None + else: + self.destPath = os.path.normpath(fname) + self.ui.destTextBox.setText(self.destPath) + + return True, None + + def _load_session(self) -> tuple: + if self.sessionPath is None: + fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", os.path.expanduser("~"), + "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", os.path.split(self.sessionPath)[0], + "GeckoLoader Session (*.gprf);;All files (*)")[0]) + + if fname == "" or fname is None: # Make sure we have something to open + return False, None + else: + self.sessionPath = os.path.normpath(fname) + + with open(self.sessionPath, "rb") as session: + p = cPickle.load(session) + + self.ui.dolTextBox.setText(p["dolPath"]) + + if p["gctFilePath"] != "": + self.ui.gctFileTextBox.setText(p["gctFilePath"]) + self.ui.gctFolderTextBox.setText("") + elif p["gctFolderPath"] != "": + self.ui.gctFileTextBox.setText("") + self.ui.gctFolderTextBox.setText(p["gctFolderPath"]) + else: + self.ui.gctFileTextBox.setText("") + self.ui.gctFolderTextBox.setText("") + + self.ui.destTextBox.setText(p["destPath"]) + self.ui.allocLineEdit.setText(p["alloc"]) + self.ui.patchTypeSelect.setCurrentIndex(p["patchIndex"]) + self.ui.handlerTypeSelect.setCurrentIndex(p["handlerIndex"]) + self.ui.hookTypeSelect.setCurrentIndex(p["hookIndex"]) + self.ui.txtCodesIncludeSelect.setCurrentIndex(p["txtIndex"]) + self.ui.optimizeSelect.setCurrentIndex(p["optimizeIndex"]) + self.uiexSettings.protectCodes.setChecked(p["protect"]) + self.uiexSettings.encryptCodes.setChecked(p["encrypt"]) + self.uiexSettings.codehookLineEdit.setText(p["hookAddress"]) + self.uiexSettings.kernelHookLineEdit.setText(p["initAddress"]) + self.uiexSettings.verbositySelect.setCurrentIndex(p["verbosity"]) + + return True, None + + def _save_session(self, saveAs=False): + if saveAs or self.sessionPath is None or self.sessionPath == "": + if self.sessionPath is None: # Just start in the home directory + fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", os.path.expanduser("~"), + "GeckoLoader Session (*.gprf);;All files (*)")[0]) + else: # Start in the last directory used by the user + fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", os.path.split(self.dolPath)[0], + "GeckoLoader Session (*.gprf);;All files (*)")[0]) + + if fname == "" or fname is None: # Make sure we have something to open + return False, None + else: + self.sessionPath = os.path.normpath(fname) + + try: + with open(self.sessionPath, "wb") as session: + p = {} + + p["dolPath"] = self.ui.dolTextBox.text().strip() + p["gctFilePath"] = self.ui.gctFileTextBox.text().strip() + p["gctFolderPath"] = self.ui.gctFolderTextBox.text().strip() + p["destPath"] = self.ui.destTextBox.text().strip() + p["alloc"] = self.ui.allocLineEdit.text().strip() + p["patchIndex"] = self.ui.patchTypeSelect.currentIndex() + p["handlerIndex"] = self.ui.handlerTypeSelect.currentIndex() + p["hookIndex"] = self.ui.hookTypeSelect.currentIndex() + p["txtIndex"] = self.ui.txtCodesIncludeSelect.currentIndex() + p["optimizeIndex"] = self.ui.optimizeSelect.currentIndex() + p["protect"] = self.uiexSettings.protectCodes.isChecked() + p["encrypt"] = self.uiexSettings.encryptCodes.isChecked() + p["hookAddress"] = self.uiexSettings.codehookLineEdit.text().strip() + p["initAddress"] = self.uiexSettings.kernelHookLineEdit.text().strip() + p["verbosity"] = self.uiexSettings.verbositySelect.currentIndex() + + try: + 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) + + def file_dialog_exec(self, event: Dialogs): + try: + if event == GUI.Dialogs.LOAD_DOL: + status, msg = self._open_dol() + elif event == GUI.Dialogs.LOAD_GCT: + status, msg = self._load_codes(False) + elif event == GUI.Dialogs.LOAD_FOLDER: + status, msg = self._load_codes(True) + elif event == GUI.Dialogs.LOAD_DEST: + status, msg = self._open_dest() + elif event == GUI.Dialogs.LOAD_SESSION: + status, msg = self._load_session() + elif event == GUI.Dialogs.SAVE_SESSION: + status, msg = self._save_session() + elif event == GUI.Dialogs.SAVE_SESSION_AS: + status, msg = self._save_session(True) + else: + return + except IndexError: + self.ui.set_edit_fields() + return - geckoKernel.build(args.codelist, dolFile, codeHandler, TMPDIR, dest) + if status is False and msg is not None: + reply = QtWidgets.QErrorMessage(self) + reply.setWindowTitle("I/O Failure") + reply.setText(msg) + reply.setInformativeText("Please try again.") + reply.setIcon(QtWidgets.QMessageBox.Warning) + reply.setStandardButtons(QtWidgets.QMessageBox.Ok) + reply.exec_() + else: + self.ui.set_edit_fields() + + def close_session(self): + self.dolPath = None + self.codePath = None + self.gctData = None + self.ui.dolTextBox.setText("") + self.ui.gctFileTextBox.setText("") + self.ui.gctFolderTextBox.setText("") + self.ui.destTextBox.setText("") + self.ui.allocLineEdit.setText("") + self.ui.patchTypeSelect.setCurrentIndex(0) + self.ui.handlerTypeSelect.setCurrentIndex(0) + self.ui.hookTypeSelect.setCurrentIndex(0) + self.ui.txtCodesIncludeSelect.setCurrentIndex(0) + self.ui.optimizeSelect.setCurrentIndex(0) + self.ui.responses.setPlainText("") + self.uiexSettings.protectCodes.setChecked(False) + self.uiexSettings.encryptCodes.setChecked(False) + self.uiexSettings.codehookLineEdit.setText("") + self.uiexSettings.kernelHookLineEdit.setText("") + self.uiexSettings.verbositySelect.setCurrentIndex(0) + self.ui.set_edit_fields() + #Reset all ui elements as needed + + def load_prefs(self): + datapath = get_program_folder("GeckoLoader") + + try: + with open(os.path.join(datapath, ".GeckoLoader.conf"), "rb") as f: + try: + p = cPickle.load(f) + except cPickle.UnpicklingError as e: + self.log.exception(e) # Use defaults for prefs + else: + # Input validation + if (p.get("qtstyle") in list(QtWidgets.QStyleFactory.keys()) or + p.get("qtstyle") == "Default"): + self.prefs["qtstyle"] = p.get("qtstyle") + + if p.get("darktheme") in (True, False): + self.prefs["darktheme"] = p.get("darktheme") + + setCIndex = self.uiprefs.qtstyleSelect.setCurrentIndex + + if self.prefs.get("qtstyle") in (None, "Default"): + setCIndex(0) + else: + setCIndex(self.uiprefs.qtstyleSelect.findText( + self.prefs.get("qtstyle"), + flags=QtCore.Qt.MatchFixedString)) + + 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() + + try: + with open(os.path.join(datapath, ".GeckoLoader.conf"), "wb") as f: + cPickle.dump(self.prefs, f) + except IOError as e: + self.log.exception(e) + + def load_qtstyle(self, style, first_style_load=False): + self.style_log.append( [self.app.style, self.uiprefs.qtstyleSelect.currentText()] ) + + if len(self.style_log) > 2: + self.style_log.pop(0) + + if style != "Default": + self.app.setStyle(style) + else: + self.app.setStyle(self.default_qtstyle) + + if first_style_load: + setCIndex = self.uiprefs.qtstyleSelect.setCurrentIndex + setCIndex(self.uiprefs.qtstyleSelect.findText(style, + flags=QtCore.Qt.MatchFixedString)) + + def update_theme(self): + if self.uiprefs.qtdarkButton.isChecked(): + self.app.setPalette(self.ui.DarkTheme) + self.load_qtstyle("Fusion", True) + self.uiprefs.qtstyleSelect.setDisabled(True) + else: + self.app.setPalette(self.ui.LightTheme) + self.load_qtstyle(self.style_log[0][1], True) + self.uiprefs.qtstyleSelect.setEnabled(True) + + def display_update(self): + _outpipe = StringIO() + _errpipe = StringIO() + + with redirect_stdout(_outpipe), redirect_stderr(_errpipe): + try: + self.cli.check_updates() + except SystemExit: + _status = False + else: + _status = True + + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("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.setIcon(QtWidgets.QMessageBox.Warning) + reply.setStandardButtons(QtWidgets.QMessageBox.Ok) + reply.exec_() + else: + 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.setIcon(QtWidgets.QMessageBox.Information) + reply.setStandardButtons(QtWidgets.QMessageBox.Ok) + reply.exec_() + + @staticmethod + def _enforce_mask(textbox: QtWidgets.QTextEdit, mask: int, _or: int = 0): + 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() + textbox.setText(aligned) + + @staticmethod + def _remove_ansi(msg: str) -> str: + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + 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.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.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.allocLineEdit.textChanged.connect(lambda: self._enforce_mask(self.ui.allocLineEdit, 0xFFFFFC)) + + 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.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)) + + def _exec_api(self): + if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "": + dol = os.path.normpath(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") + return + + if self.ui.gctFileTextBox.isEnabled and self.ui.gctFileTextBox.text().strip() != "": + gct = os.path.normpath(self.ui.gctFileTextBox.text().strip()) + elif self.ui.gctFolderTextBox.isEnabled and self.ui.gctFolderTextBox.text().strip() != "": + gct = os.path.normpath(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") + return + + alloc = self.ui.allocLineEdit.text().strip() + patchJob = self.ui.patchTypeSelect.currentText().strip() + hookType = self.ui.hookTypeSelect.currentText().strip() + hookAddress = self.uiexSettings.codehookLineEdit.text().strip() + initAddress = self.uiexSettings.kernelHookLineEdit.text().strip() + txtInclude = self.ui.txtCodesIncludeSelect.currentText().strip() + codeHandlerType = self.ui.handlerTypeSelect.currentText().strip() + optimize = self.ui.optimizeSelect.currentText().strip() == "TRUE" + protect = self.uiexSettings.protectCodes.isChecked() + encrypt = self.uiexSettings.encryptCodes.isChecked() + verbosity = int(self.uiexSettings.verbositySelect.currentText().strip()) + dest = self.ui.destTextBox.text().strip() + + argslist = [ dol, gct, "-m", patchJob, "-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") + return + + argslist.append("--hookaddress") + argslist.append(hookAddress) + + if initAddress != "": + if int(initAddress, 16) < 0x80000000: + self.ui.responses.appendPlainText("The specified initialization address is invalid" + "\n") + return + + argslist.append("-i") + argslist.append(initAddress) + + if dest != "": + if dest.lower().endswith(".dol") and len(dest) > 4: + argslist.append("--dest") + argslist.append(dest) + else: + self.ui.responses.appendPlainText("The destination file path is not a valid DOL file\n") + return + + if optimize: + argslist.append("-o") + + if protect: + argslist.append("-p") + + if encrypt: + argslist.append("--encrypt") + + if verbosity > 0: + argslist.append("-" + "v"*verbosity) + else: + argslist.append("-q") + + args = self.cli.parse_args(argslist) + + _outpipe = StringIO() + _errpipe = StringIO() + _status = False + _msg = "" + + with redirect_stdout(_outpipe), redirect_stderr(_errpipe): + try: + self.cli._exec(args, tmpdir=TMPDIR) + except SystemExit: + _status = False + else: + _status = True + + 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(_msg.strip() + "\n") + else: + for line in self._remove_ansi(_outpipe.getvalue()).split("\n"): + if line.strip() != "": + _msg += line.lstrip() + self.ui.responses.appendPlainText(_msg.strip() + "\n") + + def run(self): + if sys.platform != "win32": + datapath = os.path.join(os.getenv("HOME"), ".GeckoLoader") + else: + datapath = os.path.join(os.getenv("APPDATA"), "GeckoLoader") + + if not os.path.isdir(datapath): + os.mkdir(datapath) + + self.app = QtWidgets.QApplication(sys.argv) + self.default_qtstyle = self.app.style().objectName() + self.ui = MainWindow(self.cli.__version__) + self.uiprefs = PrefWindow() + self.uiexSettings = SettingsWindow() + + self.uiprefs.qtstyleSelect.addItem("Default") + + for i in range(0, len(list(QtWidgets.QStyleFactory.keys()))): + self.uiprefs.qtstyleSelect.addItem(list(QtWidgets.QStyleFactory.keys())[i]) + + self.load_prefs() + self.load_qtstyle(self.prefs.get("qtstyle"), True) + + regex = QtCore.QRegExp("[0-9A-Fa-f]*") + validator = QtGui.QRegExpValidator(regex) + self.ui.allocLineEdit.setValidator(validator) + self.uiexSettings.codehookLineEdit.setValidator(validator) + self.uiexSettings.kernelHookLineEdit.setValidator(validator) + + self.connect_signals() + self.ui.show() + sys.exit(self.app.exec_()) + +if __name__ == "__main__": + cli = GeckoLoaderCli('GeckoLoader', __version__, description='Dol editing tool for allocating extended codespace') + + if len(sys.argv) == 1: + cli.print_splash() + app = GUI(cli) + signal.signal(signal.SIGINT, signal.SIG_DFL) + app.run() + sys.exit(1) #Should never reach here + + elif '--checkupdate' in sys.argv: + cli.check_updates() + sys.exit(0) + elif '--splash' in sys.argv: + cli.print_splash() sys.exit(0) - except FileNotFoundError as e: - parser.error(color_text(e + '\n', defaultColor=TREDLIT)) + args = cli.parse_args() + cli._exec(args, TMPDIR) diff --git a/fileutils.py b/fileutils.py index 396b1c4..8b173ed 100644 --- a/fileutils.py +++ b/fileutils.py @@ -1,5 +1,34 @@ -from tools import get_alignment, align_byte_size +import os import struct +import sys + +from tools import align_byte_size, get_alignment + +def resource_path(relative_path: str = "") -> str: + """ Get absolute path to resource, works for dev and for cx_freeze """ + if getattr(sys, "frozen", False): + # The application is frozen + base_path = os.path.dirname(sys.executable) + else: + base_path = os.path.dirname(os.path.abspath(__file__)) + + return os.path.join(base_path, relative_path) + +def get_program_folder(folder: str = "") -> str: + """ Get path to appdata """ + if sys.platform == "win32": + datapath = os.path.join(os.getenv("APPDATA"), folder) + elif sys.platform == "darwin": + if folder: + folder = "." + folder + datapath = os.path.join(os.path.expanduser("~"), "Library", "Application Support", folder) + elif "linux" in sys.platform: + if folder: + folder = "." + folder + datapath = os.path.join(os.getenv("HOME"), folder) + else: + raise NotImplementedError(f"{sys.platform} OS is unsupported") + return datapath def read_sbyte(f): return struct.unpack("b", f.read(1))[0] @@ -54,4 +83,4 @@ def read_bool(f, vSize=1): def write_bool(f, val, vSize=1): if val is True: f.write(b'\x00'*(vSize-1) + b'\x01') - else: f.write(b'\x00' * vSize) \ No newline at end of file + else: f.write(b'\x00' * vSize) diff --git a/installer.cs b/installer.cs deleted file mode 100644 index 826ace1..0000000 --- a/installer.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Serialization; - -public class Program -{ - static void Main(string[] args) - { - Installer installer = new Installer(); - installer.GetUserInput(); - } -} - -public class Installer -{ - public string programfolder; - public bool copyfiles; - public bool overwrite; - - public Installer() - { - programfolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GeckoLoader"); - copyfiles = true; - overwrite = true; - } - - void CopyAll (DirectoryInfo source, DirectoryInfo destination, string wildcard, string[] exclude, int maxdepth = 4) - { - if (maxdepth <= 0) return; - - DirectoryInfo[] subdirs = source.GetDirectories(); - - foreach (DirectoryInfo dirPath in source.EnumerateDirectories()) - { - Directory.CreateDirectory(dirPath.FullName.Replace(source.FullName, destination.FullName)); - } - - - foreach (FileInfo filePath in source.EnumerateFiles(wildcard)) - { - File.Copy(filePath.FullName, filePath.FullName.Replace(source.FullName, destination.FullName), true); - } - - foreach (DirectoryInfo dir in subdirs) - { - DirectoryInfo dest = new DirectoryInfo(Path.Combine(destination.FullName, dir.Name)); - DirectoryInfo src = new DirectoryInfo(dir.FullName); - - CopyAll(src, dest, wildcard, exclude, maxdepth - 1); - } - } - - private static void ClearConsoleLine(int index, bool moveto) - { - int currentLineCursor = Console.CursorTop; - Console.SetCursorPosition(0, index); - Console.Write(new string(' ', Console.WindowWidth)); - if (moveto) Console.SetCursorPosition(0, index); - else Console.SetCursorPosition(0, currentLineCursor); - } - - private static string HandleConsoleQuestion(string msg, string[] options) - { - bool handled = false; - string input = String.Empty; - - while (handled == false) - { - Console.Write("{0} ({1}): ", msg, String.Join("|", options)); - - input = Console.ReadLine(); - if (options.Any(s => s.Contains(input.ToLower()))) - { - handled = true; - } - else - { - ClearConsoleLine(Console.CursorTop - 1, true); - } - } - return input; - } - - private void SetProgramFolder(string folderName) - { - this.programfolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GeckoLoader"); - } - - private void SetFolderToPath(string dir, string subdir) - { - var scope = EnvironmentVariableTarget.User; - var curPATH = Environment.GetEnvironmentVariable("PATH", scope); - - if (!curPATH.Contains(Path.Combine(dir, subdir))) - { - var newValue = curPATH + ";" + Path.Combine(dir, subdir); - Environment.SetEnvironmentVariable("PATH", newValue.Replace(";;", ";"), scope); - } - } - - private void RemoveFolderGroupFromPath(string dir) - { - var scope = EnvironmentVariableTarget.User; - var curPATH = Environment.GetEnvironmentVariable("PATH", scope); - - string[] oldPATHList = curPATH.Split(';'); - List newPATHList = new List(); - - foreach(string path in oldPATHList) - { - if (!path.ToLower().Contains(dir.ToLower())) - { - newPATHList.Add(path); - } - } - Environment.SetEnvironmentVariable("PATH", String.Join(";", newPATHList.ToArray()), scope); - - } - - private bool MoveFilesToprogramfolder(string wildcard, bool copy = true, bool overwrite = false) - { - DirectoryInfo cwd = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "data")); - DirectoryInfo programspace = new DirectoryInfo(this.programfolder); - - if (!programspace.Exists) programspace.Create(); - - try - { - foreach (FileInfo file in programspace.EnumerateFiles()) - { - file.Delete(); - } - foreach (DirectoryInfo dir in programspace.EnumerateDirectories()) - { - dir.Delete(true); - } - - //Copy dependancies - string[] exclude = { "installer.exe" }; - this.CopyAll(cwd, programspace, wildcard, exclude); - } - catch (UnauthorizedAccessException e) - { - Console.WriteLine(String.Format("Insufficient privledges provided! {0}\nTry running with administrator privledges", e)); - return false; - } - - return true; - } - - private void DeleteProgramFolder() - { - if (Directory.Exists(this.programfolder)) Directory.Delete(this.programfolder, true); - } - - public void GetUserInput() - { - string status; - string[] continueoptions = { "y", "n" }; - string[] actionoptions = { "install", "uninstall" }; - - Console.SetWindowSize(84, 20); - Console.Title = "GeckoLoader Installer"; - Console.WriteLine("This installer modifies the Windows User PATH variable\n"); - - status = HandleConsoleQuestion("Are you sure you want to continue?", continueoptions); - - if (status.ToLower() == (string)continueoptions.GetValue(0)) - { - this.SetProgramFolder("GeckoLoader"); - - status = HandleConsoleQuestion("What do you want to do?", actionoptions); - if (status.ToLower() == (string)actionoptions.GetValue(0)) - { - this.RemoveFolderGroupFromPath(this.programfolder); - this.SetFolderToPath(this.programfolder, ""); - if (this.MoveFilesToprogramfolder("*", this.copyfiles, this.overwrite) == false) - { - Console.WriteLine("Failed to install :("); - } - else - { - Console.WriteLine("Finished installation successfully! You can run GeckoLoader from anywhere\nby simply calling \"GeckoLoader [options]\""); - } - } - else - { - this.RemoveFolderGroupFromPath(this.programfolder); - this.DeleteProgramFolder(); - Console.WriteLine("Uninstalled successfully!"); - } - - } - else - { - Console.WriteLine("That's okay! You can always run this program again when you feel ready."); - } - Console.Write("Press any key to exit . . . "); - Console.ReadKey(); - } -} diff --git a/kernel.py b/kernel.py index d08f8a5..8e5dfb9 100644 --- a/kernel.py +++ b/kernel.py @@ -153,7 +153,7 @@ class GCT(object): elif (codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7') or codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')): dolFile.seek(address) - dolFile.insertBranch(int.from_bytes(info, byteorder='big', signed=False), dolFile.tell()) + dolFile.insert_branch(int.from_bytes(info, byteorder='big', signed=False), dolFile.tell()) continue if codetype.hex().startswith('2') or codetype.hex().startswith('3'): @@ -183,6 +183,10 @@ class GCT(object): class CodeHandler(object): + class Types: + MINI = "MINI" + FULL = "FULL" + def __init__(self, f): self._rawData = BytesIO(f.read()) @@ -212,9 +216,9 @@ class CodeHandler(object): self.optimizeList = False if self.handlerLength < 0x900: - self.type = "Mini" + self.type = CodeHandler.Types.MINI else: - self.type = "Full" + self.type = CodeHandler.Types.FULL f.seek(0) @@ -357,13 +361,12 @@ class KernelLoader(object): self.quiet = False self.encrypt = False - def error(self, msg: str, exit=True): + def error(self, msg: str): if self._cli is not None: - self._cli.error(msg, exit) + self._cli.error(msg) else: print(msg) - if exit: - sys.exit(1) + sys.exit(1) def set_variables(self, entryPoint: list, baseOffset: int=0): self._rawData.seek(0) @@ -450,8 +453,7 @@ class KernelLoader(object): if self.encrypt: codeHandler.encrypt_codes(_key) - - def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile): + def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple: self.complete_data(codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF]) self._rawData.seek(0, 2) @@ -467,10 +469,12 @@ class KernelLoader(object): dolFile.append_data_sections([(_kernelData, self.initAddress)]) except SectionCountFullError: self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT)) + return False, 'There are no unused sections left for GeckoLoader to use!' dolFile.entryPoint = self.initAddress + return True, None - def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile): + def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple: codeHandler._rawData.seek(0) codeHandler.geckoCodes.codeList.seek(0) @@ -483,6 +487,9 @@ class KernelLoader(object): dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)]) except SectionCountFullError: self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT)) + return False, 'There are no unused sections left for GeckoLoader to use!' + + return True, None def protect_game(self, codeHandler: CodeHandler): _oldpos = codeHandler.geckoCodes.codeList.tell() @@ -522,7 +529,7 @@ class KernelLoader(object): @timer def build(self, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump): - oldStart = dolFile.entryPoint + _oldStart = dolFile.entryPoint '''Initialize our codes''' @@ -530,7 +537,7 @@ class KernelLoader(object): if codeHandler.geckoCodes is None: self.error(tools.color_text('Valid codelist not found. Please provide a .txt/.gct file, or a folder of .txt/.gct files\n', defaultColor=tools.TREDLIT)) - + if self.protect and self.patchJob == "ARENA": self.protect_game(codeHandler) @@ -545,7 +552,7 @@ class KernelLoader(object): if self.initAddress: try: dolFile.resolve_address(self.initAddress) - print(tools.color_text(f'\n :: WARNING: Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections', defaultColor=tools.TYELLOW)) + self.error(tools.color_text(f'Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections', defaultColor=tools.TREDLIT)) except RuntimeError: pass else: @@ -561,33 +568,34 @@ class KernelLoader(object): with open(dump, 'wb') as final: dolFile.save(final) - if self.quiet: - return - if self.verbosity >= 3: - dolFile.print_info() - print('-'*64) - if self.verbosity >= 1: - print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT)) - + if not self.quiet: + if self.verbosity >= 3: + dolFile.print_info() + print('-'*64) + if self.verbosity >= 1: + print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT)) return + if self.patchJob == 'LEGACY': legacy = True codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength) hooked = determine_codehook(dolFile, codeHandler, True) if hooked: - self.patch_legacy(codeHandler, dolFile) + _status, _msg = self.patch_legacy(codeHandler, dolFile) else: legacy = False hooked = determine_codehook(dolFile, codeHandler, False) if hooked: - self.patch_arena(codeHandler, dolFile) - + _status, _msg = self.patch_arena(codeHandler, dolFile) + if not hooked: self.error(tools.color_text('Failed to find a hook address. Try using option --codehook to use your own address\n', defaultColor=tools.TREDLIT)) + elif _status is False: + self.error(tools.color_text(_msg + '\n', defaultColor=tools.TREDLIT)) elif codeHandler.allocation < codeHandler.geckoCodes.size: - self.error(tools.color_text('\n :: Error: Allocated codespace was smaller than the given codelist.\n', defaultColor=tools.TYELLOW)) - + self.error(tools.color_text('Allocated codespace was smaller than the given codelist\n', defaultColor=tools.TYELLOW)) + with open(dump, 'wb') as final: dolFile.save(final) @@ -606,19 +614,22 @@ class KernelLoader(object): if self.verbosity >= 2: print('') - if legacy == False: + if legacy is False: info = [f' :: Start of game modified to address 0x{self.initAddress:X}', - f' :: Game function "__start()" located at address 0x{oldStart:X}', + f' :: Game function "__start()" located at address 0x{_oldStart:X}', f' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}', f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}', f' :: Codehandler is of type "{codeHandler.type}"', - f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} are now being used'] + f' :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used', + f' :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used'] else: - info = [f' :: Game function "__start()" located at address 0x{oldStart:X}', + info = [f' :: Game function "__start()" located at address 0x{_oldStart:X}', f' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}', f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}', f' :: Codehandler is of type "{codeHandler.type}"', - f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} are now being used'] + f' :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used', + f' :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used'] + for bit in info: print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) @@ -630,13 +641,7 @@ class KernelLoader(object): for bit in info: print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) - -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, hook=False): +def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False) -> bool: if codeHandler.hookAddress is None: if not assert_code_hook(dolFile, codeHandler): return False @@ -648,7 +653,7 @@ def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False): return True -def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler): +def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler) -> bool: for _, address, size, _, _ in dolFile.textSections: dolFile.seek(address) sample = dolFile.read(size) diff --git a/main_ui.py b/main_ui.py new file mode 100644 index 0000000..0b946fd --- /dev/null +++ b/main_ui.py @@ -0,0 +1,832 @@ +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 CmdWrapper(object): + @staticmethod + def call(prog: str, *args): + return subprocess.run(" ".join([prog, *args]), shell=True, capture_output=True, text=True) + +class MainWindow(QtWidgets.QMainWindow): + def __init__(self, version: str): + super().__init__() + + self._job_active = False + self.apiRevision = version + self.setup_ui() + + self.LightTheme = self.palette() + + self.DarkTheme = QtGui.QPalette() + self.DarkTheme.setColor(QtGui.QPalette.Window, QtGui.QColor(53, 53, 53)) + self.DarkTheme.setColor(QtGui.QPalette.WindowText, QtCore.Qt.white) + self.DarkTheme.setColor(QtGui.QPalette.Base, QtGui.QColor(25, 25, 25)) + self.DarkTheme.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(53, 53, 53)) + self.DarkTheme.setColor(QtGui.QPalette.ToolTipBase, QtCore.Qt.black) + self.DarkTheme.setColor(QtGui.QPalette.ToolTipText, QtCore.Qt.white) + self.DarkTheme.setColor(QtGui.QPalette.Text, QtCore.Qt.white) + self.DarkTheme.setColor(QtGui.QPalette.Button, QtGui.QColor(53, 53, 53)) + self.DarkTheme.setColor(QtGui.QPalette.ButtonText, QtCore.Qt.white) + self.DarkTheme.setColor(QtGui.QPalette.BrightText, QtCore.Qt.red) + self.DarkTheme.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218)) + self.DarkTheme.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218)) + self.DarkTheme.setColor(QtGui.QPalette.HighlightedText, QtCore.Qt.black) + + def set_job_activity(self, active: bool): + self._job_active = active + + def close_event(self, event: QtGui.QCloseEvent): + if self._job_active: + reply = QtWidgets.QMessageBox(self) + reply.setWindowTitle("Active job") + reply.setText("GeckoLoader is busy!") + reply.setInformativeText("Exiting is disabled") + reply.setIcon(QtWidgets.QMessageBox.Warning) + reply.setStandardButtons(QtWidgets.QMessageBox.Ok) + reply.setDefaultButton(QtWidgets.QMessageBox.Ok) + reply.exec_() + event.ignore() + else: + event.accept() + + def setup_ui(self): + self.setObjectName("MainWindow") + self.setWindowModality(QtCore.Qt.NonModal) + self.setEnabled(True) + self.setFixedSize(550, 680) + font = QtGui.QFont() + font.setFamily("Helvetica") + font.setPointSize(10) + font.setWeight(42) + self.setFont(font) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("bin", "icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.setWindowIcon(icon) + + #Top level widget + self.centerWidget = QtWidgets.QWidget(self) + self.centerWidget.setObjectName("centerWidget") + + self.gridLayout = QtWidgets.QGridLayout(self.centerWidget) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + + #Layout for file paths and open boxes + self.filesLayout = QtWidgets.QGridLayout() + self.filesLayout.setHorizontalSpacing(0) + self.filesLayout.setObjectName("filesLayout") + + self.dolLayout = QtWidgets.QGridLayout() + self.dolLayout.setHorizontalSpacing(0) + self.dolLayout.setObjectName("dolLayout") + + #Layout for folder path + self.gctLayout = QtWidgets.QGridLayout() + self.gctLayout.setHorizontalSpacing(0) + self.gctLayout.setVerticalSpacing(5) + self.gctLayout.setObjectName("gctLayout") + + self.destLayout = QtWidgets.QGridLayout() + self.dolLayout.setHorizontalSpacing(0) + self.dolLayout.setObjectName("dolLayout") + + #Files label + self.filesLabel = QtWidgets.QLabel(self.centerWidget) + self.filesLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.filesLabel.sizePolicy().hasHeightForWidth()) + self.filesLabel.setSizePolicy(sizePolicy) + self.filesLabel.setMinimumSize(QtCore.QSize(80, 30)) + self.filesLabel.setMaximumSize(QtCore.QSize(16777215, 30)) + font = QtGui.QFont("Helvetica") + font.setPointSize(21) + font.setWeight(82) + font.setBold(True) + self.filesLabel.setFont(font) + self.filesLabel.setTextFormat(QtCore.Qt.PlainText) + self.filesLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.filesLabel.setObjectName("filesLabel") + + #Dol button to open file + self.dolButton = QtWidgets.QPushButton(self.centerWidget) + self.dolButton.setMinimumSize(QtCore.QSize(100, 26)) + self.dolButton.setMaximumSize(QtCore.QSize(100, 26)) + font = QtGui.QFont("Helvetica") + font.setPointSize(11) + self.dolButton.setFont(font) + self.dolButton.setCheckable(False) + self.dolButton.setChecked(False) + self.dolButton.setAutoDefault(True) + self.dolButton.setDefault(False) + self.dolButton.setFlat(False) + self.dolButton.setObjectName("dolButton") + self.dolLayout.addWidget(self.dolButton, 1, 0, 1, 1) + + #Dol path textbox + self.dolTextBox = QtWidgets.QLineEdit(self.centerWidget) + self.dolTextBox.setEnabled(False) + self.dolTextBox.setMinimumSize(QtCore.QSize(200, 24)) + self.dolTextBox.setMaximumSize(QtCore.QSize(16777215, 24)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(10) + font.setWeight(42) + self.dolTextBox.setFont(font) + self.dolTextBox.setText("") + self.dolTextBox.setMaxLength(255) + self.dolTextBox.setFrame(True) + self.dolTextBox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.dolTextBox.setObjectName("dolTextBox") + self.dolLayout.addWidget(self.dolTextBox, 1, 1, 1, 1) + + #horizontal separater codes + self.horiSepFiles = QtWidgets.QFrame(self.centerWidget) + self.horiSepFiles.setMinimumSize(QtCore.QSize(474, 30)) + self.horiSepFiles.setContentsMargins(20, 0, 20, 0) + self.horiSepFiles.setFrameShape(QtWidgets.QFrame.HLine) + self.horiSepFiles.setFrameShadow(QtWidgets.QFrame.Sunken) + self.horiSepFiles.setObjectName("horiSepFiles") + + #gctFile button to open file + self.gctFileButton = QtWidgets.QPushButton(self.centerWidget) + self.gctFileButton.setMinimumSize(QtCore.QSize(100, 26)) + self.gctFileButton.setMaximumSize(QtCore.QSize(100, 26)) + font = QtGui.QFont("Helvetica") + font.setPointSize(10) + self.gctFileButton.setFont(font) + self.gctFileButton.setCheckable(False) + self.gctFileButton.setChecked(False) + self.gctFileButton.setAutoDefault(True) + self.gctFileButton.setDefault(False) + self.gctFileButton.setFlat(False) + self.gctFileButton.setObjectName("gctFileButton") + self.gctLayout.addWidget(self.gctFileButton, 0, 0, 1, 1) + + #gctFile path textbox + self.gctFileTextBox = QtWidgets.QLineEdit(self.centerWidget) + self.gctFileTextBox.setEnabled(False) + self.gctFileTextBox.setMinimumSize(QtCore.QSize(200, 24)) + self.gctFileTextBox.setMaximumSize(QtCore.QSize(16777215, 24)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(10) + font.setWeight(42) + self.gctFileTextBox.setFont(font) + self.gctFileTextBox.setText("") + self.gctFileTextBox.setMaxLength(255) + self.gctFileTextBox.setFrame(True) + self.gctFileTextBox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.gctFileTextBox.setObjectName("gctFileTextBox") + self.gctLayout.addWidget(self.gctFileTextBox, 0, 1, 1, 1) + + #--or-- Label + self.orFolderLabel = QtWidgets.QLabel(self.centerWidget) + self.orFolderLabel.setEnabled(False) + self.orFolderLabel.setMinimumSize(QtCore.QSize(80, 8)) + self.orFolderLabel.setMaximumSize(QtCore.QSize(16777215, 8)) + font = QtGui.QFont("Helvetica") + font.setPointSize(8) + font.setWeight(82) + font.setBold(True) + self.orFolderLabel.setFont(font) + self.orFolderLabel.setTextFormat(QtCore.Qt.PlainText) + self.orFolderLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.orFolderLabel.setObjectName("orFolderLabel") + self.gctLayout.addWidget(self.orFolderLabel, 1, 0, 1, 2) + + #gctFolder button to open file + self.gctFolderButton = QtWidgets.QPushButton(self.centerWidget) + self.gctFolderButton.setMinimumSize(QtCore.QSize(100, 26)) + self.gctFolderButton.setMaximumSize(QtCore.QSize(100, 26)) + font = QtGui.QFont("Helvetica") + font.setPointSize(10) + self.gctFolderButton.setFont(font) + self.gctFolderButton.setCheckable(False) + self.gctFolderButton.setChecked(False) + self.gctFolderButton.setAutoDefault(True) + self.gctFolderButton.setDefault(False) + self.gctFolderButton.setFlat(False) + self.gctFolderButton.setObjectName("gctFolderButton") + self.gctLayout.addWidget(self.gctFolderButton, 2, 0, 1, 1) + + #gctFolder path textbox + self.gctFolderTextBox = QtWidgets.QLineEdit(self.centerWidget) + self.gctFolderTextBox.setEnabled(False) + self.gctFolderTextBox.setMinimumSize(QtCore.QSize(200, 24)) + self.gctFolderTextBox.setMaximumSize(QtCore.QSize(16777215, 24)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(10) + font.setWeight(42) + self.gctFolderTextBox.setFont(font) + self.gctFolderTextBox.setText("") + self.gctFolderTextBox.setMaxLength(255) + self.gctFolderTextBox.setFrame(True) + self.gctFolderTextBox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.gctFolderTextBox.setObjectName("gctFolderTextBox") + self.gctLayout.addWidget(self.gctFolderTextBox, 2, 1, 1, 1) + + #horizontal separater dest + self.horiSepDest = QtWidgets.QFrame(self.centerWidget) + self.horiSepDest.setMinimumSize(QtCore.QSize(474, 30)) + self.horiSepDest.setContentsMargins(20, 0, 20, 0) + self.horiSepDest.setFrameShape(QtWidgets.QFrame.HLine) + self.horiSepDest.setFrameShadow(QtWidgets.QFrame.Sunken) + self.horiSepDest.setObjectName("horiSepDest") + + #Dest button to open file + self.destButton = QtWidgets.QPushButton(self.centerWidget) + self.destButton.setMinimumSize(QtCore.QSize(100, 26)) + self.destButton.setMaximumSize(QtCore.QSize(100, 26)) + font = QtGui.QFont("Helvetica") + font.setPointSize(11) + self.destButton.setFont(font) + self.destButton.setCheckable(False) + self.destButton.setChecked(False) + self.destButton.setAutoDefault(True) + self.destButton.setDefault(False) + self.destButton.setFlat(False) + self.destButton.setObjectName("destButton") + self.destLayout.addWidget(self.destButton, 0, 0, 1, 1) + + #Dest path textbox + self.destTextBox = QtWidgets.QLineEdit(self.centerWidget) + self.destTextBox.setEnabled(False) + self.destTextBox.setMinimumSize(QtCore.QSize(200, 24)) + self.destTextBox.setMaximumSize(QtCore.QSize(16777215, 24)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(10) + font.setWeight(42) + self.destTextBox.setFont(font) + self.destTextBox.setText("") + self.destTextBox.setMaxLength(255) + self.destTextBox.setFrame(True) + self.destTextBox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.destTextBox.setObjectName("destTextBox") + self.destLayout.addWidget(self.destTextBox, 0, 1, 1, 1) + + self.filesLayout.addLayout(self.dolLayout, 0, 0, 1, 1) + self.filesLayout.addWidget(self.horiSepFiles, 1, 0, 1, 1) + self.filesLayout.addLayout(self.gctLayout, 2, 0, 1, 1) + self.filesLayout.addWidget(self.horiSepDest, 3, 0, 1, 1) + self.filesLayout.addLayout(self.destLayout, 4, 0, 1, 1) + + #Options Layout + self.optionsLayout = QtWidgets.QGridLayout() + self.optionsLayout.setHorizontalSpacing(20) + self.optionsLayout.setObjectName("optionsLayout") + + #Options Label + self.optionsLabel = QtWidgets.QLabel(self.centerWidget) + self.optionsLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.optionsLabel.sizePolicy().hasHeightForWidth()) + self.optionsLabel.setSizePolicy(sizePolicy) + self.optionsLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.optionsLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + font = QtGui.QFont("Helvetica") + font.setPointSize(18) + font.setWeight(82) + font.setBold(True) + self.optionsLabel.setFont(font) + self.optionsLabel.setTextFormat(QtCore.Qt.PlainText) + self.optionsLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.optionsLabel.setObjectName("optionsLabel") + self.optionsLayout.addWidget(self.optionsLabel, 0, 0, 1, 4) + + #Allocation Label + self.allocLabel = QtWidgets.QLabel(self.centerWidget) + self.allocLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.allocLabel.setSizePolicy(sizePolicy) + self.allocLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.allocLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.allocLabel.setTextFormat(QtCore.Qt.PlainText) + self.allocLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.allocLabel.setObjectName("allocLabel") + self.optionsLayout.addWidget(self.allocLabel, 1, 0, 1, 1) + + #Allocation Textbox + self.allocLineEdit = QtWidgets.QLineEdit(self.centerWidget) + self.allocLineEdit.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLineEdit.sizePolicy().hasHeightForWidth()) + self.allocLineEdit.setSizePolicy(sizePolicy) + self.allocLineEdit.setMinimumSize(QtCore.QSize(79, 23)) + self.allocLineEdit.setMaximumSize(QtCore.QSize(79, 23)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(12) + font.setWeight(42) + self.allocLineEdit.setFont(font) + self.allocLineEdit.setText("") + self.allocLineEdit.setMaxLength(6) + self.allocLineEdit.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.allocLineEdit.setObjectName("allocLineEdit") + self.optionsLayout.addWidget(self.allocLineEdit, 2, 0, 1, 1) + + #Patch label + self.patchLabel = QtWidgets.QLabel(self.centerWidget) + self.patchLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.patchLabel.sizePolicy().hasHeightForWidth()) + self.patchLabel.setSizePolicy(sizePolicy) + self.patchLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.patchLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.patchLabel.setTextFormat(QtCore.Qt.PlainText) + self.patchLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.patchLabel.setObjectName("patchLabel") + self.optionsLayout.addWidget(self.patchLabel, 1, 1, 1, 1) + + #Patch selection + self.patchTypeSelect = QtWidgets.QComboBox(self.centerWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.patchTypeSelect.setSizePolicy(sizePolicy) + self.patchTypeSelect.setMinimumSize(QtCore.QSize(79, 23)) + self.patchTypeSelect.setMaximumSize(QtCore.QSize(79, 23)) + self.patchTypeSelect.setObjectName("patchTypeSelect") + self.patchTypeSelect.addItems(["AUTO", "LEGACY", "ARENA"]) + self.optionsLayout.addWidget(self.patchTypeSelect, 2, 1, 1, 1) + + #handlerType label + self.handlerTypeLabel = QtWidgets.QLabel(self.centerWidget) + self.handlerTypeLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.handlerTypeLabel.sizePolicy().hasHeightForWidth()) + self.handlerTypeLabel.setSizePolicy(sizePolicy) + self.handlerTypeLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.handlerTypeLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.handlerTypeLabel.setTextFormat(QtCore.Qt.PlainText) + self.handlerTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.handlerTypeLabel.setObjectName("handlerTypeLabel") + self.optionsLayout.addWidget(self.handlerTypeLabel, 1, 2, 1, 1) + + #handlerType selection + self.handlerTypeSelect = QtWidgets.QComboBox(self.centerWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.handlerTypeSelect.setSizePolicy(sizePolicy) + self.handlerTypeSelect.setMinimumSize(QtCore.QSize(79, 23)) + self.handlerTypeSelect.setMaximumSize(QtCore.QSize(79, 23)) + self.handlerTypeSelect.setObjectName("handlerTypeSelect") + self.handlerTypeSelect.addItems(["FULL", "MINI"]) + self.optionsLayout.addWidget(self.handlerTypeSelect, 2, 2, 1, 1) + + #hookType label + self.hookTypeLabel = QtWidgets.QLabel(self.centerWidget) + self.hookTypeLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.hookTypeLabel.sizePolicy().hasHeightForWidth()) + self.hookTypeLabel.setSizePolicy(sizePolicy) + self.hookTypeLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.hookTypeLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.hookTypeLabel.setTextFormat(QtCore.Qt.PlainText) + self.hookTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.hookTypeLabel.setObjectName("hookTypeLabel") + self.optionsLayout.addWidget(self.hookTypeLabel, 1, 3, 1, 1) + + #hookType selection + self.hookTypeSelect = QtWidgets.QComboBox(self.centerWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.hookTypeSelect.setSizePolicy(sizePolicy) + self.hookTypeSelect.setMinimumSize(QtCore.QSize(79, 23)) + self.hookTypeSelect.setMaximumSize(QtCore.QSize(79, 23)) + self.hookTypeSelect.setObjectName("hookTypeSelect") + self.hookTypeSelect.addItems(["VI", "GX", "PAD"]) + self.optionsLayout.addWidget(self.hookTypeSelect, 2, 3, 1, 1) + + #txtCodesInclude label + self.txtCodesIncludeLabel = QtWidgets.QLabel(self.centerWidget) + self.txtCodesIncludeLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.txtCodesIncludeLabel.sizePolicy().hasHeightForWidth()) + self.txtCodesIncludeLabel.setSizePolicy(sizePolicy) + self.txtCodesIncludeLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.txtCodesIncludeLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.txtCodesIncludeLabel.setTextFormat(QtCore.Qt.PlainText) + self.txtCodesIncludeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.txtCodesIncludeLabel.setObjectName("txtCodesIncludeLabel") + self.optionsLayout.addWidget(self.txtCodesIncludeLabel, 3, 0, 1, 1) + + #txtCodesInclude selection + self.txtCodesIncludeSelect = QtWidgets.QComboBox(self.centerWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.txtCodesIncludeSelect.setSizePolicy(sizePolicy) + self.txtCodesIncludeSelect.setMinimumSize(QtCore.QSize(79, 23)) + self.txtCodesIncludeSelect.setMaximumSize(QtCore.QSize(79, 23)) + self.txtCodesIncludeSelect.setObjectName("txtCodesIncludeSelect") + self.txtCodesIncludeSelect.addItems(["ACTIVE", "ALL"]) + self.optionsLayout.addWidget(self.txtCodesIncludeSelect, 4, 0, 1, 1) + + #optimize label + self.optimizeLabel = QtWidgets.QLabel(self.centerWidget) + self.optimizeLabel.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.optimizeLabel.sizePolicy().hasHeightForWidth()) + self.optimizeLabel.setSizePolicy(sizePolicy) + self.optimizeLabel.setMinimumSize(QtCore.QSize(79, 23)) + self.optimizeLabel.setMaximumSize(QtCore.QSize(16777215, 23)) + self.optimizeLabel.setTextFormat(QtCore.Qt.PlainText) + self.optimizeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) + self.optimizeLabel.setObjectName("optimizeLabel") + self.optionsLayout.addWidget(self.optimizeLabel, 3, 1, 1, 1) + + #optimize selection + self.optimizeSelect = QtWidgets.QComboBox(self.centerWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allocLabel.sizePolicy().hasHeightForWidth()) + self.optimizeSelect.setSizePolicy(sizePolicy) + self.optimizeSelect.setMinimumSize(QtCore.QSize(79, 23)) + self.optimizeSelect.setMaximumSize(QtCore.QSize(79, 23)) + self.optimizeSelect.setObjectName("optimizeSelect") + self.optimizeSelect.addItems(["TRUE", "FALSE"]) + self.optionsLayout.addWidget(self.optimizeSelect, 4, 1, 1, 1) + + #Advanced options button + self.exOptionsButton = QtWidgets.QPushButton(self.centerWidget) + font = QtGui.QFont("Helvetica") + font.setPointSize(13) + self.exOptionsButton.setFont(font) + self.exOptionsButton.setCheckable(False) + self.exOptionsButton.setChecked(False) + self.exOptionsButton.setAutoDefault(True) + self.exOptionsButton.setDefault(False) + self.exOptionsButton.setFlat(False) + self.exOptionsButton.setDisabled(True) + self.exOptionsButton.setObjectName("exOptionsButton") + self.optionsLayout.addWidget(self.exOptionsButton, 4, 2, 1, 2) + + #horizontal separater 1 + self.horiSepA = QtWidgets.QFrame(self.centerWidget) + self.horiSepA.setMinimumSize(QtCore.QSize(470, 30)) + self.horiSepA.setFrameShape(QtWidgets.QFrame.HLine) + self.horiSepA.setFrameShadow(QtWidgets.QFrame.Sunken) + self.horiSepA.setObjectName("horiSepA") + + #horizontal separater 2 + self.horiSepB = QtWidgets.QFrame(self.centerWidget) + self.horiSepB.setMinimumSize(QtCore.QSize(470, 30)) + self.horiSepB.setFrameShape(QtWidgets.QFrame.HLine) + self.horiSepB.setFrameShadow(QtWidgets.QFrame.Sunken) + self.horiSepB.setObjectName("horiSepB") + + #response panel + self.responses = QtWidgets.QPlainTextEdit(self.centerWidget) + self.responses.setEnabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.responses.sizePolicy().hasHeightForWidth()) + self.responses.setSizePolicy(sizePolicy) + self.responses.setMinimumSize(QtCore.QSize(474, 180)) + self.responses.setMaximumSize(QtCore.QSize(16777215, 180)) + font = QtGui.QFont() + font.setFamily("Consolas") + font.setPointSize(8) + font.setWeight(42) + fontMetrics = QtGui.QFontMetricsF(font) + spaceWidth = fontMetrics.width(' ') + self.responses.setFont(font) + self.responses.setPlainText("") + self.responses.setTabStopDistance(spaceWidth * 4) + self.responses.setReadOnly(True) + self.responses.setObjectName("responses") + + #Compile button + self.compileButton = QtWidgets.QPushButton(self.centerWidget) + font = QtGui.QFont("Helvetica") + font.setPointSize(34) + self.compileButton.setFont(font) + self.compileButton.setCheckable(False) + self.compileButton.setChecked(False) + self.compileButton.setAutoDefault(True) + self.compileButton.setDefault(False) + self.compileButton.setFlat(False) + self.compileButton.setDisabled(True) + self.compileButton.setObjectName("compileButton") + + self.gridLayout.addWidget(self.filesLabel, 0, 0, 1, 1) + self.gridLayout.addLayout(self.filesLayout, 1, 0, 1, 1) + self.gridLayout.addWidget(self.horiSepA, 2, 0, 1, 1) + self.gridLayout.addLayout(self.optionsLayout, 3, 0, 1, 1) + self.gridLayout.addWidget(self.horiSepB, 4, 0, 1, 1) + self.gridLayout.addWidget(self.responses, 5, 0, 1, 1) + self.gridLayout.addWidget(self.compileButton, 6, 0, 1, 1) + + self.setCentralWidget(self.centerWidget) + + #Toolbar + self.menubar = QtWidgets.QMenuBar(self) + self.menubar.setGeometry(QtCore.QRect(0, 0, 470, 22)) + self.menubar.setObjectName("menubar") + + self.menuFile = QtWidgets.QMenu(self.menubar) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.menuFile.setFont(font) + self.menuFile.setObjectName("menuFile") + + self.menuEdit = QtWidgets.QMenu(self.menubar) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.menuEdit.setFont(font) + self.menuEdit.setObjectName("menuEdit") + + self.menuHelp = QtWidgets.QMenu(self.menubar) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.menuHelp.setFont(font) + self.menuHelp.setObjectName("menuHelp") + + self.setMenuBar(self.menubar) + + self.actionOpen = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionOpen.setFont(font) + self.actionOpen.setObjectName("actionOpen") + + self.actionClose = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionClose.setFont(font) + self.actionClose.setObjectName("actionClose") + + self.actionSave = QtWidgets.QAction(self) + self.actionSave.setEnabled(False) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionSave.setFont(font) + self.actionSave.setObjectName("actionSave") + + self.actionSave_As = QtWidgets.QAction(self) + self.actionSave_As.setEnabled(False) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionSave_As.setFont(font) + self.actionSave_As.setObjectName("actionSave_As") + + self.actionUndo = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionUndo.setFont(font) + self.actionUndo.setMenuRole(QtWidgets.QAction.TextHeuristicRole) + self.actionUndo.setObjectName("actionUndo") + self.actionRedo = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionRedo.setFont(font) + self.actionRedo.setObjectName("actionRedo") + self.actionCut = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionCut.setFont(font) + self.actionCut.setObjectName("actionCut") + self.actionCopy = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionCopy.setFont(font) + self.actionCopy.setObjectName("actionCopy") + self.actionPaste = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionPaste.setFont(font) + self.actionPaste.setObjectName("actionPaste") + self.actionDelete = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionDelete.setFont(font) + self.actionDelete.setObjectName("actionDelete") + self.actionSelect_All = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionSelect_All.setFont(font) + self.actionSelect_All.setObjectName("actionSelect_All") + self.actionPreferences = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionPreferences.setFont(font) + self.actionPreferences.setMenuRole(QtWidgets.QAction.PreferencesRole) + self.actionPreferences.setObjectName("actionPreferences") + + self.actionAbout_GeckoLoader = QtWidgets.QAction(self) + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionAbout_GeckoLoader.setFont(font) + self.actionAbout_GeckoLoader.setMenuRole(QtWidgets.QAction.AboutRole) + self.actionAbout_GeckoLoader.setObjectName("actionAbout_GeckoLoader") + + self.actionAbout_Qt = QtWidgets.QAction(self) + self.actionAbout_Qt.setStatusTip("") + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionAbout_Qt.setFont(font) + self.actionAbout_Qt.setMenuRole(QtWidgets.QAction.AboutQtRole) + self.actionAbout_Qt.setObjectName("actionAbout_Qt") + + self.actionCheck_Update = QtWidgets.QAction(self) + self.actionCheck_Update.setStatusTip("") + font = QtGui.QFont() + font.setFamily("Helvetica") + self.actionCheck_Update.setFont(font) + self.actionCheck_Update.setObjectName("actionCheck_Update") + + self.menuFile.addAction(self.actionOpen) + self.menuFile.addAction(self.actionClose) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSave_As) + + self.menuEdit.addAction(self.actionPreferences) + + self.menuHelp.addAction(self.actionAbout_GeckoLoader) + self.menuHelp.addAction(self.actionAbout_Qt) + self.menuHelp.addAction(self.actionCheck_Update) + + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuEdit.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + #Statusbar + self.statusbar = QtWidgets.QStatusBar(self) + self.statusbar.setObjectName("statusbar") + self.setStatusBar(self.statusbar) + + self.retranslate_ui() + self.set_edit_fields() + + QtCore.QMetaObject.connectSlotsByName(self) + + def _lstrip_textboxes(self): + attributes = [item for item in vars(self) if not item.startswith('__')] + + for item in attributes: + item = getattr(self, item) + if isinstance(item, QtWidgets.QLineEdit): + item.setText(item.text().lstrip()) + elif isinstance(item, QtWidgets.QPlainTextEdit): + item.setPlainText(item.toPlainText().lstrip()) + + def set_edit_fields(self): + self.filesLabel.setEnabled(True) + self.dolTextBox.setEnabled(True) + self.destTextBox.setEnabled(True) + self.optionsLabel.setEnabled(True) + self.allocLabel.setEnabled(True) + self.allocLineEdit.setEnabled(True) + self.patchLabel.setEnabled(True) + self.patchTypeSelect.setEnabled(True) + self.handlerTypeLabel.setEnabled(True) + self.handlerTypeSelect.setEnabled(True) + self.hookTypeLabel.setEnabled(True) + self.hookTypeSelect.setEnabled(True) + self.txtCodesIncludeLabel.setEnabled(True) + self.txtCodesIncludeSelect.setEnabled(True) + self.optimizeLabel.setEnabled(True) + self.optimizeSelect.setEnabled(True) + self.exOptionsButton.setEnabled(True) + self.actionSave.setEnabled(True) + self.actionSave_As.setEnabled(True) + + self._lstrip_textboxes() + + if self.gctFileTextBox.text() != "": + self.gctFileTextBox.setEnabled(True) + self.gctFolderTextBox.setDisabled(True) + elif self.gctFolderTextBox.text() != "": + self.gctFileTextBox.setDisabled(True) + self.gctFolderTextBox.setEnabled(True) + else: + self.gctFileTextBox.setEnabled(True) + self.gctFolderTextBox.setEnabled(True) + + if self.dolTextBox.text().lower().endswith(".dol") and len(self.dolTextBox.text()) > 4: + self.compileButton.setEnabled(self.gctFileTextBox.text() != "" or self.gctFolderTextBox.text() != "") + else: + self.compileButton.setDisabled(True) + + def retranslate_ui(self): + self.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", f"GeckoLoader {self.apiRevision} - untitled", None)) + self.menuFile.setTitle(QtWidgets.QApplication.translate("MainWindow", "&File", None)) + self.menuEdit.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Edit", None)) + self.menuHelp.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Help", None)) + self.actionOpen.setText(QtWidgets.QApplication.translate("MainWindow", "&Open Session...", None)) + self.actionOpen.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Open a session", None)) + self.actionOpen.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+O", None)) + self.actionClose.setText(QtWidgets.QApplication.translate("MainWindow", "&Close Session...", None)) + self.actionClose.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Close the current session", None)) + self.actionClose.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+Shift+C", None)) + self.actionSave.setText(QtWidgets.QApplication.translate("MainWindow", "&Save Session", None)) + self.actionSave.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Save the current session", None)) + self.actionSave.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+S", None)) + self.actionSave_As.setText(QtWidgets.QApplication.translate("MainWindow", "&Save Session As...", None)) + self.actionSave_As.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Save the current session to the specified location", None)) + self.actionSave_As.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+Shift+S", None)) + self.actionUndo.setText(QtWidgets.QApplication.translate("MainWindow", "Undo", None)) + self.actionUndo.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Undo the last action", None)) + self.actionUndo.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+Z", None)) + self.actionRedo.setText(QtWidgets.QApplication.translate("MainWindow", "Redo", None)) + self.actionRedo.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Redo the last action", None)) + self.actionRedo.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+Shift+Z", None)) + self.actionCut.setText(QtWidgets.QApplication.translate("MainWindow", "Cut", None)) + self.actionCut.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Cuts the selected text and places it on the clipboard", None)) + self.actionCut.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+X", None)) + self.actionCopy.setText(QtWidgets.QApplication.translate("MainWindow", "Copy", None)) + self.actionCopy.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Copies the selected text and places it on the clipboard", None)) + self.actionCopy.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+C", None)) + self.actionPaste.setText(QtWidgets.QApplication.translate("MainWindow", "Paste", None)) + self.actionPaste.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Paste the contents of the clipboard", None)) + self.actionPaste.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+V", None)) + self.actionDelete.setText(QtWidgets.QApplication.translate("MainWindow", "Delete", None)) + self.actionDelete.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Deletes the selected text", None)) + self.actionSelect_All.setText(QtWidgets.QApplication.translate("MainWindow", "Select All", None)) + self.actionSelect_All.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Select all of the text", None)) + self.actionSelect_All.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+A", None)) + self.actionPreferences.setText(QtWidgets.QApplication.translate("MainWindow", "&Preferences...", None)) + self.actionPreferences.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Open the application preferences dialog", None)) + self.actionAbout_GeckoLoader.setText(QtWidgets.QApplication.translate("MainWindow", "About &GeckoLoader...", None)) + self.actionAbout_Qt.setText(QtWidgets.QApplication.translate("MainWindow", "About &Qt...", None)) + self.actionCheck_Update.setText(QtWidgets.QApplication.translate("MainWindow", "&Check Update", None)) + + self.filesLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Files", None)) + + self.dolButton.setText(QtWidgets.QApplication.translate("MainWindow", "Open DOL", None)) + self.gctFileButton.setText(QtWidgets.QApplication.translate("MainWindow", "Open Codes", None)) + self.orFolderLabel.setText(QtWidgets.QApplication.translate("MainWindow", "-"*40 + "OR" + "-"*40, None)) + self.gctFolderButton.setText(QtWidgets.QApplication.translate("MainWindow", "Open Folder", None)) + self.destButton.setText(QtWidgets.QApplication.translate("MainWindow", "Destination", None)) + + self.optionsLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Options", None)) + + self.allocLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Allocation", None)) + self.allocLineEdit.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "AUTO", None)) + + self.patchLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Patch Type", None)) + self.patchTypeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "AUTO", None)) + self.patchTypeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "LEGACY", None)) + self.patchTypeSelect.setItemText(2, QtWidgets.QApplication.translate("Dialog", "ARENA", None)) + + self.handlerTypeLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Codehandler", None)) + self.handlerTypeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "FULL", None)) + self.handlerTypeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "MINI", None)) + + self.hookTypeLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Code Hook", None)) + self.hookTypeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "VI", None)) + self.hookTypeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "GX", None)) + self.hookTypeSelect.setItemText(2, QtWidgets.QApplication.translate("Dialog", "PAD", None)) + + self.txtCodesIncludeLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Include Codes", None)) + self.txtCodesIncludeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "ACTIVE", None)) + self.txtCodesIncludeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "ALL", None)) + + self.optimizeLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Optimize", None)) + self.optimizeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "TRUE", None)) + self.optimizeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "FALSE", None)) + + self.exOptionsButton.setText(QtWidgets.QApplication.translate("MainWindow", "Advanced Settings", None)) + + self.compileButton.setText(QtWidgets.QApplication.translate("MainWindow", "RUN", None)) diff --git a/versioncheck.py b/versioncheck.py index 3551f0e..975c8e7 100644 --- a/versioncheck.py +++ b/versioncheck.py @@ -14,7 +14,7 @@ class Updater(object): html = response.read() return html - def get_newest_version(self) -> str: + def get_newest_version(self) -> tuple: """ Returns newest release version """ try: response = self.request_release_data()