Skip to content

Commit

Permalink
Add LARGEDLC mode for titles with > 1024 contents
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
d0k3 committed Jul 7, 2021
1 parent 33a115b commit c9b6a33
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 56 deletions.
6 changes: 6 additions & 0 deletions Makefile.common
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 81 additions & 18 deletions arm9/source/game/ticket.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,91 @@ u32 ValidateTicketSignature(Ticket* ticket) {
return ret;
}

u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
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
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
};

// 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_COMMON_SIZE); // 0xAC being size of this fake ticket's content index
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
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
memset(&ticket->content_index[sizeof(ticket_cnt_index)], 0xFF, 0x80); // 1024 content indexes
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;
}

Expand Down
7 changes: 5 additions & 2 deletions arm9/source/game/ticket.h
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion arm9/source/game/tmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down
21 changes: 9 additions & 12 deletions arm9/source/utils/gameutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
55 changes: 32 additions & 23 deletions arm9/source/virtual/vgame.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit c9b6a33

Please sign in to comment.