diff --git a/GeckoLoader.py b/GeckoLoader.py index 6029dad..ea9696a 100644 --- a/GeckoLoader.py +++ b/GeckoLoader.py @@ -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,56 +36,12 @@ 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': @@ -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) diff --git a/bin/geckoloader.bin b/bin/geckoloader.bin index 941791a..d6d57f1 100644 Binary files a/bin/geckoloader.bin and b/bin/geckoloader.bin differ diff --git a/dolreader.py b/dolreader.py new file mode 100644 index 0000000..725fa15 --- /dev/null +++ b/dolreader.py @@ -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(Data, GameAddress or None), tuple(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(Data, GameAddress or None), tuple(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) diff --git a/imports/dolreader.py b/imports/dolreader.py deleted file mode 100644 index 0d2dad7..0000000 --- a/imports/dolreader.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/imports/kernel.py b/kernel.py similarity index 60% rename from imports/kernel.py rename to kernel.py index 575e028..7429582 100644 --- a/imports/kernel.py +++ b/kernel.py @@ -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 - - write_uint32(tmp, codeHandler.allocation) + codeHandler.allocation = (codeHandler.handlerLength + codeHandler.geckoCodes.size + 7) & -8 - elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader - tmp.seek(-4, 1) - write_uint32(tmp, get_size(self._rawData)) + tools.write_uint32(self._rawData, codeHandler.allocation) - 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'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 == 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) - - sample = tmp.read(4) - - gpDiscOffset = get_size(tmp, -4) + tools.write_uint32(self._rawData, codeHandler.hookAddress) - if int(lowerAddr, 16) + gpDiscOffset > 0x7FFF: #Absolute addressing - gpDiscUpperAddr = int(upperAddr, 16) + 1 - else: - gpDiscUpperAddr = int(upperAddr, 16) + elif sample == b'CRPT': #Found keyword "CRPT". Boolean of the encryption + self._rawData.seek(-4, 1) + tools.write_bool(self._rawData, self.encrypt, 4) + + elif sample == b'CYPT': #Found keyword "CYPT". Encryption Key + self._rawData.seek(-4, 1) + + gpKeyOffset = self._rawData.tell() + if lowerAddr + gpKeyOffset > 0x7FFF: #Absolute addressing + gpKeyUpperAddr = upperAddr + 1 + else: + gpKeyUpperAddr = upperAddr + + 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, '' \ No newline at end of file diff --git a/imports/access.py b/tools.py similarity index 56% rename from imports/access.py rename to tools.py index c1b34b3..895628b 100644 --- a/imports/access.py +++ b/tools.py @@ -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 diff --git a/imports/versioncheck.py b/versioncheck.py similarity index 63% rename from imports/versioncheck.py rename to versioncheck.py index 13b8f4a..d7b25e6 100644 --- a/imports/versioncheck.py +++ b/versioncheck.py @@ -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 \ No newline at end of file