Also enable verification of TMD files

this verifies all referenced contents
This commit is contained in:
d0k3 2016-12-09 15:33:04 +01:00
parent fec4f081a0
commit baf1a645ca
6 changed files with 94 additions and 20 deletions

View File

@ -46,6 +46,9 @@ u32 IdentifyFileType(const char* path) {
NcchHeader* ncch = (NcchHeader*) (void*) header; NcchHeader* ncch = (NcchHeader*) (void*) header;
if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) if (fsize >= (ncch->size * NCCH_MEDIA_UNIT))
return GAME_NCCH; // NCSD (".3DS") file 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; return 0;

View File

@ -2,11 +2,17 @@
#include "common.h" #include "common.h"
#define IMG_FAT 1 #define IMG_FAT (1<<0)
#define IMG_NAND 2 #define IMG_NAND (1<<1)
#define GAME_CIA 3 #define GAME_CIA (1<<2)
#define GAME_NCSD 4 #define GAME_NCSD (1<<3)
#define GAME_NCCH 5 #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); u32 IdentifyFileType(const char* path);

View File

@ -85,7 +85,7 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
return 0; return 0;
} }
u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk) { u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
memset(ctr, 0, 16); memset(ctr, 0, 16);
memcpy(ctr, chunk->index, 2); memcpy(ctr, chunk->index, 2);
return 0; return 0;
@ -136,7 +136,7 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
// fill ticket values // fill ticket values
memcpy(ticket->sig_type, sig_type, 4); memcpy(ticket->sig_type, sig_type, 4);
memset(ticket->signature, 0xFF, 0x100); 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); memset(ticket->ecdsa, 0xFF, 0x3C);
ticket->version = 0x01; ticket->version = 0x01;
memset(ticket->titlekey, 0xFF, 16); memset(ticket->titlekey, 0xFF, 16);
@ -157,7 +157,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
// file TMD values // file TMD values
memcpy(tmd->sig_type, sig_type, 4); memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100); memset(tmd->signature, 0xFF, 0x100);
snprintf((char*) tmd->issuer, 0x40, "Root-CA00000003-CP0000000b"); snprintf((char*) tmd->issuer, 0x40, CIA_TMD_ISSUER);
tmd->version = 0x01; tmd->version = 0x01;
memcpy(tmd->title_id, title_id, 8); memcpy(tmd->title_id, title_id, 8);
tmd->title_type[3] = 0x40; // whatever tmd->title_type[3] = 0x40; // whatever

View File

@ -2,6 +2,9 @@
#include "common.h" #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_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content
#define CIA_HEADER_SIZE sizeof(CiaHeader) #define CIA_HEADER_SIZE sizeof(CiaHeader)
#define CIA_CERT_SIZE 0xA00 #define CIA_CERT_SIZE 0xA00
@ -9,6 +12,7 @@
#define CIA_TICKET_SIZE sizeof(Ticket) #define CIA_TICKET_SIZE sizeof(Ticket)
#define CIA_TMD_SIZE_MIN sizeof(TitleMetaData) #define CIA_TMD_SIZE_MIN sizeof(TitleMetaData)
#define CIA_TMD_SIZE_MAX (sizeof(TitleMetaData) + (CIA_MAX_CONTENTS*sizeof(TmdContentChunk))) #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 // see: https://www.3dbrew.org/wiki/CIA#Meta
typedef struct { typedef struct {
@ -147,7 +151,7 @@ u32 ValidateCiaHeader(CiaHeader* header);
u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); u32 GetCiaInfo(CiaInfo* info, CiaHeader* header);
u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd); u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd);
u32 GetTitleKey(u8* titlekey, Ticket* ticket); u32 GetTitleKey(u8* titlekey, Ticket* ticket);
u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 BuildCiaCert(u8* ciacert); u32 BuildCiaCert(u8* ciacert);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id); u32 BuildFakeTicket(Ticket* ticket, u8* title_id);

View File

@ -197,7 +197,26 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) {
return 0; 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 hash[32];
u8 ctr[16]; u8 ctr[16];
FIL file; FIL file;
@ -206,6 +225,7 @@ u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const
u64 size = getbe64(chunk->size); u64 size = getbe64(chunk->size);
bool encrypted = getbe16(chunk->type) & 0x1; 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) if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1; return 1;
if (offset + size > f_size(&file)) { if (offset + size > f_size(&file)) {
@ -214,9 +234,8 @@ u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const
} }
f_lseek(&file, offset); f_lseek(&file, offset);
GetCiaCtr(ctr, chunk); GetTmdCtr(ctr, chunk);
sha_init(SHA256_MODE); sha_init(SHA256_MODE);
ShowProgress(0, 0, path);
for (u32 i = 0; i < size; i += MAIN_BUFFER_SIZE) { for (u32 i = 0; i < size; i += MAIN_BUFFER_SIZE) {
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
UINT bytes_read; UINT bytes_read;
@ -253,7 +272,7 @@ u32 VerifyCiaFile(const char* path) {
u64 next_offset = info.offset_content; u64 next_offset = info.offset_content;
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[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", ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed",
pathstr, getbe32(chunk->id), getbe64(chunk->size), next_offset, i); pathstr, getbe32(chunk->id), getbe64(chunk->size), next_offset, i);
return 1; return 1;
@ -264,6 +283,44 @@ u32 VerifyCiaFile(const char* path) {
return 0; 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 VerifyGameFile(const char* path) {
u32 filetype = IdentifyFileType(path); u32 filetype = IdentifyFileType(path);
if (filetype == GAME_CIA) if (filetype == GAME_CIA)
@ -272,5 +329,7 @@ u32 VerifyGameFile(const char* path) {
return VerifyNcsdFile(path); return VerifyNcsdFile(path);
else if (filetype == GAME_NCCH) else if (filetype == GAME_NCCH)
return VerifyNcchFile(path, 0, 0); return VerifyNcchFile(path, 0, 0);
else if (filetype == GAME_TMD)
return VerifyTmdFile(path);
else return 1; else return 1;
} }

View File

@ -667,13 +667,12 @@ u32 GodMode() {
u32 filetype = IdentifyFileType(curr_entry->path); u32 filetype = IdentifyFileType(curr_entry->path);
u32 drvtype = DriveType(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))) ? !(drvtype & (DRV_IMAGE|DRV_RAMDRIVE))) ?
++n_opt : -1; ++n_opt : -1;
int hexviewer = ++n_opt; int hexviewer = ++n_opt;
int calcsha = ++n_opt; int calcsha = ++n_opt;
int verificable = ((filetype == GAME_CIA) || (filetype == GAME_NCSD) || int verificable = (filetype & FYTPE_VERIFICABLE) ? ++n_opt : -1;
(filetype == GAME_NCCH)) ? ++n_opt : -1;
int injectable = ((clipboard->n_entries == 1) && int injectable = ((clipboard->n_entries == 1) &&
(clipboard->entry[0].type == T_FILE) && (clipboard->entry[0].type == T_FILE) &&
(drvtype & DRV_FAT) && (drvtype & DRV_FAT) &&
@ -721,12 +720,15 @@ u32 GodMode() {
n_other++; n_other++;
continue; continue;
} }
if ((filetype != GAME_CIA) && !ShowProgress(n_processed++, n_marked, path)) break; if (!(filetype & (GAME_CIA|GAME_TMD)) &&
if (VerifyGameFile(path) == 0) n_success++; !ShowProgress(n_processed++, n_marked, path)) break;
else if (filetype != GAME_CIA) ShowProgress(0, 0, path); // redraw progress bar
current_dir->entry[i].marked = false; 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", 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); n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked); else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);