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
This commit is contained in:
ZeroSkill 2026-05-24 14:40:59 +03:00 committed by GitHub
parent f477da087c
commit 4058c9c5f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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);