diff --git a/Makefile.common b/Makefile.common index fb214f4..d8c8258 100644 --- a/Makefile.common +++ b/Makefile.common @@ -25,6 +25,12 @@ else ifeq ($(FLAVOR),ZuishMode9) CFLAGS += -DDEFAULT_FONT=\"font_zuish_8x8.pbm\" endif +ifeq ($(LARGEDLC),1) + CFLAGS += -DTITLE_MAX_CONTENTS=1536 +else + CFLAGS += -DTITLE_MAX_CONTENTS=1024 +endif + ifeq ($(SALTMODE),1) CFLAGS += -DSALTMODE endif diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index 14e5a7e..00e61b6 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -41,28 +41,91 @@ u32 ValidateTicketSignature(Ticket* ticket) { return ret; } -u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { - static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256 - static const u8 ticket_cnt_index[] = { // whatever this is - 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, - 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - // set ticket all zero for a clean start - 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); - snprintf((char*) ticket->issuer, 0x40, IS_DEVKIT ? TICKET_ISSUER_DEV : TICKET_ISSUER); - memset(ticket->ecdsa, 0xFF, 0x3C); - ticket->version = 0x01; - memset(ticket->titlekey, 0xFF, 16); - memcpy(ticket->title_id, title_id, 8); - ticket->commonkey_idx = 0x00; // eshop - ticket->audit = 0x01; // whatever - memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index)); - memset(&ticket->content_index[sizeof(ticket_cnt_index)], 0xFF, 0x80); // 1024 content indexes +u32 BuildVariableFakeTicket(Ticket** ticket, u32* ticket_size, const u8* title_id, u32 index_max) { + if (!ticket || !ticket_size) + return 1; + static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256 + + // calculate sizes and determine pointers to use + u32 rights_field_count = (min(index_max, 0x10000) + 1023) >> 10; // round up to 1024 and cap at 65536, and div by 1024 + u32 content_index_size = sizeof(TicketContentIndexMainHeader) + sizeof(TicketContentIndexDataHeader) + sizeof(TicketRightsField) * rights_field_count; + u32 _ticket_size = sizeof(Ticket) + content_index_size; + Ticket *_ticket; + + if (*ticket) { // if a pointer was pregiven + if (*ticket_size < _ticket_size) { // then check given boundary size + *ticket_size = _ticket_size; // if not enough, inform the actual needed size + return 2; // indicate a size error + } + _ticket = *ticket; // get the pointer if we good to go + } else // if not pregiven, allocate one + _ticket = (Ticket*)malloc(_ticket_size); + + if (!_ticket) + return 1; + + // set ticket all zero for a clean start + memset(_ticket, 0x00, _ticket_size); + // fill ticket values + memcpy(_ticket->sig_type, sig_type, 4); + memset(_ticket->signature, 0xFF, 0x100); + snprintf((char*) _ticket->issuer, 0x40, IS_DEVKIT ? TICKET_ISSUER_DEV : TICKET_ISSUER); + memset(_ticket->ecdsa, 0xFF, 0x3C); + _ticket->version = 0x01; + memset(_ticket->titlekey, 0xFF, 16); + memcpy(_ticket->title_id, title_id, 8); + _ticket->commonkey_idx = 0x00; // eshop + _ticket->audit = 0x01; // whatever + + // fill in rights + TicketContentIndexMainHeader* mheader = (TicketContentIndexMainHeader*)&_ticket->content_index[0]; + TicketContentIndexDataHeader* dheader = (TicketContentIndexDataHeader*)&_ticket->content_index[0x14]; + TicketRightsField* rights = (TicketRightsField*)&_ticket->content_index[0x28]; + + // first main data header + mheader->unk1[1] = 0x1; mheader->unk2[1] = 0x14; + mheader->content_index_size[3] = (u8)(content_index_size >> 0); + mheader->content_index_size[2] = (u8)(content_index_size >> 8); + mheader->content_index_size[1] = (u8)(content_index_size >> 16); + mheader->content_index_size[0] = (u8)(content_index_size >> 24); + mheader->data_header_relative_offset[3] = 0x14; // relative offset for TicketContentIndexDataHeader + mheader->unk3[1] = 0x1; mheader->unk4[1] = 0x14; + + // then the data header + dheader->data_relative_offset[3] = 0x28; // relative offset for TicketRightsField + dheader->max_entry_count[3] = (u8)(rights_field_count >> 0); + dheader->max_entry_count[2] = (u8)(rights_field_count >> 8); + dheader->max_entry_count[1] = (u8)(rights_field_count >> 16); + dheader->max_entry_count[0] = (u8)(rights_field_count >> 24); + dheader->size_per_entry[3] = (u8)sizeof(TicketRightsField); // sizeof should be 0x84 + dheader->total_size_used[3] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 0); + dheader->total_size_used[2] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 8); + dheader->total_size_used[1] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 16); + dheader->total_size_used[0] = (u8)((sizeof(TicketRightsField) * rights_field_count) >> 24); + dheader->data_type[1] = 3; // right fields + + // now the right fields + // indexoffets must be in accending order to have the desired effect + for (u32 i = 0; i < rights_field_count; ++i) { + rights[i].indexoffset[1] = (u8)((1024 * i) >> 0); + rights[i].indexoffset[0] = (u8)((1024 * i) >> 8); + memset(&rights[i].rightsbitfield[0], 0xFF, sizeof(rights[0].rightsbitfield)); + } + + *ticket = _ticket; + *ticket_size = _ticket_size; + + return 0; +} + +u32 BuildFakeTicket(Ticket* ticket, const u8* title_id) { + Ticket* tik; + u32 ticket_size = sizeof(TicketCommon); + u32 res = BuildVariableFakeTicket(&tik, &ticket_size, title_id, TICKET_MAX_CONTENTS); + if (res != 0) return res; + memcpy(ticket, tik, ticket_size); + free(tik); return 0; } diff --git a/arm9/source/game/ticket.h b/arm9/source/game/ticket.h index 956023d..1ed4891 100644 --- a/arm9/source/game/ticket.h +++ b/arm9/source/game/ticket.h @@ -1,11 +1,14 @@ #pragma once #include "common.h" +#include "tmd.h" #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_MAX_CONTENTS TITLE_MAX_CONTENTS // should be TMD_MAX_CONTENTS +#define TICKET_COMMON_CNT_INDEX_SIZE (0x28 + (((TICKET_MAX_CONTENTS + 1023) >> 10) * 0x84)) #define TICKET_ISSUER "Root-CA00000003-XS0000000c" #define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" @@ -54,7 +57,7 @@ typedef struct { typedef struct { TICKETBASE; - u8 content_index[0xAC]; + u8 content_index[TICKET_COMMON_CNT_INDEX_SIZE]; } PACKED_STRUCT TicketCommon; // minimum allowed content_index is 0x14 @@ -97,7 +100,7 @@ typedef struct { u32 ValidateTicket(Ticket* ticket); u32 ValidateTwlTicket(Ticket* ticket); u32 ValidateTicketSignature(Ticket* ticket); -u32 BuildFakeTicket(Ticket* ticket, u8* title_id); +u32 BuildFakeTicket(Ticket* ticket, const u8* title_id); u32 GetTicketContentIndexSize(const Ticket* ticket); u32 GetTicketSize(const Ticket* ticket); u32 BuildTicketCert(u8* tickcert); diff --git a/arm9/source/game/tmd.h b/arm9/source/game/tmd.h index 00baac0..1ed7618 100644 --- a/arm9/source/game/tmd.h +++ b/arm9/source/game/tmd.h @@ -2,7 +2,7 @@ #include "common.h" -#define TMD_MAX_CONTENTS 1000 // 383 // theme CIAs contain maximum 100 themes + 1 index content +#define TMD_MAX_CONTENTS TITLE_MAX_CONTENTS // 1024 // 383 // theme CIAs contain maximum 100 themes + 1 index content #define TMD_SIZE_MIN sizeof(TitleMetaData) #define TMD_SIZE_MAX (sizeof(TitleMetaData) + (TMD_MAX_CONTENTS*sizeof(TmdContentChunk))) diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index a441bd1..2249e6c 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -225,6 +225,10 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { if (fvx_qread(path, tmd, 0, TMD_SIZE_STUB, NULL) != FR_OK) return 1; + // sanity check + if (getbe16(tmd->content_count) > TMD_MAX_CONTENTS) + return 1; + // second part (read full size) if (ValidateTmd(tmd) == 0) { if (fvx_qread(path, tmd, 0, TMD_SIZE_N(getbe16(tmd->content_count)), NULL) != FR_OK) @@ -348,7 +352,7 @@ u32 GetTmdContentPath(char* path_content, const char* path_tmd) { free(tmd); return 1; } - snprintf(name_content, 256 - (name_content - path_content), cdn ? "%08lx" : + snprintf(name_content, 255 - (name_content - path_content), cdn ? "%08lx" : (memcmp(tmd->title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id)); free(tmd); @@ -2173,19 +2177,12 @@ u32 BuildCiaLegitTicket(Ticket* ticket, u8* title_id, const char* path_cnt, bool bool copy = true; if ((cdn && (LoadCdnTicketFile(&ticket_tmp, path_cnt) != 0)) || - (!cdn && (FindTicket(&ticket_tmp, title_id, true, src_emunand) != 0))) { + (!cdn && (FindTicket(&ticket_tmp, title_id, true, src_emunand) != 0)) || + (GetTicketSize(ticket_tmp) != TICKET_COMMON_SIZE)) { FindTitleKey(ticket, title_id); copy = false; } - // 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 (copy && GetTicketSize(ticket_tmp) != TICKET_COMMON_SIZE) { - ShowPrompt(false, "ID %016llX\nLegit ticket of unsupported size.", getbe64(title_id)); - free(ticket_tmp); - return 1; - } - // check the tickets' console id, warn if it isn't zero if (copy && getbe32(ticket_tmp->console_id)) { static u32 default_action = 0; @@ -2392,9 +2389,9 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest, TicketRightsCheck rights_ctx; TicketRightsCheck_InitContext(&rights_ctx, (Ticket*)&(cia->ticket)); snprintf(name_content, 256 - (name_content - path_content), - (cdn) ? "%08lx" : (dlc && !cdn) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id)); + (cdn) ? "%08lx" : (dlc) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id)); if ((fvx_stat(path_content, &fno) != FR_OK) || (fno.fsize != (u32) getbe64(chunk->size)) || - !TicketRightsCheck_CheckIndex(&rights_ctx, getbe16(chunk->index))) { + (!cdn && !TicketRightsCheck_CheckIndex(&rights_ctx, getbe16(chunk->index)))) { present[i / 8] ^= 1 << (i % 8); u16 index = getbe16(chunk->index); diff --git a/arm9/source/virtual/vgame.c b/arm9/source/virtual/vgame.c index 1ccff43..cd3e41d 100644 --- a/arm9/source/virtual/vgame.c +++ b/arm9/source/virtual/vgame.c @@ -108,11 +108,11 @@ static u64 offset_ccnt = (u64) -1; static u64 offset_tad = (u64) -1; static u32 index_ccnt = (u32) -1; -static CiaStub* cia = NULL; +// static CiaStub* cia = NULL; *unused* static TwlHeader* twl = NULL; +static NcsdHeader* ncsd = NULL; static FirmA9LHeader* a9l = NULL; static FirmHeader* firm = NULL; -static NcsdHeader* ncsd = NULL; static NcchHeader* ncch = NULL; static ExeFsHeader* exefs = NULL; static RomFsLv3Index lv3idx; @@ -357,7 +357,7 @@ bool BuildVGameNcsdDir(void) { return true; } -bool BuildVGameCiaDir(void) { +bool BuildVGameCiaDir(CiaStub* cia) { CiaInfo info; VirtualFile* templates = templates_cia; u32 n = 0; @@ -777,21 +777,20 @@ u64 InitVGameDrive(void) { // prerequisite: game file mounted as image vgame_buffer = (void*) malloc(0x40000); if (!vgame_buffer) return 0; - templates_cia = (void*) ((u8*) vgame_buffer); // first 184kb reserved (enough for 3364 entries) - templates_firm = (void*) (((u8*) vgame_buffer) + 0x2E000); // 2kb reserved (enough for 36 entries) - templates_ncsd = (void*) (((u8*) vgame_buffer) + 0x2E800); // 2kb reserved (enough for 36 entries) - templates_ncch = (void*) (((u8*) vgame_buffer) + 0x2F000); // 1kb reserved (enough for 18 entries) - templates_nds = (void*) (((u8*) vgame_buffer) + 0x2F400); // 1kb reserved (enough for 18 entries) - templates_exefs = (void*) (((u8*) vgame_buffer) + 0x2F800); // 1kb reserved (enough for 18 entries) - templates_tad = (void*) (((u8*) vgame_buffer) + 0x2FC00); // 1kb reserved (enough for 18 entries) - cia = (CiaStub*) (void*) (((u8*) vgame_buffer) + 0x30000); // 61kB reserved - should be enough by far - twl = (TwlHeader*) (void*) (((u8*) vgame_buffer) + 0x3F400); // 512 byte reserved (not the full thing) - a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x3F600); // 512 byte reserved - firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x3F800); // 512 byte reserved - ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x3FA00); // 512 byte reserved - ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x3FC00); // 512 byte reserved - exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x3FE00); // 512 byte reserved - // filesystem stuff (RomFS / NitroFS) will be allocated on demand + templates_cia = (void*) ((u8*) vgame_buffer); // first 180kb reserved (enough for 3291 entries) + templates_firm = (void*) (((u8*) vgame_buffer) + 0x2D000); // 2kb reserved (enough for 36 entries) + templates_ncsd = (void*) (((u8*) vgame_buffer) + 0x2D800); // 2kb reserved (enough for 36 entries) + templates_ncch = (void*) (((u8*) vgame_buffer) + 0x2E000); // 1kb reserved (enough for 18 entries) + templates_nds = (void*) (((u8*) vgame_buffer) + 0x2E400); // 1kb reserved (enough for 18 entries) + templates_exefs = (void*) (((u8*) vgame_buffer) + 0x2E800); // 1kb reserved (enough for 18 entries) + templates_tad = (void*) (((u8*) vgame_buffer) + 0x2EC00); // 1kb reserved (enough for 18 entries) + twl = (TwlHeader*) (void*) (((u8*) vgame_buffer) + 0x2F000); // 512 byte reserved (not the full thing) + a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x2F200); // 512 byte reserved + firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x2F400); // 512 byte reserved + ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x2F600); // 512 byte reserved + ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x2F800); // 512 byte reserved + exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x2FA00); // 512 byte reserved (1kb reserve) + // filesystem stuff (RomFS / NitroFS) and CIA/TADX will be allocated on demand vgame_type = type; return type; @@ -842,14 +841,24 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { if (!BuildVGameTadDir()) return false; } else if ((vdir->flags & VFLAG_CIA) && (offset_cia != vdir->offset)) { CiaInfo info; - if ((ReadImageBytes((u8*) cia, 0, 0x20) != 0) || - (ValidateCiaHeader(&(cia->header)) != 0) || - (GetCiaInfo(&info, &(cia->header)) != 0) || - (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) + CiaStub* cia; + u8 __attribute__((aligned(32))) hdr[0x20]; + if ((ReadImageBytes(hdr, 0, 0x20) != 0) || + (ValidateCiaHeader((CiaHeader*) (void*) hdr) != 0) || + (GetCiaInfo(&info, (CiaHeader*) (void*) hdr) != 0) || + !(cia = (CiaStub*) malloc(info.offset_content))) return false; + if (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0) { + free(cia); + return false; + } offset_cia = vdir->offset; // always zero(!) GetTitleKey(cia_titlekey, (Ticket*)&(cia->ticket)); - if (!BuildVGameCiaDir()) return false; + if (!BuildVGameCiaDir(cia)) { + free(cia); + return false; + } + free(cia); } else if ((vdir->flags & VFLAG_NCSD) && (offset_ncsd != vdir->offset)) { if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) || (ValidateNcsdHeader(ncsd) != 0))