From 4058c9c5f336c7914bb191a875e612a33792dd14 Mon Sep 17 00:00:00 2001 From: ZeroSkill <18742654+ZeroSkill1@users.noreply.github.com> Date: Sun, 24 May 2026 14:40:59 +0300 Subject: [PATCH] Fix install destination detection for CXI-based game files (#953) Fixces #952 * Fix install destination detection * fix NCCH extheader reading logic * check for empty mountpath on title uninstallation --- arm9/source/utils/gameutil.c | 343 +++++++++++++++++++++++++---------- 1 file changed, 245 insertions(+), 98 deletions(-) diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 98f83a6..8635129 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -24,24 +24,24 @@ u32 GetCbcBlocks(FIL* file, void* buffer, u64 offset, u32 count, u8* titlekey, u8* forced_iv) { u8 iv[16] __attribute__((aligned(4))); UINT btr; - + // sanity, get IV if ((count % 0x10) || (offset % 0x10)) return 1; // not possible if (forced_iv) memcpy(iv, forced_iv, 0x10); else if ((offset < 0x10) || (fvx_lseek(file, offset - 0x10) != FR_OK) || (fvx_read(file, iv, 0x10, &btr) != FR_OK) || (btr != 0x10)) return 1; - + // load data if ((fvx_lseek(file, offset) != FR_OK) || (fvx_read(file, buffer, count, &btr) != FR_OK) || (btr != count)) return 1; - + // decrypt if (titlekey) { setup_aeskey(0x11, titlekey); use_aeskey(0x11); - cbc_decrypt(buffer, buffer, count / 0x10, AES_CNT_TITLEKEY_DECRYPT_MODE, iv); + cbc_decrypt(buffer, buffer, count / 0x10, AES_CNT_TITLEKEY_DECRYPT_MODE, iv); } return 0; @@ -231,7 +231,7 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { return 1; // 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) return 1; } else if ((ValidateTwlTmd(tmd) == 0) && (getbe16(tmd->content_count) == 1)) { @@ -310,7 +310,7 @@ u32 LoadTicketForTitleId(Ticket** ticket, const u64 title_id) { char* path_bak = NULL; strncpy(path_store, GetMountPath(), 256); if (*path_store) path_bak = path_store; - + // path to ticket.db char path_ticketdb[32]; char drv = *path_store; @@ -330,7 +330,7 @@ u32 LoadTicketForTitleId(Ticket** ticket, const u64 title_id) { u32 GetTmdContentPath(char* path_content, const char* path_tmd) { // path_content should be 256 bytes in size! - + // get path to TMD first content static const u8 dlc_tid_high[] = { DLC_TID_HIGH }; @@ -904,7 +904,7 @@ u32 VerifyTadFile(const char* path) { return 1; // verify contents - u32 content_start = sizeof(TadStub); + u32 content_start = sizeof(TadStub); for (u32 i = 0; i < TAD_NUM_CONTENT; i++) { u8 hash[32]; u32 len = align(hdr->content_size[i], 0x10); @@ -1046,6 +1046,143 @@ u32 VerifyTicketFile(const char* path) { return res; } +u32 LoadNcchFromTmdFile(const char *path, NcchHeader *ncch, NcchExtHeader *extheader) { + char pathstr[UTF_BUFFER_BYTESIZE(32)]; + TruncateString(pathstr, path, 32, 8); + + // content path + char path_content[256]; + char *name_content; // points to content file name (%08lx) + strncpy(path_content, path, 256); + path_content[255] = '\0'; + name_content = strrchr(path_content, '/'); + if (!name_content) return 1; + name_content++; + + TitleMetaData *tmd = (TitleMetaData*) malloc(TMD_SIZE_MAX); + TmdContentChunk *content_list = (TmdContentChunk*) (tmd + 1); + if ((LoadTmdFile(tmd, path) != 0) || (VerifyTmd(tmd) != 0)) { + ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_TMD_PROBABLY_CORRUPTED); + free(tmd); + return 1; + } + + u8 titlekey[0x10] = { 0xFF }; + Ticket* ticket = NULL; + if (!((LoadCdnTicketFile(&ticket, path) == 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\n%s", pathstr, STR_ERROR_CDN_TITLEKEY_NOT_FOUND); + free(ticket); + free(tmd); + return 1; + } + free(ticket); + + u32 content_count = getbe16(tmd->content_count); + u32 res = 0; + bool found = false; + TmdContentChunk chunk = { 0 }; + for (u32 i = 0; !res && i < content_count && (i < TMD_MAX_CONTENTS); i++) { + TmdContentChunk *tmp = &(content_list[i]); + if (getbe16(tmp->index) == 0) { + found = true; + memcpy(&chunk, tmp, sizeof(TmdContentChunk)); + break; + } + } + + free(tmd); + + if (!found) { + return 1; + } + + // path to content with index 0 + snprintf(name_content, 256 - (name_content - path_content), "%08lx", getbe32(chunk.id)); + bool encrypted = getbe16(chunk.type) & 0x1; + u8 ctr[16]; + + GetTmdCtr(ctr, &chunk); + + if (fvx_qread(path_content, ncch, 0, sizeof(NcchHeader), NULL) != FR_OK || + (extheader && (fvx_qread(path_content, extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) != FR_OK))) + return 1; + + if (encrypted) { + DecryptCiaContentSequential(ncch, sizeof(NcchHeader), ctr, titlekey); + if (extheader) + DecryptCiaContentSequential(extheader, sizeof(NcchExtHeader), ctr, titlekey); + } + + if (ValidateNcchHeader(ncch) != 0 || + (extheader && (DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) != 0))) + return 1; + + return 0; +} + +u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch, NcchExtHeader* extheader) { + u64 filetype = IdentifyFileType(path); + + if (filetype & GAME_NCCH) { + if ((fvx_qread(path, ncch, 0, sizeof(NcchHeader), NULL) == FR_OK) && + (ValidateNcchHeader(ncch) == 0) && + (!extheader || ((fvx_qread(path, extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) == FR_OK) && + DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0))) return 0; + } else if (filetype & GAME_NCSD) { + if ((fvx_qread(path, ncch, NCSD_CNT0_OFFSET, sizeof(NcchHeader), NULL) == FR_OK) && + (ValidateNcchHeader(ncch) == 0) && + (!extheader || ((fvx_qread(path, extheader, NCSD_CINFO_OFFSET + NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) == 0) && + DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0))) return 0; + } else if (filetype & GAME_CDNTMD) { + return LoadNcchFromTmdFile(path, ncch, extheader); + } else if (filetype & GAME_CIA) { + CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); + CiaInfo info; + + // load CIA stub from path + if ((LoadCiaStub(cia, path) != 0) || + (GetCiaInfo(&info, &(cia->header)) != 0)) { + free(cia); + return 1; + } + + // decrypt / load NCCH header from first CIA content + u32 ret = 1; + if (getbe16(cia->tmd.content_count)) { + TmdContentChunk* chunk = cia->content_list; + if ((getbe64(chunk->size) < sizeof(NcchHeader)) || + (fvx_qread(path, ncch, info.offset_content, sizeof(NcchHeader), NULL) != FR_OK) || + (extheader && (fvx_qread(path, extheader, info.offset_content + NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) != FR_OK))) { + free(cia); + return 1; + } + if (getbe16(chunk->type) & 0x1) { // decrypt first content header + u8 titlekey[16]; + u8 ctr[16]; + GetTmdCtr(ctr, chunk); + if (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) { + free(cia); + return 1; + } + DecryptCiaContentSequential((void*) ncch, sizeof(NcchHeader), ctr, titlekey); + if (extheader) + DecryptCiaContentSequential((void*) extheader, sizeof(NcchExtHeader), ctr, titlekey); + } + if (ValidateNcchHeader(ncch) == 0 && + (!extheader || DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0)) ret = 0; + } + + free(cia); + return ret; + } + + return 1; +} + u32 VerifyGameFile(const char* path, bool sig_check) { u64 filetype = IdentifyFileType(path); if (filetype & GAME_CIA) @@ -1509,7 +1646,28 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt, bool restore) { return ret; } -u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) { +u32 GetTwlInstallDataDrive(char *drv, bool to_emunand) { + drv[0] = to_emunand ? '5' : '2'; + drv[1] = ':'; + drv[2] = '\0'; + return 0; +} + +u32 GetSdInstallDataDrive(char *drv, bool to_emunand) { + drv[0] = to_emunand ? 'B' : 'A'; + drv[1] = ':'; + drv[2] = '\0'; + return 0; +} + +u32 GetNandInstallDataDrive(char *drv, bool to_emunand) { + drv[0] = to_emunand ? '4' : '1'; + drv[1] = ':'; + drv[2] = '\0'; + return 0; +} + +u32 GetInstallDataDriveGeneric(char* drv, u64 tid64, bool to_emunand) { // check the title id bool to_twl = ((tid64 >> 32) & 0x8000); bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10)); @@ -1517,14 +1675,8 @@ u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) { // sanity if (!tid64) return 1; - // determine the correct drive - drv[0] = to_emunand ? - (to_twl ? '5' : to_sd ? 'B' : '4') : - (to_twl ? '2' : to_sd ? 'A' : '1'); - drv[1] = ':'; - drv[2] = '\0'; - - return 0; + return to_twl ? GetTwlInstallDataDrive(drv, to_emunand) : + to_sd ? GetSdInstallDataDrive(drv, to_emunand) : GetNandInstallDataDrive(drv, to_emunand); } u32 GetInstallDbsPath(char* path, const char* drv, const char* str) { @@ -1628,14 +1780,9 @@ u32 CreateSaveData(const char* drv, u64 tid64, const char* name, u32 save_size, return 0; } -u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) { - char drv[3]; - +u32 UninstallGameData(const char *drv, u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save) { // check permissions for SysNAND (this includes everything we need) - if (!CheckWritePermissions(from_emunand ? "4:" : "1:")) return 1; - - // determine the drive - if (GetInstallDataDrive(drv, tid64, from_emunand) != 0) return 1; + if (!CheckWritePermissions(drv)) return 1; // remove data path char path_data[256]; @@ -1694,7 +1841,7 @@ u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, u64 tid64; const char* mntpath = GetMountPath(); - if (!mntpath) return 1; + if (!mntpath || !*mntpath) return 1; // title.db from emunand? if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) || @@ -1705,7 +1852,18 @@ u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, if (sscanf(path, "T:/%016llx", &tid64) != 1) return 1; - return UninstallGameData(tid64, remove_tie, remove_ticket, remove_save, from_emunand); + // tid here is in the 00048000... format if it's a TWL title + bool is_twl = ((tid64 >> 32) & 0x8000) == 0x8000; + char drv[3] = { 0xFF, ':', '\0' }; + + // in all cases except for TWL titles, the uninstall drive is the same as the drive + // containing the .db file that is mounted. + if (is_twl) + drv[0] = from_emunand ? '5' : '2'; + else + drv[0] = mntpath[0]; + + return UninstallGameData(drv, tid64, remove_tie, remove_ticket, remove_save); } u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool cia_meta) { @@ -1723,7 +1881,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool void* data = (void*) malloc(max(sizeof(CiaStub), 0x1000)); if (!data) return 0; - + // get content path/offset and TMD data TitleMetaData* tmd = (TitleMetaData*) data; Ticket* ticket = NULL; @@ -1744,7 +1902,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool free(data); return 0; } - + // title_id, titlekey & iv TmdContentChunk* chunk = (TmdContentChunk*) (tmd+1); GetTmdCtr(iv, chunk); @@ -1757,7 +1915,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool return 0; } } - + // load first block of data FIL file; if (fvx_open(&file, path_cnt, FA_READ | FA_OPEN_EXISTING) != FR_OK) { @@ -1769,7 +1927,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool free(data); return 0; } - + // find out what it is and proceed u32 ret = 0; if (!cia_meta && ValidateTwlHeader((TwlHeader*) data) == 0) { @@ -1787,7 +1945,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool if (cia_meta) { CiaMeta* meta = (CiaMeta*) output; NcchExtHeader* exthdr = (NcchExtHeader*) (void*) (((u8*)data) + NCCH_EXTHDR_OFFSET); - if (!ncch->size_exthdr || + if (!ncch->size_exthdr || (DecryptNcch(exthdr, NCCH_EXTHDR_OFFSET, NCCH_EXTHDR_SIZE, ncch, NULL) != 0) || (BuildCiaMeta(meta, exthdr, NULL) != 0)) { fvx_close(&file); @@ -2193,7 +2351,7 @@ u32 BuildCiaLegitTicket(Ticket* ticket, u8* title_id, const char* path_cnt, bool bool src_emunand = ((*path_cnt == 'B') || (*path_cnt == '4')); if (BuildFakeTicket(ticket, title_id) != 0) - return 1; + return 1; if (force_legit) { Ticket* ticket_tmp = NULL; @@ -2268,7 +2426,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_ if ((fvx_qread(path_tad, &tad, 0, sizeof(TadStub), NULL) != FR_OK) || (VerifyTadStub(&tad) != 0) || (hdr->content_size[0] < TMD_SIZE_N(1))) return 1; - + // build the CIA stub CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); if (!cia) return 1; @@ -2285,7 +2443,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_ free(cia); return 1; } - + // extract info from TMD u32 content_count = getbe16(tmd->content_count); u8* title_id = tmd->title_id; @@ -2293,7 +2451,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_ free(cia); return 1; } - + // check for legit TMD if (force_legit && ((ValidateTmdSignature(tmd) != 0) || VerifyTmd(tmd) != 0)) { ShowPrompt(false, STR_ID_N_TMD_IN_TAD_NOT_LEGIT, getbe64(title_id)); @@ -2320,7 +2478,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_ free(cia); return 1; } - + // attempt to find a titlekey u8 titlekey[16] = { 0xFF }; FindTitleKey((Ticket*) ticket, title_id); @@ -2790,7 +2948,7 @@ u32 GetGameFileTitleVersion(const char* path) { if (cia && LoadCiaStub(cia, path) == 0) version = getbe16(cia->tmd.title_version); if (cia) free(cia); - } + } return version; } @@ -2869,14 +3027,51 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) { return ret; } +u32 GetInstallDataDriveForGameFile(const char *path, u64 filetype, u64 titleid, char *drv, bool to_emunand) { + // CTR filetypes + if (filetype & (GAME_CIA | GAME_NCSD | GAME_NCCH | GAME_CDNTMD)) { + if ((filetype & GAME_CIA) && (titleid >> 32) & 0x8000) { + // only CIAs may be either CTR or TWL titles + GetTwlInstallDataDrive(drv, to_emunand); + } else if ((titleid >> 32) == 0x0004008C || (filetype & GAME_NCSD)) { + // DLC, although it does not contain a CXI, must be installed to SD always + // gamecarts, and backups thereof, are also always installed to SD + GetSdInstallDataDrive(drv, to_emunand); + } else { + // definitely a CTR title + NcchHeader ncch; + NcchExtHeader extheader; + + if (LoadNcchFromGameFile(path, &ncch, &extheader) != 0) + return 1; + + if (!NCCH_IS_CXI(&ncch)) { + // for CFA, we don't have much to go off of. but CFAs can just be separated by the system title bit, + // as most CFAs are either part of system titles (NAND) or instruction manuals (SD). + if (GetInstallDataDriveGeneric(drv, titleid, to_emunand) != 0) + return 1; + } else { + // for files that are CXIs or files whose first content is a CXI, we can check the exheader for the SD bit + if (extheader.flag & BIT(1)) + GetSdInstallDataDrive(drv, to_emunand); + else + GetNandInstallDataDrive(drv, to_emunand); + } + } + } else { // TWL file types (GAME_NDS, GAME_TWLTMD) + GetTwlInstallDataDrive(drv, to_emunand); + } + + return 0; +} + u32 InstallGameFile(const char* path, bool to_emunand) { char drv[3]; u64 filetype = IdentifyFileType(path); + u64 tid64 = GetGameFileTitleId(path); u32 ret = 0; - // find out the destination - u64 tid64 = GetGameFileTitleId(path); - if (GetInstallDataDrive(drv, tid64, to_emunand) != 0) + if (GetInstallDataDriveForGameFile(path, filetype, tid64, drv, to_emunand) != 0) return 1; // check dbs @@ -2893,7 +3088,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) { // cleanup content folder before starting install ShowProgress(0, 0, path); - UninstallGameData(tid64, false, false, false, to_emunand); + UninstallGameData(drv, tid64, false, false, false); // install game file if (filetype & GAME_CIA) @@ -2909,7 +3104,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) { else ret = 1; // cleanup on failed installs, but leave ticket and save untouched - if (ret != 0) UninstallGameData(tid64, true, false, false, to_emunand); + if (ret != 0) UninstallGameData(drv, tid64, true, false, false); return ret; } @@ -3068,7 +3263,7 @@ u32 DumpTicketForGameFile(const char* path, bool force_legit) { free(ticket); return 1; } - + // build output name char dest[256]; snprintf(dest, sizeof(dest), OUTPUT_PATH "/"); @@ -3131,7 +3326,7 @@ u32 DumpCxiSrlFromGameFile(const char* path) { return 1; path_tmd = path_data; } - + return DumpCxiSrlFromTmdFile(path_tmd); } @@ -3268,7 +3463,7 @@ u32 CompressCode(const char* path, const char* path_out) { u64 GetAnyFileTrimmedSize(const char* path) { u64 fsize = 0; u64 trimsize = 0; - u8 pad_byte = 0x7F; + u8 pad_byte = 0x7F; FIL fp; UINT br; @@ -3513,7 +3708,7 @@ u32 ShowGameCheckerInfo(const char* path) { content_found = content_count = getbe16(tmd->content_count); sd_title = (*path_tmd == 'A') || (*path_tmd == 'B'); } - + // load ticket u64 tid64 = tmd ? getbe64(tmd->title_id) : 0; if (LoadTicketForTitleId(&ticket, tid64) != 0) @@ -3552,7 +3747,7 @@ u32 ShowGameCheckerInfo(const char* path) { memcpy(tmd, &(cia->tmd), cia->header.size_tmd); ticket = (Ticket*) malloc(cia->header.size_ticket); if (ticket) memcpy(ticket, &(cia->ticket), cia->header.size_ticket); - + free(cia); } @@ -3854,7 +4049,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { u32 num_entries = 0; u8* title_ids = NULL; - if (!InitImgFS(path_in) || + if (!InitImgFS(path_in) || !(num_entries = GetNumTickets(PART_PATH)) || !(title_ids = (u8*) malloc(num_entries * 8)) || (ListTicketTitleIDs(PART_PATH, title_ids, num_entries) != 0)) { @@ -3871,7 +4066,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) { AddTicketToInfo(tik_info, ticket, dec); // ignore result free(ticket); } - + free(title_ids); InitImgFS(NULL); } else if (filetype & BIN_TIKDB) { @@ -4008,54 +4203,6 @@ u32 BuildSeedInfo(const char* path, bool dump) { return 0; } -u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch) { - u64 filetype = IdentifyFileType(path); - - if (filetype & GAME_NCCH) { - if ((fvx_qread(path, ncch, 0, sizeof(NcchHeader), NULL) == FR_OK) && - (ValidateNcchHeader(ncch) == 0)) return 0; - } else if (filetype & GAME_NCSD) { - if ((fvx_qread(path, ncch, NCSD_CNT0_OFFSET, sizeof(NcchHeader), NULL) == FR_OK) && - (ValidateNcchHeader(ncch) == 0)) return 0; - } else if (filetype & GAME_CIA) { - CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub)); - CiaInfo info; - - // load CIA stub from path - if ((LoadCiaStub(cia, path) != 0) || - (GetCiaInfo(&info, &(cia->header)) != 0)) { - free(cia); - return 1; - } - - // decrypt / load NCCH header from first CIA content - u32 ret = 1; - if (getbe16(cia->tmd.content_count)) { - TmdContentChunk* chunk = cia->content_list; - if ((getbe64(chunk->size) < sizeof(NcchHeader)) || - (fvx_qread(path, ncch, info.offset_content, sizeof(NcchHeader), NULL) != FR_OK)) { - free(cia); - return 1; - } - if (getbe16(chunk->type) & 0x1) { // decrypt first content header - u8 titlekey[16]; - u8 ctr[16]; - GetTmdCtr(ctr, chunk); - if (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) { - free(cia); - return 1; - } - DecryptCiaContentSequential((void*) ncch, sizeof(NcchHeader), ctr, titlekey); - } - if (ValidateNcchHeader(ncch) == 0) ret = 0; - } - - free(cia); - return ret; - } - - return 1; -} u32 GetGoodName(char* name, const char* path, bool quick) { // name should be 128+1 byte @@ -4117,7 +4264,7 @@ u32 GetGoodName(char* name, const char* path, bool quick) { if (LoadTwlMetaData(path_donor, (TwlHeader*) header, quick ? NULL : (TwlIconData*) icon) != 0) type_donor = 0; } else if (type_donor & (GAME_NCSD|GAME_NCCH)) { // CTR (data from NCCH) - if (LoadNcchFromGameFile(path_donor, (NcchHeader*) header) != 0) type_donor = 0; + if (LoadNcchFromGameFile(path_donor, (NcchHeader*) header, NULL) != 0) type_donor = 0; if (!quick && (LoadSmdhFromGameFile(path_donor, (Smdh*) icon) != 0)) quick = true; } else if (type_donor & (GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) { // encrypted type_donor = LoadEncryptedIconFromCiaTmd(path_donor, icon, header, false);