diff --git a/source/filesys/filetype.c b/source/filesys/filetype.c index 50513da..aa9c2d0 100644 --- a/source/filesys/filetype.c +++ b/source/filesys/filetype.c @@ -23,9 +23,11 @@ u32 IdentifyFileType(const char* path) { if (!fsize) return 0; if (fsize >= 0x200) { - if ((getbe32(header + 0x100) == 0x4E435344) && (getbe64(header + 0x110) == (u64) 0x0104030301000000) && - (getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) { - return IMG_NAND; // NAND image + if (ValidateNandNcsdHeader((NandNcsdHeader*) data) == 0) { + return (fsize >= GetNandNcsdMinSizeSectors((NandNcsdHeader*) data) * 0x200) ? + IMG_NAND : (fsize == sizeof(NandNcsdHeader)) ? HDR_NAND : 0; // NAND image or just header + } else if ((strncasecmp(path, "S:/nand.bin", 16) == 0) || (strncasecmp(path, "E:/nand.bin", 16) == 0)) { + return NOIMG_NAND; // on NAND, but no proper NAND image } else if (ValidateFatHeader(header) == 0) { return IMG_FAT; // FAT image file } else if (ValidateMbrHeader((MbrHeader*) data) == 0) { diff --git a/source/filesys/filetype.h b/source/filesys/filetype.h index 31bfd26..22d09f8 100644 --- a/source/filesys/filetype.h +++ b/source/filesys/filetype.h @@ -23,6 +23,8 @@ #define BIN_LEGKEY (1UL<<18) #define TXT_SCRIPT (1UL<<19) #define TXT_GENERIC (1UL<<20) +#define NOIMG_NAND (1UL<<21) +#define HDR_NAND (1UL<<22) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_ENC (1UL<<28) @@ -42,6 +44,7 @@ #define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS)) #define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) +#define FTYPE_NCSDFIXABLE(tp) (tp&(HDR_NAND|NOIMG_NAND)) #define FTYPE_HASCODE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) #define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) #define FTYPE_EBACKUP(tp) (tp&(IMG_NAND)) diff --git a/source/godmode.c b/source/godmode.c index 6768024..7d11b38 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -713,12 +713,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool extrcodeable = (FTYPE_HASCODE(filetype)); bool restorable = (FTYPE_RESTORABLE(filetype) && IS_A9LH && !(drvtype & DRV_SYSNAND)); bool ebackupable = (FTYPE_EBACKUP(filetype)); + bool ncsdfixable = (FTYPE_NCSDFIXABLE(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); bool keyinitable = (FTYPE_KEYINIT(filetype)); bool scriptable = (FTYPE_SCRIPT(filetype)); bool bootable = (FTYPE_BOOTABLE(filetype) && !(drvtype & DRV_VIRTUAL)); bool installable = (FTYPE_INSTALLABLE(filetype) && !(drvtype & DRV_VIRTUAL)); - bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || extrcodeable || keyinitable || bootable || scriptable || installable; + bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -763,7 +764,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & BIN_KEYDB) ? "AESkeydb options..." : (filetype & BIN_LEGKEY) ? "Build " KEYDB_NAME : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : - (filetype & TXT_SCRIPT) ? "Execute GM9 script" : "???"; + (filetype & TXT_SCRIPT) ? "Execute GM9 script" : + (filetype & HDR_NAND) ? "Rebuild NCSD header" : + (filetype & NOIMG_NAND) ? "Rebuild NCSD header" : "???"; optionstr[hexviewer-1] = "Show in Hexeditor"; optionstr[calcsha-1] = "Calculate SHA-256"; if (textviewer > 0) optionstr[textviewer-1] = "Show in Textviewer"; @@ -776,6 +779,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur "%s\n%(%lu files selected)" : "%s", pathstr, n_marked); if (user_select == hexviewer) { // -> show in hex viewer FileHexViewer(curr_entry->path); + GetDirContents(current_dir, current_path); return 0; } else if (user_select == textviewer) { // -> show in text viewer FileTextViewer(curr_entry->path, scriptable); @@ -859,6 +863,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int mount = (mountable) ? ++n_opt : -1; int restore = (restorable) ? ++n_opt : -1; int ebackup = (ebackupable) ? ++n_opt : -1; + int ncsdfix = (ncsdfixable) ? ++n_opt : -1; int decrypt = (decryptable) ? ++n_opt : -1; int encrypt = (encryptable) ? ++n_opt : -1; int cia_build = (cia_buildable) ? ++n_opt : -1; @@ -881,6 +886,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (mount > 0) optionstr[mount-1] = "Mount image to drive"; if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup"; + if (ncsdfix > 0) optionstr[ncsdfix-1] = "Rebuild NCSD header"; if (show_info > 0) optionstr[show_info-1] = "Show title info"; if (decrypt > 0) optionstr[decrypt-1] = (cryptable_inplace) ? "Decrypt file (...)" : "Decrypt file (" OUTPUT_PATH ")"; if (encrypt > 0) optionstr[encrypt-1] = (cryptable_inplace) ? "Encrypt file (...)" : "Encrypt file (" OUTPUT_PATH ")"; @@ -1216,7 +1222,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur ShowPrompt(false, "%s\nNAND restore %s", pathstr, (SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed"); return 0; - } else if ((user_select == xorpad) || (user_select == xorpad_inplace)) { + } else if (user_select == ncsdfix) { // -> inject sighaxed NCSD + ShowPrompt(false, "%s\nRebuild NCSD %s", pathstr, + (FixNandHeader(curr_entry->path, !(filetype == HDR_NAND)) == 0) ? "success" : "failed"); + GetDirContents(current_dir, current_path); + InitExtFS(); // this might have fixed something, so try this + return 0; + } else if ((user_select == xorpad) || (user_select == xorpad_inplace)) { // -> build xorpads bool inplace = (user_select == xorpad_inplace); bool success = (BuildNcchInfoXorpads((inplace) ? current_path : OUTPUT_PATH, curr_entry->path) == 0); ShowPrompt(false, "%s\nNCCHinfo padgen %s%s", pathstr, @@ -1232,7 +1244,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur *cursor = 1; } return 0; - } else if (user_select == ebackup) { + } else if (user_select == ebackup) { // -> update embedded backup ShowString("%s\nUpdating embedded backup...", pathstr); bool required = (CheckEmbeddedBackup(curr_entry->path) != 0); bool success = (required && (EmbedEssentialBackup(curr_entry->path) == 0)); @@ -1240,11 +1252,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (success) ? "completed" : "failed!"); GetDirContents(current_dir, current_path); return 0; - } else if ((user_select == keyinit)) { + } else if ((user_select == keyinit)) { // -> initialise keys from aeskeydb.bin if (ShowPrompt(true, "Warning: Keys are not verified.\nContinue on your own risk?")) ShowPrompt(false, "%s\nAESkeydb init %s", pathstr, (InitKeyDb(curr_entry->path) == 0) ? "success" : "failed"); return 0; - } else if ((user_select == install)) { + } else if ((user_select == install)) { // -> install FIRM size_t firm_size = FileGetSize(curr_entry->path); u32 slots = 1; if (GetNandPartitionInfo(NULL, NP_TYPE_FIRM, NP_SUBTYPE_CTR, 1, NAND_SYSNAND) == 0) { @@ -1257,7 +1269,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (slots) ShowPrompt(false, "%s (%dkB)\nInstall %s", pathstr, firm_size / 1024, (SafeInstallFirm(curr_entry->path, slots) == 0) ? "success" : "failed!"); return 0; - } else if ((user_select == boot)) { + } else if ((user_select == boot)) { // -> boot FIRM size_t firm_size = FileGetSize(curr_entry->path); if (firm_size > TEMP_BUFFER_SIZE) { ShowPrompt(false, "FIRM too big, can't launch"); // unlikely @@ -1287,7 +1299,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } else ShowPrompt(false, "Not a bootable FIRM, can't boot"); } return 0; - } else if ((user_select == script)) { + } else if ((user_select == script)) { // execute script if (ShowPrompt(true, "%s\nWarning: Do not run scripts\nfrom untrusted sources.\n \nExecute script?", pathstr)) ShowPrompt(false, "%s\nScript execute %s", pathstr, ExecuteGM9Script(curr_entry->path) ? "success" : "failure"); GetDirContents(current_dir, current_path); diff --git a/source/utils/nandutil.c b/source/utils/nandutil.c index df55e46..1fa35d3 100644 --- a/source/utils/nandutil.c +++ b/source/utils/nandutil.c @@ -13,6 +13,15 @@ #include "vff.h" +static const u8 twl_mbr_std[0x42] = { + 0x00, 0x04, 0x18, 0x00, 0x06, 0x01, 0xA0, 0x3F, 0x97, 0x00, 0x00, 0x00, 0xA9, 0x7D, 0x04, 0x00, + 0x00, 0x04, 0x8E, 0x40, 0x06, 0x01, 0xA0, 0xC3, 0x8D, 0x80, 0x04, 0x00, 0xB3, 0x05, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x55, 0xAA +}; + + u32 ReadNandFile(FIL* file, void* buffer, u32 sector, u32 count, u32 keyslot) { u32 offset = sector * 0x200; u32 size = count * 0x200; @@ -108,6 +117,76 @@ u32 EmbedEssentialBackup(const char* path) { return 0; } +u32 RebuildNandNcsdHeader(NandNcsdHeader* ncsd) { + // signature (retail or dev) + u8* signature = (IS_DEVKIT) ? sig_nand_ncsd_dev : sig_nand_ncsd_retail; + + // encrypted TWL MBR + u8 twl_mbr_data[0x200] = { 0 }; + u8* twl_mbr = twl_mbr_data + (0x200 - sizeof(twl_mbr_std)); + memcpy(twl_mbr, twl_mbr_std, sizeof(twl_mbr_std)); + CryptNand(twl_mbr_data, 0, 1, 0x03); + + // rebuild NAND header for console + memset(ncsd, 0x00, sizeof(NandNcsdHeader)); + memcpy(ncsd->signature, signature, 0x100); // signature + memcpy(ncsd->twl_mbr, twl_mbr, 0x42); // TWL MBR + memcpy(ncsd->magic, "NCSD", 0x4); // magic number + ncsd->size = (IS_O3DS) ? 0x200000 : 0x280000; // total size + + // TWL partition (0) + ncsd->partitions_fs_type[0] = 0x01; + ncsd->partitions_crypto_type[0] = 0x01; + ncsd->partitions[0].offset = 0x000000; + ncsd->partitions[0].size = 0x058800; + + // AGBSAVE partition (1) + ncsd->partitions_fs_type[1] = 0x04; + ncsd->partitions_crypto_type[1] = 0x02; + ncsd->partitions[1].offset = 0x058800; + ncsd->partitions[1].size = 0x000180; + + // FIRM0 partition (2) + ncsd->partitions_fs_type[2] = 0x03; + ncsd->partitions_crypto_type[2] = 0x02; + ncsd->partitions[2].offset = 0x058980; + ncsd->partitions[2].size = 0x002000; + + // FIRM1 partition (3) + ncsd->partitions_fs_type[3] = 0x03; + ncsd->partitions_crypto_type[3] = 0x02; + ncsd->partitions[3].offset = 0x05A980; + ncsd->partitions[3].size = 0x002000; + + // CTR partition (4) + ncsd->partitions_fs_type[4] = 0x01; + ncsd->partitions_crypto_type[4] = (IS_O3DS) ? 0x02 : 0x03; + ncsd->partitions[4].offset = 0x05C980; + ncsd->partitions[4].size = (IS_O3DS) ? 0x17AE80 : 0x20F680; + + // unknown stuff - whatever this is ¯\_(ツ)_/¯ + ncsd->unknown[0x25] = 0x04; + ncsd->unknown[0x2C] = 0x01; + + // done + return 0; +} + +u32 FixNandHeader(const char* path, bool check_size) { + NandNcsdHeader ncsd; + if (RebuildNandNcsdHeader(&ncsd) != 0) return 1; + + // safety check + FILINFO fno; + FSIZE_t min_size = check_size ? GetNandNcsdMinSizeSectors(&ncsd) * 0x200 : 0x200; + if ((fvx_stat(path, &fno) != FR_OK) || (min_size > fno.fsize)) + return 1; + + // inject to path + if (!CheckWritePermissions(path)) return 1; + return (fvx_qwrite(path, &ncsd, 0x0, 0x200, NULL) == FR_OK) ? 0 : 1; +} + u32 ValidateNandDump(const char* path) { NandPartitionInfo info; FIL file; @@ -235,6 +314,7 @@ u32 SafeRestoreNandDump(const char* path) { } // compare NCSD header partitioning + // FIRMS must be at the same place for image and local NAND bool header_inject = false; if (memcmp(&ncsd_loc, &ncsd_img, sizeof(NandNcsdHeader)) != 0) { for (int p = -1; p <= 8; p++) { @@ -246,7 +326,7 @@ u32 SafeRestoreNandDump(const char* path) { bool np_loc = (GetNandNcsdPartitionInfo(&info_loc, type, subtype, idx, &ncsd_loc) == 0); bool np_img = (GetNandNcsdPartitionInfo(&info_img, type, subtype, idx, &ncsd_img) == 0); if (!np_loc && !np_img) { - if (p >= 1) header_inject = true; + if (p >= 1) header_inject = true; // at least one matching FIRM found break; } if ((np_loc != np_img) || (memcmp(&info_loc, &info_img, sizeof(NandPartitionInfo)) != 0)) diff --git a/source/utils/nandutil.h b/source/utils/nandutil.h index 9ba537f..7fe8687 100644 --- a/source/utils/nandutil.h +++ b/source/utils/nandutil.h @@ -4,6 +4,7 @@ u32 CheckEmbeddedBackup(const char* path); u32 EmbedEssentialBackup(const char* path); +u32 FixNandHeader(const char* path, bool check_size); u32 ValidateNandDump(const char* path); u32 SafeRestoreNandDump(const char* path); u32 SafeInstallFirm(const char* path, u32 slots);