From eccd3f0c1c3e41857924bfc2b2a9d818291f5b75 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 15 Apr 2020 16:50:07 -0400 Subject: [PATCH] Baby steps. --- Makefile | 4 +- {source => old}/dumper.c | 0 {source => old}/dumper.h | 0 old/main.c | 177 ++ old/nca.c | 3465 ++++++++++++++++++++++++++ old/nca.h | 761 ++++++ {source => old}/nso.c | 0 {source => old}/nso.h | 0 {source => old}/ui.c | 0 {source => old}/ui.h | 0 {source => old}/util.c | 0 {source => old}/util.h | 57 - source/{new => }/cert.c | 4 +- source/{new => }/cert.h | 1 + source/{new => }/crc32_fast.c | 0 source/{new => }/crc32_fast.h | 0 source/{new => }/es.c | 3 +- source/{new => }/es.h | 0 source/{new => }/fatfs/LICENSE.txt | 0 source/{new => }/fatfs/diskio.c | 2 +- source/{new => }/fatfs/diskio.h | 0 source/{new => }/fatfs/ff.c | 0 source/{new => }/fatfs/ff.h | 0 source/{new => }/fatfs/ffconf.h | 0 source/{new => }/fatfs/ffsystem.c | 0 source/{new => }/fatfs/ffunicode.c | 0 source/{new => }/fs_ext.c | 0 source/{new => }/fs_ext.h | 0 source/fspusb.c | 55 + source/fspusb.h | 37 + source/{new => }/gamecard.c | 232 +- source/{new => }/gamecard.h | 20 +- source/{new => }/keys.c | 2 +- source/{new => }/keys.h | 2 + source/{new => }/lz4.c | 0 source/{new => }/lz4.h | 0 source/main.c | 207 +- source/nca.c | 3742 +++------------------------- source/nca.h | 944 ++----- source/new/nca.c | 357 --- source/new/nca.h | 335 --- source/{new => }/rsa.c | 14 +- source/{new => }/rsa.h | 4 +- source/{new => }/save.c | 0 source/{new => }/save.h | 0 source/{new => }/service_guard.h | 0 source/services.c | 233 ++ source/services.h | 20 + source/{new => }/signature.h | 0 source/{new => }/tik.c | 13 +- source/{new => }/tik.h | 9 +- source/utils.c | 164 ++ source/utils.h | 66 + 53 files changed, 5785 insertions(+), 5145 deletions(-) rename {source => old}/dumper.c (100%) rename {source => old}/dumper.h (100%) create mode 100644 old/main.c create mode 100644 old/nca.c create mode 100644 old/nca.h rename {source => old}/nso.c (100%) rename {source => old}/nso.h (100%) rename {source => old}/ui.c (100%) rename {source => old}/ui.h (100%) rename {source => old}/util.c (100%) rename {source => old}/util.h (92%) rename source/{new => }/cert.c (97%) rename source/{new => }/cert.h (99%) rename source/{new => }/crc32_fast.c (100%) rename source/{new => }/crc32_fast.h (100%) rename source/{new => }/es.c (97%) rename source/{new => }/es.h (100%) rename source/{new => }/fatfs/LICENSE.txt (100%) rename source/{new => }/fatfs/diskio.c (99%) rename source/{new => }/fatfs/diskio.h (100%) rename source/{new => }/fatfs/ff.c (100%) rename source/{new => }/fatfs/ff.h (100%) rename source/{new => }/fatfs/ffconf.h (100%) rename source/{new => }/fatfs/ffsystem.c (100%) rename source/{new => }/fatfs/ffunicode.c (100%) rename source/{new => }/fs_ext.c (100%) rename source/{new => }/fs_ext.h (100%) create mode 100644 source/fspusb.c create mode 100644 source/fspusb.h rename source/{new => }/gamecard.c (75%) rename source/{new => }/gamecard.h (92%) rename source/{new => }/keys.c (99%) rename source/{new => }/keys.h (94%) rename source/{new => }/lz4.c (100%) rename source/{new => }/lz4.h (100%) delete mode 100644 source/new/nca.c delete mode 100644 source/new/nca.h rename source/{new => }/rsa.c (97%) rename source/{new => }/rsa.h (84%) rename source/{new => }/save.c (100%) rename source/{new => }/save.h (100%) rename source/{new => }/service_guard.h (100%) create mode 100644 source/services.c create mode 100644 source/services.h rename source/{new => }/signature.h (100%) rename source/{new => }/tik.c (98%) rename source/{new => }/tik.h (94%) create mode 100644 source/utils.c create mode 100644 source/utils.h diff --git a/Makefile b/Makefile index 91a039c..4f9ed42 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,8 @@ include $(DEVKITPRO)/libnx/switch_rules #--------------------------------------------------------------------------------- VERSION_MAJOR := 1 -VERSION_MINOR := 1 -VERSION_MICRO := 9 +VERSION_MINOR := 2 +VERSION_MICRO := 0 APP_TITLE := nxdumptool APP_AUTHOR := DarkMatterCore diff --git a/source/dumper.c b/old/dumper.c similarity index 100% rename from source/dumper.c rename to old/dumper.c diff --git a/source/dumper.h b/old/dumper.h similarity index 100% rename from source/dumper.h rename to old/dumper.h diff --git a/old/main.c b/old/main.c new file mode 100644 index 0000000..b907320 --- /dev/null +++ b/old/main.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include + +#include "ui.h" +#include "util.h" + +int main(int argc, char *argv[]) +{ + int ret = 0; + bool exitMainLoop = false; + + /* Initialize application resources */ + if (!initApplicationResources(argc, argv)) + { + ret = -1; + goto out; + } + + /* Main application loop */ + while(appletMainLoop()) + { + UIResult result = uiProcess(); + switch(result) + { + case resultShowMainMenu: + uiSetState(stateMainMenu); + break; + case resultShowGameCardMenu: + uiSetState(stateGameCardMenu); + break; + case resultShowXciDumpMenu: + uiSetState(stateXciDumpMenu); + break; + case resultDumpXci: + uiSetState(stateDumpXci); + break; + case resultShowNspDumpMenu: + uiSetState(stateNspDumpMenu); + break; + case resultShowNspAppDumpMenu: + uiSetState(stateNspAppDumpMenu); + break; + case resultShowNspPatchDumpMenu: + uiSetState(stateNspPatchDumpMenu); + break; + case resultShowNspAddOnDumpMenu: + uiSetState(stateNspAddOnDumpMenu); + break; + case resultDumpNsp: + uiSetState(stateDumpNsp); + break; + case resultShowHfs0Menu: + uiSetState(stateHfs0Menu); + break; + case resultShowRawHfs0PartitionDumpMenu: + uiSetState(stateRawHfs0PartitionDumpMenu); + break; + case resultDumpRawHfs0Partition: + uiSetState(stateDumpRawHfs0Partition); + break; + case resultShowHfs0PartitionDataDumpMenu: + uiSetState(stateHfs0PartitionDataDumpMenu); + break; + case resultDumpHfs0PartitionData: + uiSetState(stateDumpHfs0PartitionData); + break; + case resultShowHfs0BrowserMenu: + uiSetState(stateHfs0BrowserMenu); + break; + case resultHfs0BrowserGetList: + uiSetState(stateHfs0BrowserGetList); + break; + case resultShowHfs0Browser: + uiSetState(stateHfs0Browser); + break; + case resultHfs0BrowserCopyFile: + uiSetState(stateHfs0BrowserCopyFile); + break; + case resultShowExeFsMenu: + uiSetState(stateExeFsMenu); + break; + case resultShowExeFsSectionDataDumpMenu: + uiSetState(stateExeFsSectionDataDumpMenu); + break; + case resultDumpExeFsSectionData: + uiSetState(stateDumpExeFsSectionData); + break; + case resultShowExeFsSectionBrowserMenu: + uiSetState(stateExeFsSectionBrowserMenu); + break; + case resultExeFsSectionBrowserGetList: + uiSetState(stateExeFsSectionBrowserGetList); + break; + case resultShowExeFsSectionBrowser: + uiSetState(stateExeFsSectionBrowser); + break; + case resultExeFsSectionBrowserCopyFile: + uiSetState(stateExeFsSectionBrowserCopyFile); + break; + case resultShowRomFsMenu: + uiSetState(stateRomFsMenu); + break; + case resultShowRomFsSectionDataDumpMenu: + uiSetState(stateRomFsSectionDataDumpMenu); + break; + case resultDumpRomFsSectionData: + uiSetState(stateDumpRomFsSectionData); + break; + case resultShowRomFsSectionBrowserMenu: + uiSetState(stateRomFsSectionBrowserMenu); + break; + case resultRomFsSectionBrowserGetEntries: + uiSetState(stateRomFsSectionBrowserGetEntries); + break; + case resultShowRomFsSectionBrowser: + uiSetState(stateRomFsSectionBrowser); + break; + case resultRomFsSectionBrowserChangeDir: + uiSetState(stateRomFsSectionBrowserChangeDir); + break; + case resultRomFsSectionBrowserCopyFile: + uiSetState(stateRomFsSectionBrowserCopyFile); + break; + case resultRomFsSectionBrowserCopyDir: + uiSetState(stateRomFsSectionBrowserCopyDir); + break; + case resultDumpGameCardCertificate: + uiSetState(stateDumpGameCardCertificate); + break; + case resultShowSdCardEmmcMenu: + uiSetState(stateSdCardEmmcMenu); + break; + case resultShowSdCardEmmcTitleMenu: + uiSetState(stateSdCardEmmcTitleMenu); + break; + case resultShowSdCardEmmcOrphanPatchAddOnMenu: + uiSetState(stateSdCardEmmcOrphanPatchAddOnMenu); + break; + case resultShowSdCardEmmcBatchModeMenu: + uiSetState(stateSdCardEmmcBatchModeMenu); + break; + case resultSdCardEmmcBatchDump: + uiSetState(stateSdCardEmmcBatchDump); + break; + case resultShowTicketMenu: + uiSetState(stateTicketMenu); + break; + case resultDumpTicket: + uiSetState(stateDumpTicket); + break; + case resultShowUpdateMenu: + uiSetState(stateUpdateMenu); + break; + case resultUpdateNSWDBXml: + uiSetState(stateUpdateNSWDBXml); + break; + case resultUpdateApplication: + uiSetState(stateUpdateApplication); + break; + case resultExit: + exitMainLoop = true; + break; + default: + break; + } + + if (exitMainLoop) break; + } + +out: + /* Deinitialize application resources */ + deinitApplicationResources(); + + return ret; +} diff --git a/old/nca.c b/old/nca.c new file mode 100644 index 0000000..3f82d2c --- /dev/null +++ b/old/nca.c @@ -0,0 +1,3465 @@ +#include +#include +#include +#include + +#include "keys.h" +#include "util.h" +#include "ui.h" +#include "rsa.h" +#include "nso.h" + +/* Extern variables */ + +extern int breaks; +extern int font_height; + +extern exefs_ctx_t exeFsContext; +extern romfs_ctx_t romFsContext; +extern bktr_ctx_t bktrContext; + +extern nca_keyset_t nca_keyset; + +extern u8 *ncaCtrBuf; + +char *getTitleType(u8 type) +{ + char *out = NULL; + + switch(type) + { + case NcmContentMetaType_Application: + out = "Application"; + break; + case NcmContentMetaType_Patch: + out = "Patch"; + break; + case NcmContentMetaType_AddOnContent: + out = "AddOnContent"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getContentType(u8 type) +{ + char *out = NULL; + + switch(type) + { + case NcmContentType_Meta: + out = "Meta"; + break; + case NcmContentType_Program: + out = "Program"; + break; + case NcmContentType_Data: + out = "Data"; + break; + case NcmContentType_Control: + out = "Control"; + break; + case NcmContentType_HtmlDocument: + out = "HtmlDocument"; + break; + case NcmContentType_LegalInformation: + out = "LegalInformation"; + break; + case NcmContentType_DeltaFragment: + out = "DeltaFragment"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getRequiredMinTitleType(u8 type) +{ + char *out = NULL; + + switch(type) + { + case NcmContentMetaType_Application: + case NcmContentMetaType_Patch: + out = "RequiredSystemVersion"; + break; + case NcmContentMetaType_AddOnContent: + out = "RequiredApplicationVersion"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getReferenceTitleIDType(u8 type) +{ + char *out = NULL; + + switch(type) + { + case NcmContentMetaType_Application: + out = "PatchId"; + break; + case NcmContentMetaType_Patch: + out = "OriginalId"; + break; + case NcmContentMetaType_AddOnContent: + out = "ApplicationId"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out) +{ + if (!xml_program_info || !xml_content_info || !xml_program_info->nca_cnt || !out) return; + + u32 i; + char tmp[NAME_BUF_LEN] = {'\0'}; + + sprintf(out, "\n" \ + "\n" \ + " %s\n" \ + " 0x%016lx\n" \ + " %u\n" \ + " %u\n", \ + getTitleType(xml_program_info->type), \ + xml_program_info->title_id, \ + xml_program_info->version, \ + xml_program_info->required_dl_sysver); + + for(i = 0; i < xml_program_info->nca_cnt; i++) + { + sprintf(tmp, " \n" \ + " %s\n" \ + " %s\n" \ + " %lu\n" \ + " %s\n" \ + " %u\n" \ + " %u\n" \ + " \n", + getContentType(xml_content_info[i].type), \ + xml_content_info[i].nca_id_str, \ + xml_content_info[i].size, \ + xml_content_info[i].hash_str, \ + xml_content_info[i].keyblob, \ + xml_content_info[i].id_offset); + + strcat(out, tmp); + } + + sprintf(tmp, " %s\n" \ + " %u\n" \ + " <%s>%u\n" \ + " <%s>0x%016lx\n", \ + xml_program_info->digest_str, \ + xml_program_info->min_keyblob, \ + getRequiredMinTitleType(xml_program_info->type), \ + xml_program_info->min_sysver, \ + getRequiredMinTitleType(xml_program_info->type), \ + getReferenceTitleIDType(xml_program_info->type), \ + xml_program_info->patch_tid, \ + getReferenceTitleIDType(xml_program_info->type)); + + strcat(out, tmp); + + if (xml_program_info->type == NcmContentMetaType_Application) + { + sprintf(tmp, " %u\n", xml_program_info->min_appver); + strcat(out, tmp); + } + + strcat(out, ""); +} + +void convertNcaSizeToU64(const u8 size[0x6], u64 *out) +{ + if (!size || !out) return; + + u64 tmp = 0; + + tmp |= (((u64)size[5] << 40) & (u64)0xFF0000000000); + tmp |= (((u64)size[4] << 32) & (u64)0x00FF00000000); + tmp |= (((u64)size[3] << 24) & (u64)0x0000FF000000); + tmp |= (((u64)size[2] << 16) & (u64)0x000000FF0000); + tmp |= (((u64)size[1] << 8) & (u64)0x00000000FF00); + tmp |= ((u64)size[0] & (u64)0x0000000000FF); + + *out = tmp; +} + +void convertU64ToNcaSize(const u64 size, u8 out[0x6]) +{ + if (!size || !out) return; + + u8 tmp[0x6]; + + tmp[5] = (u8)(size >> 40); + tmp[4] = (u8)(size >> 32); + tmp[3] = (u8)(size >> 24); + tmp[2] = (u8)(size >> 16); + tmp[1] = (u8)(size >> 8); + tmp[0] = (u8)size; + + memcpy(out, tmp, 6); +} + +bool loadNcaKeyset() +{ + // Check if the keyset has been already loaded + if (nca_keyset.total_key_cnt > 0) return true; + + if (!(envIsSyscallHinted(0x60) && // svcDebugActiveProcess + envIsSyscallHinted(0x63) && // svcGetDebugEvent + envIsSyscallHinted(0x65) && // svcGetProcessList + envIsSyscallHinted(0x69) && // svcQueryDebugProcessMemory + envIsSyscallHinted(0x6a))) // svcReadDebugProcessMemory + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: please run the application with debug svc permissions!", __func__); + return false; + } + + return loadMemoryKeys(); +} + +size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u32 sector, bool encrypt) +{ + if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0) return 0; + + size_t i, crypt_res = 0, out = 0; + u32 cur_sector = sector; + + for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++) + { + // We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak + aes128XtsContextResetSector(ctx, cur_sector, true); + + if (encrypt) + { + crypt_res = aes128XtsEncrypt(ctx, (u8*)dst + i, (const u8*)src + i, NCA_AES_XTS_SECTOR_SIZE); + } else { + crypt_res = aes128XtsDecrypt(ctx, (u8*)dst + i, (const u8*)src + i, NCA_AES_XTS_SECTOR_SIZE); + } + + if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break; + + out += crypt_res; + } + + return out; +} + +/* Updates the CTR for an offset. */ +static void nca_update_ctr(unsigned char *ctr, u64 ofs) +{ + ofs >>= 4; + unsigned int i; + + for(i = 0; i < 0x8; i++) + { + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } +} + +/* Updates the CTR for a bktr offset. */ +static void nca_update_bktr_ctr(unsigned char *ctr, u32 ctr_val, u64 ofs) +{ + ofs >>= 4; + unsigned int i; + + for(i = 0; i < 0x8; i++) + { + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + for(i = 0; i < 0x4; i++) + { + ctr[0x8 - i - 1] = (unsigned char)(ctr_val & 0xFF); + ctr_val >>= 8; + } +} + +bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize) +{ + if (!ncmStorage || !ncaId || !outBuf || !bufSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read data from NCA!", __func__); + return false; + } + + Result result = 0; + bool success = false; + + char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}, nca_path[0x301] = {'\0'}; + convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); + + result = ncmContentStorageGetPath(ncmStorage, nca_path, MAX_CHARACTERS(nca_path), ncaId); + if (R_FAILED(result) || !strlen(nca_path)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to retrieve content path for NCA \"%s\"! (0x%08X)", __func__, nca_id, result); + return false; + } + + // Check if we're dealing with a gamecard NCA + if (!strncmp(nca_path, "@Gc", 3)) + { + // Retrieve NCA data using raw IStorage reads + // Fixes NCA access problems with gamecards under low HOS versions when using ncmContentStorageReadContentIdFile() + success = readFileFromSecureHfs0PartitionByName(strchr(nca_path, '/') + 1, offset, outBuf, bufSize); + if (!success) breaks++; + } else { + // Retrieve NCA data normally + // This strips NAX0 encryption from SD card NCAs (not used with eMMC NCAs) + result = ncmContentStorageReadContentIdFile(ncmStorage, outBuf, bufSize, ncaId, offset); + success = R_SUCCEEDED(result); + } + + if (!success) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", __func__, bufSize, offset, nca_id, result); + + return success; +} + +bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt) +{ + if (!ncmStorage || !ncaId || !outBuf || !bufSize || !ctx) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process %s NCA section block!", __func__, (encrypt ? "decrypted" : "encrypted")); + return false; + } + + if (!loadNcaKeyset()) return false; + + unsigned char ctr[0x10]; + + char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}; + convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); + + u64 block_start_offset = (offset - (offset % 0x10)); + u64 block_end_offset = (u64)round_up(offset + bufSize, 0x10); + u64 block_size = (block_end_offset - block_start_offset); + + u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); + u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (offset - block_start_offset)) : bufSize); + + if (!readNcaDataByContentId(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted data block from NCA \"%s\"!", __func__, nca_id); + return false; + } + + // Update CTR + memcpy(ctr, ctx->ctr, 0x10); + nca_update_ctr(ctr, block_start_offset); + aes128CtrContextResetCtr(ctx, ctr); + + // Decrypt CTR block + aes128CtrCrypt(ctx, ncaCtrBuf, ncaCtrBuf, block_size_used); + + if (encrypt) + { + // Copy data to be encrypted + memcpy(ncaCtrBuf + (offset - block_start_offset), outBuf, output_block_size); + + // Reset CTR + aes128CtrContextResetCtr(ctx, ctr); + + // Encrypt CTR block + aes128CtrCrypt(ctx, ncaCtrBuf, ncaCtrBuf, block_size_used); + } + + memcpy(outBuf, ncaCtrBuf + (offset - block_start_offset), output_block_size); + + if (block_size > NCA_CTR_BUFFER_SIZE) return processNcaCtrSectionBlock(ncmStorage, ncaId, ctx, offset + output_block_size, outBuf + output_block_size, bufSize - output_block_size, encrypt); + + return true; +} + +bktr_relocation_bucket_t *bktr_get_relocation_bucket(bktr_relocation_block_t *block, u32 i) +{ + return (bktr_relocation_bucket_t*)((u8*)block->buckets + ((sizeof(bktr_relocation_bucket_t) + sizeof(bktr_relocation_entry_t)) * (u64)i)); +} + +// Get a relocation entry from offset and relocation block +bktr_relocation_entry_t *bktr_get_relocation(bktr_relocation_block_t *block, u64 offset) +{ + // Weak check for invalid offset + if (offset > block->total_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: too big offset looked up in BKTR relocation table!", __func__); + return NULL; + } + + u32 i, bucket_num = 0; + + for(i = 1; i < block->num_buckets; i++) + { + if (block->bucket_virtual_offsets[i] <= offset) bucket_num++; + } + + bktr_relocation_bucket_t *bucket = bktr_get_relocation_bucket(block, bucket_num); + + // Check for edge case, short circuit + if (bucket->num_entries == 1) return &(bucket->entries[0]); + + // Binary search + u32 low = 0, high = (bucket->num_entries - 1); + + while(low <= high) + { + u32 mid = ((low + high) / 2); + + if (bucket->entries[mid].virt_offset > offset) + { + // Too high + high = (mid - 1); + } else { + // block->entries[mid].offset <= offset + + // Check for success + if (mid == (bucket->num_entries - 1) || bucket->entries[mid + 1].virt_offset > offset) return &(bucket->entries[mid]); + + low = (mid + 1); + } + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR relocation table!", __func__, offset); + return NULL; +} + +bktr_subsection_bucket_t *bktr_get_subsection_bucket(bktr_subsection_block_t *block, u32 i) +{ + return (bktr_subsection_bucket_t*)((u8*)block->buckets + ((sizeof(bktr_subsection_bucket_t) + sizeof(bktr_subsection_entry_t)) * (u64)i)); +} + +// Get a subsection entry from offset and subsection block +bktr_subsection_entry_t *bktr_get_subsection(bktr_subsection_block_t *block, u64 offset) +{ + // If offset is past the virtual, we're reading from the BKTR_HEADER subsection + bktr_subsection_bucket_t *last_bucket = bktr_get_subsection_bucket(block, block->num_buckets - 1); + if (offset >= last_bucket->entries[last_bucket->num_entries].offset) return &(last_bucket->entries[last_bucket->num_entries]); + + u32 i, bucket_num = 0; + + for(i = 1; i < block->num_buckets; i++) + { + if (block->bucket_physical_offsets[i] <= offset) bucket_num++; + } + + bktr_subsection_bucket_t *bucket = bktr_get_subsection_bucket(block, bucket_num); + + // Check for edge case, short circuit + if (bucket->num_entries == 1) return &(bucket->entries[0]); + + // Binary search + u32 low = 0, high = (bucket->num_entries - 1); + + while (low <= high) + { + u32 mid = ((low + high) / 2); + + if (bucket->entries[mid].offset > offset) + { + // Too high + high = (mid - 1); + } else { + // block->entries[mid].offset <= offset + + // Check for success + if (mid == (bucket->num_entries - 1) || bucket->entries[mid + 1].offset > offset) return &(bucket->entries[mid]); + + low = (mid + 1); + } + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR subsection table!", __func__, offset); + return NULL; +} + +bool bktrSectionSeek(u64 offset) +{ + if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to seek within NCA BKTR section!", __func__); + return false; + } + + bktr_relocation_entry_t *reloc = bktr_get_relocation(bktrContext.relocation_block, offset); + if (!reloc) return false; + + // No better way to do this than to make all BKTR seeking virtual + bktrContext.virtual_seek = offset; + + u64 section_ofs = (offset - reloc->virt_offset + reloc->phys_offset); + + if (reloc->is_patch) + { + // Seeked within the patch RomFS + bktrContext.bktr_seek = section_ofs; + bktrContext.base_seek = 0; + } else { + // Seeked within the base RomFS + bktrContext.bktr_seek = 0; + bktrContext.base_seek = section_ofs; + } + + return true; +} + +bool bktrSectionPhysicalRead(void *outBuf, size_t bufSize) +{ + if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !outBuf || !bufSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform physical block read from NCA BKTR section!", __func__); + return false; + } + + unsigned char ctr[0x10]; + + bktr_subsection_entry_t *subsec = bktr_get_subsection(bktrContext.subsection_block, bktrContext.bktr_seek); + if (!subsec) return false; + + bktr_subsection_entry_t *next_subsec = (subsec + 1); + + u64 base_offset = (bktrContext.section_offset + bktrContext.bktr_seek); + + u64 virt_seek = bktrContext.virtual_seek; + + if ((bktrContext.bktr_seek + bufSize) <= next_subsec->offset) + { + // Easy path, reading *only* within the subsection + u64 block_start_offset = (base_offset - (base_offset % 0x10)); + u64 block_end_offset = (u64)round_up(base_offset + bufSize, 0x10); + u64 block_size = (block_end_offset - block_start_offset); + + u64 output_offset = 0; + u64 ctr_buf_offset = (base_offset - block_start_offset); + u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (base_offset - block_start_offset)) : bufSize); + + while(block_size > 0) + { + u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); + + if (!readNcaDataByContentId(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted %lu bytes block at offset 0x%016lX!", __func__, block_size_used, block_start_offset); + return false; + } + + // Update BKTR CTR + memcpy(ctr, bktrContext.aes_ctx.ctr, 0x10); + nca_update_bktr_ctr(ctr, subsec->ctr_val, block_start_offset); + aes128CtrContextResetCtr(&(bktrContext.aes_ctx), ctr); + + // Decrypt CTR block + aes128CtrCrypt(&(bktrContext.aes_ctx), ncaCtrBuf, ncaCtrBuf, block_size_used); + memcpy(outBuf + output_offset, ncaCtrBuf + ctr_buf_offset, output_block_size); + + block_start_offset += block_size_used; + block_size -= block_size_used; + + if (block_size) + { + output_offset += output_block_size; + ctr_buf_offset = 0; + output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : ((base_offset + bufSize) - block_start_offset)); + } + } + } else { + // Sad path + u64 within_subsection = (next_subsec->offset - bktrContext.bktr_seek); + + if (!readBktrSectionBlock(virt_seek, outBuf, within_subsection)) return false; + + if (!readBktrSectionBlock(virt_seek + within_subsection, (u8*)outBuf + within_subsection, bufSize - within_subsection)) return false; + } + + return true; +} + +bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize) +{ + if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !romFsContext.section_offset || !romFsContext.section_size || !outBuf || !bufSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read block from NCA BKTR section!", __func__); + return false; + } + + if (!loadNcaKeyset()) return false; + + if (!bktrSectionSeek(offset)) return false; + + bktr_relocation_entry_t *reloc = bktr_get_relocation(bktrContext.relocation_block, bktrContext.virtual_seek); + if (!reloc) return false; + + bktr_relocation_entry_t *next_reloc = (reloc + 1); + + u64 virt_seek = bktrContext.virtual_seek; + + // Perform read operation + if ((bktrContext.virtual_seek + bufSize) <= next_reloc->virt_offset) + { + // Easy path: We're reading *only* within the current relocation + + if (reloc->is_patch) + { + if (!bktrSectionPhysicalRead(outBuf, bufSize)) return false; + } else { + // Nice and easy read from the base RomFS + if (!processNcaCtrSectionBlock(&(romFsContext.ncmStorage), &(romFsContext.ncaId), &(romFsContext.aes_ctx), romFsContext.section_offset + bktrContext.base_seek, outBuf, bufSize, false)) return false; + } + } else { + u64 within_relocation = (next_reloc->virt_offset - bktrContext.virtual_seek); + + if (!readBktrSectionBlock(virt_seek, outBuf, within_relocation)) return false; + + if (!readBktrSectionBlock(virt_seek + within_relocation, (u8*)outBuf + within_relocation, bufSize - within_relocation)) return false; + } + + return true; +} + +bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) +{ + if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (__builtin_bswap32(input->magic) != NCA3_MAGIC && __builtin_bswap32(input->magic) != NCA2_MAGIC)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header encryption parameters.", __func__); + return false; + } + + if (!loadNcaKeyset()) return false; + + u32 i; + size_t crypt_res; + Aes128XtsContext hdr_aes_ctx; + + u8 header_key_0[16]; + u8 header_key_1[16]; + + memcpy(header_key_0, nca_keyset.header_key, 16); + memcpy(header_key_1, nca_keyset.header_key + 16, 16); + + aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, true); + + if (__builtin_bswap32(input->magic) == NCA3_MAGIC) + { + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, true); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); + return false; + } + } else + if (__builtin_bswap32(input->magic) == NCA2_MAGIC) + { + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, true); + if (crypt_res != NCA_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); + return false; + } + + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) + { + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, true); + if (crypt_res != NCA_SECTION_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); + return false; + } + } + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decrypted NCA magic word! (0x%08X)", __func__, __builtin_bswap32(input->magic)); + return false; + } + + return true; +} + +bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData) +{ + if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH || !out || !decrypted_nca_keys) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header decryption parameters!", __func__); + return false; + } + + if (!loadNcaKeyset()) return false; + + int ret; + + u32 i; + size_t crypt_res; + Aes128XtsContext hdr_aes_ctx; + + u8 header_key_0[16]; + u8 header_key_1[16]; + + bool has_rights_id = false; + + memcpy(header_key_0, nca_keyset.header_key, 16); + memcpy(header_key_1, nca_keyset.header_key + 16, 16); + + aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, false); + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, false); + if (crypt_res != NCA_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); + return false; + } + + if (__builtin_bswap32(out->magic) == NCA3_MAGIC) + { + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, false); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); + return false; + } + } else + if (__builtin_bswap32(out->magic) == NCA2_MAGIC) + { + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) + { + if (out->fs_headers[i]._0x148[0] != 0 || memcmp(out->fs_headers[i]._0x148, out->fs_headers[i]._0x148 + 1, 0xB7)) + { + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, false); + if (crypt_res != NCA_SECTION_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); + return false; + } + } else { + memset(&(out->fs_headers[i]), 0, sizeof(nca_fs_header_t)); + } + } + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(out->magic)); + return false; + } + + for(i = 0; i < 0x10; i++) + { + if (out->rights_id[i] != 0) + { + has_rights_id = true; + break; + } + } + + if (has_rights_id) + { + if (rights_info != NULL) + { + // If we're dealing with a rights info context, retrieve the ticket for the current title + + if (!rights_info->has_rights_id) + { + rights_info->has_rights_id = true; + + memcpy(rights_info->rights_id, out->rights_id, 16); + convertDataToHexString(out->rights_id, 16, rights_info->rights_id_str, 33); + sprintf(rights_info->tik_filename, "%s.tik", rights_info->rights_id_str); + sprintf(rights_info->cert_filename, "%s.cert", rights_info->rights_id_str); + + if (retrieveTitleKeyData) + { + ret = retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey); + + if (ret >= 0) + { + memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); + memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); + + rights_info->retrieved_tik = true; + } else { + if (ret == -2) + { + // We are probably dealing with a pre-installed title + // Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway + rights_info->missing_tik = true; + } else { + return false; + } + } + } + } else { + // Copy what we already have + if (retrieveTitleKeyData && rights_info->retrieved_tik) + { + memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); + memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); + } + } + } else { + // Otherwise, only retrieve the decrypted titlekey. This is used with ExeFS/RomFS section parsing for SD/eMMC titles + if (retrieveTitleKeyData) + { + u8 tmp_dec_titlekey[0x10]; + + if (retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey) < 0) return false; + + memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); + memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10); + } + } + } else { + if (!decryptNcaKeyArea(out, decrypted_nca_keys)) return false; + } + + return true; +} + +bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys) +{ + if (!rights_info || !rights_info->has_rights_id || !strlen(rights_info->tik_filename) || !decrypted_nca_keys) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve titlekey from gamecard ticket!", __func__); + return false; + } + + // Check if the ticket has already been retrieved from the HFS0 partition in the gamecard + if (rights_info->retrieved_tik) return true; + + // Load external keys + if (!loadExternalKeys()) return false; + + // Retrieve ticket + if (!readFileFromSecureHfs0PartitionByName(rights_info->tik_filename, 0, &(rights_info->tik_data), ETICKET_TIK_FILE_SIZE)) return false; + + // Save encrypted titlekey + memcpy(rights_info->enc_titlekey, rights_info->tik_data.titlekey_block, 0x10); + + // Decrypt titlekey + u8 crypto_type = rights_info->rights_id[0x0F]; + if (crypto_type) crypto_type--; + + if (crypto_type >= 0x20) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); + return false; + } + + Aes128Context titlekey_aes_ctx; + aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false); + aes128DecryptBlock(&titlekey_aes_ctx, rights_info->dec_titlekey, rights_info->enc_titlekey); + + // Update retrieved ticket flag + rights_info->retrieved_tik = true; + + // Save the decrypted NCA key area keys + memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); + memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); + + return true; +} + +bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output || !cur_mod_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process Program NCA!", __func__); + return false; + } + + if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); + return false; + } + + if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); + return false; + } + + if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); + return false; + } + + u32 i; + + u64 section_offset; + u64 hash_table_offset; + u64 nca_pfs0_offset; + + pfs0_header nca_pfs0_header; + pfs0_file_entry *nca_pfs0_entries = NULL; + u64 nca_pfs0_data_offset; + + npdm_t npdm_header; + bool found_meta = false; + u64 meta_offset; + u64 acid_pubkey_offset; + + u64 block_hash_table_offset; + u64 block_hash_table_end_offset; + u64 block_start_offset[2] = { 0, 0 }; + u64 block_size[2] = { 0, 0 }; + u8 block_hash[2][SHA256_HASH_SIZE]; + u8 *block_data[2] = { NULL, NULL }; + + u64 sig_write_size[2] = { 0, 0 }; + + u8 *hash_table = NULL; + + Aes128CtrContext aes_ctx; + + section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); + hash_table_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_offset); + nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset); + + if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !hash_table_offset || hash_table_offset < section_offset || !nca_pfs0_offset || nca_pfs0_offset <= hash_table_offset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); + return false; + } + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, xml_content_info->decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); + return false; + } + + if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); + return false; + } + + if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); + return false; + } + + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); + if (!nca_pfs0_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); + free(nca_pfs0_entries); + return false; + } + + nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)) + (u64)nca_pfs0_header.str_table_size); + + // Looking for META magic + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + u64 nca_pfs0_cur_file_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); + + // Read and decrypt NPDM header + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_cur_file_offset, &npdm_header, sizeof(npdm_t), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 entry #%u!", __func__, i); + free(nca_pfs0_entries); + return false; + } + + if (__builtin_bswap32(npdm_header.magic) == META_MAGIC) + { + found_meta = true; + meta_offset = nca_pfs0_cur_file_offset; + acid_pubkey_offset = (meta_offset + (u64)npdm_header.acid_offset + (u64)NPDM_SIGNATURE_SIZE); + break; + } + } + + free(nca_pfs0_entries); + + if (!found_meta) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find NPDM entry in Program NCA section #0 PFS0 partition!", __func__); + return false; + } + + // Calculate block offsets + block_hash_table_offset = (hash_table_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)) * (u64)SHA256_HASH_SIZE); + block_hash_table_end_offset = (hash_table_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)SHA256_HASH_SIZE)); + block_start_offset[0] = (nca_pfs0_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)); + + // Make sure our block doesn't pass PFS0 end offset + if ((block_start_offset[0] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) + { + block_size[0] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[0] - nca_pfs0_offset)); + } else { + block_size[0] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size; + } + + block_data[0] = malloc(block_size[0]); + if (!block_data[0]) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!", __func__); + return false; + } + + // Read and decrypt block + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 0!", __func__); + free(block_data[0]); + return false; + } + + // Make sure that 1 block will cover all patched bytes, otherwise we'll have to recalculate another hash block + if (block_hash_table_offset != block_hash_table_end_offset) + { + sig_write_size[1] = (acid_pubkey_offset - block_start_offset[0] + (u64)NPDM_SIGNATURE_SIZE - block_size[0]); + sig_write_size[0] = ((u64)NPDM_SIGNATURE_SIZE - sig_write_size[1]); + } else { + sig_write_size[0] = (u64)NPDM_SIGNATURE_SIZE; + } + + // Patch ACID public key changing it to a self-generated pubkey + memcpy(block_data[0] + (acid_pubkey_offset - block_start_offset[0]), rsa_get_public_key(), sig_write_size[0]); + + // Calculate new block hash + sha256CalculateHash(block_hash[0], block_data[0], block_size[0]); + + if (block_hash_table_offset != block_hash_table_end_offset) + { + block_start_offset[1] = (nca_pfs0_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)); + + if ((block_start_offset[1] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) + { + block_size[1] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[1] - nca_pfs0_offset)); + } else { + block_size[1] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size; + } + + block_data[1] = malloc(block_size[1]); + if (!block_data[1]) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!", __func__); + free(block_data[0]); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 1!", __func__); + free(block_data[0]); + free(block_data[1]); + return false; + } + + memcpy(block_data[1], rsa_get_public_key() + sig_write_size[0], sig_write_size[1]); + + sha256CalculateHash(block_hash[1], block_data[1], block_size[1]); + } + + hash_table = malloc(dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size); + if (!hash_table) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 hash table!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 hash table!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + free(hash_table); + return false; + } + + // Update block hashes + memcpy(hash_table + (block_hash_table_offset - hash_table_offset), block_hash[0], SHA256_HASH_SIZE); + if (block_hash_table_offset != block_hash_table_end_offset) memcpy(hash_table + (block_hash_table_end_offset - hash_table_offset), block_hash[1], SHA256_HASH_SIZE); + + // Calculate PFS0 superblock master hash + sha256CalculateHash(dec_nca_header->fs_headers[0].pfs0_superblock.master_hash, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size); + + // Calculate section hash + sha256CalculateHash(dec_nca_header->section_hashes[0], &(dec_nca_header->fs_headers[0]), sizeof(nca_fs_header_t)); + + // Recreate NPDM signature + if (!rsa_sign(&(dec_nca_header->magic), NPDM_SIGNATURE_AREA_SIZE, dec_nca_header->npdm_key_sig, NPDM_SIGNATURE_SIZE)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to recreate Program NCA NPDM signature!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + free(hash_table); + return false; + } + + // Reencrypt relevant data blocks + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], true)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + free(hash_table); + return false; + } + + if (block_hash_table_offset != block_hash_table_end_offset) + { + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], true)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1!", __func__); + free(block_data[0]); + free(block_data[1]); + free(hash_table); + return false; + } + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, true)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 hash table!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + free(hash_table); + return false; + } + + // Save data to the output struct so we can write it later + // The caller function must free these data pointers + nca_program_mod_data *tmp_mod_data = realloc(*output, (*cur_mod_cnt + 1) * sizeof(nca_program_mod_data)); + if (!tmp_mod_data) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to reallocate Program NCA mod data buffer!", __func__); + free(block_data[0]); + if (block_data[1]) free(block_data[1]); + free(hash_table); + return false; + } + + memset(&(tmp_mod_data[*cur_mod_cnt]), 0, sizeof(nca_program_mod_data)); + + tmp_mod_data[*cur_mod_cnt].nca_index = idx; + + tmp_mod_data[*cur_mod_cnt].hash_table = hash_table; + tmp_mod_data[*cur_mod_cnt].hash_table_offset = hash_table_offset; + tmp_mod_data[*cur_mod_cnt].hash_table_size = dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size; + + tmp_mod_data[*cur_mod_cnt].block_mod_cnt = (block_hash_table_offset != block_hash_table_end_offset ? 2 : 1); + + tmp_mod_data[*cur_mod_cnt].block_data[0] = block_data[0]; + tmp_mod_data[*cur_mod_cnt].block_offset[0] = block_start_offset[0]; + tmp_mod_data[*cur_mod_cnt].block_size[0] = block_size[0]; + + if (block_hash_table_offset != block_hash_table_end_offset) + { + tmp_mod_data[*cur_mod_cnt].block_data[1] = block_data[1]; + tmp_mod_data[*cur_mod_cnt].block_offset[1] = block_start_offset[1]; + tmp_mod_data[*cur_mod_cnt].block_size[1] = block_size[1]; + } + + *output = tmp_mod_data; + tmp_mod_data = NULL; + + *cur_mod_cnt += 1; + + return true; +} + +bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info) +{ + if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve CNMT NCA!", __func__); + return false; + } + + nca_header_t dec_header; + + u32 i, j, k = 0; + + u64 section_offset; + u64 section_size; + u8 *section_data = NULL; + + Aes128CtrContext aes_ctx; + + u64 nca_pfs0_offset; + u64 nca_pfs0_str_table_offset; + u64 nca_pfs0_data_offset; + pfs0_header nca_pfs0_header; + pfs0_file_entry *nca_pfs0_entries = NULL; + + bool found_cnmt = false; + + u64 title_cnmt_offset; + u64 title_cnmt_size; + + cnmt_header title_cnmt_header; + cnmt_extended_header title_cnmt_extended_header; + + u64 digest_offset; + + // Generate filename for our required CNMT file + char cnmtFileName[50] = {'\0'}; + snprintf(cnmtFileName, MAX_CHARACTERS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id); + + // Decrypt the NCA header + // Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard + if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) return false; + + if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 doesn't hold a PFS0 partition!", __func__); + return false; + } + + if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in CNMT NCA section #0!", __func__); + return false; + } + + if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", __func__, dec_header.fs_headers[0].crypt_type); + return false; + } + + bool has_rights_id = false; + + for(i = 0; i < 0x10; i++) + { + if (dec_header.rights_id[i] != 0) + { + has_rights_id = true; + break; + } + } + + // CNMT NCAs never use titlekey crypto + if (has_rights_id) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in CNMT NCA header not empty!", __func__); + return false; + } + + // Modify distribution type + if (curStorageId == NcmStorageId_GameCard) dec_header.distribution = 0; + + section_offset = ((u64)dec_header.section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); + section_size = (((u64)dec_header.section_entries[0].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); + + if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for CNMT NCA section #0!", __func__); + return false; + } + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, xml_content_info[cnmtNcaIndex].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + section_data = malloc(section_size); + if (!section_data) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decrypted CNMT NCA section #0!", __func__); + return false; + } + + aes128CtrCrypt(&aes_ctx, section_data, ncaBuf + section_offset, section_size); + + nca_pfs0_offset = dec_header.fs_headers[0].pfs0_superblock.pfs0_offset; + memcpy(&nca_pfs0_header, section_data + nca_pfs0_offset, sizeof(pfs0_header)); + + if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); + free(section_data); + return false; + } + + if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); + free(section_data); + return false; + } + + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); + if (!nca_pfs0_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!", __func__); + free(section_data); + return false; + } + + memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)); + + nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); + nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); + + // Look for the CNMT + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + u64 filename_offset = (nca_pfs0_str_table_offset + nca_pfs0_entries[i].filename_offset); + if (!strncasecmp((char*)section_data + filename_offset, cnmtFileName, strlen(cnmtFileName))) + { + found_cnmt = true; + title_cnmt_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); + title_cnmt_size = nca_pfs0_entries[i].file_size; + break; + } + } + + free(nca_pfs0_entries); + + if (!found_cnmt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", __func__, cnmtFileName); + free(section_data); + return false; + } + + memcpy(&title_cnmt_header, section_data + title_cnmt_offset, sizeof(cnmt_header)); + memcpy(&title_cnmt_extended_header, section_data + title_cnmt_offset + sizeof(cnmt_header), sizeof(cnmt_extended_header)); + + // Fill information for our CNMT XML + digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE); + memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE); + convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1); + xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type); + xml_program_info->required_dl_sysver = title_cnmt_header.required_dl_sysver; + xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info[cnmtNcaIndex].keyblob); + xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver; + xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid; + xml_program_info->min_appver = title_cnmt_extended_header.min_appver; + + // Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA) + // Also wipe each of the content records we're gonna replace + for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA + { + for(j = 0; j < title_cnmt_header.content_cnt; j++) + { + cnmt_content_record cnt_record; + memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record)); + + if (memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) != 0) continue; + + // Save content record offset + xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record)); + + // Empty CNMT content record + memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record)); + + // Increase counter + k++; + + break; + } + } + + // Verify counter + if (k != (xml_program_info->nca_cnt - 1)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid content record entries in the CNMT NCA!", __func__); + free(section_data); + return false; + } + + // Replace input buffer data in-place + memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH); + memcpy(ncaBuf + section_offset, section_data, section_size); + + free(section_data); + + // Update offsets + nca_pfs0_offset += section_offset; + title_cnmt_offset += section_offset; + + // Save data to output struct + output->section_offset = section_offset; + output->section_size = section_size; + output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset); + output->hash_block_size = dec_header.fs_headers[0].pfs0_superblock.block_size; + output->hash_block_cnt = (dec_header.fs_headers[0].pfs0_superblock.hash_table_size / SHA256_HASH_SIZE); + output->pfs0_offset = nca_pfs0_offset; + output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size; + output->title_cnmt_offset = title_cnmt_offset; + output->title_cnmt_size = title_cnmt_size; + + return true; +} + +bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod) +{ + if (!ncaBuf || !ncaBufSize || !xml_program_info || xml_program_info->nca_cnt <= 1 || !xml_content_info || !cnmt_mod) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to patch CNMT NCA!", __func__); + return false; + } + + u32 i; + + u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA + + cnmt_header title_cnmt_header; + cnmt_content_record title_cnmt_content_record; + u64 title_cnmt_content_records_offset; + + nca_header_t dec_header; + + Aes128CtrContext aes_ctx; + + // Copy CNMT header + memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header)); + + // Calculate the start offset for the content records + title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size); + + // Write content records + for(i = 0; i < nca_cnt; i++) + { + memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record)); + + memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE); + memcpy(title_cnmt_content_record.nca_id, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2); + convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_record.size); + title_cnmt_content_record.type = xml_content_info[i].type; + title_cnmt_content_record.id_offset = xml_content_info[i].id_offset; + + memcpy(ncaBuf + title_cnmt_content_records_offset + xml_content_info[i].cnt_record_offset, &title_cnmt_content_record, sizeof(cnmt_content_record)); + } + + // Recalculate block hashes + for(i = 0; i < cnmt_mod->hash_block_cnt; i++) + { + u64 blk_offset = ((u64)i * cnmt_mod->hash_block_size); + + u64 rest_size = (cnmt_mod->pfs0_size - blk_offset); + u64 blk_size = (rest_size > cnmt_mod->hash_block_size ? cnmt_mod->hash_block_size : rest_size); + + sha256CalculateHash(ncaBuf + cnmt_mod->hash_table_offset + (i * SHA256_HASH_SIZE), ncaBuf + cnmt_mod->pfs0_offset + blk_offset, blk_size); + } + + // Copy header to struct + memcpy(&dec_header, ncaBuf, sizeof(nca_header_t)); + + // Calculate PFS0 superblock master hash + sha256CalculateHash(dec_header.fs_headers[0].pfs0_superblock.master_hash, ncaBuf + cnmt_mod->hash_table_offset, dec_header.fs_headers[0].pfs0_superblock.hash_table_size); + + // Calculate section hash + sha256CalculateHash(dec_header.section_hashes[0], &(dec_header.fs_headers[0]), sizeof(nca_fs_header_t)); + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (cnmt_mod->section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, xml_content_info[xml_program_info->nca_cnt - 1].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + // Reencrypt CNMT NCA + if (!encryptNcaHeader(&dec_header, ncaBuf, ncaBufSize)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt modified CNMT NCA header!", __func__); + return false; + } + + aes128CtrCrypt(&aes_ctx, ncaBuf + cnmt_mod->section_offset, ncaBuf + cnmt_mod->section_offset, cnmt_mod->section_size); + + // Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML + sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize); + convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1); + memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE / 2); + convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, SHA256_HASH_SIZE + 1); + + return true; +} + +bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); + return false; + } + + u8 exefs_index; + bool found_exefs = false; + + u32 i; + + u64 section_offset; + u64 section_size; + + unsigned char ctr[0x10]; + memset(ctr, 0, 0x10); + + u64 ofs; + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + + Aes128CtrContext aes_ctx; + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + u64 nca_pfs0_offset; + pfs0_header nca_pfs0_header; + + u64 nca_pfs0_entries_offset; + pfs0_file_entry *nca_pfs0_entries = NULL; + + u64 nca_pfs0_str_table_offset; + char *nca_pfs0_str_table = NULL; + + u64 nca_pfs0_data_offset; + + initExeFsContext(); + + for(exefs_index = 0; exefs_index < 4; exefs_index++) + { + if (dec_nca_header->fs_headers[exefs_index].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[exefs_index].fs_type != NCA_FS_HEADER_FSTYPE_PFS0 || !dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_size || dec_nca_header->fs_headers[exefs_index].crypt_type != NCA_FS_HEADER_CRYPT_CTR) continue; + + section_offset = ((u64)dec_nca_header->section_entries[exefs_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); + section_size = (((u64)dec_nca_header->section_entries[exefs_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); + + if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) continue; + + // Generate initial CTR + ofs = (section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_nca_header->fs_headers[exefs_index].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + aes128CtrContextResetCtr(&aes_ctx, ctr); + + nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_offset); + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) return false; + + if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC || !nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) continue; + + nca_pfs0_entries_offset = (nca_pfs0_offset + sizeof(pfs0_header)); + + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); + if (!nca_pfs0_entries) continue; + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_entries_offset, nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) + { + free(nca_pfs0_entries); + return false; + } + + nca_pfs0_str_table_offset = (nca_pfs0_entries_offset + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); + + nca_pfs0_str_table = calloc(nca_pfs0_header.str_table_size, sizeof(char)); + if (!nca_pfs0_str_table) + { + free(nca_pfs0_entries); + nca_pfs0_entries = NULL; + continue; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_str_table_offset, nca_pfs0_str_table, (u64)nca_pfs0_header.str_table_size, false)) + { + free(nca_pfs0_str_table); + free(nca_pfs0_entries); + return false; + } + + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + char *cur_filename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); + + if (!strncasecmp(cur_filename, "main.npdm", 9)) + { + found_exefs = true; + break; + } + } + + if (found_exefs) break; + + free(nca_pfs0_str_table); + nca_pfs0_str_table = NULL; + + free(nca_pfs0_entries); + nca_pfs0_entries = NULL; + } + + if (!found_exefs) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold an ExeFS section! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); + return false; + } + + nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); + + // Save data to output struct + // The caller function must free these data pointers + memcpy(&(exeFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); + memcpy(&(exeFsContext.ncaId), ncaId, sizeof(NcmContentId)); + memcpy(&(exeFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); + exeFsContext.exefs_offset = nca_pfs0_offset; + exeFsContext.exefs_size = dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_size; + memcpy(&(exeFsContext.exefs_header), &nca_pfs0_header, sizeof(pfs0_header)); + exeFsContext.exefs_entries_offset = nca_pfs0_entries_offset; + exeFsContext.exefs_entries = nca_pfs0_entries; + exeFsContext.exefs_str_table_offset = nca_pfs0_str_table_offset; + exeFsContext.exefs_str_table = nca_pfs0_str_table; + exeFsContext.exefs_data_offset = nca_pfs0_data_offset; + + return true; +} + +bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); + return false; + } + + u8 romfs_index; + bool found_romfs = false; + + u32 i; + + u64 section_offset; + u64 section_size; + + Aes128CtrContext aes_ctx; + + u64 romfs_offset; + u64 romfs_size; + romfs_header romFsHeader; + + u64 romfs_dirtable_offset; + u64 romfs_dirtable_size; + + u64 romfs_filetable_offset; + u64 romfs_filetable_size; + + u64 romfs_filedata_offset; + + romfs_dir *romfs_dir_entries = NULL; + romfs_file *romfs_file_entries = NULL; + + initRomFsContext(); + + for(romfs_index = 0; romfs_index < 4; romfs_index++) + { + if (dec_nca_header->fs_headers[romfs_index].partition_type == NCA_FS_HEADER_PARTITION_ROMFS && dec_nca_header->fs_headers[romfs_index].fs_type == NCA_FS_HEADER_FSTYPE_ROMFS) + { + found_romfs = true; + break; + } + } + + if (!found_romfs) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a RomFS section!", __func__); + return false; + } + + section_offset = ((u64)dec_nca_header->section_entries[romfs_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); + section_size = (((u64)dec_nca_header->section_entries[romfs_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); + + if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section! (#%u)", __func__, romfs_index); + return false; + } + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_nca_header->fs_headers[romfs_index].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + if (__builtin_bswap32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic) != IVFC_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IVFC magic word for NCA RomFS section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic)); + return false; + } + + if (dec_nca_header->fs_headers[romfs_index].crypt_type != NCA_FS_HEADER_CRYPT_CTR) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA RomFS section! (0x%02X)", __func__, dec_nca_header->fs_headers[romfs_index].crypt_type); + return false; + } + + romfs_offset = (section_offset + dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].logical_offset); + romfs_size = dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].hash_data_size; + + if (romfs_offset < section_offset || romfs_offset >= (section_offset + section_size) || romfs_size < ROMFS_HEADER_SIZE || (romfs_offset + romfs_size) > (section_offset + section_size)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section!", __func__); + return false; + } + + // First read the RomFS header + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_offset, &romFsHeader, sizeof(romfs_header), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section header!", __func__); + return false; + } + + if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, romfs_offset); + return false; + } + + romfs_dirtable_offset = (romfs_offset + romFsHeader.dirTableOff); + romfs_dirtable_size = romFsHeader.dirTableSize; + + romfs_filetable_offset = (romfs_offset + romFsHeader.fileTableOff); + romfs_filetable_size = romFsHeader.fileTableSize; + + if (romfs_dirtable_offset >= (section_offset + section_size) || !romfs_dirtable_size || (romfs_dirtable_offset + romfs_dirtable_size) > (section_offset + section_size) || romfs_filetable_offset >= (section_offset + section_size) || !romfs_filetable_size || (romfs_filetable_offset + romfs_filetable_size) > (section_offset + section_size)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA RomFS section!", __func__); + return false; + } + + romfs_filedata_offset = (romfs_offset + romFsHeader.fileDataOff); + + if (romfs_filedata_offset >= (section_offset + section_size)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA RomFS section!", __func__); + return false; + } + + romfs_dir_entries = calloc(1, romfs_dirtable_size); + if (!romfs_dir_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section directory entries!", __func__); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_dirtable_offset, romfs_dir_entries, romfs_dirtable_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section directory entries!", __func__); + free(romfs_dir_entries); + return false; + } + + romfs_file_entries = calloc(1, romfs_filetable_size); + if (!romfs_file_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section file entries!", __func__); + free(romfs_dir_entries); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_filetable_offset, romfs_file_entries, romfs_filetable_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); + free(romfs_file_entries); + free(romfs_dir_entries); + return false; + } + + // Save data to output struct + // The caller function must free these data pointers + memcpy(&(romFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); + memcpy(&(romFsContext.ncaId), ncaId, sizeof(NcmContentId)); + memcpy(&(romFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); + romFsContext.section_offset = section_offset; + romFsContext.section_size = section_size; + romFsContext.romfs_offset = romfs_offset; + romFsContext.romfs_size = romfs_size; + romFsContext.romfs_dirtable_offset = romfs_dirtable_offset; + romFsContext.romfs_dirtable_size = romfs_dirtable_size; + romFsContext.romfs_dir_entries = romfs_dir_entries; + romFsContext.romfs_filetable_offset = romfs_filetable_offset; + romFsContext.romfs_filetable_size = romfs_filetable_size; + romFsContext.romfs_file_entries = romfs_file_entries; + romFsContext.romfs_filedata_offset = romfs_filedata_offset; + + return true; +} + +bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !romFsContext.section_offset || !romFsContext.section_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_file_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read BKTR section from NCA!", __func__); + return false; + } + + u32 i; + + u8 bktr_index; + bool found_bktr = false, success = false; + + romfs_header romFsHeader; + + initBktrContext(); + + memcpy(&(bktrContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); + memcpy(&(bktrContext.ncaId), ncaId, sizeof(NcmContentId)); + + for(bktr_index = 0; bktr_index < 4; bktr_index++) + { + if (dec_nca_header->fs_headers[bktr_index].partition_type == NCA_FS_HEADER_PARTITION_ROMFS && dec_nca_header->fs_headers[bktr_index].fs_type == NCA_FS_HEADER_FSTYPE_ROMFS) + { + found_bktr = true; + break; + } + } + + if (!found_bktr) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a BKTR section!", __func__); + return false; + } + + bktrContext.section_offset = ((u64)dec_nca_header->section_entries[bktr_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); + bktrContext.section_size = (((u64)dec_nca_header->section_entries[bktr_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - bktrContext.section_offset); + + if (!bktrContext.section_offset || bktrContext.section_offset < NCA_FULL_HEADER_LENGTH || !bktrContext.section_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR section! (#%u)", __func__, bktr_index); + return false; + } + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (bktrContext.section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_nca_header->fs_headers[bktr_index].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&(bktrContext.aes_ctx), ctr_key, ctr); + + memcpy(&(bktrContext.superblock), &(dec_nca_header->fs_headers[bktr_index].bktr_superblock), sizeof(bktr_superblock_t)); + + if (__builtin_bswap32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IVFC magic word for NCA BKTR section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(bktrContext.superblock.ivfc_header.magic)); + return false; + } + + if (dec_nca_header->fs_headers[bktr_index].crypt_type != NCA_FS_HEADER_CRYPT_BKTR) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA BKTR section! (0x%02X)", __func__, dec_nca_header->fs_headers[bktr_index].crypt_type); + return false; + } + + if (__builtin_bswap32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || __builtin_bswap32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid BKTR magic word for NCA BKTR relocation/subsection header! Wrong KAEK? (0x%02X | 0x%02X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(bktrContext.superblock.relocation_header.magic), __builtin_bswap32(bktrContext.superblock.subsection_header.magic)); + return false; + } + + if ((bktrContext.superblock.relocation_header.offset + bktrContext.superblock.relocation_header.size) != bktrContext.superblock.subsection_header.offset || (bktrContext.superblock.subsection_header.offset + bktrContext.superblock.subsection_header.size) != bktrContext.section_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid layout for NCA BKTR section!", __func__); + return false; + } + + // Allocate space for an extra (fake) relocation entry, to simplify our logic + bktrContext.relocation_block = calloc(1, bktrContext.superblock.relocation_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_relocation_entry_t))); + if (!bktrContext.relocation_block) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR relocation header!", __func__); + return false; + } + + // Allocate space for an extra (fake) subsection entry, to simplify our logic + bktrContext.subsection_block = calloc(1, bktrContext.superblock.subsection_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_subsection_entry_t)) + sizeof(bktr_subsection_entry_t)); + if (!bktrContext.subsection_block) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR subsection header!", __func__); + goto out; + } + + // Read the relocation header + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.relocation_header.offset, bktrContext.relocation_block, bktrContext.superblock.relocation_header.size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR relocation header!", __func__); + goto out; + } + + // Read the subsection header + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.subsection_header.offset, bktrContext.subsection_block, bktrContext.superblock.subsection_header.size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR subsection header!", __func__); + goto out; + } + + if (bktrContext.subsection_block->total_size != bktrContext.superblock.subsection_header.offset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA BKTR subsection header size!", __func__); + goto out; + } + + // This simplifies logic greatly... + for(i = (bktrContext.relocation_block->num_buckets - 1); i > 0; i--) + { + bktr_relocation_bucket_t tmp_bucket; + memcpy(&tmp_bucket, &(bktrContext.relocation_block->buckets[i]), sizeof(bktr_relocation_bucket_t)); + memcpy(bktr_get_relocation_bucket(bktrContext.relocation_block, i), &tmp_bucket, sizeof(bktr_relocation_bucket_t)); + } + + for(i = 0; (i + 1) < bktrContext.relocation_block->num_buckets; i++) + { + bktr_relocation_bucket_t *cur_bucket = bktr_get_relocation_bucket(bktrContext.relocation_block, i); + cur_bucket->entries[cur_bucket->num_entries].virt_offset = bktrContext.relocation_block->bucket_virtual_offsets[i + 1]; + } + + for(i = (bktrContext.subsection_block->num_buckets - 1); i > 0; i--) + { + bktr_subsection_bucket_t tmp_bucket; + memcpy(&tmp_bucket, &(bktrContext.subsection_block->buckets[i]), sizeof(bktr_subsection_bucket_t)); + memcpy(bktr_get_subsection_bucket(bktrContext.subsection_block, i), &tmp_bucket, sizeof(bktr_subsection_bucket_t)); + } + + for(i = 0; (i + 1) < bktrContext.subsection_block->num_buckets; i++) + { + bktr_subsection_bucket_t *cur_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, i); + bktr_subsection_bucket_t *next_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, i + 1); + cur_bucket->entries[cur_bucket->num_entries].offset = next_bucket->entries[0].offset; + cur_bucket->entries[cur_bucket->num_entries].ctr_val = next_bucket->entries[0].ctr_val; + } + + bktr_relocation_bucket_t *last_reloc_bucket = bktr_get_relocation_bucket(bktrContext.relocation_block, bktrContext.relocation_block->num_buckets - 1); + bktr_subsection_bucket_t *last_subsec_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, bktrContext.subsection_block->num_buckets - 1); + last_reloc_bucket->entries[last_reloc_bucket->num_entries].virt_offset = bktrContext.relocation_block->total_size; + last_subsec_bucket->entries[last_subsec_bucket->num_entries].offset = bktrContext.superblock.relocation_header.offset; + last_subsec_bucket->entries[last_subsec_bucket->num_entries].ctr_val = dec_nca_header->fs_headers[bktr_index].section_ctr_low; + last_subsec_bucket->entries[last_subsec_bucket->num_entries + 1].offset = bktrContext.section_size; + last_subsec_bucket->entries[last_subsec_bucket->num_entries + 1].ctr_val = 0; + + // Parse RomFS section + bktrContext.romfs_offset = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].logical_offset; + bktrContext.romfs_size = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].hash_data_size; + + // Do not check the RomFS offset/size, because it reflects the full patched RomFS image + if (!bktrContext.romfs_offset || bktrContext.romfs_size < ROMFS_HEADER_SIZE) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR RomFS section!", __func__); + goto out; + } + + if (!readBktrSectionBlock(bktrContext.romfs_offset, &romFsHeader, sizeof(romfs_header))) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section header!", __func__); + goto out; + } + + if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA BKTR RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, bktrContext.section_offset + bktrContext.romfs_offset); + goto out; + } + + bktrContext.romfs_dirtable_offset = (bktrContext.romfs_offset + romFsHeader.dirTableOff); + bktrContext.romfs_dirtable_size = romFsHeader.dirTableSize; + + bktrContext.romfs_filetable_offset = (bktrContext.romfs_offset + romFsHeader.fileTableOff); + bktrContext.romfs_filetable_size = romFsHeader.fileTableSize; + + // Then again, do not check these offsets/sizes, because they reflect the patched RomFS image + if (!bktrContext.romfs_dirtable_offset || !bktrContext.romfs_dirtable_size || !bktrContext.romfs_filetable_offset || !bktrContext.romfs_filetable_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA BKTR RomFS section!", __func__); + goto out; + } + + bktrContext.romfs_filedata_offset = (bktrContext.romfs_offset + romFsHeader.fileDataOff); + + if (!bktrContext.romfs_filedata_offset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA BKTR RomFS section!", __func__); + goto out; + } + + bktrContext.romfs_dir_entries = calloc(1, bktrContext.romfs_dirtable_size); + if (!bktrContext.romfs_dir_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section directory entries!", __func__); + goto out; + } + + if (!readBktrSectionBlock(bktrContext.romfs_dirtable_offset, bktrContext.romfs_dir_entries, bktrContext.romfs_dirtable_size)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section directory entries!", __func__); + goto out; + } + + bktrContext.romfs_file_entries = calloc(1, bktrContext.romfs_filetable_size); + if (!bktrContext.romfs_file_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section file entries!", __func__); + goto out; + } + + if (!readBktrSectionBlock(bktrContext.romfs_filetable_offset, bktrContext.romfs_file_entries, bktrContext.romfs_filetable_size)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); + goto out; + } + + success = true; + +out: + if (!success) + { + if (bktrContext.romfs_file_entries != NULL) + { + free(bktrContext.romfs_file_entries); + bktrContext.romfs_file_entries = NULL; + } + + if (bktrContext.romfs_dir_entries != NULL) + { + free(bktrContext.romfs_dir_entries); + bktrContext.romfs_dir_entries = NULL; + } + + if (bktrContext.subsection_block != NULL) + { + free(bktrContext.subsection_block); + bktrContext.subsection_block = NULL; + } + + if (bktrContext.relocation_block != NULL) + { + free(bktrContext.relocation_block); + bktrContext.relocation_block = NULL; + } + } + + // The caller function must free the data pointers from the bktrContext struct + + return success; +} + +bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, bool useCustomAcidRsaPubKey, char **outBuf, u64 *outBufSize) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !outBuf || !outBufSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate \"programinfo.xml\"!", __func__); + return false; + } + + if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); + return false; + } + + if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); + return false; + } + + if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); + return false; + } + + u32 i; + + bool proceed = true, success = false; + + u64 section_offset; + u64 nca_pfs0_offset; + + pfs0_header nca_pfs0_header; + pfs0_file_entry *nca_pfs0_entries = NULL; + char *nca_pfs0_str_table = NULL; + + u64 nca_pfs0_str_table_offset; + u64 nca_pfs0_data_offset; + + Aes128CtrContext aes_ctx; + + char *programInfoXml = NULL; + char tmp[NAME_BUF_LEN] = {'\0'}; + + u32 npdmEntry = 0; + npdm_t npdm_header; + u8 *npdm_acid_section = NULL; + + u64 npdm_acid_section_b64_size = 0; + char *npdm_acid_section_b64 = NULL; + + u32 acid_flags = 0; + + section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); + nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset); + + if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !nca_pfs0_offset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); + return false; + } + + // Generate initial CTR + unsigned char ctr[0x10]; + u64 ofs = (section_offset >> 4); + + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } + + u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; + memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); + aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); + return false; + } + + if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); + return false; + } + + if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); + return false; + } + + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); + if (!nca_pfs0_entries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); + return false; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); + goto out; + } + + nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); + + nca_pfs0_str_table = calloc((u64)nca_pfs0_header.str_table_size, sizeof(char)); + if (!nca_pfs0_str_table) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 string table!", __func__); + goto out; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_str_table_offset, nca_pfs0_str_table, (u64)nca_pfs0_header.str_table_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 string table!", __func__); + goto out; + } + + nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); + + // Allocate memory for the programinfo.xml contents, making sure there's enough space + programInfoXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); + if (!programInfoXml) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); + goto out; + } + + sprintf(programInfoXml, "\n" \ + "\n" \ + " %u_%u_%u\n", dec_nca_header->sdk_major, dec_nca_header->sdk_minor, dec_nca_header->sdk_micro); + + // Retrieve the main.npdm contents + bool found_npdm = false; + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); + + if (strlen(curFilename) == 9 && !strncasecmp(curFilename, "main.npdm", 9) && nca_pfs0_entries[i].file_size > 0) + { + found_npdm = true; + npdmEntry = i; + break; + } + } + + if (!found_npdm) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); + goto out; + } + + // Read the META header from the NPDM + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset, &npdm_header, sizeof(npdm_t), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NPDM entry header from Program NCA section #0 PFS0!", __func__); + goto out; + } + + if (__builtin_bswap32(npdm_header.magic) != META_MAGIC) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NPDM META magic word! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(npdm_header.magic)); + goto out; + } + + // Allocate memory for the ACID section + npdm_acid_section = malloc(npdm_header.acid_size); + if (!npdm_acid_section) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Program NCA NPDM ACID section contents!", __func__); + goto out; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset + (u64)npdm_header.acid_offset, npdm_acid_section, (u64)npdm_header.acid_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read ACID section from Program NCA NPDM!", __func__); + goto out; + } + + // If we're dealing with a gamecard title, replace the ACID public key with the patched one + if (useCustomAcidRsaPubKey) memcpy(npdm_acid_section + (u64)NPDM_SIGNATURE_SIZE, rsa_get_public_key(), (u64)NPDM_SIGNATURE_SIZE); + + sprintf(tmp, " %u\n", ((npdm_header.mmu_flags & 0x01) ? 64 : 32)); + strcat(programInfoXml, tmp); + + // Default this one to Release + strcat(programInfoXml, " Release\n"); + + // Retrieve the Base64 conversion length for the whole ACID section + mbedtls_base64_encode(NULL, 0, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size); + if (npdm_acid_section_b64_size <= (u64)npdm_header.acid_size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid Base64 conversion length for the ACID section from Program NCA NPDM!", __func__); + goto out; + } + + npdm_acid_section_b64 = calloc(npdm_acid_section_b64_size + 1, sizeof(char)); + if (!npdm_acid_section_b64) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Base64 converted ACID section from Program NCA NPDM!", __func__); + goto out; + } + + // Perform the Base64 conversion + if (mbedtls_base64_encode((unsigned char*)npdm_acid_section_b64, npdm_acid_section_b64_size + 1, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size) != 0) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Base64 conversion failed for the ACID section from Program NCA NPDM!", __func__); + goto out; + } + + strcat(programInfoXml, " "); + strcat(programInfoXml, npdm_acid_section_b64); + strcat(programInfoXml, "\n"); + + // TO-DO: Add more ACID flags? + + acid_flags = *((u32*)(&(npdm_acid_section[0x20C]))); + + strcat(programInfoXml, " \n"); + + sprintf(tmp, " %s\n", ((acid_flags & 0x01) ? "true" : "false")); + strcat(programInfoXml, tmp); + + sprintf(tmp, " %s\n", ((acid_flags & 0x02) ? "true" : "false")); + strcat(programInfoXml, tmp); + + strcat(programInfoXml, " \n"); + + // Middleware list + strcat(programInfoXml, " \n"); + + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + nso_header_t nsoHeader; + char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); + u64 curFileOffset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); + proceed = false; + break; + } + + // Check if we're dealing with a NSO + if (__builtin_bswap32(nsoHeader.magic) != NSO_MAGIC) continue; + + // Retrieve middleware list from this NSO + if (!retrieveMiddlewareListFromNso(ncmStorage, ncaId, &aes_ctx, curFilename, curFileOffset, &nsoHeader, programInfoXml)) + { + proceed = false; + break; + } + } + + if (!proceed) goto out; + + strcat(programInfoXml, " \n"); + + // Leave these fields empty (for now) + strcat(programInfoXml, " \n"); + strcat(programInfoXml, " \n"); + + // Symbols list from main NSO + strcat(programInfoXml, " \n"); + + for(i = 0; i < nca_pfs0_header.file_cnt; i++) + { + nso_header_t nsoHeader; + char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); + u64 curFileOffset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); + proceed = false; + break; + } + + // Check if we're dealing with the main NSO + if (strlen(curFilename) != 4 || strncmp(curFilename, "main", 4) != 0 || __builtin_bswap32(nsoHeader.magic) != NSO_MAGIC) continue; + + // Retrieve symbols list from main NSO + if (!retrieveSymbolsListFromNso(ncmStorage, ncaId, &aes_ctx, curFilename, curFileOffset, &nsoHeader, programInfoXml)) proceed = false; + + break; + } + + if (!proceed) goto out; + + strcat(programInfoXml, " \n"); + + // Leave this field empty (for now) + strcat(programInfoXml, " \n"); + + strcat(programInfoXml, ""); + + *outBuf = programInfoXml; + *outBufSize = strlen(programInfoXml); + + success = true; + +out: + if (npdm_acid_section_b64) free(npdm_acid_section_b64); + + if (npdm_acid_section) free(npdm_acid_section); + + if (!success && programInfoXml) free(programInfoXml); + + if (nca_pfs0_str_table) free(nca_pfs0_str_table); + + if (nca_pfs0_entries) free(nca_pfs0_entries); + + return success; +} + +char *getNacpLangName(Language val) +{ + char *out = NULL; + + switch(val) + { + case Language_AmericanEnglish: + out = "AmericanEnglish"; + break; + case Language_BritishEnglish: + out = "BritishEnglish"; + break; + case Language_Japanese: + out = "Japanese"; + break; + case Language_French: + out = "French"; + break; + case Language_German: + out = "German"; + break; + case Language_LatinAmericanSpanish: + out = "LatinAmericanSpanish"; + break; + case Language_Spanish: + out = "Spanish"; + break; + case Language_Italian: + out = "Italian"; + break; + case Language_Dutch: + out = "Dutch"; + break; + case Language_CanadianFrench: + out = "CanadianFrench"; + break; + case Language_Portuguese: + out = "Portuguese"; + break; + case Language_Russian: + out = "Russian"; + break; + case Language_Korean: + out = "Korean"; + break; + case Language_TraditionalChinese: + out = "TraditionalChinese"; + break; + case Language_SimplifiedChinese: + out = "SimplifiedChinese"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpStartupUserAccount(u8 val) +{ + char *out = NULL; + + switch(val) + { + case StartupUserAccount_None: + out = "None"; + break; + case StartupUserAccount_Required: + out = "Required"; + break; + case StartupUserAccount_RequiredWithNetworkServiceAccountAvailable: + out = "RequiredWithNetworkServiceAccountAvailable"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpUserAccountSwitchLock(u8 val) +{ + char *out = NULL; + + switch(val) + { + case UserAccountSwitchLock_Disable: + out = "Disable"; + break; + case UserAccountSwitchLock_Enable: + out = "Enable"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpSupportedLanguageFlag(SupportedLanguageFlag *data, u8 idx) +{ + if (!data || idx >= 0x10) return NULL; + + u32 flag; + memcpy(&flag, data, sizeof(u32)); + flag = ((flag >> idx) & 0x1); + + return (flag ? getNacpLangName((Language)flag) : NULL); +} + +char *getNacpScreenshot(u8 val) +{ + char *out = NULL; + + switch(val) + { + case Screenshot_Allow: + out = "Allow"; + break; + case Screenshot_Deny: + out = "Deny"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpVideoCapture(u8 val) +{ + char *out = NULL; + + switch(val) + { + case VideoCapture_Disable: + out = "Disable"; + break; + case VideoCapture_Manual: + out = "Manual"; + break; + case VideoCapture_Enable: + out = "Enable"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpRatingAgeOrganization(RatingAgeOrganization val) +{ + char *out = NULL; + + switch(val) + { + case RatingAgeOrganization_CERO: + out = "CERO"; + break; + case RatingAgeOrganization_GRACGCRB: + out = "GRACGCRB"; + break; + case RatingAgeOrganization_GSRMR: + out = "GSRMR"; + break; + case RatingAgeOrganization_ESRB: + out = "ESRB"; + break; + case RatingAgeOrganization_ClassInd: + out = "ClassInd"; + break; + case RatingAgeOrganization_USK: + out = "USK"; + break; + case RatingAgeOrganization_PEGI: + out = "PEGI"; + break; + case RatingAgeOrganization_PEGIPortugal: + out = "PEGIPortugal"; + break; + case RatingAgeOrganization_PEGIBBFC: + out = "PEGIBBFC"; + break; + case RatingAgeOrganization_Russian: + out = "Russian"; + break; + case RatingAgeOrganization_ACB: + out = "ACB"; + break; + case RatingAgeOrganization_OFLC: + out = "OFLC"; + break; + case RatingAgeOrganization_IARCGeneric: + out = "IARCGeneric"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpDataLossConfirmation(u8 val) +{ + char *out = NULL; + + switch(val) + { + case DataLossConfirmation_None: + out = "None"; + break; + case DataLossConfirmation_Required: + out = "Required"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpPlayLogPolicy(u8 val) +{ + char *out = NULL; + + switch(val) + { + case PlayLogPolicy_All: + out = "All"; + break; + case PlayLogPolicy_LogOnly: + out = "LogOnly"; + break; + case PlayLogPolicy_None: + out = "None"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpLogoType(u8 val) +{ + char *out = NULL; + + switch(val) + { + case LogoType_LicensedByNintendo: + out = "LicensedByNintendo"; + break; + case LogoType_DistributedByNintendo: + out = "DistributedByNintendo"; + break; + case LogoType_Nintendo: + out = "Nintendo"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpLogoHandling(u8 val) +{ + char *out = NULL; + + switch(val) + { + case LogoHandling_Auto: + out = "Auto"; + break; + case LogoHandling_Manual: + out = "Manual"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpAddOnContentRegistrationType(u8 val) +{ + char *out = NULL; + + switch(val) + { + case AddOnContentRegistrationType_AllOnLaunch: + out = "AllOnLaunch"; + break; + case AddOnContentRegistrationType_OnDemand: + out = "OnDemand"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpHdcp(u8 val) +{ + char *out = NULL; + + switch(val) + { + case Hdcp_None: + out = "None"; + break; + case Hdcp_Required: + out = "Required"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpCrashReport(u8 val) +{ + char *out = NULL; + + switch(val) + { + case CrashReport_Deny: + out = "Deny"; + break; + case CrashReport_Allow: + out = "Allow"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpRuntimeAddOnContentInstall(u8 val) +{ + char *out = NULL; + + switch(val) + { + case RuntimeAddOnContentInstall_Deny: + out = "Deny"; + break; + case RuntimeAddOnContentInstall_AllowAppend: + out = "AllowAppend"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpRuntimeParameterDelivery(u8 val) +{ + char *out = NULL; + + switch(val) + { + case RuntimeParameterDelivery_Always: + out = "Always"; + break; + case RuntimeParameterDelivery_AlwaysIfUserStateMatched: + out = "AlwaysIfUserStateMatched"; + break; + case RuntimeParameterDelivery_OnRestart: + out = "OnRestart"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpPlayLogQueryCapability(u8 val) +{ + char *out = NULL; + + switch(val) + { + case PlayLogQueryCapability_None: + out = "None"; + break; + case PlayLogQueryCapability_WhiteList: + out = "WhiteList"; + break; + case PlayLogQueryCapability_All: + out = "All"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +char *getNacpJitConfigurationFlag(u64 val) +{ + char *out = NULL; + + switch(val) + { + case JitConfigurationFlag_None: + out = "None"; + break; + case JitConfigurationFlag_Enabled: + out = "Enabled"; + break; + default: + out = "Unknown"; + break; + } + + return out; +} + +bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !out_nacp_xml || !out_nacp_xml_size || !out_nacp_icons_ctx || !out_nacp_icons_ctx_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate NACP XML!", __func__); + return false; + } + + u64 entryOffset = 0; + romfs_file *entry = NULL; + bool found_nacp = false, success = false; + + nacp_t controlNacp; + char *nacpXml = NULL; + + u8 i = 0, j = 0; + char tmp[NAME_BUF_LEN] = {'\0'}; + u32 flag; + + u8 nacpIconCnt = 0; + nacp_icons_ctx *nacpIcons = NULL; + + bool found_icon = false; + u8 languageIconHash[SHA256_HASH_SIZE]; + char languageIconHashStr[SHA256_HASH_SIZE + 1] = {'\0'}; + + char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; + convertDataToHexString(ncaId->c, 0x10, ncaIdStr, 0x21); + + char dataStr[100] = {'\0'}; + + u8 null_key[0x10]; + memset(null_key, 0, 0x10); + + bool availableSGC = false, availableRGC = false; + + if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; + + // Look for the control.nacp file + while(entryOffset < romFsContext.romfs_filetable_size) + { + entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); + + if (entry->parent == 0 && entry->nameLen == 12 && !strncasecmp((char*)entry->name, "control.nacp", 12)) + { + found_nacp = true; + break; + } + + entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); + } + + if (!found_nacp) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"control.nacp\" file in Control NCA RomFS section!", __func__); + goto out; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, &controlNacp, sizeof(nacp_t), false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"control.nacp\" from RomFS section in Control NCA!", __func__); + goto out; + } + + // Make sure that the output buffer for our NACP XML is big enough + nacpXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); + if (!nacpXml) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP XML!", __func__); + goto out; + } + + sprintf(nacpXml, "\n" \ + "\n"); + + for(i = 0; i < 0x10; i++) + { + if (strlen(controlNacp.titles[i].name) || strlen(controlNacp.titles[i].publisher)) + { + sprintf(tmp, " \n" \ + " <Language>%s</Language>\n" \ + " <Name>%s</Name>\n" \ + " <Publisher>%s</Publisher>\n" \ + " \n", \ + getNacpLangName(i), \ + controlNacp.titles[i].name, \ + controlNacp.titles[i].publisher); + + strcat(nacpXml, tmp); + } + } + + if (strlen(controlNacp.isbn)) + { + sprintf(tmp, " %s\n", controlNacp.isbn); + strcat(nacpXml, tmp); + } else { + strcat(nacpXml, " \n"); + } + + sprintf(tmp, " %s\n", getNacpStartupUserAccount(controlNacp.startup_user_account)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpUserAccountSwitchLock(controlNacp.user_account_switch_lock)); + strcat(nacpXml, tmp); + + strcat(nacpXml, " "); + + memcpy(&flag, &(controlNacp.parental_control_flag), sizeof(u32)); + if (flag != 0) + { + if (controlNacp.parental_control_flag.ParentalControlFlag_FreeCommunication) strcat(nacpXml, "FreeCommunication"); + } else { + strcat(nacpXml, "None"); + } + + strcat(nacpXml, "\n"); + + for(i = 0; i < 0x10; i++) + { + char *str = getNacpSupportedLanguageFlag(&(controlNacp.supported_language_flag), i); + if (!str) continue; + + sprintf(tmp, " %s\n", str); + strcat(nacpXml, tmp); + + nacpIconCnt++; + } + + sprintf(tmp, " %s\n", getNacpScreenshot(controlNacp.screenshot)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpVideoCapture(controlNacp.video_capture)); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.presence_group_id); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", controlNacp.display_version); + strcat(nacpXml, tmp); + + for(i = 0; i < 0x20; i++) + { + u8 *ptr = ((u8*)(&(controlNacp.rating_ages)) + i); + if (*ptr == 0xFF) continue; + + sprintf(tmp, " \n" \ + " %s\n" \ + " %u\n" \ + " \n", \ + getNacpRatingAgeOrganization(i), \ + *ptr); + + strcat(nacpXml, tmp); + } + + sprintf(tmp, " %s\n", getNacpDataLossConfirmation(controlNacp.data_loss_confirmation)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpPlayLogPolicy(controlNacp.play_log_policy)); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.save_data_owner_id); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_journal_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_journal_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.bcat_delivery_cache_storage_size); + strcat(nacpXml, tmp); + + if (strlen(controlNacp.application_error_code_category)) + { + sprintf(tmp, " %s\n", controlNacp.application_error_code_category); + strcat(nacpXml, tmp); + } else { + strcat(nacpXml, " \n"); + } + + sprintf(tmp, " 0x%016lx\n", controlNacp.add_on_content_base_id); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpLogoType(controlNacp.logo_type)); + strcat(nacpXml, tmp); + + for(i = 0; i < 0x8; i++) + { + if (controlNacp.local_communication_ids[i] != 0) + { + sprintf(tmp, " 0x%016lx\n", controlNacp.local_communication_ids[i]); + strcat(nacpXml, tmp); + } + } + + sprintf(tmp, " %s\n", getNacpLogoHandling(controlNacp.logo_handling)); + strcat(nacpXml, tmp); + + if (nacpIconCnt) + { + nacpIcons = calloc(nacpIconCnt, sizeof(nacp_icons_ctx)); + if (!nacpIcons) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP icons!", __func__); + goto out; + } + + for(i = 0; i < 0x10; i++) + { + char *str = getNacpSupportedLanguageFlag(&(controlNacp.supported_language_flag), i); + if (!str) continue; + + // Retrieve the icon file for this language and calculate its SHA-256 checksum + found_icon = false; + + memset(languageIconHash, 0, SHA256_HASH_SIZE); + memset(languageIconHashStr, 0, SHA256_HASH_SIZE + 1); + + entryOffset = 0; + sprintf(tmp, "icon_%s.dat", getNacpLangName(i)); + + while(entryOffset < romFsContext.romfs_filetable_size) + { + entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); + + if (entry->parent == 0 && entry->nameLen == strlen(tmp) && !strncasecmp((char*)entry->name, tmp, strlen(tmp)) && entry->dataSize <= 0x20000) + { + found_icon = true; + break; + } + + entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); + } + + if (!found_icon) + { + nacpIconCnt--; + continue; + } + + strcat(nacpXml, " \n"); + + sprintf(tmp, " %s\n", getNacpLangName(i)); + strcat(nacpXml, tmp); + + // Fill details for our NACP icon context + sprintf(nacpIcons[j].filename, "%s.nx.%s.jpg", ncaIdStr, getNacpLangName(i)); // Temporary, the NCA ID is subject to change + nacpIcons[j].icon_size = entry->dataSize; + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, nacpIcons[j].icon_data, nacpIcons[j].icon_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"%s\" from RomFS section in Control NCA!", __func__, tmp); + goto out; + } + + sha256CalculateHash(languageIconHash, nacpIcons[j].icon_data, nacpIcons[j].icon_size); + + // Only retrieve the first half from the SHA-256 checksum + convertDataToHexString(languageIconHash, SHA256_HASH_SIZE / 2, languageIconHashStr, SHA256_HASH_SIZE + 1); + + // Now print the hash + sprintf(tmp, " %s\n", languageIconHashStr); + strcat(nacpXml, tmp); + + strcat(nacpXml, " \n"); + + j++; + } + } + + sprintf(tmp, " 0x%016lx\n", controlNacp.seed_for_pseudo_device_id); + strcat(nacpXml, tmp); + + if (strlen(controlNacp.bcat_passphrase)) + { + sprintf(tmp, " %s\n", controlNacp.bcat_passphrase); + strcat(nacpXml, tmp); + } else { + strcat(nacpXml, " \n"); + } + + strcat(nacpXml, " "); + + if (*((u8*)&(controlNacp.startup_user_account_option)) != 0) + { + if (controlNacp.startup_user_account_option.StartupUserAccountOptionFlag_IsOptional) strcat(nacpXml, "IsOptional"); + } else { + strcat(nacpXml, "None"); + } + + strcat(nacpXml, "\n"); + + sprintf(tmp, " %s\n", getNacpAddOnContentRegistrationType(controlNacp.add_on_content_registration_type)); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_size_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_journal_size_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_size_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_journal_size_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.temporary_storage_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_journal_size); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_data_and_journal_size_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " 0x%04x\n", controlNacp.cache_storage_index_max); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpHdcp(controlNacp.hdcp)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpCrashReport(controlNacp.crash_report)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpRuntimeAddOnContentInstall(controlNacp.runtime_add_on_content_install)); + strcat(nacpXml, tmp); + + sprintf(tmp, " %s\n", getNacpRuntimeParameterDelivery(controlNacp.runtime_parameter_delivery)); + strcat(nacpXml, tmp); + + for(i = 0; i < 0x10; i++) + { + if (controlNacp.play_log_queryable_application_ids[i] != 0) + { + sprintf(tmp, " 0x%016lx\n", controlNacp.play_log_queryable_application_ids[i]); + strcat(nacpXml, tmp); + } + } + + sprintf(tmp, " %s\n", getNacpPlayLogQueryCapability(controlNacp.play_log_query_capability)); + strcat(nacpXml, tmp); + + strcat(nacpXml, " "); + + if (*((u8*)&(controlNacp.repair_flag)) != 0) + { + if (controlNacp.repair_flag.RepairFlag_SuppressGameCardAccess) strcat(nacpXml, "SuppressGameCardAccess"); + } else { + strcat(nacpXml, "None"); + } + + strcat(nacpXml, "\n"); + + strcat(nacpXml, " "); + + memcpy(&flag, &(controlNacp.attribute_flag), sizeof(u32)); + if (flag != 0) + { + if (controlNacp.attribute_flag.AttributeFlag_Demo) strcat(nacpXml, "Demo"); + + if (controlNacp.attribute_flag.AttributeFlag_RetailInteractiveDisplay) + { + if (controlNacp.attribute_flag.AttributeFlag_Demo) strcat(nacpXml, ","); + strcat(nacpXml, "RetailInteractiveDisplay"); + } + } else { + strcat(nacpXml, "None"); + } + + strcat(nacpXml, "\n"); + + sprintf(tmp, " %u\n", controlNacp.program_index); + strcat(nacpXml, tmp); + + strcat(nacpXml, " "); + + if (*((u8*)&(controlNacp.required_network_service_license_on_launch_flag)) != 0) + { + if (controlNacp.required_network_service_license_on_launch_flag.RequiredNetworkServiceLicenseOnLaunchFlag_Common) strcat(nacpXml, "Common"); + } else { + strcat(nacpXml, "None"); + } + + strcat(nacpXml, "\n"); + + // Check if we actually have valid NeighborDetectionClientConfiguration values + availableSGC = (controlNacp.neighbor_detection_client_configuration.send_group_configuration.group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.send_group_configuration.key, null_key, 0x10) != 0); + + for(i = 0; i < 0x10; i++) + { + if (controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, null_key, 0x10) != 0) + { + availableRGC = true; + break; + } + } + + if (availableSGC || availableRGC) + { + strcat(nacpXml, " \n"); + + if (availableSGC) + { + convertDataToHexString(controlNacp.neighbor_detection_client_configuration.send_group_configuration.key, 0x10, dataStr, 100); + + sprintf(tmp, " \n" \ + " 0x%016lx\n" \ + " %s\n" \ + " \n", \ + controlNacp.neighbor_detection_client_configuration.send_group_configuration.group_id, \ + dataStr); + + strcat(nacpXml, tmp); + } + + if (availableRGC) + { + for(i = 0; i < 0x10; i++) + { + if (controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, null_key, 0x10) != 0) + { + convertDataToHexString(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, 0x10, dataStr, 100); + + sprintf(tmp, " \n" \ + " 0x%016lx\n" \ + " %s\n" \ + " \n", \ + controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id, \ + dataStr); + + strcat(nacpXml, tmp); + } + } + } + + strcat(nacpXml, " \n"); + } + + sprintf(tmp, " \n" \ + " %s\n" \ + " 0x%016lx\n" \ + " \n", \ + getNacpJitConfigurationFlag(controlNacp.jit_configuration.jit_configuration_flag), \ + controlNacp.jit_configuration.memory_size); + + strcat(nacpXml, tmp); + + strcat(nacpXml, ""); + + *out_nacp_xml = nacpXml; + *out_nacp_xml_size = strlen(nacpXml); + + if (nacpIconCnt) + { + *out_nacp_icons_ctx = nacpIcons; + *out_nacp_icons_ctx_cnt = nacpIconCnt; + } + + success = true; + +out: + if (!success) + { + if (nacpIcons != NULL) free(nacpIcons); + + if (nacpXml != NULL) free(nacpXml); + } + + // Manually free these pointers + // Calling freeRomFsContext() would also close the ncmStorage handle + free(romFsContext.romfs_dir_entries); + romFsContext.romfs_dir_entries = NULL; + + free(romFsContext.romfs_file_entries); + romFsContext.romfs_file_entries = NULL; + + return success; +} + +bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize) +{ + if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !outBuf) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve \"legalinfo.xml\"!", __func__); + return false; + } + + u64 entryOffset = 0; + romfs_file *entry = NULL; + bool found_legalinfo = false, success = false; + + u64 legalInfoXmlSize = 0; + char *legalInfoXml = NULL; + + if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; + + // Look for the legalinfo.xml file + while(entryOffset < romFsContext.romfs_filetable_size) + { + entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); + + if (entry->parent == 0 && entry->nameLen == 13 && !strncasecmp((char*)entry->name, "legalinfo.xml", 13)) + { + found_legalinfo = true; + break; + } + + entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); + } + + if (!found_legalinfo) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"legalinfo.xml\" file in Manual NCA RomFS section!", __func__); + goto out; + } + + // Allocate memory for the legalinfo.xml contents + legalInfoXmlSize = entry->dataSize; + legalInfoXml = calloc(legalInfoXmlSize, sizeof(char)); + if (!legalInfoXml) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"legalinfo.xml\" contents!", __func__); + goto out; + } + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, legalInfoXml, legalInfoXmlSize, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"legalinfo.xml\" from RomFS section in Manual NCA!", __func__); + goto out; + } + + *outBuf = legalInfoXml; + *outBufSize = legalInfoXmlSize; + + success = true; + +out: + if (!success && legalInfoXml != NULL) free(legalInfoXml); + + // Manually free these pointers + // Calling freeRomFsContext() would also close the ncmStorage handle + free(romFsContext.romfs_dir_entries); + romFsContext.romfs_dir_entries = NULL; + + free(romFsContext.romfs_file_entries); + romFsContext.romfs_file_entries = NULL; + + return success; +} diff --git a/old/nca.h b/old/nca.h new file mode 100644 index 0000000..6e43f0b --- /dev/null +++ b/old/nca.h @@ -0,0 +1,761 @@ +#pragma once + +#ifndef __NCA_H__ +#define __NCA_H__ + +#include + +#define NCA3_MAGIC (u32)0x4E434133 // "NCA3" +#define NCA2_MAGIC (u32)0x4E434132 // "NCA2" + +#define NCA_HEADER_LENGTH 0x400 +#define NCA_SECTION_HEADER_LENGTH 0x200 +#define NCA_SECTION_HEADER_CNT 4 +#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT)) + +#define NCA_AES_XTS_SECTOR_SIZE 0x200 + +#define NCA_KEY_AREA_KEY_CNT 4 +#define NCA_KEY_AREA_KEY_SIZE 0x10 +#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE) + +#define NCA_FS_HEADER_PARTITION_PFS0 0x01 +#define NCA_FS_HEADER_FSTYPE_PFS0 0x02 + +#define NCA_FS_HEADER_PARTITION_ROMFS 0x00 +#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03 + +#define NCA_FS_HEADER_CRYPT_NONE 0x01 +#define NCA_FS_HEADER_CRYPT_XTS 0x02 +#define NCA_FS_HEADER_CRYPT_CTR 0x03 +#define NCA_FS_HEADER_CRYPT_BKTR 0x04 + +#define PFS0_MAGIC (u32)0x50465330 // "PFS0" + +#define IVFC_MAGIC (u32)0x49564643 // "IVFC" +#define IVFC_MAX_LEVEL 6 + +#define BKTR_MAGIC (u32)0x424B5452 // "BKTR" + +#define ROMFS_HEADER_SIZE 0x50 +#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF + +#define ROMFS_NONAME_DIRENTRY_SIZE 0x18 +#define ROMFS_NONAME_FILEENTRY_SIZE 0x20 + +#define ROMFS_ENTRY_DIR 1 +#define ROMFS_ENTRY_FILE 2 + +#define META_MAGIC (u32)0x4D455441 // "META" + +#define NPDM_SIGNATURE_SIZE 0x100 +#define NPDM_SIGNATURE_AREA_SIZE 0x200 + +#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator +#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator +#define NSP_PROGRAM_XML_FILENAME_LENGTH 0x31 // NCA ID + ".programinfo.xml" + NULL terminator +#define NSP_NACP_XML_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator +#define NSP_LEGAL_XML_FILENAME_LENGTH 0x2F // NCA ID + ".legalinfo.xml" + NULL terminator +#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator +#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator + +#define ETICKET_ENTRY_SIZE 0x400 +#define ETICKET_TITLEKEY_OFFSET 0x180 +#define ETICKET_RIGHTSID_OFFSET 0x2A0 +#define ETICKET_UNKNOWN_FIELD_SIZE 0x140 +#define ETICKET_DATA_OFFSET 0x140 + +#define ETICKET_CA_CERT_SIZE 0x400 +#define ETICKET_XS_CERT_SIZE 0x300 + +#define ETICKET_TIK_FILE_SIZE (ETICKET_ENTRY_SIZE - 0x140) +#define ETICKET_CERT_FILE_SIZE (ETICKET_CA_CERT_SIZE + ETICKET_XS_CERT_SIZE) + +#define ETICKET_TITLEKEY_COMMON 0 +#define ETICKET_TITLEKEY_PERSONALIZED 1 + +typedef enum { + DUMP_APP_NSP = 0, + DUMP_PATCH_NSP, + DUMP_ADDON_NSP +} nspDumpType; + +typedef struct { + u32 magic; + u32 file_cnt; + u32 str_table_size; + u32 reserved; +} PACKED pfs0_header; + +typedef struct { + u64 file_offset; + u64 file_size; + u32 filename_offset; + u32 reserved; +} PACKED pfs0_file_entry; + +typedef struct { + u32 media_start_offset; + u32 media_end_offset; + u8 _0x8[0x8]; /* Padding. */ +} PACKED nca_section_entry_t; + +typedef struct { + u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */ + u32 block_size; /* In bytes. */ + u32 always_2; + u64 hash_table_offset; /* Normally zero. */ + u64 hash_table_size; + u64 pfs0_offset; + u64 pfs0_size; + u8 _0x48[0xF0]; +} PACKED pfs0_superblock_t; + +typedef struct { + u64 logical_offset; + u64 hash_data_size; + u32 block_size; + u32 reserved; +} PACKED ivfc_level_hdr_t; + +typedef struct { + u32 magic; + u32 id; + u32 master_hash_size; + u32 num_levels; + ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL]; + u8 _0xA0[0x20]; + u8 master_hash[0x20]; +} PACKED ivfc_hdr_t; + +typedef struct { + ivfc_hdr_t ivfc_header; + u8 _0xE0[0x58]; +} PACKED romfs_superblock_t; + +typedef struct { + u64 offset; + u64 size; + u32 magic; /* "BKTR" */ + u32 _0x14; /* Version? */ + u32 num_entries; + u32 _0x1C; /* Reserved? */ +} PACKED bktr_header_t; + +typedef struct { + ivfc_hdr_t ivfc_header; + u8 _0xE0[0x18]; + bktr_header_t relocation_header; + bktr_header_t subsection_header; +} PACKED bktr_superblock_t; + +/* NCA FS header. */ +typedef struct { + u8 _0x0; + u8 _0x1; + u8 partition_type; + u8 fs_type; + u8 crypt_type; + u8 _0x5[0x3]; + union { /* FS-specific superblock. Size = 0x138. */ + pfs0_superblock_t pfs0_superblock; + romfs_superblock_t romfs_superblock; + bktr_superblock_t bktr_superblock; + }; + union { + u8 section_ctr[0x8]; + struct { + u32 section_ctr_low; + u32 section_ctr_high; + }; + }; + u8 _0x148[0xB8]; /* Padding. */ +} PACKED nca_fs_header_t; + +/* Nintendo content archive header. */ +typedef struct { + u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */ + u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */ + u32 magic; + u8 distribution; /* System vs gamecard. */ + u8 content_type; + u8 crypto_type; /* Which keyblob (field 1) */ + u8 kaek_ind; /* Which kaek index? */ + u64 nca_size; /* Entire archive size. */ + u64 title_id; + u8 _0x218[0x4]; /* Padding. */ + union { + u32 sdk_version; /* What SDK was this built with? */ + struct { + u8 sdk_revision; + u8 sdk_micro; + u8 sdk_minor; + u8 sdk_major; + }; + }; + u8 crypto_type2; /* Which keyblob (field 2) */ + u8 fixed_key_generation; + u8 _0x222[0xE]; /* Padding. */ + u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */ + nca_section_entry_t section_entries[4]; /* Section entry metadata. */ + u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */ + u8 nca_keys[4][0x10]; /* Key area (encrypted) */ + u8 _0x340[0xC0]; /* Padding. */ + nca_fs_header_t fs_headers[4]; /* FS section headers. */ +} PACKED nca_header_t; + +typedef struct { + u32 magic; + u32 _0x4; + u32 _0x8; + u8 mmu_flags; + u8 _0xD; + u8 main_thread_prio; + u8 default_cpuid; + u64 _0x10; + u32 process_category; + u32 main_stack_size; + char title_name[0x50]; + u32 aci0_offset; + u32 aci0_size; + u32 acid_offset; + u32 acid_size; +} PACKED npdm_t; + +typedef struct { + u64 title_id; + u32 version; + u8 type; + u8 unk1; + u16 extended_header_size; + u16 content_cnt; + u16 content_meta_cnt; + u8 attr; + u8 unk2[0x03]; + u32 required_dl_sysver; + u8 unk3[0x04]; +} PACKED cnmt_header; + +typedef struct { + u64 patch_tid; /* Patch TID / Application TID */ + u32 min_sysver; /* Minimum system/application version */ + u32 min_appver; /* Minimum application version (only for base applications) */ +} PACKED cnmt_extended_header; + +typedef struct { + u8 hash[0x20]; + u8 nca_id[0x10]; + u8 size[6]; + u8 type; + u8 id_offset; +} PACKED cnmt_content_record; + +typedef struct { + u8 type; + u64 title_id; + u32 version; + u32 required_dl_sysver; + u32 nca_cnt; + u8 digest[SHA256_HASH_SIZE]; + char digest_str[(SHA256_HASH_SIZE * 2) + 1]; + u8 min_keyblob; + u32 min_sysver; + u64 patch_tid; + u32 min_appver; +} cnmt_xml_program_info; + +typedef struct { + u8 type; + u8 nca_id[SHA256_HASH_SIZE / 2]; + char nca_id_str[SHA256_HASH_SIZE + 1]; + u64 size; + u8 hash[SHA256_HASH_SIZE]; + char hash_str[(SHA256_HASH_SIZE * 2) + 1]; + u8 keyblob; + u8 id_offset; + u64 cnt_record_offset; // Relative to the start of the content records section in the CNMT + u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE]; + u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH]; +} cnmt_xml_content_info; + +typedef struct { + u32 nca_index; + u8 *hash_table; + u64 hash_table_offset; // Relative to NCA start + u64 hash_table_size; + u8 block_mod_cnt; + u8 *block_data[2]; + u64 block_offset[2]; // Relative to NCA start + u64 block_size[2]; +} nca_program_mod_data; + +typedef struct { + char filename[100]; + u64 icon_size; + u8 icon_data[0x20000]; +} nacp_icons_ctx; + +typedef struct { + u32 nca_index; + u64 xml_size; + char *xml_data; + u8 nacp_icon_cnt; // Only used with Control NCAs + nacp_icons_ctx *nacp_icons; // Only used with Control NCAs +} xml_record_info; + +typedef struct { + u64 section_offset; // Relative to NCA start + u64 section_size; + u64 hash_table_offset; // Relative to NCA start + u64 hash_block_size; + u32 hash_block_cnt; + u64 pfs0_offset; // Relative to NCA start + u64 pfs0_size; + u64 title_cnmt_offset; // Relative to NCA start + u64 title_cnmt_size; +} nca_cnmt_mod_data; + +typedef struct { + u32 sig_type; + u8 signature[0x100]; + u8 padding[0x3C]; + char sig_issuer[0x40]; + u8 titlekey_block[0x100]; + u8 unk1; + u8 titlekey_type; + u8 unk2[0x03]; + u8 master_key_rev; + u8 unk3[0x0A]; + u64 ticket_id; + u64 device_id; + u8 rights_id[0x10]; + u32 account_id; + u8 unk4[0x0C]; +} PACKED rsa2048_sha256_ticket; + +typedef struct { + bool has_rights_id; + u8 rights_id[0x10]; + char rights_id_str[33]; + char tik_filename[37]; + char cert_filename[38]; + u8 enc_titlekey[0x10]; + u8 dec_titlekey[0x10]; + u8 cert_data[ETICKET_CERT_FILE_SIZE]; + rsa2048_sha256_ticket tik_data; + bool retrieved_tik; + bool missing_tik; +} title_rights_ctx; + +typedef struct { + NcmStorageId storageId; + NcmContentStorage ncmStorage; + NcmContentId ncaId; + u8 idOffset; + Aes128CtrContext aes_ctx; + u64 exefs_offset; // Relative to NCA start + u64 exefs_size; + pfs0_header exefs_header; + u64 exefs_entries_offset; // Relative to NCA start + pfs0_file_entry *exefs_entries; + u64 exefs_str_table_offset; // Relative to NCA start + char *exefs_str_table; + u64 exefs_data_offset; // Relative to NCA start +} exefs_ctx_t; + +typedef struct { + NcmStorageId storageId; + NcmContentStorage ncmStorage; + NcmContentId ncaId; + u8 idOffset; + Aes128CtrContext aes_ctx; + u64 section_offset; // Relative to NCA start + u64 section_size; + u64 romfs_offset; // Relative to NCA start + u64 romfs_size; + u64 romfs_dirtable_offset; // Relative to NCA start + u64 romfs_dirtable_size; + romfs_dir *romfs_dir_entries; + u64 romfs_filetable_offset; // Relative to NCA start + u64 romfs_filetable_size; + romfs_file *romfs_file_entries; + u64 romfs_filedata_offset; // Relative to NCA start +} romfs_ctx_t; + +typedef struct { + u64 virt_offset; + u64 phys_offset; + u32 is_patch; +} PACKED bktr_relocation_entry_t; + +typedef struct { + u32 _0x0; + u32 num_entries; + u64 virtual_offset_end; + bktr_relocation_entry_t entries[0x3FF0 / sizeof(bktr_relocation_entry_t)]; + u8 padding[0x3FF0 % sizeof(bktr_relocation_entry_t)]; +} PACKED bktr_relocation_bucket_t; + +typedef struct { + u32 _0x0; + u32 num_buckets; + u64 total_size; + u64 bucket_virtual_offsets[0x3FF0 / sizeof(u64)]; + bktr_relocation_bucket_t buckets[]; +} PACKED bktr_relocation_block_t; + +typedef struct { + u64 offset; + u32 _0x8; + u32 ctr_val; +} PACKED bktr_subsection_entry_t; + +typedef struct { + u32 _0x0; + u32 num_entries; + u64 physical_offset_end; + bktr_subsection_entry_t entries[0x3FF]; +} PACKED bktr_subsection_bucket_t; + +typedef struct { + u32 _0x0; + u32 num_buckets; + u64 total_size; + u64 bucket_physical_offsets[0x3FF0 / sizeof(u64)]; + bktr_subsection_bucket_t buckets[]; +} PACKED bktr_subsection_block_t; + +typedef struct { + NcmStorageId storageId; + NcmContentStorage ncmStorage; + NcmContentId ncaId; + u8 idOffset; + Aes128CtrContext aes_ctx; + u64 section_offset; // Relative to NCA start + u64 section_size; + bktr_superblock_t superblock; + bktr_relocation_block_t *relocation_block; + bktr_subsection_block_t *subsection_block; + u64 virtual_seek; // Relative to section start + u64 bktr_seek; // Relative to section start (patch BKTR section) + u64 base_seek; // Relative to section start (base application RomFS section) + u64 romfs_offset; // Relative to section start + u64 romfs_size; + u64 romfs_dirtable_offset; // Relative to section start + u64 romfs_dirtable_size; + romfs_dir *romfs_dir_entries; + u64 romfs_filetable_offset; // Relative to section start + u64 romfs_filetable_size; + romfs_file *romfs_file_entries; + u64 romfs_filedata_offset; // Relative to section start +} bktr_ctx_t; + +// Used in HFS0 / ExeFS / RomFS browsers +typedef struct { + u64 size; + char sizeStr[32]; +} browser_entry_size_info; + +typedef struct { + u8 type; // 1 = Dir, 2 = File + u64 offset; // Relative to directory/file table, depending on type + browser_entry_size_info sizeInfo; // Only used if type == 2 +} romfs_browser_entry; + +typedef struct { + char name[0x200]; + char publisher[0x100]; +} Title; + +typedef enum { + Language_AmericanEnglish = 0, + Language_BritishEnglish = 1, + Language_Japanese = 2, + Language_French = 3, + Language_German = 4, + Language_LatinAmericanSpanish = 5, + Language_Spanish = 6, + Language_Italian = 7, + Language_Dutch = 8, + Language_CanadianFrench = 9, + Language_Portuguese = 10, + Language_Russian = 11, + Language_Korean = 12, + Language_TraditionalChinese = 13, + Language_SimplifiedChinese = 14 +} Language; + +typedef enum { + StartupUserAccount_None = 0, + StartupUserAccount_Required = 1, + StartupUserAccount_RequiredWithNetworkServiceAccountAvailable = 2 +} StartupUserAccount; + +/* Introduced as of SDK 6.4.0 */ +typedef enum { + UserAccountSwitchLock_Disable = 0, + UserAccountSwitchLock_Enable = 1 +} UserAccountSwitchLock; + +/* Introduced as of SDK 3.4.0 */ +typedef enum { + AddOnContentRegistrationType_AllOnLaunch = 0, + AddOnContentRegistrationType_OnDemand = 1 +} AddOnContentRegistrationType; + +typedef struct { + u32 AttributeFlag_Demo : 1; + u32 AttributeFlag_RetailInteractiveDisplay : 1; /* Introduced as of SDK 3.4.0 */ +} AttributeFlag; + +typedef struct { + u32 SupportedLanguageFlag_AmericanEnglish : 1; + u32 SupportedLanguageFlag_BritishEnglish : 1; + u32 SupportedLanguageFlag_Japanese : 1; + u32 SupportedLanguageFlag_French : 1; + u32 SupportedLanguageFlag_German : 1; + u32 SupportedLanguageFlag_LatinAmericanSpanish : 1; + u32 SupportedLanguageFlag_Spanish : 1; + u32 SupportedLanguageFlag_Italian : 1; + u32 SupportedLanguageFlag_Dutch : 1; + u32 SupportedLanguageFlag_CanadianFrench : 1; + u32 SupportedLanguageFlag_Portuguese : 1; + u32 SupportedLanguageFlag_Russian : 1; + u32 SupportedLanguageFlag_Korean : 1; + u32 SupportedLanguageFlag_TraditionalChinese : 1; + u32 SupportedLanguageFlag_SimplifiedChinese : 1; +} SupportedLanguageFlag; + +typedef struct { + u32 ParentalControlFlag_FreeCommunication : 1; +} ParentalControlFlag; + +typedef enum { + Screenshot_Allow = 0, + Screenshot_Deny = 1 +} Screenshot; + +typedef enum { + VideoCapture_Disable = 0, + VideoCapture_Manual = 1, + VideoCapture_Enable = 2 +} VideoCapture; + +typedef enum { + DataLossConfirmation_None = 0, + DataLossConfirmation_Required = 1 +} DataLossConfirmation; + +typedef enum { + PlayLogPolicy_All = 0, + PlayLogPolicy_LogOnly = 1, + PlayLogPolicy_None = 2 +} PlayLogPolicy; + +typedef enum { + RatingAgeOrganization_CERO = 0, + RatingAgeOrganization_GRACGCRB = 1, + RatingAgeOrganization_GSRMR = 2, + RatingAgeOrganization_ESRB = 3, + RatingAgeOrganization_ClassInd = 4, + RatingAgeOrganization_USK = 5, + RatingAgeOrganization_PEGI = 6, + RatingAgeOrganization_PEGIPortugal = 7, + RatingAgeOrganization_PEGIBBFC = 8, + RatingAgeOrganization_Russian = 9, + RatingAgeOrganization_ACB = 10, + RatingAgeOrganization_OFLC = 11, + RatingAgeOrganization_IARCGeneric = 12 /* Introduced as of SDK 9.3.1 */ +} RatingAgeOrganization; + +typedef struct { + u8 cero; + u8 gracgcrb; + u8 gsrmr; + u8 esrb; + u8 class_ind; + u8 usk; + u8 pegi; + u8 pegi_portugal; + u8 pegibbfc; + u8 russian; + u8 acb; + u8 oflc; + u8 iarc_generic; + u8 reserved_1[0x13]; +} RatingAge; + +typedef enum { + LogoType_LicensedByNintendo = 0, + LogoType_DistributedByNintendo = 1, /* Removed in SDK 3.5.2 */ + LogoType_Nintendo = 2 +} LogoType; + +typedef enum { + LogoHandling_Auto = 0, + LogoHandling_Manual = 1 +} LogoHandling; + +/* Introduced as of SDK 4.5.0 */ +typedef enum { + RuntimeAddOnContentInstall_Deny = 0, + RuntimeAddOnContentInstall_AllowAppend = 1 +} RuntimeAddOnContentInstall; + +/* Introduced as of SDK 9.3.1 */ +typedef enum { + RuntimeParameterDelivery_Always = 0, + RuntimeParameterDelivery_AlwaysIfUserStateMatched = 1, + RuntimeParameterDelivery_OnRestart = 2 +} RuntimeParameterDelivery; + +/* Introduced as of SDK 3.5.2 */ +typedef enum { + CrashReport_Deny = 0, + CrashReport_Allow = 1 +} CrashReport; + +typedef enum { + Hdcp_None = 0, + Hdcp_Required = 1 +} Hdcp; + +/* Introduced as of SDK 7.6.0 */ +typedef struct { + u8 StartupUserAccountOptionFlag_IsOptional : 1; +} StartupUserAccountOptionFlag; + +/* Introduced as of SDK 5.3.0 */ +typedef enum { + PlayLogQueryCapability_None = 0, + PlayLogQueryCapability_WhiteList = 1, + PlayLogQueryCapability_All = 2 +} PlayLogQueryCapability; + +/* Introduced as of SDK 5.3.0 */ +typedef struct { + u8 RepairFlag_SuppressGameCardAccess : 1; +} RepairFlag; + +/* Introduced as of SDK 6.4.0 */ +typedef struct { + u8 RequiredNetworkServiceLicenseOnLaunchFlag_Common : 1; +} RequiredNetworkServiceLicenseOnLaunchFlag; + +typedef struct { + u64 group_id; + u8 key[0x10]; +} NeighborDetectionGroupConfiguration; + +typedef struct { + NeighborDetectionGroupConfiguration send_group_configuration; + NeighborDetectionGroupConfiguration receivable_group_configurations[0x10]; +} NeighborDetectionClientConfiguration; + +/* Introduced as of SDK 7.6.0 */ +typedef enum { + JitConfigurationFlag_None = 0, + JitConfigurationFlag_Enabled = 1 +} JitConfigurationFlag; + +typedef struct { + u64 jit_configuration_flag; + u64 memory_size; +} JitConfiguration; + +typedef struct { + Title titles[0x10]; + char isbn[0x25]; + u8 startup_user_account; + u8 user_account_switch_lock; /* Old: touch_screen_usage (None, Supported, Required) */ + u8 add_on_content_registration_type; + AttributeFlag attribute_flag; + SupportedLanguageFlag supported_language_flag; + ParentalControlFlag parental_control_flag; + u8 screenshot; + u8 video_capture; + u8 data_loss_confirmation; + u8 play_log_policy; + u64 presence_group_id; + RatingAge rating_ages; + char display_version[0x10]; + u64 add_on_content_base_id; + u64 save_data_owner_id; + u64 user_account_save_data_size; + u64 user_account_save_data_journal_size; + u64 device_save_data_size; + u64 device_save_data_journal_size; + u64 bcat_delivery_cache_storage_size; + char application_error_code_category[0x8]; + u64 local_communication_ids[0x8]; + u8 logo_type; + u8 logo_handling; + u8 runtime_add_on_content_install; + u8 runtime_parameter_delivery; + u8 reserved_1[0x2]; + u8 crash_report; + u8 hdcp; + u64 seed_for_pseudo_device_id; + char bcat_passphrase[0x41]; + StartupUserAccountOptionFlag startup_user_account_option; + u8 reserved_2[0x6]; + u64 user_account_save_data_size_max; + u64 user_account_save_data_journal_size_max; + u64 device_save_data_size_max; + u64 device_save_data_journal_size_max; + u64 temporary_storage_size; + u64 cache_storage_size; + u64 cache_storage_journal_size; + u64 cache_storage_data_and_journal_size_max; + u16 cache_storage_index_max; + u8 reserved_3[0x6]; + u64 play_log_queryable_application_ids[0x10]; + u8 play_log_query_capability; + RepairFlag repair_flag; + u8 program_index; + RequiredNetworkServiceLicenseOnLaunchFlag required_network_service_license_on_launch_flag; + u8 reserved_4[0x4]; + NeighborDetectionClientConfiguration neighbor_detection_client_configuration; + JitConfiguration jit_configuration; + u8 reserved_5[0xC40]; +} nacp_t; + +char *getContentType(u8 type); + +void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out); + +void convertNcaSizeToU64(const u8 size[0x6], u64 *out); + +void convertU64ToNcaSize(const u64 size, u8 out[0x6]); + +bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize); + +bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt); + +bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize); + +bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize); + +bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData); + +bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys); + +bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx); + +bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info); + +bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod); + +bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); + +bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); + +bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); + +bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, bool useCustomAcidRsaPubKey, char **outBuf, u64 *outBufSize); + +bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt); + +bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize); + +#endif diff --git a/source/nso.c b/old/nso.c similarity index 100% rename from source/nso.c rename to old/nso.c diff --git a/source/nso.h b/old/nso.h similarity index 100% rename from source/nso.h rename to old/nso.h diff --git a/source/ui.c b/old/ui.c similarity index 100% rename from source/ui.c rename to old/ui.c diff --git a/source/ui.h b/old/ui.h similarity index 100% rename from source/ui.h rename to old/ui.h diff --git a/source/util.c b/old/util.c similarity index 100% rename from source/util.c rename to old/util.c diff --git a/source/util.h b/old/util.h similarity index 92% rename from source/util.h rename to old/util.h index 08d9122..850d7bf 100644 --- a/source/util.h +++ b/old/util.h @@ -19,63 +19,6 @@ - - - - -#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) - -#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) - -#define SLEEP(x) svcSleepThread((x) * (u64)1000000000) - -static Mutex g_logfileMutex = 0; - -void utilsWriteLogMessage(const char *func_name, const char *fmt, ...) -{ - mutexLock(&g_logfileMutex); - - va_list args; - FILE *logfile = NULL; - - logfile = fopen(APP_BASE_PATH "log.txt", "a+"); - if (!logfile) return; - - time_t now = time(NULL); - struct tm *ts = localtime(&now); - - fprintf(logfile, "%d-%d-%d %d:%d:%d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name); - - va_start(args, fmt); - vfprintf(logfile, fmt, args); - va_end(args); - - fprintf(logfile, "\r\n"); - - fclose(logfile); - - mutexUnlock(&g_logfileMutex); -} - -typedef enum { - UtilsCustomFirmwareType_Atmosphere = 0, - UtilsCustomFirmwareType_SXOS = 1, - UtilsCustomFirmwareType_ReiNX = 2 -} UtilsCustomFirmwareType; - - -typedef struct { - u16 major : 6; - u16 minor : 6; - u16 micro : 4; - u16 bugfix; -} TitleVersion; - - - - - - #define CONFIG_PATH APP_BASE_PATH "config.bin" #define NRO_NAME APP_TITLE ".nro" #define NRO_PATH APP_BASE_PATH NRO_NAME diff --git a/source/new/cert.c b/source/cert.c similarity index 97% rename from source/new/cert.c rename to source/cert.c index b09e4f5..751faf0 100644 --- a/source/new/cert.c +++ b/source/cert.c @@ -9,7 +9,9 @@ #define CERT_SAVEFILE_PATH "sys:/save/80000000000000e0" #define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/" -#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig_PubKeyRsa4096 : (pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig_PubKeyRsa2048 : CertType_Sig##sig_PubKeyEcsda240)) +#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : (pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig##_PubKeyRsa2048 : CertType_Sig##sig##_PubKeyEcsda240)) + +/* Function prototypes. */ static u8 certGetCertificateType(const void *data, u64 data_size); static u32 certGetCertificateCountInSignatureIssuer(const char *issuer); diff --git a/source/new/cert.h b/source/cert.h similarity index 99% rename from source/new/cert.h rename to source/cert.h index 6206c47..64b0824 100644 --- a/source/new/cert.h +++ b/source/cert.h @@ -3,6 +3,7 @@ #ifndef __CERT_H__ #define __CERT_H__ +#include #include "signature.h" #define CERT_MAX_SIZE 0x500 /* Equivalent to sizeof(CertSigRsa4096PubKeyRsa4096) */ diff --git a/source/new/crc32_fast.c b/source/crc32_fast.c similarity index 100% rename from source/new/crc32_fast.c rename to source/crc32_fast.c diff --git a/source/new/crc32_fast.h b/source/crc32_fast.h similarity index 100% rename from source/new/crc32_fast.h rename to source/crc32_fast.h diff --git a/source/new/es.c b/source/es.c similarity index 97% rename from source/new/es.c rename to source/es.c index 75d2aff..397437d 100644 --- a/source/new/es.c +++ b/source/es.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -34,7 +33,7 @@ Result esCountPersonalizedTicket(s32 *out_count) return rc; } -Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count); +Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 count) { struct { s32 num_rights_ids_written; diff --git a/source/new/es.h b/source/es.h similarity index 100% rename from source/new/es.h rename to source/es.h diff --git a/source/new/fatfs/LICENSE.txt b/source/fatfs/LICENSE.txt similarity index 100% rename from source/new/fatfs/LICENSE.txt rename to source/fatfs/LICENSE.txt diff --git a/source/new/fatfs/diskio.c b/source/fatfs/diskio.c similarity index 99% rename from source/new/fatfs/diskio.c rename to source/fatfs/diskio.c index a5c5528..a10270e 100644 --- a/source/new/fatfs/diskio.c +++ b/source/fatfs/diskio.c @@ -12,7 +12,7 @@ #include -#include "utils.h" +#include "../utils.h" /*-----------------------------------------------------------------------*/ /* Get Drive Status */ diff --git a/source/new/fatfs/diskio.h b/source/fatfs/diskio.h similarity index 100% rename from source/new/fatfs/diskio.h rename to source/fatfs/diskio.h diff --git a/source/new/fatfs/ff.c b/source/fatfs/ff.c similarity index 100% rename from source/new/fatfs/ff.c rename to source/fatfs/ff.c diff --git a/source/new/fatfs/ff.h b/source/fatfs/ff.h similarity index 100% rename from source/new/fatfs/ff.h rename to source/fatfs/ff.h diff --git a/source/new/fatfs/ffconf.h b/source/fatfs/ffconf.h similarity index 100% rename from source/new/fatfs/ffconf.h rename to source/fatfs/ffconf.h diff --git a/source/new/fatfs/ffsystem.c b/source/fatfs/ffsystem.c similarity index 100% rename from source/new/fatfs/ffsystem.c rename to source/fatfs/ffsystem.c diff --git a/source/new/fatfs/ffunicode.c b/source/fatfs/ffunicode.c similarity index 100% rename from source/new/fatfs/ffunicode.c rename to source/fatfs/ffunicode.c diff --git a/source/new/fs_ext.c b/source/fs_ext.c similarity index 100% rename from source/new/fs_ext.c rename to source/fs_ext.c diff --git a/source/new/fs_ext.h b/source/fs_ext.h similarity index 100% rename from source/new/fs_ext.h rename to source/fs_ext.h diff --git a/source/fspusb.c b/source/fspusb.c new file mode 100644 index 0000000..05544e4 --- /dev/null +++ b/source/fspusb.c @@ -0,0 +1,55 @@ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "fspusb.h" +#include "service_guard.h" + +#include + +static Service g_fspusbSrv; + +NX_GENERATE_SERVICE_GUARD(fspusb); + +Result _fspusbInitialize(void) { + return smGetService(&g_fspusbSrv, "fsp-usb"); +} + +void _fspusbCleanup(void) { + serviceClose(&g_fspusbSrv); +} + +Service* fspusbGetServiceSession(void) { + return &g_fspusbSrv; +} + +Result fspusbListMountedDrives(s32 *drives_buf, size_t drive_count, s32 *out_total) { + return serviceDispatchOut(&g_fspusbSrv, 0, *out_total, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias }, + .buffers = { { drives_buf, drive_count * sizeof(s32) } }, + ); +} + +Result fspusbGetDriveFileSystemType(s32 interface_id, FspUsbFileSystemType *out_type) { + return serviceDispatchInOut(&g_fspusbSrv, 1, interface_id, *out_type); +} + +Result fspusbGetDriveLabel(s32 interface_id, char *out_label, size_t out_label_size) { + return serviceDispatchIn(&g_fspusbSrv, 2, interface_id, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias }, + .buffers = { { out_label, out_label_size } }, + ); +} + +Result fspusbSetDriveLabel(s32 interface_id, const char *label) { + char inputlbl[11 + 1] = {0}; // Actual limit is 11 characters + strncpy(inputlbl, label, 11); + return serviceDispatchIn(&g_fspusbSrv, 3, interface_id, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias }, + .buffers = { { inputlbl, 11 + 1 } }, + ); +} + +Result fspusbOpenDriveFileSystem(s32 interface_id, FsFileSystem *out_fs) { + return serviceDispatchIn(&g_fspusbSrv, 4, interface_id, + .out_num_objects = 1, + .out_objects = &out_fs->s, + ); +} \ No newline at end of file diff --git a/source/fspusb.h b/source/fspusb.h new file mode 100644 index 0000000..efcd982 --- /dev/null +++ b/source/fspusb.h @@ -0,0 +1,37 @@ +/** + * @file fspusb.h + * @brief USB filesystem extension (fsp-usb) service IPC wrapper. + * @author XorTroll + * @copyright libnx Authors + */ +#pragma once + +#ifndef __FSPUSB_H__ +#define __FSPUSB_H__ + +#include + +/// This is basically FATFS' file system types. +typedef enum { + FspUsbFileSystemType_FAT12 = 1, + FspUsbFileSystemType_FAT16 = 2, + FspUsbFileSystemType_FAT32 = 3, + FspUsbFileSystemType_exFAT = 4, +} FspUsbFileSystemType; + +/// Initialize fsp-usb. +Result fspusbInitialize(void); + +/// Exit fsp-usb. +void fspusbExit(void); + +/// Gets the Service object for the actual fsp-usb service session. +Service* fspusbGetServiceSession(void); + +Result fspusbListMountedDrives(s32 *drives_buf, size_t drive_count, s32 *out_total); +Result fspusbGetDriveFileSystemType(s32 interface_id, FspUsbFileSystemType *out_type); +Result fspusbGetDriveLabel(s32 interface_id, char *out_label, size_t out_label_size); +Result fspusbSetDriveLabel(s32 interface_id, const char *label); +Result fspusbOpenDriveFileSystem(s32 interface_id, FsFileSystem *out_fs); + +#endif /* __FSPUSB_H__ */ diff --git a/source/new/gamecard.c b/source/gamecard.c similarity index 75% rename from source/new/gamecard.c rename to source/gamecard.c index 973c5a6..d476191 100644 --- a/source/new/gamecard.c +++ b/source/gamecard.c @@ -16,12 +16,24 @@ #define GAMECARD_ECC_BLOCK_SIZE 0x200 #define GAMECARD_ECC_DATA_SIZE 0x24 +#define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none")) + +/* Type definitions. */ + +typedef enum { + GameCardStorageArea_None = 0, + GameCardStorageArea_Normal = 1, + GameCardStorageArea_Secure = 2 +} GameCardStorageArea; + typedef struct { u64 offset; ///< Relative to the start of the gamecard header. u64 size; ///< Whole partition size. u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table. } GameCardHashFileSystemPartitionInfo; +/* Global variables. */ + static FsDeviceOperator g_deviceOperator = {0}; static FsEventNotifier g_gameCardEventNotifier = {0}; static Event g_gameCardKernelEvent = {0}; @@ -33,20 +45,23 @@ static mtx_t g_gameCardSharedDataMutex; static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false; static FsGameCardHandle g_gameCardHandle = {0}; -static FsStorage g_gameCardStorageNormal = {0}, g_gameCardStorageSecure = {0}; +static FsStorage g_gameCardStorage = {0}; +static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None; static u8 *g_gameCardReadBuf = NULL; static GameCardHeader g_gameCardHeader = {0}; static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0; -static u8 *g_gameCardHfsRootHeader = NULL; /* GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table */ +static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table. static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL; +/* Function prototypes. */ + static bool gamecardCreateDetectionThread(void); static void gamecardDestroyDetectionThread(void); static int gamecardDetectionThreadFunc(void *arg); -static inline bool gamecardCheckIfInserted(void); +static inline bool gamecardIsInserted(void); static void gamecardLoadInfo(void); static void gamecardFreeInfo(void); @@ -54,16 +69,16 @@ static void gamecardFreeInfo(void); static bool gamecardGetHandle(void); static inline void gamecardCloseHandle(void); -static bool gamecardOpenStorageAreas(void); -static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock); -static void gamecardCloseStorageAreas(void); +static bool gamecardOpenStorageArea(u8 area); +static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lock); +static void gamecardCloseStorageArea(void); static bool gamecardGetSizesFromStorageAreas(void); /* Service guard used to generate thread-safe initialize + exit functions */ NX_GENERATE_SERVICE_GUARD(gamecard); -bool gamecardCheckReadyStatus(void) +bool gamecardIsReady(void) { mtx_lock(&g_gameCardSharedDataMutex); bool status = (g_gameCardInserted && g_gameCardInfoLoaded); @@ -71,9 +86,9 @@ bool gamecardCheckReadyStatus(void) return status; } -bool gamecardStorageRead(void *out, u64 out_size, u64 offset) +bool gamecardRead(void *out, u64 out_size, u64 offset) { - return _gamecardStorageRead(out, out_size, offset, true); + return gamecardReadStorageArea(out, out_size, offset, true); } bool gamecardGetHeader(GameCardHeader *out) @@ -321,10 +336,10 @@ static int gamecardDetectionThreadFunc(void *arg) mtx_lock(&g_gameCardSharedDataMutex); /* Retrieve initial gamecard insertion status */ - g_gameCardInserted = prev_status = gamecardCheckIfInserted(); + g_gameCardInserted = prev_status = gamecardIsInserted(); - /* Load gamecard info right away if a gamecard is inserted and if a handle can be retrieved */ - if (g_gameCardInserted && gamecardGetHandle()) gamecardLoadInfo(); + /* Load gamecard info right away if a gamecard is inserted */ + if (g_gameCardInserted) gamecardLoadInfo(); mtx_unlock(&g_gameCardSharedDataMutex); @@ -341,19 +356,18 @@ static int gamecardDetectionThreadFunc(void *arg) /* Only proceed if we're dealing with a status change */ mtx_lock(&g_gameCardSharedDataMutex); - g_gameCardInserted = gamecardCheckIfInserted(); + g_gameCardInserted = gamecardIsInserted(); if (!prev_status && g_gameCardInserted) { /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */ SLEEP(GAMECARD_ACCESS_WAIT_TIME); - /* Load gamecard info if a gamecard is inserted and if a handle can be retrieved */ - if (gamecardGetHandle()) gamecardLoadInfo(); + /* Load gamecard info */ + gamecardLoadInfo(); } else { - /* Free gamecard info and close gamecard handle */ + /* Free gamecard info */ gamecardFreeInfo(); - gamecardCloseHandle(); } prev_status = g_gameCardInserted; @@ -364,14 +378,13 @@ static int gamecardDetectionThreadFunc(void *arg) /* Free gamecard info and close gamecard handle */ mtx_lock(&g_gameCardSharedDataMutex); gamecardFreeInfo(); - gamecardCloseHandle(); g_gameCardInserted = false; mtx_unlock(&g_gameCardSharedDataMutex); return 0; } -static inline bool gamecardCheckIfInserted(void) +static inline bool gamecardIsInserted(void) { bool inserted = false; Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted); @@ -386,15 +399,16 @@ static void gamecardLoadInfo(void) GameCardHashFileSystemHeader *fs_header = NULL; GameCardHashFileSystemEntry *fs_entry = NULL; - /* Open gamecard storage areas */ - if (!gamecardOpenStorageAreas()) + /* Retrieve gamecard storage area sizes */ + /* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must first perform this step */ + if (!gamecardGetSizesFromStorageAreas()) { - LOGFILE("Failed to open gamecard storage areas!"); + LOGFILE("Failed to retrieve gamecard storage area sizes!"); goto out; } /* Read gamecard header */ - if (!_gamecardStorageRead(&g_gameCardHeader, sizeof(GameCardHeader), 0, false)) + if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0, false)) { LOGFILE("Failed to read gamecard header!"); goto out; @@ -409,9 +423,9 @@ static void gamecardLoadInfo(void) if (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_SXOS) { - /* Total size for the secure storage area is maxed out under SX OS */ + /* The total size for the secure storage area is maxed out under SX OS */ /* Let's try to calculate it manually */ - u64 capacity = gamecardGetCapacity(&g_gameCardHeader); + u64 capacity = gamecardGetCapacityFromHeader(&g_gameCardHeader); if (!capacity) { LOGFILE("Invalid gamecard capacity value! (0x%02X)", g_gameCardHeader.rom_size); @@ -430,7 +444,7 @@ static void gamecardLoadInfo(void) } /* Read root hash FS header */ - if (!_gamecardStorageRead(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false)) + if (!gamecardReadStorageArea(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false)) { LOGFILE("Failed to read root hash FS header from offset 0x%lX!", g_gameCardHeader.partition_fs_header_address); goto out; @@ -475,7 +489,7 @@ static void gamecardLoadInfo(void) /* Partially read the current hash FS partition header */ GameCardHashFileSystemHeader partition_header = {0}; - if (!_gamecardStorageRead(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false)) + if (!gamecardReadStorageArea(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false)) { LOGFILE("Failed to partially read hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset); goto out; @@ -505,7 +519,7 @@ static void gamecardLoadInfo(void) } /* Finally, read the full hash FS partition header */ - if (!_gamecardStorageRead(g_gameCardHfsPartitions[i].header, partition_header_size, g_gameCardHfsPartitions[i].offset, false)) + if (!gamecardReadStorageArea(g_gameCardHfsPartitions[i].header, partition_header_size, g_gameCardHfsPartitions[i].offset, false)) { LOGFILE("Failed to read full hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset); goto out; @@ -521,6 +535,8 @@ out: static void gamecardFreeInfo(void) { memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); + g_gameCardStorageNormalAreaSize = 0; + g_gameCardStorageSecureAreaSize = 0; if (g_gameCardHfsRootHeader) { @@ -544,7 +560,7 @@ static void gamecardFreeInfo(void) g_gameCardHfsPartitions = NULL; } - gamecardCloseStorageAreas(); + gamecardCloseStorageArea(); g_gameCardInfoLoaded = false; } @@ -557,8 +573,6 @@ static bool gamecardGetHandle(void) return false; } - if (g_gameCardInfoLoaded && g_gameCardHandle.value) return true; - Result rc1 = 0, rc2 = 0; FsStorage tmp_storage = {0}; @@ -598,58 +612,50 @@ static inline void gamecardCloseHandle(void) g_gameCardHandle.value = 0; } -static bool gamecardOpenStorageAreas(void) +static bool gamecardOpenStorageArea(u8 area) { - if (!g_gameCardInserted || !g_gameCardHandle.value) + if (!g_gameCardInserted || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure)) { LOGFILE("Invalid parameters!"); return false; } - if (g_gamecardInfoLoaded && serviceIsActive(&(g_gameCardStorageNormal.s)) && serviceIsActive(&(g_gameCardStorageSecure.s))) return true; + if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardStorageCurrentArea == area) return true; - gamecardCloseStorageAreas(); + gamecardCloseStorageArea(); Result rc = 0; - bool success = false; + u32 partition = (area - 1); /* Zero-based index */ - rc = fsOpenGameCardStorage(&g_gameCardStorageNormal, &g_gameCardHandle, 0); + /* Retrieve a new gamecard handle */ + if (!gamecardGetHandle()) + { + LOGFILE("Failed to retrieve gamecard handle!"); + return false; + } + + /* Open storage area */ + rc = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition); if (R_FAILED(rc)) { - LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (normal)", rc); - goto out; + LOGFILE("fsOpenGameCardStorage failed to open %s storage area! (0x%08X)", GAMECARD_STORAGE_AREA_NAME(area), rc); + gamecardCloseHandle(); + return false; } - rc = fsOpenGameCardStorage(&g_gameCardStorageSecure, &g_gameCardHandle, 1); - if (R_FAILED(rc)) - { - LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (secure)", rc); - goto out; - } + g_gameCardStorageCurrentArea = area; - if (!gamecardGetSizesFromStorageAreas()) - { - LOGFILE("Failed to retrieve sizes from storage areas!"); - goto out; - } - - success = true; - -out: - if (!success) gamecardCloseStorageAreas(); - - return success; + return true; } -static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock) +static bool gamecardReadStorageArea(void *out, u64 out_size, u64 offset, bool lock) { if (lock) mtx_lock(&g_gameCardSharedDataMutex); bool success = false; - if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !g_gameCardStorageNormalAreaSize || !serviceIsActive(&(g_gameCardStorageSecure.s)) || \ - !g_gameCardStorageSecureAreaSize || !out || !out_size || offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || \ - (offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) + if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !out_size || \ + offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) { LOGFILE("Invalid parameters!"); goto out; @@ -657,33 +663,41 @@ static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock) Result rc = 0; u8 *out_u8 = (u8*)out; + u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); - /* Handle reads between the end of the normal storage area and the start of the secure storage area */ - if (offset < g_gameCardStorageNormalAreaSize && (offset + out_size) > g_gameCardStorageNormalAreaSize) + /* Handle reads that span both the normal and secure gamecard storage areas */ + if (area == GameCardStorageArea_Normal && (offset + out_size) > g_gameCardStorageNormalAreaSize) { /* Calculate normal storage area size difference */ u64 diff_size = (g_gameCardStorageNormalAreaSize - offset); - if (!_gamecardStorageRead(out_u8, diff_size, offset, false)) goto out; + if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto out; - /* Adjust variables to start reading right from the start of the secure storage area */ - out_u8 += diff_size; - offset = g_gameCardStorageNormalAreaSize; + /* Adjust variables to read right from the start of the secure storage area */ out_size -= diff_size; + offset = g_gameCardStorageNormalAreaSize; + out_u8 += diff_size; + area = GameCardStorageArea_Secure; + } + + /* Open a storage area if needed */ + /* If the right storage area has already been opened, this will return true */ + if (!gamecardOpenStorageArea(area)) + { + LOGFILE("Failed to open %s storage area!", GAMECARD_STORAGE_AREA_NAME(area)); + goto out; } /* Calculate appropiate storage area offset and retrieve the right storage area pointer */ - const char *area = (offset < g_gameCardStorageNormalAreaSize ? "normal" : "secure"); - u64 base_offset = (offset < g_gameCardStorageNormalAreaSize ? offset : (offset - g_gameCardStorageNormalAreaSize)); - FsStorage *storage = (offset < g_gameCardStorageNormalAreaSize ? &g_gameCardStorageNormal : &g_gameCardStorageSecure); + u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardStorageNormalAreaSize)); if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(out_size % GAMECARD_MEDIA_UNIT_SIZE)) { /* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */ - rc = fsStorageRead(storage, base_offset, out_u8, out_size); + rc = fsStorageRead(&g_gameCardStorage, base_offset, out_u8, out_size); if (R_FAILED(rc)) { - LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, area, rc); + LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, GAMECARD_STORAGE_AREA_NAME(area), rc); goto out; } @@ -691,22 +705,22 @@ static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock) } else { /* Fix offset and/or size to avoid unaligned reads */ u64 block_start_offset = (base_offset - (base_offset % GAMECARD_MEDIA_UNIT_SIZE)); - u64 block_end_offset = round_up(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE); + u64 block_end_offset = ROUND_UP(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE); u64 block_size = (block_end_offset - block_start_offset); u64 chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size); u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : out_size); - rc = fsStorageRead(storage, block_start_offset, g_gameCardReadBuf, chunk_size); - if (!R_FAILED(rc)) + rc = fsStorageRead(&g_gameCardStorage, block_start_offset, g_gameCardReadBuf, chunk_size); + if (R_FAILED(rc)) { - LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, area, rc); + LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, GAMECARD_STORAGE_AREA_NAME(area), rc); goto out; } memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size); - success = (block_size > GAMECARD_READ_BUFFER_SIZE ? _gamecardStorageRead(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true); + success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true); } out: @@ -715,48 +729,56 @@ out: return success; } -static void gamecardCloseStorageAreas(void) +static void gamecardCloseStorageArea(void) { - if (serviceIsActive(&(g_gameCardStorageNormal.s))) + if (serviceIsActive(&(g_gameCardStorage.s))) { - fsStorageClose(&g_gameCardStorageNormal); - memset(&g_gameCardStorageNormal, 0, sizeof(FsStorage)); + fsStorageClose(&g_gameCardStorage); + memset(&g_gameCardStorage, 0, sizeof(FsStorage)); } - g_gameCardStorageNormalAreaSize = 0; + gamecardCloseHandle(); - if (serviceIsActive(&(g_gameCardStorageSecure.s))) - { - fsStorageClose(&g_gameCardStorageSecure); - memset(&g_gameCardStorageSecure, 0, sizeof(FsStorage)); - } - - g_gameCardStorageSecureAreaSize = 0; + g_gameCardStorageCurrentArea = GameCardStorageArea_None; } static bool gamecardGetSizesFromStorageAreas(void) { - if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !serviceIsActive(&(g_gameCardStorageSecure.s))) + if (!g_gameCardInserted) { - LOGFILE("Invalid parameters!"); + LOGFILE("Gamecard not inserted!"); return false; } - Result rc = 0; - - rc = fsStorageGetSize(&g_gameCardStorageNormal, (s64*)&g_gameCardStorageNormalAreaSize); - if (R_FAILED(rc)) + for(u8 i = 0; i < 2; i++) { - LOGFILE("fsStorageGetSize failed! (0x%08X) (normal)", rc); - return false; - } - - rc = fsStorageGetSize(&g_gameCardStorageSecure, (s64*)&g_gameCardStorageSecureAreaSize); - if (R_FAILED(rc)) - { - LOGFILE("fsStorageGetSize failed! (0x%08X) (secure)", rc); - g_gameCardStorageNormalAreaSize = 0; - return false; + Result rc = 0; + u64 area_size = 0; + u8 area = (i == 0 ? GameCardStorageArea_Normal : GameCardStorageArea_Secure); + + if (!gamecardOpenStorageArea(area)) + { + LOGFILE("Failed to open %s storage area!", GAMECARD_STORAGE_AREA_NAME(area)); + return false; + } + + rc = fsStorageGetSize(&g_gameCardStorage, (s64*)&area_size); + + gamecardCloseStorageArea(); + + if (R_FAILED(rc)) + { + LOGFILE("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X)", GAMECARD_STORAGE_AREA_NAME(area), rc); + g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = 0; + return false; + } + + if (area == GameCardStorageArea_Normal) + { + g_gameCardStorageNormalAreaSize = area_size; + } else { + g_gameCardStorageSecureAreaSize = area_size; + } } return true; diff --git a/source/new/gamecard.h b/source/gamecard.h similarity index 92% rename from source/new/gamecard.h rename to source/gamecard.h index 1e761f5..300fc37 100644 --- a/source/new/gamecard.h +++ b/source/gamecard.h @@ -124,12 +124,12 @@ Result gamecardInitialize(void); void gamecardExit(void); /// Used to check if a gamecard has been inserted and if info could be loaded from it. -bool gamecardCheckReadyStatus(void); +bool gamecardIsReady(void); /// Used to read data from the inserted gamecard. -/// All required handles are managed internally. +/// All required handles, changes between normal <-> secure storage areas and proper offset calculations are managed internally. /// offset + out_size should never exceed the value returned by gamecardGetTotalRomSize(). -bool gamecardStorageRead(void *out, u64 out_size, u64 offset); +bool gamecardRead(void *out, u64 out_size, u64 offset); /// Miscellaneous functions. bool gamecardGetHeader(GameCardHeader *out); @@ -138,13 +138,11 @@ bool gamecardGetTrimmedRomSize(u64 *out); bool gamecardGetCertificate(FsGameCardCertificate *out); bool gamecardGetBundledFirmwareUpdateVersion(u32 *out); -static inline u64 gamecardGetCapacity(GameCardHeader *header) +static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size) { - if (!header) return 0; - u64 capacity = 0; - switch(header->rom_size) + switch(rom_size) { case GameCardRomSize_1GB: capacity = (u64)0x40000000; @@ -168,7 +166,13 @@ static inline u64 gamecardGetCapacity(GameCardHeader *header) break; } - return out; + return capacity; +} + +static inline u64 gamecardGetCapacityFromHeader(GameCardHeader *header) +{ + if (!header) return 0; + return gamecardGetCapacityFromRomSizeValue(header->rom_size); } #endif /* __GAMECARD_H__ */ diff --git a/source/new/keys.c b/source/keys.c similarity index 99% rename from source/new/keys.c rename to source/keys.c index 627e587..9921f91 100644 --- a/source/new/keys.c +++ b/source/keys.c @@ -277,7 +277,7 @@ static bool keysRetrieveDebugHandleFromProcessByProgramId(Handle *out, u64 progr return false; } - rc = svcGetProcessList(&num_processes, pids, 300); + rc = svcGetProcessList((s32*)&num_processes, pids, 300); if (R_FAILED(rc)) { LOGFILE("svcGetProcessList failed! (0x%08X)", rc); diff --git a/source/new/keys.h b/source/keys.h similarity index 94% rename from source/new/keys.h rename to source/keys.h index 5a6032c..d81930a 100644 --- a/source/new/keys.h +++ b/source/keys.h @@ -3,6 +3,8 @@ #ifndef __KEYS_H__ #define __KEYS_H__ +#include + #define KEYS_FILE_PATH "sdmc:/switch/prod.keys" /* Location used by Lockpick_RCM */ bool keysLoadNcaKeyset(void); diff --git a/source/new/lz4.c b/source/lz4.c similarity index 100% rename from source/new/lz4.c rename to source/lz4.c diff --git a/source/new/lz4.h b/source/lz4.h similarity index 100% rename from source/new/lz4.h rename to source/lz4.h diff --git a/source/main.c b/source/main.c index b907320..4a21345 100644 --- a/source/main.c +++ b/source/main.c @@ -3,175 +3,72 @@ #include #include -#include "ui.h" -#include "util.h" +//#include "lvgl_helper.h" +#include "utils.h" +#include "gamecard.h" int main(int argc, char *argv[]) { - int ret = 0; - bool exitMainLoop = false; + (void)argc; + (void)argv; - /* Initialize application resources */ - if (!initApplicationResources(argc, argv)) + int ret = 0; + + LOGFILE("nxdumptool starting."); + + if (!utilsInitializeResources()) { ret = -1; goto out; } - /* Main application loop */ + /*lv_test(); + while(appletMainLoop()) { - UIResult result = uiProcess(); - switch(result) - { - case resultShowMainMenu: - uiSetState(stateMainMenu); - break; - case resultShowGameCardMenu: - uiSetState(stateGameCardMenu); - break; - case resultShowXciDumpMenu: - uiSetState(stateXciDumpMenu); - break; - case resultDumpXci: - uiSetState(stateDumpXci); - break; - case resultShowNspDumpMenu: - uiSetState(stateNspDumpMenu); - break; - case resultShowNspAppDumpMenu: - uiSetState(stateNspAppDumpMenu); - break; - case resultShowNspPatchDumpMenu: - uiSetState(stateNspPatchDumpMenu); - break; - case resultShowNspAddOnDumpMenu: - uiSetState(stateNspAddOnDumpMenu); - break; - case resultDumpNsp: - uiSetState(stateDumpNsp); - break; - case resultShowHfs0Menu: - uiSetState(stateHfs0Menu); - break; - case resultShowRawHfs0PartitionDumpMenu: - uiSetState(stateRawHfs0PartitionDumpMenu); - break; - case resultDumpRawHfs0Partition: - uiSetState(stateDumpRawHfs0Partition); - break; - case resultShowHfs0PartitionDataDumpMenu: - uiSetState(stateHfs0PartitionDataDumpMenu); - break; - case resultDumpHfs0PartitionData: - uiSetState(stateDumpHfs0PartitionData); - break; - case resultShowHfs0BrowserMenu: - uiSetState(stateHfs0BrowserMenu); - break; - case resultHfs0BrowserGetList: - uiSetState(stateHfs0BrowserGetList); - break; - case resultShowHfs0Browser: - uiSetState(stateHfs0Browser); - break; - case resultHfs0BrowserCopyFile: - uiSetState(stateHfs0BrowserCopyFile); - break; - case resultShowExeFsMenu: - uiSetState(stateExeFsMenu); - break; - case resultShowExeFsSectionDataDumpMenu: - uiSetState(stateExeFsSectionDataDumpMenu); - break; - case resultDumpExeFsSectionData: - uiSetState(stateDumpExeFsSectionData); - break; - case resultShowExeFsSectionBrowserMenu: - uiSetState(stateExeFsSectionBrowserMenu); - break; - case resultExeFsSectionBrowserGetList: - uiSetState(stateExeFsSectionBrowserGetList); - break; - case resultShowExeFsSectionBrowser: - uiSetState(stateExeFsSectionBrowser); - break; - case resultExeFsSectionBrowserCopyFile: - uiSetState(stateExeFsSectionBrowserCopyFile); - break; - case resultShowRomFsMenu: - uiSetState(stateRomFsMenu); - break; - case resultShowRomFsSectionDataDumpMenu: - uiSetState(stateRomFsSectionDataDumpMenu); - break; - case resultDumpRomFsSectionData: - uiSetState(stateDumpRomFsSectionData); - break; - case resultShowRomFsSectionBrowserMenu: - uiSetState(stateRomFsSectionBrowserMenu); - break; - case resultRomFsSectionBrowserGetEntries: - uiSetState(stateRomFsSectionBrowserGetEntries); - break; - case resultShowRomFsSectionBrowser: - uiSetState(stateRomFsSectionBrowser); - break; - case resultRomFsSectionBrowserChangeDir: - uiSetState(stateRomFsSectionBrowserChangeDir); - break; - case resultRomFsSectionBrowserCopyFile: - uiSetState(stateRomFsSectionBrowserCopyFile); - break; - case resultRomFsSectionBrowserCopyDir: - uiSetState(stateRomFsSectionBrowserCopyDir); - break; - case resultDumpGameCardCertificate: - uiSetState(stateDumpGameCardCertificate); - break; - case resultShowSdCardEmmcMenu: - uiSetState(stateSdCardEmmcMenu); - break; - case resultShowSdCardEmmcTitleMenu: - uiSetState(stateSdCardEmmcTitleMenu); - break; - case resultShowSdCardEmmcOrphanPatchAddOnMenu: - uiSetState(stateSdCardEmmcOrphanPatchAddOnMenu); - break; - case resultShowSdCardEmmcBatchModeMenu: - uiSetState(stateSdCardEmmcBatchModeMenu); - break; - case resultSdCardEmmcBatchDump: - uiSetState(stateSdCardEmmcBatchDump); - break; - case resultShowTicketMenu: - uiSetState(stateTicketMenu); - break; - case resultDumpTicket: - uiSetState(stateDumpTicket); - break; - case resultShowUpdateMenu: - uiSetState(stateUpdateMenu); - break; - case resultUpdateNSWDBXml: - uiSetState(stateUpdateNSWDBXml); - break; - case resultUpdateApplication: - uiSetState(stateUpdateApplication); - break; - case resultExit: - exitMainLoop = true; - break; - default: - break; - } - - if (exitMainLoop) break; + lv_task_handler(); + if (lvglHelperGetExitFlag()) break; + }*/ + + + + + + consoleInit(NULL); + + printf("waiting...\n"); + consoleUpdate(NULL); + + while(appletMainLoop()) + { + if (gamecardIsReady()) break; } + u64 size = 0; + if (!gamecardGetTotalRomSize(&size)) + { + printf("totalromsize failed"); + goto out2; + } + + printf("totalromsize: 0x%lX\n", size); + consoleUpdate(NULL); + + if (!gamecardGetTrimmedRomSize(&size)) + { + printf("trimmedromsize failed"); + goto out2; + } + + printf("trimmedromsize: 0x%lX\n", size); + +out2: + consoleUpdate(NULL); + SLEEP(3); + consoleExit(NULL); + out: - /* Deinitialize application resources */ - deinitApplicationResources(); + utilsCloseResources(); return ret; } diff --git a/source/nca.c b/source/nca.c index 3f82d2c..bf8ee17 100644 --- a/source/nca.c +++ b/source/nca.c @@ -1,3465 +1,361 @@ #include #include #include -#include +#include "nca.h" #include "keys.h" -#include "util.h" -#include "ui.h" #include "rsa.h" -#include "nso.h" +#include "utils.h" -/* Extern variables */ +/* Global variables. */ -extern int breaks; -extern int font_height; +static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { + 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, + 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB +}; -extern exefs_ctx_t exeFsContext; -extern romfs_ctx_t romFsContext; -extern bktr_ctx_t bktrContext; +/* Function prototypes. */ -extern nca_keyset_t nca_keyset; +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); +static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); +static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); -extern u8 *ncaCtrBuf; - -char *getTitleType(u8 type) +size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt) { - char *out = NULL; - - switch(type) + if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0) { - case NcmContentMetaType_Application: - out = "Application"; - break; - case NcmContentMetaType_Patch: - out = "Patch"; - break; - case NcmContentMetaType_AddOnContent: - out = "AddOnContent"; - break; - default: - out = "Unknown"; - break; + LOGFILE("Invalid parameters!"); + return 0; } - return out; -} - -char *getContentType(u8 type) -{ - char *out = NULL; + size_t i, crypt_res = 0; + u64 cur_sector = sector; - switch(type) + u8 *dst_u8 = (u8*)dst; + const u8 *src_u8 = (const u8*)src; + + for(i = 0; i < size; i += sector_size, cur_sector++) { - case NcmContentType_Meta: - out = "Meta"; - break; - case NcmContentType_Program: - out = "Program"; - break; - case NcmContentType_Data: - out = "Data"; - break; - case NcmContentType_Control: - out = "Control"; - break; - case NcmContentType_HtmlDocument: - out = "HtmlDocument"; - break; - case NcmContentType_LegalInformation: - out = "LegalInformation"; - break; - case NcmContentType_DeltaFragment: - out = "DeltaFragment"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getRequiredMinTitleType(u8 type) -{ - char *out = NULL; - - switch(type) - { - case NcmContentMetaType_Application: - case NcmContentMetaType_Patch: - out = "RequiredSystemVersion"; - break; - case NcmContentMetaType_AddOnContent: - out = "RequiredApplicationVersion"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getReferenceTitleIDType(u8 type) -{ - char *out = NULL; - - switch(type) - { - case NcmContentMetaType_Application: - out = "PatchId"; - break; - case NcmContentMetaType_Patch: - out = "OriginalId"; - break; - case NcmContentMetaType_AddOnContent: - out = "ApplicationId"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out) -{ - if (!xml_program_info || !xml_content_info || !xml_program_info->nca_cnt || !out) return; - - u32 i; - char tmp[NAME_BUF_LEN] = {'\0'}; - - sprintf(out, "\n" \ - "\n" \ - " %s\n" \ - " 0x%016lx\n" \ - " %u\n" \ - " %u\n", \ - getTitleType(xml_program_info->type), \ - xml_program_info->title_id, \ - xml_program_info->version, \ - xml_program_info->required_dl_sysver); - - for(i = 0; i < xml_program_info->nca_cnt; i++) - { - sprintf(tmp, " \n" \ - " %s\n" \ - " %s\n" \ - " %lu\n" \ - " %s\n" \ - " %u\n" \ - " %u\n" \ - " \n", - getContentType(xml_content_info[i].type), \ - xml_content_info[i].nca_id_str, \ - xml_content_info[i].size, \ - xml_content_info[i].hash_str, \ - xml_content_info[i].keyblob, \ - xml_content_info[i].id_offset); - - strcat(out, tmp); - } - - sprintf(tmp, " %s\n" \ - " %u\n" \ - " <%s>%u\n" \ - " <%s>0x%016lx\n", \ - xml_program_info->digest_str, \ - xml_program_info->min_keyblob, \ - getRequiredMinTitleType(xml_program_info->type), \ - xml_program_info->min_sysver, \ - getRequiredMinTitleType(xml_program_info->type), \ - getReferenceTitleIDType(xml_program_info->type), \ - xml_program_info->patch_tid, \ - getReferenceTitleIDType(xml_program_info->type)); - - strcat(out, tmp); - - if (xml_program_info->type == NcmContentMetaType_Application) - { - sprintf(tmp, " %u\n", xml_program_info->min_appver); - strcat(out, tmp); - } - - strcat(out, ""); -} - -void convertNcaSizeToU64(const u8 size[0x6], u64 *out) -{ - if (!size || !out) return; - - u64 tmp = 0; - - tmp |= (((u64)size[5] << 40) & (u64)0xFF0000000000); - tmp |= (((u64)size[4] << 32) & (u64)0x00FF00000000); - tmp |= (((u64)size[3] << 24) & (u64)0x0000FF000000); - tmp |= (((u64)size[2] << 16) & (u64)0x000000FF0000); - tmp |= (((u64)size[1] << 8) & (u64)0x00000000FF00); - tmp |= ((u64)size[0] & (u64)0x0000000000FF); - - *out = tmp; -} - -void convertU64ToNcaSize(const u64 size, u8 out[0x6]) -{ - if (!size || !out) return; - - u8 tmp[0x6]; - - tmp[5] = (u8)(size >> 40); - tmp[4] = (u8)(size >> 32); - tmp[3] = (u8)(size >> 24); - tmp[2] = (u8)(size >> 16); - tmp[1] = (u8)(size >> 8); - tmp[0] = (u8)size; - - memcpy(out, tmp, 6); -} - -bool loadNcaKeyset() -{ - // Check if the keyset has been already loaded - if (nca_keyset.total_key_cnt > 0) return true; - - if (!(envIsSyscallHinted(0x60) && // svcDebugActiveProcess - envIsSyscallHinted(0x63) && // svcGetDebugEvent - envIsSyscallHinted(0x65) && // svcGetProcessList - envIsSyscallHinted(0x69) && // svcQueryDebugProcessMemory - envIsSyscallHinted(0x6a))) // svcReadDebugProcessMemory - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: please run the application with debug svc permissions!", __func__); - return false; - } - - return loadMemoryKeys(); -} - -size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u32 sector, bool encrypt) -{ - if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0) return 0; - - size_t i, crypt_res = 0, out = 0; - u32 cur_sector = sector; - - for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++) - { - // We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak + /* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */ aes128XtsContextResetSector(ctx, cur_sector, true); if (encrypt) { - crypt_res = aes128XtsEncrypt(ctx, (u8*)dst + i, (const u8*)src + i, NCA_AES_XTS_SECTOR_SIZE); + crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); } else { - crypt_res = aes128XtsDecrypt(ctx, (u8*)dst + i, (const u8*)src + i, NCA_AES_XTS_SECTOR_SIZE); + crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); } - if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break; - - out += crypt_res; + if (crypt_res != sector_size) break; } - return out; + return i; } -/* Updates the CTR for an offset. */ -static void nca_update_ctr(unsigned char *ctr, u64 ofs) + + + + + + + + + + + + + + + + +bool ncaDecryptKeyArea(NcaContext *ctx) { - ofs >>= 4; - unsigned int i; - - for(i = 0; i < 0x8; i++) + if (!ctx) { - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; + LOGFILE("Invalid NCA context!"); + return false; + } + + Result rc = 0; + const u8 *kek_src = NULL; + u8 key_count, tmp_kek[0x10] = {0}; + + /* Check if we're dealing with a NCA0 with a plain text key area */ + if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) + { + memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40); + return true; + } + + kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index); + if (!kek_src) + { + LOGFILE("Unable to retrieve KAEK source for index 0x%02X!", ctx->header.kaek_index); + return false; + } + + rc = splCryptoGenerateAesKek(kek_src, ctx->key_generation, 0, tmp_kek); + if (R_FAILED(rc)) + { + LOGFILE("splCryptoGenerateAesKek failed! (0x%08X)", rc); + return false; + } + + key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); + + for(u8 i = 0; i < key_count; i++) + { + rc = splCryptoGenerateAesKey(tmp_kek, &(ctx->header.encrypted_keys[i]), &(ctx->decrypted_keys[i])); + if (R_FAILED(rc)) + { + LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc); + return false; + } + } + + return true; +} + +bool ncaEncryptKeyArea(NcaContext *ctx) +{ + if (!ctx) + { + LOGFILE("Invalid NCA context!"); + return false; + } + + u8 key_count; + const u8 *kaek = NULL; + Aes128Context key_area_ctx = {0}; + + /* Check if we're dealing with a NCA0 with a plain text key area */ + if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) + { + memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40); + return true; + } + + kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index); + if (!kaek) + { + LOGFILE("Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X!", ctx->key_generation, ctx->header.kaek_index); + return false; + } + + key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); + + aes128ContextCreate(&key_area_ctx, kaek, true); + for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, &(ctx->header.encrypted_keys[i]), &(ctx->decrypted_keys[i])); + + return true; +} + +bool ncaDecryptHeader(NcaContext *ctx) +{ + if (!ctx) + { + LOGFILE("Invalid NCA context!"); + return false; + } + + u32 i, magic = 0; + size_t crypt_res = 0; + const u8 *header_key = NULL; + Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; + + header_key = keysGetNcaHeaderKey(); + + aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false); + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != NCA_HEADER_LENGTH) + { + LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res); + return false; + } + + magic = __builtin_bswap32(ctx->header.magic); + + switch(magic) + { + case NCA_NCA3_MAGIC: + ctx->format_version = NcaVersion_Nca3; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + LOGFILE("Error decrypting full NCA3 header!"); + return false; + } + + break; + case NCA_NCA2_MAGIC: + ctx->format_version = NcaVersion_Nca2; + + for(i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + if (!ctx->header.fs_entries[i].enable_entry) continue; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != NCA_FS_HEADER_LENGTH) + { + LOGFILE("Error decrypting NCA2 FS section header #%u!", i); + return false; + } + } + + break; + case NCA_NCA0_MAGIC: + ctx->format_version = NcaVersion_Nca0; + + /* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */ + if (!ncaDecryptKeyArea(ctx)) + { + LOGFILE("Error decrypting key area from NCA0 header!"); + return false; + } + + aes128XtsContextCreate(&nca0_fs_header_ctx, &(ctx->decrypted_keys[0]), &(ctx->decrypted_keys[1]), false); + + for(i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + if (!ctx->header.fs_entries[i].enable_entry) continue; + + + + + + + + } + + break; + default: + LOGFILE("Invalid NCA magic word! Wrong header key? (0x%08X)", magic); + return false; + } + + /* Fill additional context info */ + ctx->key_generation = ncaGetKeyGenerationValue(ctx); + ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx); + + return true; +} + +bool ncaEncryptHeader(NcaContext *ctx) +{ + if (!ctx) + { + LOGFILE("Invalid NCA context!"); + return false; + } + + u32 i; + size_t crypt_res = 0; + const u8 *header_key = NULL; + Aes128XtsContext hdr_aes_ctx = {0}; + + header_key = keysGetNcaHeaderKey(); + + aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true); + + switch(ctx->format_version) + { + case NcaVersion_Nca3: + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + LOGFILE("Error encrypting full NCA3 header!"); + return false; + } + + break; + case NcaVersion_Nca2: + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_HEADER_LENGTH) + { + LOGFILE("Error encrypting partial NCA2 header!"); + return false; + } + + for(i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + if (!ctx->header.fs_entries[i].enable_entry) continue; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_FS_HEADER_LENGTH) + { + LOGFILE("Error encrypting NCA2 FS section header #%u!", i); + return false; + } + } + + break; + case NcaVersion_Nca0: + /* There's nothing else to do */ + break; + default: + LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version); + return false; + } + + return true; +} + + + + + + + + + + + + + + + +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) +{ + if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; + + u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0}; + sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40); + + if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false; + + return true; +} + +static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) +{ + if (!ctr) return; + + offset >>= 4; + + for(u8 i = 0; i < 8; i++) + { + ctr[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; } } -/* Updates the CTR for a bktr offset. */ -static void nca_update_bktr_ctr(unsigned char *ctr, u32 ctr_val, u64 ofs) +static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) { - ofs >>= 4; - unsigned int i; + if (!ctr) return; - for(i = 0; i < 0x8; i++) + offset >>= 4; + + for(u8 i = 0; i < 8; i++) { - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; + ctr[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; } - for(i = 0; i < 0x4; i++) + for(u8 i = 0; i < 4; i++) { - ctr[0x8 - i - 1] = (unsigned char)(ctr_val & 0xFF); + ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF); ctr_val >>= 8; } } - -bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize) -{ - if (!ncmStorage || !ncaId || !outBuf || !bufSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read data from NCA!", __func__); - return false; - } - - Result result = 0; - bool success = false; - - char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}, nca_path[0x301] = {'\0'}; - convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); - - result = ncmContentStorageGetPath(ncmStorage, nca_path, MAX_CHARACTERS(nca_path), ncaId); - if (R_FAILED(result) || !strlen(nca_path)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to retrieve content path for NCA \"%s\"! (0x%08X)", __func__, nca_id, result); - return false; - } - - // Check if we're dealing with a gamecard NCA - if (!strncmp(nca_path, "@Gc", 3)) - { - // Retrieve NCA data using raw IStorage reads - // Fixes NCA access problems with gamecards under low HOS versions when using ncmContentStorageReadContentIdFile() - success = readFileFromSecureHfs0PartitionByName(strchr(nca_path, '/') + 1, offset, outBuf, bufSize); - if (!success) breaks++; - } else { - // Retrieve NCA data normally - // This strips NAX0 encryption from SD card NCAs (not used with eMMC NCAs) - result = ncmContentStorageReadContentIdFile(ncmStorage, outBuf, bufSize, ncaId, offset); - success = R_SUCCEEDED(result); - } - - if (!success) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", __func__, bufSize, offset, nca_id, result); - - return success; -} - -bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt) -{ - if (!ncmStorage || !ncaId || !outBuf || !bufSize || !ctx) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process %s NCA section block!", __func__, (encrypt ? "decrypted" : "encrypted")); - return false; - } - - if (!loadNcaKeyset()) return false; - - unsigned char ctr[0x10]; - - char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}; - convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); - - u64 block_start_offset = (offset - (offset % 0x10)); - u64 block_end_offset = (u64)round_up(offset + bufSize, 0x10); - u64 block_size = (block_end_offset - block_start_offset); - - u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); - u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (offset - block_start_offset)) : bufSize); - - if (!readNcaDataByContentId(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted data block from NCA \"%s\"!", __func__, nca_id); - return false; - } - - // Update CTR - memcpy(ctr, ctx->ctr, 0x10); - nca_update_ctr(ctr, block_start_offset); - aes128CtrContextResetCtr(ctx, ctr); - - // Decrypt CTR block - aes128CtrCrypt(ctx, ncaCtrBuf, ncaCtrBuf, block_size_used); - - if (encrypt) - { - // Copy data to be encrypted - memcpy(ncaCtrBuf + (offset - block_start_offset), outBuf, output_block_size); - - // Reset CTR - aes128CtrContextResetCtr(ctx, ctr); - - // Encrypt CTR block - aes128CtrCrypt(ctx, ncaCtrBuf, ncaCtrBuf, block_size_used); - } - - memcpy(outBuf, ncaCtrBuf + (offset - block_start_offset), output_block_size); - - if (block_size > NCA_CTR_BUFFER_SIZE) return processNcaCtrSectionBlock(ncmStorage, ncaId, ctx, offset + output_block_size, outBuf + output_block_size, bufSize - output_block_size, encrypt); - - return true; -} - -bktr_relocation_bucket_t *bktr_get_relocation_bucket(bktr_relocation_block_t *block, u32 i) -{ - return (bktr_relocation_bucket_t*)((u8*)block->buckets + ((sizeof(bktr_relocation_bucket_t) + sizeof(bktr_relocation_entry_t)) * (u64)i)); -} - -// Get a relocation entry from offset and relocation block -bktr_relocation_entry_t *bktr_get_relocation(bktr_relocation_block_t *block, u64 offset) -{ - // Weak check for invalid offset - if (offset > block->total_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: too big offset looked up in BKTR relocation table!", __func__); - return NULL; - } - - u32 i, bucket_num = 0; - - for(i = 1; i < block->num_buckets; i++) - { - if (block->bucket_virtual_offsets[i] <= offset) bucket_num++; - } - - bktr_relocation_bucket_t *bucket = bktr_get_relocation_bucket(block, bucket_num); - - // Check for edge case, short circuit - if (bucket->num_entries == 1) return &(bucket->entries[0]); - - // Binary search - u32 low = 0, high = (bucket->num_entries - 1); - - while(low <= high) - { - u32 mid = ((low + high) / 2); - - if (bucket->entries[mid].virt_offset > offset) - { - // Too high - high = (mid - 1); - } else { - // block->entries[mid].offset <= offset - - // Check for success - if (mid == (bucket->num_entries - 1) || bucket->entries[mid + 1].virt_offset > offset) return &(bucket->entries[mid]); - - low = (mid + 1); - } - } - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR relocation table!", __func__, offset); - return NULL; -} - -bktr_subsection_bucket_t *bktr_get_subsection_bucket(bktr_subsection_block_t *block, u32 i) -{ - return (bktr_subsection_bucket_t*)((u8*)block->buckets + ((sizeof(bktr_subsection_bucket_t) + sizeof(bktr_subsection_entry_t)) * (u64)i)); -} - -// Get a subsection entry from offset and subsection block -bktr_subsection_entry_t *bktr_get_subsection(bktr_subsection_block_t *block, u64 offset) -{ - // If offset is past the virtual, we're reading from the BKTR_HEADER subsection - bktr_subsection_bucket_t *last_bucket = bktr_get_subsection_bucket(block, block->num_buckets - 1); - if (offset >= last_bucket->entries[last_bucket->num_entries].offset) return &(last_bucket->entries[last_bucket->num_entries]); - - u32 i, bucket_num = 0; - - for(i = 1; i < block->num_buckets; i++) - { - if (block->bucket_physical_offsets[i] <= offset) bucket_num++; - } - - bktr_subsection_bucket_t *bucket = bktr_get_subsection_bucket(block, bucket_num); - - // Check for edge case, short circuit - if (bucket->num_entries == 1) return &(bucket->entries[0]); - - // Binary search - u32 low = 0, high = (bucket->num_entries - 1); - - while (low <= high) - { - u32 mid = ((low + high) / 2); - - if (bucket->entries[mid].offset > offset) - { - // Too high - high = (mid - 1); - } else { - // block->entries[mid].offset <= offset - - // Check for success - if (mid == (bucket->num_entries - 1) || bucket->entries[mid + 1].offset > offset) return &(bucket->entries[mid]); - - low = (mid + 1); - } - } - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR subsection table!", __func__, offset); - return NULL; -} - -bool bktrSectionSeek(u64 offset) -{ - if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to seek within NCA BKTR section!", __func__); - return false; - } - - bktr_relocation_entry_t *reloc = bktr_get_relocation(bktrContext.relocation_block, offset); - if (!reloc) return false; - - // No better way to do this than to make all BKTR seeking virtual - bktrContext.virtual_seek = offset; - - u64 section_ofs = (offset - reloc->virt_offset + reloc->phys_offset); - - if (reloc->is_patch) - { - // Seeked within the patch RomFS - bktrContext.bktr_seek = section_ofs; - bktrContext.base_seek = 0; - } else { - // Seeked within the base RomFS - bktrContext.bktr_seek = 0; - bktrContext.base_seek = section_ofs; - } - - return true; -} - -bool bktrSectionPhysicalRead(void *outBuf, size_t bufSize) -{ - if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !outBuf || !bufSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform physical block read from NCA BKTR section!", __func__); - return false; - } - - unsigned char ctr[0x10]; - - bktr_subsection_entry_t *subsec = bktr_get_subsection(bktrContext.subsection_block, bktrContext.bktr_seek); - if (!subsec) return false; - - bktr_subsection_entry_t *next_subsec = (subsec + 1); - - u64 base_offset = (bktrContext.section_offset + bktrContext.bktr_seek); - - u64 virt_seek = bktrContext.virtual_seek; - - if ((bktrContext.bktr_seek + bufSize) <= next_subsec->offset) - { - // Easy path, reading *only* within the subsection - u64 block_start_offset = (base_offset - (base_offset % 0x10)); - u64 block_end_offset = (u64)round_up(base_offset + bufSize, 0x10); - u64 block_size = (block_end_offset - block_start_offset); - - u64 output_offset = 0; - u64 ctr_buf_offset = (base_offset - block_start_offset); - u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (base_offset - block_start_offset)) : bufSize); - - while(block_size > 0) - { - u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); - - if (!readNcaDataByContentId(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted %lu bytes block at offset 0x%016lX!", __func__, block_size_used, block_start_offset); - return false; - } - - // Update BKTR CTR - memcpy(ctr, bktrContext.aes_ctx.ctr, 0x10); - nca_update_bktr_ctr(ctr, subsec->ctr_val, block_start_offset); - aes128CtrContextResetCtr(&(bktrContext.aes_ctx), ctr); - - // Decrypt CTR block - aes128CtrCrypt(&(bktrContext.aes_ctx), ncaCtrBuf, ncaCtrBuf, block_size_used); - memcpy(outBuf + output_offset, ncaCtrBuf + ctr_buf_offset, output_block_size); - - block_start_offset += block_size_used; - block_size -= block_size_used; - - if (block_size) - { - output_offset += output_block_size; - ctr_buf_offset = 0; - output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : ((base_offset + bufSize) - block_start_offset)); - } - } - } else { - // Sad path - u64 within_subsection = (next_subsec->offset - bktrContext.bktr_seek); - - if (!readBktrSectionBlock(virt_seek, outBuf, within_subsection)) return false; - - if (!readBktrSectionBlock(virt_seek + within_subsection, (u8*)outBuf + within_subsection, bufSize - within_subsection)) return false; - } - - return true; -} - -bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize) -{ - if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !romFsContext.section_offset || !romFsContext.section_size || !outBuf || !bufSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read block from NCA BKTR section!", __func__); - return false; - } - - if (!loadNcaKeyset()) return false; - - if (!bktrSectionSeek(offset)) return false; - - bktr_relocation_entry_t *reloc = bktr_get_relocation(bktrContext.relocation_block, bktrContext.virtual_seek); - if (!reloc) return false; - - bktr_relocation_entry_t *next_reloc = (reloc + 1); - - u64 virt_seek = bktrContext.virtual_seek; - - // Perform read operation - if ((bktrContext.virtual_seek + bufSize) <= next_reloc->virt_offset) - { - // Easy path: We're reading *only* within the current relocation - - if (reloc->is_patch) - { - if (!bktrSectionPhysicalRead(outBuf, bufSize)) return false; - } else { - // Nice and easy read from the base RomFS - if (!processNcaCtrSectionBlock(&(romFsContext.ncmStorage), &(romFsContext.ncaId), &(romFsContext.aes_ctx), romFsContext.section_offset + bktrContext.base_seek, outBuf, bufSize, false)) return false; - } - } else { - u64 within_relocation = (next_reloc->virt_offset - bktrContext.virtual_seek); - - if (!readBktrSectionBlock(virt_seek, outBuf, within_relocation)) return false; - - if (!readBktrSectionBlock(virt_seek + within_relocation, (u8*)outBuf + within_relocation, bufSize - within_relocation)) return false; - } - - return true; -} - -bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) -{ - if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (__builtin_bswap32(input->magic) != NCA3_MAGIC && __builtin_bswap32(input->magic) != NCA2_MAGIC)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header encryption parameters.", __func__); - return false; - } - - if (!loadNcaKeyset()) return false; - - u32 i; - size_t crypt_res; - Aes128XtsContext hdr_aes_ctx; - - u8 header_key_0[16]; - u8 header_key_1[16]; - - memcpy(header_key_0, nca_keyset.header_key, 16); - memcpy(header_key_1, nca_keyset.header_key + 16, 16); - - aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, true); - - if (__builtin_bswap32(input->magic) == NCA3_MAGIC) - { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, true); - if (crypt_res != NCA_FULL_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); - return false; - } - } else - if (__builtin_bswap32(input->magic) == NCA2_MAGIC) - { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, true); - if (crypt_res != NCA_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); - return false; - } - - for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) - { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, true); - if (crypt_res != NCA_SECTION_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); - return false; - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decrypted NCA magic word! (0x%08X)", __func__, __builtin_bswap32(input->magic)); - return false; - } - - return true; -} - -bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData) -{ - if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH || !out || !decrypted_nca_keys) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header decryption parameters!", __func__); - return false; - } - - if (!loadNcaKeyset()) return false; - - int ret; - - u32 i; - size_t crypt_res; - Aes128XtsContext hdr_aes_ctx; - - u8 header_key_0[16]; - u8 header_key_1[16]; - - bool has_rights_id = false; - - memcpy(header_key_0, nca_keyset.header_key, 16); - memcpy(header_key_1, nca_keyset.header_key + 16, 16); - - aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, false); - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, false); - if (crypt_res != NCA_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); - return false; - } - - if (__builtin_bswap32(out->magic) == NCA3_MAGIC) - { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, false); - if (crypt_res != NCA_FULL_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); - return false; - } - } else - if (__builtin_bswap32(out->magic) == NCA2_MAGIC) - { - for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) - { - if (out->fs_headers[i]._0x148[0] != 0 || memcmp(out->fs_headers[i]._0x148, out->fs_headers[i]._0x148 + 1, 0xB7)) - { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, false); - if (crypt_res != NCA_SECTION_HEADER_LENGTH) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); - return false; - } - } else { - memset(&(out->fs_headers[i]), 0, sizeof(nca_fs_header_t)); - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(out->magic)); - return false; - } - - for(i = 0; i < 0x10; i++) - { - if (out->rights_id[i] != 0) - { - has_rights_id = true; - break; - } - } - - if (has_rights_id) - { - if (rights_info != NULL) - { - // If we're dealing with a rights info context, retrieve the ticket for the current title - - if (!rights_info->has_rights_id) - { - rights_info->has_rights_id = true; - - memcpy(rights_info->rights_id, out->rights_id, 16); - convertDataToHexString(out->rights_id, 16, rights_info->rights_id_str, 33); - sprintf(rights_info->tik_filename, "%s.tik", rights_info->rights_id_str); - sprintf(rights_info->cert_filename, "%s.cert", rights_info->rights_id_str); - - if (retrieveTitleKeyData) - { - ret = retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey); - - if (ret >= 0) - { - memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); - memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); - - rights_info->retrieved_tik = true; - } else { - if (ret == -2) - { - // We are probably dealing with a pre-installed title - // Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway - rights_info->missing_tik = true; - } else { - return false; - } - } - } - } else { - // Copy what we already have - if (retrieveTitleKeyData && rights_info->retrieved_tik) - { - memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); - memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); - } - } - } else { - // Otherwise, only retrieve the decrypted titlekey. This is used with ExeFS/RomFS section parsing for SD/eMMC titles - if (retrieveTitleKeyData) - { - u8 tmp_dec_titlekey[0x10]; - - if (retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey) < 0) return false; - - memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); - memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10); - } - } - } else { - if (!decryptNcaKeyArea(out, decrypted_nca_keys)) return false; - } - - return true; -} - -bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys) -{ - if (!rights_info || !rights_info->has_rights_id || !strlen(rights_info->tik_filename) || !decrypted_nca_keys) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve titlekey from gamecard ticket!", __func__); - return false; - } - - // Check if the ticket has already been retrieved from the HFS0 partition in the gamecard - if (rights_info->retrieved_tik) return true; - - // Load external keys - if (!loadExternalKeys()) return false; - - // Retrieve ticket - if (!readFileFromSecureHfs0PartitionByName(rights_info->tik_filename, 0, &(rights_info->tik_data), ETICKET_TIK_FILE_SIZE)) return false; - - // Save encrypted titlekey - memcpy(rights_info->enc_titlekey, rights_info->tik_data.titlekey_block, 0x10); - - // Decrypt titlekey - u8 crypto_type = rights_info->rights_id[0x0F]; - if (crypto_type) crypto_type--; - - if (crypto_type >= 0x20) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); - return false; - } - - Aes128Context titlekey_aes_ctx; - aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false); - aes128DecryptBlock(&titlekey_aes_ctx, rights_info->dec_titlekey, rights_info->enc_titlekey); - - // Update retrieved ticket flag - rights_info->retrieved_tik = true; - - // Save the decrypted NCA key area keys - memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); - memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); - - return true; -} - -bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output || !cur_mod_cnt) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process Program NCA!", __func__); - return false; - } - - if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); - return false; - } - - if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); - return false; - } - - if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); - return false; - } - - u32 i; - - u64 section_offset; - u64 hash_table_offset; - u64 nca_pfs0_offset; - - pfs0_header nca_pfs0_header; - pfs0_file_entry *nca_pfs0_entries = NULL; - u64 nca_pfs0_data_offset; - - npdm_t npdm_header; - bool found_meta = false; - u64 meta_offset; - u64 acid_pubkey_offset; - - u64 block_hash_table_offset; - u64 block_hash_table_end_offset; - u64 block_start_offset[2] = { 0, 0 }; - u64 block_size[2] = { 0, 0 }; - u8 block_hash[2][SHA256_HASH_SIZE]; - u8 *block_data[2] = { NULL, NULL }; - - u64 sig_write_size[2] = { 0, 0 }; - - u8 *hash_table = NULL; - - Aes128CtrContext aes_ctx; - - section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); - hash_table_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_offset); - nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset); - - if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !hash_table_offset || hash_table_offset < section_offset || !nca_pfs0_offset || nca_pfs0_offset <= hash_table_offset) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); - return false; - } - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, xml_content_info->decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); - return false; - } - - if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); - return false; - } - - if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - return false; - } - - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); - if (!nca_pfs0_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); - free(nca_pfs0_entries); - return false; - } - - nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)) + (u64)nca_pfs0_header.str_table_size); - - // Looking for META magic - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - u64 nca_pfs0_cur_file_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); - - // Read and decrypt NPDM header - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_cur_file_offset, &npdm_header, sizeof(npdm_t), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 entry #%u!", __func__, i); - free(nca_pfs0_entries); - return false; - } - - if (__builtin_bswap32(npdm_header.magic) == META_MAGIC) - { - found_meta = true; - meta_offset = nca_pfs0_cur_file_offset; - acid_pubkey_offset = (meta_offset + (u64)npdm_header.acid_offset + (u64)NPDM_SIGNATURE_SIZE); - break; - } - } - - free(nca_pfs0_entries); - - if (!found_meta) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find NPDM entry in Program NCA section #0 PFS0 partition!", __func__); - return false; - } - - // Calculate block offsets - block_hash_table_offset = (hash_table_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)) * (u64)SHA256_HASH_SIZE); - block_hash_table_end_offset = (hash_table_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)SHA256_HASH_SIZE)); - block_start_offset[0] = (nca_pfs0_offset + (((acid_pubkey_offset - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)); - - // Make sure our block doesn't pass PFS0 end offset - if ((block_start_offset[0] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) - { - block_size[0] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[0] - nca_pfs0_offset)); - } else { - block_size[0] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size; - } - - block_data[0] = malloc(block_size[0]); - if (!block_data[0]) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!", __func__); - return false; - } - - // Read and decrypt block - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 0!", __func__); - free(block_data[0]); - return false; - } - - // Make sure that 1 block will cover all patched bytes, otherwise we'll have to recalculate another hash block - if (block_hash_table_offset != block_hash_table_end_offset) - { - sig_write_size[1] = (acid_pubkey_offset - block_start_offset[0] + (u64)NPDM_SIGNATURE_SIZE - block_size[0]); - sig_write_size[0] = ((u64)NPDM_SIGNATURE_SIZE - sig_write_size[1]); - } else { - sig_write_size[0] = (u64)NPDM_SIGNATURE_SIZE; - } - - // Patch ACID public key changing it to a self-generated pubkey - memcpy(block_data[0] + (acid_pubkey_offset - block_start_offset[0]), rsa_get_public_key(), sig_write_size[0]); - - // Calculate new block hash - sha256CalculateHash(block_hash[0], block_data[0], block_size[0]); - - if (block_hash_table_offset != block_hash_table_end_offset) - { - block_start_offset[1] = (nca_pfs0_offset + (((acid_pubkey_offset + (u64)NPDM_SIGNATURE_SIZE - nca_pfs0_offset) / (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size) * (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size)); - - if ((block_start_offset[1] - nca_pfs0_offset + dec_nca_header->fs_headers[0].pfs0_superblock.block_size) > dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) - { - block_size[1] = (dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size - (block_start_offset[1] - nca_pfs0_offset)); - } else { - block_size[1] = (u64)dec_nca_header->fs_headers[0].pfs0_superblock.block_size; - } - - block_data[1] = malloc(block_size[1]); - if (!block_data[1]) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!", __func__); - free(block_data[0]); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 1!", __func__); - free(block_data[0]); - free(block_data[1]); - return false; - } - - memcpy(block_data[1], rsa_get_public_key() + sig_write_size[0], sig_write_size[1]); - - sha256CalculateHash(block_hash[1], block_data[1], block_size[1]); - } - - hash_table = malloc(dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size); - if (!hash_table) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 hash table!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 hash table!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - free(hash_table); - return false; - } - - // Update block hashes - memcpy(hash_table + (block_hash_table_offset - hash_table_offset), block_hash[0], SHA256_HASH_SIZE); - if (block_hash_table_offset != block_hash_table_end_offset) memcpy(hash_table + (block_hash_table_end_offset - hash_table_offset), block_hash[1], SHA256_HASH_SIZE); - - // Calculate PFS0 superblock master hash - sha256CalculateHash(dec_nca_header->fs_headers[0].pfs0_superblock.master_hash, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size); - - // Calculate section hash - sha256CalculateHash(dec_nca_header->section_hashes[0], &(dec_nca_header->fs_headers[0]), sizeof(nca_fs_header_t)); - - // Recreate NPDM signature - if (!rsa_sign(&(dec_nca_header->magic), NPDM_SIGNATURE_AREA_SIZE, dec_nca_header->npdm_key_sig, NPDM_SIGNATURE_SIZE)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to recreate Program NCA NPDM signature!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - free(hash_table); - return false; - } - - // Reencrypt relevant data blocks - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], true)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - free(hash_table); - return false; - } - - if (block_hash_table_offset != block_hash_table_end_offset) - { - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], true)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1!", __func__); - free(block_data[0]); - free(block_data[1]); - free(hash_table); - return false; - } - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, true)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 hash table!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - free(hash_table); - return false; - } - - // Save data to the output struct so we can write it later - // The caller function must free these data pointers - nca_program_mod_data *tmp_mod_data = realloc(*output, (*cur_mod_cnt + 1) * sizeof(nca_program_mod_data)); - if (!tmp_mod_data) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to reallocate Program NCA mod data buffer!", __func__); - free(block_data[0]); - if (block_data[1]) free(block_data[1]); - free(hash_table); - return false; - } - - memset(&(tmp_mod_data[*cur_mod_cnt]), 0, sizeof(nca_program_mod_data)); - - tmp_mod_data[*cur_mod_cnt].nca_index = idx; - - tmp_mod_data[*cur_mod_cnt].hash_table = hash_table; - tmp_mod_data[*cur_mod_cnt].hash_table_offset = hash_table_offset; - tmp_mod_data[*cur_mod_cnt].hash_table_size = dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size; - - tmp_mod_data[*cur_mod_cnt].block_mod_cnt = (block_hash_table_offset != block_hash_table_end_offset ? 2 : 1); - - tmp_mod_data[*cur_mod_cnt].block_data[0] = block_data[0]; - tmp_mod_data[*cur_mod_cnt].block_offset[0] = block_start_offset[0]; - tmp_mod_data[*cur_mod_cnt].block_size[0] = block_size[0]; - - if (block_hash_table_offset != block_hash_table_end_offset) - { - tmp_mod_data[*cur_mod_cnt].block_data[1] = block_data[1]; - tmp_mod_data[*cur_mod_cnt].block_offset[1] = block_start_offset[1]; - tmp_mod_data[*cur_mod_cnt].block_size[1] = block_size[1]; - } - - *output = tmp_mod_data; - tmp_mod_data = NULL; - - *cur_mod_cnt += 1; - - return true; -} - -bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info) -{ - if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve CNMT NCA!", __func__); - return false; - } - - nca_header_t dec_header; - - u32 i, j, k = 0; - - u64 section_offset; - u64 section_size; - u8 *section_data = NULL; - - Aes128CtrContext aes_ctx; - - u64 nca_pfs0_offset; - u64 nca_pfs0_str_table_offset; - u64 nca_pfs0_data_offset; - pfs0_header nca_pfs0_header; - pfs0_file_entry *nca_pfs0_entries = NULL; - - bool found_cnmt = false; - - u64 title_cnmt_offset; - u64 title_cnmt_size; - - cnmt_header title_cnmt_header; - cnmt_extended_header title_cnmt_extended_header; - - u64 digest_offset; - - // Generate filename for our required CNMT file - char cnmtFileName[50] = {'\0'}; - snprintf(cnmtFileName, MAX_CHARACTERS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id); - - // Decrypt the NCA header - // Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard - if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) return false; - - if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 doesn't hold a PFS0 partition!", __func__); - return false; - } - - if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in CNMT NCA section #0!", __func__); - return false; - } - - if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", __func__, dec_header.fs_headers[0].crypt_type); - return false; - } - - bool has_rights_id = false; - - for(i = 0; i < 0x10; i++) - { - if (dec_header.rights_id[i] != 0) - { - has_rights_id = true; - break; - } - } - - // CNMT NCAs never use titlekey crypto - if (has_rights_id) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in CNMT NCA header not empty!", __func__); - return false; - } - - // Modify distribution type - if (curStorageId == NcmStorageId_GameCard) dec_header.distribution = 0; - - section_offset = ((u64)dec_header.section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); - section_size = (((u64)dec_header.section_entries[0].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); - - if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for CNMT NCA section #0!", __func__); - return false; - } - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, xml_content_info[cnmtNcaIndex].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - section_data = malloc(section_size); - if (!section_data) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decrypted CNMT NCA section #0!", __func__); - return false; - } - - aes128CtrCrypt(&aes_ctx, section_data, ncaBuf + section_offset, section_size); - - nca_pfs0_offset = dec_header.fs_headers[0].pfs0_superblock.pfs0_offset; - memcpy(&nca_pfs0_header, section_data + nca_pfs0_offset, sizeof(pfs0_header)); - - if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); - free(section_data); - return false; - } - - if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - free(section_data); - return false; - } - - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); - if (!nca_pfs0_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!", __func__); - free(section_data); - return false; - } - - memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)); - - nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); - nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); - - // Look for the CNMT - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - u64 filename_offset = (nca_pfs0_str_table_offset + nca_pfs0_entries[i].filename_offset); - if (!strncasecmp((char*)section_data + filename_offset, cnmtFileName, strlen(cnmtFileName))) - { - found_cnmt = true; - title_cnmt_offset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); - title_cnmt_size = nca_pfs0_entries[i].file_size; - break; - } - } - - free(nca_pfs0_entries); - - if (!found_cnmt) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", __func__, cnmtFileName); - free(section_data); - return false; - } - - memcpy(&title_cnmt_header, section_data + title_cnmt_offset, sizeof(cnmt_header)); - memcpy(&title_cnmt_extended_header, section_data + title_cnmt_offset + sizeof(cnmt_header), sizeof(cnmt_extended_header)); - - // Fill information for our CNMT XML - digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE); - memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE); - convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1); - xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type); - xml_program_info->required_dl_sysver = title_cnmt_header.required_dl_sysver; - xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info[cnmtNcaIndex].keyblob); - xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver; - xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid; - xml_program_info->min_appver = title_cnmt_extended_header.min_appver; - - // Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA) - // Also wipe each of the content records we're gonna replace - for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA - { - for(j = 0; j < title_cnmt_header.content_cnt; j++) - { - cnmt_content_record cnt_record; - memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record)); - - if (memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) != 0) continue; - - // Save content record offset - xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record)); - - // Empty CNMT content record - memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record)); - - // Increase counter - k++; - - break; - } - } - - // Verify counter - if (k != (xml_program_info->nca_cnt - 1)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid content record entries in the CNMT NCA!", __func__); - free(section_data); - return false; - } - - // Replace input buffer data in-place - memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH); - memcpy(ncaBuf + section_offset, section_data, section_size); - - free(section_data); - - // Update offsets - nca_pfs0_offset += section_offset; - title_cnmt_offset += section_offset; - - // Save data to output struct - output->section_offset = section_offset; - output->section_size = section_size; - output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset); - output->hash_block_size = dec_header.fs_headers[0].pfs0_superblock.block_size; - output->hash_block_cnt = (dec_header.fs_headers[0].pfs0_superblock.hash_table_size / SHA256_HASH_SIZE); - output->pfs0_offset = nca_pfs0_offset; - output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size; - output->title_cnmt_offset = title_cnmt_offset; - output->title_cnmt_size = title_cnmt_size; - - return true; -} - -bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod) -{ - if (!ncaBuf || !ncaBufSize || !xml_program_info || xml_program_info->nca_cnt <= 1 || !xml_content_info || !cnmt_mod) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to patch CNMT NCA!", __func__); - return false; - } - - u32 i; - - u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA - - cnmt_header title_cnmt_header; - cnmt_content_record title_cnmt_content_record; - u64 title_cnmt_content_records_offset; - - nca_header_t dec_header; - - Aes128CtrContext aes_ctx; - - // Copy CNMT header - memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header)); - - // Calculate the start offset for the content records - title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size); - - // Write content records - for(i = 0; i < nca_cnt; i++) - { - memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record)); - - memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE); - memcpy(title_cnmt_content_record.nca_id, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2); - convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_record.size); - title_cnmt_content_record.type = xml_content_info[i].type; - title_cnmt_content_record.id_offset = xml_content_info[i].id_offset; - - memcpy(ncaBuf + title_cnmt_content_records_offset + xml_content_info[i].cnt_record_offset, &title_cnmt_content_record, sizeof(cnmt_content_record)); - } - - // Recalculate block hashes - for(i = 0; i < cnmt_mod->hash_block_cnt; i++) - { - u64 blk_offset = ((u64)i * cnmt_mod->hash_block_size); - - u64 rest_size = (cnmt_mod->pfs0_size - blk_offset); - u64 blk_size = (rest_size > cnmt_mod->hash_block_size ? cnmt_mod->hash_block_size : rest_size); - - sha256CalculateHash(ncaBuf + cnmt_mod->hash_table_offset + (i * SHA256_HASH_SIZE), ncaBuf + cnmt_mod->pfs0_offset + blk_offset, blk_size); - } - - // Copy header to struct - memcpy(&dec_header, ncaBuf, sizeof(nca_header_t)); - - // Calculate PFS0 superblock master hash - sha256CalculateHash(dec_header.fs_headers[0].pfs0_superblock.master_hash, ncaBuf + cnmt_mod->hash_table_offset, dec_header.fs_headers[0].pfs0_superblock.hash_table_size); - - // Calculate section hash - sha256CalculateHash(dec_header.section_hashes[0], &(dec_header.fs_headers[0]), sizeof(nca_fs_header_t)); - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (cnmt_mod->section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, xml_content_info[xml_program_info->nca_cnt - 1].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - // Reencrypt CNMT NCA - if (!encryptNcaHeader(&dec_header, ncaBuf, ncaBufSize)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt modified CNMT NCA header!", __func__); - return false; - } - - aes128CtrCrypt(&aes_ctx, ncaBuf + cnmt_mod->section_offset, ncaBuf + cnmt_mod->section_offset, cnmt_mod->section_size); - - // Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML - sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize); - convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1); - memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE / 2); - convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, SHA256_HASH_SIZE + 1); - - return true; -} - -bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); - return false; - } - - u8 exefs_index; - bool found_exefs = false; - - u32 i; - - u64 section_offset; - u64 section_size; - - unsigned char ctr[0x10]; - memset(ctr, 0, 0x10); - - u64 ofs; - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - - Aes128CtrContext aes_ctx; - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - u64 nca_pfs0_offset; - pfs0_header nca_pfs0_header; - - u64 nca_pfs0_entries_offset; - pfs0_file_entry *nca_pfs0_entries = NULL; - - u64 nca_pfs0_str_table_offset; - char *nca_pfs0_str_table = NULL; - - u64 nca_pfs0_data_offset; - - initExeFsContext(); - - for(exefs_index = 0; exefs_index < 4; exefs_index++) - { - if (dec_nca_header->fs_headers[exefs_index].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[exefs_index].fs_type != NCA_FS_HEADER_FSTYPE_PFS0 || !dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_size || dec_nca_header->fs_headers[exefs_index].crypt_type != NCA_FS_HEADER_CRYPT_CTR) continue; - - section_offset = ((u64)dec_nca_header->section_entries[exefs_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); - section_size = (((u64)dec_nca_header->section_entries[exefs_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); - - if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) continue; - - // Generate initial CTR - ofs = (section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_nca_header->fs_headers[exefs_index].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - aes128CtrContextResetCtr(&aes_ctx, ctr); - - nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_offset); - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) return false; - - if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC || !nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) continue; - - nca_pfs0_entries_offset = (nca_pfs0_offset + sizeof(pfs0_header)); - - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); - if (!nca_pfs0_entries) continue; - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_entries_offset, nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) - { - free(nca_pfs0_entries); - return false; - } - - nca_pfs0_str_table_offset = (nca_pfs0_entries_offset + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); - - nca_pfs0_str_table = calloc(nca_pfs0_header.str_table_size, sizeof(char)); - if (!nca_pfs0_str_table) - { - free(nca_pfs0_entries); - nca_pfs0_entries = NULL; - continue; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_str_table_offset, nca_pfs0_str_table, (u64)nca_pfs0_header.str_table_size, false)) - { - free(nca_pfs0_str_table); - free(nca_pfs0_entries); - return false; - } - - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - char *cur_filename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); - - if (!strncasecmp(cur_filename, "main.npdm", 9)) - { - found_exefs = true; - break; - } - } - - if (found_exefs) break; - - free(nca_pfs0_str_table); - nca_pfs0_str_table = NULL; - - free(nca_pfs0_entries); - nca_pfs0_entries = NULL; - } - - if (!found_exefs) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold an ExeFS section! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - return false; - } - - nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); - - // Save data to output struct - // The caller function must free these data pointers - memcpy(&(exeFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(exeFsContext.ncaId), ncaId, sizeof(NcmContentId)); - memcpy(&(exeFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); - exeFsContext.exefs_offset = nca_pfs0_offset; - exeFsContext.exefs_size = dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_size; - memcpy(&(exeFsContext.exefs_header), &nca_pfs0_header, sizeof(pfs0_header)); - exeFsContext.exefs_entries_offset = nca_pfs0_entries_offset; - exeFsContext.exefs_entries = nca_pfs0_entries; - exeFsContext.exefs_str_table_offset = nca_pfs0_str_table_offset; - exeFsContext.exefs_str_table = nca_pfs0_str_table; - exeFsContext.exefs_data_offset = nca_pfs0_data_offset; - - return true; -} - -bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); - return false; - } - - u8 romfs_index; - bool found_romfs = false; - - u32 i; - - u64 section_offset; - u64 section_size; - - Aes128CtrContext aes_ctx; - - u64 romfs_offset; - u64 romfs_size; - romfs_header romFsHeader; - - u64 romfs_dirtable_offset; - u64 romfs_dirtable_size; - - u64 romfs_filetable_offset; - u64 romfs_filetable_size; - - u64 romfs_filedata_offset; - - romfs_dir *romfs_dir_entries = NULL; - romfs_file *romfs_file_entries = NULL; - - initRomFsContext(); - - for(romfs_index = 0; romfs_index < 4; romfs_index++) - { - if (dec_nca_header->fs_headers[romfs_index].partition_type == NCA_FS_HEADER_PARTITION_ROMFS && dec_nca_header->fs_headers[romfs_index].fs_type == NCA_FS_HEADER_FSTYPE_ROMFS) - { - found_romfs = true; - break; - } - } - - if (!found_romfs) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a RomFS section!", __func__); - return false; - } - - section_offset = ((u64)dec_nca_header->section_entries[romfs_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); - section_size = (((u64)dec_nca_header->section_entries[romfs_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); - - if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section! (#%u)", __func__, romfs_index); - return false; - } - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_nca_header->fs_headers[romfs_index].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - if (__builtin_bswap32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic) != IVFC_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IVFC magic word for NCA RomFS section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic)); - return false; - } - - if (dec_nca_header->fs_headers[romfs_index].crypt_type != NCA_FS_HEADER_CRYPT_CTR) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA RomFS section! (0x%02X)", __func__, dec_nca_header->fs_headers[romfs_index].crypt_type); - return false; - } - - romfs_offset = (section_offset + dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].logical_offset); - romfs_size = dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].hash_data_size; - - if (romfs_offset < section_offset || romfs_offset >= (section_offset + section_size) || romfs_size < ROMFS_HEADER_SIZE || (romfs_offset + romfs_size) > (section_offset + section_size)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section!", __func__); - return false; - } - - // First read the RomFS header - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_offset, &romFsHeader, sizeof(romfs_header), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section header!", __func__); - return false; - } - - if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, romfs_offset); - return false; - } - - romfs_dirtable_offset = (romfs_offset + romFsHeader.dirTableOff); - romfs_dirtable_size = romFsHeader.dirTableSize; - - romfs_filetable_offset = (romfs_offset + romFsHeader.fileTableOff); - romfs_filetable_size = romFsHeader.fileTableSize; - - if (romfs_dirtable_offset >= (section_offset + section_size) || !romfs_dirtable_size || (romfs_dirtable_offset + romfs_dirtable_size) > (section_offset + section_size) || romfs_filetable_offset >= (section_offset + section_size) || !romfs_filetable_size || (romfs_filetable_offset + romfs_filetable_size) > (section_offset + section_size)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA RomFS section!", __func__); - return false; - } - - romfs_filedata_offset = (romfs_offset + romFsHeader.fileDataOff); - - if (romfs_filedata_offset >= (section_offset + section_size)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA RomFS section!", __func__); - return false; - } - - romfs_dir_entries = calloc(1, romfs_dirtable_size); - if (!romfs_dir_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section directory entries!", __func__); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_dirtable_offset, romfs_dir_entries, romfs_dirtable_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section directory entries!", __func__); - free(romfs_dir_entries); - return false; - } - - romfs_file_entries = calloc(1, romfs_filetable_size); - if (!romfs_file_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section file entries!", __func__); - free(romfs_dir_entries); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_filetable_offset, romfs_file_entries, romfs_filetable_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); - free(romfs_file_entries); - free(romfs_dir_entries); - return false; - } - - // Save data to output struct - // The caller function must free these data pointers - memcpy(&(romFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(romFsContext.ncaId), ncaId, sizeof(NcmContentId)); - memcpy(&(romFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); - romFsContext.section_offset = section_offset; - romFsContext.section_size = section_size; - romFsContext.romfs_offset = romfs_offset; - romFsContext.romfs_size = romfs_size; - romFsContext.romfs_dirtable_offset = romfs_dirtable_offset; - romFsContext.romfs_dirtable_size = romfs_dirtable_size; - romFsContext.romfs_dir_entries = romfs_dir_entries; - romFsContext.romfs_filetable_offset = romfs_filetable_offset; - romFsContext.romfs_filetable_size = romfs_filetable_size; - romFsContext.romfs_file_entries = romfs_file_entries; - romFsContext.romfs_filedata_offset = romfs_filedata_offset; - - return true; -} - -bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !romFsContext.section_offset || !romFsContext.section_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_file_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read BKTR section from NCA!", __func__); - return false; - } - - u32 i; - - u8 bktr_index; - bool found_bktr = false, success = false; - - romfs_header romFsHeader; - - initBktrContext(); - - memcpy(&(bktrContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(bktrContext.ncaId), ncaId, sizeof(NcmContentId)); - - for(bktr_index = 0; bktr_index < 4; bktr_index++) - { - if (dec_nca_header->fs_headers[bktr_index].partition_type == NCA_FS_HEADER_PARTITION_ROMFS && dec_nca_header->fs_headers[bktr_index].fs_type == NCA_FS_HEADER_FSTYPE_ROMFS) - { - found_bktr = true; - break; - } - } - - if (!found_bktr) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a BKTR section!", __func__); - return false; - } - - bktrContext.section_offset = ((u64)dec_nca_header->section_entries[bktr_index].media_start_offset * (u64)MEDIA_UNIT_SIZE); - bktrContext.section_size = (((u64)dec_nca_header->section_entries[bktr_index].media_end_offset * (u64)MEDIA_UNIT_SIZE) - bktrContext.section_offset); - - if (!bktrContext.section_offset || bktrContext.section_offset < NCA_FULL_HEADER_LENGTH || !bktrContext.section_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR section! (#%u)", __func__, bktr_index); - return false; - } - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (bktrContext.section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_nca_header->fs_headers[bktr_index].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&(bktrContext.aes_ctx), ctr_key, ctr); - - memcpy(&(bktrContext.superblock), &(dec_nca_header->fs_headers[bktr_index].bktr_superblock), sizeof(bktr_superblock_t)); - - if (__builtin_bswap32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IVFC magic word for NCA BKTR section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(bktrContext.superblock.ivfc_header.magic)); - return false; - } - - if (dec_nca_header->fs_headers[bktr_index].crypt_type != NCA_FS_HEADER_CRYPT_BKTR) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA BKTR section! (0x%02X)", __func__, dec_nca_header->fs_headers[bktr_index].crypt_type); - return false; - } - - if (__builtin_bswap32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || __builtin_bswap32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid BKTR magic word for NCA BKTR relocation/subsection header! Wrong KAEK? (0x%02X | 0x%02X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(bktrContext.superblock.relocation_header.magic), __builtin_bswap32(bktrContext.superblock.subsection_header.magic)); - return false; - } - - if ((bktrContext.superblock.relocation_header.offset + bktrContext.superblock.relocation_header.size) != bktrContext.superblock.subsection_header.offset || (bktrContext.superblock.subsection_header.offset + bktrContext.superblock.subsection_header.size) != bktrContext.section_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid layout for NCA BKTR section!", __func__); - return false; - } - - // Allocate space for an extra (fake) relocation entry, to simplify our logic - bktrContext.relocation_block = calloc(1, bktrContext.superblock.relocation_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_relocation_entry_t))); - if (!bktrContext.relocation_block) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR relocation header!", __func__); - return false; - } - - // Allocate space for an extra (fake) subsection entry, to simplify our logic - bktrContext.subsection_block = calloc(1, bktrContext.superblock.subsection_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_subsection_entry_t)) + sizeof(bktr_subsection_entry_t)); - if (!bktrContext.subsection_block) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR subsection header!", __func__); - goto out; - } - - // Read the relocation header - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.relocation_header.offset, bktrContext.relocation_block, bktrContext.superblock.relocation_header.size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR relocation header!", __func__); - goto out; - } - - // Read the subsection header - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.subsection_header.offset, bktrContext.subsection_block, bktrContext.superblock.subsection_header.size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR subsection header!", __func__); - goto out; - } - - if (bktrContext.subsection_block->total_size != bktrContext.superblock.subsection_header.offset) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA BKTR subsection header size!", __func__); - goto out; - } - - // This simplifies logic greatly... - for(i = (bktrContext.relocation_block->num_buckets - 1); i > 0; i--) - { - bktr_relocation_bucket_t tmp_bucket; - memcpy(&tmp_bucket, &(bktrContext.relocation_block->buckets[i]), sizeof(bktr_relocation_bucket_t)); - memcpy(bktr_get_relocation_bucket(bktrContext.relocation_block, i), &tmp_bucket, sizeof(bktr_relocation_bucket_t)); - } - - for(i = 0; (i + 1) < bktrContext.relocation_block->num_buckets; i++) - { - bktr_relocation_bucket_t *cur_bucket = bktr_get_relocation_bucket(bktrContext.relocation_block, i); - cur_bucket->entries[cur_bucket->num_entries].virt_offset = bktrContext.relocation_block->bucket_virtual_offsets[i + 1]; - } - - for(i = (bktrContext.subsection_block->num_buckets - 1); i > 0; i--) - { - bktr_subsection_bucket_t tmp_bucket; - memcpy(&tmp_bucket, &(bktrContext.subsection_block->buckets[i]), sizeof(bktr_subsection_bucket_t)); - memcpy(bktr_get_subsection_bucket(bktrContext.subsection_block, i), &tmp_bucket, sizeof(bktr_subsection_bucket_t)); - } - - for(i = 0; (i + 1) < bktrContext.subsection_block->num_buckets; i++) - { - bktr_subsection_bucket_t *cur_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, i); - bktr_subsection_bucket_t *next_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, i + 1); - cur_bucket->entries[cur_bucket->num_entries].offset = next_bucket->entries[0].offset; - cur_bucket->entries[cur_bucket->num_entries].ctr_val = next_bucket->entries[0].ctr_val; - } - - bktr_relocation_bucket_t *last_reloc_bucket = bktr_get_relocation_bucket(bktrContext.relocation_block, bktrContext.relocation_block->num_buckets - 1); - bktr_subsection_bucket_t *last_subsec_bucket = bktr_get_subsection_bucket(bktrContext.subsection_block, bktrContext.subsection_block->num_buckets - 1); - last_reloc_bucket->entries[last_reloc_bucket->num_entries].virt_offset = bktrContext.relocation_block->total_size; - last_subsec_bucket->entries[last_subsec_bucket->num_entries].offset = bktrContext.superblock.relocation_header.offset; - last_subsec_bucket->entries[last_subsec_bucket->num_entries].ctr_val = dec_nca_header->fs_headers[bktr_index].section_ctr_low; - last_subsec_bucket->entries[last_subsec_bucket->num_entries + 1].offset = bktrContext.section_size; - last_subsec_bucket->entries[last_subsec_bucket->num_entries + 1].ctr_val = 0; - - // Parse RomFS section - bktrContext.romfs_offset = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].logical_offset; - bktrContext.romfs_size = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].hash_data_size; - - // Do not check the RomFS offset/size, because it reflects the full patched RomFS image - if (!bktrContext.romfs_offset || bktrContext.romfs_size < ROMFS_HEADER_SIZE) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR RomFS section!", __func__); - goto out; - } - - if (!readBktrSectionBlock(bktrContext.romfs_offset, &romFsHeader, sizeof(romfs_header))) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section header!", __func__); - goto out; - } - - if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA BKTR RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, bktrContext.section_offset + bktrContext.romfs_offset); - goto out; - } - - bktrContext.romfs_dirtable_offset = (bktrContext.romfs_offset + romFsHeader.dirTableOff); - bktrContext.romfs_dirtable_size = romFsHeader.dirTableSize; - - bktrContext.romfs_filetable_offset = (bktrContext.romfs_offset + romFsHeader.fileTableOff); - bktrContext.romfs_filetable_size = romFsHeader.fileTableSize; - - // Then again, do not check these offsets/sizes, because they reflect the patched RomFS image - if (!bktrContext.romfs_dirtable_offset || !bktrContext.romfs_dirtable_size || !bktrContext.romfs_filetable_offset || !bktrContext.romfs_filetable_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA BKTR RomFS section!", __func__); - goto out; - } - - bktrContext.romfs_filedata_offset = (bktrContext.romfs_offset + romFsHeader.fileDataOff); - - if (!bktrContext.romfs_filedata_offset) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA BKTR RomFS section!", __func__); - goto out; - } - - bktrContext.romfs_dir_entries = calloc(1, bktrContext.romfs_dirtable_size); - if (!bktrContext.romfs_dir_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section directory entries!", __func__); - goto out; - } - - if (!readBktrSectionBlock(bktrContext.romfs_dirtable_offset, bktrContext.romfs_dir_entries, bktrContext.romfs_dirtable_size)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section directory entries!", __func__); - goto out; - } - - bktrContext.romfs_file_entries = calloc(1, bktrContext.romfs_filetable_size); - if (!bktrContext.romfs_file_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section file entries!", __func__); - goto out; - } - - if (!readBktrSectionBlock(bktrContext.romfs_filetable_offset, bktrContext.romfs_file_entries, bktrContext.romfs_filetable_size)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); - goto out; - } - - success = true; - -out: - if (!success) - { - if (bktrContext.romfs_file_entries != NULL) - { - free(bktrContext.romfs_file_entries); - bktrContext.romfs_file_entries = NULL; - } - - if (bktrContext.romfs_dir_entries != NULL) - { - free(bktrContext.romfs_dir_entries); - bktrContext.romfs_dir_entries = NULL; - } - - if (bktrContext.subsection_block != NULL) - { - free(bktrContext.subsection_block); - bktrContext.subsection_block = NULL; - } - - if (bktrContext.relocation_block != NULL) - { - free(bktrContext.relocation_block); - bktrContext.relocation_block = NULL; - } - } - - // The caller function must free the data pointers from the bktrContext struct - - return success; -} - -bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, bool useCustomAcidRsaPubKey, char **outBuf, u64 *outBufSize) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !outBuf || !outBufSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate \"programinfo.xml\"!", __func__); - return false; - } - - if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); - return false; - } - - if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); - return false; - } - - if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); - return false; - } - - u32 i; - - bool proceed = true, success = false; - - u64 section_offset; - u64 nca_pfs0_offset; - - pfs0_header nca_pfs0_header; - pfs0_file_entry *nca_pfs0_entries = NULL; - char *nca_pfs0_str_table = NULL; - - u64 nca_pfs0_str_table_offset; - u64 nca_pfs0_data_offset; - - Aes128CtrContext aes_ctx; - - char *programInfoXml = NULL; - char tmp[NAME_BUF_LEN] = {'\0'}; - - u32 npdmEntry = 0; - npdm_t npdm_header; - u8 *npdm_acid_section = NULL; - - u64 npdm_acid_section_b64_size = 0; - char *npdm_acid_section_b64 = NULL; - - u32 acid_flags = 0; - - section_offset = ((u64)dec_nca_header->section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); - nca_pfs0_offset = (section_offset + dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_offset); - - if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !nca_pfs0_offset) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); - return false; - } - - // Generate initial CTR - unsigned char ctr[0x10]; - u64 ofs = (section_offset >> 4); - - for(i = 0; i < 0x8; i++) - { - ctr[i] = dec_nca_header->fs_headers[0].section_ctr[0x08 - i - 1]; - ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); - ofs >>= 8; - } - - u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; - memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); - aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); - return false; - } - - if (__builtin_bswap32(nca_pfs0_header.magic) != PFS0_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(nca_pfs0_header.magic)); - return false; - } - - if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - return false; - } - - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); - if (!nca_pfs0_entries) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); - return false; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); - goto out; - } - - nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); - - nca_pfs0_str_table = calloc((u64)nca_pfs0_header.str_table_size, sizeof(char)); - if (!nca_pfs0_str_table) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 string table!", __func__); - goto out; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_str_table_offset, nca_pfs0_str_table, (u64)nca_pfs0_header.str_table_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 string table!", __func__); - goto out; - } - - nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); - - // Allocate memory for the programinfo.xml contents, making sure there's enough space - programInfoXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); - if (!programInfoXml) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); - goto out; - } - - sprintf(programInfoXml, "\n" \ - "\n" \ - " %u_%u_%u\n", dec_nca_header->sdk_major, dec_nca_header->sdk_minor, dec_nca_header->sdk_micro); - - // Retrieve the main.npdm contents - bool found_npdm = false; - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); - - if (strlen(curFilename) == 9 && !strncasecmp(curFilename, "main.npdm", 9) && nca_pfs0_entries[i].file_size > 0) - { - found_npdm = true; - npdmEntry = i; - break; - } - } - - if (!found_npdm) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); - goto out; - } - - // Read the META header from the NPDM - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset, &npdm_header, sizeof(npdm_t), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NPDM entry header from Program NCA section #0 PFS0!", __func__); - goto out; - } - - if (__builtin_bswap32(npdm_header.magic) != META_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NPDM META magic word! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__, __builtin_bswap32(npdm_header.magic)); - goto out; - } - - // Allocate memory for the ACID section - npdm_acid_section = malloc(npdm_header.acid_size); - if (!npdm_acid_section) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Program NCA NPDM ACID section contents!", __func__); - goto out; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset + (u64)npdm_header.acid_offset, npdm_acid_section, (u64)npdm_header.acid_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read ACID section from Program NCA NPDM!", __func__); - goto out; - } - - // If we're dealing with a gamecard title, replace the ACID public key with the patched one - if (useCustomAcidRsaPubKey) memcpy(npdm_acid_section + (u64)NPDM_SIGNATURE_SIZE, rsa_get_public_key(), (u64)NPDM_SIGNATURE_SIZE); - - sprintf(tmp, " %u\n", ((npdm_header.mmu_flags & 0x01) ? 64 : 32)); - strcat(programInfoXml, tmp); - - // Default this one to Release - strcat(programInfoXml, " Release\n"); - - // Retrieve the Base64 conversion length for the whole ACID section - mbedtls_base64_encode(NULL, 0, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size); - if (npdm_acid_section_b64_size <= (u64)npdm_header.acid_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid Base64 conversion length for the ACID section from Program NCA NPDM!", __func__); - goto out; - } - - npdm_acid_section_b64 = calloc(npdm_acid_section_b64_size + 1, sizeof(char)); - if (!npdm_acid_section_b64) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Base64 converted ACID section from Program NCA NPDM!", __func__); - goto out; - } - - // Perform the Base64 conversion - if (mbedtls_base64_encode((unsigned char*)npdm_acid_section_b64, npdm_acid_section_b64_size + 1, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size) != 0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Base64 conversion failed for the ACID section from Program NCA NPDM!", __func__); - goto out; - } - - strcat(programInfoXml, " "); - strcat(programInfoXml, npdm_acid_section_b64); - strcat(programInfoXml, "\n"); - - // TO-DO: Add more ACID flags? - - acid_flags = *((u32*)(&(npdm_acid_section[0x20C]))); - - strcat(programInfoXml, " \n"); - - sprintf(tmp, " %s\n", ((acid_flags & 0x01) ? "true" : "false")); - strcat(programInfoXml, tmp); - - sprintf(tmp, " %s\n", ((acid_flags & 0x02) ? "true" : "false")); - strcat(programInfoXml, tmp); - - strcat(programInfoXml, " \n"); - - // Middleware list - strcat(programInfoXml, " \n"); - - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - nso_header_t nsoHeader; - char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); - u64 curFileOffset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); - proceed = false; - break; - } - - // Check if we're dealing with a NSO - if (__builtin_bswap32(nsoHeader.magic) != NSO_MAGIC) continue; - - // Retrieve middleware list from this NSO - if (!retrieveMiddlewareListFromNso(ncmStorage, ncaId, &aes_ctx, curFilename, curFileOffset, &nsoHeader, programInfoXml)) - { - proceed = false; - break; - } - } - - if (!proceed) goto out; - - strcat(programInfoXml, " \n"); - - // Leave these fields empty (for now) - strcat(programInfoXml, " \n"); - strcat(programInfoXml, " \n"); - - // Symbols list from main NSO - strcat(programInfoXml, " \n"); - - for(i = 0; i < nca_pfs0_header.file_cnt; i++) - { - nso_header_t nsoHeader; - char *curFilename = (nca_pfs0_str_table + nca_pfs0_entries[i].filename_offset); - u64 curFileOffset = (nca_pfs0_data_offset + nca_pfs0_entries[i].file_offset); - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); - proceed = false; - break; - } - - // Check if we're dealing with the main NSO - if (strlen(curFilename) != 4 || strncmp(curFilename, "main", 4) != 0 || __builtin_bswap32(nsoHeader.magic) != NSO_MAGIC) continue; - - // Retrieve symbols list from main NSO - if (!retrieveSymbolsListFromNso(ncmStorage, ncaId, &aes_ctx, curFilename, curFileOffset, &nsoHeader, programInfoXml)) proceed = false; - - break; - } - - if (!proceed) goto out; - - strcat(programInfoXml, " \n"); - - // Leave this field empty (for now) - strcat(programInfoXml, " \n"); - - strcat(programInfoXml, ""); - - *outBuf = programInfoXml; - *outBufSize = strlen(programInfoXml); - - success = true; - -out: - if (npdm_acid_section_b64) free(npdm_acid_section_b64); - - if (npdm_acid_section) free(npdm_acid_section); - - if (!success && programInfoXml) free(programInfoXml); - - if (nca_pfs0_str_table) free(nca_pfs0_str_table); - - if (nca_pfs0_entries) free(nca_pfs0_entries); - - return success; -} - -char *getNacpLangName(Language val) -{ - char *out = NULL; - - switch(val) - { - case Language_AmericanEnglish: - out = "AmericanEnglish"; - break; - case Language_BritishEnglish: - out = "BritishEnglish"; - break; - case Language_Japanese: - out = "Japanese"; - break; - case Language_French: - out = "French"; - break; - case Language_German: - out = "German"; - break; - case Language_LatinAmericanSpanish: - out = "LatinAmericanSpanish"; - break; - case Language_Spanish: - out = "Spanish"; - break; - case Language_Italian: - out = "Italian"; - break; - case Language_Dutch: - out = "Dutch"; - break; - case Language_CanadianFrench: - out = "CanadianFrench"; - break; - case Language_Portuguese: - out = "Portuguese"; - break; - case Language_Russian: - out = "Russian"; - break; - case Language_Korean: - out = "Korean"; - break; - case Language_TraditionalChinese: - out = "TraditionalChinese"; - break; - case Language_SimplifiedChinese: - out = "SimplifiedChinese"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpStartupUserAccount(u8 val) -{ - char *out = NULL; - - switch(val) - { - case StartupUserAccount_None: - out = "None"; - break; - case StartupUserAccount_Required: - out = "Required"; - break; - case StartupUserAccount_RequiredWithNetworkServiceAccountAvailable: - out = "RequiredWithNetworkServiceAccountAvailable"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpUserAccountSwitchLock(u8 val) -{ - char *out = NULL; - - switch(val) - { - case UserAccountSwitchLock_Disable: - out = "Disable"; - break; - case UserAccountSwitchLock_Enable: - out = "Enable"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpSupportedLanguageFlag(SupportedLanguageFlag *data, u8 idx) -{ - if (!data || idx >= 0x10) return NULL; - - u32 flag; - memcpy(&flag, data, sizeof(u32)); - flag = ((flag >> idx) & 0x1); - - return (flag ? getNacpLangName((Language)flag) : NULL); -} - -char *getNacpScreenshot(u8 val) -{ - char *out = NULL; - - switch(val) - { - case Screenshot_Allow: - out = "Allow"; - break; - case Screenshot_Deny: - out = "Deny"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpVideoCapture(u8 val) -{ - char *out = NULL; - - switch(val) - { - case VideoCapture_Disable: - out = "Disable"; - break; - case VideoCapture_Manual: - out = "Manual"; - break; - case VideoCapture_Enable: - out = "Enable"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpRatingAgeOrganization(RatingAgeOrganization val) -{ - char *out = NULL; - - switch(val) - { - case RatingAgeOrganization_CERO: - out = "CERO"; - break; - case RatingAgeOrganization_GRACGCRB: - out = "GRACGCRB"; - break; - case RatingAgeOrganization_GSRMR: - out = "GSRMR"; - break; - case RatingAgeOrganization_ESRB: - out = "ESRB"; - break; - case RatingAgeOrganization_ClassInd: - out = "ClassInd"; - break; - case RatingAgeOrganization_USK: - out = "USK"; - break; - case RatingAgeOrganization_PEGI: - out = "PEGI"; - break; - case RatingAgeOrganization_PEGIPortugal: - out = "PEGIPortugal"; - break; - case RatingAgeOrganization_PEGIBBFC: - out = "PEGIBBFC"; - break; - case RatingAgeOrganization_Russian: - out = "Russian"; - break; - case RatingAgeOrganization_ACB: - out = "ACB"; - break; - case RatingAgeOrganization_OFLC: - out = "OFLC"; - break; - case RatingAgeOrganization_IARCGeneric: - out = "IARCGeneric"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpDataLossConfirmation(u8 val) -{ - char *out = NULL; - - switch(val) - { - case DataLossConfirmation_None: - out = "None"; - break; - case DataLossConfirmation_Required: - out = "Required"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpPlayLogPolicy(u8 val) -{ - char *out = NULL; - - switch(val) - { - case PlayLogPolicy_All: - out = "All"; - break; - case PlayLogPolicy_LogOnly: - out = "LogOnly"; - break; - case PlayLogPolicy_None: - out = "None"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpLogoType(u8 val) -{ - char *out = NULL; - - switch(val) - { - case LogoType_LicensedByNintendo: - out = "LicensedByNintendo"; - break; - case LogoType_DistributedByNintendo: - out = "DistributedByNintendo"; - break; - case LogoType_Nintendo: - out = "Nintendo"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpLogoHandling(u8 val) -{ - char *out = NULL; - - switch(val) - { - case LogoHandling_Auto: - out = "Auto"; - break; - case LogoHandling_Manual: - out = "Manual"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpAddOnContentRegistrationType(u8 val) -{ - char *out = NULL; - - switch(val) - { - case AddOnContentRegistrationType_AllOnLaunch: - out = "AllOnLaunch"; - break; - case AddOnContentRegistrationType_OnDemand: - out = "OnDemand"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpHdcp(u8 val) -{ - char *out = NULL; - - switch(val) - { - case Hdcp_None: - out = "None"; - break; - case Hdcp_Required: - out = "Required"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpCrashReport(u8 val) -{ - char *out = NULL; - - switch(val) - { - case CrashReport_Deny: - out = "Deny"; - break; - case CrashReport_Allow: - out = "Allow"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpRuntimeAddOnContentInstall(u8 val) -{ - char *out = NULL; - - switch(val) - { - case RuntimeAddOnContentInstall_Deny: - out = "Deny"; - break; - case RuntimeAddOnContentInstall_AllowAppend: - out = "AllowAppend"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpRuntimeParameterDelivery(u8 val) -{ - char *out = NULL; - - switch(val) - { - case RuntimeParameterDelivery_Always: - out = "Always"; - break; - case RuntimeParameterDelivery_AlwaysIfUserStateMatched: - out = "AlwaysIfUserStateMatched"; - break; - case RuntimeParameterDelivery_OnRestart: - out = "OnRestart"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpPlayLogQueryCapability(u8 val) -{ - char *out = NULL; - - switch(val) - { - case PlayLogQueryCapability_None: - out = "None"; - break; - case PlayLogQueryCapability_WhiteList: - out = "WhiteList"; - break; - case PlayLogQueryCapability_All: - out = "All"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpJitConfigurationFlag(u64 val) -{ - char *out = NULL; - - switch(val) - { - case JitConfigurationFlag_None: - out = "None"; - break; - case JitConfigurationFlag_Enabled: - out = "Enabled"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !out_nacp_xml || !out_nacp_xml_size || !out_nacp_icons_ctx || !out_nacp_icons_ctx_cnt) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate NACP XML!", __func__); - return false; - } - - u64 entryOffset = 0; - romfs_file *entry = NULL; - bool found_nacp = false, success = false; - - nacp_t controlNacp; - char *nacpXml = NULL; - - u8 i = 0, j = 0; - char tmp[NAME_BUF_LEN] = {'\0'}; - u32 flag; - - u8 nacpIconCnt = 0; - nacp_icons_ctx *nacpIcons = NULL; - - bool found_icon = false; - u8 languageIconHash[SHA256_HASH_SIZE]; - char languageIconHashStr[SHA256_HASH_SIZE + 1] = {'\0'}; - - char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; - convertDataToHexString(ncaId->c, 0x10, ncaIdStr, 0x21); - - char dataStr[100] = {'\0'}; - - u8 null_key[0x10]; - memset(null_key, 0, 0x10); - - bool availableSGC = false, availableRGC = false; - - if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; - - // Look for the control.nacp file - while(entryOffset < romFsContext.romfs_filetable_size) - { - entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); - - if (entry->parent == 0 && entry->nameLen == 12 && !strncasecmp((char*)entry->name, "control.nacp", 12)) - { - found_nacp = true; - break; - } - - entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); - } - - if (!found_nacp) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"control.nacp\" file in Control NCA RomFS section!", __func__); - goto out; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, &controlNacp, sizeof(nacp_t), false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"control.nacp\" from RomFS section in Control NCA!", __func__); - goto out; - } - - // Make sure that the output buffer for our NACP XML is big enough - nacpXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); - if (!nacpXml) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP XML!", __func__); - goto out; - } - - sprintf(nacpXml, "\n" \ - "\n"); - - for(i = 0; i < 0x10; i++) - { - if (strlen(controlNacp.titles[i].name) || strlen(controlNacp.titles[i].publisher)) - { - sprintf(tmp, " \n" \ - " <Language>%s</Language>\n" \ - " <Name>%s</Name>\n" \ - " <Publisher>%s</Publisher>\n" \ - " \n", \ - getNacpLangName(i), \ - controlNacp.titles[i].name, \ - controlNacp.titles[i].publisher); - - strcat(nacpXml, tmp); - } - } - - if (strlen(controlNacp.isbn)) - { - sprintf(tmp, " %s\n", controlNacp.isbn); - strcat(nacpXml, tmp); - } else { - strcat(nacpXml, " \n"); - } - - sprintf(tmp, " %s\n", getNacpStartupUserAccount(controlNacp.startup_user_account)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpUserAccountSwitchLock(controlNacp.user_account_switch_lock)); - strcat(nacpXml, tmp); - - strcat(nacpXml, " "); - - memcpy(&flag, &(controlNacp.parental_control_flag), sizeof(u32)); - if (flag != 0) - { - if (controlNacp.parental_control_flag.ParentalControlFlag_FreeCommunication) strcat(nacpXml, "FreeCommunication"); - } else { - strcat(nacpXml, "None"); - } - - strcat(nacpXml, "\n"); - - for(i = 0; i < 0x10; i++) - { - char *str = getNacpSupportedLanguageFlag(&(controlNacp.supported_language_flag), i); - if (!str) continue; - - sprintf(tmp, " %s\n", str); - strcat(nacpXml, tmp); - - nacpIconCnt++; - } - - sprintf(tmp, " %s\n", getNacpScreenshot(controlNacp.screenshot)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpVideoCapture(controlNacp.video_capture)); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.presence_group_id); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", controlNacp.display_version); - strcat(nacpXml, tmp); - - for(i = 0; i < 0x20; i++) - { - u8 *ptr = ((u8*)(&(controlNacp.rating_ages)) + i); - if (*ptr == 0xFF) continue; - - sprintf(tmp, " \n" \ - " %s\n" \ - " %u\n" \ - " \n", \ - getNacpRatingAgeOrganization(i), \ - *ptr); - - strcat(nacpXml, tmp); - } - - sprintf(tmp, " %s\n", getNacpDataLossConfirmation(controlNacp.data_loss_confirmation)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpPlayLogPolicy(controlNacp.play_log_policy)); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.save_data_owner_id); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_journal_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_journal_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.bcat_delivery_cache_storage_size); - strcat(nacpXml, tmp); - - if (strlen(controlNacp.application_error_code_category)) - { - sprintf(tmp, " %s\n", controlNacp.application_error_code_category); - strcat(nacpXml, tmp); - } else { - strcat(nacpXml, " \n"); - } - - sprintf(tmp, " 0x%016lx\n", controlNacp.add_on_content_base_id); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpLogoType(controlNacp.logo_type)); - strcat(nacpXml, tmp); - - for(i = 0; i < 0x8; i++) - { - if (controlNacp.local_communication_ids[i] != 0) - { - sprintf(tmp, " 0x%016lx\n", controlNacp.local_communication_ids[i]); - strcat(nacpXml, tmp); - } - } - - sprintf(tmp, " %s\n", getNacpLogoHandling(controlNacp.logo_handling)); - strcat(nacpXml, tmp); - - if (nacpIconCnt) - { - nacpIcons = calloc(nacpIconCnt, sizeof(nacp_icons_ctx)); - if (!nacpIcons) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP icons!", __func__); - goto out; - } - - for(i = 0; i < 0x10; i++) - { - char *str = getNacpSupportedLanguageFlag(&(controlNacp.supported_language_flag), i); - if (!str) continue; - - // Retrieve the icon file for this language and calculate its SHA-256 checksum - found_icon = false; - - memset(languageIconHash, 0, SHA256_HASH_SIZE); - memset(languageIconHashStr, 0, SHA256_HASH_SIZE + 1); - - entryOffset = 0; - sprintf(tmp, "icon_%s.dat", getNacpLangName(i)); - - while(entryOffset < romFsContext.romfs_filetable_size) - { - entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); - - if (entry->parent == 0 && entry->nameLen == strlen(tmp) && !strncasecmp((char*)entry->name, tmp, strlen(tmp)) && entry->dataSize <= 0x20000) - { - found_icon = true; - break; - } - - entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); - } - - if (!found_icon) - { - nacpIconCnt--; - continue; - } - - strcat(nacpXml, " \n"); - - sprintf(tmp, " %s\n", getNacpLangName(i)); - strcat(nacpXml, tmp); - - // Fill details for our NACP icon context - sprintf(nacpIcons[j].filename, "%s.nx.%s.jpg", ncaIdStr, getNacpLangName(i)); // Temporary, the NCA ID is subject to change - nacpIcons[j].icon_size = entry->dataSize; - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, nacpIcons[j].icon_data, nacpIcons[j].icon_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"%s\" from RomFS section in Control NCA!", __func__, tmp); - goto out; - } - - sha256CalculateHash(languageIconHash, nacpIcons[j].icon_data, nacpIcons[j].icon_size); - - // Only retrieve the first half from the SHA-256 checksum - convertDataToHexString(languageIconHash, SHA256_HASH_SIZE / 2, languageIconHashStr, SHA256_HASH_SIZE + 1); - - // Now print the hash - sprintf(tmp, " %s\n", languageIconHashStr); - strcat(nacpXml, tmp); - - strcat(nacpXml, " \n"); - - j++; - } - } - - sprintf(tmp, " 0x%016lx\n", controlNacp.seed_for_pseudo_device_id); - strcat(nacpXml, tmp); - - if (strlen(controlNacp.bcat_passphrase)) - { - sprintf(tmp, " %s\n", controlNacp.bcat_passphrase); - strcat(nacpXml, tmp); - } else { - strcat(nacpXml, " \n"); - } - - strcat(nacpXml, " "); - - if (*((u8*)&(controlNacp.startup_user_account_option)) != 0) - { - if (controlNacp.startup_user_account_option.StartupUserAccountOptionFlag_IsOptional) strcat(nacpXml, "IsOptional"); - } else { - strcat(nacpXml, "None"); - } - - strcat(nacpXml, "\n"); - - sprintf(tmp, " %s\n", getNacpAddOnContentRegistrationType(controlNacp.add_on_content_registration_type)); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_size_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.user_account_save_data_journal_size_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_size_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.device_save_data_journal_size_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.temporary_storage_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_journal_size); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.cache_storage_data_and_journal_size_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%04x\n", controlNacp.cache_storage_index_max); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpHdcp(controlNacp.hdcp)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpCrashReport(controlNacp.crash_report)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpRuntimeAddOnContentInstall(controlNacp.runtime_add_on_content_install)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpRuntimeParameterDelivery(controlNacp.runtime_parameter_delivery)); - strcat(nacpXml, tmp); - - for(i = 0; i < 0x10; i++) - { - if (controlNacp.play_log_queryable_application_ids[i] != 0) - { - sprintf(tmp, " 0x%016lx\n", controlNacp.play_log_queryable_application_ids[i]); - strcat(nacpXml, tmp); - } - } - - sprintf(tmp, " %s\n", getNacpPlayLogQueryCapability(controlNacp.play_log_query_capability)); - strcat(nacpXml, tmp); - - strcat(nacpXml, " "); - - if (*((u8*)&(controlNacp.repair_flag)) != 0) - { - if (controlNacp.repair_flag.RepairFlag_SuppressGameCardAccess) strcat(nacpXml, "SuppressGameCardAccess"); - } else { - strcat(nacpXml, "None"); - } - - strcat(nacpXml, "\n"); - - strcat(nacpXml, " "); - - memcpy(&flag, &(controlNacp.attribute_flag), sizeof(u32)); - if (flag != 0) - { - if (controlNacp.attribute_flag.AttributeFlag_Demo) strcat(nacpXml, "Demo"); - - if (controlNacp.attribute_flag.AttributeFlag_RetailInteractiveDisplay) - { - if (controlNacp.attribute_flag.AttributeFlag_Demo) strcat(nacpXml, ","); - strcat(nacpXml, "RetailInteractiveDisplay"); - } - } else { - strcat(nacpXml, "None"); - } - - strcat(nacpXml, "\n"); - - sprintf(tmp, " %u\n", controlNacp.program_index); - strcat(nacpXml, tmp); - - strcat(nacpXml, " "); - - if (*((u8*)&(controlNacp.required_network_service_license_on_launch_flag)) != 0) - { - if (controlNacp.required_network_service_license_on_launch_flag.RequiredNetworkServiceLicenseOnLaunchFlag_Common) strcat(nacpXml, "Common"); - } else { - strcat(nacpXml, "None"); - } - - strcat(nacpXml, "\n"); - - // Check if we actually have valid NeighborDetectionClientConfiguration values - availableSGC = (controlNacp.neighbor_detection_client_configuration.send_group_configuration.group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.send_group_configuration.key, null_key, 0x10) != 0); - - for(i = 0; i < 0x10; i++) - { - if (controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, null_key, 0x10) != 0) - { - availableRGC = true; - break; - } - } - - if (availableSGC || availableRGC) - { - strcat(nacpXml, " \n"); - - if (availableSGC) - { - convertDataToHexString(controlNacp.neighbor_detection_client_configuration.send_group_configuration.key, 0x10, dataStr, 100); - - sprintf(tmp, " \n" \ - " 0x%016lx\n" \ - " %s\n" \ - " \n", \ - controlNacp.neighbor_detection_client_configuration.send_group_configuration.group_id, \ - dataStr); - - strcat(nacpXml, tmp); - } - - if (availableRGC) - { - for(i = 0; i < 0x10; i++) - { - if (controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id != 0 && memcmp(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, null_key, 0x10) != 0) - { - convertDataToHexString(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, 0x10, dataStr, 100); - - sprintf(tmp, " \n" \ - " 0x%016lx\n" \ - " %s\n" \ - " \n", \ - controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id, \ - dataStr); - - strcat(nacpXml, tmp); - } - } - } - - strcat(nacpXml, " \n"); - } - - sprintf(tmp, " \n" \ - " %s\n" \ - " 0x%016lx\n" \ - " \n", \ - getNacpJitConfigurationFlag(controlNacp.jit_configuration.jit_configuration_flag), \ - controlNacp.jit_configuration.memory_size); - - strcat(nacpXml, tmp); - - strcat(nacpXml, ""); - - *out_nacp_xml = nacpXml; - *out_nacp_xml_size = strlen(nacpXml); - - if (nacpIconCnt) - { - *out_nacp_icons_ctx = nacpIcons; - *out_nacp_icons_ctx_cnt = nacpIconCnt; - } - - success = true; - -out: - if (!success) - { - if (nacpIcons != NULL) free(nacpIcons); - - if (nacpXml != NULL) free(nacpXml); - } - - // Manually free these pointers - // Calling freeRomFsContext() would also close the ncmStorage handle - free(romFsContext.romfs_dir_entries); - romFsContext.romfs_dir_entries = NULL; - - free(romFsContext.romfs_file_entries); - romFsContext.romfs_file_entries = NULL; - - return success; -} - -bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize) -{ - if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !outBuf) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve \"legalinfo.xml\"!", __func__); - return false; - } - - u64 entryOffset = 0; - romfs_file *entry = NULL; - bool found_legalinfo = false, success = false; - - u64 legalInfoXmlSize = 0; - char *legalInfoXml = NULL; - - if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; - - // Look for the legalinfo.xml file - while(entryOffset < romFsContext.romfs_filetable_size) - { - entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); - - if (entry->parent == 0 && entry->nameLen == 13 && !strncasecmp((char*)entry->name, "legalinfo.xml", 13)) - { - found_legalinfo = true; - break; - } - - entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); - } - - if (!found_legalinfo) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"legalinfo.xml\" file in Manual NCA RomFS section!", __func__); - goto out; - } - - // Allocate memory for the legalinfo.xml contents - legalInfoXmlSize = entry->dataSize; - legalInfoXml = calloc(legalInfoXmlSize, sizeof(char)); - if (!legalInfoXml) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"legalinfo.xml\" contents!", __func__); - goto out; - } - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, legalInfoXml, legalInfoXmlSize, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"legalinfo.xml\" from RomFS section in Manual NCA!", __func__); - goto out; - } - - *outBuf = legalInfoXml; - *outBufSize = legalInfoXmlSize; - - success = true; - -out: - if (!success && legalInfoXml != NULL) free(legalInfoXml); - - // Manually free these pointers - // Calling freeRomFsContext() would also close the ncmStorage handle - free(romFsContext.romfs_dir_entries); - romFsContext.romfs_dir_entries = NULL; - - free(romFsContext.romfs_file_entries); - romFsContext.romfs_file_entries = NULL; - - return success; -} diff --git a/source/nca.h b/source/nca.h index 6e43f0b..fc1a67c 100644 --- a/source/nca.h +++ b/source/nca.h @@ -5,757 +5,333 @@ #include -#define NCA3_MAGIC (u32)0x4E434133 // "NCA3" -#define NCA2_MAGIC (u32)0x4E434132 // "NCA2" - #define NCA_HEADER_LENGTH 0x400 -#define NCA_SECTION_HEADER_LENGTH 0x200 -#define NCA_SECTION_HEADER_CNT 4 -#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT)) +#define NCA_FS_HEADER_LENGTH 0x200 +#define NCA_FS_HEADER_COUNT 4 +#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_FS_HEADER_LENGTH * NCA_FS_HEADER_COUNT)) + +#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0" */ +#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */ +#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */ + +#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */ + +#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */ + +#define NCA_FS_ENTRY_BLOCK_SIZE 0x200 #define NCA_AES_XTS_SECTOR_SIZE 0x200 -#define NCA_KEY_AREA_KEY_CNT 4 -#define NCA_KEY_AREA_KEY_SIZE 0x10 -#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE) - -#define NCA_FS_HEADER_PARTITION_PFS0 0x01 -#define NCA_FS_HEADER_FSTYPE_PFS0 0x02 - -#define NCA_FS_HEADER_PARTITION_ROMFS 0x00 -#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03 - -#define NCA_FS_HEADER_CRYPT_NONE 0x01 -#define NCA_FS_HEADER_CRYPT_XTS 0x02 -#define NCA_FS_HEADER_CRYPT_CTR 0x03 -#define NCA_FS_HEADER_CRYPT_BKTR 0x04 - -#define PFS0_MAGIC (u32)0x50465330 // "PFS0" - -#define IVFC_MAGIC (u32)0x49564643 // "IVFC" -#define IVFC_MAX_LEVEL 6 - -#define BKTR_MAGIC (u32)0x424B5452 // "BKTR" - -#define ROMFS_HEADER_SIZE 0x50 -#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF - -#define ROMFS_NONAME_DIRENTRY_SIZE 0x18 -#define ROMFS_NONAME_FILEENTRY_SIZE 0x20 - -#define ROMFS_ENTRY_DIR 1 -#define ROMFS_ENTRY_FILE 2 - -#define META_MAGIC (u32)0x4D455441 // "META" - -#define NPDM_SIGNATURE_SIZE 0x100 -#define NPDM_SIGNATURE_AREA_SIZE 0x200 - -#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator -#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator -#define NSP_PROGRAM_XML_FILENAME_LENGTH 0x31 // NCA ID + ".programinfo.xml" + NULL terminator -#define NSP_NACP_XML_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator -#define NSP_LEGAL_XML_FILENAME_LENGTH 0x2F // NCA ID + ".legalinfo.xml" + NULL terminator -#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator -#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator - -#define ETICKET_ENTRY_SIZE 0x400 -#define ETICKET_TITLEKEY_OFFSET 0x180 -#define ETICKET_RIGHTSID_OFFSET 0x2A0 -#define ETICKET_UNKNOWN_FIELD_SIZE 0x140 -#define ETICKET_DATA_OFFSET 0x140 - -#define ETICKET_CA_CERT_SIZE 0x400 -#define ETICKET_XS_CERT_SIZE 0x300 - -#define ETICKET_TIK_FILE_SIZE (ETICKET_ENTRY_SIZE - 0x140) -#define ETICKET_CERT_FILE_SIZE (ETICKET_CA_CERT_SIZE + ETICKET_XS_CERT_SIZE) - -#define ETICKET_TITLEKEY_COMMON 0 -#define ETICKET_TITLEKEY_PERSONALIZED 1 +#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) typedef enum { - DUMP_APP_NSP = 0, - DUMP_PATCH_NSP, - DUMP_ADDON_NSP -} nspDumpType; + NcaVersion_Nca0 = 0, + NcaVersion_Nca2 = 1, + NcaVersion_Nca3 = 2 +} NcaVersion; + +typedef enum { + NcaDistributionType_Download = 0, + NcaDistributionType_GameCard = 1 +} NcaDistributionType; + +typedef enum { + NcaContentType_Program = 0, + NcaContentType_Meta = 1, + NcaContentType_Control = 2, + NcaContentType_Manual = 3, + NcaContentType_Data = 4, + NcaContentType_PublicData = 5 +} NcaContentType; + +typedef enum { + NcaKeyGenerationOld_100_230 = 0, + NcaKeyGenerationOld_300 = 2 +} NcaKeyGenerationOld; + +typedef enum { + NcaKeyAreaEncryptionKeyIndex_Application = 0, + NcaKeyAreaEncryptionKeyIndex_Ocean = 1, + NcaKeyAreaEncryptionKeyIndex_System = 2 +} NcaKeyAreaEncryptionKeyIndex; + +/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value. +typedef enum { + NcaKeyGeneration_301_302 = 3, + NcaKeyGeneration_400_410 = 4, + NcaKeyGeneration_500_510 = 5, + NcaKeyGeneration_600_610 = 6, + NcaKeyGeneration_620 = 7, + NcaKeyGeneration_700_801 = 8, + NcaKeyGeneration_810_811 = 9, + NcaKeyGeneration_900_901 = 10, + NcaKeyGeneration_910_920 = 11, + NcaKeyGeneration_Latest = NcaKeyGeneration_910_920 +} NcaKeyGeneration; typedef struct { - u32 magic; - u32 file_cnt; - u32 str_table_size; - u32 reserved; -} PACKED pfs0_header; + u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. + u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. + u8 enable_entry; + u8 reserved[0x7]; +} NcaFsEntry; typedef struct { - u64 file_offset; - u64 file_size; - u32 filename_offset; - u32 reserved; -} PACKED pfs0_file_entry; + u8 hash[SHA256_HASH_SIZE]; +} NcaFsHash; typedef struct { - u32 media_start_offset; - u32 media_end_offset; - u8 _0x8[0x8]; /* Padding. */ -} PACKED nca_section_entry_t; + u8 key[0x10]; +} NcaEncryptedKey; -typedef struct { - u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */ - u32 block_size; /* In bytes. */ - u32 always_2; - u64 hash_table_offset; /* Normally zero. */ - u64 hash_table_size; - u64 pfs0_offset; - u64 pfs0_size; - u8 _0x48[0xF0]; -} PACKED pfs0_superblock_t; +typedef enum { + NcaFsType_RomFs = 0, + NcaFsType_PartitionFs = 1 +} NcaFsType; -typedef struct { - u64 logical_offset; - u64 hash_data_size; - u32 block_size; - u32 reserved; -} PACKED ivfc_level_hdr_t; +typedef enum { + NcaHashType_Auto = 0, + NcaHashType_None = 1, + NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs. + NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs. +} NcaHashType; -typedef struct { - u32 magic; - u32 id; - u32 master_hash_size; - u32 num_levels; - ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL]; - u8 _0xA0[0x20]; - u8 master_hash[0x20]; -} PACKED ivfc_hdr_t; - -typedef struct { - ivfc_hdr_t ivfc_header; - u8 _0xE0[0x58]; -} PACKED romfs_superblock_t; +typedef enum { + NcaEncryptionType_Auto = 0, + NcaEncryptionType_None = 1, + NcaEncryptionType_AesXts = 2, + NcaEncryptionType_AesCtr = 3, + NcaEncryptionType_AesCtrEx = 4 +} NcaEncryptionType; typedef struct { u64 offset; u64 size; - u32 magic; /* "BKTR" */ - u32 _0x14; /* Version? */ - u32 num_entries; - u32 _0x1C; /* Reserved? */ -} PACKED bktr_header_t; +} NcaHierarchicalSha256LayerInfo; + +/// Used for NcaFsType_PartitionFs and NCA0 RomFS. +typedef struct { + u8 master_hash[SHA256_HASH_SIZE]; + u32 hash_block_size; + u32 layer_count; + NcaHierarchicalSha256LayerInfo hash_data_layer_info; + NcaHierarchicalSha256LayerInfo hash_target_layer_info; +} NcaHierarchicalSha256; typedef struct { - ivfc_hdr_t ivfc_header; - u8 _0xE0[0x18]; - bktr_header_t relocation_header; - bktr_header_t subsection_header; -} PACKED bktr_superblock_t; + u64 offset; + u64 size; + u32 block_size; ///< Use NCA_IVFC_CALC_BLOCK_SIZE to calculate the actual block size using this value. + u8 reserved[0x4]; +} NcaHierarchicalIntegrityLayerInfo; -/* NCA FS header. */ +/// Used for NcaFsType_RomFs. typedef struct { - u8 _0x0; - u8 _0x1; - u8 partition_type; - u8 fs_type; - u8 crypt_type; - u8 _0x5[0x3]; - union { /* FS-specific superblock. Size = 0x138. */ - pfs0_superblock_t pfs0_superblock; - romfs_superblock_t romfs_superblock; - bktr_superblock_t bktr_superblock; + u32 magic; ///< "IVFC". + u32 version; + u32 master_hash_size; + u32 layer_count; + NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[5]; + NcaHierarchicalIntegrityLayerInfo hash_target_layer_info; + u8 signature_salt[0x20]; + u8 master_hash[0x20]; +} NcaHierarchicalIntegrity; + +typedef struct { + union { + struct { + ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs). + NcaHierarchicalSha256 hierarchical_sha256; + u8 reserved_1[0xB0]; + }; + struct { + ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs). + NcaHierarchicalIntegrity hierarchical_integrity; + u8 reserved_2[0x18]; + }; }; +} NcaHashInfo; + +typedef struct { + u32 magic; ///< "BKTR". + u32 bucket_count; + u32 entry_count; + u8 reserved[0x4]; +} NcaBucketTreeHeader; + +/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs). +typedef struct { + u64 indirect_offset; + u64 indirect_size; + NcaBucketTreeHeader indirect_header; + u64 aes_ctr_ex_offset; + u64 aes_ctr_ex_size; + NcaBucketTreeHeader aes_ctr_ex_header; +} NcaPatchInfo; + +/// Format unknown. +typedef struct { + u8 unknown[0x30]; +} NcaSparseInfo; + +typedef struct { + u16 version; + u8 fs_type; ///< NcaFsType. + u8 hash_type; ///< NcaHashType. + u8 encryption_type; ///< NcaEncryptionType. + u8 reserved_1[0x3]; + NcaHashInfo hash_info; + NcaPatchInfo patch_info; union { u8 section_ctr[0x8]; struct { - u32 section_ctr_low; - u32 section_ctr_high; + u32 generation; + u32 secure_value; }; }; - u8 _0x148[0xB8]; /* Padding. */ -} PACKED nca_fs_header_t; + NcaSparseInfo sparse_info; + u8 reserved_2[0x88]; +} NcaFsHeader; -/* Nintendo content archive header. */ typedef struct { - u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */ - u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */ - u32 magic; - u8 distribution; /* System vs gamecard. */ - u8 content_type; - u8 crypto_type; /* Which keyblob (field 1) */ - u8 kaek_ind; /* Which kaek index? */ - u64 nca_size; /* Entire archive size. */ - u64 title_id; - u8 _0x218[0x4]; /* Padding. */ + u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key. + u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM. + u32 magic; ///< "NCA0" / "NCA2" / "NCA3". + u8 distribution_type; ///< NcaDistributionType. + u8 content_type; ///< NcaContentType. + u8 key_generation_old; ///< NcaKeyGenerationOld. + u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex. + u64 content_size; + u64 program_id; + u32 content_index; union { - u32 sdk_version; /* What SDK was this built with? */ + u32 sdk_addon_version; struct { - u8 sdk_revision; - u8 sdk_micro; - u8 sdk_minor; - u8 sdk_major; + u8 sdk_addon_revision; + u8 sdk_addon_micro; + u8 sdk_addon_minor; + u8 sdk_addon_major; }; }; - u8 crypto_type2; /* Which keyblob (field 2) */ - u8 fixed_key_generation; - u8 _0x222[0xE]; /* Padding. */ - u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */ - nca_section_entry_t section_entries[4]; /* Section entry metadata. */ - u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */ - u8 nca_keys[4][0x10]; /* Key area (encrypted) */ - u8 _0x340[0xC0]; /* Padding. */ - nca_fs_header_t fs_headers[4]; /* FS section headers. */ -} PACKED nca_header_t; + u8 key_generation; ///< NcaKeyGeneration. + u8 main_signature_key_generation; + u8 reserved_1[0xE]; + FsRightsId rights_id; ///< Used for titlekey crypto. + NcaFsEntry fs_entries[4]; ///< Start and end offsets for each NCA FS section. + NcaFsHash fs_hashes[4]; ///< SHA-256 hashes calculated over each NCA FS section header. + NcaEncryptedKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted. + u8 reserved_2[0xC0]; + NcaFsHeader fs_headers[4]; /// NCA FS section headers. +} NcaHeader; -typedef struct { - u32 magic; - u32 _0x4; - u32 _0x8; - u8 mmu_flags; - u8 _0xD; - u8 main_thread_prio; - u8 default_cpuid; - u64 _0x10; - u32 process_category; - u32 main_stack_size; - char title_name[0x50]; - u32 aci0_offset; - u32 aci0_size; - u32 acid_offset; - u32 acid_size; -} PACKED npdm_t; -typedef struct { - u64 title_id; - u32 version; - u8 type; - u8 unk1; - u16 extended_header_size; - u16 content_cnt; - u16 content_meta_cnt; - u8 attr; - u8 unk2[0x03]; - u32 required_dl_sysver; - u8 unk3[0x04]; -} PACKED cnmt_header; -typedef struct { - u64 patch_tid; /* Patch TID / Application TID */ - u32 min_sysver; /* Minimum system/application version */ - u32 min_appver; /* Minimum application version (only for base applications) */ -} PACKED cnmt_extended_header; -typedef struct { - u8 hash[0x20]; - u8 nca_id[0x10]; - u8 size[6]; - u8 type; - u8 id_offset; -} PACKED cnmt_content_record; -typedef struct { - u8 type; - u64 title_id; - u32 version; - u32 required_dl_sysver; - u32 nca_cnt; - u8 digest[SHA256_HASH_SIZE]; - char digest_str[(SHA256_HASH_SIZE * 2) + 1]; - u8 min_keyblob; - u32 min_sysver; - u64 patch_tid; - u32 min_appver; -} cnmt_xml_program_info; -typedef struct { - u8 type; - u8 nca_id[SHA256_HASH_SIZE / 2]; - char nca_id_str[SHA256_HASH_SIZE + 1]; - u64 size; - u8 hash[SHA256_HASH_SIZE]; - char hash_str[(SHA256_HASH_SIZE * 2) + 1]; - u8 keyblob; - u8 id_offset; - u64 cnt_record_offset; // Relative to the start of the content records section in the CNMT - u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE]; - u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH]; -} cnmt_xml_content_info; -typedef struct { - u32 nca_index; - u8 *hash_table; - u64 hash_table_offset; // Relative to NCA start - u64 hash_table_size; - u8 block_mod_cnt; - u8 *block_data[2]; - u64 block_offset[2]; // Relative to NCA start - u64 block_size[2]; -} nca_program_mod_data; -typedef struct { - char filename[100]; - u64 icon_size; - u8 icon_data[0x20000]; -} nacp_icons_ctx; -typedef struct { - u32 nca_index; - u64 xml_size; - char *xml_data; - u8 nacp_icon_cnt; // Only used with Control NCAs - nacp_icons_ctx *nacp_icons; // Only used with Control NCAs -} xml_record_info; - -typedef struct { - u64 section_offset; // Relative to NCA start - u64 section_size; - u64 hash_table_offset; // Relative to NCA start - u64 hash_block_size; - u32 hash_block_cnt; - u64 pfs0_offset; // Relative to NCA start - u64 pfs0_size; - u64 title_cnmt_offset; // Relative to NCA start - u64 title_cnmt_size; -} nca_cnmt_mod_data; - -typedef struct { - u32 sig_type; - u8 signature[0x100]; - u8 padding[0x3C]; - char sig_issuer[0x40]; - u8 titlekey_block[0x100]; - u8 unk1; - u8 titlekey_type; - u8 unk2[0x03]; - u8 master_key_rev; - u8 unk3[0x0A]; - u64 ticket_id; - u64 device_id; - u8 rights_id[0x10]; - u32 account_id; - u8 unk4[0x0C]; -} PACKED rsa2048_sha256_ticket; - -typedef struct { - bool has_rights_id; - u8 rights_id[0x10]; - char rights_id_str[33]; - char tik_filename[37]; - char cert_filename[38]; - u8 enc_titlekey[0x10]; - u8 dec_titlekey[0x10]; - u8 cert_data[ETICKET_CERT_FILE_SIZE]; - rsa2048_sha256_ticket tik_data; - bool retrieved_tik; - bool missing_tik; -} title_rights_ctx; - -typedef struct { - NcmStorageId storageId; - NcmContentStorage ncmStorage; - NcmContentId ncaId; - u8 idOffset; - Aes128CtrContext aes_ctx; - u64 exefs_offset; // Relative to NCA start - u64 exefs_size; - pfs0_header exefs_header; - u64 exefs_entries_offset; // Relative to NCA start - pfs0_file_entry *exefs_entries; - u64 exefs_str_table_offset; // Relative to NCA start - char *exefs_str_table; - u64 exefs_data_offset; // Relative to NCA start -} exefs_ctx_t; - -typedef struct { - NcmStorageId storageId; - NcmContentStorage ncmStorage; - NcmContentId ncaId; - u8 idOffset; - Aes128CtrContext aes_ctx; - u64 section_offset; // Relative to NCA start - u64 section_size; - u64 romfs_offset; // Relative to NCA start - u64 romfs_size; - u64 romfs_dirtable_offset; // Relative to NCA start - u64 romfs_dirtable_size; - romfs_dir *romfs_dir_entries; - u64 romfs_filetable_offset; // Relative to NCA start - u64 romfs_filetable_size; - romfs_file *romfs_file_entries; - u64 romfs_filedata_offset; // Relative to NCA start -} romfs_ctx_t; - -typedef struct { - u64 virt_offset; - u64 phys_offset; - u32 is_patch; -} PACKED bktr_relocation_entry_t; - -typedef struct { - u32 _0x0; - u32 num_entries; - u64 virtual_offset_end; - bktr_relocation_entry_t entries[0x3FF0 / sizeof(bktr_relocation_entry_t)]; - u8 padding[0x3FF0 % sizeof(bktr_relocation_entry_t)]; -} PACKED bktr_relocation_bucket_t; - -typedef struct { - u32 _0x0; - u32 num_buckets; - u64 total_size; - u64 bucket_virtual_offsets[0x3FF0 / sizeof(u64)]; - bktr_relocation_bucket_t buckets[]; -} PACKED bktr_relocation_block_t; typedef struct { u64 offset; - u32 _0x8; - u32 ctr_val; -} PACKED bktr_subsection_entry_t; - -typedef struct { - u32 _0x0; - u32 num_entries; - u64 physical_offset_end; - bktr_subsection_entry_t entries[0x3FF]; -} PACKED bktr_subsection_bucket_t; - -typedef struct { - u32 _0x0; - u32 num_buckets; - u64 total_size; - u64 bucket_physical_offsets[0x3FF0 / sizeof(u64)]; - bktr_subsection_bucket_t buckets[]; -} PACKED bktr_subsection_block_t; - -typedef struct { - NcmStorageId storageId; - NcmContentStorage ncmStorage; - NcmContentId ncaId; - u8 idOffset; - Aes128CtrContext aes_ctx; - u64 section_offset; // Relative to NCA start - u64 section_size; - bktr_superblock_t superblock; - bktr_relocation_block_t *relocation_block; - bktr_subsection_block_t *subsection_block; - u64 virtual_seek; // Relative to section start - u64 bktr_seek; // Relative to section start (patch BKTR section) - u64 base_seek; // Relative to section start (base application RomFS section) - u64 romfs_offset; // Relative to section start - u64 romfs_size; - u64 romfs_dirtable_offset; // Relative to section start - u64 romfs_dirtable_size; - romfs_dir *romfs_dir_entries; - u64 romfs_filetable_offset; // Relative to section start - u64 romfs_filetable_size; - romfs_file *romfs_file_entries; - u64 romfs_filedata_offset; // Relative to section start -} bktr_ctx_t; - -// Used in HFS0 / ExeFS / RomFS browsers -typedef struct { u64 size; - char sizeStr[32]; -} browser_entry_size_info; + u32 section_num; + NcaFsHeader *fs_header; + + + + + + u8 ctr; +} NcaFsContext; typedef struct { - u8 type; // 1 = Dir, 2 = File - u64 offset; // Relative to directory/file table, depending on type - browser_entry_size_info sizeInfo; // Only used if type == 2 -} romfs_browser_entry; + u8 storage_id; ///< NcmStorageId. + NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data. + u64 gc_secure_area_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. + NcmContentId id; ///< Also used to read NCA data. + char id_str[0x21]; + u8 hash[0x20]; + char hash_str[0x41]; + u8 format_version; ///< NcaVersion. + u8 type; ///< NcmContentType. Retrieved from NcmContentInfo. + u64 size; ///< Retrieved from NcmContentInfo. + u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. + u8 id_offset; ///< Retrieved from NcmContentInfo. + bool rights_id_available; + NcaHeader header; + bool dirty_header; + NcaEncryptedKey decrypted_keys[4]; + NcaFsContext fs_contexts[4]; +} NcaContext; -typedef struct { - char name[0x200]; - char publisher[0x100]; -} Title; +static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out) +{ + if (!size || !out) return; + *out = 0; + memcpy(out, size, 6); +} -typedef enum { - Language_AmericanEnglish = 0, - Language_BritishEnglish = 1, - Language_Japanese = 2, - Language_French = 3, - Language_German = 4, - Language_LatinAmericanSpanish = 5, - Language_Spanish = 6, - Language_Italian = 7, - Language_Dutch = 8, - Language_CanadianFrench = 9, - Language_Portuguese = 10, - Language_Russian = 11, - Language_Korean = 12, - Language_TraditionalChinese = 13, - Language_SimplifiedChinese = 14 -} Language; +static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out) +{ + if (!size || !out) return; + memcpy(out, size, 6); +} -typedef enum { - StartupUserAccount_None = 0, - StartupUserAccount_Required = 1, - StartupUserAccount_RequiredWithNetworkServiceAccountAvailable = 2 -} StartupUserAccount; -/* Introduced as of SDK 6.4.0 */ -typedef enum { - UserAccountSwitchLock_Disable = 0, - UserAccountSwitchLock_Enable = 1 -} UserAccountSwitchLock; -/* Introduced as of SDK 3.4.0 */ -typedef enum { - AddOnContentRegistrationType_AllOnLaunch = 0, - AddOnContentRegistrationType_OnDemand = 1 -} AddOnContentRegistrationType; +static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx) +{ + if (!ctx) return 0; + return (ctx->header.key_generation > ctx->header.key_generation_old ? ctx->header.key_generation : ctx->header.key_generation_old); +} -typedef struct { - u32 AttributeFlag_Demo : 1; - u32 AttributeFlag_RetailInteractiveDisplay : 1; /* Introduced as of SDK 3.4.0 */ -} AttributeFlag; +static inline void ncaSetDownloadDistributionType(NcaContext *ctx) +{ + if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; + ctx->header.distribution_type = NcaDistributionType_Download; + ctx->dirty_header = true; +} -typedef struct { - u32 SupportedLanguageFlag_AmericanEnglish : 1; - u32 SupportedLanguageFlag_BritishEnglish : 1; - u32 SupportedLanguageFlag_Japanese : 1; - u32 SupportedLanguageFlag_French : 1; - u32 SupportedLanguageFlag_German : 1; - u32 SupportedLanguageFlag_LatinAmericanSpanish : 1; - u32 SupportedLanguageFlag_Spanish : 1; - u32 SupportedLanguageFlag_Italian : 1; - u32 SupportedLanguageFlag_Dutch : 1; - u32 SupportedLanguageFlag_CanadianFrench : 1; - u32 SupportedLanguageFlag_Portuguese : 1; - u32 SupportedLanguageFlag_Russian : 1; - u32 SupportedLanguageFlag_Korean : 1; - u32 SupportedLanguageFlag_TraditionalChinese : 1; - u32 SupportedLanguageFlag_SimplifiedChinese : 1; -} SupportedLanguageFlag; +static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx) +{ + if (!ctx) return false; + + bool rights_id_available = false; + + for(u8 i = 0; i < 0x10; i++) + { + if (ctx->header.rights_id.c[i] != 0) + { + rights_id_available = true; + break; + } + } + + return rights_id_available; +} -typedef struct { - u32 ParentalControlFlag_FreeCommunication : 1; -} ParentalControlFlag; +static inline void ncaWipeRightsId(NcaContext *ctx) +{ + if (!ctx) return; + memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId)); + ctx->dirty_header = true; +} -typedef enum { - Screenshot_Allow = 0, - Screenshot_Deny = 1 -} Screenshot; -typedef enum { - VideoCapture_Disable = 0, - VideoCapture_Manual = 1, - VideoCapture_Enable = 2 -} VideoCapture; -typedef enum { - DataLossConfirmation_None = 0, - DataLossConfirmation_Required = 1 -} DataLossConfirmation; -typedef enum { - PlayLogPolicy_All = 0, - PlayLogPolicy_LogOnly = 1, - PlayLogPolicy_None = 2 -} PlayLogPolicy; +bool ncaDecryptKeyArea(NcaContext *nca_ctx); +bool ncaEncryptKeyArea(NcaContext *nca_ctx); -typedef enum { - RatingAgeOrganization_CERO = 0, - RatingAgeOrganization_GRACGCRB = 1, - RatingAgeOrganization_GSRMR = 2, - RatingAgeOrganization_ESRB = 3, - RatingAgeOrganization_ClassInd = 4, - RatingAgeOrganization_USK = 5, - RatingAgeOrganization_PEGI = 6, - RatingAgeOrganization_PEGIPortugal = 7, - RatingAgeOrganization_PEGIBBFC = 8, - RatingAgeOrganization_Russian = 9, - RatingAgeOrganization_ACB = 10, - RatingAgeOrganization_OFLC = 11, - RatingAgeOrganization_IARCGeneric = 12 /* Introduced as of SDK 9.3.1 */ -} RatingAgeOrganization; +bool ncaDecryptHeader(NcaContext *ctx); +bool ncaEncryptHeader(NcaContext *ctx); -typedef struct { - u8 cero; - u8 gracgcrb; - u8 gsrmr; - u8 esrb; - u8 class_ind; - u8 usk; - u8 pegi; - u8 pegi_portugal; - u8 pegibbfc; - u8 russian; - u8 acb; - u8 oflc; - u8 iarc_generic; - u8 reserved_1[0x13]; -} RatingAge; -typedef enum { - LogoType_LicensedByNintendo = 0, - LogoType_DistributedByNintendo = 1, /* Removed in SDK 3.5.2 */ - LogoType_Nintendo = 2 -} LogoType; -typedef enum { - LogoHandling_Auto = 0, - LogoHandling_Manual = 1 -} LogoHandling; -/* Introduced as of SDK 4.5.0 */ -typedef enum { - RuntimeAddOnContentInstall_Deny = 0, - RuntimeAddOnContentInstall_AllowAppend = 1 -} RuntimeAddOnContentInstall; -/* Introduced as of SDK 9.3.1 */ -typedef enum { - RuntimeParameterDelivery_Always = 0, - RuntimeParameterDelivery_AlwaysIfUserStateMatched = 1, - RuntimeParameterDelivery_OnRestart = 2 -} RuntimeParameterDelivery; -/* Introduced as of SDK 3.5.2 */ -typedef enum { - CrashReport_Deny = 0, - CrashReport_Allow = 1 -} CrashReport; -typedef enum { - Hdcp_None = 0, - Hdcp_Required = 1 -} Hdcp; - -/* Introduced as of SDK 7.6.0 */ -typedef struct { - u8 StartupUserAccountOptionFlag_IsOptional : 1; -} StartupUserAccountOptionFlag; - -/* Introduced as of SDK 5.3.0 */ -typedef enum { - PlayLogQueryCapability_None = 0, - PlayLogQueryCapability_WhiteList = 1, - PlayLogQueryCapability_All = 2 -} PlayLogQueryCapability; - -/* Introduced as of SDK 5.3.0 */ -typedef struct { - u8 RepairFlag_SuppressGameCardAccess : 1; -} RepairFlag; - -/* Introduced as of SDK 6.4.0 */ -typedef struct { - u8 RequiredNetworkServiceLicenseOnLaunchFlag_Common : 1; -} RequiredNetworkServiceLicenseOnLaunchFlag; - -typedef struct { - u64 group_id; - u8 key[0x10]; -} NeighborDetectionGroupConfiguration; - -typedef struct { - NeighborDetectionGroupConfiguration send_group_configuration; - NeighborDetectionGroupConfiguration receivable_group_configurations[0x10]; -} NeighborDetectionClientConfiguration; - -/* Introduced as of SDK 7.6.0 */ -typedef enum { - JitConfigurationFlag_None = 0, - JitConfigurationFlag_Enabled = 1 -} JitConfigurationFlag; - -typedef struct { - u64 jit_configuration_flag; - u64 memory_size; -} JitConfiguration; - -typedef struct { - Title titles[0x10]; - char isbn[0x25]; - u8 startup_user_account; - u8 user_account_switch_lock; /* Old: touch_screen_usage (None, Supported, Required) */ - u8 add_on_content_registration_type; - AttributeFlag attribute_flag; - SupportedLanguageFlag supported_language_flag; - ParentalControlFlag parental_control_flag; - u8 screenshot; - u8 video_capture; - u8 data_loss_confirmation; - u8 play_log_policy; - u64 presence_group_id; - RatingAge rating_ages; - char display_version[0x10]; - u64 add_on_content_base_id; - u64 save_data_owner_id; - u64 user_account_save_data_size; - u64 user_account_save_data_journal_size; - u64 device_save_data_size; - u64 device_save_data_journal_size; - u64 bcat_delivery_cache_storage_size; - char application_error_code_category[0x8]; - u64 local_communication_ids[0x8]; - u8 logo_type; - u8 logo_handling; - u8 runtime_add_on_content_install; - u8 runtime_parameter_delivery; - u8 reserved_1[0x2]; - u8 crash_report; - u8 hdcp; - u64 seed_for_pseudo_device_id; - char bcat_passphrase[0x41]; - StartupUserAccountOptionFlag startup_user_account_option; - u8 reserved_2[0x6]; - u64 user_account_save_data_size_max; - u64 user_account_save_data_journal_size_max; - u64 device_save_data_size_max; - u64 device_save_data_journal_size_max; - u64 temporary_storage_size; - u64 cache_storage_size; - u64 cache_storage_journal_size; - u64 cache_storage_data_and_journal_size_max; - u16 cache_storage_index_max; - u8 reserved_3[0x6]; - u64 play_log_queryable_application_ids[0x10]; - u8 play_log_query_capability; - RepairFlag repair_flag; - u8 program_index; - RequiredNetworkServiceLicenseOnLaunchFlag required_network_service_license_on_launch_flag; - u8 reserved_4[0x4]; - NeighborDetectionClientConfiguration neighbor_detection_client_configuration; - JitConfiguration jit_configuration; - u8 reserved_5[0xC40]; -} nacp_t; - -char *getContentType(u8 type); - -void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out); - -void convertNcaSizeToU64(const u8 size[0x6], u64 *out); - -void convertU64ToNcaSize(const u64 size, u8 out[0x6]); - -bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize); - -bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt); - -bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize); - -bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize); - -bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys, bool retrieveTitleKeyData); - -bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys); - -bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data **output, u32 *cur_mod_cnt, u32 idx); - -bool retrieveCnmtNcaData(NcmStorageId curStorageId, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info); - -bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod); - -bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); - -bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); - -bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); - -bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, bool useCustomAcidRsaPubKey, char **outBuf, u64 *outBufSize); - -bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt); - -bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize); - -#endif +#endif /* __NCA_H__ */ diff --git a/source/new/nca.c b/source/new/nca.c deleted file mode 100644 index 8285159..0000000 --- a/source/new/nca.c +++ /dev/null @@ -1,357 +0,0 @@ -#include -#include -#include - -#include "nca.h" -#include "keys.h" -#include "rsa.h" -#include "utils.h" - -static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { - 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, - 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB -}; - -static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); -static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); -static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); - -size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt) -{ - if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0) - { - LOGFILE("Invalid parameters!"); - return 0; - } - - size_t i, crypt_res = 0; - u64 cur_sector = sector; - - u8 *dst_u8 = (u8*)dst; - const u8 *src_u8 = (const u8*)src; - - for(i = 0; i < size; i += sector_size, cur_sector++) - { - /* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */ - aes128XtsContextResetSector(ctx, cur_sector, true); - - if (encrypt) - { - crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); - } else { - crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); - } - - if (crypt_res != sector_size) break; - } - - return i; -} - - - - - - - - - - - - - - - - - -bool ncaDecryptKeyArea(NcaContext *ctx) -{ - if (!ctx) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - Result rc = 0; - const u8 *kek_src = NULL; - u8 key_count, tmp_kek[0x10] = {0}; - - /* Check if we're dealing with a NCA0 with a plain text key area */ - if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) - { - memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40); - return true; - } - - kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index); - if (!kek_src) - { - LOGFILE("Unable to retrieve KAEK source for index 0x%02X!", ctx->header.kaek_index); - return false; - } - - rc = splCryptoGenerateAesKek(kek_src, ctx->key_generation, 0, tmp_kek); - if (R_FAILED(rc)) - { - LOGFILE("splCryptoGenerateAesKek failed! (0x%08X)", rc); - return false; - } - - key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); - - for(u8 i = 0; i < key_count; i++) - { - rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]); - if (R_FAILED(rc)) - { - LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc); - return false; - } - } - - return true; -} - -bool ncaEncryptKeyArea(NcaContext *ctx) -{ - if (!ctx) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - u8 key_count; - const u8 *kaek = NULL; - Aes128Context key_area_ctx = {0}; - - /* Check if we're dealing with a NCA0 with a plain text key area */ - if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) - { - memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40); - return true; - } - - kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index); - if (!kaek) - { - LOGFILE("Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X!", ctx->key_generation, ctx->header.kaek_index); - return false; - } - - key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); - - aes128ContextCreate(&key_area_ctx, kaek, true); - for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]); - - return true; -} - -bool ncaDecryptHeader(NcaContext *ctx) -{ - if (!ctx) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - u32 i, magic = 0; - size_t crypt_res = 0; - const u8 *header_key = NULL; - Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; - - header_key = keysGetNcaHeaderKey(); - - aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false); - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_HEADER_LENGTH) - { - LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res); - return false; - } - - magic = __builtin_bswap32(ctx->header.magic); - - switch(magic) - { - case NCA_NCA3_MAGIC: - ctx->format_version = NcaVersion_Nca3; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_FULL_HEADER_LENGTH) - { - LOGFILE("Error decrypting full NCA3 header!"); - return false; - } - - break; - case NCA_NCA2_MAGIC: - ctx->format_version = NcaVersion_Nca2; - - for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error decrypting NCA2 FS section header #%u!", i); - return false; - } - } - - break; - case NCA_NCA0_MAGIC: - ctx->format_version = NcaVersion_Nca0; - - /* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */ - if (!ncaDecryptKeyArea(ctx)) - { - LOGFILE("Error decrypting key area from NCA0 header!"); - return false; - } - - aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0], ctx->decrypted_keys[1], false); - - for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - - - - - - - } - - break; - default: - LOGFILE("Invalid NCA magic word! Wrong header key? (0x%08X)", magic); - return false; - } - - /* Fill additional context info */ - ctx->key_generation = ncaGetKeyGenerationValue(ctx); - ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx); - - return true; -} - -bool ncaEncryptHeader(NcaContext *ctx) -{ - if (!ctx) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - u32 i; - size_t crypt_res = 0; - const u8 *header_key = NULL; - Aes128XtsContext hdr_aes_ctx = {0}; - - header_key = keysGetNcaHeaderKey(); - - aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true); - - switch(ctx->format_version) - { - case NcaVersion_Nca3: - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_FULL_HEADER_LENGTH) - { - LOGFILE("Error encrypting full NCA3 header!"); - return false; - } - - break; - case NcaVersion_Nca2: - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_HEADER_LENGTH) - { - LOGFILE("Error encrypting partial NCA2 header!"); - return false; - } - - for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error encrypting NCA2 FS section header #%u!", i); - return false; - } - } - - break; - case NcaVersion_Nca0: - /* There's nothing else to do */ - break; - default: - LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version); - return false; - } - - return true; -} - - - - - - - - - - - - - - - -static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) -{ - if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; - - u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0}; - sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40); - - if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false; - - return true; -} - -static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) -{ - if (!ctr) return; - - offset >>= 4; - - for(u8 i = 0; i < 8; i++) - { - ctr[0x10 - i - 1] = (u8)(offset & 0xFF); - offset >>= 8; - } -} - -static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) -{ - if (!ctr) return; - - offset >>= 4; - - for(u8 i = 0; i < 8; i++) - { - ctr[0x10 - i - 1] = (u8)(offset & 0xFF); - offset >>= 8; - } - - for(u8 i = 0; i < 4; i++) - { - ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF); - ctr_val >>= 8; - } -} diff --git a/source/new/nca.h b/source/new/nca.h deleted file mode 100644 index 51c90e5..0000000 --- a/source/new/nca.h +++ /dev/null @@ -1,335 +0,0 @@ -#pragma once - -#ifndef __NCA_H__ -#define __NCA_H__ - -#define NCA_HEADER_LENGTH 0x400 -#define NCA_FS_HEADER_LENGTH 0x200 -#define NCA_FS_HEADER_COUNT 4 -#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT)) - -#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0" */ -#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */ -#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */ - -#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */ - -#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */ - -#define NCA_FS_ENTRY_BLOCK_SIZE 0x200 - -#define NCA_AES_XTS_SECTOR_SIZE 0x200 - -#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) - -typedef enum { - NcaVersion_Nca0 = 0, - NcaVersion_Nca2 = 1, - NcaVersion_Nca3 = 2 -} NcaVersion; - -typedef enum { - NcaDistributionType_Download = 0, - NcaDistributionType_GameCard = 1 -} NcaDistributionType; - -typedef enum { - NcaContentType_Program = 0, - NcaContentType_Meta = 1, - NcaContentType_Control = 2, - NcaContentType_Manual = 3, - NcaContentType_Data = 4, - NcaContentType_PublicData = 5 -} NcaContentType; - -typedef enum { - NcaKeyGenerationOld_100_230 = 0, - NcaKeyGenerationOld_300 = 2 -} NcaKeyGenerationOld; - -typedef enum { - NcaKeyAreaEncryptionKeyIndex_Application = 0, - NcaKeyAreaEncryptionKeyIndex_Ocean = 1, - NcaKeyAreaEncryptionKeyIndex_System = 2 -} NcaKeyAreaEncryptionKeyIndex; - -/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value. -typedef enum { - NcaKeyGeneration_301_302 = 3, - NcaKeyGeneration_400_410 = 4, - NcaKeyGeneration_500_510 = 5, - NcaKeyGeneration_600_610 = 6, - NcaKeyGeneration_620 = 7, - NcaKeyGeneration_700_801 = 8, - NcaKeyGeneration_810_811 = 9, - NcaKeyGeneration_900_901 = 10, - NcaKeyGeneration_910_920 = 11, - NcaKeyGeneration_Latest = NcaKeyGeneration_910_920 -} NcaKeyGeneration; - -typedef struct { - u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. - u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. - u8 enable_entry; - u8 reserved[0x7]; -} NcaFsEntry; - -typedef struct { - u8 hash[SHA256_HASH_SIZE]; -} NcaFsHash; - -typedef struct { - u8 key[0x10]; -} NcaEncryptedKey; - -typedef enum { - NcaFsType_RomFs = 0, - NcaFsType_PartitionFs = 1 -} NcaFsType; - -typedef enum { - NcaHashType_Auto = 0, - NcaHashType_None = 1, - NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs. - NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs. -} NcaHashType; - -typedef enum { - NcaEncryptionType_Auto = 0, - NcaEncryptionType_None = 1, - NcaEncryptionType_AesXts = 2, - NcaEncryptionType_AesCtr = 3, - NcaEncryptionType_AesCtrEx = 4 -} NcaEncryptionType; - -typedef struct { - u64 offset; - u64 size; -} NcaHierarchicalSha256LayerInfo; - -/// Used for NcaFsType_PartitionFs and NCA0 RomFS. -typedef struct { - u8 master_hash[SHA256_HASH_SIZE]; - u32 hash_block_size; - u32 layer_count; - NcaHierarchicalSha256LayerInfo hash_data_layer_info; - NcaHierarchicalSha256LayerInfo hash_target_layer_info; -} NcaHierarchicalSha256; - -typedef struct { - u64 offset; - u64 size; - u32 block_size; ///< Use NCA_IVFC_CALC_BLOCK_SIZE to calculate the actual block size using this value. - u8 reserved[0x4]; -} NcaHierarchicalIntegrityLayerInfo; - -/// Used for NcaFsType_RomFs. -typedef struct { - u32 magic; ///< "IVFC". - u32 version; - u32 master_hash_size; - u32 layer_count; - NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[5]; - NcaHierarchicalIntegrityLayerInfo hash_target_layer_info; - u8 signature_salt[0x20]; - u8 master_hash[0x20]; -} NcaHierarchicalIntegrity; - -typedef struct { - union { - struct { - ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs). - NcaHierarchicalSha256 hierarchical_sha256; - u8 reserved_1[0xB0]; - }; - struct { - ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs). - NcaHierarchicalIntegrity hierarchical_integrity; - u8 reserved_2[0x18]; - }; - }; -} NcaHashInfo; - -typedef struct { - u32 magic; ///< "BKTR". - u32 bucket_count; - u32 entry_count; - u8 reserved[0x4]; -} NcaBucketTreeHeader; - -/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs). -typedef struct { - u64 indirect_offset; - u64 indirect_size; - NcaBucketTreeHeader indirect_header; - u64 aes_ctr_ex_offset; - u64 aes_ctr_ex_size; - NcaBucketTreeHeader aes_ctr_ex_header; -} NcaPatchInfo; - -/// Format unknown. -typedef struct { - u8 unknown[0x30]; -} NcaSparseInfo; - -typedef struct { - u16 version; - u8 fs_type; ///< NcaFsType. - u8 hash_type; ///< NcaHashType. - u8 encryption_type; ///< NcaEncryptionType. - u8 reserved_1[0x3]; - NcaHashInfo hash_info; - NcaPatchInfo patch_info; - union { - u8 section_ctr[0x8]; - struct { - u32 generation; - u32 secure_value; - }; - }; - NcaSparseInfo sparse_info; - u8 reserved_2[0x88]; -} NcaFsHeader; - -typedef struct { - u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key. - u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM. - u32 magic; ///< "NCA0" / "NCA2" / "NCA3". - u8 distribution_type; ///< NcaDistributionType. - u8 content_type; ///< NcaContentType. - u8 key_generation_old; ///< NcaKeyGenerationOld. - u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex. - u64 content_size; - u64 program_id; - u32 content_index; - union { - u32 sdk_addon_version; - struct { - u8 sdk_addon_revision; - u8 sdk_addon_micro; - u8 sdk_addon_minor; - u8 sdk_addon_major; - }; - }; - u8 key_generation; ///< NcaKeyGeneration. - u8 main_signature_key_generation; - u8 reserved_1[0xE]; - FsRightsId rights_id; ///< Used for titlekey crypto. - NcaFsEntry fs_entries[4]; ///< Start and end offsets for each NCA FS section. - NcaFsHash fs_hashes[4]; ///< SHA-256 hashes calculated over each NCA FS section header. - NcaEncryptedKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted. - u8 reserved_2[0xC0]; - NcaFsHeader fs_headers[4]; /// NCA FS section headers. -} NcaHeader; - - - - - - - - - - -typedef struct { - u64 offset; - u64 size; - u32 section_num; - NcaFsHeader *fs_header; - - - - - - u8 ctr -} NcaFsContext; - -typedef struct { - u8 storage_id; ///< NcmStorageId. - NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data. - u64 gc_secure_area_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. - NcmContentId id; ///< Also used to read NCA data. - char id_str[0x21]; - u8 hash[0x20]; - char hash_str[0x41]; - u8 format_version; ///< NcaVersion. - u8 type; ///< NcmContentType. Retrieved from NcmContentInfo. - u64 size; ///< Retrieved from NcmContentInfo. - u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. - u8 id_offset; ///< Retrieved from NcmContentInfo. - bool rights_id_available; - NcaHeader header; - bool dirty_header; - NcaEncryptedKey decrypted_keys[4]; - NcaFsContext fs_contexts[4]; -} NcaContext; - -static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out) -{ - if (!size || !out) return; - *out = 0; - memcpy(out, size, 6); -} - -static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out) -{ - if (!size || !out) return; - memcpy(out, size, 6); -} - - - -static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx) -{ - if (!ctx) return 0; - return (ctx->header.key_generation > ctx->header.key_generation_old ? ctx->header.key_generation : ctx->header.key_generation_old); -} - -static inline void ncaSetDownloadDistributionType(NcaContext *ctx) -{ - if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; - ctx->header.distribution_type = NcaDistributionType_Download; - ctx->dirty_header = true; -} - -static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx) -{ - if (!ctx) return; - - bool rights_id_available = false; - - for(u8 i = 0; i < 0x10; i++) - { - if (ctx->header.rights_id.c[i] != 0) - { - rights_id_available = true; - break; - } - } - - return rights_id_available; -} - -static inline void ncaWipeRightsId(NcaContext *ctx) -{ - if (!ctx) return; - memset(ctx->header.rights_id, 0, sizeof(FsRightsId)); - ctx->dirty_header = true; -} - - - - -bool ncaDecryptKeyArea(NcaContext *nca_ctx); -bool ncaEncryptKeyArea(NcaContext *nca_ctx); - -bool ncaDecryptHeader(NcaContext *ctx); -bool ncaEncryptHeader(NcaContext *ctx); - - - - - - - -#endif /* __NCA_H__ */ diff --git a/source/new/rsa.c b/source/rsa.c similarity index 97% rename from source/new/rsa.c rename to source/rsa.c index 9da9991..d1ebbcb 100644 --- a/source/new/rsa.c +++ b/source/rsa.c @@ -9,7 +9,9 @@ #include "rsa.h" #include "utils.h" -/* Self-generated private key */ +/* Global variables. */ + +/// Self-generated private key. static const char g_rsa2048CustomAcidPrivateKey[] = "-----BEGIN RSA PRIVATE KEY-----\r\n" "MIIEowIBAAKCAQEAvVRzt+8mE7oE4RkmSh3ws4CGlBj7uhHkfwCpPFsn4TNVdLRo\r\n" "YYY17jQYWTtcOYPMcHxwUpgJyspGN8QGXEkJqY8jILv2eO0jBGtg7Br2afUBp6/x\r\n" @@ -38,7 +40,7 @@ static const char g_rsa2048CustomAcidPrivateKey[] = "-----BEGIN RSA PRIVATE KEY- "n5NEG+mY4WZaOFRNiZu8+4aJI1yycXMyA22iKcU8+nN/sMAJs3Nx\r\n" "-----END RSA PRIVATE KEY-----\r\n"; -/* Self-generated public key */ +/// Self-generated public key. static const u8 g_rsa2048CustomAcidPublicKey[] = { 0xBD, 0x54, 0x73, 0xB7, 0xEF, 0x26, 0x13, 0xBA, 0x04, 0xE1, 0x19, 0x26, 0x4A, 0x1D, 0xF0, 0xB3, 0x80, 0x86, 0x94, 0x18, 0xFB, 0xBA, 0x11, 0xE4, 0x7F, 0x00, 0xA9, 0x3C, 0x5B, 0x27, 0xE1, 0x33, @@ -58,6 +60,8 @@ static const u8 g_rsa2048CustomAcidPublicKey[] = { 0x6D, 0x5D, 0x5B, 0xD2, 0x55, 0x20, 0xB9, 0x1E, 0x59, 0x13, 0x3C, 0x17, 0xC2, 0x25, 0x56, 0xC7 }; +/* Function prototypes. */ + static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_src, size_t h_src_size); bool rsa2048GenerateSha256BasedCustomAcidSignature(void *dst, const void *src, size_t size) @@ -127,7 +131,7 @@ out: return success; } -const u8 rsa2048GetCustomAcidPublicKey(void) +const u8 *rsa2048GetCustomAcidPublicKey(void) { return g_rsa2048CustomAcidPublicKey; } @@ -205,6 +209,8 @@ static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_s u32 seed = 0; size_t i, offset = 0; + u8 *data_u8 = (u8*)data; + u8 mgf1_buf[SHA256_HASH_SIZE] = {0}; u8 h_buf[RSA2048_SIGNATURE_SIZE] = {0}; @@ -216,7 +222,7 @@ static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_s sha256CalculateHash(mgf1_buf, h_buf, h_src_size + 4); - for(i = offset; i < data_size && i < (offset + 0x20); i++) data[i] ^= mgf1_buf[i - offset]; + for(i = offset; i < data_size && i < (offset + 0x20); i++) data_u8[i] ^= mgf1_buf[i - offset]; seed++; offset += 0x20; diff --git a/source/new/rsa.h b/source/rsa.h similarity index 84% rename from source/new/rsa.h rename to source/rsa.h index 4112b55..d33a2c8 100644 --- a/source/new/rsa.h +++ b/source/rsa.h @@ -3,12 +3,12 @@ #ifndef __RSA_H__ #define __RSA_H__ -#include +#include #define RSA2048_SIGNATURE_SIZE 0x100 bool rsa2048GenerateSha256BasedCustomAcidSignature(void *dst, const void *src, size_t size); -const u8 rsa2048GetCustomAcidPublicKey(void); +const u8 *rsa2048GetCustomAcidPublicKey(void); bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size); diff --git a/source/new/save.c b/source/save.c similarity index 100% rename from source/new/save.c rename to source/save.c diff --git a/source/new/save.h b/source/save.h similarity index 100% rename from source/new/save.h rename to source/save.h diff --git a/source/new/service_guard.h b/source/service_guard.h similarity index 100% rename from source/new/service_guard.h rename to source/service_guard.h diff --git a/source/services.c b/source/services.c new file mode 100644 index 0000000..435fa94 --- /dev/null +++ b/source/services.c @@ -0,0 +1,233 @@ +#include +#include +#include + +#include "services.h" +#include "es.h" +#include "fspusb.h" +#include "utils.h" + +/* Type definitions. */ + +typedef bool (*ServiceCondFunction)(void *arg); /* Used to perform a runtime condition check (e.g. system version) before initializing the service */ +typedef Result (*ServiceInitFunction)(void); /* Used to initialize the service */ +typedef void (*ServiceCloseFunction)(void); /* Used to close the service */ + +typedef struct ServicesInfoEntry { + bool initialized; + char name[8]; + ServiceCondFunction cond_func; + ServiceInitFunction init_func; + ServiceCloseFunction close_func; +} ServicesInfoEntry; + +/* Function prototypes. */ + +static Result servicesNifmUserInitialize(void); +static bool servicesClkGetServiceType(void *arg); +static bool servicesSplCryptoCheckAvailability(void *arg); +static bool servicesFspUsbCheckAvailability(void *arg); + +/* Global variables. */ + +static ServicesInfoEntry g_serviceInfo[] = { + { false, "ncm", NULL, &ncmInitialize, &ncmExit }, + { false, "ns", NULL, &nsInitialize, &nsExit }, + { false, "csrng", NULL, &csrngInitialize, &csrngExit }, + { false, "spl", NULL, &splInitialize, &splExit }, + { false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice) */ + { false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit }, + { false, "pl", NULL, &plInitialize, &plExit }, + { false, "psm", NULL, &psmInitialize, &psmExit }, + { false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit }, + { false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst */ + { false, "fsp-usb", &servicesFspUsbCheckAvailability, &fspusbInitialize, &fspusbExit }, /* Checks if fsp-usb is really available */ + { false, "es", NULL, &esInitialize, &esExit }, + { false, "set:cal", NULL, &setcalInitialize, &setcalExit } +}; + +static const u32 g_serviceInfoCount = MAX_ELEMENTS(g_serviceInfo); + +static bool g_clkSvcUsePcv = false; +static ClkrstSession g_clkrstCpuSession = {0}, g_clkrstMemSession = {0}; + +bool servicesInitialize(void) +{ + Result rc = 0; + bool ret = true; + + for(u32 i = 0; i < g_serviceInfoCount; i++) + { + /* Check if this service has been already initialized */ + if (g_serviceInfo[i].initialized) continue; + + /* Check if this service depends on a condition function */ + if (g_serviceInfo[i].cond_func != NULL) + { + /* Run the condition function - it will update the current service member */ + /* Skip this service if the required conditions aren't met */ + if (!g_serviceInfo[i].cond_func(&(g_serviceInfo[i]))) continue; + } + + /* Check if this service has a valid initialize function */ + if (g_serviceInfo[i].init_func == NULL) continue; + + /* Initialize service */ + rc = g_serviceInfo[i].init_func(); + if (R_FAILED(rc)) + { + utilsConsoleErrorScreen("%s: failed to initialize %s service! (0x%08X)", __func__, g_serviceInfo[i].name, rc); + ret = false; + break; + } + + /* Update initialized flag */ + g_serviceInfo[i].initialized = true; + } + + return ret; +} + +void servicesClose(void) +{ + for(u32 i = 0; i < g_serviceInfoCount; i++) + { + /* Check if this service has not been initialized, or if it doesn't have a valid close function */ + if (!g_serviceInfo[i].initialized || g_serviceInfo[i].close_func == NULL) continue; + + /* Close service */ + g_serviceInfo[i].close_func(); + } +} + +bool servicesCheckRunningServiceByName(const char *name) +{ + if (!name || !strlen(name)) return false; + + Handle handle; + SmServiceName service_name = smEncodeName(name); + Result rc = smRegisterService(&handle, service_name, false, 1); + bool running = R_FAILED(rc); + + svcCloseHandle(handle); + + if (!running) smUnregisterService(service_name); + + return running; +} + +bool servicesCheckInitializedServiceByName(const char *name) +{ + if (!name || !strlen(name)) return false; + + bool ret = false; + size_t name_len = strlen(name); + + for(u32 i = 0; i < g_serviceInfoCount; i++) + { + if (!strncmp(g_serviceInfo[i].name, name, name_len)) + { + ret = g_serviceInfo[i].initialized; + break; + } + } + + return ret; +} + +void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate) +{ + if (g_clkSvcUsePcv) + { + pcvSetClockRate(PcvModule_CpuBus, cpu_rate); + pcvSetClockRate(PcvModule_EMC, mem_rate); + } else { + clkrstSetClockRate(&g_clkrstCpuSession, cpu_rate); + clkrstSetClockRate(&g_clkrstMemSession, mem_rate); + } +} + +static Result servicesNifmUserInitialize(void) +{ + return nifmInitialize(NifmServiceType_User); +} + +static Result servicesClkrstInitialize(void) +{ + Result rc = 0; + + /* Open clkrst service handle */ + rc = clkrstInitialize(); + if (R_FAILED(rc)) return rc; + + /* Initialize CPU and MEM clkrst sessions */ + memset(&g_clkrstCpuSession, 0, sizeof(ClkrstSession)); + memset(&g_clkrstMemSession, 0, sizeof(ClkrstSession)); + + rc = clkrstOpenSession(&g_clkrstCpuSession, PcvModuleId_CpuBus, 3); + if (R_FAILED(rc)) + { + clkrstExit(); + return rc; + } + + rc = clkrstOpenSession(&g_clkrstMemSession, PcvModuleId_EMC, 3); + if (R_FAILED(rc)) + { + clkrstCloseSession(&g_clkrstCpuSession); + clkrstExit(); + } + + return rc; +} + +static void servicesClkrstExit(void) +{ + /* Close CPU and MEM clkrst sessions */ + clkrstCloseSession(&g_clkrstMemSession); + clkrstCloseSession(&g_clkrstCpuSession); + + /* Close clkrst service handle */ + clkrstExit(); +} + +static bool servicesClkGetServiceType(void *arg) +{ + if (!arg) return false; + + ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + if (!strlen(info->name) || strncmp(info->name, "clk", 3) != 0 || info->init_func != NULL || info->close_func != NULL) return false; + + /* Determine which service needs to be used to control hardware clock rates, depending on the system version */ + /* This may either be pcv (sysver lower than 8.0.0) or clkrst (sysver equal or greater than 8.0.0) */ + g_clkSvcUsePcv = hosversionBefore(8, 0, 0); + + /* Fill service info */ + sprintf(info->name, "%s", (g_clkSvcUsePcv ? "pcv" : "clkrst")); + info->init_func = (g_clkSvcUsePcv ? &pcvInitialize : &servicesClkrstInitialize); + info->close_func = (g_clkSvcUsePcv ? &pcvExit : &servicesClkrstExit); + + return true; +} + +static bool servicesSplCryptoCheckAvailability(void *arg) +{ + if (!arg) return false; + + ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + if (!strlen(info->name) || strncmp(info->name, "spl:mig", 7) != 0 || info->init_func == NULL || info->close_func == NULL) return false; + + /* Check if spl:mig is available (sysver equal or greater than 4.0.0) */ + return !hosversionBefore(4, 0, 0); +} + +static bool servicesFspUsbCheckAvailability(void *arg) +{ + if (!arg) return false; + + ServicesInfoEntry *info = (ServicesInfoEntry*)arg; + if (!strlen(info->name) || strncmp(info->name, "fsp-usb", 7) != 0 || info->init_func == NULL || info->close_func == NULL) return false; + + /* Check if fsp-usb is actually running in the background */ + return servicesCheckRunningServiceByName("fsp-usb"); +} diff --git a/source/services.h b/source/services.h new file mode 100644 index 0000000..246f08f --- /dev/null +++ b/source/services.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef __SERVICES_H__ +#define __SERVICES_H__ + +/* Hardware clocks expressed in MHz */ +#define CPU_CLKRT_NORMAL 1020 +#define CPU_CLKRT_OVERCLOCKED 1785 +#define MEM_CLKRT_NORMAL 1331 +#define MEM_CLKRT_OVERCLOCKED 1600 + +bool servicesInitialize(); +void servicesClose(); + +bool servicesCheckRunningServiceByName(const char *name); +bool servicesCheckInitializedServiceByName(const char *name); + +void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate); + +#endif /* __SERVICES_H__ */ diff --git a/source/new/signature.h b/source/signature.h similarity index 100% rename from source/new/signature.h rename to source/signature.h diff --git a/source/new/tik.c b/source/tik.c similarity index 98% rename from source/new/tik.c rename to source/tik.c index d0e66c1..74feaa8 100644 --- a/source/new/tik.c +++ b/source/tik.c @@ -15,6 +15,8 @@ #define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001 +/* Type definitions. */ + /// Everything after the AES CTR is encrypted. typedef struct { u8 ctr[0x10]; @@ -26,15 +28,19 @@ typedef struct { u8 ghash[0x10]; } tikEticketDeviceKeyData; +/* Global variables. */ + static SetCalRsa2048DeviceKey g_eTicketDeviceKey = {0}; static bool g_eTicketDeviceKeyRetrieved = false; -/* Used during the RSA-OAEP titlekey decryption stage */ +/// Used during the RSA-OAEP titlekey decryption stage. static const u8 g_nullHash[0x20] = { 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 }; +/* Function prototypes. */ + static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized); static u8 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id); static bool tikGetTicketTypeAndSize(const void *data, u64 data_size, u8 *out_type, u64 *out_size); @@ -176,7 +182,7 @@ out: return success; } -bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TicketCommonBlock *tik_common_blk) +bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_blk) { if (!dst || !tik_common_blk) { @@ -184,7 +190,6 @@ bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TicketCommonBlock *tik_commo return false; } - Result rc = 0; size_t out_keydata_size = 0; u8 out_keydata[0x100] = {0}; tikEticketDeviceKeyData *eticket_devkey = NULL; @@ -288,7 +293,7 @@ void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, const void *titleke if (!tik || tik->type > TikType_SigEcsda240 || tik->size < TIK_MIN_SIZE || !titlekey) return; bool dev_cert = false; - TicketCommonBlock *tik_common_blk = NULL; + TikCommonBlock *tik_common_blk = NULL; tik_common_blk = tikGetTicketCommonBlockFromTicket(tik); if (!tik_common_blk || tik_common_blk->title_key_type != TikTitleKeyType_Personalized) return; diff --git a/source/new/tik.h b/source/tik.h similarity index 94% rename from source/new/tik.h rename to source/tik.h index 55f7a8f..6acba51 100644 --- a/source/new/tik.h +++ b/source/tik.h @@ -3,6 +3,7 @@ #ifndef __TIK_H__ #define __TIK_H__ +#include #include "signature.h" #define TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES system savefiles */ @@ -70,17 +71,17 @@ typedef struct { } TikCommonBlock; typedef struct { - SignatureRsa4096Block sig_block; + SignatureBlockRsa4096 sig_block; TikCommonBlock tik_common_blk; } TikSigRsa4096; typedef struct { - SignatureRsa2048Block sig_block; + SignatureBlockRsa2048 sig_block; TikCommonBlock tik_common_blk; } TikSigRsa2048; typedef struct { - SignatureEcsda240Block sig_block; + SignatureBlockEcsda240 sig_block; TikCommonBlock tik_common_blk; } TikSigEcsda240; @@ -112,7 +113,7 @@ static inline TikCommonBlock *tikGetTicketCommonBlockFromTicket(Ticket *tik) bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id); -bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TicketCommonBlock *tik_common_blk); +bool tikGetTitleKeyFromTicketCommonBlock(void *dst, TikCommonBlock *tik_common_blk); static inline bool tikGetTitleKeyFromTicket(void *dst, Ticket *tik) { diff --git a/source/utils.c b/source/utils.c new file mode 100644 index 0000000..fe12a8f --- /dev/null +++ b/source/utils.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include + +//#include "freetype_helper.h" +//#include "lvgl_helper.h" +#include "services.h" +#include "gamecard.h" +#include "utils.h" + +/* Global variables. */ + +static AppletHookCookie g_systemOverclockCookie = {0}; + +static Mutex g_logfileMutex = 0; + +/* Function prototypes. */ + +static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); + +u64 utilsHidKeysAllDown(void) +{ + u8 controller; + u64 keys_down = 0; + + for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_down |= hidKeysDown((HidControllerID)controller); + + return keys_down; +} + +u64 utilsHidKeysAllHeld(void) +{ + u8 controller; + u64 keys_held = 0; + + for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keys_held |= hidKeysHeld((HidControllerID)controller); + + return keys_held; +} + +void utilsWaitForButtonPress(void) +{ + u64 flag, keys_down; + + /* Don't consider touch screen presses nor stick movement as button inputs */ + flag = ~(KEY_TOUCH | KEY_LSTICK_LEFT | KEY_LSTICK_RIGHT | KEY_LSTICK_UP | KEY_LSTICK_DOWN | KEY_RSTICK_LEFT | KEY_RSTICK_RIGHT | KEY_RSTICK_UP | KEY_RSTICK_DOWN); + + while(appletMainLoop()) + { + hidScanInput(); + keys_down = utilsHidKeysAllDown(); + if (keys_down & flag) break; + } +} + +void utilsConsoleErrorScreen(const char *fmt, ...) +{ + consoleInit(NULL); + + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + + printf("\nPress any button to exit.\n"); + + consoleUpdate(NULL); + utilsWaitForButtonPress(); + consoleExit(NULL); +} + +void utilsWriteLogMessage(const char *func_name, const char *fmt, ...) +{ + mutexLock(&g_logfileMutex); + + va_list args; + FILE *logfile = NULL; + + logfile = fopen(APP_BASE_PATH "nxdumptool.log", "a+"); + if (!logfile) goto out; + + time_t now = time(NULL); + struct tm *ts = localtime(&now); + + fprintf(logfile, "%d/%d/%d %d:%d:%d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name); + + va_start(args, fmt); + vfprintf(logfile, fmt, args); + va_end(args); + + fprintf(logfile, "\r\n"); + fclose(logfile); + +out: + mutexUnlock(&g_logfileMutex); +} + +void utilsOverclockSystem(bool restore) +{ + u32 cpuClkRate = ((restore ? CPU_CLKRT_NORMAL : CPU_CLKRT_OVERCLOCKED) * 1000000); + u32 memClkRate = ((restore ? MEM_CLKRT_NORMAL : MEM_CLKRT_OVERCLOCKED) * 1000000); + servicesChangeHardwareClockRates(cpuClkRate, memClkRate); +} + +bool utilsInitializeResources(void) +{ + /* Initialize all needed services */ + if (!servicesInitialize()) return false; + + /* Initialize FreeType */ + //if (!freeTypeHelperInitialize()) return false; + + /* Initialize LVGL */ + //if (!lvglHelperInitialize()) return false; + + /* Overclock system */ + utilsOverclockSystem(false); + + /* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked) */ + appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL); + + /* Initialize gamecard */ + Result rc = gamecardInitialize(); + if (R_FAILED(rc)) + { + utilsConsoleErrorScreen("gamecard fail\n"); + return false; + } + + return true; +} + +void utilsCloseResources(void) +{ + /* Deinitialize gamecard */ + gamecardExit(); + + /* Unset our overclock applet hook */ + appletUnhook(&g_systemOverclockCookie); + + /* Restore hardware clocks */ + utilsOverclockSystem(true); + + /* Free LVGL resources */ + //lvglHelperExit(); + + /* Free FreeType resouces */ + //freeTypeHelperExit(); + + /* Close initialized services */ + servicesClose(); +} + +static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) +{ + (void)param; + + if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return; + + /* To do: read config here to actually know the value to use with utilsOverclockSystem */ + utilsOverclockSystem(true); +} diff --git a/source/utils.h b/source/utils.h new file mode 100644 index 0000000..d055d33 --- /dev/null +++ b/source/utils.h @@ -0,0 +1,66 @@ +#pragma once + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include + +#define APP_BASE_PATH "sdmc:/switch/nxdumptool/" + +#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) + +#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) + +#define SLEEP(x) svcSleepThread((x) * (u64)1000000000) + +#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) + +#define ROUND_UP(x, y) ((x) + (((y) - ((x) % (y))) % (y))) /* Aligns 'x' bytes to a 'y' bytes boundary. */ + + + +typedef enum { + UtilsCustomFirmwareType_Atmosphere = 0, + UtilsCustomFirmwareType_SXOS = 1, + UtilsCustomFirmwareType_ReiNX = 2 +} UtilsCustomFirmwareType; + +typedef struct { + u16 major : 6; + u16 minor : 6; + u16 micro : 4; + u16 bugfix; +} TitleVersion; + + + + +u64 utilsHidKeysAllDown(void); +u64 utilsHidKeysAllHeld(void); + +void utilsWaitForButtonPress(void); + +void utilsConsoleErrorScreen(const char *fmt, ...); + +void utilsWriteLogMessage(const char *func_name, const char *fmt, ...); + +void utilsOverclockSystem(bool restore); + +bool utilsInitializeResources(void); +void utilsCloseResources(void); + + + +static inline FsStorage *utilsGetEmmcBisSystemStorage(void) +{ + return NULL; +} + +static inline u8 utilsGetCustomFirmwareType(void) +{ + return UtilsCustomFirmwareType_Atmosphere; +} + + + +#endif /* __UTILS_H__ */