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"
|
2017-04-13 01:03:37 +02:00
|
|
|
#include "fsldr.h"
|
2016-03-29 17:43:53 +02:00
|
|
|
#include "ifile.h"
|
2016-08-29 17:51:03 +02:00
|
|
|
#include "CFWInfo.h"
|
2016-11-16 03:41:59 +01:00
|
|
|
#include "../build/bundled.h"
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-08-28 13:49:15 +02:00
|
|
|
static CFWInfo info;
|
2016-03-29 17:43:53 +02:00
|
|
|
|
2016-11-06 14:45:45 +01: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-11-06 14:45:45 +01:00
|
|
|
u32 i;
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-10-14 18:03:17 +02:00
|
|
|
static 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-04-13 02:49:19 +02:00
|
|
|
static u32 dirCheck(FS_ArchiveID archiveId, const char *path)
|
2017-04-13 01:03:37 +02:00
|
|
|
{
|
2017-04-13 02:49:19 +02:00
|
|
|
u32 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 *)""};
|
|
|
|
|
2017-04-13 02:49:19 +02:00
|
|
|
if(R_FAILED(FSLDR_OpenArchive(&archive, archiveId, archivePath))) ret = 1;
|
2017-04-13 01:03:37 +02:00
|
|
|
else
|
|
|
|
{
|
2017-04-13 02:49:19 +02:00
|
|
|
ret = R_SUCCEEDED(FSLDR_OpenDirectory(&handle, archive, dirPath)) ? 0 : 2;
|
2017-04-13 01:03:37 +02:00
|
|
|
if(ret) FSDIRLDR_Close(handle);
|
|
|
|
FSLDR_CloseArchive(archive);
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
{
|
|
|
|
Result res = fileOpen(file, ARCHIVE_SDMC, path, FS_OPEN_READ);
|
|
|
|
|
2017-04-14 17:42:17 +02:00
|
|
|
if(R_SUCCEEDED(res)) return true;
|
2016-10-14 18:03:17 +02:00
|
|
|
|
2016-11-17 15:38:28 +01:00
|
|
|
//Returned if SD is not mounted
|
2017-04-13 02:49:19 +02:00
|
|
|
return (u32)res == 0xC88044AB && R_SUCCEEDED(fileOpen(file, ARCHIVE_NAND_RW, 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-04-13 02:49:19 +02:00
|
|
|
u32 res = dirCheck(ARCHIVE_SDMC, path);
|
|
|
|
|
|
|
|
if(!res) return ARCHIVE_SDMC;
|
|
|
|
|
|
|
|
return res == 1 && !dirCheck(ARCHIVE_NAND_RW, path) ? ARCHIVE_NAND_RW : 0;
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
static inline void loadCFWInfo(void)
|
2016-08-20 18:45:56 +02:00
|
|
|
{
|
|
|
|
static bool infoLoaded = false;
|
2016-08-26 18:44:39 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(infoLoaded) return;
|
2016-08-28 13:49:15 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
svcGetCFWInfo(&info);
|
2016-08-26 18:44:39 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
IFile file;
|
|
|
|
if(LOADERFLAG(ISSAFEMODE) && R_SUCCEEDED(fileOpen(&file, ARCHIVE_SDMC, "/", FS_OPEN_READ))) //Init SD card if SAFE_MODE is being booted
|
|
|
|
IFile_Close(&file);
|
|
|
|
|
|
|
|
infoLoaded = true;
|
2016-08-20 18:45:56 +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
|
|
|
}
|
|
|
|
|
2016-11-04 22:28:33 +01:00
|
|
|
static inline u8 *getCfgOffsets(u8 *code, u32 size, u32 *CFGUHandleOffset)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-04-15 03:34:08 +02:00
|
|
|
/* HANS:
|
|
|
|
Look for error code which is known to be stored near cfg:u handle
|
|
|
|
this way we can find the right candidate
|
|
|
|
(handle should also be stored right after end of candidate function) */
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-04-15 03:34:08 +02:00
|
|
|
u32 n = 0,
|
|
|
|
possible[24];
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-16 20:06:01 +01:00
|
|
|
for(u8 *pos = code + 16; n < 24 && pos <= code + size - 16; pos += 4)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
if(*(u32 *)pos != 0xD8A103F9) continue;
|
|
|
|
|
|
|
|
for(u32 *l = (u32 *)pos - 4; n < 24 && l < (u32 *)pos + 4; l++)
|
|
|
|
if(*l <= 0x10000000) possible[n++] = *l;
|
2016-04-15 03:34:08 +02:00
|
|
|
}
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(!n) return NULL;
|
|
|
|
|
2016-11-16 20:06:01 +01:00
|
|
|
for(u8 *CFGU_GetConfigInfoBlk2_endPos = code; CFGU_GetConfigInfoBlk2_endPos <= code + size - 12; CFGU_GetConfigInfoBlk2_endPos += 4)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
static const u32 CFGU_GetConfigInfoBlk2_endPattern[] = {0xE8BD8010, 0x00010082};
|
2016-04-15 03:34:08 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
//There might be multiple implementations of GetConfigInfoBlk2 but let's search for the one we want
|
|
|
|
u32 *cmp = (u32 *)CFGU_GetConfigInfoBlk2_endPos;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(cmp[0] != CFGU_GetConfigInfoBlk2_endPattern[0] || cmp[1] != CFGU_GetConfigInfoBlk2_endPattern[1]) continue;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
for(u32 i = 0; i < n; i++)
|
2016-11-16 20:06:01 +01:00
|
|
|
if(possible[i] == cmp[2])
|
|
|
|
{
|
|
|
|
*CFGUHandleOffset = cmp[2];
|
|
|
|
return CFGU_GetConfigInfoBlk2_endPos;
|
|
|
|
}
|
2016-11-15 19:29:48 +01:00
|
|
|
|
|
|
|
CFGU_GetConfigInfoBlk2_endPos += 4;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
|
|
|
|
2016-04-15 03:34:08 +02:00
|
|
|
return NULL;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
|
|
|
|
2016-11-15 19:35:57 +01:00
|
|
|
static inline bool patchCfgGetLanguage(u8 *code, u32 size, u8 languageId, u8 *CFGU_GetConfigInfoBlk2_endPos)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-04-15 03:34:08 +02:00
|
|
|
u8 *CFGU_GetConfigInfoBlk2_startPos; //Let's find STMFD SP (there might be a NOP before, but nevermind)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-04-15 03:34:08 +02:00
|
|
|
for(CFGU_GetConfigInfoBlk2_startPos = CFGU_GetConfigInfoBlk2_endPos - 4;
|
2016-11-16 20:06:01 +01:00
|
|
|
*((u16 *)CFGU_GetConfigInfoBlk2_startPos + 1) != 0xE92D; CFGU_GetConfigInfoBlk2_startPos -= 4)
|
|
|
|
if(CFGU_GetConfigInfoBlk2_startPos < code + 4) return false;
|
2016-11-15 19:29:48 +01:00
|
|
|
|
2016-11-16 20:06:01 +01:00
|
|
|
for(u8 *languageBlkIdPos = code; languageBlkIdPos <= code + size - 4; languageBlkIdPos += 4)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
if(*(u32 *)languageBlkIdPos != 0xA0002) continue;
|
|
|
|
|
|
|
|
for(u8 *instr = languageBlkIdPos - 8; instr >= languageBlkIdPos - 0x1008 && instr >= code + 4; instr -= 4) //Should be enough
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
if(instr[3] != 0xEB) continue; //We're looking for BL
|
|
|
|
|
|
|
|
u8 *calledFunction = instr;
|
|
|
|
u32 i = 0;
|
|
|
|
|
|
|
|
do
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
u32 low24 = (*(u32 *)calledFunction & 0x00FFFFFF) << 2;
|
|
|
|
u32 signMask = (u32)(-(low24 >> 25)) & 0xFC000000; //Sign extension
|
|
|
|
s32 offset = (s32)(low24 | signMask) + 8; //Branch offset + 8 for prefetch
|
|
|
|
|
|
|
|
calledFunction += offset;
|
|
|
|
|
|
|
|
if(calledFunction >= CFGU_GetConfigInfoBlk2_startPos - 4 && calledFunction <= CFGU_GetConfigInfoBlk2_endPos)
|
2016-04-15 03:34:08 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
*((u32 *)instr - 1) = 0xE3A00000 | languageId; //mov r0, sp => mov r0, =languageId
|
|
|
|
*(u32 *)instr = 0xE5CD0000; //bl CFGU_GetConfigInfoBlk2 => strb r0, [sp]
|
|
|
|
*((u32 *)instr + 1) = 0xE3B00000; //(1 or 2 instructions) => movs r0, 0 (result code)
|
|
|
|
|
|
|
|
//We're done
|
2016-11-15 19:35:57 +01:00
|
|
|
return true;
|
2016-04-15 03:34:08 +02:00
|
|
|
}
|
2016-11-15 19:29:48 +01:00
|
|
|
|
|
|
|
i++;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
while(i < 2 && calledFunction[3] == 0xEA);
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-09 16:33:54 +01:00
|
|
|
|
2016-11-15 19:35:57 +01:00
|
|
|
return false;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
|
|
|
|
2016-11-11 04:04:12 +01:00
|
|
|
static inline void patchCfgGetRegion(u8 *code, u32 size, u8 regionId, u32 CFGUHandleOffset)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-16 20:06:01 +01:00
|
|
|
for(u8 *cmdPos = code; cmdPos <= code + size - 28; cmdPos += 4)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-09 16:33:54 +01:00
|
|
|
static const u32 cfgSecureInfoGetRegionCmdPattern[] = {0xEE1D0F70, 0xE3A00802};
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-04-15 03:34:08 +02:00
|
|
|
u32 *cmp = (u32 *)cmdPos;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(*cmp != cfgSecureInfoGetRegionCmdPattern[1]) continue;
|
|
|
|
|
|
|
|
for(u32 i = 1; i < 3; i++)
|
|
|
|
if((*(cmp - i) & 0xFFFF0FFF) == cfgSecureInfoGetRegionCmdPattern[0] && *((u16 *)cmdPos + 5) == 0xE59F &&
|
|
|
|
*(u32 *)(cmdPos + 16 + *((u16 *)cmdPos + 4)) == CFGUHandleOffset)
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
{
|
2016-11-15 19:29:48 +01:00
|
|
|
cmp[3] = 0xE3A00000 | regionId; //mov r0, =regionId
|
|
|
|
cmp[4] = 0xE5C40008; //strb r0, [r4, #8]
|
|
|
|
cmp[5] = 0xE3A00000; //mov r0, #0 (result code)
|
|
|
|
cmp[6] = 0xE5840004; //str r0, [r4, #4]
|
2016-11-09 16:33:54 +01:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
//The remaining, not patched, function code will do the rest for us
|
|
|
|
return;
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
static u32 findFunctionStart(u8* code, u32 pos)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2016-11-16 20:06:01 +01:00
|
|
|
while(pos >= 4)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
|
|
|
pos -= 4;
|
2016-11-16 20:06:01 +01:00
|
|
|
if(*(u16 *)(code + pos + 2) == 0xE92D) return pos;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 20:06:01 +01:00
|
|
|
return 0xFFFFFFFF;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
static bool findLayeredFsSymbols(u8* code, u32 size, u32 *fsMountArchive, u32 *fsRegisterArchive, u32 *fsTryOpenFile, u32 *fsOpenFileDirectly, u32 *throwFatalError)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-04-13 01:03:37 +02:00
|
|
|
u32 svcConnectToPort = 0xFFFFFFFF;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
for(u32 addr = 0; addr <= size - 4; addr += 4)
|
|
|
|
{
|
|
|
|
if(*fsMountArchive == 0xFFFFFFFF)
|
|
|
|
{
|
|
|
|
if(addr <= size - 12 && *(u32 *)(code + addr) == 0xE5970010)
|
|
|
|
{
|
|
|
|
if((*(u32 *)(code + addr + 4) == 0xE1CD20D8) && ((*(u32 *)(code + addr + 8) & 0xFFFFFF) == 0x008D0000))
|
2017-04-13 03:38:36 +02:00
|
|
|
*fsMountArchive = findFunctionStart(code, addr);
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
else if(addr <= size - 16 && *(u32 *)(code + addr) == 0xE24DD028)
|
|
|
|
{
|
|
|
|
if((*(u32 *)(code + addr + 4) == 0xE1A04000) && (*(u32 *)(code + addr + 8) == 0xE59F60A8) && (*(u32 *)(code + addr + 0xC) == 0xE3A0C001))
|
2017-04-13 03:38:36 +02:00
|
|
|
*fsMountArchive = findFunctionStart(code, addr);
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
if(addr <= size - 12 && *fsRegisterArchive == 0xFFFFFFFF && *(u32 *)(code + addr) == 0xE3500008 && (*(u32 *)(code + addr + 4) & 0xFFF00FF0) == 0xE1800400 && (*(u32 *)(code + addr + 8) & 0xFFF00FF0) == 0xE1800FC0)
|
|
|
|
*fsRegisterArchive = findFunctionStart(code, addr);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
if(addr <= size - 16 && *fsTryOpenFile == 0xFFFFFFFF && *(u32 *)(code + addr + 0xC) == 0xE12FFF3C &&
|
|
|
|
((*(u32 *)(code + addr) == 0xE1A0100D) || (*(u32 *)(code + addr) == 0xE28D1010)) && (*(u32 *)(code + addr + 4) == 0xE590C000) &&
|
|
|
|
((*(u32 *)(code + addr + 8) == 0xE1A00004) || (*(u32 *)(code + addr + 8) == 0xE1A00005)))
|
|
|
|
*fsTryOpenFile = findFunctionStart(code, addr);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
if(*fsOpenFileDirectly == 0xFFFFFFFF && *(u32 *)(code + addr) == 0x08030204)
|
|
|
|
*fsOpenFileDirectly = findFunctionStart(code, addr);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
if(addr >= 4 && svcConnectToPort == 0xFFFFFFFF && *(u32 *)(code + addr) == 0xEF00002D)
|
|
|
|
svcConnectToPort = addr - 4;
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
if(svcConnectToPort != 0xFFFFFFFF && *fsMountArchive != 0xFFFFFFFF && *fsRegisterArchive != 0xFFFFFFFF && *fsTryOpenFile != 0xFFFFFFFF && *fsOpenFileDirectly != 0xFFFFFFFF)
|
2016-11-16 14:52:50 +01:00
|
|
|
{
|
2017-04-13 01:03:37 +02:00
|
|
|
u32 func = 0xFFFFFFFF;
|
|
|
|
|
|
|
|
for(u32 i = 4; func == 0xFFFFFFFF && i <= size - 4; i += 4)
|
|
|
|
{
|
|
|
|
if(*(u32 *)(code + i) != MAKE_BRANCH_LINK(i, svcConnectToPort)) continue;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
func = findFunctionStart(code, i);
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
for(u32 pos = func + 4; func != 0xFFFFFFFF && pos <= size - 4 && *(u16 *)(code + pos + 2) != 0xE92D; pos += 4)
|
|
|
|
if(*(u32 *)(code + pos) == 0xE200167E) func = 0xFFFFFFFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
*throwFatalError = func;
|
|
|
|
|
|
|
|
if(func != 0xFFFFFFFF) return true;
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
return false;
|
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;
|
|
|
|
}
|
|
|
|
|
2016-11-16 03:41:59 +01:00
|
|
|
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;
|
|
|
|
|
2016-11-17 15:38:28 +01:00
|
|
|
if(!openLumaFile(&file, path)) return true;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-11-17 15:38:28 +01:00
|
|
|
if(!openLumaFile(&file, path)) return true;
|
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;
|
|
|
|
|
2016-11-16 14:52:50 +01:00
|
|
|
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize < 6 || fileSize > 8) goto exit;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
char buf[8];
|
|
|
|
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
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
static inline bool patchLayeredFs(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]/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-13 02:49:19 +02:00
|
|
|
u32 archive = checkLumaDir(path);
|
|
|
|
|
|
|
|
if(!archive) return true;
|
|
|
|
|
|
|
|
const char *mount = archive == ARCHIVE_SDMC ? "sdmc:" : "nand:";
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
u32 fsMountArchive = 0xFFFFFFFF,
|
|
|
|
fsRegisterArchive = 0xFFFFFFFF,
|
|
|
|
fsTryOpenFile = 0xFFFFFFFF,
|
|
|
|
fsOpenFileDirectly = 0xFFFFFFFF,
|
|
|
|
throwFatalError;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
2017-04-13 03:38:36 +02:00
|
|
|
if(!findLayeredFsSymbols(code, size, &fsMountArchive, &fsRegisterArchive, &fsTryOpenFile, &fsOpenFileDirectly, &throwFatalError)) return false;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
//Setup the payload
|
2016-11-17 15:38:28 +01:00
|
|
|
u8 *payload = code + throwFatalError;
|
|
|
|
memcpy(payload, romfsredir_bin, romfsredir_bin_size);
|
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
//Insert symbols in the payload
|
|
|
|
u32 *payload32 = (u32 *)payload;
|
|
|
|
for(u32 i = 0; i < romfsredir_bin_size / 4; i++)
|
2016-11-16 03:41:59 +01:00
|
|
|
{
|
2017-04-13 01:03:37 +02:00
|
|
|
switch (payload32[i])
|
|
|
|
{
|
|
|
|
case 0xdead0000:
|
|
|
|
payload32[i] = *(u32 *)(code + fsOpenFileDirectly);
|
|
|
|
break;
|
|
|
|
case 0xdead0001:
|
|
|
|
payload32[i] = MAKE_BRANCH(throwFatalError + i * 4, fsOpenFileDirectly + 4);
|
|
|
|
break;
|
|
|
|
case 0xdead0002:
|
|
|
|
payload32[i] = *(u32 *)(code + fsTryOpenFile);
|
|
|
|
break;
|
|
|
|
case 0xdead0003:
|
|
|
|
payload32[i] = MAKE_BRANCH(throwFatalError + i * 4, fsTryOpenFile + 4);
|
|
|
|
break;
|
|
|
|
case 0xdead0004:
|
2017-04-13 02:49:19 +02:00
|
|
|
memcpy(payload32 + i, mount, 5);
|
2017-04-13 01:03:37 +02:00
|
|
|
memcpy((u8 *)(payload32 + i) + 5, path, sizeof(path));
|
|
|
|
break;
|
|
|
|
case 0xdead0005:
|
|
|
|
payload32[i] = 0x100000 + fsMountArchive;
|
|
|
|
break;
|
|
|
|
case 0xdead0006:
|
|
|
|
payload32[i] = 0x100000 + fsRegisterArchive;
|
|
|
|
break;
|
2017-04-13 02:49:19 +02:00
|
|
|
case 0xdead0007:
|
|
|
|
memcpy(payload32 + i, mount, 4);
|
|
|
|
break;
|
|
|
|
case 0xdead0008:
|
|
|
|
payload32[i] = archive;
|
|
|
|
break;
|
2017-04-13 01:03:37 +02:00
|
|
|
}
|
2016-11-16 03:41:59 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 01:03:37 +02:00
|
|
|
//Place the hooks
|
|
|
|
*(u32 *)(code + fsOpenFileDirectly) = MAKE_BRANCH(fsOpenFileDirectly, throwFatalError);
|
|
|
|
*(u32 *)(code + fsTryOpenFile) = MAKE_BRANCH(fsTryOpenFile, throwFatalError + 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
|
|
|
}
|
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
void patchCode(u64 progId, u16 progVer, u8 *code, u32 size)
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-08-17 23:47:30 +02:00
|
|
|
loadCFWInfo();
|
2016-08-26 18:44:39 +02:00
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
if(((progId == 0x0004003000008F02LL || //USA Home Menu
|
|
|
|
progId == 0x0004003000008202LL || //JPN Home Menu
|
|
|
|
progId == 0x0004003000009802LL) //EUR Home Menu
|
|
|
|
&& progVer > 4) ||
|
|
|
|
(progId == 0x000400300000A902LL //KOR Home Menu
|
|
|
|
&& progVer > 0) ||
|
|
|
|
progId == 0x000400300000A102LL || //CHN Home Menu
|
|
|
|
progId == 0x000400300000B102LL) //TWN Home Menu
|
2016-04-02 17:58:06 +02:00
|
|
|
{
|
2016-11-06 14:45:45 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x0A, 0x0C, 0x00, 0x10
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
patch[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x01, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1
|
|
|
|
};
|
|
|
|
|
|
|
|
//Patch SMDH region checks
|
2016-11-06 14:52:10 +01:00
|
|
|
if(!patchMemory(code, size,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
2016-11-06 14:52:10 +01:00
|
|
|
sizeof(pattern), -31,
|
|
|
|
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-03-31 01:38:28 +02:00
|
|
|
|
2016-11-03 19:02:38 +01:00
|
|
|
else if(progId == 0x0004013000003202LL) //FRIENDS
|
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
|
|
|
0x42, 0xE0, 0x1E, 0xFF
|
|
|
|
};
|
2016-03-29 22:43:15 +02:00
|
|
|
|
2017-04-11 16:33:40 +02:00
|
|
|
u8 mostRecentFpdVer = 10;
|
2016-09-08 02:12:29 +02:00
|
|
|
|
2016-11-06 14:45:45 +01:00
|
|
|
u8 *off = memsearch(code, pattern, size, sizeof(pattern));
|
2016-09-08 02:12:29 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(off == NULL) goto error;
|
2016-04-08 23:28:40 +02:00
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
//Allow online access to work with old friends modules
|
2016-11-15 19:29:48 +01:00
|
|
|
if(off[0xA] < mostRecentFpdVer) off[0xA] = mostRecentFpdVer;
|
2016-11-03 18:55:40 +01:00
|
|
|
}
|
2016-10-10 15:53:56 +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
|
2016-11-06 14:52:10 +01:00
|
|
|
if(!patchMemory(code, size,
|
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)
|
2016-11-06 14:52:10 +01:00
|
|
|
u32 ret = patchMemory(code, size,
|
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
|
|
|
|
2016-11-03 18:55:40 +01:00
|
|
|
if(LOADERFLAG(ISN3DS))
|
|
|
|
{
|
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
|
|
|
};
|
|
|
|
|
2016-11-06 14:52:10 +01:00
|
|
|
u32 *off = (u32 *)memsearch(code, pattern, size, 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);
|
|
|
|
memcpy(off - 1, off, 16);
|
|
|
|
*(off + 3) = 0xE3800000 | cpuSetting;
|
2016-04-12 15:25:36 +02:00
|
|
|
}
|
2016-03-29 17:43:53 +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
|
2016-11-06 14:52:10 +01:00
|
|
|
if(!patchMemory(code, size,
|
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
|
2016-11-06 14:52:10 +01:00
|
|
|
if(patchMemory(code, size,
|
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)
|
2016-11-06 14:52:10 +01:00
|
|
|
if(!patchMemory(code, size,
|
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
|
|
|
) ||
|
|
|
|
!patchMemory(code, size,
|
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
|
|
|
) ||
|
|
|
|
!patchMemory(code, size,
|
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 == 0x0004003000008A02LL && CONFIG(ENABLEEXCEPTIONHANDLERS) && !CONFIG(PATCHUNITINFO)) //ErrDisp
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2017-03-05 14:03:15 +01:00
|
|
|
static const u8 pattern[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x00, 0xD0, 0xE5, 0xDB
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern2[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x14, 0x00, 0xD0, 0xE5, 0x01
|
|
|
|
},
|
2016-11-06 14:45:45 +01:00
|
|
|
patch[] = {
|
2016-11-03 18:55:40 +01:00
|
|
|
0x00, 0x00, 0xA0, 0xE3
|
|
|
|
};
|
|
|
|
|
2016-11-06 14:45:45 +01:00
|
|
|
//Patch UNITINFO checks to make ErrDisp more verbose
|
2016-11-06 14:52:10 +01:00
|
|
|
if(!patchMemory(code, size,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern,
|
|
|
|
sizeof(pattern), -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
|
|
|
) ||
|
|
|
|
patchMemory(code, size,
|
2016-11-06 14:45:45 +01:00
|
|
|
pattern2,
|
|
|
|
sizeof(pattern2), 0,
|
2016-11-06 14:52:10 +01:00
|
|
|
patch,
|
2016-11-06 14:45:45 +01:00
|
|
|
sizeof(patch), 3
|
2016-11-15 19:29:48 +01:00
|
|
|
) != 3) 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
|
|
|
|
|
|
|
//Patch DLP region checks
|
2016-12-22 17:51:17 +01:00
|
|
|
if(!patchMemory(code, size,
|
|
|
|
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
|
|
|
|
2016-11-26 15:01:07 +01:00
|
|
|
if(CONFIG(PATCHGAMES) && (u32)((progId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000)
|
2016-11-03 18:55:40 +01:00
|
|
|
{
|
2016-11-11 02:16:33 +01:00
|
|
|
u8 regionId = 0xFF,
|
2016-11-10 22:41:51 +01:00
|
|
|
languageId;
|
2016-11-16 03:41:59 +01:00
|
|
|
|
|
|
|
if(!loadTitleCodeSection(progId, code, size) ||
|
2017-04-13 03:38:36 +02:00
|
|
|
!applyCodeIpsPatch(progId, code, size) ||
|
2016-11-19 15:44:10 +01:00
|
|
|
!loadTitleLocaleConfig(progId, ®ionId, &languageId) ||
|
2017-04-13 03:38:36 +02:00
|
|
|
!patchLayeredFs(progId, code, size)) goto error;
|
2016-09-06 13:43:00 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(regionId != 0xFF)
|
2016-05-28 16:13:22 +02:00
|
|
|
{
|
2016-11-03 18:55:40 +01:00
|
|
|
u32 CFGUHandleOffset;
|
|
|
|
u8 *CFGU_GetConfigInfoBlk2_endPos = getCfgOffsets(code, size, &CFGUHandleOffset);
|
Added region/language emulation feature, thanks to the hard work of @TuxSH
Create a "locales" folder inside aurei, and one .txt for each game, with the title id of the game. The txt must be exactly 6 bytes long: 3 characters for the region ("JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"), a space, and two for the language ("JP", "EN", "FR", "DE", "IT", "ES", "ZH", "KO", "NL", "PT", "RU", "TW"). You can enable the feature globally in the config menu. This should also make DLCs for foreign games work.
2016-04-14 00:46:36 +02:00
|
|
|
|
2016-11-15 19:29:48 +01:00
|
|
|
if(CFGU_GetConfigInfoBlk2_endPos == NULL ||
|
2016-11-15 19:35:57 +01:00
|
|
|
!patchCfgGetLanguage(code, size, languageId, CFGU_GetConfigInfoBlk2_endPos)) goto error;
|
2016-11-15 19:29:48 +01:00
|
|
|
|
|
|
|
patchCfgGetRegion(code, size, regionId, CFGUHandleOffset);
|
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);
|
|
|
|
while(true);
|
2016-12-22 17:51:17 +01:00
|
|
|
}
|