2020-06-03 16:12:29 +09:00
#Written by JoshuaMK 2020
2020-11-06 19:14:27 +09:00
import atexit
import logging
2020-06-03 16:12:29 +09:00
import os
2020-11-06 19:14:27 +09:00
import pickle as cPickle
import re
2020-08-19 20:21:15 +09:00
import shutil
2020-11-06 19:14:27 +09:00
import signal
2020-08-24 13:08:44 +09:00
import sys
2020-11-06 19:14:27 +09:00
import tempfile
from contextlib import redirect_stdout , redirect_stderr
2020-08-19 20:21:15 +09:00
from distutils . version import LooseVersion
2020-11-06 19:14:27 +09:00
from io import StringIO
2020-12-05 23:06:00 +09:00
from pathlib import Path
2020-06-03 16:12:29 +09:00
2020-11-06 19:14:27 +09:00
from PyQt5 import QtCore , QtGui , QtWidgets
from children_ui import PrefWindow , SettingsWindow
2020-08-24 13:08:44 +09:00
from dolreader import DolFile
2020-11-06 19:14:27 +09:00
from fileutils import resource_path , get_program_folder
2020-08-24 13:08:44 +09:00
from kernel import CodeHandler , KernelLoader
2020-11-06 19:14:27 +09:00
from main_ui import MainWindow
2020-08-24 13:08:44 +09:00
from tools import CommandLineParser , color_text
2020-08-19 20:21:15 +09:00
from versioncheck import Updater
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 = ' '
2020-12-05 23:06:00 +09:00
__version__ = " v7.1.0 "
2020-08-19 20:21:15 +09:00
2020-12-05 23:06:00 +09:00
TMPDIR = Path ( tempfile . mkdtemp ( prefix = " GeckoLoader- " ) )
2020-06-03 16:12:29 +09:00
2020-09-28 17:02:59 +09:00
@atexit.register
def clean_tmp_resources ( ) :
2020-12-05 23:06:00 +09:00
tmpfolder = TMPDIR . parent
for entry in tmpfolder . iterdir ( ) :
if entry . name . startswith ( " GeckoLoader- " ) :
2020-11-06 19:14:27 +09:00
shutil . rmtree ( entry , ignore_errors = True )
2020-09-28 17:02:59 +09:00
class GeckoLoaderCli ( CommandLineParser ) :
def __init__ ( self , name , version = None , description = ' ' ) :
2020-10-20 17:52:53 +09:00
super ( ) . __init__ ( prog = ( f " { name } { version } " ) , description = description , allow_abbrev = False )
2020-09-28 17:02:59 +09:00
self . __version__ = version
self . __doc__ = description
self . add_argument ( ' dolfile ' , help = ' DOL file ' )
self . add_argument ( ' codelist ' , help = ' Folder or Gecko GCT|TXT file ' )
self . add_argument ( ' -a ' , ' --alloc ' ,
2020-11-06 19:14:27 +09:00
help = ' Define the size of the code allocation in hex, only applies when using the ARENA space ' ,
metavar = ' SIZE ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -i ' , ' --init ' ,
2020-11-06 19:14:27 +09:00
help = ' Define where GeckoLoader is initialized in hex ' ,
metavar = ' ADDRESS ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -tc ' , ' --txtcodes ' ,
2020-11-06 19:14:27 +09:00
help = ''' [ " ACTIVE " , " ALL " ] What codes get parsed when a txt file is used.
" ALL " makes all codes get parsed ,
" ACTIVE " makes only activated codes get parsed .
" ACTIVE " is the default ''' ,
default = ' ACTIVE ' ,
metavar = ' TYPE ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' --handler ' ,
2020-11-06 19:14:27 +09:00
help = ''' [ " MINI " , " FULL " ] Which codehandler gets used. " MINI " uses a smaller codehandler
which only supports ( 0 x , 2 x , Cx , and E0 types ) and supports up to
600 lines of gecko codes when using the legacy codespace .
" FULL " is the standard codehandler , supporting up to 350 lines of code
in the legacy codespace . " FULL " is the default ''' ,
default = ' FULL ' ,
choices = [ ' MINI ' , ' FULL ' ] ,
metavar = ' TYPE ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' --hooktype ' ,
2020-08-25 21:06:56 +09:00
help = ''' [ " VI " , " GX " , " PAD " ] The type of hook used for the RAM search. " VI " or " GX " are recommended,
although " PAD " can work just as well . " VI " is the default ''' ,
2020-08-24 19:10:23 +09:00
default = ' VI ' ,
choices = [ ' VI ' , ' GX ' , ' PAD ' ] ,
metavar = ' HOOK ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' --hookaddress ' ,
2020-08-25 21:21:23 +09:00
help = ' Choose where the codehandler hooks to in hex, overrides auto hooks ' ,
2020-06-03 16:12:29 +09:00
metavar = ' ADDRESS ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -o ' , ' --optimize ' ,
2020-06-26 15:56:07 +09:00
help = ''' Optimizes the codelist by directly patching qualifying
ram writes into the dol file , and removing them from the codelist ''' ,
action = ' store_true ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -p ' , ' --protect ' ,
2020-08-25 21:16:46 +09:00
help = ''' Targets and nullifies the standard codehandler provided by loaders and Dolphin Emulator,
only applies when the ARENA is used ''' ,
2020-08-18 10:38:37 +09:00
action = ' store_true ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' --dest ' ,
2020-08-18 10:38:37 +09:00
help = ' Target path to put the modified DOL, can be a folder or file ' ,
metavar = ' PATH ' )
2020-11-06 19:14:27 +09:00
self . add_argument ( ' --checkupdate ' ,
2020-08-19 20:21:15 +09:00
help = ''' Checks to see if a new update exists on the GitHub Repository releases page,
this option overrides all other commands . ''' ,
action = ' store_true ' )
2020-11-06 19:14:27 +09:00
self . add_argument ( ' --splash ' ,
help = ''' Print the splash screen, this option overrides
all other commands excluding - - checkupdate ''' ,
action = ' store_true ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' --encrypt ' ,
2020-08-24 13:08:44 +09:00
help = ' Encrypts the codelist on compile time, helping to slow the snoopers ' ,
action = ' store_true ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -q ' , ' --quiet ' ,
2020-08-24 19:10:23 +09:00
help = ' Print nothing to the console ' ,
action = ' store_true ' )
2020-09-28 17:02:59 +09:00
self . add_argument ( ' -v ' , ' --verbose ' ,
2020-08-24 19:10:23 +09:00
help = ' Print extra info to the console ' ,
default = 0 ,
action = ' count ' )
2020-09-28 17:02:59 +09:00
2020-11-06 19:14:27 +09:00
def __str__ ( self ) - > str :
2020-09-28 17:02:59 +09:00
return self . __doc__
2020-08-18 10:38:37 +09:00
2020-09-28 17:02:59 +09:00
def print_splash ( self ) :
2020-08-25 21:06:56 +09:00
helpMessage = ' Try option -h for more info on this program ' . center ( 64 , ' ' )
2020-09-28 17:02:59 +09:00
version = self . __version__ . rjust ( 9 , ' ' )
2020-08-19 20:21:15 +09:00
2020-08-18 10:38:37 +09:00
logo = [ ' ' ,
2020-08-19 20:21:15 +09:00
' ╔═══════════════════════════════════════════════════════════╗ ' ,
' ║ ║ ' ,
' ║ ┌───┐┌───┐┌───┐┌┐┌─┐┌───┐┌┐ ┌───┐┌───┐┌───┐┌───┐┌───┐ ║ ' ,
' ║ │┌─┐││┌──┘│┌─┐││││┌┘│┌─┐│││ │┌─┐││┌─┐│└┐┌┐││┌──┘│┌─┐│ ║ ' ,
' ║ ││ └┘│└──┐││ └┘│└┘┘ ││ ││││ ││ ││││ ││ │││││└──┐│└─┘│ ║ ' ,
' ║ ││┌─┐│┌──┘││ ┌┐│┌┐│ ││ ││││ ┌┐││ │││└─┘│ │││││┌──┘│┌┐┌┘ ║ ' ,
' ║ │└┴─││└──┐│└─┘││││└┐│└─┘││└─┘││└─┘││┌─┐│┌┘└┘││└──┐│││└┐ ║ ' ,
' ║ └───┘└───┘└───┘└┘└─┘└───┘└───┘└───┘└┘ └┘└───┘└───┘└┘└─┘ ║ ' ,
' ║ ║ ' ,
' ║ ┌┐┌───┐┌───┐┌┐ ┌┐┌┐ ┌┐┌───┐┌─┐┌─┐┌┐┌─┐ ║ ' ,
' ║ │││┌─┐││┌─┐│││ ││││ │││┌─┐││ └┘ ││││┌┘ ║ ' ,
' ║ ││││ │││└──┐│└─┘│││ ││││ │││┌┐┌┐││└┘┘ ║ ' ,
' ║ ┌──┐┌┐││││ ││└──┐││┌─┐│││ │││└─┘││││││││┌┐│ ┌──┐ ║ ' ,
' ║ └──┘│└┘││└─┘││└─┘│││ │││└─┘││┌─┐││││││││││└┐└──┘ ║ ' ,
' ║ └──┘└───┘└───┘└┘ └┘└───┘└┘ └┘└┘└┘└┘└┘└─┘ ║ ' ,
f ' ║ { version } ║ ' ,
' ╚═══════════════════════════════════════════════════════════╝ ' ,
' ' ,
' GeckoLoader is a cli tool for allowing extended ' ,
' gecko code space in all Wii and GC games. ' ,
' ' ,
f ' { helpMessage } ' ,
2020-08-18 10:38:37 +09:00
' ' ]
2020-09-28 17:02:59 +09:00
2020-08-18 10:38:37 +09:00
for line in logo :
2020-08-19 20:21:15 +09:00
print ( color_text ( line , [ ( ' ║ ' , TREDLIT ) , ( ' ╔╚╝╗═ ' , TRED ) ] , TGREENLIT ) )
2020-09-28 17:02:59 +09:00
def check_updates ( self ) :
2020-08-19 20:21:15 +09:00
repoChecker = Updater ( ' JoshuaMKW ' , ' GeckoLoader ' )
tag , status = repoChecker . get_newest_version ( )
if status is False :
2020-09-28 17:02:59 +09:00
self . error ( color_text ( tag + ' \n ' , defaultColor = TREDLIT ) , print_usage = False )
2020-08-19 20:21:15 +09:00
2020-12-05 23:06:00 +09:00
print ( ' ' )
2020-09-28 17:02:59 +09:00
if LooseVersion ( tag ) > LooseVersion ( self . __version__ ) :
2020-08-19 20:21:15 +09:00
print ( color_text ( f ' :: A new update is live at { repoChecker . gitReleases . format ( repoChecker . owner , repoChecker . repo ) } ' , defaultColor = TYELLOWLIT ) )
2020-09-28 17:02:59 +09:00
print ( color_text ( f ' :: Current version is " { self . __version__ } " , Most recent version is " { tag } " ' , defaultColor = TYELLOWLIT ) )
elif LooseVersion ( tag ) < LooseVersion ( self . __version__ ) :
2020-08-19 20:21:15 +09:00
print ( color_text ( ' :: No update available ' , defaultColor = TGREENLIT ) )
2020-09-28 17:02:59 +09:00
print ( color_text ( f ' :: Current version is " { self . __version__ } (dev) " , Most recent version is " { tag } (release) " ' , defaultColor = TGREENLIT ) )
2020-08-19 20:21:15 +09:00
else :
print ( color_text ( ' :: No update available ' , defaultColor = TGREENLIT ) )
2020-09-28 17:02:59 +09:00
print ( color_text ( f ' :: Current version is " { self . __version__ } (release) " , Most recent version is " { tag } (release) " ' , defaultColor = TGREENLIT ) )
2020-08-19 20:21:15 +09:00
print ( ' ' )
2020-09-28 17:02:59 +09:00
2020-11-06 19:14:27 +09:00
def _validate_args ( self , args ) - > tuple :
if args . alloc :
2020-06-03 16:12:29 +09:00
try :
2020-11-06 19:14:27 +09:00
_allocation = int ( args . alloc , 16 )
2020-08-18 10:38:37 +09:00
except ValueError :
2020-11-06 19:14:27 +09:00
self . error ( color_text ( ' The allocation was invalid \n ' , defaultColor = TREDLIT ) )
else :
_allocation = None
2020-06-03 16:12:29 +09:00
2020-11-06 19:14:27 +09:00
if args . hookaddress :
if 0x80000000 > int ( args . hookaddress , 16 ) > = 0x81800000 :
self . error ( color_text ( ' The codehandler hook address was beyond bounds \n ' , defaultColor = TREDLIT ) )
else :
try :
_codehook = int ( args . hookaddress , 16 )
except ValueError :
self . error ( color_text ( ' The codehandler hook address was invalid \n ' , defaultColor = TREDLIT ) )
else :
_codehook = None
if args . handler == CodeHandler . Types . MINI :
2020-12-05 23:06:00 +09:00
codeHandlerFile = Path ( ' bin/codehandler-mini.bin ' )
2020-11-06 19:14:27 +09:00
elif args . handler == CodeHandler . Types . FULL :
2020-12-05 23:06:00 +09:00
codeHandlerFile = Path ( ' bin/codehandler.bin ' )
2020-11-06 19:14:27 +09:00
else :
self . error ( color_text ( f ' Codehandler type { args . handler } is invalid \n ' , defaultColor = TREDLIT ) )
2020-06-03 16:12:29 +09:00
2020-08-19 20:21:15 +09:00
if not os . path . isfile ( args . dolfile ) :
2020-11-06 19:14:27 +09:00
self . error ( color_text ( f ' File " { args . dolfile } " does not exist \n ' , defaultColor = TREDLIT ) )
2020-06-03 16:12:29 +09:00
2020-08-18 10:38:37 +09:00
if not os . path . exists ( args . codelist ) :
2020-11-06 19:14:27 +09:00
self . error ( color_text ( f ' File/folder " { args . codelist } " does not exist \n ' , defaultColor = TREDLIT ) )
return _allocation , _codehook , codeHandlerFile
def _exec ( self , args , tmpdir ) :
_allocation , _codehook , codeHandlerFile = self . _validate_args ( args )
try :
2020-12-05 23:06:00 +09:00
with open ( args . dolfile , " rb " ) as dol :
2020-11-06 19:14:27 +09:00
dolFile = DolFile ( dol )
2020-12-05 23:06:00 +09:00
with resource_path ( str ( codeHandlerFile ) ) . open ( " rb " ) as handler :
2020-11-06 19:14:27 +09:00
codeHandler = CodeHandler ( handler )
codeHandler . allocation = _allocation
codeHandler . hookAddress = _codehook
codeHandler . hookType = args . hooktype
codeHandler . includeAll = args . txtcodes . lower ( ) == ' all '
codeHandler . optimizeList = args . optimize
2020-12-05 23:06:00 +09:00
with resource_path ( " bin/geckoloader.bin " ) . open ( " rb " ) as kernelfile :
2020-11-06 19:14:27 +09:00
geckoKernel = KernelLoader ( kernelfile , cli )
if args . init is not None :
geckoKernel . initAddress = int ( args . init , 16 )
geckoKernel . verbosity = args . verbose
geckoKernel . quiet = args . quiet
geckoKernel . encrypt = args . encrypt
geckoKernel . protect = args . protect
if args . dest :
2020-12-05 23:06:00 +09:00
dest = Path ( args . dest ) . resolve ( )
if dest . suffix == " " :
dest = dest / args . dolfile . name
2020-11-06 19:14:27 +09:00
else :
2020-12-05 23:06:00 +09:00
dest = Path . cwd ( ) / " geckoloader-build " / args . dolfile . name
2020-07-22 14:32:25 +09:00
2020-12-05 23:06:00 +09:00
if not dest . parent . exists ( ) :
dest . parent . mkdir ( parents = True , exist_ok = True )
geckoKernel . build ( Path ( args . codelist ) , dolFile , codeHandler , TMPDIR , dest )
2020-11-06 19:14:27 +09:00
except FileNotFoundError as e :
self . error ( color_text ( e , defaultColor = TREDLIT ) )
class GUI ( object ) :
class Dialogs :
LOAD_DEST = 0
LOAD_GCT = 1
LOAD_FOLDER = 2
LOAD_DOL = 3
LOAD_SESSION = 4
SAVE_SESSION = 5
SAVE_SESSION_AS = 6
def __init__ ( self , cli : GeckoLoaderCli ) :
self . cli = cli
self . app = None
self . ui = None
self . uiprefs = None
self . uiexSettings = None
self . dolPath = None
self . codePath = [ None , None ]
self . destPath = None
self . sessionPath = None
self . prefs = { " qtstyle " : " Default " , " darktheme " : False }
self . style_log = [ ]
2020-11-06 20:45:05 +09:00
self . compileCount = 0
2020-11-06 19:14:27 +09:00
2020-12-05 23:06:00 +09:00
self . log = logging . getLogger ( f " GeckoLoader { self . version } " )
2020-11-06 19:14:27 +09:00
2020-12-05 23:06:00 +09:00
if not get_program_folder ( " GeckoLoader " ) . exists ( ) :
get_program_folder ( " GeckoLoader " ) . mkdir ( )
2020-11-06 20:45:05 +09:00
2020-12-05 23:06:00 +09:00
hdlr = logging . FileHandler ( get_program_folder ( " GeckoLoader " ) / " error.log " )
2020-11-06 19:14:27 +09:00
formatter = logging . Formatter ( " \n %(levelname)s ( %(asctime)s ): %(message)s " )
hdlr . setFormatter ( formatter )
self . log . addHandler ( hdlr )
def show_dialog ( self , dialog_type = None ) :
if dialog_type == " aboutqt " :
QtWidgets . QMessageBox . aboutQt ( self . app . activeWindow ( ) )
elif dialog_type == " aboutGeckoLoader " :
desc = " " . join ( [ " GeckoLoader is a cross platform tool designed to give " ,
" the user the most efficient codespace usage possible. \n \n " ,
" This application supports various features, such as " ,
" pre-patching codes, dynamic codehandler hooks, codespace " ,
" extension through memory reallocation, multiple patching " ,
" of a single DOL, and more. \n \n " ,
2020-12-05 23:06:00 +09:00
f " Current running version: { self . version } \n \n "
2020-11-06 19:14:27 +09:00
" Copyright (c) 2020 \n \n " ,
" JoshuaMK <joshuamkw2002@gmail.com> \n \n " ,
" All rights reserved. " ] )
QtWidgets . QMessageBox . about ( self . app . activeWindow ( ) , " About GeckoLoader " , desc )
elif dialog_type == " Preferences " :
self . uiprefs . show ( )
else :
self . uiexSettings . show ( )
2020-12-05 23:06:00 +09:00
@property
def version ( self ) - > str :
return self . cli . __version__
2020-11-06 19:14:27 +09:00
def _open_dol ( self ) - > tuple :
if self . dolPath is None : # Just start in the home directory
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open DOL " , str ( Path . home ( ) ) ,
2020-11-06 19:14:27 +09:00
" Nintendo DOL Executable (*.dol);;All files (*) " ) [ 0 ] )
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open DOL " , str ( self . dolPath . parent ) ,
2020-11-06 19:14:27 +09:00
" Nintendo DOL Executable (*.dol);;All files (*) " ) [ 0 ] )
if fname == " " or fname is None : # Make sure we have something to open
return False , None
else :
2020-12-05 23:06:00 +09:00
self . dolPath = Path ( fname ) . resolve ( )
2020-08-18 10:38:37 +09:00
2020-12-05 23:06:00 +09:00
if self . dolPath . is_file ( ) :
self . ui . dolTextBox . setText ( str ( self . dolPath ) )
2020-11-06 19:14:27 +09:00
return True , None
else :
return False , " The file does not exist! "
def _load_codes ( self , isFolder : bool = False ) - > tuple :
if not isFolder :
if self . codePath [ 0 ] is None : # Just start in the home directory
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open Codelist " , str ( Path . home ( ) ) ,
2020-11-06 19:14:27 +09:00
" Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*) " ) [ 0 ] )
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open Codelist " , str ( self . codePath [ 0 ] . parent ) ,
2020-11-06 19:14:27 +09:00
" Gecko Code Table (*.gct);;Gecko Codelist (*.txt);;All files (*) " ) [ 0 ] )
else :
if self . codePath [ 0 ] is None : # Just start in the home directory
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getExistingDirectory ( self . ui , " Open Codelist " , str ( Path . home ( ) ) ,
QtWidgets . QFileDialog . ShowDirsOnly ) )
2020-11-06 19:14:27 +09:00
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getExistingDirectory ( self . ui , " Open Codelist " , str ( self . codePath [ 0 ] . parent ) ,
2020-11-06 19:14:27 +09:00
QtWidgets . QFileDialog . ShowDirsOnly ) )
if fname == " " or fname is None : # Make sure we have something to open
return False , None
else :
2020-12-05 23:06:00 +09:00
self . codePath = [ Path ( fname ) . resolve ( ) , isFolder ]
2020-08-18 10:38:37 +09:00
2020-11-06 19:14:27 +09:00
if not isFolder :
2020-12-05 23:06:00 +09:00
self . ui . gctFileTextBox . setText ( str ( self . codePath [ 0 ] ) )
2020-11-06 19:14:27 +09:00
self . ui . gctFolderTextBox . setText ( " " )
else :
self . ui . gctFileTextBox . setText ( " " )
2020-12-05 23:06:00 +09:00
self . ui . gctFolderTextBox . setText ( str ( self . codePath [ 0 ] ) )
2020-08-18 10:38:37 +09:00
2020-11-06 19:14:27 +09:00
return True , None
2020-08-18 10:38:37 +09:00
2020-11-06 19:14:27 +09:00
def _open_dest ( self ) - > tuple :
if self . dolPath is None : # Just start in the home directory
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open DOL " , str ( Path . home ( ) ) ,
2020-11-06 19:14:27 +09:00
" Nintendo DOL Executable (*.dol);;All files (*) " ) [ 0 ] )
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open DOL " , str ( self . dolPath . parent ) ,
2020-11-06 19:14:27 +09:00
" Nintendo DOL Executable (*.dol);;All files (*) " ) [ 0 ] )
if fname == " " or fname is None : # Make sure we have something to open
return False , None
else :
2020-12-05 23:06:00 +09:00
self . destPath = Path ( fname ) . resolve ( )
self . ui . destTextBox . setText ( str ( self . destPath ) )
2020-11-06 19:14:27 +09:00
return True , None
def _load_session ( self ) - > tuple :
if self . sessionPath is None :
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open Session " , str ( Path . home ( ) ) ,
2020-11-06 19:14:27 +09:00
" GeckoLoader Session (*.gprf);;All files (*) " ) [ 0 ] )
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getOpenFileName ( self . ui , " Open Session " , str ( self . sessionPath . parent ) ,
2020-11-06 19:14:27 +09:00
" GeckoLoader Session (*.gprf);;All files (*) " ) [ 0 ] )
if fname == " " or fname is None : # Make sure we have something to open
return False , None
else :
2020-12-05 23:06:00 +09:00
self . sessionPath = Path ( fname ) . resolve ( )
2020-11-06 19:14:27 +09:00
2020-12-05 23:06:00 +09:00
with self . sessionPath . open ( " rb " ) as session :
2020-11-06 19:14:27 +09:00
p = cPickle . load ( session )
self . ui . dolTextBox . setText ( p [ " dolPath " ] )
if p [ " gctFilePath " ] != " " :
self . ui . gctFileTextBox . setText ( p [ " gctFilePath " ] )
self . ui . gctFolderTextBox . setText ( " " )
elif p [ " gctFolderPath " ] != " " :
self . ui . gctFileTextBox . setText ( " " )
self . ui . gctFolderTextBox . setText ( p [ " gctFolderPath " ] )
else :
self . ui . gctFileTextBox . setText ( " " )
self . ui . gctFolderTextBox . setText ( " " )
self . ui . destTextBox . setText ( p [ " destPath " ] )
self . ui . allocLineEdit . setText ( p [ " alloc " ] )
self . ui . handlerTypeSelect . setCurrentIndex ( p [ " handlerIndex " ] )
self . ui . hookTypeSelect . setCurrentIndex ( p [ " hookIndex " ] )
self . ui . txtCodesIncludeSelect . setCurrentIndex ( p [ " txtIndex " ] )
2020-12-05 23:06:00 +09:00
self . uiexSettings . optimizeCodes . setChecked ( p [ " optimize " ] )
2020-11-06 19:14:27 +09:00
self . uiexSettings . protectCodes . setChecked ( p [ " protect " ] )
self . uiexSettings . encryptCodes . setChecked ( p [ " encrypt " ] )
self . uiexSettings . codehookLineEdit . setText ( p [ " hookAddress " ] )
self . uiexSettings . kernelHookLineEdit . setText ( p [ " initAddress " ] )
self . uiexSettings . verbositySelect . setCurrentIndex ( p [ " verbosity " ] )
return True , None
def _save_session ( self , saveAs = False ) :
if saveAs or self . sessionPath is None or self . sessionPath == " " :
if self . sessionPath is None : # Just start in the home directory
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getSaveFileName ( self . ui , " Save Session " , str ( Path . home ( ) ) ,
2020-11-06 19:14:27 +09:00
" GeckoLoader Session (*.gprf);;All files (*) " ) [ 0 ] )
else : # Start in the last directory used by the user
2020-12-05 23:06:00 +09:00
fname = str ( QtWidgets . QFileDialog . getSaveFileName ( self . ui , " Save Session " , str ( self . dolPath . parent ) ,
2020-11-06 19:14:27 +09:00
" GeckoLoader Session (*.gprf);;All files (*) " ) [ 0 ] )
if fname == " " or fname is None : # Make sure we have something to open
return False , None
else :
2020-12-05 23:06:00 +09:00
self . sessionPath = Path ( fname ) . resolve ( )
2020-11-06 19:14:27 +09:00
try :
2020-12-05 23:06:00 +09:00
with self . sessionPath . open ( " wb " ) as session :
2020-11-06 19:14:27 +09:00
p = { }
p [ " dolPath " ] = self . ui . dolTextBox . text ( ) . strip ( )
p [ " gctFilePath " ] = self . ui . gctFileTextBox . text ( ) . strip ( )
p [ " gctFolderPath " ] = self . ui . gctFolderTextBox . text ( ) . strip ( )
p [ " destPath " ] = self . ui . destTextBox . text ( ) . strip ( )
p [ " alloc " ] = self . ui . allocLineEdit . text ( ) . strip ( )
p [ " handlerIndex " ] = self . ui . handlerTypeSelect . currentIndex ( )
p [ " hookIndex " ] = self . ui . hookTypeSelect . currentIndex ( )
p [ " txtIndex " ] = self . ui . txtCodesIncludeSelect . currentIndex ( )
2020-12-05 23:06:00 +09:00
p [ " optimize " ] = self . uiexSettings . optimizeCodes . isChecked ( )
2020-11-06 19:14:27 +09:00
p [ " protect " ] = self . uiexSettings . protectCodes . isChecked ( )
p [ " encrypt " ] = self . uiexSettings . encryptCodes . isChecked ( )
p [ " hookAddress " ] = self . uiexSettings . codehookLineEdit . text ( ) . strip ( )
p [ " initAddress " ] = self . uiexSettings . kernelHookLineEdit . text ( ) . strip ( )
p [ " verbosity " ] = self . uiexSettings . verbositySelect . currentIndex ( )
try :
cPickle . dump ( p , session )
except cPickle . PicklingError as e :
return False , str ( e )
return True , None
except ( IOError , PermissionError ) as e :
return False , str ( e )
def file_dialog_exec ( self , event : Dialogs ) :
try :
if event == GUI . Dialogs . LOAD_DOL :
status , msg = self . _open_dol ( )
elif event == GUI . Dialogs . LOAD_GCT :
status , msg = self . _load_codes ( False )
elif event == GUI . Dialogs . LOAD_FOLDER :
status , msg = self . _load_codes ( True )
elif event == GUI . Dialogs . LOAD_DEST :
status , msg = self . _open_dest ( )
elif event == GUI . Dialogs . LOAD_SESSION :
status , msg = self . _load_session ( )
elif event == GUI . Dialogs . SAVE_SESSION :
status , msg = self . _save_session ( )
elif event == GUI . Dialogs . SAVE_SESSION_AS :
status , msg = self . _save_session ( True )
else :
return
except IndexError :
self . ui . set_edit_fields ( )
return
if status is False and msg is not None :
reply = QtWidgets . QErrorMessage ( self )
reply . setWindowTitle ( " I/O Failure " )
reply . setText ( msg )
reply . setInformativeText ( " Please try again. " )
reply . setIcon ( QtWidgets . QMessageBox . Warning )
reply . setStandardButtons ( QtWidgets . QMessageBox . Ok )
reply . exec_ ( )
else :
self . ui . set_edit_fields ( )
def close_session ( self ) :
self . dolPath = None
self . codePath = None
2020-11-06 20:45:05 +09:00
self . sessionPath = None
2020-11-06 19:14:27 +09:00
self . ui . dolTextBox . setText ( " " )
self . ui . gctFileTextBox . setText ( " " )
self . ui . gctFolderTextBox . setText ( " " )
self . ui . destTextBox . setText ( " " )
self . ui . allocLineEdit . setText ( " " )
self . ui . handlerTypeSelect . setCurrentIndex ( 0 )
self . ui . hookTypeSelect . setCurrentIndex ( 0 )
self . ui . txtCodesIncludeSelect . setCurrentIndex ( 0 )
self . ui . optimizeSelect . setCurrentIndex ( 0 )
self . ui . responses . setPlainText ( " " )
self . uiexSettings . protectCodes . setChecked ( False )
self . uiexSettings . encryptCodes . setChecked ( False )
self . uiexSettings . codehookLineEdit . setText ( " " )
self . uiexSettings . kernelHookLineEdit . setText ( " " )
self . uiexSettings . verbositySelect . setCurrentIndex ( 0 )
self . ui . set_edit_fields ( )
def load_prefs ( self ) :
datapath = get_program_folder ( " GeckoLoader " )
try :
2020-12-05 23:06:00 +09:00
with ( datapath / " .GeckoLoader.conf " ) . open ( " rb " ) as f :
2020-11-06 19:14:27 +09:00
try :
p = cPickle . load ( f )
except cPickle . UnpicklingError as e :
self . log . exception ( e ) # Use defaults for prefs
else :
# Input validation
if ( p . get ( " qtstyle " ) in list ( QtWidgets . QStyleFactory . keys ( ) ) or
p . get ( " qtstyle " ) == " Default " ) :
self . prefs [ " qtstyle " ] = p . get ( " qtstyle " )
if p . get ( " darktheme " ) in ( True , False ) :
self . prefs [ " darktheme " ] = p . get ( " darktheme " )
setCIndex = self . uiprefs . qtstyleSelect . setCurrentIndex
if self . prefs . get ( " qtstyle " ) in ( None , " Default " ) :
setCIndex ( 0 )
else :
setCIndex ( self . uiprefs . qtstyleSelect . findText (
self . prefs . get ( " qtstyle " ) ,
flags = QtCore . Qt . MatchFixedString ) )
self . uiprefs . qtdarkButton . setChecked ( self . prefs . get ( " darktheme " ) )
self . update_theme ( )
except FileNotFoundError :
self . log . warning ( " No preferences file found; using defaults. " )
def save_prefs ( self ) :
datapath = get_program_folder ( " GeckoLoader " )
self . prefs [ " qtstyle " ] = str ( self . uiprefs . qtstyleSelect . currentText ( ) )
self . prefs [ " darktheme " ] = self . uiprefs . qtdarkButton . isChecked ( )
try :
2020-12-05 23:06:00 +09:00
with ( datapath / " .GeckoLoader.conf " ) . open ( " wb " ) as f :
2020-11-06 19:14:27 +09:00
cPickle . dump ( self . prefs , f )
except IOError as e :
self . log . exception ( e )
def load_qtstyle ( self , style , first_style_load = False ) :
2020-12-05 23:06:00 +09:00
self . style_log . append ( [ self . app . style , self . uiprefs . qtstyleSelect . currentText ( ) ] )
2020-11-06 19:14:27 +09:00
if len ( self . style_log ) > 2 :
self . style_log . pop ( 0 )
if style != " Default " :
self . app . setStyle ( style )
else :
self . app . setStyle ( self . default_qtstyle )
if first_style_load :
setCIndex = self . uiprefs . qtstyleSelect . setCurrentIndex
setCIndex ( self . uiprefs . qtstyleSelect . findText ( style ,
flags = QtCore . Qt . MatchFixedString ) )
def update_theme ( self ) :
if self . uiprefs . qtdarkButton . isChecked ( ) :
self . app . setPalette ( self . ui . DarkTheme )
self . load_qtstyle ( " Fusion " , True )
self . uiprefs . qtstyleSelect . setDisabled ( True )
else :
self . app . setPalette ( self . ui . LightTheme )
self . load_qtstyle ( self . style_log [ 0 ] [ 1 ] , True )
self . uiprefs . qtstyleSelect . setEnabled ( True )
def display_update ( self ) :
_outpipe = StringIO ( )
_errpipe = StringIO ( )
with redirect_stdout ( _outpipe ) , redirect_stderr ( _errpipe ) :
try :
self . cli . check_updates ( )
except SystemExit :
_status = False
2020-08-18 10:38:37 +09:00
else :
2020-11-06 19:14:27 +09:00
_status = True
icon = QtGui . QIcon ( )
2020-12-05 23:09:16 +09:00
icon . addPixmap ( QtGui . QPixmap ( str ( resource_path ( Path ( " bin/icon.ico " ) ) ) ) , QtGui . QIcon . Normal , QtGui . QIcon . Off )
2020-11-06 19:14:27 +09:00
if _status is False :
reply = QtWidgets . QErrorMessage ( )
reply . setWindowIcon ( icon )
reply . setWindowTitle ( " Response Error " )
reply . setText ( self . _remove_ansi ( _errpipe . getvalue ( ) ) )
reply . setInformativeText ( " Make sure you have an internet connection " )
reply . setIcon ( QtWidgets . QMessageBox . Warning )
reply . setStandardButtons ( QtWidgets . QMessageBox . Ok )
reply . exec_ ( )
else :
reply = QtWidgets . QMessageBox ( )
reply . setWindowIcon ( icon )
reply . setWindowTitle ( " Update Info " )
reply . setText ( self . _remove_ansi ( _outpipe . getvalue ( ) ) . strip ( " \n " ) + " \n \n You can find all GeckoLoader releases at: \n https://github.com/JoshuaMKW/GeckoLoader/releases " )
reply . setIcon ( QtWidgets . QMessageBox . Information )
reply . setStandardButtons ( QtWidgets . QMessageBox . Ok )
reply . exec_ ( )
@staticmethod
def _enforce_mask ( textbox : QtWidgets . QTextEdit , mask : int , _or : int = 0 ) :
textbox . setText ( textbox . text ( ) . strip ( ) )
if len ( textbox . text ( ) ) > 0 :
_depth = len ( hex ( mask ) [ 2 : ] )
_address = int ( textbox . text ( ) , 16 ) << ( ( _depth - len ( textbox . text ( ) ) ) << 2 )
aligned = hex ( ( ( _address & mask ) | _or ) >> ( ( _depth - len ( textbox . text ( ) ) ) << 2 ) ) [ 2 : ] . upper ( )
textbox . setText ( aligned )
@staticmethod
def _remove_ansi ( msg : str ) - > str :
ansi_escape = re . compile ( r ' \ x1B(?:[@-Z \\ -_]| \ [[0-?]*[ -/]*[@-~]) ' )
return ansi_escape . sub ( ' ' , msg )
def connect_signals ( self ) :
self . ui . actionPreferences . triggered . connect ( lambda : self . show_dialog ( " Preferences " ) )
self . ui . actionAbout_Qt . triggered . connect ( lambda : self . show_dialog ( " aboutqt " ) )
self . ui . actionAbout_GeckoLoader . triggered . connect ( lambda : self . show_dialog ( " aboutGeckoLoader " ) )
self . ui . actionCheck_Update . triggered . connect ( lambda : self . display_update ( ) )
self . ui . actionOpen . triggered . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . LOAD_SESSION ) )
self . ui . actionClose . triggered . connect ( lambda : self . close_session ( ) )
self . ui . actionSave_As . triggered . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . SAVE_SESSION_AS ) )
self . ui . actionSave . triggered . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . SAVE_SESSION ) )
self . ui . dolButton . clicked . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . LOAD_DOL ) )
self . ui . gctFileButton . clicked . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . LOAD_GCT ) )
self . ui . gctFolderButton . clicked . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . LOAD_FOLDER ) )
self . ui . destButton . clicked . connect ( lambda : self . file_dialog_exec ( GUI . Dialogs . LOAD_DEST ) )
self . ui . dolTextBox . textChanged . connect ( lambda : self . ui . set_edit_fields ( ) )
self . ui . gctFolderTextBox . textChanged . connect ( lambda : self . ui . set_edit_fields ( ) )
self . ui . gctFileTextBox . textChanged . connect ( lambda : self . ui . set_edit_fields ( ) )
2020-12-06 12:17:57 +09:00
self . ui . destTextBox . textChanged . connect ( lambda : self . ui . set_edit_fields ( ) )
2020-11-06 19:14:27 +09:00
self . ui . allocLineEdit . textChanged . connect ( lambda : self . _enforce_mask ( self . ui . allocLineEdit , 0xFFFFFC ) )
self . ui . exOptionsButton . clicked . connect ( lambda : self . show_dialog ( " Advanced Settings " ) )
self . ui . compileButton . clicked . connect ( lambda : self . _exec_api ( ) )
self . uiprefs . buttonBox . accepted . connect ( self . save_prefs )
self . uiprefs . qtstyleSelect . currentIndexChanged . connect ( lambda : self . load_qtstyle ( self . uiprefs . qtstyleSelect . currentText ( ) ) )
self . uiprefs . qtdarkButton . clicked . connect ( lambda : self . update_theme ( ) )
self . uiexSettings . codehookLineEdit . textChanged . connect ( lambda : self . _enforce_mask ( self . uiexSettings . codehookLineEdit , 0x817FFFFC , 0x80000000 ) )
self . uiexSettings . kernelHookLineEdit . textChanged . connect ( lambda : self . _enforce_mask ( self . uiexSettings . kernelHookLineEdit , 0x817FFFFC , 0x80000000 ) )
def _exec_api ( self ) :
2020-11-07 17:52:03 +09:00
if sys . platform == " win32 " :
self . ui . responses . appendPlainText ( f " | Session { self . compileCount } | " . center ( 84 , " = " ) + " \n " )
else :
self . ui . responses . appendPlainText ( f " | Session { self . compileCount } | " . center ( 76 , " = " ) + " \n " )
2020-11-06 20:45:05 +09:00
self . compileCount + = 1
2020-11-06 19:14:27 +09:00
if self . ui . dolTextBox . isEnabled and self . ui . dolTextBox . text ( ) . strip ( ) != " " :
2020-12-05 23:06:00 +09:00
dol = self . ui . dolTextBox . text ( ) . strip ( )
2020-08-18 10:38:37 +09:00
else :
2020-11-06 19:14:27 +09:00
self . ui . responses . appendPlainText ( " DOL is missing, please add the path to your codes in the respective textbox " + " \n \n " )
return
2020-08-18 10:38:37 +09:00
2020-11-06 19:14:27 +09:00
if self . ui . gctFileTextBox . isEnabled and self . ui . gctFileTextBox . text ( ) . strip ( ) != " " :
2020-12-05 23:06:00 +09:00
gct = self . ui . gctFileTextBox . text ( ) . strip ( )
2020-11-06 19:14:27 +09:00
elif self . ui . gctFolderTextBox . isEnabled and self . ui . gctFolderTextBox . text ( ) . strip ( ) != " " :
2020-12-05 23:06:00 +09:00
gct = self . ui . gctFolderTextBox . text ( ) . strip ( )
2020-11-06 19:14:27 +09:00
else :
self . ui . responses . appendPlainText ( " GCT is missing, please add the path to your codes in the respective textbox " + " \n \n " )
return
alloc = self . ui . allocLineEdit . text ( ) . strip ( )
hookType = self . ui . hookTypeSelect . currentText ( ) . strip ( )
hookAddress = self . uiexSettings . codehookLineEdit . text ( ) . strip ( )
initAddress = self . uiexSettings . kernelHookLineEdit . text ( ) . strip ( )
txtInclude = self . ui . txtCodesIncludeSelect . currentText ( ) . strip ( )
codeHandlerType = self . ui . handlerTypeSelect . currentText ( ) . strip ( )
2020-12-05 23:06:00 +09:00
optimize = self . uiexSettings . optimizeCodes . isChecked ( )
2020-11-06 19:14:27 +09:00
protect = self . uiexSettings . protectCodes . isChecked ( )
encrypt = self . uiexSettings . encryptCodes . isChecked ( )
verbosity = int ( self . uiexSettings . verbositySelect . currentText ( ) . strip ( ) )
dest = self . ui . destTextBox . text ( ) . strip ( )
2020-12-05 23:06:00 +09:00
argslist = [ dol , gct , " -t " , txtInclude , " --handler " , codeHandlerType , " --hooktype " , hookType ]
2020-11-06 19:14:27 +09:00
if alloc != " " :
argslist . append ( " -a " )
argslist . append ( hex ( int ( alloc , 16 ) & 0xFFFFFC ) [ 2 : ] . upper ( ) )
if hookAddress != " " :
if int ( hookAddress , 16 ) < 0x80000000 :
self . ui . responses . appendPlainText ( " The specified code hook is invalid " + " \n " )
return
argslist . append ( " --hookaddress " )
argslist . append ( hookAddress )
if initAddress != " " :
if int ( initAddress , 16 ) < 0x80000000 :
self . ui . responses . appendPlainText ( " The specified initialization address is invalid " + " \n " )
return
argslist . append ( " -i " )
argslist . append ( initAddress )
if dest != " " :
if dest . lower ( ) . endswith ( " .dol " ) and len ( dest ) > 4 :
argslist . append ( " --dest " )
argslist . append ( dest )
else :
self . ui . responses . appendPlainText ( " The destination file path is not a valid DOL file \n " )
return
if optimize :
argslist . append ( " -o " )
2020-08-18 10:38:37 +09:00
2020-11-06 19:14:27 +09:00
if protect :
argslist . append ( " -p " )
if encrypt :
argslist . append ( " --encrypt " )
if verbosity > 0 :
argslist . append ( " - " + " v " * verbosity )
else :
argslist . append ( " -q " )
2020-09-28 17:02:59 +09:00
2020-11-06 19:14:27 +09:00
args = self . cli . parse_args ( argslist )
_outpipe = StringIO ( )
_errpipe = StringIO ( )
_status = False
_msg = " "
with redirect_stdout ( _outpipe ) , redirect_stderr ( _errpipe ) :
try :
self . cli . _exec ( args , tmpdir = TMPDIR )
2020-12-06 12:17:57 +09:00
except ( SystemExit , Exception ) :
2020-11-06 19:14:27 +09:00
_status = False
else :
_status = True
if _status is False :
_msg = f " Arguments failed! GeckoLoader couldn ' t execute the job \n \n Args: { args . __repr__ ( ) } \n \n stderr: { self . _remove_ansi ( _errpipe . getvalue ( ) ) } "
2020-12-06 12:17:57 +09:00
self . ui . responses . appendPlainText ( _outpipe . getvalue ( ) + " \n \n " + _msg . strip ( ) + " \n " )
2020-11-06 19:14:27 +09:00
else :
for line in self . _remove_ansi ( _outpipe . getvalue ( ) ) . split ( " \n " ) :
2020-11-06 20:45:05 +09:00
_msg + = line . lstrip ( ) + " \n "
2020-11-06 19:14:27 +09:00
self . ui . responses . appendPlainText ( _msg . strip ( ) + " \n " )
def run ( self ) :
if sys . platform != " win32 " :
2020-12-05 23:06:00 +09:00
datapath = Path . home ( ) / " .GeckoLoader "
2020-11-06 19:14:27 +09:00
else :
2020-12-05 23:06:00 +09:00
datapath = Path ( os . getenv ( " APPDATA " ) ) / " GeckoLoader "
2020-11-06 19:14:27 +09:00
2020-12-05 23:06:00 +09:00
if not datapath . is_dir ( ) :
datapath . mkdir ( )
2020-09-28 17:02:59 +09:00
2020-11-06 19:14:27 +09:00
self . app = QtWidgets . QApplication ( sys . argv )
self . default_qtstyle = self . app . style ( ) . objectName ( )
2020-12-05 23:06:00 +09:00
self . ui = MainWindow ( self . version )
2020-11-06 19:14:27 +09:00
self . uiprefs = PrefWindow ( )
self . uiexSettings = SettingsWindow ( )
self . uiprefs . qtstyleSelect . addItem ( " Default " )
2020-12-05 23:06:00 +09:00
styleKeys = list ( QtWidgets . QStyleFactory . keys ( ) )
self . uiprefs . qtstyleSelect . addItems ( styleKeys )
2020-11-06 19:14:27 +09:00
self . load_prefs ( )
self . load_qtstyle ( self . prefs . get ( " qtstyle " ) , True )
regex = QtCore . QRegExp ( " [0-9A-Fa-f]* " )
validator = QtGui . QRegExpValidator ( regex )
self . ui . allocLineEdit . setValidator ( validator )
self . uiexSettings . codehookLineEdit . setValidator ( validator )
self . uiexSettings . kernelHookLineEdit . setValidator ( validator )
self . connect_signals ( )
self . ui . show ( )
sys . exit ( self . app . exec_ ( ) )
if __name__ == " __main__ " :
cli = GeckoLoaderCli ( ' GeckoLoader ' , __version__ , description = ' Dol editing tool for allocating extended codespace ' )
if len ( sys . argv ) == 1 :
cli . print_splash ( )
app = GUI ( cli )
signal . signal ( signal . SIGINT , signal . SIG_DFL )
app . run ( )
elif ' --checkupdate ' in sys . argv :
cli . check_updates ( )
elif ' --splash ' in sys . argv :
cli . print_splash ( )
2020-12-05 23:06:00 +09:00
else :
args = cli . parse_args ( )
cli . _exec ( args , TMPDIR )