From 89b6946789de6c878fe742cbb2b4cc306c384e1d Mon Sep 17 00:00:00 2001 From: d0k3 Date: Wed, 12 Apr 2017 00:27:02 +0200 Subject: [PATCH] Added ability to build seeddb.bin ... plus new entry in HOME more... menu --- source/game/firm.c | 42 ++--------------------- source/game/firm.h | 3 -- source/game/gameutil.c | 75 ++++++++++++++++++++++++++++++++++++++++++ source/game/gameutil.h | 1 + source/game/ncch.c | 40 +++++++++++----------- source/game/ncch.h | 20 +++++++++++ source/godmode.c | 44 +++++++++++++++++++++++++ source/nand/nand.c | 38 +++++++++++++++++++++ source/nand/nand.h | 6 ++++ 9 files changed, 207 insertions(+), 62 deletions(-) diff --git a/source/game/firm.c b/source/game/firm.c index eedc950..48f5702 100644 --- a/source/game/firm.c +++ b/source/game/firm.c @@ -53,51 +53,13 @@ u32 GetArm9BinarySize(FirmA9LHeader* a9l) { } u32 SetupSecretKey(u32 keynum) { - const char* base[] = { INPUT_PATHS }; - // from: https://github.com/AuroraWright/SafeA9LHInstaller/blob/master/source/installer.c#L9-L17 - const u8 sectorHash[0x20] = { - 0x82, 0xF2, 0x73, 0x0D, 0x2C, 0x2D, 0xA3, 0xF3, 0x01, 0x65, 0xF9, 0x87, 0xFD, 0xCC, 0xAC, 0x5C, - 0xBA, 0xB2, 0x4B, 0x4E, 0x5F, 0x65, 0xC9, 0x81, 0xCD, 0x7B, 0xE6, 0xF4, 0x38, 0xE6, 0xD9, 0xD3 - }; static u8 __attribute__((aligned(32))) sector[0x200]; - u8 hash[0x20]; // safety check if (keynum >= 0x200/0x10) return 1; - // secret sector already loaded? - sha_quick(hash, sector, 0x200, SHA256_MODE); - if (memcmp(hash, sectorHash, 0x20) == 0) { - setup_aeskey(0x11, sector + (keynum*0x10)); - use_aeskey(0x11); - return 0; - } - - // search for valid secret sector in SysNAND / EmuNAND - const u32 nand_src[] = { NAND_SYSNAND, NAND_EMUNAND }; - for (u32 i = 0; i < sizeof(nand_src) / sizeof(u32); i++) { - ReadNandSectors(sector, 0x96, 1, 0x11, nand_src[i]); - sha_quick(hash, sector, 0x200, SHA256_MODE); - if (memcmp(hash, sectorHash, 0x20) != 0) continue; - setup_aeskey(0x11, sector + (keynum*0x10)); - use_aeskey(0x11); - return 0; - } - - // no luck? try searching for a file - for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) { - char path[64]; - FIL fp; - UINT btr; - snprintf(path, 64, "%s/%s", base[i], SECTOR_NAME); - if (f_open(&fp, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) { - snprintf(path, 64, "%s/%s", base[i], SECRET_NAME); - if (f_open(&fp, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) continue; - } - f_read(&fp, sector, 0x200, &btr); - f_close(&fp); - sha_quick(hash, sector, 0x200, SHA256_MODE); - if (memcmp(hash, sectorHash, 0x20) != 0) continue; + // seach for secret sector data... + if (GetLegitSector0x96(sector) == 0) { setup_aeskey(0x11, sector + (keynum*0x10)); use_aeskey(0x11); return 0; diff --git a/source/game/firm.h b/source/game/firm.h index 72d33b8..4d1c209 100644 --- a/source/game/firm.h +++ b/source/game/firm.h @@ -4,9 +4,6 @@ #define FIRM_MAGIC 'F', 'I', 'R', 'M' -#define SECTOR_NAME "sector0x96.bin" -#define SECRET_NAME "secret_sector.bin" - #define FIRM_MAX_SIZE 0x400000 // 4MB, due to FIRM partition size #define ARM11NCCH_OFFSET 0, 0x2A000, 0x2B000, 0x2C000 #define ARM9BIN_OFFSET 0x800 diff --git a/source/game/gameutil.c b/source/game/gameutil.c index e8c8fa5..7889ec5 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -1729,3 +1729,78 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { return 0; } + +u32 BuildSeedInfo(const char* path, bool dump) { + SeedInfo* seed_info = (SeedInfo*) MAIN_BUFFER; + const char* path_out = OUTPUT_PATH "/" SEEDDB_NAME; + const char* path_in = path; + u32 inputtype = 0; // 0 -> none, 1 -> seeddb.bin, 2 -> seed system save + UINT br; + + if (!path_in && !dump) { // no input path given - initialize + memset(seed_info, 0, 16); + if ((fvx_stat(path_out, NULL) == FR_OK) && + (ShowPrompt(true, "%s\nOutput file already exists.\nUpdate this?", path_out))) { + path_in = path_out; + inputtype = 1; + } else return 0; + } + + char path_str[128]; + if (path_in && (strnlen(path_in, 16) == 2)) { // when only a drive is given... + // grab the key Y from movable.sed + u8 movable_keyy[16]; + snprintf(path_str, 128, "%s/private/movable.sed", path_in); + if ((fvx_qread(path_str, movable_keyy, 0x110, 0x10, &br) != FR_OK) || (br != 0x10)) + return 1; + // build the seed save path + u32 sha256sum[8]; + sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE); + snprintf(path_str, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000", + path_in, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]); + path_in = path_str; + inputtype = 2; + } + + if (inputtype == 1) { // seeddb.bin input + SeedInfo* seed_info_merge = (SeedInfo*) TEMP_BUFFER; + if ((fvx_qread(path_in, seed_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || + (SEEDDB_SIZE(seed_info_merge) != br)) return 1; + // merge and rebuild SeedInfo + u32 n_entries = seed_info_merge->n_entries; + SeedInfoEntry* seed = seed_info_merge->entries; + for (u32 i = 0; i < n_entries; i++, seed++) { + if (SEEDDB_SIZE(seed_info) + 32 > MAIN_BUFFER_SIZE) return 1; + AddSeedToDb(seed_info, seed); // ignore result + } + } else if (inputtype == 2) { // seed system save input + static const u32 seed_offset[2] = {SEEDSAVE_AREA_OFFSETS}; + u8* seedsave = (u8*) TEMP_BUFFER; + if ((fvx_qread(path_in, seedsave, 0, 0x200, &br) != FR_OK) || (br != 0x200)) + return 1; + u32 p_active = (getle32(seedsave + 0x168)) ? 1 : 0; + for (u32 p = 0; p < 2; p++) { + SeedInfoEntry seed = { 0 }; + if ((fvx_qread(path_in, seedsave, seed_offset[(p + p_active) % 2], SEEDSAVE_MAX_ENTRIES*(8+16), &br) != FR_OK) || + (br != SEEDSAVE_MAX_ENTRIES*(8+16))) + return 1; + for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) { + seed.titleId = getle64(seedsave + (s*8)); + memcpy(seed.seed, seedsave + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16); + if (((seed.titleId >> 32) != 0x00040000) || + (!getle64(seed.seed) && !getle64(seed.seed + 8))) continue; + if (SEEDDB_SIZE(seed_info) + 32 > MAIN_BUFFER_SIZE) return 1; + AddSeedToDb(seed_info, &seed); // ignore result + } + } + } + + if (dump) { + u32 dump_size = SEEDDB_SIZE(seed_info); + f_unlink(path_out); + if ((dump_size <= 16) || (fvx_qwrite(path_out, seed_info, 0, dump_size, &br) != FR_OK) || (br != dump_size)) + return 1; + } + + return 0; +} diff --git a/source/game/gameutil.h b/source/game/gameutil.h index f112e43..eed5db9 100644 --- a/source/game/gameutil.h +++ b/source/game/gameutil.h @@ -11,3 +11,4 @@ u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv); u32 InjectHealthAndSafety(const char* path, const char* destdrv); u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump); +u32 BuildSeedInfo(const char* path, bool dump); diff --git a/source/game/ncch.c b/source/game/ncch.c index 75c8789..77a1b19 100644 --- a/source/game/ncch.c +++ b/source/game/ncch.c @@ -4,21 +4,8 @@ #include "sha.h" #include "ff.h" -#define SEEDDB_NAME "seeddb.bin" #define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1) -typedef struct { - u64 titleId; - u8 seed[16]; - u8 reserved[8]; -} __attribute__((packed)) SeedInfoEntry; - -typedef struct { - u32 n_entries; - u8 padding[12]; - SeedInfoEntry entries[256]; // this number is only a placeholder -} __attribute__((packed)) SeedInfo; - u32 ValidateNcchHeader(NcchHeader* header) { if (memcmp(header->magic, "NCCH", 4) != 0) // check magic number return 1; @@ -102,14 +89,13 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) { continue; f_read(&file, seedsave, 0x200, &btr); u32 p_active = (getle32(seedsave + 0x168)) ? 1 : 0; - static const u32 seed_offset[2] = {0x7000, 0x5C000}; + static const u32 seed_offset[2] = {SEEDSAVE_AREA_OFFSETS}; for (u32 p = 0; p < 2; p++) { f_lseek(&file, seed_offset[(p + p_active) % 2]); - f_read(&file, seedsave, 2000*(8+16), &btr); - for (u32 s = 0; s < 2000; s++) { - if (titleId != getle64(seedsave + (s*8))) - continue; - memcpy(lseed, seedsave + (2000*8) + (s*16), 16); + f_read(&file, seedsave, SEEDSAVE_MAX_ENTRIES*(8+16), &btr); + for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) { + if (titleId != getle64(seedsave + (s*8))) continue; + memcpy(lseed, seedsave + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16); sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE); if (hash_seed == sha256sum[0]) { memcpy(seed, lseed, 16); @@ -148,6 +134,22 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) { return 1; } +u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry) { + if (!seed_entry) { // no seed entry -> reset database + memset(seed_info, 0, 16); + return 0; + } + // check if entry already in DB + u32 n_entries = seed_info->n_entries; + SeedInfoEntry* seed = seed_info->entries; + for (u32 i = 0; i < n_entries; i++, seed++) + if (seed->titleId == seed_entry->titleId) return 0; + // actually a new seed entry + memcpy(seed, seed_entry, sizeof(SeedInfoEntry)); + seed_info->n_entries++; + return 0; +} + u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) { u8 flags3 = (crypto >> 8) & 0xFF; u8 flags7 = crypto & 0xFF; diff --git a/source/game/ncch.h b/source/game/ncch.h index 8357df1..3aab741 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -15,6 +15,12 @@ #define NCCH_STDCRYPTO 0x0000 #define NCCH_GET_CRYPTO(ncch) (!NCCH_ENCRYPTED(ncch) ? NCCH_NOCRYPTO : (((ncch)->flags[3] << 8) | ((ncch)->flags[7]&(0x01|0x20)))) +#define SEEDDB_NAME "seeddb.bin" +#define SEEDDB_SIZE(sdb) (16 + ((sdb)->n_entries * sizeof(SeedInfoEntry))) + +#define SEEDSAVE_AREA_OFFSETS 0x7000, 0x5C000 +#define SEEDSAVE_MAX_ENTRIES 2000 + // wrapper defines #define DecryptNcch(data, offset, size, ncch, exefs) CryptNcch(data, offset, size, ncch, exefs, NCCH_NOCRYPTO) #define EncryptNcch(data, offset, size, ncch, exefs, crypto) CryptNcch(data, offset, size, ncch, exefs, crypto) @@ -70,9 +76,23 @@ typedef struct { u8 hash_romfs[0x20]; } __attribute__((packed, aligned(16))) NcchHeader; +typedef struct { + u64 titleId; + u8 seed[16]; + u8 reserved[8]; +} __attribute__((packed)) SeedInfoEntry; + +typedef struct { + u32 n_entries; + u8 padding[12]; + SeedInfoEntry entries[256]; // this number is only a placeholder +} __attribute__((packed)) SeedInfo; + u32 ValidateNcchHeader(NcchHeader* header); u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid); u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to); u32 CryptNcch(u8* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto); u32 CryptNcchSequential(u8* data, u32 offset, u32 size, u16 crypto); u32 SetNcchSdFlag(u8* data); + +u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry); diff --git a/source/godmode.c b/source/godmode.c index caa52d4..1efaac4 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -1120,12 +1120,14 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar int sdformat = ++n_opt; int bonus = (GetNandUnusedSectors(NAND_SYSNAND) > 0x2000) ? (int) ++n_opt : -1; // 4MB minsize int multi = (CheckMultiEmuNand()) ? (int) ++n_opt : -1; + int bsupport = ++n_opt; int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1; int nandbak = ++n_opt; if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu"; if (bonus > 0) optionstr[bonus - 1] = "Bonus drive setup"; if (multi > 0) optionstr[multi - 1] = "Switch EmuNAND"; + if (bsupport > 0) optionstr[bsupport - 1] = "Build support files"; if (hsrestore > 0) optionstr[hsrestore - 1] = "Restore H&S"; if (nandbak > 0) optionstr[nandbak - 1] = "Backup NAND"; @@ -1162,6 +1164,48 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar } GetDirContents(current_dir, current_path); return 0; + } else if (user_select == bsupport) { // build support files + bool tik_enc_sys = false; + bool tik_enc_emu = false; + if (BuildTitleKeyInfo(NULL, false, false) == 0) { + ShowString("Building " TIKDB_NAME_ENC "..."); + tik_enc_sys = (BuildTitleKeyInfo("1:/dbs/ticket.db", false, false) == 0); + tik_enc_emu = (BuildTitleKeyInfo("4:/dbs/ticket.db", false, false) == 0); + if (BuildTitleKeyInfo(NULL, false, true) != 0) + tik_enc_sys = tik_enc_emu = false; + } + bool tik_dec_sys = false; + bool tik_dec_emu = false; + if (BuildTitleKeyInfo(NULL, true, false) == 0) { + ShowString("Building " TIKDB_NAME_DEC "..."); + tik_dec_sys = (BuildTitleKeyInfo("1:/dbs/ticket.db", true, false) == 0); + tik_dec_emu = (BuildTitleKeyInfo("4:/dbs/ticket.db", true, false) == 0); + if (!tik_dec_sys || BuildTitleKeyInfo(NULL, true, true) != 0) + tik_dec_sys = tik_dec_emu = false; + } + bool seed_sys = false; + bool seed_emu = false; + if (BuildSeedInfo(NULL, false) == 0) { + ShowString("Building " SEEDDB_NAME "..."); + seed_sys = (BuildSeedInfo("1:", false) == 0); + seed_emu = (BuildSeedInfo("4:", false) == 0); + if (!seed_sys || BuildSeedInfo(NULL, true) != 0) + seed_sys = seed_emu = false; + } + bool lsector = false; + u8 legit_sector[0x200]; + if (GetLegitSector0x96(legit_sector) == 0) { + ShowString("Searching secret sector..."); + const char* path_sector = OUTPUT_PATH "/" SECRET_NAME; + lsector = FileSetData(path_sector, legit_sector, 0x200, 0, true); + } + ShowPrompt(false, "Built in " OUTPUT_PATH ":\n \n%18.18-s %s\n%18.18-s %s\n%18.18-s %s\n%18.18-s %s", + TIKDB_NAME_ENC, tik_enc_sys ? tik_enc_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed", + TIKDB_NAME_DEC, tik_dec_sys ? tik_dec_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed", + SEEDDB_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed", + SECRET_NAME, lsector ? "OK (legit)" : "Failed"); + GetDirContents(current_dir, current_path); + return 0; } else if (user_select == hsrestore) { // restore Health & Safety n_opt = 0; int sys = (CheckHealthAndSafetyInject("1:") == 0) ? (int) ++n_opt : -1; diff --git a/source/nand/nand.c b/source/nand/nand.c index f9ab670..9e08c53 100644 --- a/source/nand/nand.c +++ b/source/nand/nand.c @@ -24,6 +24,12 @@ static const u8 slot0x11Key95_sha256[0x20] = { // slot0x11Key95 hash (first 16 b 0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78, 0x6C, 0x78, 0x5F, 0xAC, 0xEC, 0x7E, 0xC0, 0x11, 0x26, 0x9D, 0x4E, 0x47, 0xB3, 0x64, 0xC4, 0xA5 }; + +// from: https://github.com/AuroraWright/SafeA9LHInstaller/blob/master/source/installer.c#L9-L17 +static const u8 sector0x96_sha256[0x20] = { // hash for legit sector 0x96 (different on A9LH) + 0x82, 0xF2, 0x73, 0x0D, 0x2C, 0x2D, 0xA3, 0xF3, 0x01, 0x65, 0xF9, 0x87, 0xFD, 0xCC, 0xAC, 0x5C, + 0xBA, 0xB2, 0x4B, 0x4E, 0x5F, 0x65, 0xC9, 0x81, 0xCD, 0x7B, 0xE6, 0xF4, 0x38, 0xE6, 0xD9, 0xD3 +}; static const u8 nand_magic_n3ds[0x60] = { // NCSD NAND header N3DS magic 0x4E, 0x43, 0x53, 0x44, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -438,6 +444,38 @@ u64 GetNandUnusedSectors(u32 nand_src) return GetNandSizeSectors(nand_src) - NAND_MIN_SECTORS; } +u32 GetLegitSector0x96(u8* sector) +{ + // secret sector already in buffer? + if (sha_cmp(sector0x96_sha256, sector, 0x200, SHA256_MODE) == 0) + return 0; + + // search for valid secret sector in SysNAND / EmuNAND + const u32 nand_src[] = { NAND_SYSNAND, NAND_EMUNAND }; + for (u32 i = 0; i < sizeof(nand_src) / sizeof(u32); i++) { + ReadNandSectors(sector, 0x96, 1, 0x11, nand_src[i]); + if (sha_cmp(sector0x96_sha256, sector, 0x200, SHA256_MODE) == 0) + return 0; + } + + // no luck? try searching for a file + const char* base[] = { INPUT_PATHS }; + for (u32 i = 0; i < (sizeof(base)/sizeof(char*)); i++) { + char path[64]; + snprintf(path, 64, "%s/%s", base[i], SECTOR_NAME); + if ((FileGetData(path, sector, 0x200, 0) == 0x200) && + (sha_cmp(sector0x96_sha256, sector, 0x200, SHA256_MODE) == 0)) + return 0; + snprintf(path, 64, "%s/%s", base[i], SECRET_NAME); + if ((FileGetData(path, sector, 0x200, 0) == 0x200) && + (sha_cmp(sector0x96_sha256, sector, 0x200, SHA256_MODE) == 0)) + return 0; + } + + // failed if we arrive here + return 1; +} + bool CheckMultiEmuNand(void) { // this only checks for the theoretical possibility diff --git a/source/nand/nand.h b/source/nand/nand.h index f0d6735..bd5709a 100644 --- a/source/nand/nand.h +++ b/source/nand/nand.h @@ -35,6 +35,10 @@ #define SIZE_CTR_O3DS 0x17AE80 #define SIZE_CTR_N3DS 0x20F680 +// filenames for sector 0x96 +#define SECTOR_NAME "sector0x96.bin" +#define SECRET_NAME "secret_sector.bin" + bool InitNandCrypto(void); bool CheckSlot0x05Crypto(void); bool CheckSector0x96Crypto(void); @@ -52,6 +56,8 @@ u32 CheckNandMbr(u8* mbr); u32 CheckNandHeader(u8* header); u32 CheckNandType(u32 src); +u32 GetLegitSector0x96(u8* sector); + bool CheckMultiEmuNand(void); u32 InitEmuNandBase(bool reset); u32 GetEmuNandBase(void);