diff --git a/source/fs/filetype.c b/source/fs/filetype.c index f448d44..b6eb094 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -37,8 +37,9 @@ u32 IdentifyFileType(const char* path) { return GAME_NCSD; // NCSD (".3DS") file } else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) { NcchHeader* ncch = (NcchHeader*) (void*) header; + u32 type = GAME_NCCH | (NCCH_IS_CXI(ncch) ? FLAG_CXI : 0) | (NCCH_ENCRYPTED(ncch) ? FLAG_ENCRYPTED : 0); if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) - return GAME_NCCH; // NCCH (".APP") file + return type; // NCCH (".APP") file } else if (ValidateExeFsHeader((ExeFsHeader*) (void*) header, fsize) == 0) { return GAME_EXEFS; // ExeFS file (false positives possible) } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { diff --git a/source/fs/filetype.h b/source/fs/filetype.h index 5b7bb6c..3b9093e 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -14,13 +14,17 @@ #define BIN_NCCHNFO (1<<9) #define BIN_LAUNCH (1<<10) -#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM) -#define FYTPE_VERIFICABLE (IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM) -#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM) -#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD) -#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD)) -#define FTYPE_RESTORABLE (IMG_NAND) -#define FTYPE_XORPAD (BIN_NCCHNFO) -#define FTYPE_PAYLOAD (BIN_LAUNCH) +#define FLAG_CXI (1<<30) +#define FLAG_ENCRYPTED (1<<31) + +#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM)) +#define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM)) +#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM)) +#define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) +#define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD))) +#define FTYPE_HSINJECTABLE(tp) ((tp&(GAME_NCCH|FLAG_CXI|FLAG_ENCRYPTED)) == (GAME_NCCH|FLAG_CXI)) +#define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) +#define FTYPE_XORPAD(tp) (tp&(BIN_NCCHNFO)) +#define FTYPE_PAYLOAD(tp) (tp&(BIN_LAUNCH)) u32 IdentifyFileType(const char* path); diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 6c4b064..1f410a6 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -3,6 +3,7 @@ #include "ui.h" #include "fsperm.h" #include "filetype.h" +#include "platform.h" #include "aes.h" #include "sha.h" #include "vff.h" @@ -610,7 +611,7 @@ u32 CheckEncryptedGameFile(const char* path) { else return 1; } -u32 DecryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode, +u32 CryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode, u16 crypto, // crypto only relevant for NCCH u32 offset, u32 size, TmdContentChunk* chunk, const u8* titlekey) { // this line only for CIA contents // this will do a simple copy for unencrypted files bool inplace = (strncmp(orig, dest, 256) == 0); @@ -646,7 +647,7 @@ u32 DecryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode, u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); UINT bytes_read, bytes_written; if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; - if (((mode & GAME_NCCH) && (DecryptNcchSequential(MAIN_BUFFER, i, read_bytes) != 0)) || + if (((mode & GAME_NCCH) && (CryptNcchSequential(MAIN_BUFFER, i, read_bytes, crypto) != 0)) || ((mode & GAME_NCSD) && (DecryptNcsdSequential(MAIN_BUFFER, i, read_bytes) != 0)) || ((mode & SYS_FIRM) && (DecryptFirmSequential(MAIN_BUFFER, i, read_bytes) != 0))) ret = 1; @@ -718,7 +719,7 @@ u32 DecryptCiaFile(const char* orig, const char* dest) { for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) { TmdContentChunk* chunk = &(cia->content_list[i]); u64 size = getbe64(chunk->size); - if (DecryptNcchNcsdFirmFile(orig, dest, GAME_CIA, next_offset, size, chunk, titlekey) != 0) + if (CryptNcchNcsdFirmFile(orig, dest, GAME_CIA, NCCH_NOCRYPTO, next_offset, size, chunk, titlekey) != 0) return 1; next_offset += size; } @@ -737,7 +738,7 @@ u32 DecryptFirmFile(const char* orig, const char* dest) { UINT btr; // actual decryption - if (DecryptNcchNcsdFirmFile(orig, dest, SYS_FIRM, 0, 0, NULL, NULL) != 0) + if (CryptNcchNcsdFirmFile(orig, dest, SYS_FIRM, NCCH_NOCRYPTO, 0, 0, NULL, NULL) != 0) return 1; // open destination file, get FIRM header @@ -814,7 +815,7 @@ u32 DecryptGameFile(const char* path, bool inplace) { else if (filetype & SYS_FIRM) ret = DecryptFirmFile(path, destptr); else if (filetype & (GAME_NCCH|GAME_NCSD)) - ret = DecryptNcchNcsdFirmFile(path, destptr, filetype, 0, 0, NULL, NULL); + ret = CryptNcchNcsdFirmFile(path, destptr, filetype, NCCH_NOCRYPTO, 0, 0, NULL, NULL); else ret = 1; if (!inplace && (ret != 0)) @@ -1176,3 +1177,92 @@ u32 BuildNcchInfoXorpads(const char* destdir, const char* path) { fvx_close(&fp_info); return ret; } + +u32 InjectHealthAndSafety(const char* path, const char* destdrv) { + const u32 tidlow_hs_o3ds[] = { 0x00020300, 0x00021300, 0x00022300, 0, 0x00026300, 0x00027300, 0x00028300 }; + const u32 tidlow_hs_n3ds[] = { 0x20020300, 0x20021300, 0x20022300, 0, 0, 0x00027300, 0 }; + NcchHeader ncch; + + // check input file + if ((LoadNcchHeaders(&ncch, NULL, NULL, path, 0) != 0) || + (NCCH_ENCRYPTED(&ncch)) || !(NCCH_IS_CXI(&ncch))) + return 1; + + // write permissions + if (!CheckWritePermissions(destdrv)) + return 1; + + // get H&S title id low + u32 tidlow_hs = 0; + for (char secchar = 'C'; secchar >= 'A'; secchar--) { + char path_secinfo[32]; + u8 secinfo[0x111]; + u32 region = 0xFF; + UINT br; + snprintf(path_secinfo, 32, "%s/rw/sys/SecureInfo_%c", destdrv, secchar); + if ((fvx_qread(path_secinfo, secinfo, 0, 0x111, &br) != FR_OK) || + (br != 0x111)) + continue; + region = secinfo[0x100]; + if (region >= sizeof(tidlow_hs_o3ds) / sizeof(u32)) continue; + tidlow_hs = (GetUnitPlatform() == PLATFORM_3DS) ? + tidlow_hs_o3ds[region] : tidlow_hs_n3ds[region]; + break; + } + if (!tidlow_hs) return 1; + + // build paths + char path_tmd[64]; + char path_cxi[64]; + char path_bak[64]; + snprintf(path_tmd, 64, "%s/title/00040010/%08lx/content/00000000.tmd", destdrv, tidlow_hs); + + TitleMetaData* tmd = (TitleMetaData*) TEMP_BUFFER; + TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1); + if (LoadTmdFile(tmd, path_tmd) != 0) return 1; + if (!getbe16(tmd->content_count)) return 1; + snprintf(path_cxi, 64, "%s/title/00040010/%08lx/content/%08lX.app", destdrv, tidlow_hs, getbe32(chunk->id)); + snprintf(path_bak, 64, "%s/title/00040010/%08lx/content/%08lX.bak", destdrv, tidlow_hs, getbe32(chunk->id)); + + // check crypto, get sig + u64 tid_hs = ((u64) 0x00040010 << 32) | tidlow_hs; + u16 crypto = NCCH_NOCRYPTO; + u8 sig[0x100]; + if ((LoadNcchHeaders(&ncch, NULL, NULL, path_cxi, 0) != 0) || (SetupNcchCrypto(&ncch) != 0) || + !(NCCH_IS_CXI(&ncch)) || (ncch.programId != tid_hs) || (ncch.partitionId != tid_hs)) + return 1; + crypto = NCCH_GET_CRYPTO(&ncch); + memcpy(sig, ncch.signature, 0x100); + + // make a backup copy if there is not already one (point of no return) + if (f_stat(path_bak, NULL) != FR_OK) { + if (f_rename(path_cxi, path_bak) != FR_OK) return 1; + } else f_unlink(path_cxi); + + // copy the source CXI + u32 ret = 0; + if (CryptNcchNcsdFirmFile(path, path_cxi, GAME_NCCH, NCCH_NOCRYPTO, 0, 0, NULL, NULL) != 0) + ret = 1; + + // fix up the injected H&S NCCH header (copy H&S signature, title ID) + if ((ret == 0) && (LoadNcchHeaders(&ncch, NULL, NULL, path_cxi, 0) == 0)) { + UINT bw; + ncch.programId = tid_hs; + ncch.partitionId = tid_hs; + memcpy(ncch.signature, sig, 0x100); + if ((fvx_qwrite(path_cxi, &ncch, 0, sizeof(NcchHeader), &bw) != FR_OK) || + (bw != sizeof(NcchHeader))) + ret = 1; + } else ret = 1; + + // encrypt the CXI in place + if (CryptNcchNcsdFirmFile(path_cxi, path_cxi, GAME_NCCH, crypto, 0, 0, NULL, NULL) != 0) + ret = 1; + + if (ret != 0) { // try recover + f_unlink(path_cxi); + f_rename(path_bak, path_cxi); + } + + return ret; +} diff --git a/source/game/gameutil.h b/source/game/gameutil.h index fcd5f2f..f3a8b80 100644 --- a/source/game/gameutil.h +++ b/source/game/gameutil.h @@ -7,3 +7,4 @@ u32 CheckEncryptedGameFile(const char* path); u32 DecryptGameFile(const char* path, bool inplace); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); +u32 InjectHealthAndSafety(const char* path, const char* destdrv); diff --git a/source/godmode.c b/source/godmode.c index 343a56a..d1897f9 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -582,17 +582,18 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool in_output_path = (strncmp(current_path, OUTPUT_PATH, 256) == 0); // special stuff, only available for known filetypes (see int special below) - bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & DRV_IMAGE)); - bool verificable = (filetype & FYTPE_VERIFICABLE); - bool decryptable = (filetype & FYTPE_DECRYPTABLE); + bool mountable = (FTYPE_MOUNTABLE(filetype) && !(drvtype & DRV_IMAGE)); + bool verificable = (FYTPE_VERIFICABLE(filetype)); + bool decryptable = (FYTPE_DECRYPTABLE(filetype)); bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE))); - bool buildable = (filetype & FTYPE_BUILDABLE); - bool buildable_legit = (filetype & FTYPE_BUILDABLE_L); - bool restorable = (CheckA9lh() && (filetype & FTYPE_RESTORABLE) && !(drvtype & DRV_SYSNAND)); - bool xorpadable = (filetype & FTYPE_XORPAD); - bool launchable = ((filetype & FTYPE_PAYLOAD) && (drvtype & DRV_FAT)); + bool buildable = (FTYPE_BUILDABLE(filetype)); + bool buildable_legit = (FTYPE_BUILDABLE_L(filetype)); + bool hsinjectable = (FTYPE_HSINJECTABLE(filetype)); + bool restorable = (FTYPE_RESTORABLE(filetype) && CheckA9lh() && !(drvtype & DRV_SYSNAND)); + bool xorpadable = (FTYPE_XORPAD(filetype)); + bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT)); bool special_opt = mountable || verificable || decryptable || decryptable_inplace || - buildable || buildable_legit || restorable || xorpadable || launchable; + buildable || buildable_legit || hsinjectable || restorable || xorpadable || launchable; char pathstr[32 + 1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -699,6 +700,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int build = (buildable) ? ++n_opt : -1; int build_legit = (buildable_legit) ? ++n_opt : -1; int verify = (verificable) ? ++n_opt : -1; + int hsinject = (hsinjectable) ? ++n_opt : -1; int xorpad = (xorpadable) ? ++n_opt : -1; int xorpad_inplace = (xorpadable) ? ++n_opt : -1; int launch = (launchable) ? ++n_opt : -1; @@ -709,6 +711,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)"; if (verify > 0) optionstr[verify-1] = "Verify file"; + if (hsinject > 0) optionstr[hsinject-1] = "Inject to H&S"; if (xorpad > 0) optionstr[xorpad-1] = "Build XORpads (SD output)"; if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)"; if (launch > 0) optionstr[launch-1] = "Launch as ARM9 payload"; @@ -845,6 +848,22 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); } return 0; + } else if (user_select == hsinject) { // -> Inject to Health & Safety + char* destdrv[2] = { NULL }; + n_opt = 0; + if (DriveType("1:")) { + optionstr[n_opt] = "SysNAND H&S inject"; + destdrv[n_opt++] = "1:"; + } + if (DriveType("4:")) { + optionstr[n_opt] = "EmuNAND H&S inject"; + destdrv[n_opt++] = "4:"; + } + user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, pathstr) : n_opt; + if (user_select) { + ShowPrompt(false, "%s\nH&S inject %s", pathstr, + (InjectHealthAndSafety(curr_entry->path, destdrv[user_select-1]) == 0) ? "success" : "failed"); + } } else if (user_select == restore) { // -> restore SysNAND (A9LH preserving) ShowPrompt(false, "%s\nNAND restore %s", pathstr, (SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed");