✨ Remove legacy option, refactored code
This commit is contained in:
parent
c0af5c030c
commit
cd01e736cb
7 changed files with 398 additions and 525 deletions
182
GeckoLoader.py
182
GeckoLoader.py
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
168
dolreader.py
168
dolreader.py
|
@ -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")
|
||||
|
||||
|
|
21
fileutils.py
21
fileutils.py
|
@ -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
424
kernel.py
|
@ -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)
|
97
main_ui.py
97
main_ui.py
|
@ -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))
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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",
|
||||
|
|
Reference in a new issue