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 #Written by JoshuaMK 2020
import argparse import argparse
import os
import random
import shutil
import sys
import atexit 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 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 dolreader import DolFile
from fileutils import resource_path, get_program_folder
from kernel import CodeHandler, KernelLoader from kernel import CodeHandler, KernelLoader
from main_ui import MainWindow
from tools import CommandLineParser, color_text from tools import CommandLineParser, color_text
from versioncheck import Updater from versioncheck import Updater
@ -34,30 +45,16 @@ except ImportError:
TRED = '' TRED = ''
TREDLIT = '' TREDLIT = ''
__version__ = 'v6.2.0' __version__ = "v6.3.0"
def resource_path(relative_path: str = ""): TMPDIR = tempfile.mkdtemp("GeckoLoader-")
""" 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
@atexit.register @atexit.register
def clean_tmp_resources(): def clean_tmp_resources():
if os.path.isdir(TMPDIR): tmpfolder = os.path.dirname(TMPDIR)
shutil.rmtree(TMPDIR) for entry in os.listdir(tmpfolder):
if entry.startswith("GeckoLoader-"):
shutil.rmtree(entry, ignore_errors=True)
class GeckoLoaderCli(CommandLineParser): class GeckoLoaderCli(CommandLineParser):
@ -69,34 +66,34 @@ class GeckoLoaderCli(CommandLineParser):
self.add_argument('dolfile', help='DOL file') self.add_argument('dolfile', help='DOL file')
self.add_argument('codelist', help='Folder or Gecko GCT|TXT file') self.add_argument('codelist', help='Folder or Gecko GCT|TXT file')
self.add_argument('-a', '--alloc', self.add_argument('-a', '--alloc',
help='Define the size of the code allocation in hex, only applies when using the ARENA space', help='Define the size of the code allocation in hex, only applies when using the ARENA space',
metavar ='SIZE') metavar ='SIZE')
self.add_argument('-i', '--init', self.add_argument('-i', '--init',
help='Define where GeckoLoader is initialized in hex', help='Define where GeckoLoader is initialized in hex',
metavar='ADDRESS') metavar='ADDRESS')
self.add_argument('-m', '--movecodes', self.add_argument('-m', '--movecodes',
help='''["AUTO", "LEGACY", "ARENA"] Choose if GeckoLoader moves the codes to OSArenaHi, help='''["AUTO", "LEGACY", "ARENA"] Choose if GeckoLoader moves the codes to OSArenaHi,
or the legacy space. Default is "AUTO", or the legacy space. Default is "AUTO",
which auto decides where to insert the codes''', which auto decides where to insert the codes''',
default='AUTO', default='AUTO',
choices=['AUTO', 'LEGACY', 'ARENA'], choices=['AUTO', 'LEGACY', 'ARENA'],
metavar='TYPE') metavar='TYPE')
self.add_argument('-tc', '--txtcodes', self.add_argument('-tc', '--txtcodes',
help='''["ACTIVE", "ALL"] What codes get parsed when a txt file is used. help='''["ACTIVE", "ALL"] What codes get parsed when a txt file is used.
"ALL" makes all codes get parsed, "ALL" makes all codes get parsed,
"ACTIVE" makes only activated codes get parsed. "ACTIVE" makes only activated codes get parsed.
"ACTIVE" is the default''', "ACTIVE" is the default''',
default='ACTIVE', default='ACTIVE',
metavar='TYPE') metavar='TYPE')
self.add_argument('--handler', self.add_argument('--handler',
help='''["MINI", "FULL"] Which codehandler gets used. "MINI" uses a smaller codehandler help='''["MINI", "FULL"] Which codehandler gets used. "MINI" uses a smaller codehandler
which only supports (0x, 2x, Cx, and E0 types) and supports up to which only supports (0x, 2x, Cx, and E0 types) and supports up to
600 lines of gecko codes when using the legacy codespace. 600 lines of gecko codes when using the legacy codespace.
"FULL" is the standard codehandler, supporting up to 350 lines of code "FULL" is the standard codehandler, supporting up to 350 lines of code
in the legacy codespace. "FULL" is the default''', in the legacy codespace. "FULL" is the default''',
default='FULL', default='FULL',
choices=['MINI', 'FULL'], choices=['MINI', 'FULL'],
metavar='TYPE') metavar='TYPE')
self.add_argument('--hooktype', self.add_argument('--hooktype',
help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended, help='''["VI", "GX", "PAD"] The type of hook used for the RAM search. "VI" or "GX" are recommended,
although "PAD" can work just as well. "VI" is the default''', although "PAD" can work just as well. "VI" is the default''',
@ -117,16 +114,17 @@ class GeckoLoaderCli(CommandLineParser):
self.add_argument('--dest', self.add_argument('--dest',
help='Target path to put the modified DOL, can be a folder or file', help='Target path to put the modified DOL, can be a folder or file',
metavar='PATH') metavar='PATH')
self.add_argument('--check-update', self.add_argument('--checkupdate',
help='''Checks to see if a new update exists on the GitHub Repository releases page, help='''Checks to see if a new update exists on the GitHub Repository releases page,
this option overrides all other commands.''', this option overrides all other commands.''',
action='store_true') action='store_true')
self.add_argument('--splash',
help='''Print the splash screen, this option overrides
all other commands excluding --checkupdate''',
action='store_true')
self.add_argument('--encrypt', self.add_argument('--encrypt',
help='Encrypts the codelist on compile time, helping to slow the snoopers', help='Encrypts the codelist on compile time, helping to slow the snoopers',
action='store_true') action='store_true')
self.add_argument('--disablecolor',
help='Disables the colorization of text output',
action='store_true')
self.add_argument('-q', '--quiet', self.add_argument('-q', '--quiet',
help='Print nothing to the console', help='Print nothing to the console',
action='store_true') action='store_true')
@ -135,7 +133,7 @@ class GeckoLoaderCli(CommandLineParser):
default=0, default=0,
action='count') action='count')
def __str__(self): def __str__(self) -> str:
return self.__doc__ return self.__doc__
def print_splash(self): def print_splash(self):
@ -169,8 +167,6 @@ class GeckoLoaderCli(CommandLineParser):
for line in logo: for line in logo:
print(color_text(line, [('', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT)) print(color_text(line, [('', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT))
sys.exit(0)
def check_updates(self): def check_updates(self):
repoChecker = Updater('JoshuaMKW', 'GeckoLoader') 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(color_text(f' :: Current version is "{self.__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
print('') print('')
sys.exit(0)
if __name__ == "__main__": def _validate_args(self, args) -> tuple:
parser = GeckoLoaderCli('GeckoLoader', __version__, description='Dol editing tool for allocating extended codespace') if args.alloc:
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:
try: try:
_codehook = int(args.hookaddress, 16) _allocation = int(args.alloc, 16)
except ValueError: except ValueError:
parser.error(color_text('The codehandler hook address was invalid\n', defaultColor=TREDLIT)) self.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT))
else: else:
_codehook = None _allocation = None
if args.handler == 'MINI': if args.hookaddress:
codeHandlerFile = 'codehandler-mini.bin' if 0x80000000 > int(args.hookaddress, 16) >= 0x81800000:
else: self.error(color_text('The codehandler hook address was beyond bounds\n', defaultColor=TREDLIT))
codeHandlerFile = 'codehandler.bin' 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): 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): 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: return _allocation, _codehook, codeHandlerFile
dolFile = DolFile(dol)
with open(resource_path(os.path.join('bin', os.path.normpath(codeHandlerFile))), 'rb') as handler: def _exec(self, args, tmpdir):
codeHandler = CodeHandler(handler) if not os.path.isabs(args.dolfile):
codeHandler.allocation = _allocation args.dolfile = os.path.abspath(args.dolfile)
codeHandler.hookAddress = _codehook
codeHandler.hookType = args.hooktype if not os.path.isabs(args.codelist):
codeHandler.includeAll = args.txtcodes.lower() == 'all' args.codelist = os.path.abspath(args.codelist)
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
if args.dest: if args.dest:
if os.path.splitext(args.dest)[1] == "": if not os.path.isabs(args.dest):
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/'), os.path.basename(args.dolfile))) 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: 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 <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: 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 ('', '/'): def _open_dol(self) -> tuple:
os.makedirs(os.path.dirname(dest), exist_ok=True) 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): if fname == "" or fname is None: # Make sure we have something to open
os.mkdir(TMPDIR) 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) sys.exit(0)
except FileNotFoundError as e: args = cli.parse_args()
parser.error(color_text(e + '\n', defaultColor=TREDLIT)) cli._exec(args, TMPDIR)

View file

@ -1,5 +1,34 @@
from tools import get_alignment, align_byte_size import os
import struct 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): def read_sbyte(f):
return struct.unpack("b", f.read(1))[0] 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): def write_bool(f, val, vSize=1):
if val is True: f.write(b'\x00'*(vSize-1) + b'\x01') if val is True: f.write(b'\x00'*(vSize-1) + b'\x01')
else: f.write(b'\x00' * vSize) else: f.write(b'\x00' * vSize)

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') elif (codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')
or codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')): or codetype.startswith(b'\xC6') or codetype.startswith(b'\xC7')):
dolFile.seek(address) 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 continue
if codetype.hex().startswith('2') or codetype.hex().startswith('3'): if codetype.hex().startswith('2') or codetype.hex().startswith('3'):
@ -183,6 +183,10 @@ class GCT(object):
class CodeHandler(object): class CodeHandler(object):
class Types:
MINI = "MINI"
FULL = "FULL"
def __init__(self, f): def __init__(self, f):
self._rawData = BytesIO(f.read()) self._rawData = BytesIO(f.read())
@ -212,9 +216,9 @@ class CodeHandler(object):
self.optimizeList = False self.optimizeList = False
if self.handlerLength < 0x900: if self.handlerLength < 0x900:
self.type = "Mini" self.type = CodeHandler.Types.MINI
else: else:
self.type = "Full" self.type = CodeHandler.Types.FULL
f.seek(0) f.seek(0)
@ -357,13 +361,12 @@ class KernelLoader(object):
self.quiet = False self.quiet = False
self.encrypt = False self.encrypt = False
def error(self, msg: str, exit=True): def error(self, msg: str):
if self._cli is not None: if self._cli is not None:
self._cli.error(msg, exit) self._cli.error(msg)
else: else:
print(msg) print(msg)
if exit: sys.exit(1)
sys.exit(1)
def set_variables(self, entryPoint: list, baseOffset: int=0): def set_variables(self, entryPoint: list, baseOffset: int=0):
self._rawData.seek(0) self._rawData.seek(0)
@ -450,8 +453,7 @@ class KernelLoader(object):
if self.encrypt: if self.encrypt:
codeHandler.encrypt_codes(_key) codeHandler.encrypt_codes(_key)
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile) -> tuple:
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile):
self.complete_data(codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF]) self.complete_data(codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF])
self._rawData.seek(0, 2) self._rawData.seek(0, 2)
@ -467,10 +469,12 @@ class KernelLoader(object):
dolFile.append_data_sections([(_kernelData, self.initAddress)]) dolFile.append_data_sections([(_kernelData, self.initAddress)])
except SectionCountFullError: except SectionCountFullError:
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT)) 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 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._rawData.seek(0)
codeHandler.geckoCodes.codeList.seek(0) codeHandler.geckoCodes.codeList.seek(0)
@ -483,6 +487,9 @@ class KernelLoader(object):
dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)]) dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)])
except SectionCountFullError: except SectionCountFullError:
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT)) 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): def protect_game(self, codeHandler: CodeHandler):
_oldpos = codeHandler.geckoCodes.codeList.tell() _oldpos = codeHandler.geckoCodes.codeList.tell()
@ -522,7 +529,7 @@ class KernelLoader(object):
@timer @timer
def build(self, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump): def build(self, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump):
oldStart = dolFile.entryPoint _oldStart = dolFile.entryPoint
'''Initialize our codes''' '''Initialize our codes'''
@ -530,7 +537,7 @@ class KernelLoader(object):
if codeHandler.geckoCodes is None: 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)) 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": if self.protect and self.patchJob == "ARENA":
self.protect_game(codeHandler) self.protect_game(codeHandler)
@ -545,7 +552,7 @@ class KernelLoader(object):
if self.initAddress: if self.initAddress:
try: try:
dolFile.resolve_address(self.initAddress) 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: except RuntimeError:
pass pass
else: else:
@ -561,33 +568,34 @@ class KernelLoader(object):
with open(dump, 'wb') as final: with open(dump, 'wb') as final:
dolFile.save(final) dolFile.save(final)
if self.quiet: if not self.quiet:
return if self.verbosity >= 3:
if self.verbosity >= 3: dolFile.print_info()
dolFile.print_info() print('-'*64)
print('-'*64) if self.verbosity >= 1:
if self.verbosity >= 1: print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT))
print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT))
return return
if self.patchJob == 'LEGACY': if self.patchJob == 'LEGACY':
legacy = True legacy = True
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength) codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
hooked = determine_codehook(dolFile, codeHandler, True) hooked = determine_codehook(dolFile, codeHandler, True)
if hooked: if hooked:
self.patch_legacy(codeHandler, dolFile) _status, _msg = self.patch_legacy(codeHandler, dolFile)
else: else:
legacy = False legacy = False
hooked = determine_codehook(dolFile, codeHandler, False) hooked = determine_codehook(dolFile, codeHandler, False)
if hooked: if hooked:
self.patch_arena(codeHandler, dolFile) _status, _msg = self.patch_arena(codeHandler, dolFile)
if not hooked: 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)) 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: 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: with open(dump, 'wb') as final:
dolFile.save(final) dolFile.save(final)
@ -606,19 +614,22 @@ class KernelLoader(object):
if self.verbosity >= 2: if self.verbosity >= 2:
print('') print('')
if legacy == False: if legacy is False:
info = [f' :: Start of game modified to address 0x{self.initAddress:X}', 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' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}', f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}',
f' :: Codehandler is of type "{codeHandler.type}"', 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: 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' :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}', f' :: Codehandler hooked at 0x{codeHandler.hookAddress:X}',
f' :: Codehandler is of type "{codeHandler.type}"', 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: for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
@ -630,13 +641,7 @@ class KernelLoader(object):
for bit in info: for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False) -> bool:
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):
if codeHandler.hookAddress is None: if codeHandler.hookAddress is None:
if not assert_code_hook(dolFile, codeHandler): if not assert_code_hook(dolFile, codeHandler):
return False return False
@ -648,7 +653,7 @@ def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False):
return True 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: for _, address, size, _, _ in dolFile.textSections:
dolFile.seek(address) dolFile.seek(address)
sample = dolFile.read(size) 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() html = response.read()
return html return html
def get_newest_version(self) -> str: def get_newest_version(self) -> tuple:
""" Returns newest release version """ """ Returns newest release version """
try: try:
response = self.request_release_data() response = self.request_release_data()