Added simple encryption option, major refactoring
This commit is contained in:
parent
aeb152884c
commit
d66af69b99
7 changed files with 569 additions and 526 deletions
|
@ -1,19 +1,20 @@
|
|||
#Written by JoshuaMK 2020
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
import sys
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
sys.path.extend([os.path.join(os.path.dirname(__file__), 'imports')])
|
||||
|
||||
from kernel import *
|
||||
from access import *
|
||||
from dolreader import DolFile
|
||||
from kernel import CodeHandler, KernelLoader
|
||||
from tools import CommandLineParser, color_text
|
||||
from versioncheck import Updater
|
||||
|
||||
#sys.path.extend([os.path.join(os.path.dirname(__file__), 'imports')])
|
||||
|
||||
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
|
@ -35,57 +36,13 @@ except ImportError:
|
|||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
__version__ = 'v5.1.1'
|
||||
__version__ = 'v5.3.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)
|
||||
|
||||
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)
|
||||
|
||||
result = sample.find(gcnhook)
|
||||
if result >= 0:
|
||||
dolFile.seek(address, 0)
|
||||
dolFile.seek(result, 1)
|
||||
else:
|
||||
result = sample.find(wiihook)
|
||||
if result >= 0:
|
||||
dolFile.seek(address, 0)
|
||||
dolFile.seek(result, 1)
|
||||
else:
|
||||
continue
|
||||
|
||||
sample = read_uint32(dolFile)
|
||||
while sample != 0x4E800020:
|
||||
sample = read_uint32(dolFile)
|
||||
|
||||
dolFile.seek(-4, 1)
|
||||
codeHandler.hookAddress = dolFile.tell()
|
||||
|
||||
insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
|
||||
return
|
||||
|
||||
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):
|
||||
dolFile.seek(address)
|
||||
|
||||
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)
|
||||
|
||||
def sort_file_args(fileA, fileB):
|
||||
if os.path.splitext(fileA)[1].lower() == '.dol':
|
||||
dolFile = fileA
|
||||
|
@ -158,6 +115,9 @@ if __name__ == "__main__":
|
|||
help='''Checks to see if a new update exists on the GitHub Repository releases page,
|
||||
this option overrides all other commands.''',
|
||||
action='store_true')
|
||||
parser.add_argument('--encrypt',
|
||||
help='Encrypts the codelist on compile time, helping to slow the snoopers',
|
||||
action='store_true')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
version = __version__.rjust(9, ' ')
|
||||
|
@ -268,11 +228,12 @@ if __name__ == "__main__":
|
|||
geckoKernel = KernelLoader(kernelfile)
|
||||
|
||||
if args.init is not None:
|
||||
geckoKernel.initAddress = args.init.lstrip("0x").upper()
|
||||
geckoKernel.initAddress = int(args.init, 16)
|
||||
|
||||
geckoKernel.codeLocation = args.movecodes
|
||||
geckoKernel.verbosity = args.verbose
|
||||
geckoKernel.quiet = args.quiet
|
||||
geckoKernel.encrypt = args.encrypt
|
||||
|
||||
with open(os.path.normpath(args.dolfile), 'rb') as dol:
|
||||
dolFile = DolFile(dol)
|
||||
|
|
Binary file not shown.
298
dolreader.py
Normal file
298
dolreader.py
Normal file
|
@ -0,0 +1,298 @@
|
|||
from io import BytesIO
|
||||
|
||||
import tools
|
||||
|
||||
|
||||
class DolFile:
|
||||
|
||||
def __init__(self, f=None):
|
||||
self.fileOffsetLoc = 0
|
||||
self.fileAddressLoc = 0x48
|
||||
self.fileSizeLoc = 0x90
|
||||
self.fileBssInfoLoc = 0xD8
|
||||
self.fileEntryLoc = 0xE0
|
||||
|
||||
self.textSections = []
|
||||
self.dataSections = []
|
||||
self.maxTextSections = 7
|
||||
self.maxDataSections = 11
|
||||
|
||||
self.bssAddress = 0
|
||||
self.bssSize = 0
|
||||
self.entryPoint = 0x80003000
|
||||
|
||||
self._currentEnd = None
|
||||
|
||||
if f is None:
|
||||
return
|
||||
|
||||
# Read text and data section addresses and sizes
|
||||
for i in range(self.maxTextSections + self.maxDataSections):
|
||||
f.seek(self.fileOffsetLoc + (i << 2))
|
||||
offset = tools.read_uint32(f)
|
||||
f.seek(self.fileAddressLoc + (i << 2))
|
||||
address = tools.read_uint32(f)
|
||||
f.seek(self.fileSizeLoc + (i << 2))
|
||||
size = tools.read_uint32(f)
|
||||
|
||||
if offset >= 0x100:
|
||||
f.seek(offset)
|
||||
data = BytesIO(f.read(size))
|
||||
if i < self.maxTextSections:
|
||||
self.textSections.append((offset, address, size, data))
|
||||
else:
|
||||
self.dataSections.append((offset, address, size, data))
|
||||
|
||||
f.seek(self.fileBssInfoLoc)
|
||||
self.bssAddress = tools.read_uint32(f)
|
||||
self.bssSize = tools.read_uint32(f)
|
||||
|
||||
f.seek(self.fileEntryLoc)
|
||||
self.entryPoint = tools.read_uint32(f)
|
||||
|
||||
self._currLogicAddr = self.textSections[0][1]
|
||||
self.seek(self._currLogicAddr)
|
||||
f.seek(0)
|
||||
|
||||
# Internal function for
|
||||
def resolve_address(self, gcAddr, raiseError=True):
|
||||
'''Returns the data of the section that houses the given address
|
||||
If raiseError is True, a RuntimeError is raised when the address is unmapped,
|
||||
otherwise it returns None'''
|
||||
for offset, address, size, data in self.textSections:
|
||||
if address <= gcAddr < address+size:
|
||||
return offset, address, size, data
|
||||
for offset, address, size, data in self.dataSections:
|
||||
if address <= gcAddr < address+size:
|
||||
return offset, address, size, data
|
||||
|
||||
if raiseError:
|
||||
raise RuntimeError("Unmapped address: 0x{:X}".format(gcAddr))
|
||||
|
||||
return None
|
||||
|
||||
def seek_nearest_unmapped(self, gcAddr, buffer=0):
|
||||
'''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:
|
||||
continue
|
||||
gcAddr = address + size
|
||||
for _, address, size, _ in self.dataSections:
|
||||
if address > (gcAddr + buffer) or address+size < gcAddr:
|
||||
continue
|
||||
gcAddr = address + size
|
||||
return gcAddr
|
||||
|
||||
def get_final_section(self):
|
||||
largestOffset = 0
|
||||
indexToTarget = 0
|
||||
targetType = 0
|
||||
|
||||
for i, sectionData in enumerate(self.textSections):
|
||||
if sectionData[0] > largestOffset:
|
||||
largestOffset = sectionData[0]
|
||||
indexToTarget = i
|
||||
targetType = 0
|
||||
for i, sectionData in enumerate(self.dataSections):
|
||||
if sectionData[0] > largestOffset:
|
||||
largestOffset = sectionData[0]
|
||||
indexToTarget = i
|
||||
targetType = 1
|
||||
|
||||
if targetType == 0:
|
||||
return self.textSections[indexToTarget]
|
||||
else:
|
||||
return self.dataSections[indexToTarget]
|
||||
|
||||
# Unsupported: Reading an entire dol file
|
||||
# Assumption: A read should not go beyond the current section
|
||||
def read(self, size):
|
||||
_, address, size, data = self.resolve_address(self._currLogicAddr)
|
||||
if self._currLogicAddr + size > address + size:
|
||||
raise RuntimeError("Read goes over current section")
|
||||
|
||||
self._currLogicAddr += size
|
||||
return data.read(size)
|
||||
|
||||
# Assumption: A write should not go beyond the current section
|
||||
def write(self, data):
|
||||
offset, address, size, data = self.resolve_address(self._currLogicAddr)
|
||||
if self._currLogicAddr + len(data) > address + size:
|
||||
raise RuntimeError("Write goes over current section")
|
||||
|
||||
data.write(data)
|
||||
self._currLogicAddr += len(data)
|
||||
|
||||
def seek(self, where, whence=0):
|
||||
if whence == 0:
|
||||
offset, address, size, data = self.resolve_address(where)
|
||||
data.seek(where - address)
|
||||
|
||||
self._currLogicAddr = where
|
||||
self._currentEnd = address + size
|
||||
elif whence == 1:
|
||||
offset, address, size, data = self.resolve_address(self._currLogicAddr + where)
|
||||
data.seek((self._currLogicAddr + where) - address)
|
||||
|
||||
self._currLogicAddr += where
|
||||
else:
|
||||
raise RuntimeError("Unsupported whence type '{}'".format(whence))
|
||||
|
||||
def tell(self):
|
||||
return self._currLogicAddr
|
||||
|
||||
def save(self, f):
|
||||
f.seek(0)
|
||||
f.write(b"\x00" * 0x100)
|
||||
|
||||
for i in range(self.maxTextSections + self.maxDataSections):
|
||||
if i < self.maxTextSections:
|
||||
if i < len(self.textSections):
|
||||
offset, address, size, data = self.textSections[i]
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
if i - self.maxTextSections < len(self.dataSections):
|
||||
offset, address, size, data = self.dataSections[i - self.maxTextSections]
|
||||
else:
|
||||
continue
|
||||
|
||||
f.seek(self.fileOffsetLoc + (i * 4))
|
||||
tools.write_uint32(f, offset) #offset in file
|
||||
f.seek(self.fileAddressLoc + (i * 4))
|
||||
tools.write_uint32(f, address) #game address
|
||||
f.seek(self.fileSizeLoc + (i * 4))
|
||||
tools.write_uint32(f, size) #size in file
|
||||
|
||||
if offset > tools.get_size(f):
|
||||
f.seek(0, 2)
|
||||
f.write(b"\x00" * (offset - tools.get_size(f)))
|
||||
|
||||
f.seek(offset)
|
||||
f.write(data.getbuffer())
|
||||
tools.align_file(f, 32)
|
||||
|
||||
f.seek(self.fileBssInfoLoc)
|
||||
tools.write_uint32(f, self.bssAddress)
|
||||
tools.write_uint32(f, self.bssSize)
|
||||
|
||||
f.seek(self.fileEntryLoc)
|
||||
tools.write_uint32(f, self.entryPoint)
|
||||
tools.align_file(f, 256)
|
||||
|
||||
def get_full_size(self):
|
||||
fullSize = 0x100
|
||||
for section in self.textSections:
|
||||
fullSize += section[2]
|
||||
for section in self.dataSections:
|
||||
fullSize += section[2]
|
||||
return fullSize
|
||||
|
||||
def get_section_size(self, sectionsList: list, index: int):
|
||||
return sectionsList[index][2]
|
||||
|
||||
def append_text_sections(self, sectionsList: list):
|
||||
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
|
||||
|
||||
'''Write offset/address/size to each section in DOL file header'''
|
||||
for i, dataSet in enumerate(sectionsList):
|
||||
if len(self.textSections) >= self.maxTextSections:
|
||||
return False
|
||||
|
||||
fOffset, _, fSize, _ = self.get_final_section()
|
||||
_, pAddress, pSize, _ = self.textSections[len(self.textSections) - 1]
|
||||
data, address = dataSet
|
||||
|
||||
if not isinstance(data, BytesIO):
|
||||
data = BytesIO(data)
|
||||
|
||||
offset = fOffset + fSize
|
||||
|
||||
if i < len(sectionsList) - 1:
|
||||
size = (len(data.getbuffer()) + 31) & -32
|
||||
else:
|
||||
size = (len(data.getbuffer()) + 255) & -256
|
||||
|
||||
if address is None:
|
||||
address = self.seek_nearest_unmapped(pAddress + pSize, size)
|
||||
|
||||
if address < 0x80000000 or address >= 0x81200000:
|
||||
raise ValueError("Address '{:08X}' of text section {} is beyond scope (0x80000000 <-> 0x81200000)".format(address, i))
|
||||
|
||||
self.textSections.append((offset, address, size, data))
|
||||
|
||||
return True
|
||||
|
||||
def append_data_sections(self, sectionsList: list):
|
||||
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
|
||||
|
||||
'''Write offset/address/size to each section in DOL file header'''
|
||||
for i, dataSet in enumerate(sectionsList):
|
||||
if len(self.dataSections) >= self.maxDataSections:
|
||||
return False
|
||||
|
||||
fOffset, _, fSize, _ = self.get_final_section()
|
||||
_, pAddress, pSize, _ = self.dataSections[len(self.dataSections) - 1]
|
||||
data, address = dataSet
|
||||
|
||||
if not isinstance(data, BytesIO):
|
||||
data = BytesIO(data)
|
||||
|
||||
offset = fOffset + fSize
|
||||
|
||||
if i < len(sectionsList) - 1:
|
||||
size = (len(data.getbuffer()) + 31) & -32
|
||||
else:
|
||||
size = (len(data.getbuffer()) + 255) & -256
|
||||
|
||||
if address is None:
|
||||
address = self.seek_nearest_unmapped(pAddress + pSize, size)
|
||||
|
||||
if address < 0x80000000 or address >= 0x81200000:
|
||||
raise ValueError("Address '{:08X}' of data section {} is beyond scope (0x80000000 <-> 0x81200000)".format(address, i))
|
||||
|
||||
self.dataSections.append((offset, address, size, data))
|
||||
|
||||
return True
|
||||
|
||||
def insert_branch(self, to, _from, lk=0):
|
||||
self.write(((to - _from) & 0x3FFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False))
|
||||
|
||||
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 = tools.read_uint32(dol)
|
||||
identifier = tools.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)
|
|
@ -1,241 +0,0 @@
|
|||
from io import BytesIO
|
||||
from access import *
|
||||
|
||||
class DolFile:
|
||||
|
||||
def __init__(self, f):
|
||||
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
|
||||
|
||||
self._currentEnd = None
|
||||
|
||||
# Read text and data section addresses and sizes
|
||||
for i in range(18):
|
||||
f.seek(self.fileOffsetLoc + (i << 2))
|
||||
offset = read_uint32(f)
|
||||
f.seek(self.fileAddressLoc + (i << 2))
|
||||
address = read_uint32(f)
|
||||
f.seek(self.fileSizeLoc + (i << 2))
|
||||
size = read_uint32(f)
|
||||
|
||||
if offset != 0:
|
||||
if i < self.maxTextSections:
|
||||
self.textSections.append((offset, address, size))
|
||||
else:
|
||||
self.dataSections.append((offset, address, size))
|
||||
|
||||
f.seek(0xD8)
|
||||
self.bssOffset = read_uint32(f)
|
||||
self.bssSize = read_uint32(f)
|
||||
self.entryPoint = read_uint32(f)
|
||||
|
||||
self._bssData = BytesIO(self._rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize])
|
||||
|
||||
self._currAddr = self.textSections[0][1]
|
||||
self.seek(self._currAddr)
|
||||
f.seek(0)
|
||||
|
||||
# Internal function for
|
||||
def resolve_address(self, gcAddr):
|
||||
for offset, address, size in self.textSections:
|
||||
if address <= gcAddr < address+size:
|
||||
return offset, address, size
|
||||
for offset, address, size in self.dataSections:
|
||||
if address <= gcAddr < address+size:
|
||||
return offset, address, size
|
||||
|
||||
raise RuntimeError(f"Unmapped address: 0x{gcAddr:X}")
|
||||
|
||||
def seek_safe_address(self, gcAddr, buffer=0):
|
||||
for offset, address, size in self.textSections:
|
||||
if address > (gcAddr + buffer) or address+size < gcAddr:
|
||||
continue
|
||||
gcAddr = address + size
|
||||
for offset, address, size in self.dataSections:
|
||||
if address > (gcAddr + buffer) or address+size < gcAddr:
|
||||
continue
|
||||
gcAddr = address + size
|
||||
return gcAddr
|
||||
|
||||
# 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._currentEnd:
|
||||
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._currentEnd:
|
||||
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._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))
|
||||
|
||||
self._currAddr += where
|
||||
self._currentEnd = 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 get_size(self):
|
||||
oldpos = self._rawData.tell()
|
||||
self._rawData.seek(0, 2)
|
||||
size = self._rawData.tell()
|
||||
self._rawData.seek(oldpos)
|
||||
return size
|
||||
|
||||
def get_alignment(self, alignment):
|
||||
size = self.get_size()
|
||||
|
||||
if size % alignment != 0:
|
||||
return alignment - (size % alignment)
|
||||
else:
|
||||
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)
|
||||
|
||||
def append_text_sections(self, sections_list: list):
|
||||
offset = len(self.textSections) << 2
|
||||
|
||||
if len(sections_list) + len(self.textSections) > self.maxTextSections:
|
||||
return False
|
||||
|
||||
'''Write offset to each section in DOL file header'''
|
||||
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.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
|
||||
|
||||
'''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(self.get_size() - section_offset[1])
|
||||
else:
|
||||
size_list.append(sections_list[i][1] - section_offset[1])
|
||||
|
||||
'''Write size of each section into DOL file header'''
|
||||
self._rawData.seek(0x90 + offset)
|
||||
for size in size_list:
|
||||
self._rawData.write(size.to_bytes(4, byteorder='big', signed=False))
|
||||
|
||||
return True
|
||||
|
||||
def append_data_sections(self, sections_list: list):
|
||||
offset = len(self.dataSections) << 2
|
||||
|
||||
if len(sections_list) + len(self.dataSections) > self.maxDataSections:
|
||||
return False
|
||||
|
||||
'''Write offset to each section in DOL file header'''
|
||||
self._rawData.seek(offset)
|
||||
for section_offset in sections_list:
|
||||
write_uint32(self._rawData, section_offset[1]) #offset in file
|
||||
|
||||
self._rawData.seek(0x64 + offset)
|
||||
|
||||
'''Write in game memory addresses for each section in DOL file header'''
|
||||
for section_addr in sections_list:
|
||||
write_uint32(self._rawData, 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(self.get_size() - section_offset[1])
|
||||
else:
|
||||
size_list.append(sections_list[i][1] - section_offset[1])
|
||||
|
||||
'''Write size of each section into DOL file header'''
|
||||
self._rawData.seek(0xAC + offset)
|
||||
for size in size_list:
|
||||
write_uint32(self._rawData, size)
|
||||
|
||||
return True
|
||||
|
||||
def set_entry_point(self, address):
|
||||
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))
|
||||
|
||||
|
||||
|
||||
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)
|
|
@ -1,11 +1,12 @@
|
|||
import sys
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from io import BytesIO
|
||||
from dolreader import *
|
||||
from access import *
|
||||
|
||||
import tools
|
||||
from dolreader import DolFile
|
||||
|
||||
try:
|
||||
import chardet
|
||||
|
@ -13,58 +14,19 @@ except ImportError as IE:
|
|||
print(IE)
|
||||
sys.exit(1)
|
||||
|
||||
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'\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)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_size(file, offset=0):
|
||||
""" Return a file's size in bytes """
|
||||
file.seek(0, 2)
|
||||
return file.tell() + offset
|
||||
|
||||
def get_file_alignment(file, alignment: int):
|
||||
""" Return file alignment, 0 = aligned, non zero = misaligned """
|
||||
size = get_size(file)
|
||||
return get_alignment(size, alignment)
|
||||
|
||||
def align_file(file, alignment: int, fillchar='00'):
|
||||
""" Align a file to be the specified size """
|
||||
file.write(bytes.fromhex(fillchar * get_file_alignment(file, alignment)))
|
||||
|
||||
class GCT:
|
||||
|
||||
def __init__(self, f):
|
||||
self.codeList = BytesIO(f.read())
|
||||
self.rawLineCount = get_size(f) >> 3
|
||||
self.rawLineCount = tools.get_size(f) >> 3
|
||||
self.lineCount = self.rawLineCount - 2
|
||||
self.size = get_size(f)
|
||||
self.size = tools.get_size(f)
|
||||
f.seek(0)
|
||||
|
||||
def determine_codelength(self, codetype, info):
|
||||
if codetype.startswith(b'\x06'):
|
||||
bytelength = int.from_bytes(info, byteorder='big', signed=False)
|
||||
padding = get_alignment(bytelength, 8)
|
||||
padding = tools.get_alignment(bytelength, 8)
|
||||
return 0x8 + bytelength + padding
|
||||
|
||||
elif (codetype.startswith(b'\x08') or codetype.startswith(b'\x09')
|
||||
|
@ -132,7 +94,7 @@ class GCT:
|
|||
dolFile.seek(address)
|
||||
|
||||
arraylength = int.from_bytes(info, byteorder='big', signed=False)
|
||||
padding = get_alignment(arraylength, 8)
|
||||
padding = tools.get_alignment(arraylength, 8)
|
||||
|
||||
while arraylength > 0:
|
||||
value = self.codeList.read(1)
|
||||
|
@ -150,18 +112,18 @@ class GCT:
|
|||
data = self.codeList.read(2).hex()
|
||||
size = int(data[:-3], 16)
|
||||
counter = int(data[1:], 16)
|
||||
address_increment = read_uint16(self.codeList)
|
||||
value_increment = read_uint32(self.codeList)
|
||||
address_increment = tools.read_uint16(self.codeList)
|
||||
value_increment = tools.read_uint32(self.codeList)
|
||||
|
||||
while counter + 1 > 0:
|
||||
if size == 0:
|
||||
write_ubyte(dolFile, value)
|
||||
tools.write_ubyte(dolFile, value)
|
||||
dolFile.seek(-1, 1)
|
||||
elif size == 1:
|
||||
write_uint16(dolFile, value)
|
||||
tools.write_uint16(dolFile, value)
|
||||
dolFile.seek(-2, 1)
|
||||
elif size == 2:
|
||||
write_uint32(dolFile, value)
|
||||
tools.write_uint32(dolFile, value)
|
||||
dolFile.seek(-4, 1)
|
||||
else:
|
||||
raise ValueError('Size type {} does not match 08 codetype specs'.format(size))
|
||||
|
@ -203,7 +165,7 @@ class GCT:
|
|||
length -= 1
|
||||
|
||||
self.codeList = BytesIO(codelist)
|
||||
self.size = get_size(self.codeList)
|
||||
self.size = tools.get_size(self.codeList)
|
||||
|
||||
class CodeHandler:
|
||||
|
||||
|
@ -217,9 +179,11 @@ class CodeHandler:
|
|||
codelistLower = f.read(2).hex()
|
||||
|
||||
self.codeListPointer = int(codelistUpper[2:] + codelistLower[2:], 16)
|
||||
self.handlerLength = get_size(f)
|
||||
self.handlerLength = tools.get_size(f)
|
||||
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.allocation = None
|
||||
self.hookAddress = None
|
||||
self.geckoCodes = None
|
||||
|
@ -269,6 +233,33 @@ class CodeHandler:
|
|||
|
||||
return geckoCodes
|
||||
|
||||
def encrypt_key(self, key: int):
|
||||
b1 = key & 0xFF
|
||||
b2 = (key >> 8) & 0xFF
|
||||
b3 = (key >> 16) & 0xFF
|
||||
b4 = (key >> 24) & 0xFF
|
||||
b3 ^= b4
|
||||
b2 ^= b3
|
||||
b1 ^= b2
|
||||
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
|
||||
|
||||
def encrypt_data(self, key: int):
|
||||
self.geckoCodes.codeList.seek(0)
|
||||
print(f'real key: {key:X}')
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
packet = tools.read_uint32(self.geckoCodes.codeList)
|
||||
self.geckoCodes.codeList.seek(-4, 1)
|
||||
#print(hex(self.geckoCodes.codeList.tell()), hex(packet), hex((packet^key) & 0xFFFFFFFF), hex(key))
|
||||
tools.write_uint32(self.geckoCodes.codeList, (packet^key) & 0xFFFFFFFF)
|
||||
key += (i ^ key) & 0xFFFFFFFF
|
||||
if key > 0xFFFFFFFF:
|
||||
key -= 0x100000000
|
||||
i += 1
|
||||
except:
|
||||
break
|
||||
|
||||
class KernelLoader:
|
||||
|
||||
def __init__(self, f):
|
||||
|
@ -276,141 +267,144 @@ class KernelLoader:
|
|||
self.initDataList = None
|
||||
self.gpModDataList = None
|
||||
self.gpDiscDataList = None
|
||||
self.gpKeyAddrList = None
|
||||
self.codeLocation = None
|
||||
self.initAddress = None
|
||||
self.protect = False
|
||||
self.verbosity = 0
|
||||
self.quiet = False
|
||||
self.encrypt = False
|
||||
|
||||
def fill_loader_data(self, tmp, entryPoint: list, lowerAddr: int):
|
||||
tmp.seek(0)
|
||||
if self.gpModDataList is None or self.gpDiscDataList is None:
|
||||
def set_variables(self, entryPoint: list, baseOffset: int=0):
|
||||
self._rawData.seek(0)
|
||||
if self.gpModDataList is None:
|
||||
return
|
||||
|
||||
sample = tmp.read(2)
|
||||
sample = self._rawData.read(2)
|
||||
|
||||
while sample:
|
||||
if sample == DH:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, self.gpDiscDataList[0])
|
||||
elif sample == DL:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, lowerAddr + self.gpDiscDataList[1])
|
||||
elif sample == GH:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, self.gpModDataList[0])
|
||||
elif sample == GL:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, lowerAddr + self.gpModDataList[1])
|
||||
elif sample == IH:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, entryPoint[0])
|
||||
elif sample == IL:
|
||||
tmp.seek(-2, 1)
|
||||
write_uint16(tmp, entryPoint[1])
|
||||
sample = tmp.read(2)
|
||||
if sample == b'GH':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, self.gpModDataList[0])
|
||||
elif sample == b'GL':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, baseOffset + self.gpModDataList[1])
|
||||
elif sample == b'IH':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, entryPoint[0])
|
||||
elif sample == b'IL':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, entryPoint[1])
|
||||
elif sample == b'KH':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, self.gpKeyAddrList[0])
|
||||
elif sample == b'KL':
|
||||
self._rawData.seek(-2, 1)
|
||||
tools.write_uint16(self._rawData, baseOffset + self.gpKeyAddrList[1])
|
||||
|
||||
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 = self._rawData.read(2)
|
||||
|
||||
sample = tmp.read(4)
|
||||
def complete_data(self, codeHandler: CodeHandler, initpoint: list):
|
||||
upperAddr, lowerAddr = ((self.initAddress >> 16) & 0xFFFF, self.initAddress & 0xFFFF)
|
||||
key = random.randrange(0x100000000)
|
||||
self._rawData.seek(0)
|
||||
|
||||
sample = self._rawData.read(4)
|
||||
|
||||
while sample:
|
||||
if sample == HEAP: #Found keyword "HEAP". Goes with the resize of the heap
|
||||
tmp.seek(-4, 1)
|
||||
if sample == b'HEAP': #Found keyword "HEAP". Goes with the resize of the heap
|
||||
self._rawData.seek(-4, 1)
|
||||
|
||||
gpModInfoOffset = tmp.tell()
|
||||
if int(lowerAddr, 16) + gpModInfoOffset > 0x7FFF: #Absolute addressing
|
||||
gpModUpperAddr = int(upperAddr, 16) + 1
|
||||
gpModInfoOffset = self._rawData.tell()
|
||||
if lowerAddr + gpModInfoOffset > 0x7FFF: #Absolute addressing
|
||||
gpModUpperAddr = upperAddr + 1
|
||||
else:
|
||||
gpModUpperAddr = int(upperAddr, 16)
|
||||
gpModUpperAddr = upperAddr
|
||||
|
||||
if codeHandler.allocation == None:
|
||||
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & 0xFFFFFFF8
|
||||
codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8
|
||||
|
||||
write_uint32(tmp, codeHandler.allocation)
|
||||
tools.write_uint32(self._rawData, codeHandler.allocation)
|
||||
|
||||
elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader
|
||||
tmp.seek(-4, 1)
|
||||
write_uint32(tmp, get_size(self._rawData))
|
||||
elif sample == b'LSIZ': #Found keyword "LSIZ". Goes with the size of the loader
|
||||
self._rawData.seek(-4, 1)
|
||||
tools.write_uint32(self._rawData, len(self._rawData.getbuffer()))
|
||||
|
||||
elif sample == HANDLERSIZE: #Found keyword "HSIZ". Goes with the size of the codeHandler
|
||||
tmp.seek(-4, 1)
|
||||
write_sint32(tmp, codeHandler.handlerLength)
|
||||
elif sample == b'HSIZ': #Found keyword "HSIZ". Goes with the size of the codeHandler
|
||||
self._rawData.seek(-4, 1)
|
||||
tools.write_sint32(self._rawData, codeHandler.handlerLength)
|
||||
|
||||
elif sample == CODESIZE: #Found keyword "CSIZ". Goes with the size of the codes
|
||||
tmp.seek(-4, 1)
|
||||
write_sint32(tmp, codeHandler.geckoCodes.size)
|
||||
elif sample == b'CSIZ': #Found keyword "CSIZ". Goes with the size of the codes
|
||||
self._rawData.seek(-4, 1)
|
||||
tools.write_sint32(self._rawData, codeHandler.geckoCodes.size)
|
||||
|
||||
elif sample == CODEHOOK:
|
||||
tmp.seek(-4, 1)
|
||||
elif sample == b'HOOK': #Found keyword "HOOK". Goes with the codehandler hook
|
||||
self._rawData.seek(-4, 1)
|
||||
if codeHandler.hookAddress == None:
|
||||
write_uint32(tmp, 0)
|
||||
tools.write_uint32(self._rawData, 0)
|
||||
else:
|
||||
write_uint32(tmp, codeHandler.hookAddress)
|
||||
tools.write_uint32(self._rawData, codeHandler.hookAddress)
|
||||
|
||||
sample = tmp.read(4)
|
||||
elif sample == b'CRPT': #Found keyword "CRPT". Boolean of the encryption
|
||||
self._rawData.seek(-4, 1)
|
||||
tools.write_bool(self._rawData, self.encrypt, 4)
|
||||
|
||||
gpDiscOffset = get_size(tmp, -4)
|
||||
elif sample == b'CYPT': #Found keyword "CYPT". Encryption Key
|
||||
self._rawData.seek(-4, 1)
|
||||
|
||||
if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing
|
||||
gpDiscUpperAddr = int(upperAddr, 16) + 1
|
||||
gpKeyOffset = self._rawData.tell()
|
||||
if lowerAddr + gpKeyOffset > 0x7FFF: #Absolute addressing
|
||||
gpKeyUpperAddr = upperAddr + 1
|
||||
else:
|
||||
gpDiscUpperAddr = int(upperAddr, 16)
|
||||
gpKeyUpperAddr = upperAddr
|
||||
|
||||
tools.write_uint32(self._rawData, codeHandler.encrypt_key(key))
|
||||
|
||||
sample = self._rawData.read(4)
|
||||
|
||||
self.gpModDataList = (gpModUpperAddr, gpModInfoOffset)
|
||||
self.gpDiscDataList = (gpDiscUpperAddr, gpDiscOffset)
|
||||
self.gpKeyAddrList = (gpKeyUpperAddr, gpKeyOffset)
|
||||
|
||||
self.fill_loader_data(tmp, initpoint, int(lowerAddr, 16))
|
||||
self.set_variables(initpoint, lowerAddr)
|
||||
|
||||
tmp.seek(0, 2)
|
||||
codeHandler._rawData.seek(0)
|
||||
codeHandler.geckoCodes.codeList.seek(0)
|
||||
if self.encrypt:
|
||||
codeHandler.encrypt_data(key)
|
||||
|
||||
tmp.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read())
|
||||
|
||||
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile, tmp):
|
||||
tmp.write(self._rawData.getbuffer())
|
||||
geckoloader_offset = dolFile.get_size()
|
||||
def patch_arena(self, codeHandler: CodeHandler, dolFile: DolFile):
|
||||
self.complete_data(codeHandler, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF])
|
||||
|
||||
self.figure_loader_data(tmp, codeHandler, dolFile, [(dolFile.entryPoint >> 16) & 0xFFFF, dolFile.entryPoint & 0xFFFF])
|
||||
self._rawData.seek(0, 2)
|
||||
self._rawData.write(codeHandler._rawData.getvalue() + codeHandler.geckoCodes.codeList.getvalue())
|
||||
|
||||
tmp.seek(0)
|
||||
dolFile._rawData.seek(0, 2)
|
||||
dolFile._rawData.write(tmp.read())
|
||||
dolFile.align(256)
|
||||
self._rawData.seek(0)
|
||||
kernelData = self._rawData.getvalue()
|
||||
|
||||
status = dolFile.append_text_sections([(int(self.initAddress, 16), geckoloader_offset)])
|
||||
status = dolFile.append_text_sections([(kernelData, self.initAddress)])
|
||||
|
||||
if status is True:
|
||||
dolFile.set_entry_point(int(self.initAddress, 16))
|
||||
dolFile.entryPoint = self.initAddress
|
||||
|
||||
return status
|
||||
|
||||
def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile, tmp):
|
||||
handlerOffset = dolFile.get_size()
|
||||
|
||||
dolFile._rawData.seek(0, 2)
|
||||
def patch_legacy(self, codeHandler: CodeHandler, dolFile: DolFile):
|
||||
codeHandler._rawData.seek(0)
|
||||
codeHandler.geckoCodes.codeList.seek(0)
|
||||
|
||||
dolFile._rawData.write(codeHandler._rawData.read() + codeHandler.geckoCodes.codeList.read())
|
||||
dolFile.align(256)
|
||||
|
||||
status = dolFile.append_text_sections([(codeHandler.initAddress, handlerOffset)])
|
||||
handlerData = codeHandler._rawData.getvalue() + codeHandler.geckoCodes.codeList.getvalue()
|
||||
|
||||
status = dolFile.append_text_sections([(handlerData, codeHandler.initAddress)])
|
||||
return status
|
||||
|
||||
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',
|
||||
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\x04\x3C\x60\x80\x00',
|
||||
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',
|
||||
|
@ -420,32 +414,31 @@ class KernelLoader:
|
|||
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\x38\x80\x11\x00',
|
||||
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'\x54\x66\x07\xBE\x7C\xDE\x30\xAE',
|
||||
b'\x38\x63\x00\x01\x9C\xC4\x00\x01',
|
||||
b'\x42\x00\xFF\xF0\x83\xE1\x00\x10',
|
||||
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\x80\x7C\x08\x03\xA6',
|
||||
b'\x4E\x80\x00\x20\x00\x00\x00\x00',
|
||||
b'\xF0\x00\x00\x00\x00\x00\x00\x00']
|
||||
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()
|
||||
codeHandler.geckoCodes.codeList.seek(oldpos)
|
||||
|
||||
def build(self, parser: CommandLineParser, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump):
|
||||
def build(self, parser: tools.CommandLineParser, gctFile, dolFile: DolFile, codeHandler: CodeHandler, tmpdir, dump):
|
||||
beginTime = time.time()
|
||||
|
||||
if not os.path.isdir(tmpdir):
|
||||
os.mkdir(tmpdir)
|
||||
with open(dump, 'wb+') as final:
|
||||
|
||||
with open(os.path.join(tmpdir, 'tmp.bin'), 'wb+') as tmp, open(dump, 'wb+') as final:
|
||||
|
||||
if dolFile.get_size() < 0x100:
|
||||
parser.error(color_text('DOL header is corrupted. Please provide a clean file\n', defaultColor=TREDLIT), exit=False)
|
||||
if dolFile.get_full_size() < 0x100:
|
||||
parser.error(tools.color_text('DOL header is corrupted. Please provide a clean file\n', defaultColor=tools.TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
'''Initialize our codes'''
|
||||
|
@ -478,14 +471,14 @@ class KernelLoader:
|
|||
temp.write(gct.read()[8:-8])
|
||||
foundData = True
|
||||
else:
|
||||
print(color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=TYELLOWLIT))
|
||||
print(tools.color_text(f' :: HINT: {file} is not a .txt or .gct file', defaultColor=tools.TYELLOWLIT))
|
||||
|
||||
temp.write(bytes.fromhex('F000000000000000'))
|
||||
temp.seek(0)
|
||||
codeHandler.geckoCodes = GCT(temp)
|
||||
|
||||
if not foundData:
|
||||
parser.error(color_text('No valid gecko code file found\n', defaultColor=TREDLIT), exit=False)
|
||||
parser.error(tools.color_text('No valid gecko code file found\n', defaultColor=tools.TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
if self.protect and self.codeLocation == "ARENA":
|
||||
|
@ -501,13 +494,12 @@ class KernelLoader:
|
|||
|
||||
if self.initAddress:
|
||||
try:
|
||||
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))
|
||||
dolFile.resolve_address(self.initAddress)
|
||||
print(tools.color_text(f'\n :: WARNING: Init address specified for GeckoLoader (0x{self.initAddress:X}) clobbers existing dol sections', defaultColor=tools.TYELLOW))
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
self.initAddress = '{:08X}'.format(dolFile.seek_safe_address((dolFile.bssOffset + (dolFile.bssSize >> 1)) & 0xFFFFFF00,
|
||||
get_size(self._rawData) + codeHandler.handlerLength + codeHandler.geckoCodes.size))
|
||||
self.initAddress = dolFile.seek_nearest_unmapped(dolFile.bssAddress, tools.get_size(self._rawData) + codeHandler.handlerLength + codeHandler.geckoCodes.size)
|
||||
self._rawData.seek(0)
|
||||
|
||||
'''Is insertion legacy?'''
|
||||
|
@ -515,41 +507,43 @@ class KernelLoader:
|
|||
if codeHandler.geckoCodes.size <= 0x10:
|
||||
dolFile.save(final)
|
||||
if self.verbosity >= 1:
|
||||
print(color_text('\n :: All codes have been successfully pre patched', defaultColor=TGREENLIT))
|
||||
print(tools.color_text('\n :: All codes have been successfully pre patched', defaultColor=tools.TGREENLIT))
|
||||
return
|
||||
|
||||
if self.codeLocation == 'LEGACY':
|
||||
codeHandler.allocation = 0x80003000 - (codeHandler.initAddress + codeHandler.handlerLength)
|
||||
status = self.patch_legacy(codeHandler, dolFile, tmp)
|
||||
status = self.patch_legacy(codeHandler, dolFile)
|
||||
if status is False:
|
||||
determine_codehook(dolFile, codeHandler)
|
||||
hooked, msg = determine_codehook(dolFile, codeHandler)
|
||||
if not hooked:
|
||||
parser.error(tools.color_text(msg, defaultColor=tools.TREDLIT))
|
||||
legacy = True
|
||||
else:
|
||||
status = self.patch_arena(codeHandler, dolFile, tmp)
|
||||
status = self.patch_arena(codeHandler, dolFile)
|
||||
legacy = False
|
||||
|
||||
if status is False:
|
||||
parser.error(color_text('Not enough text sections to patch the DOL file! Potentially due to previous mods?\n', defaultColor=TREDLIT), exit=False)
|
||||
parser.error(tools.color_text('Not enough text sections to patch the DOL file! Potentially due to previous mods?\n', defaultColor=tools.TREDLIT), exit=False)
|
||||
return
|
||||
|
||||
dolFile.save(final)
|
||||
|
||||
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))
|
||||
print(tools.color_text('\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run', defaultColor=tools.TYELLOW))
|
||||
|
||||
if self.quiet:
|
||||
return
|
||||
|
||||
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))
|
||||
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(color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=TYELLOWLIT))
|
||||
print(tools.color_text(f'\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{codeHandler.allocation:X}', defaultColor=tools.TYELLOWLIT))
|
||||
|
||||
if self.verbosity >= 2:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}, start of game modified to address 0x{self.initAddress.upper()}',
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress:X}, start of game modified to address 0x{self.initAddress:X}',
|
||||
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}"',
|
||||
|
@ -565,12 +559,12 @@ class KernelLoader:
|
|||
if codeHandler.hookAddress is not None:
|
||||
info.insert(1, f' :: Codehandler hooked at 0x{codeHandler.hookAddress:08X}')
|
||||
for bit in info:
|
||||
print(color_text(bit, defaultColor=TGREENLIT))
|
||||
print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
|
||||
|
||||
elif self.verbosity >= 1:
|
||||
print('')
|
||||
if legacy == False:
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress.upper()}',
|
||||
info = [f' :: GeckoLoader set at address 0x{self.initAddress:X}',
|
||||
f' :: Codehandler is of type "{codeHandler.type}"',
|
||||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}']
|
||||
else:
|
||||
|
@ -578,6 +572,56 @@ class KernelLoader:
|
|||
f' :: Code allocation is 0x{codeHandler.allocation:X}; codelist size is 0x{codeHandler.geckoCodes.size:X}']
|
||||
|
||||
for bit in info:
|
||||
print(color_text(bit, defaultColor=TGREENLIT))
|
||||
print(tools.color_text(bit, defaultColor=tools.TGREENLIT))
|
||||
|
||||
print(color_text(f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n', defaultColor=TGREENLIT))
|
||||
print(tools.color_text(f'\n :: Compiled in {(time.time() - beginTime):0.4f} seconds!\n', defaultColor=tools.TGREENLIT))
|
||||
|
||||
|
||||
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:
|
||||
return assert_code_hook(dolFile, codeHandler)
|
||||
else:
|
||||
return insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
|
||||
|
||||
|
||||
def assert_code_hook(dolFile: DolFile, codeHandler: CodeHandler):
|
||||
for _, address, size in dolFile.textSections:
|
||||
dolFile.seek(address, 0)
|
||||
sample = dolFile.read(size)
|
||||
|
||||
result = sample.find(codeHandler.gcnVIHook)
|
||||
if result >= 0:
|
||||
dolFile.seek(address, 0)
|
||||
dolFile.seek(result, 1)
|
||||
else:
|
||||
result = sample.find(codeHandler.wiiVIHook)
|
||||
if result >= 0:
|
||||
dolFile.seek(address, 0)
|
||||
dolFile.seek(result, 1)
|
||||
else:
|
||||
continue
|
||||
|
||||
sample = tools.read_uint32(dolFile)
|
||||
while sample != 0x4E800020:
|
||||
sample = tools.read_uint32(dolFile)
|
||||
|
||||
dolFile.seek(-4, 1)
|
||||
codeHandler.hookAddress = dolFile.tell()
|
||||
|
||||
return insert_code_hook(dolFile, codeHandler, codeHandler.hookAddress)
|
||||
return False, 'Failed to find a hook address. Try using option --codehook to use your own address\n'
|
||||
|
||||
def insert_code_hook(dolFile: DolFile, codeHandler: CodeHandler, address: int):
|
||||
dolFile.seek(address)
|
||||
|
||||
if tools.read_uint32(dolFile) != 0x4E800020:
|
||||
return False, 'Codehandler hook given is not a blr\n'
|
||||
|
||||
dolFile.seek(-4, 1)
|
||||
dolFile.insert_branch(codeHandler.startAddress, address, lk=0)
|
||||
return True, ''
|
|
@ -24,65 +24,45 @@ except ImportError:
|
|||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
def read_sbyte(f):
|
||||
return struct.unpack("b", f.read(1))[0]
|
||||
def read_sbyte(f): return struct.unpack("b", f.read(1))[0]
|
||||
def write_sbyte(f, val): f.write(struct.pack("b", val))
|
||||
def read_sint16(f): return struct.unpack(">h", f.read(2))[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_ubyte(f): return struct.unpack("B", f.read(1))[0]
|
||||
def write_ubyte(f, val): f.write(struct.pack("B", val))
|
||||
def read_uint16(f): return struct.unpack(">H", f.read(2))[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 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)
|
||||
|
||||
def write_sbyte(f):
|
||||
struct.unpack("b", f.read(1))
|
||||
def get_alignment(number, align: int):
|
||||
if number % align != 0:
|
||||
return align - (number % align)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def read_sint16(f):
|
||||
return struct.unpack(">h", f.read(4))[0]
|
||||
def get_size(f, offset=0):
|
||||
""" Return a stream's size in bytes """
|
||||
f.seek(0, 2)
|
||||
return f.tell() + offset
|
||||
|
||||
def write_sint16(f, val):
|
||||
f.write(struct.pack(">h", val))
|
||||
def get_file_alignment(f, alignment: int):
|
||||
""" Return file alignment, 0 = aligned, non zero = misaligned """
|
||||
return get_alignment(get_size(f), alignment)
|
||||
|
||||
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 align_file(f, alignment: int, fillchar='00'):
|
||||
""" Align a file to be the specified size """
|
||||
f.write(bytes.fromhex(fillchar * get_file_alignment(f, alignment)))
|
||||
|
||||
def color_text(text: str, textToColor: list=[('', None)], defaultColor: str=None):
|
||||
currentColor = None
|
|
@ -1,4 +1,4 @@
|
|||
import requests
|
||||
from urllib import request
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
class Updater:
|
||||
|
@ -10,16 +10,17 @@ class Updater:
|
|||
|
||||
def request_release_data(self):
|
||||
'''Returns "soup" data of the repository releases tab'''
|
||||
return requests.get(self.gitReleases.format(self.owner, self.repo))
|
||||
with request.urlopen(self.gitReleases.format(self.owner, self.repo)) as response:
|
||||
html = response.read()
|
||||
return html
|
||||
|
||||
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')
|
||||
soup = BeautifulSoup(response, '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:
|
||||
except request.HTTPError as e:
|
||||
return f'HTTP request failed with error code ({e.code})', False
|
||||
except request.URLError:
|
||||
return 'Request failed, ensure you have a working internet connection and try again', False
|
Reference in a new issue