mirror of
https://github.com/d0k3/GodMode9.git
synced 2026-05-30 22:36:55 +00:00
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:
parent
f477da087c
commit
4058c9c5f3
@ -24,24 +24,24 @@
|
|||||||
u32 GetCbcBlocks(FIL* file, void* buffer, u64 offset, u32 count, u8* titlekey, u8* forced_iv) {
|
u32 GetCbcBlocks(FIL* file, void* buffer, u64 offset, u32 count, u8* titlekey, u8* forced_iv) {
|
||||||
u8 iv[16] __attribute__((aligned(4)));
|
u8 iv[16] __attribute__((aligned(4)));
|
||||||
UINT btr;
|
UINT btr;
|
||||||
|
|
||||||
// sanity, get IV
|
// sanity, get IV
|
||||||
if ((count % 0x10) || (offset % 0x10)) return 1; // not possible
|
if ((count % 0x10) || (offset % 0x10)) return 1; // not possible
|
||||||
if (forced_iv) memcpy(iv, forced_iv, 0x10);
|
if (forced_iv) memcpy(iv, forced_iv, 0x10);
|
||||||
else if ((offset < 0x10) || (fvx_lseek(file, offset - 0x10) != FR_OK) ||
|
else if ((offset < 0x10) || (fvx_lseek(file, offset - 0x10) != FR_OK) ||
|
||||||
(fvx_read(file, iv, 0x10, &btr) != FR_OK) || (btr != 0x10))
|
(fvx_read(file, iv, 0x10, &btr) != FR_OK) || (btr != 0x10))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// load data
|
// load data
|
||||||
if ((fvx_lseek(file, offset) != FR_OK) ||
|
if ((fvx_lseek(file, offset) != FR_OK) ||
|
||||||
(fvx_read(file, buffer, count, &btr) != FR_OK) || (btr != count))
|
(fvx_read(file, buffer, count, &btr) != FR_OK) || (btr != count))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
if (titlekey) {
|
if (titlekey) {
|
||||||
setup_aeskey(0x11, titlekey);
|
setup_aeskey(0x11, titlekey);
|
||||||
use_aeskey(0x11);
|
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;
|
return 0;
|
||||||
@ -231,7 +231,7 @@ u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
|
|||||||
return 1;
|
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)
|
||||||
return 1;
|
return 1;
|
||||||
} else if ((ValidateTwlTmd(tmd) == 0) && (getbe16(tmd->content_count) == 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;
|
char* path_bak = NULL;
|
||||||
strncpy(path_store, GetMountPath(), 256);
|
strncpy(path_store, GetMountPath(), 256);
|
||||||
if (*path_store) path_bak = path_store;
|
if (*path_store) path_bak = path_store;
|
||||||
|
|
||||||
// path to ticket.db
|
// path to ticket.db
|
||||||
char path_ticketdb[32];
|
char path_ticketdb[32];
|
||||||
char drv = *path_store;
|
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) {
|
u32 GetTmdContentPath(char* path_content, const char* path_tmd) {
|
||||||
// path_content should be 256 bytes in size!
|
// path_content should be 256 bytes in size!
|
||||||
|
|
||||||
// get path to TMD first content
|
// get path to TMD first content
|
||||||
static const u8 dlc_tid_high[] = { DLC_TID_HIGH };
|
static const u8 dlc_tid_high[] = { DLC_TID_HIGH };
|
||||||
|
|
||||||
@ -904,7 +904,7 @@ u32 VerifyTadFile(const char* path) {
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// verify contents
|
// verify contents
|
||||||
u32 content_start = sizeof(TadStub);
|
u32 content_start = sizeof(TadStub);
|
||||||
for (u32 i = 0; i < TAD_NUM_CONTENT; i++) {
|
for (u32 i = 0; i < TAD_NUM_CONTENT; i++) {
|
||||||
u8 hash[32];
|
u8 hash[32];
|
||||||
u32 len = align(hdr->content_size[i], 0x10);
|
u32 len = align(hdr->content_size[i], 0x10);
|
||||||
@ -1046,6 +1046,143 @@ u32 VerifyTicketFile(const char* path) {
|
|||||||
return res;
|
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) {
|
u32 VerifyGameFile(const char* path, bool sig_check) {
|
||||||
u64 filetype = IdentifyFileType(path);
|
u64 filetype = IdentifyFileType(path);
|
||||||
if (filetype & GAME_CIA)
|
if (filetype & GAME_CIA)
|
||||||
@ -1509,7 +1646,28 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt, bool restore) {
|
|||||||
return ret;
|
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
|
// check the title id
|
||||||
bool to_twl = ((tid64 >> 32) & 0x8000);
|
bool to_twl = ((tid64 >> 32) & 0x8000);
|
||||||
bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10));
|
bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10));
|
||||||
@ -1517,14 +1675,8 @@ u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) {
|
|||||||
// sanity
|
// sanity
|
||||||
if (!tid64) return 1;
|
if (!tid64) return 1;
|
||||||
|
|
||||||
// determine the correct drive
|
return to_twl ? GetTwlInstallDataDrive(drv, to_emunand) :
|
||||||
drv[0] = to_emunand ?
|
to_sd ? GetSdInstallDataDrive(drv, to_emunand) : GetNandInstallDataDrive(drv, to_emunand);
|
||||||
(to_twl ? '5' : to_sd ? 'B' : '4') :
|
|
||||||
(to_twl ? '2' : to_sd ? 'A' : '1');
|
|
||||||
drv[1] = ':';
|
|
||||||
drv[2] = '\0';
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GetInstallDbsPath(char* path, const char* drv, const char* str) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) {
|
u32 UninstallGameData(const char *drv, u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save) {
|
||||||
char drv[3];
|
|
||||||
|
|
||||||
// check permissions for SysNAND (this includes everything we need)
|
// check permissions for SysNAND (this includes everything we need)
|
||||||
if (!CheckWritePermissions(from_emunand ? "4:" : "1:")) return 1;
|
if (!CheckWritePermissions(drv)) return 1;
|
||||||
|
|
||||||
// determine the drive
|
|
||||||
if (GetInstallDataDrive(drv, tid64, from_emunand) != 0) return 1;
|
|
||||||
|
|
||||||
// remove data path
|
// remove data path
|
||||||
char path_data[256];
|
char path_data[256];
|
||||||
@ -1694,7 +1841,7 @@ u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket,
|
|||||||
u64 tid64;
|
u64 tid64;
|
||||||
|
|
||||||
const char* mntpath = GetMountPath();
|
const char* mntpath = GetMountPath();
|
||||||
if (!mntpath) return 1;
|
if (!mntpath || !*mntpath) return 1;
|
||||||
|
|
||||||
// title.db from emunand?
|
// title.db from emunand?
|
||||||
if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) ||
|
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)
|
if (sscanf(path, "T:/%016llx", &tid64) != 1)
|
||||||
return 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) {
|
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));
|
void* data = (void*) malloc(max(sizeof(CiaStub), 0x1000));
|
||||||
if (!data) return 0;
|
if (!data) return 0;
|
||||||
|
|
||||||
// get content path/offset and TMD data
|
// get content path/offset and TMD data
|
||||||
TitleMetaData* tmd = (TitleMetaData*) data;
|
TitleMetaData* tmd = (TitleMetaData*) data;
|
||||||
Ticket* ticket = NULL;
|
Ticket* ticket = NULL;
|
||||||
@ -1744,7 +1902,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool
|
|||||||
free(data);
|
free(data);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// title_id, titlekey & iv
|
// title_id, titlekey & iv
|
||||||
TmdContentChunk* chunk = (TmdContentChunk*) (tmd+1);
|
TmdContentChunk* chunk = (TmdContentChunk*) (tmd+1);
|
||||||
GetTmdCtr(iv, chunk);
|
GetTmdCtr(iv, chunk);
|
||||||
@ -1757,7 +1915,7 @@ u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load first block of data
|
// load first block of data
|
||||||
FIL file;
|
FIL file;
|
||||||
if (fvx_open(&file, path_cnt, FA_READ | FA_OPEN_EXISTING) != FR_OK) {
|
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);
|
free(data);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find out what it is and proceed
|
// find out what it is and proceed
|
||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
if (!cia_meta && ValidateTwlHeader((TwlHeader*) data) == 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) {
|
if (cia_meta) {
|
||||||
CiaMeta* meta = (CiaMeta*) output;
|
CiaMeta* meta = (CiaMeta*) output;
|
||||||
NcchExtHeader* exthdr = (NcchExtHeader*) (void*) (((u8*)data) + NCCH_EXTHDR_OFFSET);
|
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) ||
|
(DecryptNcch(exthdr, NCCH_EXTHDR_OFFSET, NCCH_EXTHDR_SIZE, ncch, NULL) != 0) ||
|
||||||
(BuildCiaMeta(meta, exthdr, NULL) != 0)) {
|
(BuildCiaMeta(meta, exthdr, NULL) != 0)) {
|
||||||
fvx_close(&file);
|
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'));
|
bool src_emunand = ((*path_cnt == 'B') || (*path_cnt == '4'));
|
||||||
|
|
||||||
if (BuildFakeTicket(ticket, title_id) != 0)
|
if (BuildFakeTicket(ticket, title_id) != 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (force_legit) {
|
if (force_legit) {
|
||||||
Ticket* ticket_tmp = NULL;
|
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) ||
|
if ((fvx_qread(path_tad, &tad, 0, sizeof(TadStub), NULL) != FR_OK) ||
|
||||||
(VerifyTadStub(&tad) != 0) || (hdr->content_size[0] < TMD_SIZE_N(1)))
|
(VerifyTadStub(&tad) != 0) || (hdr->content_size[0] < TMD_SIZE_N(1)))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// build the CIA stub
|
// build the CIA stub
|
||||||
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
|
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
|
||||||
if (!cia) return 1;
|
if (!cia) return 1;
|
||||||
@ -2285,7 +2443,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_
|
|||||||
free(cia);
|
free(cia);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract info from TMD
|
// extract info from TMD
|
||||||
u32 content_count = getbe16(tmd->content_count);
|
u32 content_count = getbe16(tmd->content_count);
|
||||||
u8* title_id = tmd->title_id;
|
u8* title_id = tmd->title_id;
|
||||||
@ -2293,7 +2451,7 @@ u32 BuildCiaFromTadFile(const char* path_tad, const char* path_dest, bool force_
|
|||||||
free(cia);
|
free(cia);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for legit TMD
|
// check for legit TMD
|
||||||
if (force_legit && ((ValidateTmdSignature(tmd) != 0) || VerifyTmd(tmd) != 0)) {
|
if (force_legit && ((ValidateTmdSignature(tmd) != 0) || VerifyTmd(tmd) != 0)) {
|
||||||
ShowPrompt(false, STR_ID_N_TMD_IN_TAD_NOT_LEGIT, getbe64(title_id));
|
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);
|
free(cia);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to find a titlekey
|
// attempt to find a titlekey
|
||||||
u8 titlekey[16] = { 0xFF };
|
u8 titlekey[16] = { 0xFF };
|
||||||
FindTitleKey((Ticket*) ticket, title_id);
|
FindTitleKey((Ticket*) ticket, title_id);
|
||||||
@ -2790,7 +2948,7 @@ u32 GetGameFileTitleVersion(const char* path) {
|
|||||||
if (cia && LoadCiaStub(cia, path) == 0)
|
if (cia && LoadCiaStub(cia, path) == 0)
|
||||||
version = getbe16(cia->tmd.title_version);
|
version = getbe16(cia->tmd.title_version);
|
||||||
if (cia) free(cia);
|
if (cia) free(cia);
|
||||||
}
|
}
|
||||||
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@ -2869,14 +3027,51 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
|
|||||||
return ret;
|
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) {
|
u32 InstallGameFile(const char* path, bool to_emunand) {
|
||||||
char drv[3];
|
char drv[3];
|
||||||
u64 filetype = IdentifyFileType(path);
|
u64 filetype = IdentifyFileType(path);
|
||||||
|
u64 tid64 = GetGameFileTitleId(path);
|
||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
|
|
||||||
// find out the destination
|
if (GetInstallDataDriveForGameFile(path, filetype, tid64, drv, to_emunand) != 0)
|
||||||
u64 tid64 = GetGameFileTitleId(path);
|
|
||||||
if (GetInstallDataDrive(drv, tid64, to_emunand) != 0)
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// check dbs
|
// check dbs
|
||||||
@ -2893,7 +3088,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) {
|
|||||||
|
|
||||||
// cleanup content folder before starting install
|
// cleanup content folder before starting install
|
||||||
ShowProgress(0, 0, path);
|
ShowProgress(0, 0, path);
|
||||||
UninstallGameData(tid64, false, false, false, to_emunand);
|
UninstallGameData(drv, tid64, false, false, false);
|
||||||
|
|
||||||
// install game file
|
// install game file
|
||||||
if (filetype & GAME_CIA)
|
if (filetype & GAME_CIA)
|
||||||
@ -2909,7 +3104,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) {
|
|||||||
else ret = 1;
|
else ret = 1;
|
||||||
|
|
||||||
// cleanup on failed installs, but leave ticket and save untouched
|
// 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;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -3068,7 +3263,7 @@ u32 DumpTicketForGameFile(const char* path, bool force_legit) {
|
|||||||
free(ticket);
|
free(ticket);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// build output name
|
// build output name
|
||||||
char dest[256];
|
char dest[256];
|
||||||
snprintf(dest, sizeof(dest), OUTPUT_PATH "/");
|
snprintf(dest, sizeof(dest), OUTPUT_PATH "/");
|
||||||
@ -3131,7 +3326,7 @@ u32 DumpCxiSrlFromGameFile(const char* path) {
|
|||||||
return 1;
|
return 1;
|
||||||
path_tmd = path_data;
|
path_tmd = path_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DumpCxiSrlFromTmdFile(path_tmd);
|
return DumpCxiSrlFromTmdFile(path_tmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3268,7 +3463,7 @@ u32 CompressCode(const char* path, const char* path_out) {
|
|||||||
u64 GetAnyFileTrimmedSize(const char* path) {
|
u64 GetAnyFileTrimmedSize(const char* path) {
|
||||||
u64 fsize = 0;
|
u64 fsize = 0;
|
||||||
u64 trimsize = 0;
|
u64 trimsize = 0;
|
||||||
u8 pad_byte = 0x7F;
|
u8 pad_byte = 0x7F;
|
||||||
FIL fp;
|
FIL fp;
|
||||||
UINT br;
|
UINT br;
|
||||||
|
|
||||||
@ -3513,7 +3708,7 @@ u32 ShowGameCheckerInfo(const char* path) {
|
|||||||
content_found = content_count = getbe16(tmd->content_count);
|
content_found = content_count = getbe16(tmd->content_count);
|
||||||
sd_title = (*path_tmd == 'A') || (*path_tmd == 'B');
|
sd_title = (*path_tmd == 'A') || (*path_tmd == 'B');
|
||||||
}
|
}
|
||||||
|
|
||||||
// load ticket
|
// load ticket
|
||||||
u64 tid64 = tmd ? getbe64(tmd->title_id) : 0;
|
u64 tid64 = tmd ? getbe64(tmd->title_id) : 0;
|
||||||
if (LoadTicketForTitleId(&ticket, tid64) != 0)
|
if (LoadTicketForTitleId(&ticket, tid64) != 0)
|
||||||
@ -3552,7 +3747,7 @@ u32 ShowGameCheckerInfo(const char* path) {
|
|||||||
memcpy(tmd, &(cia->tmd), cia->header.size_tmd);
|
memcpy(tmd, &(cia->tmd), cia->header.size_tmd);
|
||||||
ticket = (Ticket*) malloc(cia->header.size_ticket);
|
ticket = (Ticket*) malloc(cia->header.size_ticket);
|
||||||
if (ticket) memcpy(ticket, &(cia->ticket), cia->header.size_ticket);
|
if (ticket) memcpy(ticket, &(cia->ticket), cia->header.size_ticket);
|
||||||
|
|
||||||
free(cia);
|
free(cia);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3854,7 +4049,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
|||||||
u32 num_entries = 0;
|
u32 num_entries = 0;
|
||||||
u8* title_ids = NULL;
|
u8* title_ids = NULL;
|
||||||
|
|
||||||
if (!InitImgFS(path_in) ||
|
if (!InitImgFS(path_in) ||
|
||||||
!(num_entries = GetNumTickets(PART_PATH)) ||
|
!(num_entries = GetNumTickets(PART_PATH)) ||
|
||||||
!(title_ids = (u8*) malloc(num_entries * 8)) ||
|
!(title_ids = (u8*) malloc(num_entries * 8)) ||
|
||||||
(ListTicketTitleIDs(PART_PATH, title_ids, num_entries) != 0)) {
|
(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
|
AddTicketToInfo(tik_info, ticket, dec); // ignore result
|
||||||
free(ticket);
|
free(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(title_ids);
|
free(title_ids);
|
||||||
InitImgFS(NULL);
|
InitImgFS(NULL);
|
||||||
} else if (filetype & BIN_TIKDB) {
|
} else if (filetype & BIN_TIKDB) {
|
||||||
@ -4008,54 +4203,6 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
|||||||
return 0;
|
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) {
|
u32 GetGoodName(char* name, const char* path, bool quick) {
|
||||||
// name should be 128+1 byte
|
// 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,
|
if (LoadTwlMetaData(path_donor, (TwlHeader*) header,
|
||||||
quick ? NULL : (TwlIconData*) icon) != 0) type_donor = 0;
|
quick ? NULL : (TwlIconData*) icon) != 0) type_donor = 0;
|
||||||
} else if (type_donor & (GAME_NCSD|GAME_NCCH)) { // CTR (data from NCCH)
|
} 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;
|
if (!quick && (LoadSmdhFromGameFile(path_donor, (Smdh*) icon) != 0)) quick = true;
|
||||||
} else if (type_donor & (GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) { // encrypted
|
} else if (type_donor & (GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) { // encrypted
|
||||||
type_donor = LoadEncryptedIconFromCiaTmd(path_donor, icon, header, false);
|
type_donor = LoadEncryptedIconFromCiaTmd(path_donor, icon, header, false);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user