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

View file

@ -17,7 +17,7 @@ class PrefWindow(QtWidgets.QDialog):
self.setFixedSize(300, 120)
self.setModal(True)
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)
#Buttonbox
@ -73,13 +73,13 @@ class SettingsWindow(QtWidgets.QDialog):
self.setObjectName("Dialog")
if sys.platform == "win32":
self.setFixedSize(300, 210)
self.setFixedSize(300, 240)
else:
self.setFixedSize(370, 210)
self.setFixedSize(370, 240)
self.setModal(True)
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)
#Buttonbox
@ -113,10 +113,17 @@ class SettingsWindow(QtWidgets.QDialog):
self.encryptCodes.setText("Encrypt codes")
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
self.codehookLabel = QtWidgets.QLabel()
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
self.codehookLineEdit = QtWidgets.QLineEdit()
@ -137,12 +144,12 @@ class SettingsWindow(QtWidgets.QDialog):
self.codehookLineEdit.setMaxLength(8)
self.codehookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
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
self.kernelHookLabel = QtWidgets.QLabel()
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
self.kernelHookLineEdit = QtWidgets.QLineEdit()
@ -163,19 +170,18 @@ class SettingsWindow(QtWidgets.QDialog):
self.kernelHookLineEdit.setMaxLength(8)
self.kernelHookLineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.kernelHookLineEdit.setObjectName("kernelHookLineEdit")
self.comboBoxLayout.addWidget(self.kernelHookLineEdit, 4, 1, 1, 1)
self.comboBoxLayout.addWidget(self.kernelHookLineEdit, 5, 1, 1, 1)
#verbosity label
self.verbosityLabel = QtWidgets.QLabel()
self.verbosityLabel.setObjectName("verbosityLabel")
self.comboBoxLayout.addWidget(self.verbosityLabel, 5, 0, 1, 1)
self.comboBoxLayout.addWidget(self.verbosityLabel, 6, 0, 1, 1)
#verbosity box
self.verbositySelect = QtWidgets.QComboBox()
self.verbositySelect.addItems(["1", "2", "3", "0"])
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.addWidget(self.buttonBox, 1, 0, 1, 1)
@ -189,6 +195,7 @@ class SettingsWindow(QtWidgets.QDialog):
def set_edit_fields(self):
self.protectCodes.setEnabled(True)
self.encryptCodes.setEnabled(True)
self.optimizeCodes.setEnabled(True)
self.codehookLineEdit.setEnabled(True)
self.kernelHookLineEdit.setEnabled(True)
self.verbositySelect.setEnabled(True)

View file

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

View file

@ -2,30 +2,32 @@ import os
import struct
import sys
from pathlib import Path
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 """
if getattr(sys, "frozen", False):
# The application is frozen
base_path = os.path.dirname(sys.executable)
base_path = Path(sys.executable).parent
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 """
if sys.platform == "win32":
datapath = os.path.join(os.getenv("APPDATA"), folder)
datapath = Path(os.getenv("APPDATA")) / folder
elif sys.platform == "darwin":
if 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:
if folder:
folder = "." + folder
datapath = os.path.join(os.getenv("HOME"), folder)
datapath = Path.home() / folder
else:
raise NotImplementedError(f"{sys.platform} OS is unsupported")
return datapath
@ -82,5 +84,4 @@ def read_bool(f, vSize=1):
return struct.unpack("B", f.read(vSize))[0] > 0
def write_bool(f, val, vSize=1):
if val is True: f.write(b'\x00'*(vSize-1) + b'\x01')
else: f.write(b'\x00' * vSize)
f.write(b'\x00'*(vSize-1) + b'\x01') if val is True else f.write(b'\x00' * vSize)

424
kernel.py
View file

@ -4,10 +4,12 @@ import re
import sys
import time
import functools
from io import BytesIO
from pathlib import Path
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
try:
@ -22,7 +24,7 @@ def timer(func):
start = time.perf_counter()
value = func(*args, **kwargs)
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 wrapper
@ -34,53 +36,53 @@ class GCT(object):
self.codeList = BytesIO(f.read())
self.rawLineCount = tools.stream_size(self.codeList) >> 3
self.lineCount = self.rawLineCount - 2
self.size = tools.stream_size(self.codeList)
f.seek(0)
@property
def size(self):
return len(self.codeList.getbuffer())
@staticmethod
def determine_codelength(codetype, info: bytes) -> int:
if codetype.startswith(b'\x06'):
bytelength = int.from_bytes(info, byteorder='big', signed=False)
if codetype.startswith(b"\x06"):
bytelength = int.from_bytes(info, byteorder="big", signed=False)
padding = get_alignment(bytelength, 8)
return 0x8 + bytelength + padding
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09')
or codetype.startswith(b'\x18') or codetype.startswith(b'\x18')):
elif (codetype.startswith(b"\x08") or codetype.startswith(b"\x09")
or codetype.startswith(b"\x18") or codetype.startswith(b"\x18")):
return 0x16
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'\xD4') or codetype.startswith(b'\xD3') or codetype.startswith(b'\xD5')):
return 0x8 + (int.from_bytes(info, byteorder='big', signed=False) << 3)
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"\xD4") or codetype.startswith(b"\xD3") or codetype.startswith(b"\xD5")):
return 0x8 + (int.from_bytes(info, byteorder="big", signed=False) << 3)
elif (codetype.startswith(b'\xF2') or codetype.startswith(b'\xF3')
or codetype.startswith(b'\xF4') or codetype.startswith(b'\xF5')):
return 0x8 + (int.from_bytes(info[:2], byteorder='big', signed=False) << 3)
elif (codetype.startswith(b"\xF2") or codetype.startswith(b"\xF3")
or codetype.startswith(b"\xF4") or codetype.startswith(b"\xF5")):
return 0x8 + (int.from_bytes(info[:2], byteorder="big", signed=False) << 3)
elif codetype.startswith(b'\xF6'):
return 0x8 + (int.from_bytes(info[:4], byteorder='big', signed=False) << 3)
elif codetype.startswith(b"\xF6"):
return 0x8 + (int.from_bytes(info[:4], byteorder="big", signed=False) << 3)
else:
return 0x8
def optimize_codelist(self, dolFile: DolFile):
codelist = b'\x00\xD0\xC0\xDE'*2
codelist = b"\x00\xD0\xC0\xDE"*2
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)
while codetype := 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:
if skipcodes <= 0:
if (codetype.startswith(b'\x00') or codetype.startswith(b'\x01')
or codetype.startswith(b'\x10') or codetype.startswith(b'\x11')):
if (codetype.startswith(b"\x00") or codetype.startswith(b"\x01")
or codetype.startswith(b"\x10") or codetype.startswith(b"\x11")):
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:]
while counter + 1 > 0:
@ -88,11 +90,11 @@ class GCT(object):
counter -= 1
continue
elif (codetype.startswith(b'\x02') or codetype.startswith(b'\x03')
or codetype.startswith(b'\x12') or codetype.startswith(b'\x13')):
elif (codetype.startswith(b"\x02") or codetype.startswith(b"\x03")
or codetype.startswith(b"\x12") or codetype.startswith(b"\x13")):
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:]
while counter + 1 > 0:
@ -100,17 +102,17 @@ class GCT(object):
counter -= 1
continue
elif (codetype.startswith(b'\x04') or codetype.startswith(b'\x05')
or codetype.startswith(b'\x14') or codetype.startswith(b'\x15')):
elif (codetype.startswith(b"\x04") or codetype.startswith(b"\x05")
or codetype.startswith(b"\x14") or codetype.startswith(b"\x15")):
dolFile.seek(address)
dolFile.write(info)
continue
elif (codetype.startswith(b'\x06') or codetype.startswith(b'\x07')
or codetype.startswith(b'\x16') or codetype.startswith(b'\x17')):
elif (codetype.startswith(b"\x06") or codetype.startswith(b"\x07")
or codetype.startswith(b"\x16") or codetype.startswith(b"\x17")):
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)
dolFile.write(self.codeList.read(arraylength))
@ -118,29 +120,29 @@ class GCT(object):
self.codeList.seek(padding, 1)
continue
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09')
or codetype.startswith(b'\x18') or codetype.startswith(b'\x19')):
elif (codetype.startswith(b"\x08") or codetype.startswith(b"\x09")
or codetype.startswith(b"\x18") or codetype.startswith(b"\x19")):
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)
size = data & 0x3
size = (data & 0x3000) >> 12
counter = data & 0xFFF
address_increment = read_uint16(self.codeList)
value_increment = read_uint32(self.codeList)
while counter + 1 > 0:
if size == 0:
write_ubyte(dolFile, value)
write_ubyte(dolFile, value & 0xFF)
dolFile.seek(-1, 1)
elif size == 1:
write_uint16(dolFile, value)
write_uint16(dolFile, value & 0xFFFF)
dolFile.seek(-2, 1)
elif size == 2:
write_uint32(dolFile, value)
dolFile.seek(-4, 1)
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)
value += value_increment
@ -149,19 +151,19 @@ class GCT(object):
value -= 0x100000000
continue
elif (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)
elif (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)
continue
if codetype.hex().startswith('2') or codetype.hex().startswith('3'):
if codetype.hex().startswith("2") or codetype.hex().startswith("3"):
skipcodes += 1
elif codetype.startswith(b'\xE0'):
elif codetype.startswith(b"\xE0"):
skipcodes -= 1
elif codetype.startswith(b'\xF0'):
codelist += b'\xF0\x00\x00\x00\x00\x00\x00\x00'
elif codetype.startswith(b"\xF0"):
codelist += b"\xF0\x00\x00\x00\x00\x00\x00\x00"
break
self.codeList.seek(-8, 1)
@ -172,7 +174,6 @@ class GCT(object):
codelist += self.codeList.read(GCT.determine_codelength(codetype, info))
self.codeList = BytesIO(codelist)
self.size = len(self.codeList.getbuffer())
class CodeHandler(object):
@ -183,7 +184,7 @@ class CodeHandler(object):
def __init__(self, f):
self._rawData = BytesIO(f.read())
'''Get codelist pointer'''
"""Get codelist pointer"""
self._rawData.seek(0xFA)
codelistUpper = self._rawData.read(2).hex()
self._rawData.seek(0xFE)
@ -194,12 +195,12 @@ class CodeHandler(object):
self.initAddress = 0x80001800
self.startAddress = 0x800018A8
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.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.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.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.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.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.allocation = None
self.hookAddress = None
@ -215,65 +216,71 @@ class CodeHandler(object):
f.seek(0)
def init_gct(self, gctFile: str, tmpdir: str=""):
if '.' in gctFile:
if os.path.splitext(gctFile)[1].lower() == '.txt':
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)
def init_gct(self, gctFile: Path, tmpdir: Path=None):
if tmpdir is not None:
_tmpGct = tmpdir / "gct.bin"
else:
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp:
temp.write(b'\x00\xD0\xC0\xDE'*2)
_tmpGct = Path("gct.bin")
for file in os.listdir(gctFile):
if os.path.isfile(os.path.join(gctFile, file)):
if os.path.splitext(file)[1].lower() == '.txt':
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')
if gctFile.suffix.lower() == ".txt":
with _tmpGct.open("wb+") as temp:
temp.write(bytes.fromhex("00D0C0DE"*2 + self.parse_input(gctFile) + "F000000000000000"))
temp.seek(0)
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:
with open(r'{}'.format(geckoText), 'rb') as gecko:
for file in gctFile.iterdir():
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())
encodeType = result['encoding']
encodeType = result["encoding"]
with open(r'{}'.format(geckoText), 'r', encoding=encodeType) as gecko:
geckoCodes = ''
with geckoText.open("r", encoding=encodeType) as gecko:
geckoCodes = ""
state = None
for line in gecko.readlines():
if line in ('', '\n'):
if line in ("", "\n"):
continue
if state is None:
if line.startswith('$') or line.startswith('['):
state = 'Dolphin'
if line.startswith("$") or line.startswith("["):
state = "Dolphin"
else:
state = 'OcarinaM'
state = "OcarinaM"
try:
if state == 'OcarinaM':
if state == "OcarinaM":
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:
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:
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:
continue
geckoCodes += geckoLine.replace(' ', '').strip()
geckoCodes += geckoLine.replace(" ", "").strip()
return geckoCodes
@ -306,9 +313,6 @@ class CodeHandler(object):
def find_variable_data(self, variable) -> int:
self._rawData.seek(0)
if self._rawData.read(4) == variable:
return self._rawData.tell() - 4
while sample := self._rawData.read(4):
if sample == variable:
return self._rawData.tell() - 4
@ -329,7 +333,7 @@ class CodeHandler(object):
write_uint32(self._rawData, ppc)
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:
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._gpKeyAddrList = None
self._cli = cli
self.patchJob = None
self.initAddress = None
self.protect = False
self.verbosity = 0
@ -368,22 +371,22 @@ class KernelLoader(object):
return
while sample := self._rawData.read(2):
if sample == b'GH':
if sample == b"GH":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, self._gpModDataList[0])
elif sample == b'GL':
elif sample == b"GL":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, baseOffset + self._gpModDataList[1])
elif sample == b'IH':
elif sample == b"IH":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[0])
elif sample == b'IL':
elif sample == b"IL":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, entryPoint[1])
elif sample == b'KH':
elif sample == b"KH":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, self._gpKeyAddrList[0])
elif sample == b'KL':
elif sample == b"KL":
self._rawData.seek(-2, 1)
write_uint16(self._rawData, baseOffset + self._gpKeyAddrList[1])
@ -393,48 +396,42 @@ class KernelLoader(object):
self._rawData.seek(0)
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)
gpModInfoOffset = self._rawData.tell()
if _lowerAddr + gpModInfoOffset > 0x7FFF: #Absolute addressing
gpModUpperAddr = _upperAddr + 1
else:
gpModUpperAddr = _upperAddr
gpModUpperAddr = _upperAddr + 1 if (_lowerAddr + gpModInfoOffset) > 0x7FFF else _upperAddr #Absolute addressing
if codeHandler.allocation == None:
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8
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)
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)
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)
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)
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)
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)
gpKeyOffset = self._rawData.tell()
if _lowerAddr + gpKeyOffset > 0x7FFF: #Absolute addressing
gpKeyUpperAddr = _upperAddr + 1
else:
gpKeyUpperAddr = _upperAddr
gpKeyUpperAddr = _upperAddr + 1 if (_lowerAddr + gpKeyOffset) > 0x7FFF else _upperAddr #Absolute addressing
write_uint32(self._rawData, CodeHandler.encrypt_key(_key))
@ -461,8 +458,7 @@ class KernelLoader(object):
try:
dolFile.append_data_sections([(_kernelData, self.initAddress)])
except SectionCountFullError:
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT))
return False, 'There are no unused sections left for GeckoLoader to use!'
self.error(tools.color_text("There are no unused sections left for GeckoLoader to use!\n", defaultColor=tools.TREDLIT))
dolFile.entryPoint = self.initAddress
return True, None
@ -479,74 +475,67 @@ class KernelLoader(object):
try:
dolFile.append_data_sections([(_handlerData, codeHandler.initAddress)])
except SectionCountFullError:
self.error(tools.color_text('There are no unused sections left for GeckoLoader to use!\n', defaultColor=tools.TREDLIT))
return False, 'There are no unused sections left for GeckoLoader to use!'
self.error(tools.color_text("There are no unused sections left for GeckoLoader to use!\n", defaultColor=tools.TREDLIT))
return True, None
def protect_game(self, codeHandler: CodeHandler):
_oldpos = codeHandler.geckoCodes.codeList.tell()
protectdata = (b'\xC0\x00\x00\x00\x00\x00\x00\x17',
b'\x7C\x08\x02\xA6\x94\x21\xFF\x70',
b'\x90\x01\x00\x08\xBC\x61\x00\x0C',
b'\x48\x00\x00\x0D\x00\xD0\xC0\xDE',
b'\x00\xD0\xDE\xAD\x7F\xE8\x02\xA6',
b'\x3B\xDF\x00\x08\x3C\x60\x80\x00',
b'\x38\x80\x11\x00\x38\xA0\x00\x00',
b'\x60\x63\x1E\xF8\x7C\x89\x03\xA6',
b'\x38\x80\x00\x00\x7D\x03\x22\x14',
b'\x54\xE9\x06\x3E\x89\x08\x00\x08',
b'\x7D\x3F\x48\xAE\x38\xE7\x00\x01',
b'\x7C\x08\x48\x40\x41\x82\x00\x0C',
b'\x60\xA7\x00\x00\x48\x00\x00\x04',
b'\x54\xE8\x06\x3E\x28\x08\x00\x03',
b'\x41\x81\x00\x10\x38\x84\x00\x01',
b'\x42\x00\xFF\xCC\x48\x00\x00\x2C',
b'\x38\xA0\x00\x08\x7C\x84\x1A\x14',
b'\x7C\xA9\x03\xA6\x38\x60\x00\x00',
b'\x38\x84\xFF\xFF\x54\x66\x07\xBE',
b'\x7C\xDE\x30\xAE\x38\x63\x00\x01',
b'\x9C\xC4\x00\x01\x42\x00\xFF\xF0',
b'\xB8\x61\x00\x0C\x80\x01\x00\x08',
b'\x38\x21\x00\x90\x7C\x08\x03\xA6',
b'\x4E\x80\x00\x20\x00\x00\x00\x00')
protectdata = (b"\xC0\x00\x00\x00\x00\x00\x00\x17",
b"\x7C\x08\x02\xA6\x94\x21\xFF\x70",
b"\x90\x01\x00\x08\xBC\x61\x00\x0C",
b"\x48\x00\x00\x0D\x00\xD0\xC0\xDE",
b"\x00\xD0\xDE\xAD\x7F\xE8\x02\xA6",
b"\x3B\xDF\x00\x08\x3C\x60\x80\x00",
b"\x38\x80\x11\x00\x38\xA0\x00\x00",
b"\x60\x63\x1E\xF8\x7C\x89\x03\xA6",
b"\x38\x80\x00\x00\x7D\x03\x22\x14",
b"\x54\xE9\x06\x3E\x89\x08\x00\x08",
b"\x7D\x3F\x48\xAE\x38\xE7\x00\x01",
b"\x7C\x08\x48\x40\x41\x82\x00\x0C",
b"\x60\xA7\x00\x00\x48\x00\x00\x04",
b"\x54\xE8\x06\x3E\x28\x08\x00\x03",
b"\x41\x81\x00\x10\x38\x84\x00\x01",
b"\x42\x00\xFF\xCC\x48\x00\x00\x2C",
b"\x38\xA0\x00\x08\x7C\x84\x1A\x14",
b"\x7C\xA9\x03\xA6\x38\x60\x00\x00",
b"\x38\x84\xFF\xFF\x54\x66\x07\xBE",
b"\x7C\xDE\x30\xAE\x38\x63\x00\x01",
b"\x9C\xC4\x00\x01\x42\x00\xFF\xF0",
b"\xB8\x61\x00\x0C\x80\x01\x00\x08",
b"\x38\x21\x00\x90\x7C\x08\x03\xA6",
b"\x4E\x80\x00\x20\x00\x00\x00\x00")
codeHandler.geckoCodes.codeList.seek(-8, 2)
for chunk in protectdata:
codeHandler.geckoCodes.codeList.write(chunk)
codeHandler.geckoCodes.codeList.write(b'\xF0\x00\x00\x00\x00\x00\x00\x00')
codeHandler.geckoCodes.codeList.seek(0, 2)
codeHandler.geckoCodes.size = codeHandler.geckoCodes.codeList.tell()
for line in protectdata:
codeHandler.geckoCodes.codeList.write(line)
codeHandler.geckoCodes.codeList.write(b"\xF0\x00\x00\x00\x00\x00\x00\x00")
codeHandler.geckoCodes.codeList.seek(_oldpos)
@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
'''Initialize our codes'''
"""Initialize our codes"""
codeHandler.init_gct(gctFile, tmpdir)
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)
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:
try:
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))
except RuntimeError:
self.error(tools.color_text(f"Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections", defaultColor=tools.TREDLIT))
except UnmappedAddressError:
pass
else:
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:
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':
with open(dump, 'wb') as final:
if codeHandler.geckoCodes.codeList.getvalue() == b"\x00\xD0\xC0\xDE\x00\xD0\xC0\xDE\xF0\x00\x00\x00\x00\x00\x00\x00":
with dump.open("wb") as final:
dolFile.save(final)
if not self.quiet:
if self.verbosity >= 3:
dolFile.print_info()
print('-'*64)
print("-"*64)
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
if self.patchJob == 'LEGACY':
legacy = True
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
hooked = determine_codehook(dolFile, codeHandler, True)
if hooked:
_status, _msg = self.patch_legacy(codeHandler, dolFile)
hooked = determine_codehook(dolFile, codeHandler, False)
if hooked:
_status, _msg = self.patch_arena(codeHandler, dolFile)
else:
legacy = False
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))
self.error(tools.color_text("Failed to find a hook address. Try using option --codehook to use your own address\n", defaultColor=tools.TREDLIT))
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)
if self.quiet:
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:
dolFile.print_info()
print('-'*64)
print("-"*64)
if self.verbosity >= 2:
print('')
if legacy is False:
info = [f' :: Start of game modified to address 0x{self.initAddress:X}',
f' :: Game function "__start()" located at address 0x{_oldStart:X}',
f' :: 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']
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']
print("")
info = [f" :: Start of game modified to address 0x{self.initAddress:X}",
f" :: Game function `__start()' located at address 0x{_oldStart:X}",
f" :: Allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}",
f" :: Codehandler hooked at 0x{codeHandler.hookAddress:X}",
f" :: Codehandler is of type `{codeHandler.type}'",
f" :: Of the {DolFile.maxTextSections} text sections in this DOL file, {len(dolFile.textSections)} are now being used",
f" :: Of the {DolFile.maxDataSections} text sections in this DOL file, {len(dolFile.dataSections)} are now being used"]
for bit in info:
print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
elif self.verbosity >= 1:
print('')
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' :: Codehandler is of type "{codeHandler.type}"']
print("")
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" :: Codehandler is of type `{codeHandler.type}'"]
for bit in info:
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:
for _, address, size, _, _ in dolFile.textSections:
dolFile.seek(address)
sample = dolFile.read(size)
for section in dolFile.textSections:
dolFile.seek(section["address"])
sample = dolFile.read(section["size"])
if codeHandler.hookType == 'VI':
if codeHandler.hookType == "VI":
result = sample.find(codeHandler.gcnVIHook)
elif codeHandler.hookType == 'GX':
elif codeHandler.hookType == "GX":
result = sample.find(codeHandler.gcnGXDrawHook)
elif codeHandler.hookType == 'PAD':
elif codeHandler.hookType == "PAD":
result = sample.find(codeHandler.gcnPADHook)
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:
dolFile.seek(address + result)
dolFile.seek(section["address"] + result)
else:
if codeHandler.hookType == 'VI':
if codeHandler.hookType == "VI":
result = sample.find(codeHandler.wiiVIHook)
elif codeHandler.hookType == 'GX':
elif codeHandler.hookType == "GX":
result = sample.find(codeHandler.wiiGXDrawHook)
elif codeHandler.hookType == 'PAD':
elif codeHandler.hookType == "PAD":
result = sample.find(codeHandler.wiiPADHook)
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:
dolFile.seek(address + result)
dolFile.seek(section["address"] + result)
else:
continue
@ -691,7 +657,7 @@ def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int):
ppc = read_uint32(dolFile)
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.insert_branch(codeHandler.startAddress, address, lk=0)

View file

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

View file

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