From 8fa85437dd6d7696f9d8690e9f4d2524fedeba48 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Mon, 8 Mar 2021 23:49:21 +0100 Subject: [PATCH] Added ability to dump tickets in title manager --- arm9/source/filesys/filetype.h | 1 + arm9/source/godmode.c | 42 +++++++++++++++- arm9/source/utils/gameutil.c | 87 +++++++++++++++++++++++++++------- arm9/source/utils/gameutil.h | 1 + 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 6be13f2..99927be 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -54,6 +54,7 @@ #define FTYPE_CIABUILD_L(tp) (tp&(GAME_TMD|GAME_CDNTMD|GAME_TIE|GAME_TAD)) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_TIKINSTALL(tp) (tp&(GAME_TICKET)) +#define FTYPE_TIKDUMP(tp) (tp&(GAME_TIE)) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD)) #define FTYPE_UNINSTALL(tp) (tp&(GAME_TIE)) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 57df350..8311074 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1095,6 +1095,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool cia_installable = (FTYPE_CIAINSTALL(filetype)) && !(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS) && !(drvtype & DRV_IMAGE); bool tik_installable = (FTYPE_TIKINSTALL(filetype)); + bool tik_dumpable = (FTYPE_TIKDUMP(filetype)); bool uninstallable = (FTYPE_UNINSTALL(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; @@ -1138,7 +1139,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || trimable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || fontable || viewable || installable || agbexportable || - agbimportable || cia_installable || tik_installable; + agbimportable || cia_installable || tik_installable || tik_dumpable; char pathstr[32+1]; TruncateString(pathstr, file_path, 32, 8); @@ -1338,6 +1339,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int cxi_dump = (cxi_dumpable) ? ++n_opt : -1; int cia_install = (cia_installable) ? ++n_opt : -1; int tik_install = (tik_installable) ? ++n_opt : -1; + int tik_dump = (tik_dumpable) ? ++n_opt : -1; int uninstall = (uninstallable) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1; @@ -1373,6 +1375,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file"; if (cia_install > 0) optionstr[cia_install-1] = "Install game image"; if (tik_install > 0) optionstr[tik_install-1] = "Install ticket"; + if (tik_dump > 0) optionstr[tik_dump-1] = "Dump ticket file"; if (uninstall > 0) optionstr[uninstall-1] = "Uninstall title"; 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; @@ -1712,7 +1715,42 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan } return 0; } - else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database + else if (user_select == tik_dump) { // dump ticket file + if ((n_marked > 1) && ShowPrompt(true, "Dump for all %lu selected files?", n_marked)) { + u32 n_success = 0; + u32 n_legit = 0; + bool force_legit = true; + for (u32 n_processed = 0;; n_processed = 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 (!ShowProgress(n_processed++, n_marked, path)) break; + DrawDirContents(current_dir, (*cursor = i), scroll); + if (DumpTicketForGameFile(path, force_legit) == 0) n_success++; + else if (IdentifyFileType(path) & filetype & TYPE_BASE) continue; + if (force_legit) n_legit++; + current_dir->entry[i].marked = false; + } + if (force_legit && (n_success != n_marked)) + if (!ShowPrompt(true, "%lu/%lu legit tickets dumped.\n \nAttempt to dump all tickets?", + n_legit, n_marked)) break; + if (!force_legit) break; + force_legit = false; + } + ShowPrompt(false, "%lu/%lu tickets dumped to %s", + n_success, n_marked, OUTPUT_PATH); + } else { + if (DumpTicketForGameFile(file_path, true) == 0) { + ShowPrompt(false, "%s\nTicket dumped to %s", pathstr, OUTPUT_PATH); + } else if (ShowPrompt(false, "%s\nLegit ticket not found.\n \nDump anyways?", pathstr)) { + if (DumpTicketForGameFile(file_path, false) == 0) + ShowPrompt(false, "%s\nTicket dumped to %s", pathstr, OUTPUT_PATH); + else ShowPrompt(false, "%s\nDump ticket 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 diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index bb7458b..2b6d216 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -295,6 +295,35 @@ u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { return LoadTicketFile(ticket, path_cetk); } +u32 LoadTicketForTitleId(Ticket** ticket, const u64 title_id) { + u8 tid[8]; + for (u32 i = 0; i < 8; i++) + tid[7-i] = (title_id >> (i*8)) & 0xFF; + + // ensure remounting the old mount path + char path_store[256] = { 0 }; + char* path_bak = NULL; + strncpy(path_store, GetMountPath(), 256); + if (*path_store) path_bak = path_store; + + // path to ticket.db + char path_ticketdb[32]; + char* drv = path_store; + snprintf(path_ticketdb, 32, "%2.2s/dbs/ticket.db", + ((*drv == 'A') || (*drv == '2')) ? "1:" : + ((*drv == 'B') || (*drv == '5')) ? "4:" : drv); + + // load ticket + if (!InitImgFS(path_ticketdb) || + ((ReadTicketFromDB(PART_PATH, tid, ticket)) != 0)) + *ticket = NULL; + + // remount old path + InitImgFS(path_bak); + + return (*ticket) ? 0 : 1; +} + u32 GetTmdContentPath(char* path_content, const char* path_tmd) { // path_content should be 256 bytes in size! @@ -2699,6 +2728,9 @@ u64 GetGameFileTitleId(const char* path) { TwlHeader twl; if ((fvx_qread(path, &twl, 0, sizeof(TwlHeader), NULL) == FR_OK) && (twl.unit_code & 0x02)) tid64 = twl.title_id; + } else if (filetype & GAME_TIE) { + if ((*path == 'T') && (sscanf(path, "T:/%016llx", &tid64) != 1)) + tid64 = 0; } if ((tid64 & 0xFFFFFF0000000000ull) == 0x0003000000000000ull) @@ -2890,6 +2922,42 @@ u32 InstallTicketFile(const char* path, bool to_emunand) { return 0; } +u32 DumpTicketForGameFile(const char* path, bool force_legit) { + u64 tid64 = GetGameFileTitleId(path); + if (!tid64) return 1; + + Ticket* ticket; + if (LoadTicketForTitleId(&ticket, tid64) != 0) + return 1; + + if ((ValidateTicket(ticket) != 0) || + (force_legit && (ValidateTicketSignature(ticket) != 0))) { + free(ticket); + return 1; + } + + // build output name + char dest[256]; + snprintf(dest, 256, OUTPUT_PATH "/"); + char* dname = dest + strnlen(dest, 256); + if (GetGoodName(dname, path, false) != 0) + snprintf(dest, 256, "%s/%016llX", OUTPUT_PATH, tid64); + + // replace extension + char* dot = strrchr(dest, '.'); + if (!dot || (dot < strrchr(dest, '/'))) + dot = dest + strnlen(dest, 256); + snprintf(dot, 16, ".%s", force_legit ? "legit.tik" : "tik"); + + // dump ticket + if (!CheckWritePermissions(dest)) return 1; + f_unlink(dest); // remove the file if it already exists + fvx_qwrite(dest, ticket, 0, GetTicketSize(ticket), NULL); + + free(ticket); + return 0; +} + // this has very limited uses right now u32 DumpCxiSrlFromTmdFile(const char* path) { u64 filetype = 0; @@ -3294,25 +3362,10 @@ u32 ShowCiaTieCheckerInfo(const char* path) { tmd = NULL; } else content_found = content_count = getbe16(tmd->content_count); - // ticket needs some preparation - // ensure remounting the old mount path - char path_store[256] = { 0 }; - char* path_bak = NULL; - strncpy(path_store, GetMountPath(), 256); - if (*path_store) path_bak = path_store; - - char path_ticketdb[32]; - char* drv = path_store; - snprintf(path_ticketdb, 32, "%2.2s/dbs/ticket.db", - ((*drv == 'A') || (*drv == '2')) ? "1:" : - ((*drv == 'B') || (*drv == '5')) ? "4:" : drv); - // load ticket - if (!InitImgFS(path_ticketdb) || - ((ReadTicketFromDB(PART_PATH, tmd->title_id, &ticket)) != 0)) + u64 tid64 = tmd ? getbe64(tmd->title_id) : 0; + if (LoadTicketForTitleId(&ticket, tid64) != 0) ticket = NULL; - - InitImgFS(path_bak); } else if (type & GAME_CIA) { CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) { diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index 9300415..1f6c226 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -8,6 +8,7 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 InstallGameFile(const char* path, bool to_emunand); u32 InstallTicketFile(const char* path, bool to_emunand); +u32 DumpTicketForGameFile(const char* path, bool force_legit); u32 DumpCxiSrlFromTmdFile(const char* path); u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr); u32 CompressCode(const char* path, const char* path_out);