From 667a1bf2c050102b74bf61ff5b1c3c11481417b5 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Sat, 6 Feb 2021 14:47:40 +0100 Subject: [PATCH] Install, verify, info handling for tickets --- arm9/source/filesys/filetype.h | 5 +- arm9/source/godmode.c | 13 ++- arm9/source/utils/gameutil.c | 179 +++++++++++++++++++++++++++++---- arm9/source/utils/gameutil.h | 1 + 4 files changed, 172 insertions(+), 26 deletions(-) diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 47ba861..ac42d5d 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -46,17 +46,18 @@ #define FLAG_CXI (1ULL<<63) #define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|GAME_NDS|GAME_TAD|SYS_FIRM|SYS_DIFF|SYS_DISA|SYS_TICKDB|BIN_KEYDB)) -#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_BOSS|SYS_FIRM)) +#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_TICKET|GAME_BOSS|SYS_FIRM)) #define FTYPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB)) #define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB)) #define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) #define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD|GAME_TIE))) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))) || (tp&(GAME_TMD)&&(tp&(FLAG_NUSCDN)))) +#define FTYPE_TIKINSTALL(tp) (tp&(GAME_TICKET)) #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)) #define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) -#define FTYPE_TITLEINFO(tp) (tp&(GAME_TIE|GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX)) +#define FTYPE_TITLEINFO(tp) (tp&(GAME_TIE|GAME_TICKET|GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX)) #define FTYPE_CIACHECK(tp) (tp&GAME_CIA) #define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS|GAME_GBA)) #define FTYPE_TRIMABLE(tp) (tp&(IMG_NAND|GAME_NCCH|GAME_NCSD|GAME_NDS|SYS_FIRM)) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index eb3966c..c901ee1 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_buildable_legit = (FTYPE_CIABUILD_L(filetype)); 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 uninstallable = (FTYPE_UNINSTALL(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; @@ -1330,6 +1331,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1; 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 uninstall = (uninstallable) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1; @@ -1364,6 +1366,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)"; 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 (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; @@ -1572,7 +1575,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan } return 0; } - else if (user_select == cia_install) { // -> install game file + else if ((user_select == cia_install) || (user_select == tik_install)) { // -> install game/ticket file + bool install_tik = (user_select == tik_install); bool to_emunand = false; if (CheckVirtualDrive("E:")) { optionstr[0] = "Install to SysNAND"; @@ -1595,7 +1599,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan continue; } DrawDirContents(current_dir, (*cursor = i), scroll); - if (InstallGameFile(path, to_emunand) == 0) n_success++; + if ((!install_tik && (InstallGameFile(path, to_emunand) == 0)) || + (install_tik && (InstallTicketFile(path, to_emunand) == 0))) + n_success++; else { // on failure: show error, continue char lpathstr[32+1]; TruncateString(lpathstr, path, 32, 8); @@ -1609,7 +1615,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan n_success, n_marked, n_other, n_marked); } else ShowPrompt(false, "%lu/%lu files installed ok", n_success, n_marked); } else { - u32 ret = InstallGameFile(file_path, to_emunand); + u32 ret = install_tik ? InstallTicketFile(file_path, to_emunand) : + InstallGameFile(file_path, to_emunand); ShowPrompt(false, "%s\nInstall %s", pathstr, (ret == 0) ? "success" : "failed"); if ((ret != 0) && (filetype & (GAME_NCCH|GAME_NCSD)) && ShowPrompt(true, "%s\nfile failed install.\n \nVerify now?", pathstr)) { diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 7e63440..9ed2400 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -205,8 +205,30 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { return 0; } -u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { +u32 LoadTicketFile(Ticket** ticket, const char* path_tik) { if(!ticket) return 1; + + // load and check ticket + TicketMinimum tmp; + UINT br; + if ((fvx_qread(path_tik, &tmp, 0, TICKET_MINIMUM_SIZE, &br) != FR_OK) || (br != TICKET_MINIMUM_SIZE) || + ValidateTicket((Ticket*)&tmp) != 0) return 1; + + u32 tik_size = GetTicketSize((Ticket*)&tmp); + + Ticket* tik = (Ticket*)malloc(tik_size); + if (!tik) return 1; + + if ((fvx_qread(path_tik, tik, 0, tik_size, &br) != FR_OK) || (br != tik_size)) { + free(tik); + return 1; + } + + *ticket = tik; + return 0; +} + +u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { // path points to CDN content file char path_cetk[256]; strncpy(path_cetk, path_cnt, 256); @@ -216,25 +238,8 @@ u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { char* ext_cetk = strrchr(++name_cetk, '.'); ext_cetk = (ext_cetk) ? ext_cetk + 1 : name_cetk; snprintf(ext_cetk, 256 - (ext_cetk - path_cetk), "cetk"); - - // load and check ticket - TicketMinimum tmp; - UINT br; - if ((fvx_qread(path_cetk, &tmp, 0, TICKET_MINIMUM_SIZE, &br) != FR_OK) || (br != TICKET_MINIMUM_SIZE) || - ValidateTicket((Ticket*)&tmp) != 0) return 1; - - u32 tik_size = GetTicketSize((Ticket*)&tmp); - - Ticket* tik = (Ticket*)malloc(tik_size); - if (!tik) return 1; - - if ((fvx_qread(path_cetk, tik, 0, tik_size, &br) != FR_OK) || (br != tik_size)) { - free(tik); - return 1; - } - - *ticket = tik; - return 0; + // ticket is loaded and vaildated here + return LoadTicketFile(ticket, path_cetk); } u32 GetTmdContentPath(char* path_content, const char* path_tmd) { @@ -316,6 +321,52 @@ u32 GetTieContentPath(char* path_content, const char* path_tie) { return GetTmdContentPath(path_content, path_tmd); } +u32 GetTitleIdTmdPath(char* path_tmd, const u64 title_id, bool from_emunand) { + u32 tid_high = (u32) ((title_id >> 32) & 0xFFFFFFFF); + u32 tid_low = (u32) (title_id & 0xFFFFFFFF); + char* drv = from_emunand ? + ((tid_high & 0x8000) ? "5:" : ((tid_high & 0x10) ? "4:" : "B:")) : + ((tid_high & 0x8000) ? "2:" : ((tid_high & 0x10) ? "1:" : "A:")); + if (tid_high & 0x8000) tid_high = 0x00030000 | (tid_high&0xFF); + + char path_pat[64]; + snprintf(path_pat, 64, "%2.2s/title/%08lX/%08lX/content/*.tmd", + drv, tid_high, tid_low); + + if (fvx_findpath(path_tmd, path_pat, FN_HIGHEST) != FR_OK) + return 1; + + // done + return 0; +} + +u32 GetTicketContentPath(char* path_content, const char* path_tik) { + char path_tmd[256]; + bool from_emunand = false; + u64 tid64 = 0; + + // available for all tickets, but will fail for titles not installed + + // from mounted ticket.db? + if (*path_tik == 'T') { + const char* mntpath = GetMountPath(); + from_emunand = (mntpath && (*mntpath == '4')); + } + + // load ticket, get title id + Ticket ticket; + if (fvx_qread(path_tik, &ticket, 0, sizeof(Ticket), NULL) != FR_OK) + return 1; + tid64 = getbe64(ticket.title_id); + + // get the TMD path + if (GetTitleIdTmdPath(path_tmd, tid64, from_emunand) != 0) + return 1; + + // let the TMD content path function take over + return GetTmdContentPath(path_content, path_tmd); +} + u32 WriteCiaStub(CiaStub* stub, const char* path) { FIL file; UINT btw; @@ -846,6 +897,18 @@ u32 VerifyBossFile(const char* path) { return 0; } +u32 VerifyTicketFile(const char* path) { + // load ticket + Ticket* ticket; + if (LoadTicketFile(&ticket, path) != 0) + return 1; + + // ticket verification is strict, fake-signed tickets are discarded + u32 res = ValidateTicketSignature(ticket); + free(ticket); + return res; +} + u32 VerifyGameFile(const char* path) { u64 filetype = IdentifyFileType(path); if (filetype & GAME_CIA) @@ -862,6 +925,8 @@ u32 VerifyGameFile(const char* path) { return VerifyBossFile(path); else if (filetype & SYS_FIRM) return VerifyFirmFile(path); + else if (filetype & GAME_TICKET) + return VerifyTicketFile(path); else return 1; } @@ -2416,6 +2481,71 @@ u32 InstallGameFile(const char* path, bool to_emunand) { return ret; } +u32 InstallTicketFile(const char* path, bool to_emunand) { + // sanity check + if (!(IdentifyFileType(path) & GAME_TICKET)) + return 1; + + // path string + char pathstr[32 + 1]; + TruncateString(pathstr, path, 32, 8); + + // check ticket db + char path_ticketdb[32]; + if ((GetInstallDbsPath(path_ticketdb, to_emunand ? "4:" : "1:", "ticket.db") != 0) || !fvx_qsize(path_ticketdb)) { + ShowPrompt(false, "Install error:\nThis system is missing the\nticket.db file."); + return 1; + } + + // load & verify ticket + Ticket* ticket; + if (LoadTicketFile(&ticket, path) != 0) + return 1; + if (ValidateTicketSignature(ticket) != 0) { + ShowPrompt(false, "%s\nError: Fake-signed ticket\n \nOnly valid signed tickets can\nbe installed to the system.", pathstr); + free(ticket); + return 1; + } + + // check ticket console id + u32 cid = getbe32(ticket->console_id); + if (cid && (cid != (&ARM9_ITCM->otp)->deviceId)) { + ShowPrompt(false, "%s\nError: Unknown cid %08lX\n \nThis ticket does not belong to\nthis 3DS console.", pathstr, cid); + free(ticket); + return 1; + } + + // check permissions for SysNAND + if (!CheckWritePermissions(to_emunand ? "4:" : "1:")) { + free(ticket); + return 1; + } + + // let the user know we're working + ShowString("%s\nInstalling ticket...\n", pathstr); + + // write ticket database + // 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; + + // ticket database + if (!InitImgFS(path_ticketdb) || + ((AddTicketToDB(PART_PATH, ticket->title_id, (Ticket*) ticket, true)) != 0)) { + InitImgFS(path_bak); + free(ticket); + return 1; + } + + // restore old mount path + InitImgFS(path_bak); + + free(ticket); + return 0; +} + // this has very limited uses right now u32 DumpCxiSrlFromTmdFile(const char* path) { u64 filetype = 0; @@ -2648,6 +2778,10 @@ u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) { char path_content[256]; if (GetTieContentPath(path_content, path) != 0) return 1; return LoadSmdhFromGameFile(path_content, smdh); + } else if (filetype & GAME_TICKET) { + char path_content[256]; + if (GetTicketContentPath(path_content, path) != 0) return 1; + return LoadSmdhFromGameFile(path_content, smdh); } else if (filetype & GAME_3DSX) { ThreedsxHeader threedsx; if ((fvx_qread(path, &threedsx, 0, sizeof(ThreedsxHeader), NULL) != FR_OK) || @@ -2709,6 +2843,9 @@ u32 ShowGameFileTitleInfoF(const char* path, u16* screen, bool clear) { } else if (itype & GAME_TIE) { if (GetTieContentPath(path_content, path) != 0) return 1; path = path_content; + } else if (itype & GAME_TICKET) { + if (GetTicketContentPath(path_content, path) != 0) return 1; + path = path_content; } void* buffer = (void*) malloc(max(sizeof(Smdh), sizeof(TwlIconData))); @@ -2741,7 +2878,7 @@ u32 ShowCiaCheckerInfo(const char* path) { CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) return 1; - // path string + // path string char pathstr[32 + 1]; TruncateString(pathstr, path, 32, 8); diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index bed434e..56f36ca 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -7,6 +7,7 @@ u32 CheckEncryptedGameFile(const char* path); 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 DumpCxiSrlFromTmdFile(const char* path); u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr); u32 CompressCode(const char* path, const char* path_out);