/* This file is part of Wii (U) NAND Extractor C. * Copyright (C) 2020 GaryOderNichts * * This file was ported from Wii NAND Extractor. * Copyright (C) 2009 Ben Wilson * * Wii NAND Extractor is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Wii NAND Extractor is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "extractor.h" #include #include #include #include #include #include #include "aes.h" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define bswap32 __builtin_bswap32 #define bswap16 __builtin_bswap16 #endif uint8_t* key; FILE* rom; int32_t loc_super; int32_t loc_fat; int32_t loc_fst; FileType fileType = Invalid; NandType nandType; char* nandName; int8_t initSuccess = 0; char* stringReplaceAll(const char *search, const char *replace, char *string) { char* searchStart = strstr(string, search); while (searchStart != NULL) { char* tempString = (char*) malloc(strlen(string) * sizeof(char)); if(tempString == NULL) { return NULL; } strcpy(tempString, string); int len = searchStart - string; string[len] = '\0'; strcat(string, replace); len += strlen(search); strcat(string, (char*)tempString+len); free(tempString); searchStart = strstr(string, search); } return string; } int main(int argc, char* argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s [output]\n", argv[0]); return 0; } //init nand rom = fopen(argv[1], "rb"); if (rom == NULL) { printf("error opening %s\n", argv[1]); return -1; } if (!getFileType() || !getNandType() || !getKey()) return -1; loc_super = findSuperblock(); if (loc_super == -1) { printf("can't find superblock!\n"); return 0; } int32_t fatlen = getClusterSize() * 4; loc_fat = loc_super; loc_fst = loc_fat + 0x0C + fatlen; nandName = calloc(PATH_MAX, sizeof(char)); if (argc > 2) { strncpy(nandName, argv[2], PATH_MAX); } else { sscanf(argv[1], "%[^.]", nandName); } initSuccess = 1; extractNand(); fclose(rom); free(key); free(nandName); return 0; } int32_t getPageSize(void) { if (fileType == NoECC) return PAGE_SIZE; else return PAGE_SIZE + SPARE_SIZE; } int32_t getClusterSize(void) { return getPageSize() * 8; } uint8_t getFileType(void) { rewind(rom); fseek(rom, 0, SEEK_END); uint64_t length = ftell(rom); switch (length) { case PAGE_SIZE * 8 * CLUSTERS_COUNT: fileType = NoECC; return 1; case (PAGE_SIZE + SPARE_SIZE) * 8 * CLUSTERS_COUNT: fileType = ECC; return 1; case (PAGE_SIZE + SPARE_SIZE) * 8 * CLUSTERS_COUNT + 0x400: fileType = BootMii; return 1; default: return 0; } } uint8_t getNandType(void) { rewind(rom); fseek(rom, getClusterSize() * 0x7FF0, SEEK_SET); uint32_t cluster; fread(&cluster, sizeof(uint32_t), 1, rom); switch (bswap32(cluster)) { case 0x53464653: // SFFS nandType = Wii; return 1; case 0x53465321: // SFS! or a byteswapped !SFS if (fileType == BootMii) return 0; // Invalid dump type for WiiU nandType = WiiU; return 1; default: return 0; } } uint8_t getKey(void) { //TODO key from text if (fileType == BootMii) { rewind(rom); fseek(rom, 0x21000158, SEEK_SET); uint8_t* bootmiikey = calloc(16, sizeof(uint8_t)); fread(bootmiikey, sizeof(uint8_t), 16, rom); key = bootmiikey; return 1; } else { key = readOTP("otp.bin"); if (key != NULL) return 1; if (nandType == Wii) { key = readKeyfile("keys.bin"); if (key != NULL) return 1; } } printf("Error key not valid\n"); return 0; } uint8_t* readKeyfile(char* path) { uint8_t* retval = malloc(sizeof(char) * 16); FILE* keyfile = fopen(path, "rb"); if (keyfile == NULL) { free(retval); return NULL; } fseek(keyfile, 0x158, SEEK_SET); fread(retval, sizeof(uint8_t), 16, keyfile); fclose(keyfile); return retval; } uint8_t* readOTP(char* path) { uint8_t* retval = malloc(sizeof(char) * 16); FILE* otpfile = fopen(path, "rb"); if (otpfile == NULL) { free(retval); return NULL; } if (nandType == Wii) fseek(otpfile, 0x058, SEEK_SET); else fseek(otpfile, 0x170, SEEK_SET); fread(retval, sizeof(uint8_t), 16, otpfile); fclose(otpfile); return retval; } int32_t findSuperblock(void) { uint32_t loc = ((nandType == Wii) ? 0x7F00 : 0x7C00) * getClusterSize(); uint32_t end = CLUSTERS_COUNT * getClusterSize(); uint32_t len = getClusterSize() * 0x10; uint32_t current, magic, last = 0; uint8_t irewind = 1; for (; loc < end; loc += len) { rewind(rom); fseek(rom, loc, SEEK_SET); fread(&magic, 4, 1, rom); if (bswap32(magic) != ((nandType == Wii) ? 0x53464653 /*SFFS*/ : 0x53465321 /*!SFS*/)) { printf("this is not a supercluster\n"); irewind++; continue; } fread(¤t, 4, 1, rom); current = bswap32(current); if (current > last) last = current; else { irewind = 1; break; } if (loc == end) irewind = 1; } if (!last) return -1; loc -= len * irewind; return loc; } fst_t getFST(uint16_t entry) { fst_t fst; // compensate for 64 bytes of ecc data every 64 fst entries int32_t n_fst = (fileType == NoECC) ? 0 : 2; int32_t loc_entry = (((entry / 0x40) * n_fst) + entry) * 0x20; rewind(rom); fseek(rom, loc_fst + loc_entry, SEEK_SET); fread(&fst.filename, sizeof(uint8_t), 0x0C, rom); fread(&fst.mode, sizeof(uint8_t), 1, rom); fread(&fst.attr, sizeof(uint8_t), 1, rom); uint16_t sub; fread(&sub, sizeof(uint16_t), 1, rom); sub = bswap16(sub); fst.sub = sub; uint16_t sib; fread(&sib, sizeof(uint16_t), 1, rom); sib = bswap16(sib); fst.sib = sib; uint32_t size; if ((entry + 1) % 64 == 0) //the entry for every 64th fst item is interrupted { fread(&size, 2, 1, rom); fseek(rom, 0x40, SEEK_CUR); fread((char*) (&size) + 2, 2, 1, rom); } else { fread(&size, sizeof(uint32_t), 1, rom); } size = bswap32(size); fst.size = size; uint32_t uid; fread(&uid, sizeof(uint32_t), 1, rom); uid = bswap32(uid); fst.uid = uid; uint16_t gid; fread(&gid, sizeof(uint16_t), 1, rom); gid = bswap16(gid); fst.gid = gid; uint32_t x3; fread(&x3, sizeof(uint32_t), 1, rom); x3 = bswap32(x3); fst.x3 = x3; fst.mode &= 1; return fst; } void extractNand(void) { if (initSuccess != 1) { printf("NAND has not been initialized successfully!"); return; } mkdir(nandName, 0777); extractFST(0, ""); } uint8_t* getCluster(uint16_t cluster_entry) { uint8_t* cluster = calloc(0x4000, sizeof(uint8_t)); uint8_t* page = calloc(getPageSize(), sizeof(uint8_t)); rewind(rom); fseek(rom, cluster_entry * getClusterSize(), SEEK_SET); for (int i = 0; i < 8; i++) { fread(page, sizeof(uint8_t), getPageSize(), rom); memcpy((uint8_t*) cluster + (i * 0x800), page, 0x800); } free(page); return aesDecrypt(key, cluster, 0x4000); } uint16_t getFAT(uint16_t fat_entry) { /* * compensate for "off-16" storage at beginning of superblock * 53 46 46 53 XX XX XX XX 00 00 00 00 * S F F S "version" padding? * 1 2 3 4 5 6 */ fat_entry += 6; // location in fat of cluster chain int32_t n_fat = (fileType == NoECC) ? 0 : 0x20; int32_t loc = loc_fat + ((((fat_entry / 0x400) * n_fat) + fat_entry) * 2); rewind(rom); fseek(rom, loc, SEEK_SET); uint16_t fat; fread(&fat, sizeof(uint16_t), 1, rom); return bswap16(fat); } void extractFST(uint16_t entry, char* parent) { fst_t fst = getFST(entry); if (fst.sib != 0xffff) extractFST(fst.sib, parent); switch (fst.mode) { case 0: extractDir(fst, entry, parent); break; case 1: extractFile(fst, entry, parent); break; default: printf("ignoring unsupported mode: %x\n", fst.mode); break; } } void extractDir(fst_t fst, uint16_t entry, char* parent) { char* newfilename = malloc(PATH_MAX); newfilename[0] = '\0'; if (strcmp(fst.filename, "/") != 0) { if (strcmp(parent, "/") != 0 && strcmp(parent, "") != 0) { snprintf(newfilename, PATH_MAX, "%s/%s", parent, fst.filename); } else { strcpy(newfilename, fst.filename); } char path[PATH_MAX] = { 0 }; snprintf(path, PATH_MAX, "%s/%s", nandName, newfilename); mkdir(path, 0777); printf("dir: %s\n", path); } else { strcpy(newfilename, fst.filename); } if (fst.sub != 0xffff) extractFST(fst.sub, newfilename); free(newfilename); } void extractFile(fst_t fst, uint16_t entry, char* parent) { uint16_t fat; uint32_t cluster_span = (uint32_t) (fst.size / 0x4000) + 1; uint8_t* data = calloc(cluster_span * 0x4000, sizeof(uint8_t)); char filename[PATH_MAX] = { 0 }; snprintf(filename, 13, "%s", fst.filename); stringReplaceAll(":", "-", filename); char newfilename[PATH_MAX] = { 0 }; snprintf(newfilename, PATH_MAX, "/%s/%s", parent, filename); char path[PATH_MAX] = { 0 }; snprintf(path, PATH_MAX, "%s%s", nandName, newfilename); FILE* bf = fopen(path, "wb"); if(bf == NULL) { printf("Error opening %s: %d\n", path, errno); } fat = fst.sub; for (int i = 0; fat < 0xFFF0; i++) { //extracting... printf("extracting %s cluster %i\n", filename, i); uint8_t* cluster = getCluster(fat); memcpy((uint8_t*) (data + (i * 0x4000)), cluster, 0x4000); fat = getFAT(fat); free(cluster); } fwrite(data, fst.size, 1, bf); fclose(bf); free(data); printf("extracted file: %s\n", newfilename); } uint8_t* aesDecrypt(uint8_t* key, uint8_t* enc_data, size_t data_size) { uint8_t iv[16] = { 0 }; struct AES_ctx ctx; AES_init_ctx_iv(&ctx, key, iv); AES_CBC_decrypt_buffer(&ctx, enc_data, data_size); return enc_data; }