1
0
Fork 0

Major overhaul, added multiple features

This commit is contained in:
JoshuaMKW 2020-06-03 02:12:29 -05:00
parent 70a708ca05
commit a02e6c55c8
6 changed files with 923 additions and 0 deletions

522
GeckoLoader.py Normal file
View file

@ -0,0 +1,522 @@
#Written by JoshuaMK 2020
import sys
import os
import time
import re
import shutil
import dolreader
from io import BytesIO, RawIOBase
try:
import argparse
import chardet
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 = ''
def resource_path(relative_path):
""" 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_size(file, offset=0):
""" Return a file's size in bytes """
file.seek(0, 2)
return(bytes.fromhex('{:08X}'.format(file.tell() + offset)))
def getFileAlignment(file, alignment):
""" Return file alignment, 0 = aligned, non zero = misaligned """
size = int.from_bytes(get_size(file), byteorder='big', signed=False)
if size % alignment != 0:
return alignment - (size % alignment)
else:
return 0
def alignFile(file, alignment):
""" Align a file to be the specified size """
file.write(bytes.fromhex("00" * getFileAlignment(file, alignment)))
class GCT(object):
def __init__(self, f):
self.codelist = BytesIO(f.read())
self.rawlinecount = int.from_bytes(get_size(f), byteorder='big', signed=True) >> 3
self.linecount = self.rawlinecount - 2
self.size = int.from_bytes(get_size(f), byteorder='big', signed=True)
f.seek(0)
class CodeHandler(object):
def __init__(self, f, gctFile, isText):
self.codehandler = BytesIO(f.read())
'''Get codelist pointer'''
f.seek(0xFA, 0)
codelistUpper = f.read(2).hex()
f.seek(0xFE, 0)
codelistLower = f.read(2).hex()
self.codelistpointer = int(codelistUpper[2:] + codelistLower[2:], 16)
self.handlerlength = int.from_bytes(get_size(f), byteorder='big', signed=True)
self.initaddress = 0x80001800
self.startaddress = 0x800018A8
if self.handlerlength < 0x900:
self.type = "Mini"
else:
self.type = "Full"
if isText == True:
self.geckocodes = self.geckoParser(gctFile, args.txtcodes)
else:
with open(r'{}'.format(gctFile), 'rb') as gct:
self.geckocodes = GCT(gct)
f.seek(0)
def geckoParser(self, geckoText, parseAll):
geckoMagic = '00D0C0DE00D0C0DE'
geckoTerminate = 'F000000000000000'
with open(r'{}'.format(geckoText), 'rb') as gecko:
result = chardet.detect(gecko.read())
encodeType = result['encoding']
with open(r'{}'.format(geckoText), 'r', encoding=encodeType) as gecko:
data = gecko.readlines()
geckoCodes = ''
for line in data:
if parseAll.lower() == 'all':
geckoLine = re.findall(r'[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
elif parseAll.lower() == 'active':
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
else:
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
geckoLine = ''.join(geckoLine)
geckoLine = re.sub(r'\s+', '', geckoLine)
geckoCodes = geckoCodes + geckoLine.replace('*', '')
with open(os.path.join('tmp', 'gct.bin'), 'wb+') as code:
code.write(bytes.fromhex(geckoMagic + geckoCodes + geckoTerminate))
code.seek(0)
gct = GCT(code)
return gct
def build(gctFile, dolFile, codehandlerFile, size):
global isText, _allocation, _codehook
with open(resource_path(os.path.join('bin', 'geckoloader.bin')), 'rb') as code, open(r'{}'.format(dolFile), 'rb') as dol, open(resource_path(os.path.join('bin', r'{}'.format(codehandlerFile))), 'rb') as handler, open(os.path.join('tmp', 'tmp.bin'), 'wb+') as tmp, open(os.path.join('BUILD', os.path.basename(dolFile)), 'wb+') as final:
if int(get_size(dol).hex(), 16) < 0x100:
shutil.rmtree('tmp')
parser.error('DOL header is corrupted. Please provide a clean file')
dol.seek(0)
'''Initialize the new DOL file'''
final.write(dol.read())
final.seek(0)
dolfile = dolreader.DolFile(final)
'''Initialize our codehandler + codes'''
codehandler = CodeHandler(handler, gctFile, isText)
'''Get entrypoint (or BSS midpoint) for insert'''
if args.init:
dump_address = args.init.lstrip("0x").upper()
else:
dump_address = '{:08X}'.format(dolfile._bssoffset + (dolfile._bsssize >> 1))[:-2] + '00'
'''Is insertion legacy?'''
if args.movecodes == 'LEGACY':
_allocation = '{:X}'.format(0x80003000 - (codehandler.initaddress + codehandler.handlerlength))
patchLegacyHandler(codehandler, tmp, dolfile)
legacy = True
elif args.movecodes == 'ARENA':
patchGeckoLoader(code, codehandler, tmp, dolfile, dump_address)
legacy = False
else: #Auto decide area
if codehandler.initaddress + codehandler.handlerlength + codehandler.geckocodes.size > 0x80002FFF:
patchGeckoLoader(code, codehandler, tmp, dolfile, dump_address)
legacy = False
else:
_allocation = '{:X}'.format(0x80003000 - (codehandler.initaddress + codehandler.handlerlength))
patchLegacyHandler(codehandler, tmp, dolfile)
legacy = True
dolfile.save(final)
if int(_allocation, 16) < codehandler.geckocodes.size:
print(TYELLOW + '\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run' + TRESET)
if args.quiet:
return
if int(_allocation, 16) > int('70000', 16):
print(TYELLOW + '\n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x{}'.format(_allocation.upper().lstrip('0')) + TRESET)
elif int(_allocation, 16) > int('40000', 16):
print(TYELLOWLIT + '\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{}'.format(_allocation.upper().lstrip('0')) + TRESET)
if args.verbose >= 2:
print('')
if legacy == False:
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}, start of game modified to address 0x{}'.format(dump_address.upper().lstrip('0'), dump_address.upper().lstrip('0')),
' :: Game function "_init_registers" located at address 0x{:X}'.format(dolfile._init),
' :: Codehandler hooked at 0x{}'.format(_codehook.upper().lstrip('0')),
' :: Code allocation is 0x{}; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.geckocodes.size),
' :: Codehandler is of type "{}"'.format(codehandler.type),
' :: Of the 7 text sections in this DOL file, {} were already used'.format(len(dolfile._text)) + TRESET]
else:
info = [TGREENLIT + ' :: Game function "_init_registers" located at address 0x{:X}'.format(dolfile._init),
' :: Codehandler hooked at 0x{}'.format(_codehook.upper().lstrip('0')),
' :: Code allocation is 0x{}; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.geckocodes.size),
' :: Codehandler is of type "{}"'.format(codehandler.type),
' :: Of the 7 text sections in this DOL file, {} were already used'.format(len(dolfile._text)) + TRESET]
for bit in info:
print(bit)
elif args.verbose >= 1:
print('')
if legacy == False:
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}'.format(dump_address.upper()),
' :: Codehandler is of type "{}"'.format(args.handler),
' :: Code allocation is 0x{} in hex; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.handlerlength) + TRESET]
else:
info = [TGREENLIT + ' :: Codehandler is of type "{}"'.format(args.handler),
' :: Code allocation is 0x{} in hex; codelist size is 0x{:X}'.format(_allocation.upper().lstrip('0'), codehandler.handlerlength) + TRESET]
for bit in info:
print(bit)
return
def patchGeckoLoader(fLoader, codehandler, tmp, dolfile, entrypoint):
tmp.write(fLoader.read())
geckoloader_offset = dolfile.getsize()
figureLoaderData(tmp, fLoader, codehandler, entrypoint,
[bytes.fromhex('{:X}'.format(dolfile._init)[:4]), bytes.fromhex('{:X}'.format(dolfile._init)[4:])])
tmp.seek(0)
dolfile._rawdata.seek(0, 2)
dolfile._rawdata.write(tmp.read())
dolfile.align(256)
assertTextSections(dolfile, 6, [[int(entrypoint, 16), geckoloader_offset]])
'''Write game entry in DOL file header'''
dolfile.setInitPoint(int(entrypoint, 16))
def patchLegacyHandler(codehandler, tmp, dolfile):
handler_offset = dolfile.getsize()
dolfile._rawdata.seek(0, 2)
dolfile._rawdata.write(codehandler.codehandler.read() + codehandler.geckocodes.codelist.read())
dolfile.align(256)
assertTextSections(dolfile, 6, [[0x80001800, handler_offset]])
determineCodeHook(dolfile, codehandler)
def assertTextSections(dolfile, textsections, sections_list):
offset = len(dolfile._text) << 2
if len(sections_list) + len(dolfile._text) <= 7:
'''Write offset to each section in DOL file header'''
dolfile._rawdata.seek(offset)
for section_offset in sections_list:
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(section_offset[1]))) #offset in file
dolfile._rawdata.seek(0x48 + offset)
'''Write in game memory addresses for each section in DOL file header'''
for section_addr in sections_list:
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(section_addr[0]))) #absolute address in game
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
size_list = []
for i, section_offset in enumerate(sections_list, start=1):
if i > len(sections_list) - 1:
size_list.append(dolfile.getsize() - section_offset[1])
else:
size_list.append(sections_list[i][1] - section_offset[1])
'''Write size of each section into DOL file header'''
dolfile._rawdata.seek(0x90 + offset)
for size in size_list:
dolfile._rawdata.write(bytes.fromhex('{:08X}'.format(size)))
else:
shutil.rmtree('tmp')
parser.error(TREDLIT + 'Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET)
def figureLoaderData(tmp, fLoader, codehandler, entrypoint, initpoint):
global _allocation, _codehook
upperAddr, lowerAddr = entrypoint[:int(len(entrypoint)/2)], entrypoint[int(len(entrypoint)/2):]
tmp.seek(0)
sample = tmp.read(4)
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 = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
else:
gpModUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16)))
if _allocation == None:
_allocation = '{:08X}'.format(codehandler.handlerlength + codehandler.geckocodes.size)
tmp.write(bytes.fromhex(_allocation))
elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader
tmp.seek(-4, 1)
tmp.write(get_size(fLoader))
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))
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))
elif sample == CODEHOOK:
tmp.seek(-4, 1)
if _codehook == None:
tmp.write(b'\x00\x00\x00\x00')
else:
tmp.write(bytes.fromhex(_codehook))
sample = tmp.read(4)
gpDiscOffset = int.from_bytes(get_size(tmp, -4), byteorder="big", signed=False)
if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing
gpDiscUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
else:
gpDiscUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16)))
fillLoaderData(tmp, initpoint, lowerAddr, [gpModUpperAddr, gpModInfoOffset], [gpDiscUpperAddr, gpDiscOffset])
tmp.seek(0, 2)
tmp.write(codehandler.codehandler.read() + codehandler.geckocodes.codelist.read())
def fillLoaderData(tmp, _init, lowerAddr, gpModInfo, gpDiscInfo):
tmp.seek(0)
sample = tmp.read(2)
while sample:
if sample == DH:
tmp.seek(-2, 1)
tmp.write(gpDiscInfo[0])
elif sample == DL:
tmp.seek(-2, 1)
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gpDiscInfo[1])))
elif sample == GH:
tmp.seek(-2, 1)
tmp.write(gpModInfo[0])
elif sample == GL:
tmp.seek(-2, 1)
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gpModInfo[1])))
elif sample == IH:
tmp.seek(-2, 1)
tmp.write(_init[0])
elif sample == IL:
tmp.seek(-2, 1)
tmp.write(_init[1])
sample = tmp.read(2)
def sortArgFiles(fileA, fileB):
global isText
if os.path.splitext(fileA)[1].lower() == '.dol':
dolFile = fileA
elif os.path.splitext(fileB)[1].lower() == '.dol':
dolFile = fileB
else:
parser.error('No dol file was passed\n')
if os.path.splitext(fileA)[1].lower() == '.gct':
gctFile = fileA
isText = False
elif os.path.splitext(fileA)[1].lower() == '.txt':
gctFile = fileA
isText = True
elif os.path.splitext(fileB)[1].lower() == '.gct':
gctFile = fileB
isText = False
elif os.path.splitext(fileB)[1].lower() == '.txt':
gctFile = fileB
isText = True
else:
parser.error('Neither a gct or gecko text file was passed\n')
return dolFile, gctFile
def determineCodeHook(dolfile, codehandler):
global GCNVIHOOK, WIIVIHOOK, _codehook
if _codehook == None:
assertCodeHook(dolfile, codehandler, GCNVIHOOK, WIIVIHOOK)
else:
insertCodeHook(dolfile, codehandler, int(_codehook, 16))
def assertCodeHook(dolfile, codehandler, GCNVIHOOK, WIIVIHOOK):
for offset, address, size in dolfile._text:
dolfile.seek(address, 0)
sample = dolfile.read(size)
if sample.find(GCNVIHOOK) != -1 or sample.find(WIIVIHOOK):
sample = dolfile.read(4)
while sample != b'4E800020':
sample = dolfile.read(4)
dolfile.seek(-4, 1)
insertCodeHook(dolfile, codehandler, dolfile.tell())
def insertCodeHook(dolfile, codehandler, address):
dolfile.seek(address)
if dolfile.read(4) == bytes.fromhex('4E800020'):
lk = 1
else:
parser.error("Codehandler hook given is not a blr")
dolfile.seek(-4, 1)
dolfile.write(((codehandler.startaddress - address) & 0x3FFFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False))
if __name__ == "__main__":
isText = False
if not os.path.isdir('tmp'):
os.mkdir('tmp')
parser = argparse.ArgumentParser(prog='GeckoLoader',
description='Process files and allocations for GeckoLoader',
allow_abbrev=False)
parser.add_argument('file', help='First file')
parser.add_argument('file2', help='Second file')
parser.add_argument('-a', '--alloc',
help='Define the size of the code allocation in hex, only applies when using the ARENA space',
metavar ='SIZE')
parser.add_argument('-i', '--init',
help='Define where geckoloader is injected in hex',
metavar='ADDRESS')
parser.add_argument('-m', '--movecodes',
help='''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')
parser.add_argument('-tc', '--txtcodes',
help='''What codes get parsed when a txt file is used.
"ALL" makes all codes get parsed,
"ACTIVE" makes only activated codes get parsed.''',
default='active',
metavar='TYPE')
parser.add_argument('--handler',
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
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''',
metavar='ADDRESS')
parser.add_argument('-q', '--quiet',
help='Print nothing to the console',
action='store_true')
parser.add_argument('-v', '--verbose',
help='Print extra info to the console',
default=0,
action='count')
args = parser.parse_args()
if args.alloc:
try:
_allocation = '{:08X}'.format(int(args.alloc.lstrip('0x'), 16))
except:
parser.error('The allocation was invalid\n')
else:
_allocation = None
if args.codehook:
if int(args.codehook, 16) < 0x80000000 or int(args.codehook, 16) >= 0x81800000:
parser.error('The codehandler hook address was beyond bounds\n')
else:
try:
_codehook = '{:08X}'.format(int(args.codehook.lstrip('0x'), 16))
except:
parser.error('The codehandler hook address was invalid\n')
else:
_codehook = None
if args.handler:
if args.handler == 'MINI':
codehandlerFile = 'codehandler-mini.bin'
else:
codehandlerFile = 'codehandler.bin'
else:
codehandlerFile = 'codehandler.bin'
dolFile, gctFile = sortArgFiles(args.file, args.file2)
HEAP = b'HEAP'
LOADERSIZE = b'LSIZ'
HANDLERSIZE = b'HSIZ'
CODESIZE = b'CSIZ'
CODEHOOK = b'HOOK'
DH = b'DH'
DL = b'DL'
GH = b'GH'
GL = b'GL'
IH = b'IH'
IL = b'IL'
WIIVIHOOK = b'7CE33B783887003438A7003838C7004C'
GCNVIHOOK = b'7C030034388300205485083C7C7F2A14A00300007C7D2A1420A4003FB0030000'
try:
if not os.path.isdir('BUILD'):
os.mkdir('BUILD')
if not os.path.isfile(dolFile):
parser.error(dolFile + ' Does not exist')
if not os.path.isfile(gctFile):
parser.error(gctFile + ' Does not exist')
time1 = time.time()
build(gctFile, dolFile, codehandlerFile, _allocation)
shutil.rmtree('tmp')
if not args.quiet:
print(TGREENLIT + '\n :: Compiled in {:0.4f} seconds!\n'.format(time.time() - time1) + TRESET)
except FileNotFoundError as err:
parser.error(err)
sys.exit(1)

BIN
bin/codehandler-mini.bin Normal file

Binary file not shown.

BIN
bin/codehandler.bin Normal file

Binary file not shown.

BIN
bin/geckoloader.bin Normal file

Binary file not shown.

180
dolreader.py Normal file
View file

@ -0,0 +1,180 @@
import struct
from io import BytesIO, RawIOBase
def read_ubyte(f):
return struct.unpack("B", f.read(1))[0]
def read_uint32(f):
return struct.unpack(">I", f.read(4))[0]
def write_uint32(f, val):
f.write(struct.pack(">I", val))
class DolFile(object):
def __init__(self, f):
self._rawdata = BytesIO(f.read())
fileoffset = 0
addressoffset = 0x48
sizeoffset = 0x90
self._text = []
self._data = []
nomoretext = False
nomoredata = False
self._current_end = None
# Read text and data section addresses and sizes
for i in range(18):
f.seek(fileoffset+i*4)
offset = read_uint32(f)
f.seek(addressoffset+i*4)
address = read_uint32(f)
f.seek(sizeoffset+i*4)
size = read_uint32(f)
if i <= 6:
if offset == 0:
nomoretext = True
elif not nomoretext:
self._text.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:
self._data.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._init = read_uint32(f)
self.bss = BytesIO(self._rawdata.getbuffer()[self._bssoffset:self._bssoffset+self._bsssize])
self._curraddr = self._text[0][1]
self.seek(self._curraddr)
f.seek(0)
# Internal function for
def _resolve_address(self, gc_addr):
for offset, address, size in self._text:
if address <= gc_addr < address+size:
return offset, address, size
for offset, address, size in self._data:
if address <= gc_addr < address+size:
return offset, address, size
raise RuntimeError("Unmapped address: {0}".format(hex(gc_addr)))
# 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:
raise RuntimeError("Read goes over current section")
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:
raise RuntimeError("Write goes over current section")
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._curraddr = where
self._current_end = 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))
self._curraddr += where
self._current_end = gc_start + gc_size
else:
raise RuntimeError("Unsupported whence type '{}'".format(whence))
def tell(self):
return self._curraddr
def save(self, f):
f.seek(0)
f.write(self._rawdata.getbuffer())
def getsize(self):
oldpos = self._rawdata.tell()
self._rawdata.seek(0, 2)
size = self._rawdata.tell()
self._rawdata.seek(oldpos)
return size
def getalignment(self, alignment):
size = self.getsize()
if size % alignment != 0:
return alignment - (size % alignment)
else:
return 0
def setInitPoint(self, address):
oldpos = self._rawdata.tell()
self._rawdata.seek(0xE0)
self._rawdata.write(bytes.fromhex('{:08X}'.format(address)))
self._rawdata.seek(oldpos)
def align(self, alignment):
oldpos = self._rawdata.tell()
self._rawdata.seek(0, 2)
self._rawdata.write(bytes.fromhex("00" * self.getalignment(alignment)))
self._rawdata.seek(oldpos)
if __name__ == "__main__":
# Example usage (reading some enemy info from the Pikmin 2 demo from US demo disc 17)
def read_string(f):
start = f.tell()
length = 0
while f.read(1) != b"\x00":
length += 1
if length > 100:
break
f.seek(start)
return f.read(length)
entries = []
with open("main.dol", "rb") as f:
dol = DolFile(f)
start = 0x804ac478 # memory address to start of enemy info table.
for i in range(100):
dol.seek(start+0x34*i, 0)
# string offset would normally be pointing to a location in RAM and thus
# wouldn't be suitable as a file offset but because the seek function of DolFile
# takes into account the memory address at which the data sections of the dol file
# is loaded, we can use the string offset directly..
stringoffset = read_uint32(dol)
identifier = read_ubyte(dol)
dol.seek(stringoffset, 0)
name = read_string(dol)
entries.append((identifier,i, name, hex(stringoffset)))
entries.sort(key=lambda x: x[0])
for val in entries:
print(hex(val[0]), val)

221
loader.cpp Normal file
View file

@ -0,0 +1,221 @@
/*Credits to riidefi for hook code, cache asm, and teaching me C*/
#define dcbst(_val) asm volatile("dcbst 0, %0" \
: \
: "r"(_val))
#define dcbf(_val) asm volatile("dcbf 0, %0" \
: \
: "r"(_val))
#define icbi(_val) asm volatile("icbi 0, %0" \
: \
: "r"(_val))
#define call(addr) ((void (*)(...))addr)
#define MEM1_START 0x80000000
#define MEM1_END 0x81800000
#define CODEHANDLER 0x800018A8
#define GCT_MAGIC 0x00D0C0DE
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef int s32;
typedef short s16;
typedef char s8;
__attribute__((noreturn)) int main();
struct CodeList {
u16 mBaseASM;
u16 mUpperBase;
u16 mOffsetASM;
u16 mLowerOffset;
};
struct Info {
const u32 allocsize;
const u32 loaderSize;
const u32 handlerSize;
const u32 codeSize;
const u32* codehandlerHook;
const u32 wiiVIHook[4];
const u32 gcnVIHook[8];
};
struct DiscInfo {
const u8 mDiscID;
const u16 mGameCode;
const u8 mRegionCode;
const u16 mMakerCode;
const u8 mDiscNumber;
const u8 mDiscVersion;
const u8 mAudioStreaming;
const u8 mStreamBufferSize;
const u8 _00[12];
const u32 mWiiMagic;
const u32 mGCNMagic;
const u32 _01[2];
u32 mRAMSize;
const u32 _02[2];
u32* mHeapPointer;
u32 mHeapMirror;
u32 mFstSize;
u32 mData[0x30D0 / 4];
u32 mWiiHeap;
};
Info gpModInfo = {
0, /*This is the code allocation*/
0, /*This is the size of the GeckoLoader*/
0, /*This is the size of the codehandler*/
0, /*This is the size of the GeckoLoader + the codelist*/
0, /*This is the codehandler hook address*/
{ 0x7CE33B78, 0x38870034, 0x38A70038, 0x38C7004C },
{ 0x7C030034, 0x38830020, 0x5485083C, 0x7C7F2A14, 0xA0030000, 0x7C7D2A14, 0x20A4003F, 0xB0030000 },
};
DiscInfo* gpDiscResources = (DiscInfo*)MEM1_START;
static inline void flushAddr(void* addr)
{
dcbf(addr);
icbi(addr);
}
static inline void directWrite(u32* addr, u32 value)
{
*addr = value;
flushAddr(addr);
}
/*This constructs a branch instruction. &TO = ((TO - FROM) & MAX_OFFSET) | BRANCH_TYPE | !!isLink*/
static inline void directBranchEx(void* addr, void* ptr, bool lk)
{
directWrite((u32*)(addr), ((((u32)(ptr) - (u32)(addr)) & 0x3ffffff) | 0x48000000 | !!lk));
}
static inline u32* findArrayInstance(u32* start, const u32 end, u32 arrayLength, const u32* hookData)
{
u32 index = 0;
/*Loop through the games RAM, make sure we don't find our own hook data by accident*/
for (u32 i = 0; (u32)&start[i] < end; ++i) {
/*If the data matches, increase the index counter and continue search,
else set index to 0 and continue searching*/
if (start[i] == hookData[index])
++index;
else
index = 0;
/*If the data has matched the whole array, return the address of the match*/
if (index >= (arrayLength) && ((u32)&start[i] < (u32)&gpModInfo || (u32)&start[i] > (u32)&gpModInfo + sizeof(Info)))
return (u32*)&start[i];
}
return nullptr;
}
static inline u32* findU32Instance(u32* start, u32 end, u32 hookData)
{
for (u32 i = 0; (u32)&start[i] < end; ++i) {
if (start[i] == hookData) {
return (u32*)&start[i];
}
}
return nullptr;
}
/*Find VI hook for Game*/
static inline u32* findVIHook(u32* start, const u32 end)
{
const u32* hookData;
u32 arrayLength;
/*If the game is built for the Wii, set the hookdata to be the Wii variant*/
if (gpDiscResources->mWiiMagic) {
hookData = (const u32*)gpModInfo.wiiVIHook;
arrayLength = sizeof(gpModInfo.wiiVIHook) / sizeof(u32);
} else /*The game is built for the GCN, set the hookdata to be the GCN variant*/
{
hookData = (const u32*)gpModInfo.gcnVIHook;
arrayLength = sizeof(gpModInfo.gcnVIHook) / sizeof(u32);
}
return findArrayInstance(start, end, arrayLength, hookData);
}
/*Call this after findFunction, finds the address of the first instance
of value hookInstruction, and hooks it to the pointer hookTo*/
static inline void hookFunction(u32* start, u32 hookInstruction, u32 hookTo, bool isLink)
{
int i = 0;
while (start[i] != hookInstruction) {
++i;
}
directBranchEx((u32*)(&start[i]), (void*)(hookTo), isLink);
}
/*Reallocate the games internal memory heap based on the console
the game is for, to make space for our codes*/
static inline void setHeap(u32 alloc)
{
if (gpDiscResources->mWiiMagic) {
gpDiscResources->mHeapPointer = (u32*)((u32)gpDiscResources->mWiiHeap - alloc);
gpDiscResources->mWiiHeap = (u32)gpDiscResources->mHeapPointer;
} else {
gpDiscResources->mHeapPointer = (u32*)((u32)gpDiscResources->mHeapPointer - alloc);
}
}
static inline void memCopy(u32* to, u32* from, s32 size)
{
for (s32 i = 0; i < size; ++i) {
*to++ = *from++;
}
}
static inline void flushCacheRange(u8* addr, s32 size)
{
if ((u32)addr & 31) size += 32;
else size += 31;
for (u32 i = 0; i < (size >> 5); ++i) {
flushAddr((void*)addr);
addr += 32;
}
}
static inline bool initMods(DiscInfo* gpDiscResources)
{
setHeap(gpModInfo.allocsize); /*Reallocate the internal heap*/
/*Change codelist pointer to the new address in the allocation*/
CodeList* codelistPointer = (CodeList*)((u32)&gpModInfo + sizeof(gpModInfo) + 0xFC);
codelistPointer->mUpperBase = (((u32)gpDiscResources->mHeapPointer + gpModInfo.handlerSize) >> 16) & 0xFFFF;
codelistPointer->mLowerOffset = ((u32)gpDiscResources->mHeapPointer + gpModInfo.handlerSize) & 0xFFFF;
/*Copy codelist to the new allocation*/
memCopy(gpDiscResources->mHeapPointer, (u32*)((u32)&gpModInfo + sizeof(gpModInfo) + 4), (gpModInfo.handlerSize + gpModInfo.codeSize) >> 2);
/*Update the cache, so that the instructions fully update*/
flushAddr(&codelistPointer->mBaseASM);
if (!gpModInfo.codehandlerHook || *gpModInfo.codehandlerHook != 0x4E800020) {
u32* functionAddr = findVIHook((u32*)MEM1_START, MEM1_END);
if (functionAddr == nullptr)
return false;
hookFunction(functionAddr, 0x4E800020, (u32)gpDiscResources->mHeapPointer + 0xA8, false);
} else {
directBranchEx((void*)gpModInfo.codehandlerHook, (void*)((u32)gpDiscResources->mHeapPointer + 0xA8), false);
}
flushCacheRange((u8*)gpDiscResources->mHeapPointer, gpModInfo.handlerSize + gpModInfo.codeSize);
return true;
}
int main()
{
if ((gpDiscResources->mWiiMagic || gpDiscResources->mGCNMagic) && initMods(gpDiscResources) == true)
call((void*)((u32)(gpDiscResources->mHeapPointer) + 0xA8))(); /*Call the codehandler if successful*/
call(0xDEADBEEF)(); /*Call the game start*/
}