2016-03-29 17:43:53 +02:00
|
|
|
#include <3ds.h>
|
|
|
|
#include "patcher.h"
|
2016-09-06 14:09:29 +02:00
|
|
|
#include "memory.h"
|
|
|
|
#include "strings.h"
|
2018-05-23 01:50:25 +02:00
|
|
|
#include "romfsredir.h"
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2017-05-07 23:49:49 +02:00
|
|
|
static u32 patchMemory(u8 *start, u32 size, const void *pattern, u32 patSize, s32 offset, const void *replace, u32 repSize, u32 count)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
u32 i;
|
|
|
|
|
2017-04-29 18:12:14 +02:00
|
|
|
for(i = 0; i < count; i++)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-04-02 18:48:31 +02:00
|
|
|
u8 *found = memsearch(start, pattern, size, patSize);
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-11-06 14:45:45 +01:00
|
|
|
if(found == NULL) break;
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
memcpy(found + offset, replace, repSize);
|
2016-03-29 17:43:53 +02:00
|
|
|
|
|
|
|
u32 at = (u32)(found - start);
|
|
|
|
|
2016-04-11 17:57:22 +02:00
|
|
|
if(at + patSize > size) break;
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-04-11 17:57:22 +02:00
|
|
|
size -= at + patSize;
|
2016-04-02 18:48:31 +02:00
|
|
|
start = found + patSize;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
2016-11-06 14:45:45 +01:00
|
|
|
|
|
|
|
return i;
|
2016-04-18 20:50:52 +02:00
|
|
|
}
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
Result fileOpen(IFile *file, FS_ArchiveID archiveId, const char *path, int flags)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-09-11 19:17:56 +02:00
|
|
|
FS_Path filePath = {PATH_ASCII, strnlen(path, 255) + 1, path},
|
2016-05-09 03:41:00 +02:00
|
|
|
archivePath = {PATH_EMPTY, 1, (u8 *)""};
|
|
|
|
|
|
|
|
return IFile_Open(file, archiveId, archivePath, filePath, flags);
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
|
2017-05-20 05:09:48 +02:00
|
|
|
static bool dirCheck(FS_ArchiveID archiveId, const char *path)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-05-20 05:09:48 +02:00
|
|
|
bool ret;
|
2017-04-13 01:03:37 +02:00
|
|
|
Handle handle;
|
|
|
|
FS_Archive archive;
|
|
|
|
FS_Path dirPath = {PATH_ASCII, strnlen(path, 255) + 1, path},
|
|
|
|
archivePath = {PATH_EMPTY, 1, (u8 *)""};
|
|
|
|
|
2019-03-11 00:17:01 +01:00
|
|
|
if(R_FAILED(FSUSER_OpenArchive(&archive, archiveId, archivePath))) ret = false;
|
2017-04-13 01:03:37 +02:00
|
|
|
else
|
|
|
|
{
|
2019-03-11 00:17:01 +01:00
|
|
|
ret = R_SUCCEEDED(FSUSER_OpenDirectory(&handle, archive, dirPath));
|
2017-05-20 05:09:48 +02:00
|
|
|
if(ret) FSDIR_Close(handle);
|
2019-03-11 00:17:01 +01:00
|
|
|
FSUSER_CloseArchive(archive);
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-04-13 02:49:19 +02:00
|
|
|
static bool openLumaFile(IFile *file, const char *path)
|
2016-10-14 18:03:17 +02:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
|
2016-10-14 18:03:17 +02:00
|
|
|
|
2017-05-20 05:09:48 +02:00
|
|
|
return R_SUCCEEDED(fileOpen(file, archiveId, path, FS_OPEN_READ));
|
2016-10-14 18:03:17 +02:00
|
|
|
}
|
|
|
|
|
2017-04-13 02:49:19 +02:00
|
|
|
static u32 checkLumaDir(const char *path)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
FS_ArchiveID archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
|
2017-04-13 02:49:19 +02:00
|
|
|
|
2017-05-20 05:09:48 +02:00
|
|
|
return dirCheck(archiveId, path) ? archiveId : 0;
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
|
2016-11-04 22:28:33 +01:00
|
|
|
static inline bool secureInfoExists(void)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-07-02 14:44:01 +02:00
|
|
|
static bool exists = false;
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(exists) return true;
|
|
|
|
|
|
|
|
IFile file;
|
|
|
|
if(R_SUCCEEDED(fileOpen(&file, ARCHIVE_NAND_RW, "/sys/SecureInfo_C", FS_OPEN_READ)))
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
exists = true;
|
|
|
|
IFile_Close(&file);
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
|
2016-07-02 14:44:01 +02:00
|
|
|
return exists;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
|
2016-11-04 22:28:33 +01:00
|
|
|
static inline void loadCustomVerString(u16 *out, u32 *verStringSize, u32 currentNand)
|
2016-09-13 23:01:38 +02:00
|
|
|
{
|
2016-09-23 00:08:15 +02:00
|
|
|
static const char *paths[] = { "/luma/customversion_sys.txt",
|
|
|
|
"/luma/customversion_emu.txt",
|
|
|
|
"/luma/customversion_emu2.txt",
|
|
|
|
"/luma/customversion_emu3.txt",
|
|
|
|
"/luma/customversion_emu4.txt" };
|
2016-09-13 23:01:38 +02:00
|
|
|
|
|
|
|
IFile file;
|
2016-09-15 19:53:51 +02:00
|
|
|
|
2016-11-17 15:38:28 +01:00
|
|
|
if(!openLumaFile(&file, paths[currentNand])) return;
|
2016-09-13 23:01:38 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
u64 fileSize;
|
2016-09-13 23:01:38 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize > 62) goto exit;
|
2016-09-16 03:14:37 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
u8 buf[62];
|
|
|
|
u64 total;
|
2016-09-16 03:14:37 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buf, fileSize))) goto exit;
|
|
|
|
|
|
|
|
static const u8 bom[] = {0xEF, 0xBB, 0xBF};
|
|
|
|
u32 finalSize = 0;
|
|
|
|
|
|
|
|
//Convert from UTF-8 to UTF-16 (Nintendo doesn't support 4-byte UTF-16, so 4-byte UTF-8 is unsupported)
|
|
|
|
for(u32 increase, fileSizeTmp = (u32)fileSize, i = (fileSizeTmp > 2 && memcmp(buf, bom, 3) == 0) ? 3 : 0;
|
|
|
|
i < fileSizeTmp && finalSize < 19; i += increase, finalSize++)
|
|
|
|
{
|
|
|
|
if((buf[i] & 0x80) == 0 && !(buf[i] == 0xA || buf[i] == 0xD))
|
|
|
|
{
|
|
|
|
increase = 1;
|
|
|
|
out[finalSize] = (u16)buf[i];
|
|
|
|
}
|
|
|
|
else if((buf[i] & 0xE0) == 0xC0 && i + 1 < fileSizeTmp && (buf[i + 1] & 0xC0) == 0x80)
|
|
|
|
{
|
|
|
|
increase = 2;
|
|
|
|
out[finalSize] = (u16)(((buf[i] & 0x1F) << 6) | (buf[i + 1] & 0x3F));
|
2016-09-13 23:01:38 +02:00
|
|
|
}
|
2016-11-15 19:29:48 +01:00
|
|
|
else if((buf[i] & 0xF0) == 0xE0 && i + 2 < fileSizeTmp && (buf[i + 1] & 0xC0) == 0x80 && (buf[i + 2] & 0xC0) == 0x80)
|
|
|
|
{
|
|
|
|
increase = 3;
|
|
|
|
out[finalSize] = (u16)(((buf[i] & 0xF) << 12) | ((buf[i + 1] & 0x3F) << 6) | (buf[i + 2] & 0x3F));
|
|
|
|
}
|
|
|
|
else break;
|
|
|
|
}
|
2016-09-13 23:01:38 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(finalSize > 0)
|
|
|
|
{
|
|
|
|
if(finalSize > 5 && finalSize < 19) out[finalSize++] = 0;
|
|
|
|
*verStringSize = finalSize * 2;
|
2016-09-13 23:01:38 +02:00
|
|
|
}
|
2016-11-15 19:29:48 +01:00
|
|
|
|
|
|
|
exit:
|
|
|
|
IFile_Close(&file);
|
2016-09-13 23:01:38 +02:00
|
|
|
}
|
|
|
|
|
2017-06-02 05:48:29 +02:00
|
|
|
static u32 findFunctionStart(u8 *code, u32 pos)
|
|
|
|
{
|
|
|
|
while(pos >= 4)
|
|
|
|
{
|
|
|
|
pos -= 4;
|
|
|
|
if(*(u16 *)(code + pos + 2) == 0xE92D) return pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0xFFFFFFFF;
|
|
|
|
}
|
|
|
|
|
2017-04-26 18:04:02 +02:00
|
|
|
static inline bool findLayeredFsSymbols(u8 *code, u32 size, u32 *fsMountArchive, u32 *fsRegisterArchive, u32 *fsTryOpenFile, u32 *fsOpenFileDirectly)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-04-28 23:09:33 +02:00
|
|
|
u32 found = 0,
|
|
|
|
*temp = NULL;
|
|
|
|
|
2017-04-16 23:59:20 +02:00
|
|
|
for(u32 addr = 0; addr <= size - 4; addr += 4)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-05-06 00:14:33 +02:00
|
|
|
u32 *addr32 = (u32 *)(code + addr);
|
|
|
|
|
|
|
|
switch(*addr32)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-04-28 23:09:33 +02:00
|
|
|
case 0xE5970010:
|
2017-05-06 00:14:33 +02:00
|
|
|
if(addr <= size - 12 && *fsMountArchive == 0xFFFFFFFF && addr32[1] == 0xE1CD20D8 && (addr32[2] & 0xFFFFFF) == 0x008D0000) temp = fsMountArchive;
|
2017-04-28 23:09:33 +02:00
|
|
|
break;
|
|
|
|
case 0xE24DD028:
|
2017-05-06 00:14:33 +02:00
|
|
|
if(addr <= size - 16 && *fsMountArchive == 0xFFFFFFFF && addr32[1] == 0xE1A04000 && addr32[2] == 0xE59F60A8 && addr32[3] == 0xE3A0C001) temp = fsMountArchive;
|
2017-04-28 23:09:33 +02:00
|
|
|
break;
|
|
|
|
case 0xE3500008:
|
2017-05-06 00:14:33 +02:00
|
|
|
if(addr <= size - 12 && *fsRegisterArchive == 0xFFFFFFFF && (addr32[1] & 0xFFF00FF0) == 0xE1800400 && (addr32[2] & 0xFFF00FF0) == 0xE1800FC0) temp = fsRegisterArchive;
|
2017-04-28 23:09:33 +02:00
|
|
|
break;
|
|
|
|
case 0xE351003A:
|
2017-05-06 00:14:33 +02:00
|
|
|
if(addr <= size - 0x40 && *fsTryOpenFile == 0xFFFFFFFF && addr32[1] == 0x1AFFFFFC && addr32[0xD] == 0xE590C000 && addr32[0xF] == 0xE12FFF3C) temp = fsTryOpenFile;
|
2017-04-28 23:09:33 +02:00
|
|
|
break;
|
|
|
|
case 0x08030204:
|
|
|
|
if(*fsOpenFileDirectly == 0xFFFFFFFF) temp = fsOpenFileDirectly;
|
|
|
|
break;
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-28 23:09:33 +02:00
|
|
|
if(temp != NULL)
|
|
|
|
{
|
|
|
|
*temp = findFunctionStart(code, addr);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-28 23:09:33 +02:00
|
|
|
if(*temp != 0xFFFFFFFF)
|
|
|
|
{
|
|
|
|
found++;
|
|
|
|
if(found == 4) break;
|
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-28 23:09:33 +02:00
|
|
|
temp = NULL;
|
|
|
|
}
|
2017-04-16 23:59:20 +02:00
|
|
|
}
|
2017-04-15 22:36:34 +02:00
|
|
|
|
2017-04-28 23:09:33 +02:00
|
|
|
return found == 4;
|
2017-04-16 23:59:20 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
static inline bool findLayeredFsPayloadOffset(u8 *code, u32 size, u32 roSize, u32 dataSize, u32 roAddress, u32 dataAddress, u32 *payloadOffset, u32 *pathOffset, u32 *pathAddress)
|
2017-04-17 01:01:03 +02:00
|
|
|
{
|
2017-05-05 20:16:44 +02:00
|
|
|
u32 roundedTextSize = ((size + 4095) & 0xFFFFF000),
|
|
|
|
roundedRoSize = ((roSize + 4095) & 0xFFFFF000),
|
|
|
|
roundedDataSize = ((dataSize + 4095) & 0xFFFFF000);
|
|
|
|
|
2017-04-17 01:01:03 +02:00
|
|
|
//First check for sufficient padding at the end of the .text segment
|
2018-05-23 01:50:25 +02:00
|
|
|
if(roundedTextSize - size >= romfsRedirPatchSize) *payloadOffset = size;
|
2017-05-05 20:16:44 +02:00
|
|
|
else
|
2017-04-17 01:01:03 +02:00
|
|
|
{
|
2017-05-05 20:16:44 +02:00
|
|
|
//If there isn't enough padding look for the "throwFatalError" function to replace
|
|
|
|
u32 svcConnectToPort = 0xFFFFFFFF;
|
2017-04-23 19:20:45 +02:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
for(u32 addr = 4; svcConnectToPort == 0xFFFFFFFF && addr <= size - 4; addr += 4)
|
|
|
|
{
|
|
|
|
if(*(u32 *)(code + addr) == 0xEF00002D)
|
|
|
|
svcConnectToPort = addr - 4;
|
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
if(svcConnectToPort != 0xFFFFFFFF)
|
|
|
|
{
|
|
|
|
u32 func = 0xFFFFFFFF;
|
2017-04-16 23:59:20 +02:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
for(u32 i = 4; func == 0xFFFFFFFF && i <= size - 4; i += 4)
|
|
|
|
{
|
|
|
|
if(*(u32 *)(code + i) != MAKE_BRANCH_LINK(i, svcConnectToPort)) continue;
|
|
|
|
|
|
|
|
func = findFunctionStart(code, i);
|
|
|
|
|
|
|
|
for(u32 pos = func + 4; func != 0xFFFFFFFF && pos <= size - 4 && *(u16 *)(code + pos + 2) != 0xE92D; pos += 4)
|
|
|
|
if(*(u32 *)(code + pos) == 0xE200167E) func = 0xFFFFFFFF;
|
|
|
|
}
|
|
|
|
|
2017-06-28 17:57:09 +02:00
|
|
|
if(func != 0xFFFFFFFF) *payloadOffset = func;
|
2017-05-05 20:16:44 +02:00
|
|
|
}
|
2017-04-16 23:59:20 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
if(roundedRoSize - roSize >= 39)
|
2016-11-16 14:52:50 +01:00
|
|
|
{
|
2017-05-05 20:16:44 +02:00
|
|
|
*pathOffset = roundedTextSize + roSize;
|
|
|
|
*pathAddress = roAddress + roSize;
|
|
|
|
}
|
|
|
|
else if(roundedDataSize - dataSize >= 39)
|
|
|
|
{
|
|
|
|
*pathOffset = roundedTextSize + roundedRoSize + dataSize;
|
|
|
|
*pathAddress = dataAddress + dataSize;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
u32 strSpace = 0xFFFFFFFF;
|
2017-04-13 01:03:37 +02:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
for(u32 addr = 0; strSpace == 0xFFFFFFFF && addr <= size - 4; addr += 4)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-05-05 20:16:44 +02:00
|
|
|
if(*(u32 *)(code + addr) == 0xE3A00B42)
|
|
|
|
strSpace = findFunctionStart(code, addr);
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
if(strSpace != 0xFFFFFFFF)
|
2017-04-17 01:01:03 +02:00
|
|
|
{
|
2017-05-05 20:16:44 +02:00
|
|
|
*pathOffset = strSpace;
|
|
|
|
*pathAddress = 0x100000 + strSpace;
|
2017-04-16 23:59:20 +02:00
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
return *payloadOffset != 0 && *pathOffset != 0;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2016-11-19 15:44:10 +01:00
|
|
|
static inline bool applyCodeIpsPatch(u64 progId, u8 *code, u32 size)
|
|
|
|
{
|
|
|
|
/* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/code.ips"
|
|
|
|
If it exists it should be an IPS format patch */
|
|
|
|
|
|
|
|
char path[] = "/luma/titles/0000000000000000/code.ips";
|
|
|
|
progIdToStr(path + 28, progId);
|
|
|
|
|
|
|
|
IFile file;
|
|
|
|
|
|
|
|
if(!openLumaFile(&file, path)) return true;
|
|
|
|
|
|
|
|
bool ret = false;
|
|
|
|
u8 buffer[5];
|
|
|
|
u64 total;
|
|
|
|
|
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buffer, 5)) || total != 5 || memcmp(buffer, "PATCH", 5) != 0) goto exit;
|
|
|
|
|
|
|
|
while(R_SUCCEEDED(IFile_Read(&file, &total, buffer, 3)) && total == 3)
|
|
|
|
{
|
|
|
|
if(memcmp(buffer, "EOF", 3) == 0)
|
|
|
|
{
|
|
|
|
ret = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 offset = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
|
|
|
|
|
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buffer, 2)) || total != 2) break;
|
|
|
|
|
|
|
|
u32 patchSize = (buffer[0] << 8) | buffer[1];
|
|
|
|
|
|
|
|
if(!patchSize)
|
|
|
|
{
|
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buffer, 2)) || total != 2) break;
|
|
|
|
|
|
|
|
u32 rleSize = (buffer[0] << 8) | buffer[1];
|
|
|
|
|
|
|
|
if(offset + rleSize > size) break;
|
|
|
|
|
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buffer, 1)) || total != 1) break;
|
|
|
|
|
|
|
|
for(u32 i = 0; i < rleSize; i++)
|
|
|
|
code[offset + i] = buffer[0];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(offset + patchSize > size) break;
|
|
|
|
|
|
|
|
if(R_FAILED(IFile_Read(&file, &total, code + offset, patchSize)) || total != patchSize) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
bool loadTitleCodeSection(u64 progId, u8 *code, u32 size)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
|
|
|
/* 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;
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
if(!openLumaFile(&file, path)) return false;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
u64 fileSize;
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize > size) goto error;
|
2016-11-16 03:41:59 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
u64 total;
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
if(R_FAILED(IFile_Read(&file, &total, code, fileSize)) || total != fileSize) goto error;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
2017-08-01 17:27:14 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
error:
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
svcBreak(USERBREAK_ASSERT);
|
|
|
|
while(true);
|
|
|
|
}
|
|
|
|
|
2019-04-18 19:48:01 +02:00
|
|
|
bool loadTitleExheaderInfo(u64 progId, ExHeader_Info *exheaderInfo)
|
2017-08-01 17:27:14 +02:00
|
|
|
{
|
|
|
|
/* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/exheader.bin"
|
2019-04-18 19:48:01 +02:00
|
|
|
If it exists it should be a decrypted exheader / exheader info */
|
2017-08-01 17:27:14 +02:00
|
|
|
|
|
|
|
char path[] = "/luma/titles/0000000000000000/exheader.bin";
|
|
|
|
progIdToStr(path + 28, progId);
|
|
|
|
|
|
|
|
IFile file;
|
|
|
|
|
|
|
|
if(!openLumaFile(&file, path)) return false;
|
|
|
|
|
|
|
|
u64 fileSize;
|
|
|
|
|
2019-07-07 15:06:15 +02:00
|
|
|
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || (fileSize != sizeof(ExHeader_Info) && fileSize != sizeof(ExHeader))) goto error;
|
2017-08-01 17:27:14 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
u64 total;
|
|
|
|
|
2019-04-18 19:48:01 +02:00
|
|
|
if(R_FAILED(IFile_Read(&file, &total, exheaderInfo, sizeof(ExHeader_Info))) || total != sizeof(ExHeader_Info)) goto error;
|
2017-08-01 17:27:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
error:
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
svcBreak(USERBREAK_ASSERT);
|
|
|
|
while(true);
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
static inline bool loadTitleLocaleConfig(u64 progId, u8 *mask, u8 *regionId, u8 *languageId, u8 *countryId, u8 *stateId)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
|
|
|
/* 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);
|
2017-06-05 02:02:04 +02:00
|
|
|
*mask = *regionId = *languageId = *countryId = *stateId = 0;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
IFile file;
|
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
if(!openLumaFile(&file, path)) return false;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2016-11-16 14:52:50 +01:00
|
|
|
bool ret = false;
|
2016-11-16 03:41:59 +01:00
|
|
|
u64 fileSize;
|
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize < 3) goto exit;
|
|
|
|
if(fileSize >= 12) fileSize = 12;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
char buf[12] = "------------";
|
2016-11-16 03:41:59 +01:00
|
|
|
u64 total;
|
|
|
|
|
2016-11-16 20:06:01 +01:00
|
|
|
if(R_FAILED(IFile_Read(&file, &total, buf, fileSize))) goto exit;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
ret = true;
|
|
|
|
|
2017-06-06 19:37:16 +02:00
|
|
|
static const char *regions[] = {"---", "JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"},
|
2017-06-05 02:02:04 +02:00
|
|
|
*languages[] = {"--", "JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"},
|
|
|
|
*countries[] = {"--", "JP", "--", "--", "--", "--", "--", "--", "AI", "AG", "AR", "AW",
|
|
|
|
"BS", "BB", "BZ", "BO", "BR", "VG", "CA", "KY", "CL", "CO", "CR", "DM",
|
|
|
|
"DO", "EC", "SV", "GF", "GD", "GP", "GT", "GY", "HT", "HN", "JM", "MQ",
|
|
|
|
"MX", "MS", "AN", "NI", "PA", "PY", "PE", "KN", "LC", "VC", "SR", "TT",
|
|
|
|
"TC", "US", "UY", "VI", "VE", "--", "--", "--", "--", "--", "--", "--",
|
|
|
|
"--", "--", "--", "--", "AL", "AU", "AT", "BE", "BA", "BW", "BG", "HR",
|
|
|
|
"CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE", "IT",
|
|
|
|
"LV", "LS", "LI", "LT", "LU", "MK", "MT", "ME", "MZ", "NA", "NL", "NZ",
|
|
|
|
"NO", "PL", "PT", "RO", "RU", "RS", "SK", "SI", "ZA", "ES", "SZ", "SE",
|
|
|
|
"CH", "TR", "GB", "ZM", "ZW", "AZ", "MR", "ML", "NE", "TD", "SD", "ER",
|
|
|
|
"DJ", "SO", "AD", "GI", "GG", "IM", "JE", "MC", "TW", "--", "--", "--",
|
|
|
|
"--", "--", "--", "--", "KR", "--", "--", "--", "--", "--", "--", "--",
|
|
|
|
"HK", "MO", "--", "--", "--", "--", "--", "--", "ID", "SG", "TH", "PH",
|
|
|
|
"MY", "--", "--", "--", "CN", "--", "--", "--", "--", "--", "--", "--",
|
|
|
|
"AE", "IN", "EG", "OM", "QA", "KW", "SA", "SY", "BH", "JO", "--", "--",
|
|
|
|
"--", "--", "--", "--", "SM", "VA"};
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-28 23:09:33 +02:00
|
|
|
u32 i;
|
|
|
|
for(i = 0; i < sizeof(regions) / sizeof(char *); i++)
|
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
if(memcmp(buf, regions[i], 3) == 0 && i != 0)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
*regionId = (u8)(i - 1);
|
|
|
|
*mask |= 1;
|
2016-11-16 03:41:59 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
for(i = 0; fileSize >= 6 && i < sizeof(languages) / sizeof(char *); i++)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
if(memcmp(buf + 4, languages[i], 2) == 0 && i != 0)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
*languageId = (u8)(i - 1);
|
|
|
|
*mask |= 2;
|
|
|
|
break;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
for(i = 0; fileSize >= 9 && i < sizeof(countries) / sizeof(char *); i++)
|
|
|
|
{
|
|
|
|
if(memcmp(buf + 7, countries[i], 2) == 0 && i != 0)
|
|
|
|
{
|
|
|
|
*countryId = (u8)i;
|
|
|
|
*mask |= 4;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fileSize >= 12 &&
|
|
|
|
((buf[10] >= '0' && buf[10] <= '9') || (buf[10] >= 'a' && buf[10] <= 'f') || (buf[10] >= 'A' && buf[10] <= 'F')) &&
|
|
|
|
((buf[11] >= '0' && buf[11] <= '9') || (buf[11] >= 'a' && buf[11] <= 'f') || (buf[11] >= 'A' && buf[11] <= 'F')))
|
|
|
|
{
|
|
|
|
if (buf[10] >= '0' && buf[10] <= '9') *stateId = 16 * (buf[10] - '0');
|
|
|
|
else if(buf[10] >= 'a' && buf[10] <= 'f') *stateId = 16 * (buf[10] - 'a');
|
|
|
|
else if(buf[10] >= 'A' && buf[10] <= 'F') *stateId = 16 * (buf[10] - 'A');
|
|
|
|
|
|
|
|
if (buf[11] >= '0' && buf[11] <= '9') *stateId += buf[11] - '0';
|
|
|
|
else if(buf[11] >= 'a' && buf[11] <= 'f') *stateId += buf[11] - 'a';
|
|
|
|
else if(buf[11] >= 'A' && buf[11] <= 'F') *stateId += buf[11] - 'A';
|
|
|
|
|
|
|
|
*mask |= 8;
|
|
|
|
}
|
|
|
|
|
2016-11-16 03:41:59 +01:00
|
|
|
exit:
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
static inline bool patchLayeredFs(u64 progId, u8 *code, u32 size, u32 textSize, u32 roSize, u32 dataSize, u32 roAddress, u32 dataAddress)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
|
|
|
/* Here we look for "/luma/titles/[u64 titleID in hex, uppercase]/romfs"
|
2017-04-13 03:38:36 +02:00
|
|
|
If it exists it should be a folder containing ROMFS files */
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
char path[] = "/luma/titles/0000000000000000/romfs";
|
|
|
|
progIdToStr(path + 28, progId);
|
|
|
|
|
2017-04-23 03:13:38 +02:00
|
|
|
u32 archiveId = checkLumaDir(path);
|
2017-04-13 02:49:19 +02:00
|
|
|
|
2017-04-23 03:13:38 +02:00
|
|
|
if(!archiveId) return true;
|
2017-04-13 02:49:19 +02:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
u32 fsMountArchive = 0xFFFFFFFF,
|
|
|
|
fsRegisterArchive = 0xFFFFFFFF,
|
|
|
|
fsTryOpenFile = 0xFFFFFFFF,
|
2017-04-17 01:01:03 +02:00
|
|
|
fsOpenFileDirectly = 0xFFFFFFFF,
|
2017-05-05 20:16:44 +02:00
|
|
|
payloadOffset = 0,
|
|
|
|
pathOffset = 0,
|
|
|
|
pathAddress;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-29 19:15:41 +02:00
|
|
|
if(!findLayeredFsSymbols(code, textSize, &fsMountArchive, &fsRegisterArchive, &fsTryOpenFile, &fsOpenFileDirectly) ||
|
2017-05-05 20:16:44 +02:00
|
|
|
!findLayeredFsPayloadOffset(code, textSize, roSize, dataSize, roAddress, dataAddress, &payloadOffset, &pathOffset, &pathAddress)) return false;
|
2017-04-29 19:15:41 +02:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
static const char *updateRomFsMounts[] = { "rom2:",
|
2017-05-06 15:48:38 +02:00
|
|
|
"rex:",
|
2017-05-05 20:16:44 +02:00
|
|
|
"patch:",
|
|
|
|
"ext:",
|
|
|
|
"rom:" };
|
2017-06-18 22:31:21 +02:00
|
|
|
u32 updateRomFsIndex;
|
2017-04-29 19:15:41 +02:00
|
|
|
|
|
|
|
//Locate update RomFSes
|
2017-05-05 20:16:44 +02:00
|
|
|
for(updateRomFsIndex = 0; updateRomFsIndex < sizeof(updateRomFsMounts) / sizeof(char *) - 1; updateRomFsIndex++)
|
2017-05-08 21:35:46 +02:00
|
|
|
{
|
|
|
|
u32 patternSize = strnlen(updateRomFsMounts[updateRomFsIndex], 255);
|
|
|
|
u8 temp[7];
|
|
|
|
temp[0] = 0;
|
|
|
|
memcpy(temp + 1, updateRomFsMounts[updateRomFsIndex], patternSize);
|
|
|
|
if(memsearch(code, temp, size, patternSize + 1) != NULL) break;
|
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
//Setup the payload
|
2017-04-17 01:01:03 +02:00
|
|
|
u8 *payload = code + payloadOffset;
|
2016-11-17 15:38:28 +01:00
|
|
|
|
2018-05-23 01:50:25 +02:00
|
|
|
romfsRedirPatchSubstituted1 = *(u32 *)(code + fsOpenFileDirectly);
|
|
|
|
romfsRedirPatchHook1 = MAKE_BRANCH(payloadOffset + (u32)&romfsRedirPatchHook1 - (u32)romfsRedirPatch, fsOpenFileDirectly + 4);
|
2019-04-21 12:15:50 +02:00
|
|
|
romfsRedirPatchSubstituted2 = *(u32 *)(code + fsTryOpenFile);
|
2018-05-23 01:50:25 +02:00
|
|
|
romfsRedirPatchHook2 = MAKE_BRANCH(payloadOffset + (u32)&romfsRedirPatchHook2 - (u32)romfsRedirPatch, fsTryOpenFile + 4);
|
|
|
|
romfsRedirPatchCustomPath = pathAddress;
|
|
|
|
romfsRedirPatchFsMountArchive = 0x100000 + fsMountArchive;
|
|
|
|
romfsRedirPatchFsRegisterArchive = 0x100000 + fsRegisterArchive;
|
|
|
|
romfsRedirPatchArchiveId = archiveId;
|
2019-07-03 22:19:38 +02:00
|
|
|
memcpy(&romfsRedirPatchUpdateRomFsMount, updateRomFsMounts[updateRomFsIndex], 4);
|
2018-05-23 01:50:25 +02:00
|
|
|
|
|
|
|
memcpy(payload, romfsRedirPatch, romfsRedirPatchSize);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
memcpy(code + pathOffset, "lf:", 3);
|
|
|
|
memcpy(code + pathOffset + 3, path, sizeof(path));
|
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
//Place the hooks
|
2017-04-17 01:01:03 +02:00
|
|
|
*(u32 *)(code + fsOpenFileDirectly) = MAKE_BRANCH(fsOpenFileDirectly, payloadOffset);
|
|
|
|
*(u32 *)(code + fsTryOpenFile) = MAKE_BRANCH(fsTryOpenFile, payloadOffset + 12);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
return true;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-05-05 20:16:44 +02:00
|
|
|
void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 roSize, u32 dataSize, u32 roAddress, u32 dataAddress)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2017-06-02 03:39:26 +02:00
|
|
|
if(progId == 0x0004003000008F02LL || //USA Home Menu
|
|
|
|
progId == 0x0004003000008202LL || //JPN Home Menu
|
|
|
|
progId == 0x0004003000009802LL || //EUR Home Menu
|
|
|
|
progId == 0x000400300000A902LL || //KOR Home Menu
|
|
|
|
progId == 0x000400300000A102LL || //CHN Home Menu
|
|
|
|
progId == 0x000400300000B102LL) //TWN Home Menu
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2017-06-02 04:35:02 +02:00
|
|
|
bool applyRegionFreePatch = true;
|
2017-06-02 03:39:26 +02:00
|
|
|
|
|
|
|
switch(progId)
|
|
|
|
{
|
|
|
|
case 0x0004003000008F02LL: //USA Home Menu
|
|
|
|
case 0x0004003000008202LL: //JPN Home Menu
|
|
|
|
case 0x0004003000009802LL: //EUR Home Menu
|
|
|
|
if(progVer <= 4) applyRegionFreePatch = false;
|
|
|
|
break;
|
|
|
|
case 0x000400300000A902LL: //KOR Home Menu
|
|
|
|
if(!progVer) applyRegionFreePatch = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-11-03 18:55:40 +01:00
|
|
|
|
2017-06-02 03:39:26 +02:00
|
|
|
if(applyRegionFreePatch)
|
|
|
|
{
|
|
|
|
static const u8 pattern[] = {
|
|
|
|
0x0A, 0x0C, 0x00, 0x10
|
|
|
|
},
|
|
|
|
patch[] = {
|
|
|
|
0x01, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1
|
|
|
|
};
|
|
|
|
|
2017-06-28 17:37:12 +02:00
|
|
|
//Patch SMDH region check
|
2017-06-02 03:39:26 +02:00
|
|
|
if(!patchMemory(code, textSize,
|
|
|
|
pattern,
|
|
|
|
sizeof(pattern), -31,
|
|
|
|
patch,
|
|
|
|
sizeof(patch), 1
|
|
|
|
)) goto error;
|
|
|
|
}
|
|
|
|
|
2017-06-28 17:37:12 +02:00
|
|
|
//Patch SMDH region check for manuals
|
|
|
|
u32 i;
|
2017-06-28 17:57:09 +02:00
|
|
|
for(i = 4; i < textSize; i += 4)
|
2017-06-28 17:37:12 +02:00
|
|
|
{
|
|
|
|
u32 *code32 = (u32 *)(code + i);
|
|
|
|
if(code32[1] == 0xE1A0000D && (*code32 & 0xFFFFFF00) == 0x0A000000 && (code32[-1] & 0xFFFFFF00) == 0xE1110000)
|
|
|
|
{
|
|
|
|
*code32 = 0xE320F000;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-28 17:57:09 +02:00
|
|
|
if(i == textSize) goto error;
|
2017-06-28 17:37:12 +02:00
|
|
|
|
|
|
|
//Patch DS flashcart whitelist check
|
2017-06-02 04:35:02 +02:00
|
|
|
static const u8 pattern[] = {
|
|
|
|
0x10, 0xD1, 0xE5, 0x08, 0x00, 0x8D
|
|
|
|
};
|
2017-06-02 03:39:26 +02:00
|
|
|
|
2017-06-02 04:35:02 +02:00
|
|
|
u8 *temp = memsearch(code, pattern, textSize, sizeof(pattern));
|
2017-06-02 03:39:26 +02:00
|
|
|
|
2017-06-02 04:35:02 +02:00
|
|
|
if(temp == NULL) goto error;
|
|
|
|
|
|
|
|
u32 additive = findFunctionStart(code, (u32)(temp - code - 1));
|
|
|
|
|
|
|
|
if(additive == 0xFFFFFFFF) goto error;
|
|
|
|
|
|
|
|
u32 *off = (u32 *)(code + additive);
|
|
|
|
|
|
|
|
off[0] = 0xE3A00000; //mov r0, #0
|
|
|
|
off[1] = 0xE12FFF1E; //bx lr
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-03-31 01:38:28 +02:00
|
|
|
|
2016-11-03 19:02:38 +01:00
|
|
|
else if((progId == 0x0004001000021000LL || //USA MSET
|
|
|
|
progId == 0x0004001000020000LL || //JPN MSET
|
|
|
|
progId == 0x0004001000022000LL || //EUR MSET
|
|
|
|
progId == 0x0004001000026000LL || //CHN MSET
|
|
|
|
progId == 0x0004001000027000LL || //KOR MSET
|
|
|
|
progId == 0x0004001000028000LL) //TWN MSET
|
2017-03-05 14:03:15 +01:00
|
|
|
&& CONFIG(PATCHVERSTRING))
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u16 pattern[] = u"Ve";
|
|
|
|
static u16 *patch;
|
|
|
|
u32 patchSize = 0,
|
2016-11-03 18:55:40 +01:00
|
|
|
currentNand = BOOTCFG_NAND;
|
2016-04-08 23:28:40 +02:00
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
u16 customVerString[19];
|
2016-11-06 14:45:45 +01:00
|
|
|
loadCustomVerString(customVerString, &patchSize, currentNand);
|
2016-10-10 17:30:53 +02:00
|
|
|
|
2016-11-06 14:45:45 +01:00
|
|
|
if(patchSize != 0) patch = customVerString;
|
2016-11-03 18:55:40 +01:00
|
|
|
else
|
2016-03-29 22:43:15 +02:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
patchSize = 8;
|
2016-11-03 18:55:40 +01:00
|
|
|
u32 currentFirm = BOOTCFG_FIRM;
|
|
|
|
|
|
|
|
static u16 *verStringsNands[] = { u" Sys",
|
|
|
|
u" Emu",
|
|
|
|
u"Emu2",
|
|
|
|
u"Emu3",
|
|
|
|
u"Emu4" },
|
|
|
|
|
|
|
|
*verStringsEmuSys[] = { u"EmuS",
|
|
|
|
u"Em2S",
|
|
|
|
u"Em3S",
|
|
|
|
u"Em4S" },
|
|
|
|
|
|
|
|
*verStringsSysEmu[] = { u"SysE",
|
|
|
|
u"SyE2",
|
|
|
|
u"SyE3",
|
|
|
|
u"SyE4" };
|
|
|
|
|
2016-11-06 14:45:45 +01:00
|
|
|
patch = (currentFirm != 0) == (currentNand != 0) ? verStringsNands[currentNand] :
|
|
|
|
(!currentNand ? verStringsSysEmu[currentFirm - 1] : verStringsEmuSys[currentNand - 1]);
|
2016-03-29 22:43:15 +02:00
|
|
|
}
|
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
//Patch Ver. string
|
2017-04-17 02:14:17 +02:00
|
|
|
if(!patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern) - 2, 0,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
patchSize, 1
|
2016-11-15 19:29:48 +01:00
|
|
|
)) goto error;
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
|
|
|
|
2016-11-03 19:02:38 +01:00
|
|
|
else if(progId == 0x0004013000008002LL) //NS
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-11-03 19:36:08 +01:00
|
|
|
if(progVer > 4)
|
2016-03-29 22:43:15 +02:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-03-29 22:43:15 +02:00
|
|
|
0x0C, 0x18, 0xE1, 0xD8
|
2016-10-10 17:30:53 +02:00
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
patch[] = {
|
2016-03-29 22:43:15 +02:00
|
|
|
0x0B, 0x18, 0x21, 0xC8
|
|
|
|
};
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Disable updates from foreign carts (makes carts region-free)
|
2017-04-17 02:14:17 +02:00
|
|
|
u32 ret = patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern), 0,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 2
|
|
|
|
);
|
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(ret == 0 || (ret == 1 && progVer > 0xB)) goto error;
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2017-06-05 02:02:04 +02:00
|
|
|
if(isN3DS)
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-09-11 19:17:56 +02:00
|
|
|
u32 cpuSetting = MULTICONFIG(NEWCPU);
|
2016-04-19 17:17:39 +02:00
|
|
|
|
2016-09-08 02:12:29 +02:00
|
|
|
if(cpuSetting != 0)
|
2016-04-12 15:25:36 +02:00
|
|
|
{
|
2016-11-06 14:52:10 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x0C, 0x00, 0x94, 0x15
|
2016-04-12 15:25:36 +02:00
|
|
|
};
|
|
|
|
|
2017-04-17 02:14:17 +02:00
|
|
|
u32 *off = (u32 *)memsearch(code, pattern, textSize, sizeof(pattern));
|
2016-04-12 15:25:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(off == NULL) goto error;
|
|
|
|
|
|
|
|
//Patch N3DS CPU Clock and L2 cache setting
|
|
|
|
*(off - 4) = *(off - 3);
|
|
|
|
*(off - 3) = *(off - 1);
|
2018-05-24 00:55:38 +02:00
|
|
|
memmove(off - 1, off, 16);
|
2016-11-15 19:29:48 +01:00
|
|
|
*(off + 3) = 0xE3800000 | cpuSetting;
|
2016-04-12 15:25:36 +02:00
|
|
|
}
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
2017-06-05 02:02:04 +02:00
|
|
|
|
2017-09-20 01:02:14 +02:00
|
|
|
if(progVer > 0x12)
|
|
|
|
{
|
|
|
|
static const u8 pattern[] = {
|
|
|
|
0x00, 0xB1, 0x15, 0x00
|
|
|
|
};
|
|
|
|
|
|
|
|
u8 *roStart = code + ((textSize + 4095) & 0xFFFFF000),
|
|
|
|
*start = memsearch(roStart, pattern, roSize, sizeof(pattern));
|
|
|
|
|
|
|
|
if(start == NULL) goto error;
|
|
|
|
|
|
|
|
start++;
|
|
|
|
u8 *end;
|
|
|
|
for(end = start + 8; *(u32 *)end != 0xCC010000; end += 8)
|
|
|
|
if(end >= roStart + roSize - 12) goto error;
|
|
|
|
|
2018-05-24 00:55:38 +02:00
|
|
|
memset(start, 0, end - start);
|
2017-09-20 01:02:14 +02:00
|
|
|
}
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-03-29 22:43:15 +02:00
|
|
|
|
2016-11-03 19:02:38 +01:00
|
|
|
else if(progId == 0x0004013000001702LL) //CFG
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x06, 0x46, 0x10, 0x48
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
patch[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x00, 0x26
|
|
|
|
};
|
|
|
|
|
|
|
|
//Disable SecureInfo signature check
|
2017-04-17 02:14:17 +02:00
|
|
|
if(!patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern), 0,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 1
|
2016-11-15 19:29:48 +01:00
|
|
|
)) goto error;
|
2016-11-03 18:55:40 +01:00
|
|
|
|
|
|
|
if(secureInfoExists())
|
2016-03-29 22:43:15 +02:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u16 pattern[] = u"Sec",
|
|
|
|
patch[] = u"C";
|
2016-03-29 22:43:15 +02:00
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
//Use SecureInfo_C
|
2017-05-05 20:16:44 +02:00
|
|
|
if(patchMemory(code + ((textSize + 4095) & 0xFFFFF000), roSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern) - 2, 22,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch) - 2, 2
|
2016-11-15 19:29:48 +01:00
|
|
|
) != 2) goto error;
|
2016-05-14 15:35:59 +02:00
|
|
|
}
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-11-05 17:46:47 +01:00
|
|
|
|
2016-11-03 19:02:38 +01:00
|
|
|
else if(progId == 0x0004013000003702LL && progVer > 0) //RO
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x20, 0xA0, 0xE1, 0x8B
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern2[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0xE1, 0x30, 0x40, 0x2D
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern3[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x2D, 0xE9, 0x01, 0x70
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
patch[] = {
|
2016-11-03 19:02:38 +01:00
|
|
|
0x00, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1 //mov r0, #0; bx lr
|
2016-11-03 18:55:40 +01:00
|
|
|
};
|
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
//Disable CRR0 signature (RSA2048 with SHA256) check and CRO0/CRR0 SHA256 hash checks (section hashes, and hash table)
|
2017-04-17 02:14:17 +02:00
|
|
|
if(!patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern), -9,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 1
|
2016-11-15 19:29:48 +01:00
|
|
|
) ||
|
2017-04-17 02:14:17 +02:00
|
|
|
!patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern2,
|
|
|
|
sizeof(pattern2), 1,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 1
|
2016-11-15 19:29:48 +01:00
|
|
|
) ||
|
2017-04-17 02:14:17 +02:00
|
|
|
!patchMemory(code, textSize,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern3,
|
|
|
|
sizeof(pattern3), -2,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 1
|
2016-11-15 19:29:48 +01:00
|
|
|
)) goto error;
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-10-10 17:30:53 +02:00
|
|
|
|
2017-04-15 15:47:10 +02:00
|
|
|
else if(progId == 0x0004013000002802LL) //DLP
|
2016-12-22 17:51:17 +01:00
|
|
|
{
|
|
|
|
static const u8 pattern[] = {
|
|
|
|
0x0C, 0xAC, 0xC0, 0xD8
|
|
|
|
},
|
|
|
|
patch[] = {
|
|
|
|
0x00, 0x00, 0x00, 0x00
|
|
|
|
};
|
2016-12-22 17:53:31 +01:00
|
|
|
|
2017-06-28 17:37:12 +02:00
|
|
|
//Patch DLP region check
|
2017-04-17 02:14:17 +02:00
|
|
|
if(!patchMemory(code, textSize,
|
2016-12-22 17:51:17 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern), 0,
|
|
|
|
patch,
|
|
|
|
sizeof(patch), 1
|
|
|
|
)) goto error;
|
2016-12-22 17:53:31 +01:00
|
|
|
}
|
2017-03-05 14:03:15 +01:00
|
|
|
|
2017-06-26 23:39:28 +02:00
|
|
|
else if(progId == 0x0004013000001A02LL) //DSP
|
|
|
|
{
|
|
|
|
static const u8 pattern[] = {
|
2017-06-26 23:42:41 +02:00
|
|
|
0xE3, 0x10, 0x10, 0x80, 0xE2
|
2017-06-26 23:39:28 +02:00
|
|
|
},
|
|
|
|
patch[] = {
|
|
|
|
0x00, 0x20, 0xA0, 0xE3
|
|
|
|
};
|
|
|
|
|
|
|
|
//Patch DSP signature check
|
|
|
|
if(!patchMemory(code, textSize,
|
|
|
|
pattern,
|
2017-06-26 23:42:41 +02:00
|
|
|
sizeof(pattern), -3,
|
2017-06-26 23:39:28 +02:00
|
|
|
patch,
|
|
|
|
sizeof(patch), 1
|
|
|
|
)) goto error;
|
|
|
|
}
|
|
|
|
|
2017-05-01 18:21:18 +02:00
|
|
|
if(CONFIG(PATCHGAMES))
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2017-08-01 17:27:14 +02:00
|
|
|
if(!applyCodeIpsPatch(progId, code, size)) goto error;
|
2016-09-06 13:43:00 +02:00
|
|
|
|
2017-05-01 18:21:18 +02:00
|
|
|
if((u32)((progId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000)
|
2016-05-28 16:13:22 +02:00
|
|
|
{
|
2017-06-05 02:02:04 +02:00
|
|
|
u8 mask,
|
|
|
|
regionId,
|
|
|
|
languageId,
|
|
|
|
countryId,
|
|
|
|
stateId;
|
|
|
|
|
|
|
|
if(loadTitleLocaleConfig(progId, &mask, ®ionId, &languageId, &countryId, &stateId))
|
|
|
|
svcKernelSetState(0x10001, ((u32)stateId << 24) | ((u32)countryId << 16) | ((u32)languageId << 8) | ((u32)regionId << 4) | (u32)mask , progId);
|
|
|
|
if(!patchLayeredFs(progId, code, size, textSize, roSize, dataSize, roAddress, dataAddress)) goto error;
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
2016-11-06 14:45:45 +01:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
error:
|
|
|
|
svcBreak(USERBREAK_ASSERT);
|
2016-12-22 17:51:17 +01:00
|
|
|
}
|