From dd377dbf8e490708cd8269b0cbc90d83aec830b8 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Sat, 8 Apr 2017 14:17:58 +0200 Subject: [PATCH] Enable building titlekey databases --- source/common/common.h | 2 +- source/fs/filetype.c | 7 +++-- source/fs/filetype.h | 9 ++++-- source/game/gameutil.c | 67 ++++++++++++++++++++++++++++++++++++++++++ source/game/gameutil.h | 1 + source/game/ticket.c | 48 +++++++++++++++--------------- source/game/ticket.h | 16 ++++++++++ source/godmode.c | 61 ++++++++++++++++++++++++++++++-------- 8 files changed, 167 insertions(+), 44 deletions(-) diff --git a/source/common/common.h b/source/common/common.h index 7e81e99..40d2747 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -53,7 +53,7 @@ // GodMode9 version -#define VERSION "1.0.8" +#define VERSION "1.0.9" // Maximum payload size (arbitrary value!) #define SELF_MAX_SIZE (320 * 1024) // 320kB diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 23a5938..cf4229c 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -9,6 +9,7 @@ u32 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; const u8 tickdb_magic[] = { TICKDB_MAGIC }; const u8 smdh_magic[] = { SMDH_MAGIC }; + if (!path) return 0; // safety u8 header[0x200] __attribute__((aligned(32))); // minimum required size void* data = (void*) header; size_t fsize = FileGetSize(path); @@ -90,9 +91,11 @@ u32 IdentifyFileType(const char* path) { strncpy(ext_cetk, "cetk", 5); if (FileGetSize(path_cetk) > 0) return GAME_NUSCDN; // NUS/CDN type 2 + } else if (strncasecmp(fname, TIKDB_NAME_ENC, sizeof(TIKDB_NAME_ENC)) == 0) { + return BIN_TIKDB | FLAG_ENC; // titlekey database / encrypted + } else if (strncasecmp(fname, TIKDB_NAME_DEC, sizeof(TIKDB_NAME_DEC)) == 0) { + return BIN_TIKDB; // titlekey database / decrypted } else if ((strncasecmp(fname, "seeddb.bin", 11) == 0) || - (strncasecmp(fname, "encTitlekeys.bin", 17) == 0) || - (strncasecmp(fname, "decTitlekeys.bin", 17) == 0) || (strncasecmp(fname, "aeskeydb.bin", 13) == 0) || (strncasecmp(fname, "otp.bin", 8) == 0) || (strncasecmp(fname, "secret_sector.bin", 18) == 0) || diff --git a/source/fs/filetype.h b/source/fs/filetype.h index d9cd92a..6438fda 100644 --- a/source/fs/filetype.h +++ b/source/fs/filetype.h @@ -19,9 +19,11 @@ #define SYS_TICKDB (1UL<<14) #define BIN_NCCHNFO (1UL<<15) #define BIN_LAUNCH (1UL<<16) -#define BIN_SUPPORT (1UL<<17) +#define BIN_TIKDB (1UL<<17) +#define BIN_SUPPORT (1UL<<18) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types +#define FLAG_ENC (1UL<<28) #define FLAG_CTR (1UL<<29) #define FLAG_NUSCDN (1UL<<30) #define FLAG_CXI (1UL<<31) @@ -30,8 +32,9 @@ #define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM)) #define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM)) #define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS)) -#define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) -#define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD))) +#define FTYPE_CIABUILD(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) +#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD))) +#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) #define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) #define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) diff --git a/source/game/gameutil.c b/source/game/gameutil.c index bd221a5..e8c8fa5 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -1662,3 +1662,70 @@ u32 InjectHealthAndSafety(const char* path, const char* destdrv) { return ret; } + +u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { + TitleKeysInfo* tik_info = (TitleKeysInfo*) MAIN_BUFFER; + const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC; + const char* path_in = path; + UINT br; + + if (!path_in && !dump) { // no input path given - initialize + memset(tik_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; + else return 0; + } + + u32 filetype = path_in ? IdentifyFileType(path_in) : 0; + if (filetype & GAME_TICKET) { + Ticket* ticket = (Ticket*) TEMP_BUFFER; + if ((fvx_qread(path_in, ticket, 0, TICKET_SIZE, &br) != FR_OK) || (br != TICKET_SIZE) || + (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) || (AddTicketToInfo(tik_info, ticket, dec) != 0)) return 1; + } else if (filetype & SYS_TICKDB) { + const u32 area_offsets[] = { TICKDB_AREA_OFFSETS }; + FIL file; + if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; + // parse file, sector by sector + for (u32 p = 0; p < sizeof(area_offsets) / sizeof(u32); p++) { + fvx_lseek(&file, area_offsets[p]); + fvx_sync(&file); + for (u32 i = 0; i < TICKDB_AREA_SIZE; i += (TEMP_BUFFER_SIZE - 0x200)) { + u32 read_bytes = min(TEMP_BUFFER_SIZE, TICKDB_AREA_SIZE - i); + u8* data = (u8*) TEMP_BUFFER; + if ((fvx_read(&file, data, read_bytes, &br) != FR_OK) || (br != read_bytes)) { + fvx_close(&file); + return 1; + } + for (; data + TICKET_SIZE < ((u8*) TEMP_BUFFER) + read_bytes; data += 0x200) { + Ticket* ticket = TicketFromTickDbChunk(data, NULL, false); + if (!ticket || (ticket->commonkey_idx >= 2) || !getbe64(ticket->ticket_id)) continue; + if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1; + AddTicketToInfo(tik_info, ticket, dec); // ignore result + } + } + } + fvx_close(&file); + } else if (filetype & BIN_TIKDB) { + TitleKeysInfo* tik_info_merge = (TitleKeysInfo*) TEMP_BUFFER; + if ((fvx_qread(path_in, tik_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || + (TIKDB_SIZE(tik_info_merge) != br)) return 1; + // merge and rebuild TitleKeyInfo + u32 n_entries = tik_info_merge->n_entries; + TitleKeyEntry* tik = tik_info_merge->entries; + for (u32 i = 0; i < n_entries; i++, tik++) { + if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1; + AddTitleKeyToInfo(tik_info, tik, !(filetype & FLAG_ENC), dec, false); // ignore result + + } + } + + if (dump) { + u32 dump_size = TIKDB_SIZE(tik_info); + f_unlink(path_out); + if ((dump_size <= 16) || (fvx_qwrite(path_out, tik_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 100f334..f112e43 100644 --- a/source/game/gameutil.h +++ b/source/game/gameutil.h @@ -10,3 +10,4 @@ u32 ShowGameFileTitleInfo(const char* path); 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); diff --git a/source/game/ticket.c b/source/game/ticket.c index 932dfdb..b18ed4f 100644 --- a/source/game/ticket.c +++ b/source/game/ticket.c @@ -4,19 +4,6 @@ #include "sha.h" #include "ff.h" -typedef struct { - u32 commonkey_idx; - u8 reserved[4]; - u8 title_id[8]; - u8 titlekey[16]; -} __attribute__((packed)) TitleKeyEntry; - -typedef struct { - u32 n_entries; - u8 reserved[12]; - TitleKeyEntry entries[256]; // this number is only a placeholder -} __attribute__((packed)) TitleKeysInfo; - u32 ValidateTicket(Ticket* ticket) { const u8 magic[] = { TICKET_SIG_TYPE }; if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) || @@ -148,20 +135,31 @@ u32 FindTitleKey(Ticket* ticket, u8* title_id) { return (found) ? 0 : 1; } -/*u32 BuildTitleKeyInfo(TitleKeysInfo* tik_info, TicketInfo* tick_info, bool decrypt) { - memset(tik_info, 0, 16); - for (u32 i = 0; i < tick_info->n_entries; i++) { - TicketEntry* tick_entry = tick_info->entries + i; - TitleKeyEntry* tik_entry = tik_info->entries + tik_info->n_entries; - if (!getbe64(tick_entry->ticket_id)) continue; - tik_entry->commonkey_idx = tick_entry->commonkey_idx; - memcpy(tik_entry->title_id, tick_entry->title_id, 8); - memcpy(tik_entry->titlekey, tick_entry->titlekey, 16); - if (decrypt) CryptTitleKey(tik_entry, false, false); - tik_info->n_entries++; +u32 AddTitleKeyToInfo(TitleKeysInfo* tik_info, TitleKeyEntry* tik_entry, bool decrypted_in, bool decrypted_out, bool devkit) { + if (!tik_entry) { // no titlekey entry -> reset database + memset(tik_info, 0, 16); + return 0; } + // check if entry already in DB + u32 n_entries = tik_info->n_entries; + TitleKeyEntry* tik = tik_info->entries; + for (u32 i = 0; i < n_entries; i++, tik++) + if (memcmp(tik->title_id, tik_entry->title_id, 8) == 0) return 0; + // actually a new titlekey + memcpy(tik, tik_entry, sizeof(TitleKeyEntry)); + if ((decrypted_in != decrypted_out) && (CryptTitleKey(tik, !decrypted_out, devkit) != 0)) return 1; + tik_info->n_entries++; return 0; -}*/ +} + +u32 AddTicketToInfo(TitleKeysInfo* tik_info, Ticket* ticket, bool decrypt) { // TODO: check for legit tickets? + if (!ticket) return AddTitleKeyToInfo(tik_info, NULL, false, false, false); + TitleKeyEntry tik = { 0 }; + memcpy(tik.title_id, ticket->title_id, 8); + memcpy(tik.titlekey, ticket->titlekey, 16); + tik.commonkey_idx = ticket->commonkey_idx; + return AddTitleKeyToInfo(tik_info, &tik, false, decrypt, TICKET_DEVKIT(ticket)); +} u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256 diff --git a/source/game/ticket.h b/source/game/ticket.h index 1678771..7ac4f6b 100644 --- a/source/game/ticket.h +++ b/source/game/ticket.h @@ -13,6 +13,7 @@ #define TIKDB_NAME_ENC "encTitleKeys.bin" #define TIKDB_NAME_DEC "decTitleKeys.bin" +#define TIKDB_SIZE(tdb) (16 + ((tdb)->n_entries * sizeof(TitleKeyEntry))) #define TICKDB_PATH(emu) ((emu) ? "4:/dbs/ticket.db" : "1:/dbs/ticket.db") // EmuNAND / SysNAND #define TICKDB_AREA_OFFSETS 0x0137F000, 0x001C0C00 // second partition is more likely to be in use @@ -56,10 +57,25 @@ typedef struct { u8 content_index[0xAC]; } __attribute__((packed)) Ticket; +typedef struct { + u32 commonkey_idx; + u8 reserved[4]; + u8 title_id[8]; + u8 titlekey[16]; +} __attribute__((packed)) TitleKeyEntry; + +typedef struct { + u32 n_entries; + u8 reserved[12]; + TitleKeyEntry entries[256]; // this number is only a placeholder +} __attribute__((packed)) TitleKeysInfo; + u32 ValidateTicket(Ticket* ticket); u32 GetTitleKey(u8* titlekey, Ticket* ticket); Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls); u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand); u32 FindTitleKey(Ticket* ticket, u8* title_id); +u32 AddTitleKeyToInfo(TitleKeysInfo* tik_info, TitleKeyEntry* tik_entry, bool decrypted_in, bool decrypted_out, bool devkit); +u32 AddTicketToInfo(TitleKeysInfo* tik_info, Ticket* ticket, bool decrypt); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); u32 BuildTicketCert(u8* tickcert); diff --git a/source/godmode.c b/source/godmode.c index 8d65e9a..caa52d4 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -12,6 +12,7 @@ #include "nand.h" #include "virtual.h" #include "vcart.h" +#include "game.h" #include "nandcmac.h" #include "ctrtransfer.h" #include "ncchinfo.h" @@ -651,8 +652,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool decryptable = (FYTPE_DECRYPTABLE(filetype)); bool encryptable = (FYTPE_ENCRYPTABLE(filetype)); bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT)); - bool buildable = (FTYPE_BUILDABLE(filetype)); - bool buildable_legit = (FTYPE_BUILDABLE_L(filetype)); + bool cia_buildable = (FTYPE_CIABUILD(filetype)); + bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); + bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT)); bool hsinjectable = (FTYPE_HSINJECTABLE(filetype)); @@ -660,8 +662,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool ebackupable = (FTYPE_EBACKUP(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT)); - bool special_opt = mountable || verificable || decryptable || encryptable || buildable || buildable_legit || - titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable; + bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || + tik_buildable || titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -697,9 +699,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & GAME_BOSS ) ? "BOSS file options..." : (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : (filetype & GAME_SMDH) ? "Show SMDH title info" : - (filetype & GAME_NDS) ? "Show NDS title info" : + (filetype & GAME_NDS) ? "Show NDS title info" : + (filetype & GAME_TICKET)? "Ticket options..." : (filetype & SYS_FIRM ) ? "FIRM image options..." : - (filetype & SYS_TICKDB) ? "Mount as ticket.db" : + (filetype & SYS_TICKDB) ? (tik_buildable) ? "Ticket.db options..." : "Mount as ticket.db" : + (filetype & BIN_TIKDB) ? "Titlekey options..." : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : (filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???"; optionstr[hexviewer-1] = "Show in Hexeditor"; @@ -795,8 +799,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int ebackup = (ebackupable) ? ++n_opt : -1; int decrypt = (decryptable) ? ++n_opt : -1; int encrypt = (encryptable) ? ++n_opt : -1; - int build = (buildable) ? ++n_opt : -1; - int build_legit = (buildable_legit) ? ++n_opt : -1; + int cia_build = (cia_buildable) ? ++n_opt : -1; + int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1; + int tik_build_enc = (tik_buildable) ? ++n_opt : -1; + int tik_build_dec = (tik_buildable) ? ++n_opt : -1; int verify = (verificable) ? ++n_opt : -1; int ctrtransfer = (transferable) ? ++n_opt : -1; int hsinject = (hsinjectable) ? ++n_opt : -1; @@ -809,8 +815,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur 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 ")"; - if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; - if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)"; + if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; + if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)"; + if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC; + if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC; if (verify > 0) optionstr[verify-1] = "Verify file"; if (ctrtransfer > 0) optionstr[ctrtransfer-1] = "Transfer image to CTRNAND"; if (hsinject > 0) optionstr[hsinject-1] = "Inject to H&S"; @@ -930,8 +938,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur else ShowPrompt(false, "%s\nEncrypted to %s", pathstr, OUTPUT_PATH); } return 0; - } else if ((user_select == build) || (user_select == build_legit)) { // -> build CIA - bool force_legit = (user_select == build_legit); + } else if ((user_select == cia_build) || (user_select == cia_build_legit)) { // -> build CIA + bool force_legit = (user_select == cia_build_legit); if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) { u32 n_success = 0; u32 n_other = 0; @@ -998,6 +1006,33 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed"); } return 0; + } else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database + bool dec = (user_select == tik_build_dec); + const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC; + if (BuildTitleKeyInfo(NULL, dec, false) != 0) return 1; // init database + ShowString("Building %s...", (dec) ? TIKDB_NAME_DEC : TIKDB_NAME_ENC); + if (n_marked > 1) { + u32 n_success = 0; + u32 n_other = 0; + for (u32 i = 0; i < current_dir->n_entries; i++) { + const char* path = current_dir->entry[i].path; + if (!current_dir->entry[i].marked) + continue; + if (!FTYPE_TIKBUILD(IdentifyFileType(path))) { + n_other++; + continue; + } + current_dir->entry[i].marked = false; + if (BuildTitleKeyInfo(path, dec, false) == 0) n_success++; // ignore failures for now + } + if (BuildTitleKeyInfo(NULL, dec, true) == 0) { + if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored", + path_out, n_success, n_marked, n_other, n_marked); + else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked); + } else ShowPrompt(false, "%s\nBuild database failed."); + } else ShowPrompt(false, "%s\nBuild database %s.", path_out, + (BuildTitleKeyInfo(curr_entry->path, dec, true) == 0) ? "success" : "failed"); + return 0; } else if (user_select == show_info) { // -> Show title info if (ShowGameFileTitleInfo(curr_entry->path) != 0) ShowPrompt(false, "Title info: not found"); @@ -1019,7 +1054,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (InjectHealthAndSafety(curr_entry->path, destdrv[user_select-1]) == 0) ? "success" : "failed"); } return 0; - } else if (user_select == ctrtransfer) { // -> adapt CTRNAND image to SysNAND + } else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND char* destdrv[2] = { NULL }; n_opt = 0; if (DriveType("1:")) {