Install, build CIA, verify handling for TWL CDN content

This commit is contained in:
d0k3 2021-02-18 18:34:20 +01:00
parent f9408a9c10
commit 24195c319a
10 changed files with 183 additions and 94 deletions

View File

@ -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;

View File

@ -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))

View File

@ -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 };

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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 };

View File

@ -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);

View File

@ -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" :

View File

@ -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