1
0
Fork 0

New GUI, lots of code restructuring

This commit is contained in:
JoshuaMKW 2020-11-06 04:14:27 -06:00
parent a6b20ae53e
commit 12d031d2ec
6 changed files with 1592 additions and 366 deletions

View file

@ -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):
@ -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):
@ -170,8 +168,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,49 +189,56 @@ 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()
def _validate_args(self, args) -> tuple:
if args.alloc:
try:
_allocation = int(args.alloc, 16)
except ValueError:
parser.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:
parser.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:
parser.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
if args.handler == 'MINI':
if args.handler == CodeHandler.Types.MINI:
codeHandlerFile = 'codehandler-mini.bin'
else:
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))
return _allocation, _codehook, codeHandlerFile
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 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)
@ -248,7 +251,7 @@ if __name__ == "__main__":
codeHandler.optimizeList = args.optimize
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile:
geckoKernel = KernelLoader(kernelfile, parser)
geckoKernel = KernelLoader(kernelfile, cli)
if args.init is not None:
geckoKernel.initAddress = int(args.init, 16)
@ -260,22 +263,582 @@ if __name__ == "__main__":
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('/'), os.path.basename(args.dolfile)))
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('/')))
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(), "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)
if not os.path.isdir(TMPDIR):
os.mkdir(TMPDIR)
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 <joshuamkw2002@gmail.com> \n\n",
"All rights reserved." ])
QtWidgets.QMessageBox.about(self.app.activeWindow(), "About GeckoLoader", desc)
elif dialog_type == "Preferences":
self.uiprefs.show()
else:
self.uiexSettings.show()
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 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
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)

View file

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

View file

@ -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<string> newPATHList = new List<string>();
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 <dol> <gct|txt|folder> [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();
}
}

View file

@ -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,12 +361,11 @@ 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)
def set_variables(self, entryPoint: list, baseOffset: int=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'''
@ -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,32 +568,33 @@ class KernelLoader(object):
with open(dump, 'wb') as final:
dolFile.save(final)
if self.quiet:
return
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)

832
main_ui.py Normal file
View file

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

View file

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