diff --git a/injector/Makefile b/injector/Makefile index cc42d07..57be1ba 100755 --- a/injector/Makefile +++ b/injector/Makefile @@ -14,6 +14,7 @@ OC := arm-none-eabi-objcopy name := $(shell basename $(CURDIR)) dir_source := source +dir_patches := patches dir_build := build dir_out := ../$(dir_build) @@ -31,6 +32,14 @@ LDFLAGS := -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ASFLAGS) objects = $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \ $(call rwildcard, $(dir_source), *.s *.c)) +bundled = $(dir_build)/romfsredir.bin.o + +define bin2o + bin2s $< | $(AS) -o $(@) + echo "extern const u8" `(echo $(> $(dir_build)/bundled.h + echo "extern const u32" `(echo $(> $(dir_build)/bundled.h +endef + .PHONY: all all: $(dir_out)/$(name).bin @@ -38,12 +47,23 @@ all: $(dir_out)/$(name).bin clean: @rm -rf $(dir_build) +.PRECIOUS: $(dir_build)/%.bin + +$(dir_build): + @mkdir -p "$@" + $(dir_out)/$(name).bin: $(dir_build)/$(name).elf @makerom -f ncch -rsf loader.rsf -nocodepadding -o $@ -elf $< -$(dir_build)/$(name).elf: $(objects) +$(dir_build)/$(name).elf: $(bundled) $(objects) $(LINK.o) $(OUTPUT_OPTION) $^ $(LIBPATHS) $(LIBS) +$(dir_build)/%.bin.o: $(dir_build)/%.bin + @$(bin2o) + +$(dir_build)/%.bin: $(dir_patches)/%.s $(dir_build) + @armips $< + $(dir_build)/memory.o $(dir_build)/strings.o: CFLAGS += -O3 $(dir_build)/%.o: $(dir_source)/%.c diff --git a/injector/patches/romfsredir.s b/injector/patches/romfsredir.s new file mode 100644 index 0000000..c852173 --- /dev/null +++ b/injector/patches/romfsredir.s @@ -0,0 +1,81 @@ +; Code from delebile + +.arm.little +.create "build/romfsredir.bin", 0 + +.macro load, reg, func + ldr reg, [pc, #func-.-8] +.endmacro + +.arm + ; fsOpenFileDirectly function will be redirected here. + ; If the requested archive is not ROMFS, we'll return + ; to the original fucntion. + openFileDirectlyHook: + cmp r3, #3 + beq openRomfs + load r12, fsOpenFileDirectly + add r12, r12, #4 + nop ; Will be replaced with the original function opcode + bx r12 + + ; We redirect ROMFS file opening by changing the parameters and call + ; the fsOpenFileDirectly function recursively. The parameter format: + ; r0 : fsUserHandle + ; r1 : Output FileHandle + ; r2 : Transaction (usually 0) + ; r3 : Archive ID + ; [sp, #0x00] : Archive PathType + ; [sp, #0x04] : Archive DataPointer + ; [sp, #0x08] : Archive PathSize + ; [sp, #0x0C] : File PathType + ; [sp, #0x10] : File DataPointer + ; [sp, #0x14] : File PathSize + ; [sp, #0x18] : File OpenFlags + ; [sp, #0x1C] : Attributes (usually 0) + openRomfs: + sub sp, sp, #0x50 + stmfd sp!, {r0, r1, lr} + add sp, sp, #0x5C + str r3, [sp, #0x0C] ; File PathType (ASCII = 3) + load r12, romfsFileName + str r12, [sp, #0x10] ; File DataPointer + load r12, romfsFileNameSize + str r12, [sp, #0x14] ; File PathSize + mov r3, #9 ; SDMC Archive ID + bl openFileDirectlyHook + sub sp, sp, #0x5C + ldmfd sp!, {r0, r1, lr} + add sp, sp, #0x50 + mov r0, r1 ; Substitute fsUserHandle with the fileHandle + + ; Once we have the sd romfs file opened, we'll open a subfile + ; in order to skip the useless data. + fsOpenSubFile: + stmfd sp!, {r1, r3-r11} + mrc p15, 0, r4, c13, c0, 3 + add r4, r4, #0x80 + mov r1, r4 + add r3, pc, #fsOpenSubFileCmd-.-8 + ldmia r3!, {r5-r9} + stmia r1!, {r5-r9} + ldr r0, [r0] + swi 0x32 + ldr r0, [r4, #0x0C] + ldmfd sp!, {r1, r3-r11} + str r0, [r1] + mov r0, #0 + bx lr + +.pool +.align 4 +; Part of these symbols will be set from outside + fsOpenFileDirectly : .word 0x00000000 + fsOpenSubFileCmd : .word 0x08010100 + .word 0x00000000 ; File Offset + .word 0x00000000 + .word 0x00000000 ; File Size + .word 0x00000000 + romfsFileNameSize : .word 0x00000000 + romfsFileName : .word 0x00000000 ; File DataPointer +.close \ No newline at end of file diff --git a/injector/source/patcher.c b/injector/source/patcher.c index c8ef156..8e50220 100644 --- a/injector/source/patcher.c +++ b/injector/source/patcher.c @@ -4,6 +4,7 @@ #include "strings.h" #include "ifile.h" #include "CFWInfo.h" +#include "../build/bundled.h" static CFWInfo info; @@ -134,97 +135,6 @@ exit: IFile_Close(&file); } -static inline bool loadTitleCodeSection(u64 progId, u8 *code, u32 size) -{ - /* Here we look for "/luma/code_sections/[u64 titleID in hex, uppercase].bin" - If it exists it should be a decompressed binary code file */ - - char path[] = "/luma/code_sections/0000000000000000.bin"; - progIdToStr(path + 35, progId); - - IFile file; - - if(R_FAILED(openLumaFile(&file, path))) return true; - - bool ret; - u64 fileSize; - - if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize > size) ret = false; - else - { - u64 total; - - ret = R_SUCCEEDED(IFile_Read(&file, &total, code, fileSize)) && total == fileSize; - } - - IFile_Close(&file); - - return ret; -} - -static inline bool loadTitleLocaleConfig(u64 progId, u8 *regionId, u8 *languageId) -{ - /* Here we look for "/luma/locales/[u64 titleID in hex, uppercase].txt" - If it exists it should contain, for example, "EUR IT" */ - - char path[] = "/luma/locales/0000000000000000.txt"; - progIdToStr(path + 29, progId); - - IFile file; - - if(R_FAILED(openLumaFile(&file, path))) return true; - - bool ret; - u64 fileSize; - - if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize < 6 || fileSize > 8) - { - ret = false; - goto exit; - } - - char buf[8]; - u64 total; - - if(R_FAILED(IFile_Read(&file, &total, buf, fileSize))) - { - ret = false; - goto exit; - } - - u32 i, - j; - - for(i = 0; i < 7; i++) - { - static const char *regions[] = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"}; - - if(memcmp(buf, regions[i], 3) == 0) - { - *regionId = (u8)i; - break; - } - } - - for(j = 0; j < 12; j++) - { - static const char *languages[] = {"JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"}; - - if(memcmp(buf + 4, languages[j], 2) == 0) - { - *languageId = (u8)j; - break; - } - } - - ret = i != 7 && j != 12; - -exit: - IFile_Close(&file); - - return ret; -} - static inline u8 *getCfgOffsets(u8 *code, u32 size, u32 *CFGUHandleOffset) { /* HANS: @@ -285,7 +195,6 @@ static inline bool patchCfgGetLanguage(u8 *code, u32 size, u8 languageId, u8 *CF u8 *calledFunction = instr; u32 i = 0; - bool found; do { @@ -307,7 +216,7 @@ static inline bool patchCfgGetLanguage(u8 *code, u32 size, u8 languageId, u8 *CF i++; } - while(i < 2 && !found && calledFunction[3] == 0xEA); + while(i < 2 && calledFunction[3] == 0xEA); } } @@ -339,6 +248,214 @@ static inline void patchCfgGetRegion(u8 *code, u32 size, u8 regionId, u32 CFGUHa } } +static u32 findNearestStmfd(u8* code, u32 pos) +{ + while(pos > 0) + { + if(*(u16 *)(code + pos + 2) == 0xE92D) return pos; + pos -= 4; + } + + return 0; +} + +static u32 findFunctionCommand(u8* code, u32 size, u32 command) +{ + u32 func = 0; + + for(u32 i = 0; i < size && !func; i += 4) + if(*(u32 *)(code + i) == command) func = i; + + return !func ? 0 : findNearestStmfd(code, func); +} + +static inline u32 findThrowFatalError(u8* code, u32 size) +{ + u32 connectToPort = 0; + + for(u32 i = 0; i < size && !connectToPort; i += 4) + if(*(u32 *)(code + i) == 0xEF00002D) connectToPort = i - 4; + + if(!connectToPort) return 0; + + u32 func = 0; + + for(u32 i = 0; i < size && !func; i += 4) + { + if(*(u32 *)(code + i) != MAKE_BRANCH_LINK(i, connectToPort)) continue; + + func = findNearestStmfd(code, i); + + for(u32 pos = func + 4; func != 0 && pos <= size - 4 && *(u16 *)(code + pos + 2) != 0xE92D; pos += 4) + if(*(u32 *)(code + pos) == 0xE200167E) func = 0; + } + + return func; +} + +static inline bool loadTitleCodeSection(u64 progId, u8 *code, u32 size) +{ + /* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/code.bin" + If it exists it should be a decrypted and decompressed binary code file */ + + char path[] = "/luma/titles/0000000000000000/code.bin"; + progIdToStr(path + 28, progId); + + IFile file; + + if(R_FAILED(openLumaFile(&file, path))) return true; + + bool ret; + u64 fileSize; + + if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize > size) ret = false; + else + { + u64 total; + + ret = R_SUCCEEDED(IFile_Read(&file, &total, code, fileSize)) && total == fileSize; + } + + IFile_Close(&file); + + return ret; +} + +static inline bool loadTitleLocaleConfig(u64 progId, u8 *regionId, u8 *languageId) +{ + /* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/locale.txt" + If it exists it should contain, for example, "EUR IT" */ + + char path[] = "/luma/titles/0000000000000000/locale.txt"; + progIdToStr(path + 28, progId); + + IFile file; + + if(R_FAILED(openLumaFile(&file, path))) return true; + + bool ret; + u64 fileSize; + + if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize < 6 || fileSize > 8) + { + ret = false; + goto exit; + } + + char buf[8]; + u64 total; + + if(R_FAILED(IFile_Read(&file, &total, buf, fileSize))) + { + ret = false; + goto exit; + } + + u32 i, + j; + + for(i = 0; i < 7; i++) + { + static const char *regions[] = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"}; + + if(memcmp(buf, regions[i], 3) == 0) + { + *regionId = (u8)i; + break; + } + } + + for(j = 0; j < 12; j++) + { + static const char *languages[] = {"JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"}; + + if(memcmp(buf + 4, languages[j], 2) == 0) + { + *languageId = (u8)j; + break; + } + } + + ret = i != 7 && j != 12; + +exit: + IFile_Close(&file); + + return ret; +} + +static bool patchRomfsRedirection(u64 progId, u8* code, u32 size) +{ + /* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/romfs" + If it exists it should be a decrypted raw RomFS */ + + char path[] = "/luma/titles/0000000000000000/romfs"; + progIdToStr(path + 28, progId); + + IFile file; + + if(R_FAILED(openLumaFile(&file, path))) return true; + + bool ret; + u64 romfsSize; + + if(R_FAILED(IFile_GetSize(&file, &romfsSize))) + { + ret = false; + goto exit; + } + + u64 total; + u32 header; + + if(R_FAILED(IFile_Read(&file, &total, &header, 4)) || total != 4 || header != 0x43465649) + { + ret = false; + goto exit; + } + + u32 fsOpenFileDirectly = findFunctionCommand(code, size, 0x08030204), + fsOpenLinkFile = findFunctionCommand(code, size, 0x80C0000), + throwFatalError = findThrowFatalError(code, size); + + if(!fsOpenFileDirectly || !throwFatalError) + { + ret = false; + goto exit; + } + + //Setup the payload + memcpy(code + throwFatalError, romfsredir_bin, romfsredir_bin_size); + *((u32 *)(code + throwFatalError + 0x10)) = *(u32 *)(code + fsOpenFileDirectly); + *((u32 *)(code + throwFatalError + romfsredir_bin_size - 0x08)) = sizeof(path); + *((u64 *)(code + throwFatalError + romfsredir_bin_size - 0x10)) = romfsSize - 0x1000ULL; + *((u64 *)(code + throwFatalError + romfsredir_bin_size - 0x18)) = 0x1000ULL; + *((u32 *)(code + throwFatalError + romfsredir_bin_size - 0x20)) = fsOpenFileDirectly + 0x100000; + + //Place the hooks + *(u32 *)(code + fsOpenFileDirectly) = MAKE_BRANCH(fsOpenFileDirectly, throwFatalError); + + if(fsOpenLinkFile != 0) + { + *(u32 *)(code + fsOpenLinkFile) = 0xE3A03003; //mov r3, #3 + *(u32 *)(code + fsOpenLinkFile + 4) = MAKE_BRANCH(fsOpenLinkFile + 4, throwFatalError); + memcpy(code + fsOpenLinkFile + 8, path, sizeof(path)); + *(u32 *)(code + throwFatalError + romfsredir_bin_size - 4) = fsOpenLinkFile + 8 + 0x100000; //String pointer + } + else + { + memcpy(code + throwFatalError + romfsredir_bin_size, path, 0x30); + *(u32 *)(code + throwFatalError + romfsredir_bin_size - 4) = throwFatalError + romfsredir_bin_size + 0x100000; //String pointer + } + + ret = true; + +exit: + IFile_Close(&file); + + return ret; +} + void patchCode(u64 progId, u16 progVer, u8 *code, u32 size) { loadCFWInfo(); @@ -575,12 +692,13 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size) ) != 3) goto error; } - else if(CONFIG(USELANGEMUANDCODE) && (u32)((progId & 0xFFFFFFF000000000LL) >> 0x24) == 0x0004000) + else if(CONFIG(PATCHGAMES) && (u32)((progId & 0xFFFFFFF000000000LL) >> 0x24) == 0x0004000) { u8 regionId = 0xFF, languageId; - if(!loadTitleLocaleConfig(progId, ®ionId, &languageId) || - !loadTitleCodeSection(progId, code, size)) goto error; + + if(!loadTitleCodeSection(progId, code, size) || + !loadTitleLocaleConfig(progId, ®ionId, &languageId)) goto error; if(regionId != 0xFF) { @@ -592,6 +710,8 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size) patchCfgGetRegion(code, size, regionId, CFGUHandleOffset); } + + if(!patchRomfsRedirection(progId, code, size)) goto error; } return; diff --git a/injector/source/patcher.h b/injector/source/patcher.h index d7c8ac8..236a1b0 100644 --- a/injector/source/patcher.h +++ b/injector/source/patcher.h @@ -2,6 +2,9 @@ #include <3ds/types.h> +#define MAKE_BRANCH(src,dst) (0xEA000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF)) +#define MAKE_BRANCH_LINK(src,dst) (0xEB000000 | ((u32)((((u8 *)(dst) - (u8 *)(src)) >> 2) - 2) & 0xFFFFFF)) + #define CONFIG(a) (((info.config >> (a + 20)) & 1) != 0) #define MULTICONFIG(a) ((info.config >> (a * 2 + 8)) & 3) #define BOOTCONFIG(a, b) ((info.config >> a) & b) @@ -28,7 +31,7 @@ enum singleOptions USESYSFIRM, LOADEXTFIRMSANDMODULES, USECUSTOMPATH, - USELANGEMUANDCODE, + PATCHGAMES, PATCHVERSTRING, SHOWGBABOOT, PATCHACCESS diff --git a/source/config.c b/source/config.c index e474107..811d10a 100644 --- a/source/config.c +++ b/source/config.c @@ -80,7 +80,7 @@ void configMenu(bool isSdMode, bool oldPinStatus, u32 oldPinMode) "( ) Use SysNAND FIRM if booting with R", "( ) Enable loading external FIRMs and modules", "( ) Use custom path", - "( ) Enable region/language emu. and ext. .code", + "( ) Enable game patching", "( ) Show NAND or user string in System Settings", "( ) Show GBA boot screen in patched AGB_FIRM", "( ) Patch SVC/service/archive/ARM9 access" @@ -160,10 +160,12 @@ void configMenu(bool isSdMode, bool oldPinStatus, u32 oldPinMode) "Enable overriding the region and\n" "language configuration and the usage\n" - "of patched code binaries for specific\n" - "games.\n\n" + "of patched code binaries and\n" + "custom RomFS for specific games.\n\n" "Also makes certain DLCs for\n" "out-of-region games work.\n\n" + "Enabling this requires the\n" + "archive patch to be applied.\n\n" "Refer to the wiki for instructions.", "Enable showing the current NAND/FIRM:\n\n" diff --git a/source/config.h b/source/config.h index 10fe496..acfd4b9 100644 --- a/source/config.h +++ b/source/config.h @@ -53,7 +53,7 @@ enum singleOptions USESYSFIRM, LOADEXTFIRMSANDMODULES, USECUSTOMPATH, - USELANGEMUANDCODE, + PATCHGAMES, PATCHVERSTRING, SHOWGBABOOT, PATCHACCESS diff --git a/source/firm.c b/source/firm.c index a3ebb7e..afee52f 100755 --- a/source/firm.c +++ b/source/firm.c @@ -195,11 +195,18 @@ u32 patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32 emuHeader, boo ret += patchKernel9Panic(arm9Section, kernel9Size); } - if(CONFIG(PATCHACCESS)) + bool patchAccess = CONFIG(PATCHACCESS), + patchGames = CONFIG(PATCHGAMES); + + if(patchAccess || patchGames) { - ret += patchArm11SvcAccessChecks(arm11SvcHandler, (u32 *)(arm11Section1 + firm->section[1].size)); - ret += patchK11ModuleChecks(arm11Section1, firm->section[1].size, &freeK11Space); - ret += patchP9AccessChecks(process9Offset, process9Size); + ret += patchK11ModuleChecks(arm11Section1, firm->section[1].size, &freeK11Space, patchGames); + + if(patchAccess) + { + ret += patchArm11SvcAccessChecks(arm11SvcHandler, (u32 *)(arm11Section1 + firm->section[1].size)); + ret += patchP9AccessChecks(process9Offset, process9Size); + } } return ret; diff --git a/source/patches.c b/source/patches.c index e33307b..82919b2 100644 --- a/source/patches.c +++ b/source/patches.c @@ -429,13 +429,13 @@ u32 patchArm11SvcAccessChecks(u32 *arm11SvcHandler, u32 *endPos) return 0; } -u32 patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space) +u32 patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space, bool patchGames) { /* We have to detour a function in the ARM11 kernel because builtin modules are compressed in memory and are only decompressed at runtime */ //Check that we have enough free space - if(*(u32 *)(*freeK11Space + k11modules_bin_size - 4) != 0xFFFFFFFF) return 0; + if(*(u32 *)(*freeK11Space + k11modules_bin_size - 4) != 0xFFFFFFFF) return patchGames ? 1 : 0; //Look for the code that decompresses the .code section of the builtin modules const u8 pattern[] = {0xE5, 0x48, 0x00, 0x9D}; diff --git a/source/patches.h b/source/patches.h index 533ca93..34f92aa 100644 --- a/source/patches.h +++ b/source/patches.h @@ -54,7 +54,7 @@ u32 patchKernel9Panic(u8 *pos, u32 size); u32 patchKernel11Panic(u8 *pos, u32 size); u32 patchP9AccessChecks(u8 *pos, u32 size); u32 patchArm11SvcAccessChecks(u32 *arm11SvcHandler, u32 *endPos); -u32 patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space); +u32 patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space, bool patchGames); u32 patchUnitInfoValueSet(u8 *pos, u32 size); u32 patchLgySignatureChecks(u8 *pos, u32 size); u32 patchTwlInvalidSignatureChecks(u8 *pos, u32 size); diff --git a/source/pin.c b/source/pin.c index 6dd877d..85be357 100644 --- a/source/pin.c +++ b/source/pin.c @@ -93,7 +93,8 @@ void newPin(bool allowSkipping, u32 pinMode) if(!pressed) continue; - enteredPassword[cnt] = (u8)pinKeyToLetter(pressed); //Add character to password + //Add character to password + enteredPassword[cnt] = (u8)pinKeyToLetter(pressed); //Visualize character on screen drawCharacter(enteredPassword[cnt], true, 10 + (16 + 2 * cnt) * SPACING_X, 10 + 3 * SPACING_Y, COLOR_WHITE); @@ -200,7 +201,8 @@ bool verifyPin(u32 pinMode) if(!pressed) continue; - enteredPassword[cnt] = (u8)pinKeyToLetter(pressed); //Add character to password + //Add character to password + enteredPassword[cnt] = (u8)pinKeyToLetter(pressed); //Visualize character on screen drawCharacter((char)enteredPassword[cnt], true, 10 + (16 + 2 * cnt) * SPACING_X, 10 + 3 * SPACING_Y, COLOR_WHITE);