2016-03-29 17:43:53 +02:00
|
|
|
#include <3ds.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "patcher.h"
|
|
|
|
#include "ifile.h"
|
|
|
|
|
|
|
|
#ifndef PATH_MAX
|
|
|
|
#define PATH_MAX 255
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static u32 config = 0;
|
|
|
|
static u8 secureinfo[0x111] = {0};
|
|
|
|
|
|
|
|
//Quick Search algorithm, adapted from http://igm.univ-mlv.fr/~lecroq/string/node19.html#SECTION00190
|
2016-04-02 17:58:06 +02:00
|
|
|
static u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize)
|
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
const u8 *patternc = (const u8 *)pattern;
|
|
|
|
|
|
|
|
//Preprocessing
|
2016-03-29 18:47:30 +02:00
|
|
|
u32 table[256];
|
2016-03-29 17:43:53 +02:00
|
|
|
|
|
|
|
for(u32 i = 0; i < 256; ++i)
|
|
|
|
table[i] = patternSize + 1;
|
|
|
|
for(u32 i = 0; i < patternSize; ++i)
|
|
|
|
table[patternc[i]] = patternSize - i;
|
|
|
|
|
|
|
|
//Searching
|
|
|
|
u32 j = 0;
|
|
|
|
|
2016-04-02 17:58:06 +02:00
|
|
|
while(j <= size - patternSize)
|
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
if(memcmp(patternc, startPos + j, patternSize) == 0)
|
|
|
|
return startPos + j;
|
|
|
|
j += table[startPos[j + patternSize]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
static u32 patchMemory(u8 *start, u32 size, const void *pattern, u32 patSize, int offset, const void *replace, u32 repSize, u32 count)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
u32 i;
|
|
|
|
|
2016-04-02 17:58:06 +02:00
|
|
|
for(i = 0; i < count; i++)
|
|
|
|
{
|
2016-04-02 18:48:31 +02:00
|
|
|
u8 *found = memsearch(start, pattern, size, patSize);
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-03-29 17:43:53 +02:00
|
|
|
if(found == NULL)
|
|
|
|
break;
|
|
|
|
|
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-02 18:48:31 +02:00
|
|
|
if(at + patSize > size) size = 0;
|
|
|
|
else size = size - (at + patSize);
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
start = found + patSize;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
static int fileOpen(IFile *file, FS_ArchiveID id, const char *path, int flags)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
FS_Archive archive;
|
|
|
|
FS_Path ppath;
|
|
|
|
|
|
|
|
size_t len = strnlen(path, PATH_MAX);
|
|
|
|
archive.id = id;
|
|
|
|
archive.lowPath.type = PATH_EMPTY;
|
|
|
|
archive.lowPath.size = 1;
|
|
|
|
archive.lowPath.data = (u8 *)"";
|
|
|
|
ppath.type = PATH_ASCII;
|
|
|
|
ppath.data = path;
|
|
|
|
ppath.size = len+1;
|
2016-04-04 18:19:00 +02:00
|
|
|
|
2016-03-29 17:43:53 +02:00
|
|
|
return IFile_Open(file, archive, ppath, flags);
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
static int loadSecureinfo()
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
IFile file;
|
|
|
|
Result ret;
|
|
|
|
u64 total;
|
|
|
|
|
|
|
|
if(secureinfo[0] == 0xFF)
|
|
|
|
return 0;
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
ret = fileOpen(&file, ARCHIVE_NAND_RW, "/sys/SecureInfo_C", FS_OPEN_READ);
|
2016-04-02 17:58:06 +02:00
|
|
|
if(R_SUCCEEDED(ret))
|
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
ret = IFile_Read(&file, &total, secureinfo, sizeof(secureinfo));
|
|
|
|
IFile_Close(&file);
|
2016-03-29 22:43:15 +02:00
|
|
|
if(R_SUCCEEDED(ret) && total == sizeof(secureinfo))
|
|
|
|
secureinfo[0] = 0xFF;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
static int loadConfig()
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 17:43:53 +02:00
|
|
|
IFile file;
|
|
|
|
Result ret;
|
|
|
|
u64 total;
|
|
|
|
|
|
|
|
if(config)
|
|
|
|
return 0;
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
ret = fileOpen(&file, ARCHIVE_SDMC, "/aurei/config.bin", FS_OPEN_READ);
|
2016-04-02 17:58:06 +02:00
|
|
|
if(R_SUCCEEDED(ret))
|
|
|
|
{
|
2016-04-04 18:19:00 +02:00
|
|
|
ret = IFile_Read(&file, &total, &config, 3);
|
2016-03-29 17:43:53 +02:00
|
|
|
IFile_Close(&file);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-04-04 18:19:00 +02:00
|
|
|
void patchCode(u64 progId, u8 *code, u32 size)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-04-04 18:19:00 +02:00
|
|
|
switch(progId)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 22:43:15 +02:00
|
|
|
case 0x0004003000008F02LL: // USA Menu
|
|
|
|
case 0x0004003000008202LL: // EUR Menu
|
|
|
|
case 0x0004003000009802LL: // JPN Menu
|
|
|
|
case 0x000400300000A102LL: // CHN Menu
|
|
|
|
case 0x000400300000A902LL: // KOR Menu
|
|
|
|
case 0x000400300000B102LL: // TWN Menu
|
|
|
|
{
|
|
|
|
static const u8 regionFreePattern[] = {
|
|
|
|
0x00, 0x00, 0x55, 0xE3, 0x01, 0x10, 0xA0, 0xE3
|
|
|
|
};
|
|
|
|
static const u8 regionFreePatch[] = {
|
|
|
|
0x01, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1
|
|
|
|
};
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Patch SMDH region checks
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
regionFreePattern,
|
|
|
|
sizeof(regionFreePattern), -16,
|
|
|
|
regionFreePatch,
|
|
|
|
sizeof(regionFreePatch), 1
|
2016-03-29 17:43:53 +02:00
|
|
|
);
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-03-29 22:43:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x0004013000002C02LL: // NIM
|
|
|
|
{
|
|
|
|
static const u8 blockAutoUpdatesPattern[] = {
|
|
|
|
0x25, 0x79, 0x0B, 0x99
|
|
|
|
};
|
|
|
|
static const u8 blockAutoUpdatesPatch[] = {
|
|
|
|
0xE3, 0xA0
|
|
|
|
};
|
2016-04-02 18:48:31 +02:00
|
|
|
static const u8 skipEshopUpdateCheckPattern[] = {
|
2016-03-29 22:43:15 +02:00
|
|
|
0x30, 0xB5, 0xF1, 0xB0
|
|
|
|
};
|
2016-04-02 18:48:31 +02:00
|
|
|
static const u8 skipEshopUpdateCheckPatch[] = {
|
2016-03-29 22:43:15 +02:00
|
|
|
0x00, 0x20, 0x08, 0x60, 0x70, 0x47
|
|
|
|
};
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Block silent auto-updates
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
blockAutoUpdatesPattern,
|
|
|
|
sizeof(blockAutoUpdatesPattern), 0,
|
|
|
|
blockAutoUpdatesPatch,
|
|
|
|
sizeof(blockAutoUpdatesPatch), 1
|
2016-03-29 17:43:53 +02:00
|
|
|
);
|
2016-04-02 18:48:31 +02:00
|
|
|
|
|
|
|
//Skip update checks to access the EShop
|
|
|
|
patchMemory(code, size,
|
|
|
|
skipEshopUpdateCheckPattern,
|
|
|
|
sizeof(skipEshopUpdateCheckPattern), 0,
|
|
|
|
skipEshopUpdateCheckPatch,
|
|
|
|
sizeof(skipEshopUpdateCheckPatch), 1
|
2016-03-29 22:43:15 +02:00
|
|
|
);
|
2016-03-31 01:38:28 +02:00
|
|
|
|
2016-03-29 22:43:15 +02:00
|
|
|
break;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
2016-03-29 22:43:15 +02:00
|
|
|
|
|
|
|
case 0x0004001000021000LL: // USA MSET
|
|
|
|
case 0x0004001000020000LL: // JPN MSET
|
|
|
|
case 0x0004001000022000LL: // EUR MSET
|
|
|
|
case 0x0004001000026000LL: // CHN MSET
|
|
|
|
case 0x0004001000027000LL: // KOR MSET
|
|
|
|
case 0x0004001000028000LL: // TWN MSET
|
|
|
|
{
|
2016-04-11 05:15:44 +02:00
|
|
|
if(R_SUCCEEDED(loadConfig()) && ((config >> 4) & 1))
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-29 22:43:15 +02:00
|
|
|
static const u16 verPattern[] = u"Ver.";
|
2016-04-11 05:15:44 +02:00
|
|
|
const u32 currentNand = ((config >> 16) & 3);
|
|
|
|
const u32 matchingFirm = ((config >> 18) & 1) == (currentNand != 0);
|
2016-03-29 22:43:15 +02:00
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Patch Ver. string
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
verPattern,
|
|
|
|
sizeof(verPattern) - sizeof(u16), 0,
|
2016-04-11 05:15:44 +02:00
|
|
|
!currentNand ? ((matchingFirm) ? u" Sys" : u"SysA") :
|
|
|
|
((currentNand == 1) ? (matchingFirm ? u" Emu" : u"EmuA") : ((matchingFirm) ? u"Emu2" : u"Em2A")),
|
2016-03-29 22:43:15 +02:00
|
|
|
sizeof(verPattern) - sizeof(u16), 1
|
|
|
|
);
|
|
|
|
}
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-03-29 22:43:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x0004013000008002LL: // NS
|
|
|
|
{
|
|
|
|
static const u8 stopCartUpdatesPattern[] = {
|
|
|
|
0x0C, 0x18, 0xE1, 0xD8
|
|
|
|
};
|
|
|
|
static const u8 stopCartUpdatesPatch[] = {
|
|
|
|
0x0B, 0x18, 0x21, 0xC8
|
|
|
|
};
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Disable updates from foreign carts (makes carts region-free)
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
stopCartUpdatesPattern,
|
|
|
|
sizeof(stopCartUpdatesPattern), 0,
|
|
|
|
stopCartUpdatesPatch,
|
|
|
|
sizeof(stopCartUpdatesPatch), 2
|
2016-03-29 17:43:53 +02:00
|
|
|
);
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-03-29 22:43:15 +02:00
|
|
|
break;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
2016-03-29 22:43:15 +02:00
|
|
|
|
|
|
|
case 0x0004013000001702LL: // CFG
|
|
|
|
{
|
|
|
|
static const u8 secureinfoSigCheckPattern[] = {
|
|
|
|
0x06, 0x46, 0x10, 0x48, 0xFC
|
|
|
|
};
|
|
|
|
static const u8 secureinfoSigCheckPatch[] = {
|
|
|
|
0x00, 0x26
|
|
|
|
};
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Disable SecureInfo signature check
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
secureinfoSigCheckPattern,
|
|
|
|
sizeof(secureinfoSigCheckPattern), 0,
|
|
|
|
secureinfoSigCheckPatch,
|
|
|
|
sizeof(secureinfoSigCheckPatch), 1
|
2016-03-29 17:43:53 +02:00
|
|
|
);
|
2016-03-31 01:38:28 +02:00
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
if(R_SUCCEEDED(loadSecureinfo()))
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-03-31 01:38:28 +02:00
|
|
|
static const u16 secureinfoFilenamePattern[] = u"SecureInfo_";
|
|
|
|
static const u16 secureinfoFilenamePatch[] = u"C";
|
|
|
|
|
2016-04-02 18:48:31 +02:00
|
|
|
//Use SecureInfo_C
|
|
|
|
patchMemory(code, size,
|
2016-03-29 22:43:15 +02:00
|
|
|
secureinfoFilenamePattern,
|
|
|
|
sizeof(secureinfoFilenamePattern) - sizeof(u16),
|
|
|
|
sizeof(secureinfoFilenamePattern) - sizeof(u16),
|
|
|
|
secureinfoFilenamePatch,
|
|
|
|
sizeof(secureinfoFilenamePatch) - sizeof(u16), 2
|
|
|
|
);
|
|
|
|
}
|
2016-04-02 17:58:06 +02:00
|
|
|
|
2016-03-29 22:43:15 +02:00
|
|
|
break;
|
2016-03-29 17:43:53 +02:00
|
|
|
}
|
|
|
|
}
|
2016-04-02 18:48:31 +02:00
|
|
|
}
|