2020-08-18 10:38:37 +09:00
import os
2020-08-24 13:08:44 +09:00
import random
2020-08-18 10:38:37 +09:00
import re
2020-08-24 13:08:44 +09:00
import sys
2020-08-18 10:38:37 +09:00
import time
2020-09-26 11:33:17 +09:00
import functools
2020-08-18 10:38:37 +09:00
from io import BytesIO
2020-08-24 13:08:44 +09:00
import tools
2020-09-26 11:33:17 +09:00
from fileutils import *
2020-10-19 17:07:28 +09:00
from dolreader import DolFile , SectionCountFullError , UnmappedAddressError
2020-08-18 10:38:37 +09:00
try :
import chardet
except ImportError as IE :
print ( IE )
sys . exit ( 1 )
2020-09-26 11:33:17 +09:00
def timer ( func ) :
@functools.wraps ( func )
def wrapper ( * args , * * kwargs ) :
start = time . perf_counter ( )
value = func ( * args , * * kwargs )
end = time . perf_counter ( )
print ( tools . color_text ( f ' \n :: Completed in { ( end - start ) : 0.4f } seconds! \n ' , defaultColor = tools . TGREENLIT ) )
return value
2020-09-28 17:02:59 +09:00
return wrapper
class InvalidGeckoCodeError ( Exception ) : pass
2020-08-18 10:38:37 +09:00
2020-09-26 11:33:17 +09:00
class GCT ( object ) :
2020-10-04 16:30:11 +09:00
def __init__ ( self , f ) :
2020-08-18 10:38:37 +09:00
self . codeList = BytesIO ( f . read ( ) )
2020-09-28 17:02:59 +09:00
self . rawLineCount = tools . stream_size ( self . codeList ) >> 3
2020-08-18 10:38:37 +09:00
self . lineCount = self . rawLineCount - 2
2020-09-28 17:02:59 +09:00
self . size = tools . stream_size ( self . codeList )
2020-08-18 10:38:37 +09:00
f . seek ( 0 )
2020-09-26 11:33:17 +09:00
@staticmethod
2020-10-04 16:30:11 +09:00
def determine_codelength ( codetype , info : bytes ) - > int :
2020-08-18 10:38:37 +09:00
if codetype . startswith ( b ' \x06 ' ) :
bytelength = int . from_bytes ( info , byteorder = ' big ' , signed = False )
2020-09-26 11:33:17 +09:00
padding = get_alignment ( bytelength , 8 )
2020-08-18 10:38:37 +09:00
return 0x8 + bytelength + padding
elif ( codetype . startswith ( b ' \x08 ' ) or codetype . startswith ( b ' \x09 ' )
or codetype . startswith ( b ' \x18 ' ) or codetype . startswith ( b ' \x18 ' ) ) :
return 0x16
elif ( codetype . startswith ( b ' \xC2 ' ) or codetype . startswith ( b ' \xC4 ' )
or codetype . startswith ( b ' \xC3 ' ) or codetype . startswith ( b ' \xC5 ' )
or codetype . startswith ( b ' \xD2 ' ) or codetype . startswith ( b ' \xD4 ' )
or codetype . startswith ( b ' \xD3 ' ) or codetype . startswith ( b ' \xD5 ' ) ) :
return 0x8 + ( int . from_bytes ( info , byteorder = ' big ' , signed = False ) << 3 )
elif ( codetype . startswith ( b ' \xF2 ' ) or codetype . startswith ( b ' \xF3 ' )
or codetype . startswith ( b ' \xF4 ' ) or codetype . startswith ( b ' \xF5 ' ) ) :
return 0x8 + ( int . from_bytes ( info [ : 2 ] , byteorder = ' big ' , signed = False ) << 3 )
elif codetype . startswith ( b ' \xF6 ' ) :
return 0x8 + ( int . from_bytes ( info [ : 4 ] , byteorder = ' big ' , signed = False ) << 3 )
else :
return 0x8
def optimize_codelist ( self , dolFile : DolFile ) :
2020-10-04 16:30:11 +09:00
codelist = b ' \x00 \xD0 \xC0 \xDE ' * 2
2020-08-18 10:38:37 +09:00
skipcodes = 0
2020-08-27 15:50:12 +09:00
2020-10-04 16:30:11 +09:00
self . codeList . seek ( 8 )
while codetype := self . codeList . read ( 4 ) :
2020-08-18 10:38:37 +09:00
info = self . codeList . read ( 4 )
address = 0x80000000 | ( int . from_bytes ( codetype , byteorder = ' big ' , signed = False ) & 0x01FFFFFF )
try :
if skipcodes < = 0 :
if ( codetype . startswith ( b ' \x00 ' ) or codetype . startswith ( b ' \x01 ' )
or codetype . startswith ( b ' \x10 ' ) or codetype . startswith ( b ' \x11 ' ) ) :
dolFile . seek ( address )
counter = int . from_bytes ( info [ : - 2 ] , byteorder = ' big ' , signed = False )
value = info [ 2 : ]
while counter + 1 > 0 :
dolFile . write ( value [ 1 : ] )
counter - = 1
continue
elif ( codetype . startswith ( b ' \x02 ' ) or codetype . startswith ( b ' \x03 ' )
or codetype . startswith ( b ' \x12 ' ) or codetype . startswith ( b ' \x13 ' ) ) :
dolFile . seek ( address )
counter = int . from_bytes ( info [ : - 2 ] , byteorder = ' big ' , signed = False )
value = info [ 2 : ]
while counter + 1 > 0 :
dolFile . write ( value )
counter - = 1
continue
elif ( codetype . startswith ( b ' \x04 ' ) or codetype . startswith ( b ' \x05 ' )
or codetype . startswith ( b ' \x14 ' ) or codetype . startswith ( b ' \x15 ' ) ) :
dolFile . seek ( address )
dolFile . write ( info )
continue
elif ( codetype . startswith ( b ' \x06 ' ) or codetype . startswith ( b ' \x07 ' )
or codetype . startswith ( b ' \x16 ' ) or codetype . startswith ( b ' \x17 ' ) ) :
dolFile . seek ( address )
arraylength = int . from_bytes ( info , byteorder = ' big ' , signed = False )
2020-09-26 11:33:17 +09:00
padding = get_alignment ( arraylength , 8 )
2020-08-18 10:38:37 +09:00
while arraylength > 0 :
value = self . codeList . read ( 1 )
dolFile . write ( value )
arraylength - = 1
self . codeList . seek ( padding , 1 )
continue
elif ( codetype . startswith ( b ' \x08 ' ) or codetype . startswith ( b ' \x09 ' )
or codetype . startswith ( b ' \x18 ' ) or codetype . startswith ( b ' \x19 ' ) ) :
dolFile . seek ( address )
value = int . from_bytes ( info , byteorder = ' big ' , signed = False )
2020-09-28 17:02:59 +09:00
data = read_uint16 ( self . codeList )
size = data & 0x3000
counter = data & 0xFFF
2020-09-26 11:33:17 +09:00
address_increment = read_uint16 ( self . codeList )
value_increment = read_uint32 ( self . codeList )
2020-08-18 10:38:37 +09:00
while counter + 1 > 0 :
if size == 0 :
2020-09-26 11:33:17 +09:00
write_ubyte ( dolFile , value )
2020-08-18 10:38:37 +09:00
dolFile . seek ( - 1 , 1 )
elif size == 1 :
2020-09-26 11:33:17 +09:00
write_uint16 ( dolFile , value )
2020-08-18 10:38:37 +09:00
dolFile . seek ( - 2 , 1 )
elif size == 2 :
2020-09-26 11:33:17 +09:00
write_uint32 ( dolFile , value )
2020-08-18 10:38:37 +09:00
dolFile . seek ( - 4 , 1 )
else :
raise ValueError ( ' Size type {} does not match 08 codetype specs ' . format ( size ) )
dolFile . seek ( address_increment , 1 )
value + = value_increment
counter - = 1
if value > 0xFFFFFFFF :
value - = 0x100000000
continue
elif ( codetype . startswith ( b ' \xC6 ' ) or codetype . startswith ( b ' \xC7 ' )
or codetype . startswith ( b ' \xC6 ' ) or codetype . startswith ( b ' \xC7 ' ) ) :
dolFile . seek ( address )
dolFile . insertBranch ( int . from_bytes ( info , byteorder = ' big ' , signed = False ) , dolFile . tell ( ) )
continue
if codetype . hex ( ) . startswith ( ' 2 ' ) or codetype . hex ( ) . startswith ( ' 3 ' ) :
skipcodes + = 1
elif codetype . startswith ( b ' \xE0 ' ) :
skipcodes - = 1
elif codetype . startswith ( b ' \xF0 ' ) :
codelist + = b ' \xF0 \x00 \x00 \x00 \x00 \x00 \x00 \x00 '
break
2020-10-04 16:30:11 +09:00
self . codeList . seek ( - 8 , 1 ) * 5
2020-09-26 11:33:17 +09:00
length = GCT . determine_codelength ( codetype , info )
2020-08-18 10:38:37 +09:00
while length > 0 :
codelist + = self . codeList . read ( 1 )
length - = 1
2020-10-19 17:07:28 +09:00
except ( RuntimeError , UnmappedAddressError ) :
2020-08-18 10:38:37 +09:00
self . codeList . seek ( - 8 , 1 )
2020-09-26 11:33:17 +09:00
length = GCT . determine_codelength ( codetype , info )
2020-10-04 05:25:18 +09:00
codelist + = self . codeList . read ( length )
2020-08-18 10:38:37 +09:00
self . codeList = BytesIO ( codelist )
2020-09-26 11:33:17 +09:00
self . size = len ( self . codeList . getbuffer ( ) )
2020-08-18 10:38:37 +09:00
2020-09-26 11:33:17 +09:00
class CodeHandler ( object ) :
2020-08-18 10:38:37 +09:00
def __init__ ( self , f ) :
2020-08-19 20:21:15 +09:00
self . _rawData = BytesIO ( f . read ( ) )
2020-08-18 10:38:37 +09:00
''' Get codelist pointer '''
2020-09-28 17:02:59 +09:00
self . _rawData . seek ( 0xFA )
codelistUpper = self . _rawData . read ( 2 ) . hex ( )
self . _rawData . seek ( 0xFE )
codelistLower = self . _rawData . read ( 2 ) . hex ( )
2020-08-18 10:38:37 +09:00
self . codeListPointer = int ( codelistUpper [ 2 : ] + codelistLower [ 2 : ] , 16 )
2020-09-28 17:02:59 +09:00
self . handlerLength = tools . stream_size ( self . _rawData )
2020-08-18 10:38:37 +09:00
self . initAddress = 0x80001800
self . startAddress = 0x800018A8
2020-08-24 19:10:23 +09:00
2020-08-24 13:08:44 +09:00
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 '
2020-08-24 19:10:23 +09:00
self . wiiGXDrawHook = b ' \x3C \xA0 \xCC \x01 \x38 \x00 \x00 \x61 \x3C \x80 \x45 \x00 \x98 \x05 \x80 \x00 '
self . gcnGXDrawHook = b ' \x38 \x00 \x00 \x61 \x3C \xA0 \xCC \x01 \x3C \x80 \x45 \x00 \x98 \x05 \x80 \x00 '
self . wiiPADHook = b ' \x3A \xB5 \x00 \x01 \x3A \x73 \x00 \x0C \x2C \x15 \x00 \x04 \x3B \x18 \x00 \x0C '
self . gcnPADHook = b ' \x3A \xB5 \x00 \x01 \x2C \x15 \x00 \x04 \x3B \x18 \x00 \x0C \x3B \xFF \x00 \x0C '
2020-08-18 10:38:37 +09:00
self . allocation = None
self . hookAddress = None
2020-08-24 19:10:23 +09:00
self . hookType = None
2020-08-18 10:38:37 +09:00
self . geckoCodes = None
self . includeAll = False
self . optimizeList = False
if self . handlerLength < 0x900 :
self . type = " Mini "
else :
self . type = " Full "
f . seek ( 0 )
2020-10-20 13:15:33 +09:00
def init_gct ( self , gctFile : str , tmpdir : str = " " ) :
if ' . ' in gctFile :
if os . path . splitext ( gctFile ) [ 1 ] . lower ( ) == ' .txt ' :
with open ( os . path . join ( tmpdir , ' gct.bin ' ) , ' wb+ ' ) as temp :
temp . write ( bytes . fromhex ( ' 00D0C0DE ' * 2 + self . parse_input ( gctFile ) + ' F000000000000000 ' ) )
temp . seek ( 0 )
self . geckoCodes = GCT ( temp )
elif os . path . splitext ( gctFile ) [ 1 ] . lower ( ) == ' .gct ' :
with open ( gctFile , ' rb ' ) as gct :
self . geckoCodes = GCT ( gct )
else :
with open ( os . path . join ( tmpdir , ' gct.bin ' ) , ' wb+ ' ) as temp :
temp . write ( b ' \x00 \xD0 \xC0 \xDE ' * 2 )
for file in os . listdir ( gctFile ) :
if os . path . isfile ( os . path . join ( gctFile , file ) ) :
if os . path . splitext ( file ) [ 1 ] . lower ( ) == ' .txt ' :
temp . write ( bytes . fromhex ( self . parse_input ( os . path . join ( gctFile , file ) ) ) )
elif os . path . splitext ( file ) [ 1 ] . lower ( ) == ' .gct ' :
with open ( os . path . join ( gctFile , file ) , ' rb ' ) as gct :
temp . write ( gct . read ( ) [ 8 : - 8 ] )
else :
print ( tools . color_text ( f ' :: HINT: { file } is not a .txt or .gct file ' , defaultColor = tools . TYELLOWLIT ) )
temp . write ( b ' \xF0 \x00 \x00 \x00 \x00 \x00 \x00 \x00 ' )
temp . seek ( 0 )
self . geckoCodes = GCT ( temp )
2020-10-04 16:30:11 +09:00
def parse_input ( self , geckoText ) - > str :
2020-08-18 10:38:37 +09:00
with open ( r ' {} ' . format ( geckoText ) , ' rb ' ) as gecko :
result = chardet . detect ( gecko . read ( ) )
encodeType = result [ ' encoding ' ]
with open ( r ' {} ' . format ( geckoText ) , ' r ' , encoding = encodeType ) as gecko :
geckoCodes = ' '
state = None
for line in gecko . readlines ( ) :
if line in ( ' ' , ' \n ' ) :
continue
if state is None :
2020-08-24 19:10:23 +09:00
if line . startswith ( ' $ ' ) or line . startswith ( ' [ ' ) :
2020-08-18 10:38:37 +09:00
state = ' Dolphin '
else :
state = ' OcarinaM '
try :
if state == ' OcarinaM ' :
2020-08-25 21:06:56 +09:00
if self . includeAll :
2020-08-18 10:38:37 +09:00
geckoLine = re . findall ( r ' [A-F0-9] {8} [ \ t \ f ][A-F0-9] {8} ' , line , re . IGNORECASE ) [ 0 ]
else :
geckoLine = re . findall ( r ' (?: \ * \ s*)([A-F0-9] {8} [ \ t \ f ][A-F0-9] {8} ) ' , line , re . IGNORECASE ) [ 0 ]
else :
geckoLine = re . findall ( r ' (?<![$ \ *])[A-F0-9] {8} [ \ t \ f ][A-F0-9] {8} ' , line , re . IGNORECASE ) [ 0 ]
except IndexError :
continue
geckoCodes + = geckoLine . replace ( ' ' , ' ' ) . strip ( )
return geckoCodes
2020-09-26 11:33:17 +09:00
@staticmethod
2020-09-28 17:02:59 +09:00
def encrypt_key ( key : int ) - > int :
2020-08-24 13:08:44 +09:00
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
2020-10-04 16:30:11 +09:00
def encrypt_codes ( self , key : int ) :
2020-08-24 13:08:44 +09:00
self . geckoCodes . codeList . seek ( 0 )
i = 0
while True :
try :
2020-09-26 11:33:17 +09:00
packet = read_uint32 ( self . geckoCodes . codeList )
2020-08-24 13:08:44 +09:00
self . geckoCodes . codeList . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_uint32 ( self . geckoCodes . codeList , ( packet ^ key ) & 0xFFFFFFFF )
2020-08-24 19:24:50 +09:00
key + = ( i << 3 ) & 0xFFFFFFFF
2020-08-24 13:08:44 +09:00
if key > 0xFFFFFFFF :
key - = 0x100000000
i + = 1
except :
break
2020-09-28 17:02:59 +09:00
def find_variable_data ( self , variable ) - > int :
2020-08-25 18:28:22 +09:00
self . _rawData . seek ( 0 )
2020-09-26 11:33:17 +09:00
if self . _rawData . read ( 4 ) == variable :
2020-08-25 18:28:22 +09:00
return self . _rawData . tell ( ) - 4
2020-09-26 11:33:17 +09:00
while sample := self . _rawData . read ( 4 ) :
if sample == variable :
2020-08-25 18:28:22 +09:00
return self . _rawData . tell ( ) - 4
return None
def set_hook_instruction ( self , dolFile : DolFile , address : int , varOffset : int , lk = 0 ) :
self . _rawData . seek ( varOffset )
dolFile . seek ( address )
2020-09-26 11:33:17 +09:00
ppc = read_uint32 ( dolFile )
2020-08-25 18:28:22 +09:00
2020-10-19 17:07:28 +09:00
if ( ( ( ( ppc >> 24 ) & 0xFF ) > 0x47 and ( ( ppc >> 24 ) & 0xFF ) < 0x4C ) or ( ( ( ppc >> 24 ) & 0xFF ) > 0x3F and ( ( ppc >> 24 ) & 0xFF ) < 0x44 ) ) :
to , conditional = dolFile . extract_branch_addr ( address )
if conditional :
raise NotImplementedError ( " Hooking to a conditional non spr branch is unsupported " )
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , ( to - ( self . initAddress + varOffset ) ) & 0x3FFFFFD | 0x48000000 | lk )
2020-08-25 18:28:22 +09:00
else :
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , ppc )
2020-08-25 18:28:22 +09:00
def set_variables ( self , dolFile : DolFile ) :
2020-09-26 11:33:17 +09:00
varOffset = self . find_variable_data ( b ' \x00 \xDE \xDE \xDE ' )
2020-08-25 18:28:22 +09:00
if varOffset is None :
2020-10-04 16:30:11 +09:00
raise RuntimeError ( tools . color_text ( " Variable codehandler data not found \n " , defaultColor = tools . TREDLIT ) )
2020-08-25 18:28:22 +09:00
self . set_hook_instruction ( dolFile , self . hookAddress , varOffset , 0 )
self . _rawData . seek ( varOffset + 4 )
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , ( ( self . hookAddress + 4 ) - ( self . initAddress + ( varOffset + 4 ) ) ) & 0x3FFFFFD | 0x48000000 | 0 )
2020-08-25 18:28:22 +09:00
2020-09-26 11:33:17 +09:00
class KernelLoader ( object ) :
2020-08-18 10:38:37 +09:00
2020-10-04 05:25:18 +09:00
def __init__ ( self , f , cli : tools . CommandLineParser = None ) :
2020-08-19 20:21:15 +09:00
self . _rawData = BytesIO ( f . read ( ) )
2020-09-28 17:02:59 +09:00
self . _initDataList = None
self . _gpModDataList = None
self . _gpDiscDataList = None
self . _gpKeyAddrList = None
2020-10-04 05:25:18 +09:00
self . _cli = cli
2020-09-28 17:02:59 +09:00
self . patchJob = None
2020-08-18 10:38:37 +09:00
self . initAddress = None
2020-08-18 12:23:10 +09:00
self . protect = False
2020-08-18 10:38:37 +09:00
self . verbosity = 0
self . quiet = False
2020-08-24 13:08:44 +09:00
self . encrypt = False
2020-08-18 10:38:37 +09:00
2020-10-04 05:25:18 +09:00
def error ( self , msg : str , exit = True ) :
if self . _cli is not None :
self . _cli . error ( msg , exit )
else :
print ( msg )
if exit :
sys . exit ( 1 )
2020-08-24 13:08:44 +09:00
def set_variables ( self , entryPoint : list , baseOffset : int = 0 ) :
self . _rawData . seek ( 0 )
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
if self . _gpModDataList is None :
return
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
while sample := self . _rawData . read ( 2 ) :
2020-08-24 13:08:44 +09:00
if sample == b ' GH ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-28 17:02:59 +09:00
write_uint16 ( self . _rawData , self . _gpModDataList [ 0 ] )
2020-08-24 13:08:44 +09:00
elif sample == b ' GL ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-28 17:02:59 +09:00
write_uint16 ( self . _rawData , baseOffset + self . _gpModDataList [ 1 ] )
2020-08-24 13:08:44 +09:00
elif sample == b ' IH ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-26 11:33:17 +09:00
write_uint16 ( self . _rawData , entryPoint [ 0 ] )
2020-08-24 13:08:44 +09:00
elif sample == b ' IL ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-26 11:33:17 +09:00
write_uint16 ( self . _rawData , entryPoint [ 1 ] )
2020-08-24 13:08:44 +09:00
elif sample == b ' KH ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-28 17:02:59 +09:00
write_uint16 ( self . _rawData , self . _gpKeyAddrList [ 0 ] )
2020-08-24 13:08:44 +09:00
elif sample == b ' KL ' :
self . _rawData . seek ( - 2 , 1 )
2020-09-28 17:02:59 +09:00
write_uint16 ( self . _rawData , baseOffset + self . _gpKeyAddrList [ 1 ] )
2020-08-24 13:08:44 +09:00
def complete_data ( self , codeHandler : CodeHandler , initpoint : list ) :
2020-09-28 17:02:59 +09:00
_upperAddr , _lowerAddr = ( ( self . initAddress >> 16 ) & 0xFFFF , self . initAddress & 0xFFFF )
_key = random . randrange ( 0x100000000 )
2020-08-24 13:08:44 +09:00
self . _rawData . seek ( 0 )
2020-09-28 17:02:59 +09:00
while sample := self . _rawData . read ( 4 ) :
2020-08-24 13:08:44 +09:00
if sample == b ' HEAP ' : #Found keyword "HEAP". Goes with the resize of the heap
self . _rawData . seek ( - 4 , 1 )
2020-08-19 20:21:15 +09:00
2020-08-24 13:08:44 +09:00
gpModInfoOffset = self . _rawData . tell ( )
2020-09-28 17:02:59 +09:00
if _lowerAddr + gpModInfoOffset > 0x7FFF : #Absolute addressing
gpModUpperAddr = _upperAddr + 1
2020-08-18 10:38:37 +09:00
else :
2020-09-28 17:02:59 +09:00
gpModUpperAddr = _upperAddr
2020-08-19 20:21:15 +09:00
if codeHandler . allocation == None :
2020-08-24 13:08:44 +09:00
codeHandler . allocation = ( codeHandler . handlerLength + codeHandler . geckoCodes . size + 7 ) & - 8
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , codeHandler . allocation )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
elif sample == b ' LSIZ ' : #Found keyword "LSIZ". Goes with the size of the loader
self . _rawData . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , len ( self . _rawData . getbuffer ( ) ) )
2020-08-18 10:38:37 +09:00
2020-10-19 17:07:28 +09:00
elif sample == b ' HSIZ ' : #Found keyword "HSIZ". Goes with the size of the codehandler
2020-08-24 13:08:44 +09:00
self . _rawData . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_sint32 ( self . _rawData , codeHandler . handlerLength )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
elif sample == b ' CSIZ ' : #Found keyword "CSIZ". Goes with the size of the codes
self . _rawData . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_sint32 ( self . _rawData , codeHandler . geckoCodes . size )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
elif sample == b ' HOOK ' : #Found keyword "HOOK". Goes with the codehandler hook
self . _rawData . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_uint32 ( self . _rawData , codeHandler . hookAddress )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
elif sample == b ' CRPT ' : #Found keyword "CRPT". Boolean of the encryption
self . _rawData . seek ( - 4 , 1 )
2020-09-26 11:33:17 +09:00
write_bool ( self . _rawData , self . encrypt , 4 )
2020-08-24 13:08:44 +09:00
elif sample == b ' CYPT ' : #Found keyword "CYPT". Encryption Key
self . _rawData . seek ( - 4 , 1 )
gpKeyOffset = self . _rawData . tell ( )
2020-09-28 17:02:59 +09:00
if _lowerAddr + gpKeyOffset > 0x7FFF : #Absolute addressing
gpKeyUpperAddr = _upperAddr + 1
2020-08-24 13:08:44 +09:00
else :
2020-09-28 17:02:59 +09:00
gpKeyUpperAddr = _upperAddr
2020-08-24 13:08:44 +09:00
2020-09-28 17:02:59 +09:00
write_uint32 ( self . _rawData , CodeHandler . encrypt_key ( _key ) )
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
self . _gpModDataList = ( gpModUpperAddr , gpModInfoOffset )
self . _gpKeyAddrList = ( gpKeyUpperAddr , gpKeyOffset )
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
self . set_variables ( initpoint , _lowerAddr )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
if self . encrypt :
2020-10-04 16:30:11 +09:00
codeHandler . encrypt_codes ( _key )
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
def patch_arena ( self , codeHandler : CodeHandler , dolFile : DolFile ) :
self . complete_data ( codeHandler , [ ( dolFile . entryPoint >> 16 ) & 0xFFFF , dolFile . entryPoint & 0xFFFF ] )
2020-08-19 20:21:15 +09:00
2020-08-24 13:08:44 +09:00
self . _rawData . seek ( 0 , 2 )
self . _rawData . write ( codeHandler . _rawData . getvalue ( ) + codeHandler . geckoCodes . codeList . getvalue ( ) )
2020-08-19 20:21:15 +09:00
2020-08-24 13:08:44 +09:00
self . _rawData . seek ( 0 )
2020-09-28 17:02:59 +09:00
_kernelData = self . _rawData . getvalue ( )
2020-08-18 10:38:37 +09:00
2020-10-04 05:25:18 +09:00
try :
dolFile . append_text_sections ( [ ( _kernelData , self . initAddress ) ] )
except SectionCountFullError :
try :
dolFile . append_data_sections ( [ ( _kernelData , self . initAddress ) ] )
except SectionCountFullError :
2020-10-04 16:30:11 +09:00
self . error ( tools . color_text ( ' There are no unused sections left for GeckoLoader to use! \n ' , defaultColor = tools . TREDLIT ) )
2020-09-28 17:02:59 +09:00
dolFile . entryPoint = self . initAddress
2020-08-18 10:38:37 +09:00
2020-08-24 13:08:44 +09:00
def patch_legacy ( self , codeHandler : CodeHandler , dolFile : DolFile ) :
2020-08-19 20:21:15 +09:00
codeHandler . _rawData . seek ( 0 )
codeHandler . geckoCodes . codeList . seek ( 0 )
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
_handlerData = codeHandler . _rawData . getvalue ( ) + codeHandler . geckoCodes . codeList . getvalue ( )
2020-08-18 10:38:37 +09:00
2020-10-04 05:25:18 +09:00
try :
dolFile . append_text_sections ( [ ( _handlerData , codeHandler . initAddress ) ] )
except SectionCountFullError :
try :
dolFile . append_data_sections ( [ ( _handlerData , codeHandler . initAddress ) ] )
except SectionCountFullError :
2020-10-04 16:30:11 +09:00
self . error ( tools . color_text ( ' There are no unused sections left for GeckoLoader to use! \n ' , defaultColor = tools . TREDLIT ) )
2020-08-18 10:38:37 +09:00
2020-08-19 20:21:15 +09:00
def protect_game ( self , codeHandler : CodeHandler ) :
2020-09-28 17:02:59 +09:00
_oldpos = codeHandler . geckoCodes . codeList . tell ( )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
protectdata = ( b ' \xC0 \x00 \x00 \x00 \x00 \x00 \x00 \x17 ' ,
2020-08-24 13:08:44 +09:00
b ' \x7C \x08 \x02 \xA6 \x94 \x21 \xFF \x70 ' ,
2020-08-18 10:38:37 +09:00
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 ' ,
2020-08-24 13:08:44 +09:00
b ' \x3B \xDF \x00 \x08 \x3C \x60 \x80 \x00 ' ,
2020-08-18 10:38:37 +09:00
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 ' ,
b ' \x54 \xE9 \x06 \x3E \x89 \x08 \x00 \x08 ' ,
b ' \x7D \x3F \x48 \xAE \x38 \xE7 \x00 \x01 ' ,
b ' \x7C \x08 \x48 \x40 \x41 \x82 \x00 \x0C ' ,
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 ' ,
2020-08-24 13:08:44 +09:00
b ' \x42 \x00 \xFF \xCC \x48 \x00 \x00 \x2C ' ,
2020-08-18 10:38:37 +09:00
b ' \x38 \xA0 \x00 \x08 \x7C \x84 \x1A \x14 ' ,
b ' \x7C \xA9 \x03 \xA6 \x38 \x60 \x00 \x00 ' ,
2020-08-24 13:08:44 +09:00
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 ' ,
2020-08-18 10:38:37 +09:00
b ' \xB8 \x61 \x00 \x0C \x80 \x01 \x00 \x08 ' ,
2020-08-24 13:08:44 +09:00
b ' \x38 \x21 \x00 \x90 \x7C \x08 \x03 \xA6 ' ,
2020-10-04 16:30:11 +09:00
b ' \x4E \x80 \x00 \x20 \x00 \x00 \x00 \x00 ' )
2020-08-18 10:38:37 +09:00
2020-08-19 20:21:15 +09:00
codeHandler . geckoCodes . codeList . seek ( - 8 , 2 )
2020-08-18 10:38:37 +09:00
for chunk in protectdata :
2020-08-19 20:21:15 +09:00
codeHandler . geckoCodes . codeList . write ( chunk )
2020-08-24 13:08:44 +09:00
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 ( )
2020-09-28 17:02:59 +09:00
codeHandler . geckoCodes . codeList . seek ( _oldpos )
2020-08-18 10:38:37 +09:00
2020-09-26 11:33:17 +09:00
@timer
2020-10-04 05:25:18 +09:00
def build ( self , gctFile , dolFile : DolFile , codeHandler : CodeHandler , tmpdir , dump ) :
2020-10-04 16:30:11 +09:00
oldStart = dolFile . entryPoint
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
''' Initialize our codes '''
2020-10-20 13:15:33 +09:00
codeHandler . init_gct ( gctFile , tmpdir )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
if codeHandler . geckoCodes is None :
self . error ( tools . color_text ( ' Valid codelist not found. Please provide a .txt/.gct file, or a folder of .txt/.gct files \n ' , defaultColor = tools . TREDLIT ) )
2020-08-18 12:23:10 +09:00
2020-10-04 16:30:11 +09:00
if self . protect and self . patchJob == " ARENA " :
self . protect_game ( codeHandler )
if self . patchJob == ' AUTO ' :
if codeHandler . initAddress + codeHandler . handlerLength + codeHandler . geckoCodes . size > 0x80002FFF :
self . patchJob = ' ARENA '
else :
self . patchJob = ' LEGACY '
2020-08-18 12:23:10 +09:00
2020-10-04 16:30:11 +09:00
''' Get entrypoint (or BSS midpoint) for insert '''
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
if self . initAddress :
try :
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 = dolFile . seek_nearest_unmapped ( dolFile . bssAddress , len ( self . _rawData . getbuffer ( ) ) + codeHandler . handlerLength + codeHandler . geckoCodes . size )
self . _rawData . seek ( 0 )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
if codeHandler . optimizeList :
codeHandler . geckoCodes . optimize_codelist ( dolFile )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
''' Is codelist optimized away? '''
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
if codeHandler . geckoCodes . codeList . getvalue ( ) == b ' \x00 \xD0 \xC0 \xDE \x00 \xD0 \xC0 \xDE \xF0 \x00 \x00 \x00 \x00 \x00 \x00 \x00 ' :
with open ( dump , ' wb ' ) as final :
2020-08-18 10:38:37 +09:00
dolFile . save ( final )
2020-10-04 16:30:11 +09:00
if self . quiet :
2020-08-18 10:38:37 +09:00
return
2020-10-04 16:30:11 +09:00
if self . verbosity > = 3 :
dolFile . print_info ( )
print ( ' - ' * 64 )
if self . verbosity > = 1 :
print ( tools . color_text ( ' \n :: All codes have been successfully pre patched ' , defaultColor = tools . TGREENLIT ) )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
return
if self . patchJob == ' LEGACY ' :
2020-10-19 17:07:28 +09:00
legacy = True
2020-10-04 16:30:11 +09:00
codeHandler . allocation = 0x80003000 - ( codeHandler . initAddress + codeHandler . handlerLength )
hooked = determine_codehook ( dolFile , codeHandler , True )
2020-10-19 17:07:28 +09:00
if hooked :
self . patch_legacy ( codeHandler , dolFile )
2020-10-04 16:30:11 +09:00
else :
legacy = False
2020-10-19 17:07:28 +09:00
hooked = determine_codehook ( dolFile , codeHandler , False )
if hooked :
self . patch_arena ( codeHandler , dolFile )
2020-10-04 16:30:11 +09:00
if not hooked :
self . error ( tools . color_text ( ' Failed to find a hook address. Try using option --codehook to use your own address \n ' , defaultColor = tools . TREDLIT ) )
elif codeHandler . allocation < codeHandler . geckoCodes . size :
self . error ( tools . color_text ( ' \n :: Error: Allocated codespace was smaller than the given codelist. \n ' , defaultColor = tools . TYELLOW ) )
2020-08-24 19:10:23 +09:00
2020-10-04 16:30:11 +09:00
with open ( dump , ' wb ' ) as final :
2020-09-28 17:02:59 +09:00
dolFile . save ( final )
2020-10-04 16:30:11 +09:00
if self . quiet :
return
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
if codeHandler . allocation > 0x70000 :
print ( tools . color_text ( f ' \n :: WARNING: Allocations beyond 0x70000 will crash certain games. You allocated 0x { codeHandler . allocation : X } ' , defaultColor = tools . TYELLOW ) )
2020-08-18 10:38:37 +09:00
2020-10-04 16:30:11 +09:00
elif codeHandler . allocation > 0x40000 :
print ( tools . color_text ( f ' \n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x { codeHandler . allocation : X } ' , defaultColor = tools . TYELLOWLIT ) )
if self . verbosity > = 3 :
dolFile . print_info ( )
print ( ' - ' * 64 )
if self . verbosity > = 2 :
print ( ' ' )
if legacy == False :
info = [ f ' :: Start of game modified to address 0x { self . initAddress : X } ' ,
f ' :: Game function " __start() " located at address 0x { oldStart : X } ' ,
f ' :: Allocation is 0x { codeHandler . allocation : X } ; codelist size is 0x { codeHandler . geckoCodes . size : X } ' ,
f ' :: Codehandler hooked at 0x { codeHandler . hookAddress : X } ' ,
f ' :: Codehandler is of type " { codeHandler . type } " ' ,
f ' :: Of the 7 text sections in this DOL file, { len ( dolFile . textSections ) } are now being used ' ]
else :
info = [ f ' :: Game function " __start() " located at address 0x { oldStart : X } ' ,
f ' :: Allocation is 0x { codeHandler . allocation : X } ; codelist size is 0x { codeHandler . geckoCodes . size : X } ' ,
f ' :: Codehandler hooked at 0x { codeHandler . hookAddress : X } ' ,
f ' :: Codehandler is of type " { codeHandler . type } " ' ,
f ' :: Of the 7 text sections in this DOL file, { len ( dolFile . textSections ) } are now being used ' ]
for bit in info :
print ( tools . color_text ( bit , defaultColor = tools . TGREENLIT ) )
elif self . verbosity > = 1 :
print ( ' ' )
info = [ f ' :: GeckoLoader set at address 0x { self . initAddress : X } ' ,
f ' :: Legacy size is 0x { codeHandler . allocation : X } ; codelist size is 0x { codeHandler . geckoCodes . size : X } ' ,
f ' :: Codehandler is of type " { codeHandler . type } " ' ]
for bit in info :
print ( tools . color_text ( bit , defaultColor = tools . TGREENLIT ) )
2020-08-24 13:08:44 +09:00
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 )
2020-08-24 19:10:23 +09:00
def determine_codehook ( dolFile : DolFile , codeHandler : CodeHandler , hook = False ) :
2020-10-04 05:25:18 +09:00
if codeHandler . hookAddress is None :
2020-08-24 19:10:23 +09:00
if not assert_code_hook ( dolFile , codeHandler ) :
return False
if hook :
2020-10-19 17:07:28 +09:00
codeHandler . set_variables ( dolFile )
2020-08-24 19:10:23 +09:00
insert_code_hook ( dolFile , codeHandler , codeHandler . hookAddress )
2020-10-20 13:15:33 +09:00
2020-08-24 19:10:23 +09:00
return True
2020-08-24 13:08:44 +09:00
2020-08-24 19:13:09 +09:00
def assert_code_hook ( dolFile : DolFile , codeHandler : CodeHandler ) :
2020-09-29 03:54:19 +09:00
for _ , address , size , _ , _ in dolFile . textSections :
2020-09-28 17:02:59 +09:00
dolFile . seek ( address )
2020-08-24 13:08:44 +09:00
sample = dolFile . read ( size )
2020-08-24 19:10:23 +09:00
if codeHandler . hookType == ' VI ' :
result = sample . find ( codeHandler . gcnVIHook )
elif codeHandler . hookType == ' GX ' :
result = sample . find ( codeHandler . gcnGXDrawHook )
elif codeHandler . hookType == ' PAD ' :
result = sample . find ( codeHandler . gcnPADHook )
else :
2020-08-25 18:28:22 +09:00
raise NotImplementedError ( tools . color_text ( f ' Unsupported hook type specified ( { codeHandler . hookType } ) ' , defaultColor = tools . TREDLIT ) )
2020-08-24 19:10:23 +09:00
2020-08-24 13:08:44 +09:00
if result > = 0 :
2020-09-28 17:02:59 +09:00
dolFile . seek ( address + result )
2020-08-24 13:08:44 +09:00
else :
2020-08-24 19:10:23 +09:00
if codeHandler . hookType == ' VI ' :
result = sample . find ( codeHandler . wiiVIHook )
elif codeHandler . hookType == ' GX ' :
result = sample . find ( codeHandler . wiiGXDrawHook )
elif codeHandler . hookType == ' PAD ' :
result = sample . find ( codeHandler . wiiPADHook )
else :
2020-08-25 18:28:22 +09:00
raise NotImplementedError ( tools . color_text ( f ' Unsupported hook type specified ( { codeHandler . hookType } ) ' , defaultColor = tools . TREDLIT ) )
2020-08-24 19:10:23 +09:00
2020-08-24 13:08:44 +09:00
if result > = 0 :
2020-09-28 17:02:59 +09:00
dolFile . seek ( address + result )
2020-08-24 13:08:44 +09:00
else :
continue
2020-09-28 17:02:59 +09:00
while ( sample := read_uint32 ( dolFile ) ) != 0x4E800020 :
pass
2020-08-24 13:08:44 +09:00
dolFile . seek ( - 4 , 1 )
codeHandler . hookAddress = dolFile . tell ( )
2020-08-24 19:10:23 +09:00
return True
return False
2020-08-24 13:08:44 +09:00
def insert_code_hook ( dolFile : DolFile , codeHandler : CodeHandler , address : int ) :
dolFile . seek ( address )
2020-09-26 11:33:17 +09:00
ppc = read_uint32 ( dolFile )
2020-08-25 18:28:22 +09:00
if ( ( ppc >> 24 ) & 0xFF ) > 0x3F and ( ( ppc >> 24 ) & 0xFF ) < 0x48 :
2020-10-04 05:25:18 +09:00
raise NotImplementedError ( tools . color_text ( ' Hooking the codehandler to a conditional non spr branch is unsupported ' , defaultColor = tools . TREDLIT ) )
2020-08-25 18:28:22 +09:00
dolFile . seek ( - 4 , 1 )
2020-08-24 19:10:23 +09:00
dolFile . insert_branch ( codeHandler . startAddress , address , lk = 0 )