1
0
Fork 0

Remove legacy option, refactored code

This commit is contained in:
JoshuaMK 2020-12-05 08:06:00 -06:00
parent c0af5c030c
commit cd01e736cb
7 changed files with 398 additions and 525 deletions

View file

@ -1,6 +1,5 @@
#Written by JoshuaMK 2020 #Written by JoshuaMK 2020
import argparse
import atexit import atexit
import logging import logging
import os import os
@ -13,6 +12,7 @@ import tempfile
from contextlib import redirect_stdout, redirect_stderr from contextlib import redirect_stdout, redirect_stderr
from distutils.version import LooseVersion from distutils.version import LooseVersion
from io import StringIO from io import StringIO
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -45,15 +45,15 @@ except ImportError:
TRED = '' TRED = ''
TREDLIT = '' TREDLIT = ''
__version__ = "v7.0.0" __version__ = "v7.1.0"
TMPDIR = tempfile.mkdtemp("GeckoLoader-") TMPDIR = Path(tempfile.mkdtemp(prefix="GeckoLoader-"))
@atexit.register @atexit.register
def clean_tmp_resources(): def clean_tmp_resources():
tmpfolder = os.path.dirname(TMPDIR) tmpfolder = TMPDIR.parent
for entry in os.listdir(tmpfolder): for entry in tmpfolder.iterdir():
if entry.startswith("GeckoLoader-"): if entry.name.startswith("GeckoLoader-"):
shutil.rmtree(entry, ignore_errors=True) shutil.rmtree(entry, ignore_errors=True)
class GeckoLoaderCli(CommandLineParser): class GeckoLoaderCli(CommandLineParser):
@ -71,13 +71,6 @@ class GeckoLoaderCli(CommandLineParser):
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',
help='''["AUTO", "LEGACY", "ARENA"] Choose if GeckoLoader moves the codes to OSArenaHi,
or the legacy space. Default is "AUTO",
which auto decides where to insert the codes''',
default='AUTO',
choices=['AUTO', 'LEGACY', 'ARENA'],
metavar='TYPE')
self.add_argument('-tc', '--txtcodes', 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,
@ -173,11 +166,11 @@ class GeckoLoaderCli(CommandLineParser):
tag, status = repoChecker.get_newest_version() tag, status = repoChecker.get_newest_version()
print('')
if status is False: if status is False:
self.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False) self.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False)
print('')
if LooseVersion(tag) > LooseVersion(self.__version__): if LooseVersion(tag) > LooseVersion(self.__version__):
print(color_text(f' :: A new update is live at {repoChecker.gitReleases.format(repoChecker.owner, repoChecker.repo)}', defaultColor=TYELLOWLIT)) print(color_text(f' :: A new update is live at {repoChecker.gitReleases.format(repoChecker.owner, repoChecker.repo)}', defaultColor=TYELLOWLIT))
print(color_text(f' :: Current version is "{self.__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT)) print(color_text(f' :: Current version is "{self.__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT))
@ -211,9 +204,9 @@ class GeckoLoaderCli(CommandLineParser):
_codehook = None _codehook = None
if args.handler == CodeHandler.Types.MINI: if args.handler == CodeHandler.Types.MINI:
codeHandlerFile = 'codehandler-mini.bin' codeHandlerFile = Path('bin/codehandler-mini.bin')
elif args.handler == CodeHandler.Types.FULL: elif args.handler == CodeHandler.Types.FULL:
codeHandlerFile = 'codehandler.bin' codeHandlerFile = Path('bin/codehandler.bin')
else: else:
self.error(color_text(f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT)) self.error(color_text(f'Codehandler type {args.handler} is invalid\n', defaultColor=TREDLIT))
@ -226,23 +219,13 @@ class GeckoLoaderCli(CommandLineParser):
return _allocation, _codehook, codeHandlerFile return _allocation, _codehook, codeHandlerFile
def _exec(self, args, tmpdir): 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) _allocation, _codehook, codeHandlerFile = self._validate_args(args)
try: try:
with open(os.path.normpath(args.dolfile), 'rb') as dol: with open(args.dolfile, "rb") as dol:
dolFile = DolFile(dol) dolFile = DolFile(dol)
with open(resource_path(os.path.join('bin', os.path.normpath(codeHandlerFile))), 'rb') as handler: with resource_path(str(codeHandlerFile)).open("rb") as handler:
codeHandler = CodeHandler(handler) codeHandler = CodeHandler(handler)
codeHandler.allocation = _allocation codeHandler.allocation = _allocation
codeHandler.hookAddress = _codehook codeHandler.hookAddress = _codehook
@ -250,36 +233,28 @@ class GeckoLoaderCli(CommandLineParser):
codeHandler.includeAll = args.txtcodes.lower() == 'all' codeHandler.includeAll = args.txtcodes.lower() == 'all'
codeHandler.optimizeList = args.optimize codeHandler.optimizeList = args.optimize
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile: with resource_path("bin/geckoloader.bin").open("rb") as kernelfile:
geckoKernel = KernelLoader(kernelfile, cli) geckoKernel = KernelLoader(kernelfile, cli)
if args.init is not None: if args.init is not None:
geckoKernel.initAddress = int(args.init, 16) geckoKernel.initAddress = int(args.init, 16)
geckoKernel.patchJob = args.movecodes
geckoKernel.verbosity = args.verbose geckoKernel.verbosity = args.verbose
geckoKernel.quiet = args.quiet geckoKernel.quiet = args.quiet
geckoKernel.encrypt = args.encrypt geckoKernel.encrypt = args.encrypt
geckoKernel.protect = args.protect geckoKernel.protect = args.protect
if args.dest: if args.dest:
if not os.path.isabs(args.dest): dest = Path(args.dest).resolve()
if os.path.splitext(args.dest)[1] == "": if dest.suffix == "":
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('.').lstrip('\\').lstrip('/'), os.path.basename(args.dolfile))) dest = dest / args.dolfile.name
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(), "geckoloader-build", os.path.basename(args.dolfile))) dest = Path.cwd() / "geckoloader-build" / args.dolfile.name
if not os.path.exists(dest) and os.path.dirname(dest) not in ('', '/'): if not dest.parent.exists():
os.makedirs(os.path.dirname(dest), exist_ok=True) dest.parent.mkdir(parents=True, exist_ok=True)
geckoKernel.build(args.codelist, dolFile, codeHandler, TMPDIR, dest) geckoKernel.build(Path(args.codelist), dolFile, codeHandler, TMPDIR, dest)
except FileNotFoundError as e: except FileNotFoundError as e:
self.error(color_text(e, defaultColor=TREDLIT)) self.error(color_text(e, defaultColor=TREDLIT))
@ -309,12 +284,12 @@ class GUI(object):
self.style_log = [] self.style_log = []
self.compileCount = 0 self.compileCount = 0
self.log = logging.getLogger(f"GeckoLoader {self.cli.__version__}") self.log = logging.getLogger(f"GeckoLoader {self.version}")
if not os.path.exists(get_program_folder("GeckoLoader")): if not get_program_folder("GeckoLoader").exists():
os.mkdir(get_program_folder("GeckoLoader")) get_program_folder("GeckoLoader").mkdir()
hdlr = logging.FileHandler(os.path.join(get_program_folder("GeckoLoader"), "error.log")) hdlr = logging.FileHandler(get_program_folder("GeckoLoader") / "error.log")
formatter = logging.Formatter("\n%(levelname)s (%(asctime)s): %(message)s") formatter = logging.Formatter("\n%(levelname)s (%(asctime)s): %(message)s")
hdlr.setFormatter(formatter) hdlr.setFormatter(formatter)
self.log.addHandler(hdlr) self.log.addHandler(hdlr)
@ -329,7 +304,7 @@ class GUI(object):
"pre-patching codes, dynamic codehandler hooks, codespace ", "pre-patching codes, dynamic codehandler hooks, codespace ",
"extension through memory reallocation, multiple patching ", "extension through memory reallocation, multiple patching ",
"of a single DOL, and more.\n\n", "of a single DOL, and more.\n\n",
f"Current running version: {self.cli.__version__}\n\n" f"Current running version: {self.version}\n\n"
"Copyright (c) 2020\n\n", "Copyright (c) 2020\n\n",
"JoshuaMK <joshuamkw2002@gmail.com> \n\n", "JoshuaMK <joshuamkw2002@gmail.com> \n\n",
"All rights reserved." ]) "All rights reserved." ])
@ -340,21 +315,25 @@ class GUI(object):
else: else:
self.uiexSettings.show() self.uiexSettings.show()
@property
def version(self) -> str:
return self.cli.__version__
def _open_dol(self) -> tuple: def _open_dol(self) -> tuple:
if self.dolPath is None: # Just start in the home directory if self.dolPath is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", os.path.split(self.dolPath)[0], fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
else: else:
self.dolPath = os.path.normpath(fname) self.dolPath = Path(fname).resolve()
if os.path.isfile(self.dolPath): if self.dolPath.is_file():
self.ui.dolTextBox.setText(self.dolPath) self.ui.dolTextBox.setText(str(self.dolPath))
return True, None return True, None
else: else:
return False, "The file does not exist!" return False, "The file does not exist!"
@ -362,63 +341,63 @@ class GUI(object):
def _load_codes(self, isFolder: bool=False) -> tuple: def _load_codes(self, isFolder: bool=False) -> tuple:
if not isFolder: if not isFolder:
if self.codePath[0] is None: # Just start in the home directory if self.codePath[0] is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(Path.home()),
"Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0]) "Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", os.path.split(self.codePath[0])[0], fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Codelist", str(self.codePath[0].parent),
"Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0]) "Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*)")[0])
else: else:
if self.codePath[0] is None: # Just start in the home directory if self.codePath[0] is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(Path.home()),
QtWidgets.QFileDialog.ShowDirsOnly)) QtWidgets.QFileDialog.ShowDirsOnly))
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", os.path.split(self.codePath[0])[0], fname = str(QtWidgets.QFileDialog.getExistingDirectory(self.ui, "Open Codelist", str(self.codePath[0].parent),
QtWidgets.QFileDialog.ShowDirsOnly)) QtWidgets.QFileDialog.ShowDirsOnly))
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
else: else:
self.codePath = [os.path.normpath(fname), isFolder] self.codePath = [Path(fname).resolve(), isFolder]
if not isFolder: if not isFolder:
self.ui.gctFileTextBox.setText(self.codePath[0]) self.ui.gctFileTextBox.setText(str(self.codePath[0]))
self.ui.gctFolderTextBox.setText("") self.ui.gctFolderTextBox.setText("")
else: else:
self.ui.gctFileTextBox.setText("") self.ui.gctFileTextBox.setText("")
self.ui.gctFolderTextBox.setText(self.codePath[0]) self.ui.gctFolderTextBox.setText(str(self.codePath[0]))
return True, None return True, None
def _open_dest(self) -> tuple: def _open_dest(self) -> tuple:
if self.dolPath is None: # Just start in the home directory if self.dolPath is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(Path.home()),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", os.path.split(self.dolPath)[0], fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open DOL", str(self.dolPath.parent),
"Nintendo DOL Executable (*.dol);;All files (*)")[0]) "Nintendo DOL Executable (*.dol);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
else: else:
self.destPath = os.path.normpath(fname) self.destPath = Path(fname).resolve()
self.ui.destTextBox.setText(self.destPath) self.ui.destTextBox.setText(str(self.destPath))
return True, None return True, None
def _load_session(self) -> tuple: def _load_session(self) -> tuple:
if self.sessionPath is None: if self.sessionPath is None:
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(Path.home()),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", os.path.split(self.sessionPath)[0], fname = str(QtWidgets.QFileDialog.getOpenFileName(self.ui, "Open Session", str(self.sessionPath.parent),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
else: else:
self.sessionPath = os.path.normpath(fname) self.sessionPath = Path(fname).resolve()
with open(self.sessionPath, "rb") as session: with self.sessionPath.open("rb") as session:
p = cPickle.load(session) p = cPickle.load(session)
self.ui.dolTextBox.setText(p["dolPath"]) self.ui.dolTextBox.setText(p["dolPath"])
@ -435,11 +414,10 @@ class GUI(object):
self.ui.destTextBox.setText(p["destPath"]) self.ui.destTextBox.setText(p["destPath"])
self.ui.allocLineEdit.setText(p["alloc"]) self.ui.allocLineEdit.setText(p["alloc"])
self.ui.patchTypeSelect.setCurrentIndex(p["patchIndex"])
self.ui.handlerTypeSelect.setCurrentIndex(p["handlerIndex"]) self.ui.handlerTypeSelect.setCurrentIndex(p["handlerIndex"])
self.ui.hookTypeSelect.setCurrentIndex(p["hookIndex"]) self.ui.hookTypeSelect.setCurrentIndex(p["hookIndex"])
self.ui.txtCodesIncludeSelect.setCurrentIndex(p["txtIndex"]) self.ui.txtCodesIncludeSelect.setCurrentIndex(p["txtIndex"])
self.ui.optimizeSelect.setCurrentIndex(p["optimizeIndex"]) self.uiexSettings.optimizeCodes.setChecked(p["optimize"])
self.uiexSettings.protectCodes.setChecked(p["protect"]) self.uiexSettings.protectCodes.setChecked(p["protect"])
self.uiexSettings.encryptCodes.setChecked(p["encrypt"]) self.uiexSettings.encryptCodes.setChecked(p["encrypt"])
self.uiexSettings.codehookLineEdit.setText(p["hookAddress"]) self.uiexSettings.codehookLineEdit.setText(p["hookAddress"])
@ -451,19 +429,19 @@ class GUI(object):
def _save_session(self, saveAs=False): def _save_session(self, saveAs=False):
if saveAs or self.sessionPath is None or self.sessionPath == "": if saveAs or self.sessionPath is None or self.sessionPath == "":
if self.sessionPath is None: # Just start in the home directory if self.sessionPath is None: # Just start in the home directory
fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", os.path.expanduser("~"), fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", str(Path.home()),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
else: # Start in the last directory used by the user else: # Start in the last directory used by the user
fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", os.path.split(self.dolPath)[0], fname = str(QtWidgets.QFileDialog.getSaveFileName(self.ui, "Save Session", str(self.dolPath.parent),
"GeckoLoader Session (*.gprf);;All files (*)")[0]) "GeckoLoader Session (*.gprf);;All files (*)")[0])
if fname == "" or fname is None: # Make sure we have something to open if fname == "" or fname is None: # Make sure we have something to open
return False, None return False, None
else: else:
self.sessionPath = os.path.normpath(fname) self.sessionPath = Path(fname).resolve()
try: try:
with open(self.sessionPath, "wb") as session: with self.sessionPath.open("wb") as session:
p = {} p = {}
p["dolPath"] = self.ui.dolTextBox.text().strip() p["dolPath"] = self.ui.dolTextBox.text().strip()
@ -471,11 +449,10 @@ class GUI(object):
p["gctFolderPath"] = self.ui.gctFolderTextBox.text().strip() p["gctFolderPath"] = self.ui.gctFolderTextBox.text().strip()
p["destPath"] = self.ui.destTextBox.text().strip() p["destPath"] = self.ui.destTextBox.text().strip()
p["alloc"] = self.ui.allocLineEdit.text().strip() p["alloc"] = self.ui.allocLineEdit.text().strip()
p["patchIndex"] = self.ui.patchTypeSelect.currentIndex()
p["handlerIndex"] = self.ui.handlerTypeSelect.currentIndex() p["handlerIndex"] = self.ui.handlerTypeSelect.currentIndex()
p["hookIndex"] = self.ui.hookTypeSelect.currentIndex() p["hookIndex"] = self.ui.hookTypeSelect.currentIndex()
p["txtIndex"] = self.ui.txtCodesIncludeSelect.currentIndex() p["txtIndex"] = self.ui.txtCodesIncludeSelect.currentIndex()
p["optimizeIndex"] = self.ui.optimizeSelect.currentIndex() p["optimize"] = self.uiexSettings.optimizeCodes.isChecked()
p["protect"] = self.uiexSettings.protectCodes.isChecked() p["protect"] = self.uiexSettings.protectCodes.isChecked()
p["encrypt"] = self.uiexSettings.encryptCodes.isChecked() p["encrypt"] = self.uiexSettings.encryptCodes.isChecked()
p["hookAddress"] = self.uiexSettings.codehookLineEdit.text().strip() p["hookAddress"] = self.uiexSettings.codehookLineEdit.text().strip()
@ -533,7 +510,6 @@ class GUI(object):
self.ui.gctFolderTextBox.setText("") self.ui.gctFolderTextBox.setText("")
self.ui.destTextBox.setText("") self.ui.destTextBox.setText("")
self.ui.allocLineEdit.setText("") self.ui.allocLineEdit.setText("")
self.ui.patchTypeSelect.setCurrentIndex(0)
self.ui.handlerTypeSelect.setCurrentIndex(0) self.ui.handlerTypeSelect.setCurrentIndex(0)
self.ui.hookTypeSelect.setCurrentIndex(0) self.ui.hookTypeSelect.setCurrentIndex(0)
self.ui.txtCodesIncludeSelect.setCurrentIndex(0) self.ui.txtCodesIncludeSelect.setCurrentIndex(0)
@ -545,13 +521,12 @@ class GUI(object):
self.uiexSettings.kernelHookLineEdit.setText("") self.uiexSettings.kernelHookLineEdit.setText("")
self.uiexSettings.verbositySelect.setCurrentIndex(0) self.uiexSettings.verbositySelect.setCurrentIndex(0)
self.ui.set_edit_fields() self.ui.set_edit_fields()
#Reset all ui elements as needed
def load_prefs(self): def load_prefs(self):
datapath = get_program_folder("GeckoLoader") datapath = get_program_folder("GeckoLoader")
try: try:
with open(os.path.join(datapath, ".GeckoLoader.conf"), "rb") as f: with (datapath / ".GeckoLoader.conf").open("rb") as f:
try: try:
p = cPickle.load(f) p = cPickle.load(f)
except cPickle.UnpicklingError as e: except cPickle.UnpicklingError as e:
@ -587,13 +562,13 @@ class GUI(object):
self.prefs["darktheme"] = self.uiprefs.qtdarkButton.isChecked() self.prefs["darktheme"] = self.uiprefs.qtdarkButton.isChecked()
try: try:
with open(os.path.join(datapath, ".GeckoLoader.conf"), "wb") as f: with (datapath / ".GeckoLoader.conf").open("wb") as f:
cPickle.dump(self.prefs, f) cPickle.dump(self.prefs, f)
except IOError as e: except IOError as e:
self.log.exception(e) self.log.exception(e)
def load_qtstyle(self, style, first_style_load=False): def load_qtstyle(self, style, first_style_load=False):
self.style_log.append( [self.app.style, self.uiprefs.qtstyleSelect.currentText()] ) self.style_log.append([self.app.style, self.uiprefs.qtstyleSelect.currentText()])
if len(self.style_log) > 2: if len(self.style_log) > 2:
self.style_log.pop(0) self.style_log.pop(0)
@ -631,7 +606,7 @@ class GUI(object):
_status = True _status = True
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("bin", "icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(resource_path(Path("bin/icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
if _status is False: if _status is False:
reply = QtWidgets.QErrorMessage() reply = QtWidgets.QErrorMessage()
@ -707,33 +682,32 @@ class GUI(object):
self.compileCount += 1 self.compileCount += 1
if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "": if self.ui.dolTextBox.isEnabled and self.ui.dolTextBox.text().strip() != "":
dol = os.path.normpath(self.ui.dolTextBox.text().strip()) dol = self.ui.dolTextBox.text().strip()
else: else:
self.ui.responses.appendPlainText("DOL is missing, please add the path to your codes in the respective textbox" + "\n\n") self.ui.responses.appendPlainText("DOL is missing, please add the path to your codes in the respective textbox" + "\n\n")
return return
if self.ui.gctFileTextBox.isEnabled and self.ui.gctFileTextBox.text().strip() != "": if self.ui.gctFileTextBox.isEnabled and self.ui.gctFileTextBox.text().strip() != "":
gct = os.path.normpath(self.ui.gctFileTextBox.text().strip()) gct = self.ui.gctFileTextBox.text().strip()
elif self.ui.gctFolderTextBox.isEnabled and self.ui.gctFolderTextBox.text().strip() != "": elif self.ui.gctFolderTextBox.isEnabled and self.ui.gctFolderTextBox.text().strip() != "":
gct = os.path.normpath(self.ui.gctFolderTextBox.text().strip()) gct = self.ui.gctFolderTextBox.text().strip()
else: else:
self.ui.responses.appendPlainText("GCT is missing, please add the path to your codes in the respective textbox" + "\n\n") self.ui.responses.appendPlainText("GCT is missing, please add the path to your codes in the respective textbox" + "\n\n")
return return
alloc = self.ui.allocLineEdit.text().strip() alloc = self.ui.allocLineEdit.text().strip()
patchJob = self.ui.patchTypeSelect.currentText().strip()
hookType = self.ui.hookTypeSelect.currentText().strip() hookType = self.ui.hookTypeSelect.currentText().strip()
hookAddress = self.uiexSettings.codehookLineEdit.text().strip() hookAddress = self.uiexSettings.codehookLineEdit.text().strip()
initAddress = self.uiexSettings.kernelHookLineEdit.text().strip() initAddress = self.uiexSettings.kernelHookLineEdit.text().strip()
txtInclude = self.ui.txtCodesIncludeSelect.currentText().strip() txtInclude = self.ui.txtCodesIncludeSelect.currentText().strip()
codeHandlerType = self.ui.handlerTypeSelect.currentText().strip() codeHandlerType = self.ui.handlerTypeSelect.currentText().strip()
optimize = self.ui.optimizeSelect.currentText().strip() == "TRUE" optimize = self.uiexSettings.optimizeCodes.isChecked()
protect = self.uiexSettings.protectCodes.isChecked() protect = self.uiexSettings.protectCodes.isChecked()
encrypt = self.uiexSettings.encryptCodes.isChecked() encrypt = self.uiexSettings.encryptCodes.isChecked()
verbosity = int(self.uiexSettings.verbositySelect.currentText().strip()) verbosity = int(self.uiexSettings.verbositySelect.currentText().strip())
dest = self.ui.destTextBox.text().strip() dest = self.ui.destTextBox.text().strip()
argslist = [ dol, gct, "-m", patchJob, "-t", txtInclude, "--handler", codeHandlerType, "--hooktype", hookType ] argslist = [ dol, gct, "-t", txtInclude, "--handler", codeHandlerType, "--hooktype", hookType ]
if alloc != "": if alloc != "":
argslist.append("-a") argslist.append("-a")
@ -802,23 +776,23 @@ class GUI(object):
def run(self): def run(self):
if sys.platform != "win32": if sys.platform != "win32":
datapath = os.path.join(os.getenv("HOME"), ".GeckoLoader") datapath = Path.home() / ".GeckoLoader"
else: else:
datapath = os.path.join(os.getenv("APPDATA"), "GeckoLoader") datapath = Path(os.getenv("APPDATA")) / "GeckoLoader"
if not os.path.isdir(datapath): if not datapath.is_dir():
os.mkdir(datapath) datapath.mkdir()
self.app = QtWidgets.QApplication(sys.argv) self.app = QtWidgets.QApplication(sys.argv)
self.default_qtstyle = self.app.style().objectName() self.default_qtstyle = self.app.style().objectName()
self.ui = MainWindow(self.cli.__version__) self.ui = MainWindow(self.version)
self.uiprefs = PrefWindow() self.uiprefs = PrefWindow()
self.uiexSettings = SettingsWindow() self.uiexSettings = SettingsWindow()
self.uiprefs.qtstyleSelect.addItem("Default") self.uiprefs.qtstyleSelect.addItem("Default")
for i in range(0, len(list(QtWidgets.QStyleFactory.keys()))): styleKeys = list(QtWidgets.QStyleFactory.keys())
self.uiprefs.qtstyleSelect.addItem(list(QtWidgets.QStyleFactory.keys())[i]) self.uiprefs.qtstyleSelect.addItems(styleKeys)
self.load_prefs() self.load_prefs()
self.load_qtstyle(self.prefs.get("qtstyle"), True) self.load_qtstyle(self.prefs.get("qtstyle"), True)
@ -841,14 +815,10 @@ if __name__ == "__main__":
app = GUI(cli) app = GUI(cli)
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
app.run() app.run()
sys.exit(1) #Should never reach here
elif '--checkupdate' in sys.argv: elif '--checkupdate' in sys.argv:
cli.check_updates() cli.check_updates()
sys.exit(0)
elif '--splash' in sys.argv: elif '--splash' in sys.argv:
cli.print_splash() cli.print_splash()
sys.exit(0) else:
args = cli.parse_args()
args = cli.parse_args() cli._exec(args, TMPDIR)
cli._exec(args, TMPDIR)

View file

@ -17,7 +17,7 @@ class PrefWindow(QtWidgets.QDialog):
self.setFixedSize(300, 120) self.setFixedSize(300, 120)
self.setModal(True) self.setModal(True)
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("bin", "icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(str(resource_path(os.path.join("bin", "icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.setWindowIcon(icon) self.setWindowIcon(icon)
#Buttonbox #Buttonbox
@ -73,13 +73,13 @@ class SettingsWindow(QtWidgets.QDialog):
self.setObjectName("Dialog") self.setObjectName("Dialog")
if sys.platform == "win32": if sys.platform == "win32":
self.setFixedSize(300, 210) self.setFixedSize(300, 240)
else: else:
self.setFixedSize(370, 210) self.setFixedSize(370, 240)
self.setModal(True) self.setModal(True)
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("bin", "icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(str(resource_path(os.path.join("bin", "icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.setWindowIcon(icon) self.setWindowIcon(icon)
#Buttonbox #Buttonbox
@ -113,10 +113,17 @@ class SettingsWindow(QtWidgets.QDialog):
self.encryptCodes.setText("Encrypt codes") self.encryptCodes.setText("Encrypt codes")
self.comboBoxLayout.addWidget(self.encryptCodes, 2, 0, 1, 1) self.comboBoxLayout.addWidget(self.encryptCodes, 2, 0, 1, 1)
#optimize codes
self.optimizeCodes = QtWidgets.QCheckBox()
self.optimizeCodes.setObjectName("optimizeCodes")
self.optimizeCodes.setText("Optimize codes")
self.optimizeCodes.setChecked(True)
self.comboBoxLayout.addWidget(self.optimizeCodes, 3, 0, 1, 1)
#Codehook Address Label #Codehook Address Label
self.codehookLabel = QtWidgets.QLabel() self.codehookLabel = QtWidgets.QLabel()
self.codehookLabel.setObjectName("codehookLabel") self.codehookLabel.setObjectName("codehookLabel")
self.comboBoxLayout.addWidget(self.codehookLabel, 3, 0, 1, 1) self.comboBoxLayout.addWidget(self.codehookLabel, 4, 0, 1, 1)
#Codehook Address Textbox #Codehook Address Textbox
self.codehookLineEdit = QtWidgets.QLineEdit() self.codehookLineEdit = QtWidgets.QLineEdit()
@ -137,12 +144,12 @@ class SettingsWindow(QtWidgets.QDialog):
self.codehookLineEdit.setMaxLength(8) self.codehookLineEdit.setMaxLength(8)
self.codehookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) self.codehookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.codehookLineEdit.setObjectName("codehookLineEdit") self.codehookLineEdit.setObjectName("codehookLineEdit")
self.comboBoxLayout.addWidget(self.codehookLineEdit, 3, 1, 1, 1) self.comboBoxLayout.addWidget(self.codehookLineEdit, 4, 1, 1, 1)
#kernelHook Address Label #kernelHook Address Label
self.kernelHookLabel = QtWidgets.QLabel() self.kernelHookLabel = QtWidgets.QLabel()
self.kernelHookLabel.setObjectName("kernelHookLabel") self.kernelHookLabel.setObjectName("kernelHookLabel")
self.comboBoxLayout.addWidget(self.kernelHookLabel, 4, 0, 1, 1) self.comboBoxLayout.addWidget(self.kernelHookLabel, 5, 0, 1, 1)
#kernelHook Address Textbox #kernelHook Address Textbox
self.kernelHookLineEdit = QtWidgets.QLineEdit() self.kernelHookLineEdit = QtWidgets.QLineEdit()
@ -163,19 +170,18 @@ class SettingsWindow(QtWidgets.QDialog):
self.kernelHookLineEdit.setMaxLength(8) self.kernelHookLineEdit.setMaxLength(8)
self.kernelHookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) self.kernelHookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.kernelHookLineEdit.setObjectName("kernelHookLineEdit") self.kernelHookLineEdit.setObjectName("kernelHookLineEdit")
self.comboBoxLayout.addWidget(self.kernelHookLineEdit, 4, 1, 1, 1) self.comboBoxLayout.addWidget(self.kernelHookLineEdit, 5, 1, 1, 1)
#verbosity label #verbosity label
self.verbosityLabel = QtWidgets.QLabel() self.verbosityLabel = QtWidgets.QLabel()
self.verbosityLabel.setObjectName("verbosityLabel") self.verbosityLabel.setObjectName("verbosityLabel")
self.comboBoxLayout.addWidget(self.verbosityLabel, 5, 0, 1, 1) self.comboBoxLayout.addWidget(self.verbosityLabel, 6, 0, 1, 1)
#verbosity box #verbosity box
self.verbositySelect = QtWidgets.QComboBox() self.verbositySelect = QtWidgets.QComboBox()
self.verbositySelect.addItems(["1", "2", "3", "0"]) self.verbositySelect.addItems(["1", "2", "3", "0"])
self.verbositySelect.setObjectName("verbositySelect") self.verbositySelect.setObjectName("verbositySelect")
self.comboBoxLayout.addWidget(self.verbositySelect, 5, 1, 1, 1) self.comboBoxLayout.addWidget(self.verbositySelect, 6, 1, 1, 1)
self.formLayoutWidget.addLayout(self.comboBoxLayout, 0, 0, 1, 1) self.formLayoutWidget.addLayout(self.comboBoxLayout, 0, 0, 1, 1)
self.formLayoutWidget.addWidget(self.buttonBox, 1, 0, 1, 1) self.formLayoutWidget.addWidget(self.buttonBox, 1, 0, 1, 1)
@ -189,6 +195,7 @@ class SettingsWindow(QtWidgets.QDialog):
def set_edit_fields(self): def set_edit_fields(self):
self.protectCodes.setEnabled(True) self.protectCodes.setEnabled(True)
self.encryptCodes.setEnabled(True) self.encryptCodes.setEnabled(True)
self.optimizeCodes.setEnabled(True)
self.codehookLineEdit.setEnabled(True) self.codehookLineEdit.setEnabled(True)
self.kernelHookLineEdit.setEnabled(True) self.kernelHookLineEdit.setEnabled(True)
self.verbositySelect.setEnabled(True) self.verbositySelect.setEnabled(True)

View file

@ -45,9 +45,9 @@ class DolFile(object):
f.seek(offset) f.seek(offset)
data = BytesIO(f.read(size)) data = BytesIO(f.read(size))
if i < DolFile.maxTextSections: if i < DolFile.maxTextSections:
self.textSections.append([offset, address, size, data, DolFile.SectionType.Text]) self.textSections.append({"offset": offset, "address": address, "size": size, "data": data, "type": DolFile.SectionType.Text})
else: else:
self.dataSections.append([offset, address, size, data, DolFile.SectionType.Data]) self.dataSections.append({"offset": offset, "address": address, "size": size, "data": data, "type": DolFile.SectionType.Data})
f.seek(DolFile.bssInfoLoc) f.seek(DolFile.bssInfoLoc)
self.bssAddress = read_uint32(f) self.bssAddress = read_uint32(f)
@ -56,38 +56,38 @@ class DolFile(object):
f.seek(DolFile.entryInfoLoc) f.seek(DolFile.entryInfoLoc)
self.entryPoint = read_uint32(f) self.entryPoint = read_uint32(f)
self._currLogicAddr = self.get_first_section()[1] self._currLogicAddr = self.first_section["address"]
self.seek(self._currLogicAddr) self.seek(self._currLogicAddr)
f.seek(0) f.seek(0)
def __str__(self): def __repr__(self) -> str:
return "Nintendo DOL format executable for the Wii and Gamecube" return f"repr={vars(self)}"
def __str__(self) -> str:
return f"Nintendo DOL executable {self.__repr__()}"
# Internal function for
def resolve_address(self, gcAddr: int) -> tuple: def resolve_address(self, gcAddr: int) -> tuple:
""" Returns the data of the section that houses the given address\n """ Returns the data of the section that houses the given address\n
UnmappedAddressError is raised when the address is unmapped """ UnmappedAddressError is raised when the address is unmapped """
for offset, address, size, data, sectiontype in self.textSections: for section in self.sections:
if address <= gcAddr < address+size: if section["address"] <= gcAddr < (section["address"] + section["size"]):
return offset, address, size, data, sectiontype return section
for offset, address, size, data, sectiontype in self.dataSections:
if address <= gcAddr < address+size:
return offset, address, size, data, sectiontype
raise UnmappedAddressError(f"Unmapped address: 0x{gcAddr:X}") raise UnmappedAddressError(f"Unmapped address: 0x{gcAddr:X}")
def seek_nearest_unmapped(self, gcAddr: int, buffer=0) -> int: def seek_nearest_unmapped(self, gcAddr: int, buffer=0) -> int:
'''Returns the nearest unmapped address (greater) if the given address is already taken by data''' '''Returns the nearest unmapped address (greater) if the given address is already taken by data'''
for _, address, size, _, _ in self.textSections: for section in self.sections:
if address > (gcAddr + buffer) or address+size < gcAddr: if section["address"] > (gcAddr + buffer) or (section["address"] + section["size"]) < gcAddr:
continue continue
gcAddr = address + size gcAddr = section["address"] + section["size"]
for _, address, size, _, _ in self.dataSections:
if address > (gcAddr + buffer) or address+size < gcAddr: try:
continue self.resolve_address(gcAddr)
gcAddr = address + size except UnmappedAddressError:
break
return gcAddr return gcAddr
@property @property
@ -98,24 +98,23 @@ class DolFile(object):
yield i yield i
for i in self.dataSections: for i in self.dataSections:
yield i yield i
return
def get_final_section(self) -> tuple: @property
def last_section(self) -> tuple:
""" Returns the last section in the dol file as sorted by internal offset """ """ Returns the last section in the dol file as sorted by internal offset """
largestOffset = 0 largestOffset = 0
indexToTarget = 0 indexToTarget = 0
targetType = DolFile.SectionType.Text targetType = DolFile.SectionType.Text
for i, sectionData in enumerate(self.textSections): for i, section in enumerate(self.textSections):
if sectionData[0] > largestOffset: if section["offset"] > largestOffset:
largestOffset = sectionData[0] largestOffset = section["offset"]
indexToTarget = i indexToTarget = i
targetType = DolFile.SectionType.Text targetType = DolFile.SectionType.Text
for i, sectionData in enumerate(self.dataSections): for i, section in enumerate(self.dataSections):
if sectionData[0] > largestOffset: if section["offset"] > largestOffset:
largestOffset = sectionData[0] largestOffset = section["offset"]
indexToTarget = i indexToTarget = i
targetType = DolFile.SectionType.Data targetType = DolFile.SectionType.Data
@ -124,21 +123,22 @@ class DolFile(object):
else: else:
return self.dataSections[indexToTarget] return self.dataSections[indexToTarget]
def get_first_section(self) -> tuple: @property
def first_section(self) -> tuple:
""" Returns the first section in the dol file as sorted by internal offset """ """ Returns the first section in the dol file as sorted by internal offset """
smallestOffset = 0xFFFFFFFF smallestOffset = 0xFFFFFFFF
indexToTarget = 0 indexToTarget = 0
targetType = DolFile.SectionType.Text targetType = DolFile.SectionType.Text
for i, sectionData in enumerate(self.textSections): for i, section in enumerate(self.textSections):
if sectionData[0] < smallestOffset: if section["offset"] < smallestOffset:
smallestOffset = sectionData[0] smallestOffset = section["offset"]
indexToTarget = i indexToTarget = i
targetType = DolFile.SectionType.Text targetType = DolFile.SectionType.Text
for i, sectionData in enumerate(self.dataSections): for i, section in enumerate(self.dataSections):
if sectionData[0] < smallestOffset: if section["offset"] < smallestOffset:
smallestOffset = sectionData[0] smallestOffset = section["offset"]
indexToTarget = i indexToTarget = i
targetType = DolFile.SectionType.Data targetType = DolFile.SectionType.Data
@ -149,32 +149,32 @@ class DolFile(object):
# Unsupported: Reading an entire dol file # Unsupported: Reading an entire dol file
# Assumption: A read should not go beyond the current section # Assumption: A read should not go beyond the current section
def read(self, _size) -> bytes: def read(self, _size: int) -> bytes:
_, address, size, data, _ = self.resolve_address(self._currLogicAddr) section = self.resolve_address(self._currLogicAddr)
if self._currLogicAddr + _size > address + size: if self._currLogicAddr + _size > (section["address"] + section["size"]):
raise UnmappedAddressError("Read goes over current section") raise UnmappedAddressError("Read goes over current section")
self._currLogicAddr += _size self._currLogicAddr += _size
return data.read(_size) return section["data"].read(_size)
# Assumption: A write should not go beyond the current section # Assumption: A write should not go beyond the current section
def write(self, _data): def write(self, _data: bytes):
_, address, size, data, _ = self.resolve_address(self._currLogicAddr) section = self.resolve_address(self._currLogicAddr)
if self._currLogicAddr + len(_data) > address + size: if self._currLogicAddr + len(_data) > (section["address"] + section["size"]):
raise UnmappedAddressError("Write goes over current section") raise UnmappedAddressError("Write goes over current section")
data.write(_data) section["data"].write(_data)
self._currLogicAddr += len(_data) self._currLogicAddr += len(_data)
def seek(self, where, whence=0): def seek(self, where: int, whence: int = 0):
if whence == 0: if whence == 0:
_, address, _, data, _ = self.resolve_address(where) section = self.resolve_address(where)
data.seek(where - address) section["data"].seek(where - section["address"])
self._currLogicAddr = where self._currLogicAddr = where
elif whence == 1: elif whence == 1:
_, address, _, data, _ = self.resolve_address(self._currLogicAddr + where) section = self.resolve_address(self._currLogicAddr + where)
data.seek((self._currLogicAddr + where) - address) section["data"].seek((self._currLogicAddr + where) - section["address"])
self._currLogicAddr += where self._currLogicAddr += where
else: else:
@ -185,29 +185,23 @@ class DolFile(object):
def save(self, f): def save(self, f):
f.seek(0) f.seek(0)
f.write(b"\x00" * self.get_full_size()) f.write(b"\x00" * self.size)
for i in range(DolFile.maxTextSections + DolFile.maxDataSections): for i, section in enumerate(self.sections):
if i < DolFile.maxTextSections: if section["type"] == DolFile.SectionType.Data:
if i < len(self.textSections): entry = i + (DolFile.maxTextSections - len(self.textSections))
offset, address, size, data, _ = self.textSections[i]
else:
continue
else: else:
if i - DolFile.maxTextSections < len(self.dataSections): entry = i
offset, address, size, data, _ = self.dataSections[i - DolFile.maxTextSections]
else:
continue
f.seek(DolFile.offsetInfoLoc + (i << 2)) f.seek(DolFile.offsetInfoLoc + (entry << 2))
write_uint32(f, offset) #offset in file write_uint32(f, section["offset"]) #offset in file
f.seek(DolFile.addressInfoLoc + (i << 2)) f.seek(DolFile.addressInfoLoc + (entry << 2))
write_uint32(f, address) #game address write_uint32(f, section["address"]) #game address
f.seek(DolFile.sizeInfoLoc + (i << 2)) f.seek(DolFile.sizeInfoLoc + (entry << 2))
write_uint32(f, size) #size in file write_uint32(f, section["size"]) #size in file
f.seek(offset) f.seek(section["offset"])
f.write(data.getbuffer()) f.write(section["data"].getbuffer())
f.seek(DolFile.bssInfoLoc) f.seek(DolFile.bssInfoLoc)
write_uint32(f, self.bssAddress) write_uint32(f, self.bssAddress)
@ -217,10 +211,11 @@ class DolFile(object):
write_uint32(f, self.entryPoint) write_uint32(f, self.entryPoint)
align_byte_size(f, 256) align_byte_size(f, 256)
def get_full_size(self) -> int: @property
def size(self) -> int:
try: try:
offset, _, size, _, _ = self.get_final_section() section = self.last_section
return (offset + size + 255) & -256 return (section["offset"] + section["size"] + 255) & -256
except IndexError: except IndexError:
return 0x100 return 0x100
@ -229,20 +224,20 @@ class DolFile(object):
section: DolFile.SectionType """ section: DolFile.SectionType """
if section == DolFile.SectionType.Text: if section == DolFile.SectionType.Text:
return self.textSections[index][2] return self.textSections[index]["size"]
else: else:
return self.dataSections[index][2] return self.dataSections[index]["size"]
def append_text_sections(self, sectionsList: list) -> bool: def append_text_sections(self, sectionsList: list):
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """ """ Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
for i, dataSet in enumerate(sectionsList): for i, dataSet in enumerate(sectionsList):
if len(self.textSections) >= DolFile.maxTextSections: if len(self.textSections) >= DolFile.maxTextSections:
raise SectionCountFullError(f"Exceeded max text section limit of {DolFile.maxTextSections}") raise SectionCountFullError(f"Exceeded max text section limit of {DolFile.maxTextSections}")
fOffset, _, fSize, _, _ = self.get_final_section() finalSection = self.last_section
_, pAddress, pSize, _, _ = self.textSections[len(self.textSections) - 1] lastSection = self.textSections[len(self.textSections) - 1]
data, address = dataSet data, address = dataSet
if not hasattr(data, "getbuffer"): if not hasattr(data, "getbuffer"):
@ -252,7 +247,7 @@ class DolFile(object):
else: else:
data = BytesIO(data) data = BytesIO(data)
offset = fOffset + fSize offset = finalSection["offset"] + finalSection["size"]
if i < len(sectionsList) - 1: if i < len(sectionsList) - 1:
size = (len(data.getbuffer()) + 31) & -32 size = (len(data.getbuffer()) + 31) & -32
@ -260,22 +255,22 @@ class DolFile(object):
size = (len(data.getbuffer()) + 255) & -256 size = (len(data.getbuffer()) + 255) & -256
if address is None: if address is None:
address = self.seek_nearest_unmapped(pAddress + pSize, size) address = self.seek_nearest_unmapped(lastSection["address"] + lastSection["size"], size)
if address < 0x80000000 or address >= 0x81200000: if address < 0x80000000 or address >= 0x81200000:
raise AddressOutOfRangeError(f"Address '{address:08X}' of text section {i} is beyond scope (0x80000000 <-> 0x81200000)") raise AddressOutOfRangeError(f"Address '{address:08X}' of text section {i} is beyond scope (0x80000000 <-> 0x81200000)")
self.textSections.append((offset, address, size, data, DolFile.SectionType.Text)) self.textSections.append({"offset": offset, "address": address, "size": size, "data": data, "type": DolFile.SectionType.Text})
def append_data_sections(self, sectionsList: list) -> bool: def append_data_sections(self, sectionsList: list):
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """ """ Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
for i, dataSet in enumerate(sectionsList): for i, dataSet in enumerate(sectionsList):
if len(self.dataSections) >= DolFile.maxDataSections: if len(self.dataSections) >= DolFile.maxDataSections:
raise SectionCountFullError(f"Exceeded max data section limit of {DolFile.maxDataSections}") raise SectionCountFullError(f"Exceeded max data section limit of {DolFile.maxDataSections}")
fOffset, _, fSize, _, _ = self.get_final_section() finalSection = self.last_section
_, pAddress, pSize, _, _ = self.dataSections[len(self.dataSections) - 1] lastSection = self.dataSections[len(self.dataSections) - 1]
data, address = dataSet data, address = dataSet
if not hasattr(data, "getbuffer"): if not hasattr(data, "getbuffer"):
@ -285,7 +280,7 @@ class DolFile(object):
else: else:
data = BytesIO(data) data = BytesIO(data)
offset = fOffset + fSize offset = finalSection["offset"] + finalSection["size"]
if i < len(sectionsList) - 1: if i < len(sectionsList) - 1:
size = (len(data.getbuffer()) + 31) & -32 size = (len(data.getbuffer()) + 31) & -32
@ -293,12 +288,12 @@ class DolFile(object):
size = (len(data.getbuffer()) + 255) & -256 size = (len(data.getbuffer()) + 255) & -256
if address is None: if address is None:
address = self.seek_nearest_unmapped(pAddress + pSize, size) address = self.seek_nearest_unmapped(lastSection["address"] + lastSection["size"], size)
if address < 0x80000000 or address >= 0x81200000: if address < 0x80000000 or address >= 0x81200000:
raise AddressOutOfRangeError(f"Address '{address:08X}' of data section {i} is beyond scope (0x80000000 <-> 0x81200000)") raise AddressOutOfRangeError(f"Address '{address:08X}' of data section {i} is beyond scope (0x80000000 <-> 0x81200000)")
self.dataSections.append((offset, address, size, data, DolFile.SectionType.Data)) self.dataSections.append({"offset": offset, "address": address, "size": size, "data": data, "type": DolFile.SectionType.Data})
def insert_branch(self, to: int, _from: int, lk=0): def insert_branch(self, to: int, _from: int, lk=0):
""" Insert a branch instruction at _from\n """ Insert a branch instruction at _from\n
@ -309,7 +304,6 @@ class DolFile(object):
_from &= 0xFFFFFFFC _from &= 0xFFFFFFFC
to &= 0xFFFFFFFC to &= 0xFFFFFFFC
self.seek(_from) self.seek(_from)
print(hex(to), hex(_from), hex((to - _from) & 0x3FFFFFD | 0x48000000 | lk))
write_uint32(self, (to - _from) & 0x3FFFFFD | 0x48000000 | lk) write_uint32(self, (to - _from) & 0x3FFFFFD | 0x48000000 | lk)
def extract_branch_addr(self, bAddr: int) -> tuple: def extract_branch_addr(self, bAddr: int) -> tuple:
@ -389,7 +383,7 @@ class DolFile(object):
info = [ "-"*len(header) + "\n" + header + "\n" + "-"*len(header), info = [ "-"*len(header) + "\n" + header + "\n" + "-"*len(header),
"Text sections:".ljust(16, " ") + f"0x{len(self.textSections):X}", "Text sections:".ljust(16, " ") + f"0x{len(self.textSections):X}",
"Data sections:".ljust(16, " ") + f"0x{len(self.dataSections):X}", "Data sections:".ljust(16, " ") + f"0x{len(self.dataSections):X}",
"File length:".ljust(16, " ") + f"0x{self.get_full_size():X}" ] "File length:".ljust(16, " ") + f"0x{self.size:X}" ]
print("\n".join(info) + "\n") print("\n".join(info) + "\n")

View file

@ -2,30 +2,32 @@ import os
import struct import struct
import sys import sys
from pathlib import Path
from tools import align_byte_size, get_alignment from tools import align_byte_size, get_alignment
def resource_path(relative_path: str = "") -> str: def resource_path(relative_path: str = "") -> Path:
""" Get absolute path to resource, works for dev and for cx_freeze """ """ Get absolute path to resource, works for dev and for cx_freeze """
if getattr(sys, "frozen", False): if getattr(sys, "frozen", False):
# The application is frozen # The application is frozen
base_path = os.path.dirname(sys.executable) base_path = Path(sys.executable).parent
else: else:
base_path = os.path.dirname(os.path.abspath(__file__)) base_path = Path(__file__).parent
return os.path.join(base_path, relative_path) return base_path / relative_path
def get_program_folder(folder: str = "") -> str: def get_program_folder(folder: str = "") -> Path:
""" Get path to appdata """ """ Get path to appdata """
if sys.platform == "win32": if sys.platform == "win32":
datapath = os.path.join(os.getenv("APPDATA"), folder) datapath = Path(os.getenv("APPDATA")) / folder
elif sys.platform == "darwin": elif sys.platform == "darwin":
if folder: if folder:
folder = "." + folder folder = "." + folder
datapath = os.path.join(os.path.expanduser("~"), "Library", "Application Support", folder) datapath = Path("~/Library/Application Support").expanduser() / folder
elif "linux" in sys.platform: elif "linux" in sys.platform:
if folder: if folder:
folder = "." + folder folder = "." + folder
datapath = os.path.join(os.getenv("HOME"), folder) datapath = Path.home() / folder
else: else:
raise NotImplementedError(f"{sys.platform} OS is unsupported") raise NotImplementedError(f"{sys.platform} OS is unsupported")
return datapath return datapath
@ -82,5 +84,4 @@ def read_bool(f, vSize=1):
return struct.unpack("B", f.read(vSize))[0] > 0 return struct.unpack("B", f.read(vSize))[0] > 0
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') f.write(b'\x00'*(vSize-1) + b'\x01') if val is True else f.write(b'\x00' * vSize)
else: f.write(b'\x00' * vSize)

424
kernel.py
View file

@ -4,10 +4,12 @@ import re
import sys import sys
import time import time
import functools import functools
from io import BytesIO from io import BytesIO
from pathlib import Path
import tools import tools
from fileutils import * from fileutils import read_uint16, read_uint32, write_bool, write_ubyte, write_uint16, write_uint32, write_sint32, get_alignment
from dolreader import DolFile, SectionCountFullError, UnmappedAddressError from dolreader import DolFile, SectionCountFullError, UnmappedAddressError
try: try:
@ -22,7 +24,7 @@ def timer(func):
start = time.perf_counter() start = time.perf_counter()
value = func(*args, **kwargs) value = func(*args, **kwargs)
end = time.perf_counter() end = time.perf_counter()
print(tools.color_text(f'\n :: Completed in {(end - start):0.4f} seconds!\n', defaultColor=tools.TGREENLIT)) print(tools.color_text(f"\n :: Completed in {(end - start):0.4f} seconds!\n", defaultColor=tools.TGREENLIT))
return value return value
return wrapper return wrapper
@ -34,53 +36,53 @@ class GCT(object):
self.codeList = BytesIO(f.read()) self.codeList = BytesIO(f.read())
self.rawLineCount = tools.stream_size(self.codeList) >> 3 self.rawLineCount = tools.stream_size(self.codeList) >> 3
self.lineCount = self.rawLineCount - 2 self.lineCount = self.rawLineCount - 2
self.size = tools.stream_size(self.codeList)
f.seek(0) f.seek(0)
@property
def size(self):
return len(self.codeList.getbuffer())
@staticmethod @staticmethod
def determine_codelength(codetype, info: bytes) -> int: def determine_codelength(codetype, info: bytes) -> int:
if codetype.startswith(b'\x06'): if codetype.startswith(b"\x06"):
bytelength = int.from_bytes(info, byteorder='big', signed=False) bytelength = int.from_bytes(info, byteorder="big", signed=False)
padding = get_alignment(bytelength, 8) padding = get_alignment(bytelength, 8)
return 0x8 + bytelength + padding return 0x8 + bytelength + padding
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09') elif (codetype.startswith(b"\x08") or codetype.startswith(b"\x09")
or codetype.startswith(b'\x18') or codetype.startswith(b'\x18')): or codetype.startswith(b"\x18") or codetype.startswith(b"\x18")):
return 0x16 return 0x16
elif (codetype.startswith(b'\xC0') or codetype.startswith(b'\xC2') or codetype.startswith(b'\xC4') elif (codetype.startswith(b"\xC0") or codetype.startswith(b"\xC2") or codetype.startswith(b"\xC4")
or codetype.startswith(b'\xC3') or codetype.startswith(b'\xC5') or codetype.startswith(b'\xD2') or codetype.startswith(b"\xC3") or codetype.startswith(b"\xC5") or codetype.startswith(b"\xD2")
or codetype.startswith(b'\xD4') or codetype.startswith(b'\xD3') or codetype.startswith(b'\xD5')): or codetype.startswith(b"\xD4") or codetype.startswith(b"\xD3") or codetype.startswith(b"\xD5")):
return 0x8 + (int.from_bytes(info, byteorder='big', signed=False) << 3) return 0x8 + (int.from_bytes(info, byteorder="big", signed=False) << 3)
elif (codetype.startswith(b'\xF2') or codetype.startswith(b'\xF3') elif (codetype.startswith(b"\xF2") or codetype.startswith(b"\xF3")
or codetype.startswith(b'\xF4') or codetype.startswith(b'\xF5')): or codetype.startswith(b"\xF4") or codetype.startswith(b"\xF5")):
return 0x8 + (int.from_bytes(info[:2], byteorder='big', signed=False) << 3) return 0x8 + (int.from_bytes(info[:2], byteorder="big", signed=False) << 3)
elif codetype.startswith(b'\xF6'): elif codetype.startswith(b"\xF6"):
return 0x8 + (int.from_bytes(info[:4], byteorder='big', signed=False) << 3) return 0x8 + (int.from_bytes(info[:4], byteorder="big", signed=False) << 3)
else: else:
return 0x8 return 0x8
def optimize_codelist(self, dolFile: DolFile): def optimize_codelist(self, dolFile: DolFile):
codelist = b'\x00\xD0\xC0\xDE'*2 codelist = b"\x00\xD0\xC0\xDE"*2
skipcodes = 0 skipcodes = 0
with open(os.path.join(os.path.expanduser("~"), "Desktop", "suss.gct"), "wb") as suss:
suss.write(self.codeList.getbuffer())
self.codeList.seek(8) self.codeList.seek(8)
while codetype := self.codeList.read(4): while codetype := self.codeList.read(4):
info = self.codeList.read(4) info = self.codeList.read(4)
address = 0x80000000 | (int.from_bytes(codetype, byteorder='big', signed=False) & 0x1FFFFFF) address = 0x80000000 | (int.from_bytes(codetype, byteorder="big", signed=False) & 0x1FFFFFF)
try: try:
if skipcodes <= 0: if skipcodes <= 0:
if (codetype.startswith(b'\x00') or codetype.startswith(b'\x01') if (codetype.startswith(b"\x00") or codetype.startswith(b"\x01")
or codetype.startswith(b'\x10') or codetype.startswith(b'\x11')): or codetype.startswith(b"\x10") or codetype.startswith(b"\x11")):
dolFile.seek(address) dolFile.seek(address)
counter = int.from_bytes(info[:-2], byteorder='big', signed=False) counter = int.from_bytes(info[:-2], byteorder="big", signed=False)
value = info[2:] value = info[2:]
while counter + 1 > 0: while counter + 1 > 0:
@ -88,11 +90,11 @@ class GCT(object):
counter -= 1 counter -= 1
continue continue
elif (codetype.startswith(b'\x02') or codetype.startswith(b'\x03') elif (codetype.startswith(b"\x02") or codetype.startswith(b"\x03")
or codetype.startswith(b'\x12') or codetype.startswith(b'\x13')): or codetype.startswith(b"\x12") or codetype.startswith(b"\x13")):
dolFile.seek(address) dolFile.seek(address)
counter = int.from_bytes(info[:-2], byteorder='big', signed=False) counter = int.from_bytes(info[:-2], byteorder="big", signed=False)
value = info[2:] value = info[2:]
while counter + 1 > 0: while counter + 1 > 0:
@ -100,17 +102,17 @@ class GCT(object):
counter -= 1 counter -= 1
continue continue
elif (codetype.startswith(b'\x04') or codetype.startswith(b'\x05') elif (codetype.startswith(b"\x04") or codetype.startswith(b"\x05")
or codetype.startswith(b'\x14') or codetype.startswith(b'\x15')): or codetype.startswith(b"\x14") or codetype.startswith(b"\x15")):
dolFile.seek(address) dolFile.seek(address)
dolFile.write(info) dolFile.write(info)
continue continue
elif (codetype.startswith(b'\x06') or codetype.startswith(b'\x07') elif (codetype.startswith(b"\x06") or codetype.startswith(b"\x07")
or codetype.startswith(b'\x16') or codetype.startswith(b'\x17')): or codetype.startswith(b"\x16") or codetype.startswith(b"\x17")):
dolFile.seek(address) dolFile.seek(address)
arraylength = int.from_bytes(info, byteorder='big', signed=False) arraylength = int.from_bytes(info, byteorder="big", signed=False)
padding = get_alignment(arraylength, 8) padding = get_alignment(arraylength, 8)
dolFile.write(self.codeList.read(arraylength)) dolFile.write(self.codeList.read(arraylength))
@ -118,29 +120,29 @@ class GCT(object):
self.codeList.seek(padding, 1) self.codeList.seek(padding, 1)
continue continue
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09') elif (codetype.startswith(b"\x08") or codetype.startswith(b"\x09")
or codetype.startswith(b'\x18') or codetype.startswith(b'\x19')): or codetype.startswith(b"\x18") or codetype.startswith(b"\x19")):
dolFile.seek(address) dolFile.seek(address)
value = int.from_bytes(info, byteorder='big', signed=False) value = int.from_bytes(info, byteorder="big", signed=False)
data = read_uint16(self.codeList) data = read_uint16(self.codeList)
size = data & 0x3 size = (data & 0x3000) >> 12
counter = data & 0xFFF counter = data & 0xFFF
address_increment = read_uint16(self.codeList) address_increment = read_uint16(self.codeList)
value_increment = read_uint32(self.codeList) value_increment = read_uint32(self.codeList)
while counter + 1 > 0: while counter + 1 > 0:
if size == 0: if size == 0:
write_ubyte(dolFile, value) write_ubyte(dolFile, value & 0xFF)
dolFile.seek(-1, 1) dolFile.seek(-1, 1)
elif size == 1: elif size == 1:
write_uint16(dolFile, value) write_uint16(dolFile, value & 0xFFFF)
dolFile.seek(-2, 1) dolFile.seek(-2, 1)
elif size == 2: elif size == 2:
write_uint32(dolFile, value) write_uint32(dolFile, value)
dolFile.seek(-4, 1) dolFile.seek(-4, 1)
else: else:
raise ValueError('Size type {} does not match 08 codetype specs'.format(size)) raise ValueError("Size type {} does not match 08 codetype specs".format(size))
dolFile.seek(address_increment, 1) dolFile.seek(address_increment, 1)
value += value_increment value += value_increment
@ -149,19 +151,19 @@ class GCT(object):
value -= 0x100000000 value -= 0x100000000
continue continue
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.insert_branch(int.from_bytes(info, byteorder='big', signed=False), address, lk=address&1) dolFile.insert_branch(int.from_bytes(info, byteorder="big", signed=False), address, lk=address&1)
continue continue
if codetype.hex().startswith('2') or codetype.hex().startswith('3'): if codetype.hex().startswith("2") or codetype.hex().startswith("3"):
skipcodes += 1 skipcodes += 1
elif codetype.startswith(b'\xE0'): elif codetype.startswith(b"\xE0"):
skipcodes -= 1 skipcodes -= 1
elif codetype.startswith(b'\xF0'): elif codetype.startswith(b"\xF0"):
codelist += b'\xF0\x00\x00\x00\x00\x00\x00\x00' codelist += b"\xF0\x00\x00\x00\x00\x00\x00\x00"
break break
self.codeList.seek(-8, 1) self.codeList.seek(-8, 1)
@ -172,7 +174,6 @@ class GCT(object):
codelist += self.codeList.read(GCT.determine_codelength(codetype, info)) codelist += self.codeList.read(GCT.determine_codelength(codetype, info))
self.codeList = BytesIO(codelist) self.codeList = BytesIO(codelist)
self.size = len(self.codeList.getbuffer())
class CodeHandler(object): class CodeHandler(object):
@ -183,7 +184,7 @@ class CodeHandler(object):
def __init__(self, f): def __init__(self, f):
self._rawData = BytesIO(f.read()) self._rawData = BytesIO(f.read())
'''Get codelist pointer''' """Get codelist pointer"""
self._rawData.seek(0xFA) self._rawData.seek(0xFA)
codelistUpper = self._rawData.read(2).hex() codelistUpper = self._rawData.read(2).hex()
self._rawData.seek(0xFE) self._rawData.seek(0xFE)
@ -194,12 +195,12 @@ class CodeHandler(object):
self.initAddress = 0x80001800 self.initAddress = 0x80001800
self.startAddress = 0x800018A8 self.startAddress = 0x800018A8
self.wiiVIHook = b'\x7C\xE3\x3B\x78\x38\x87\x00\x34\x38\xA7\x00\x38\x38\xC7\x00\x4C' self.wiiVIHook = b"\x7C\xE3\x3B\x78\x38\x87\x00\x34\x38\xA7\x00\x38\x38\xC7\x00\x4C"
self.gcnVIHook = b'\x7C\x03\x00\x34\x38\x83\x00\x20\x54\x85\x08\x3C\x7C\x7F\x2A\x14\xA0\x03\x00\x00\x7C\x7D\x2A\x14\x20\xA4\x00\x3F\xB0\x03\x00\x00' self.gcnVIHook = b"\x7C\x03\x00\x34\x38\x83\x00\x20\x54\x85\x08\x3C\x7C\x7F\x2A\x14\xA0\x03\x00\x00\x7C\x7D\x2A\x14\x20\xA4\x00\x3F\xB0\x03\x00\x00"
self.wiiGXDrawHook = b'\x3C\xA0\xCC\x01\x38\x00\x00\x61\x3C\x80\x45\x00\x98\x05\x80\x00' self.wiiGXDrawHook = b"\x3C\xA0\xCC\x01\x38\x00\x00\x61\x3C\x80\x45\x00\x98\x05\x80\x00"
self.gcnGXDrawHook = b'\x38\x00\x00\x61\x3C\xA0\xCC\x01\x3C\x80\x45\x00\x98\x05\x80\x00' self.gcnGXDrawHook = b"\x38\x00\x00\x61\x3C\xA0\xCC\x01\x3C\x80\x45\x00\x98\x05\x80\x00"
self.wiiPADHook = b'\x3A\xB5\x00\x01\x3A\x73\x00\x0C\x2C\x15\x00\x04\x3B\x18\x00\x0C' self.wiiPADHook = b"\x3A\xB5\x00\x01\x3A\x73\x00\x0C\x2C\x15\x00\x04\x3B\x18\x00\x0C"
self.gcnPADHook = b'\x3A\xB5\x00\x01\x2C\x15\x00\x04\x3B\x18\x00\x0C\x3B\xFF\x00\x0C' self.gcnPADHook = b"\x3A\xB5\x00\x01\x2C\x15\x00\x04\x3B\x18\x00\x0C\x3B\xFF\x00\x0C"
self.allocation = None self.allocation = None
self.hookAddress = None self.hookAddress = None
@ -215,65 +216,71 @@ class CodeHandler(object):
f.seek(0) f.seek(0)
def init_gct(self, gctFile: str, tmpdir: str=""): def init_gct(self, gctFile: Path, tmpdir: Path=None):
if '.' in gctFile: if tmpdir is not None:
if os.path.splitext(gctFile)[1].lower() == '.txt': _tmpGct = tmpdir / "gct.bin"
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp:
temp.write(bytes.fromhex('00D0C0DE'*2 + self.parse_input(gctFile) + 'F000000000000000'))
temp.seek(0)
self.geckoCodes = GCT(temp)
elif os.path.splitext(gctFile)[1].lower() == '.gct':
with open(gctFile, 'rb') as gct:
self.geckoCodes = GCT(gct)
else: else:
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp: _tmpGct = Path("gct.bin")
temp.write(b'\x00\xD0\xC0\xDE'*2)
for file in os.listdir(gctFile): if gctFile.suffix.lower() == ".txt":
if os.path.isfile(os.path.join(gctFile, file)): with _tmpGct.open("wb+") as temp:
if os.path.splitext(file)[1].lower() == '.txt': temp.write(bytes.fromhex("00D0C0DE"*2 + self.parse_input(gctFile) + "F000000000000000"))
temp.write(bytes.fromhex(self.parse_input(os.path.join(gctFile, file))))
elif os.path.splitext(file)[1].lower() == '.gct':
with open(os.path.join(gctFile, file), 'rb') as gct:
temp.write(gct.read()[8:-8])
else:
print(tools.color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=tools.TYELLOWLIT))
temp.write(b'\xF0\x00\x00\x00\x00\x00\x00\x00')
temp.seek(0) temp.seek(0)
self.geckoCodes = GCT(temp) self.geckoCodes = GCT(temp)
elif gctFile.suffix.lower() == ".gct":
with gctFile.open("rb") as gct:
self.geckoCodes = GCT(gct)
elif gctFile.suffix == "":
with _tmpGct.open("wb+") as temp:
temp.write(b"\x00\xD0\xC0\xDE"*2)
def parse_input(self, geckoText) -> str: for file in gctFile.iterdir():
with open(r'{}'.format(geckoText), 'rb') as gecko: if file.is_file():
if file.suffix.lower() == ".txt":
temp.write(bytes.fromhex(self.parse_input(file)))
elif file.suffix.lower() == ".gct":
with file.open("rb") as gct:
temp.write(gct.read()[8:-8])
else:
print(tools.color_text(f" :: HINT: {file} is not a .txt or .gct file", defaultColor=tools.TYELLOWLIT))
temp.write(b"\xF0\x00\x00\x00\x00\x00\x00\x00")
temp.seek(0)
self.geckoCodes = GCT(temp)
else:
raise NotImplementedError(f"Parsing file type `{gctFile.suffix}' as a GCT is unsupported")
def parse_input(self, geckoText: Path) -> str:
with geckoText.open("rb") as gecko:
result = chardet.detect(gecko.read()) result = chardet.detect(gecko.read())
encodeType = result['encoding'] encodeType = result["encoding"]
with open(r'{}'.format(geckoText), 'r', encoding=encodeType) as gecko: with geckoText.open("r", encoding=encodeType) as gecko:
geckoCodes = '' geckoCodes = ""
state = None state = None
for line in gecko.readlines(): for line in gecko.readlines():
if line in ('', '\n'): if line in ("", "\n"):
continue continue
if state is None: if state is None:
if line.startswith('$') or line.startswith('['): if line.startswith("$") or line.startswith("["):
state = 'Dolphin' state = "Dolphin"
else: else:
state = 'OcarinaM' state = "OcarinaM"
try: try:
if state == 'OcarinaM': if state == "OcarinaM":
if self.includeAll: if self.includeAll:
geckoLine = re.findall(r'[A-F0-9]{8}[\t\f ][A-F0-9]{8}', line, re.IGNORECASE)[0] geckoLine = re.findall(r"[A-F0-9]{8}[\t\f ][A-F0-9]{8}", line, re.IGNORECASE)[0]
else: else:
geckoLine = re.findall(r'(?:\*\s*)([A-F0-9]{8}[\t\f ][A-F0-9]{8})', line, re.IGNORECASE)[0] geckoLine = re.findall(r"(?:\*\s*)([A-F0-9]{8}[\t\f ][A-F0-9]{8})", line, re.IGNORECASE)[0]
else: else:
geckoLine = re.findall(r'(?<![$\*])[A-F0-9]{8}[\t\f ][A-F0-9]{8}', line, re.IGNORECASE)[0] geckoLine = re.findall(r"(?<![$\*])[A-F0-9]{8}[\t\f ][A-F0-9]{8}", line, re.IGNORECASE)[0]
except IndexError: except IndexError:
continue continue
geckoCodes += geckoLine.replace(' ', '').strip() geckoCodes += geckoLine.replace(" ", "").strip()
return geckoCodes return geckoCodes
@ -306,9 +313,6 @@ class CodeHandler(object):
def find_variable_data(self, variable) -> int: def find_variable_data(self, variable) -> int:
self._rawData.seek(0) self._rawData.seek(0)
if self._rawData.read(4) == variable:
return self._rawData.tell() - 4
while sample := self._rawData.read(4): while sample := self._rawData.read(4):
if sample == variable: if sample == variable:
return self._rawData.tell() - 4 return self._rawData.tell() - 4
@ -329,7 +333,7 @@ class CodeHandler(object):
write_uint32(self._rawData, ppc) write_uint32(self._rawData, ppc)
def set_variables(self, dolFile: DolFile): def set_variables(self, dolFile: DolFile):
varOffset = self.find_variable_data(b'\x00\xDE\xDE\xDE') varOffset = self.find_variable_data(b"\x00\xDE\xDE\xDE")
if varOffset is None: if varOffset is None:
raise RuntimeError(tools.color_text("Variable codehandler data not found\n", defaultColor=tools.TREDLIT)) raise RuntimeError(tools.color_text("Variable codehandler data not found\n", defaultColor=tools.TREDLIT))
@ -347,7 +351,6 @@ class KernelLoader(object):
self._gpDiscDataList = None self._gpDiscDataList = None
self._gpKeyAddrList = None self._gpKeyAddrList = None
self._cli = cli self._cli = cli
self.patchJob = None
self.initAddress = None self.initAddress = None
self.protect = False self.protect = False
self.verbosity = 0 self.verbosity = 0
@ -368,22 +371,22 @@ class KernelLoader(object):
return return
while sample := self._rawData.read(2): while sample := self._rawData.read(2):
if sample == b'GH': if sample == b"GH":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, self._gpModDataList[0]) write_uint16(self._rawData, self._gpModDataList[0])
elif sample == b'GL': elif sample == b"GL":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, baseOffset + self._gpModDataList[1]) write_uint16(self._rawData, baseOffset + self._gpModDataList[1])
elif sample == b'IH': elif sample == b"IH":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[0]) write_uint16(self._rawData, entryPoint[0])
elif sample == b'IL': elif sample == b"IL":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[1]) write_uint16(self._rawData, entryPoint[1])
elif sample == b'KH': elif sample == b"KH":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, self._gpKeyAddrList[0]) write_uint16(self._rawData, self._gpKeyAddrList[0])
elif sample == b'KL': elif sample == b"KL":
self._rawData.seek(-2, 1) self._rawData.seek(-2, 1)
write_uint16(self._rawData, baseOffset + self._gpKeyAddrList[1]) write_uint16(self._rawData, baseOffset + self._gpKeyAddrList[1])
@ -393,48 +396,42 @@ class KernelLoader(object):
self._rawData.seek(0) self._rawData.seek(0)
while sample := self._rawData.read(4): while sample := self._rawData.read(4):
if sample == b'HEAP': #Found keyword "HEAP". Goes with the resize of the heap if sample == b"HEAP": #Found keyword "HEAP". Goes with the resize of the heap
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
gpModInfoOffset = self._rawData.tell() gpModInfoOffset = self._rawData.tell()
if _lowerAddr + gpModInfoOffset > 0x7FFF: #Absolute addressing gpModUpperAddr = _upperAddr + 1 if (_lowerAddr + gpModInfoOffset) > 0x7FFF else _upperAddr #Absolute addressing
gpModUpperAddr = _upperAddr + 1
else:
gpModUpperAddr = _upperAddr
if codeHandler.allocation == None: if codeHandler.allocation == None:
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8 codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8
write_uint32(self._rawData, codeHandler.allocation) write_uint32(self._rawData, codeHandler.allocation)
elif sample == b'LSIZ': #Found keyword "LSIZ". Goes with the size of the loader elif sample == b"LSIZ": #Found keyword "LSIZ". Goes with the size of the loader
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
write_uint32(self._rawData, len(self._rawData.getbuffer())) write_uint32(self._rawData, len(self._rawData.getbuffer()))
elif sample == b'HSIZ': #Found keyword "HSIZ". Goes with the size of the codehandler elif sample == b"HSIZ": #Found keyword "HSIZ". Goes with the size of the codehandler
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
write_sint32(self._rawData, codeHandler.handlerLength) write_sint32(self._rawData, codeHandler.handlerLength)
elif sample == b'CSIZ': #Found keyword "CSIZ". Goes with the size of the codes elif sample == b"CSIZ": #Found keyword "CSIZ". Goes with the size of the codes
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
write_sint32(self._rawData, codeHandler.geckoCodes.size) write_sint32(self._rawData, codeHandler.geckoCodes.size)
elif sample == b'HOOK': #Found keyword "HOOK". Goes with the codehandler hook elif sample == b"HOOK": #Found keyword "HOOK". Goes with the codehandler hook
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
write_uint32(self._rawData, codeHandler.hookAddress) write_uint32(self._rawData, codeHandler.hookAddress)
elif sample == b'CRPT': #Found keyword "CRPT". Boolean of the encryption elif sample == b"CRPT": #Found keyword "CRPT". Boolean of the encryption
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
write_bool(self._rawData, self.encrypt, 4) write_bool(self._rawData, self.encrypt, 4)
elif sample == b'CYPT': #Found keyword "CYPT". Encryption Key elif sample == b"CYPT": #Found keyword "CYPT". Encryption Key
self._rawData.seek(-4, 1) self._rawData.seek(-4, 1)
gpKeyOffset = self._rawData.tell() gpKeyOffset = self._rawData.tell()
if _lowerAddr + gpKeyOffset > 0x7FFF: #Absolute addressing gpKeyUpperAddr = _upperAddr + 1 if (_lowerAddr + gpKeyOffset) > 0x7FFF else _upperAddr #Absolute addressing
gpKeyUpperAddr = _upperAddr + 1
else:
gpKeyUpperAddr = _upperAddr
write_uint32(self._rawData, CodeHandler.encrypt_key(_key)) write_uint32(self._rawData, CodeHandler.encrypt_key(_key))
@ -461,8 +458,7 @@ class KernelLoader(object):
try: try:
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 return True, None
@ -479,74 +475,67 @@ class KernelLoader(object):
try: try:
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 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()
protectdata = (b'\xC0\x00\x00\x00\x00\x00\x00\x17', protectdata = (b"\xC0\x00\x00\x00\x00\x00\x00\x17",
b'\x7C\x08\x02\xA6\x94\x21\xFF\x70', b"\x7C\x08\x02\xA6\x94\x21\xFF\x70",
b'\x90\x01\x00\x08\xBC\x61\x00\x0C', b"\x90\x01\x00\x08\xBC\x61\x00\x0C",
b'\x48\x00\x00\x0D\x00\xD0\xC0\xDE', b"\x48\x00\x00\x0D\x00\xD0\xC0\xDE",
b'\x00\xD0\xDE\xAD\x7F\xE8\x02\xA6', b"\x00\xD0\xDE\xAD\x7F\xE8\x02\xA6",
b'\x3B\xDF\x00\x08\x3C\x60\x80\x00', b"\x3B\xDF\x00\x08\x3C\x60\x80\x00",
b'\x38\x80\x11\x00\x38\xA0\x00\x00', b"\x38\x80\x11\x00\x38\xA0\x00\x00",
b'\x60\x63\x1E\xF8\x7C\x89\x03\xA6', b"\x60\x63\x1E\xF8\x7C\x89\x03\xA6",
b'\x38\x80\x00\x00\x7D\x03\x22\x14', b"\x38\x80\x00\x00\x7D\x03\x22\x14",
b'\x54\xE9\x06\x3E\x89\x08\x00\x08', b"\x54\xE9\x06\x3E\x89\x08\x00\x08",
b'\x7D\x3F\x48\xAE\x38\xE7\x00\x01', b"\x7D\x3F\x48\xAE\x38\xE7\x00\x01",
b'\x7C\x08\x48\x40\x41\x82\x00\x0C', b"\x7C\x08\x48\x40\x41\x82\x00\x0C",
b'\x60\xA7\x00\x00\x48\x00\x00\x04', b"\x60\xA7\x00\x00\x48\x00\x00\x04",
b'\x54\xE8\x06\x3E\x28\x08\x00\x03', b"\x54\xE8\x06\x3E\x28\x08\x00\x03",
b'\x41\x81\x00\x10\x38\x84\x00\x01', b"\x41\x81\x00\x10\x38\x84\x00\x01",
b'\x42\x00\xFF\xCC\x48\x00\x00\x2C', b"\x42\x00\xFF\xCC\x48\x00\x00\x2C",
b'\x38\xA0\x00\x08\x7C\x84\x1A\x14', b"\x38\xA0\x00\x08\x7C\x84\x1A\x14",
b'\x7C\xA9\x03\xA6\x38\x60\x00\x00', b"\x7C\xA9\x03\xA6\x38\x60\x00\x00",
b'\x38\x84\xFF\xFF\x54\x66\x07\xBE', b"\x38\x84\xFF\xFF\x54\x66\x07\xBE",
b'\x7C\xDE\x30\xAE\x38\x63\x00\x01', b"\x7C\xDE\x30\xAE\x38\x63\x00\x01",
b'\x9C\xC4\x00\x01\x42\x00\xFF\xF0', b"\x9C\xC4\x00\x01\x42\x00\xFF\xF0",
b'\xB8\x61\x00\x0C\x80\x01\x00\x08', b"\xB8\x61\x00\x0C\x80\x01\x00\x08",
b'\x38\x21\x00\x90\x7C\x08\x03\xA6', b"\x38\x21\x00\x90\x7C\x08\x03\xA6",
b'\x4E\x80\x00\x20\x00\x00\x00\x00') b"\x4E\x80\x00\x20\x00\x00\x00\x00")
codeHandler.geckoCodes.codeList.seek(-8, 2) codeHandler.geckoCodes.codeList.seek(-8, 2)
for chunk in protectdata:
codeHandler.geckoCodes.codeList.write(chunk) for line in protectdata:
codeHandler.geckoCodes.codeList.write(b'\xF0\x00\x00\x00\x00\x00\x00\x00') codeHandler.geckoCodes.codeList.write(line)
codeHandler.geckoCodes.codeList.seek(0, 2)
codeHandler.geckoCodes.size = codeHandler.geckoCodes.codeList.tell() codeHandler.geckoCodes.codeList.write(b"\xF0\x00\x00\x00\x00\x00\x00\x00")
codeHandler.geckoCodes.codeList.seek(_oldpos) codeHandler.geckoCodes.codeList.seek(_oldpos)
@timer @timer
def build(self, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump): def build(self, gctFile: Path, dolFile: DolFile, codeHandler: CodeHandler, tmpdir: Path, dump: Path):
_oldStart = dolFile.entryPoint _oldStart = dolFile.entryPoint
'''Initialize our codes''' """Initialize our codes"""
codeHandler.init_gct(gctFile, tmpdir) codeHandler.init_gct(gctFile, tmpdir)
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:
self.protect_game(codeHandler) self.protect_game(codeHandler)
if self.patchJob == 'AUTO':
if codeHandler.initAddress + codeHandler.handlerLength + codeHandler.geckoCodes.size > 0x80002FFF:
self.patchJob = 'ARENA'
else:
self.patchJob = 'LEGACY'
'''Get entrypoint (or BSS midpoint) for insert''' """Get entrypoint (or BSS midpoint) for insert"""
if self.initAddress: if self.initAddress:
try: try:
dolFile.resolve_address(self.initAddress) dolFile.resolve_address(self.initAddress)
self.error(tools.color_text(f'Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections', defaultColor=tools.TREDLIT)) 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 UnmappedAddressError:
pass pass
else: else:
self.initAddress = dolFile.seek_nearest_unmapped(dolFile.bssAddress, len(self._rawData.getbuffer()) + codeHandler.handlerLength + codeHandler.geckoCodes.size) self.initAddress = dolFile.seek_nearest_unmapped(dolFile.bssAddress, len(self._rawData.getbuffer()) + codeHandler.handlerLength + codeHandler.geckoCodes.size)
@ -555,82 +544,59 @@ class KernelLoader(object):
if codeHandler.optimizeList: if codeHandler.optimizeList:
codeHandler.geckoCodes.optimize_codelist(dolFile) codeHandler.geckoCodes.optimize_codelist(dolFile)
'''Is codelist optimized away?''' """Is codelist optimized away?"""
if codeHandler.geckoCodes.codeList.getvalue() == b'\x00\xD0\xC0\xDE\x00\xD0\xC0\xDE\xF0\x00\x00\x00\x00\x00\x00\x00': if codeHandler.geckoCodes.codeList.getvalue() == b"\x00\xD0\xC0\xDE\x00\xD0\xC0\xDE\xF0\x00\x00\x00\x00\x00\x00\x00":
with open(dump, 'wb') as final: with dump.open("wb") as final:
dolFile.save(final) dolFile.save(final)
if not self.quiet: if not self.quiet:
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
hooked = determine_codehook(dolFile, codeHandler, False)
if self.patchJob == 'LEGACY': if hooked:
legacy = True _status, _msg = self.patch_arena(codeHandler, dolFile)
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
hooked = determine_codehook(dolFile, codeHandler, True)
if hooked:
_status, _msg = self.patch_legacy(codeHandler, dolFile)
else: else:
legacy = False self.error(tools.color_text("Failed to find a hook address. Try using option --codehook to use your own address\n", defaultColor=tools.TREDLIT))
hooked = determine_codehook(dolFile, codeHandler, False)
if hooked:
_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('Allocated codespace was smaller than the given codelist\n', defaultColor=tools.TYELLOW))
with open(dump, 'wb') as final: if _status is False:
self.error(tools.color_text(_msg + "\n", defaultColor=tools.TREDLIT))
elif codeHandler.allocation < codeHandler.geckoCodes.size:
self.error(tools.color_text("Allocated codespace was smaller than the given codelist\n", defaultColor=tools.TYELLOW))
with dump.open("wb") as final:
dolFile.save(final) dolFile.save(final)
if self.quiet: if self.quiet:
return return
if codeHandler.allocation > 0x70000:
print(tools.color_text(f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codeHandler.allocation:X}', defaultColor=tools.TYELLOW))
elif codeHandler.allocation > 0x40000:
print(tools.color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=tools.TYELLOWLIT))
if self.verbosity >= 3: if self.verbosity >= 3:
dolFile.print_info() dolFile.print_info()
print('-'*64) print("-"*64)
if self.verbosity >= 2: if self.verbosity >= 2:
print('') print("")
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 {DolFile.maxTextSections} 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"]
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}',
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 {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))
elif self.verbosity >= 1: elif self.verbosity >= 1:
print('') print("")
info = [f' :: GeckoLoader set at address 0x{self.initAddress:X}', info = [f" :: GeckoLoader set at address 0x{self.initAddress:X}",
f' :: Legacy size is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}', f" :: Legacy size is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}",
f' :: Codehandler is of type "{codeHandler.type}"'] f" :: Codehandler is of type `{codeHandler.type}'"]
for bit in info: for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT)) print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
@ -647,33 +613,33 @@ def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler, hook=False) -
def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler) -> bool: def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler) -> bool:
for _, address, size, _, _ in dolFile.textSections: for section in dolFile.textSections:
dolFile.seek(address) dolFile.seek(section["address"])
sample = dolFile.read(size) sample = dolFile.read(section["size"])
if codeHandler.hookType == 'VI': if codeHandler.hookType == "VI":
result = sample.find(codeHandler.gcnVIHook) result = sample.find(codeHandler.gcnVIHook)
elif codeHandler.hookType == 'GX': elif codeHandler.hookType == "GX":
result = sample.find(codeHandler.gcnGXDrawHook) result = sample.find(codeHandler.gcnGXDrawHook)
elif codeHandler.hookType == 'PAD': elif codeHandler.hookType == "PAD":
result = sample.find(codeHandler.gcnPADHook) result = sample.find(codeHandler.gcnPADHook)
else: else:
raise NotImplementedError(tools.color_text(f'Unsupported hook type specified ({codeHandler.hookType})', defaultColor=tools.TREDLIT)) raise NotImplementedError(tools.color_text(f"Unsupported hook type specified ({codeHandler.hookType})", defaultColor=tools.TREDLIT))
if result >= 0: if result >= 0:
dolFile.seek(address + result) dolFile.seek(section["address"] + result)
else: else:
if codeHandler.hookType == 'VI': if codeHandler.hookType == "VI":
result = sample.find(codeHandler.wiiVIHook) result = sample.find(codeHandler.wiiVIHook)
elif codeHandler.hookType == 'GX': elif codeHandler.hookType == "GX":
result = sample.find(codeHandler.wiiGXDrawHook) result = sample.find(codeHandler.wiiGXDrawHook)
elif codeHandler.hookType == 'PAD': elif codeHandler.hookType == "PAD":
result = sample.find(codeHandler.wiiPADHook) result = sample.find(codeHandler.wiiPADHook)
else: else:
raise NotImplementedError(tools.color_text(f'Unsupported hook type specified ({codeHandler.hookType})', defaultColor=tools.TREDLIT)) raise NotImplementedError(tools.color_text(f"Unsupported hook type specified ({codeHandler.hookType})", defaultColor=tools.TREDLIT))
if result >= 0: if result >= 0:
dolFile.seek(address + result) dolFile.seek(section["address"] + result)
else: else:
continue continue
@ -691,7 +657,7 @@ def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int):
ppc = read_uint32(dolFile) ppc = read_uint32(dolFile)
if ((ppc >> 24) & 0xFF) > 0x3F and ((ppc >> 24) & 0xFF) < 0x48: if ((ppc >> 24) & 0xFF) > 0x3F and ((ppc >> 24) & 0xFF) < 0x48:
raise NotImplementedError(tools.color_text('Hooking the codehandler to a conditional non spr branch is unsupported', defaultColor=tools.TREDLIT)) raise NotImplementedError(tools.color_text("Hooking the codehandler to a conditional non spr branch is unsupported", defaultColor=tools.TREDLIT))
dolFile.seek(-4, 1) dolFile.seek(-4, 1)
dolFile.insert_branch(codeHandler.startAddress, address, lk=0) dolFile.insert_branch(codeHandler.startAddress, address, lk=0)

View file

@ -13,11 +13,6 @@ from dolreader import DolFile
from fileutils import resource_path from fileutils import resource_path
from kernel import CodeHandler, KernelLoader 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): class MainWindow(QtWidgets.QMainWindow):
def __init__(self, version: str): def __init__(self, version: str):
super().__init__() super().__init__()
@ -71,7 +66,7 @@ class MainWindow(QtWidgets.QMainWindow):
font.setWeight(42) font.setWeight(42)
self.setFont(font) self.setFont(font)
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(resource_path(os.path.join("bin", "icon.ico"))), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(str(resource_path(os.path.join("bin", "icon.ico")))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.setWindowIcon(icon) self.setWindowIcon(icon)
#Top level widget #Top level widget
@ -346,34 +341,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.allocLineEdit.setObjectName("allocLineEdit") self.allocLineEdit.setObjectName("allocLineEdit")
self.optionsLayout.addWidget(self.allocLineEdit, 2, 0, 1, 1) 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 #handlerType label
self.handlerTypeLabel = QtWidgets.QLabel(self.centerWidget) self.handlerTypeLabel = QtWidgets.QLabel(self.centerWidget)
self.handlerTypeLabel.setEnabled(False) self.handlerTypeLabel.setEnabled(False)
@ -387,7 +354,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.handlerTypeLabel.setTextFormat(QtCore.Qt.PlainText) self.handlerTypeLabel.setTextFormat(QtCore.Qt.PlainText)
self.handlerTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) self.handlerTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.handlerTypeLabel.setObjectName("handlerTypeLabel") self.handlerTypeLabel.setObjectName("handlerTypeLabel")
self.optionsLayout.addWidget(self.handlerTypeLabel, 1, 2, 1, 1) self.optionsLayout.addWidget(self.handlerTypeLabel, 1, 1, 1, 1)
#handlerType selection #handlerType selection
self.handlerTypeSelect = QtWidgets.QComboBox(self.centerWidget) self.handlerTypeSelect = QtWidgets.QComboBox(self.centerWidget)
@ -400,7 +367,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.handlerTypeSelect.setMaximumSize(QtCore.QSize(79, 23)) self.handlerTypeSelect.setMaximumSize(QtCore.QSize(79, 23))
self.handlerTypeSelect.setObjectName("handlerTypeSelect") self.handlerTypeSelect.setObjectName("handlerTypeSelect")
self.handlerTypeSelect.addItems(["FULL", "MINI"]) self.handlerTypeSelect.addItems(["FULL", "MINI"])
self.optionsLayout.addWidget(self.handlerTypeSelect, 2, 2, 1, 1) self.optionsLayout.addWidget(self.handlerTypeSelect, 2, 1, 1, 1)
#hookType label #hookType label
self.hookTypeLabel = QtWidgets.QLabel(self.centerWidget) self.hookTypeLabel = QtWidgets.QLabel(self.centerWidget)
@ -415,7 +382,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.hookTypeLabel.setTextFormat(QtCore.Qt.PlainText) self.hookTypeLabel.setTextFormat(QtCore.Qt.PlainText)
self.hookTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) self.hookTypeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.hookTypeLabel.setObjectName("hookTypeLabel") self.hookTypeLabel.setObjectName("hookTypeLabel")
self.optionsLayout.addWidget(self.hookTypeLabel, 1, 3, 1, 1) self.optionsLayout.addWidget(self.hookTypeLabel, 1, 2, 1, 1)
#hookType selection #hookType selection
self.hookTypeSelect = QtWidgets.QComboBox(self.centerWidget) self.hookTypeSelect = QtWidgets.QComboBox(self.centerWidget)
@ -428,7 +395,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.hookTypeSelect.setMaximumSize(QtCore.QSize(79, 23)) self.hookTypeSelect.setMaximumSize(QtCore.QSize(79, 23))
self.hookTypeSelect.setObjectName("hookTypeSelect") self.hookTypeSelect.setObjectName("hookTypeSelect")
self.hookTypeSelect.addItems(["VI", "GX", "PAD"]) self.hookTypeSelect.addItems(["VI", "GX", "PAD"])
self.optionsLayout.addWidget(self.hookTypeSelect, 2, 3, 1, 1) self.optionsLayout.addWidget(self.hookTypeSelect, 2, 2, 1, 1)
#txtCodesInclude label #txtCodesInclude label
self.txtCodesIncludeLabel = QtWidgets.QLabel(self.centerWidget) self.txtCodesIncludeLabel = QtWidgets.QLabel(self.centerWidget)
@ -443,7 +410,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.txtCodesIncludeLabel.setTextFormat(QtCore.Qt.PlainText) self.txtCodesIncludeLabel.setTextFormat(QtCore.Qt.PlainText)
self.txtCodesIncludeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter) self.txtCodesIncludeLabel.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.txtCodesIncludeLabel.setObjectName("txtCodesIncludeLabel") self.txtCodesIncludeLabel.setObjectName("txtCodesIncludeLabel")
self.optionsLayout.addWidget(self.txtCodesIncludeLabel, 3, 0, 1, 1) self.optionsLayout.addWidget(self.txtCodesIncludeLabel, 1, 3, 1, 1)
#txtCodesInclude selection #txtCodesInclude selection
self.txtCodesIncludeSelect = QtWidgets.QComboBox(self.centerWidget) self.txtCodesIncludeSelect = QtWidgets.QComboBox(self.centerWidget)
@ -456,35 +423,16 @@ class MainWindow(QtWidgets.QMainWindow):
self.txtCodesIncludeSelect.setMaximumSize(QtCore.QSize(79, 23)) self.txtCodesIncludeSelect.setMaximumSize(QtCore.QSize(79, 23))
self.txtCodesIncludeSelect.setObjectName("txtCodesIncludeSelect") self.txtCodesIncludeSelect.setObjectName("txtCodesIncludeSelect")
self.txtCodesIncludeSelect.addItems(["ACTIVE", "ALL"]) self.txtCodesIncludeSelect.addItems(["ACTIVE", "ALL"])
self.optionsLayout.addWidget(self.txtCodesIncludeSelect, 4, 0, 1, 1) self.optionsLayout.addWidget(self.txtCodesIncludeSelect, 2, 3, 1, 1)
#optimize label #horizontal separater options
self.optimizeLabel = QtWidgets.QLabel(self.centerWidget) self.horiSepOptions = QtWidgets.QFrame(self.centerWidget)
self.optimizeLabel.setEnabled(False) self.horiSepOptions.setMinimumSize(QtCore.QSize(300, 30))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.horiSepOptions.setContentsMargins(20, 0, 20, 0)
sizePolicy.setHorizontalStretch(0) self.horiSepOptions.setFrameShape(QtWidgets.QFrame.HLine)
sizePolicy.setVerticalStretch(0) self.horiSepOptions.setFrameShadow(QtWidgets.QFrame.Sunken)
sizePolicy.setHeightForWidth(self.optimizeLabel.sizePolicy().hasHeightForWidth()) self.horiSepOptions.setObjectName("horiSepOptions")
self.optimizeLabel.setSizePolicy(sizePolicy) self.optionsLayout.addWidget(self.horiSepOptions, 3, 0, 1, 4)
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 #Advanced options button
self.exOptionsButton = QtWidgets.QPushButton(self.centerWidget) self.exOptionsButton = QtWidgets.QPushButton(self.centerWidget)
@ -498,7 +446,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.exOptionsButton.setFlat(False) self.exOptionsButton.setFlat(False)
self.exOptionsButton.setDisabled(True) self.exOptionsButton.setDisabled(True)
self.exOptionsButton.setObjectName("exOptionsButton") self.exOptionsButton.setObjectName("exOptionsButton")
self.optionsLayout.addWidget(self.exOptionsButton, 4, 2, 1, 2) self.optionsLayout.addWidget(self.exOptionsButton, 4, 0, 1, 4)
#horizontal separater 1 #horizontal separater 1
self.horiSepA = QtWidgets.QFrame(self.centerWidget) self.horiSepA = QtWidgets.QFrame(self.centerWidget)
@ -718,16 +666,12 @@ class MainWindow(QtWidgets.QMainWindow):
self.optionsLabel.setEnabled(True) self.optionsLabel.setEnabled(True)
self.allocLabel.setEnabled(True) self.allocLabel.setEnabled(True)
self.allocLineEdit.setEnabled(True) self.allocLineEdit.setEnabled(True)
self.patchLabel.setEnabled(True)
self.patchTypeSelect.setEnabled(True)
self.handlerTypeLabel.setEnabled(True) self.handlerTypeLabel.setEnabled(True)
self.handlerTypeSelect.setEnabled(True) self.handlerTypeSelect.setEnabled(True)
self.hookTypeLabel.setEnabled(True) self.hookTypeLabel.setEnabled(True)
self.hookTypeSelect.setEnabled(True) self.hookTypeSelect.setEnabled(True)
self.txtCodesIncludeLabel.setEnabled(True) self.txtCodesIncludeLabel.setEnabled(True)
self.txtCodesIncludeSelect.setEnabled(True) self.txtCodesIncludeSelect.setEnabled(True)
self.optimizeLabel.setEnabled(True)
self.optimizeSelect.setEnabled(True)
self.exOptionsButton.setEnabled(True) self.exOptionsButton.setEnabled(True)
self.actionSave.setEnabled(True) self.actionSave.setEnabled(True)
self.actionSave_As.setEnabled(True) self.actionSave_As.setEnabled(True)
@ -805,11 +749,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.allocLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Allocation", None)) self.allocLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Allocation", None))
self.allocLineEdit.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "AUTO", 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.handlerTypeLabel.setText(QtWidgets.QApplication.translate("MainWindow", "Codehandler", None))
self.handlerTypeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "FULL", None)) self.handlerTypeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "FULL", None))
self.handlerTypeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "MINI", None)) self.handlerTypeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "MINI", None))
@ -823,10 +762,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.txtCodesIncludeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "ACTIVE", None)) self.txtCodesIncludeSelect.setItemText(0, QtWidgets.QApplication.translate("Dialog", "ACTIVE", None))
self.txtCodesIncludeSelect.setItemText(1, QtWidgets.QApplication.translate("Dialog", "ALL", 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.exOptionsButton.setText(QtWidgets.QApplication.translate("MainWindow", "Advanced Settings", None))
self.compileButton.setText(QtWidgets.QApplication.translate("MainWindow", "RUN", None)) self.compileButton.setText(QtWidgets.QApplication.translate("MainWindow", "RUN", None))

View file

@ -18,7 +18,7 @@ executables = [
] ]
setup(name = "GeckoLoader", setup(name = "GeckoLoader",
version = "7.0.0", version = "7.1.0",
description = "DOL Patcher for extending the codespace of Wii/GC games", description = "DOL Patcher for extending the codespace of Wii/GC games",
executables = [Executable("GeckoLoader.py", icon=os.path.join("bin", "icon.ico"))], executables = [Executable("GeckoLoader.py", icon=os.path.join("bin", "icon.ico"))],
author = "JoshuaMK", author = "JoshuaMK",