Added a simple update finder, and further cleaned code
This commit is contained in:
parent
927835e760
commit
2a7ac8a3d9
6 changed files with 480 additions and 343 deletions
221
GeckoLoader.py
221
GeckoLoader.py
|
@ -1,25 +1,54 @@
|
|||
#Written by JoshuaMK 2020
|
||||
#Start.dol EclipseCodes -m ARENA --codehook 802A80D0 -o -vv
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import random
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
sys.path.extend([os.path.join(os.path.dirname(__file__), 'imports')])
|
||||
|
||||
from kernel import *
|
||||
from access import *
|
||||
from versioncheck import Updater
|
||||
|
||||
_VERSION_ = "v5.0.0"
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
colorama.init()
|
||||
TRESET = Style.RESET_ALL
|
||||
TGREEN = Fore.GREEN
|
||||
TGREENLIT = Style.BRIGHT + Fore.GREEN
|
||||
TYELLOW = Fore.YELLOW
|
||||
TYELLOWLIT = Style.BRIGHT + Fore.YELLOW
|
||||
TRED = Fore.RED
|
||||
TREDLIT = Style.BRIGHT + Fore.RED
|
||||
|
||||
def determine_codehook(dolFile: DolFile, codehandler: CodeHandler):
|
||||
if codehandler.hookAddress == None:
|
||||
assert_code_hook(dolFile, codehandler, GCNVIHOOK, WIIVIHOOK)
|
||||
except ImportError:
|
||||
TRESET = ''
|
||||
TGREEN = ''
|
||||
TGREENLIT = ''
|
||||
TYELLOW = ''
|
||||
TYELLOWLIT = ''
|
||||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
__version__ = 'v5.1.0'
|
||||
|
||||
def resource_path(relative_path: str):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
def determine_codehook(dolFile: DolFile, codeHandler: CodeHandler):
|
||||
if codeHandler.hookAddress == None:
|
||||
assert_code_hook(dolFile, codeHandler, GCNVIHOOK, WIIVIHOOK)
|
||||
else:
|
||||
insert_code_hook(dolFile, codehandler, codehandler.hookAddress)
|
||||
insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
|
||||
|
||||
def assert_code_hook(dolFile: DolFile, codehandler: CodeHandler, gcnhook: bytes, wiihook: bytes):
|
||||
def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, gcnhook: bytes, wiihook: bytes):
|
||||
for offset, address, size in dolFile.textSections:
|
||||
dolFile.seek(address, 0)
|
||||
sample = dolFile.read(size)
|
||||
|
@ -36,26 +65,26 @@ def assert_code_hook(dolFile: DolFile, codehandler: CodeHandler, gcnhook: bytes,
|
|||
else:
|
||||
continue
|
||||
|
||||
sample = dolFile.read(4)
|
||||
while sample != b'\x4E\x80\x00\x20':
|
||||
sample = dolFile.read(4)
|
||||
sample = read_uint32(dolFile)
|
||||
while sample != 0x4E800020:
|
||||
sample = read_uint32(dolFile)
|
||||
|
||||
dolFile.seek(-4, 1)
|
||||
codehandler.hookAddress = dolFile.tell()
|
||||
codeHandler.hookAddress = dolFile.tell()
|
||||
|
||||
insert_code_hook(dolFile, codehandler, codehandler.hookAddress)
|
||||
insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
|
||||
return
|
||||
|
||||
parser.error('Failed to find a hook address. Try using option --codehook to use your own address')
|
||||
parser.error(color_text('Failed to find a hook address. Try using option --codehook to use your own address\n', defaultColor=TREDLIT))
|
||||
|
||||
def insert_code_hook(dolFile: DolFile, codehandler: CodeHandler, address: int):
|
||||
def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int):
|
||||
dolFile.seek(address)
|
||||
|
||||
if dolFile.read(4) != b'\x4E\x80\x00\x20':
|
||||
parser.error("Codehandler hook given is not a blr")
|
||||
if read_uint32(dolFile) != 0x4E800020:
|
||||
parser.error(color_text("Codehandler hook given is not a blr\n", defaultColor=TREDLIT))
|
||||
|
||||
dolFile.seek(-4, 1)
|
||||
dolFile.insert_branch(codehandler.startAddress, address, lk=0)
|
||||
dolFile.insert_branch(codeHandler.startAddress, address, lk=0)
|
||||
|
||||
def sort_file_args(fileA, fileB):
|
||||
if os.path.splitext(fileA)[1].lower() == '.dol':
|
||||
|
@ -65,15 +94,15 @@ def sort_file_args(fileA, fileB):
|
|||
dolFile = fileB
|
||||
gctFile = fileA
|
||||
else:
|
||||
parser.error('No dol file was passed\n')
|
||||
parser.error(color_text('No dol file was passed\n', defaultColor=TREDLIT))
|
||||
return dolFile, gctFile
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='GeckoLoader ' + _VERSION_,
|
||||
description='Process files and allocations for GeckoLoader',
|
||||
allow_abbrev=False)
|
||||
parser = CommandLineParser(prog='GeckoLoader ' + __version__,
|
||||
description='Process files and allocations for GeckoLoader',
|
||||
allow_abbrev=False)
|
||||
|
||||
parser.add_argument('dolFile', help='DOL file')
|
||||
parser.add_argument('dolfile', help='DOL file')
|
||||
parser.add_argument('codelist', help='Folder or Gecko GCT|TXT file')
|
||||
parser.add_argument('-a', '--alloc',
|
||||
help='Define the size of the code allocation in hex, only applies when using the ARENA space',
|
||||
|
@ -95,17 +124,17 @@ if __name__ == "__main__":
|
|||
default='active',
|
||||
metavar='TYPE')
|
||||
parser.add_argument('--handler',
|
||||
help='''Which codehandler gets used. "MINI" uses a smaller codehandler
|
||||
help='''Which codeHandler gets used. "MINI" uses a smaller codeHandler
|
||||
which only supports (0x, 2x, Cx, and E0 types) and supports up to
|
||||
600 lines of gecko codes when using the legacy codespace.
|
||||
"FULL" is the standard codehandler, supporting up to 350 lines of code
|
||||
"FULL" is the standard codeHandler, supporting up to 350 lines of code
|
||||
in the legacy codespace.
|
||||
"MINI" should only be considered if using the legacy codespace''',
|
||||
default='FULL',
|
||||
choices=['MINI', 'FULL'],
|
||||
metavar='TYPE')
|
||||
parser.add_argument('--codehook',
|
||||
help='''Choose where the codehandler hooks to, needs to exist at a blr instruction''',
|
||||
help='''Choose where the codeHandler hooks to, needs to exist at a blr instruction''',
|
||||
metavar='ADDRESS')
|
||||
parser.add_argument('-q', '--quiet',
|
||||
help='Print nothing to the console',
|
||||
|
@ -119,36 +148,76 @@ if __name__ == "__main__":
|
|||
ram writes into the dol file, and removing them from the codelist''',
|
||||
action='store_true')
|
||||
parser.add_argument('-p', '--protect',
|
||||
help='''Targets and nullifies the standard codehandler provided by loaders and Dolphin Emulator,
|
||||
help='''Targets and nullifies the standard codeHandler provided by loaders and Dolphin Emulator,
|
||||
only applies when the ARENA is used (Can be forced using option (-m|--movecodes))''',
|
||||
action='store_true')
|
||||
parser.add_argument('--dest',
|
||||
help='Target path to put the modified DOL, can be a folder or file',
|
||||
metavar='PATH')
|
||||
parser.add_argument('--check-update',
|
||||
help='''Checks to see if a new update exists on the GitHub Repository releases page,
|
||||
this option overrides all other commands.''',
|
||||
action='store_true')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
version = _VERSION_.rjust(9, ' ')
|
||||
version = __version__.rjust(9, ' ')
|
||||
|
||||
if os.path.normpath(os.path.join(os.path.expanduser('~'), "AppData", "Roaming")) in os.path.dirname(__file__):
|
||||
helpMessage = 'Try the command: GeckoLoader -h'.center(64, ' ')
|
||||
else:
|
||||
if os.path.splitext(__file__)[1].lower() == ".py":
|
||||
helpMessage = 'Try the command: python GeckoLoader.py -h'.center(64, ' ')
|
||||
else:
|
||||
helpMessage = 'Try the command: .\GeckLoader.exe -h'.center(64, ' ')
|
||||
|
||||
logo = [' ',
|
||||
' ╔═════════════════════════════════════════════════╗ ',
|
||||
' ║ ║ ',
|
||||
' ║ ┌───┬───┬───┬┐┌─┬───┬┐ ┌───┬───┬───┬───┬───┐ ║ ',
|
||||
' ║ │┌─┐│┌──┤┌─┐│││┌┤┌─┐││ │┌─┐│┌─┐├┐┌┐│┌──┤┌─┐│ ║ ',
|
||||
' ║ ││ └┤└──┤│ └┤└┘┘││ │││ ││ │││ ││││││└──┤└─┘│ ║ ',
|
||||
' ║ ││┌─┤┌──┤│ ┌┤┌┐│││ │││ ┌┤│ ││└─┘│││││┌──┤┌┐┌┘ ║ ',
|
||||
' ║ │└┴─│└──┤└─┘│││└┤└─┘│└─┘│└─┘│┌─┐├┘└┘│└──┤││└┐ ║ ',
|
||||
' ║ └───┴───┴───┴┘└─┴───┴───┴───┴┘ └┴───┴───┴┘└─┘ ║ ',
|
||||
' ║ ║ ',
|
||||
' ║ ┌┬───┬───┬┐ ┌┬┐ ┌┬───┬─┐┌─┬┐┌─┐ ║ ',
|
||||
' ║ ││┌─┐│┌─┐││ │││ ││┌─┐│ └┘ │││┌┘ ║ ',
|
||||
' ║ │││ ││└──┤└─┘││ │││ ││┌┐┌┐│└┘┘ ║ ',
|
||||
' ║ ┌──┐┌┐│││ │├──┐│┌─┐││ ││└─┘││││││┌┐│ ┌──┐ ║ ',
|
||||
' ║ └──┘│└┘│└─┘│└─┘││ ││└─┘│┌─┐││││││││└┐└──┘ ║ ',
|
||||
' ║ └──┴───┴───┴┘ └┴───┴┘ └┴┘└┘└┴┘└─┘ ║ ',
|
||||
f' ║ {version} ║ ',
|
||||
' ╚═════════════════════════════════════════════════╝ ',
|
||||
' ╔═══════════════════════════════════════════════════════════╗ ',
|
||||
' ║ ║ ',
|
||||
' ║ ┌───┐┌───┐┌───┐┌┐┌─┐┌───┐┌┐ ┌───┐┌───┐┌───┐┌───┐┌───┐ ║ ',
|
||||
' ║ │┌─┐││┌──┘│┌─┐││││┌┘│┌─┐│││ │┌─┐││┌─┐│└┐┌┐││┌──┘│┌─┐│ ║ ',
|
||||
' ║ ││ └┘│└──┐││ └┘│└┘┘ ││ ││││ ││ ││││ ││ │││││└──┐│└─┘│ ║ ',
|
||||
' ║ ││┌─┐│┌──┘││ ┌┐│┌┐│ ││ ││││ ┌┐││ │││└─┘│ │││││┌──┘│┌┐┌┘ ║ ',
|
||||
' ║ │└┴─││└──┐│└─┘││││└┐│└─┘││└─┘││└─┘││┌─┐│┌┘└┘││└──┐│││└┐ ║ ',
|
||||
' ║ └───┘└───┘└───┘└┘└─┘└───┘└───┘└───┘└┘ └┘└───┘└───┘└┘└─┘ ║ ',
|
||||
' ║ ║ ',
|
||||
' ║ ┌┐┌───┐┌───┐┌┐ ┌┐┌┐ ┌┐┌───┐┌─┐┌─┐┌┐┌─┐ ║ ',
|
||||
' ║ │││┌─┐││┌─┐│││ ││││ │││┌─┐││ └┘ ││││┌┘ ║ ',
|
||||
' ║ ││││ │││└──┐│└─┘│││ ││││ │││┌┐┌┐││└┘┘ ║ ',
|
||||
' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ',
|
||||
' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ',
|
||||
' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ',
|
||||
f' ║ {version} ║ ',
|
||||
' ╚═══════════════════════════════════════════════════════════╝ ',
|
||||
' ',
|
||||
' GeckoLoader is a cli tool for allowing extended ',
|
||||
' gecko code space in all Wii and GC games. ',
|
||||
' ',
|
||||
f'{helpMessage}',
|
||||
' ']
|
||||
for line in logo:
|
||||
print(line)
|
||||
print(color_text(line, [('║', TREDLIT), ('╔╚╝╗═', TRED)], TGREENLIT))
|
||||
sys.exit(0)
|
||||
elif '--check-update' in sys.argv:
|
||||
repoChecker = Updater('JoshuaMKW', 'GeckoLoader')
|
||||
|
||||
tag, status = repoChecker.get_newest_version()
|
||||
|
||||
print('')
|
||||
|
||||
if status is False:
|
||||
parser.error(color_text(tag + '\n', defaultColor=TREDLIT), print_usage=False)
|
||||
|
||||
if LooseVersion(tag) > LooseVersion(__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 "{__version__}", Most recent version is "{tag}"', defaultColor=TYELLOWLIT))
|
||||
elif LooseVersion(tag) < LooseVersion(__version__):
|
||||
print(color_text(' :: No update available', defaultColor=TGREENLIT))
|
||||
print(color_text(f' :: Current version is "{__version__}(dev)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
|
||||
else:
|
||||
print(color_text(' :: No update available', defaultColor=TGREENLIT))
|
||||
print(color_text(f' :: Current version is "{__version__}(release)", Most recent version is "{tag}(release)"', defaultColor=TGREENLIT))
|
||||
|
||||
print('')
|
||||
sys.exit(0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
@ -157,84 +226,78 @@ if __name__ == "__main__":
|
|||
try:
|
||||
_allocation = int(args.alloc, 16)
|
||||
except ValueError:
|
||||
parser.error('The allocation was invalid\n')
|
||||
parser.error(color_text('The allocation was invalid\n', defaultColor=TREDLIT))
|
||||
else:
|
||||
_allocation = None
|
||||
|
||||
if args.codehook:
|
||||
if 0x80000000 > int(args.codehook, 16) >= 0x81800000:
|
||||
parser.error('The codehandler hook address was beyond bounds\n')
|
||||
parser.error(color_text('The codeHandler hook address was beyond bounds\n', defaultColor=TREDLIT))
|
||||
else:
|
||||
try:
|
||||
_codehook = int(args.codehook, 16)
|
||||
except ValueError:
|
||||
parser.error('The codehandler hook address was invalid\n')
|
||||
parser.error(color_text('The codeHandler hook address was invalid\n', defaultColor=TREDLIT))
|
||||
else:
|
||||
_codehook = None
|
||||
|
||||
if args.handler:
|
||||
if args.handler == 'MINI':
|
||||
codehandlerFile = 'codehandler-mini.bin'
|
||||
codeHandlerFile = 'codehandler-mini.bin'
|
||||
else:
|
||||
codehandlerFile = 'codehandler.bin'
|
||||
codeHandlerFile = 'codehandler.bin'
|
||||
else:
|
||||
codehandlerFile = 'codehandler.bin'
|
||||
|
||||
#dolFile, gctFile = sort_file_args(args.fileA, args.fileB)
|
||||
codeHandlerFile = 'codehandler.bin'
|
||||
|
||||
try:
|
||||
if not os.path.isdir('BUILD'):
|
||||
os.mkdir('BUILD')
|
||||
|
||||
if not os.path.isfile(args.dolFile):
|
||||
parser.error('File "' + dolFile + '" does not exist')
|
||||
if not os.path.isfile(args.dolfile):
|
||||
parser.error(color_text(f'File "{args.dolfile}" does not exist\n', defaultColor=TREDLIT))
|
||||
|
||||
if not os.path.exists(args.codelist):
|
||||
parser.error('File/folder "' + gctFile + '" does not exist')
|
||||
parser.error(color_text(f'File/folder "{args.codelist}" does not exist\n', defaultColor=TREDLIT))
|
||||
|
||||
tmpdir = ''.join(random.choice('1234567890-_abcdefghijklomnpqrstuvwxyz') for i in range(6)) + '-GeckoLoader'
|
||||
|
||||
if not os.path.isdir(tmpdir):
|
||||
os.mkdir(tmpdir)
|
||||
|
||||
with open(resource_path(os.path.join('bin', os.path.normpath(codehandlerFile))), 'rb') as handler:
|
||||
codehandler = CodeHandler(handler)
|
||||
codehandler.allocation = _allocation
|
||||
codehandler.hookAddress = _codehook
|
||||
codehandler.includeAll = args.txtcodes
|
||||
with open(resource_path(os.path.join('bin', os.path.normpath(codeHandlerFile))), 'rb') as handler:
|
||||
codeHandler = CodeHandler(handler)
|
||||
codeHandler.allocation = _allocation
|
||||
codeHandler.hookAddress = _codehook
|
||||
codeHandler.includeAll = args.txtcodes
|
||||
|
||||
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as kernelfile:
|
||||
geckoKernel = KernelLoader(kernelfile)
|
||||
|
||||
if (args.init is not None):
|
||||
if args.init is not None:
|
||||
geckoKernel.initAddress = args.init.lstrip("0x").upper()
|
||||
|
||||
geckoKernel.codeLocation = args.movecodes
|
||||
geckoKernel.verbosity = args.verbose
|
||||
geckoKernel.quiet = args.quiet
|
||||
|
||||
with open(os.path.normpath(args.dolFile), 'rb') as dol:
|
||||
with open(os.path.normpath(args.dolfile), 'rb') as dol:
|
||||
dolFile = DolFile(dol)
|
||||
|
||||
codehandler.optimizeList = args.optimize
|
||||
codeHandler.optimizeList = args.optimize
|
||||
geckoKernel.protect = args.protect
|
||||
|
||||
if args.dest:
|
||||
if os.path.splitext(args.dest)[1] == "":
|
||||
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/'), os.path.basename(args.dolFile)))
|
||||
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/'), os.path.basename(args.dolfile)))
|
||||
else:
|
||||
dest = os.path.normpath(os.path.join(os.getcwd(), args.dest.lstrip('\\').lstrip('/')))
|
||||
else:
|
||||
dest = os.path.normpath(os.path.join(os.getcwd(), "BUILD", os.path.basename(args.dolFile)))
|
||||
dest = os.path.normpath(os.path.join(os.getcwd(), "BUILD", os.path.basename(args.dolfile)))
|
||||
|
||||
if not os.path.exists(dest) and os.path.dirname(dest) not in ('', '/'):
|
||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||
|
||||
geckoKernel.build(args.codelist, dolFile, codehandler, tmpdir, dest)
|
||||
|
||||
if not os.path.exists(os.path.abspath(tmpdir)):
|
||||
os.mkdir(tmpdir)
|
||||
|
||||
geckoKernel.build(parser, args.codelist, dolFile, codeHandler, tmpdir, dest)
|
||||
|
||||
shutil.rmtree(tmpdir)
|
||||
sys.exit(0)
|
||||
|
||||
except FileNotFoundError as err:
|
||||
parser.error(err)
|
||||
sys.exit(1)
|
||||
except FileNotFoundError as e:
|
||||
parser.error(color_text(e + '\n', defaultColor=TREDLIT))
|
||||
|
|
68
access.py
68
access.py
|
@ -1,68 +0,0 @@
|
|||
import struct
|
||||
import sys
|
||||
import os
|
||||
|
||||
def read_sbyte(f):
|
||||
return struct.unpack("b", f.read(1))[0]
|
||||
|
||||
def write_sbyte(f):
|
||||
struct.unpack("b", f.read(1))
|
||||
|
||||
def read_sint16(f):
|
||||
return struct.unpack(">h", f.read(4))[0]
|
||||
|
||||
def write_sint16(f, val):
|
||||
f.write(struct.pack(">h", val))
|
||||
|
||||
def read_sint32(f):
|
||||
return struct.unpack(">i", f.read(4))[0]
|
||||
|
||||
def write_sint32(f, val):
|
||||
f.write(struct.pack(">i", val))
|
||||
|
||||
def read_float(f):
|
||||
return struct.unpack(">f", f.read(4))[0]
|
||||
|
||||
def write_float(f, val):
|
||||
f.write(struct.pack(">f", val))
|
||||
|
||||
def read_double(f):
|
||||
return struct.unpack(">d", f.read(4))[0]
|
||||
|
||||
def write_double(f, val):
|
||||
f.write(struct.pack(">d", val))
|
||||
|
||||
def read_ubyte(f):
|
||||
return struct.unpack("B", f.read(1))[0]
|
||||
|
||||
def write_ubyte(f):
|
||||
struct.unpack("B", f.read(1))
|
||||
|
||||
def read_uint16(f):
|
||||
return struct.unpack(">H", f.read(4))[0]
|
||||
|
||||
def write_uint16(f, val):
|
||||
f.write(struct.pack(">H", val))
|
||||
|
||||
def read_uint32(f):
|
||||
return struct.unpack(">I", f.read(4))[0]
|
||||
|
||||
def write_uint32(f, val):
|
||||
f.write(struct.pack(">I", val))
|
||||
|
||||
def read_float(f):
|
||||
return struct.unpack(">f", f.read(4))[0]
|
||||
|
||||
def write_float(f, val):
|
||||
f.write(struct.pack(">f", val))
|
||||
|
||||
def read_double(f):
|
||||
return struct.unpack(">d", f.read(4))[0]
|
||||
|
||||
def write_double(f, val):
|
||||
f.write(struct.pack(">d", val))
|
||||
|
||||
def resource_path(relative_path: str):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base_path, relative_path)
|
132
imports/access.py
Normal file
132
imports/access.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
import struct
|
||||
import sys
|
||||
import os
|
||||
from argparse import ArgumentParser
|
||||
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
colorama.init()
|
||||
TRESET = Style.RESET_ALL
|
||||
TGREEN = Fore.GREEN
|
||||
TGREENLIT = Style.BRIGHT + Fore.GREEN
|
||||
TYELLOW = Fore.YELLOW
|
||||
TYELLOWLIT = Style.BRIGHT + Fore.YELLOW
|
||||
TRED = Fore.RED
|
||||
TREDLIT = Style.BRIGHT + Fore.RED
|
||||
|
||||
except ImportError:
|
||||
TRESET = ''
|
||||
TGREEN = ''
|
||||
TGREENLIT = ''
|
||||
TYELLOW = ''
|
||||
TYELLOWLIT = ''
|
||||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
def read_sbyte(f):
|
||||
return struct.unpack("b", f.read(1))[0]
|
||||
|
||||
def write_sbyte(f):
|
||||
struct.unpack("b", f.read(1))
|
||||
|
||||
def read_sint16(f):
|
||||
return struct.unpack(">h", f.read(4))[0]
|
||||
|
||||
def write_sint16(f, val):
|
||||
f.write(struct.pack(">h", val))
|
||||
|
||||
def read_sint32(f):
|
||||
return struct.unpack(">i", f.read(4))[0]
|
||||
|
||||
def write_sint32(f, val):
|
||||
f.write(struct.pack(">i", val))
|
||||
|
||||
def read_float(f):
|
||||
return struct.unpack(">f", f.read(4))[0]
|
||||
|
||||
def write_float(f, val):
|
||||
f.write(struct.pack(">f", val))
|
||||
|
||||
def read_double(f):
|
||||
return struct.unpack(">d", f.read(4))[0]
|
||||
|
||||
def write_double(f, val):
|
||||
f.write(struct.pack(">d", val))
|
||||
|
||||
def read_ubyte(f):
|
||||
return struct.unpack("B", f.read(1))[0]
|
||||
|
||||
def write_ubyte(f):
|
||||
struct.unpack("B", f.read(1))
|
||||
|
||||
def read_uint16(f):
|
||||
return struct.unpack(">H", f.read(4))[0]
|
||||
|
||||
def write_uint16(f, val):
|
||||
f.write(struct.pack(">H", val))
|
||||
|
||||
def read_uint32(f):
|
||||
return struct.unpack(">I", f.read(4))[0]
|
||||
|
||||
def write_uint32(f, val):
|
||||
f.write(struct.pack(">I", val))
|
||||
|
||||
def read_float(f):
|
||||
return struct.unpack(">f", f.read(4))[0]
|
||||
|
||||
def write_float(f, val):
|
||||
f.write(struct.pack(">f", val))
|
||||
|
||||
def read_double(f):
|
||||
return struct.unpack(">d", f.read(4))[0]
|
||||
|
||||
def write_double(f, val):
|
||||
f.write(struct.pack(">d", val))
|
||||
|
||||
def color_text(text: str, textToColor: list=[('', None)], defaultColor: str=None):
|
||||
currentColor = None
|
||||
formattedText = ''
|
||||
|
||||
for char in text:
|
||||
handled = False
|
||||
for itemPair in textToColor:
|
||||
if (char in itemPair[0] or '\*' in itemPair[0]) and itemPair[1] is not None:
|
||||
if currentColor != itemPair[1]:
|
||||
formattedText += TRESET
|
||||
formattedText += itemPair[1]
|
||||
currentColor = itemPair[1]
|
||||
handled = True
|
||||
|
||||
elif defaultColor is not None:
|
||||
formattedText += TRESET
|
||||
formattedText += defaultColor
|
||||
currentColor = defaultColor
|
||||
|
||||
elif currentColor is not None:
|
||||
formattedText += TRESET
|
||||
currentColor = None
|
||||
|
||||
if handled:
|
||||
break
|
||||
|
||||
formattedText += char
|
||||
|
||||
return formattedText + TRESET
|
||||
|
||||
class CommandLineParser(ArgumentParser):
|
||||
|
||||
def error(self, message: str, prefix: str=None, print_usage=True, exit=True):
|
||||
if print_usage:
|
||||
self.print_usage(sys.stderr)
|
||||
|
||||
if prefix is None:
|
||||
if exit:
|
||||
self.exit(2, f'{self.prog}: error: {message}\n')
|
||||
else:
|
||||
self._print_message(f'{self.prog}: error: {message}\n')
|
||||
else:
|
||||
if exit:
|
||||
self.exit(2, f'{prefix} {message}\n')
|
||||
else:
|
||||
self._print_message(f'{prefix} {message}\n')
|
|
@ -4,53 +4,43 @@ from access import *
|
|||
class DolFile:
|
||||
|
||||
def __init__(self, f):
|
||||
self.rawData = BytesIO(f.read())
|
||||
fileoffset = 0
|
||||
addressoffset = 0x48
|
||||
sizeoffset = 0x90
|
||||
self._rawData = BytesIO(f.read())
|
||||
self.fileOffsetLoc = 0
|
||||
self.fileAddressLoc = 0x48
|
||||
self.fileSizeLoc = 0x90
|
||||
self.fileEntryLoc = 0xE0
|
||||
|
||||
self.textSections = []
|
||||
self.dataSections = []
|
||||
self.maxTextSections = 7
|
||||
self.maxDataSections = 11
|
||||
|
||||
nomoretext = False
|
||||
nomoredata = False
|
||||
|
||||
self._current_end = None
|
||||
self._currentEnd = None
|
||||
|
||||
# Read text and data section addresses and sizes
|
||||
for i in range(18):
|
||||
f.seek(fileoffset + (i << 2))
|
||||
f.seek(self.fileOffsetLoc + (i << 2))
|
||||
offset = read_uint32(f)
|
||||
f.seek(addressoffset + (i << 2))
|
||||
f.seek(self.fileAddressLoc + (i << 2))
|
||||
address = read_uint32(f)
|
||||
f.seek(sizeoffset + (i << 2))
|
||||
f.seek(self.fileSizeLoc + (i << 2))
|
||||
size = read_uint32(f)
|
||||
|
||||
if i <= 6:
|
||||
if offset == 0:
|
||||
nomoretext = True
|
||||
elif not nomoretext:
|
||||
if offset != 0:
|
||||
if i < self.maxTextSections:
|
||||
self.textSections.append((offset, address, size))
|
||||
# print("text{0}".format(i), hex(offset), hex(address), hex(size))
|
||||
else:
|
||||
#datanum = i - 7
|
||||
if offset == 0:
|
||||
nomoredata = True
|
||||
elif not nomoredata:
|
||||
else:
|
||||
self.dataSections.append((offset, address, size))
|
||||
# print("data{0}".format(datanum), hex(offset), hex(address), hex(size))
|
||||
|
||||
f.seek(0xD8)
|
||||
self.bssOffset = read_uint32(f)
|
||||
self.bssSize = read_uint32(f)
|
||||
self.entryPoint = read_uint32(f)
|
||||
|
||||
self.bss = BytesIO(self.rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize])
|
||||
self._bssData = BytesIO(self._rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize])
|
||||
|
||||
self.currAddr = self.textSections[0][1]
|
||||
self.seek(self.currAddr)
|
||||
self._currAddr = self.textSections[0][1]
|
||||
self.seek(self._currAddr)
|
||||
f.seek(0)
|
||||
|
||||
# Internal function for
|
||||
|
@ -78,48 +68,48 @@ class DolFile:
|
|||
# Unsupported: Reading an entire dol file
|
||||
# Assumption: A read should not go beyond the current section
|
||||
def read(self, size):
|
||||
if self.currAddr + size > self._current_end:
|
||||
if self._currAddr + size > self._currentEnd:
|
||||
raise RuntimeError("Read goes over current section")
|
||||
|
||||
self.currAddr += size
|
||||
return self.rawData.read(size)
|
||||
self._currAddr += size
|
||||
return self._rawData.read(size)
|
||||
|
||||
# Assumption: A write should not go beyond the current section
|
||||
def write(self, data):
|
||||
if self.currAddr + len(data) > self._current_end:
|
||||
if self._currAddr + len(data) > self._currentEnd:
|
||||
raise RuntimeError("Write goes over current section")
|
||||
|
||||
self.rawData.write(data)
|
||||
self.currAddr += len(data)
|
||||
self._rawData.write(data)
|
||||
self._currAddr += len(data)
|
||||
|
||||
def seek(self, where, whence=0):
|
||||
if whence == 0:
|
||||
offset, gc_start, gc_size = self.resolve_address(where)
|
||||
self.rawData.seek(offset + (where-gc_start))
|
||||
self._rawData.seek(offset + (where-gc_start))
|
||||
|
||||
self.currAddr = where
|
||||
self._current_end = gc_start + gc_size
|
||||
self._currAddr = where
|
||||
self._currentEnd = gc_start + gc_size
|
||||
elif whence == 1:
|
||||
offset, gc_start, gc_size = self.resolve_address(self.currAddr + where)
|
||||
self.rawData.seek(offset + ((self.currAddr + where)-gc_start))
|
||||
offset, gc_start, gc_size = self.resolve_address(self._currAddr + where)
|
||||
self._rawData.seek(offset + ((self._currAddr + where)-gc_start))
|
||||
|
||||
self.currAddr += where
|
||||
self._current_end = gc_start + gc_size
|
||||
self._currAddr += where
|
||||
self._currentEnd = gc_start + gc_size
|
||||
else:
|
||||
raise RuntimeError("Unsupported whence type '{}'".format(whence))
|
||||
|
||||
def tell(self):
|
||||
return self.currAddr
|
||||
return self._currAddr
|
||||
|
||||
def save(self, f):
|
||||
f.seek(0)
|
||||
f.write(self.rawData.getbuffer())
|
||||
f.write(self._rawData.getbuffer())
|
||||
|
||||
def get_size(self):
|
||||
oldpos = self.rawData.tell()
|
||||
self.rawData.seek(0, 2)
|
||||
size = self.rawData.tell()
|
||||
self.rawData.seek(oldpos)
|
||||
oldpos = self._rawData.tell()
|
||||
self._rawData.seek(0, 2)
|
||||
size = self._rawData.tell()
|
||||
self._rawData.seek(oldpos)
|
||||
return size
|
||||
|
||||
def get_alignment(self, alignment):
|
||||
|
@ -131,10 +121,10 @@ class DolFile:
|
|||
return 0
|
||||
|
||||
def align(self, alignment):
|
||||
oldpos = self.rawData.tell()
|
||||
self.rawData.seek(0, 2)
|
||||
self.rawData.write(bytes.fromhex("00" * self.get_alignment(alignment)))
|
||||
self.rawData.seek(oldpos)
|
||||
oldpos = self._rawData.tell()
|
||||
self._rawData.seek(0, 2)
|
||||
self._rawData.write(bytes.fromhex("00" * self.get_alignment(alignment)))
|
||||
self._rawData.seek(oldpos)
|
||||
|
||||
def append_text_sections(self, sections_list: list):
|
||||
offset = len(self.textSections) << 2
|
||||
|
@ -143,15 +133,15 @@ class DolFile:
|
|||
return False
|
||||
|
||||
'''Write offset to each section in DOL file header'''
|
||||
self.rawData.seek(offset)
|
||||
self._rawData.seek(offset)
|
||||
for section_offset in sections_list:
|
||||
self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file
|
||||
self._rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file
|
||||
|
||||
self.rawData.seek(0x48 + offset)
|
||||
self._rawData.seek(0x48 + offset)
|
||||
|
||||
'''Write in game memory addresses for each section in DOL file header'''
|
||||
for section_addr in sections_list:
|
||||
self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game
|
||||
self._rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game
|
||||
|
||||
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
|
||||
size_list = []
|
||||
|
@ -162,9 +152,9 @@ class DolFile:
|
|||
size_list.append(sections_list[i][1] - section_offset[1])
|
||||
|
||||
'''Write size of each section into DOL file header'''
|
||||
self.rawData.seek(0x90 + offset)
|
||||
self._rawData.seek(0x90 + offset)
|
||||
for size in size_list:
|
||||
self.rawData.write(size.to_bytes(4, byteorder='big', signed=False))
|
||||
self._rawData.write(size.to_bytes(4, byteorder='big', signed=False))
|
||||
|
||||
return True
|
||||
|
||||
|
@ -175,15 +165,15 @@ class DolFile:
|
|||
return False
|
||||
|
||||
'''Write offset to each section in DOL file header'''
|
||||
self.rawData.seek(offset)
|
||||
self._rawData.seek(offset)
|
||||
for section_offset in sections_list:
|
||||
self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file
|
||||
write_uint32(self._rawData, section_offset[1]) #offset in file
|
||||
|
||||
self.rawData.seek(0x64 + offset)
|
||||
self._rawData.seek(0x64 + offset)
|
||||
|
||||
'''Write in game memory addresses for each section in DOL file header'''
|
||||
for section_addr in sections_list:
|
||||
self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game
|
||||
write_uint32(self._rawData, section_addr[0]) #absolute address in game
|
||||
|
||||
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
|
||||
size_list = []
|
||||
|
@ -194,18 +184,17 @@ class DolFile:
|
|||
size_list.append(sections_list[i][1] - section_offset[1])
|
||||
|
||||
'''Write size of each section into DOL file header'''
|
||||
self.rawData.seek(0xAC + offset)
|
||||
self._rawData.seek(0xAC + offset)
|
||||
for size in size_list:
|
||||
self.rawData.write(size.to_bytes(4, byteorder='big', signed=False))
|
||||
write_uint32(self._rawData, size)
|
||||
|
||||
return True
|
||||
|
||||
def set_entry_point(self, address):
|
||||
oldpos = self.rawData.tell()
|
||||
self.rawData.seek(0xE0)
|
||||
self.rawData.write(bytes.fromhex('{:08X}'.format(address)))
|
||||
self.rawData.seek(oldpos)
|
||||
|
||||
oldpos = self._rawData.tell()
|
||||
self._rawData.seek(self.fileEntryLoc)
|
||||
write_uint32(self._rawData, address)
|
||||
self._rawData.seek(oldpos)
|
||||
|
||||
def insert_branch(self, to, _from, lk=0):
|
||||
self.write(((to - _from) & 0x3FFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False))
|
|
@ -13,27 +13,6 @@ except ImportError as IE:
|
|||
print(IE)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
colorama.init()
|
||||
TRESET = Style.RESET_ALL
|
||||
TGREEN = Fore.GREEN
|
||||
TGREENLIT = Style.BRIGHT + Fore.GREEN
|
||||
TYELLOW = Fore.YELLOW
|
||||
TYELLOWLIT = Style.BRIGHT + Fore.YELLOW
|
||||
TRED = Fore.RED
|
||||
TREDLIT = Style.BRIGHT + Fore.RED
|
||||
|
||||
except ImportError:
|
||||
TRESET = ''
|
||||
TGREEN = ''
|
||||
TGREENLIT = ''
|
||||
TYELLOW = ''
|
||||
TYELLOWLIT = ''
|
||||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
HEAP = b'HEAP'
|
||||
LOADERSIZE = b'LSIZ'
|
||||
HANDLERSIZE = b'HSIZ'
|
||||
|
@ -48,6 +27,11 @@ IL = b'IL'
|
|||
WIIVIHOOK = b'\x7C\xE3\x3B\x78\x38\x87\x00\x34\x38\xA7\x00\x38\x38\xC7\x00\x4C'
|
||||
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'
|
||||
|
||||
def resource_path(relative_path: str):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
def get_alignment(number, align: int):
|
||||
if number % align != 0:
|
||||
return align - (number % align)
|
||||
|
@ -166,18 +150,18 @@ class GCT:
|
|||
data = self.codeList.read(2).hex()
|
||||
size = int(data[:-3], 16)
|
||||
counter = int(data[1:], 16)
|
||||
address_increment = int.from_bytes(self.codeList.read(2), byteorder='big', signed=False)
|
||||
value_increment = int.from_bytes(self.codeList.read(4), byteorder='big', signed=False)
|
||||
address_increment = read_uint16(self.codeList)
|
||||
value_increment = read_uint32(self.codeList)
|
||||
|
||||
while counter + 1 > 0:
|
||||
if size == 0:
|
||||
dolFile.write(value.to_bytes(length=1, byteorder='big', signed=False))
|
||||
write_ubyte(dolFile, value)
|
||||
dolFile.seek(-1, 1)
|
||||
elif size == 1:
|
||||
dolFile.write(value.to_bytes(length=2, byteorder='big', signed=False))
|
||||
write_uint16(dolFile, value)
|
||||
dolFile.seek(-2, 1)
|
||||
elif size == 2:
|
||||
dolFile.write(value.to_bytes(length=4, byteorder='big', signed=False))
|
||||
write_uint32(dolFile, value)
|
||||
dolFile.seek(-4, 1)
|
||||
else:
|
||||
raise ValueError('Size type {} does not match 08 codetype specs'.format(size))
|
||||
|
@ -224,7 +208,7 @@ class GCT:
|
|||
class CodeHandler:
|
||||
|
||||
def __init__(self, f):
|
||||
self.rawData = BytesIO(f.read())
|
||||
self._rawData = BytesIO(f.read())
|
||||
|
||||
'''Get codelist pointer'''
|
||||
f.seek(0xFA)
|
||||
|
@ -288,7 +272,7 @@ class CodeHandler:
|
|||
class KernelLoader:
|
||||
|
||||
def __init__(self, f):
|
||||
self.rawData = BytesIO(f.read())
|
||||
self._rawData = BytesIO(f.read())
|
||||
self.initDataList = None
|
||||
self.gpModDataList = None
|
||||
self.gpDiscDataList = None
|
||||
|
@ -308,26 +292,26 @@ class KernelLoader:
|
|||
while sample:
|
||||
if sample == DH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(self.gpDiscDataList[0])
|
||||
write_uint16(tmp, self.gpDiscDataList[0])
|
||||
elif sample == DL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write((lowerAddr + self.gpDiscDataList[1]).to_bytes(2, byteorder='big', signed=False))
|
||||
write_uint16(tmp, lowerAddr + self.gpDiscDataList[1])
|
||||
elif sample == GH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(self.gpModDataList[0])
|
||||
write_uint16(tmp, self.gpModDataList[0])
|
||||
elif sample == GL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write((lowerAddr + self.gpModDataList[1]).to_bytes(2, byteorder='big', signed=False))
|
||||
write_uint16(tmp, lowerAddr + self.gpModDataList[1])
|
||||
elif sample == IH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(entryPoint[0])
|
||||
write_uint16(tmp, entryPoint[0])
|
||||
elif sample == IL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(entryPoint[1])
|
||||
write_uint16(tmp, entryPoint[1])
|
||||
sample = tmp.read(2)
|
||||
|
||||
def figure_loader_data(self, tmp, codehandler: CodeHandler, dolFile: DolFile, entrypoint: str, initpoint: list):
|
||||
upperAddr, lowerAddr = entrypoint[:int(len(entrypoint)/2)], entrypoint[int(len(entrypoint)/2):]
|
||||
def figure_loader_data(self, tmp, codeHandler: CodeHandler, dolFile: DolFile, initpoint: list):
|
||||
upperAddr, lowerAddr = self.initAddress[:int(len(self.initAddress)/2)], self.initAddress[int(len(self.initAddress)/2):]
|
||||
tmp.seek(0)
|
||||
|
||||
sample = tmp.read(4)
|
||||
|
@ -335,42 +319,45 @@ class KernelLoader:
|
|||
while sample:
|
||||
if sample == HEAP: #Found keyword "HEAP". Goes with the resize of the heap
|
||||
tmp.seek(-4, 1)
|
||||
|
||||
gpModInfoOffset = tmp.tell()
|
||||
if int(lowerAddr, 16) + gpModInfoOffset > 0x7FFF: #Absolute addressing
|
||||
gpModUpperAddr = (int(upperAddr, 16) + 1).to_bytes(2, byteorder='big', signed=False)
|
||||
gpModUpperAddr = int(upperAddr, 16) + 1
|
||||
else:
|
||||
gpModUpperAddr = int(upperAddr, 16).to_bytes(2, byteorder='big', signed=False)
|
||||
if codehandler.allocation == None:
|
||||
codehandler.allocation = (codehandler.handlerLength + codehandler.geckoCodes.size + 7) & 0xFFFFFFF8
|
||||
tmp.write(codehandler.allocation.to_bytes(4, byteorder='big', signed=False))
|
||||
gpModUpperAddr = int(upperAddr, 16)
|
||||
|
||||
if codeHandler.allocation == None:
|
||||
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & 0xFFFFFFF8
|
||||
|
||||
write_uint32(tmp, codeHandler.allocation)
|
||||
|
||||
elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(get_size(self.rawData).to_bytes(4, byteorder='big', signed=False))
|
||||
write_uint32(tmp, get_size(self._rawData))
|
||||
|
||||
elif sample == HANDLERSIZE: #Found keyword "HSIZ". Goes with the size of the codehandler
|
||||
elif sample == HANDLERSIZE: #Found keyword "HSIZ". Goes with the size of the codeHandler
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(codehandler.handlerLength.to_bytes(4, byteorder='big', signed=True))
|
||||
write_sint32(tmp, codeHandler.handlerLength)
|
||||
|
||||
elif sample == CODESIZE: #Found keyword "CSIZ". Goes with the size of the codes
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(codehandler.geckoCodes.size.to_bytes(4, byteorder='big', signed=True))
|
||||
write_sint32(tmp, codeHandler.geckoCodes.size)
|
||||
|
||||
elif sample == CODEHOOK:
|
||||
tmp.seek(-4, 1)
|
||||
if codehandler.hookAddress == None:
|
||||
tmp.write(b'\x00\x00\x00\x00')
|
||||
if codeHandler.hookAddress == None:
|
||||
write_uint32(tmp, 0)
|
||||
else:
|
||||
tmp.write(codehandler.hookAddress.to_bytes(4, byteorder='big', signed=False))
|
||||
write_uint32(tmp, codeHandler.hookAddress)
|
||||
|
||||
sample = tmp.read(4)
|
||||
|
||||
gpDiscOffset = get_size(tmp, -4)
|
||||
|
||||
if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing
|
||||
gpDiscUpperAddr = (int(upperAddr, 16) + 1).to_bytes(2, byteorder='big', signed=False)
|
||||
gpDiscUpperAddr = int(upperAddr, 16) + 1
|
||||
else:
|
||||
gpDiscUpperAddr = int(upperAddr, 16).to_bytes(2, byteorder='big', signed=False)
|
||||
gpDiscUpperAddr = int(upperAddr, 16)
|
||||
|
||||
self.gpModDataList = (gpModUpperAddr, gpModInfoOffset)
|
||||
self.gpDiscDataList = (gpDiscUpperAddr, gpDiscOffset)
|
||||
|
@ -378,45 +365,45 @@ class KernelLoader:
|
|||
self.fill_loader_data(tmp, initpoint, int(lowerAddr, 16))
|
||||
|
||||
tmp.seek(0, 2)
|
||||
codehandler.rawData.seek(0)
|
||||
codehandler.geckoCodes.codeList.seek(0)
|
||||
codeHandler._rawData.seek(0)
|
||||
codeHandler.geckoCodes.codeList.seek(0)
|
||||
|
||||
tmp.write(codehandler.rawData.read() + codehandler.geckoCodes.codeList.read())
|
||||
tmp.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read())
|
||||
|
||||
def patch_arena(self, codehandler: CodeHandler, dolFile: DolFile, entrypoint: str, tmp):
|
||||
tmp.write(self.rawData.getbuffer())
|
||||
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile, tmp):
|
||||
tmp.write(self._rawData.getbuffer())
|
||||
geckoloader_offset = dolFile.get_size()
|
||||
self.figure_loader_data(tmp, codehandler, dolFile, entrypoint,
|
||||
[((dolFile.entryPoint >> 16) & 0xFFFF).to_bytes(2, byteorder='big', signed=False),
|
||||
(dolFile.entryPoint & 0xFFFF).to_bytes(2, byteorder='big', signed=False)])
|
||||
|
||||
self.figure_loader_data(tmp, codeHandler, dolFile, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF])
|
||||
|
||||
tmp.seek(0)
|
||||
dolFile.rawData.seek(0, 2)
|
||||
dolFile.rawData.write(tmp.read())
|
||||
dolFile._rawData.seek(0, 2)
|
||||
dolFile._rawData.write(tmp.read())
|
||||
dolFile.align(256)
|
||||
|
||||
status = dolFile.append_text_sections([(int(entrypoint, 16), geckoloader_offset)])
|
||||
status = dolFile.append_text_sections([(int(self.initAddress, 16), geckoloader_offset)])
|
||||
|
||||
if status is True:
|
||||
dolFile.set_entry_point(int(entrypoint, 16))
|
||||
dolFile.set_entry_point(int(self.initAddress, 16))
|
||||
|
||||
return status
|
||||
|
||||
def patch_legacy(self, codehandler: CodeHandler, dolFile: DolFile, tmp):
|
||||
def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile, tmp):
|
||||
handlerOffset = dolFile.get_size()
|
||||
|
||||
dolFile.rawData.seek(0, 2)
|
||||
codehandler.rawData.seek(0)
|
||||
codehandler.geckoCodes.codeList.seek(0)
|
||||
dolFile._rawData.seek(0, 2)
|
||||
codeHandler._rawData.seek(0)
|
||||
codeHandler.geckoCodes.codeList.seek(0)
|
||||
|
||||
dolFile.rawData.write(codehandler.rawData.read() + codehandler.geckoCodes.codeList.read())
|
||||
dolFile._rawData.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read())
|
||||
dolFile.align(256)
|
||||
|
||||
status = dolFile.append_text_sections([(codehandler.initAddress, handlerOffset)])
|
||||
status = dolFile.append_text_sections([(codeHandler.initAddress, handlerOffset)])
|
||||
|
||||
return status
|
||||
|
||||
def protect_game(self, codehandler: CodeHandler):
|
||||
oldpos = codehandler.geckoCodes.codeList.tell()
|
||||
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\x80',
|
||||
|
@ -444,33 +431,39 @@ class KernelLoader:
|
|||
b'\x4E\x80\x00\x20\x00\x00\x00\x00',
|
||||
b'\xF0\x00\x00\x00\x00\x00\x00\x00']
|
||||
|
||||
codehandler.geckoCodes.codeList.seek(-8, 2)
|
||||
codeHandler.geckoCodes.codeList.seek(-8, 2)
|
||||
for chunk in protectdata:
|
||||
codehandler.geckoCodes.codeList.write(chunk)
|
||||
codehandler.geckoCodes.codeList.seek(oldpos)
|
||||
codeHandler.geckoCodes.codeList.write(chunk)
|
||||
codeHandler.geckoCodes.codeList.seek(oldpos)
|
||||
|
||||
def build(self, gctFile, dolFile: DolFile, codehandler: CodeHandler, tmpdir, dump):
|
||||
def build(self, parser: CommandLineParser, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump):
|
||||
beginTime = time.time()
|
||||
|
||||
if not os.path.isdir(tmpdir):
|
||||
os.mkdir(tmpdir)
|
||||
|
||||
with open(os.path.join(tmpdir, 'tmp.bin'), 'wb+') as tmp, open(dump, 'wb+') as final:
|
||||
|
||||
if dolFile.get_size() < 0x100:
|
||||
shutil.rmtree(tmpdir)
|
||||
parser.error('DOL header is corrupted. Please provide a clean file')
|
||||
parser.error(color_text('DOL header is corrupted. Please provide a clean file\n', defaultColor=TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
'''Initialize our codes'''
|
||||
|
||||
foundData = False
|
||||
|
||||
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 + codehandler.gecko_parser(gctFile, codehandler.includeAll) + 'F000000000000000'))
|
||||
temp.write(bytes.fromhex('00D0C0DE'*2 + codeHandler.gecko_parser(gctFile, codeHandler.includeAll) + 'F000000000000000'))
|
||||
temp.seek(0)
|
||||
codehandler.geckoCodes = GCT(temp)
|
||||
codeHandler.geckoCodes = GCT(temp)
|
||||
foundData = True
|
||||
elif os.path.splitext(gctFile)[1].lower() == '.gct':
|
||||
with open(r'{}'.format(gctFile), 'rb') as gct:
|
||||
codehandler.geckoCodes = GCT(gct)
|
||||
else:
|
||||
parser.error('No valid gecko code file found')
|
||||
with open(gctFile, 'rb') as gct:
|
||||
codeHandler.geckoCodes = GCT(gct)
|
||||
foundData = True
|
||||
|
||||
else:
|
||||
with open(os.path.join(tmpdir, 'gct.bin'), 'wb+') as temp:
|
||||
temp.write(bytes.fromhex('00D0C0DE'*2))
|
||||
|
@ -478,24 +471,28 @@ class KernelLoader:
|
|||
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(codehandler.gecko_parser(os.path.join(gctFile, file), codehandler.includeAll)))
|
||||
temp.write(bytes.fromhex(codeHandler.gecko_parser(os.path.join(gctFile, file), codeHandler.includeAll)))
|
||||
foundData = True
|
||||
elif os.path.splitext(file)[1].lower() == '.gct':
|
||||
with open(os.path.join(gctFile, file), 'rb') as gct:
|
||||
temp.write(gct.read()[8:-8])
|
||||
foundData = True
|
||||
else:
|
||||
print(TYELLOW + ' :: WARNING: {} is not a .txt or .gct file'.format(file) + TRESET)
|
||||
print(color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=TYELLOWLIT))
|
||||
|
||||
temp.write(bytes.fromhex('F000000000000000'))
|
||||
temp.seek(0)
|
||||
codehandler.geckoCodes = GCT(temp)
|
||||
codeHandler.geckoCodes = GCT(temp)
|
||||
|
||||
|
||||
if not foundData:
|
||||
parser.error(color_text('No valid gecko code file found\n', defaultColor=TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
if self.protect and self.build == "ARENA":
|
||||
self.protect_game(codehandler)
|
||||
if self.protect and self.codeLocation == "ARENA":
|
||||
self.protect_game(codeHandler)
|
||||
|
||||
if self.codeLocation == 'AUTO':
|
||||
if codehandler.initAddress + codehandler.handlerLength + codehandler.geckoCodes.size > 0x80002FFF:
|
||||
if codeHandler.initAddress + codeHandler.handlerLength + codeHandler.geckoCodes.size > 0x80002FFF:
|
||||
self.codeLocation = 'ARENA'
|
||||
else:
|
||||
self.codeLocation = 'LEGACY'
|
||||
|
@ -503,85 +500,84 @@ class KernelLoader:
|
|||
'''Get entrypoint (or BSS midpoint) for insert'''
|
||||
|
||||
if self.initAddress:
|
||||
dump_address = self.initAddress.lstrip("0x").upper()
|
||||
try:
|
||||
dolFile.resolve_address(int(dump_address, 16))
|
||||
print(TYELLOW + '\n :: WARNING: Init address specified for GeckoLoader (0x{}) clobbers existing dol sections'.format(dump_address) + TRESET)
|
||||
dolFile.resolve_address(int(self.initAddress, 16))
|
||||
print(color_text(f'\n :: WARNING: Init address specified for GeckoLoader (0x{self.initAddress}) clobbers existing dol sections', defaultColor=TYELLOW))
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
dump_address = '{:08X}'.format(dolFile.seek_safe_address((dolFile.bssOffset + (dolFile.bssSize >> 1)) & 0xFFFFFF00,
|
||||
get_size(self.rawData) + codehandler.handlerLength + codehandler.geckoCodes.size))
|
||||
self.rawData.seek(0)
|
||||
self.initAddress = '{:08X}'.format(dolFile.seek_safe_address((dolFile.bssOffset + (dolFile.bssSize >> 1)) & 0xFFFFFF00,
|
||||
get_size(self._rawData) + codeHandler.handlerLength + codeHandler.geckoCodes.size))
|
||||
self._rawData.seek(0)
|
||||
|
||||
'''Is insertion legacy?'''
|
||||
|
||||
if codehandler.geckoCodes.size <= 0x10:
|
||||
if codeHandler.geckoCodes.size <= 0x10:
|
||||
dolFile.save(final)
|
||||
if self.verbosity >= 1:
|
||||
print(TGREENLIT + '\n :: All codes have been successfully pre patched' + TRESET)
|
||||
print(color_text('\n :: All codes have been successfully pre patched', defaultColor=TGREENLIT))
|
||||
return
|
||||
|
||||
if self.codeLocation == 'LEGACY':
|
||||
codehandler.allocation = 0x80003000 - (codehandler.initAddress + codehandler.handlerLength)
|
||||
status = self.patch_legacy(codehandler, dolFile, tmp)
|
||||
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
|
||||
status = self.patch_legacy(codeHandler, dolFile, tmp)
|
||||
if status is False:
|
||||
determine_codehook(dolFile, codehandler)
|
||||
determine_codehook(dolFile, codeHandler)
|
||||
legacy = True
|
||||
else:
|
||||
status = self.patch_arena(codehandler, dolFile, dump_address, tmp)
|
||||
status = self.patch_arena(codeHandler, dolFile, tmp)
|
||||
legacy = False
|
||||
|
||||
if status is False:
|
||||
shutil.rmtree(tmpdir)
|
||||
parser.error(TREDLIT + 'Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET)
|
||||
parser.error(color_text('Not enough text sections to patch the DOL file! Potentially due to previous mods?\n', defaultColor=TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
dolFile.save(final)
|
||||
|
||||
if codehandler.allocation < codehandler.geckoCodes.size:
|
||||
print(TYELLOW + '\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run' + TRESET)
|
||||
|
||||
if codeHandler.allocation < codeHandler.geckoCodes.size:
|
||||
print(color_text('\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run', defaultColor=TYELLOW))
|
||||
|
||||
if self.quiet:
|
||||
return
|
||||
|
||||
if codehandler.allocation > 0x70000:
|
||||
print(TYELLOW + f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codehandler.allocation:X}' + TRESET)
|
||||
if codeHandler.allocation > 0x70000:
|
||||
print(color_text(f'\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{codeHandler.allocation:X}', defaultColor=TYELLOW))
|
||||
|
||||
elif codehandler.allocation > 0x40000:
|
||||
print(TYELLOWLIT + f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codehandler.allocation:X}' + TRESET)
|
||||
elif codeHandler.allocation > 0x40000:
|
||||
print(color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=TYELLOWLIT))
|
||||
|
||||
if self.verbosity >= 2:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [TGREENLIT + f' :: GeckoLoader set at address 0x{dump_address.upper()}, start of game modified to address 0x{dump_address.upper()}',
|
||||
f' :: Game function "__init_registers" located at address 0x{dolFile.entryPoint:X}'.format(),
|
||||
f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}',
|
||||
f' :: Codehandler is of type "{codehandler.type}"',
|
||||
f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used' + TRESET]
|
||||
if codehandler.hookAddress is not None:
|
||||
info.insert(2, f' :: Codehandler hooked at 0x{codehandler.hookAddress:08X}')
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}, start of game modified to address 0x{self.initAddress.upper()}',
|
||||
f' :: Game function "__init_registers()" located at address 0x{dolFile.entryPoint:X}',
|
||||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
|
||||
f' :: Codehandler is of type "{codeHandler.type}"',
|
||||
f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used']
|
||||
if codeHandler.hookAddress is not None:
|
||||
info.insert(2, f' :: Codehandler hooked at 0x{codeHandler.hookAddress:08X}')
|
||||
|
||||
else:
|
||||
info = [TGREENLIT + f' :: Game function "__init_registers" located at address 0x{dolFile.entryPoint:X}',
|
||||
f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}',
|
||||
f' :: Codehandler is of type "{codehandler.type}"',
|
||||
f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used' + TRESET]
|
||||
if codehandler.hookAddress is not None:
|
||||
info.insert(1, f' :: Codehandler hooked at 0x{codehandler.hookAddress:08X}')
|
||||
info = [f' :: Game function "__init_registers()" located at address 0x{dolFile.entryPoint:X}',
|
||||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}',
|
||||
f' :: Codehandler is of type "{codeHandler.type}"',
|
||||
f' :: Of the 7 text sections in this DOL file, {len(dolFile.textSections)} were already used']
|
||||
if codeHandler.hookAddress is not None:
|
||||
info.insert(1, f' :: Codehandler hooked at 0x{codeHandler.hookAddress:08X}')
|
||||
for bit in info:
|
||||
print(bit)
|
||||
print(color_text(bit, defaultColor=TGREENLIT))
|
||||
|
||||
elif self.verbosity >= 1:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [TGREENLIT + f' :: GeckoLoader set at address 0x{dump_address.upper()}',
|
||||
f' :: Codehandler is of type "{codehandler.type}"',
|
||||
f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}' + TRESET]
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}',
|
||||
f' :: Codehandler is of type "{codeHandler.type}"',
|
||||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}']
|
||||
else:
|
||||
info = [TGREENLIT + f' :: Codehandler is of type "{codehandler.type}"',
|
||||
f' :: Code allocation is 0x{codehandler.allocation:X}; codelist size is 0x{codehandler.geckoCodes.size:X}' + TRESET]
|
||||
info = [f' :: Codehandler is of type "{codeHandler.type}"',
|
||||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}']
|
||||
|
||||
for bit in info:
|
||||
print(bit)
|
||||
print(color_text(bit, defaultColor=TGREENLIT))
|
||||
|
||||
print(TGREENLIT + f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n' + TRESET)
|
||||
print(color_text(f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n', defaultColor=TGREENLIT))
|
25
imports/versioncheck.py
Normal file
25
imports/versioncheck.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
class Updater:
|
||||
|
||||
def __init__(self, owner: str, repository: str):
|
||||
self.owner = owner
|
||||
self.repo = repository
|
||||
self.gitReleases = 'https://github.com/{}/{}/releases'
|
||||
|
||||
def request_release_data(self):
|
||||
'''Returns "soup" data of the repository releases tab'''
|
||||
return requests.get(self.gitReleases.format(self.owner, self.repo))
|
||||
|
||||
def get_newest_version(self):
|
||||
'''Returns newest release version'''
|
||||
try:
|
||||
response = self.request_release_data()
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
return soup.find('span', {'class': 'css-truncate-target'}).get_text(strip=True), True
|
||||
except requests.HTTPError as e:
|
||||
return f'HTTP request failed with error code ({response.status_code})', False
|
||||
except requests.ConnectionError:
|
||||
return 'Request failed, ensure you have a working internet connection and try again', False
|
Reference in a new issue