From 4e04849860b4b9ad044bb481009e04dbe5a4a913 Mon Sep 17 00:00:00 2001 From: luigoalma Date: Thu, 23 Jan 2020 00:29:11 +0000 Subject: [PATCH] Support for variable sized tickets Except for cia building or loading cia just yet. Added more checks on ticket content index, mainly due to having effects in the ticket format itself, and are unknown still. Ability to determine ticket size. Verify signature with ticket's proper size. Changes to use the new Ticket struct with the flexible array member. --- arm9/source/filesys/filetype.c | 4 +- arm9/source/game/bdri.c | 50 ++++++++---- arm9/source/game/bdri.h | 4 +- arm9/source/game/cia.c | 6 +- arm9/source/game/cia.h | 6 +- arm9/source/game/ticket.c | 18 ++++- arm9/source/game/ticket.h | 74 +++++++++++------- arm9/source/game/ticketdb.c | 10 ++- arm9/source/game/ticketdb.h | 2 +- arm9/source/utils/gameutil.c | 138 ++++++++++++++++++++++----------- arm9/source/virtual/vgame.c | 2 +- arm9/source/virtual/vtickdb.c | 4 +- 12 files changed, 211 insertions(+), 107 deletions(-) diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index 893653e..479ea73 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -20,7 +20,7 @@ u64 IdentifyFileType(const char* path) { const u8 png_magic[] = { PNG_MAGIC }; if (!path) return 0; // safety - u8 ALIGN(32) header[0x200]; // minimum required size + u8 ALIGN(32) header[0x2B8]; // minimum required size void* data = (void*) header; size_t fsize = FileGetSize(path); char* fname = strrchr(path, '/'); @@ -36,7 +36,7 @@ u64 IdentifyFileType(const char* path) { } else { ext = ""; } - if (FileGetData(path, header, 0x200, 0) < min(0x200, fsize)) return 0; + if (FileGetData(path, header, 0x2B8, 0) < min(0x2B8, fsize)) return 0; if (!fsize) return 0; if (fsize >= 0x200) { diff --git a/arm9/source/game/bdri.c b/arm9/source/game/bdri.c index 9d0417a..3de4649 100644 --- a/arm9/source/game/bdri.c +++ b/arm9/source/game/bdri.c @@ -669,12 +669,13 @@ u32 ReadTitleInfoEntryFromDB(const char* path, const u8* title_id, TitleInfoEntr return 0; } -u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket* ticket) +u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) { FIL file; TickDBPreHeader pre_header; - TicketEntry te; - + TicketEntry* te = NULL; + u32 entry_size; + if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; @@ -682,8 +683,12 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket* ticket) if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) || !CheckDBMagic((u8*) &pre_header, true) || - (ReadBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, (u8*) &te, + (GetBDRIEntrySize(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, &entry_size) != 0) || + entry_size < sizeof(TicketEntry) + 0x14 || + (te = (TicketEntry*)malloc(entry_size), te == NULL) || + (ReadBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, (u8*) te, sizeof(TicketEntry)) != 0)) { + free(te); // if allocated fvx_close(bdrifp); bdrifp = NULL; return 1; @@ -692,11 +697,21 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket* ticket) fvx_close(bdrifp); bdrifp = NULL; - if (te.ticket_size != sizeof(Ticket)) + if (te->ticket_size != GetTicketSize(&te->ticket)) { + free(te); return 1; + } - if (ticket) *ticket = te.ticket; + if (ticket) { + u32 size = te->ticket_size; + memmove(te, &te->ticket, size); // recycle this memory, instead of allocating another + Ticket* tik = realloc(te, size); + if(!tik) tik = (Ticket*)te; + *ticket = tik; + return 0; + } + free(te); return 0; } @@ -770,26 +785,35 @@ u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoE u32 AddTicketToDB(const char* path, const u8* title_id, const Ticket* ticket, bool replace) { FIL file; TickDBPreHeader pre_header; + u32 entry_size = sizeof(TicketEntry) + GetTicketContentIndexSize(ticket); - TicketEntry te; - te.unknown = 1; - te.ticket_size = sizeof(Ticket); - te.ticket = *ticket; - - if (fvx_open(&file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK) + TicketEntry* te = (TicketEntry*)malloc(entry_size); + if (!te) { return 1; + } + + te->unknown = 1; + te->ticket_size = GetTicketSize(ticket); + memcpy(&te->ticket, ticket, te->ticket_size); + + if (fvx_open(&file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK) { + free(te); + return 1; + } bdrifp = &file; if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) || !CheckDBMagic((u8*) &pre_header, true) || (AddBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, - (const u8*) &te, sizeof(TicketEntry), replace) != 0)) { + (const u8*) te, entry_size, replace) != 0)) { + free(te); fvx_close(bdrifp); bdrifp = NULL; return 1; } + free(te); fvx_close(bdrifp); bdrifp = NULL; return 0; diff --git a/arm9/source/game/bdri.h b/arm9/source/game/bdri.h index aac014e..28d68c6 100644 --- a/arm9/source/game/bdri.h +++ b/arm9/source/game/bdri.h @@ -91,7 +91,7 @@ typedef struct { typedef struct { u32 unknown; // usually (assuming always) == 1 - u32 ticket_size; // == 0x350 == sizeof(Ticket) + u32 ticket_size; // commonly == 0x350 Ticket ticket; } __attribute__((packed, aligned(4))) TicketEntry; @@ -100,7 +100,7 @@ u32 GetNumTickets(const char* path); u32 ListTitleInfoEntryTitleIDs(const char* path, u8* title_ids, u32 max_title_ids); u32 ListTicketTitleIDs(const char* path, u8* title_ids, u32 max_title_ids); u32 ReadTitleInfoEntryFromDB(const char* path, const u8* title_id, TitleInfoEntry* tie); -u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket* ticket); +u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket); u32 RemoveTitleInfoEntryFromDB(const char* path, const u8* title_id); u32 RemoveTicketFromDB(const char* path, const u8* title_id); u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoEntry* tie, bool replace); diff --git a/arm9/source/game/cia.c b/arm9/source/game/cia.c index 62f4b58..62b3b02 100644 --- a/arm9/source/game/cia.c +++ b/arm9/source/game/cia.c @@ -8,7 +8,7 @@ u32 ValidateCiaHeader(CiaHeader* header) { if ((header->size_header != CIA_HEADER_SIZE) || (header->size_cert != CIA_CERT_SIZE) || - (header->size_ticket != TICKET_SIZE) || + (header->size_ticket != TICKET_COMMON_SIZE) || (header->size_tmd < TMD_SIZE_MIN) || (header->size_tmd > TMD_SIZE_MAX) || (header->size_content == 0)) @@ -95,12 +95,12 @@ u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh) { return 0; } -u32 BuildCiaHeader(CiaHeader* header) { +u32 BuildCiaHeader(CiaHeader* header, u32 ticket_size) { memset(header, 0, sizeof(CiaHeader)); // sizes in header - fill only known sizes, others zero header->size_header = sizeof(CiaHeader); header->size_cert = CIA_CERT_SIZE; - header->size_ticket = sizeof(Ticket); + header->size_ticket = ticket_size; header->size_tmd = 0; header->size_meta = 0; header->size_content = 0; diff --git a/arm9/source/game/cia.h b/arm9/source/game/cia.h index 6f83dc6..d8c690b 100644 --- a/arm9/source/game/cia.h +++ b/arm9/source/game/cia.h @@ -34,8 +34,8 @@ typedef struct { u8 header_padding[0x40 - (CIA_HEADER_SIZE % 0x40)]; u8 cert[CIA_CERT_SIZE]; // cert is aligned and needs no padding - Ticket ticket; - u8 ticket_padding[0x40 - (TICKET_SIZE % 0x40)]; + TicketCommon ticket; + u8 ticket_padding[0x40 - (TICKET_COMMON_SIZE % 0x40)]; TitleMetaData tmd; TmdContentChunk content_list[TMD_MAX_CONTENTS]; } PACKED_ALIGN(16) CiaStub; @@ -66,7 +66,7 @@ u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd); u32 BuildCiaCert(u8* ciacert); u32 BuildCiaMeta(CiaMeta* meta, void* exthdr, void* smdh); -u32 BuildCiaHeader(CiaHeader* header); +u32 BuildCiaHeader(CiaHeader* header, u32 ticket_size); u32 DecryptCiaContentSequential(void* data, u32 size, u8* ctr, const u8* titlekey); u32 EncryptCiaContentSequential(void* data, u32 size, u8* ctr, const u8* titlekey); diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index 239c7c1..34035cc 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -10,7 +10,11 @@ u32 ValidateTicket(Ticket* ticket) { if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) || ((strncmp((char*) ticket->issuer, TICKET_ISSUER, 0x40) != 0) && (strncmp((char*) ticket->issuer, TICKET_ISSUER_DEV, 0x40) != 0)) || - (ticket->commonkey_idx >= 6)) + (ticket->commonkey_idx >= 6) || + (getbe32(&ticket->content_index[0]) != 0x10014) || + (getbe32(&ticket->content_index[4]) < 0x14) || + (getbe32(&ticket->content_index[12]) != 0x10014) || + (getbe32(&ticket->content_index[16]) != 0)) return 1; return 0; } @@ -28,7 +32,7 @@ u32 ValidateTicketSignature(Ticket* ticket) { } if (!RSA_setKey2048(3, mod, exp) || - !RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), 0x210)) + !RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), GetTicketSize(ticket) - 0x140)) return 1; return 0; @@ -44,7 +48,7 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // set ticket all zero for a clean start - memset(ticket, 0x00, sizeof(Ticket)); + memset(ticket, 0x00, TICKET_COMMON_SIZE); // 0xAC being size of this fake ticket's content index // fill ticket values memcpy(ticket->sig_type, sig_type, 4); memset(ticket->signature, 0xFF, 0x100); @@ -60,6 +64,14 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { return 0; } +u32 GetTicketContentIndexSize(const Ticket* ticket) { + return getbe32(&ticket->content_index[4]); +} + +u32 GetTicketSize(const Ticket* ticket) { + return sizeof(Ticket) + GetTicketContentIndexSize(ticket); +} + u32 BuildTicketCert(u8* tickcert) { const u8 cert_hash_expected[0x20] = { 0xDC, 0x15, 0x3C, 0x2B, 0x8A, 0x0A, 0xC8, 0x74, 0xA9, 0xDC, 0x78, 0x61, 0x0E, 0x6A, 0x8F, 0xE3, diff --git a/arm9/source/game/ticket.h b/arm9/source/game/ticket.h index 3c0a5bc..0752269 100644 --- a/arm9/source/game/ticket.h +++ b/arm9/source/game/ticket.h @@ -2,7 +2,8 @@ #include "common.h" -#define TICKET_SIZE sizeof(Ticket) +#define TICKET_COMMON_SIZE sizeof(TicketCommon) +#define TICKET_MINIMUM_SIZE sizeof(TicketMinimum) #define TICKET_CDNCERT_SIZE 0x700 #define TICKET_ISSUER "Root-CA00000003-XS0000000c" @@ -13,37 +14,54 @@ // from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 // all numbers in big endian + +#define TICKETBASE \ + u8 sig_type[4]; \ + u8 signature[0x100]; \ + u8 padding1[0x3C]; \ + u8 issuer[0x40]; \ + u8 ecdsa[0x3C]; \ + u8 version; \ + u8 ca_crl_version; \ + u8 signer_crl_version; \ + u8 titlekey[0x10]; \ + u8 reserved0; \ + u8 ticket_id[8]; \ + u8 console_id[4]; \ + u8 title_id[8]; \ + u8 sys_access[2]; \ + u8 ticket_version[2]; \ + u8 time_mask[4]; \ + u8 permit_mask[4]; \ + u8 title_export; \ + u8 commonkey_idx; \ + u8 reserved1[0x2A]; \ + u8 eshop_id[4]; \ + u8 reserved2; \ + u8 audit; \ + u8 content_permissions[0x40]; \ + u8 reserved3[2]; \ + u8 timelimits[0x40] + typedef struct { - u8 sig_type[4]; - u8 signature[0x100]; - u8 padding1[0x3C]; - u8 issuer[0x40]; - u8 ecdsa[0x3C]; - u8 version; - u8 ca_crl_version; - u8 signer_crl_version; - u8 titlekey[0x10]; - u8 reserved0; - u8 ticket_id[8]; - u8 console_id[4]; - u8 title_id[8]; - u8 sys_access[2]; - u8 ticket_version[2]; - u8 time_mask[4]; - u8 permit_mask[4]; - u8 title_export; - u8 commonkey_idx; - u8 reserved1[0x2A]; - u8 eshop_id[4]; - u8 reserved2; - u8 audit; - u8 content_permissions[0x40]; - u8 reserved3[2]; - u8 timelimits[0x40]; - u8 content_index[0xAC]; + TICKETBASE; + u8 content_index[]; } __attribute__((packed, aligned(4))) Ticket; +typedef struct { + TICKETBASE; + u8 content_index[0xAC]; +} __attribute__((packed, aligned(4))) TicketCommon; + +// minimum allowed content_index is 0x14 +typedef struct { + TICKETBASE; + u8 content_index[0x14]; +} __attribute__((packed, aligned(4))) TicketMinimum; + u32 ValidateTicket(Ticket* ticket); u32 ValidateTicketSignature(Ticket* ticket); u32 BuildFakeTicket(Ticket* ticket, u8* title_id); +u32 GetTicketContentIndexSize(const Ticket* ticket); +u32 GetTicketSize(const Ticket* ticket); u32 BuildTicketCert(u8* tickcert); diff --git a/arm9/source/game/ticketdb.c b/arm9/source/game/ticketdb.c index a99cfd0..0005ce0 100644 --- a/arm9/source/game/ticketdb.c +++ b/arm9/source/game/ticketdb.c @@ -55,7 +55,7 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) { Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls) { // chunk must be aligned to 0x200 byte in file and at least 0x400 byte big Ticket* tick = (Ticket*) (void*) (chunk + 0x18); - if ((getle32(chunk + 0x10) == 0) || (getle32(chunk + 0x14) != sizeof(Ticket))) return NULL; + if ((getle32(chunk + 0x10) == 0) || (getle32(chunk + 0x14) != GetTicketSize(tick))) return NULL; if (ValidateTicket(tick) != 0) return NULL; // ticket not validated if (title_id && (memcmp(title_id, tick->title_id, 8) != 0)) return NULL; // title id not matching if (legit_pls && (ValidateTicketSignature(tick) != 0)) return NULL; // legit check using RSA sig @@ -63,7 +63,7 @@ Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls) { return tick; } -u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand) { +u32 FindTicket(Ticket** ticket, u8* title_id, bool force_legit, bool emunand) { const char* path_db = TICKDB_PATH(emunand); // EmuNAND / SysNAND u8* data = (u8*) malloc(TICKDB_AREA_SIZE); if (!data) return 1; @@ -79,7 +79,11 @@ u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand) { for (u32 i = 0; !found && (i <= TICKDB_AREA_SIZE - 0x400); i += 0x200) { Ticket* tick = TicketFromTickDbChunk(data + i, title_id, force_legit); if (!tick) continue; - memcpy(ticket, tick, sizeof(Ticket)); + u32 size = GetTicketSize(tick); + Ticket* newtick = (Ticket*)malloc(size); + if (!newtick) break; + memcpy(newtick, tick, size); + *ticket = newtick; found = true; } diff --git a/arm9/source/game/ticketdb.h b/arm9/source/game/ticketdb.h index 41dc53b..2a6d512 100644 --- a/arm9/source/game/ticketdb.h +++ b/arm9/source/game/ticketdb.h @@ -36,7 +36,7 @@ typedef struct { u32 GetTitleKey(u8* titlekey, Ticket* ticket); Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls); -u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand); +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); diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 193750f..ab96fc0 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -199,7 +199,8 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { return 0; } -u32 LoadCdnTicketFile(Ticket* ticket, const char* path_cnt) { +u32 LoadCdnTicketFile(Ticket** ticket, const char* path_cnt) { + if(!ticket) return 1; // path points to CDN content file char path_cetk[256]; strncpy(path_cetk, path_cnt, 256); @@ -211,10 +212,22 @@ u32 LoadCdnTicketFile(Ticket* ticket, const char* path_cnt) { snprintf(ext_cetk, 256 - (ext_cetk - path_cetk), "cetk"); // load and check ticket + TicketMinimum tmp; UINT br; - if ((fvx_qread(path_cetk, ticket, 0, TICKET_SIZE, &br) != FR_OK) || (br != TICKET_SIZE) || - (ValidateTicket(ticket) != 0)) return 1; - + 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; } @@ -546,7 +559,7 @@ u32 VerifyCiaFile(const char* path) { // load CIA stub if ((LoadCiaStub(cia, path) != 0) || (GetCiaInfo(&info, &(cia->header)) != 0) || - (GetTitleKey(titlekey, &(cia->ticket)) != 0)) { + (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0)) { ShowPrompt(false, "%s\nError: Probably not a CIA file", pathstr); free(cia); return 1; @@ -607,15 +620,18 @@ u32 VerifyTmdFile(const char* path, bool cdn) { u8 titlekey[0x10] = { 0xFF }; if (cdn) { // load / build ticket (for titlekey / CDN only) - Ticket ticket; + Ticket* ticket = NULL; if (!((LoadCdnTicketFile(&ticket, path) == 0) || - ((BuildFakeTicket(&ticket, tmd->title_id) == 0) && - (FindTitleKey(&ticket, tmd->title_id) == 0))) || - (GetTitleKey(titlekey, &ticket) != 0)) { + ((ticket = (Ticket*)malloc(TICKET_COMMON_SIZE), ticket != NULL) && + (BuildFakeTicket(ticket, tmd->title_id) == 0) && + (FindTitleKey(ticket, tmd->title_id) == 0))) || + (GetTitleKey(titlekey, ticket) != 0)) { ShowPrompt(false, "%s\nError: CDN titlekey not found", pathstr); + free(ticket); free(tmd); return 1; } + free(ticket); } // verify contents @@ -1010,7 +1026,7 @@ u32 CryptCiaFile(const char* orig, const char* dest, u16 crypto) { if (!cia) return 1; if ((LoadCiaStub(cia, orig) != 0) || (GetCiaInfo(&info, &(cia->header)) != 0) || - (GetTitleKey(titlekey, &(cia->ticket)) != 0)) { + (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0)) { free(cia); return 1; } @@ -1106,16 +1122,27 @@ u32 CryptCdnFileBuffered(const char* orig, const char* dest, u16 crypto, void* b } else tmd = NULL; // load or build ticket - Ticket ticket; + Ticket* ticket = NULL; if (LoadCdnTicketFile(&ticket, orig) != 0) { - if (!tmd || (BuildFakeTicket(&ticket, tmd->title_id) != 0)) return 1; - if (FindTitleKey(&ticket, tmd->title_id) != 0) return 1; + if (!tmd || (ticket = (Ticket*)malloc(TICKET_COMMON_SIZE), !ticket)) return 1; + if ((BuildFakeTicket(ticket, tmd->title_id) != 0)) { + free(ticket); + return 1; + } + if (FindTitleKey(ticket, tmd->title_id) != 0) { + free(ticket); + return 1; + } } // get titlekey u8 titlekey[0x10] = { 0xFF }; - if (GetTitleKey(titlekey, &ticket) != 0) + if (GetTitleKey(titlekey, ticket) != 0) { + free(ticket); return 1; + } + + free(ticket); // find (build fake) content chunk TmdContentChunk* chunk = NULL; @@ -1311,11 +1338,11 @@ u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool // build the CIA stub memset(cia, 0, sizeof(CiaStub)); - if ((BuildCiaHeader(&(cia->header)) != 0) || + if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) || (LoadTmdFile(&(cia->tmd), path_tmd) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (BuildCiaCert(cia->cert) != 0) || - (BuildFakeTicket(&(cia->ticket), cia->tmd.title_id) != 0)) { + (BuildFakeTicket((Ticket*)&(cia->ticket), cia->tmd.title_id) != 0)) { return 1; } @@ -1328,17 +1355,25 @@ u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool if (!content_count) return 1; // get (legit) ticket - Ticket* ticket = &(cia->ticket); + Ticket* ticket = (Ticket*)&(cia->ticket); bool src_emunand = ((*path_tmd == 'B') || (*path_tmd == '4')); if (force_legit) { - Ticket ticket_fake; // backup of the fake ticket - memcpy(&ticket_fake, ticket, sizeof(Ticket)); - if ((cdn && (LoadCdnTicketFile(ticket, path_tmd) != 0)) || - (!cdn && (FindTicket(ticket, title_id, true, src_emunand) != 0))) { + Ticket* ticket_tmp = NULL; + if ((cdn && (LoadCdnTicketFile(&ticket_tmp, path_tmd) != 0)) || + (!cdn && (FindTicket(&ticket_tmp, title_id, true, src_emunand) != 0))) { ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id)); + free(ticket_tmp); return 1; } - if (getbe32(ticket->console_id)) { + // either, it's a ticket without ways to check ownership data, smaller sized + // or, it's title ticket with > 1024 contents, of which can't make it work with current CiaStub + if (GetTicketSize(ticket_tmp) != TICKET_COMMON_SIZE) { + ShowPrompt(false, "ID %016llX\nLegit ticket of unsupported size.", getbe64(title_id)); + free(ticket_tmp); + return 1; + } + bool copy = true; + if (getbe32(ticket_tmp->console_id)) { static u32 default_action = 0; const char* optionstr[2] = {"Use personalized ticket (legit)", "Use generic ticket (not legit)"}; @@ -1347,27 +1382,36 @@ u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool "ID %016llX\nLegit ticket is personalized.\nCIA may not be installable.\nChoose default action:", getbe64(title_id)); ShowProgress(0, 0, path_tmd); } - if (!default_action) return 1; + if (!default_action) { + free(ticket_tmp); + return 1; + } else if (default_action == 2) { - memcpy(ticket_fake.titlekey, ticket->titlekey, 0x10); - ticket_fake.commonkey_idx = ticket->commonkey_idx; - memcpy(ticket, &ticket_fake, sizeof(Ticket)); + memcpy(ticket->titlekey, ticket_tmp->titlekey, 0x10); + ticket->commonkey_idx = ticket_tmp->commonkey_idx; + copy = false; } } + if (copy) memcpy(ticket, ticket_tmp, TICKET_COMMON_SIZE); + free(ticket_tmp); } else if (cdn) { - if ((LoadCdnTicketFile(ticket, path_tmd) != 0) && - (FindTitleKey(ticket, title_id) != 0)) { + Ticket* ticket_tmp = NULL; + if ((LoadCdnTicketFile(&ticket_tmp, path_tmd) != 0) && + (FindTitleKey(ticket_tmp, title_id) != 0)) { ShowPrompt(false, "ID %016llX\nTitlekey not found.", getbe64(title_id)); + free(ticket_tmp); return 1; } + free(ticket_tmp); } else { - Ticket ticket_tmp; + Ticket* ticket_tmp; if ((FindTitleKey(ticket, title_id) != 0) && (FindTicket(&ticket_tmp, title_id, false, src_emunand) == 0)) { // we just copy the titlekey from a valid ticket (if we can) - memcpy(ticket->titlekey, ticket_tmp.titlekey, 0x10); - ticket->commonkey_idx = ticket_tmp.commonkey_idx; + memcpy(ticket->titlekey, ticket_tmp->titlekey, 0x10); + ticket->commonkey_idx = ticket_tmp->commonkey_idx; } + free(ticket_tmp); } // content path string @@ -1399,7 +1443,7 @@ u32 BuildCiaFromTmdFileBuffered(const char* path_tmd, const char* path_cia, bool // insert contents u8 titlekey[16] = { 0xFF }; - if ((GetTitleKey(titlekey, &(cia->ticket)) != 0) && force_legit) return 1; + if ((GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) && force_legit) return 1; if (WriteCiaStub(cia, path_cia) != 0) return 1; for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) { TmdContentChunk* chunk = &(content_list[i]); @@ -1472,9 +1516,9 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) return 1; memset(cia, 0, sizeof(CiaStub)); - if ((BuildCiaHeader(&(cia->header)) != 0) || + if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) || (BuildCiaCert(cia->cert) != 0) || - (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -1499,7 +1543,7 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { free(meta); // write the CIA stub (take #2) - FindTitleKey((&cia->ticket), title_id); + FindTitleKey((Ticket*)(&cia->ticket), title_id); if ((FixTmdHashes(&(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -1538,9 +1582,9 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) return 1; memset(cia, 0, sizeof(CiaStub)); - if ((BuildCiaHeader(&(cia->header)) != 0) || + if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) || (BuildCiaCert(cia->cert) != 0) || - (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -1572,7 +1616,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { if (meta) free(meta); // write the CIA stub (take #2) - FindTitleKey(&(cia->ticket), title_id); + FindTitleKey((Ticket*)&(cia->ticket), title_id); if ((FixTmdHashes(&(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -1616,9 +1660,9 @@ u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) { CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) return 1; memset(cia, 0, sizeof(CiaStub)); - if ((BuildCiaHeader(&(cia->header)) != 0) || + if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) || (BuildCiaCert(cia->cert) != 0) || - (BuildFakeTicket(&(cia->ticket), title_id) != 0) || + (BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) || (BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -1635,7 +1679,7 @@ u32 BuildCiaFromNdsFile(const char* path_nds, const char* path_cia) { } // write the CIA stub (take #2) - FindTitleKey((&cia->ticket), title_id); + FindTitleKey((Ticket*)(&cia->ticket), title_id); if ((FixTmdHashes(&(cia->tmd)) != 0) || (FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) || (WriteCiaStub(cia, path_cia) != 0)) { @@ -2030,8 +2074,8 @@ u32 ShowCiaCheckerInfo(const char* path) { bool is_dlc = ((title_id >> 32) == 0x0004008C); // check ticket - if (ValidateTicket(&(cia->ticket)) == 0) - state_ticket = (ValidateTicketSignature(&(cia->ticket)) == 0) ? 2 : 1; + if (ValidateTicket((Ticket*)&(cia->ticket)) == 0) + state_ticket = (ValidateTicketSignature((Ticket*)&(cia->ticket)) == 0) ? 2 : 1; // check tmd if (ValidateTmd(&(cia->tmd)) == 0) @@ -2275,10 +2319,10 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { u64 filetype = path_in ? IdentifyFileType(path_in) : 0; if (filetype & GAME_TICKET) { - Ticket ticket; - if ((fvx_qread(path_in, &ticket, 0, TICKET_SIZE, NULL) != FR_OK) || + TicketCommon ticket; + if ((fvx_qread(path_in, &ticket, 0, TICKET_COMMON_SIZE, NULL) != FR_OK) || (TIKDB_SIZE(tik_info) + 32 > STD_BUFFER_SIZE) || - (AddTicketToInfo(tik_info, &ticket, dec) != 0)) { + (AddTicketToInfo(tik_info, (Ticket*)&ticket, dec) != 0)) { return 1; } } else if (filetype & SYS_TICKDB) { @@ -2476,7 +2520,7 @@ u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch) { u8 titlekey[16]; u8 ctr[16]; GetTmdCtr(ctr, chunk); - if (GetTitleKey(titlekey, &(cia->ticket)) != 0) { + if (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) { free(cia); return 1; } diff --git a/arm9/source/virtual/vgame.c b/arm9/source/virtual/vgame.c index 1171188..544e9a5 100644 --- a/arm9/source/virtual/vgame.c +++ b/arm9/source/virtual/vgame.c @@ -848,7 +848,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) return false; offset_cia = vdir->offset; // always zero(!) - GetTitleKey(cia_titlekey, &(cia->ticket)); + GetTitleKey(cia_titlekey, (Ticket*)&(cia->ticket)); if (!BuildVGameCiaDir()) return false; } else if ((vdir->flags & VFLAG_NCSD) && (offset_ncsd != vdir->offset)) { if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) || diff --git a/arm9/source/virtual/vtickdb.c b/arm9/source/virtual/vtickdb.c index ccc8079..0028e76 100644 --- a/arm9/source/virtual/vtickdb.c +++ b/arm9/source/virtual/vtickdb.c @@ -19,6 +19,7 @@ typedef struct { u32 commonkey_idx; u32 offset; + u32 size; u8 title_id[8]; u8 titlekey[16]; u8 ticket_id[8]; @@ -54,6 +55,7 @@ u32 AddTickDbInfo(TickDbInfo* info, Ticket* ticket, u32 offset, bool replace) { TickDbEntry* entry = info->entries + info->n_entries; entry->commonkey_idx = ticket->commonkey_idx; entry->offset = offset; + entry->size = GetTicketSize(ticket); memcpy(entry->title_id, ticket->title_id, 8); memcpy(entry->titlekey, ticket->titlekey, 16); memcpy(entry->ticket_id, ticket->ticket_id, 8); @@ -178,7 +180,7 @@ bool ReadVTickDbDir(VirtualFile* vfile, VirtualDir* vdir) { memset(vfile, 0, sizeof(VirtualFile)); snprintf(vfile->name, 32, NAME_TIK, getbe64(tick_entry->title_id), getbe32(tick_entry->console_id)); vfile->offset = tick_entry->offset & ~OFLAG_RAW; - vfile->size = sizeof(Ticket); + vfile->size = tick_entry->size; vfile->keyslot = 0xFF; vfile->flags = (vdir->flags | VFLAG_READONLY) & ~VFLAG_DIR;