From d06770063cb7c992b7ae37d6a5113afd26b904c0 Mon Sep 17 00:00:00 2001 From: BullyWiiPlaza Date: Fri, 2 Jun 2017 16:46:14 +0200 Subject: [PATCH] Fix kernel memory read crashing sometimes --- Makefile | 2 +- src/dynamic_libs/gx2_functions.h | 1 + src/dynamic_libs/socket_functions.h | 2 +- src/kernel/syscalls.c | 382 +++++++++++++------------- src/kernel/syscalls.h | 1 - src/main.c | 22 +- src/main.h | 2 - src/patcher/fs_logger.c | 1 - src/patcher/function_hooks.c | 141 ---------- src/patcher/function_hooks.h | 15 -- src/patcher/function_patcher_gx2.c | 74 +++++ src/patcher/function_patcher_gx2.h | 34 +++ src/patcher/texture.h | 103 +++++++ src/pygecko.c | 25 +- src/system/exception_handler.h | 5 +- src/utils/function_patcher.c | 404 ++++++++++++++++++++++++++++ src/utils/function_patcher.h | 123 +++++++++ src/utils/logger.h | 1 + tcpgecko.elf | Bin 146828 -> 148748 bytes 19 files changed, 960 insertions(+), 378 deletions(-) delete mode 100644 src/patcher/function_hooks.c delete mode 100644 src/patcher/function_hooks.h create mode 100644 src/patcher/function_patcher_gx2.c create mode 100644 src/patcher/function_patcher_gx2.h create mode 100644 src/patcher/texture.h create mode 100644 src/utils/function_patcher.c create mode 100644 src/utils/function_patcher.h diff --git a/Makefile b/Makefile index eab66e0..f572f5b 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ MAKEFLAGS += --no-print-directory #--------------------------------------------------------------------------------- # any extra libraries we wish to link with the project #--------------------------------------------------------------------------------- -LIBS := -lz -liosuhax +LIBS := -lz -liosuhax -lgd #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/src/dynamic_libs/gx2_functions.h b/src/dynamic_libs/gx2_functions.h index 0affa11..2ecf3d2 100644 --- a/src/dynamic_libs/gx2_functions.h +++ b/src/dynamic_libs/gx2_functions.h @@ -29,6 +29,7 @@ extern "C" { #endif #include "gx2_types.h" +#include "../common/fs_defs.h" void InitGX2FunctionPointers(void); diff --git a/src/dynamic_libs/socket_functions.h b/src/dynamic_libs/socket_functions.h index 85b12be..03c9071 100644 --- a/src/dynamic_libs/socket_functions.h +++ b/src/dynamic_libs/socket_functions.h @@ -95,4 +95,4 @@ extern int (*inet_aton)(const char *cp, struct in_addr *inp); } #endif -#endif // __SOCKET_FUNCTIONS_H_ +#endif \ No newline at end of file diff --git a/src/kernel/syscalls.c b/src/kernel/syscalls.c index 03bcca6..580e794 100644 --- a/src/kernel/syscalls.c +++ b/src/kernel/syscalls.c @@ -8,247 +8,235 @@ extern void my_PrepareTitle_hook(void); static unsigned int origPrepareTitleInstr = 0; -static void KernelCopyData(unsigned int addr, unsigned int src, unsigned int len) -{ - /* - * Setup a DBAT access with cache inhibited to write through and read directly from memory - */ - unsigned int dbatu0, dbatl0, dbatu1, dbatl1; - // save the original DBAT value - asm volatile("mfdbatu %0, 0" : "=r" (dbatu0)); - asm volatile("mfdbatl %0, 0" : "=r" (dbatl0)); - asm volatile("mfdbatu %0, 1" : "=r" (dbatu1)); - asm volatile("mfdbatl %0, 1" : "=r" (dbatl1)); +static void KernelCopyData(unsigned int addr, unsigned int src, unsigned int len) { + /* + * Setup a DBAT access with cache inhibited to write through and read directly from memory + */ + unsigned int dbatu0, dbatl0, dbatu1, dbatl1; + // save the original DBAT value + asm volatile("mfdbatu %0, 0" : "=r" (dbatu0)); + asm volatile("mfdbatl %0, 0" : "=r" (dbatl0)); + asm volatile("mfdbatu %0, 1" : "=r" (dbatu1)); + asm volatile("mfdbatl %0, 1" : "=r" (dbatl1)); - unsigned int target_dbatu0 = 0; - unsigned int target_dbatl0 = 0; - unsigned int target_dbatu1 = 0; - unsigned int target_dbatl1 = 0; + unsigned int target_dbatu0 = 0; + unsigned int target_dbatl0 = 0; + unsigned int target_dbatu1 = 0; + unsigned int target_dbatl1 = 0; - unsigned char *dst_p = (unsigned char*)addr; - unsigned char *src_p = (unsigned char*)src; + unsigned char *dst_p = (unsigned char *) addr; + unsigned char *src_p = (unsigned char *) src; - // we only need DBAT modification for addresses out of our own DBAT range - // as our own DBAT is available everywhere for user and supervisor - // since our own DBAT is on DBAT5 position we don't collide here - if(addr < 0x00800000 || addr >= 0x01000000) - { - target_dbatu0 = (addr & 0x00F00000) | 0xC0000000 | 0x1F; - target_dbatl0 = (addr & 0xFFF00000) | 0x32; - asm volatile("mtdbatu 0, %0" : : "r" (target_dbatu0)); - asm volatile("mtdbatl 0, %0" : : "r" (target_dbatl0)); - dst_p = (unsigned char*)((addr & 0xFFFFFF) | 0xC0000000); - } - if(src < 0x00800000 || src >= 0x01000000) - { - target_dbatu1 = (src & 0x00F00000) | 0xB0000000 | 0x1F; - target_dbatl1 = (src & 0xFFF00000) | 0x32; + // we only need DBAT modification for addresses out of our own DBAT range + // as our own DBAT is available everywhere for user and supervisor + // since our own DBAT is on DBAT5 position we don't collide here + if (addr < 0x00800000 || addr >= 0x01000000) { + target_dbatu0 = (addr & 0x00F00000) | 0xC0000000 | 0x1F; + target_dbatl0 = (addr & 0xFFF00000) | 0x32; + asm volatile("mtdbatu 0, %0" : : "r" (target_dbatu0)); + asm volatile("mtdbatl 0, %0" : : "r" (target_dbatl0)); + dst_p = (unsigned char *) ((addr & 0xFFFFFF) | 0xC0000000); + } + if (src < 0x00800000 || src >= 0x01000000) { + target_dbatu1 = (src & 0x00F00000) | 0xB0000000 | 0x1F; + target_dbatl1 = (src & 0xFFF00000) | 0x32; - asm volatile("mtdbatu 1, %0" : : "r" (target_dbatu1)); - asm volatile("mtdbatl 1, %0" : : "r" (target_dbatl1)); - src_p = (unsigned char*)((src & 0xFFFFFF) | 0xB0000000); - } + asm volatile("mtdbatu 1, %0" : : "r" (target_dbatu1)); + asm volatile("mtdbatl 1, %0" : : "r" (target_dbatl1)); + src_p = (unsigned char *) ((src & 0xFFFFFF) | 0xB0000000); + } - asm volatile("eieio; isync"); + asm volatile("eieio; isync"); - unsigned int i; - for(i = 0; i < len; i++) - { - // if we are on the edge to next chunk - if((target_dbatu0 != 0) && (((unsigned int)dst_p & 0x00F00000) != (target_dbatu0 & 0x00F00000))) - { - target_dbatu0 = ((addr + i) & 0x00F00000) | 0xC0000000 | 0x1F; - target_dbatl0 = ((addr + i) & 0xFFF00000) | 0x32; - dst_p = (unsigned char*)(((addr + i) & 0xFFFFFF) | 0xC0000000); + unsigned int i; + for (i = 0; i < len; i++) { + // if we are on the edge to next chunk + if ((target_dbatu0 != 0) && (((unsigned int) dst_p & 0x00F00000) != (target_dbatu0 & 0x00F00000))) { + target_dbatu0 = ((addr + i) & 0x00F00000) | 0xC0000000 | 0x1F; + target_dbatl0 = ((addr + i) & 0xFFF00000) | 0x32; + dst_p = (unsigned char *) (((addr + i) & 0xFFFFFF) | 0xC0000000); - asm volatile("eieio; isync"); - asm volatile("mtdbatu 0, %0" : : "r" (target_dbatu0)); - asm volatile("mtdbatl 0, %0" : : "r" (target_dbatl0)); - asm volatile("eieio; isync"); - } - if((target_dbatu1 != 0) && (((unsigned int)src_p & 0x00F00000) != (target_dbatu1 & 0x00F00000))) - { - target_dbatu1 = ((src + i) & 0x00F00000) | 0xB0000000 | 0x1F; - target_dbatl1 = ((src + i) & 0xFFF00000) | 0x32; - src_p = (unsigned char*)(((src + i) & 0xFFFFFF) | 0xB0000000); + asm volatile("eieio; isync"); + asm volatile("mtdbatu 0, %0" : : "r" (target_dbatu0)); + asm volatile("mtdbatl 0, %0" : : "r" (target_dbatl0)); + asm volatile("eieio; isync"); + } + if ((target_dbatu1 != 0) && (((unsigned int) src_p & 0x00F00000) != (target_dbatu1 & 0x00F00000))) { + target_dbatu1 = ((src + i) & 0x00F00000) | 0xB0000000 | 0x1F; + target_dbatl1 = ((src + i) & 0xFFF00000) | 0x32; + src_p = (unsigned char *) (((src + i) & 0xFFFFFF) | 0xB0000000); - asm volatile("eieio; isync"); - asm volatile("mtdbatu 1, %0" : : "r" (target_dbatu1)); - asm volatile("mtdbatl 1, %0" : : "r" (target_dbatl1)); - asm volatile("eieio; isync"); - } + asm volatile("eieio; isync"); + asm volatile("mtdbatu 1, %0" : : "r" (target_dbatu1)); + asm volatile("mtdbatl 1, %0" : : "r" (target_dbatl1)); + asm volatile("eieio; isync"); + } - *dst_p = *src_p; + *dst_p = *src_p; - ++dst_p; - ++src_p; - } + ++dst_p; + ++src_p; + } - /* - * Restore original DBAT value + /* + * Restore original DBAT value */ - asm volatile("eieio; isync"); - asm volatile("mtdbatu 0, %0" : : "r" (dbatu0)); - asm volatile("mtdbatl 0, %0" : : "r" (dbatl0)); - asm volatile("mtdbatu 1, %0" : : "r" (dbatu1)); - asm volatile("mtdbatl 1, %0" : : "r" (dbatl1)); - asm volatile("eieio; isync"); + asm volatile("eieio; isync"); + asm volatile("mtdbatu 0, %0" : : "r" (dbatu0)); + asm volatile("mtdbatl 0, %0" : : "r" (dbatl0)); + asm volatile("mtdbatu 1, %0" : : "r" (dbatu1)); + asm volatile("mtdbatl 1, %0" : : "r" (dbatl1)); + asm volatile("eieio; isync"); } -static void KernelReadDBATs(bat_table_t * table) -{ - u32 i = 0; +static void KernelReadDBATs(bat_table_t *table) { + u32 i = 0; - asm volatile("eieio; isync"); + asm volatile("eieio; isync"); - asm volatile("mfspr %0, 536" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 537" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 538" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 539" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 540" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 541" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 542" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 543" : "=r" (table->bat[i].l)); - i++; + asm volatile("mfspr %0, 536" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 537" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 538" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 539" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 540" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 541" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 542" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 543" : "=r" (table->bat[i].l)); + i++; - asm volatile("mfspr %0, 568" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 569" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 570" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 571" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 572" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 573" : "=r" (table->bat[i].l)); - i++; - asm volatile("mfspr %0, 574" : "=r" (table->bat[i].h)); - asm volatile("mfspr %0, 575" : "=r" (table->bat[i].l)); + asm volatile("mfspr %0, 568" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 569" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 570" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 571" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 572" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 573" : "=r" (table->bat[i].l)); + i++; + asm volatile("mfspr %0, 574" : "=r" (table->bat[i].h)); + asm volatile("mfspr %0, 575" : "=r" (table->bat[i].l)); } -static void KernelWriteDBATs(bat_table_t * table) -{ - u32 i = 0; +static void KernelWriteDBATs(bat_table_t *table) { + u32 i = 0; - asm volatile("eieio; isync"); + asm volatile("eieio; isync"); - asm volatile("mtspr 536, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 537, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 538, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 539, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 540, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 541, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 542, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 543, %0" : : "r" (table->bat[i].l)); - i++; + asm volatile("mtspr 536, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 537, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 538, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 539, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 540, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 541, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 542, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 543, %0" : : "r" (table->bat[i].l)); + i++; - asm volatile("mtspr 568, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 569, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 570, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 571, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 572, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 573, %0" : : "r" (table->bat[i].l)); - i++; - asm volatile("mtspr 574, %0" : : "r" (table->bat[i].h)); - asm volatile("mtspr 575, %0" : : "r" (table->bat[i].l)); + asm volatile("mtspr 568, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 569, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 570, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 571, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 572, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 573, %0" : : "r" (table->bat[i].l)); + i++; + asm volatile("mtspr 574, %0" : : "r" (table->bat[i].h)); + asm volatile("mtspr 575, %0" : : "r" (table->bat[i].l)); - asm volatile("eieio; isync"); + asm volatile("eieio; isync"); } /* Read a 32-bit word with kernel permissions */ -uint32_t __attribute__ ((noinline)) kern_read(const void *addr) -{ +uint32_t __attribute__ ((noinline)) kern_read(const void *addr) { uint32_t result; asm volatile ( - "li 3,1\n" - "li 4,0\n" - "li 5,0\n" - "li 6,0\n" - "li 7,0\n" - "lis 8,1\n" - "mr 9,%1\n" - "li 0,0x3400\n" - "mr %0,1\n" - "sc\n" - "nop\n" - "mr 1,%0\n" - "mr %0,3\n" - : "=r"(result) - : "b"(addr) - : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", - "11", "12" + "li 3,1\n" + "li 4,0\n" + "li 5,0\n" + "li 6,0\n" + "li 7,0\n" + "lis 8,1\n" + "mr 9,%1\n" + "li 0,0x3400\n" + "mr %0,1\n" + "sc\n" + "nop\n" + "mr 1,%0\n" + "mr %0,3\n" + : "=r"(result) + : "b"(addr) + : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12" ); return result; } /* Write a 32-bit word with kernel permissions */ -void __attribute__ ((noinline)) kern_write(void *addr, uint32_t value) -{ +void __attribute__ ((noinline)) kern_write(void *addr, uint32_t value) { asm volatile ( - "li 3,1\n" - "li 4,0\n" - "mr 5,%1\n" - "li 6,0\n" - "li 7,0\n" - "lis 8,1\n" - "mr 9,%0\n" - "mr %1,1\n" - "li 0,0x3500\n" - "sc\n" - "nop\n" - "mr 1,%1\n" - : - : "r"(addr), "r"(value) - : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", - "11", "12" - ); + "li 3,1\n" + "li 4,0\n" + "mr 5,%1\n" + "li 6,0\n" + "li 7,0\n" + "lis 8,1\n" + "mr 9,%0\n" + "mr %1,1\n" + "li 0,0x3500\n" + "sc\n" + "nop\n" + "mr 1,%1\n" + : + : "r"(addr), "r"(value) + : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12" + ); } -void KernelSetupSyscalls(void) -{ - //! assign 1 so that this variable gets into the retained .data section - static uint8_t ucSyscallsSetupRequired = 1; - if(!ucSyscallsSetupRequired) - return; +void KernelSetupSyscalls(void) { + //! assign 1 so that this variable gets into the retained .data section + static uint8_t ucSyscallsSetupRequired = 1; + if (!ucSyscallsSetupRequired) + return; - ucSyscallsSetupRequired = 0; + ucSyscallsSetupRequired = 0; - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl1 + (0x36 * 4)), (unsigned int)KernelReadDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl2 + (0x36 * 4)), (unsigned int)KernelReadDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl3 + (0x36 * 4)), (unsigned int)KernelReadDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl4 + (0x36 * 4)), (unsigned int)KernelReadDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl5 + (0x36 * 4)), (unsigned int)KernelReadDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl1 + (0x36 * 4)), (unsigned int) KernelReadDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl2 + (0x36 * 4)), (unsigned int) KernelReadDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl3 + (0x36 * 4)), (unsigned int) KernelReadDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl4 + (0x36 * 4)), (unsigned int) KernelReadDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl5 + (0x36 * 4)), (unsigned int) KernelReadDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl1 + (0x37 * 4)), (unsigned int)KernelWriteDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl2 + (0x37 * 4)), (unsigned int)KernelWriteDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl3 + (0x37 * 4)), (unsigned int)KernelWriteDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl4 + (0x37 * 4)), (unsigned int)KernelWriteDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl5 + (0x37 * 4)), (unsigned int)KernelWriteDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl1 + (0x37 * 4)), (unsigned int) KernelWriteDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl2 + (0x37 * 4)), (unsigned int) KernelWriteDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl3 + (0x37 * 4)), (unsigned int) KernelWriteDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl4 + (0x37 * 4)), (unsigned int) KernelWriteDBATs); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl5 + (0x37 * 4)), (unsigned int) KernelWriteDBATs); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl1 + (0x25 * 4)), (unsigned int)KernelCopyData); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl2 + (0x25 * 4)), (unsigned int)KernelCopyData); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl3 + (0x25 * 4)), (unsigned int)KernelCopyData); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl4 + (0x25 * 4)), (unsigned int)KernelCopyData); - kern_write((void*)(OS_SPECIFICS->addr_KernSyscallTbl5 + (0x25 * 4)), (unsigned int)KernelCopyData); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl1 + (0x25 * 4)), (unsigned int) KernelCopyData); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl2 + (0x25 * 4)), (unsigned int) KernelCopyData); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl3 + (0x25 * 4)), (unsigned int) KernelCopyData); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl4 + (0x25 * 4)), (unsigned int) KernelCopyData); + kern_write((void *) (OS_SPECIFICS->addr_KernSyscallTbl5 + (0x25 * 4)), (unsigned int) KernelCopyData); - //! write our hook to the - u32 addr_my_PrepareTitle_hook = ((u32)my_PrepareTitle_hook) | 0x48000003; - DCFlushRange(&addr_my_PrepareTitle_hook, sizeof(addr_my_PrepareTitle_hook)); + //! write our hook to the + u32 addr_my_PrepareTitle_hook = ((u32) my_PrepareTitle_hook) | 0x48000003; + DCFlushRange(&addr_my_PrepareTitle_hook, sizeof(addr_my_PrepareTitle_hook)); - SC0x25_KernelCopyData((u32)&origPrepareTitleInstr, (u32)addr_PrepareTitle_hook, 4); - SC0x25_KernelCopyData((u32)addr_PrepareTitle_hook, (u32)OSEffectiveToPhysical(&addr_my_PrepareTitle_hook), 4); + SC0x25_KernelCopyData((u32) &origPrepareTitleInstr, (u32) addr_PrepareTitle_hook, 4); + SC0x25_KernelCopyData((u32) addr_PrepareTitle_hook, (u32) OSEffectiveToPhysical(&addr_my_PrepareTitle_hook), 4); } -void KernelRestoreInstructions(void) -{ - if(origPrepareTitleInstr != 0) - SC0x25_KernelCopyData((u32)addr_PrepareTitle_hook, (u32)&origPrepareTitleInstr, 4); +void KernelRestoreInstructions(void) { + if (origPrepareTitleInstr != 0) + SC0x25_KernelCopyData((u32) addr_PrepareTitle_hook, (u32) &origPrepareTitleInstr, 4); } diff --git a/src/kernel/syscalls.h b/src/kernel/syscalls.h index 283aa0d..298d599 100644 --- a/src/kernel/syscalls.h +++ b/src/kernel/syscalls.h @@ -6,7 +6,6 @@ extern "C" { #endif #include -#include "common/kernel_defs.h" #include "../common/kernel_defs.h" void KernelSetupSyscalls(void); diff --git a/src/main.c b/src/main.c index 158ed0d..f25f6b5 100644 --- a/src/main.c +++ b/src/main.c @@ -10,21 +10,28 @@ #include "dynamic_libs/sys_functions.h" #include "dynamic_libs/vpad_functions.h" #include "dynamic_libs/socket_functions.h" -#include "patcher/function_hooks.h" #include "kernel/kernel_functions.h" #include "system/memory.h" #include "common/common.h" #include "main.h" #include "code_handler.h" +#include "utils/logger.h" +#include "utils/function_patcher.h" +#include "patcher/function_patcher_gx2.h" bool isCodeHandlerInstalled; #define PRINT_TEXT2(x, y, ...) { snprintf(messageBuffer, 80, __VA_ARGS__); OSScreenPutFontEx(0, x, y, messageBuffer); OSScreenPutFontEx(1, x, y, messageBuffer); } typedef enum { - EXIT = 0x0, TCP_GECKO = 0x1 + EXIT, + TCP_GECKO } LaunchMethod; +void applyFunctionPatches() { + patchIndividualMethodHooks(method_hooks_gx2, method_hooks_size_gx2, method_calls_gx2); +} + /* Entry point */ int Menu_Main(void) { //!******************************************************************* @@ -37,6 +44,10 @@ int Menu_Main(void) { InitVPadFunctionPointers(); InitSysFunctionPointers(); + log_init(COMPUTER_IP_ADDRESS); + log_print("Patching functions\n"); + applyFunctionPatches(); + if (strcasecmp("men.rpx", cosAppXmlInfoStruct.rpx_name) == 0) { return EXIT_RELAUNCH_ON_LOAD; } else if (strlen(cosAppXmlInfoStruct.rpx_name) > 0 && @@ -73,7 +84,7 @@ int Menu_Main(void) { ); SetupKernelCallback(); - PatchMethodHooks(); + // PatchMethodHooks(); memoryInitialize(); @@ -143,7 +154,8 @@ int Menu_Main(void) { } // Button pressed ? - update_screen = (pressedButtons & (VPAD_BUTTON_LEFT | VPAD_BUTTON_RIGHT | VPAD_BUTTON_UP | VPAD_BUTTON_DOWN)) ? 1 : 0; + update_screen = (pressedButtons & (VPAD_BUTTON_LEFT | VPAD_BUTTON_RIGHT | VPAD_BUTTON_UP | VPAD_BUTTON_DOWN)) + ? 1 : 0; usleep(20 * 1000); } @@ -156,7 +168,7 @@ int Menu_Main(void) { memoryRelease(); if (launchMethod == EXIT) { - RestoreInstructions(); + // RestoreInstructions(); return EXIT_SUCCESS; } else { SYSLaunchMenu(); diff --git a/src/main.h b/src/main.h index 361e1a6..cc72010 100644 --- a/src/main.h +++ b/src/main.h @@ -9,8 +9,6 @@ extern "C" { #endif -#define COMPUTER_IP_ADDRESS "192.168.2.103" - //! C wrapper for our C++ functions int Menu_Main(void); diff --git a/src/patcher/fs_logger.c b/src/patcher/fs_logger.c index 163ddf5..20e9e37 100644 --- a/src/patcher/fs_logger.c +++ b/src/patcher/fs_logger.c @@ -2,7 +2,6 @@ #include "common/common.h" #include "../dynamic_libs/os_functions.h" #include "../dynamic_libs/socket_functions.h" -#include "function_hooks.h" #include "fs_logger.h" #include "utils/utils.h" diff --git a/src/patcher/function_hooks.c b/src/patcher/function_hooks.c deleted file mode 100644 index 469801e..0000000 --- a/src/patcher/function_hooks.c +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include -#include -#include "../common/common.h" -#include "../dynamic_libs/os_functions.h" -#include "../kernel/kernel_functions.h" -#include "function_hooks.h" -#include "../dynamic_libs/gx2_types.h" - -#define LIB_CODE_RW_BASE_OFFSET 0xC1000000 -#define CODE_RW_BASE_OFFSET 0x00000000 - -#define DECL(res, name, ...) \ - res (* real_ ## name)(__VA_ARGS__); \ - res my_ ## name(__VA_ARGS__) - -/* ***************************************************************************** - * Creates function pointer array - * ****************************************************************************/ -// #define MAKE_MAGIC(x, lib) { (unsigned int) my_ ## x, (unsigned int) &real_ ## x, lib, # x } - -#define MAKE_MAGIC(x, lib, functionType) { (unsigned int) my_ ## x, (unsigned int) &real_ ## x, lib, # x,0,0,functionType,0} - -DECL(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, - s32 scan_target) { - // TODO Does not execute - GX2Surface surface = colorBuffer->surface; - u32 image_size = surface.image_size; - char buffer[100] = {0}; - __os_snprintf(buffer, 100, "Image size: %i", image_size); - OSFatal(buffer); - - real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target); -} - -DECL(int, FSInit, void) { - return real_FSInit(); -} - -DECL(int, socket_lib_finish, void) { - return 0; -} - -static const struct hooks_magic_t { - const unsigned int replaceAddr; - const unsigned int replaceCall; - const unsigned int library; - const char functionName[50]; - unsigned int realAddr; - unsigned int restoreInstruction; - unsigned char functionType; - unsigned char alreadyPatched; -} method_hooks[] = { - MAKE_MAGIC(FSInit, LIB_CORE_INIT, STATIC_FUNCTION), - MAKE_MAGIC(socket_lib_finish, LIB_CORE_INIT, STATIC_FUNCTION), - MAKE_MAGIC(GX2CopyColorBufferToScanBuffer, LIB_GX2, DYNAMIC_FUNCTION), -}; - -//! buffer to store our 2 instructions needed for our replacements -//! the code will be placed in the address of that buffer - CODE_RW_BASE_OFFSET -//! avoid this buffer to be placed in BSS and reset on start up -volatile unsigned int fs_method_calls[sizeof(method_hooks) / sizeof(struct hooks_magic_t) * 2]; - -void PatchMethodHooks(void) { - restore_instructions_t *restore = RESTORE_INSTR_ADDR; - //! check if it is already patched - if (restore->magic == RESTORE_INSTR_MAGIC) - return; - - restore->magic = RESTORE_INSTR_MAGIC; - restore->instr_count = 0; - - bat_table_t table; - KernelSetDBATs(&table); - - /* Patch branches to it. */ - volatile unsigned int *space = &fs_method_calls[0]; - - int method_hooks_count = sizeof(method_hooks) / sizeof(struct hooks_magic_t); - - for (int i = 0; i < method_hooks_count; i++) { - unsigned int repl_addr = method_hooks[i].replaceAddr; - unsigned int call_addr = method_hooks[i].replaceCall; - - unsigned int real_addr = 0; - - if (strcmp(method_hooks[i].functionName, "OSDynLoad_Acquire") == 0) { - memcpy(&real_addr, &OSDynLoad_Acquire, 4); - } else { - OSDynLoad_FindExport(coreinit_handle, 0, method_hooks[i].functionName, &real_addr); - } - - // fill the restore instruction section - restore->data[restore->instr_count].addr = real_addr; - restore->data[restore->instr_count].instr = *(volatile unsigned int *) (LIB_CODE_RW_BASE_OFFSET + real_addr); - restore->instr_count++; - - // set pointer to the real function - *(volatile unsigned int *) (call_addr) = (unsigned int) (space) - CODE_RW_BASE_OFFSET; - DCFlushRange((void *) (call_addr), 4); - - // fill the instruction of the real function - *space = *(volatile unsigned int *) (LIB_CODE_RW_BASE_OFFSET + real_addr); - space++; - - // jump to real function skipping the first/replaced instruction - *space = 0x48000002 | ((real_addr + 4) & 0x03fffffc); - space++; - DCFlushRange((void *) (space - 2), 8); - ICInvalidateRange((unsigned char *) (space - 2) - CODE_RW_BASE_OFFSET, 8); - - unsigned int replace_instr = 0x48000002 | (repl_addr & 0x03fffffc); - *(volatile unsigned int *) (LIB_CODE_RW_BASE_OFFSET + real_addr) = replace_instr; - DCFlushRange((void *) (LIB_CODE_RW_BASE_OFFSET + real_addr), 4); - ICInvalidateRange((void *) (real_addr), 4); - } - - KernelRestoreDBATs(&table); -} - -/* ****************************************************************** */ -/* RESTORE ORIGINAL INSTRUCTIONS */ -/* ****************************************************************** */ -void RestoreInstructions(void) { - bat_table_t table; - KernelSetDBATs(&table); - - restore_instructions_t *restore = RESTORE_INSTR_ADDR; - if (restore->magic == RESTORE_INSTR_MAGIC) { - for (unsigned int i = 0; i < restore->instr_count; i++) { - *(volatile unsigned int *) (LIB_CODE_RW_BASE_OFFSET + restore->data[i].addr) = restore->data[i].instr; - DCFlushRange((void *) (LIB_CODE_RW_BASE_OFFSET + restore->data[i].addr), (u32) 4); - ICInvalidateRange((void *) restore->data[i].addr, (u32) 4); - } - } - restore->magic = 0; - restore->instr_count = 0; - - KernelRestoreDBATs(&table); - KernelRestoreInstructions(); -} \ No newline at end of file diff --git a/src/patcher/function_hooks.h b/src/patcher/function_hooks.h deleted file mode 100644 index 02fddfd..0000000 --- a/src/patcher/function_hooks.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _FUNCTION_HOOKS_H_ -#define _FUNCTION_HOOKS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -void PatchMethodHooks(void); -void RestoreInstructions(void); - -#ifdef __cplusplus -} -#endif - -#endif /* _FS_H */ diff --git a/src/patcher/function_patcher_gx2.c b/src/patcher/function_patcher_gx2.c new file mode 100644 index 0000000..8674bdc --- /dev/null +++ b/src/patcher/function_patcher_gx2.c @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2017 Maschell, BullyWiiPlaza + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ + +#include // malloc()/free() +#include // image library +#include "../utils/function_patcher.h" +#include "../utils/logger.h" +#include "texture.h" +#include + +static volatile int executionCounter = 0; + +declareFunctionHook(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, s32 + scan_target) { + if (executionCounter > 120) { + GX2Surface surface = colorBuffer->surface; + /*s32 format = surface.format; + + gdImagePtr gdImagePtr = 0; + bool no_convert; + u8 *image_data = NULL; + int img_size = 0; + if (format == 0x1A) { + UnormR8G8B8A82Yuv420p(&image_data, surface.image_data, &img_size, surface.width, surface.height, + surface.pitch); + } else if (format == 0x19) { + no_convert = true; + UnormR8G8B8A8TogdImage(&gdImagePtr, surface.image_data, surface.width, surface.height, surface.pitch); + } else { + no_convert = true; + } + + u32 imd_size = 0; + void *data = gdImageJpegPtr(gdImagePtr, &imd_size, 95); + if (data) { + JpegData jpeg; + jpeg.img_size = imd_size; + jpeg.img_data = data; + jpeg.img_id = 0; + }*/ + + log_printf("GX2CopyColorBufferToScanBuffer {surface width:%d, height:%d, image size:%d, image data:%x}\n", + surface.width, surface.height, surface.image_size, surface.image_data); + + executionCounter = 0; + } + + executionCounter++; + + real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target); +} + +FunctionHook method_hooks_gx2[] __attribute__((section(".data"))) = { + makeFunctionHook(GX2CopyColorBufferToScanBuffer, LIB_GX2, STATIC_FUNCTION) +}; + +u32 method_hooks_size_gx2 __attribute__((section(".data"))) = sizeof(method_hooks_gx2) / sizeof(FunctionHook); + +volatile unsigned int method_calls_gx2[sizeof(method_hooks_gx2) / sizeof(FunctionHook) * + FUNCTION_PATCHER_METHOD_STORE_SIZE] __attribute__((section(".data"))); \ No newline at end of file diff --git a/src/patcher/function_patcher_gx2.h b/src/patcher/function_patcher_gx2.h new file mode 100644 index 0000000..86f6893 --- /dev/null +++ b/src/patcher/function_patcher_gx2.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright (C) 2016 Maschell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ + +#ifndef FUNCTION_PATCHER_EXAMPLE_GX2_FUNCTION_PATCHER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../utils/function_patcher.h" + +extern FunctionHook method_hooks_gx2[]; +extern u32 method_hooks_size_gx2; +extern volatile unsigned int method_calls_gx2[]; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/patcher/texture.h b/src/patcher/texture.h new file mode 100644 index 0000000..7724dc4 --- /dev/null +++ b/src/patcher/texture.h @@ -0,0 +1,103 @@ +#pragma once + +#include "../common/fs_defs.h" +#include "../utils/logger.h" +#include "../dynamic_libs/os_functions.h" +#include + +typedef struct +{ + u32 img_size; + u32 img_id; + void * img_data; +} JpegData; + +typedef struct _R8G8B8A8_COLOR { + u8 R, G, B, A; +} R8G8B8A8_COLOR; + +void UnormR8G8B8A8TogdImage(gdImagePtr *gdImgTmp, void *image_data, u32 width, u32 rows, u32 pitch) { + *gdImgTmp = gdImageCreateTrueColor(width / 2, rows / 2); + + R8G8B8A8_COLOR *buffer = (R8G8B8A8_COLOR *) image_data; + R8G8B8A8_COLOR val; + for (u32 row = 0; row < rows; ++row) { + for (u32 x = 0; x < width; ++x) { + val = buffer[row * pitch + x]; + gdImageSetPixel(*gdImgTmp, x / 2, row / 2, gdTrueColor(val.R, val.G, val.B)); + ++x; + } + ++row; + } +} + +void UnormR8G8B8A82Yuv420p(u8 **destination_, void *image_data, int *dest_img_size, u32 width, u32 height, u32 pitch) { + u32 image_size = width * height; + u32 upos = image_size; + u32 vpos = upos + upos / 4; + *dest_img_size = (vpos + upos / 4); + if (*destination_) { + free(destination_); + } + *destination_ = (u8 *) malloc(sizeof(u8) * *dest_img_size); + u8 *destination = *destination_; + if (!destination) { + *dest_img_size = 0; + return; + } + log_printf("allocated %d \n", *dest_img_size); + + R8G8B8A8_COLOR *buffer = (R8G8B8A8_COLOR *) image_data; + + u32 i = 0; + + for (u32 line = 0; line < height; ++line) { + if (!(line % 2)) { + for (u32 x = 0; x < width; x += 2) { + u8 r = buffer[line * pitch + x].R; + u8 g = buffer[line * pitch + x].G; + u8 b = buffer[line * pitch + x].B; + + destination[i++] = ((66 * r + 129 * g + 25 * b) >> 8) + 16; + + destination[upos++] = ((-38 * r + -74 * g + 112 * b) >> 8) + 128; + destination[vpos++] = ((112 * r + -94 * g + -18 * b) >> 8) + 128; + + r = buffer[line * pitch + x].R; + g = buffer[line * pitch + x].G; + b = buffer[line * pitch + x].B; + + destination[i++] = ((66 * r + 129 * g + 25 * b) >> 8) + 16; + } + } else { + for (u32 x = 0; x < width; x += 1) { + u8 r = buffer[line * pitch + x].R; + u8 g = buffer[line * pitch + x].G; + u8 b = buffer[line * pitch + x].B; + + destination[i++] = ((66 * r + 129 * g + 25 * b) >> 8) + 16; + } + } + } + sleep(1); + log_printf("done %d \n", *dest_img_size); +} + +void UnormR10G10B10A2TogdImage(gdImagePtr *gdImgTmp, void *image_data, u32 width, u32 rows, u32 pitch) { + u32 *buffer = (u32 *) image_data; + u32 val = 0; + for (u32 row = 0; row < rows; ++row) { + for (u32 x = 0; x < width; ++x) { + /* + R ((test >> 24) & 0xFF)) + G ((test >> 14) & 0xFF)) + B ((test >> 4) & 0xFF)) + alpha (test & 0x3); + */ + val = buffer[row * pitch + x]; + gdImageSetPixel(*gdImgTmp, x, row, gdTrueColor(((val >> 24) & 0xFF), + ((val >> 14) & 0xFF), + ((val >> 4) & 0xFF))); + } + } +} \ No newline at end of file diff --git a/src/pygecko.c b/src/pygecko.c index cbed7b7..a0e9854 100644 --- a/src/pygecko.c +++ b/src/pygecko.c @@ -78,7 +78,7 @@ struct pygecko_bss_t { #define EWOULDBLOCK 6 #define DATA_BUFFER_SIZE 0x5000 // #define WRITE_SCREEN_MESSAGE_BUFFER_SIZE 100 -#define SERVER_VERSION "05/24/2017" +#define SERVER_VERSION "06/02/2017" #define ONLY_ZEROS_READ 0xB0 #define NON_ZEROS_READ 0xBD @@ -378,7 +378,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { } case COMMAND_READ_MEMORY_KERNEL: { const unsigned char *startingAddress, *endingAddress, *useKernRead; - ret = recvwait(bss, clientfd, buffer, 3 * 4); + ret = recvwait(bss, clientfd, buffer, 3 * sizeof(int)); CHECK_ERROR(ret < 0) startingAddress = ((const unsigned char **) buffer)[0]; endingAddress = ((const unsigned char **) buffer)[1]; @@ -395,7 +395,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { // Figure out if all bytes are zero to possibly avoid sending them int rangeIterationIndex = 0; for (; rangeIterationIndex < length; rangeIterationIndex++) { - int character = useKernRead ? kern_read(startingAddress + rangeIterationIndex) + int character = useKernRead ? readKernelMemory(startingAddress + rangeIterationIndex) : startingAddress[rangeIterationIndex]; if (character != 0) { break; @@ -407,11 +407,12 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { ret = sendByte(bss, clientfd, ONLY_ZEROS_READ); CHECK_ERROR(ret < 0) } else { + // Send the real bytes now buffer[0] = NON_ZEROS_READ; if (useKernRead) { - for (int offset = 0; offset < length; offset += 4) { - *((int *) (buffer + 1) + offset / 4) = kern_read(startingAddress + offset); + for (int offset = 0; offset < length; offset += sizeof(int)) { + *((int *) (buffer + 1) + offset / sizeof(int)) = readKernelMemory(startingAddress + offset); } } else { memcpy(buffer + 1, startingAddress, length); @@ -601,26 +602,26 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { } case COMMAND_KERNEL_WRITE: { void *ptr, *value; - ret = recvwait(bss, clientfd, buffer, 8); + ret = recvwait(bss, clientfd, buffer, sizeof(int) * 2); CHECK_ERROR(ret < 0) ptr = ((void **) buffer)[0]; value = ((void **) buffer)[1]; - kern_write(ptr, (uint32_t) value); + writeKernelMemory(ptr, (uint32_t) value); break; } case COMMAND_KERNEL_READ: { void *ptr, *value; - ret = recvwait(bss, clientfd, buffer, 4); + ret = recvwait(bss, clientfd, buffer, sizeof(int)); CHECK_ERROR(ret < 0); ptr = ((void **) buffer)[0]; - value = (void *) kern_read(ptr); + value = (void *) readKernelMemory(ptr); *(void **) buffer = value; - sendwait(bss, clientfd, buffer, 4); + sendwait(bss, clientfd, buffer, sizeof(int)); break; } case COMMAND_TAKE_SCREEN_SHOT: { @@ -1271,7 +1272,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { bufferIndex += sizeof(bool); bool write = buffer[bufferIndex]; bufferIndex += sizeof(bool); - setDataAddressBreakPointRegister(address, read, write); + setDataBreakpoint(address, read, write); break; } @@ -1282,7 +1283,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { // Parse the address and set the breakpoint unsigned int address = ((unsigned int *) buffer)[0]; - setInstructionAddressBreakPointRegister(address); + setInstructionBreakpoint(address); break; } diff --git a/src/system/exception_handler.h b/src/system/exception_handler.h index d1dac63..f15f2a4 100644 --- a/src/system/exception_handler.h +++ b/src/system/exception_handler.h @@ -40,9 +40,10 @@ typedef struct OSContext { #define CPU_STACK_TRACE_DEPTH 10 -#define mfspr(_rn) \ +// http://elixir.free-electrons.com/linux/v2.6.24/source/include/asm-powerpc/reg.h#L713 +#define mfspr(spr) \ ({ register uint32_t _rval = 0; \ - asm volatile("mfspr %0," __stringify(_rn) \ + asm volatile("mfspr %0," __stringify(spr) \ : "=r" (_rval));\ _rval; \ }) diff --git a/src/utils/function_patcher.c b/src/utils/function_patcher.c new file mode 100644 index 0000000..18ed37a --- /dev/null +++ b/src/utils/function_patcher.c @@ -0,0 +1,404 @@ +/**************************************************************************** + * Copyright (C) 2016 Maschell + * With code from chadderz and dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ + +#include +#include "../utils/logger.h" +#include "../kernel/kernel_functions.h" +#include "function_patcher.h" + +#define CODE_RW_BASE_OFFSET 0x00000000 +#define DEBUG_LOG_DYN 1 + +void printFunctionHooks(FunctionHook *functionHooks, u32 functionHooksSize) { + for (unsigned int functionHookIndex = 0; functionHookIndex < functionHooksSize; functionHookIndex++) { + log_printf("Real address (%s): %08x\n", functionHooks[functionHookIndex].functionName, + functionHooks[functionHookIndex].realAddress); + } +} + +bool setRealAddress(FunctionHook functionHooks[], int functionHooksSize, const char *functionName, u32 address) { + for (int functionHookIndex = 0; functionHookIndex < functionHooksSize; functionHookIndex++) { + if (strncmp(functionName, functionHooks[functionHookIndex].functionName, 50) == 0) { + functionHooks[functionHookIndex].realAddress = address; + log_printf("Function %s defined with address %08x\n", functionHooks[functionHookIndex].functionName, + functionHooks[functionHookIndex].realAddress); + // printFunctionHooks(functionHooks, functionHooksSize); + + return true; + } + } + + return false; +} + +/* +* Patches a function that is loaded at the start of each application. Its not required to restore, at least when they are really dynamic. +* "normal" functions should be patch with the normal patcher. Current Code by Maschell with the help of dimok. Orignal code by Chadderz. +*/ +void patchIndividualMethodHooks(FunctionHook *functionHook, int hook_information_size, + volatile unsigned int *dynamic_method_calls) { + log_printf("Patching %d given functions\n", hook_information_size); + /* Patch branches to it. */ + volatile unsigned int *space = &dynamic_method_calls[0]; + + int method_hooks_count = hook_information_size; + + u32 skipInstructionLength = 1; + u32 myInstructionLength = 6; + u32 instructionLength = myInstructionLength + skipInstructionLength; + u32 flush_len = 4 * instructionLength; + for (int functionIndex = 0; functionIndex < method_hooks_count; functionIndex++) { + log_printf("Patching %s...\n", functionHook[functionIndex].functionName); + if (functionHook[functionIndex].functionType == STATIC_FUNCTION && + functionHook[functionIndex].alreadyPatched == 1) { + if (isDynamicFunction((u32) OSEffectiveToPhysical((void *) functionHook[functionIndex].realAddress))) { + log_printf("The function %s is a dynamic function. Please fix that <3\n", + functionHook[functionIndex].functionName); + functionHook[functionIndex].functionType = DYNAMIC_FUNCTION; + } else { + log_printf("Skipping %s, its already patched\n", functionHook[functionIndex].functionName); + space += instructionLength; + continue; + } + } + + u32 physical = 0; + unsigned int replaceAddress = functionHook[functionIndex].replaceAddress; + unsigned int callAddress = functionHook[functionIndex].replaceCall; + + unsigned int realAddress = functionHook[functionIndex].realAddress; + + if (realAddress == 0) { + log_printf("The real address was NULL, we need to find it.\n"); + realAddress = getFunctionAddress(functionHook[functionIndex].library, + functionHook[functionIndex].functionName); + } else { + log_printf("The real address was not NULL! We patch it by the given address.\n"); + } + + if (realAddress == 0) { + log_printf("[Patch] OSDynLoad_FindExport failed for %s\n", functionHook[functionIndex].functionName); + space += instructionLength; + continue; + } + + if (DEBUG_LOG_DYN)log_printf("%s is located at %08X!\n", functionHook[functionIndex].functionName, realAddress); + + physical = (u32) OSEffectiveToPhysical((void *) realAddress); + if (!physical) { + log_printf("Error. Something is wrong with the physical address\n"); + space += instructionLength; + continue; + } + + if (DEBUG_LOG_DYN) + log_printf("%s physical is located at %08X!\n", functionHook[functionIndex].functionName, physical); + + *(volatile unsigned int *) (callAddress) = (unsigned int) (space) - CODE_RW_BASE_OFFSET; + + SC0x25_KernelCopyData((u32) space, physical, 4); + space++; + + //Only works if skip_instr == 1 + functionHook[functionIndex].realAddress = realAddress; + functionHook[functionIndex].restoreInstruction = *(space - 1); + if (DEBUG_LOG_DYN)log_printf("method_hooks[i].realAddr = %08X!\n", functionHook[functionIndex].realAddress); + if (DEBUG_LOG_DYN) + log_printf("method_hooks[i].restoreInstruction = %08X!\n", functionHook[functionIndex].restoreInstruction); + + //adding jump to real function thx @ dimok for the assembler code + /* + 90 61 ff e0 stw r3,-32(r1) + 3c 60 12 34 lis r3,4660 + 60 63 56 78 ori r3,r3,22136 + 7c 69 03 a6 mtctr r3 + 80 61 ff e0 lwz r3,-32(r1) + 4e 80 04 20 bctr*/ + *space = 0x9061FFE0; + space++; + *space = 0x3C600000 | (((realAddress + (skipInstructionLength * 4)) >> 16) & 0x0000FFFF); // lis r3, real_addr@h + space++; + *space = 0x60630000 | ((realAddress + (skipInstructionLength * 4)) & 0x0000ffff); // ori r3, r3, real_addr@l + space++; + *space = 0x7C6903A6; // mtctr r3 + space++; + *space = 0x8061FFE0; // lwz r3,-32(r1) + space++; + *space = 0x4E800420; // bctr + space++; + DCFlushRange((void *) (space - instructionLength), flush_len); + ICInvalidateRange((unsigned char *) (space - instructionLength), flush_len); + + //setting jump back + unsigned int replace_instr = 0x48000002 | (replaceAddress & 0x03fffffc); + DCFlushRange(&replace_instr, 4); + + SC0x25_KernelCopyData(physical, (u32) OSEffectiveToPhysical(&replace_instr), 4); + ICInvalidateRange((void *) (realAddress), 4); + + functionHook[functionIndex].alreadyPatched = 1; + log_printf("done!\n"); + } + + log_print("Done with patching given functions!\n"); +} + +void restoreIndividualInstructions(FunctionHook *functionHooks, int hook_information_size) { + log_printf("Restoring given functions!\n"); + int method_hooks_count = hook_information_size; + for (int functionIndex = 0; functionIndex < method_hooks_count; functionIndex++) { + log_printf("Restoring %s... ", functionHooks[functionIndex].functionName); + if (functionHooks[functionIndex].restoreInstruction == 0 || functionHooks[functionIndex].realAddress == 0) { + log_printf("I don't have the information for the restore =( skip\n"); + continue; + } + + unsigned int realAddress = functionHooks[functionIndex].realAddress; + + if (realAddress == 0) { + log_printf("The real address was NULL, we need to find it.\n"); + realAddress = getFunctionAddress(functionHooks[functionIndex].library, + functionHooks[functionIndex].functionName); + } else { + log_printf("The real address was not NULL! We patch it by the given address.\n"); + } + + if (!realAddress) { + log_printf("[Restore] OSDynLoad_FindExport failed for %s\n", functionHooks[functionIndex].functionName); + continue; + } + + u32 physical = (u32) OSEffectiveToPhysical((void *) realAddress); + if (!physical) { + log_printf("Something is wrong with the physical address\n"); + continue; + } + + if (isDynamicFunction((unsigned int) physical)) { + log_printf("Its a dynamic function. We don't need to restore it!\n", + functionHooks[functionIndex].functionName); + } else { + physical = (u32) OSEffectiveToPhysical( + (void *) functionHooks[functionIndex].realAddress); //When its an static function, we need to use the old location + if (DEBUG_LOG_DYN) + log_printf("Restoring %08X to %08X\n", (u32) functionHooks[functionIndex].restoreInstruction, physical); + SC0x25_KernelCopyData(physical, (u32) &functionHooks[functionIndex].restoreInstruction, 4); + if (DEBUG_LOG_DYN)log_printf("ICInvalidateRange %08X\n", (void *) functionHooks[functionIndex].realAddress); + ICInvalidateRange((void *) functionHooks[functionIndex].realAddress, 4); + log_printf("done\n"); + } + functionHooks[functionIndex].alreadyPatched = 0; // In case a + } + + KernelRestoreInstructions(); + log_print("Done with restoring given functions!\n"); +} + +bool isDynamicFunction(unsigned int physicalAddress) { + return (physicalAddress & 0x80000000) == 0x80000000; +} + +unsigned int getRPLHandle(int library, const char *functionName) { + unsigned int rplHandle = 0; + + switch (library) { + case LIB_CORE_INIT: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_CORE_INIT\n", functionName); + if (coreinit_handle == 0) { + log_print("LIB_CORE_INIT not acquired\n"); + return 0; + } + rplHandle = coreinit_handle; + break; + + case LIB_GX2: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_GX2\n", functionName); + unsigned int gx2_handle = 0; + OSDynLoad_Acquire("gx2.rpl", &gx2_handle); + if (gx2_handle == 0) { + log_print("LIB_GX2 not acquired\n"); + return 0; + } + rplHandle = gx2_handle; + break; + + /*case LIB_NSYSNET: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_NSYSNET\n", functionName); + if (nsysnet_handle == 0) { + log_print("LIB_NSYSNET not acquired\n"); + return 0; + } + rplHandle = nsysnet_handle; + break; + + case LIB_AOC: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_AOC\n", functionName); + if (aoc_handle == 0) { + log_print("LIB_AOC not acquired\n"); + return 0; + } + rplHandle = aoc_handle; + break; + + case LIB_AX: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_AX\n", functionName); + if (sound_handle == 0) { + log_print("LIB_AX not acquired\n"); + return 0; + } + rplHandle = sound_handle; + break;*/ + + case LIB_FS: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_FS\n", functionName); + if (coreinit_handle == 0) { + log_print("LIB_FS not acquired\n"); + return 0; + } + rplHandle = coreinit_handle; + break; + + case LIB_OS: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_OS\n", functionName); + if (coreinit_handle == 0) { + log_print("LIB_OS not acquired\n"); + return 0; + } + rplHandle = coreinit_handle; + break; + + /* + case LIB_PADSCORE: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_PADSCORE\n", functionName); + if (padscore_handle == 0) { + log_print("LIB_PADSCORE not acquired\n"); + return 0; + } + rplHandle = padscore_handle; + break; + + case LIB_SOCKET: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_SOCKET\n", functionName); + if (nsysnet_handle == 0) { + log_print("LIB_SOCKET not acquired\n"); + return 0; + } + rplHandle = nsysnet_handle; + break; + + case LIB_SYS: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_SYS\n", functionName); + if (sysapp_handle == 0) { + log_print("LIB_SYS not acquired\n"); + return 0; + } + rplHandle = sysapp_handle; + break; + + case LIB_VPAD: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_VPAD\n", functionName); + if (vpad_handle == 0) { + log_print("LIB_VPAD not acquired\n"); + return 0; + } + rplHandle = vpad_handle; + break; + + case LIB_NN_ACP: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_NN_ACP\n", functionName); + if (acp_handle == 0) { + log_print("LIB_NN_ACP not acquired\n"); + return 0; + } + rplHandle = acp_handle; + break; + + case LIB_SYSHID: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_SYSHID\n", functionName); + if (syshid_handle == 0) { + log_print("LIB_SYSHID not acquired\n"); + return 0; + } + rplHandle = syshid_handle; + break; + + case LIB_VPADBASE: + if (DEBUG_LOG_DYN)log_printf("FindExport of %s! From LIB_VPADBASE\n", functionName); + if (vpadbase_handle == 0) { + log_print("LIB_VPADBASE not acquired\n"); + return 0; + } + rplHandle = vpadbase_handle; + break; */ + + default:; + char messageBuffer[50]; + __os_snprintf(messageBuffer, 50, "Unhandled library %i", library); + OSFatal(messageBuffer); + break; + } + + return rplHandle; +} + +unsigned int getFunctionAddress(unsigned int library, const char *functionName) { + unsigned int realAddress = 0; + + if (strcmp(functionName, "OSDynLoad_Acquire") == 0) { + memcpy(&realAddress, &OSDynLoad_Acquire, 4); + return realAddress; + } else if (strcmp(functionName, "LiWaitOneChunk") == 0) { + realAddress = (unsigned int) addr_LiWaitOneChunk; + return realAddress; + } else if (strcmp(functionName, "LiBounceOneChunk") == 0) { + //! not required on firmwares above 3.1.0 + if (OS_FIRMWARE >= 400) + return 0; + + unsigned int addr_LiBounceOneChunk = 0x010003A0; + realAddress = addr_LiBounceOneChunk; + return realAddress; + } + + unsigned int rpl_handle = getRPLHandle(library, functionName); + + if (!rpl_handle) { + log_printf("Failed to find the RPL handle for %s\n", functionName); + return 0; + } + + OSDynLoad_FindExport((u32) rpl_handle, 0, functionName, &realAddress); + + if (!realAddress) { + log_printf("[Get] OSDynLoad_FindExport failed for %s\n", functionName); + return 0; + } + + if ((u32) (*(volatile unsigned int *) (realAddress) & 0x48000002) == 0x48000000) { + unsigned int address_diff = (u32) (*(volatile unsigned int *) (realAddress) & 0x03FFFFFC); + if ((address_diff & 0x03000000) == 0x03000000) { + address_diff |= 0xFC000000; + } + realAddress += (int) address_diff; + if ((u32) (*(volatile unsigned int *) (realAddress) & 0x48000002) == 0x48000000) { + return 0; + } + } + + return realAddress; +} \ No newline at end of file diff --git a/src/utils/function_patcher.h b/src/utils/function_patcher.h new file mode 100644 index 0000000..37c1756 --- /dev/null +++ b/src/utils/function_patcher.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * Copyright (C) 2016 Maschell + * With code from chadderz and dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ + +#ifndef _FUNCTION_HOOKS_H_ +#define _FUNCTION_HOOKS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "../common/common.h" // OS_FIRMWARE + +// Library handles +#include "../dynamic_libs/aoc_functions.h" +#include "../dynamic_libs/ax_functions.h" +#include "../dynamic_libs/fs_functions.h" +#include "../dynamic_libs/gx2_functions.h" +#include "../dynamic_libs/os_functions.h" +#include "../dynamic_libs/padscore_functions.h" +#include "../dynamic_libs/socket_functions.h" +#include "../dynamic_libs/sys_functions.h" +#include "../dynamic_libs/vpad_functions.h" +/*#include "../dynamic_libs/acp_functions.h" +#include "../dynamic_libs/syshid_functions.h"*/ + +/* Macros for libs */ +#define LIB_CORE_INIT 0 +#define LIB_NSYSNET 1 +#define LIB_GX2 2 +#define LIB_AOC 3 +#define LIB_AX 4 +#define LIB_FS 5 +#define LIB_OS 6 +#define LIB_PADSCORE 7 +#define LIB_SOCKET 8 +#define LIB_SYS 9 +#define LIB_VPAD 10 +#define LIB_NN_ACP 11 +#define LIB_SYSHID 12 +#define LIB_VPADBASE 13 + +// functions types +#define STATIC_FUNCTION 0 +#define DYNAMIC_FUNCTION 1 + +/*enum Library { + LIB_CORE_INIT, + LIB_NSYSNET, + LIB_GX2, + LIB_AOC, + LIB_AX, + LIB_FS, + LIB_OS, + LIB_PADSCORE, + LIB_SOCKET, + LIB_SYS, + LIB_VPAD, + LIB_NN_ACP, + LIB_SYSHID, + LIB_VPADBASE +}; + +enum FunctionTypes { + STATIC_FUNCTION, + DYNAMIC_FUNCTION +};*/ + +// Original code by Chadderz +#define declareFunctionHook(returnType, functionName, ...) \ + returnType (* real_ ## functionName)(__VA_ARGS__) __attribute__((section(".data"))); \ + returnType my_ ## functionName(__VA_ARGS__) + +#define declareGameFunctionHook(functionName) { (unsigned int) my_ ## functionName, (unsigned int) &real_ ## functionName, 0, # functionName,0,0,1,0} + +#define makeFunctionHook(functionName, library, functionType) { (unsigned int) my_ ## functionName, (unsigned int) &real_ ## functionName, library, # functionName,0,0,functionType,0} + +#define FUNCTION_PATCHER_METHOD_STORE_SIZE 7 + +typedef struct { + const unsigned int replaceAddress; + const unsigned int replaceCall; + const unsigned int library; + const char functionName[50]; + unsigned int realAddress; + unsigned int restoreInstruction; + unsigned char functionType; + unsigned char alreadyPatched; +} FunctionHook; + +void printFunctionHooks(FunctionHook *functionHooks, u32 functionHooksSize); + +bool setRealAddress(FunctionHook functionHooks[], int functionHooksSize, const char *functionName, u32 address); + +void patchIndividualMethodHooks(FunctionHook *functionHook, int hook_information_size, + volatile unsigned int *dynamic_method_calls); + +void restoreIndividualInstructions(FunctionHook *functionHooks, int hook_information_size); + +unsigned int getFunctionAddress(unsigned int library, const char *functionName); + +bool isDynamicFunction(unsigned int physicalAddress); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/utils/logger.h b/src/utils/logger.h index dd7cc71..d4b22fe 100644 --- a/src/utils/logger.h +++ b/src/utils/logger.h @@ -6,6 +6,7 @@ extern "C" { #endif #define DEBUG_LOGGER 1 +#define COMPUTER_IP_ADDRESS "192.168.2.103" #ifdef DEBUG_LOGGER void log_init(const char * ip); diff --git a/tcpgecko.elf b/tcpgecko.elf index 423b2e3dcaffb60c01624ef154b6da411aaa7c13..0696c2ab3166faace9d6425d5bba8f2eb09c2b17 100644 GIT binary patch delta 34886 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