Added txt parser, rewrote most code
This commit is contained in:
parent
abddea6916
commit
d94c36e4ff
1 changed files with 187 additions and 155 deletions
342
main.py
342
main.py
|
@ -1,14 +1,76 @@
|
|||
#Written by JoshuaMK 2020
|
||||
|
||||
import sys, os, time
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
|
||||
try:
|
||||
import argparse
|
||||
import chardet
|
||||
except ImportError as IE:
|
||||
print(IE)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
from colorama import Fore, Style
|
||||
colorama.init()
|
||||
TRESET = Style.RESET_ALL
|
||||
TGREEN = Fore.GREEN
|
||||
TGREENLIT = Style.BRIGHT + Fore.GREEN
|
||||
TYELLOW = Fore.YELLOW
|
||||
TYELLOWLIT = Style.BRIGHT + Fore.YELLOW
|
||||
TRED = Fore.RED
|
||||
TREDLIT = Style.BRIGHT + Fore.RED
|
||||
|
||||
except ImportError:
|
||||
TRESET = ''
|
||||
TGREEN = ''
|
||||
TGREENLIT = ''
|
||||
TYELLOW = ''
|
||||
TYELLOWLIT = ''
|
||||
TRED = ''
|
||||
TREDLIT = ''
|
||||
|
||||
|
||||
|
||||
def resource_path(relative_path):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
def build(gct, dol, size):
|
||||
with open(resource_path('sme-code.bin'), 'rb') as code, open(dol, 'rb+') as dol, open(gct, 'rb') as gecko, open(resource_path('codehandler.bin'), 'rb') as handler, open('tmp.bin', 'wb+') as tmp, open(os.path.join('BUILD', os.path.split(r'{}'.format(dol))[1][:-2]), 'wb+') as final:
|
||||
def geckoParser(geckoText, parseAll):
|
||||
|
||||
geckoMagic = '00D0C0DE00D0C0DE'
|
||||
geckoTerminate = 'F000000000000000'
|
||||
with open(geckoText, 'rb') as gecko:
|
||||
result = chardet.detect(gecko.read())
|
||||
encodeType = result['encoding']
|
||||
|
||||
with open(geckoText, 'r', encoding=encodeType) as gecko:
|
||||
data = gecko.readlines()
|
||||
geckoCodes = ''
|
||||
|
||||
for line in data:
|
||||
if parseAll.lower() == 'all':
|
||||
geckoLine = re.findall(r'[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
elif parseAll.lower() == 'active':
|
||||
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
else:
|
||||
geckoLine = re.findall(r'\*\s[A-F0-9]{8}\s[A-F0-9]{8}', line, re.IGNORECASE)
|
||||
|
||||
geckoLine = ''.join(geckoLine)
|
||||
geckoLine = re.sub(r'\s+', '', geckoLine)
|
||||
geckoCodes = geckoCodes + geckoLine.replace('*', '')
|
||||
|
||||
geckoCodes = geckoMagic + geckoCodes + geckoTerminate
|
||||
geckoSize = '{:08X}'.format(len(bytes.fromhex(geckoCodes))).lstrip('0')
|
||||
|
||||
return [bytes.fromhex(geckoCodes), geckoSize]
|
||||
|
||||
def build(gctFile, dolFile, size, isText):
|
||||
with open(resource_path('sme-code.bin'), 'rb') as code, open(dolFile, 'rb+') as dol, open(gctFile, 'rb') as gecko, open(resource_path('codehandler.bin'), 'rb') as handler, open('tmp.bin', 'wb+') as tmp, open(os.path.join('BUILD', dolFile), 'wb+') as final:
|
||||
'''Initialize the new DOL file'''
|
||||
|
||||
final.write(dol.read())
|
||||
|
@ -48,52 +110,43 @@ def build(gct, dol, size):
|
|||
'''Get code initialize address'''
|
||||
|
||||
final.seek(int('E0', 16))
|
||||
_init = final.read(4)
|
||||
_init = [final.read(2), final.read(2)]
|
||||
|
||||
'''Patch the values for the addresses and such'''
|
||||
|
||||
hooked = False
|
||||
heaped = False
|
||||
sized = False
|
||||
fsized = False
|
||||
|
||||
initUpperAddr = bytes.fromhex(upperAddr)
|
||||
geckoUpperAddr = bytes.fromhex(upperAddr)
|
||||
gUpperAddr = bytes.fromhex(upperAddr)
|
||||
|
||||
if isText == True:
|
||||
geckoCheats = geckoParser(gctFile, args.txtcodes)
|
||||
|
||||
while hooked == False or heaped == False or sized == False or fsized == False:
|
||||
while heaped == False or sized == False or fsized == False:
|
||||
try:
|
||||
sample = tmp.read(4)
|
||||
if sample == HOOK: #Found keyword "HOOK". Goes with the entry to the game
|
||||
if not hooked:
|
||||
tmp.seek(-4, 1)
|
||||
|
||||
initInfo = tmp.tell()
|
||||
if int(lowerAddr, 16) + initInfo > int('7FFF', 16): #Absolute addressing
|
||||
initUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
|
||||
if int(lowerAddr, 16) + (initInfo + 4) > int('7FFF', 16): #Absolute addressing
|
||||
geckoUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
|
||||
|
||||
tmp.write(_init)
|
||||
hooked = True
|
||||
elif sample == HEAP: #Found keyword "HEAP". Goes with the resize of the heap
|
||||
if sample == HEAP: #Found keyword "HEAP". Goes with the resize of the heap
|
||||
if not heaped:
|
||||
tmp.seek(-4, 1)
|
||||
|
||||
gInfo = tmp.tell()
|
||||
if int(lowerAddr, 16) + gInfo > int('7FFF', 16): #Absolute addressing
|
||||
gUpperAddr = bytes.fromhex('{:04X}'.format(int(upperAddr, 16) + 1))
|
||||
|
||||
if size == '0' or size == '':
|
||||
tmp.write(get_size(gecko))
|
||||
if isText == False:
|
||||
size = get_size(gecko).hex().upper()
|
||||
else:
|
||||
size = geckoCheats[1]
|
||||
else:
|
||||
tmp.write(bytes.fromhex('{:08X}'.format(int(size, 16))))
|
||||
heaped = True
|
||||
|
||||
elif sample == LOADERSIZE: #Found keyword "LSIZ". Goes with the size of the loader
|
||||
if not sized:
|
||||
tmp.seek(-4, 1)
|
||||
tmp.write(get_size(code))
|
||||
sized = True
|
||||
|
||||
elif sample == FULLSIZE: #Found keyword "FSIZ". Goes with the size of the loader + codes
|
||||
if not fsized:
|
||||
tmp.seek(-4, 1)
|
||||
|
@ -102,8 +155,7 @@ def build(gct, dol, size):
|
|||
tmp.write(get_size(code, gecko.tell()))
|
||||
fsized = True
|
||||
except TypeError as err:
|
||||
print('Fatal error (' + err + '), build failed to complete')
|
||||
time.sleep(3)
|
||||
parser.error(err)
|
||||
sys.exit(1)
|
||||
|
||||
'''Patch all load/store offsets to data'''
|
||||
|
@ -117,41 +169,37 @@ def build(gct, dol, size):
|
|||
elif sample == GL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gInfo)))
|
||||
elif sample == CH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(geckoUpperAddr)
|
||||
elif sample == CL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + (initInfo + 4))))
|
||||
elif sample == IH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(initUpperAddr)
|
||||
tmp.write(_init[0])
|
||||
elif sample == IL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + initInfo)))
|
||||
elif sample == JH:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(geckoUpperAddr)
|
||||
elif sample == JL:
|
||||
tmp.seek(-2, 1)
|
||||
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + (initInfo + 8))))
|
||||
tmp.write(_init[1])
|
||||
sample = tmp.read(2)
|
||||
|
||||
tmp.seek(0)
|
||||
gecko.seek(0)
|
||||
|
||||
dol_handler_offset = get_size(final)
|
||||
final.write(handler.read())
|
||||
time.sleep(0.01)
|
||||
dol_sme_offset = get_size(final)
|
||||
|
||||
final.write(tmp.read())
|
||||
time.sleep(0.01)
|
||||
final.write(gecko.read())
|
||||
|
||||
if isText == False:
|
||||
final.write(gecko.read())
|
||||
else:
|
||||
final.write(geckoCheats[0])
|
||||
final.seek(0, 0)
|
||||
|
||||
status = False
|
||||
i = 0
|
||||
|
||||
while i < 6:
|
||||
size = int(final.read(4).hex(), 16)
|
||||
if size == 0:
|
||||
textOffset = int(final.read(4).hex(), 16)
|
||||
if textOffset == 0:
|
||||
status = True
|
||||
offset = i * 4
|
||||
final.seek(-4, 1)
|
||||
|
@ -173,8 +221,45 @@ def build(gct, dol, size):
|
|||
break
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if status == False:
|
||||
print('Not enough sections to patch the DOL file! Potentially due to previous mods?')
|
||||
parser.error(TREDLIT + '\n :: ERROR: Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET)
|
||||
|
||||
if int(size, 16) < int(get_size(gecko).hex(), 16):
|
||||
print(TYELLOW + '\n :: WARNING: Allocated codespace was smaller than the given codelist. The game will crash if run' + TRESET)
|
||||
|
||||
if args.quiet:
|
||||
return
|
||||
|
||||
if int(size, 16) > int('70000', 16):
|
||||
print(TYELLOW + '\n :: WARNING: Allocations beyond 70000 will crash certain games. You allocated 0x{}'.format(size) + TRESET)
|
||||
|
||||
elif int(size, 16) > int('40000', 16):
|
||||
print(TYELLOWLIT + '\n :: HINT: Recommended allocation limit is 0x40000. You allocated 0x{}'.format(size) + TRESET)
|
||||
|
||||
if isText == False:
|
||||
codelistSize = get_size(gecko).hex().upper().lstrip('0')
|
||||
else:
|
||||
codelistSize = geckoCheats[1]
|
||||
|
||||
if args.verbose >= 2:
|
||||
print('')
|
||||
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}, start of game modified to address 0x{}'.format(dump_address.upper(), _START.hex().upper()),
|
||||
' :: Game function "_init_registers" located at address 0x{}{}'.format(_init[0].hex(), _init[1].hex().upper()),
|
||||
' :: Code allocation is 0x{}; codelist size is 0x{}'.format(size.upper().lstrip('0'), codelistSize),
|
||||
' :: Of the 6 text sections in this DOL file, {} were already used'.format(i) + TRESET]
|
||||
|
||||
for bit in info:
|
||||
print(bit)
|
||||
|
||||
elif args.verbose >= 1:
|
||||
print('')
|
||||
info = [TGREENLIT + ' :: GeckoLoader set at address 0x{}'.format(dump_address.upper()),
|
||||
' :: Code allocation is 0x{} in hex; codelist size is 0x{}'.format(size.upper().lstrip('0'), codelistSize) + TRESET]
|
||||
|
||||
for bit in info:
|
||||
print(bit)
|
||||
return
|
||||
|
||||
def get_size(file, offset=0):
|
||||
""" Return a file's size in bytes """
|
||||
|
@ -183,114 +268,62 @@ def get_size(file, offset=0):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
while True:
|
||||
gct = input('Name of the input GCT file? ')
|
||||
if os.path.splitext(gct)[1] == '':
|
||||
gct = gct + '.gct'
|
||||
if os.path.splitext(gct)[1] != '.gct' and os.path.splitext(gct)[1] != '.GCT':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(gct):
|
||||
gct = os.path.abspath(gct)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
while True:
|
||||
dol = input('Name of the input dol file? ')
|
||||
if os.path.splitext(dol)[1] == '':
|
||||
dol = dol + '.dol'
|
||||
if os.path.splitext(dol)[1] != '.dol':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(dol):
|
||||
dol = os.path.abspath(dol)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
elif len(sys.argv) == 2:
|
||||
if sys.argv[1].endswith('.gct') or sys.argv[1].endswith('.GCT'):
|
||||
gct = sys.argv[1]
|
||||
while True:
|
||||
dol = input('Name of the input dol file? ')
|
||||
if os.path.splitext(dol)[1] == '':
|
||||
dol = dol + '.dol'
|
||||
if os.path.splitext(dol)[1] != '.dol':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(dol):
|
||||
dol = os.path.abspath(dol)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
elif sys.argv[1].endswith('.dol'):
|
||||
dol = sys.argv[1]
|
||||
while True:
|
||||
gct = input('Name of the input GCT file? ')
|
||||
if os.path.splitext(gct)[1] == '':
|
||||
gct = gct + '.gct'
|
||||
if os.path.splitext(gct)[1] != '.gct' and os.path.splitext(gct)[1] != '.GCT':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(gct):
|
||||
gct = os.path.abspath(gct)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
else:
|
||||
print('The given file is invalid! Please provide a valid dol file, a valid GCT file, or both.')
|
||||
time.sleep(1)
|
||||
sys.exit(1)
|
||||
else:
|
||||
if sys.argv[1].endswith('.gct') or sys.argv[1].endswith('.GCT'):
|
||||
gct = sys.argv[1]
|
||||
if sys.argv[2].endswith('.dol'):
|
||||
dol = sys.argv[2]
|
||||
else:
|
||||
while True:
|
||||
dol = input('Name of the input DOL file? ')
|
||||
if os.path.splitext(dol)[1] == '':
|
||||
dol = dol + '.dol'
|
||||
if os.path.splitext(dol)[1] != '.dol':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(dol):
|
||||
dol = os.path.abspath(dol)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
elif sys.argv[1].endswith('.dol'):
|
||||
dol = sys.argv[1]
|
||||
if sys.argv[2].endswith('.gct') or sys.argv[2].endswith('.GCT'):
|
||||
gct = sys.argv[2]
|
||||
else:
|
||||
while True:
|
||||
gct = input('Name of the input GCT file? ')
|
||||
if os.path.splitext(gct)[1] == '':
|
||||
gct = gct + '.gct'
|
||||
if os.path.splitext(gct)[1] != '.gct' and os.path.splitext(gct)[1] != '.GCT':
|
||||
print('Invalid input!')
|
||||
continue
|
||||
if os.path.exists(gct):
|
||||
gct = os.path.abspath(gct)
|
||||
break
|
||||
else:
|
||||
print('File not found! Please try again.')
|
||||
else:
|
||||
print('The given files are invalid! Please provide a valid DOL file, a valid GCT file, or both.')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
while True:
|
||||
size = input('Define code allocation in hex. (Type 0 or press Enter on empty input for auto size): ')
|
||||
colorama.init()
|
||||
|
||||
TRESET = '\033[0m'
|
||||
TBOLD = '\033[1m'
|
||||
|
||||
TGREEN = '\033[32m'
|
||||
TGREENLIT = '\033[92m'
|
||||
TYELLOW = '\033[33m'
|
||||
TYELLOWLIT = '\033[93m'
|
||||
TRED = '\033[31m'
|
||||
TREDLIT = '\033[91m'
|
||||
|
||||
isText = False
|
||||
|
||||
parser = argparse.ArgumentParser(description='Process files and allocations for GeckoLoader')
|
||||
parser.add_argument('file', help='First file')
|
||||
parser.add_argument('file2', help='Second file')
|
||||
parser.add_argument('--alloc', help='Define the size of the code allocation: --alloc hex')
|
||||
parser.add_argument('-tc', '--txtcodes', help='What codes get parsed when a txt file is used.\n"ALL" makes all codes get parsed,\n"ACTIVE" makes only activated codes get parsed.', default='active')
|
||||
parser.add_argument('-q', '--quiet', help='Print nothing to the console', action='store_true')
|
||||
parser.add_argument('-v', '--verbose', help='Print extra info to the console', default=0, action='count')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.alloc:
|
||||
size = args.alloc.lstrip('0x')
|
||||
try:
|
||||
int(size, 16)
|
||||
break
|
||||
except Exception:
|
||||
if size == '':
|
||||
break
|
||||
else:
|
||||
print('Invalid input! {} is not hexadecimal!'.format(size))
|
||||
except:
|
||||
parser.error(TREDLIT + '\n :: ERROR: The allocation was invalid\n' + TRESET)
|
||||
else:
|
||||
size = '0'
|
||||
|
||||
if os.path.splitext(args.file)[1].lower() == '.dol':
|
||||
dolFile = args.file
|
||||
elif os.path.splitext(args.file2)[1].lower() == '.dol':
|
||||
dolFile = args.file2
|
||||
else:
|
||||
parser.error(TREDLIT + '\n :: ERROR: No dol file was passed\n' + TRESET)
|
||||
|
||||
if os.path.splitext(args.file)[1].lower() == '.gct':
|
||||
gctFile = args.file
|
||||
isText = False
|
||||
elif os.path.splitext(args.file)[1].lower() == '.txt':
|
||||
gctFile = args.file
|
||||
isText = True
|
||||
elif os.path.splitext(args.file2)[1].lower() == '.gct':
|
||||
gctFile = args.file2
|
||||
isText = False
|
||||
elif os.path.splitext(args.file2)[1].lower() == '.txt':
|
||||
gctFile = args.file2
|
||||
isText = True
|
||||
else:
|
||||
parser.error(TREDLIT + '\n :: ERROR: Neither a gct or gecko text file was passed\n' + TRESET)
|
||||
|
||||
time1 = time.time()
|
||||
|
||||
HEAP = bytes.fromhex('48454150')
|
||||
|
@ -310,12 +343,11 @@ if __name__ == "__main__":
|
|||
try:
|
||||
if not os.path.isdir('BUILD'):
|
||||
os.mkdir('BUILD')
|
||||
build(gct, dol, size)
|
||||
build(gctFile, dolFile, size, isText)
|
||||
os.remove('tmp.bin')
|
||||
print('Compiled in {:0.4f} seconds!'.format(time.time() - time1))
|
||||
time.sleep(4)
|
||||
if not args.quiet:
|
||||
print(TGREENLIT + '\n :: Compiled in {:0.4f} seconds!\n'.format(time.time() - time1) + TRESET)
|
||||
|
||||
except Exception as err:
|
||||
print(err)
|
||||
time.sleep(4)
|
||||
except FileNotFoundError as err:
|
||||
parser.error(err)
|
||||
sys.exit(1)
|
||||
|
|
Reference in a new issue