#include "fs.h" #include "draw.h" #include "hid.h" #include "platform.h" #include "decryptor/aes.h" #include "decryptor/decryptor.h" #include "decryptor/nand.h" #include "fatfs/sdmmc.h" // see: http://3dbrew.org/wiki/Flash_Filesystem static PartitionInfo partitions[] = { { "TWLN", {0xE9, 0x00, 0x00, 0x54, 0x57, 0x4C, 0x20, 0x20}, 0x00012E00, 0x08FB5200, 0x3, AES_CNT_TWLNAND_MODE }, { "TWLP", {0xE9, 0x00, 0x00, 0x54, 0x57, 0x4C, 0x20, 0x20}, 0x09011A00, 0x020B6600, 0x3, AES_CNT_TWLNAND_MODE }, { "AGBSAVE", {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 0x0B100000, 0x00030000, 0x7, AES_CNT_CTRNAND_MODE }, { "FIRM0", {0x46, 0x49, 0x52, 0x4D, 0x00, 0x00, 0x00, 0x00}, 0x0B130000, 0x00400000, 0x6, AES_CNT_CTRNAND_MODE }, { "FIRM1", {0x46, 0x49, 0x52, 0x4D, 0x00, 0x00, 0x00, 0x00}, 0x0B530000, 0x00400000, 0x6, AES_CNT_CTRNAND_MODE }, { "CTRNAND", {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}, 0x0B95CA00, 0x2F3E3600, 0x4, AES_CNT_CTRNAND_MODE }, // O3DS { "CTRNAND", {0xE9, 0x00, 0x00, 0x43, 0x54, 0x52, 0x20, 0x20}, 0x0B95AE00, 0x41D2D200, 0x5, AES_CNT_CTRNAND_MODE } // N3DS }; static u32 emunand_header = 0; static u32 emunand_offset = 0; u32 CheckEmuNand(void) { u8* buffer = BUFFER_ADDRESS; u32 nand_size_sectors = getMMCDevice(0)->total_size; u32 multi_sectors = (GetUnitPlatform() == PLATFORM_3DS) ? EMUNAND_MULTI_OFFSET_O3DS : EMUNAND_MULTI_OFFSET_N3DS; u32 ret = EMUNAND_NOT_READY; // check the MBR for presence of a hidden partition sdmmc_sdcard_readsectors(0, 1, buffer); u32 hidden_sectors = getle32(buffer + 0x1BE + 0x8); for (u32 offset_sector = 0; offset_sector + nand_size_sectors < hidden_sectors; offset_sector += multi_sectors) { // check for Gateway type EmuNAND sdmmc_sdcard_readsectors(offset_sector + nand_size_sectors, 1, buffer); if (memcmp(buffer + 0x100, "NCSD", 4) == 0) { ret |= EMUNAND_GATEWAY << (2 * (offset_sector / multi_sectors)); continue; } // check for RedNAND type EmuNAND sdmmc_sdcard_readsectors(offset_sector + 1, 1, buffer); if (memcmp(buffer + 0x100, "NCSD", 4) == 0) { ret |= EMUNAND_REDNAND << (2 * (offset_sector / multi_sectors)); continue; } // EmuNAND ready but not set up ret |= EMUNAND_READY << (2 * (offset_sector / multi_sectors)); } return ret; } u32 SetNand(bool set_emunand, bool force_emunand) { if (set_emunand) { u32 emunand_state = CheckEmuNand(); u32 emunand_count = 0; u32 offset_sector = 0; for (emunand_count = 0; (emunand_state >> (2 * emunand_count)) & 0x3; emunand_count++); if (emunand_count > 1) { // multiple EmuNANDs -> use selector u32 multi_sectors = (GetUnitPlatform() == PLATFORM_3DS) ? EMUNAND_MULTI_OFFSET_O3DS : EMUNAND_MULTI_OFFSET_N3DS; u32 emunand_no = 0; Debug("Use arrow keys and to choose EmuNAND"); while (true) { u32 emunandn_state = (emunand_state >> (2 * emunand_no)) & 0x3; offset_sector = emunand_no * multi_sectors; Debug("\rEmuNAND #%u: %s", emunand_no, (emunandn_state == EMUNAND_READY) ? "EmuNAND ready" : (emunandn_state == EMUNAND_GATEWAY) ? "GW EmuNAND" : "RedNAND"); // user input routine u32 pad_state = InputWait(); if (pad_state & BUTTON_DOWN) { emunand_no = (emunand_no + 1) % emunand_count; } else if (pad_state & BUTTON_UP) { emunand_no = (emunand_no) ? emunand_no - 1 : emunand_count - 1; } else if (pad_state & BUTTON_A) { Debug("EmuNAND #%u", emunand_no); emunand_state = emunandn_state; break; } else if (pad_state & BUTTON_B) { Debug("(cancelled by user)"); return 2; } } } if ((emunand_state == EMUNAND_READY) && force_emunand) emunand_state = EMUNAND_GATEWAY; switch (emunand_state) { case EMUNAND_NOT_READY: Debug("SD is not formatted for EmuNAND"); return 1; case EMUNAND_GATEWAY: emunand_header = offset_sector + getMMCDevice(0)->total_size; emunand_offset = offset_sector; Debug("Using EmuNAND @ %06X/%06X", emunand_header, emunand_offset); return 0; case EMUNAND_REDNAND: emunand_header = offset_sector + 1; emunand_offset = offset_sector + 1; Debug("Using RedNAND @ %06X/%06X", emunand_header, emunand_offset); return 0; default: Debug("EmuNAND is not available"); return 1; } } else { emunand_header = 0; emunand_offset = 0; return 0; } } static inline int ReadNandSectors(u32 sector_no, u32 numsectors, u8 *out) { if (emunand_header) { if (sector_no == 0) { int errorcode = sdmmc_sdcard_readsectors(emunand_header, 1, out); if (errorcode) return errorcode; sector_no = 1; numsectors--; out += 0x200; } return sdmmc_sdcard_readsectors(sector_no + emunand_offset, numsectors, out); } else return sdmmc_nand_readsectors(sector_no, numsectors, out); } static inline int WriteNandSectors(u32 sector_no, u32 numsectors, u8 *in) { if (emunand_header) { if (sector_no == 0) { int errorcode = sdmmc_sdcard_writesectors(emunand_header, 1, in); if (errorcode) return errorcode; sector_no = 1; numsectors--; in += 0x200; } return sdmmc_sdcard_writesectors(sector_no + emunand_offset, numsectors, in); } else return sdmmc_nand_writesectors(sector_no, numsectors, in); } u32 OutputFileNameSelector(char* filename, const char* basename, char* extension) { char bases[3][64] = { 0 }; char* dotpos = NULL; // build first base name and extension strncpy(bases[0], basename, 63); dotpos = strrchr(bases[0], '.'); if (dotpos) { *dotpos = '\0'; if (!extension) extension = dotpos + 1; } // build other two base names snprintf(bases[1], 63, "%s_%s", bases[0], (emunand_header) ? "emu" : "sys"); snprintf(bases[2], 63, "%s%s" , (emunand_header) ? "emu" : "sys", bases[0]); u32 fn_id = (emunand_header) ? 1 : 0; u32 fn_num = (emunand_header) ? (emunand_offset / ((GetUnitPlatform() == PLATFORM_3DS) ? EMUNAND_MULTI_OFFSET_O3DS : EMUNAND_MULTI_OFFSET_N3DS)) : 0; bool exists = false; char extstr[16] = { 0 }; if (extension) snprintf(extstr, 15, ".%s", extension); Debug("Use arrow keys and to choose a name"); while (true) { char numstr[2] = { 0 }; // build and output file name (plus "(!)" if existing) numstr[0] = (fn_num > 0) ? '0' + fn_num : '\0'; snprintf(filename, 63, "%s%s%s", bases[fn_id], numstr, extstr); if ((exists = FileOpen(filename))) FileClose(); Debug("\r%s%s", filename, (exists) ? " (!)" : ""); // user input routine u32 pad_state = InputWait(); if (pad_state & BUTTON_DOWN) { // increment filename id fn_id = (fn_id + 1) % 3; } else if (pad_state & BUTTON_UP) { // decrement filename id fn_id = (fn_id > 0) ? fn_id - 1 : 2; } else if ((pad_state & BUTTON_RIGHT) && (fn_num < 9)) { // increment number fn_num++; } else if ((pad_state & BUTTON_LEFT) && (fn_num > 0)) { // decrement number fn_num--; } else if (pad_state & BUTTON_A) { Debug("%s%s", filename, (exists) ? " (!)" : ""); break; } else if (pad_state & BUTTON_B) { Debug("(cancelled by user)"); return 2; } } // overwrite confirmation if (exists) { Debug("Press to overwrite existing file"); while (true) { u32 pad_state = InputWait(); if (pad_state & BUTTON_A) { break; } else if (pad_state & BUTTON_B) { Debug("(cancelled by user)"); return 2; } } } return 0; } u32 InputFileNameSelector(char* filename, const char* basename, char* extension, u8* magic, u32 msize, u32 fsize) { char** fnptr = (char**) 0x20400000; // allow using 0x8000 byte char* fnlist = (char*) 0x20408000; // allow using 0x80000 byte u32 n_names = 0; // get the file list - try work directory first if (!GetFileList(WORK_DIR, fnlist, 0x80000, false, true, false) && !GetFileList("/", fnlist, 0x800000, false, true, false)) { Debug("Failed retrieving the file names list"); return 1; } // get base name, extension char base[64] = { 0 }; if (basename != NULL) { // build base name and extension strncpy(base, basename, 63); char* dotpos = strrchr(base, '.'); if (dotpos) { *dotpos = '\0'; if (!extension) extension = dotpos + 1; } } // limit magic number size if (msize > 0x200) msize = 0x200; // parse the file names list for usable entries for (char* fn = strtok(fnlist, "\n"); fn != NULL; fn = strtok(NULL, "\n")) { u8 data[0x200]; char* dotpos = strrchr(fn, '.'); if (strrchr(fn, '/')) fn = strrchr(fn, '/') + 1; if (strnlen(fn, 128) > 63) continue; // file name too long if ((basename != NULL) && !strstr(fn, base)) continue; // basename check failed if ((extension != NULL) && (dotpos != NULL) && (strncmp(dotpos + 1, extension, strnlen(extension, 16)))) continue; // extension check failed else if ((extension == NULL) != (dotpos == NULL)) continue; // extension check failed if (!FileOpen(fn)) continue; // file can't be opened if (fsize && (FileGetSize() != fsize)) { FileClose(); continue; // file size check failed } if (msize) { if (FileRead(data, msize, 0) != msize) { FileClose(); continue; // can't be read } if (memcmp(data, magic, msize) != 0) { FileClose(); continue; // magic number does not match } } FileClose(); // this is a match - keep it fnptr[n_names++] = fn; if (n_names * sizeof(char**) >= 0x8000) return 1; } if (n_names == 0) { Debug("No usable file found"); return 1; } u32 index = 0; Debug("Use arrow keys and to choose a file"); while (true) { snprintf(filename, 63, "%s", fnptr[index]); Debug("\r%s", filename); u32 pad_state = InputWait(); if (pad_state & BUTTON_DOWN) { // next filename index = (index + 1) % n_names; } else if (pad_state & BUTTON_UP) { // previous filename index = (index > 0) ? index - 1 : n_names - 1; } else if (pad_state & BUTTON_A) { Debug("%s", filename); break; } else if (pad_state & BUTTON_B) { Debug("(cancelled by user)"); return 2; } } return 0; } 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<= 32) ? NULL : &(partitions[partition_num]); } 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 DumpNand(u32 param) { char filename[64]; u8* buffer = BUFFER_ADDRESS; u32 nand_size = getMMCDevice(0)->total_size * NAND_SECTOR_SIZE; u32 result = 0; Debug("Dumping %sNAND. Size (MB): %u", (param & N_EMUNAND) ? "Emu" : "Sys", nand_size / (1024 * 1024)); if (OutputFileNameSelector(filename, "NAND.bin", NULL) != 0) return 1; if (!DebugFileCreate(filename, true)) return 1; u32 n_sectors = nand_size / NAND_SECTOR_SIZE; for (u32 i = 0; i < n_sectors; i += SECTORS_PER_READ) { u32 read_sectors = min(SECTORS_PER_READ, (n_sectors - i)); ShowProgress(i, n_sectors); ReadNandSectors(i, read_sectors, buffer); if(!DebugFileWrite(buffer, NAND_SECTOR_SIZE * read_sectors, i * NAND_SECTOR_SIZE)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; } u32 DecryptNandPartition(u32 param) { PartitionInfo* p_info = NULL; char filename[64]; u8 magic[NAND_SECTOR_SIZE]; for (u32 partition_id = P_TWLN; partition_id <= P_CTRNAND; partition_id = partition_id << 1) { if (param & partition_id) { p_info = GetPartitionInfo(partition_id); break; } } if (p_info == NULL) { Debug("No partition to dump"); return 1; } Debug("Dumping & Decrypting %s, size (MB): %u", p_info->name, p_info->size / (1024 * 1024)); if (DecryptNandToMem(magic, p_info->offset, 16, p_info) != 0) return 1; if ((p_info->magic[0] != 0xFF) && (memcmp(p_info->magic, magic, 8) != 0)) { Debug("Decryption error, please contact us"); return 1; } if (OutputFileNameSelector(filename, p_info->name, "bin") != 0) return 1; return DecryptNandToFile(filename, p_info->offset, p_info->size, p_info); } u32 EncryptMemToNand(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; CryptBuffer(&info); WriteNandSectors(start_sector, n_sectors, buffer); return 0; } u32 EncryptFileToNand(const char* filename, u32 offset, u32 size, PartitionInfo* partition) { u8* buffer = BUFFER_ADDRESS; u32 result = 0; if (!DebugFileOpen(filename)) return 1; if (FileGetSize() != size) { Debug("%s has wrong size", filename); FileClose(); 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); if(!DebugFileRead(buffer, read_bytes, i)) { result = 1; break; } EncryptMemToNand(buffer, offset + i, read_bytes, partition); } ShowProgress(0, 0); FileClose(); return result; } u32 RestoreNand(u32 param) { char filename[64]; u8* buffer = BUFFER_ADDRESS; u32 nand_size = getMMCDevice(0)->total_size * NAND_SECTOR_SIZE; u32 result = 0; u8 magic[4]; if (!(param & N_NANDWRITE)) // developer screwup protection return 1; // User file select if (InputFileNameSelector(filename, "NAND.bin", NULL, NULL, 0, nand_size) != 0) return 1; if (!DebugFileOpen(filename)) return 1; if (nand_size != FileGetSize()) { FileClose(); Debug("NAND backup has the wrong size!"); return 1; }; if(!DebugFileRead(magic, 4, 0x100)) { FileClose(); return 1; } if (memcmp(magic, "NCSD", 4) != 0) { FileClose(); Debug("Not a proper NAND backup!"); return 1; } Debug("Restoring %sNAND. Size (MB): %u", (param & N_EMUNAND) ? "Emu" : "Sys", nand_size / (1024 * 1024)); u32 n_sectors = nand_size / NAND_SECTOR_SIZE; for (u32 i = 0; i < n_sectors; i += SECTORS_PER_READ) { u32 read_sectors = min(SECTORS_PER_READ, (n_sectors - i)); ShowProgress(i, n_sectors); if(!DebugFileRead(buffer, NAND_SECTOR_SIZE * read_sectors, i * NAND_SECTOR_SIZE)) { result = 1; break; } WriteNandSectors(i, read_sectors, buffer); } ShowProgress(0, 0); FileClose(); return result; } u32 InjectNandPartition(u32 param) { PartitionInfo* p_info = NULL; char filename[64]; u8 magic[NAND_SECTOR_SIZE]; if (!(param & N_NANDWRITE)) // developer screwup protection return 1; for (u32 partition_id = P_TWLN; partition_id <= P_CTRNAND; partition_id = partition_id << 1) { if (param & partition_id) { p_info = GetPartitionInfo(partition_id); break; } } if (p_info == NULL) { Debug("No partition to inject to"); return 1; } Debug("Encrypting & Injecting %s, size (MB): %u", p_info->name, p_info->size / (1024 * 1024)); // User file select if (InputFileNameSelector(filename, p_info->name, "bin", p_info->magic, (p_info->magic[0] != 0xFF) ? 8 : 0, p_info->size) != 0) return 1; // Encryption check if (DecryptNandToMem(magic, p_info->offset, 16, p_info) != 0) return 1; if ((p_info->magic[0] != 0xFF) && (memcmp(p_info->magic, magic, 8) != 0)) { Debug("Decryption error, please contact us"); return 1; } // File check if (FileOpen(filename)) { if(!DebugFileRead(magic, 8, 0)) { FileClose(); return 1; } if ((p_info->magic[0] != 0xFF) && (memcmp(p_info->magic, magic, 8) != 0)) { Debug("Bad file content, won't inject"); FileClose(); return 1; } FileClose(); } return EncryptFileToNand(filename, p_info->offset, p_info->size, p_info); }