diff --git a/Makefile b/Makefile index 7b7aeff..91a039c 100644 --- a/Makefile +++ b/Makefile @@ -33,13 +33,13 @@ include $(DEVKITPRO)/libnx/switch_rules VERSION_MAJOR := 1 VERSION_MINOR := 1 -VERSION_MICRO := 8 +VERSION_MICRO := 9 APP_TITLE := nxdumptool -APP_AUTHOR := MCMrARM, DarkMatterCore +APP_AUTHOR := DarkMatterCore APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO} -TARGET := nxdumptool +TARGET := ${APP_TITLE} BUILD := build SOURCES := source source/fatfs DATA := data @@ -52,10 +52,8 @@ ROMFS := romfs #--------------------------------------------------------------------------------- ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -CFLAGS := -g -Wall -Wno-address-of-packed-member -O2 -ffunction-sections \ - $(ARCH) $(DEFINES) - -CFLAGS += $(INCLUDE) -D__SWITCH__ -D__LINUX_ERRNO_EXTENSIONS__ -DAPP_VERSION=\"${APP_VERSION}\" +CFLAGS := -g -Wall -Wextra -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__ +CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_VERSION=\"${APP_VERSION}\" CFLAGS += `freetype-config --cflags` CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags` CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags` diff --git a/README.md b/README.md index e3c8ec1..b0cb625 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Nintendo Switch Dump Tool Main features -------------- -* Generates full Cartridge Image dumps (XCI) with optional certificate removal and/or trimming. +* Generates NX Card Image (XCI) dumps from the inserted gamecard, with optional certificate removal and/or trimming. * Generates installable Nintendo Submission Packages (NSP) from base applications, updates and DLCs stored in the inserted gamecard, SD card and eMMC storage devices. * The generated dumps follow the `AuditingTool` format from Scene releases. * Capable of generating dumps without console specific information (common ticket). @@ -78,6 +78,30 @@ If you like my work and you'd like to support me in any way, it's not necessary, Changelog -------------- +**v1.1.9:** + +* Built using libnx commit d7e6207. +* Removed unnecessary code in NSP dumping steps. +* Improved GitHub JSON parsing code. +* Added NSP/ExeFS/RomFS support for titles with multiple Program NCAs (populated ID offset fields). Big thanks to [Cirosan](https://github.com/Cirosan) and [ITotalJustice](https://github.com/ITotalJustice) for testing! +* Fixed compatibility with consoles that use the new PRODINFO key generation scheme. Big thanks to dimitriblaiddyd78 from GBAtemp for reporting the issue and providing with testing! +* Fixed ExeFS/RomFS browsing/dumping support for bundled-in game updates in gamecards. +* Recursive directory removal after a failed HFS0/ExeFS/RomFS data dump is now optional. +* Fixed RomFS section dump support for titles that hold enough files in a single directory to exceed the max file count per directory limit in FAT32 (e.g. Animal Crossing: New Horizons). + * In order to overcome this problem, a secondary directory is created using the current RomFS directory name + a counter value (e.g. `/Model` -> `/Model_0`). + * This directory is used to save the rest of the data from the current RomFS directory until: + * All files from the current RomFS directory have been dumped, or... + * The directory reaches the max file count as well and another directory must be created to continue the process (e.g. `/Model_0` -> `/Model_1`). + * Big thanks to [Michael18751](https://github.com/Michael18751), [TechGeekGamer](https://github.com/TechGeekGamer) and [SusejLav](https://github.com/SusejLav) for testing! +* Button presses are now retrieved from all connected controllers. +* HOME button presses are now only blocked during dump operations. Fixes problems with homebrew forwarders and qlaunch replacements. + * Additionally, long HOME button presses are now blocked as well. +* Removed max entry count limit for HFS0/ExeFS/RomFS browsers. All filenames are now dynamically allocated, as it should have been from the very start. +* Updated NACP struct to reflect latest discoveries made by [0Liam](https://github.com/0Liam). +* The application now displays a FW update warning when the contents from an inserted gamecard can't be parsed because they use an unsupported NCA keygen. Thanks to [ITotalJustice](https://github.com/ITotalJustice) for spotting it! + +This is only a bugfix release. I don't expect to release any new versions until the rewrite is finished - the only exception being fixing some kind of feature-breaking bug. Please understand. + **v1.1.8:** * Added compatibility with latest devkitA64 and libnx releases. Thanks to [HookedBehemoth](https://github.com/HookedBehemoth) for porting the extra IPC calls used by the application to the new IPC system! * Now using global title contexts instead of global variables for each different title property (ID, version, source storage, etc.). Simplifies metadata retrieval functions. diff --git a/source/dumper.c b/source/dumper.c index 071697d..37f0c70 100644 --- a/source/dumper.c +++ b/source/dumper.c @@ -35,8 +35,6 @@ extern u32 emmcTitleAppCount, emmcTitlePatchCount, emmcTitleAddOnCount; extern base_app_ctx_t *baseAppEntries; extern patch_addon_ctx_t *patchEntries, *addOnEntries; -extern AppletType programAppletType; - extern exefs_ctx_t exeFsContext; extern romfs_ctx_t romFsContext; extern bktr_ctx_t bktrContext; @@ -44,10 +42,6 @@ extern bktr_ctx_t bktrContext; extern char curRomFsPath[NAME_BUF_LEN]; extern u32 curRomFsDirOffset; -extern char *filenameBuffer; -extern char *filenames[FILENAME_MAX_CNT]; -extern int filenamesCount; - extern u8 *enabledNormalIconBuf; extern u8 *enabledHighlightIconBuf; extern u8 *disabledNormalIconBuf; @@ -62,6 +56,12 @@ extern char freeSpaceStr[32]; extern char cfwDirStr[32]; +static void dumpStartMsg() +{ + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold " NINTENDO_FONT_B " to cancel."); + breaks++; +} + bool dumpNXCardImage(xciOptions *xciDumpCfg) { if (!xciDumpCfg) @@ -177,7 +177,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) progressCtx.curOffset = ((u64)seqXciCtx.partNumber * SPLIT_FILE_SEQUENTIAL_SIZE); } - u64 part_size = (seqDumpMode ? SPLIT_FILE_SEQUENTIAL_SIZE : (!setXciArchiveBit ? SPLIT_FILE_XCI_PART_SIZE : SPLIT_FILE_NSP_PART_SIZE)); + u64 partSize = (seqDumpMode ? SPLIT_FILE_SEQUENTIAL_SIZE : (!setXciArchiveBit ? SPLIT_FILE_XCI_PART_SIZE : SPLIT_FILE_NSP_PART_SIZE)); // Retrieve dump sizes for each IStorage partition for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) @@ -224,7 +224,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) u64 curXciOffset = 0, restSize = 0; - for(int i = 0; i < seqXciCtx.partitionIndex; i++) curXciOffset += partitionSizes[i]; + for(u32 i = 0; i < seqXciCtx.partitionIndex; i++) curXciOffset += partitionSizes[i]; curXciOffset += seqXciCtx.partitionOffset; restSize = (progressCtx.totalSize - curXciOffset); @@ -272,14 +272,14 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); uiRefreshDisplay(); // Modify config parameters isFat32 = true; setXciArchiveBit = false; - part_size = SPLIT_FILE_SEQUENTIAL_SIZE; + partSize = SPLIT_FILE_SEQUENTIAL_SIZE; seqDumpMode = true; seqDumpFileSize = sizeof(sequentialXciCtx); @@ -344,7 +344,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -352,7 +352,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) { // Since we may actually be dealing with an existing directory with the archive bit set or unset, let's try both // Better safe than sorry - unlink(dumpPath); + remove(dumpPath); fsdevDeleteDirectoryRecursively(dumpPath); mkdir(dumpPath, 0744); @@ -369,16 +369,13 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) goto out; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); + // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); + uiRefreshDisplay(); breaks++; - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -415,9 +412,9 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) if (n > (partitionSizes[partition] - partitionOffset)) n = (partitionSizes[partition] - partitionOffset); // Check if the next read chunk will exceed the size of the current part file - if (seqDumpMode && (seqDumpSessionOffset + n) >= (((splitIndex - seqXciCtx.partNumber) + 1) * part_size)) + if (seqDumpMode && (seqDumpSessionOffset + n) >= (((splitIndex - seqXciCtx.partNumber) + 1) * partSize)) { - u64 new_file_chunk_size = ((seqDumpSessionOffset + n) - (((splitIndex - seqXciCtx.partNumber) + 1) * part_size)); + u64 new_file_chunk_size = ((seqDumpSessionOffset + n) - (((splitIndex - seqXciCtx.partNumber) + 1) * partSize)); u64 old_file_chunk_size = (n - new_file_chunk_size); u64 remainderDumpSize = (progressCtx.totalSize - (progressCtx.curOffset + old_file_chunk_size)); @@ -425,7 +422,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) // Check if we have enough space for the next part // If so, set the chunk size to old_file_chunk_size - if ((remainderDumpSize <= part_size && remainderDumpSize > remainderFreeSize) || (remainderDumpSize > part_size && part_size > remainderFreeSize)) + if ((remainderDumpSize <= partSize && remainderDumpSize > remainderFreeSize) || (remainderDumpSize > partSize && partSize > remainderFreeSize)) { n = old_file_chunk_size; seqDumpFinish = true; @@ -483,9 +480,9 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) } } - if ((seqDumpMode || (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)) && (progressCtx.curOffset + n) >= ((splitIndex + 1) * part_size)) + if ((seqDumpMode || (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)) && (progressCtx.curOffset + n) >= ((splitIndex + 1) * partSize)) { - u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * part_size)); + u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * partSize)); u64 old_file_chunk_size = (n - new_file_chunk_size); if (old_file_chunk_size > 0) @@ -693,7 +690,7 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) for(u8 i = 0; i <= splitIndex; i++) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, i); - unlink(dumpPath); + remove(dumpPath); } } else { if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) @@ -706,11 +703,11 @@ bool dumpNXCardImage(xciOptions *xciDumpCfg) for(u8 i = 0; i <= splitIndex; i++) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, i); - unlink(dumpPath); + remove(dumpPath); } } } else { - unlink(dumpPath); + remove(dumpPath); } } } @@ -720,10 +717,12 @@ out: if (seqDumpFile) fclose(seqDumpFile); - if (seqDumpFileRemove) unlink(seqDumpFilename); + if (seqDumpFileRemove) remove(seqDumpFilename); breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -769,16 +768,12 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde u8 ncaHeader[NCA_FULL_HEADER_LENGTH] = {0}; nca_header_t dec_nca_header; - nca_program_mod_data ncaProgramMod; - memset(&ncaProgramMod, 0, sizeof(nca_program_mod_data)); - - ncaProgramMod.hash_table = NULL; - ncaProgramMod.block_data[0] = NULL; - ncaProgramMod.block_data[1] = NULL; - nca_cnmt_mod_data ncaCnmtMod; memset(&ncaCnmtMod, 0, sizeof(nca_cnmt_mod_data)); + u32 ncaProgramModCnt = 0; + nca_program_mod_data *ncaProgramMod = NULL; + title_rights_ctx rights_info; memset(&rights_info, 0, sizeof(title_rights_ctx)); @@ -787,39 +782,31 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde bool cnmtFound = false; char *cnmtXml = NULL; - u32 programNcaIndex = 0; - u64 programInfoXmlSize = 0; - char *programInfoXml = NULL; + u32 xml_rec_cnt = 0; + xml_record_info *xml_records = NULL, *tmp_xml_rec = NULL; - u32 nacpNcaIndex = 0; - u64 nacpXmlSize = 0; - char *nacpXml = NULL; - - u8 nacpIconCnt = 0; - nacp_icons_ctx *nacpIcons = NULL; - - u32 legalInfoNcaIndex = 0; - u64 legalInfoXmlSize = 0; - char *legalInfoXml = NULL; - - u32 nspFileCount = 0; pfs0_header nspPfs0Header; + memset(&nspPfs0Header, 0, sizeof(pfs0_header)); + nspPfs0Header.magic = __builtin_bswap32(PFS0_MAGIC); + pfs0_file_entry *nspPfs0EntryTable = NULL; + char *nspPfs0StrTable = NULL; u64 nspPfs0StrTableSize = 0; - u64 full_nsp_header_size = 0; + + u64 fullPfs0HeaderSize = 0; + + u8 **nspPfs0FilePtrs = NULL; Sha256Context nca_hash_ctx; sha256ContextCreate(&nca_hash_ctx); - u64 n, nca_offset; + u64 n, fileOffset; FILE *outFile = NULL; u8 splitIndex = 0; u32 crc = 0; bool proceed = true, dumping = false, fat32_error = false, removeFile = true; - memset(dumpBuf, 0, DUMP_BUFFER_SIZE); - progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); @@ -904,19 +891,11 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde seqDumpFileSize = ftell(seqDumpFile); rewind(seqDumpFile); - // Check file size - if (seqDumpFileSize <= sizeof(sequentialNspCtx) || ((seqDumpFileSize - sizeof(sequentialNspCtx)) % SHA256_HASH_SIZE) != 0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid sequential dump reference file size!", __func__); - seqDumpFileRemove = true; - goto out; - } - // Read sequentialNspCtx struct info read_res = fread(&seqNspCtx, 1, sizeof(sequentialNspCtx), seqDumpFile); if (read_res != sizeof(sequentialNspCtx)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", __func__, seqDumpFileSize, read_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", __func__, sizeof(sequentialNspCtx), read_res); goto out; } @@ -927,15 +906,24 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde goto out; } - // Check if we have the right amount of NCA hashes - if ((seqNspCtx.ncaCount * SHA256_HASH_SIZE) != (seqDumpFileSize - sizeof(sequentialNspCtx))) + // Check if the Program NCA mod count field is valid + if (seqNspCtx.programNcaModCount > 0 && !seqNspCtx.npdmAcidRsaPatch) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA count and/or NCA hash count in sequential dump reference file!", __func__); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid Program NCA mod count sequential dump reference file!", __func__); + seqDumpFileRemove = true; + goto out; + } + + // Check file size + if (seqDumpFileSize != (sizeof(sequentialNspCtx) + (seqNspCtx.ncaCount * SHA256_HASH_SIZE) + (seqNspCtx.programNcaModCount * NCA_FULL_HEADER_LENGTH))) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid sequential dump reference file size!", __func__); + seqDumpFileRemove = true; goto out; } // Allocate memory for the NCA hashes - seqDumpNcaHashes = calloc(1, seqDumpFileSize - sizeof(sequentialNspCtx)); + seqDumpNcaHashes = calloc(1, seqNspCtx.ncaCount * SHA256_HASH_SIZE); if (!seqDumpNcaHashes) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA hashes from the sequential dump reference file!", __func__); @@ -943,10 +931,8 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Read NCA hashes - read_res = fread(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); - rewind(seqDumpFile); - - if (read_res != (seqDumpFileSize - sizeof(sequentialNspCtx))) + read_res = fread(seqDumpNcaHashes, 1, seqNspCtx.ncaCount * SHA256_HASH_SIZE, seqDumpFile); + if (read_res != (seqNspCtx.ncaCount * SHA256_HASH_SIZE)) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", __func__, seqNspCtx.ncaCount * SHA256_HASH_SIZE, read_res); goto out; @@ -963,7 +949,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } } - u64 part_size = (seqDumpMode ? SPLIT_FILE_SEQUENTIAL_SIZE : SPLIT_FILE_NSP_PART_SIZE); + u64 partSize = (seqDumpMode ? SPLIT_FILE_SEQUENTIAL_SIZE : SPLIT_FILE_NSP_PART_SIZE); if (!batch) { @@ -1095,7 +1081,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Remove the prompt / error from the screen - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } // Fill information for our CNMT XML @@ -1121,7 +1107,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // Patch ACID public RSA key and recreate the NCA NPDM signature if we're dealing with the Program NCA if (xml_content_info[i].type == NcmContentType_Program && npdmAcidRsaPatch) { - if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod)) + if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod, &ncaProgramModCnt, i)) { proceed = false; break; @@ -1135,42 +1121,13 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // If no Rights ID is available, we may be dealing with a custom XCI mounted through SX OS. In this particular case, no further modifications should be needed if (has_rights_id) { - // Check if we have retrieved the ticket from the HFS0 partition in the gamecard - if (!rights_info.retrieved_tik) + // Retrieve the ticket from the HFS0 partition in the gamecard + if (!retrieveTitleKeyFromGameCardTicket(&rights_info, xml_content_info[i].decrypted_nca_keys)) { - // Retrieve ticket. We're going to use our own certificate chain down the road, there's no need to retrieve it as well - if (!readFileFromSecureHfs0PartitionByName(rights_info.tik_filename, 0, dumpBuf, ETICKET_TIK_FILE_SIZE)) - { - proceed = false; - break; - } - - memcpy(&(rights_info.tik_data), dumpBuf, ETICKET_TIK_FILE_SIZE); - - memcpy(rights_info.enc_titlekey, rights_info.tik_data.titlekey_block, 0x10); - - // Load external keys - if (!loadExternalKeys()) - { - proceed = false; - break; - } - - // Decrypt titlekey - u8 crypto_type = xml_content_info[i].keyblob; - if (crypto_type) crypto_type--; - - 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); - - rights_info.retrieved_tik = true; + proceed = false; + break; } - // Save the decrypted NCA key area keys - memset(xml_content_info[i].decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); - memcpy(xml_content_info[i].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info.dec_titlekey, 0x10); - // Mess with the NCA header if we're dealing with a NCA with a populated Rights ID field and if tiklessDump is true (removeConsoleData is ignored) if (tiklessDump) { @@ -1187,7 +1144,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // Patch ACID pubkey and recreate NCA NPDM signature if we're dealing with the Program NCA if (xml_content_info[i].type == NcmContentType_Program && npdmAcidRsaPatch) { - if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod)) + if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod, &ncaProgramModCnt, i)) { proceed = false; break; @@ -1216,7 +1173,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // Patch ACID pubkey and recreate NCA NPDM signature if we're dealing with the Program NCA if (xml_content_info[i].type == NcmContentType_Program && npdmAcidRsaPatch) { - if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod)) + if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod, &ncaProgramModCnt, i)) { proceed = false; break; @@ -1225,14 +1182,40 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } } - if (!has_rights_id || (has_rights_id && rights_info.retrieved_tik)) + if ((!has_rights_id || (has_rights_id && rights_info.retrieved_tik)) && (xml_content_info[i].type == NcmContentType_Program || xml_content_info[i].type == NcmContentType_Control || xml_content_info[i].type == NcmContentType_LegalInformation)) { - // Generate programinfo.xml - if (!programInfoXml && xml_content_info[i].type == NcmContentType_Program) + // Reallocate XML records + tmp_xml_rec = realloc(xml_records, (xml_rec_cnt + 1) * sizeof(xml_record_info)); + if (!tmp_xml_rec) { - programNcaIndex = i; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: error reallocating XML records buffer!", __func__); + proceed = false; + break; + } + + xml_records = tmp_xml_rec; + tmp_xml_rec = NULL; + + memset(&(xml_records[xml_rec_cnt]), 0, sizeof(xml_record_info)); + xml_records[xml_rec_cnt].nca_index = i; + + xml_rec_cnt++; + + // Generate programinfo.xml + if (xml_content_info[i].type == NcmContentType_Program) + { + bool use_acid_pubkey = false; - if (!generateProgramInfoXml(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &ncaProgramMod, &programInfoXml, &programInfoXmlSize)) + for(j = 0; j < ncaProgramModCnt; j++) + { + if (ncaProgramMod[j].nca_index == i) + { + use_acid_pubkey = true; + break; + } + } + + if (!generateProgramInfoXml(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, use_acid_pubkey, &(xml_records[xml_rec_cnt - 1].xml_data), &(xml_records[xml_rec_cnt - 1].xml_size))) { proceed = false; break; @@ -1240,11 +1223,9 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Retrieve NACP data (XML and icons) - if (!nacpXml && xml_content_info[i].type == NcmContentType_Control) + if (xml_content_info[i].type == NcmContentType_Control) { - nacpNcaIndex = i; - - if (!retrieveNacpDataFromNca(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &nacpXml, &nacpXmlSize, &nacpIcons, &nacpIconCnt)) + if (!retrieveNacpDataFromNca(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &(xml_records[xml_rec_cnt - 1].xml_data), &(xml_records[xml_rec_cnt - 1].xml_size), &(xml_records[xml_rec_cnt - 1].nacp_icons), &(xml_records[xml_rec_cnt - 1].nacp_icon_cnt))) { proceed = false; break; @@ -1252,11 +1233,9 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Retrieve legalinfo.xml - if (!legalInfoXml && xml_content_info[i].type == NcmContentType_LegalInformation) + if (xml_content_info[i].type == NcmContentType_LegalInformation) { - legalInfoNcaIndex = i; - - if (!retrieveLegalInfoXmlFromNca(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &legalInfoXml, &legalInfoXmlSize)) + if (!retrieveLegalInfoXmlFromNca(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &(xml_records[xml_rec_cnt - 1].xml_data), &(xml_records[xml_rec_cnt - 1].xml_size))) { proceed = false; break; @@ -1311,7 +1290,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Retrieve CNMT NCA data - if (!retrieveCnmtNcaData(curStorageId, selectedNspDumpType, cnmtNcaBuf, &xml_program_info, xml_content_info, cnmtNcaIndex, &ncaCnmtMod, &rights_info)) goto out; + if (!retrieveCnmtNcaData(curStorageId, cnmtNcaBuf, &xml_program_info, xml_content_info, cnmtNcaIndex, &ncaCnmtMod, &rights_info)) goto out; // Generate a placeholder CNMT XML. It's length will be used to calculate the final output dump size @@ -1341,56 +1320,45 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // File count = NCA count + CNMT XML + tik + cert - nspFileCount = (titleContentInfoCnt + 3); + nspPfs0Header.file_cnt = (titleContentInfoCnt + 3); // Calculate PFS0 String Table size - nspPfs0StrTableSize = (((nspFileCount - 4) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2) + NSP_TIK_FILENAME_LENGTH + NSP_CERT_FILENAME_LENGTH); + nspPfs0StrTableSize = (((nspPfs0Header.file_cnt - 4) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2) + NSP_TIK_FILENAME_LENGTH + NSP_CERT_FILENAME_LENGTH); } else { // File count = NCA count + CNMT XML - nspFileCount = (titleContentInfoCnt + 1); + nspPfs0Header.file_cnt = (titleContentInfoCnt + 1); // Calculate PFS0 String Table size - nspPfs0StrTableSize = (((nspFileCount - 2) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2)); + nspPfs0StrTableSize = (((nspPfs0Header.file_cnt - 2) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2)); } - // Add our programinfo.xml if we created it - if (programInfoXml) + // Add our XML records + if (xml_rec_cnt) { - nspFileCount++; - nspPfs0StrTableSize += NSP_PROGRAM_XML_FILENAME_LENGTH; - } - - // Add our NACP XML if we created it - if (nacpXml) - { - // Add icons if we retrieved them - if (nacpIcons && nacpIconCnt) + for(i = 0; i < xml_rec_cnt; i++) { - for(i = 0; i < nacpIconCnt; i++) + if (!xml_records[i].xml_data || !xml_records[i].xml_size) continue; + + nspPfs0Header.file_cnt++; + u8 type = xml_content_info[xml_records[i].nca_index].type; + nspPfs0StrTableSize += (type == NcmContentType_Program ? NSP_PROGRAM_XML_FILENAME_LENGTH : (type == NcmContentType_Control ? NSP_NACP_XML_FILENAME_LENGTH : NSP_LEGAL_XML_FILENAME_LENGTH)); + progressCtx.totalSize += xml_records[i].xml_size; + + // Add icons if we retrieved them + if (type == NcmContentType_Control && xml_records[i].nacp_icons && xml_records[i].nacp_icon_cnt) { - nspFileCount++; - nspPfs0StrTableSize += (strlen(nacpIcons[i].filename) + 1); + for(j = 0; j < xml_records[i].nacp_icon_cnt; j++) + { + nspPfs0Header.file_cnt++; + nspPfs0StrTableSize += (u32)(strlen(xml_records[i].nacp_icons[j].filename) + 1); + progressCtx.totalSize += xml_records[i].nacp_icons[j].icon_size; + } } } - - nspFileCount++; - nspPfs0StrTableSize += NSP_NACP_XML_FILENAME_LENGTH; - } - - // Add our legalinfo.xml if we retrieved it - if (legalInfoXml) - { - nspFileCount++; - nspPfs0StrTableSize += NSP_LEGAL_XML_FILENAME_LENGTH; } // Start NSP creation - - memset(&nspPfs0Header, 0, sizeof(pfs0_header)); - nspPfs0Header.magic = bswap_32(PFS0_MAGIC); - nspPfs0Header.file_cnt = nspFileCount; - - nspPfs0EntryTable = calloc(nspFileCount, sizeof(pfs0_file_entry)); + nspPfs0EntryTable = calloc(nspPfs0Header.file_cnt, sizeof(pfs0_file_entry)); if (!nspPfs0EntryTable) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the PFS0 file entries!", __func__); @@ -1406,36 +1374,113 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Determine our full NSP header size - full_nsp_header_size = (sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_file_entry)) + nspPfs0StrTableSize); + fullPfs0HeaderSize = (sizeof(pfs0_header) + ((u64)nspPfs0Header.file_cnt * sizeof(pfs0_file_entry)) + nspPfs0StrTableSize); // Round up our full NSP header size to a 0x10-byte boundary - if (!(full_nsp_header_size % 0x10)) full_nsp_header_size++; // If it's already rounded, add more padding - full_nsp_header_size = round_up(full_nsp_header_size, 0x10); + if (!(fullPfs0HeaderSize % 0x10)) fullPfs0HeaderSize++; // If it's already rounded, add more padding + fullPfs0HeaderSize = round_up(fullPfs0HeaderSize, 0x10); // Determine our String Table size - nspPfs0Header.str_table_size = (full_nsp_header_size - (sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_file_entry)))); + nspPfs0Header.str_table_size = (fullPfs0HeaderSize - (sizeof(pfs0_header) + ((u64)nspPfs0Header.file_cnt * sizeof(pfs0_file_entry)))); - // Calculate total dump size - progressCtx.totalSize = full_nsp_header_size; - - for(i = 0; i < titleContentInfoCnt; i++) progressCtx.totalSize += xml_content_info[i].size; - - progressCtx.totalSize += strlen(cnmtXml); - - if (programInfoXml) progressCtx.totalSize += programInfoXmlSize; - - if (nacpXml) + // Allocate memory for PFS0 file data pointer array. Exclude all NCAs but the CNMT NCA + nspPfs0FilePtrs = calloc(nspPfs0Header.file_cnt - (titleContentInfoCnt - 1), sizeof(u8*)); + if (!nspPfs0FilePtrs) { - if (nacpIcons && nacpIconCnt) - { - for(i = 0; i < nacpIconCnt; i++) progressCtx.totalSize += nacpIcons[i].icon_size; - } - - progressCtx.totalSize += nacpXmlSize; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the PFS0 file data pointer array!", __func__); + goto out; } - if (legalInfoXml) progressCtx.totalSize += legalInfoXmlSize; + // Fill PFS0 entry table + // PFS0 string table will be filled at a later time + u64 curFileOffset = 0; + u32 curFilenameOffset = 0; + u64 entrySize = 0; + u32 entryFilenameSize = 0; + + u32 entryIdx = 0, ptrIdx = 0; + + for(i = 0; i <= titleContentInfoCnt; i++, entryIdx++) + { + if (i < titleContentInfoCnt) + { + // Always reserve the first titleContentInfoCnt entries for our NCAs + // Only save the CNMT NCA buffer pointer to the PFS0 file data pointer array. We don't have any other pointers to raw NCA data, so we leave the rest untouched + entrySize = xml_content_info[i].size; + entryFilenameSize = (i == cnmtNcaIndex ? NSP_CNMT_FILENAME_LENGTH : NSP_NCA_FILENAME_LENGTH); + if (i == cnmtNcaIndex) nspPfs0FilePtrs[ptrIdx++] = cnmtNcaBuf; + } else { + // Reserve the entry right after our NCAs for the CNMT XML + entrySize = strlen(cnmtXml); + entryFilenameSize = NSP_CNMT_FILENAME_LENGTH; + nspPfs0FilePtrs[ptrIdx++] = (u8*)cnmtXml; + } + + nspPfs0EntryTable[i].file_size = entrySize; + nspPfs0EntryTable[i].file_offset = curFileOffset; + nspPfs0EntryTable[i].filename_offset = curFilenameOffset; + + curFileOffset += entrySize; + curFilenameOffset += entryFilenameSize; + } + + for(i = 0; i < xml_rec_cnt; i++, entryIdx++) + { + u8 type = xml_content_info[xml_records[i].nca_index].type; + + if (type == NcmContentType_Control && xml_records[i].nacp_icons && xml_records[i].nacp_icon_cnt) + { + // Process all icons at once + for(j = 0; j < xml_records[i].nacp_icon_cnt; j++, entryIdx++) + { + entrySize = xml_records[i].nacp_icons[j].icon_size; + entryFilenameSize = (u32)(strlen(xml_records[i].nacp_icons[j].filename) + 1); // This is the only entry type with variable filename length + nspPfs0FilePtrs[ptrIdx++] = xml_records[i].nacp_icons[j].icon_data; + + nspPfs0EntryTable[entryIdx].file_size = entrySize; + nspPfs0EntryTable[entryIdx].file_offset = curFileOffset; + nspPfs0EntryTable[entryIdx].filename_offset = curFilenameOffset; + + curFileOffset += entrySize; + curFilenameOffset += entryFilenameSize; + } + } + + // XML entry + entrySize = xml_records[i].xml_size; + entryFilenameSize = (type == NcmContentType_Program ? NSP_PROGRAM_XML_FILENAME_LENGTH : (type == NcmContentType_Control ? NSP_NACP_XML_FILENAME_LENGTH : NSP_LEGAL_XML_FILENAME_LENGTH)); + nspPfs0FilePtrs[ptrIdx++] = (u8*)xml_records[i].xml_data; + + nspPfs0EntryTable[entryIdx].file_size = entrySize; + nspPfs0EntryTable[entryIdx].file_offset = curFileOffset; + nspPfs0EntryTable[entryIdx].filename_offset = curFilenameOffset; + + curFileOffset += entrySize; + curFilenameOffset += entryFilenameSize; + } + + if (includeTikAndCert) + { + for(i = 0; i < 2; i++, entryIdx++) + { + entrySize = (i == 0 ? ETICKET_TIK_FILE_SIZE : ETICKET_CERT_FILE_SIZE); + entryFilenameSize = (i == 0 ? NSP_TIK_FILENAME_LENGTH : NSP_CERT_FILENAME_LENGTH); + nspPfs0FilePtrs[ptrIdx++] = (i == 0 ? (u8*)(&(rights_info.tik_data)) : rights_info.cert_data); + + nspPfs0EntryTable[entryIdx].file_size = entrySize; + nspPfs0EntryTable[entryIdx].file_offset = curFileOffset; + nspPfs0EntryTable[entryIdx].filename_offset = curFilenameOffset; + + curFileOffset += entrySize; + curFilenameOffset += entryFilenameSize; + } + } + + // Calculate total dump size + progressCtx.totalSize += fullPfs0HeaderSize; + for(i = 0; i < titleContentInfoCnt; i++) progressCtx.totalSize += xml_content_info[i].size; + progressCtx.totalSize += strlen(cnmtXml); if (includeTikAndCert) progressCtx.totalSize += (ETICKET_TIK_FILE_SIZE + ETICKET_CERT_FILE_SIZE); convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); @@ -1463,15 +1508,22 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde goto out; } - // Check if the PFS0 file count is valid - if (seqNspCtx.nspFileCount != nspFileCount) + // Check if the Program NCA mod count is valid + if (seqNspCtx.programNcaModCount != ncaProgramModCnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: PFS0 file count mismatch in the sequential dump reference file! (%u != %u)", __func__, seqNspCtx.nspFileCount, nspFileCount); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA mod count mismatch in the sequential dump reference file! (%u != %u)", __func__, seqNspCtx.programNcaModCount, ncaProgramModCnt); + goto out; + } + + // Check if the PFS0 file count is valid + if (seqNspCtx.pfs0FileCount != nspPfs0Header.file_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: PFS0 file count mismatch in the sequential dump reference file! (%u != %u)", __func__, seqNspCtx.pfs0FileCount, nspPfs0Header.file_cnt); goto out; } // Check if the current PFS0 file index is valid - if (seqNspCtx.fileIndex >= nspFileCount) + if (seqNspCtx.fileIndex >= nspPfs0Header.file_cnt) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid PFS0 file index in the sequential dump reference file!", __func__); goto out; @@ -1485,46 +1537,9 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Check if the current overall offset is aligned to SPLIT_FILE_SEQUENTIAL_SIZE - u64 curNspOffset = full_nsp_header_size, restSize = 0; - - for(i = 0; i < seqNspCtx.fileIndex; i++) - { - if (i < titleContentInfoCnt) - { - curNspOffset += xml_content_info[i].size; - } else - if (i == titleContentInfoCnt) - { - curNspOffset += strlen(cnmtXml); - } else - if (programInfoXml && i == (titleContentInfoCnt + 1)) - { - curNspOffset += programInfoXmlSize; - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) - { - u32 icon_idx = (!programInfoXml ? (i - (titleContentInfoCnt + 1)) : (i - (titleContentInfoCnt + 2))); - curNspOffset += nacpIcons[icon_idx].icon_size; - } else - if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) - { - curNspOffset += nacpXmlSize; - } else - if (legalInfoXml && ((!includeTikAndCert && i == (nspFileCount - 1)) || (includeTikAndCert && i == (nspFileCount - 3)))) - { - curNspOffset += legalInfoXmlSize; - } else { - if (i == (nspFileCount - 2)) - { - curNspOffset += ETICKET_TIK_FILE_SIZE; - } else { - curNspOffset += ETICKET_CERT_FILE_SIZE; - } - } - } - + u64 curNspOffset = fullPfs0HeaderSize; + for(i = 0; i < seqNspCtx.fileIndex; i++) curNspOffset += nspPfs0EntryTable[i].file_size; curNspOffset += seqNspCtx.fileOffset; - restSize = (progressCtx.totalSize - curNspOffset); if (curNspOffset != progressCtx.curOffset) { @@ -1533,6 +1548,7 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Check if there's enough free space to continue the sequential dump process + u64 restSize = (progressCtx.totalSize - curNspOffset); if (progressCtx.totalSize > freeSpace && ((restSize > SPLIT_FILE_SEQUENTIAL_SIZE && freeSpace < SPLIT_FILE_SEQUENTIAL_SIZE) || (restSize <= SPLIT_FILE_SEQUENTIAL_SIZE && freeSpace < restSize))) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: not enough free space available in the SD card!", __func__); @@ -1540,133 +1556,85 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Now check if the current PFS0 file entry offset is correct - for(i = 0; i < nspFileCount; i++) + if (seqNspCtx.fileOffset >= nspPfs0EntryTable[seqNspCtx.fileIndex].file_size) { - if (i < seqNspCtx.fileIndex) + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset for current PFS0 file entry in the sequential dump reference file!", __func__); + goto out; + } + + // Copy previously calculated NCA IDs and hashes + for(i = 0; i < seqNspCtx.fileIndex; i++) + { + // Exit loop if we reach the CNMT NCA + // Its ID/hash calculation is always handled by patchCnmtNca() + if (i >= (titleContentInfoCnt - 1)) break; + + // Fill information for our CNMT XML + memcpy(xml_content_info[i].nca_id, seqDumpNcaHashes + (i * SHA256_HASH_SIZE), SHA256_HASH_SIZE / 2); + convertDataToHexString(xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); + memcpy(xml_content_info[i].hash, seqDumpNcaHashes + (i * SHA256_HASH_SIZE), SHA256_HASH_SIZE); + convertDataToHexString(xml_content_info[i].hash, SHA256_HASH_SIZE, xml_content_info[i].hash_str, (SHA256_HASH_SIZE * 2) + 1); + } + + // Copy the NCA SHA-256 context data, but only if we're not dealing with the CNMT NCA + if (seqNspCtx.fileIndex < (titleContentInfoCnt - 1)) memcpy(&nca_hash_ctx, &(seqNspCtx.hashCtx), sizeof(Sha256Context)); + + // Restore the modified Program NCA headers + // The NPDM signature from the NCA headers is generated using cryptographically secure random numbers, so the modified header is stored during the first sequential dump session + // If needed, it must be restored in later sessions + for(i = 0; i < ncaProgramModCnt; i++) + { + read_res = fread(xml_content_info[ncaProgramMod[i].nca_index].encrypted_header_mod, 1, NCA_FULL_HEADER_LENGTH, seqDumpFile); + if (read_res != NCA_FULL_HEADER_LENGTH) { - // Exclude the CNMT NCA - if (i < (titleContentInfoCnt - 1)) - { - // Fill information for our CNMT XML - memcpy(xml_content_info[i].nca_id, seqDumpNcaHashes + (i * SHA256_HASH_SIZE), SHA256_HASH_SIZE / 2); - convertDataToHexString(xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); - memcpy(xml_content_info[i].hash, seqDumpNcaHashes + (i * SHA256_HASH_SIZE), SHA256_HASH_SIZE); - convertDataToHexString(xml_content_info[i].hash, SHA256_HASH_SIZE, xml_content_info[i].hash_str, (SHA256_HASH_SIZE * 2) + 1); - } - } else { - if (i < titleContentInfoCnt) - { - // Check if the offset for the current NCA is valid - if (seqNspCtx.fileOffset < xml_content_info[i].size) - { - // Copy the SHA-256 context data, but only if we're not dealing with the CNMT NCA - // NCA ID/hash for the CNMT NCA is handled in patchCnmtNca() - if (i != cnmtNcaIndex) memcpy(&nca_hash_ctx, &(seqNspCtx.hashCtx), sizeof(Sha256Context)); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else - if (i == titleContentInfoCnt) - { - // Check if the offset for the CNMT XML is valid - if (seqNspCtx.fileOffset >= strlen(cnmtXml)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid CNMT XML offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else { - if (programInfoXml && i == (titleContentInfoCnt + 1)) - { - // Check if the offset for the programinfo.xml is valid - if (seqNspCtx.fileOffset >= programInfoXmlSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid programinfo.xml offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) - { - // Check if the offset for the NACP icon is valid - u32 icon_idx = (!programInfoXml ? (i - (titleContentInfoCnt + 1)) : (i - (titleContentInfoCnt + 2))); - if (seqNspCtx.fileOffset >= nacpIcons[icon_idx].icon_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NACP icon offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else - if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) - { - // Check if the offset for the NACP XML is valid - if (seqNspCtx.fileOffset >= nacpXmlSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NACP XML offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else - if (legalInfoXml && ((!includeTikAndCert && i == (nspFileCount - 1)) || (includeTikAndCert && i == (nspFileCount - 3)))) - { - // Check if the offset for the legalinfo.xml is valid - if (seqNspCtx.fileOffset >= legalInfoXmlSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid legalinfo.xml offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else { - if (i == (nspFileCount - 2)) - { - // Check if the offset for the ticket is valid - if (seqNspCtx.fileOffset >= ETICKET_TIK_FILE_SIZE) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid ticket offset in the sequential dump reference file!", __func__); - proceed = false; - } - } else { - // Check if the offset for the certificate chain is valid - if (seqNspCtx.fileOffset >= ETICKET_CERT_FILE_SIZE) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid certificate chain offset in the sequential dump reference file!", __func__); - proceed = false; - } - } - } - } - - break; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", __func__, NCA_FULL_HEADER_LENGTH, read_res); + goto out; } } - if (!proceed) goto out; - - // Restore the modified Program NCA header - // The NPDM signature from the NCA header is generated using cryptographically secure random numbers, so the modified header is stored during the first sequential dump session - // If needed, it must be restored in later sessions - if (ncaProgramMod.block_mod_cnt) memcpy(xml_content_info[programNcaIndex].encrypted_header_mod, &(seqNspCtx.programNcaHeaderMod), NCA_FULL_HEADER_LENGTH); + rewind(seqDumpFile); // Inform that we are resuming an already started sequential dump operation if (curStorageId == NcmStorageId_GameCard) { - if (selectedNspDumpType == DUMP_APP_NSP || selectedNspDumpType == DUMP_ADDON_NSP) + if (selectedNspDumpType == DUMP_APP_NSP) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Resuming previous sequential dump operation."); - } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Resuming previous sequential dump operation. Configuration parameters overrided."); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Generate ticket-less dump: %s.", (tiklessDump ? "Yes" : "No")); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Change NPDM RSA key/sig in Program NCA: %s.", (npdmAcidRsaPatch ? "Yes" : "No")); + } else + if (selectedNspDumpType == DUMP_PATCH_NSP) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Resuming previous sequential dump operation. Configuration parameters overrided."); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Generate ticket-less dump: %s | Change NPDM RSA key/sig in Program NCA: %s.", (tiklessDump ? "Yes" : "No"), (npdmAcidRsaPatch ? "Yes" : "No")); + } else + if (selectedNspDumpType == DUMP_ADDON_NSP) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Resuming previous sequential dump operation."); } } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Resuming previous sequential dump operation. Configuration parameters overrided."); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Remove console specific data: %s | Generate ticket-less dump: %s.", (removeConsoleData ? "Yes" : "No"), (tiklessDump ? "Yes" : "No")); + + if (selectedNspDumpType == DUMP_APP_NSP || selectedNspDumpType == DUMP_PATCH_NSP) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Remove console specific data: %s | Generate ticket-less dump: %s | Change NPDM RSA key/sig in Program NCA: %s.", (removeConsoleData ? "Yes" : "No"), (tiklessDump ? "Yes" : "No"), (npdmAcidRsaPatch ? "Yes" : "No")); + } else + if (selectedNspDumpType == DUMP_ADDON_NSP) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Remove console specific data: %s | Generate ticket-less dump: %s.", (removeConsoleData ? "Yes" : "No"), (tiklessDump ? "Yes" : "No")); + } } breaks++; } else { if (progressCtx.totalSize > freeSpace) { - // Check if we have at least (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE))) of free space + // Check if we have enough free space // The CNMT NCA is excluded from the hash list - if (freeSpace < (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE)))) + seqDumpFileSize = (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE) + (ncaProgramModCnt * NCA_FULL_HEADER_LENGTH)); + if (freeSpace < (SPLIT_FILE_SEQUENTIAL_SIZE + seqDumpFileSize)) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: not enough free space available in the SD card!", __func__); goto out; @@ -1683,16 +1651,13 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); uiRefreshDisplay(); // Modify config parameters isFat32 = true; - - part_size = SPLIT_FILE_SEQUENTIAL_SIZE; - + partSize = SPLIT_FILE_SEQUENTIAL_SIZE; seqDumpMode = true; - seqDumpFileSize = (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE)); // Fill information in our sequential context seqNspCtx.storageId = curStorageId; @@ -1700,12 +1665,9 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde seqNspCtx.tiklessDump = tiklessDump; seqNspCtx.npdmAcidRsaPatch = npdmAcidRsaPatch; seqNspCtx.preInstall = preInstall; - seqNspCtx.nspFileCount = nspFileCount; + seqNspCtx.pfs0FileCount = nspPfs0Header.file_cnt; seqNspCtx.ncaCount = (titleContentInfoCnt - 1); // Exclude the CNMT NCA from the hash list - - // Store the modified Program NCA header - // The NPDM signature from the NCA header is generated using cryptographically secure random numbers, so we must store the modified header during the first sequential dump session - if (ncaProgramMod.block_mod_cnt) memcpy(&(seqNspCtx.programNcaHeaderMod), xml_content_info[programNcaIndex].encrypted_header_mod, NCA_FULL_HEADER_LENGTH); + seqNspCtx.programNcaModCount = ncaProgramModCnt; // Allocate memory for the NCA hashes seqDumpNcaHashes = calloc(1, (titleContentInfoCnt - 1) * SHA256_HASH_SIZE); @@ -1733,16 +1695,29 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } // Write the NCA hashes block - write_res = fwrite(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); - rewind(seqDumpFile); - - if (write_res != (seqDumpFileSize - sizeof(sequentialNspCtx))) + write_res = fwrite(seqDumpNcaHashes, 1, (titleContentInfoCnt - 1) * SHA256_HASH_SIZE, seqDumpFile); + if (write_res != ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", __func__, titleContentInfoCnt * SHA256_HASH_SIZE, write_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", __func__, (titleContentInfoCnt - 1) * SHA256_HASH_SIZE, write_res); seqDumpFileRemove = true; goto out; } + // Write the modified Program NCA headers + // The NPDM signature from the NCA headers is generated using cryptographically secure random numbers, so we must store the modified header during the first sequential dump session + for(i = 0; i < ncaProgramModCnt; i++) + { + write_res = fwrite(xml_content_info[ncaProgramMod[i].nca_index].encrypted_header_mod, 1, NCA_FULL_HEADER_LENGTH, seqDumpFile); + if (write_res != NCA_FULL_HEADER_LENGTH) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", __func__, NCA_FULL_HEADER_LENGTH, write_res); + seqDumpFileRemove = true; + goto out; + } + } + + rewind(seqDumpFile); + // Update free space freeSpace -= seqDumpFileSize; } @@ -1777,13 +1752,13 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } // Since we may actually be dealing with an existing directory with the archive bit set or unset, let's try both // Better safe than sorry - unlink(dumpPath); + remove(dumpPath); fsdevDeleteDirectoryRecursively(dumpPath); if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) @@ -1802,39 +1777,33 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde goto out; } + // Start dump process + if (!batch) dumpStartMsg(); + appletModeOperationWarning(); + uiRefreshDisplay(); + if (!batch) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - uiRefreshDisplay(); breaks++; + changeHomeButtonBlockStatus(true); } - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - if (!batch) breaks++; - - memset(dumpBuf, 0, DUMP_BUFFER_SIZE); - if (seqDumpMode) { // Skip the PFS0 header in the first part file // It will be saved to an additional ".nsp.hdr" file - if (!seqNspCtx.partNumber) progressCtx.curOffset = seqDumpSessionOffset = full_nsp_header_size; + if (!seqNspCtx.partNumber) progressCtx.curOffset = seqDumpSessionOffset = fullPfs0HeaderSize; } else { // Write placeholder zeroes - write_res = fwrite(dumpBuf, 1, full_nsp_header_size, outFile); - if (write_res != full_nsp_header_size) + write_res = fwrite(dumpBuf, 1, fullPfs0HeaderSize, outFile); + if (write_res != fullPfs0HeaderSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes placeholder data to file offset 0x%016lX! (wrote %lu bytes)", __func__, full_nsp_header_size, (u64)0, write_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes placeholder data to file offset 0x%016lX! (wrote %lu bytes)", __func__, fullPfs0HeaderSize, (u64)0, write_res); goto out; } // Advance our current offset - progressCtx.curOffset = full_nsp_header_size; + progressCtx.curOffset = fullPfs0HeaderSize; } progressCtx.line_offset = (breaks + 4); @@ -1845,33 +1814,132 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde u32 startFileIndex = (seqDumpMode ? seqNspCtx.fileIndex : 0); u64 startFileOffset; - // Dump all NCAs excluding the CNMT NCA - for(i = startFileIndex; i < (titleContentInfoCnt - 1); i++, startFileIndex++) + // Write all PFS0 entries + for(i = startFileIndex; i < nspPfs0Header.file_cnt; i++, startFileIndex++) { + char *entryFilename = NULL; + n = DUMP_BUFFER_SIZE; startFileOffset = ((seqDumpMode && i == seqNspCtx.fileIndex) ? seqNspCtx.fileOffset : 0); - memcpy(ncaId.c, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2); + int programModIdx = -1; - if (!seqDumpMode || (seqDumpMode && i != seqNspCtx.fileIndex)) sha256ContextCreate(&nca_hash_ctx); - - for(nca_offset = startFileOffset; nca_offset < xml_content_info[i].size; nca_offset += n, progressCtx.curOffset += n, seqDumpSessionOffset += n) + // Check if we're dealing with a NCA + if (i < titleContentInfoCnt) { - if (seqDumpMode && seqDumpFinish) break; + // Check if we're not dealing with the CNMT NCA + if (i < (titleContentInfoCnt - 1)) + { + // Copy NCA ID + memcpy(ncaId.c, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2); + + // Reset SHA-256 context if necessary + if (!seqDumpMode || (seqDumpMode && i != seqNspCtx.fileIndex)) sha256ContextCreate(&nca_hash_ctx); + + // Retrieve Program NCA mod data index + if (xml_content_info[i].type == NcmContentType_Program && ncaProgramModCnt > 0) + { + for(j = 0; j < ncaProgramModCnt; j++) + { + if (ncaProgramMod[j].nca_index == i) + { + programModIdx = (int)j; + break; + } + } + } + } else { + // Patch CNMT NCA + breaks = (progressCtx.line_offset + 2); + + proceed = patchCnmtNca(cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size, &xml_program_info, xml_content_info, &ncaCnmtMod); + if (!proceed) + { + dumping = false; + break; + } + + breaks = (progressCtx.line_offset - 4); + + // Generate proper CNMT XML + generateCnmtXml(&xml_program_info, xml_content_info, cnmtXml); + + // Fill PFS0 string table + // This is done here because we'll need to display filenames for the rest of the PFS0 entries starting with the next loop iteration + entryIdx = 0; + + for(j = 0; j <= titleContentInfoCnt; j++, entryIdx++) + { + char *curFilename = (nspPfs0StrTable + nspPfs0EntryTable[entryIdx].filename_offset); + + if (j < titleContentInfoCnt) + { + sprintf(curFilename, "%s.%s", xml_content_info[j].nca_id_str, (j == cnmtNcaIndex ? "cnmt.nca" : "nca")); + } else + if (j == titleContentInfoCnt) + { + sprintf(curFilename, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str); + } + } + + for(j = 0; j < xml_rec_cnt; j++, entryIdx++) + { + u8 type = xml_content_info[xml_records[j].nca_index].type; + + if (type == NcmContentType_Control && xml_records[j].nacp_icons && xml_records[j].nacp_icon_cnt) + { + // Process all icons at once + for(u32 k = 0; k < xml_records[j].nacp_icon_cnt; k++, entryIdx++) + { + char *curFilename = (nspPfs0StrTable + nspPfs0EntryTable[entryIdx].filename_offset); + sprintf(curFilename, "%s%s", xml_content_info[xml_records[j].nca_index].nca_id_str, strchr(xml_records[j].nacp_icons[k].filename, '.')); + } + } + + char *curFilename = (nspPfs0StrTable + nspPfs0EntryTable[entryIdx].filename_offset); + sprintf(curFilename, "%s.%s.xml", xml_content_info[xml_records[j].nca_index].nca_id_str, (type == NcmContentType_Program ? "programinfo" : (type == NcmContentType_Control ? "nacp" : "legalinfo"))); + } + + if (includeTikAndCert) + { + for(j = 0; j < 2; j++, entryIdx++) + { + char *curFilename = (nspPfs0StrTable + nspPfs0EntryTable[entryIdx].filename_offset); + sprintf(curFilename, (j == 0 ? rights_info.tik_filename : rights_info.cert_filename)); + } + } + } + } else { + // Copy current filename + entryFilename = (nspPfs0StrTable + nspPfs0EntryTable[i].filename_offset); + } + + for(fileOffset = startFileOffset; fileOffset < nspPfs0EntryTable[i].file_size; fileOffset += n, progressCtx.curOffset += n, seqDumpSessionOffset += n) + { + if (seqDumpMode && seqDumpFinish) + { + ret = 0; + break; + } uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 4), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Dumping NCA content \"%s\"...", xml_content_info[i].nca_id_str); + if (i < titleContentInfoCnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Dumping NCA \"%s\" (%s)...", xml_content_info[i].nca_id_str, getContentType(xml_content_info[i].type)); + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Writing \"%s\"...", entryFilename); + } - if (n > (xml_content_info[i].size - nca_offset)) n = (xml_content_info[i].size - nca_offset); + if (n > (nspPfs0EntryTable[i].file_size - fileOffset)) n = (nspPfs0EntryTable[i].file_size - fileOffset); // Check if the next read chunk will exceed the size of the current part file - if (seqDumpMode && (seqDumpSessionOffset + n) >= (((splitIndex - seqNspCtx.partNumber) + 1) * part_size)) + if (seqDumpMode && (seqDumpSessionOffset + n) >= (((splitIndex - seqNspCtx.partNumber) + 1) * partSize)) { - u64 new_file_chunk_size = ((seqDumpSessionOffset + n) - (((splitIndex - seqNspCtx.partNumber) + 1) * part_size)); + u64 new_file_chunk_size = ((seqDumpSessionOffset + n) - (((splitIndex - seqNspCtx.partNumber) + 1) * partSize)); u64 old_file_chunk_size = (n - new_file_chunk_size); u64 remainderDumpSize = (progressCtx.totalSize - (progressCtx.curOffset + old_file_chunk_size)); @@ -1879,84 +1947,91 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde // Check if we have enough space for the next part // If so, set the chunk size to old_file_chunk_size - if ((remainderDumpSize <= part_size && remainderDumpSize > remainderFreeSize) || (remainderDumpSize > part_size && part_size > remainderFreeSize)) + if ((remainderDumpSize <= partSize && remainderDumpSize > remainderFreeSize) || (remainderDumpSize > partSize && partSize > remainderFreeSize)) { n = old_file_chunk_size; seqDumpFinish = true; } } - breaks = (progressCtx.line_offset + 2); - - proceed = readNcaDataByContentId(&ncmStorage, &ncaId, nca_offset, dumpBuf, n); - if (!proceed) + if (i < (titleContentInfoCnt - 1)) { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk at offset 0x%016lX from NCA \"%s\"!", __func__, n, nca_offset, xml_content_info[i].nca_id_str); - dumping = false; - break; - } - - breaks = (progressCtx.line_offset - 4); - - // Replace NCA header with our modified one - if (nca_offset < NCA_FULL_HEADER_LENGTH) - { - u64 write_size = (NCA_FULL_HEADER_LENGTH - nca_offset); - if (write_size > n) write_size = n; + breaks = (progressCtx.line_offset + 2); - memcpy(dumpBuf, xml_content_info[i].encrypted_header_mod + nca_offset, write_size); - } - - // Replace modified Program NCA data blocks - if (ncaProgramMod.block_mod_cnt > 0 && xml_content_info[i].type == NcmContentType_Program) - { - u64 internal_block_offset; - u64 internal_block_chunk_size; - - u64 buffer_offset; - u64 buffer_chunk_size; - - if ((nca_offset + n) > ncaProgramMod.hash_table_offset && (ncaProgramMod.hash_table_offset + ncaProgramMod.hash_table_size) > nca_offset) + proceed = readNcaDataByContentId(&ncmStorage, &ncaId, fileOffset, dumpBuf, n); + if (!proceed) { - internal_block_offset = (nca_offset > ncaProgramMod.hash_table_offset ? (nca_offset - ncaProgramMod.hash_table_offset) : 0); - internal_block_chunk_size = (ncaProgramMod.hash_table_size - internal_block_offset); - - buffer_offset = (nca_offset > ncaProgramMod.hash_table_offset ? 0 : (ncaProgramMod.hash_table_offset - nca_offset)); - buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); - - memcpy(dumpBuf + buffer_offset, ncaProgramMod.hash_table + internal_block_offset, buffer_chunk_size); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk at offset 0x%016lX from NCA \"%s\"!", __func__, n, fileOffset, xml_content_info[i].nca_id_str); + dumping = false; + break; } - if ((nca_offset + n) > ncaProgramMod.block_offset[0] && (ncaProgramMod.block_offset[0] + ncaProgramMod.block_size[0]) > nca_offset) + breaks = (progressCtx.line_offset - 4); + + // Replace NCA header with our modified one + if (fileOffset < NCA_FULL_HEADER_LENGTH) { - internal_block_offset = (nca_offset > ncaProgramMod.block_offset[0] ? (nca_offset - ncaProgramMod.block_offset[0]) : 0); - internal_block_chunk_size = (ncaProgramMod.block_size[0] - internal_block_offset); + u64 write_size = (NCA_FULL_HEADER_LENGTH - fileOffset); + if (write_size > n) write_size = n; - buffer_offset = (nca_offset > ncaProgramMod.block_offset[0] ? 0 : (ncaProgramMod.block_offset[0] - nca_offset)); - buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); - - memcpy(dumpBuf + buffer_offset, ncaProgramMod.block_data[0] + internal_block_offset, buffer_chunk_size); + memcpy(dumpBuf, xml_content_info[i].encrypted_header_mod + fileOffset, write_size); } - if (ncaProgramMod.block_mod_cnt == 2 && (nca_offset + n) > ncaProgramMod.block_offset[1] && (ncaProgramMod.block_offset[1] + ncaProgramMod.block_size[1]) > nca_offset) + // Replace modified Program NCA data blocks + if (programModIdx != -1) { - internal_block_offset = (nca_offset > ncaProgramMod.block_offset[1] ? (nca_offset - ncaProgramMod.block_offset[1]) : 0); - internal_block_chunk_size = (ncaProgramMod.block_size[1] - internal_block_offset); + u64 internal_block_offset; + u64 internal_block_chunk_size; - buffer_offset = (nca_offset > ncaProgramMod.block_offset[1] ? 0 : (ncaProgramMod.block_offset[1] - nca_offset)); - buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); + u64 buffer_offset; + u64 buffer_chunk_size; - memcpy(dumpBuf + buffer_offset, ncaProgramMod.block_data[1] + internal_block_offset, buffer_chunk_size); + if ((fileOffset + n) > ncaProgramMod[programModIdx].hash_table_offset && (ncaProgramMod[programModIdx].hash_table_offset + ncaProgramMod[programModIdx].hash_table_size) > fileOffset) + { + internal_block_offset = (fileOffset > ncaProgramMod[programModIdx].hash_table_offset ? (fileOffset - ncaProgramMod[programModIdx].hash_table_offset) : 0); + internal_block_chunk_size = (ncaProgramMod[programModIdx].hash_table_size - internal_block_offset); + + buffer_offset = (fileOffset > ncaProgramMod[programModIdx].hash_table_offset ? 0 : (ncaProgramMod[programModIdx].hash_table_offset - fileOffset)); + buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); + + memcpy(dumpBuf + buffer_offset, ncaProgramMod[programModIdx].hash_table + internal_block_offset, buffer_chunk_size); + } + + if ((fileOffset + n) > ncaProgramMod[programModIdx].block_offset[0] && (ncaProgramMod[programModIdx].block_offset[0] + ncaProgramMod[programModIdx].block_size[0]) > fileOffset) + { + internal_block_offset = (fileOffset > ncaProgramMod[programModIdx].block_offset[0] ? (fileOffset - ncaProgramMod[programModIdx].block_offset[0]) : 0); + internal_block_chunk_size = (ncaProgramMod[programModIdx].block_size[0] - internal_block_offset); + + buffer_offset = (fileOffset > ncaProgramMod[programModIdx].block_offset[0] ? 0 : (ncaProgramMod[programModIdx].block_offset[0] - fileOffset)); + buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); + + memcpy(dumpBuf + buffer_offset, ncaProgramMod[programModIdx].block_data[0] + internal_block_offset, buffer_chunk_size); + } + + if (ncaProgramMod[programModIdx].block_mod_cnt == 2 && (fileOffset + n) > ncaProgramMod[programModIdx].block_offset[1] && (ncaProgramMod[programModIdx].block_offset[1] + ncaProgramMod[programModIdx].block_size[1]) > fileOffset) + { + internal_block_offset = (fileOffset > ncaProgramMod[programModIdx].block_offset[1] ? (fileOffset - ncaProgramMod[programModIdx].block_offset[1]) : 0); + internal_block_chunk_size = (ncaProgramMod[programModIdx].block_size[1] - internal_block_offset); + + buffer_offset = (fileOffset > ncaProgramMod[programModIdx].block_offset[1] ? 0 : (ncaProgramMod[programModIdx].block_offset[1] - fileOffset)); + buffer_chunk_size = ((n - buffer_offset) > internal_block_chunk_size ? internal_block_chunk_size : (n - buffer_offset)); + + memcpy(dumpBuf + buffer_offset, ncaProgramMod[programModIdx].block_data[1] + internal_block_offset, buffer_chunk_size); + } } + + // Update SHA-256 calculation + sha256ContextUpdate(&nca_hash_ctx, dumpBuf, n); + } else { + // Copy data using pointer array + u32 ptrIdx = (i - (titleContentInfoCnt - 1)); + memcpy(dumpBuf, nspPfs0FilePtrs[ptrIdx] + fileOffset, n); } - // Update SHA-256 calculation - sha256ContextUpdate(&nca_hash_ctx, dumpBuf, n); - - if ((seqDumpMode || (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)) && (progressCtx.curOffset + n) >= ((splitIndex + 1) * part_size)) + if ((seqDumpMode || (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)) && (progressCtx.curOffset + n) >= ((splitIndex + 1) * partSize)) { - u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * part_size)); + u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * partSize)); u64 old_file_chunk_size = (n - new_file_chunk_size); if (old_file_chunk_size > 0) @@ -2026,42 +2101,49 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde } } - if (!proceed) - { - setProgressBarError(&progressCtx); - if (seqDumpMode) seqDumpFileRemove = true; - break; - } else { - if (seqDumpMode && seqDumpFinish) - { - ret = 0; - break; - } - } + if (!proceed || ret >= 0) break; // Support empty files - if (!xml_content_info[i].size) + if (!nspPfs0EntryTable[i].file_size) { uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 4), FONT_COLOR_RGB, strrchr(dumpPath, '/' ) + 1); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 4), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Dumping NCA content \"%s\"...", xml_content_info[i].nca_id_str); + if (i < titleContentInfoCnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Dumping NCA \"%s\" (%s)...", xml_content_info[i].nca_id_str, getContentType(xml_content_info[i].type)); + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Writing \"%s\"...", entryFilename); + } printProgressBar(&progressCtx, false, 0); } - // Update content info - sha256ContextGetHash(&nca_hash_ctx, xml_content_info[i].hash); - convertDataToHexString(xml_content_info[i].hash, SHA256_HASH_SIZE, xml_content_info[i].hash_str, (SHA256_HASH_SIZE * 2) + 1); - memcpy(xml_content_info[i].nca_id, xml_content_info[i].hash, SHA256_HASH_SIZE / 2); - convertDataToHexString(xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); - - // If we're doing a sequential dump, copy the hash from the NCA we just finished dumping - if (seqDumpMode) memcpy(seqDumpNcaHashes + (i * SHA256_HASH_SIZE), xml_content_info[i].hash, SHA256_HASH_SIZE); + // Check if we're not dealing with the CNMT NCA + if (i < (titleContentInfoCnt - 1)) + { + // Update content info + sha256ContextGetHash(&nca_hash_ctx, xml_content_info[i].hash); + convertDataToHexString(xml_content_info[i].hash, SHA256_HASH_SIZE, xml_content_info[i].hash_str, (SHA256_HASH_SIZE * 2) + 1); + memcpy(xml_content_info[i].nca_id, xml_content_info[i].hash, SHA256_HASH_SIZE / 2); + convertDataToHexString(xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); + + // If we're doing a sequential dump and we just finished dumping a NCA, copy its calculated hash + if (seqDumpMode) memcpy(seqDumpNcaHashes + (i * SHA256_HASH_SIZE), xml_content_info[i].hash, SHA256_HASH_SIZE); + } } - if (!proceed || ret >= 0) goto out; + if (!proceed || ret >= 0) + { + if (!proceed) + { + setProgressBarError(&progressCtx); + if (seqDumpMode) seqDumpFileRemove = true; + } + + goto out; + } uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); @@ -2071,134 +2153,51 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde uiRefreshDisplay(); - // Now we can patch our CNMT NCA and generate our proper CNMT XML - if (!patchCnmtNca(cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size, &xml_program_info, xml_content_info, &ncaCnmtMod)) - { - setProgressBarError(&progressCtx); - goto out; - } - - generateCnmtXml(&xml_program_info, xml_content_info, cnmtXml); - - // Fill our Entry and String Tables - u64 file_offset = 0; - u32 filename_offset = 0; - - for(i = 0; i < nspFileCount; i++) - { - char ncaFileName[100] = {'\0'}; - u64 cur_file_size = 0; - - if (i < titleContentInfoCnt) - { - // Always reserve the first titleContentInfoCnt entries for our NCA contents - sprintf(ncaFileName, "%s.%s", xml_content_info[i].nca_id_str, (i == cnmtNcaIndex ? "cnmt.nca" : "nca")); - cur_file_size = xml_content_info[i].size; - } else - if (i == titleContentInfoCnt) - { - // Reserve the entry right after our NCA contents for the CNMT XML - sprintf(ncaFileName, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str); - cur_file_size = strlen(cnmtXml); - } else { - // Deal with additional files packed into the PFS0, in the following order: - // programinfo.xml (if available) - // NACP icons (if available) - // NACP XML (if available) - // legalinfo.xml (if available) - // Ticket (if available) - // Certificate chain (if available) - - if (programInfoXml && i == (titleContentInfoCnt + 1)) - { - // programinfo.xml entry - sprintf(ncaFileName, "%s.programinfo.xml", xml_content_info[programNcaIndex].nca_id_str); - cur_file_size = programInfoXmlSize; - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) - { - // NACP icon entry - // Replace the NCA ID from its filename, since it could have changed - u32 icon_idx = (!programInfoXml ? (i - (titleContentInfoCnt + 1)) : (i - (titleContentInfoCnt + 2))); - sprintf(ncaFileName, "%s%s", xml_content_info[nacpNcaIndex].nca_id_str, strchr(nacpIcons[icon_idx].filename, '.')); - cur_file_size = nacpIcons[icon_idx].icon_size; - } else - if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) - { - // NACP XML entry - // If there are no icons, this will effectively make it the next entry after the CNMT XML - sprintf(ncaFileName, "%s.nacp.xml", xml_content_info[nacpNcaIndex].nca_id_str); - cur_file_size = nacpXmlSize; - } else - if (legalInfoXml && ((!includeTikAndCert && i == (nspFileCount - 1)) || (includeTikAndCert && i == (nspFileCount - 3)))) - { - // legalinfo.xml entry - // If none of the previous conditions are met, assume we're dealing with a legalinfo.xml depending on the includeTikAndCert and counter values - sprintf(ncaFileName, "%s.legalinfo.xml", xml_content_info[legalInfoNcaIndex].nca_id_str); - cur_file_size = legalInfoXmlSize; - } else { - // tik/cert entry - sprintf(ncaFileName, "%s", (i == (nspFileCount - 2) ? rights_info.tik_filename : rights_info.cert_filename)); - cur_file_size = (i == (nspFileCount - 2) ? ETICKET_TIK_FILE_SIZE : ETICKET_CERT_FILE_SIZE); - } - } - - nspPfs0EntryTable[i].file_size = cur_file_size; - nspPfs0EntryTable[i].file_offset = file_offset; - nspPfs0EntryTable[i].filename_offset = filename_offset; - - strcpy(nspPfs0StrTable + filename_offset, ncaFileName); - - file_offset += nspPfs0EntryTable[i].file_size; - filename_offset += (strlen(ncaFileName) + 1); - } - // Write our full PFS0 header memcpy(dumpBuf, &nspPfs0Header, sizeof(pfs0_header)); - memcpy(dumpBuf + sizeof(pfs0_header), nspPfs0EntryTable, (u64)nspFileCount * sizeof(pfs0_file_entry)); - memcpy(dumpBuf + sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_file_entry)), nspPfs0StrTable, nspPfs0Header.str_table_size); + memcpy(dumpBuf + sizeof(pfs0_header), nspPfs0EntryTable, (u64)nspPfs0Header.file_cnt * sizeof(pfs0_file_entry)); + memcpy(dumpBuf + sizeof(pfs0_header) + ((u64)nspPfs0Header.file_cnt * sizeof(pfs0_file_entry)), nspPfs0StrTable, nspPfs0Header.str_table_size); if (seqDumpMode) { - // Check if the PFS0 header file already exists - if (!checkIfFileExists(pfs0HeaderFilename)) + // Just in case + remove(pfs0HeaderFilename); + + // Check if we have enough space for the header file + u64 curFreeSpace = (freeSpace - seqDumpSessionOffset); + if (!seqNspCtx.partNumber) curFreeSpace += fullPfs0HeaderSize; // The PFS0 header size is skipped during the first sequential dump session + + if (curFreeSpace < fullPfs0HeaderSize) { - // Check if we have enough space for the header file - u64 curFreeSpace = (freeSpace - seqDumpSessionOffset); - if (!seqNspCtx.partNumber) curFreeSpace += full_nsp_header_size; // The PFS0 header size is skipped during the first sequential dump session - - if (curFreeSpace < full_nsp_header_size) - { - // Finish current sequential dump session - seqDumpFinish = true; - ret = 0; - goto out; - } - - pfs0HeaderFile = fopen(pfs0HeaderFilename, "wb"); - if (!pfs0HeaderFile) - { - setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to create PFS0 header file!", __func__); - seqDumpFileRemove = true; - goto out; - } - - write_res = fwrite(dumpBuf, 1, full_nsp_header_size, pfs0HeaderFile); - fclose(pfs0HeaderFile); - - if (write_res != full_nsp_header_size) - { - setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes PFS0 header file! (wrote %lu bytes)", __func__, full_nsp_header_size, write_res); - unlink(pfs0HeaderFilename); - seqDumpFileRemove = true; - goto out; - } - - // Update free space - freeSpace -= full_nsp_header_size; + // Finish current sequential dump session + seqDumpFinish = true; + ret = 0; + goto out; } + + pfs0HeaderFile = fopen(pfs0HeaderFilename, "wb"); + if (!pfs0HeaderFile) + { + setProgressBarError(&progressCtx); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to create PFS0 header file!", __func__); + seqDumpFileRemove = true; + goto out; + } + + write_res = fwrite(dumpBuf, 1, fullPfs0HeaderSize, pfs0HeaderFile); + fclose(pfs0HeaderFile); + + if (write_res != fullPfs0HeaderSize) + { + setProgressBarError(&progressCtx); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes PFS0 header file! (wrote %lu bytes)", __func__, fullPfs0HeaderSize, write_res); + remove(pfs0HeaderFilename); + seqDumpFileRemove = true; + goto out; + } + + // Update free space + freeSpace -= fullPfs0HeaderSize; } else { if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { @@ -2221,261 +2220,15 @@ int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInde rewind(outFile); } - write_res = fwrite(dumpBuf, 1, full_nsp_header_size, outFile); - if (write_res != full_nsp_header_size) + write_res = fwrite(dumpBuf, 1, fullPfs0HeaderSize, outFile); + if (write_res != fullPfs0HeaderSize) { setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes PFS0 header to file offset 0x%016lX! (wrote %lu bytes)", __func__, full_nsp_header_size, (u64)0, write_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes PFS0 header to file offset 0x%016lX! (wrote %lu bytes)", __func__, fullPfs0HeaderSize, (u64)0, write_res); goto out; } - - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) - { - if (outFile) - { - fclose(outFile); - outFile = NULL; - } - - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex); - - outFile = fopen(dumpPath, "rb+"); - if (!outFile) - { - setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to re-open output file for part #%u!", __func__, splitIndex); - goto out; - } - } - - fseek(outFile, 0, SEEK_END); } - startFileIndex = ((seqDumpMode && seqNspCtx.fileIndex > (titleContentInfoCnt - 1)) ? seqNspCtx.fileIndex : (titleContentInfoCnt - 1)); - - // Now let's write the rest of the data, including our modified CNMT NCA - for(i = startFileIndex; i < nspFileCount; i++, startFileIndex++) - { - n = DUMP_BUFFER_SIZE; - - startFileOffset = ((seqDumpMode && i == seqNspCtx.fileIndex) ? seqNspCtx.fileOffset : 0); - - char ncaFileName[100] = {'\0'}; - u64 cur_file_size = 0; - - if (i == (titleContentInfoCnt - 1)) - { - // CNMT NCA - sprintf(ncaFileName, "%s.cnmt.nca", xml_content_info[i].nca_id_str); - cur_file_size = xml_content_info[cnmtNcaIndex].size; - } else - if (i == titleContentInfoCnt) - { - // CNMT XML - sprintf(ncaFileName, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str); - cur_file_size = strlen(cnmtXml); - } else { - if (programInfoXml && i == (titleContentInfoCnt + 1)) - { - // programinfo.xml entry - sprintf(ncaFileName, "%s.programinfo.xml", xml_content_info[programNcaIndex].nca_id_str); - cur_file_size = programInfoXmlSize; - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) - { - // NACP icon entry - u32 icon_idx = (!programInfoXml ? (i - (titleContentInfoCnt + 1)) : (i - (titleContentInfoCnt + 2))); - sprintf(ncaFileName, nacpIcons[icon_idx].filename); - cur_file_size = nacpIcons[icon_idx].icon_size; - } else - if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) - { - // NACP XML entry - sprintf(ncaFileName, "%s.nacp.xml", xml_content_info[nacpNcaIndex].nca_id_str); - cur_file_size = nacpXmlSize; - } else - if (legalInfoXml && ((!includeTikAndCert && i == (nspFileCount - 1)) || (includeTikAndCert && i == (nspFileCount - 3)))) - { - // legalinfo.xml entry - sprintf(ncaFileName, "%s.legalinfo.xml", xml_content_info[legalInfoNcaIndex].nca_id_str); - cur_file_size = legalInfoXmlSize; - } else { - // tik/cert entry - sprintf(ncaFileName, "%s", (i == (nspFileCount - 2) ? rights_info.tik_filename : rights_info.cert_filename)); - cur_file_size = (i == (nspFileCount - 2) ? ETICKET_TIK_FILE_SIZE : ETICKET_CERT_FILE_SIZE); - } - } - - for(nca_offset = startFileOffset; nca_offset < cur_file_size; nca_offset += n, progressCtx.curOffset += n, seqDumpSessionOffset += n) - { - if (seqDumpMode && seqDumpFinish) break; - - uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 4), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Writing \"%s\"...", ncaFileName); - - uiRefreshDisplay(); - - if (n > (cur_file_size - nca_offset)) n = (cur_file_size - nca_offset); - - // Check if the next read chunk will exceed the size of the current part file - if (seqDumpMode && (seqDumpSessionOffset + n) >= (((splitIndex - seqNspCtx.partNumber) + 1) * part_size)) - { - u64 new_file_chunk_size = ((seqDumpSessionOffset + n) - (((splitIndex - seqNspCtx.partNumber) + 1) * part_size)); - u64 old_file_chunk_size = (n - new_file_chunk_size); - - u64 remainderDumpSize = (progressCtx.totalSize - (progressCtx.curOffset + old_file_chunk_size)); - u64 remainderFreeSize = (freeSpace - (seqDumpSessionOffset + old_file_chunk_size)); - - // Check if we have enough space for the next part - // If so, set the chunk size to old_file_chunk_size - if ((remainderDumpSize <= part_size && remainderDumpSize > remainderFreeSize) || (remainderDumpSize > part_size && part_size > remainderFreeSize)) - { - n = old_file_chunk_size; - seqDumpFinish = true; - } - } - - // Retrieve data from its respective source - if (i == (titleContentInfoCnt - 1)) - { - // CNMT NCA - memcpy(dumpBuf, cnmtNcaBuf + nca_offset, n); - } else - if (i == titleContentInfoCnt) - { - // CNMT XML - memcpy(dumpBuf, cnmtXml + nca_offset, n); - } else { - if (programInfoXml && i == (titleContentInfoCnt + 1)) - { - // programinfo.xml entry - memcpy(dumpBuf, programInfoXml + nca_offset, n); - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) - { - // NACP icon entry - u32 icon_idx = (!programInfoXml ? (i - (titleContentInfoCnt + 1)) : (i - (titleContentInfoCnt + 2))); - memcpy(dumpBuf, nacpIcons[icon_idx].icon_data + nca_offset, n); - } else - if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) - { - // NACP XML entry - memcpy(dumpBuf, nacpXml + nca_offset, n); - } else - if (legalInfoXml && ((!includeTikAndCert && i == (nspFileCount - 1)) || (includeTikAndCert && i == (nspFileCount - 3)))) - { - // legalinfo.xml entry - memcpy(dumpBuf, legalInfoXml + nca_offset, n); - } else { - // tik/cert entry - if (i == (nspFileCount - 2)) - { - memcpy(dumpBuf, (u8*)(&(rights_info.tik_data)) + nca_offset, n); - } else { - memcpy(dumpBuf, rights_info.cert_data + nca_offset, n); - } - } - } - - if ((seqDumpMode || (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)) && (progressCtx.curOffset + n) >= ((splitIndex + 1) * part_size)) - { - u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * part_size)); - u64 old_file_chunk_size = (n - new_file_chunk_size); - - if (old_file_chunk_size > 0) - { - write_res = fwrite(dumpBuf, 1, old_file_chunk_size, outFile); - if (write_res != old_file_chunk_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", __func__, old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); - proceed = false; - break; - } - } - - fclose(outFile); - outFile = NULL; - - if (((seqDumpMode && !seqDumpFinish) || !seqDumpMode) && (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)) - { - splitIndex++; - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp%c%02u", NSP_DUMP_PATH, dumpName, (seqDumpMode ? '.' : '/'), splitIndex); - - outFile = fopen(dumpPath, "wb"); - if (!outFile) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to open output file for part #%u!", __func__, splitIndex); - proceed = false; - break; - } - - if (new_file_chunk_size > 0) - { - write_res = fwrite(dumpBuf + old_file_chunk_size, 1, new_file_chunk_size, outFile); - if (write_res != new_file_chunk_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", __func__, new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); - proceed = false; - break; - } - } - } - } else { - write_res = fwrite(dumpBuf, 1, n, outFile); - if (write_res != n) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", __func__, n, progressCtx.curOffset, write_res); - - if (!seqDumpMode && (progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 4), FONT_COLOR_RGB, "You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option."); - fat32_error = true; - } - - proceed = false; - break; - } - } - - if (seqDumpMode) progressCtx.seqDumpCurOffset = seqDumpSessionOffset; - printProgressBar(&progressCtx, true, n); - - if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - ret = -2; - proceed = false; - break; - } - } - - if (!proceed) - { - setProgressBarError(&progressCtx); - if (seqDumpMode) seqDumpFileRemove = true; - break; - } else { - if (seqDumpMode && seqDumpFinish) break; - } - - // Support empty files - if (!cur_file_size) - { - uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 4), FONT_COLOR_RGB, strrchr(dumpPath, '/' ) + 1); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Writing \"%s\"...", ncaFileName); - - printProgressBar(&progressCtx, false, 0); - } - } - - if (!proceed) goto out; - dumping = false; breaks = (progressCtx.line_offset + 2); @@ -2514,10 +2267,10 @@ out: // Update line count breaks = (progressCtx.line_offset + 2); - // Update the sequence reference file in the SD card + // Update the sequence reference file seqNspCtx.partNumber = (splitIndex + 1); seqNspCtx.fileIndex = startFileIndex; - seqNspCtx.fileOffset = nca_offset; + seqNspCtx.fileOffset = fileOffset; // Copy the SHA-256 context data, but only if we're not dealing with the CNMT NCA // NCA ID/hash for the CNMT NCA is handled in patchCnmtNca() @@ -2533,10 +2286,10 @@ out: if (write_res == sizeof(sequentialNspCtx)) { // Write the NCA hashes - write_res = fwrite(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); - if (write_res != (seqDumpFileSize - sizeof(sequentialNspCtx))) + write_res = fwrite(seqDumpNcaHashes, 1, seqNspCtx.ncaCount * SHA256_HASH_SIZE, seqDumpFile); + if (write_res != (seqNspCtx.ncaCount * SHA256_HASH_SIZE)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", __func__, seqDumpFileSize - sizeof(sequentialNspCtx), write_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", __func__, seqNspCtx.ncaCount * SHA256_HASH_SIZE, write_res); ret = -1; seqDumpFileRemove = true; } @@ -2630,7 +2383,7 @@ out: for(u8 i = 0; i <= splitIndex; i++) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp.%02u", NSP_DUMP_PATH, dumpName, i); - unlink(dumpPath); + remove(dumpPath); } } else { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); @@ -2639,12 +2392,14 @@ out: { fsdevDeleteDirectoryRecursively(dumpPath); } else { - unlink(dumpPath); + remove(dumpPath); } } } } + if (nspPfs0FilePtrs) free(nspPfs0FilePtrs); + if (nspPfs0StrTable) free(nspPfs0StrTable); if (nspPfs0EntryTable) free(nspPfs0EntryTable); @@ -2653,19 +2408,28 @@ out: if (cnmtNcaBuf) free(cnmtNcaBuf); - if (ncaProgramMod.block_data[1]) free(ncaProgramMod.block_data[1]); + if (ncaProgramMod) + { + for(i = 0; i < ncaProgramModCnt; i++) + { + if (ncaProgramMod[i].hash_table) free(ncaProgramMod[i].hash_table); + if (ncaProgramMod[i].block_data[0]) free(ncaProgramMod[i].block_data[0]); + if (ncaProgramMod[i].block_data[1]) free(ncaProgramMod[i].block_data[1]); + } + + free(ncaProgramMod); + } - if (ncaProgramMod.block_data[0]) free(ncaProgramMod.block_data[0]); - - if (ncaProgramMod.hash_table) free(ncaProgramMod.hash_table); - - if (legalInfoXml) free(legalInfoXml); - - if (nacpIcons) free(nacpIcons); - - if (nacpXml) free(nacpXml); - - if (programInfoXml) free(programInfoXml); + if (xml_records) + { + for(i = 0; i < xml_rec_cnt; i++) + { + if (xml_records[i].xml_data) free(xml_records[i].xml_data); + if (xml_records[i].nacp_icons) free(xml_records[i].nacp_icons); + } + + free(xml_records); + } if (xml_content_info) free(xml_content_info); @@ -2679,10 +2443,12 @@ out: if (seqDumpFile) fclose(seqDumpFile); - if (seqDumpFileRemove) unlink(seqDumpFilename); + if (seqDumpFileRemove) remove(seqDumpFilename); if (dumpName) free(dumpName); + if (!batch) changeHomeButtonBlockStatus(false); + return ret; } @@ -2733,8 +2499,7 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) u32 titleCount = 0, titleIndex = 0; char *dumpName = NULL; - char dumpPath[NAME_BUF_LEN] = {'\0'}; - char summary_str[256] = {'\0'}; + char summary_str[128] = {'\0'}; int initial_breaks = breaks, cur_breaks; @@ -2813,16 +2578,16 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) } // Check if an override file already exists for this dump - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); - if (checkIfFileExists(dumpPath)) + if (checkIfFileExists(strbuf)) { free(dumpName); dumpName = NULL; continue; } - snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_CHARACTERS(batchEntries[batchEntryIndex].nspFilename), strrchr(dumpPath, '/') + 1); + snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_CHARACTERS(batchEntries[batchEntryIndex].nspFilename), strrchr(strbuf, '/') + 1); snprintf(batchEntries[batchEntryIndex].truncatedNspFilename, MAX_CHARACTERS(batchEntries[batchEntryIndex].truncatedNspFilename), batchEntries[batchEntryIndex].nspFilename); if (useBrackets) @@ -2841,12 +2606,12 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) } // Check if this title has already been dumped - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s.nsp", NSP_DUMP_PATH, dumpName); free(dumpName); dumpName = NULL; - if (skipDumpedTitles && checkIfFileExists(dumpPath)) continue; + if (skipDumpedTitles && checkIfFileExists(strbuf)) continue; // Save title properties batchEntries[batchEntryIndex].enabled = true; @@ -2918,27 +2683,27 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) if (totalAppCount) { - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "BASE: %u", totalAppCount); - strcat(summary_str, dumpPath); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "BASE: %u", totalAppCount); + strcat(summary_str, strbuf); } if (totalPatchCount) { if (totalAppCount) strcat(summary_str, " | "); - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "UPD: %u", totalPatchCount); - strcat(summary_str, dumpPath); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "UPD: %u", totalPatchCount); + strcat(summary_str, strbuf); } if (totalAddOnCount) { if (totalAppCount || totalPatchCount) strcat(summary_str, " | "); - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "DLC: %u", totalAddOnCount); - strcat(summary_str, dumpPath); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "DLC: %u", totalAddOnCount); + strcat(summary_str, strbuf); } strcat(summary_str, " | "); - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "Total: %u", totalTitleCount); - strcat(summary_str, dumpPath); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Total: %u", totalTitleCount); + strcat(summary_str, strbuf); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, summary_str); breaks++; @@ -2992,24 +2757,34 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) if (i >= totalTitleCount) break; xpos = STRING_X_POS; - ypos = ((cur_breaks * LINE_HEIGHT) + (j * (font_height + 12)) + 6); + ypos = (8 + (cur_breaks * LINE_HEIGHT) + (j * (font_height + 12)) + 6); if (i == selectedSummaryEntry) { highlight = true; - uiFill(0, (ypos + 8) - 6, FB_WIDTH, font_height + 12, HIGHLIGHT_BG_COLOR_RGB); + uiFill(0, ypos - 6, FB_WIDTH, font_height + 12, HIGHLIGHT_BG_COLOR_RGB); } - uiDrawIcon((highlight ? (batchEntries[i].enabled ? enabledHighlightIconBuf : disabledHighlightIconBuf) : (batchEntries[i].enabled ? enabledNormalIconBuf : disabledNormalIconBuf)), BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos + 8); - xpos += BROWSER_ICON_DIMENSION; + uiDrawIcon((highlight ? (batchEntries[i].enabled ? enabledHighlightIconBuf : disabledHighlightIconBuf) : (batchEntries[i].enabled ? enabledNormalIconBuf : disabledNormalIconBuf)), BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos); + xpos += (BROWSER_ICON_DIMENSION + 8); if (highlight) { uiDrawString(xpos, ypos, HIGHLIGHT_FONT_COLOR_RGB, batchEntries[i].truncatedNspFilename); - if (batchEntries[i].contentSize) uiDrawString(FB_WIDTH - (font_height * 7), ypos, HIGHLIGHT_FONT_COLOR_RGB, "(%s)", batchEntries[i].contentSizeStr); + + if (batchEntries[i].contentSize) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "(%s)", batchEntries[i].contentSizeStr); + uiDrawString(FB_WIDTH - (8 + uiGetStrWidth(strbuf)), ypos, HIGHLIGHT_FONT_COLOR_RGB, strbuf); + } } else { uiDrawString(xpos, ypos, FONT_COLOR_RGB, batchEntries[i].truncatedNspFilename); - if (batchEntries[i].contentSize) uiDrawString(FB_WIDTH - (font_height * 7), ypos, FONT_COLOR_RGB, "(%s)", batchEntries[i].contentSizeStr); + + if (batchEntries[i].contentSize) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "(%s)", batchEntries[i].contentSizeStr); + uiDrawString(FB_WIDTH - (8 + uiGetStrWidth(strbuf)), ypos, FONT_COLOR_RGB, strbuf); + } } if (i == selectedSummaryEntry) highlight = false; @@ -3022,8 +2797,8 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) hidScanInput(); - keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - keysHeld = hidKeysHeld(CONTROLLER_P1_AUTO); + keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); + keysHeld = hidKeysAllHeld(CONTROLLER_P1_AUTO); if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH))) break; } @@ -3152,8 +2927,12 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) if (!batchEntries[i].enabled) disabledEntryCount++; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - breaks += 2; + // Start dump process + dumpStartMsg(); + uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); initial_breaks = breaks; @@ -3182,8 +2961,8 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) // Create override file if necessary if (rememberDumpedTitles) { - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", BATCH_OVERRIDES_PATH, batchEntries[i].nspFilename); - FILE *overrideFile = fopen(dumpPath, "wb"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", BATCH_OVERRIDES_PATH, batchEntries[i].nspFilename); + FILE *overrideFile = fopen(strbuf, "wb"); if (overrideFile) fclose(overrideFile); } } else { @@ -3210,6 +2989,8 @@ int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) out: if (batchEntries) free(batchEntries); + changeHomeButtonBlockStatus(false); + return ret; } @@ -3287,7 +3068,7 @@ bool dumpRawHfs0Partition(u32 partition, bool doSplitting) } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -3305,18 +3086,13 @@ bool dumpRawHfs0Partition(u32 partition, bool doSplitting) goto out; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dumping raw HFS0 partition #%u. Hold %s to cancel.", partition, NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 2); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -3439,10 +3215,10 @@ out: for(u8 i = 0; i <= splitIndex; i++) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition), i); - unlink(dumpPath); + remove(dumpPath); } } else { - unlink(dumpPath); + remove(dumpPath); } } @@ -3452,6 +3228,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -3499,7 +3277,7 @@ bool copyFileFromHfs0Partition(u32 partition, const char *dest, const char *sour outFile = fopen(((fileSize > FAT32_FILESIZE_LIMIT && doSplitting) ? splitFilename : dest), "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_RGB, "%s: failed to open output file!", __func__); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to open output file!", __func__); goto out; } @@ -3616,10 +3394,10 @@ out: for(u8 i = 0; i <= splitIndex; i++) { snprintf(splitFilename, MAX_CHARACTERS(splitFilename), "%s.%02u", dest, i); - unlink(splitFilename); + remove(splitFilename); } } else { - unlink(dest); + remove(dest); } } @@ -3750,18 +3528,13 @@ bool dumpHfs0PartitionData(u32 partition, bool doSplitting) snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Partition %u (%s)", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Copying partition #%u data to \"%s\". Hold %s to cancel.", partition, strrchr(dumpPath, '/'), NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); @@ -3785,6 +3558,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -3869,7 +3644,7 @@ bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, boo } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -3880,18 +3655,13 @@ bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, boo goto out; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -3918,6 +3688,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -4000,11 +3772,17 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDump if (!useLayeredFSDir) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + + if (exeFsContext.idOffset > 0) + { + sprintf(strbuf, " (ID offset #%u)", exeFsContext.idOffset); + strcat(dumpPath, strbuf); + } } else { mkdir(cfwDirStr, 0744); // Always use the base application title ID - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, (usePatch ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : baseAppEntries[titleIndex].titleId)); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, (usePatch ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : baseAppEntries[titleIndex].titleId) + exeFsContext.idOffset); mkdir(dumpPath, 0744); strcat(dumpPath, "/exefs"); @@ -4014,18 +3792,12 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDump // Start dump process breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -4213,6 +3985,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -4272,11 +4046,17 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaF } snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + + if (exeFsContext.idOffset > 0) + { + sprintf(strbuf, " (ID offset #%u)", exeFsContext.idOffset); + strcat(dumpPath, strbuf); + } } else { mkdir(cfwDirStr, 0744); // Always use the base application title ID - snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, (usePatch ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : baseAppEntries[titleIndex].titleId)); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, (usePatch ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : baseAppEntries[titleIndex].titleId) + + exeFsContext.idOffset); mkdir(dumpPath, 0744); strcat(dumpPath, "/exefs"); @@ -4318,7 +4098,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaF } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -4326,7 +4106,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaF { // Since we may actually be dealing with an existing directory with the archive bit set or unset, let's try both // Better safe than sorry - unlink(dumpPath); + remove(dumpPath); fsdevDeleteDirectoryRecursively(dumpPath); mkdir(dumpPath, 0744); @@ -4334,18 +4114,14 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaF strcat(dumpPath, tmp_idx); } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); + uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Copying \"%s\"...", exeFsFilename); breaks += 2; @@ -4490,13 +4266,15 @@ out: if (removeFile) fsdevDeleteDirectoryRecursively(dumpPath); } } else { - if (!success && removeFile) unlink(dumpPath); + if (!success && removeFile) remove(dumpPath); } if (dumpName) free(dumpName); breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -4516,6 +4294,9 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path u8 splitIndex = 0; bool proceed = true, success = false, fat32_error = false; + // Used to overcome issues related to the max entry count per directory in FAT32 + int dir_limit_counter = -1; + u32 romfs_file_offset = file_offset; romfs_file *entry = NULL; @@ -4523,7 +4304,7 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path size_t write_res; - char tmp_idx[5]; + char tmp_idx[16]; memset(dumpBuf, 0, DUMP_BUFFER_SIZE); @@ -4544,7 +4325,14 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path break; } - if ((orig_romfs_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2) || (orig_output_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2)) + if (dir_limit_counter >= 0) + { + sprintf(tmp_idx, "_%d", dir_limit_counter); + } else { + tmp_idx[0] = '\0'; + } + + if ((orig_romfs_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2) || (orig_output_path_len + strlen(tmp_idx) + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2)) { uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: RomFS section file path is too long!", __func__); break; @@ -4554,9 +4342,10 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path strcat(romfs_path, "/"); strncat(romfs_path, (char*)entry->name, entry->nameLen); + if (dir_limit_counter >= 0) strcat(output_path, tmp_idx); strcat(output_path, "/"); strncat(output_path, (char*)entry->name, entry->nameLen); - removeIllegalCharacters(output_path + orig_output_path_len + 1); + removeIllegalCharacters(output_path + orig_output_path_len + strlen(tmp_idx) + 1); if (entry->dataSize > FAT32_FILESIZE_LIMIT && isFat32) { @@ -4573,8 +4362,27 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path outFile = fopen(output_path, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_RGB, "%s: failed to open output file \"%s\"!", __func__, output_path); - break; + if (entry->dataSize <= FAT32_FILESIZE_LIMIT || !isFat32) + { + output_path[orig_output_path_len] = '\0'; + + dir_limit_counter++; + sprintf(tmp_idx, "_%d", dir_limit_counter); + strcat(output_path, tmp_idx); + mkdir(output_path, 0744); + + strcat(output_path, "/"); + strncat(output_path, (char*)entry->name, entry->nameLen); + removeIllegalCharacters(output_path + orig_output_path_len + strlen(tmp_idx) + 1); + + outFile = fopen(output_path, "wb"); + } + + if (!outFile) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, output_path); + break; + } } for(off = 0; off < entry->dataSize; off += n, progressCtx->curOffset += n) @@ -4833,7 +4641,7 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsO } // Retrieve RomFS from Program NCA - if (!readNcaRomFsSection(titleIndex, curRomFsType)) + if (!readNcaRomFsSection(titleIndex, curRomFsType, -1)) { free(dumpName); breaks += 2; @@ -4858,12 +4666,20 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsO if (!useLayeredFSDir) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + + if ((curRomFsType != ROMFS_TYPE_PATCH && romFsContext.idOffset > 0) || (curRomFsType == ROMFS_TYPE_PATCH && bktrContext.idOffset > 0)) + { + sprintf(strbuf, " (ID offset #%u)", (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset)); + strcat(dumpPath, strbuf); + } } else { mkdir(cfwDirStr, 0744); // Base applications and updates: always use the base application title ID // DLCs: use DLC title ID u64 titleId = (curRomFsType == ROMFS_TYPE_APP ? baseAppEntries[titleIndex].titleId : (curRomFsType == ROMFS_TYPE_PATCH ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : addOnEntries[titleIndex].titleId)); + titleId += (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); mkdir(dumpPath, 0744); @@ -4874,18 +4690,12 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsO // Start dump process breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -4915,6 +4725,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -4989,12 +4801,20 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType } snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + + if ((curRomFsType != ROMFS_TYPE_PATCH && romFsContext.idOffset > 0) || (curRomFsType == ROMFS_TYPE_PATCH && bktrContext.idOffset > 0)) + { + sprintf(strbuf, " (ID offset #%u)", (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset)); + strcat(dumpPath, strbuf); + } } else { mkdir(cfwDirStr, 0744); // Base applications and updates: always use the base application title ID // DLCs: use DLC title ID u64 titleId = (curRomFsType == ROMFS_TYPE_APP ? baseAppEntries[titleIndex].titleId : (curRomFsType == ROMFS_TYPE_PATCH ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : addOnEntries[titleIndex].titleId)); + titleId += (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); mkdir(dumpPath, 0744); @@ -5056,7 +4876,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -5064,7 +4884,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType { // Since we may actually be dealing with an existing directory with the archive bit set or unset, let's try both // Better safe than sorry - unlink(dumpPath); + remove(dumpPath); fsdevDeleteDirectoryRecursively(dumpPath); mkdir(dumpPath, 0744); @@ -5072,20 +4892,14 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType strcat(dumpPath, tmp_idx); } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - - uiRefreshDisplay(); - // Start dump process + dumpStartMsg(); + appletModeOperationWarning(); + uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); + if (strlen(curRomFsPath) > 1) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Copying \"romfs:%s/%.*s\"...", curRomFsPath, entry->nameLen, entry->name); @@ -5241,13 +5055,15 @@ out: if (removeFile) fsdevDeleteDirectoryRecursively(dumpPath); } } else { - if (!success && removeFile) unlink(dumpPath); + if (!success && removeFile) remove(dumpPath); } if (dumpName) free(dumpName); breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -5323,12 +5139,20 @@ bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsTy if (!useLayeredFSDir) { snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + + if ((curRomFsType != ROMFS_TYPE_PATCH && romFsContext.idOffset > 0) || (curRomFsType == ROMFS_TYPE_PATCH && bktrContext.idOffset > 0)) + { + sprintf(strbuf, " (ID offset #%u)", (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset)); + strcat(dumpPath, strbuf); + } } else { mkdir(cfwDirStr, 0744); // Base applications and updates: always use the base application title ID // DLCs: use DLC title ID u64 titleId = (curRomFsType == ROMFS_TYPE_APP ? baseAppEntries[titleIndex].titleId : (curRomFsType == ROMFS_TYPE_PATCH ? (patchEntries[titleIndex].titleId & ~APPLICATION_PATCH_BITMASK) : addOnEntries[titleIndex].titleId)); + titleId += (curRomFsType != ROMFS_TYPE_PATCH ? romFsContext.idOffset : bktrContext.idOffset); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); mkdir(dumpPath, 0744); @@ -5371,18 +5195,12 @@ bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsTy // Start dump process breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - breaks++; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - - breaks++; - + dumpStartMsg(); + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -5408,6 +5226,8 @@ out: breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } @@ -5433,12 +5253,7 @@ bool dumpGameCardCertificate() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dumping gamecard certificate. Please wait."); breaks++; - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - + appletModeOperationWarning(); breaks++; uiRefreshDisplay(); @@ -5481,7 +5296,7 @@ bool dumpGameCardCertificate() } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } @@ -5509,7 +5324,7 @@ bool dumpGameCardCertificate() out: if (outFile) fclose(outFile); - if (!success && strlen(dumpPath)) unlink(dumpPath); + if (!success && strlen(dumpPath)) remove(dumpPath); closeGameCardStoragePartition(); @@ -5636,19 +5451,14 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp } else { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); } } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving Rights ID and Ticket for the selected %s...", (curTikType == TICKET_TYPE_APP ? "base application" : (curTikType == TICKET_TYPE_PATCH ? "update" : "DLC"))); breaks++; - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks++; - } - + appletModeOperationWarning(); breaks++; uiRefreshDisplay(); @@ -5755,7 +5565,7 @@ out: if (outFile) fclose(outFile); - if (!success && removeFile) unlink(dumpPath); + if (!success && removeFile) remove(dumpPath); ncmContentStorageClose(&ncmStorage); diff --git a/source/dumper.h b/source/dumper.h index 7927930..bb7828f 100644 --- a/source/dumper.h +++ b/source/dumper.h @@ -27,19 +27,21 @@ typedef struct { u32 certlessCrc; // CRC32 checksum accumulator (certless XCI). Only used if calcCrc == true } PACKED sequentialXciCtx; +// This struct is followed by 'ncaCount' SHA-256 checksums in the output file and 'programNcaModCount' modified + reencrypted Program NCA headers +// The modified NCA headers are only needed if their NPDM signature is replaced (it uses cryptographically secure random numbers). The RSA public key used in the ACID section from the main.npdm file is constant, so we don't need to keep track of that typedef struct { - NcmStorageId storageId; // Source storage from which the data is dumped + NcmStorageId storageId; // Source storage from which the data is dumped bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false bool npdmAcidRsaPatch; // Original value for the "Change NPDM RSA key/sig in Program NCA" option. Overrides the selected setting in the current session bool preInstall; // Indicates if we're dealing with a preinstalled title - e.g. if the user already accepted the missing ticket prompt u8 partNumber; // Next part number - u32 nspFileCount; // PFS0 file count + u32 pfs0FileCount; // PFS0 file count u32 ncaCount; // NCA count + u32 programNcaModCount; // Program NCA mod count u32 fileIndex; // Current PFS0 file entry index u64 fileOffset; // Current PFS0 file entry offset Sha256Context hashCtx; // Current NCA SHA-256 checksum context. Only used when dealing with the same NCA between different parts - u8 programNcaHeaderMod[NCA_FULL_HEADER_LENGTH]; // Modified + reencrypted Program NCA header. Only needed if the NPDM signature in the Program NCA header is replaced (it uses cryptographically secure random numbers). The RSA public key used in the ACID section from the main.npdm file is constant, so we don't need to keep track of that } PACKED sequentialNspCtx; typedef struct { diff --git a/source/es.c b/source/es.c index 27592b7..cd05c05 100644 --- a/source/es.c +++ b/source/es.c @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/source/fatfs/diskio.c b/source/fatfs/diskio.c index 9e2122e..497413a 100644 --- a/source/fatfs/diskio.c +++ b/source/fatfs/diskio.c @@ -22,6 +22,8 @@ DSTATUS disk_status ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { + (void)pdrv; + return 0; } @@ -35,6 +37,8 @@ DSTATUS disk_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { + (void)pdrv; + return 0; } @@ -51,9 +55,15 @@ DRESULT disk_read ( UINT count /* Number of sectors to read */ ) { - if (R_SUCCEEDED(fsStorageRead(&fatFsStorage, FF_MAX_SS * sector, buff, FF_MAX_SS * count))) - return RES_OK; - return RES_ERROR; + (void)pdrv; + (void)buff; + (void)sector; + (void)count; + + Result rc = fsStorageRead(&fatFsStorage, FF_MAX_SS * sector, buff, FF_MAX_SS * count); + if (R_FAILED(rc)) return RES_ERROR; + + return RES_OK; } @@ -71,7 +81,12 @@ DRESULT disk_write ( UINT count /* Number of sectors to write */ ) { - return RES_PARERR; + (void)pdrv; + (void)buff; + (void)sector; + (void)count; + + return RES_OK; } #endif @@ -87,6 +102,10 @@ DRESULT disk_ioctl ( void *buff /* Buffer to send/receive control data */ ) { + (void)pdrv; + (void)cmd; + (void)buff; + return RES_OK; } diff --git a/source/fs_ext.c b/source/fs_ext.c index 9068e1a..71c47f7 100644 --- a/source/fs_ext.c +++ b/source/fs_ext.c @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/source/keys.c b/source/keys.c index a24bc0a..fb71149 100644 --- a/source/keys.c +++ b/source/keys.c @@ -8,7 +8,6 @@ #include "util.h" #include "ui.h" #include "es.h" -#include "set_ext.h" #include "save.h" /* Extern variables */ @@ -24,7 +23,7 @@ extern char strbuf[NAME_BUF_LEN]; nca_keyset_t nca_keyset; -static u8 eticket_data[ETICKET_DEVKEY_DATA_SIZE]; +static SetCalRsa2048DeviceKey eticket_data; static bool setcal_eticket_retrieved = false; static keyLocation FSRodata = { @@ -553,45 +552,36 @@ static int get_kv(FILE *f, char **key, char **value) #undef SKIP_SPACE } -static int ishex(char c) -{ - if ('a' <= c && c <= 'f') return 1; - if ('A' <= c && c <= 'F') return 1; - if ('0' <= c && c <= '9') return 1; - return 0; -} - static char hextoi(char c) { if ('a' <= c && c <= 'f') return (c - 'a' + 0xA); if ('A' <= c && c <= 'F') return (c - 'A' + 0xA); if ('0' <= c && c <= '9') return (c - '0'); - return 0; + return 'z'; } int parse_hex_key(unsigned char *key, const char *hex, unsigned int len) { - if (strlen(hex) != (2 * len)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, 2 * len); - return 0; - } - u32 i; - for(i = 0; i < (2 * len); i++) + size_t hex_str_len = (2 * len); + + if (strlen(hex) != hex_str_len) { - if (!ishex(hex[i])) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, 2 * len); - return 0; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len); + return 0; } memset(key, 0, len); - for(i = 0; i < (2 * len); i++) + for(i = 0; i < hex_str_len; i++) { char val = hextoi(hex[i]); + if (val == 'z') + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len); + return 0; + } + if ((i & 1) == 0) val <<= 4; key[i >> 1] |= val; } @@ -605,6 +595,7 @@ int readKeysFromFile(FILE *f) int ret; char *key, *value; char test_name[0x100]; + bool common_eticket = false, personalized_eticket = false; while((ret = get_kv(f, &key, &value)) != 1 && ret != -2) { @@ -612,15 +603,19 @@ int readKeysFromFile(FILE *f) { if (key == NULL || value == NULL) continue; - if (strcasecmp(key, "eticket_rsa_kek") == 0) + if (!common_eticket && !personalized_eticket && strcasecmp(key, "eticket_rsa_kek") == 0) { if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0; nca_keyset.ext_key_cnt++; + common_eticket = true; } else - if (strcasecmp(key, "save_mac_key") == 0) + if (!personalized_eticket && strcasecmp(key, "eticket_rsa_kek_personalized") == 0) { - if (!parse_hex_key(nca_keyset.save_mac_key, value, sizeof(nca_keyset.save_mac_key))) return 0; - nca_keyset.ext_key_cnt++; + /* Use the personalized eTicket RSA kek if available */ + /* This only appears on consoles that use the new PRODINFO key generation scheme */ + if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0; + if (!common_eticket) nca_keyset.ext_key_cnt++; + personalized_eticket = true; } else { memset(test_name, 0, sizeof(test_name)); @@ -768,6 +763,59 @@ void mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length) free(data_counter); } +void dumpSharedTikSavedata(void) +{ + FRESULT fr; + FIL save; + u32 size, blk, i, j; + u32 rd_b, wr_b; + + FILE *out; + + for(i = 0; i < 2; i++) + { + fr = FR_OK; + memset(&save, 0, sizeof(FIL)); + size = 0; + blk = DUMP_BUFFER_SIZE; + + out = NULL; + + fr = f_open(&save, (i == 0 ? "sys:/save/80000000000000e3" : "sys:/save/80000000000000e4"), FA_READ | FA_OPEN_EXISTING); + if (fr) continue; + + size = f_size(&save); + if (!size) + { + f_close(&save); + continue; + } + + out = fopen((i == 0 ? "sdmc:/80000000000000e3" : "sdmc:/80000000000000e4"), "wb"); + if (!out) + { + f_close(&save); + continue; + } + + for(j = 0; j < size; j += blk) + { + if ((size - j) < blk) blk = (size - j); + + rd_b = wr_b = 0; + + fr = f_read(&save, dumpBuf, blk, &rd_b); + if (fr || rd_b != blk) break; + + wr_b = fwrite(dumpBuf, 1, blk, out); + if (wr_b != blk) break; + } + + fclose(out); + f_close(&save); + } +} + int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key) { int ret = -1; @@ -936,6 +984,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA rights ID unavailable in this console!", __func__); ret = -2; + dumpSharedTikSavedata(); return ret; } @@ -945,7 +994,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!setcal_eticket_retrieved) { // Get extended eTicket RSA key from PRODINFO - memset(eticket_data, 0, ETICKET_DEVKEY_DATA_SIZE); + memset(&eticket_data, 0, sizeof(SetCalRsa2048DeviceKey)); result = setcalInitialize(); if (R_FAILED(result)) @@ -954,7 +1003,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en return ret; } - result = setcalGetEticketDeviceKey(eticket_data); + result = setcalGetEticketDeviceKey(&eticket_data); setcalExit(); @@ -965,22 +1014,22 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en } // Decrypt eTicket RSA key - memcpy(ctr, eticket_data + ETICKET_DEVKEY_CTR_OFFSET, 0x10); + memcpy(ctr, eticket_data.key, ETICKET_DEVKEY_RSA_CTR_SIZE); aes128CtrContextCreate(&eticket_aes_ctx, nca_keyset.eticket_rsa_kek, ctr); - aes128CtrCrypt(&eticket_aes_ctx, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE); + aes128CtrCrypt(&eticket_aes_ctx, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE); // Public exponent must use RSA-2048 SHA-1 signature method // The value is stored use big endian byte order - if (bswap_32(*((u32*)(&(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200])))) != SIGTYPE_RSA2048_SHA1) + if (__builtin_bswap32(*((u32*)(eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200))) != SIGTYPE_RSA2048_SHA1) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid public RSA exponent for eTicket data! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); return ret; } } - D = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET]); - N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]); - E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]); + D = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET); + N = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x100); + E = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200); if (!setcal_eticket_retrieved) { @@ -1006,7 +1055,6 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en save_ctx->file = &eTicketSave; save_ctx->tool_ctx.action = 0; - memcpy(save_ctx->save_mac_key, nca_keyset.save_mac_key, 0x10); if (!save_process(save_ctx)) { @@ -1116,6 +1164,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find a matching eTicket entry for NCA rights ID!", __func__); ret = -2; + dumpSharedTikSavedata(); return ret; } diff --git a/source/keys.h b/source/keys.h index 11cd831..50d083f 100644 --- a/source/keys.h +++ b/source/keys.h @@ -12,10 +12,9 @@ #define SEG_RODATA BIT(1) #define SEG_DATA BIT(2) -#define ETICKET_DEVKEY_DATA_SIZE 0x244 -#define ETICKET_DEVKEY_CTR_OFFSET 0x4 -#define ETICKET_DEVKEY_RSA_OFFSET 0x14 -#define ETICKET_DEVKEY_RSA_SIZE (ETICKET_DEVKEY_DATA_SIZE - ETICKET_DEVKEY_RSA_OFFSET) +#define ETICKET_DEVKEY_RSA_CTR_SIZE 0x10 +#define ETICKET_DEVKEY_RSA_OFFSET ETICKET_DEVKEY_RSA_CTR_SIZE +#define ETICKET_DEVKEY_RSA_SIZE 0x230 #define SIGTYPE_RSA2048_SHA1 (u32)0x10001 #define SIGTYPE_RSA2048_SHA256 (u32)0x10004 @@ -25,13 +24,13 @@ typedef struct { u8 mask; u8 *data; u64 dataSize; -} PACKED keyLocation; +} keyLocation; typedef struct { char name[128]; u8 hash[SHA256_HASH_SIZE]; u64 size; -} PACKED keyInfo; +} keyInfo; typedef struct { u16 memory_key_cnt; /* Key counter for keys retrieved from memory. */ @@ -55,10 +54,7 @@ typedef struct { // Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file. u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */ - - // Console specific. Needed to calculate the AES CMAC for savefiles. Retrieved from the Lockpick_RCM keys file. - u8 save_mac_key[0x10]; /* Savefile CMAC key */ -} PACKED nca_keyset_t; +} nca_keyset_t; bool loadMemoryKeys(); bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out); diff --git a/source/nca.c b/source/nca.c index af30573..3f82d2c 100644 --- a/source/nca.c +++ b/source/nca.c @@ -165,8 +165,7 @@ void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_i sprintf(tmp, " %s\n" \ " %u\n" \ " <%s>%u\n" \ - " <%s>0x%016lx\n" \ - "", \ + " <%s>0x%016lx\n", \ xml_program_info->digest_str, \ xml_program_info->min_keyblob, \ getRequiredMinTitleType(xml_program_info->type), \ @@ -177,6 +176,14 @@ void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_i 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) @@ -631,7 +638,7 @@ bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize) bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) { - if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (bswap_32(input->magic) != NCA3_MAGIC && bswap_32(input->magic) != NCA2_MAGIC)) + 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; @@ -651,7 +658,7 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) aes128XtsContextCreate(&hdr_aes_ctx, header_key_0, header_key_1, true); - if (bswap_32(input->magic) == NCA3_MAGIC) + 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) @@ -660,7 +667,7 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) return false; } } else - if (bswap_32(input->magic) == NCA2_MAGIC) + 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) @@ -679,7 +686,7 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) } } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decrypted NCA magic word! (0x%08X)", __func__, bswap_32(input->magic)); + 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; } @@ -719,7 +726,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title return false; } - if (bswap_32(out->magic) == NCA3_MAGIC) + 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) @@ -728,7 +735,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title return false; } } else - if (bswap_32(out->magic) == NCA2_MAGIC) + if (__builtin_bswap32(out->magic) == NCA2_MAGIC) { for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) { @@ -745,7 +752,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title } } } 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__, bswap_32(out->magic)); + 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; } @@ -821,9 +828,53 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title 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) +bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys) { - if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output) + 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; @@ -907,9 +958,9 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, return false; } - if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) + 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__, bswap_32(nca_pfs0_header.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; } @@ -950,7 +1001,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, return false; } - if (bswap_32(npdm_header.magic) == META_MAGIC) + if (__builtin_bswap32(npdm_header.magic) == META_MAGIC) { found_meta = true; meta_offset = nca_pfs0_cur_file_offset; @@ -1120,27 +1171,46 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, // Save data to the output struct so we can write it later // The caller function must free these data pointers - output->hash_table = hash_table; - output->hash_table_offset = hash_table_offset; - output->hash_table_size = dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size; + 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; + } - output->block_mod_cnt = (block_hash_table_offset != block_hash_table_end_offset ? 2 : 1); + memset(&(tmp_mod_data[*cur_mod_cnt]), 0, sizeof(nca_program_mod_data)); - output->block_data[0] = block_data[0]; - output->block_offset[0] = block_start_offset[0]; - output->block_size[0] = block_size[0]; + 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) { - output->block_data[1] = block_data[1]; - output->block_offset[1] = block_start_offset[1]; - output->block_size[1] = block_size[1]; + 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, nspDumpType selectedNspDumpType, 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 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) { @@ -1257,9 +1327,9 @@ bool retrieveCnmtNcaData(NcmStorageId curStorageId, nspDumpType selectedNspDumpT 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 (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) + 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__, bswap_32(nca_pfs0_header.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; } @@ -1318,6 +1388,7 @@ bool retrieveCnmtNcaData(NcmStorageId curStorageId, nspDumpType selectedNspDumpT 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 @@ -1534,7 +1605,7 @@ bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *n if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) return false; - if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC || !nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) continue; + 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)); @@ -1683,9 +1754,9 @@ bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *n memcpy(ctr_key, decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); - if (bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic) != IVFC_MAGIC) + 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__, bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.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; } @@ -1851,9 +1922,9 @@ bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *nc memcpy(&(bktrContext.superblock), &(dec_nca_header->fs_headers[bktr_index].bktr_superblock), sizeof(bktr_superblock_t)); - if (bswap_32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC) + 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__, bswap_32(bktrContext.superblock.ivfc_header.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; } @@ -1863,9 +1934,9 @@ bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *nc return false; } - if (bswap_32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || bswap_32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC) + 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__, bswap_32(bktrContext.superblock.relocation_header.magic), bswap_32(bktrContext.superblock.subsection_header.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; } @@ -2058,9 +2129,9 @@ out: return success; } -bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, nca_program_mod_data *program_mod_data, char **outBuf, u64 *outBufSize) +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 || !program_mod_data || !outBuf || !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; @@ -2143,9 +2214,9 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *n return false; } - if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) + 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__, bswap_32(nca_pfs0_header.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; } @@ -2227,9 +2298,9 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *n goto out; } - if (bswap_32(npdm_header.magic) != META_MAGIC) + 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__, bswap_32(npdm_header.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; } @@ -2249,7 +2320,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *n } // If we're dealing with a gamecard title, replace the ACID public key with the patched one - if (program_mod_data->block_mod_cnt > 0) memcpy(npdm_acid_section + (u64)NPDM_SIGNATURE_SIZE, rsa_get_public_key(), (u64)NPDM_SIGNATURE_SIZE); + 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); @@ -2315,7 +2386,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *n } // Check if we're dealing with a NSO - if (bswap_32(nsoHeader.magic) != NSO_MAGIC) continue; + if (__builtin_bswap32(nsoHeader.magic) != NSO_MAGIC) continue; // Retrieve middleware list from this NSO if (!retrieveMiddlewareListFromNso(ncmStorage, ncaId, &aes_ctx, curFilename, curFileOffset, &nsoHeader, programInfoXml)) @@ -2351,7 +2422,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmContentId *n } // Check if we're dealing with the main NSO - if (strlen(curFilename) != 4 || strncmp(curFilename, "main", 4) != 0 || bswap_32(nsoHeader.magic) != NSO_MAGIC) continue; + 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; @@ -2387,58 +2458,57 @@ out: return success; } -char *getNacpLangName(u8 val) +char *getNacpLangName(Language val) { char *out = NULL; switch(val) { - case 0: + case Language_AmericanEnglish: out = "AmericanEnglish"; break; - case 1: + case Language_BritishEnglish: out = "BritishEnglish"; break; - case 2: + case Language_Japanese: out = "Japanese"; break; - case 3: + case Language_French: out = "French"; break; - case 4: + case Language_German: out = "German"; break; - case 5: + case Language_LatinAmericanSpanish: out = "LatinAmericanSpanish"; break; - case 6: + case Language_Spanish: out = "Spanish"; break; - case 7: + case Language_Italian: out = "Italian"; break; - case 8: + case Language_Dutch: out = "Dutch"; break; - case 9: + case Language_CanadianFrench: out = "CanadianFrench"; break; - case 10: + case Language_Portuguese: out = "Portuguese"; break; - case 11: + case Language_Russian: out = "Russian"; break; - case 12: + case Language_Korean: out = "Korean"; break; - case 13: + case Language_TraditionalChinese: out = "TraditionalChinese"; break; - case 14: + case Language_SimplifiedChinese: out = "SimplifiedChinese"; break; - case 15: // Unknown default: out = "Unknown"; break; @@ -2453,13 +2523,13 @@ char *getNacpStartupUserAccount(u8 val) switch(val) { - case 0: + case StartupUserAccount_None: out = "None"; break; - case 1: + case StartupUserAccount_Required: out = "Required"; break; - case 2: + case StartupUserAccount_RequiredWithNetworkServiceAccountAvailable: out = "RequiredWithNetworkServiceAccountAvailable"; break; default: @@ -2476,10 +2546,10 @@ char *getNacpUserAccountSwitchLock(u8 val) switch(val) { - case 0: + case UserAccountSwitchLock_Disable: out = "Disable"; break; - case 1: + case UserAccountSwitchLock_Enable: out = "Enable"; break; default: @@ -2490,24 +2560,15 @@ char *getNacpUserAccountSwitchLock(u8 val) return out; } -char *getNacpParentalControlFlag(u32 flag) +char *getNacpSupportedLanguageFlag(SupportedLanguageFlag *data, u8 idx) { - char *out = NULL; + if (!data || idx >= 0x10) return NULL; - switch(flag) - { - case 0: - out = "None"; - break; - case 1: - out = "FreeCommunication"; - break; - default: - out = "Unknown"; - break; - } + u32 flag; + memcpy(&flag, data, sizeof(u32)); + flag = ((flag >> idx) & 0x1); - return out; + return (flag ? getNacpLangName((Language)flag) : NULL); } char *getNacpScreenshot(u8 val) @@ -2516,10 +2577,10 @@ char *getNacpScreenshot(u8 val) switch(val) { - case 0: + case Screenshot_Allow: out = "Allow"; break; - case 1: + case Screenshot_Deny: out = "Deny"; break; default: @@ -2536,13 +2597,13 @@ char *getNacpVideoCapture(u8 val) switch(val) { - case 0: + case VideoCapture_Disable: out = "Disable"; break; - case 1: + case VideoCapture_Manual: out = "Manual"; break; - case 2: + case VideoCapture_Enable: out = "Enable"; break; default: @@ -2553,49 +2614,49 @@ char *getNacpVideoCapture(u8 val) return out; } -char *getNacpRatingAgeOrganizationName(u8 val) +char *getNacpRatingAgeOrganization(RatingAgeOrganization val) { char *out = NULL; switch(val) { - case 0: + case RatingAgeOrganization_CERO: out = "CERO"; break; - case 1: + case RatingAgeOrganization_GRACGCRB: out = "GRACGCRB"; break; - case 2: + case RatingAgeOrganization_GSRMR: out = "GSRMR"; break; - case 3: + case RatingAgeOrganization_ESRB: out = "ESRB"; break; - case 4: + case RatingAgeOrganization_ClassInd: out = "ClassInd"; break; - case 5: + case RatingAgeOrganization_USK: out = "USK"; break; - case 6: + case RatingAgeOrganization_PEGI: out = "PEGI"; break; - case 7: + case RatingAgeOrganization_PEGIPortugal: out = "PEGIPortugal"; break; - case 8: + case RatingAgeOrganization_PEGIBBFC: out = "PEGIBBFC"; break; - case 9: + case RatingAgeOrganization_Russian: out = "Russian"; break; - case 10: + case RatingAgeOrganization_ACB: out = "ACB"; break; - case 11: + case RatingAgeOrganization_OFLC: out = "OFLC"; break; - case 12: + case RatingAgeOrganization_IARCGeneric: out = "IARCGeneric"; break; default: @@ -2612,10 +2673,10 @@ char *getNacpDataLossConfirmation(u8 val) switch(val) { - case 0: + case DataLossConfirmation_None: out = "None"; break; - case 1: + case DataLossConfirmation_Required: out = "Required"; break; default: @@ -2632,13 +2693,13 @@ char *getNacpPlayLogPolicy(u8 val) switch(val) { - case 0: + case PlayLogPolicy_All: out = "All"; break; - case 1: + case PlayLogPolicy_LogOnly: out = "LogOnly"; break; - case 2: + case PlayLogPolicy_None: out = "None"; break; default: @@ -2655,10 +2716,13 @@ char *getNacpLogoType(u8 val) switch(val) { - case 0: + case LogoType_LicensedByNintendo: out = "LicensedByNintendo"; break; - case 2: + case LogoType_DistributedByNintendo: + out = "DistributedByNintendo"; + break; + case LogoType_Nintendo: out = "Nintendo"; break; default: @@ -2675,10 +2739,10 @@ char *getNacpLogoHandling(u8 val) switch(val) { - case 0: + case LogoHandling_Auto: out = "Auto"; break; - case 1: + case LogoHandling_Manual: out = "Manual"; break; default: @@ -2689,36 +2753,16 @@ char *getNacpLogoHandling(u8 val) return out; } -char *getNacpStartupUserAccountOptionFlag(u8 val) -{ - char *out = NULL; - - switch(val) - { - case 0: - out = "None"; - break; - case 1: - out = "IsOptional"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - char *getNacpAddOnContentRegistrationType(u8 val) { char *out = NULL; switch(val) { - case 0: + case AddOnContentRegistrationType_AllOnLaunch: out = "AllOnLaunch"; break; - case 1: + case AddOnContentRegistrationType_OnDemand: out = "OnDemand"; break; default: @@ -2735,10 +2779,10 @@ char *getNacpHdcp(u8 val) switch(val) { - case 0: + case Hdcp_None: out = "None"; break; - case 1: + case Hdcp_Required: out = "Required"; break; default: @@ -2755,10 +2799,10 @@ char *getNacpCrashReport(u8 val) switch(val) { - case 0: + case CrashReport_Deny: out = "Deny"; break; - case 1: + case CrashReport_Allow: out = "Allow"; break; default: @@ -2775,10 +2819,10 @@ char *getNacpRuntimeAddOnContentInstall(u8 val) switch(val) { - case 0: + case RuntimeAddOnContentInstall_Deny: out = "Deny"; break; - case 1: + case RuntimeAddOnContentInstall_AllowAppend: out = "AllowAppend"; break; default: @@ -2795,13 +2839,13 @@ char *getNacpRuntimeParameterDelivery(u8 val) switch(val) { - case 0: + case RuntimeParameterDelivery_Always: out = "Always"; break; - case 1: + case RuntimeParameterDelivery_AlwaysIfUserStateMatched: out = "AlwaysIfUserStateMatched"; break; - case 2: + case RuntimeParameterDelivery_OnRestart: out = "OnRestart"; break; default: @@ -2818,13 +2862,13 @@ char *getNacpPlayLogQueryCapability(u8 val) switch(val) { - case 0: + case PlayLogQueryCapability_None: out = "None"; break; - case 1: + case PlayLogQueryCapability_WhiteList: out = "WhiteList"; break; - case 2: + case PlayLogQueryCapability_All: out = "All"; break; default: @@ -2835,79 +2879,16 @@ char *getNacpPlayLogQueryCapability(u8 val) return out; } -char *getNacpRepairFlag(u8 val) +char *getNacpJitConfigurationFlag(u64 val) { char *out = NULL; switch(val) { - case 0: + case JitConfigurationFlag_None: out = "None"; break; - case 1: - out = "SuppressGameCardAccess"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpAttributeFlag(u32 flag) -{ - char *out = NULL; - - switch(flag) - { - case 0: - out = "None"; - break; - case 1: - out = "Demo"; - break; - case 2: - out = "RetailInteractiveDisplay"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpRequiredNetworkServiceLicenseOnLaunchFlag(u8 val) -{ - char *out = NULL; - - switch(val) - { - case 0: - out = "None"; - break; - case 1: - out = "Common"; - break; - default: - out = "Unknown"; - break; - } - - return out; -} - -char *getNacpJitConfigurationFlag(u64 flag) -{ - char *out = NULL; - - switch(flag) - { - case 0: - out = "None"; - break; - case 1: + case JitConfigurationFlag_Enabled: out = "Enabled"; break; default: @@ -2935,15 +2916,16 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * 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[0x20]; - char languageIconHashStr[0x21]; + u8 languageIconHash[SHA256_HASH_SIZE]; + char languageIconHashStr[SHA256_HASH_SIZE + 1] = {'\0'}; - char ncaIdStr[0x21] = {'\0'}; + char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; convertDataToHexString(ncaId->c, 0x10, ncaIdStr, 0x21); char dataStr[100] = {'\0'}; @@ -2951,7 +2933,7 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * u8 null_key[0x10]; memset(null_key, 0, 0x10); - bool availableSDC = false, availableRDC = false; + bool availableSGC = false, availableRGC = false; if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; @@ -2993,9 +2975,9 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * sprintf(nacpXml, "\n" \ "\n"); - for(i = 0; i < 16; i++) + for(i = 0; i < 0x10; i++) { - if (strlen(controlNacp.lang[i].name) || strlen(controlNacp.lang[i].author)) + if (strlen(controlNacp.titles[i].name) || strlen(controlNacp.titles[i].publisher)) { sprintf(tmp, " \n" \ " <Language>%s</Language>\n" \ @@ -3003,116 +2985,125 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * " <Publisher>%s</Publisher>\n" \ " \n", \ getNacpLangName(i), \ - controlNacp.lang[i].name, \ - controlNacp.lang[i].author); + controlNacp.titles[i].name, \ + controlNacp.titles[i].publisher); strcat(nacpXml, tmp); } } - if (strlen(controlNacp.Isbn)) + if (strlen(controlNacp.isbn)) { - sprintf(tmp, " %s\n", controlNacp.Isbn); + sprintf(tmp, " %s\n", controlNacp.isbn); strcat(nacpXml, tmp); } else { strcat(nacpXml, " \n"); } - sprintf(tmp, " %s\n", getNacpStartupUserAccount(controlNacp.StartupUserAccount)); + sprintf(tmp, " %s\n", getNacpStartupUserAccount(controlNacp.startup_user_account)); strcat(nacpXml, tmp); - sprintf(tmp, " %s\n", getNacpUserAccountSwitchLock(controlNacp.UserAccountSwitchLock)); + sprintf(tmp, " %s\n", getNacpUserAccountSwitchLock(controlNacp.user_account_switch_lock)); strcat(nacpXml, tmp); - sprintf(tmp, " %s\n", getNacpParentalControlFlag(controlNacp.ParentalControlFlag)); - strcat(nacpXml, tmp); + strcat(nacpXml, " "); - for(i = 0; i < 16; i++) + memcpy(&flag, &(controlNacp.parental_control_flag), sizeof(u32)); + if (flag != 0) { - u8 bit = (u8)((controlNacp.SupportedLanguageFlag >> i) & 0x1); - if (bit) - { - sprintf(tmp, " %s\n", getNacpLangName(i)); - strcat(nacpXml, tmp); - nacpIconCnt++; - } + if (controlNacp.parental_control_flag.ParentalControlFlag_FreeCommunication) strcat(nacpXml, "FreeCommunication"); + } else { + strcat(nacpXml, "None"); } - sprintf(tmp, " %s\n", getNacpScreenshot(controlNacp.Screenshot)); - strcat(nacpXml, tmp); + strcat(nacpXml, "\n"); - sprintf(tmp, " %s\n", getNacpVideoCapture(controlNacp.VideoCapture)); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.PresenceGroupId); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", controlNacp.DisplayVersion); - strcat(nacpXml, tmp); - - for(i = 0; i < 32; i++) + for(i = 0; i < 0x10; i++) { - if (controlNacp.RatingAge[i] != 0xFF) - { - sprintf(tmp, " \n" \ - " %s\n" \ - " %u\n" \ - " \n", \ - getNacpRatingAgeOrganizationName(i), \ - controlNacp.RatingAge[i]); - - strcat(nacpXml, tmp); - } + char *str = getNacpSupportedLanguageFlag(&(controlNacp.supported_language_flag), i); + if (!str) continue; + + sprintf(tmp, " %s\n", str); + strcat(nacpXml, tmp); + + nacpIconCnt++; } - sprintf(tmp, " %s\n", getNacpDataLossConfirmation(controlNacp.DataLossConfirmation)); + sprintf(tmp, " %s\n", getNacpScreenshot(controlNacp.screenshot)); strcat(nacpXml, tmp); - sprintf(tmp, " %s\n", getNacpPlayLogPolicy(controlNacp.PlayLogPolicy)); + sprintf(tmp, " %s\n", getNacpVideoCapture(controlNacp.video_capture)); strcat(nacpXml, tmp); - sprintf(tmp, " 0x%016lx\n", controlNacp.SaveDataOwnerId); + sprintf(tmp, " 0x%016lx\n", controlNacp.presence_group_id); strcat(nacpXml, tmp); - sprintf(tmp, " 0x%016lx\n", controlNacp.UserAccountSaveDataSize); + sprintf(tmp, " %s\n", controlNacp.display_version); strcat(nacpXml, tmp); - sprintf(tmp, " 0x%016lx\n", controlNacp.UserAccountSaveDataJournalSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.DeviceSaveDataSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.DeviceSaveDataJournalSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.BcatDeliveryCacheStorageSize); - strcat(nacpXml, tmp); - - if (strlen(controlNacp.ApplicationErrorCodeCategory)) + for(i = 0; i < 0x20; i++) { - sprintf(tmp, " %s\n", controlNacp.ApplicationErrorCodeCategory); + 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.AddOnContentBaseId); + sprintf(tmp, " 0x%016lx\n", controlNacp.add_on_content_base_id); strcat(nacpXml, tmp); - sprintf(tmp, " %s\n", getNacpLogoType(controlNacp.LogoType)); + sprintf(tmp, " %s\n", getNacpLogoType(controlNacp.logo_type)); strcat(nacpXml, tmp); - for(i = 0; i < 8; i++) + for(i = 0; i < 0x8; i++) { - if (controlNacp.LocalCommunicationId[i] != 0) + if (controlNacp.local_communication_ids[i] != 0) { - sprintf(tmp, " 0x%016lx\n", controlNacp.LocalCommunicationId[i]); + sprintf(tmp, " 0x%016lx\n", controlNacp.local_communication_ids[i]); strcat(nacpXml, tmp); } } - sprintf(tmp, " %s\n", getNacpLogoHandling(controlNacp.LogoHandling)); + sprintf(tmp, " %s\n", getNacpLogoHandling(controlNacp.logo_handling)); strcat(nacpXml, tmp); if (nacpIconCnt) @@ -3124,16 +3115,16 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * goto out; } - for(i = 0; i < 16; i++) + for(i = 0; i < 0x10; i++) { - u8 bit = (u8)((controlNacp.SupportedLanguageFlag >> i) & 0x1); - if (!bit) continue; + 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, 0x20); - memset(languageIconHashStr, 0, 0x21); + memset(languageIconHash, 0, SHA256_HASH_SIZE); + memset(languageIconHashStr, 0, SHA256_HASH_SIZE + 1); entryOffset = 0; sprintf(tmp, "icon_%s.dat", getNacpLangName(i)); @@ -3176,7 +3167,7 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * sha256CalculateHash(languageIconHash, nacpIcons[j].icon_data, nacpIcons[j].icon_size); // Only retrieve the first half from the SHA-256 checksum - convertDataToHexString(languageIconHash, 0x10, languageIconHashStr, 0x21); + convertDataToHexString(languageIconHash, SHA256_HASH_SIZE / 2, languageIconHashStr, SHA256_HASH_SIZE + 1); // Now print the hash sprintf(tmp, " %s\n", languageIconHashStr); @@ -3188,129 +3179,168 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * } } - sprintf(tmp, " 0x%016lx\n", controlNacp.SeedForPseudoDeviceId); + sprintf(tmp, " 0x%016lx\n", controlNacp.seed_for_pseudo_device_id); strcat(nacpXml, tmp); - if (strlen(controlNacp.BcatPassphrase)) + if (strlen(controlNacp.bcat_passphrase)) { - sprintf(tmp, " %s\n", controlNacp.BcatPassphrase); + sprintf(tmp, " %s\n", controlNacp.bcat_passphrase); strcat(nacpXml, tmp); } else { strcat(nacpXml, " \n"); } - sprintf(tmp, " %s\n", getNacpStartupUserAccountOptionFlag(controlNacp.StartupUserAccountOptionFlag)); - strcat(nacpXml, tmp); + strcat(nacpXml, " "); - sprintf(tmp, " %s\n", getNacpAddOnContentRegistrationType(controlNacp.AddOnContentRegistrationType)); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.UserAccountSaveDataSizeMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.UserAccountSaveDataJournalSizeMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.DeviceSaveDataSizeMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.DeviceSaveDataJournalSizeMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.TemporaryStorageSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.CacheStorageSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.CacheStorageJournalSize); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%016lx\n", controlNacp.CacheStorageDataAndJournalSizeMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " 0x%04x\n", controlNacp.CacheStorageIndexMax); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpHdcp(controlNacp.Hdcp)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpCrashReport(controlNacp.CrashReport)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpRuntimeAddOnContentInstall(controlNacp.RuntimeAddOnContentInstall)); - strcat(nacpXml, tmp); - - sprintf(tmp, " %s\n", getNacpRuntimeParameterDelivery(controlNacp.RuntimeParameterDelivery)); - strcat(nacpXml, tmp); - - for(i = 0; i < 16; i++) + if (*((u8*)&(controlNacp.startup_user_account_option)) != 0) { - if (controlNacp.PlayLogQueryableApplicationId[i] != 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.PlayLogQueryableApplicationId[i]); + sprintf(tmp, " 0x%016lx\n", controlNacp.play_log_queryable_application_ids[i]); strcat(nacpXml, tmp); } } - sprintf(tmp, " %s\n", getNacpPlayLogQueryCapability(controlNacp.PlayLogQueryCapability)); + sprintf(tmp, " %s\n", getNacpPlayLogQueryCapability(controlNacp.play_log_query_capability)); strcat(nacpXml, tmp); - sprintf(tmp, " %s\n", getNacpRepairFlag(controlNacp.RepairFlag)); + 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); - sprintf(tmp, " %s\n", getNacpAttributeFlag(controlNacp.AttributeFlag)); - strcat(nacpXml, tmp); + strcat(nacpXml, " "); - sprintf(tmp, " %u\n", controlNacp.ProgramIndex); - strcat(nacpXml, tmp); + 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"); + } - sprintf(tmp, " %s\n", getNacpRequiredNetworkServiceLicenseOnLaunchFlag(controlNacp.RequiredNetworkServiceLicenseOnLaunchFlag)); - strcat(nacpXml, tmp); + strcat(nacpXml, "\n"); // Check if we actually have valid NeighborDetectionClientConfiguration values - availableSDC = (controlNacp.SendDataConfiguration.id != 0 && memcmp(controlNacp.SendDataConfiguration.key, null_key, 0x10) != 0); + 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 < 16; i++) + for(i = 0; i < 0x10; i++) { - if (controlNacp.ReceivableDataConfiguration[i].id != 0 && memcmp(controlNacp.ReceivableDataConfiguration[i].key, null_key, 0x10) != 0) + 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) { - availableRDC = true; + availableRGC = true; break; } } - if (availableSDC || availableRDC) + if (availableSGC || availableRGC) { strcat(nacpXml, " \n"); - if (availableSDC) + if (availableSGC) { - convertDataToHexString(controlNacp.SendDataConfiguration.key, 0x10, dataStr, 100); + convertDataToHexString(controlNacp.neighbor_detection_client_configuration.send_group_configuration.key, 0x10, dataStr, 100); sprintf(tmp, " \n" \ " 0x%016lx\n" \ " %s\n" \ " \n", \ - controlNacp.SendDataConfiguration.id, \ + controlNacp.neighbor_detection_client_configuration.send_group_configuration.group_id, \ dataStr); strcat(nacpXml, tmp); } - if (availableRDC) + if (availableRGC) { - for(i = 0; i < 16; i++) + for(i = 0; i < 0x10; i++) { - if (controlNacp.ReceivableDataConfiguration[i].id != 0 && memcmp(controlNacp.ReceivableDataConfiguration[i].key, null_key, 0x10) != 0) + 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.ReceivableDataConfiguration[i].key, 0x10, dataStr, 100); + convertDataToHexString(controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].key, 0x10, dataStr, 100); sprintf(tmp, " \n" \ " 0x%016lx\n" \ " %s\n" \ " \n", \ - controlNacp.ReceivableDataConfiguration[i].id, \ + controlNacp.neighbor_detection_client_configuration.receivable_group_configurations[i].group_id, \ dataStr); strcat(nacpXml, tmp); @@ -3325,8 +3355,8 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId * " %s\n" \ " 0x%016lx\n" \ " \n", \ - getNacpJitConfigurationFlag(controlNacp.JitConfigurationFlag), \ - controlNacp.JitMemorySize); + getNacpJitConfigurationFlag(controlNacp.jit_configuration.jit_configuration_flag), \ + controlNacp.jit_configuration.memory_size); strcat(nacpXml, tmp); diff --git a/source/nca.h b/source/nca.h index 399cb1b..6e43f0b 100644 --- a/source/nca.h +++ b/source/nca.h @@ -194,7 +194,8 @@ typedef struct { }; }; u8 crypto_type2; /* Which keyblob (field 2) */ - u8 _0x221[0xF]; /* Padding. */ + 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. */ @@ -238,6 +239,7 @@ typedef struct { 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 { @@ -259,7 +261,8 @@ typedef struct { u8 min_keyblob; u32 min_sysver; u64 patch_tid; -} PACKED cnmt_xml_program_info; + u32 min_appver; +} cnmt_xml_program_info; typedef struct { u8 type; @@ -270,12 +273,13 @@ typedef struct { char hash_str[(SHA256_HASH_SIZE * 2) + 1]; u8 keyblob; u8 id_offset; - u64 cnt_record_offset; // Relative to the start of the content record structs in the CNMT + 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]; -} PACKED cnmt_xml_content_info; +} cnmt_xml_content_info; typedef struct { + u32 nca_index; u8 *hash_table; u64 hash_table_offset; // Relative to NCA start u64 hash_table_size; @@ -283,7 +287,21 @@ typedef struct { u8 *block_data[2]; u64 block_offset[2]; // Relative to NCA start u64 block_size[2]; -} PACKED nca_program_mod_data; +} 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 @@ -295,7 +313,7 @@ typedef struct { u64 pfs0_size; u64 title_cnmt_offset; // Relative to NCA start u64 title_cnmt_size; -} PACKED nca_cnmt_mod_data; +} nca_cnmt_mod_data; typedef struct { u32 sig_type; @@ -327,12 +345,13 @@ typedef struct { rsa2048_sha256_ticket tik_data; bool retrieved_tik; bool missing_tik; -} PACKED title_rights_ctx; +} 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; @@ -342,12 +361,13 @@ typedef struct { u64 exefs_str_table_offset; // Relative to NCA start char *exefs_str_table; u64 exefs_data_offset; // Relative to NCA start -} PACKED exefs_ctx_t; +} 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; @@ -360,7 +380,7 @@ typedef struct { u64 romfs_filetable_size; romfs_file *romfs_file_entries; u64 romfs_filedata_offset; // Relative to NCA start -} PACKED romfs_ctx_t; +} romfs_ctx_t; typedef struct { u64 virt_offset; @@ -409,6 +429,7 @@ typedef struct { NcmStorageId storageId; NcmContentStorage ncmStorage; NcmContentId ncaId; + u8 idOffset; Aes128CtrContext aes_ctx; u64 section_offset; // Relative to NCA start u64 section_size; @@ -427,94 +448,279 @@ typedef struct { u64 romfs_filetable_size; romfs_file *romfs_file_entries; u64 romfs_filedata_offset; // Relative to section start -} PACKED bktr_ctx_t; +} bktr_ctx_t; // Used in HFS0 / ExeFS / RomFS browsers typedef struct { u64 size; char sizeStr[32]; -} PACKED browser_entry_size_info; +} 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 -} PACKED romfs_browser_entry; +} romfs_browser_entry; typedef struct { - u64 id; + 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]; -} PACKED send_data_configuration; +} NeighborDetectionGroupConfiguration; typedef struct { - u64 id; - u8 key[0x10]; -} PACKED receivable_data_configurations; + 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 { - NacpLanguageEntry lang[16]; - char Isbn[0x25]; - u8 StartupUserAccount; - u8 UserAccountSwitchLock; - u8 AddOnContentRegistrationType; - u32 AttributeFlag; - u32 SupportedLanguageFlag; - u32 ParentalControlFlag; - u8 Screenshot; - u8 VideoCapture; - u8 DataLossConfirmation; - u8 PlayLogPolicy; - u64 PresenceGroupId; - u8 RatingAge[0x20]; - char DisplayVersion[0x10]; - u64 AddOnContentBaseId; - u64 SaveDataOwnerId; - u64 UserAccountSaveDataSize; - u64 UserAccountSaveDataJournalSize; - u64 DeviceSaveDataSize; - u64 DeviceSaveDataJournalSize; - u64 BcatDeliveryCacheStorageSize; - char ApplicationErrorCodeCategory[0x8]; - u64 LocalCommunicationId[0x8]; - u8 LogoType; - u8 LogoHandling; - u8 RuntimeAddOnContentInstall; - u8 RuntimeParameterDelivery; - u8 Reserved_0x30F4[0x2]; - u8 CrashReport; - u8 Hdcp; - u64 SeedForPseudoDeviceId; - char BcatPassphrase[0x41]; - u8 StartupUserAccountOptionFlag; - u8 ReservedForUserAccountSaveDataOperation[0x6]; - u64 UserAccountSaveDataSizeMax; - u64 UserAccountSaveDataJournalSizeMax; - u64 DeviceSaveDataSizeMax; - u64 DeviceSaveDataJournalSizeMax; - u64 TemporaryStorageSize; - u64 CacheStorageSize; - u64 CacheStorageJournalSize; - u64 CacheStorageDataAndJournalSizeMax; - u16 CacheStorageIndexMax; - u8 Reserved_0x318A[0x6]; - u64 PlayLogQueryableApplicationId[0x10]; - u8 PlayLogQueryCapability; - u8 RepairFlag; - u8 ProgramIndex; - u8 RequiredNetworkServiceLicenseOnLaunchFlag; - u8 Reserved_0x3214[0x4]; - send_data_configuration SendDataConfiguration; - receivable_data_configurations ReceivableDataConfiguration[0x10]; - u64 JitConfigurationFlag; - u64 JitMemorySize; - u8 Reserved[0xC40]; -} PACKED nacp_t; + u64 jit_configuration_flag; + u64 memory_size; +} JitConfiguration; typedef struct { - char filename[100]; - u64 icon_size; - u8 icon_data[0x20000]; -} PACKED nacp_icons_ctx; + 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); @@ -532,9 +738,11 @@ 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 processProgramNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data *output); +bool retrieveTitleKeyFromGameCardTicket(title_rights_ctx *rights_info, u8 *decrypted_nca_keys); -bool retrieveCnmtNcaData(NcmStorageId curStorageId, nspDumpType selectedNspDumpType, 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 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); @@ -544,7 +752,7 @@ bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *n 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, nca_program_mod_data *program_mod_data, char **outBuf, u64 *outBufSize); +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); diff --git a/source/nso.c b/source/nso.c index c701538..7f82c2b 100644 --- a/source/nso.c +++ b/source/nso.c @@ -331,9 +331,9 @@ bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmContentI mod_magic = *((u32*)(&(nsoBinaryData[mod_magic_offset]))); dynamic_section_offset = ((s32)mod_magic_offset + *((s32*)(&(nsoBinaryData[mod_magic_offset + 0x04])))); - if (bswap_32(mod_magic) != MOD_MAGIC) + if (__builtin_bswap32(mod_magic) != MOD_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid MOD0 magic word in decompressed NSO from Program NCA! (0x%08X)", __func__, bswap_32(mod_magic)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid MOD0 magic word in decompressed NSO from Program NCA! (0x%08X)", __func__, __builtin_bswap32(mod_magic)); goto out; } diff --git a/source/save.c b/source/save.c index de88625..5d43932 100644 --- a/source/save.c +++ b/source/save.c @@ -1881,7 +1881,6 @@ bool readCertsFromSystemSave() save_ctx->file = &certSave; save_ctx->tool_ctx.action = 0; - memcpy(save_ctx->save_mac_key, nca_keyset.save_mac_key, 0x10); initSaveCtx = save_process(save_ctx); if (!initSaveCtx) diff --git a/source/save.h b/source/save.h index 9c191c4..102ca1c 100644 --- a/source/save.h +++ b/source/save.h @@ -84,11 +84,13 @@ typedef struct { u8 _0x190[0x70]; } fs_layout_t; +#pragma pack(push, 1) typedef struct { u64 offset; u64 length; u32 block_size_power; -} PACKED duplex_info_t; +} duplex_info_t; +#pragma pack(pop) typedef struct { u32 magic; /* DPFS */ @@ -142,6 +144,7 @@ typedef struct { typedef struct remap_segment_ctx_t remap_segment_ctx_t; typedef struct remap_entry_ctx_t remap_entry_ctx_t; +#pragma pack(push, 1) struct remap_entry_ctx_t { u64 virtual_offset; u64 physical_offset; @@ -152,7 +155,8 @@ struct remap_entry_ctx_t { u64 physical_offset_end; remap_segment_ctx_t *segment; remap_entry_ctx_t *next; -} PACKED; +}; +#pragma pack(pop) struct remap_segment_ctx_t{ u64 offset; @@ -215,6 +219,7 @@ typedef struct { u8 salt_source[0x20]; } ivfc_save_hdr_t; +#pragma pack(push, 1) typedef struct { u8 cmac[0x10]; u8 _0x10[0xF0]; @@ -233,7 +238,8 @@ typedef struct { u8 _0x748[0x390]; ivfc_save_hdr_t fat_ivfc_header; u8 _0xB98[0x3468]; -} PACKED save_header_t; +} save_header_t; +#pragma pack(pop) typedef struct { duplex_storage_ctx_t layers[2]; @@ -344,32 +350,40 @@ typedef struct { u32 parent; } save_entry_key_t; +#pragma pack(push, 1) typedef struct { u32 start_block; u64 length; u32 _0xC[2]; -} PACKED save_file_info_t; +} save_file_info_t; +#pragma pack(pop) +#pragma pack(push, 1) typedef struct { u32 next_directory; u32 next_file; u32 _0x8[3]; -} PACKED save_find_position_t; +} save_find_position_t; +#pragma pack(pop) +#pragma pack(push, 1) typedef struct { u32 next_sibling; union { /* Save table entry type. Size = 0x14. */ save_file_info_t save_file_info; save_find_position_t save_find_position; }; -} PACKED save_table_entry_t; +} save_table_entry_t; +#pragma pack(pop) +#pragma pack(push, 1) typedef struct { u32 parent; char name[SAVE_FS_LIST_MAX_NAME_LENGTH]; save_table_entry_t value; u32 next; -} PACKED save_fs_list_entry_t; +} save_fs_list_entry_t; +#pragma pack(pop) typedef struct { u32 free_list_head_index; diff --git a/source/set_ext.c b/source/set_ext.c deleted file mode 100644 index 294df7d..0000000 --- a/source/set_ext.c +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include -#include -#include - -#include "set_ext.h" -#include "service_guard.h" - -static Service g_setcalSrv; - -NX_GENERATE_SERVICE_GUARD(setcal); - -Result _setcalInitialize() { - return smGetService(&g_setcalSrv, "set:cal"); -} - -void _setcalCleanup() { - serviceClose(&g_setcalSrv); -} - -Result setcalGetEticketDeviceKey(void *key) -{ - return serviceDispatch(&g_setcalSrv, 21, - .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, - .buffers = { { key, 0x244 } }, - ); -} diff --git a/source/set_ext.h b/source/set_ext.h deleted file mode 100644 index b326624..0000000 --- a/source/set_ext.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#ifndef __SET_EXT_H__ -#define __SET_EXT_H__ - -#include - -Result setcalInitialize(void); -void setcalExit(void); - -/** - * @brief Gets the extended ETicket RSA-2048 Key from CAL0 - * @param key Pointer to 0x244-byte output buffer. - */ -Result setcalGetEticketDeviceKey(void *key); - -#endif diff --git a/source/ui.c b/source/ui.c index d45826c..bc35e66 100644 --- a/source/ui.c +++ b/source/ui.c @@ -25,8 +25,6 @@ extern dumpOptions dumpCfg; extern bool keysFileAvailable; -extern AppletType programAppletType; - extern gamecard_ctx_t gameCardInfo; extern u32 titleAppCount, titlePatchCount, titleAddOnCount; @@ -36,8 +34,8 @@ extern u32 emmcTitleAppCount, emmcTitlePatchCount, emmcTitleAddOnCount; extern base_app_ctx_t *baseAppEntries; extern patch_addon_ctx_t *patchEntries, *addOnEntries; -extern char *filenames[FILENAME_MAX_CNT]; -extern int filenamesCount; +extern char **filenameBuffer; +extern int filenameCount; extern char curRomFsPath[NAME_BUF_LEN]; extern romfs_browser_entry *romFsBrowserEntries; @@ -114,10 +112,10 @@ static const char *dirHighlightIconPath = "romfs:/browser/dir_highlight.jpg"; static u8 *dirHighlightIconBuf = NULL; static const char *fileNormalIconPath = "romfs:/browser/file_normal.jpg"; -static u8 *fileNormalIconBuf = NULL; +u8 *fileNormalIconBuf = NULL; static const char *fileHighlightIconPath = "romfs:/browser/file_highlight.jpg"; -static u8 *fileHighlightIconBuf = NULL; +u8 *fileHighlightIconBuf = NULL; static const char *enabledNormalIconPath = "romfs:/browser/enabled_normal.jpg"; u8 *enabledNormalIconBuf = NULL; @@ -197,15 +195,15 @@ void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b) framebuf_width = (stride / sizeof(u32)); } - u32 lx, ly; + int lx, ly; u32 framex, framey; for (ly = 0; ly < height; ly++) { for (lx = 0; lx < width; lx++) { - framex = (x + lx); - framey = (y + ly); + framex = (u32)(x + lx); + framey = (u32)(y + ly); framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(r, g, b); } @@ -241,18 +239,17 @@ void uiDrawIcon(const u8 *icon, int width, int height, int x, int y) framebuf_width = (stride / sizeof(u32)); } - u32 lx, ly; - u32 framex, framey; - u32 pos = 0; + int lx, ly; + u32 framex, framey, pos; for (ly = 0; ly < height; ly++) { for (lx = 0; lx < width; lx++) { - framex = (x + lx); - framey = (y + ly); + framex = (u32)(x + lx); + framey = (u32)(y + ly); - pos = (((ly * width) + lx) * 3); + pos = (u32)(((ly * width) + lx) * 3); framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(icon[pos], icon[pos + 1], icon[pos + 2]); } @@ -458,8 +455,8 @@ void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...) vsnprintf(string, MAX_CHARACTERS(string), fmt, args); va_end(args); - u32 tmpx = (x <= 8 ? 8 : (x + 8)); - u32 tmpy = (font_height + (y <= 8 ? 8 : (y + 8))); + u32 tmpx = (x < 8 ? 8 : x); + u32 tmpy = (font_height + (y < 8 ? 8 : y)); FT_Error ret = 0; FT_UInt glyph_index = 0; @@ -513,7 +510,7 @@ void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...) if (ret) break; - if ((tmpx + (sharedFontsFaces[j]->glyph->advance.x >> 6)) >= (FB_WIDTH - 8)) + if ((tmpx + (sharedFontsFaces[j]->glyph->advance.x >> 6)) > (FB_WIDTH - 8)) { tmpx = 8; tmpy += LINE_HEIGHT; @@ -555,7 +552,7 @@ u32 uiGetStrWidth(const char *fmt, ...) if (tmpchar == '\n' || tmpchar == '\r') { - continue; + break; } else if (tmpchar == '\t') { @@ -606,12 +603,12 @@ void uiUpdateStatusMsg() { if (!strlen(statusMessage) || !statusMessageFadeout) return; - uiFill(0, FB_HEIGHT - (font_height + STRING_Y_POS(1)), FB_WIDTH, font_height + STRING_Y_POS(1), BG_COLOR_RGB); + uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height * 2, BG_COLOR_RGB); if ((statusMessageFadeout - 4) > bgColors[0]) { int fadeout = (statusMessageFadeout > 255 ? 255 : statusMessageFadeout); - uiDrawString(STRING_X_POS, FB_HEIGHT - (font_height + STRING_Y_POS(1)), fadeout, fadeout, fadeout, statusMessage); + uiDrawString(STRING_X_POS, FB_HEIGHT - (font_height * 2), fadeout, fadeout, fadeout, statusMessage); statusMessageFadeout -= 4; } else { statusMessageFadeout = 0; @@ -645,7 +642,7 @@ void uiPrintHeadline() void uiPrintOption(int x, int y, int endPosition, bool leftArrow, bool rightArrow, int r, int g, int b, const char *fmt, ...) { - if (x < 8 || x >= OPTIONS_X_END_POS || y < 8 || y >= (FB_HEIGHT - 8 - font_height) || endPosition < OPTIONS_X_END_POS || endPosition >= (FB_WIDTH - 8) || !fmt || !*fmt) return; + if (x < 8 || x > OPTIONS_X_END_POS || y < 8 || y > (FB_HEIGHT - 8) || endPosition < OPTIONS_X_END_POS || endPosition > (FB_WIDTH - 8) || !fmt || !*fmt) return; int xpos = x; char option[NAME_BUF_LEN] = {'\0'}; @@ -675,16 +672,18 @@ void uiPrintOption(int x, int y, int endPosition, bool leftArrow, bool rightArro void uiTruncateOptionStr(char *str, int x, int y, int endPosition) { - if (!str || !strlen(str) || x < 8 || x >= OPTIONS_X_END_POS || y < 8 || y >= (FB_HEIGHT - 8 - font_height) || endPosition < OPTIONS_X_END_POS || endPosition >= (FB_WIDTH - 8)) return; + if (!str || !strlen(str) || x < 8 || x > OPTIONS_X_END_POS || y < 8 || y > (FB_HEIGHT - 8) || endPosition < OPTIONS_X_END_POS || endPosition > (FB_WIDTH - 8)) return; int xpos = x; char *option = str; + u32 optionStrWidth = uiGetStrWidth(option); + u32 limit = (u32)(endPosition - xpos - (font_height * 2)); // Check if we're dealing with a long title selector string - if (optionStrWidth >= (endPosition - xpos - (font_height * 2))) + if (optionStrWidth >= limit) { - while(optionStrWidth >= (endPosition - xpos - (font_height * 2))) + while(optionStrWidth >= limit) { option++; optionStrWidth = uiGetStrWidth(option); @@ -1075,15 +1074,6 @@ UIResult uiProcess() if (titleAppCount) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve title entries from the inserted gamecard!"); - - if (strlen(gameCardInfo.updateVersionStr)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Bundled FW Update: %s", gameCardInfo.updateVersionStr); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "In order to be able to dump data from this gamecard, make sure your console is at least on this FW version."); - } } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: gamecard application count is zero!"); } @@ -1103,6 +1093,14 @@ UIResult uiProcess() { if (forcedXciDump) { + if (strlen(gameCardInfo.updateVersionStr)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Bundled FW Update: %s", gameCardInfo.updateVersionStr); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "In order to be able to parse any kind of metadata from this gamecard and/or dump its contents to NSPs,\nmake sure your console is at least on this FW version."); + breaks += 2; + } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Press " NINTENDO_FONT_Y " to dump the gamecard to \"gamecard.xci\"."); } else { if (!gameCardInfo.rootHfs0Header) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Are you using \"nogc\" spoofing in your CFW? If so, please consider this option disables all gamecard I/O."); @@ -1115,7 +1113,7 @@ UIResult uiProcess() res = resultShowGameCardMenu; hidScanInput(); - keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); // Exit if (keysDown & KEY_PLUS) res = resultExit; @@ -1174,7 +1172,7 @@ UIResult uiProcess() res = resultShowSdCardEmmcMenu; hidScanInput(); - keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); // Exit if (keysDown & KEY_PLUS) res = resultExit; @@ -1209,7 +1207,7 @@ UIResult uiProcess() /* Draw icon */ if (baseAppEntries[selectedAppInfoIndex].icon != NULL) { - uiDrawIcon(baseAppEntries[selectedAppInfoIndex].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); + uiDrawIcon(baseAppEntries[selectedAppInfoIndex].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos); xpos += (NACP_ICON_DOWNSCALED + 8); ypos += 8; } @@ -1643,8 +1641,8 @@ UIResult uiProcess() break; case stateHfs0Browser: - menu = (const char**)filenames; - menuItemsCount = filenamesCount; + menu = (const char**)filenameBuffer; + menuItemsCount = filenameCount; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems[selectedPartitionIndex] : hfs0BrowserType2MenuItems[selectedPartitionIndex])); breaks += 2; @@ -1674,8 +1672,8 @@ UIResult uiProcess() break; case stateExeFsSectionBrowser: - menu = (const char**)filenames; - menuItemsCount = filenamesCount; + menu = (const char**)filenameBuffer; + menuItemsCount = filenameCount; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, exeFsMenuItems[1]); breaks++; @@ -1718,8 +1716,8 @@ UIResult uiProcess() break; case stateRomFsSectionBrowser: - menu = (const char**)filenames; - menuItemsCount = filenamesCount; + menu = (const char**)filenameBuffer; + menuItemsCount = filenameCount; // Skip the parent directory entry ("..") in the RomFS browser if we're currently at the root directory if (menu && menuItemsCount && strlen(curRomFsPath) <= 1) @@ -1757,9 +1755,9 @@ UIResult uiProcess() if (strlen(curRomFsPath) <= 1 || (strlen(curRomFsPath) > 1 && cursor > 0)) { - snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d | Current entry: %d", filenamesCount - 1, (strlen(curRomFsPath) <= 1 ? (cursor + 1) : cursor)); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d | Current entry: %d", filenameCount - 1, (strlen(curRomFsPath) <= 1 ? (cursor + 1) : cursor)); } else { - snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d", filenamesCount - 1); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d", filenameCount - 1); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, strbuf); @@ -1768,8 +1766,8 @@ UIResult uiProcess() case stateSdCardEmmcMenu: generateSdCardEmmcTitleList(); - menu = (const char**)filenames; - menuItemsCount = filenamesCount; + menu = (const char**)filenameBuffer; + menuItemsCount = filenameCount; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, mainMenuItems[1]); @@ -1795,8 +1793,8 @@ UIResult uiProcess() // Otherwise, this will only fill filenameBuffer generateOrphanPatchOrAddOnList(); - menu = (const char**)filenames; - menuItemsCount = filenamesCount; + menu = (const char**)filenameBuffer; + menuItemsCount = filenameCount; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Dump installed content with missing base application"); @@ -1836,12 +1834,7 @@ UIResult uiProcess() { breaks++; - if (scroll > 0) - { - u32 arrowWidth = uiGetStrWidth(upwardsArrow); - - uiDrawString((FB_WIDTH / 2) - (arrowWidth / 2), STRING_Y_POS(breaks), FONT_COLOR_RGB, upwardsArrow); - } + if (scroll > 0) uiDrawString((FB_WIDTH / 2) - (uiGetStrWidth(upwardsArrow) / 2), STRING_Y_POS(breaks), FONT_COLOR_RGB, upwardsArrow); breaks++; @@ -1971,19 +1964,19 @@ UIResult uiProcess() } xpos = STRING_X_POS; - ypos = ((breaks * LINE_HEIGHT) + (uiState == stateSdCardEmmcMenu ? (j * (NACP_ICON_DOWNSCALED + 12)) : (j * (font_height + 12))) + 6); + ypos = (8 + (breaks * LINE_HEIGHT) + (uiState == stateSdCardEmmcMenu ? (j * (NACP_ICON_DOWNSCALED + 12)) : (j * (font_height + 12))) + 6); if (i == cursor) { highlight = true; - uiFill(0, (ypos + 8) - 6, FB_WIDTH, (uiState == stateSdCardEmmcMenu ? (NACP_ICON_DOWNSCALED + 12) : (font_height + 12)), HIGHLIGHT_BG_COLOR_RGB); + uiFill(0, ypos - 6, FB_WIDTH, (uiState == stateSdCardEmmcMenu ? (NACP_ICON_DOWNSCALED + 12) : (font_height + 12)), HIGHLIGHT_BG_COLOR_RGB); } if (uiState == stateSdCardEmmcMenu) { if (baseAppEntries[i].icon != NULL) { - uiDrawIcon(baseAppEntries[i].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); + uiDrawIcon(baseAppEntries[i].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos); xpos += (NACP_ICON_DOWNSCALED + 8); } @@ -2002,7 +1995,7 @@ UIResult uiProcess() icon = (highlight ? fileHighlightIconBuf : fileNormalIconBuf); } - uiDrawIcon(icon, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos + 8); + uiDrawIcon(icon, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos); xpos += (BROWSER_ICON_DIMENSION + 8); } @@ -2013,26 +2006,20 @@ UIResult uiProcess() u32 idx = ((uiState == stateRomFsSectionBrowser && strlen(curRomFsPath) <= 1) ? (i + 1) : i); // Adjust index if we're at the root directory - if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser) + if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser || (uiState == stateRomFsSectionBrowser && romFsBrowserEntries[idx].type == ROMFS_ENTRY_FILE)) { - uiDrawString(FB_WIDTH - (font_height * 7), ypos, HIGHLIGHT_FONT_COLOR_RGB, "(%s)", hfs0ExeFsEntriesSizes[idx].sizeStr); - } else - if (uiState == stateRomFsSectionBrowser && romFsBrowserEntries[idx].type == ROMFS_ENTRY_FILE) - { - uiDrawString(FB_WIDTH - (font_height * 7), ypos, HIGHLIGHT_FONT_COLOR_RGB, "(%s)", romFsBrowserEntries[idx].sizeInfo.sizeStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "(%s)", ((uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser) ? hfs0ExeFsEntriesSizes[idx].sizeStr : romFsBrowserEntries[idx].sizeInfo.sizeStr)); + uiDrawString(FB_WIDTH - (8 + uiGetStrWidth(strbuf)), ypos, HIGHLIGHT_FONT_COLOR_RGB, strbuf); } } else { uiDrawString(xpos, ypos, FONT_COLOR_RGB, menu[i]); u32 idx = ((uiState == stateRomFsSectionBrowser && strlen(curRomFsPath) <= 1) ? (i + 1) : i); // Adjust index if we're at the root directory - if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser) + if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser || (uiState == stateRomFsSectionBrowser && romFsBrowserEntries[idx].type == ROMFS_ENTRY_FILE)) { - uiDrawString(FB_WIDTH - (font_height * 7), ypos, FONT_COLOR_RGB, "(%s)", hfs0ExeFsEntriesSizes[idx].sizeStr); - } else - if (uiState == stateRomFsSectionBrowser && romFsBrowserEntries[idx].type == ROMFS_ENTRY_FILE) - { - uiDrawString(FB_WIDTH - (font_height * 7), ypos, FONT_COLOR_RGB, "(%s)", romFsBrowserEntries[idx].sizeInfo.sizeStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "(%s)", ((uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser) ? hfs0ExeFsEntriesSizes[idx].sizeStr : romFsBrowserEntries[idx].sizeInfo.sizeStr)); + uiDrawString(FB_WIDTH - (8 + uiGetStrWidth(strbuf)), ypos, FONT_COLOR_RGB, strbuf); } } @@ -2197,9 +2184,9 @@ UIResult uiProcess() } else { if (highlight) { - uiFill(FB_WIDTH / 2, (ypos + 8) - 6, FB_WIDTH / 2, font_height + 12, HIGHLIGHT_BG_COLOR_RGB); + uiFill(FB_WIDTH / 2, ypos - 6, FB_WIDTH / 2, font_height + 12, HIGHLIGHT_BG_COLOR_RGB); } else { - uiFill(FB_WIDTH / 2, (ypos + 8) - 6, FB_WIDTH / 2, font_height + 12, BG_COLOR_RGB); + uiFill(FB_WIDTH / 2, ypos - 6, FB_WIDTH / 2, font_height + 12, BG_COLOR_RGB); } } } @@ -2534,51 +2521,43 @@ UIResult uiProcess() if ((scroll + maxElements) < menuItemsCount) { - ypos = ((breaks * LINE_HEIGHT) + (uiState == stateSdCardEmmcMenu ? (j * (NACP_ICON_DOWNSCALED + 12)) : (j * (font_height + 12)))); - - u32 arrowWidth = uiGetStrWidth(downwardsArrow); - - uiDrawString((FB_WIDTH / 2) - (arrowWidth / 2), ypos, FONT_COLOR_RGB, downwardsArrow); + ypos = (8 + (breaks * LINE_HEIGHT) + (uiState == stateSdCardEmmcMenu ? (j * (NACP_ICON_DOWNSCALED + 12)) : (j * (font_height + 12)))); + uiDrawString((FB_WIDTH / 2) - (uiGetStrWidth(downwardsArrow) / 2), ypos, FONT_COLOR_RGB, downwardsArrow); } + j++; + if ((scroll + maxElements) < menuItemsCount) j++; + + ypos = (8 + (breaks * LINE_HEIGHT) + (j * (font_height + 12))); + if (uiState == stateMainMenu) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - // Print warning about missing Lockpick_RCM keys file if (!keysFileAvailable) { - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "Warning: missing keys file at \"%s\".", KEYS_FILE_PATH); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "This file is needed to deal with the encryption schemes used by Nintendo Switch content files."); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "SD/eMMC operations will be entirely disabled, along with NSP/ExeFS/RomFS related operations."); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); - uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "Please run Lockpick_RCM to generate this file. More info at: https://github.com/shchmue/Lockpick_RCM"); + uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "Please run Lockpick_RCM to generate this file. More info at: " LOCKPICK_RCM_URL); } // Print warning about running the application under applet mode - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) + if (appletModeCheck()) { - if (!keysFileAvailable) j += 2; + if (!keysFileAvailable) ypos += ((LINE_HEIGHT * 2) + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "Warning: running under applet mode."); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "It seems you used an applet (Album, Settings, etc.) to run the application. This mode greatly limits the amount of usable RAM."); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "If you ever get any memory allocation errors, please consider running the application through title override (hold R while launching a game)."); } } @@ -2586,54 +2565,33 @@ UIResult uiProcess() // Print information about the "Change NPDM RSA key/sig in Program NCA" option if (((uiState == stateNspAppDumpMenu || uiState == stateNspPatchDumpMenu) && cursor == 5) || (uiState == stateSdCardEmmcBatchModeMenu && cursor == 7)) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "Replaces the public RSA key in the NPDM ACID section and the NPDM RSA signature in the Program NCA (only if it needs other modifications)."); - j++; + ypos += (LINE_HEIGHT + LINE_STRING_OFFSET); - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "Disabling this will make the output NSP require ACID patches to work under any CFW, but will also make the Program NCA verifiable by PC tools."); } // Print information about the "Dump delta fragments" option if ((uiState == stateNspPatchDumpMenu && cursor == 6) || (uiState == stateSdCardEmmcBatchModeMenu && cursor == 8)) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "Dumps delta fragments for the selected update(s), if available. These are commonly excluded - they serve no real purpose in output dumps."); } // Print information about the "Split files bigger than 4 GiB (FAT32 support)" option if ((uiState == stateExeFsMenu || uiState == stateRomFsMenu) && cursor == 2) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "If FAT32 support is enabled, files bigger than 4 GiB will be split and stored in a subdirectory with the archive bit set (like NSPs)."); } // Print information about the "Save data to CFW directory (LayeredFS)" option if ((uiState == stateExeFsMenu || uiState == stateRomFsMenu) && cursor == 3) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "Enabling this option will save output data to \"%s[TitleID]/%s/\" (LayeredFS directory structure).", strchr(cfwDirStr, '/'), (uiState == stateExeFsMenu ? "exefs" : "romfs")); } // Print hint about dumping RomFS content from DLCs if ((uiState == stateRomFsMenu && cursor == 4 && ((menuType == MENUTYPE_GAMECARD && titleAppCount <= 1 && checkIfBaseApplicationHasPatchOrAddOn(0, true)) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))) || ((uiState == stateRomFsSectionDataDumpMenu || uiState == stateRomFsSectionBrowserMenu) && cursor == 2 && (menuType == MENUTYPE_GAMECARD && titleAppCount > 1 && checkIfBaseApplicationHasPatchOrAddOn(selectedAppIndex, true)))) { - j++; - if ((scroll + maxElements) < menuItemsCount) j++; - - ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); uiDrawString(STRING_X_POS, ypos, FONT_COLOR_RGB, "Hint: choosing a DLC will only access RomFS data from it, unlike updates (which share their RomFS data with its base application)."); } } else { @@ -2658,8 +2616,8 @@ UIResult uiProcess() hidScanInput(); - keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - keysHeld = hidKeysHeld(CONTROLLER_P1_AUTO); + keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); + keysHeld = hidKeysAllHeld(CONTROLLER_P1_AUTO); if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH)) || (menuType == MENUTYPE_GAMECARD && gameCardInfo.isInserted != curGcStatus)) break; } @@ -4167,12 +4125,14 @@ UIResult uiProcess() if (uiState == stateHfs0Browser) { freeHfs0ExeFsEntriesSizes(); + freeFilenameBuffer(); res = resultShowHfs0BrowserMenu; } else if (uiState == stateExeFsSectionBrowser) { freeHfs0ExeFsEntriesSizes(); + freeFilenameBuffer(); freeExeFsContext(); @@ -4187,9 +4147,8 @@ UIResult uiProcess() res = resultRomFsSectionBrowserChangeDir; } else { freeRomFsBrowserEntries(); - + freeFilenameBuffer(); if (curRomFsType == ROMFS_TYPE_PATCH) freeBktrContext(); - freeRomFsContext(); res = ((menuType == MENUTYPE_GAMECARD && titleAppCount > 1) ? resultShowRomFsSectionBrowserMenu : resultShowRomFsMenu); @@ -4279,9 +4238,7 @@ UIResult uiProcess() for(i = 0; i < scrollAmount; i++) { if (cursor >= (menuItemsCount - 1)) break; - cursor++; - if ((cursor - scroll) >= maxElements) scroll++; } } @@ -4297,9 +4254,7 @@ UIResult uiProcess() for(i = 0; i < -scrollAmount; i++) { if (cursor <= 0) break; - cursor--; - if ((cursor - scroll) < 0) scroll--; } } @@ -4415,13 +4370,82 @@ UIResult uiProcess() // Avoid placing the cursor on the "Dump base applications", "Dump updates" and/or "Dump DLCs" options in the batch mode menu if we're dealing with a storage source that doesn't hold any title belonging to the current category if (uiState == stateSdCardEmmcBatchModeMenu && ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && ((!titleAppCount && cursor == 1) || (!titlePatchCount && cursor == 2) || (!titleAddOnCount && cursor == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && ((!sdCardTitleAppCount && cursor == 1) || (!sdCardTitlePatchCount && cursor == 2) || (!sdCardTitleAddOnCount && cursor == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && ((!emmcTitleAppCount && cursor == 1) || (!emmcTitlePatchCount && cursor == 2) || (!emmcTitleAddOnCount && cursor == 3))))) { - if (scrollAmount > 0) + if (cursor == 1) { - cursor++; + if (scrollAmount > 0) + { + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL) + { + cursor = (titlePatchCount ? 2 : (titleAddOnCount ? 3 : 4)); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD) + { + cursor = (sdCardTitlePatchCount ? 2 : (sdCardTitleAddOnCount ? 3 : 4)); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) + { + cursor = (emmcTitlePatchCount ? 2 : (emmcTitleAddOnCount ? 3 : 4)); + } + } else + if (scrollAmount < 0) + { + cursor = 0; + } } else - if (scrollAmount < 0) + if (cursor == 2) { - cursor--; + if (scrollAmount > 0) + { + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL) + { + cursor = (titleAddOnCount ? 3 : 4); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD) + { + cursor = (sdCardTitleAddOnCount ? 3 : 4); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) + { + cursor = (emmcTitleAddOnCount ? 3 : 4); + } + } else + if (scrollAmount < 0) + { + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL) + { + cursor = (titleAppCount ? 1 : 0); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD) + { + cursor = (sdCardTitleAppCount ? 1 : 0); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) + { + cursor = (emmcTitleAppCount ? 1 : 0); + } + } + } else + if (cursor == 3) + { + if (scrollAmount > 0) + { + cursor = 4; + } else + if (scrollAmount < 0) + { + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL) + { + cursor = (titlePatchCount ? 2 : (titleAppCount ? 1 : 0)); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD) + { + cursor = (sdCardTitlePatchCount ? 2 : (sdCardTitleAppCount ? 1 : 0)); + } else + if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) + { + cursor = (emmcTitlePatchCount ? 2 : (emmcTitleAppCount ? 1 : 0)); + } + } } } @@ -4817,12 +4841,12 @@ UIResult uiProcess() } else if (uiState == stateHfs0BrowserCopyFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (HFS0 partition %u [%s])", filenames[selectedFileIndex], selectedPartitionIndex, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, selectedPartitionIndex)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (HFS0 partition %u [%s])", filenameBuffer[selectedFileIndex], selectedPartitionIndex, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, selectedPartitionIndex)); breaks += 2; uiRefreshDisplay(); - dumpFileFromHfs0Partition(selectedPartitionIndex, selectedFileIndex, filenames[selectedFileIndex], true); + dumpFileFromHfs0Partition(selectedPartitionIndex, selectedFileIndex, filenameBuffer[selectedFileIndex], true); waitForButtonPress(); @@ -4906,7 +4930,7 @@ UIResult uiProcess() } else if (uiState == stateExeFsSectionBrowserCopyFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (ExeFS)", filenames[selectedFileIndex]); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (ExeFS)", filenameBuffer[selectedFileIndex]); breaks++; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s | %s%s", exeFsMenuItems[2], (dumpCfg.exeFsDumpCfg.isFat32 ? "Yes" : "No"), exeFsMenuItems[3], (dumpCfg.exeFsDumpCfg.useLayeredFSDir ? "Yes" : "No")); @@ -5010,7 +5034,7 @@ UIResult uiProcess() bool romfs_fail = false; - if (readNcaRomFsSection(curIndex, curRomFsType)) + if (readNcaRomFsSection(curIndex, curRomFsType, -1)) { if (getRomFsFileList(0, (curRomFsType == ROMFS_TYPE_PATCH))) { @@ -5076,6 +5100,7 @@ UIResult uiProcess() if (romfs_fail) { freeRomFsBrowserEntries(); + freeFilenameBuffer(); if (curRomFsType == ROMFS_TYPE_PATCH) freeBktrContext(); freeRomFsContext(); @@ -5088,7 +5113,7 @@ UIResult uiProcess() { u32 curIndex = 0; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (RomFS)", filenames[selectedFileIndex]); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (RomFS)", filenameBuffer[selectedFileIndex]); breaks++; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s | %s%s", romFsMenuItems[2], (dumpCfg.romFsDumpCfg.isFat32 ? "Yes" : "No"), romFsMenuItems[3], (dumpCfg.romFsDumpCfg.useLayeredFSDir ? "Yes" : "No")); diff --git a/source/ui.h b/source/ui.h index ab142e2..d9b893e 100644 --- a/source/ui.h +++ b/source/ui.h @@ -15,7 +15,7 @@ #define STRING_DEFAULT_POS 8, 8 #define STRING_X_POS 8 -#define STRING_Y_POS(x) (((x) * LINE_HEIGHT) + LINE_STRING_OFFSET) +#define STRING_Y_POS(x) (8 + ((x) * LINE_HEIGHT) + ((x) > 0 ? LINE_STRING_OFFSET : 0)) #define BG_COLOR_RGB 50, 50, 50 #define FONT_COLOR_RGB 255, 255, 255 diff --git a/source/util.c b/source/util.c index 2ccadd1..6b8d2f2 100644 --- a/source/util.c +++ b/source/util.c @@ -26,6 +26,8 @@ /* Extern variables */ +extern bool highlight; + extern int breaks; extern int font_height; @@ -34,31 +36,15 @@ extern int scroll; extern curMenuType menuType; +extern u8 *fileNormalIconBuf; +extern u8 *fileHighlightIconBuf; + extern nca_keyset_t nca_keyset; -/* Constants */ - -const char *configPath = NXDUMPTOOL_BASE_PATH "config.bin"; - -const char *noIntroDatQuickCheckUrl = "https://datomatic.no-intro.org/qchknsw.php"; - -const char *nswReleasesXmlUrl = "http://nswdb.com/xml.php"; -const char *nswReleasesXmlTmpPath = NXDUMPTOOL_BASE_PATH "NSWreleases.xml.tmp"; -const char *nswReleasesXmlPath = NXDUMPTOOL_BASE_PATH "NSWreleases.xml"; -const char *nswReleasesRootElement = "releases"; -const char *nswReleasesChildren = "release"; -const char *nswReleasesChildrenTitleID = "titleid"; -const char *nswReleasesChildrenImgCrc = "imgcrc"; -const char *nswReleasesChildrenReleaseName = "releasename"; - -const char *githubReleasesApiUrl = "https://api.github.com/repos/DarkMatterCore/nxdumptool/releases/latest"; -const char *nxDumpToolPath = NXDUMPTOOL_BASE_PATH "nxdumptool.nro"; -const char *userAgent = "nxdumptool/" APP_VERSION " (Nintendo Switch)"; - /* Statically allocated variables */ -static bool initNcm = false, initNs = false, initCsrng = false, initSpl = false, initPmdmnt = false, initPl = false; -static bool openFsDevOp = false, openGcEvtNotifier = false, loadGcKernEvt = false, gcThreadInit = false; +static bool initNcm = false, initNs = false, initCsrng = false, initSpl = false, initPmdmnt = false, initPl = false, initNet = false; +static bool openFsDevOp = false, openGcEvtNotifier = false, loadGcKernEvt = false, gcThreadInit = false, homeBtnBlocked = false; dumpOptions dumpCfg; @@ -67,7 +53,7 @@ bool keysFileAvailable = false; static pthread_t gameCardDetectionThread; static UEvent exitEvent; -AppletType programAppletType; +static AppletType programAppletType; char cfwDirStr[32] = {'\0'}; @@ -96,9 +82,8 @@ static char *result_buf = NULL; static size_t result_sz = 0; static size_t result_written = 0; -char *filenameBuffer = NULL; -char *filenames[FILENAME_MAX_CNT]; -int filenamesCount = 0; +char **filenameBuffer = NULL; +int filenameCount = 0, filenameIndex = 0; u8 *dumpBuf = NULL; u8 *gcReadBuf = NULL; @@ -109,7 +94,7 @@ u32 orphanEntriesCnt = 0; char strbuf[NAME_BUF_LEN] = {'\0'}; -char appLaunchPath[NAME_BUF_LEN / 2] = {'\0'}; +static const char *appLaunchPath = NULL; FsStorage fatFsStorage; static bool openBis = false, mountBisFatFs = false; @@ -141,7 +126,7 @@ void loadConfig() dumpCfg.romFsDumpCfg.isFat32 = true; - FILE *configFile = fopen(configPath, "rb"); + FILE *configFile = fopen(CONFIG_PATH, "rb"); if (!configFile) return; fseek(configFile, 0, SEEK_END); @@ -151,7 +136,7 @@ void loadConfig() if (configFileSize != sizeof(dumpOptions)) { fclose(configFile); - unlink(configPath); + remove(CONFIG_PATH); return; } @@ -161,7 +146,7 @@ void loadConfig() if (read_res != sizeof(dumpOptions)) { - unlink(configPath); + remove(CONFIG_PATH); return; } @@ -179,13 +164,13 @@ void loadConfig() void saveConfig() { - FILE *configFile = fopen(configPath, "wb"); + FILE *configFile = fopen(CONFIG_PATH, "wb"); if (!configFile) return; size_t write_res = fwrite(&dumpCfg, 1, sizeof(dumpOptions), configFile); fclose(configFile); - if (write_res != sizeof(dumpOptions)) unlink(configPath); + if (write_res != sizeof(dumpOptions)) remove(CONFIG_PATH); } static bool isGameCardInserted() @@ -209,6 +194,8 @@ static void changeAtomicBool(volatile bool *ptr, bool value) static void *fsGameCardDetectionThreadFunc(void *arg) { + (void)arg; + Result result = 0; int idx = 0; @@ -288,8 +275,15 @@ Result openGameCardStoragePartition(openIStoragePartition partitionIndex) // Check if the provided IStorage index is valid if (!partitionIndex || partitionIndex >= ISTORAGE_PARTITION_INVALID) return MAKERESULT(Module_Libnx, LibnxError_IoError); - // Safety check: close the current IStorage instance if we have already opened one - if (gameCardInfo.curIStorageIndex && gameCardInfo.curIStorageIndex < ISTORAGE_PARTITION_INVALID) closeGameCardStoragePartition(); + // Safety check: check if we have already opened an IStorage instance + if (gameCardInfo.curIStorageIndex && gameCardInfo.curIStorageIndex < ISTORAGE_PARTITION_INVALID) + { + // If the opened IStorage instance is the same as the requested one, just return right away + if (gameCardInfo.curIStorageIndex == partitionIndex) return 0; + + // Otherwise, close the current IStorage instance + closeGameCardStoragePartition(); + } u8 i; u32 idx = (u32)(partitionIndex - 1); @@ -436,8 +430,8 @@ void delay(u8 seconds) static void createOutputDirectories() { + mkdir(HBLOADER_BASE_PATH, 0744); mkdir(APP_BASE_PATH, 0744); - mkdir(NXDUMPTOOL_BASE_PATH, 0744); mkdir(XCI_DUMP_PATH, 0744); mkdir(NSP_DUMP_PATH, 0744); mkdir(HFS0_DUMP_PATH, 0744); @@ -525,20 +519,54 @@ void updateFreeSpace() convertSize(freeSpace, freeSpaceStr, MAX_CHARACTERS(freeSpaceStr)); } +void freeFilenameBuffer(void) +{ + if (!filenameBuffer) return; + + for(int i = 0; i < filenameCount; i++) + { + if (filenameBuffer[i]) free(filenameBuffer[i]); + } + + filenameCount = filenameIndex = 0; + + free(filenameBuffer); + filenameBuffer = NULL; +} + +static bool allocateFilenameBuffer(u32 cnt) +{ + if (!cnt) return false; + + freeFilenameBuffer(); + + filenameBuffer = calloc(cnt, sizeof(char*)); + if (!filenameBuffer) return false; + + filenameCount = (int)cnt; + + return true; +} + +static bool addStringToFilenameBuffer(const char *str) +{ + if (!str || !strlen(str) || (filenameIndex + 1) > filenameCount) return false; + + filenameBuffer[filenameIndex] = strdup(str); + if (!filenameBuffer[filenameIndex]) + { + freeFilenameBuffer(); + return false; + } + + filenameIndex++; + + return true; +} + void initExeFsContext() { - exeFsContext.storageId = NcmStorageId_None; - memset(&(exeFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(exeFsContext.ncaId), 0, sizeof(NcmContentId)); - memset(&(exeFsContext.aes_ctx), 0, sizeof(Aes128CtrContext)); - exeFsContext.exefs_offset = 0; - exeFsContext.exefs_size = 0; - memset(&(exeFsContext.exefs_header), 0, sizeof(pfs0_header)); - exeFsContext.exefs_entries_offset = 0; - exeFsContext.exefs_entries = NULL; - exeFsContext.exefs_str_table_offset = 0; - exeFsContext.exefs_str_table = NULL; - exeFsContext.exefs_data_offset = 0; + memset(&exeFsContext, 0, sizeof(exefs_ctx_t)); } void freeExeFsContext() @@ -565,21 +593,7 @@ void freeExeFsContext() void initRomFsContext() { - romFsContext.storageId = NcmStorageId_None; - memset(&(romFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(romFsContext.ncaId), 0, sizeof(NcmContentId)); - memset(&(romFsContext.aes_ctx), 0, sizeof(Aes128CtrContext)); - romFsContext.section_offset = 0; - romFsContext.section_size = 0; - romFsContext.romfs_offset = 0; - romFsContext.romfs_size = 0; - romFsContext.romfs_dirtable_offset = 0; - romFsContext.romfs_dirtable_size = 0; - romFsContext.romfs_dir_entries = NULL; - romFsContext.romfs_filetable_offset = 0; - romFsContext.romfs_filetable_size = 0; - romFsContext.romfs_file_entries = NULL; - romFsContext.romfs_filedata_offset = 0; + memset(&romFsContext, 0, sizeof(romfs_ctx_t)); } void freeRomFsContext() @@ -606,27 +620,7 @@ void freeRomFsContext() void initBktrContext() { - bktrContext.storageId = NcmStorageId_None; - memset(&(bktrContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(bktrContext.ncaId), 0, sizeof(NcmContentId)); - memset(&(bktrContext.aes_ctx), 0, sizeof(Aes128CtrContext)); - bktrContext.section_offset = 0; - bktrContext.section_size = 0; - memset(&(bktrContext.superblock), 0, sizeof(bktr_superblock_t)); - bktrContext.relocation_block = NULL; - bktrContext.subsection_block = NULL; - bktrContext.virtual_seek = 0; - bktrContext.bktr_seek = 0; - bktrContext.base_seek = 0; - bktrContext.romfs_offset = 0; - bktrContext.romfs_size = 0; - bktrContext.romfs_dirtable_offset = 0; - bktrContext.romfs_dirtable_size = 0; - bktrContext.romfs_dir_entries = NULL; - bktrContext.romfs_filetable_offset = 0; - bktrContext.romfs_filetable_size = 0; - bktrContext.romfs_file_entries = NULL; - bktrContext.romfs_filedata_offset = 0; + memset(&bktrContext, 0, sizeof(bktr_ctx_t)); } void freeBktrContext() @@ -795,6 +789,28 @@ static void freeGlobalData() freeRomFsBrowserEntries(); freeHfs0ExeFsEntriesSizes(); + + freeFilenameBuffer(); +} + +u64 hidKeysAllDown() +{ + u8 controller; + u64 keysDown = 0; + + for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keysDown |= hidKeysDown((HidControllerID)controller); + + return keysDown; +} + +u64 hidKeysAllHeld() +{ + u8 controller; + u64 keysHeld = 0; + + for(controller = 0; controller < (u8)CONTROLLER_P1_AUTO; controller++) keysHeld |= hidKeysHeld((HidControllerID)controller); + + return keysHeld; } void consoleErrorScreen(const char *fmt, ...) @@ -812,7 +828,7 @@ void consoleErrorScreen(const char *fmt, ...) { hidScanInput(); - u64 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + u64 keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); if (keysDown && !((keysDown & KEY_TOUCH) || (keysDown & KEY_LSTICK_LEFT) || (keysDown & KEY_LSTICK_RIGHT) || (keysDown & KEY_LSTICK_UP) || (keysDown & KEY_LSTICK_DOWN) || \ (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_RSTICK_DOWN))) break; @@ -923,12 +939,18 @@ bool initApplicationResources(int argc, char **argv) { if (argv[i] && strlen(argv[i]) > 10 && !strncasecmp(argv[i], "sdmc:/", 6) && !strncasecmp(argv[i] + strlen(argv[i]) - 4, ".nro", 4)) { - snprintf(appLaunchPath, MAX_CHARACTERS(appLaunchPath), argv[i]); + appLaunchPath = (const char*)argv[i]; break; } } } + /* Initialize services */ + if (!initServices()) return false; + + /* Initialize UI */ + if (!uiInit()) return false; + /* Zero out gamecard info struct */ memset(&gameCardInfo, 0, sizeof(gamecard_ctx_t)); @@ -953,38 +975,18 @@ bool initApplicationResources(int argc, char **argv) /* Retrieve running CFW directory */ retrieveRunningCfwDir(); - /* Update free space */ - updateFreeSpace(); - - /* Initialize services */ - if (!initServices()) return false; - - /* Initialize UI */ - if (!uiInit()) return false; - /* Get applet type */ programAppletType = appletGetAppletType(); - /* Block HOME menu button presses if we're running as a regular application or a system application */ - if (programAppletType == AppletType_Application || programAppletType == AppletType_SystemApplication) appletBeginBlockingHomeButton(0); - /* Disable screen dimming and auto sleep */ appletSetMediaPlaybackState(true); /* Enable CPU boost mode */ appletSetCpuBoostMode(ApmCpuBoostMode_Type1); - /* Mount BIS System partition from the eMMC */ + /* Mount eMMC BIS System partition */ if (!mountSysEmmcPartition()) goto out; - /* Allocate memory for the filename buffer */ - filenameBuffer = calloc(FILENAME_BUFFER_SIZE, sizeof(char)); - if (!filenameBuffer) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the filename buffer!", __func__); - goto out; - } - /* Allocate memory for the general purpose dump buffer */ dumpBuf = calloc(DUMP_BUFFER_SIZE, sizeof(u8)); if (!dumpBuf) @@ -1050,6 +1052,9 @@ bool initApplicationResources(int argc, char **argv) /* Load settings from configuration file */ loadConfig(); + /* Update free space */ + updateFreeSpace(); + /* Set output status */ success = true; @@ -1098,10 +1103,7 @@ void deinitApplicationResources() /* Free general purpose dump buffer */ if (dumpBuf) free(dumpBuf); - /* Free filename buffer */ - if (filenameBuffer) free(filenameBuffer); - - /* Unmount BIS System partition from the eMMC */ + /* Unmount eMMC BIS System partition */ unmountSysEmmcPartition(); /* Disable CPU boost mode */ @@ -1110,9 +1112,6 @@ void deinitApplicationResources() /* Enable screen dimming and auto sleep */ appletSetMediaPlaybackState(false); - /* Unblock HOME menu button presses if we're running as a regular application or a system application */ - if (programAppletType == AppletType_Application || programAppletType == AppletType_SystemApplication) appletEndBlockingHomeButton(); - /* Deinitialize UI */ uiDeinit(); @@ -1120,6 +1119,34 @@ void deinitApplicationResources() deinitServices(); } +bool appletModeCheck() +{ + return (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication); +} + +void appletModeOperationWarning() +{ + if (!appletModeCheck()) return; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); + breaks++; +} + +void changeHomeButtonBlockStatus(bool block) +{ + // Only change HOME button blocking status if we're running as a regular application or a system application, and if it's current blocking status is different than the requested one + if (appletModeCheck() || block == homeBtnBlocked) return; + + if (block) + { + appletBeginBlockingHomeButtonShortAndLongPressed(0); + } else { + appletEndBlockingHomeButtonShortAndLongPressed(); + } + + homeBtnBlocked = block; +} + void formatETAString(u64 curTime, char *out, size_t outSize) { if (!out || !outSize) return; @@ -1144,29 +1171,16 @@ void formatETAString(u64 curTime, char *out, size_t outSize) snprintf(out, outSize, "%02luH%02luM%02luS", hour, min, sec); } -void clearFilenameBuffer() -{ - memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); - memset(filenames, 0, FILENAME_MAX_CNT * sizeof(char*)); - filenamesCount = 0; -} - -void addStringToFilenameBuffer(const char *string) -{ - if (!string || !strlen(string) || (filenamesCount + 1) >= FILENAME_MAX_CNT) return; - - char *curFilename = (filenameBuffer + (filenamesCount * FILENAME_LENGTH)); - filenames[filenamesCount++] = curFilename; - snprintf(curFilename, FILENAME_LENGTH - 1, string); -} - void generateSdCardEmmcTitleList() { - clearFilenameBuffer(); - if (!titleAppCount || !baseAppEntries) return; - for(u32 i = 0; i < titleAppCount; i++) addStringToFilenameBuffer(baseAppEntries[i].name); + if (!allocateFilenameBuffer(titleAppCount)) return; + + for(u32 i = 0; i < titleAppCount; i++) + { + if (!addStringToFilenameBuffer(baseAppEntries[i].name)) return; + } } static void convertTitleVersionToDotNotation(u32 titleVersion, u8 *outMajor, u8 *outMinor, u8 *outMicro, u16 *outBugfix) @@ -1637,9 +1651,9 @@ bool retrieveGameCardInfo() goto out; } - if (bswap_32(gameCardInfo.header.magic) != GAMECARD_HEADER_MAGIC) + if (__builtin_bswap32(gameCardInfo.header.magic) != GAMECARD_HEADER_MAGIC) { - uiStatusMsg("%s: invalid gamecard header magic word! (0x%08X)", __func__, bswap_32(gameCardInfo.header.magic)); + uiStatusMsg("%s: invalid gamecard header magic word! (0x%08X)", __func__, __builtin_bswap32(gameCardInfo.header.magic)); goto out; } @@ -1689,9 +1703,9 @@ bool retrieveGameCardInfo() memcpy(&header, gameCardInfo.rootHfs0Header, sizeof(hfs0_header)); - if (bswap_32(header.magic) != HFS0_MAGIC) + if (__builtin_bswap32(header.magic) != HFS0_MAGIC) { - uiStatusMsg("%s: invalid magic word in root HFS0 header! (0x%08X)", __func__, bswap_32(header.magic)); + uiStatusMsg("%s: invalid magic word in root HFS0 header! (0x%08X)", __func__, __builtin_bswap32(header.magic)); goto out; } @@ -1770,9 +1784,9 @@ bool retrieveGameCardInfo() } // Check the HFS0 magic word - if (bswap_32(header.magic) != HFS0_MAGIC) + if (__builtin_bswap32(header.magic) != HFS0_MAGIC) { - uiStatusMsg("%s: invalid magic word in %s HFS0 partition header! (0x%08X)", __func__, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i), bswap_32(header.magic)); + uiStatusMsg("%s: invalid magic word in %s HFS0 partition header! (0x%08X)", __func__, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i), __builtin_bswap32(header.magic)); goto out; } @@ -1981,10 +1995,11 @@ void truncateBrowserEntryName(char *str) if (!str || !strlen(str)) return; u32 strWidth = uiGetStrWidth(str); + u32 limit = (u32)(FB_WIDTH - (font_height * 8)); - if ((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 8))) + if ((BROWSER_ICON_DIMENSION + 16 + strWidth) >= limit) { - while((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 8))) + while((BROWSER_ICON_DIMENSION + 16 + strWidth) >= limit) { str[strlen(str) - 1] = '\0'; strWidth = uiGetStrWidth(str); @@ -2017,19 +2032,10 @@ bool getHfs0FileList(u32 partition) return false; } - if (gameCardInfo.hfs0Partitions[partition].file_cnt > FILENAME_MAX_CNT) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: HFS0 partition contains more than %u files! (%u entries)", __func__, FILENAME_MAX_CNT, gameCardInfo.hfs0Partitions[partition].file_cnt); - breaks += 2; - return false; - } - u32 i; hfs0_file_entry entry; char curName[NAME_BUF_LEN] = {'\0'}; - clearFilenameBuffer(); - freeHfs0ExeFsEntriesSizes(); hfs0ExeFsEntriesSizes = calloc(gameCardInfo.hfs0Partitions[partition].file_cnt, sizeof(browser_entry_size_info)); @@ -2040,6 +2046,13 @@ bool getHfs0FileList(u32 partition) return false; } + if (!allocateFilenameBuffer(gameCardInfo.hfs0Partitions[partition].file_cnt)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the filename buffer!", __func__); + breaks += 2; + return false; + } + for(i = 0; i < gameCardInfo.hfs0Partitions[partition].file_cnt; i++) { memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); @@ -2051,7 +2064,13 @@ bool getHfs0FileList(u32 partition) // Fix entry name length truncateBrowserEntryName(curName); - addStringToFilenameBuffer(curName); + if (!addStringToFilenameBuffer(curName)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for filename entry in filename buffer!", __func__); + breaks += 2; + freeHfs0ExeFsEntriesSizes(); + return false; + } // Save entry size hfs0ExeFsEntriesSizes[i].size = entry.file_size; @@ -2372,6 +2391,165 @@ void removeConsoleDataFromTicket(title_rights_ctx *rights_info) rights_info->tik_data.account_id = 0; } +bool listDesiredNcaType(NcmContentInfo *titleContentInfos, u32 titleContentInfoCnt, u8 type, int desiredIdOffset, u32 *outIndex, u32 *outCount) +{ + if (!titleContentInfos || !titleContentInfoCnt || type > NcmContentType_DeltaFragment || !outIndex || !outCount) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters.", __func__); + return false; + } + + int idx = -1; + u32 i, cnt = 0; + bool success = false; + u32 *indexes = NULL, *tmpIndexes = NULL; + + int cur_breaks, initial_breaks = breaks; + u32 selectedContent = 0; + u64 keysDown = 0, keysHeld = 0; + + char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}; + + for(i = 0; i < titleContentInfoCnt; i++) + { + if (titleContentInfos[i].content_type == type) + { + if (desiredIdOffset >= 0) + { + if (titleContentInfos[i].id_offset == (u8)desiredIdOffset) + { + // Save the index for the content with the desired ID offset + idx = (int)i; + } + } else { + tmpIndexes = realloc(indexes, (cnt + 1) * sizeof(u32)); + if (!tmpIndexes) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to reallocate indexes buffer!", __func__); + goto out; + } + + indexes = tmpIndexes; + tmpIndexes = NULL; + + indexes[cnt] = i; + } + + cnt++; + } + } + + if (!cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find any %s NCAs!", __func__, getContentType(type)); + goto out; + } + + if (desiredIdOffset >= 0) + { + if (idx < 0) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find %s NCA with ID offset %d!", __func__, getContentType(type), desiredIdOffset); + goto out; + } + } else { + // If only a single NCA with the desired content type was detected, save its index right away + if (cnt == 1) idx = (int)indexes[0]; + } + + // Return immediately if necessary + if (idx >= 0) + { + success = true; + goto out; + } + + // Display a selection list + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Select one of the available %s NCAs from the list below:", getContentType(type)); + breaks += 2; + + while(true) + { + cur_breaks = breaks; + + uiFill(0, 8 + (cur_breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (cur_breaks * LINE_HEIGHT)), BG_COLOR_RGB); + + for(i = 0; i < cnt; i++) + { + u32 xpos = STRING_X_POS; + u32 ypos = (8 + (cur_breaks * LINE_HEIGHT) + (i * (font_height + 12)) + 6); + + if (i == selectedContent) + { + highlight = true; + uiFill(0, ypos - 6, FB_WIDTH, font_height + 12, HIGHLIGHT_BG_COLOR_RGB); + } + + uiDrawIcon((highlight ? fileHighlightIconBuf : fileNormalIconBuf), BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos); + xpos += (BROWSER_ICON_DIMENSION + 8); + + convertDataToHexString(titleContentInfos[indexes[i]].content_id.c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s.nca (ID Offset: %u)", nca_id, titleContentInfos[indexes[i]].id_offset); + + if (highlight) + { + uiDrawString(xpos, ypos, HIGHLIGHT_FONT_COLOR_RGB, strbuf); + } else { + uiDrawString(xpos, ypos, FONT_COLOR_RGB, strbuf); + } + + if (i == selectedContent) highlight = false; + } + + while(true) + { + uiUpdateStatusMsg(); + uiRefreshDisplay(); + + hidScanInput(); + + keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); + keysHeld = hidKeysAllHeld(CONTROLLER_P1_AUTO); + + if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH))) break; + } + + if (keysDown & KEY_A) + { + idx = (int)indexes[selectedContent]; + break; + } + + if ((keysDown & KEY_DUP) || (keysDown & KEY_LSTICK_UP) || (keysHeld & KEY_RSTICK_UP)) + { + if (selectedContent > 0) selectedContent--; + } + + if ((keysDown & KEY_DDOWN) || (keysDown & KEY_LSTICK_DOWN) || (keysHeld & KEY_RSTICK_DOWN)) + { + if (selectedContent < (cnt - 1)) selectedContent++; + } + } + + breaks = initial_breaks; + uiFill(0, 8 + (breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (breaks * LINE_HEIGHT)), BG_COLOR_RGB); + uiRefreshDisplay(); + + success = true; + +out: + if (indexes) free(indexes); + + if (success) + { + *outIndex = (u32)idx; + *outCount = cnt; + } + + return success; +} + bool readNcaExeFsSection(u32 titleIndex, bool usePatch) { u32 i = 0; @@ -2384,6 +2562,8 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) NcmContentInfo *titleContentInfos = NULL; u32 titleContentInfoCnt = 0; + u32 contentIndex = 0, desiredNcaTypeCount = 0; + NcmContentId ncaId; char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; @@ -2393,9 +2573,12 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) u8 ncaHeader[NCA_FULL_HEADER_LENGTH] = {0}; nca_header_t dec_nca_header; + title_rights_ctx rights_info; + memset(&rights_info, 0, sizeof(title_rights_ctx)); + u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE]; - bool success = false, foundProgram = false; + bool success = false; if ((!usePatch && !baseAppEntries) || (usePatch && !patchEntries)) { @@ -2451,26 +2634,17 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) goto out; } - for(i = 0; i < titleContentInfoCnt; i++) - { - if (titleContentInfos[i].content_type == NcmContentType_Program) - { - memcpy(&ncaId, &(titleContentInfos[i].content_id), sizeof(NcmContentId)); - convertDataToHexString(titleContentInfos[i].content_id.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); - foundProgram = true; - break; - } - } + if (!listDesiredNcaType(titleContentInfos, titleContentInfoCnt, NcmContentType_Program, -1, &contentIndex, &desiredNcaTypeCount)) goto out; - if (!foundProgram) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find Program NCA!", __func__); - goto out; - } + memcpy(&ncaId, &(titleContentInfos[contentIndex].content_id), sizeof(NcmContentId)); + convertDataToHexString(titleContentInfos[contentIndex].content_id.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Found Program NCA: \"%s.nca\".", ncaIdStr); - uiRefreshDisplay(); - breaks += 2; + if (desiredNcaTypeCount == 1) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Found Program NCA: \"%s.nca\".", ncaIdStr); + uiRefreshDisplay(); + breaks += 2; + } /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving ExeFS entries..."); uiRefreshDisplay(); @@ -2491,9 +2665,9 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) } // Decrypt the NCA header - if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, NULL, decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard || (curStorageId == NcmStorageId_GameCard && usePatch)))) goto out; + if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, &rights_info, decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard || (curStorageId == NcmStorageId_GameCard && usePatch)))) goto out; - if (curStorageId == NcmStorageId_GameCard && !usePatch) + if (curStorageId == NcmStorageId_GameCard) { bool has_rights_id = false; @@ -2508,14 +2682,24 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) if (has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in Program NCA header not empty!", __func__); - goto out; + if (usePatch) + { + // Retrieve the ticket from the HFS0 partition in the gamecard + if (!retrieveTitleKeyFromGameCardTicket(&rights_info, decrypted_nca_keys)) goto out; + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in Program NCA header not empty!", __func__); + goto out; + } } } // Read file entries from the ExeFS section success = parseExeFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); - if (success) exeFsContext.storageId = curStorageId; + if (success) + { + exeFsContext.storageId = curStorageId; + exeFsContext.idOffset = titleContentInfos[contentIndex].id_offset; + } out: if (!success) @@ -2529,7 +2713,7 @@ out: return success; } -bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) +bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, int desiredIdOffset) { u32 i = 0; Result result; @@ -2541,6 +2725,8 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) NcmContentInfo *titleContentInfos = NULL; u32 titleContentInfoCnt = 0; + u32 contentIndex = 0, desiredNcaTypeCount = 0; + NcmContentId ncaId; char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; @@ -2550,9 +2736,12 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) u8 ncaHeader[NCA_FULL_HEADER_LENGTH] = {0}; nca_header_t dec_nca_header; + title_rights_ctx rights_info; + memset(&rights_info, 0, sizeof(title_rights_ctx)); + u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE]; - bool success = false, foundNca = false; + bool success = false; if (curRomFsType != ROMFS_TYPE_APP && curRomFsType != ROMFS_TYPE_PATCH && curRomFsType != ROMFS_TYPE_ADDON) { @@ -2614,26 +2803,17 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) goto out; } - for(i = 0; i < titleContentInfoCnt; i++) - { - if (((curRomFsType == ROMFS_TYPE_APP || curRomFsType == ROMFS_TYPE_PATCH) && titleContentInfos[i].content_type == NcmContentType_Program) || (curRomFsType == ROMFS_TYPE_ADDON && titleContentInfos[i].content_type == NcmContentType_Data)) - { - memcpy(&ncaId, &(titleContentInfos[i].content_id), sizeof(NcmContentId)); - convertDataToHexString(titleContentInfos[i].content_id.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); - foundNca = true; - break; - } - } + if (!listDesiredNcaType(titleContentInfos, titleContentInfoCnt, (curRomFsType == ROMFS_TYPE_ADDON ? NcmContentType_Data : NcmContentType_Program), desiredIdOffset, &contentIndex, &desiredNcaTypeCount)) goto out; - if (!foundNca) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find %s NCA!", __func__, (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); - goto out; - } + memcpy(&ncaId, &(titleContentInfos[contentIndex].content_id), sizeof(NcmContentId)); + convertDataToHexString(titleContentInfos[contentIndex].content_id.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Found %s NCA: \"%s.nca\".", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program"), ncaIdStr); - uiRefreshDisplay(); - breaks += 2; + if (desiredNcaTypeCount == 1) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Found %s NCA: \"%s.nca\".", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program"), ncaIdStr); + uiRefreshDisplay(); + breaks += 2; + } /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving RomFS entry tables..."); uiRefreshDisplay(); @@ -2654,9 +2834,9 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) } // Decrypt the NCA header - if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, NULL, decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard || (curStorageId == NcmStorageId_GameCard && curRomFsType == ROMFS_TYPE_PATCH)))) goto out; + if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, &rights_info, decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard || (curStorageId == NcmStorageId_GameCard && curRomFsType == ROMFS_TYPE_PATCH)))) goto out; - if (curStorageId == NcmStorageId_GameCard && curRomFsType != ROMFS_TYPE_PATCH) + if (curStorageId == NcmStorageId_GameCard) { bool has_rights_id = false; @@ -2671,8 +2851,14 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in %s NCA header not empty!", __func__, (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); - goto out; + if (curRomFsType == ROMFS_TYPE_PATCH) + { + // Retrieve the ticket from the HFS0 partition in the gamecard + if (!retrieveTitleKeyFromGameCardTicket(&rights_info, decrypted_nca_keys)) goto out; + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in %s NCA header not empty!", __func__, (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); + goto out; + } } } @@ -2680,7 +2866,11 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) { // Read directory and file tables from the RomFS section success = parseRomFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); - if (success) romFsContext.storageId = curStorageId; + if (success) + { + romFsContext.storageId = curStorageId; + romFsContext.idOffset = titleContentInfos[contentIndex].id_offset; + } } else { // Look for the base application title index u32 appIndex; @@ -2694,18 +2884,22 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) } } - if (i == titleAppCount) + if (i >= titleAppCount) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find base application title index for the selected update!", __func__); goto out; } // Read directory and file tables from the RomFS section in the Program NCA from the base application - if (!readNcaRomFsSection(appIndex, ROMFS_TYPE_APP)) goto out; + if (!readNcaRomFsSection(appIndex, ROMFS_TYPE_APP, (int)titleContentInfos[contentIndex].id_offset)) goto out; // Read BKTR entry data in the Program NCA from the update success = parseBktrEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); - if (success) bktrContext.storageId = curStorageId; + if (success) + { + bktrContext.storageId = curStorageId; + bktrContext.idOffset = titleContentInfos[contentIndex].id_offset; + } } out: @@ -2728,17 +2922,9 @@ bool getExeFsFileList() return false; } - if (exeFsContext.exefs_header.file_cnt > FILENAME_MAX_CNT) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: ExeFS section contains more than %u entries! (%u entries)", __func__, FILENAME_MAX_CNT, exeFsContext.exefs_header.file_cnt); - return false; - } - u32 i; char curName[NAME_BUF_LEN] = {'\0'}; - clearFilenameBuffer(); - freeHfs0ExeFsEntriesSizes(); hfs0ExeFsEntriesSizes = calloc(exeFsContext.exefs_header.file_cnt, sizeof(browser_entry_size_info)); @@ -2748,6 +2934,12 @@ bool getExeFsFileList() return false; } + if (!allocateFilenameBuffer(exeFsContext.exefs_header.file_cnt)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the filename buffer!", __func__); + return false; + } + for(i = 0; i < exeFsContext.exefs_header.file_cnt; i++) { char *cur_filename = (exeFsContext.exefs_str_table + exeFsContext.exefs_entries[i].filename_offset); @@ -2757,7 +2949,12 @@ bool getExeFsFileList() // Fix entry name length truncateBrowserEntryName(curName); - addStringToFilenameBuffer(curName); + if (!addStringToFilenameBuffer(curName)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for filename entry in filename buffer!", __func__); + freeHfs0ExeFsEntriesSizes(); + return false; + } // Save entry size hfs0ExeFsEntriesSizes[i].size = exeFsContext.exefs_entries[i].file_size; @@ -2888,17 +3085,9 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) char curName[NAME_BUF_LEN] = {'\0'}; - clearFilenameBuffer(); - // Silently return true if we're dealing with an empty directory if (!totalEntryCnt) goto out; - if (totalEntryCnt > FILENAME_MAX_CNT) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: current RomFS dir contains more than %u entries! (%u entries)", __func__, FILENAME_MAX_CNT, totalEntryCnt); - return false; - } - // Allocate memory for our entries romFsBrowserEntries = calloc(totalEntryCnt, sizeof(romfs_browser_entry)); if (!romFsBrowserEntries) @@ -2907,10 +3096,23 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) return false; } + if (!allocateFilenameBuffer(totalEntryCnt)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the filename buffer!", __func__); + freeRomFsBrowserEntries(); + return false; + } + + if (!addStringToFilenameBuffer("..")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for parent dir entry in filename buffer!", __func__); + freeRomFsBrowserEntries(); + return false; + } + // Add parent directory entry ("..") romFsBrowserEntries[0].type = ROMFS_ENTRY_DIR; romFsBrowserEntries[0].offset = romFsParentDir; - addStringToFilenameBuffer(".."); // First add the directory entries if ((!romFsParentDir && dirEntryCnt > 1) || (romFsParentDir && dirEntryCnt > 0)) @@ -2932,7 +3134,12 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) // Fix entry name length truncateBrowserEntryName(curName); - addStringToFilenameBuffer(curName); + if (!addStringToFilenameBuffer(curName)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for filename entry in filename buffer!", __func__); + freeRomFsBrowserEntries(); + return false; + } i++; } @@ -2963,7 +3170,12 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) // Fix entry name length truncateBrowserEntryName(curName); - addStringToFilenameBuffer(curName); + if (!addStringToFilenameBuffer(curName)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for filename entry in filename buffer!", __func__); + freeRomFsBrowserEntries(); + return false; + } i++; } @@ -2985,11 +3197,11 @@ char *generateGameCardDumpName(bool useBrackets) u32 i, j; - char tmp[NAME_BUF_LEN / 4] = {'\0'}; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; char *fullname = NULL; char *fullnameTmp = NULL; - size_t strsize = (NAME_BUF_LEN / 2); + size_t strsize = NAME_BUF_LEN; fullname = calloc(strsize + 1, sizeof(char)); if (!fullname) return NULL; @@ -3046,7 +3258,7 @@ char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex, bool if ((selectedNspDumpType == DUMP_APP_NSP && (!titleAppCount || !baseAppEntries || titleIndex >= titleAppCount)) || (selectedNspDumpType == DUMP_PATCH_NSP && (!titlePatchCount || !patchEntries || titleIndex >= titlePatchCount)) || (selectedNspDumpType == DUMP_ADDON_NSP && (!titleAddOnCount || !addOnEntries || titleIndex >= titleAddOnCount))) return NULL; u32 i; - size_t strsize = (NAME_BUF_LEN / 2); + size_t strsize = NAME_BUF_LEN; patch_addon_ctx_t *ptr = NULL; @@ -3325,9 +3537,12 @@ void generateOrphanPatchOrAddOnList() qsort(orphanEntries, orphanEntriesCnt, sizeof(orphan_patch_addon_entry), orphanEntryCmp); out: - clearFilenameBuffer(); + if (!allocateFilenameBuffer(orphanEntriesCnt)) return; - for(i = 0; i < orphanEntriesCnt; i++) addStringToFilenameBuffer(orphanEntries[i].orphanListStr); + for(i = 0; i < orphanEntriesCnt; i++) + { + if (!addStringToFilenameBuffer(orphanEntries[i].orphanListStr)) return; + } } bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn) @@ -3435,7 +3650,7 @@ void waitForButtonPress() hidScanInput(); - u64 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + u64 keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); if (keysDown && !((keysDown & KEY_TOUCH) || (keysDown & KEY_LSTICK_LEFT) || (keysDown & KEY_LSTICK_RIGHT) || (keysDown & KEY_LSTICK_UP) || (keysDown & KEY_LSTICK_DOWN) || \ (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_RSTICK_DOWN))) break; @@ -3503,7 +3718,7 @@ bool cancelProcessCheck(progress_ctx_t *progressCtx) hidScanInput(); - progressCtx->cancelBtnState = (hidKeysHeld(CONTROLLER_P1_AUTO) & KEY_B); + progressCtx->cancelBtnState = (hidKeysAllHeld(CONTROLLER_P1_AUTO) & KEY_B); if (progressCtx->cancelBtnState && progressCtx->cancelBtnState != progressCtx->cancelBtnStatePrev) { @@ -3576,7 +3791,7 @@ bool yesNoPrompt(const char *message) hidScanInput(); - u64 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + u64 keysDown = hidKeysAllDown(CONTROLLER_P1_AUTO); if (keysDown & KEY_A) { @@ -3672,7 +3887,7 @@ bool checkIfDumpedNspContainsConsoleData(const char *nspPath) read_bytes = fread(&nspHeader, 1, sizeof(pfs0_header), nspFile); - if (read_bytes != sizeof(pfs0_header) || bswap_32(nspHeader.magic) != PFS0_MAGIC || nspSize < (sizeof(pfs0_header) + (sizeof(pfs0_file_entry) * (u64)nspHeader.file_cnt) + (u64)nspHeader.str_table_size)) + if (read_bytes != sizeof(pfs0_header) || __builtin_bswap32(nspHeader.magic) != PFS0_MAGIC || nspSize < (sizeof(pfs0_header) + (sizeof(pfs0_file_entry) * (u64)nspHeader.file_cnt) + (u64)nspHeader.str_table_size)) { fclose(nspFile); return false; @@ -3753,20 +3968,29 @@ void removeDirectoryWithVerbose(const char *path, const char *msg) { if (!path || !strlen(path) || !msg || !strlen(msg)) return; + int initial_breaks = breaks; + breaks += 2; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, msg); + if (yesNoPrompt("Do you wish to delete the data dumped up to this point? This may take a while.")) + { + breaks = initial_breaks; + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); + + breaks += 2; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, msg); + uiRefreshDisplay(); + + fsdevDeleteDirectoryRecursively(path); + } + + breaks = initial_breaks; + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); uiRefreshDisplay(); - - fsdevDeleteDirectoryRecursively(path); - - uiFill(0, (breaks * LINE_HEIGHT) + 8, FB_WIDTH, (font_height + (font_height / 2)), BG_COLOR_RGB); - uiRefreshDisplay(); - - breaks -= 2; } -static bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u64 gc_tid, u32 crc) +static bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) { if (!doc || !cur) return false; @@ -3780,12 +4004,12 @@ static bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u64 gc_tid, u32 crc while(node) { - if ((!xmlStrcmp(node->name, (const xmlChar*)nswReleasesChildrenImgCrc))) + if ((!xmlStrcmp(node->name, (const xmlChar*)NSWDB_XML_CHILD_IMGCRC))) { key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (key) xmlCrc = strtoul((const char*)key, NULL, 16); } else - if ((!xmlStrcmp(node->name, (const xmlChar*)nswReleasesChildrenReleaseName))) + if ((!xmlStrcmp(node->name, (const xmlChar*)NSWDB_XML_CHILD_RELEASENAME))) { key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (key) snprintf(xmlReleaseName, MAX_CHARACTERS(xmlReleaseName), "%s", (const char*)key); @@ -3842,24 +4066,24 @@ void gameCardDumpNSWDBCheck(u32 crc) xmlDocPtr doc = NULL; bool found = false; - doc = xmlParseFile(nswReleasesXmlPath); + doc = xmlParseFile(NSWDB_XML_PATH); if (!doc) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open and/or parse \"%s\"!", __func__, nswReleasesXmlPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open and/or parse \"%s\"!", __func__, NSWDB_XML_PATH); return; } for(i = 0; i < titleAppCount; i++) { - snprintf(strbuf, MAX_CHARACTERS(strbuf), "//%s/%s[.//%s[contains(.,'%016lX')]]", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, baseAppEntries[i].titleId); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "//%s/%s[.//%s[contains(.,'%016lX')]]", NSWDB_XML_ROOT, NSWDB_XML_CHILD, NSWDB_XML_CHILD_TITLEID, baseAppEntries[i].titleId); xmlXPathObjectPtr nodeSet = getXPathNodeSet(doc, strbuf); if (!nodeSet) continue; - for(j = 0; j < nodeSet->nodesetval->nodeNr; j++) + for(j = 0; j < (u32)nodeSet->nodesetval->nodeNr; j++) { xmlNodePtr node = nodeSet->nodesetval->nodeTab[j]->xmlChildrenNode; - found = parseNSWDBRelease(doc, node, baseAppEntries[i].titleId, crc); + found = parseNSWDBRelease(doc, node, crc); if (found) break; } @@ -3875,15 +4099,25 @@ void gameCardDumpNSWDBCheck(u32 crc) static Result networkInit() { + if (initNet) return 0; + Result result = socketInitializeDefault(); - if (R_SUCCEEDED(result)) curl_global_init(CURL_GLOBAL_ALL); + if (R_SUCCEEDED(result)) + { + curl_global_init(CURL_GLOBAL_ALL); + initNet = true; + } + return result; } -static void networkDeinit() +static void networkExit() { + if (!initNet) return; + curl_global_cleanup(); socketExit(); + initNet = false; } static size_t writeCurlFile(char *buffer, size_t size, size_t number_of_items, void *input_stream) @@ -3935,7 +4169,7 @@ static bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent); + curl_easy_setopt(curl, CURLOPT_USERAGENT, HTTP_USER_AGENT); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_NOBODY, 0L); @@ -3986,7 +4220,7 @@ void noIntroDumpCheck(bool isDigital, u32 crc) // f = "cart" (XCI) or "dlc" (NSP) // c = search by code (Title ID or serial) // crc = search by CRC32 checksum - snprintf(noIntroUrl, MAX_CHARACTERS(noIntroUrl), "%s?f=%s&crc=%08X", noIntroDatQuickCheckUrl, (isDigital ? "dlc" : "cart"), crc); + snprintf(noIntroUrl, MAX_CHARACTERS(noIntroUrl), "%s?f=%s&crc=%08X", NOINTRO_DOM_CHECK_URL, (isDigital ? "dlc" : "cart"), crc); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Performing CRC32 checksum lookup against No-Intro, please wait..."); uiRefreshDisplay(); @@ -4025,7 +4259,7 @@ out: if (curl) curl_easy_cleanup(curl); - if (R_SUCCEEDED(result)) networkDeinit(); + if (R_SUCCEEDED(result)) networkExit(); } void updateNSWDBXml() @@ -4042,6 +4276,9 @@ void updateNSWDBXml() goto out; } + char xmlPath[256] = {'\0'}; + snprintf(xmlPath, MAX_CHARACTERS(xmlPath), "%s.tmp", NSWDB_XML_PATH); + curl = curl_easy_init(); if (!curl) { @@ -4049,41 +4286,40 @@ void updateNSWDBXml() goto out; } - nswdbXml = fopen(nswReleasesXmlTmpPath, "wb"); + nswdbXml = fopen(xmlPath, "wb"); if (!nswdbXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, nswReleasesXmlTmpPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, NSWDB_XML_URL); goto out; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Downloading XML database from \"%s\", please wait...", nswReleasesXmlUrl); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Downloading XML database from \"%s\", please wait...", NSWDB_XML_URL); breaks++; - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks += 2; - } - + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; - success = performCurlRequest(curl, nswReleasesXmlUrl, nswdbXml, false, true); + changeHomeButtonBlockStatus(true); + + success = performCurlRequest(curl, NSWDB_XML_URL, nswdbXml, false, true); + + changeHomeButtonBlockStatus(false); out: if (nswdbXml) fclose(nswdbXml); if (success) { - unlink(nswReleasesXmlPath); - rename(nswReleasesXmlTmpPath, nswReleasesXmlPath); + remove(NSWDB_XML_PATH); + rename(xmlPath, NSWDB_XML_PATH); } else { - unlink(nswReleasesXmlTmpPath); + remove(xmlPath); } if (curl) curl_easy_cleanup(curl); - if (R_SUCCEEDED(result)) networkDeinit(); + if (R_SUCCEEDED(result)) networkExit(); breaks += 2; } @@ -4198,63 +4434,78 @@ static int versionNumCmp(char *ver1, char *ver2) return res; } -static const char *retrieveJsonObjStrMember(struct json_object *jobj, char *memberName) +static struct json_object *retrieveJsonObjMemberByNameAndType(struct json_object *jobj, char *memberName, json_type memberType) { if (!jobj || !memberName || !strlen(memberName)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve string member from JSON object!", __func__); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve member by name and type from JSON object!", __func__); return NULL; } struct json_object *memberObj = NULL; - const char *memberObjStr = NULL; + json_type memberObjType; if (!json_object_object_get_ex(jobj, memberName, &memberObj)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve object \"%s\" from JSON response!", __func__, memberName); - return memberObjStr; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve member \"%s\" from JSON object!", __func__, memberName); + return NULL; } - if (json_object_get_type(memberObj) != json_type_string) + memberObjType = json_object_get_type(memberObj); + if (memberObjType != memberType) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid type for object \"%s\" in JSON response! (expected \"string\")", __func__, memberName); - return memberObjStr; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid type for member \"%s\" in JSON object! (got \"%s\", expected \"%s\")", __func__, memberName, json_type_to_name(memberObjType), json_type_to_name(memberType)); + return NULL; } - memberObjStr = json_object_get_string(memberObj); + return memberObj; +} + +static const char *retrieveJsonObjStrMemberContentsByName(struct json_object *jobj, char *memberName) +{ + if (!jobj || !memberName || !strlen(memberName)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve string member contents by name from JSON object!", __func__); + return NULL; + } + + struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_string); + if (!memberObj) return NULL; + + const char *memberObjStr = json_object_get_string(memberObj); if (!memberObjStr || !strlen(memberObjStr)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: object \"%s\" from JSON response is empty!", __func__, memberName); - memberObjStr = NULL; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: string member \"%s\" from JSON object is empty!", __func__, memberName); + return NULL; } return memberObjStr; } -static struct json_object *retrieveJsonObjArrayElementWithIndex(struct json_object *jobj, char *memberName, size_t idx) +static struct json_object *retrieveJsonObjArrayMemberByName(struct json_object *jobj, char *memberName, size_t *outputArrayLength) { - if (!jobj || !memberName || !strlen(memberName)) + if (!jobj || !memberName || !strlen(memberName) || !outputArrayLength) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve indexed element in array member from JSON object!", __func__); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve array member by name from JSON object!", __func__); return NULL; } - struct json_object *memberObj = NULL, *memberObjArrayElement = NULL; + struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_array); + if (memberObj) *outputArrayLength = json_object_array_length(memberObj); - if (!json_object_object_get_ex(jobj, memberName, &memberObj)) + return memberObj; +} + +static struct json_object *retrieveJsonObjArrayElementByIndex(struct json_object *jobj, size_t idx) +{ + if (!jobj || json_object_get_type(jobj) != json_type_array) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve object \"%s\" from JSON response!", __func__, memberName); - return memberObjArrayElement; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve element by index from JSON array object!", __func__); + return NULL; } - if (json_object_get_type(memberObj) != json_type_array) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid type for object \"%s\" in JSON response! (expected \"array\")", __func__, memberName); - return memberObjArrayElement; - } - - memberObjArrayElement = json_object_array_get_idx(memberObj, idx); - if (!memberObjArrayElement) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse object at index %lu from \"%s\" array in JSON response!", __func__); + struct json_object *memberObjArrayElement = json_object_array_get_idx(jobj, idx); + if (!memberObjArrayElement) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve element at index %lu from JSON array object!", __func__, idx); return memberObjArrayElement; } @@ -4275,11 +4526,12 @@ bool updateApplication() char releaseTag[32] = {'\0'}; bool success = false; + size_t i, assetsCnt = 0; struct json_object *jobj = NULL, *assets = NULL; - const char *nameObjStr = NULL, *dlUrlObjStr = NULL; + const char *releaseNameObjStr = NULL, *dlUrlObjStr = NULL; char nroPath[NAME_BUF_LEN] = {'\0'}; - snprintf(nroPath, MAX_CHARACTERS(nroPath), "%s.tmp", (strlen(appLaunchPath) ? appLaunchPath : nxDumpToolPath)); + snprintf(nroPath, MAX_CHARACTERS(nroPath), "%s.tmp", (appLaunchPath ? appLaunchPath : NRO_PATH)); result = networkInit(); if (R_FAILED(result)) @@ -4295,14 +4547,14 @@ bool updateApplication() goto out; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Requesting latest release information from \"%s\"...", githubReleasesApiUrl); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Requesting latest release information from \"%s\"...", GITHUB_API_URL); breaks++; uiRefreshDisplay(); - if (!performCurlRequest(curl, githubReleasesApiUrl, NULL, true, false)) goto out; + if (!performCurlRequest(curl, GITHUB_API_URL, NULL, true, false)) goto out; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Parsing response JSON data...", githubReleasesApiUrl); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Parsing response JSON data..."); breaks++; uiRefreshDisplay(); @@ -4314,10 +4566,10 @@ bool updateApplication() goto out; } - nameObjStr = retrieveJsonObjStrMember(jobj, "name"); - if (!nameObjStr) goto out; + releaseNameObjStr = retrieveJsonObjStrMemberContentsByName(jobj, GITHUB_API_JSON_RELEASE_NAME); + if (!releaseNameObjStr) goto out; - snprintf(releaseTag, MAX_CHARACTERS(releaseTag), nameObjStr); + snprintf(releaseTag, MAX_CHARACTERS(releaseTag), releaseNameObjStr); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Latest release: %s.", releaseTag); breaks++; @@ -4345,7 +4597,7 @@ bool updateApplication() { // Remove the prompt from the screen breaks = cur_breaks; - uiFill(0, 8 + STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - (8 + STRING_Y_POS(breaks)), BG_COLOR_RGB); + uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); uiRefreshDisplay(); } else { breaks -= 2; @@ -4353,25 +4605,44 @@ bool updateApplication() } } - assets = retrieveJsonObjArrayElementWithIndex(jobj, "assets", 0); + assets = retrieveJsonObjArrayMemberByName(jobj, GITHUB_API_JSON_ASSETS, &assetsCnt); if (!assets) goto out; - dlUrlObjStr = retrieveJsonObjStrMember(assets, "browser_download_url"); - if (!dlUrlObjStr) goto out; + // Cycle through the assets to find the right download URL + for(i = 0; i < assetsCnt; i++) + { + struct json_object *assetElement = retrieveJsonObjArrayElementByIndex(assets, i); + if (!assetElement) break; + + const char *assetName = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_NAME); + if (!assetName) break; + + if (!strncmp(assetName, NRO_NAME, strlen(assetName))) + { + // Found it + dlUrlObjStr = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_DL_URL); + break; + } + } + + if (!dlUrlObjStr) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate NRO download URL!", __func__); + goto out; + } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Download URL: \"%s\".", dlUrlObjStr); breaks++; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Please wait..."); - breaks += 2; - - if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem."); - breaks += 2; - } + breaks++; + appletModeOperationWarning(); uiRefreshDisplay(); + breaks++; + + changeHomeButtonBlockStatus(true); nxDumpToolNro = fopen(nroPath, "wb"); if (!nxDumpToolNro) @@ -4391,15 +4662,18 @@ bool updateApplication() out: if (nxDumpToolNro) fclose(nxDumpToolNro); - if (success) + if (strlen(nroPath)) { - snprintf(strbuf, MAX_CHARACTERS(strbuf), nroPath); - nroPath[strlen(nroPath) - 4] = '\0'; - - unlink(nroPath); - rename(strbuf, nroPath); - } else { - unlink(nroPath); + if (success) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), nroPath); + nroPath[strlen(nroPath) - 4] = '\0'; + + remove(nroPath); + rename(strbuf, nroPath); + } else { + remove(nroPath); + } } if (jobj) json_object_put(jobj); @@ -4412,9 +4686,11 @@ out: if (curl) curl_easy_cleanup(curl); - if (R_SUCCEEDED(result)) networkDeinit(); + if (R_SUCCEEDED(result)) networkExit(); breaks += 2; + changeHomeButtonBlockStatus(false); + return success; } diff --git a/source/util.h b/source/util.h index 4e7e3d2..61b6059 100644 --- a/source/util.h +++ b/source/util.h @@ -6,28 +6,51 @@ #include #include "nca.h" -#define APP_BASE_PATH "sdmc:/switch/" -#define NXDUMPTOOL_BASE_PATH APP_BASE_PATH "nxdumptool/" -#define XCI_DUMP_PATH NXDUMPTOOL_BASE_PATH "XCI/" -#define NSP_DUMP_PATH NXDUMPTOOL_BASE_PATH "NSP/" -#define HFS0_DUMP_PATH NXDUMPTOOL_BASE_PATH "HFS0/" -#define EXEFS_DUMP_PATH NXDUMPTOOL_BASE_PATH "ExeFS/" -#define ROMFS_DUMP_PATH NXDUMPTOOL_BASE_PATH "RomFS/" -#define CERT_DUMP_PATH NXDUMPTOOL_BASE_PATH "Certificate/" +#define HBLOADER_BASE_PATH "sdmc:/switch/" +#define APP_BASE_PATH HBLOADER_BASE_PATH APP_TITLE "/" +#define XCI_DUMP_PATH APP_BASE_PATH "XCI/" +#define NSP_DUMP_PATH APP_BASE_PATH "NSP/" +#define HFS0_DUMP_PATH APP_BASE_PATH "HFS0/" +#define EXEFS_DUMP_PATH APP_BASE_PATH "ExeFS/" +#define ROMFS_DUMP_PATH APP_BASE_PATH "RomFS/" +#define CERT_DUMP_PATH APP_BASE_PATH "Certificate/" #define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/" -#define TICKET_PATH NXDUMPTOOL_BASE_PATH "Ticket/" +#define TICKET_PATH APP_BASE_PATH "Ticket/" -#define KEYS_FILE_PATH APP_BASE_PATH "prod.keys" +#define CONFIG_PATH APP_BASE_PATH "config.bin" +#define NRO_NAME APP_TITLE ".nro" +#define NRO_PATH APP_BASE_PATH NRO_NAME +#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml" +#define KEYS_FILE_PATH HBLOADER_BASE_PATH "prod.keys" #define CFW_PATH_ATMOSPHERE "sdmc:/atmosphere/contents/" #define CFW_PATH_SXOS "sdmc:/sxos/titles/" #define CFW_PATH_REINX "sdmc:/ReiNX/titles/" +#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)" + +#define GITHUB_API_URL "https://api.github.com/repos/DarkMatterCore/nxdumptool/releases/latest" +#define GITHUB_API_JSON_RELEASE_NAME "name" +#define GITHUB_API_JSON_ASSETS "assets" +#define GITHUB_API_JSON_ASSETS_NAME "name" +#define GITHUB_API_JSON_ASSETS_DL_URL "browser_download_url" + +#define NOINTRO_DOM_CHECK_URL "https://datomatic.no-intro.org/qchknsw.php" + +#define NSWDB_XML_URL "http://nswdb.com/xml.php" +#define NSWDB_XML_ROOT "releases" +#define NSWDB_XML_CHILD "release" +#define NSWDB_XML_CHILD_TITLEID "titleid" +#define NSWDB_XML_CHILD_IMGCRC "imgcrc" +#define NSWDB_XML_CHILD_RELEASENAME "releasename" + +#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM" + #define KiB (1024.0) #define MiB (1024.0 * KiB) #define GiB (1024.0 * MiB) -#define NAME_BUF_LEN 4096 +#define NAME_BUF_LEN 2048 #define DUMP_BUFFER_SIZE (u64)0x400000 // 4 MiB (4194304 bytes) @@ -40,10 +63,6 @@ #define APPLICATION_PATCH_BITMASK (u64)0x800 #define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000 -#define FILENAME_LENGTH 512 -#define FILENAME_MAX_CNT 20000 -#define FILENAME_BUFFER_SIZE (FILENAME_LENGTH * FILENAME_MAX_CNT) // 10000 KiB - #define NACP_APPNAME_LEN 0x200 #define NACP_AUTHOR_LEN 0x100 #define VERSION_STR_LEN 0x40 @@ -82,7 +101,6 @@ #define NACP_ICON_SQUARE_DIMENSION 256 #define NACP_ICON_DOWNSCALED 96 -#define bswap_32(a) ((((a) << 24) & 0xff000000) | (((a) << 8) & 0xff0000) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff)) #define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary #define ORPHAN_ENTRY_TYPE_PATCH 1 @@ -159,7 +177,7 @@ typedef struct { u64 updateTitleId; u32 updateVersion; char updateVersionStr[64]; -} PACKED gamecard_ctx_t; +} gamecard_ctx_t; typedef struct { u64 titleId; @@ -173,7 +191,7 @@ typedef struct { u8 *icon; u64 contentSize; char contentSizeStr[32]; -} PACKED base_app_ctx_t; +} base_app_ctx_t; typedef struct { u64 titleId; @@ -183,7 +201,7 @@ typedef struct { char versionStr[VERSION_STR_LEN]; u64 contentSize; char contentSizeStr[32]; -} PACKED patch_addon_ctx_t; +} patch_addon_ctx_t; typedef struct { u32 index; @@ -191,7 +209,7 @@ typedef struct { char name[NACP_APPNAME_LEN]; char fixedName[NACP_APPNAME_LEN]; char orphanListStr[NACP_APPNAME_LEN * 2]; -} PACKED orphan_patch_addon_entry; +} orphan_patch_addon_entry; typedef struct { u32 magic; @@ -227,7 +245,7 @@ typedef struct { u32 cancelBtnStatePrev; u64 cancelStartTmr; u64 cancelEndTmr; -} PACKED progress_ctx_t; +} progress_ctx_t; typedef enum { ROMFS_TYPE_APP = 0, @@ -314,6 +332,8 @@ void delay(u8 seconds); void convertSize(u64 size, char *out, size_t outSize); void updateFreeSpace(); +void freeFilenameBuffer(void); + void initExeFsContext(); void freeExeFsContext(); @@ -326,13 +346,18 @@ void freeBktrContext(); void freeRomFsBrowserEntries(); void freeHfs0ExeFsEntriesSizes(); +u64 hidKeysAllDown(); +u64 hidKeysAllHeld(); + void consoleErrorScreen(const char *fmt, ...); bool initApplicationResources(int argc, char **argv); void deinitApplicationResources(); -void formatETAString(u64 curTime, char *out, size_t outSize); +bool appletModeCheck(); +void appletModeOperationWarning(); +void changeHomeButtonBlockStatus(bool block); -void addStringToFilenameBuffer(const char *string); +void formatETAString(u64 curTime, char *out, size_t outSize); void generateSdCardEmmcTitleList(); @@ -363,7 +388,7 @@ void removeConsoleDataFromTicket(title_rights_ctx *rights_info); bool readNcaExeFsSection(u32 titleIndex, bool usePatch); -bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType); +bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, int desiredIdOffset); bool getExeFsFileList();