diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index a3dc707..aa4f483 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -86,9 +86,12 @@ u64 IdentifyFileType(const char* path) { return GAME_ROMFS; // RomFS file (check could be better) } else if (ValidateTmd((TitleMetaData*) data) == 0) { if (fsize == TMD_SIZE_N(getbe16(header + 0x1DE)) + TMD_CDNCERT_SIZE) - return GAME_TMD | FLAG_NUSCDN; // TMD file from NUS/CDN + return GAME_CDNTMD; // TMD file from NUS/CDN else if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE))) return GAME_TMD; // TMD file + } else if (ValidateTwlTmd((TitleMetaData*) data) == 0) { + if (fsize == TMD_SIZE_TWL + TMD_CDNCERT_SIZE) + return GAME_TWLTMD; // TMD file from NUS/CDN (TWL) } else if (ValidateTicket((Ticket*) data) == 0) { return GAME_TICKET; // Ticket file } else if (ValidateFirmHeader((FirmHeader*) data, fsize) == 0) { @@ -143,14 +146,6 @@ u64 IdentifyFileType(const char* path) { } else if ((strncasecmp(ext, "png", 4) == 0) && (fsize > sizeof(png_magic)) && (memcmp(data, png_magic, sizeof(png_magic)) == 0)) { return GFX_PNG; - } else if ((strncasecmp(ext, "cdn", 4) == 0) || (strncasecmp(ext, "nus", 4) == 0)) { - char path_cetk[256]; - char* ext_cetk = path_cetk + (ext - path); - strncpy(path_cetk, path, 256); - path_cetk[255] = '\0'; - 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)+1) == 0) { return BIN_TIKDB | FLAG_ENC; // titlekey database / encrypted } else if (strncasecmp(fname, TIKDB_NAME_DEC, sizeof(TIKDB_NAME_DEC)+1) == 0) { @@ -176,12 +171,12 @@ u64 IdentifyFileType(const char* path) { char* name_cdn = path_cdn + (fname - path); strncpy(path_cdn, path, 256); path_cdn[255] = '\0'; - strncpy(name_cdn, "tmd", 4); + strncpy(name_cdn, "tmd", 4); // this will not catch tmd with version if (FileGetSize(path_cdn) > 0) - return GAME_NUSCDN; // NUS/CDN type 1 + return GAME_NUSCDN; // NUS/CDN, recognized by TMD strncpy(name_cdn, "cetk", 5); if (FileGetSize(path_cdn) > 0) - return GAME_NUSCDN; // NUS/CDN type 1 + return GAME_NUSCDN; // NUS/CDN, recognized by CETK } return 0; diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 6384c0e..05453f5 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -8,50 +8,51 @@ #define GAME_NCSD (1ULL<<3) #define GAME_NCCH (1ULL<<4) #define GAME_TMD (1ULL<<5) -#define GAME_CMD (1ULL<<6) -#define GAME_EXEFS (1ULL<<7) -#define GAME_ROMFS (1ULL<<8) -#define GAME_BOSS (1ULL<<9) -#define GAME_NUSCDN (1ULL<<10) -#define GAME_TICKET (1ULL<<11) -#define GAME_TIE (1ULL<<12) -#define GAME_SMDH (1ULL<<13) -#define GAME_3DSX (1ULL<<14) -#define GAME_NDS (1ULL<<15) -#define GAME_GBA (1ULL<<16) -#define GAME_TAD (1ULL<<17) -#define SYS_FIRM (1ULL<<18) -#define SYS_DIFF (1ULL<<19) -#define SYS_DISA (1ULL<<20) -#define SYS_AGBSAVE (1ULL<<21) -#define SYS_TICKDB (1ULL<<22) -#define BIN_NCCHNFO (1ULL<<23) -#define BIN_TIKDB (1ULL<<24) -#define BIN_KEYDB (1ULL<<25) -#define BIN_LEGKEY (1ULL<<26) -#define TXT_SCRIPT (1ULL<<27) -#define TXT_GENERIC (1ULL<<28) -#define GFX_PNG (1ULL<<29) -#define FONT_PBM (1ULL<<30) -#define NOIMG_NAND (1ULL<<31) -#define HDR_NAND (1ULL<<32) +#define GAME_CDNTMD (1ULL<<6) +#define GAME_TWLTMD (1ULL<<7) +#define GAME_CMD (1ULL<<8) +#define GAME_EXEFS (1ULL<<9) +#define GAME_ROMFS (1ULL<<10) +#define GAME_BOSS (1ULL<<11) +#define GAME_NUSCDN (1ULL<<12) +#define GAME_TICKET (1ULL<<13) +#define GAME_TIE (1ULL<<14) +#define GAME_SMDH (1ULL<<15) +#define GAME_3DSX (1ULL<<16) +#define GAME_NDS (1ULL<<17) +#define GAME_GBA (1ULL<<18) +#define GAME_TAD (1ULL<<19) +#define SYS_FIRM (1ULL<<20) +#define SYS_DIFF (1ULL<<21) +#define SYS_DISA (1ULL<<22) +#define SYS_AGBSAVE (1ULL<<23) +#define SYS_TICKDB (1ULL<<24) +#define BIN_NCCHNFO (1ULL<<25) +#define BIN_TIKDB (1ULL<<26) +#define BIN_KEYDB (1ULL<<27) +#define BIN_LEGKEY (1ULL<<28) +#define TXT_SCRIPT (1ULL<<29) +#define TXT_GENERIC (1ULL<<30) +#define GFX_PNG (1ULL<<31) +#define FONT_PBM (1ULL<<32) +#define NOIMG_NAND (1ULL<<33) +#define HDR_NAND (1ULL<<34) #define TYPE_BASE 0xFFFFFFFFFFULL // 40 bit reserved for base types -// #define FLAG_FIRM (1ULL<<57) // <--- for CXIs containing FIRMs -// #define FLAG_GBAVC (1ULL<<58) // <--- for GBAVC CXIs -#define FLAG_DSIW (1ULL<<59) -#define FLAG_ENC (1ULL<<60) -#define FLAG_CTR (1ULL<<61) -#define FLAG_NUSCDN (1ULL<<62) +// #define FLAG_FIRM (1ULL<<58) // <--- for CXIs containing FIRMs +// #define FLAG_GBAVC (1ULL<<59) // <--- for GBAVC CXIs +#define FLAG_DSIW (1ULL<<60) +#define FLAG_ENC (1ULL<<61) +#define FLAG_CTR (1ULL<<62) #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_TAD|GAME_TICKET|GAME_BOSS|SYS_FIRM)) +#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_CDNTMD|GAME_TWLTMD|GAME_TIE|GAME_TAD|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|GAME_TAD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) -#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD|GAME_TIE|GAME_TAD))) -#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_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_CDNTMD|GAME_TWLTMD|GAME_TIE|GAME_TAD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW)))) +#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_CXIDUMP(tp) (tp&(GAME_TMD)) #define FTYPE_UNINSTALL(tp) (tp&(GAME_TIE)) diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index c388192..ccb427c 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -19,6 +19,14 @@ u32 ValidateTicket(Ticket* ticket) { return 0; } +u32 ValidateTwlTicket(Ticket* ticket) { + static const u8 magic[] = { TICKET_SIG_TYPE_TWL }; + if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) || + (strncmp((char*) ticket->issuer, TICKET_ISSUER_TWL, 0x40) != 0)) + return 1; + return 0; +} + u32 ValidateTicketSignature(Ticket* ticket) { static bool got_modexp = false; static u32 mod[0x100 / 0x4] = { 0 }; diff --git a/arm9/source/game/ticket.h b/arm9/source/game/ticket.h index 25c99cb..34a4b01 100644 --- a/arm9/source/game/ticket.h +++ b/arm9/source/game/ticket.h @@ -4,12 +4,16 @@ #define TICKET_COMMON_SIZE sizeof(TicketCommon) #define TICKET_MINIMUM_SIZE sizeof(TicketMinimum) +#define TICKET_TWL_SIZE sizeof(Ticket) #define TICKET_CDNCERT_SIZE 0x700 #define TICKET_ISSUER "Root-CA00000003-XS0000000c" #define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" #define TICKET_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 +#define TICKET_ISSUER_TWL "Root-CA00000001-XS00000006" +#define TICKET_SIG_TYPE_TWL 0x00, 0x01, 0x00, 0x01 // RSA_2048 SHA1 + #define TICKET_DEVKIT(tick) (strncmp((char*)tick->issuer, TICKET_ISSUER_DEV, 0x40) == 0) // from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 @@ -91,6 +95,7 @@ typedef struct { } TicketRightsCheck; u32 ValidateTicket(Ticket* ticket); +u32 ValidateTwlTicket(Ticket* ticket); u32 ValidateTicketSignature(Ticket* ticket); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); u32 GetTicketContentIndexSize(const Ticket* ticket); diff --git a/arm9/source/game/ticketdb.c b/arm9/source/game/ticketdb.c index 743a635..ac55f13 100644 --- a/arm9/source/game/ticketdb.c +++ b/arm9/source/game/ticketdb.c @@ -26,15 +26,21 @@ u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt, bool devkit) { {0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4 {0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5 }; + // From unknown source + static const u8 common_key_twl[16] __attribute__((aligned(16))) = + {0xAF, 0x1B, 0xF5, 0x16, 0xA8, 0x07, 0xD2, 0x1A, 0xEA, 0x45, 0x98, 0x4F, 0x04, 0x74, 0x28, 0x61}; // TWL u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE; u8 ctr[16] = { 0 }; - // setup key 0x3D // ctr - if (tik->commonkey_idx >= 6) return 1; - if (!devkit) setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]); - else setup_aeskey(0x3D, (void*) common_key_dev[tik->commonkey_idx]); - use_aeskey(0x3D); + if (getbe16(tik->title_id) == 0x3) { // setup TWL key + setup_aeskey(0x11, (void*) common_key_twl); + use_aeskey(0x11); + } else { // setup key 0x3D // ctr + if (!devkit) setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]); + else setup_aeskey(0x3D, (void*) common_key_dev[tik->commonkey_idx]); + use_aeskey(0x3D); + } memcpy(ctr, tik->title_id, 8); set_ctr(ctr); @@ -54,6 +60,17 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) { return 0; } +u32 SetTitleKey(const u8* titlekey, Ticket* ticket) { + TitleKeyEntry tik = { 0 }; + memcpy(tik.title_id, ticket->title_id, 8); + memcpy(tik.titlekey, titlekey, 16); + tik.commonkey_idx = ticket->commonkey_idx; + + if (CryptTitleKey(&tik, true, TICKET_DEVKIT(ticket)) != 0) return 1; + memcpy(ticket->titlekey, tik.titlekey, 16); + return 0; +} + u32 FindTicket(Ticket** ticket, u8* title_id, bool force_legit, bool emunand) { const char* path_db = TICKDB_PATH(emunand); // EmuNAND / SysNAND char path_store[256] = { 0 }; @@ -141,3 +158,9 @@ u32 AddTicketToInfo(TitleKeysInfo* tik_info, Ticket* ticket, bool decrypt) { // tik.commonkey_idx = ticket->commonkey_idx; return AddTitleKeyToInfo(tik_info, &tik, false, decrypt, TICKET_DEVKIT(ticket)); } + +u32 CryptTitleKeyInfo(TitleKeysInfo* tik_info, bool encrypt) { + for (u32 t = 0; t < tik_info->n_entries; t++) + CryptTitleKey(tik_info->entries + t, encrypt, false); + return 0; +} diff --git a/arm9/source/game/ticketdb.h b/arm9/source/game/ticketdb.h index 4684168..9101cab 100644 --- a/arm9/source/game/ticketdb.h +++ b/arm9/source/game/ticketdb.h @@ -35,7 +35,9 @@ typedef struct { u32 GetTitleKey(u8* titlekey, Ticket* ticket); +u32 SetTitleKey(const u8* titlekey, Ticket* ticket); 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 CryptTitleKeyInfo(TitleKeysInfo* tik_info, bool encrypt); diff --git a/arm9/source/game/tmd.c b/arm9/source/game/tmd.c index 25c7c47..d030ecf 100644 --- a/arm9/source/game/tmd.c +++ b/arm9/source/game/tmd.c @@ -14,6 +14,15 @@ u32 ValidateTmd(TitleMetaData* tmd) { return 0; } +u32 ValidateTwlTmd(TitleMetaData* tmd) { + static const u8 magic[] = { TMD_SIG_TYPE_TWL }; + if ((memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) || + (strncmp((char*) tmd->issuer, TMD_ISSUER_TWL, 0x40) != 0) || + (getbe16(tmd->content_count) != 1)) + return 1; + return 0; +} + u32 ValidateTmdSignature(TitleMetaData* tmd) { static bool got_modexp = false; static u32 mod[0x100 / 4] = { 0 }; diff --git a/arm9/source/game/tmd.h b/arm9/source/game/tmd.h index 8baa5b8..00baac0 100644 --- a/arm9/source/game/tmd.h +++ b/arm9/source/game/tmd.h @@ -7,12 +7,17 @@ #define TMD_SIZE_MIN sizeof(TitleMetaData) #define TMD_SIZE_MAX (sizeof(TitleMetaData) + (TMD_MAX_CONTENTS*sizeof(TmdContentChunk))) #define TMD_SIZE_N(n) (sizeof(TitleMetaData) + (n*sizeof(TmdContentChunk))) +#define TMD_SIZE_STUB (TMD_SIZE_MIN - (0x20 + (64 * sizeof(TmdContentInfo)))) +#define TMD_SIZE_TWL (TMD_SIZE_STUB + 0x24) #define TMD_CDNCERT_SIZE 0x700 #define TMD_ISSUER "Root-CA00000003-CP0000000b" #define TMD_ISSUER_DEV "Root-CA00000004-CP0000000a" #define TMD_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 +#define TMD_ISSUER_TWL "Root-CA00000001-CP00000007" +#define TMD_SIG_TYPE_TWL 0x00, 0x01, 0x00, 0x01 // RSA_2048 SHA1 + #define DLC_TID_HIGH 0x00, 0x04, 0x00, 0x8C // title id high for DLC // from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59; @@ -58,6 +63,7 @@ typedef struct { } __attribute__((packed, aligned(4))) TitleMetaData; u32 ValidateTmd(TitleMetaData* tmd); +u32 ValidateTwlTmd(TitleMetaData* tmd); u32 ValidateTmdSignature(TitleMetaData* tmd); u32 VerifyTmd(TitleMetaData* tmd); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 4529b15..eb4e4f6 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1126,7 +1126,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool agbimportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); char cxi_path[256] = { 0 }; // special options for TMD - if ((filetype & GAME_TMD) && !(filetype & FLAG_NUSCDN) && + if ((filetype & GAME_TMD) && (GetTmdContentPath(cxi_path, file_path) == 0) && (PathExist(cxi_path))) { u64 filetype_cxi = IdentifyFileType(cxi_path); @@ -1134,7 +1134,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan extrcodeable = (FTYPE_HASCODE(filetype_cxi)); } - bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || 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; + bool special_opt = + mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || + 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; char pathstr[32+1]; TruncateString(pathstr, file_path, 32, 8); @@ -1176,6 +1181,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan (filetype & GAME_EXEFS) ? "Mount as EXEFS image" : (filetype & GAME_ROMFS) ? "Mount as ROMFS image" : (filetype & GAME_TMD ) ? "TMD file options..." : + (filetype & GAME_CDNTMD)? "TMD/CDN options..." : + (filetype & GAME_TWLTMD)? "TMD/TWL options..." : (filetype & GAME_TIE ) ? "Manage Title..." : (filetype & GAME_BOSS ) ? "BOSS file options..." : (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index dff5638..e3c0c50 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -194,14 +194,27 @@ u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) { } u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { - // first part (TMD only) (we need to read the content count first) - if ((fvx_qread(path, tmd, 0, TMD_SIZE_MIN, NULL) != FR_OK) || - (ValidateTmd(tmd) != 0)) + // first part (TMD stub only) (we need to read the content count first) + if (fvx_qread(path, tmd, 0, TMD_SIZE_STUB, NULL) != FR_OK) return 1; // second part (read full size) - if (fvx_qread(path, tmd, 0, TMD_SIZE_N(getbe16(tmd->content_count)), NULL) != FR_OK) - return 1; + if (ValidateTmd(tmd) == 0) { + if (fvx_qread(path, tmd, 0, TMD_SIZE_N(getbe16(tmd->content_count)), NULL) != FR_OK) + return 1; + } else if ((ValidateTwlTmd(tmd) == 0) && (getbe16(tmd->content_count) == 1)) { + // for TWL: convert to new TMD format + static const u8 magic[] = { TMD_SIG_TYPE }; + memcpy(tmd->sig_type, magic, sizeof(magic)); + strncpy((char*) tmd->issuer, TMD_ISSUER, 0x40); + memset(((u8*) tmd) + TMD_SIZE_STUB, 0x00, TMD_SIZE_N(1) - TMD_SIZE_STUB); + (tmd->contentinfo)->cmd_count[1] = 0x01; + // convert and take over chunk (SHA-1 hash stays) + TmdContentChunk* chunk = (TmdContentChunk*) (void*) (tmd + 1); + if ((fvx_qread(path, chunk, TMD_SIZE_STUB, 0x24, NULL) != FR_OK) || + (FixTmdHashes(tmd) != 0)) + return 1; + } return 0; } @@ -212,17 +225,30 @@ u32 LoadTicketFile(Ticket** ticket, const char* path_tik) { // 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; + if (fvx_qread(path_tik, &tmp, 0, TICKET_MINIMUM_SIZE, &br) != FR_OK) + return 1; - u32 tik_size = GetTicketSize((Ticket*)&tmp); + // check type of ticket, set size + u32 tik_size = 0; + if ((br == TICKET_MINIMUM_SIZE) && (ValidateTicket((Ticket*)&tmp) == 0)) { + // standard 3DS ticket + tik_size = GetTicketSize((Ticket*)&tmp); + } else if ((br == TICKET_TWL_SIZE) && (ValidateTwlTicket((Ticket*)&tmp) == 0)) { + // TWL ticket + tik_size = TICKET_COMMON_SIZE; + } else return 1; 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; + if (br == TICKET_MINIMUM_SIZE) { // standard 3DS ticket + if ((fvx_qread(path_tik, tik, 0, tik_size, &br) != FR_OK) || (br != tik_size)) { + free(tik); + return 1; + } + } else { // TWL ticket (just take over title id and key) + BuildFakeTicket(tik, tmp.title_id); + memcpy(tik->titlekey, tmp.titlekey, 0x10); } *ticket = tik; @@ -236,10 +262,9 @@ u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { path_cetk[255] = '\0'; char* name_cetk = strrchr(path_cetk, '/'); if (!name_cetk) return 1; // will not happen - char* ext_cetk = strrchr(++name_cetk, '.'); - ext_cetk = (ext_cetk) ? ext_cetk + 1 : name_cetk; - snprintf(ext_cetk, 256 - (ext_cetk - path_cetk), "cetk"); - // ticket is loaded and vaildated here + name_cetk++; + snprintf(name_cetk, 256 - (name_cetk - path_cetk), "cetk"); + // ticket is loaded and validated here return LoadTicketFile(ticket, path_cetk); } @@ -389,7 +414,7 @@ u32 WriteCiaStub(CiaStub* stub, const char* path) { } u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) { - u8 hash[32]; + u8 hash[32] = { 0 }; u8 ctr[16]; FIL file; @@ -412,8 +437,11 @@ u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const return 1; } + u32 mode = SHA1_MODE; + for (u32 i = 20; i < 32; i++) + if (expected[i]) mode = SHA256_MODE; GetTmdCtr(ctr, chunk); - sha_init(SHA256_MODE); + sha_init(mode); for (u32 i = 0; i < size; i += STD_BUFFER_SIZE) { u32 read_bytes = min(STD_BUFFER_SIZE, (size - i)); UINT bytes_read; @@ -944,8 +972,8 @@ u32 VerifyGameFile(const char* path) { return VerifyNcsdFile(path); else if (filetype & GAME_NCCH) return VerifyNcchFile(path, 0, 0); - else if (filetype & GAME_TMD) - return VerifyTmdFile(path, filetype & FLAG_NUSCDN); + else if (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD)) + return VerifyTmdFile(path, filetype & (GAME_CDNTMD|GAME_TWLTMD)); else if (filetype & GAME_TIE) return VerifyTieFile(path); else if (filetype & GAME_TAD) @@ -1065,7 +1093,7 @@ u32 CheckEncryptedGameFile(const char* path) { else if (filetype & SYS_FIRM) return CheckEncryptedFirmFile(path); else if (filetype & GAME_NUSCDN) - return 0; // these should always be encrypted + return 0; // these *should* always be encrypted else return 1; } @@ -1270,7 +1298,6 @@ u32 DecryptFirmFile(const char* orig, const char* dest) { } u32 CryptCdnFileBuffered(const char* orig, const char* dest, u16 crypto, void* buffer) { - bool inplace = (strncmp(orig, dest, 256) == 0); TitleMetaData* tmd = (TitleMetaData*) buffer; TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); @@ -1335,15 +1362,7 @@ u32 CryptCdnFileBuffered(const char* orig, const char* dest, u16 crypto, void* b } // actual crypto - if (CryptNcchNcsdBossFirmFile(orig, dest, GAME_NUSCDN, crypto, 0, 0, chunk, titlekey) != 0) - return 1; - - if (inplace && tmd) { // in that case, write the change to the TMD file, too - u32 offset = ((u8*) chunk) - ((u8*) tmd); - fvx_qwrite(path_tmd, chunk, offset, sizeof(TmdContentChunk), NULL); - } - - return 0; + return CryptNcchNcsdBossFirmFile(orig, dest, GAME_NUSCDN, crypto, 0, 0, chunk, titlekey); } u32 CryptCdnFile(const char* orig, const char* dest, u16 crypto) { @@ -2123,6 +2142,9 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest, bool force_legit, bool cdn, void* buffer, bool install) { const u8 dlc_tid_high[] = { DLC_TID_HIGH }; + static const u8 twl_tid_high[] = { 0x00, 0x03, 0x00, 0x04 }; + static const u8 ctr_tid_high[] = { 0x00, 0x04, 0x80, 0x04 }; + CiaStub* cia = (CiaStub*) buffer; TitleMetaData* tmd = &(cia->tmd); TmdContentChunk* content_list = cia->content_list; @@ -2151,6 +2173,15 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest, return 1; } + // fix title id/key for faked TWL TMDs & Tickets + if (memcmp(title_id, twl_tid_high, 3) == 0) { + u8 titlekey[16]; + memcpy(title_id, ctr_tid_high, 3); + GetTitleKey(titlekey, (Ticket*) &(cia->ticket)); + memcpy((cia->ticket).title_id, title_id, 8); + SetTitleKey(titlekey, (Ticket*) &(cia->ticket)); + } + // content path string char path_content[256]; char* name_content; @@ -2239,7 +2270,7 @@ u32 InstallFromTmdFile(const char* path_tmd, const char* path_dest) { void* buffer = (void*) malloc(sizeof(CiaStub)); if (!buffer) return 1; - u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, true, true, buffer, true); + u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, false, true, buffer, true); free(buffer); return ret; @@ -2500,7 +2531,7 @@ u64 GetGameFileTitleId(const char* path) { if (LoadCiaStub(cia, path) == 0) tid64 = getbe64(cia->tmd.title_id); free(cia); - } else if (filetype & GAME_TMD) { + } else if (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD)) { TitleMetaData* tmd = (TitleMetaData*) malloc(TMD_SIZE_MAX); if (!tmd) return 0; if (LoadTmdFile(tmd, path) == 0) @@ -2517,9 +2548,11 @@ u64 GetGameFileTitleId(const char* path) { } else if (filetype & GAME_NDS) { TwlHeader twl; if ((fvx_qread(path, &twl, 0, sizeof(TwlHeader), NULL) == FR_OK) && (twl.unit_code & 0x02)) - tid64 = 0x0004800000000000ull | (twl.title_id & 0xFFFFFFFFFFull); + tid64 = twl.title_id; } + if ((tid64 & 0xFFFFFF0000000000ull) == 0x0003000000000000ull) + tid64 = 0x0004800000000000ull | (tid64 & 0xFFFFFFFFFFull); return tid64; } @@ -2533,7 +2566,7 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { char* dname = dest + strnlen(dest, 256); if (!((filetype & (GAME_TMD|GAME_TIE)) || (strncmp(path + 1, ":/title/", 8) == 0)) || (GetGoodName(dname, path, false) != 0)) { - u64 title_id = (filetype & GAME_TMD) ? GetGameFileTitleId(path) : 0; + u64 title_id = (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD)) ? GetGameFileTitleId(path) : 0; if (!title_id) { char* name = strrchr(path, '/'); if (!name) return 1; @@ -2556,8 +2589,8 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { // build CIA from game file if (filetype & GAME_TIE) ret = BuildCiaFromTieFile(path, dest, force_legit); - else if (filetype & GAME_TMD) - ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & FLAG_NUSCDN); + else if (filetype & (GAME_TMD|GAME_CDNTMD|GAME_TWLTMD)) + ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & (GAME_CDNTMD|GAME_TWLTMD)); else if (filetype & GAME_NCCH) ret = BuildInstallFromNcchFile(path, dest, false); else if (filetype & GAME_NCSD) @@ -2625,7 +2658,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) { // install game file if (filetype & GAME_CIA) ret = InstallFromCiaFile(path, drv); - else if ((filetype & GAME_TMD) && (filetype & FLAG_NUSCDN)) + else if (filetype & (GAME_CDNTMD|GAME_TWLTMD)) ret = InstallFromTmdFile(path, drv); else if (filetype & GAME_NCCH) ret = BuildInstallFromNcchFile(path, drv, true); @@ -3069,7 +3102,7 @@ u32 ShowCiaCheckerInfo(const char* path) { state_ticket = (ValidateTicketSignature((Ticket*)&(cia->ticket)) == 0) ? 2 : 1; // check tmd - if (ValidateTmd(&(cia->tmd)) == 0) + if (VerifyTmd(&(cia->tmd)) == 0) state_tmd = (ValidateTmdSignature(&(cia->tmd)) == 0) ? 2 : 1; // check for available contents