1
0
Fork 0

Added simple encryption option, major refactoring

This commit is contained in:
JoshuaMKW 2020-08-23 23:08:44 -05:00
parent aeb152884c
commit d66af69b99
7 changed files with 569 additions and 526 deletions

View file

@ -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)

Binary file not shown.

298
dolreader.py Normal file
View 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)

View file

@ -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)

View file

@ -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, ''

View file

@ -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

View file

@ -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