1
0
Fork 0
This commit is contained in:
JoshuaMKW 2020-06-03 21:45:56 -05:00
commit 4a8986232d
3 changed files with 0 additions and 590 deletions

Binary file not shown.

215
loader.c
View file

@ -1,215 +0,0 @@
/*Credits to riidefi for hook code, cache asm, and teaching me C*/
#define dcbst(_val) asm volatile("dcbst 0, %0" \
: \
: "r"(_val))
#define dcbf(_val) asm volatile("dcbf 0, %0" \
: \
: "r"(_val))
#define icbi(_val) asm volatile("icbi 0, %0" \
: \
: "r"(_val))
#define callFunction(addr) ((void (*)())addr)()
#define FALSE 0
#define TRUE 1
#define NULL 0
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef int s32;
typedef short s16;
typedef char s8;
typedef u32 BOOL;
typedef u32 unk32;
__attribute__((noreturn)) int main();
enum {
MEM1_START = 0x80000000,
MEM1_END = 0x81800000,
CODEHANDLER_ENTRY = 0x800018A8,
GAME_ENTRY = 0xDEADBEEF,
GCT_MAGIC = 0x00D0C0DE
};
struct Info {
const u32 allocsize;
const u32 _loaderSize;
const u32 _loaderFullSize;
struct CodeList* _codelistPointer;
const u32 _wiiVIHook[4];
const u32 _gcnVIHook[8];
const u32 otherModPointer[4];
};
struct CodeList {
u16 mBaseASM;
u16 mUpperBase;
u16 mOffsetASM;
u16 mLowerOffset;
};
struct DiscInfo {
const u8 mDiscID;
const u16 mGameCode;
const u8 mRegionCode;
const u16 mMakerCode;
const u8 mDiscNumber;
const u8 mDiscVersion;
const u8 mAudioStreaming;
const u8 mStreamBufferSize;
const u8 mUnknown[12];
const u32 mWiiMagic;
const u32 mGCNMagic;
const u32 mUnknown2[2];
u32 mRAMSize;
const u32 mUnknown3[2];
u32* mHeapPointer;
u32 mHeapMirror;
u32 mFstSize;
u32 mData[(0x3110 - 0x40) / 4];
u32 mWiiHeap;
};
struct Info gInfo = {
.allocsize = 0, /*This is the code allocation*/
._loaderSize = 0, /*This is the size of the GeckoLoader*/
._loaderFullSize = 0, /*This is the size of the GeckoLoader + the codelist*/
._codelistPointer = (struct CodeList*)0x800018F8, /*This points to where the codelist address is set in the codehandler*/
._wiiVIHook = { 0x7CE33B78, 0x38870034, 0x38A70038, 0x38C7004C },
._gcnVIHook = { 0x7C030034, 0x38830020, 0x5485083C, 0x7C7F2A14, 0xA0030000, 0x7C7D2A14, 0x20A4003F, 0xB0030000 },
};
//struct Info* infoPointer = &gInfo;
static inline void flushAddr(void* addr)
{
dcbf(addr);
icbi(addr);
}
static inline void directWrite(u32* addr, u32 value)
{
*addr = value;
flushAddr(addr);
}
/*This constructs a branch instruction. &TO = ((TO - FROM) & MAX_OFFSET) | BRANCH_TYPE | !!isLink*/
static inline void directBranchEx(void* addr, void* ptr, BOOL lk)
{
directWrite((u32*)(addr), ((((u32)(ptr) - (u32)(addr)) & 0x3ffffff) | 0x48000000 | !!lk));
}
static inline u32* findArrayInstance(u32* start, const u32 end, const u32 arrayLength, const u32* hookData)
{
u32 index = 0;
/*Loop through the games RAM, make sure we don't find our own hook data by accident*/
for (u32 i = 0; (u32)&start[i] < end; ++i) {
/*If the data matches, increase the index counter and continue search,
else set index to 0 and continue searching*/
if (start[i] == hookData[index])
++index;
else
index = 0;
/*If the data has matched the whole array, return the address of the match*/
if (index >= (arrayLength) && ((u32)&start[i] < (u32)&gInfo || (u32)&start[i] > (u32)&gInfo + sizeof(gInfo))) {
return &start[i];
}
}
return NULL;
}
static inline u32* findU32Instance(u32* start, u32 end, u32 hookData)
{
for (u32 i = 0; (u32)&start[i] < end; ++i) {
if (start[i] == hookData) {
return &start[i];
}
}
return NULL;
}
/*Find VI hook for Game*/
static inline u32* findVIHook(struct DiscInfo* discResources, struct Info* infoPointer, u32* start, const u32 end)
{
volatile const u32* hookData;
volatile u32 arrayLength;
/*If the game is built for the Wii, set the hookdata to be the Wii variant*/
if (discResources->mWiiMagic) {
hookData = (const u32*)infoPointer->_wiiVIHook;
arrayLength = sizeof(infoPointer->_wiiVIHook) / sizeof(u32);
} else /*The game is built for the GCN, set the hookdata to be the GCN variant*/
{
hookData = (const u32*)infoPointer->_gcnVIHook;
arrayLength = sizeof(infoPointer->_gcnVIHook) / sizeof(u32);
}
return findArrayInstance(start, end, (const u32)arrayLength, hookData);
}
/*Call this after findFunction, finds the address of the first instance
of value hookInstruction, and hooks it to the pointer hookTo*/
static inline void hookFunction(volatile u32* start, u32 hookInstruction, u32 hookTo, BOOL isLink)
{
int i = 0;
while (start[i] != hookInstruction) {
++i;
}
directBranchEx((u32*)(&start[i]), (void*)(hookTo), isLink);
}
/*Reallocate the games internal memory heap based on the console
the game is for, to make space for our codes*/
static inline void setHeap(struct DiscInfo* discResources, u32 alloc)
{
if (discResources->mWiiMagic) {
discResources->mHeapPointer = (u32*)((u32)discResources->mWiiHeap - alloc);
discResources->mWiiHeap = (u32)discResources->mHeapPointer;
} else {
discResources->mHeapPointer = (u32*)((u32)discResources->mHeapPointer - alloc);
}
}
static inline void memCopy(u32* to, u32* from, s32 size)
{
for (s32 i = 0; i < size; ++i) {
to[i] = from[i];
}
}
static inline BOOL initMods(struct DiscInfo* discResources)
{
setHeap(discResources, gInfo.allocsize); /*Reallocate the internal heap*/
s32 sizeDiff = (gInfo._loaderFullSize - gInfo._loaderSize) / 4; /*Calculate size of codelist*/
/*Copy codelist to the new allocation*/
memCopy(discResources->mHeapPointer, findU32Instance((u32*)&gInfo, MEM1_END, GCT_MAGIC), sizeDiff);
/*Change codelist pointer to the new address in the allocation*/
gInfo._codelistPointer->mUpperBase = ((u32)discResources->mHeapPointer >> 16) & 0xFFFF;
gInfo._codelistPointer->mLowerOffset = (u32)(discResources->mHeapPointer) & 0xFFFF;
/*Update the cache, so that the instructions fully update*/
flushAddr(&gInfo._codelistPointer->mBaseASM);
u32* functionAddr = findVIHook(discResources, &gInfo, (u32*)MEM1_START, MEM1_END);
if (functionAddr == NULL) return FALSE;
hookFunction(functionAddr, 0x4E800020, CODEHANDLER_ENTRY, FALSE);
return TRUE;
}
int main()
{
struct DiscInfo* discResources = (struct DiscInfo*)MEM1_START;
if (discResources->mWiiMagic || discResources->mGCNMagic) {
if (initMods(discResources) == TRUE) {
callFunction(CODEHANDLER_ENTRY); /*Call the codehandler if successful*/
}
}
callFunction(GAME_ENTRY); /*Call the game start*/
}

375
main.py
View file

@ -1,375 +0,0 @@
#Written by JoshuaMK 2020
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 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('geckoloader.bin'), 'rb') as code, open(r'{}'.format(dolFile), 'rb') as dol, open(r'{}'.format(gctFile), 'rb') as gecko, open(resource_path('codehandler.bin'), 'rb') as handler, open('tmp.bin', 'wb+') as tmp, open(os.path.join('BUILD', os.path.basename(dolFile)), 'wb+') as final:
if int(get_size(dol).hex(), 16) < int('0x100', 16):
os.remove('tmp.bin')
parser.error('DOL header is corrupted. Please provide a clean file')
dol.seek(0)
'''Initialize the new DOL file'''
final.write(dol.read())
'''Initialize the sme-code loader'''
tmp.write(code.read())
code.seek(0, 0)
tmp.seek(0, 0)
'''Search for main entry of loader'''
entryIndex = 0
sample = tmp.read(4)
while sample:
if sample == ENTRY:
tmp.seek(-4, 1)
tmp.write(bytes.fromhex('7C0802A6'))
break
entryIndex += 4
sample = tmp.read(4)
tmp.seek(0)
'''Get BSS section for insert'''
final.seek(int('D8', 16))
BSS = int(final.read(4).hex(), 16)
BSS_length = int(final.read(4).hex(), 16)
dump_address = '{:08X}'.format(int(BSS + (BSS_length / 2)))[:-2] + '00'
_START = bytes.fromhex('{:08X}'.format(int(dump_address, 16) + entryIndex))
cLoader = bytes.fromhex(dump_address)
'''Get address split for later'''
upperAddr, lowerAddr = dump_address[:int(len(dump_address)/2)], dump_address[int(len(dump_address)/2):]
'''Get code initialize address'''
final.seek(int('E0', 16))
_init = [final.read(2), final.read(2)]
'''Patch the values for the addresses and such'''
heaped = False
sized = False
fsized = False
gUpperAddr = bytes.fromhex(upperAddr)
if isText == True:
geckoCheats = geckoParser(gctFile, args.txtcodes)
while heaped == False or sized == False or fsized == False:
try:
sample = tmp.read(4)
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 == '':
if isText == False:
size = get_size(gecko).hex().upper()
else:
size = geckoCheats[1]
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)
code.seek(0, 2)
gecko.seek(0, 2)
if isText == True:
tmp.write(get_size(code, int(geckoCheats[1], 16)))
else:
tmp.write(get_size(code, gecko.tell()))
fsized = True
except Exception as err:
print(err)
sys.exit(1)
'''Patch all load/store offsets to data'''
tmp.seek(0)
sample = tmp.read(2)
while sample:
if sample == GH:
tmp.seek(-2, 1)
tmp.write(gUpperAddr)
elif sample == GL:
tmp.seek(-2, 1)
tmp.write(bytes.fromhex('{:04X}'.format(int(lowerAddr, 16) + gInfo)))
elif sample == IH:
tmp.seek(-2, 1)
tmp.write(_init[0])
elif sample == IL:
tmp.seek(-2, 1)
tmp.write(_init[1])
sample = tmp.read(2)
final.seek(0, 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)
if isText == False:
final.write(gecko.read())
else:
final.write(geckoCheats[0])
final.seek(0, 0)
status = False
i = 0
while i < 6:
textOffset = int(final.read(4).hex(), 16)
if textOffset == 0:
status = True
offset = i * 4
'''Write offset to each section in DOL file header'''
final.seek(-4, 1)
final.write(dol_handler_offset)
final.write(dol_sme_offset)
final.seek(int('48', 16) + offset)
'''Write in game memory addresses for each section in DOL file header'''
final.write(bytes.fromhex('80001800'))
final.write(cLoader)
final.seek(int('E0', 16))
'''Write game entry in DOL file header'''
final.write(_START)
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
handler_size = get_size(handler)
tmp.seek(0, 2)
gecko.seek(0, 2)
if isText == True:
sme_code_size = get_size(tmp, int(geckoCheats[1], 16))
else:
sme_code_size = get_size(tmp, gecko.tell())
'''Write size of each section into DOL file header'''
final.seek(int('90', 16) + offset)
final.write(handler_size)
final.write(sme_code_size)
break
else:
i += 1
if status == False:
os.remove('tmp.bin')
parser.error(TREDLIT + 'Not enough text sections to patch the DOL file! Potentially due to previous mods?\n' + TRESET)
if isText == False:
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)
else:
if int(size, 16) < int(geckoCheats[1], 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 7 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 """
file.seek(0, 2)
return(bytes.fromhex('{:08X}'.format(file.tell() + offset)))
if __name__ == "__main__":
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)
except:
parser.error('The allocation was invalid\n')
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('No dol file was passed\n')
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('Neither a gct or gecko text file was passed\n')
time1 = time.time()
HEAP = bytes.fromhex('48454150')
LOADERSIZE = bytes.fromhex('4C53495A')
FULLSIZE = bytes.fromhex('4653495A')
ENTRY = bytes.fromhex('454E5452')
GH = bytes.fromhex('4748')
GL = bytes.fromhex('474C')
IH = bytes.fromhex('4948')
IL = bytes.fromhex('494C')
MODFIELD = [bytes.fromhex('BBBBBBBB'), bytes.fromhex('CCCCCCCC'), bytes.fromhex('DDDDDDDD'), bytes.fromhex('EEEEEEEE')]
try:
if not os.path.isdir('BUILD'):
os.mkdir('BUILD')
if not os.path.isfile(dolFile):
parser.error(dolFile + ' Does not exist')
if not os.path.isfile(gctFile):
parser.error(gctFile + ' Does not exist')
build(gctFile, dolFile, size, isText)
os.remove('tmp.bin')
if not args.quiet:
print('\n :: Compiled in {:0.4f} seconds!\n'.format(time.time() - time1))
except FileNotFoundError as err:
parser.error(err)
sys.exit(1)