u32 DumpHealthAndSafety(u32 param) { (void) (param); // param is unused here PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); TitleListInfo* health = titleList + ((GetUnitPlatform() == PLATFORM_3DS) ? 3 : 4); TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? titleList + 3 : NULL; char filename[64]; u32 offset_app[4]; u32 size_app[4]; u32 offset_tmd; u32 size_tmd; if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) return 1; if (OutputFileNameSelector(filename, "hs.app", NULL) != 0) return 1; Debug("Dumping & decrypting APP0..."); if (DecryptNandToFile(filename, offset_app[0], size_app[0], ctrnand_info) != 0) return 1; if (CryptNcch(filename, 0, 0, 0, NULL) != 0) return 1; return 0; }
u32 InitializeD9() { u32 errorlevel = 0; // 0 -> none, 1 -> autopause, 2 -> critical ClearScreenFull(true, true); DebugClear(); #ifndef BUILD_NAME Debug("-- Decrypt9 --"); #else Debug("-- %s --", BUILD_NAME); #endif // a little bit of information about the current menu if (sizeof(menu)) { u32 n_submenus = 0; u32 n_features = 0; for (u32 m = 0; menu[m].n_entries; m++) { n_submenus = m; for (u32 e = 0; e < menu[m].n_entries; e++) n_features += (menu[m].entries[e].function) ? 1 : 0; } Debug("Counting %u submenus and %u features", n_submenus, n_features); } Debug("Initializing, hold L+R to pause"); Debug(""); if (InitFS()) { Debug("Initializing SD card... success"); FileGetData("d9logo.bin", BOT_SCREEN0, 320 * 240 * 3, 0); memcpy(BOT_SCREEN1, BOT_SCREEN0, 320 * 240 * 3); if (SetupTwlKey0x03() != 0) // TWL KeyX / KeyY errorlevel = 2; if ((GetUnitPlatform() == PLATFORM_N3DS) && (LoadKeyFromFile(0x05, 'Y', NULL) != 0)) errorlevel = (((*(vu32*) 0x101401C0) == 0) || (errorlevel > 1)) ? 2 : 1; // N3DS CTRNAND KeyY if (LoadKeyFromFile(0x25, 'X', NULL)) // NCCH 7x KeyX errorlevel = (errorlevel < 1) ? 1 : errorlevel; if (LoadKeyFromFile(0x18, 'X', NULL)) // NCCH Secure3 KeyX errorlevel = (errorlevel < 1) ? 1 : errorlevel; if (LoadKeyFromFile(0x1B, 'X', NULL)) // NCCH Secure4 KeyX errorlevel = (errorlevel < 1) ? 1 : errorlevel; Debug("Finalizing Initialization..."); RemainingStorageSpace(); } else { Debug("Initializing SD card... failed"); errorlevel = 2; } Debug(""); Debug("Initialization: %s", (errorlevel == 0) ? "success!" : (errorlevel == 1) ? "partially failed" : "failed!"); if (((~HID_STATE & BUTTON_L1) && (~HID_STATE & BUTTON_R1)) || (errorlevel > 1)) { Debug("(A to %s)", (errorlevel > 1) ? "exit" : "continue"); while (!(InputWait() & BUTTON_A)); } return errorlevel; }
u32 MountRamDrive(void) { if (mount_state && (mount_state != IMG_RAMDRV)) f_close(&mount_file); if (GetUnitPlatform() == PLATFORM_3DS) { ramdrv_buffer = RAMDRV_BUFFER_O3DS; ramdrv_size = RAMDRV_SIZE_O3DS; } else { ramdrv_buffer = RAMDRV_BUFFER_N3DS; ramdrv_size = RAMDRV_SIZE_N3DS; } return (mount_state = IMG_RAMDRV); }
PartitionInfo* GetPartitionInfo(u32 partition_id) { u32 partition_num = 0; if (partition_id == P_CTRNAND) { partition_num = (GetUnitPlatform() == PLATFORM_3DS) ? 5 : 6; } else { for(; !(partition_id & (1<<partition_num)) && (partition_num < 32); partition_num++); } return (partition_num >= 32) ? NULL : &(partitions[partition_num]); }
u32 NandPadgen() { u8* ctrStart = FindNandCtr(); if (ctrStart == NULL) return 1; u8 ctr[16] = {0x0}; u32 i = 0; for(i = 0; i < 16; i++) ctr[i] = *(ctrStart + (15 - i)); //The CTR is stored backwards in memory. add_ctr(ctr, 0xB93000); //The CTR stored in memory would theoretically be for NAND block 0, so we need to increment it some. u32 keyslot = 0x0; u32 nand_size = 0; switch (GetUnitPlatform()) { case PLATFORM_3DS: keyslot = 0x4; nand_size = 758; break; case PLATFORM_N3DS: keyslot = 0x5; nand_size = 1055; break; } Debug("Creating NAND FAT16 xorpad. Size (MB): %u", nand_size); Debug("Filename: nand.fat16.xorpad"); PadInfo padInfo = {.keyslot = keyslot, .setKeyY = 0, .size_mb = nand_size , .filename = "/nand.fat16.xorpad"}; memcpy(padInfo.CTR, ctr, 16); u32 result = CreatePad(&padInfo); if(result == 0) { Debug("Done!"); return 0; } else { return 1; } } u32 CreatePad(PadInfo *info) { static const uint8_t zero_buf[16] __attribute__((aligned(16))) = {0}; u8* buffer = BUFFER_ADDRESS; u32 result = 0; if (!FileCreate(info->filename, true)) // No DebugFileCreate() here - messages are already given return 1; if(info->setKeyY) setup_aeskey(info->keyslot, AES_BIG_INPUT | AES_NORMAL_INPUT, info->keyY); use_aeskey(info->keyslot); u8 ctr[16] __attribute__((aligned(32))); memcpy(ctr, info->CTR, 16); u32 size_bytes = info->size_mb * 1024*1024; for (u32 i = 0; i < size_bytes; i += BUFFER_MAX_SIZE) { u32 curr_block_size = min(BUFFER_MAX_SIZE, size_bytes - i); for (u32 j = 0; j < curr_block_size; j+= 16) { set_ctr(AES_BIG_INPUT | AES_NORMAL_INPUT, ctr); aes_decrypt((void*)zero_buf, (void*)buffer + j, ctr, 1, AES_CTR_MODE); add_ctr(ctr, 1); } ShowProgress(i, size_bytes); if (!DebugFileWrite((void*)buffer, curr_block_size, i)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; } u32 NandDumper() { u8* buffer = BUFFER_ADDRESS; u32 nand_size = (GetUnitPlatform() == PLATFORM_3DS) ? 0x3AF00000 : 0x4D800000; u32 result = 0; Debug("Dumping System NAND. Size (MB): %u", nand_size / (1024 * 1024)); if (!DebugFileCreate("/NAND.bin", true)) return 1; u32 n_sectors = nand_size / NAND_SECTOR_SIZE; for (u32 i = 0; i < n_sectors; i += SECTORS_PER_READ) { ShowProgress(i, n_sectors); sdmmc_nand_readsectors(i, SECTORS_PER_READ, buffer); if(!DebugFileWrite(buffer, NAND_SECTOR_SIZE * SECTORS_PER_READ, i * NAND_SECTOR_SIZE)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; } u32 NandPartitionsDumper() { u32 ctrnand_offset; u32 ctrnand_size; u32 keyslot; switch (GetUnitPlatform()) { case PLATFORM_3DS: ctrnand_offset = 0x0B95CA00; ctrnand_size = 0x2F3E3600; keyslot = 0x4; break; case PLATFORM_N3DS: ctrnand_offset = 0x0B95AE00; ctrnand_size = 0x41D2D200; keyslot = 0x5; break; } // see: http://3dbrew.org/wiki/Flash_Filesystem Debug("Dumping firm0.bin: %s!", DumpPartition("firm0.bin", 0x0B130000, 0x00400000, 0x6) == 0 ? "succeeded" : "failed"); Debug("Dumping firm1.bin: %s!", DumpPartition("firm1.bin", 0x0B530000, 0x00400000, 0x6) == 0 ? "succeeded" : "failed"); Debug("Dumping ctrnand.bin: %s!", DumpPartition("ctrnand.bin", ctrnand_offset, ctrnand_size, keyslot) == 0 ? "succeeded" : "failed"); return 0; }
u32 NcchPadgen(u32 param) { (void) (param); // param is unused here NcchInfo *info = (NcchInfo*)0x20316000; SeedInfo *seedinfo = (SeedInfo*)0x20400000; if (CheckKeySlot(0x25, 'X') != 0) { Debug("slot0x25KeyX not set up"); Debug("7.x crypto will fail on O3DS < 7.x or A9LH"); } if ((GetUnitPlatform() == PLATFORM_3DS) && (CheckKeySlot(0x18, 'X') != 0)) { Debug("slot0x18KeyX not set up"); Debug("Secure3 crypto will fail"); } if (CheckKeySlot(0x1B, 'X') != 0) { Debug("slot0x1BKeyX not set up"); Debug("Secure4 crypto will fail"); } if (DebugFileOpen("seeddb.bin")) { if (!DebugFileRead(seedinfo, 16, 0)) { FileClose(); return 1; } if (!seedinfo->n_entries || seedinfo->n_entries > MAX_ENTRIES) { FileClose(); Debug("Bad number of seeddb entries"); return 1; } if (!DebugFileRead(seedinfo->entries, seedinfo->n_entries * sizeof(SeedInfoEntry), 16)) { FileClose(); return 1; } FileClose(); } else { Debug("9.x seed crypto will fail"); } if (!DebugFileOpen("ncchinfo.bin")) return 1; if (!DebugFileRead(info, 16, 0)) { FileClose(); return 1; } if (!info->n_entries || info->n_entries > MAX_ENTRIES) { FileClose(); Debug("Bad number of entries in ncchinfo.bin"); return 1; } if (info->ncch_info_version == 0xF0000004) { // ncchinfo v4 if (!DebugFileRead(info->entries, info->n_entries * sizeof(NcchInfoEntry), 16)) { FileClose(); return 1; } } else if (info->ncch_info_version == 0xF0000003) { // ncchinfo v3 // read ncchinfo v3 entry & convert to ncchinfo v4 for (u32 i = 0; i < info->n_entries; i++) { u8* entry_data = (u8*) (info->entries + i); if (!DebugFileRead(entry_data, 160, 16 + (160*i))) { FileClose(); return 1; } memmove(entry_data + 56, entry_data + 48, 112); *(u64*) (entry_data + 48) = 0; } } else { // unknown file / ncchinfo version FileClose(); Debug("Incompatible version ncchinfo.bin"); return 1; } FileClose(); Debug("Number of entries: %i", info->n_entries); for (u32 i = 0; i < info->n_entries; i++) { // check and fix filenames char* filename = info->entries[i].filename; if (filename[1] == 0x00) { // convert UTF-16 -> UTF-8 for (u32 j = 1; j < (112 / 2); j++) filename[j] = filename[j*2]; } if (memcmp(filename, "sdmc:", 5) == 0) // fix sdmc: prefix memmove(filename, filename + 5, 112 - 5); } for (u32 i = 0; i < info->n_entries; i++) { PadInfo padInfo = {.setKeyY = 1, .size_mb = info->entries[i].size_mb, .mode = AES_CNT_CTRNAND_MODE}; memcpy(padInfo.ctr, info->entries[i].ctr, 16); memcpy(padInfo.filename, info->entries[i].filename, 112); Debug ("%2i: %s (%iMB)", i, info->entries[i].filename, info->entries[i].size_mb); // workaround to still be able to process old ncchinfo.bin if ((info->entries[i].ncchFlag7 == 0x01) && info->entries[i].ncchFlag3) info->entries[i].ncchFlag7 = 0x20; // this combination means seed crypto rather than FixedKey if (info->entries[i].ncchFlag7 & 0x20) { // seed crypto u8 keydata[32]; memcpy(keydata, info->entries[i].keyY, 16); u32 found_seed = 0; for (u32 j = 0; j < seedinfo->n_entries; j++) { if (seedinfo->entries[j].titleId == info->entries[i].titleId) { found_seed = 1; memcpy(&keydata[16], seedinfo->entries[j].external_seed, 16); break; } } if (!found_seed) { Debug("Failed to find seed in seeddb.bin"); return 1; } u8 sha256sum[32]; sha_quick(sha256sum, keydata, 32, SHA256_MODE); memcpy(padInfo.keyY, sha256sum, 16); } else { memcpy(padInfo.keyY, info->entries[i].keyY, 16); } if (info->entries[i].ncchFlag7 == 0x01) { u8 zeroKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; u8 sysKey[16] = {0x52, 0x7C, 0xE6, 0x30, 0xA9, 0xCA, 0x30, 0x5F, 0x36, 0x96, 0xF3, 0xCD, 0xE9, 0x54, 0x19, 0x4B}; setup_aeskey(0x11, (info->entries[i].titleId & ((u64) 0x10 << 32)) ? sysKey : zeroKey); padInfo.setKeyY = 0; padInfo.keyslot = 0x11; // fixedKey crypto } else if (info->entries[i].ncchFlag3 == 0x0A) { padInfo.keyslot = 0x18; // Secure3 crypto, needs slot0x18KeyX.bin on O3DS } else if (info->entries[i].ncchFlag3 == 0x0B) { padInfo.keyslot = 0x1B; // Secure4 crypto, needs slot0x1BKeyX.bin } else if(info->entries[i].ncchFlag3 >> 8 == 0xDEC0DE) { // magic value to manually specify keyslot padInfo.keyslot = info->entries[i].ncchFlag3 & 0x3F; } else if (info->entries[i].ncchFlag3) { padInfo.keyslot = 0x25; // 7.x crypto } else { padInfo.keyslot = 0x2C; // standard crypto } Debug("Using keyslot: %02X", padInfo.keyslot); if (CreatePad(&padInfo) != 0) return 1; // this can't fail anyways } return 0; }
u32 SdPadgen(u32 param) { (void) (param); // param is unused here SdInfo *info = (SdInfo*) 0x20316000; // setup AES key from SD SetupSdKeyY0x34(false, NULL); if (!DebugFileOpen("SDinfo.bin")) return 1; if (!DebugFileRead(info, 4, 0)) { FileClose(); return 1; } if (!info->n_entries || info->n_entries > MAX_ENTRIES) { FileClose(); Debug("Bad number of entries!"); return 1; } if (!DebugFileRead(info->entries, info->n_entries * sizeof(SdInfoEntry), 4)) { FileClose(); return 1; } FileClose(); Debug("Number of entries: %i", info->n_entries); for(u32 i = 0; i < info->n_entries; i++) { PadInfo padInfo = {.keyslot = 0x34, .setKeyY = 0, .size_mb = info->entries[i].size_mb, .mode = AES_CNT_CTRNAND_MODE}; memcpy(padInfo.ctr, info->entries[i].ctr, 16); memcpy(padInfo.filename, info->entries[i].filename, 180); Debug ("%2i: %s (%iMB)", i, info->entries[i].filename, info->entries[i].size_mb); if (CreatePad(&padInfo) != 0) return 1; // this can't fail anyways } return 0; } u32 SdPadgenDirect(u32 param) { (void) (param); // param is unused here SdInfo *info = (SdInfo*) 0x20316000; char basepath[256]; u8 movable_keyY[16]; if (SetupSdKeyY0x34(true, movable_keyY) != 0) return 1; // movable.sed has to be present in NAND Debug(""); if (SdFolderSelector(basepath, movable_keyY) != 0) return 1; Debug(""); if (SdInfoGen(info, basepath) != 0) return 1; if (!info->n_entries) { Debug("Nothing found in folder"); return 1; } Debug("Number of entries: %i", info->n_entries); for(u32 i = 0; i < info->n_entries; i++) { PadInfo padInfo = {.keyslot = 0x34, .setKeyY = 0, .size_mb = info->entries[i].size_mb, .mode = AES_CNT_CTRNAND_MODE}; memcpy(padInfo.ctr, info->entries[i].ctr, 16); memcpy(padInfo.filename, info->entries[i].filename, 180); Debug ("%2i: %s (%iMB)", i, info->entries[i].filename, info->entries[i].size_mb); if (CreatePad(&padInfo) != 0) return 1; // this can't fail anyways } return 0; } u32 AnyPadgen(u32 param) { (void) (param); // param is unused here AnyPadInfo *info = (AnyPadInfo*) 0x20316000; // get header if ((FileGetData("anypad.bin", info, 16, 0) != 16) || !info->n_entries || info->n_entries > MAX_ENTRIES) { Debug("Corrupt or not existing: anypad.bin"); return 1; } // get data u32 data_size = info->n_entries * sizeof(AnyPadInfoEntry); if (FileGetData("anypad.bin", (u8*) info + 16, data_size, 16) != data_size) { Debug("File is missing data: anypad.bin"); return 1; } Debug("Processing anypad.bin..."); Debug("Number of entries: %i", info->n_entries); for (u32 i = 0; i < info->n_entries; i++) { // this translates all entries to a standard padInfo struct AnyPadInfoEntry* entry = &(info->entries[i]); PadInfo padInfo = {.keyslot = entry->keyslot, .setKeyY = 0, .size_mb = 0, .size_b = entry->size_b, .mode = entry->mode}; memcpy(padInfo.filename, entry->filename, 80); memcpy(padInfo.ctr, entry->ctr, 16); // process keys if (entry->setNormalKey) setup_aeskey(entry->keyslot, entry->normalKey); if (entry->setKeyX) setup_aeskeyX(entry->keyslot, entry->keyX); if (entry->setKeyY) setup_aeskeyY(entry->keyslot, entry->keyY); use_aeskey(entry->keyslot); // process flags if (entry->flags & (AP_USE_NAND_CTR|AP_USE_SD_CTR)) { u32 ctr_add = getbe32(padInfo.ctr + 12); u8 shasum[32]; u8 cid[16]; sdmmc_get_cid((entry->flags & AP_USE_NAND_CTR) ? 1 : 0, (uint32_t*) cid); if (entry->mode == AES_CNT_TWLNAND_MODE) { sha_quick(shasum, cid, 16, SHA1_MODE); for (u32 i = 0; i < 16; i++) padInfo.ctr[i] = shasum[15-i]; } else { sha_quick(shasum, cid, 16, SHA256_MODE); memcpy(padInfo.ctr, shasum, 16); } add_ctr(padInfo.ctr, ctr_add); } // create the pad Debug ("%2i: %s (%ikB)", i, entry->filename, entry->size_b / 1024); if (CreatePad(&padInfo) != 0) return 1; // this can't fail anyways } return 0; } u32 CtrNandPadgen(u32 param) { char* filename = (param & PG_FORCESLOT4) ? "nand.fat16.slot0x04.xorpad" : "nand.fat16.xorpad"; u32 keyslot; u32 nand_size; // legacy sizes & offset, to work with Python 3DSFAT16Tool if (GetUnitPlatform() == PLATFORM_3DS) { if (param & PG_FORCESLOT4) { Debug("This is a N3DS only feature"); return 1; } keyslot = 0x4; nand_size = 758; } else { keyslot = (param & PG_FORCESLOT4) ? 0x4 : 0x5; nand_size = 1055; } Debug("Creating NAND FAT16 xorpad. Size (MB): %u", nand_size); Debug("Filename: %s", filename); PadInfo padInfo = { .keyslot = keyslot, .setKeyY = 0, .size_mb = nand_size, .mode = AES_CNT_CTRNAND_MODE }; strncpy(padInfo.filename, filename, 64); if(GetNandCtr(padInfo.ctr, 0xB930000) != 0) return 1; return CreatePad(&padInfo); } u32 TwlNandPadgen(u32 param) { (void) (param); // param is unused here PartitionInfo* twln_info = GetPartitionInfo(P_TWLN); u32 size_mb = (twln_info->size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating TWLN FAT16 xorpad. Size (MB): %u", size_mb); Debug("Filename: twlnand.fat16.xorpad"); PadInfo padInfo = { .keyslot = twln_info->keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "twlnand.fat16.xorpad", .mode = AES_CNT_TWLNAND_MODE }; if(GetNandCtr(padInfo.ctr, twln_info->offset) != 0) return 1; return CreatePad(&padInfo); } u32 Firm0Firm1Padgen(u32 param) { (void) (param); // param is unused here PartitionInfo* firm0_info = GetPartitionInfo(P_FIRM0); PartitionInfo* firm1_info = GetPartitionInfo(P_FIRM1); u32 size_mb = (firm0_info->size + firm1_info->size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating FIRM0FIRM1 xorpad. Size (MB): %u", size_mb); Debug("Filename: firm0firm1.xorpad"); PadInfo padInfo = { .keyslot = firm0_info->keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "firm0firm1.xorpad", .mode = AES_CNT_CTRNAND_MODE }; if(GetNandCtr(padInfo.ctr, firm0_info->offset) != 0) return 1; return CreatePad(&padInfo); }
u32 CtrNandPadgen(u32 param) { u32 keyslot; u32 nand_size; // legacy sizes & offset, to work with 3DSFAT16Tool if (GetUnitPlatform() == PLATFORM_3DS) { keyslot = 0x4; nand_size = 758; } else { keyslot = 0x5; nand_size = 1055; } Debug("Creating NAND FAT16 xorpad. Size (MB): %u", nand_size); Debug("Filename: nand.fat16.xorpad"); PadInfo padInfo = { .keyslot = keyslot, .setKeyY = 0, .size_mb = nand_size, .filename = "nand.fat16.xorpad", .mode = AES_CNT_CTRNAND_MODE }; if(GetNandCtr(padInfo.ctr, 0xB930000) != 0) return 1; return CreatePad(&padInfo); } u32 TwlNandPadgen(u32 param) { u32 size_mb = (partitions[0].size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating TWLN FAT16 xorpad. Size (MB): %u", size_mb); Debug("Filename: twlnand.fat16.xorpad"); PadInfo padInfo = { .keyslot = partitions[0].keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "twlnand.fat16.xorpad", .mode = AES_CNT_TWLNAND_MODE }; if(GetNandCtr(padInfo.ctr, partitions[0].offset) != 0) return 1; return CreatePad(&padInfo); } u32 Firm0Firm1Padgen(u32 param) { u32 size_mb = (partitions[3].size + partitions[4].size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating FIRM0FIRM1 xorpad. Size (MB): %u", size_mb); Debug("Filename: firm0firm1.xorpad"); PadInfo padInfo = { .keyslot = partitions[3].keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "firm0firm1.xorpad", .mode = AES_CNT_CTRNAND_MODE }; if(GetNandCtr(padInfo.ctr, partitions[3].offset) != 0) return 1; return CreatePad(&padInfo); } u32 GetNandCtr(u8* ctr, u32 offset) { static const char* versions[] = {"4.x", "5.x", "6.x", "7.x", "8.x", "9.x"}; static const u8* version_ctrs[] = { (u8*)0x080D7CAC, (u8*)0x080D858C, (u8*)0x080D748C, (u8*)0x080D740C, (u8*)0x080D74CC, (u8*)0x080D794C }; static const u32 version_ctrs_len = sizeof(version_ctrs) / sizeof(u32); static u8* ctr_start = NULL; if (ctr_start == NULL) { for (u32 i = 0; i < version_ctrs_len; i++) { if (*(u32*)version_ctrs[i] == 0x5C980) { Debug("System version %s", versions[i]); ctr_start = (u8*) version_ctrs[i] + 0x30; } } // If value not in previous list start memory scanning (test range) if (ctr_start == NULL) { for (u8* c = (u8*) 0x080D8FFF; c > (u8*) 0x08000000; c--) { if (*(u32*)c == 0x5C980 && *(u32*)(c + 1) == 0x800005C9) { ctr_start = c + 0x30; Debug("CTR start 0x%08X", ctr_start); break; } } } if (ctr_start == NULL) { Debug("CTR start not found!"); return 1; } } // the ctr is stored backwards in memory if (offset >= 0x0B100000) { // CTRNAND/AGBSAVE region for (u32 i = 0; i < 16; i++) ctr[i] = *(ctr_start + (0xF - i)); } else { // TWL region for (u32 i = 0; i < 16; i++) ctr[i] = *(ctr_start + 0x88 + (0xF - i)); } // increment counter add_ctr(ctr, offset / 0x10); return 0; } u32 DecryptNandToMem(u8* buffer, u32 offset, u32 size, PartitionInfo* partition) { CryptBufferInfo info = {.keyslot = partition->keyslot, .setKeyY = 0, .size = size, .buffer = buffer, .mode = partition->mode}; if(GetNandCtr(info.ctr, offset) != 0) return 1; u32 n_sectors = (size + NAND_SECTOR_SIZE - 1) / NAND_SECTOR_SIZE; u32 start_sector = offset / NAND_SECTOR_SIZE; ReadNandSectors(start_sector, n_sectors, buffer); CryptBuffer(&info); return 0; } u32 DecryptNandToFile(const char* filename, u32 offset, u32 size, PartitionInfo* partition) { u8* buffer = BUFFER_ADDRESS; u32 result = 0; if (!DebugFileCreate(filename, true)) return 1; for (u32 i = 0; i < size; i += NAND_SECTOR_SIZE * SECTORS_PER_READ) { u32 read_bytes = min(NAND_SECTOR_SIZE * SECTORS_PER_READ, (size - i)); ShowProgress(i, size); DecryptNandToMem(buffer, offset + i, read_bytes, partition); if(!DebugFileWrite(buffer, read_bytes, i)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; }
u32 InjectHealthAndSafety(u32 param) { u8* buffer = BUFFER_ADDRESS; PartitionInfo* ctrnand_info = GetPartitionInfo(P_CTRNAND); TitleListInfo* health = titleList + ((GetUnitPlatform() == PLATFORM_3DS) ? 3 : 4); TitleListInfo* health_alt = (GetUnitPlatform() == PLATFORM_N3DS) ? titleList + 3 : NULL; NcchHeader* ncch = (NcchHeader*) 0x20316000; char filename[64]; u32 offset_app[4]; u32 size_app[4]; u32 offset_tmd; u32 size_tmd; u32 size_hs; if (!(param & N_NANDWRITE)) // developer screwup protection return 1; if ((DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health, 4) != 0) && (!health_alt || (DebugSeekTitleInNand(&offset_tmd, &size_tmd, offset_app, size_app, health_alt, 4) != 0))) return 1; if (size_app[0] > 0x400000) { Debug("H&S system app is too big!"); return 1; } if (DecryptNandToMem((void*) ncch, offset_app[0], 0x200, ctrnand_info) != 0) return 1; if (InputFileNameSelector(filename, NULL, "app", ncch->signature, 0x100, 0, false) != 0) return 1; if (!DebugFileOpen(filename)) return 1; size_hs = FileGetSize(); memset(buffer, 0, size_app[0]); if (size_hs > size_app[0]) { Debug("H&S inject app is too big!"); return 1; } if (!DebugFileRead(buffer, size_hs, 0)) { FileClose(); return 1; } FileClose(); if (!DebugFileCreate("hs.enc", true)) return 1; if (!DebugFileWrite(buffer, size_app[0], 0)) { FileClose(); return 1; } FileClose(); if (CryptNcch("hs.enc", 0, 0, 0, ncch->flags) != 0) return 1; Debug("Injecting H&S app..."); if (EncryptFileToNand("hs.enc", offset_app[0], size_app[0], ctrnand_info) != 0) return 1; Debug("Fixing TMD..."); u8* tmd_data = (u8*) 0x20316000; if (DecryptNandToMem(tmd_data, offset_tmd, size_tmd, ctrnand_info) != 0) return 1; tmd_data += (tmd_data[3] == 3) ? 0x240 : (tmd_data[3] == 4) ? 0x140 : 0x80; u8* content_list = tmd_data + 0xC4 + (64 * 0x24); u32 cnt_count = getbe16(tmd_data + 0x9E); if (GetHashFromFile("hs.enc", 0, size_app[0], content_list + 0x10) != 0) { Debug("Failed!"); return 1; } for (u32 i = 0, kc = 0; i < 64 && kc < cnt_count; i++) { u32 k = getbe16(tmd_data + 0xC4 + (i * 0x24) + 0x02); u8* chunk_hash = tmd_data + 0xC4 + (i * 0x24) + 0x04; sha_quick(chunk_hash, content_list + kc * 0x30, k * 0x30, SHA256_MODE); kc += k; } u8* tmd_hash = tmd_data + 0xA4; sha_quick(tmd_hash, tmd_data + 0xC4, 64 * 0x24, SHA256_MODE); tmd_data = (u8*) 0x20316000; if (EncryptMemToNand(tmd_data, offset_tmd, size_tmd, ctrnand_info) != 0) return 1; return 0; }