Add LARGEDLC mode for titles with > 1024 contents

Fixes #703 and is only active with `make LARGEDLC=1` and will break compatibility with other titles and CIAs. Thanks @luigoalma for new ticket builder code!
This commit is contained in:
d0k3 2021-07-07 18:00:11 +02:00
parent 33a115b75c
commit c9b6a335f7
6 changed files with 137 additions and 59 deletions

View File

@ -25,6 +25,12 @@ else ifeq ($(FLAVOR),ZuishMode9)
CFLAGS += -DDEFAULT_FONT=\"font_zuish_8x8.pbm\" CFLAGS += -DDEFAULT_FONT=\"font_zuish_8x8.pbm\"
endif endif
ifeq ($(LARGEDLC),1)
CFLAGS += -DTITLE_MAX_CONTENTS=1536
else
CFLAGS += -DTITLE_MAX_CONTENTS=1024
endif
ifeq ($(SALTMODE),1) ifeq ($(SALTMODE),1)
CFLAGS += -DSALTMODE CFLAGS += -DSALTMODE
endif endif

View File

@ -41,28 +41,91 @@ u32 ValidateTicketSignature(Ticket* ticket) {
return ret; return ret;
} }
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { u32 BuildVariableFakeTicket(Ticket** ticket, u32* ticket_size, const u8* title_id, u32 index_max) {
static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256 if (!ticket || !ticket_size)
static const u8 ticket_cnt_index[] = { // whatever this is return 1;
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
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; return 0;
} }

View File

@ -1,11 +1,14 @@
#pragma once #pragma once
#include "common.h" #include "common.h"
#include "tmd.h"
#define TICKET_COMMON_SIZE sizeof(TicketCommon) #define TICKET_COMMON_SIZE sizeof(TicketCommon)
#define TICKET_MINIMUM_SIZE sizeof(TicketMinimum) #define TICKET_MINIMUM_SIZE sizeof(TicketMinimum)
#define TICKET_TWL_SIZE sizeof(Ticket) #define TICKET_TWL_SIZE sizeof(Ticket)
#define TICKET_CDNCERT_SIZE 0x700 #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 "Root-CA00000003-XS0000000c"
#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" #define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
@ -54,7 +57,7 @@ typedef struct {
typedef struct { typedef struct {
TICKETBASE; TICKETBASE;
u8 content_index[0xAC]; u8 content_index[TICKET_COMMON_CNT_INDEX_SIZE];
} PACKED_STRUCT TicketCommon; } PACKED_STRUCT TicketCommon;
// minimum allowed content_index is 0x14 // minimum allowed content_index is 0x14
@ -97,7 +100,7 @@ typedef struct {
u32 ValidateTicket(Ticket* ticket); u32 ValidateTicket(Ticket* ticket);
u32 ValidateTwlTicket(Ticket* ticket); u32 ValidateTwlTicket(Ticket* ticket);
u32 ValidateTicketSignature(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 GetTicketContentIndexSize(const Ticket* ticket);
u32 GetTicketSize(const Ticket* ticket); u32 GetTicketSize(const Ticket* ticket);
u32 BuildTicketCert(u8* tickcert); u32 BuildTicketCert(u8* tickcert);

View File

@ -2,7 +2,7 @@
#include "common.h" #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_MIN sizeof(TitleMetaData)
#define TMD_SIZE_MAX (sizeof(TitleMetaData) + (TMD_MAX_CONTENTS*sizeof(TmdContentChunk))) #define TMD_SIZE_MAX (sizeof(TitleMetaData) + (TMD_MAX_CONTENTS*sizeof(TmdContentChunk)))

View File

@ -225,6 +225,10 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
if (fvx_qread(path, tmd, 0, TMD_SIZE_STUB, NULL) != FR_OK) if (fvx_qread(path, tmd, 0, TMD_SIZE_STUB, NULL) != FR_OK)
return 1; return 1;
// sanity check
if (getbe16(tmd->content_count) > TMD_MAX_CONTENTS)
return 1;
// second part (read full size) // second part (read full size)
if (ValidateTmd(tmd) == 0) { if (ValidateTmd(tmd) == 0) {
if (fvx_qread(path, tmd, 0, TMD_SIZE_N(getbe16(tmd->content_count)), NULL) != FR_OK) 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); free(tmd);
return 1; 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)); (memcmp(tmd->title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0) ? "00000000/%08lx.app" : "%08lx.app", getbe32(chunk->id));
free(tmd); free(tmd);
@ -2173,19 +2177,12 @@ u32 BuildCiaLegitTicket(Ticket* ticket, u8* title_id, const char* path_cnt, bool
bool copy = true; bool copy = true;
if ((cdn && (LoadCdnTicketFile(&ticket_tmp, path_cnt) != 0)) || 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); FindTitleKey(ticket, title_id);
copy = false; 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 // check the tickets' console id, warn if it isn't zero
if (copy && getbe32(ticket_tmp->console_id)) { if (copy && getbe32(ticket_tmp->console_id)) {
static u32 default_action = 0; static u32 default_action = 0;
@ -2392,9 +2389,9 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest,
TicketRightsCheck rights_ctx; TicketRightsCheck rights_ctx;
TicketRightsCheck_InitContext(&rights_ctx, (Ticket*)&(cia->ticket)); TicketRightsCheck_InitContext(&rights_ctx, (Ticket*)&(cia->ticket));
snprintf(name_content, 256 - (name_content - path_content), 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)) || 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); present[i / 8] ^= 1 << (i % 8);
u16 index = getbe16(chunk->index); u16 index = getbe16(chunk->index);

View File

@ -108,11 +108,11 @@ static u64 offset_ccnt = (u64) -1;
static u64 offset_tad = (u64) -1; static u64 offset_tad = (u64) -1;
static u32 index_ccnt = (u32) -1; static u32 index_ccnt = (u32) -1;
static CiaStub* cia = NULL; // static CiaStub* cia = NULL; *unused*
static TwlHeader* twl = NULL; static TwlHeader* twl = NULL;
static NcsdHeader* ncsd = NULL;
static FirmA9LHeader* a9l = NULL; static FirmA9LHeader* a9l = NULL;
static FirmHeader* firm = NULL; static FirmHeader* firm = NULL;
static NcsdHeader* ncsd = NULL;
static NcchHeader* ncch = NULL; static NcchHeader* ncch = NULL;
static ExeFsHeader* exefs = NULL; static ExeFsHeader* exefs = NULL;
static RomFsLv3Index lv3idx; static RomFsLv3Index lv3idx;
@ -357,7 +357,7 @@ bool BuildVGameNcsdDir(void) {
return true; return true;
} }
bool BuildVGameCiaDir(void) { bool BuildVGameCiaDir(CiaStub* cia) {
CiaInfo info; CiaInfo info;
VirtualFile* templates = templates_cia; VirtualFile* templates = templates_cia;
u32 n = 0; u32 n = 0;
@ -777,21 +777,20 @@ u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
vgame_buffer = (void*) malloc(0x40000); vgame_buffer = (void*) malloc(0x40000);
if (!vgame_buffer) return 0; if (!vgame_buffer) return 0;
templates_cia = (void*) ((u8*) vgame_buffer); // first 184kb reserved (enough for 3364 entries) templates_cia = (void*) ((u8*) vgame_buffer); // first 180kb reserved (enough for 3291 entries)
templates_firm = (void*) (((u8*) vgame_buffer) + 0x2E000); // 2kb reserved (enough for 36 entries) templates_firm = (void*) (((u8*) vgame_buffer) + 0x2D000); // 2kb reserved (enough for 36 entries)
templates_ncsd = (void*) (((u8*) vgame_buffer) + 0x2E800); // 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) + 0x2F000); // 1kb reserved (enough for 18 entries) templates_ncch = (void*) (((u8*) vgame_buffer) + 0x2E000); // 1kb reserved (enough for 18 entries)
templates_nds = (void*) (((u8*) vgame_buffer) + 0x2F400); // 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) + 0x2F800); // 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) + 0x2FC00); // 1kb reserved (enough for 18 entries) templates_tad = (void*) (((u8*) vgame_buffer) + 0x2EC00); // 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) + 0x2F000); // 512 byte reserved (not the full thing)
twl = (TwlHeader*) (void*) (((u8*) vgame_buffer) + 0x3F400); // 512 byte reserved (not the full thing) a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x2F200); // 512 byte reserved
a9l = (FirmA9LHeader*) (void*) (((u8*) vgame_buffer) + 0x3F600); // 512 byte reserved firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x2F400); // 512 byte reserved
firm = (FirmHeader*) (void*) (((u8*) vgame_buffer) + 0x3F800); // 512 byte reserved ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x2F600); // 512 byte reserved
ncsd = (NcsdHeader*) (void*) (((u8*) vgame_buffer) + 0x3FA00); // 512 byte reserved ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x2F800); // 512 byte reserved
ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x3FC00); // 512 byte reserved exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x2FA00); // 512 byte reserved (1kb reserve)
exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x3FE00); // 512 byte reserved // filesystem stuff (RomFS / NitroFS) and CIA/TADX will be allocated on demand
// filesystem stuff (RomFS / NitroFS) will be allocated on demand
vgame_type = type; vgame_type = type;
return type; return type;
@ -842,14 +841,24 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
if (!BuildVGameTadDir()) return false; if (!BuildVGameTadDir()) return false;
} else if ((vdir->flags & VFLAG_CIA) && (offset_cia != vdir->offset)) { } else if ((vdir->flags & VFLAG_CIA) && (offset_cia != vdir->offset)) {
CiaInfo info; CiaInfo info;
if ((ReadImageBytes((u8*) cia, 0, 0x20) != 0) || CiaStub* cia;
(ValidateCiaHeader(&(cia->header)) != 0) || u8 __attribute__((aligned(32))) hdr[0x20];
(GetCiaInfo(&info, &(cia->header)) != 0) || if ((ReadImageBytes(hdr, 0, 0x20) != 0) ||
(ReadImageBytes((u8*) cia, 0, info.offset_content) != 0)) (ValidateCiaHeader((CiaHeader*) (void*) hdr) != 0) ||
(GetCiaInfo(&info, (CiaHeader*) (void*) hdr) != 0) ||
!(cia = (CiaStub*) malloc(info.offset_content)))
return false; return false;
if (ReadImageBytes((u8*) cia, 0, info.offset_content) != 0) {
free(cia);
return false;
}
offset_cia = vdir->offset; // always zero(!) offset_cia = vdir->offset; // always zero(!)
GetTitleKey(cia_titlekey, (Ticket*)&(cia->ticket)); 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)) { } else if ((vdir->flags & VFLAG_NCSD) && (offset_ncsd != vdir->offset)) {
if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) || if ((ReadImageBytes((u8*) ncsd, 0, sizeof(NcsdHeader)) != 0) ||
(ValidateNcsdHeader(ncsd) != 0)) (ValidateNcsdHeader(ncsd) != 0))