diff --git a/source/filetype.c b/source/filetype.c index 12052df..c9153fb 100644 --- a/source/filetype.c +++ b/source/filetype.c @@ -46,6 +46,9 @@ u32 IdentifyFileType(const char* path) { NcchHeader* ncch = (NcchHeader*) (void*) header; if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) return GAME_NCCH; // NCSD (".3DS") file + } else if (strncmp(CIA_TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { + if (fsize >= CIA_TMD_SIZE_N(getbe16(header + 0x1DE))) + return GAME_TMD; // TMD file } return 0; diff --git a/source/filetype.h b/source/filetype.h index af11898..e8af134 100644 --- a/source/filetype.h +++ b/source/filetype.h @@ -2,11 +2,17 @@ #include "common.h" -#define IMG_FAT 1 -#define IMG_NAND 2 +#define IMG_FAT (1<<0) +#define IMG_NAND (1<<1) -#define GAME_CIA 3 -#define GAME_NCSD 4 -#define GAME_NCCH 5 +#define GAME_CIA (1<<2) +#define GAME_NCSD (1<<3) +#define GAME_NCCH (1<<4) +#define GAME_TMD (1<<5) +// #define GAME_EXEFS (1<<6) +// #define GAME_ROMFS (1<<7) + +#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH) +#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD) u32 IdentifyFileType(const char* path); diff --git a/source/game/cia.c b/source/game/cia.c index 060735d..e0dbb43 100644 --- a/source/game/cia.c +++ b/source/game/cia.c @@ -85,7 +85,7 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) { return 0; } -u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk) { +u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) { memset(ctr, 0, 16); memcpy(ctr, chunk->index, 2); return 0; @@ -136,7 +136,7 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { // fill ticket values memcpy(ticket->sig_type, sig_type, 4); memset(ticket->signature, 0xFF, 0x100); - snprintf((char*) ticket->issuer, 0x40, "Root-CA00000003-XS0000000c"); + snprintf((char*) ticket->issuer, 0x40, CIA_TICKET_ISSUER); memset(ticket->ecdsa, 0xFF, 0x3C); ticket->version = 0x01; memset(ticket->titlekey, 0xFF, 16); @@ -157,7 +157,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { // file TMD values memcpy(tmd->sig_type, sig_type, 4); memset(tmd->signature, 0xFF, 0x100); - snprintf((char*) tmd->issuer, 0x40, "Root-CA00000003-CP0000000b"); + snprintf((char*) tmd->issuer, 0x40, CIA_TMD_ISSUER); tmd->version = 0x01; memcpy(tmd->title_id, title_id, 8); tmd->title_type[3] = 0x40; // whatever diff --git a/source/game/cia.h b/source/game/cia.h index a34d4c8..874f1ca 100644 --- a/source/game/cia.h +++ b/source/game/cia.h @@ -2,6 +2,9 @@ #include "common.h" +#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c" +#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b" + #define CIA_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content #define CIA_HEADER_SIZE sizeof(CiaHeader) #define CIA_CERT_SIZE 0xA00 @@ -9,6 +12,7 @@ #define CIA_TICKET_SIZE sizeof(Ticket) #define CIA_TMD_SIZE_MIN sizeof(TitleMetaData) #define CIA_TMD_SIZE_MAX (sizeof(TitleMetaData) + (CIA_MAX_CONTENTS*sizeof(TmdContentChunk))) +#define CIA_TMD_SIZE_N(n) (sizeof(TitleMetaData) + (n*sizeof(TmdContentChunk))) // see: https://www.3dbrew.org/wiki/CIA#Meta typedef struct { @@ -147,7 +151,7 @@ u32 ValidateCiaHeader(CiaHeader* header); u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd); u32 GetTitleKey(u8* titlekey, Ticket* ticket); -u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk); +u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); u32 BuildCiaCert(u8* ciacert); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); diff --git a/source/game/gameio.c b/source/game/gameio.c index deda249..215f66c 100644 --- a/source/game/gameio.c +++ b/source/game/gameio.c @@ -197,7 +197,26 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) { return 0; } -u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) { +u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { + FIL file; + UINT btr; + + if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + + // full TMD file + f_lseek(&file, 0); + if ((fx_read(&file, tmd, CIA_TMD_SIZE_MAX, &btr) != FR_OK) || + (btr < CIA_TMD_SIZE_N(getbe16(tmd->content_count)))) { + fx_close(&file); + return 1; + } + + fx_close(&file); + return 0; +} + +u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) { u8 hash[32]; u8 ctr[16]; FIL file; @@ -206,6 +225,7 @@ u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const u64 size = getbe64(chunk->size); bool encrypted = getbe16(chunk->type) & 0x1; + if (!ShowProgress(0, 0, path)) return 1; if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; if (offset + size > f_size(&file)) { @@ -214,9 +234,8 @@ u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const } f_lseek(&file, offset); - GetCiaCtr(ctr, chunk); + GetTmdCtr(ctr, chunk); sha_init(SHA256_MODE); - ShowProgress(0, 0, path); for (u32 i = 0; i < size; i += MAIN_BUFFER_SIZE) { u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); UINT bytes_read; @@ -253,7 +272,7 @@ u32 VerifyCiaFile(const char* path) { u64 next_offset = info.offset_content; for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { TmdContentChunk* chunk = &(cia->content_list[i]); - if (VerifyCiaContent(path, next_offset, chunk, titlekey) != 0) { + if (VerifyTmdContent(path, next_offset, chunk, titlekey) != 0) { ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed", pathstr, getbe32(chunk->id), getbe64(chunk->size), next_offset, i); return 1; @@ -264,6 +283,44 @@ u32 VerifyCiaFile(const char* path) { return 0; } +u32 VerifyTmdFile(const char* path) { + TitleMetaData* tmd = (TitleMetaData*) TEMP_BUFFER; + TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); + + // path string + char pathstr[32 + 1]; + TruncateString(pathstr, path, 32, 8); + + // content path string + char path_content[256]; + char* name_content; + strncpy(path_content, path, 256); + name_content = strrchr(path_content, '/'); + if (!name_content) return 1; // will not happen + name_content++; + + // load TMD file + if (LoadTmdFile(tmd, path) != 0) { + ShowPrompt(false, "%s\nError: TMD probably corrupted", pathstr); + return 1; + } + + // verify contents + u32 content_count = getbe16(tmd->content_count); + for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { + TmdContentChunk* chunk = &(content_list[i]); + chunk->type[1] &= ~0x01; // remove crypto flag + snprintf(name_content, 256 - (name_content - path_content), "%08lx.app", getbe32(chunk->id)); + TruncateString(pathstr, path_content, 32, 8); + if (VerifyTmdContent(path_content, 0, chunk, NULL) != 0) { + ShowPrompt(false, "%s\nVerification failed", pathstr); + return 1; + } + } + + return 0; +} + u32 VerifyGameFile(const char* path) { u32 filetype = IdentifyFileType(path); if (filetype == GAME_CIA) @@ -272,5 +329,7 @@ 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); else return 1; } diff --git a/source/godmode.c b/source/godmode.c index 2d20398..2ad79a2 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -667,13 +667,12 @@ u32 GodMode() { u32 filetype = IdentifyFileType(curr_entry->path); u32 drvtype = DriveType(curr_entry->path); - int mountable = (filetype && (drvtype & DRV_FAT) && + int mountable = ((filetype & FTYPE_MOUNTABLE) && (drvtype & DRV_FAT) && !(drvtype & (DRV_IMAGE|DRV_RAMDRIVE))) ? ++n_opt : -1; int hexviewer = ++n_opt; int calcsha = ++n_opt; - int verificable = ((filetype == GAME_CIA) || (filetype == GAME_NCSD) || - (filetype == GAME_NCCH)) ? ++n_opt : -1; + int verificable = (filetype & FYTPE_VERIFICABLE) ? ++n_opt : -1; int injectable = ((clipboard->n_entries == 1) && (clipboard->entry[0].type == T_FILE) && (drvtype & DRV_FAT) && @@ -721,12 +720,15 @@ u32 GodMode() { n_other++; continue; } - if ((filetype != GAME_CIA) && !ShowProgress(n_processed++, n_marked, path)) break; - if (VerifyGameFile(path) == 0) n_success++; - else if (filetype != GAME_CIA) ShowProgress(0, 0, path); // redraw progress bar + if (!(filetype & (GAME_CIA|GAME_TMD)) && + !ShowProgress(n_processed++, n_marked, path)) break; current_dir->entry[i].marked = false; + if (VerifyGameFile(path) == 0) n_success++; + else { // on failure: set cursor on failed title, break; + cursor = i; + break; + } } - if (filetype != GAME_CIA) ShowProgress(1, 1, ""); // CIA verification has progress bar handling if (n_other) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu not of same type", n_success, n_marked, n_other, n_marked); else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);