diff --git a/Makefile b/Makefile index a7f5a28..7b7aeff 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules VERSION_MAJOR := 1 VERSION_MINOR := 1 -VERSION_MICRO := 7 +VERSION_MICRO := 8 APP_TITLE := nxdumptool APP_AUTHOR := MCMrARM, DarkMatterCore @@ -52,7 +52,7 @@ ROMFS := romfs #--------------------------------------------------------------------------------- ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -CFLAGS := -g -Wall -O2 -ffunction-sections \ +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}\" diff --git a/README.md b/README.md index 35063b5..1add11d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,12 @@ Main features * Manual ticket dump from installed SD/eMMC titles + optional removal of console specific data. * Compatible with multigame carts. * CRC32 checksum calculation for XCI/NSP dumps. -* Full XCI dump verification using XML database from NSWDB.COM (NSWreleases.xml). -* XML database and in-app update capabilities via libcurl. +* XCI/NSP dump verification through CRC32 checksum lookup. + * Using offline XML database from NSWDB.COM (NSWreleases.xml) (XCI only). + * Performing an online lookup against the No-Intro database. +* Bundled-in update capabilities via libcurl. + * Update to the latest version by downloading it right from GitHub. + * Update the NSWDB.COM XML database. * Precise HFS0 raw partition dumping, using the root HFS0 header from the gamecard. * HFS0 partition file data dumping + browser with manual file dump support. * Program NCA ExeFS/RomFS section & Data NCA RomFS section file data dumping + browser with manual file dump support. @@ -52,6 +56,8 @@ Thanks to * The [LZ4 project](http://www.lz4.org), for the LZ4 C-code implementation (licensed under [BSD 2-Clause](https://github.com/lz4/lz4/blob/master/lib/LICENSE)). * [AnalogMan](https://github.com/AnalogMan151) and [0Liam](https://github.com/0Liam), for their constant support and ideas. * [RattletraPM](https://github.com/RattletraPM), for the awesome icon used in the application. +* [FennecTECH](https://github.com/fennectech), for testing / breaking stuff inside the application on a regular basis. +* The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up a HTTP(S) endpoint(s) to perform CRC32 checksum lookups. * The GNOME project, from which the [high contrast icons](https://commons.wikimedia.org/wiki/GNOME_High_contrast_icons) were retrieved. * The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem. * The Comfy Boyes, for being both awesome and supportive. You know who you are. @@ -67,6 +73,60 @@ If you like my work and you'd like to support me in any way, it's not necessary, Changelog -------------- +**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. +* Refactored the HFS0/IStorage parsing code, optimizing all gamecard reads performed by the application. +* Increased dump buffer sizes to 4 MiB. +* NCA content size is now calculated and displayed for all titles. + * Content size for updates and DLCs is displayed in the title selector from the NSP menus. + * Additionally, the application now displays the size for each title in the batch dump summary screen. Plus, an *approximate* total dump size is calculated according to the selected titles. + * Please bear in mind that the displayed information does not reflect output NSP dump sizes. +* Changes to the HFS0, ExeFS and RomFS browsers: + * File sizes are now displayed for all file entries. + * Dumping a file/directory won't reset the cursor position anymore. +* Displayed lists are now lexicographically sorted. +* It is now possible to perform CRC32 checksum lookups using the No-Intro database. Big thanks to the folks from No-Intro.org! + * This new method requires a working Internet connection at runtime. + * For XCI dumps, this is merely offered as an alternative to the NSWDB.COM XML database method, without replacing it. A new option has been added to the XCI dump menu, which lets the user select the verification method they wish to use. + * For NSP dumps, on the other hand, this offers a way to actually validate dumps: + * The "CRC32 checksum calculation" feature, which was a bit pointless, has been entirely removed. The new "Verify dump using No-Intro database" option has taken its place. + * NSP dump verification is achieved by just calculating the CRC32 checksum from the output CNMT NCA and performing a lookup using the No-Intro database. This works because of the way CNMT data is handled by the application: + * The SHA-256 checksum for each NCA is always recalculated during the dump process, and the CNMT NCA is always patched afterwards. However, if no NCA modifications are performed, the CNMT NCA ends up being identical to its original counterpart, because the content records won't have changed at all. + * This lets the application verify the NSP dump by performing a CRC32 checksum lookup using the CNMT NCA data, as long as no NCA modifications take place. + * As such, this method only works with SD card / eMMC titles, as long as the "Generate ticket-less dump" option is disabled. + * This option doesn't appear in gamecard-related menus, and it's not compatible with batch dumps. +* By popular demand, an option has been added in XCI, NSP and batch dump menus to change the naming scheme used with output files to the following: + * XCI dumps: + * Single game: `TitleName [TitleID][TitleVersion]`. + * Multigame: `TitleName1 [TitleID1][TitleVersion1] + TitleName2 [TitleID2][TitleVersion2] + ... + TitleNameN [TitleIDN][TitleVersionN]`. + * NSP/Batch dumps: `TitleName [TitleID][TitleVersion][TitleType]`. + * The "Remember dumped titles" feature available in batch mode isn't affected by this new setting - batch overrides will keep using the regular naming scheme. +* Added an option to include delta fragment NCAs in output NSP dumps from installed SD/eMMC updates. It is disabled by default. +* Added a small settings menu to the ExeFS/RomFS sections with the following options: + * `Split files bigger than 4 GiB (FAT32 support)`: unlike previous versions, it is now possible to control if file splitting will take place for ExeFS/RomFS file dumps, instead of always splitting them. If this option is enabled, files bigger than 4 GiB will now be split and stored in a subdirectory with the archive bit set (like NSPs). + * `Save data to CFW directory (LayeredFS)`: enabling this option will save output data to the directory from the CFW you're running, using the LayeredFS layout. +* Added a new option to the batch mode menu to control if the batch dump process should halt on any errors. If disabled, it'll make the batch dump process wait for 5 seconds on any errors, then it will keep going. +* Free SD card space is now always displayed on every UI state. It is also displayed and updated during batch mode operations. +* ExeFS submenu is now available for updates in the orphan content list (Y button menu). +* It is now possible to exit the application from the batch dump summary screen. +* A warning is now displayed in the main menu if the application is launched using applet mode. NSP dumps from base applications and updates can fail if there's not enough heap available to hold the uncompressed `main` NSO while generating the `programinfo.xml`. +* Improved XPath query used when looking for checksum matches in the NSWDB.COM XML database. Fixes CRC32 checksum lookup for multigame cartridges. +* Empty RomFS directories are now properly handled by the RomFS browser. +* Removed a BKTR RomFS section offset check that was causing trouble while trying to perform RomFS-related operations with some updates (e.g. Luigi's Mansion 3). +* Physical IStorage reads are now performed to retrieve NCAs from gamecards, instead of using `ncmContentStorageReadContentIdFile()`. Fixes gamecard NSP/ExeFS/RomFS operations under FW versions < 4.0.0. +* Fixed unaligned IStorage reads in manual file dumps files from HFS0 partitions in gamecards. Unaligned files dumped this way should no longer contain garbage data. +* Fixed a memory leak in the XML database verification code. +* Fixed an indexing bug in the RomFS browser that could potentially cause problems when performing any action from the root directory. +* Fixed gamecard hotswapping in gamecard-related submenus. +* Fixed a free SD card space check in sequential XCI/NSP dump procedures. +* Fixed a bug where the output dump name wouldn't be generated for orphan content when no base applications are installed, preventing the NSP dump procedure from starting. Thanks to [snes878](https://github.com/snes878) for reporting this! +* Fixed a bug that prevented to retrieve the ticket for a bundled-in gamecard update from the Secure HFS0 partition during a NSP dump procedure. Thanks to [snes878](https://github.com/snes878) for reporting this! +* Fixed a bug where a NSP dump process would stop if no personalized ticket certificate is found in the ES system savefile (e.g. when no titles with personalized titlekey crypto have been downloaded from the eShop). Thanks to [satel](https://gbatemp.net/members/satel.27798/) for reporting this! +* Fixed a bug where an empty orphan content list would have been generated if no base applications are installed. Thanks to `Newb_3DS#6287` for reporting this issue! + +Thanks to [FennecTECH](https://github.com/fennectech) and MUXI from PSXTools forums for providing with testing! + **v1.1.7:** * Tickets and RSA certificates are now properly parsed from their respective system savedata files, thanks to the efforts of [shchmue](https://github.com/shchmue)! * Speeds up ticket / titlekey retrieval for NSP/ExeFS/RomFS operations. diff --git a/source/dumper.c b/source/dumper.c index 92a6698..071697d 100644 --- a/source/dumper.c +++ b/source/dumper.c @@ -18,10 +18,6 @@ /* Extern variables */ -extern bool runningSxOs; - -extern FsDeviceOperator fsOperatorInstance; - extern nca_keyset_t nca_keyset; extern u64 freeSpace; @@ -30,39 +26,14 @@ extern bool highlight; extern int breaks; extern int font_height; -extern u64 gameCardSize, trimmedCardSize; -extern char trimmedCardSizeStr[32]; +extern gamecard_ctx_t gameCardInfo; -extern u8 *hfs0_header; -extern u64 hfs0_offset, hfs0_size; -extern u32 hfs0_partition_cnt; +extern u32 titleAppCount, titlePatchCount, titleAddOnCount; +extern u32 sdCardTitleAppCount, sdCardTitlePatchCount, sdCardTitleAddOnCount; +extern u32 emmcTitleAppCount, emmcTitlePatchCount, emmcTitleAddOnCount; -extern u8 *partitionHfs0Header; -extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize; -extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize; - -extern u32 titleAppCount; -extern u64 *titleAppTitleID; -extern u32 *titleAppVersion; -extern FsStorageId *titleAppStorageId; - -extern u32 titlePatchCount; -extern u64 *titlePatchTitleID; -extern u32 *titlePatchVersion; -extern FsStorageId *titlePatchStorageId; - -extern u32 titleAddOnCount; -extern u64 *titleAddOnTitleID; -extern u32 *titleAddOnVersion; -extern FsStorageId *titleAddOnStorageId; - -extern u32 sdCardTitleAppCount; -extern u32 sdCardTitlePatchCount; -extern u32 sdCardTitleAddOnCount; - -extern u32 nandUserTitleAppCount; -extern u32 nandUserTitlePatchCount; -extern u32 nandUserTitleAddOnCount; +extern base_app_ctx_t *baseAppEntries; +extern patch_addon_ctx_t *patchEntries, *addOnEntries; extern AppletType programAppletType; @@ -86,24 +57,16 @@ extern u8 *dumpBuf; extern char strbuf[NAME_BUF_LEN]; -extern orphan_patch_addon_entry *orphanEntries; +extern u64 freeSpace; +extern char freeSpaceStr[32]; -void workaroundPartitionZeroAccess() -{ - FsGameCardHandle handle; - if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle))) return; - - FsStorage gameCardStorage; - if (R_FAILED(fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) return; - - fsStorageClose(&gameCardStorage); -} +extern char cfwDirStr[32]; -bool dumpCartridgeImage(xciOptions *xciDumpCfg) +bool dumpNXCardImage(xciOptions *xciDumpCfg) { if (!xciDumpCfg) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid XCI configuration struct!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid XCI configuration struct!", __func__); breaks += 2; return false; } @@ -113,14 +76,14 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) bool keepCert = xciDumpCfg->keepCert; bool trimDump = xciDumpCfg->trimDump; bool calcCrc = xciDumpCfg->calcCrc; + bool useNoIntroLookup = xciDumpCfg->useNoIntroLookup; + bool useBrackets = xciDumpCfg->useBrackets; u64 partitionOffset = 0, xciDataSize = 0, n; u64 partitionSizes[ISTORAGE_PARTITION_CNT]; - char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; u32 partition; Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; bool proceed = true, success = false, fat32_error = false; FILE *outFile = NULL; u8 splitIndex = 0; @@ -132,7 +95,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) memset(&progressCtx, 0, sizeof(progress_ctx_t)); bool seqDumpMode = false, seqDumpFileRemove = false, seqDumpFinish = false; - char seqDumpFilename[NAME_BUF_LEN * 2] = {'\0'}; + char seqDumpFilename[NAME_BUF_LEN] = {'\0'}; FILE *seqDumpFile = NULL; u64 seqDumpFileSize = 0, seqDumpSessionOffset = 0; @@ -143,14 +106,14 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) size_t read_res, write_res; - char *dumpName = generateFullDumpName(); + char *dumpName = generateGameCardDumpName(useBrackets); if (!dumpName) { // We're probably dealing with a forced XCI dump dumpName = calloc(16, sizeof(char)); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; return false; } @@ -159,264 +122,215 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) } // Check if we're dealing with a sequential dump - snprintf(seqDumpFilename, MAX_ELEMENTS(seqDumpFilename), "%s%s.xci.seq", XCI_DUMP_PATH, dumpName); + snprintf(seqDumpFilename, MAX_CHARACTERS(seqDumpFilename), "%s%s.xci.seq", XCI_DUMP_PATH, dumpName); seqDumpMode = checkIfFileExists(seqDumpFilename); if (seqDumpMode) { // Open sequence file seqDumpFile = fopen(seqDumpFilename, "rb+"); - if (seqDumpFile) + if (!seqDumpFile) { - // Retrieve sequence file size - fseek(seqDumpFile, 0, SEEK_END); - seqDumpFileSize = ftell(seqDumpFile); - rewind(seqDumpFile); - - // Check file size - if (seqDumpFileSize == sizeof(sequentialXciCtx)) - { - // Read file contents - read_res = fread(&seqXciCtx, 1, seqDumpFileSize, seqDumpFile); - rewind(seqDumpFile); - - if (read_res == seqDumpFileSize) - { - // Check if the IStorage partition index is valid - if (seqXciCtx.partitionIndex <= (ISTORAGE_PARTITION_CNT - 1)) - { - // Restore parameters from the sequence file - isFat32 = true; - setXciArchiveBit = false; - keepCert = seqXciCtx.keepCert; - trimDump = seqXciCtx.trimDump; - calcCrc = seqXciCtx.calcCrc; - splitIndex = seqXciCtx.partNumber; - certCrc = seqXciCtx.certCrc; - certlessCrc = seqXciCtx.certlessCrc; - progressCtx.curOffset = ((u64)seqXciCtx.partNumber * SPLIT_FILE_SEQUENTIAL_SIZE); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IStorage partition index in sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to read %lu bytes long sequential dump reference file! (read %lu bytes)", seqDumpFileSize, read_res); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: sequential dump reference file size mismatch! (%lu != %lu)", seqDumpFileSize, sizeof(sequentialXciCtx)); - proceed = false; - seqDumpFileRemove = true; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to open existing sequential dump reference file for reading! (\"%s\")", seqDumpFilename); - proceed = false; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to open existing sequential dump reference file for reading! (\"%s\")", __func__, seqDumpFilename); + goto out; } - uiRefreshDisplay(); + // Retrieve sequence file size + fseek(seqDumpFile, 0, SEEK_END); + seqDumpFileSize = ftell(seqDumpFile); + rewind(seqDumpFile); + + // Check file size + if (seqDumpFileSize != sizeof(sequentialXciCtx)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: sequential dump reference file size mismatch! (%lu != %lu)", __func__, seqDumpFileSize, sizeof(sequentialXciCtx)); + seqDumpFileRemove = true; + goto out; + } + + // Read file contents + read_res = fread(&seqXciCtx, 1, seqDumpFileSize, seqDumpFile); + rewind(seqDumpFile); + + if (read_res != seqDumpFileSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes long sequential dump reference file! (read %lu bytes)", __func__, seqDumpFileSize, read_res); + goto out; + } + + // Check if the IStorage partition index is valid + if (seqXciCtx.partitionIndex > (ISTORAGE_PARTITION_CNT - 1)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IStorage partition index in sequential dump reference file!", __func__); + seqDumpFileRemove = true; + goto out; + } + + // Restore parameters from the sequence file + isFat32 = true; + setXciArchiveBit = false; + keepCert = seqXciCtx.keepCert; + trimDump = seqXciCtx.trimDump; + calcCrc = seqXciCtx.calcCrc; + splitIndex = seqXciCtx.partNumber; + certCrc = seqXciCtx.certCrc; + certlessCrc = seqXciCtx.certlessCrc; + progressCtx.curOffset = ((u64)seqXciCtx.partNumber * SPLIT_FILE_SEQUENTIAL_SIZE); } - if (!proceed) goto out; - u64 part_size = (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++) { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Getting partition #%u size...", partition); - breaks++;*/ - - if (partition == (ISTORAGE_PARTITION_CNT - 1) && runningSxOs) - { - // Total size for IStorage instances is maxed out under SX OS, so let's manually reduce the size for the last instance - - u64 partitionSizesSum = 0; - for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i]; - - // Substract the total ECC block size as well as the size for previous IStorage instances - partitionSizes[partition] = ((gameCardSize - ((gameCardSize / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - partitionSizesSum); - - xciDataSize += partitionSizes[partition]; - convertSize(partitionSizes[partition], partitionSizesStr[partition], MAX_ELEMENTS(partitionSizesStr[partition])); - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u size: %s (%lu bytes).", partition, partitionSizesStr[partition], partitionSizes[partition]); - breaks += 2;*/ - } else { - workaroundPartitionZeroAccess(); - - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_SUCCEEDED(result)) - { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "GetGameCardHandle succeeded: 0x%08X", handle.value); - breaks++;*/ - - result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition); - if (R_SUCCEEDED(result)) - { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "OpenGameCardStorage succeeded: 0x%08X", handle.value); - breaks++;*/ - - result = fsStorageGetSize(&gameCardStorage, &(partitionSizes[partition])); - if (R_SUCCEEDED(result)) - { - xciDataSize += partitionSizes[partition]; - convertSize(partitionSizes[partition], partitionSizesStr[partition], MAX_ELEMENTS(partitionSizesStr[partition])); - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u size: %s (%lu bytes).", partition, partitionSizesStr[partition], partitionSizes[partition]); - breaks += 2;*/ - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "StorageGetSize failed! (0x%08X)", result); - proceed = false; - } - - fsStorageClose(&gameCardStorage); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); - proceed = false; - } - } - - uiRefreshDisplay(); + partitionSizes[partition] = gameCardInfo.IStoragePartitionSizes[partition]; + xciDataSize += partitionSizes[partition]; } - if (!proceed) goto out; - - convertSize(xciDataSize, xciDataSizeStr, MAX_ELEMENTS(xciDataSizeStr)); - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "XCI data size: %s (%lu bytes).", xciDataSizeStr, xciDataSize); - breaks += 2;*/ - if (trimDump) { - progressCtx.totalSize = trimmedCardSize; - snprintf(progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr), "%s", trimmedCardSizeStr); - - // Change dump size for the last IStorage partition + // Change dump size for the secure IStorage partition u64 partitionSizesSum = 0; - for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i]; + for(partition = 0; partition < (ISTORAGE_PARTITION_CNT - 1); partition++) partitionSizesSum += partitionSizes[partition]; - partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (trimmedCardSize - partitionSizesSum); + partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (gameCardInfo.trimmedSize - partitionSizesSum); + + progressCtx.totalSize = gameCardInfo.trimmedSize; } else { progressCtx.totalSize = xciDataSize; - snprintf(progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr), "%s", xciDataSizeStr); } + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Output dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); - breaks++; + breaks += 2; if (seqDumpMode) { // Check if the current offset doesn't exceed the total XCI size - if (progressCtx.curOffset < progressCtx.totalSize) + if (progressCtx.curOffset >= progressCtx.totalSize) { - // Check if the current partition offset doesn't exceed the partition size - if (seqXciCtx.partitionOffset < partitionSizes[seqXciCtx.partitionIndex]) - { - // Check if we have at least SPLIT_FILE_SEQUENTIAL_SIZE of free space - if (progressCtx.totalSize <= freeSpace || (progressCtx.totalSize > freeSpace && freeSpace >= SPLIT_FILE_SEQUENTIAL_SIZE)) - { - // Inform that we are resuming an already started sequential dump operation - breaks++; - 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, "Keep certificate: %s | Trim output dump: %s | CRC32 checksum calculation + dump verification: %s.", (keepCert ? "Yes" : "No"), (trimDump ? "Yes" : "No"), (calcCrc ? "Yes" : "No")); - breaks++; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IStorage partition offset in the sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid XCI offset in the sequential dump reference file!"); - proceed = false; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid XCI offset in the sequential dump reference file!", __func__); + seqDumpFileRemove = true; + goto out; } + + // Check if the current partition offset doesn't exceed the partition size + if (seqXciCtx.partitionOffset >= partitionSizes[seqXciCtx.partitionIndex]) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid IStorage partition offset in the sequential dump reference file!", __func__); + seqDumpFileRemove = true; + goto out; + } + + u64 curXciOffset = 0, restSize = 0; + + for(int i = 0; i < seqXciCtx.partitionIndex; i++) curXciOffset += partitionSizes[i]; + curXciOffset += seqXciCtx.partitionOffset; + + restSize = (progressCtx.totalSize - curXciOffset); + + // Check if our previously calculated XCI offset is aligned to SPLIT_FILE_SEQUENTIAL_SIZE + if (curXciOffset != progressCtx.curOffset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: overall XCI dump offset isn't aligned to 0x%08X in the sequential dump reference file!", __func__, (u32)SPLIT_FILE_SEQUENTIAL_SIZE); + seqDumpFileRemove = true; + goto out; + } + + // Check if there's enough free space to continue the sequential dump process + 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__); + goto out; + } + + // Inform that we are resuming an already started sequential dump operation + 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, "Keep certificate: %s | Trim output dump: %s | CRC32 checksum calculation + dump verification: %s.", (keepCert ? "Yes" : "No"), (trimDump ? "Yes" : "No"), (calcCrc ? "Yes" : "No")); + breaks += 2; + + uiRefreshDisplay(); } else { if (progressCtx.totalSize > freeSpace) { // Check if we have at least (SPLIT_FILE_SEQUENTIAL_SIZE + sizeof(sequentialXciCtx)) of free space - if (freeSpace >= (SPLIT_FILE_SEQUENTIAL_SIZE + sizeof(sequentialXciCtx))) + if (freeSpace < (SPLIT_FILE_SEQUENTIAL_SIZE + sizeof(sequentialXciCtx))) { - // Ask the user if they want to use the sequential dump mode - int cur_breaks = breaks; - breaks++; - - if (yesNoPrompt("There's not enough space available to generate a whole dump in this session. Do you want to use sequential dumping?\nIn this mode, the selected content will be dumped in more than one session.\nYou'll have to transfer the generated part files to a PC before continuing the process in the next session.")) - { - // 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); - uiRefreshDisplay(); - - // Modify config parameters - isFat32 = true; - setXciArchiveBit = false; - - part_size = SPLIT_FILE_SEQUENTIAL_SIZE; - - seqDumpMode = true; - seqDumpFileSize = sizeof(sequentialXciCtx); - - // Fill information in our sequential context - seqXciCtx.keepCert = keepCert; - seqXciCtx.trimDump = trimDump; - seqXciCtx.calcCrc = calcCrc; - - // Create sequential reference file and keep the handle to it opened - seqDumpFile = fopen(seqDumpFilename, "wb+"); - if (seqDumpFile) - { - write_res = fwrite(&seqXciCtx, 1, seqDumpFileSize, seqDumpFile); - rewind(seqDumpFile); - - if (write_res == seqDumpFileSize) - { - // Update free space - freeSpace -= seqDumpFileSize; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", seqDumpFileSize, write_res); - proceed = false; - seqDumpFileRemove = true; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to create sequential dump reference file! (\"%s\")", seqDumpFilename); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - proceed = false; + 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; } + + // Ask the user if they want to use the sequential dump mode + int cur_breaks = breaks; + + if (!yesNoPrompt("There's not enough space available to generate a whole dump in this session. Do you want to use sequential dumping?\nIn this mode, the selected content will be dumped in more than one session.\nYou'll have to transfer the generated part files to a PC before continuing the process in the next session.")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); + goto out; + } + + // 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); + uiRefreshDisplay(); + + // Modify config parameters + isFat32 = true; + setXciArchiveBit = false; + + part_size = SPLIT_FILE_SEQUENTIAL_SIZE; + + seqDumpMode = true; + seqDumpFileSize = sizeof(sequentialXciCtx); + + // Fill information in our sequential context + seqXciCtx.keepCert = keepCert; + seqXciCtx.trimDump = trimDump; + seqXciCtx.calcCrc = calcCrc; + + // Create sequential reference file and keep the handle to it opened + seqDumpFile = fopen(seqDumpFilename, "wb+"); + if (!seqDumpFile) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to create sequential dump reference file! (\"%s\")", __func__, seqDumpFilename); + goto out; + } + + write_res = fwrite(&seqXciCtx, 1, seqDumpFileSize, seqDumpFile); + rewind(seqDumpFile); + + if (write_res != seqDumpFileSize) + { + 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, write_res); + seqDumpFileRemove = true; + goto out; + } + + // Update free space + freeSpace -= seqDumpFileSize; } } - if (!proceed) goto out; - - breaks++; - if (seqDumpMode) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, splitIndex); } else { if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { if (setXciArchiveBit) { // Temporary, we'll use this to check if the dump already exists (it should have the archive bit set if so) - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci", XCI_DUMP_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, dumpName); } else { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex); } } else { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci", XCI_DUMP_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, dumpName); } // Check if the dump already exists - if (checkIfFileExists(filename)) + if (checkIfFileExists(dumpPath)) { // Ask the user if they want to proceed anyway int cur_breaks = breaks; @@ -438,32 +352,34 @@ bool dumpCartridgeImage(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(filename); - fsdevDeleteDirectoryRecursively(filename); + unlink(dumpPath); + fsdevDeleteDirectoryRecursively(dumpPath); - mkdir(filename, 0744); + mkdir(dumpPath, 0744); sprintf(tmp_idx, "/%02u", splitIndex); - strcat(filename, tmp_idx); + strcat(dumpPath, tmp_idx); } } - outFile = fopen(filename, "wb"); + outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file \"%s\"!", filename); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); goto out; } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -476,20 +392,12 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) startPartitionOffset = ((seqDumpMode && partition == startPartitionIndex) ? seqXciCtx.partitionOffset : 0); - workaroundPartitionZeroAccess(); + openIStoragePartition idx = (openIStoragePartition)(partition + 1); - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); + result = openGameCardStoragePartition(idx); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed for partition #%u! (0x%08X)", partition, result); - proceed = false; - break; - } - - result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed for partition #%u! (0x%08X)", partition, result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #%u! (0x%08X)", __func__, partition, result); proceed = false; break; } @@ -500,7 +408,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) 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(filename, '/' ) + 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 IStorage partition #%u...", partition); @@ -524,10 +432,10 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) } } - result = fsStorageRead(&gameCardStorage, partitionOffset, dumpBuf, n); + result = readGameCardStoragePartition(partitionOffset, dumpBuf, n); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%016lX for partition #%u", result, partitionOffset, partition); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk at offset 0x%016lX from IStorage partition #%u! (0x%08X)", __func__, n, partitionOffset, partition, result); proceed = false; break; } @@ -585,7 +493,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); + 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; } @@ -600,20 +508,20 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) if (seqDumpMode) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, splitIndex); } else { if (setXciArchiveBit) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci/%02u", XCI_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci/%02u", XCI_DUMP_PATH, dumpName, splitIndex); } else { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex); } } - outFile = fopen(filename, "wb"); + outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); + 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; } @@ -623,7 +531,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); + 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; } @@ -633,7 +541,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); + 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) { @@ -649,28 +557,28 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) if (seqDumpMode) progressCtx.seqDumpCurOffset = seqDumpSessionOffset; printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - if (seqDumpMode) seqDumpFileRemove = true; - break; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + proceed = false; + break; } } - fsStorageClose(&gameCardStorage); + closeGameCardStoragePartition(); - if (!proceed) break; + if (!proceed) + { + if (seqDumpMode) seqDumpFileRemove = true; + break; + } // Support empty files if (!partitionSizes[partition]) { 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(filename, '/' ) + 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 IStorage partition #%u...", partition); @@ -709,7 +617,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) write_res = fwrite(&seqXciCtx, 1, seqDumpFileSize, seqDumpFile); if (write_res != seqDumpFileSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", seqDumpFileSize, 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__, seqDumpFileSize, write_res); success = false; seqDumpFileRemove = true; goto out; @@ -726,7 +634,7 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); if (seqDumpMode && seqDumpFinish) @@ -745,21 +653,21 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) { if (keepCert) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "XCI dump CRC32 checksum (with certificate): %08X", certCrc); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "XCI dump CRC32 checksum (without certificate): %08X", certlessCrc); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "XCI dump CRC32 checksum (with certificate): %08X | XCI dump CRC32 checksum (without certificate): %08X", certCrc, certlessCrc); } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "XCI dump CRC32 checksum: %08X", certlessCrc); } - breaks += 2; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Starting verification process using XML database from NSWDB.COM..."); breaks++; uiRefreshDisplay(); - gameCardDumpNSWDBCheck(certlessCrc); + if (useNoIntroLookup) + { + noIntroDumpCheck(false, certlessCrc); + } else { + gameCardDumpNSWDBCheck(certlessCrc); + } } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "XCI dump CRC32 checksum: %08X", certCrc); breaks++; @@ -771,8 +679,8 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) // Set archive bit (only for FAT32 and if the required option is enabled) if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && setXciArchiveBit) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci", XCI_DUMP_PATH, dumpName); - result = fsdevSetArchiveBit(filename); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, dumpName); + result = fsdevSetConcatenationFileAttribute(dumpPath); if (R_FAILED(result)) { breaks += 2; @@ -784,73 +692,72 @@ bool dumpCartridgeImage(xciOptions *xciDumpCfg) { for(u8 i = 0; i <= splitIndex; i++) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, i); - unlink(filename); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci.%02u", XCI_DUMP_PATH, dumpName, i); + unlink(dumpPath); } } else { if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { if (setXciArchiveBit) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xci", XCI_DUMP_PATH, dumpName); - fsdevDeleteDirectoryRecursively(filename); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, dumpName); + fsdevDeleteDirectoryRecursively(dumpPath); } else { for(u8 i = 0; i <= splitIndex; i++) { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, i); - unlink(filename); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, i); + unlink(dumpPath); } } } else { - unlink(filename); + unlink(dumpPath); } } } out: - breaks += 2; - if (dumpName) free(dumpName); if (seqDumpFile) fclose(seqDumpFile); if (seqDumpFileRemove) unlink(seqDumpFilename); + breaks += 2; + return success; } -bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch) +int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch) { + int ret = -1; + if (!nspDumpCfg) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NSP configuration struct!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NSP configuration struct!", __func__); breaks += 2; - return false; + return ret; } bool isFat32 = nspDumpCfg->isFat32; - bool calcCrc = nspDumpCfg->calcCrc; + bool useNoIntroLookup = nspDumpCfg->useNoIntroLookup; bool removeConsoleData = nspDumpCfg->removeConsoleData; bool tiklessDump = nspDumpCfg->tiklessDump; bool npdmAcidRsaPatch = nspDumpCfg->npdmAcidRsaPatch; + bool dumpDeltaFragments = nspDumpCfg->dumpDeltaFragments; + bool useBrackets = nspDumpCfg->useBrackets; bool preInstall = false; Result result; u32 i = 0, j = 0; - FsStorageId curStorageId; - u8 filter; + NcmStorageId curStorageId; + NcmContentMetaType metaType; u32 titleCount = 0, ncmTitleIndex = 0; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; - NcmContentRecord *titleContentRecords = NULL; - u32 titleContentRecordsCnt = 0; - - FsGameCardHandle handle; - - FsStorage gameCardStorage; - memset(&gameCardStorage, 0, sizeof(FsStorage)); + NcmContentInfo *titleContentInfos = NULL; + u32 titleContentInfoCnt = 0; NcmContentStorage ncmStorage; memset(&ncmStorage, 0, sizeof(NcmContentStorage)); @@ -858,7 +765,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd cnmt_xml_program_info xml_program_info; cnmt_xml_content_info *xml_content_info = NULL; - NcmNcaId ncaId; + NcmContentId ncaId; u8 ncaHeader[NCA_FULL_HEADER_LENGTH] = {0}; nca_header_t dec_nca_header; @@ -897,7 +804,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd u32 nspFileCount = 0; pfs0_header nspPfs0Header; - pfs0_entry_table *nspPfs0EntryTable = NULL; + pfs0_file_entry *nspPfs0EntryTable = NULL; char *nspPfs0StrTable = NULL; u64 nspPfs0StrTableSize = 0; u64 full_nsp_header_size = 0; @@ -909,7 +816,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd FILE *outFile = NULL; u8 splitIndex = 0; u32 crc = 0; - bool proceed = true, success = false, dumping = false, fat32_error = false, removeFile = true; + bool proceed = true, dumping = false, fat32_error = false, removeFile = true; memset(dumpBuf, 0, DUMP_BUFFER_SIZE); @@ -917,7 +824,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd memset(&progressCtx, 0, sizeof(progress_ctx_t)); bool seqDumpMode = false, seqDumpFileRemove = false, seqDumpFinish = false; - char seqDumpFilename[NAME_BUF_LEN * 2] = {'\0'}; + char seqDumpFilename[NAME_BUF_LEN] = {'\0'}; FILE *seqDumpFile = NULL; u64 seqDumpFileSize = 0, seqDumpSessionOffset = 0; u8 *seqDumpNcaHashes = NULL; @@ -925,63 +832,60 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd sequentialNspCtx seqNspCtx; memset(&seqNspCtx, 0, sizeof(sequentialNspCtx)); - char pfs0HeaderFilename[NAME_BUF_LEN * 2] = {'\0'}; + char pfs0HeaderFilename[NAME_BUF_LEN] = {'\0'}; FILE *pfs0HeaderFile = NULL; char tmp_idx[5]; size_t read_res, write_res; - int initial_breaks = breaks; - - if ((selectedNspDumpType == DUMP_APP_NSP && !titleAppStorageId) || (selectedNspDumpType == DUMP_PATCH_NSP && !titlePatchStorageId) || (selectedNspDumpType == DUMP_ADDON_NSP && !titleAddOnStorageId)) + if ((selectedNspDumpType == DUMP_APP_NSP && !baseAppEntries) || (selectedNspDumpType == DUMP_PATCH_NSP && !patchEntries) || (selectedNspDumpType == DUMP_ADDON_NSP && !addOnEntries)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: title storage ID unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: title storage ID unavailable!", __func__); breaks += 2; - return false; + return ret; } if ((selectedNspDumpType == DUMP_APP_NSP && titleIndex >= titleAppCount) || (selectedNspDumpType == DUMP_PATCH_NSP && titleIndex >= titlePatchCount) || (selectedNspDumpType == DUMP_ADDON_NSP && titleIndex >= titleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); breaks += 2; - return false; + return ret; } - curStorageId = (selectedNspDumpType == DUMP_APP_NSP ? titleAppStorageId[titleIndex] : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchStorageId[titleIndex] : titleAddOnStorageId[titleIndex])); + curStorageId = (selectedNspDumpType == DUMP_APP_NSP ? baseAppEntries[titleIndex].storageId : (selectedNspDumpType == DUMP_PATCH_NSP ? patchEntries[titleIndex].storageId : addOnEntries[titleIndex].storageId)); - filter = ((u8)selectedNspDumpType + META_DB_REGULAR_APPLICATION); + ncmTitleIndex = (selectedNspDumpType == DUMP_APP_NSP ? baseAppEntries[titleIndex].ncmIndex : (selectedNspDumpType == DUMP_PATCH_NSP ? patchEntries[titleIndex].ncmIndex : addOnEntries[titleIndex].ncmIndex)); + + metaType = (selectedNspDumpType == DUMP_APP_NSP ? NcmContentMetaType_Application : (selectedNspDumpType == DUMP_PATCH_NSP ? NcmContentMetaType_Patch : NcmContentMetaType_AddOnContent)); switch(curStorageId) { - case FsStorageId_GameCard: + case NcmStorageId_GameCard: titleCount = (selectedNspDumpType == DUMP_APP_NSP ? titleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchCount : titleAddOnCount)); - ncmTitleIndex = titleIndex; break; - case FsStorageId_SdCard: + case NcmStorageId_SdCard: titleCount = (selectedNspDumpType == DUMP_APP_NSP ? sdCardTitleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? sdCardTitlePatchCount : sdCardTitleAddOnCount)); - ncmTitleIndex = titleIndex; break; - case FsStorageId_NandUser: - titleCount = (selectedNspDumpType == DUMP_APP_NSP ? nandUserTitleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? nandUserTitlePatchCount : nandUserTitleAddOnCount)); - ncmTitleIndex = (titleIndex - (selectedNspDumpType == DUMP_APP_NSP ? sdCardTitleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? sdCardTitlePatchCount : sdCardTitleAddOnCount))); + case NcmStorageId_BuiltInUser: + titleCount = (selectedNspDumpType == DUMP_APP_NSP ? emmcTitleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? emmcTitlePatchCount : emmcTitleAddOnCount)); break; default: break; } - char *dumpName = generateNSPDumpName(selectedNspDumpType, titleIndex); + char *dumpName = generateNSPDumpName(selectedNspDumpType, titleIndex, useBrackets); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; - return false; + return ret; } if (!batch) { - snprintf(seqDumpFilename, MAX_ELEMENTS(seqDumpFilename), "%s%s.nsp.seq", NSP_DUMP_PATH, dumpName); - snprintf(pfs0HeaderFilename, MAX_ELEMENTS(pfs0HeaderFilename), "%s%s.nsp.hdr", NSP_DUMP_PATH, dumpName); + snprintf(seqDumpFilename, MAX_CHARACTERS(seqDumpFilename), "%s%s.nsp.seq", NSP_DUMP_PATH, dumpName); + snprintf(pfs0HeaderFilename, MAX_CHARACTERS(pfs0HeaderFilename), "%s%s.nsp.hdr", NSP_DUMP_PATH, dumpName); // Check if we're dealing with a sequential dump seqDumpMode = checkIfFileExists(seqDumpFilename); @@ -989,79 +893,74 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd { // Open sequence file seqDumpFile = fopen(seqDumpFilename, "rb+"); - if (seqDumpFile) + if (!seqDumpFile) { - // Retrieve sequence file size - fseek(seqDumpFile, 0, SEEK_END); - seqDumpFileSize = ftell(seqDumpFile); - rewind(seqDumpFile); - - // Check file size - if (seqDumpFileSize > sizeof(sequentialNspCtx) && ((seqDumpFileSize - sizeof(sequentialNspCtx)) % SHA256_HASH_SIZE) == 0) - { - // Read sequentialNspCtx struct info - read_res = fread(&seqNspCtx, 1, sizeof(sequentialNspCtx), seqDumpFile); - if (read_res == sizeof(sequentialNspCtx)) - { - // Check if the storage ID is right - if (seqNspCtx.storageId == curStorageId) - { - // Check if we have the right amount of NCA hashes - if ((seqNspCtx.ncaCount * SHA256_HASH_SIZE) == (seqDumpFileSize - sizeof(sequentialNspCtx))) - { - // Allocate memory for the NCA hashes - seqDumpNcaHashes = calloc(1, seqDumpFileSize - sizeof(sequentialNspCtx)); - if (seqDumpNcaHashes) - { - // Read NCA hashes - read_res = fread(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); - rewind(seqDumpFile); - - if (read_res == (seqDumpFileSize - sizeof(sequentialNspCtx))) - { - // Restore parameters from the sequence file - isFat32 = true; - calcCrc = false; - removeConsoleData = seqNspCtx.removeConsoleData; - tiklessDump = seqNspCtx.tiklessDump; - npdmAcidRsaPatch = seqNspCtx.npdmAcidRsaPatch; - preInstall = seqNspCtx.preInstall; - splitIndex = seqNspCtx.partNumber; - progressCtx.curOffset = ((u64)seqNspCtx.partNumber * SPLIT_FILE_SEQUENTIAL_SIZE); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", seqNspCtx.ncaCount * SHA256_HASH_SIZE, read_res); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA hashes from the sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA count and/or NCA hash count in sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid source storage ID in sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to read %lu bytes chunk from the sequential dump reference file! (read %lu bytes)", seqDumpFileSize, read_res); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid sequential dump reference file size!"); - proceed = false; - seqDumpFileRemove = true; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to open existing sequential dump reference file for reading! (\"%s\")", seqDumpFilename); - proceed = false; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to open existing sequential dump reference file for reading! (\"%s\")", __func__, seqDumpFilename); + goto out; } - uiRefreshDisplay(); + // Retrieve sequence file size + fseek(seqDumpFile, 0, SEEK_END); + 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); + goto out; + } + + // Check if the storage ID is right + if (seqNspCtx.storageId != curStorageId) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid source storage ID in sequential dump reference file!", __func__); + goto out; + } + + // Check if we have the right amount of NCA hashes + if ((seqNspCtx.ncaCount * SHA256_HASH_SIZE) != (seqDumpFileSize - sizeof(sequentialNspCtx))) + { + 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__); + goto out; + } + + // Allocate memory for the NCA hashes + seqDumpNcaHashes = calloc(1, seqDumpFileSize - sizeof(sequentialNspCtx)); + 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__); + goto out; + } + + // Read NCA hashes + read_res = fread(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); + rewind(seqDumpFile); + + if (read_res != (seqDumpFileSize - 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__, seqNspCtx.ncaCount * SHA256_HASH_SIZE, read_res); + goto out; + } + + // Restore parameters from the sequence file + isFat32 = true; + removeConsoleData = seqNspCtx.removeConsoleData; + tiklessDump = seqNspCtx.tiklessDump; + npdmAcidRsaPatch = seqNspCtx.npdmAcidRsaPatch; + preInstall = seqNspCtx.preInstall; + splitIndex = seqNspCtx.partNumber; + progressCtx.curOffset = ((u64)seqNspCtx.partNumber * SPLIT_FILE_SEQUENTIAL_SIZE); } - - if (!proceed) goto out; } u64 part_size = (seqDumpMode ? SPLIT_FILE_SEQUENTIAL_SIZE : SPLIT_FILE_NSP_PART_SIZE); @@ -1070,70 +969,66 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving information from encrypted NCA content files..."); uiRefreshDisplay(); - breaks++; + breaks += 2; } - if (!retrieveNcaContentRecords(curStorageId, filter, titleCount, ncmTitleIndex, &titleContentRecords, &titleContentRecordsCnt)) goto out; - - // If we're dealing with a gamecard, open an IStorage instance for the HFS0 Secure partition. We may need it if we're dealing with a Patch with titlekey crypto, in order to retrieve the tik file - if (curStorageId == FsStorageId_GameCard) + if (!retrieveContentInfosFromTitle(curStorageId, metaType, titleCount, ncmTitleIndex, &titleContentInfos, &titleContentInfoCnt)) { - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); + goto out; + } + + // If we're dealing with a gamecard, open the Secure HFS0 partition (IStorage partition #1) to read NCA data + // We may also need to retrieve a ticket if we're dealing with a Patch with titlekey crypto + if (curStorageId == NcmStorageId_GameCard) + { + result = openGameCardStoragePartition(ISTORAGE_PARTITION_SECURE); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); - goto out; - } - - u32 partition = (hfs0_partition_cnt - 1); // Select the secure partition - - result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to open IStorage partition #1! (0x%08X)", __func__, result); goto out; } } - result = ncmOpenContentStorage(curStorageId, &ncmStorage); + result = ncmOpenContentStorage(&ncmStorage, curStorageId); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: ncmOpenContentStorage failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: ncmOpenContentStorage failed! (0x%08X)", __func__, result); goto out; } // Fill information for our CNMT XML memset(&xml_program_info, 0, sizeof(cnmt_xml_program_info)); - xml_program_info.type = filter; - xml_program_info.title_id = (selectedNspDumpType == DUMP_APP_NSP ? titleAppTitleID[titleIndex] : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchTitleID[titleIndex] : titleAddOnTitleID[titleIndex])); - xml_program_info.version = (selectedNspDumpType == DUMP_APP_NSP ? titleAppVersion[titleIndex] : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchVersion[titleIndex] : titleAddOnVersion[titleIndex])); - xml_program_info.nca_cnt = titleContentRecordsCnt; + xml_program_info.type = (u8)metaType; + xml_program_info.title_id = (selectedNspDumpType == DUMP_APP_NSP ? baseAppEntries[titleIndex].titleId : (selectedNspDumpType == DUMP_PATCH_NSP ? patchEntries[titleIndex].titleId : addOnEntries[titleIndex].titleId)); + xml_program_info.version = (selectedNspDumpType == DUMP_APP_NSP ? baseAppEntries[titleIndex].version : (selectedNspDumpType == DUMP_PATCH_NSP ? patchEntries[titleIndex].version : addOnEntries[titleIndex].version)); + xml_program_info.nca_cnt = titleContentInfoCnt; - xml_content_info = calloc(titleContentRecordsCnt, sizeof(cnmt_xml_content_info)); + xml_content_info = calloc(titleContentInfoCnt, sizeof(cnmt_xml_content_info)); if (!xml_content_info) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the CNMT XML content info struct!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the CNMT XML content info struct!", __func__); goto out; } // Fill our CNMT XML content records, leaving the CNMT NCA at the end - u32 titleRecordIndex; - for(i = 0, titleRecordIndex = 0; titleRecordIndex < titleContentRecordsCnt; i++, titleRecordIndex++) + u32 titleContentInfoIndex; + for(i = 0, titleContentInfoIndex = 0; titleContentInfoIndex < titleContentInfoCnt; i++, titleContentInfoIndex++) { - if (!cnmtFound && titleContentRecords[titleRecordIndex].type == NcmContentType_CNMT) + if (!cnmtFound && titleContentInfos[titleContentInfoIndex].content_type == NcmContentType_Meta) { cnmtFound = true; - cnmtNcaIndex = titleRecordIndex; + cnmtNcaIndex = titleContentInfoIndex; i--; continue; } - // Skip Delta Fragments or any other unknown content types + // Skip Delta Fragments and/or any other unknown content types (only if the related option is disabled) // Delta Fragments are used to update from a certain version to another version without needing to install the whole update // For any dumping purposes, they're useless, because they just increase the size of the output dump. The more updates come out for a title, the more Delta Fragments there will be available for that title - // Also, since they're basically an eShop thing, they're not available in gamecards. So in this particular case, we need to skip them anyway + // Also, since they're basically an eShop thing, they're not available in gamecards (so in this particular case, we need to skip them anyway) // However, their content records must be kept intact in the CNMT NCA - if (titleContentRecords[titleRecordIndex].type >= NCA_CONTENT_TYPE_DELTA) + if (titleContentInfos[titleContentInfoIndex].content_type >= NcmContentType_DeltaFragment && !dumpDeltaFragments) { xml_program_info.nca_cnt--; i--; @@ -1141,25 +1036,26 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // Fill information for our CNMT XML - xml_content_info[i].type = titleContentRecords[titleRecordIndex].type; - memcpy(xml_content_info[i].nca_id, titleContentRecords[titleRecordIndex].ncaId.c, SHA256_HASH_SIZE / 2); // Temporary - convertDataToHexString(titleContentRecords[titleRecordIndex].ncaId.c, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); // Temporary - convertNcaSizeToU64(titleContentRecords[titleRecordIndex].size, &(xml_content_info[i].size)); + xml_content_info[i].type = titleContentInfos[titleContentInfoIndex].content_type; + memcpy(xml_content_info[i].nca_id, titleContentInfos[titleContentInfoIndex].content_id.c, SHA256_HASH_SIZE / 2); // Temporary + convertDataToHexString(titleContentInfos[titleContentInfoIndex].content_id.c, SHA256_HASH_SIZE / 2, xml_content_info[i].nca_id_str, SHA256_HASH_SIZE + 1); // Temporary + convertNcaSizeToU64(titleContentInfos[titleContentInfoIndex].size, &(xml_content_info[i].size)); + xml_content_info[i].id_offset = titleContentInfos[titleContentInfoIndex].id_offset; convertDataToHexString(xml_content_info[i].hash, SHA256_HASH_SIZE, xml_content_info[i].hash_str, (SHA256_HASH_SIZE * 2) + 1); // Temporary - memcpy(&ncaId, &(titleContentRecords[titleRecordIndex].ncaId), sizeof(NcmNcaId)); + memcpy(&ncaId, &(titleContentInfos[titleContentInfoIndex].content_id), sizeof(NcmContentId)); - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read header from NCA \"%s\"! (0x%08X)", xml_content_info[i].nca_id_str, result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read header from NCA \"%s\"!", __func__, xml_content_info[i].nca_id_str); proceed = false; break; } // Decrypt the NCA header // Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard - if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, &rights_info, xml_content_info[i].decrypted_nca_keys, (curStorageId != FsStorageId_GameCard))) + if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, &rights_info, xml_content_info[i].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) { proceed = false; break; @@ -1179,7 +1075,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd // Check if the missing ticket flag is enabled // If so, we may be dealing with a preinstalled title - if (curStorageId != FsStorageId_GameCard && has_rights_id && rights_info.missing_tik && !preInstall) + if (curStorageId != NcmStorageId_GameCard && has_rights_id && rights_info.missing_tik && !preInstall) { // Only display the pre-install prompt if we're not running a batch / sequential dump operation (excluding the first run of the latter) if (!batch && !seqDumpMode) @@ -1205,10 +1101,10 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd // Fill information for our CNMT XML xml_content_info[i].keyblob = (dec_nca_header.crypto_type2 > dec_nca_header.crypto_type ? dec_nca_header.crypto_type2 : dec_nca_header.crypto_type); - if (curStorageId == FsStorageId_GameCard) + if (curStorageId == NcmStorageId_GameCard) { // Modify content distribution type - // It's always set to 1 (gamecard) in Applications and AddOns bundled in gamecards + // It's always set to 1 (gamecard) in Applications and Add-Ons bundled in gamecards // It's always set to 0 (download) in Patches bundled in gamecards. But if we're dealing with a custom XCI mounted through SX OS, we may need to change that dec_nca_header.distribution = 0; @@ -1217,7 +1113,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd // Application and AddOn titles don't have a populated Rights ID field when bundled in gamecards if (has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Rights ID field in NCA header not empty!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in NCA header not empty!", __func__); proceed = false; break; } @@ -1236,14 +1132,14 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd { // Patch titles *do* have a populated Rights ID field and a ticket + certificate chain combination when bundled in gamecards // Depending on the dump settings, we may need to change or remove that - // If no Rights ID is available, we may be dealing with a custom XCI mounted through SX OS. In this particular case, no further modification should be needed + // 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 ticket. We're going to use our own certificate chain down the road, there's no need to retrieve it as well - if (!getFileFromHfs0PartitionByName(&gameCardStorage, rights_info.tik_filename, dumpBuf, ETICKET_TIK_FILE_SIZE)) + if (!readFileFromSecureHfs0PartitionByName(rights_info.tik_filename, 0, dumpBuf, ETICKET_TIK_FILE_SIZE)) { proceed = false; break; @@ -1301,7 +1197,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } } } else - if (curStorageId == FsStorageId_SdCard || curStorageId == FsStorageId_NandUser) + if (curStorageId == NcmStorageId_SdCard || curStorageId == NcmStorageId_BuiltInUser) { // Only mess with the NCA header if we're dealing with a content with a populated Rights ID field, and if both removeConsoleData and tiklessDump are true // This will only be done if we were able to retrieve the ticket for this title @@ -1344,7 +1240,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // Retrieve NACP data (XML and icons) - if (!nacpXml && xml_content_info[i].type == NcmContentType_Icon) + if (!nacpXml && xml_content_info[i].type == NcmContentType_Control) { nacpNcaIndex = i; @@ -1356,7 +1252,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // Retrieve legalinfo.xml - if (!legalInfoXml && xml_content_info[i].type == NcmContentType_Info) + if (!legalInfoXml && xml_content_info[i].type == NcmContentType_LegalInformation) { legalInfoNcaIndex = i; @@ -1380,53 +1276,50 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (proceed && !cnmtFound) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find CNMT NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find CNMT NCA!", __func__); goto out; } - // Update NCA counter just in case we found any delta fragments - titleContentRecordsCnt = xml_program_info.nca_cnt; + // Update NCA counter just in case we found any delta fragments and excluded them + titleContentInfoCnt = xml_program_info.nca_cnt; // Fill information for our CNMT XML - xml_content_info[titleContentRecordsCnt - 1].type = titleContentRecords[cnmtNcaIndex].type; - memcpy(xml_content_info[titleContentRecordsCnt - 1].nca_id, titleContentRecords[cnmtNcaIndex].ncaId.c, SHA256_HASH_SIZE / 2); // Temporary - convertDataToHexString(titleContentRecords[cnmtNcaIndex].ncaId.c, SHA256_HASH_SIZE / 2, xml_content_info[titleContentRecordsCnt - 1].nca_id_str, SHA256_HASH_SIZE + 1); // Temporary - convertNcaSizeToU64(titleContentRecords[cnmtNcaIndex].size, &(xml_content_info[titleContentRecordsCnt - 1].size)); - convertDataToHexString(xml_content_info[titleContentRecordsCnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[titleContentRecordsCnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1); // Temporary + xml_content_info[titleContentInfoCnt - 1].type = titleContentInfos[cnmtNcaIndex].content_type; + memcpy(xml_content_info[titleContentInfoCnt - 1].nca_id, titleContentInfos[cnmtNcaIndex].content_id.c, SHA256_HASH_SIZE / 2); // Temporary + convertDataToHexString(titleContentInfos[cnmtNcaIndex].content_id.c, SHA256_HASH_SIZE / 2, xml_content_info[titleContentInfoCnt - 1].nca_id_str, SHA256_HASH_SIZE + 1); // Temporary + convertNcaSizeToU64(titleContentInfos[cnmtNcaIndex].size, &(xml_content_info[titleContentInfoCnt - 1].size)); + xml_content_info[titleContentInfoCnt - 1].id_offset = titleContentInfos[cnmtNcaIndex].id_offset; + convertDataToHexString(xml_content_info[titleContentInfoCnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[titleContentInfoCnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1); // Temporary - memcpy(&ncaId, &(titleContentRecords[cnmtNcaIndex].ncaId), sizeof(NcmNcaId)); + memcpy(&ncaId, &(titleContentInfos[cnmtNcaIndex].content_id), sizeof(NcmContentId)); // Update CNMT index - cnmtNcaIndex = (titleContentRecordsCnt - 1); + cnmtNcaIndex = (titleContentInfoCnt - 1); cnmtNcaBuf = malloc(xml_content_info[cnmtNcaIndex].size); if (!cnmtNcaBuf) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for CNMT NCA data!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA data!", __func__); goto out; } - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&ncmStorage, &ncaId, 0, cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read CNMT NCA \"%s\"! (0x%08X)", xml_content_info[cnmtNcaIndex].nca_id_str, result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read CNMT NCA \"%s\"!", __func__, xml_content_info[cnmtNcaIndex].nca_id_str); goto out; } // Retrieve CNMT NCA data - if (!retrieveCnmtNcaData(curStorageId, selectedNspDumpType, cnmtNcaBuf, &xml_program_info, xml_content_info, cnmtNcaIndex, &ncaCnmtMod, &rights_info, (removeConsoleData && tiklessDump))) goto out; + if (!retrieveCnmtNcaData(curStorageId, selectedNspDumpType, 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 - /*breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Generating placeholder CNMT XML..."); - uiRefreshDisplay(); - breaks++;*/ // Make sure that the output buffer for our CNMT XML is big enough cnmtXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); if (!cnmtXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the CNMT XML!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the CNMT XML!", __func__); goto out; } @@ -1436,28 +1329,9 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (includeTikAndCert) { - if (curStorageId == FsStorageId_GameCard) - { - // Ticket files from Patch titles bundled with gamecards have a different layout - // Let's convert it to a "normal" common ticket - memset(rights_info.tik_data.signature, 0xFF, 0x100); - - memset((u8*)(&(rights_info.tik_data)) + 0x190, 0, 0x130); - - rights_info.tik_data.unk1 = 0x02; // Always 0x02 ? - - rights_info.tik_data.master_key_rev = rights_info.rights_id[15]; - - memcpy(rights_info.tik_data.rights_id, rights_info.rights_id, 0x10); - - rights_info.tik_data.unk4[4] = 0xC0; // Always 0xC0 ? - rights_info.tik_data.unk4[5] = 0x02; // Always 0x02 ? - } else - if (curStorageId == FsStorageId_SdCard || curStorageId == FsStorageId_NandUser) - { - // Only mess with the ticket data if removeConsoleData is true, if tiklessDump is false and if we're dealing with a personalized ticket (checked in removeConsoleDataFromTicket()) - if (removeConsoleData) removeConsoleDataFromTicket(&rights_info); - } + // Only mess with the ticket data if removeConsoleData is true, if tiklessDump is false and if we're dealing with a personalized ticket (checked in removeConsoleDataFromTicket()) + // Ticket files from Patch titles bundled with gamecards always use common titlekey crypto + if ((curStorageId == NcmStorageId_SdCard || curStorageId == NcmStorageId_BuiltInUser) && removeConsoleData) removeConsoleDataFromTicket(&rights_info); // Retrieve cert file if (!retrieveCertData(rights_info.cert_data, (rights_info.tik_data.titlekey_type == ETICKET_TITLEKEY_PERSONALIZED))) @@ -1467,13 +1341,13 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // File count = NCA count + CNMT XML + tik + cert - nspFileCount = (titleContentRecordsCnt + 3); + nspFileCount = (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); } else { // File count = NCA count + CNMT XML - nspFileCount = (titleContentRecordsCnt + 1); + nspFileCount = (titleContentInfoCnt + 1); // Calculate PFS0 String Table size nspPfs0StrTableSize = (((nspFileCount - 2) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2)); @@ -1511,19 +1385,15 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // Start NSP creation - /*breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Generating placeholder PFS0 header..."); - uiRefreshDisplay(); - breaks++;*/ memset(&nspPfs0Header, 0, sizeof(pfs0_header)); nspPfs0Header.magic = bswap_32(PFS0_MAGIC); nspPfs0Header.file_cnt = nspFileCount; - nspPfs0EntryTable = calloc(nspFileCount, sizeof(pfs0_entry_table)); + nspPfs0EntryTable = calloc(nspFileCount, sizeof(pfs0_file_entry)); if (!nspPfs0EntryTable) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Unable to allocate memory for the PFS0 file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the PFS0 file entries!", __func__); goto out; } @@ -1531,24 +1401,24 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd nspPfs0StrTable = calloc(nspPfs0StrTableSize * 2, sizeof(char)); if (!nspPfs0StrTable) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Unable to allocate memory for the PFS0 string table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the PFS0 string table!", __func__); goto out; } // Determine our full NSP header size - full_nsp_header_size = (sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_entry_table)) + nspPfs0StrTableSize); + full_nsp_header_size = (sizeof(pfs0_header) + ((u64)nspFileCount * 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); // Determine our String Table size - nspPfs0Header.str_table_size = (full_nsp_header_size - (sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_entry_table)))); + nspPfs0Header.str_table_size = (full_nsp_header_size - (sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_file_entry)))); // Calculate total dump size progressCtx.totalSize = full_nsp_header_size; - for(i = 0; i < titleContentRecordsCnt; i++) progressCtx.totalSize += xml_content_info[i].size; + for(i = 0; i < titleContentInfoCnt; i++) progressCtx.totalSize += xml_content_info[i].size; progressCtx.totalSize += strlen(cnmtXml); @@ -1568,345 +1438,335 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (includeTikAndCert) progressCtx.totalSize += (ETICKET_TIK_FILE_SIZE + ETICKET_CERT_FILE_SIZE); - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Total NSP dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); + uiRefreshDisplay(); + breaks += 2; if (!batch) { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Total NSP dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); - uiRefreshDisplay(); - breaks++; - if (seqDumpMode) { // Check if the current offset doesn't exceed the total NSP size - if (progressCtx.curOffset < progressCtx.totalSize) + if (progressCtx.curOffset >= progressCtx.totalSize) { - // Check if we have at least SPLIT_FILE_SEQUENTIAL_SIZE of free space - if (progressCtx.totalSize <= freeSpace || (progressCtx.totalSize > freeSpace && freeSpace >= SPLIT_FILE_SEQUENTIAL_SIZE)) + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NSP offset in the sequential dump reference file!", __func__); + goto out; + } + + // Check if the NCA count is valid + // The CNMT NCA is excluded from the hash list + if (seqNspCtx.ncaCount != (titleContentInfoCnt - 1)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA count mismatch in the sequential dump reference file! (%u != %u)", __func__, seqNspCtx.ncaCount, titleContentInfoCnt - 1); + goto out; + } + + // Check if the PFS0 file count is valid + if (seqNspCtx.nspFileCount != nspFileCount) + { + 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); + goto out; + } + + // Check if the current PFS0 file index is valid + if (seqNspCtx.fileIndex >= nspFileCount) + { + 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; + } + + // Check if we're really dealing with a title with a missing ticket if preInstall == true + if (seqNspCtx.preInstall && !rights_info.missing_tik) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title preinstall status in the sequential dump reference file!", __func__); + goto out; + } + + // 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) { - // Check if the NCA count is valid - // The CNMT NCA is excluded from the hash list - if (seqNspCtx.ncaCount == (titleContentRecordsCnt - 1)) + 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)) { - // Check if the PFS0 file count is valid - if (seqNspCtx.nspFileCount == nspFileCount) + curNspOffset += ETICKET_TIK_FILE_SIZE; + } else { + curNspOffset += ETICKET_CERT_FILE_SIZE; + } + } + } + + curNspOffset += seqNspCtx.fileOffset; + restSize = (progressCtx.totalSize - curNspOffset); + + if (curNspOffset != progressCtx.curOffset) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: overall NSP dump offset isn't aligned to 0x%08X in the sequential dump reference file!", __func__, (u32)SPLIT_FILE_SEQUENTIAL_SIZE); + goto out; + } + + // Check if there's enough free space to continue the sequential dump process + 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__); + goto out; + } + + // Now check if the current PFS0 file entry offset is correct + for(i = 0; i < nspFileCount; i++) + { + if (i < seqNspCtx.fileIndex) + { + // 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) { - // Check if the current PFS0 file index is valid - if (seqNspCtx.fileIndex < nspFileCount) - { - // Check if we're really dealing with a title with a missing ticket if preInstall == true - if (!seqNspCtx.preInstall || (seqNspCtx.preInstall && rights_info.missing_tik)) - { - // Check if the current overall offset is aligned to SPLIT_FILE_SEQUENTIAL_SIZE - u64 curNspOffset = full_nsp_header_size; - - for(i = 0; i < seqNspCtx.fileIndex; i++) - { - if (i < titleContentRecordsCnt) - { - curNspOffset += xml_content_info[i].size; - } else - if (i == titleContentRecordsCnt) - { - curNspOffset += strlen(cnmtXml); - } else - if (programInfoXml && i == (titleContentRecordsCnt + 1)) - { - curNspOffset += programInfoXmlSize; - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentRecordsCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentRecordsCnt + 1 + nacpIconCnt)))) - { - u32 icon_idx = (!programInfoXml ? (i - (titleContentRecordsCnt + 1)) : (i - (titleContentRecordsCnt + 2))); - curNspOffset += nacpIcons[icon_idx].icon_size; - } else - if (nacpXml && ((!programInfoXml && i == (titleContentRecordsCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentRecordsCnt + 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; - } - } - } - - curNspOffset += seqNspCtx.fileOffset; - - if (!(curNspOffset % SPLIT_FILE_SEQUENTIAL_SIZE)) - { - // Now check if the current PFS0 file entry offset is correct - // Probably overkill but it's better to be safe than sorry - - for(i = 0; i < nspFileCount; i++) - { - if (i < seqNspCtx.fileIndex) - { - // Exclude the CNMT NCA - if (i < (titleContentRecordsCnt - 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 < titleContentRecordsCnt) - { - // 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, "Error: invalid NCA offset in the sequential dump reference file!"); - proceed = false; - } - } else - if (i == titleContentRecordsCnt) - { - // 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, "Error: invalid CNMT XML offset in the sequential dump reference file!"); - proceed = false; - } - } else { - if (programInfoXml && i == (titleContentRecordsCnt + 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, "Error: invalid programinfo.xml offset in the sequential dump reference file!"); - proceed = false; - } - } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentRecordsCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentRecordsCnt + 1 + nacpIconCnt)))) - { - // Check if the offset for the NACP icon is valid - u32 icon_idx = (!programInfoXml ? (i - (titleContentRecordsCnt + 1)) : (i - (titleContentRecordsCnt + 2))); - if (seqNspCtx.fileOffset >= nacpIcons[icon_idx].icon_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NACP icon offset in the sequential dump reference file!"); - proceed = false; - } - } else - if (nacpXml && ((!programInfoXml && i == (titleContentRecordsCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentRecordsCnt + 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, "Error: invalid NACP XML offset in the sequential dump reference file!"); - 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, "Error: invalid legalinfo.xml offset in the sequential dump reference file!"); - 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, "Error: invalid ticket offset in the sequential dump reference file!"); - 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, "Error: invalid certificate chain offset in the sequential dump reference file!"); - proceed = false; - } - } - } - } - - break; - } - } - - if (proceed) - { - // 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); - - // Inform that we are resuming an already started sequential dump operation - breaks++; - - if (curStorageId == FsStorageId_GameCard) - { - if (selectedNspDumpType == DUMP_APP_NSP || 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, "Generate ticket-less dump: %s.", (tiklessDump ? "Yes" : "No")); - } - } 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")); - } - - breaks++; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: overall NSP dump offset isn't aligned to 0x%08X in the sequential dump reference file!", (u32)SPLIT_FILE_SEQUENTIAL_SIZE); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title preinstall status in the sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid PFS0 file index in the sequential dump reference file!"); - proceed = false; - } + // 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, "Error: PFS0 file count mismatch in the sequential dump reference file! (%u != %u)", seqNspCtx.nspFileCount, nspFileCount); + 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 { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA count mismatch in the sequential dump reference file! (%u != %u)", seqNspCtx.ncaCount, titleContentRecordsCnt - 1); - proceed = false; + 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; + } + } + + 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); + + // Inform that we are resuming an already started sequential dump operation + if (curStorageId == NcmStorageId_GameCard) + { + if (selectedNspDumpType == DUMP_APP_NSP || 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_ERROR_RGB, "Error: not enough free space available in the SD card."); - proceed = false; + 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")); } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NSP offset in the sequential dump reference file!"); - proceed = false; + 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")); } + + breaks++; } else { if (progressCtx.totalSize > freeSpace) { - // Check if we have at least (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentRecordsCnt - 1) * SHA256_HASH_SIZE))) of free space + // Check if we have at least (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE))) of free space // The CNMT NCA is excluded from the hash list - if (freeSpace >= (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentRecordsCnt - 1) * SHA256_HASH_SIZE)))) + if (freeSpace < (SPLIT_FILE_SEQUENTIAL_SIZE + (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE)))) { - // Ask the user if they want to use the sequential dump mode - int cur_breaks = breaks; - breaks++; - - if (yesNoPrompt("There's not enough space available to generate a whole dump in this session. Do you want to use sequential dumping?\nIn this mode, the selected content will be dumped in more than one session.\nYou'll have to transfer the generated part files to a PC before continuing the process in the next session.")) - { - // 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); - uiRefreshDisplay(); - - // Modify config parameters - isFat32 = true; - calcCrc = false; - - part_size = SPLIT_FILE_SEQUENTIAL_SIZE; - - seqDumpMode = true; - seqDumpFileSize = (sizeof(sequentialNspCtx) + ((titleContentRecordsCnt - 1) * SHA256_HASH_SIZE)); - - // Fill information in our sequential context - seqNspCtx.storageId = curStorageId; - seqNspCtx.removeConsoleData = removeConsoleData; - seqNspCtx.tiklessDump = tiklessDump; - seqNspCtx.npdmAcidRsaPatch = npdmAcidRsaPatch; - seqNspCtx.preInstall = preInstall; - seqNspCtx.nspFileCount = nspFileCount; - seqNspCtx.ncaCount = (titleContentRecordsCnt - 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); - - // Allocate memory for the NCA hashes - seqDumpNcaHashes = calloc(1, (titleContentRecordsCnt - 1) * SHA256_HASH_SIZE); - if (seqDumpNcaHashes) - { - // Create sequential reference file and keep the handle to it opened - seqDumpFile = fopen(seqDumpFilename, "wb+"); - if (seqDumpFile) - { - // Write the sequential dump struct - write_res = fwrite(&seqNspCtx, 1, sizeof(sequentialNspCtx), seqDumpFile); - if (write_res == sizeof(sequentialNspCtx)) - { - // Write the NCA hashes block - write_res = fwrite(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); - rewind(seqDumpFile); - - if (write_res == (seqDumpFileSize - sizeof(sequentialNspCtx))) - { - // Update free space - freeSpace -= seqDumpFileSize; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", titleContentRecordsCnt * SHA256_HASH_SIZE, write_res); - proceed = false; - seqDumpFileRemove = true; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", sizeof(sequentialNspCtx), write_res); - proceed = false; - seqDumpFileRemove = true; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to create sequential dump reference file! (\"%s\")", seqDumpFilename); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA hashes from the sequential dump reference file!"); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - proceed = false; + 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; } + + // Ask the user if they want to use the sequential dump mode + int cur_breaks = breaks; + + if (!yesNoPrompt("There's not enough space available to generate a whole dump in this session. Do you want to use sequential dumping?\nIn this mode, the selected content will be dumped in more than one session.\nYou'll have to transfer the generated part files to a PC before continuing the process in the next session.")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); + goto out; + } + + // 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); + uiRefreshDisplay(); + + // Modify config parameters + isFat32 = true; + + part_size = SPLIT_FILE_SEQUENTIAL_SIZE; + + seqDumpMode = true; + seqDumpFileSize = (sizeof(sequentialNspCtx) + ((titleContentInfoCnt - 1) * SHA256_HASH_SIZE)); + + // Fill information in our sequential context + seqNspCtx.storageId = curStorageId; + seqNspCtx.removeConsoleData = removeConsoleData; + seqNspCtx.tiklessDump = tiklessDump; + seqNspCtx.npdmAcidRsaPatch = npdmAcidRsaPatch; + seqNspCtx.preInstall = preInstall; + seqNspCtx.nspFileCount = nspFileCount; + 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); + + // Allocate memory for the NCA hashes + seqDumpNcaHashes = calloc(1, (titleContentInfoCnt - 1) * 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__); + goto out; + } + + // Create sequential reference file and keep the handle to it opened + seqDumpFile = fopen(seqDumpFilename, "wb+"); + if (!seqDumpFile) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to create sequential dump reference file! (\"%s\")", __func__, seqDumpFilename); + goto out; + } + + // Write the sequential dump struct + write_res = fwrite(&seqNspCtx, 1, sizeof(sequentialNspCtx), seqDumpFile); + if (write_res != sizeof(sequentialNspCtx)) + { + 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__, sizeof(sequentialNspCtx), write_res); + seqDumpFileRemove = true; + goto out; + } + + // Write the NCA hashes block + write_res = fwrite(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); + rewind(seqDumpFile); + + if (write_res != (seqDumpFileSize - sizeof(sequentialNspCtx))) + { + 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); + seqDumpFileRemove = true; + goto out; + } + + // Update free space + freeSpace -= seqDumpFileSize; } } } else { if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - proceed = false; + 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; } } - if (!proceed) goto out; - if (seqDumpMode) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp.%02u", NSP_DUMP_PATH, dumpName, splitIndex); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp.%02u", NSP_DUMP_PATH, dumpName, splitIndex); } else { // Temporary, we'll use this to check if the dump already exists (it should have the archive bit set if so) - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); // Check if the dump already exists if (!batch && checkIfFileExists(dumpPath)) { // Ask the user if they want to proceed anyway int cur_breaks = breaks; - breaks++; proceed = yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?"); if (!proceed) @@ -1938,24 +1798,25 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file \"%s\"!", dumpPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); goto out; } if (!batch) { - breaks++; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B); uiRefreshDisplay(); - breaks += 2; + 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 += 2; + breaks++; } + if (!batch) breaks++; + memset(dumpBuf, 0, DUMP_BUFFER_SIZE); if (seqDumpMode) @@ -1968,7 +1829,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd write_res = fwrite(dumpBuf, 1, full_nsp_header_size, outFile); if (write_res != full_nsp_header_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to write %lu bytes placeholder data to file offset 0x%016lX! (wrote %lu bytes)", 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__, full_nsp_header_size, (u64)0, write_res); goto out; } @@ -1985,7 +1846,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd u64 startFileOffset; // Dump all NCAs excluding the CNMT NCA - for(i = startFileIndex; i < (titleContentRecordsCnt - 1); i++, startFileIndex++) + for(i = startFileIndex; i < (titleContentInfoCnt - 1); i++, startFileIndex++) { n = DUMP_BUFFER_SIZE; @@ -2025,14 +1886,19 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } } - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, nca_offset, dumpBuf, n); - if (R_FAILED(result)) + breaks = (progressCtx.line_offset + 2); + + proceed = readNcaDataByContentId(&ncmStorage, &ncaId, nca_offset, dumpBuf, n); + if (!proceed) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to read %lu bytes chunk at offset 0x%016lX from NCA \"%s\"! (0x%08X)", n, nca_offset, xml_content_info[i].nca_id_str, result); - proceed = false; + 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) { @@ -2098,7 +1964,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); + 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; } @@ -2110,12 +1976,12 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (((seqDumpMode && !seqDumpFinish) || !seqDumpMode) && (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)) { splitIndex++; - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp%c%02u", NSP_DUMP_PATH, dumpName, (seqDumpMode ? '.' : '/'), 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, "Failed to open output file for part #%u!", splitIndex); + 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; } @@ -2125,7 +1991,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); + 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; } @@ -2135,7 +2001,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); + 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) { @@ -2151,26 +2017,24 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (seqDumpMode) progressCtx.seqDumpCurOffset = seqDumpSessionOffset; printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - if (seqDumpMode) seqDumpFileRemove = true; - break; - } + 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) { - success = true; + ret = 0; break; } } @@ -2197,7 +2061,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (seqDumpMode) memcpy(seqDumpNcaHashes + (i * SHA256_HASH_SIZE), xml_content_info[i].hash, SHA256_HASH_SIZE); } - if (!proceed || success) goto out; + if (!proceed || ret >= 0) goto out; uiFill(0, ((progressCtx.line_offset - 4) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 4, BG_COLOR_RGB); @@ -2225,13 +2089,13 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd char ncaFileName[100] = {'\0'}; u64 cur_file_size = 0; - if (i < titleContentRecordsCnt) + if (i < titleContentInfoCnt) { - // Always reserve the first titleContentRecordsCnt entries for our NCA contents + // 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 == titleContentRecordsCnt) + 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); @@ -2245,21 +2109,21 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd // Ticket (if available) // Certificate chain (if available) - if (programInfoXml && i == (titleContentRecordsCnt + 1)) + 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 <= (titleContentRecordsCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentRecordsCnt + 1 + nacpIconCnt)))) + 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 - (titleContentRecordsCnt + 1)) : (i - (titleContentRecordsCnt + 2))); + 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 == (titleContentRecordsCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentRecordsCnt + 1 + nacpIconCnt + 1)))) + 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 @@ -2291,8 +2155,8 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd // Write our full PFS0 header memcpy(dumpBuf, &nspPfs0Header, sizeof(pfs0_header)); - memcpy(dumpBuf + sizeof(pfs0_header), nspPfs0EntryTable, (u64)nspFileCount * sizeof(pfs0_entry_table)); - memcpy(dumpBuf + sizeof(pfs0_header) + ((u64)nspFileCount * sizeof(pfs0_entry_table)), nspPfs0StrTable, nspPfs0Header.str_table_size); + 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); if (seqDumpMode) { @@ -2303,35 +2167,37 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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) + if (curFreeSpace < full_nsp_header_size) { - pfs0HeaderFile = fopen(pfs0HeaderFilename, "wb"); - if (pfs0HeaderFile) - { - write_res = fwrite(dumpBuf, 1, full_nsp_header_size, pfs0HeaderFile); - fclose(pfs0HeaderFile); - - if (write_res == full_nsp_header_size) - { - // Update free space - freeSpace -= full_nsp_header_size; - } else { - setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes PFS0 header file! (wrote %lu bytes)", full_nsp_header_size, write_res); - unlink(pfs0HeaderFilename); - goto out; - } - } else { - setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: failed to create PFS0 header file!"); - goto out; - } - } else { // Finish current sequential dump session seqDumpFinish = true; - success = 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; } } else { if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) @@ -2342,13 +2208,13 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd outFile = NULL; } - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, 0); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, 0); outFile = fopen(dumpPath, "rb+"); if (!outFile) { setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to re-open output file for part #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to re-open output file for part #0!", __func__); goto out; } } else { @@ -2359,7 +2225,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (write_res != full_nsp_header_size) { setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to write %lu bytes PFS0 header to file offset 0x%016lX! (wrote %lu bytes)", 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__, full_nsp_header_size, (u64)0, write_res); goto out; } @@ -2371,13 +2237,13 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd outFile = NULL; } - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex); + 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, "Failed to re-open output file for part #%u!", splitIndex); + 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; } } @@ -2385,7 +2251,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd fseek(outFile, 0, SEEK_END); } - startFileIndex = ((seqDumpMode && seqNspCtx.fileIndex > (titleContentRecordsCnt - 1)) ? seqNspCtx.fileIndex : (titleContentRecordsCnt - 1)); + 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++) @@ -2397,32 +2263,32 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd char ncaFileName[100] = {'\0'}; u64 cur_file_size = 0; - if (i == (titleContentRecordsCnt - 1)) + 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 == titleContentRecordsCnt) + 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 == (titleContentRecordsCnt + 1)) + 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 <= (titleContentRecordsCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentRecordsCnt + 1 + nacpIconCnt)))) + if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) { // NACP icon entry - u32 icon_idx = (!programInfoXml ? (i - (titleContentRecordsCnt + 1)) : (i - (titleContentRecordsCnt + 2))); + 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 == (titleContentRecordsCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentRecordsCnt + 1 + nacpIconCnt + 1)))) + 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); @@ -2473,28 +2339,28 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd } // Retrieve data from its respective source - if (i == (titleContentRecordsCnt - 1)) + if (i == (titleContentInfoCnt - 1)) { // CNMT NCA memcpy(dumpBuf, cnmtNcaBuf + nca_offset, n); } else - if (i == titleContentRecordsCnt) + if (i == titleContentInfoCnt) { // CNMT XML memcpy(dumpBuf, cnmtXml + nca_offset, n); } else { - if (programInfoXml && i == (titleContentRecordsCnt + 1)) + if (programInfoXml && i == (titleContentInfoCnt + 1)) { // programinfo.xml entry memcpy(dumpBuf, programInfoXml + nca_offset, n); } else - if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentRecordsCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentRecordsCnt + 1 + nacpIconCnt)))) + if (nacpIcons && nacpIconCnt && ((!programInfoXml && i <= (titleContentInfoCnt + nacpIconCnt)) || (programInfoXml && i <= (titleContentInfoCnt + 1 + nacpIconCnt)))) { // NACP icon entry - u32 icon_idx = (!programInfoXml ? (i - (titleContentRecordsCnt + 1)) : (i - (titleContentRecordsCnt + 2))); + 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 == (titleContentRecordsCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentRecordsCnt + 1 + nacpIconCnt + 1)))) + if (nacpXml && ((!programInfoXml && i == (titleContentInfoCnt + nacpIconCnt + 1)) || (programInfoXml && i == (titleContentInfoCnt + 1 + nacpIconCnt + 1)))) { // NACP XML entry memcpy(dumpBuf, nacpXml + nca_offset, n); @@ -2524,7 +2390,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); + 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; } @@ -2536,12 +2402,12 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (((seqDumpMode && !seqDumpFinish) || !seqDumpMode) && (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)) { splitIndex++; - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp%c%02u", NSP_DUMP_PATH, dumpName, (seqDumpMode ? '.' : '/'), 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, "Failed to open output file for part #%u!", splitIndex); + 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; } @@ -2551,7 +2417,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); + 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; } @@ -2561,7 +2427,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); + 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) { @@ -2577,21 +2443,19 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd if (seqDumpMode) progressCtx.seqDumpCurOffset = seqDumpSessionOffset; printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - if (seqDumpMode) seqDumpFileRemove = true; - break; - } + 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; @@ -2616,176 +2480,21 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd breaks = (progressCtx.line_offset + 2); - if (progressCtx.curOffset >= progressCtx.totalSize || (seqDumpMode && seqDumpFinish)) success = true; + if (progressCtx.curOffset >= progressCtx.totalSize || (seqDumpMode && seqDumpFinish)) ret = 0; - if (!success) + if (ret < 0) { setProgressBarError(&progressCtx); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Unexpected underdump error! Wrote %lu bytes, expected %lu bytes.", progressCtx.curOffset, progressCtx.totalSize); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: underdump error! Wrote %lu bytes, expected %lu bytes.", __func__, progressCtx.curOffset, progressCtx.totalSize); if (seqDumpMode) seqDumpFileRemove = true; goto out; } - // Calculate CRC32 checksum - if (!batch && calcCrc) - { - // Finalize dump - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); - progressCtx.now -= progressCtx.start; - - progressCtx.progress = 100; - progressCtx.remainingTime = 0; - - printProgressBar(&progressCtx, false, 0); - - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); - - uiRefreshDisplay(); - - breaks += 2; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "CRC32 checksum calculation will begin in %u seconds...", DUMP_NSP_CRC_WAIT); - uiRefreshDisplay(); - - delay(DUMP_NSP_CRC_WAIT); - - breaks = initial_breaks; - uiFill(0, (breaks * LINE_HEIGHT) + 8, FB_WIDTH, FB_HEIGHT - (breaks * LINE_HEIGHT), BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Calculating CRC32 checksum. Hold %s to cancel.", NINTENDO_FONT_B); - breaks += 2; - - if (outFile) - { - fclose(outFile); - outFile = NULL; - } - - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) - { - splitIndex = 0; - sprintf(tmp_idx, "/%02u", splitIndex); - strcat(dumpPath, tmp_idx); - } - - outFile = fopen(dumpPath, "rb"); - if (outFile) - { - n = DUMP_BUFFER_SIZE; - progressCtx.start = progressCtx.now = progressCtx.remainingTime = 0; - progressCtx.lastSpeed = progressCtx.averageSpeed = 0.0; - - size_t read_res; - - progressCtx.line_offset = (breaks + 2); - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); - - for(progressCtx.curOffset = 0; progressCtx.curOffset < progressCtx.totalSize; progressCtx.curOffset += n) - { - uiFill(0, ((progressCtx.line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "File: \"%s\".", strrchr(dumpPath, '/' ) + 1); - - if (n > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset); - - if (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) - { - read_res = fread(dumpBuf, 1, old_file_chunk_size, outFile); - if (read_res != old_file_chunk_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to read %lu bytes chunk from offset 0x%016lX from part #%02u! (read %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, read_res); - proceed = false; - break; - } - } - - fclose(outFile); - outFile = NULL; - - if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize) - { - splitIndex++; - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex); - - outFile = fopen(dumpPath, "rb"); - if (!outFile) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to re-open output file for part #%u!", splitIndex); - proceed = false; - break; - } - - if (new_file_chunk_size > 0) - { - read_res = fread(dumpBuf + old_file_chunk_size, 1, new_file_chunk_size, outFile); - if (read_res != new_file_chunk_size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to read %lu bytes chunk from offset 0x%016lX from part #%02u! (read %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, read_res); - proceed = false; - break; - } - } - } - } else { - read_res = fread(dumpBuf, 1, n, outFile); - if (read_res != n) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to read %lu bytes chunk from offset 0x%016lX! (read %lu bytes)", n, progressCtx.curOffset, read_res); - proceed = false; - break; - } - } - - // Update CRC32 - crc32(dumpBuf, n, &crc); - - printProgressBar(&progressCtx, true, n); - - if ((progressCtx.curOffset + n) < progressCtx.totalSize) - { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - break; - } - } - } - - breaks = (progressCtx.line_offset + 2); - - if (proceed) - { - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); - progressCtx.now -= progressCtx.start; - - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "NSP dump CRC32 checksum: %08X", crc); - breaks += 2; - } else { - setProgressBarError(&progressCtx); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to re-open output file in read mode!"); - breaks += 2; - } - } - // Set archive bit (only for FAT32) if (!seqDumpMode && progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - result = fsdevSetArchiveBit(dumpPath); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + result = fsdevSetConcatenationFileAttribute(dumpPath); if (R_FAILED(result)) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Warning: failed to set archive bit on output directory! (0x%08X)", result); @@ -2796,7 +2505,7 @@ bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleInd out: if (outFile) fclose(outFile); - if (success) + if (ret >= 0) { if (seqDumpMode) { @@ -2812,7 +2521,7 @@ out: // 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 (seqNspCtx.fileIndex < titleContentRecordsCnt && seqNspCtx.fileIndex != cnmtNcaIndex) + if (seqNspCtx.fileIndex < titleContentInfoCnt && seqNspCtx.fileIndex != cnmtNcaIndex) { memcpy(&(seqNspCtx.hashCtx), &nca_hash_ctx, sizeof(Sha256Context)); } else { @@ -2827,13 +2536,13 @@ out: write_res = fwrite(seqDumpNcaHashes, 1, seqDumpFileSize - sizeof(sequentialNspCtx), seqDumpFile); if (write_res != (seqDumpFileSize - sizeof(sequentialNspCtx))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", seqDumpFileSize - sizeof(sequentialNspCtx), write_res); - success = false; + 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); + ret = -1; seqDumpFileRemove = true; } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to write %lu bytes chunk to the sequential dump reference file! (wrote %lu bytes)", sizeof(sequentialNspCtx), write_res); - success = false; + 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__, sizeof(sequentialNspCtx), write_res); + ret = -1; seqDumpFileRemove = true; } } else { @@ -2842,7 +2551,7 @@ out: } } - if (success && !batch && !calcCrc) + if (ret >= 0 && !batch) { timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; @@ -2855,8 +2564,33 @@ out: printProgressBar(&progressCtx, false, 0); - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); + uiRefreshDisplay(); + + // Only perform the checksum lookup if we have finished the dump process + if (useNoIntroLookup && (!seqDumpMode || (seqDumpMode && !seqDumpFinish))) + { + if (curStorageId != NcmStorageId_GameCard && !tiklessDump) + { + // Calculate CRC32 checksum for the CNMT NCA + crc32(cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size, &crc); + + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "CNMT NCA CRC32 checksum: %08X.", crc); + uiRefreshDisplay(); + breaks++; + + // Perform checksum lookup + noIntroDumpCheck(true, crc); + } else { + if (curStorageId != NcmStorageId_GameCard && tiklessDump) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dump verification disabled (not compatible with NSP dumps with modified NCAs)."); + } + } + } if (seqDumpMode) { @@ -2895,11 +2629,11 @@ out: { for(u8 i = 0; i <= splitIndex; i++) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp.%02u", NSP_DUMP_PATH, dumpName, i); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp.%02u", NSP_DUMP_PATH, dumpName, i); unlink(dumpPath); } } else { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { @@ -2935,24 +2669,11 @@ out: if (xml_content_info) free(xml_content_info); - serviceClose(&(ncmStorage.s)); + ncmContentStorageClose(&ncmStorage); - if (curStorageId == FsStorageId_GameCard) - { - fsStorageClose(&gameCardStorage); - - if (partitionHfs0Header) - { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - } - } + if (curStorageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); - if (titleContentRecords) free(titleContentRecords); + if (titleContentInfos) free(titleContentInfos); if (seqDumpNcaHashes) free(seqDumpNcaHashes); @@ -2962,16 +2683,26 @@ out: if (dumpName) free(dumpName); - return success; + return ret; } -bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) +int batchEntryCmp(const void *a, const void *b) { + batchEntry *batchEntry1 = (batchEntry*)a; + batchEntry *batchEntry2 = (batchEntry*)b; + + return strcasecmp(batchEntry1->nspFilename, batchEntry2->nspFilename); +} + +int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) +{ + int ret = -1; + if (!batchDumpCfg) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid batch dump configuration struct!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid batch dump configuration struct!", __func__); breaks += 2; - return false; + return ret; } bool dumpAppTitles = batchDumpCfg->dumpAppTitles; @@ -2981,25 +2712,28 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) bool removeConsoleData = batchDumpCfg->removeConsoleData; bool tiklessDump = batchDumpCfg->tiklessDump; bool npdmAcidRsaPatch = batchDumpCfg->npdmAcidRsaPatch; + bool dumpDeltaFragments = batchDumpCfg->dumpDeltaFragments; bool skipDumpedTitles = batchDumpCfg->skipDumpedTitles; bool rememberDumpedTitles = batchDumpCfg->rememberDumpedTitles; + bool haltOnErrors = batchDumpCfg->haltOnErrors; + bool useBrackets = batchDumpCfg->useBrackets; batchModeSourceStorage batchModeSrc = batchDumpCfg->batchModeSrc; - if ((!dumpAppTitles && !dumpPatchTitles && !dumpAddOnTitles) || (batchModeSrc == BATCH_SOURCE_ALL && ((dumpAppTitles && !titleAppCount) || (dumpPatchTitles && !titlePatchCount) || (dumpAddOnTitles && !titleAddOnCount))) || (batchModeSrc == BATCH_SOURCE_SDCARD && ((dumpAppTitles && !sdCardTitleAppCount) || (dumpPatchTitles && !sdCardTitlePatchCount) || (dumpAddOnTitles && !sdCardTitleAddOnCount))) || (batchModeSrc == BATCH_SOURCE_EMMC && ((dumpAppTitles && !nandUserTitleAppCount) || (dumpPatchTitles && !nandUserTitlePatchCount) || (dumpAddOnTitles && !nandUserTitleAddOnCount))) || batchModeSrc >= BATCH_SOURCE_CNT) + if ((!dumpAppTitles && !dumpPatchTitles && !dumpAddOnTitles) || (batchModeSrc == BATCH_SOURCE_ALL && ((dumpAppTitles && !titleAppCount) || (dumpPatchTitles && !titlePatchCount) || (dumpAddOnTitles && !titleAddOnCount))) || (batchModeSrc == BATCH_SOURCE_SDCARD && ((dumpAppTitles && !sdCardTitleAppCount) || (dumpPatchTitles && !sdCardTitlePatchCount) || (dumpAddOnTitles && !sdCardTitleAddOnCount))) || (batchModeSrc == BATCH_SOURCE_EMMC && ((dumpAppTitles && !emmcTitleAppCount) || (dumpPatchTitles && !emmcTitlePatchCount) || (dumpAddOnTitles && !emmcTitleAddOnCount))) || batchModeSrc >= BATCH_SOURCE_CNT) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to perform batch NSP dump!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform batch NSP dump!", __func__); breaks += 2; - return false; + return ret; } u32 i, j; u32 totalTitleCount = 0, totalAppCount = 0, totalPatchCount = 0, totalAddOnCount = 0; - u32 titleCount, titleIndex; + u32 titleCount = 0, titleIndex = 0; char *dumpName = NULL; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; char summary_str[256] = {'\0'}; int initial_breaks = breaks, cur_breaks; @@ -3007,53 +2741,79 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) const u32 maxSummaryFileCount = 8; u32 summaryPage = 0, selectedSummaryEntry = 0; u32 xpos = 0, ypos = 0; - u32 keysDown = 0, keysHeld = 0; + u64 keysDown = 0, keysHeld = 0; u32 maxEntryCount = 0, batchEntryIndex = 0, disabledEntryCount = 0; - batchEntry *batchEntries = NULL; + batchEntry *batchEntries = NULL, *tmpBatchEntries = NULL; - bool proceed = true, success = false; + bool proceed = true; // Generate NSP configuration struct nspOptions nspDumpCfg; nspDumpCfg.isFat32 = isFat32; - nspDumpCfg.calcCrc = false; + nspDumpCfg.useNoIntroLookup = false; nspDumpCfg.removeConsoleData = removeConsoleData; nspDumpCfg.tiklessDump = tiklessDump; nspDumpCfg.npdmAcidRsaPatch = npdmAcidRsaPatch; + nspDumpCfg.dumpDeltaFragments = dumpDeltaFragments; + nspDumpCfg.useBrackets = useBrackets; // Allocate memory for the batch entries - if (dumpAppTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titleAppCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAppCount : nandUserTitleAppCount)); - if (dumpPatchTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titlePatchCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitlePatchCount : nandUserTitlePatchCount)); - if (dumpAppTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titleAddOnCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAddOnCount : nandUserTitleAddOnCount)); + if (dumpAppTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titleAppCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAppCount : emmcTitleAppCount)); + if (dumpPatchTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titlePatchCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitlePatchCount : emmcTitlePatchCount)); + if (dumpAddOnTitles) maxEntryCount += (batchModeSrc == BATCH_SOURCE_ALL ? titleAddOnCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAddOnCount : emmcTitleAddOnCount)); batchEntries = calloc(maxEntryCount, sizeof(batchEntry)); if (!batchEntries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for batch entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for batch entries!", __func__); breaks += 2; - return false; + return ret; } - if (dumpAppTitles) + for(i = 0; i < 3; i++) { - titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titleAppCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAppCount : nandUserTitleAppCount)); + if ((i == 0 && !dumpAppTitles) || (i == 1 && !dumpPatchTitles) || (i == 2 && !dumpAddOnTitles)) continue; - for(i = 0; i < titleCount; i++) + u32 emmcRefTitleCount = 0; + nspDumpType curNspDumpType = DUMP_APP_NSP; + + switch(i) { - titleIndex = ((batchModeSrc == BATCH_SOURCE_ALL || batchModeSrc == BATCH_SOURCE_SDCARD) ? i : (i + sdCardTitleAppCount)); + case 0: + titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titleAppCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAppCount : emmcTitleAppCount)); + emmcRefTitleCount = sdCardTitleAppCount; + curNspDumpType = DUMP_APP_NSP; + break; + case 1: + titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titlePatchCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitlePatchCount : emmcTitlePatchCount)); + emmcRefTitleCount = sdCardTitlePatchCount; + curNspDumpType = DUMP_PATCH_NSP; + break; + case 2: + titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titleAddOnCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAddOnCount : emmcTitleAddOnCount)); + emmcRefTitleCount = sdCardTitleAddOnCount; + curNspDumpType = DUMP_ADDON_NSP; + break; + default: + break; + } + + for(j = 0; j < titleCount; j++) + { + titleIndex = ((batchModeSrc == BATCH_SOURCE_ALL || batchModeSrc == BATCH_SOURCE_SDCARD) ? j : (j + emmcRefTitleCount)); - dumpName = generateNSPDumpName(DUMP_APP_NSP, titleIndex); + dumpName = generateNSPDumpName(curNspDumpType, titleIndex, false); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; goto out; } // Check if an override file already exists for this dump - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); if (checkIfFileExists(dumpPath)) { @@ -3062,8 +2822,26 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) continue; } + snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_CHARACTERS(batchEntries[batchEntryIndex].nspFilename), strrchr(dumpPath, '/') + 1); + snprintf(batchEntries[batchEntryIndex].truncatedNspFilename, MAX_CHARACTERS(batchEntries[batchEntryIndex].truncatedNspFilename), batchEntries[batchEntryIndex].nspFilename); + + if (useBrackets) + { + // Generate output name with brackets + free(dumpName); + dumpName = NULL; + + dumpName = generateNSPDumpName(curNspDumpType, titleIndex, true); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name with brackets!", __func__); + breaks += 2; + goto out; + } + } + // Check if this title has already been dumped - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); free(dumpName); dumpName = NULL; @@ -3072,174 +2850,30 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) // Save title properties batchEntries[batchEntryIndex].enabled = true; - batchEntries[batchEntryIndex].titleType = DUMP_APP_NSP; + batchEntries[batchEntryIndex].titleType = curNspDumpType; batchEntries[batchEntryIndex].titleIndex = titleIndex; - snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].nspFilename), strrchr(dumpPath, '/') + 1); + batchEntries[batchEntryIndex].contentSize = (i == 0 ? baseAppEntries[titleIndex].contentSize : (i == 1 ? patchEntries[titleIndex].contentSize : addOnEntries[titleIndex].contentSize)); + batchEntries[batchEntryIndex].contentSizeStr = (i == 0 ? baseAppEntries[titleIndex].contentSizeStr : (i == 1 ? patchEntries[titleIndex].contentSizeStr : addOnEntries[titleIndex].contentSizeStr)); // Fix entry name length - snprintf(batchEntries[batchEntryIndex].truncatedNspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].truncatedNspFilename), batchEntries[batchEntryIndex].nspFilename); - - u32 strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - - if ((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - while((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - batchEntries[batchEntryIndex].truncatedNspFilename[strlen(batchEntries[batchEntryIndex].truncatedNspFilename) - 1] = '\0'; - strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - } - - strcat(batchEntries[batchEntryIndex].truncatedNspFilename, "..."); - } + truncateBrowserEntryName(batchEntries[batchEntryIndex].truncatedNspFilename); // Increase batch entry index batchEntryIndex++; // Increase total base application count - totalAppCount++; - } - - // Increase total title count - totalTitleCount += totalAppCount; - } - - // Retrieve information for orphan entries - generateOrphanPatchOrAddOnList(); - - if (dumpPatchTitles) - { - titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titlePatchCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitlePatchCount : nandUserTitlePatchCount)); - - for(i = 0; i < titleCount; i++) - { - titleIndex = ((batchModeSrc == BATCH_SOURCE_ALL || batchModeSrc == BATCH_SOURCE_SDCARD) ? i : (i + sdCardTitlePatchCount)); - - dumpName = generateNSPDumpName(DUMP_PATCH_NSP, titleIndex); - if (!dumpName) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - goto out; - } - - // Check if an override file already exists for this dump - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); - - if (checkIfFileExists(dumpPath)) - { - free(dumpName); - dumpName = NULL; - continue; - } - - // Check if this title has already been dumped - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - - free(dumpName); - dumpName = NULL; - - if (skipDumpedTitles && checkIfFileExists(dumpPath)) continue; - - // Save title properties - batchEntries[batchEntryIndex].enabled = true; - batchEntries[batchEntryIndex].titleType = DUMP_PATCH_NSP; - batchEntries[batchEntryIndex].titleIndex = titleIndex; - snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].nspFilename), strrchr(dumpPath, '/') + 1); - - // Fix entry name length - snprintf(batchEntries[batchEntryIndex].truncatedNspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].truncatedNspFilename), batchEntries[batchEntryIndex].nspFilename); - - u32 strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - - if ((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - while((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - batchEntries[batchEntryIndex].truncatedNspFilename[strlen(batchEntries[batchEntryIndex].truncatedNspFilename) - 1] = '\0'; - strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - } - - strcat(batchEntries[batchEntryIndex].truncatedNspFilename, "..."); - } - - // Increase batch entry index - batchEntryIndex++; + if (i == 0) totalAppCount++; // Increase total patch count - totalPatchCount++; - } - - // Increase total title count - totalTitleCount += totalPatchCount; - } - - if (dumpAddOnTitles) - { - titleCount = (batchModeSrc == BATCH_SOURCE_ALL ? titleAddOnCount : (batchModeSrc == BATCH_SOURCE_SDCARD ? sdCardTitleAddOnCount : nandUserTitleAddOnCount)); - - for(i = 0; i < titleCount; i++) - { - titleIndex = ((batchModeSrc == BATCH_SOURCE_ALL || batchModeSrc == BATCH_SOURCE_SDCARD) ? i : (i + sdCardTitleAddOnCount)); - - dumpName = generateNSPDumpName(DUMP_ADDON_NSP, titleIndex); - if (!dumpName) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - goto out; - } - - // Check if an override file already exists for this dump - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", BATCH_OVERRIDES_PATH, dumpName); - - if (checkIfFileExists(dumpPath)) - { - free(dumpName); - dumpName = NULL; - continue; - } - - // Check if this title has already been dumped - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - - free(dumpName); - dumpName = NULL; - - if (skipDumpedTitles && checkIfFileExists(dumpPath)) continue; - - // Save title properties - batchEntries[batchEntryIndex].enabled = true; - batchEntries[batchEntryIndex].titleType = DUMP_ADDON_NSP; - batchEntries[batchEntryIndex].titleIndex = titleIndex; - snprintf(batchEntries[batchEntryIndex].nspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].nspFilename), strrchr(dumpPath, '/') + 1); - - // Fix entry name length - snprintf(batchEntries[batchEntryIndex].truncatedNspFilename, MAX_ELEMENTS(batchEntries[batchEntryIndex].truncatedNspFilename), batchEntries[batchEntryIndex].nspFilename); - - u32 strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - - if ((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - while((8 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - batchEntries[batchEntryIndex].truncatedNspFilename[strlen(batchEntries[batchEntryIndex].truncatedNspFilename) - 1] = '\0'; - strWidth = uiGetStrWidth(batchEntries[batchEntryIndex].truncatedNspFilename); - } - - strcat(batchEntries[batchEntryIndex].truncatedNspFilename, "..."); - } - - // Increase batch entry index - batchEntryIndex++; + if (i == 1) totalPatchCount++; // Increase total addon count - totalAddOnCount++; + if (i == 2) totalAddOnCount++; } - - // Increase total title count - totalTitleCount += totalAddOnCount; } + // Calculate total title count + totalTitleCount = (totalAppCount + totalPatchCount + totalAddOnCount); if (!totalTitleCount) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "You have already dumped all titles matching the selected settings!"); @@ -3247,42 +2881,63 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) goto out; } + // Sort batch entries by name + qsort(batchEntries, totalTitleCount, sizeof(batchEntry), batchEntryCmp); + + if (totalTitleCount < maxEntryCount) + { + tmpBatchEntries = realloc(batchEntries, totalTitleCount * sizeof(batchEntry)); + if (!tmpBatchEntries) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "%s: failed to reallocate batch entries!", __func__); + breaks += 2; + goto out; + } + + batchEntries = tmpBatchEntries; + tmpBatchEntries = NULL; + } + // Display summary controls if (totalTitleCount > maxSummaryFileCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_ZL " / " NINTENDO_FONT_ZR " ] Change page | [ " NINTENDO_FONT_A " ] Proceed | [ " NINTENDO_FONT_B " ] Cancel | [ " NINTENDO_FONT_Y " ] Toggle selected entry | [ " NINTENDO_FONT_L " ] Disable all entries | [ " NINTENDO_FONT_R " ] Enable all entries"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_ZL " / " NINTENDO_FONT_ZR " ] Change page | [ " NINTENDO_FONT_A " ] Proceed | [ " NINTENDO_FONT_B " ] Cancel | [ " NINTENDO_FONT_Y " ] Toggle selected entry | [ " NINTENDO_FONT_L " ] Disable all entries | [ " NINTENDO_FONT_R " ] Enable all entries\n[ " NINTENDO_FONT_PLUS " ] Exit"); } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_A " ] Proceed | [ " NINTENDO_FONT_B " ] Cancel | [ " NINTENDO_FONT_Y " ] Toggle selected entry | [ " NINTENDO_FONT_L " ] Disable all entries | [ " NINTENDO_FONT_R " ] Enable all entries"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_A " ] Proceed | [ " NINTENDO_FONT_B " ] Cancel | [ " NINTENDO_FONT_Y " ] Toggle selected entry | [ " NINTENDO_FONT_L " ] Disable all entries | [ " NINTENDO_FONT_R " ] Enable all entries | [ " NINTENDO_FONT_PLUS " ] Exit"); } breaks += 2; + // Display free space + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Free SD card space: %s (%lu bytes).", freeSpaceStr, freeSpace); + breaks += 2; + // Display summary uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Summary:"); breaks += 2; if (totalAppCount) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "BASE: %u", totalAppCount); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "BASE: %u", totalAppCount); strcat(summary_str, dumpPath); } if (totalPatchCount) { if (totalAppCount) strcat(summary_str, " | "); - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "UPD: %u", totalPatchCount); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "UPD: %u", totalPatchCount); strcat(summary_str, dumpPath); } if (totalAddOnCount) { if (totalAppCount || totalPatchCount) strcat(summary_str, " | "); - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "DLC: %u", totalAddOnCount); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "DLC: %u", totalAddOnCount); strcat(summary_str, dumpPath); } strcat(summary_str, " | "); - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "Total: %u", totalTitleCount); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "Total: %u", totalTitleCount); strcat(summary_str, dumpPath); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, summary_str); @@ -3292,20 +2947,39 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) { cur_breaks = breaks; - uiFill(0, 8 + (cur_breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (cur_breaks * LINE_HEIGHT)) - ((3 * LINE_HEIGHT) + 8), BG_COLOR_RGB); + uiFill(0, 8 + (cur_breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (cur_breaks * LINE_HEIGHT)), BG_COLOR_RGB); // Calculate the number of selected titles j = 0; + u64 totalOutSize = 0; + char totalOutSizeStr[32] = {'\0'}; + for(i = 0; i < totalTitleCount; i++) { - if (batchEntries[i].enabled) j++; + if (batchEntries[i].enabled) + { + j++; + totalOutSize += batchEntries[i].contentSize; + } } + convertSize(totalOutSize, totalOutSizeStr, MAX_CHARACTERS(totalOutSizeStr)); + if (totalTitleCount > maxSummaryFileCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Current page: %u | Selected titles: %u", summaryPage + 1, j); + if (j && totalOutSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Current page: %u | Selected titles: %u | Approximate total dump size: %s (%lu bytes)", summaryPage + 1, j, totalOutSizeStr, totalOutSize); + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Current page: %u | Selected titles: %u", summaryPage + 1, j); + } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Selected titles: %u", j); + if (j && totalOutSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Selected titles: %u | Approximate total dump size: %s (%lu bytes)", j, totalOutSizeStr, totalOutSize); + } else { + uiDrawString(STRING_X_POS, STRING_Y_POS(cur_breaks), FONT_COLOR_RGB, "Selected titles: %u", j); + } } cur_breaks += 2; @@ -3332,8 +3006,10 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) 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); } 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 (i == selectedSummaryEntry) highlight = false; @@ -3352,6 +3028,14 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH))) break; } + // Exit + if (keysDown & KEY_PLUS) + { + ret = -2; + proceed = false; + break; + } + // Start batch dump process if (keysDown & KEY_A) { @@ -3446,13 +3130,19 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) } } + uiClearStatusMsg(); + breaks = initial_breaks; uiFill(0, 8 + (breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (breaks * LINE_HEIGHT)), BG_COLOR_RGB); if (!proceed) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled"); - breaks += 2; + if (ret != -2) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled"); + breaks += 2; + } + goto out; } @@ -3476,24 +3166,38 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) breaks = initial_breaks; uiFill(0, 8 + (breaks * LINE_HEIGHT), FB_WIDTH, FB_HEIGHT - (8 + (breaks * LINE_HEIGHT)), BG_COLOR_RGB); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Title: %u / %u.", j + 1, totalTitleCount - disabledEntryCount); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Title: %.*s [%u / %u].", strlen(batchEntries[i].nspFilename) - 4, batchEntries[i].nspFilename, j + 1, totalTitleCount - disabledEntryCount); + breaks++; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Free SD card space: %s (%lu bytes).", freeSpaceStr, freeSpace); + breaks++; + uiRefreshDisplay(); - breaks += 2; - // Dump title - if (!dumpNintendoSubmissionPackage(batchEntries[i].titleType, batchEntries[i].titleIndex, &nspDumpCfg, true)) goto out; - - // Create override file if necessary - if (rememberDumpedTitles) + int nspRet = dumpNintendoSubmissionPackage(batchEntries[i].titleType, batchEntries[i].titleIndex, &nspDumpCfg, true); + if (nspRet >= 0) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", BATCH_OVERRIDES_PATH, batchEntries[i].nspFilename); - FILE *overrideFile = fopen(dumpPath, "wb"); - if (overrideFile) fclose(overrideFile); + // 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"); + if (overrideFile) fclose(overrideFile); + } + } else { + // If "Halt dump process on errors" is disabled, just wait a little bit and keep going (unless the process was truly canceled by the user) + if (!haltOnErrors && nspRet != -2) + { + delay(5); + } else { + goto out; + } } // Update free space - uiUpdateFreeSpace(); + updateFreeSpace(); j++; } @@ -3501,25 +3205,31 @@ bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed!"); breaks += 2; + ret = 0; + out: - free(batchEntries); + if (batchEntries) free(batchEntries); - freeOrphanPatchOrAddOnList(); - - return success; + return ret; } bool dumpRawHfs0Partition(u32 partition, bool doSplitting) { + if (!gameCardInfo.rootHfs0Header || !gameCardInfo.hfs0PartitionCnt || partition >= gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].size) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to dump raw HFS0 partition!", __func__); + breaks += 2; + return false; + } + Result result; u64 partitionOffset; - bool proceed = true, success = false, fat32_error = false; + bool success = false, fat32_error = false; u64 n = DUMP_BUFFER_SIZE; - FsGameCardHandle handle; - FsStorage gameCardStorage; - char filename[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; FILE *outFile = NULL; u8 splitIndex = 0; + openIStoragePartition storageIndex; memset(dumpBuf, 0, DUMP_BUFFER_SIZE); @@ -3528,255 +3238,247 @@ bool dumpRawHfs0Partition(u32 partition, bool doSplitting) size_t write_res; - char *dumpName = generateFullDumpName(); + char *dumpName = generateGameCardDumpName(false); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; return false; } - workaroundPartitionZeroAccess(); - - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_SUCCEEDED(result)) - { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "GetGameCardHandle succeeded: 0x%08X", handle.value); - breaks++;*/ - - // Ugly hack - // The IStorage instance returned for partition == 0 contains the gamecard header, the gamecard certificate, the root HFS0 header and: - // * The "update" (0) partition and the "normal" (1) partition (for gamecard type 0x01) - // * The "update" (0) partition, the "logo" (1) partition and the "normal" (2) partition (for gamecard type 0x02) - // The IStorage instance returned for partition == 1 contains the "secure" partition (which can either be 2 or 3 depending on the gamecard type) - // This ugly hack makes sure we just dump the *actual* raw HFS0 partition, without preceding data, padding, etc. - // Oddly enough, IFileSystem instances actually point to the specified partition ID filesystem. I don't understand why it doesn't work like that for IStorage, but whatever - // NOTE: Using partition == 2 returns error 0x149002, and using higher values probably do so, too - - result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)); - if (R_SUCCEEDED(result)) - { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "OpenGameCardStorage succeeded: 0x%08X", handle.value); - breaks++;*/ - - if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionOffset, &(progressCtx.totalSize))) - { - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "HFS0 partition size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); - breaks += 2; - - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "HFS0 partition offset (relative to IStorage instance): 0x%016lX", partitionOffset); - breaks += 2;*/ - - if (progressCtx.totalSize <= freeSpace) - { - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); - } else { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s - Partition %u (%s).hfs0", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); - } - - // Check if the dump already exists - if (checkIfFileExists(filename)) - { - // Ask the user if they want to proceed anyway - int cur_breaks = breaks; - - proceed = yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?"); - if (!proceed) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); - } 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); - } - } - - if (proceed) - { - outFile = fopen(filename, "wb"); - if (outFile) - { - 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 += 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; - } - - uiRefreshDisplay(); - - progressCtx.line_offset = (breaks + 2); - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); - - for (progressCtx.curOffset = 0; progressCtx.curOffset < progressCtx.totalSize; progressCtx.curOffset += n) - { - uiFill(0, ((progressCtx.line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(filename, '/' ) + 1); - - if (n > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset); - - result = fsStorageRead(&gameCardStorage, partitionOffset + progressCtx.curOffset, dumpBuf, n); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset + progressCtx.curOffset); - break; - } - - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) - { - u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); - break; - } - } - - fclose(outFile); - outFile = NULL; - - if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize) - { - splitIndex++; - snprintf(filename, MAX_ELEMENTS(filename), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); - - outFile = fopen(filename, "wb"); - if (!outFile) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); - 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); - 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); - - if ((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 file splitting."); - fat32_error = true; - } - - break; - } - } - - printProgressBar(&progressCtx, true, n); - - if ((progressCtx.curOffset + n) < progressCtx.totalSize) - { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - break; - } - } - } - - if (progressCtx.curOffset >= progressCtx.totalSize) success = true; - - // Support empty files - if (!progressCtx.totalSize) - { - uiFill(0, ((progressCtx.line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(filename, '/' ) + 1); - - progressCtx.progress = 100; - - printProgressBar(&progressCtx, false, 0); - } - - breaks = (progressCtx.line_offset + 2); - - if (success) - { - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); - progressCtx.now -= progressCtx.start; - - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); - } else { - setProgressBarError(&progressCtx); - if (fat32_error) breaks += 2; - } - - if (outFile) fclose(outFile); - - if (!success) - { - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - for(u8 i = 0; i <= splitIndex; i++) - { - snprintf(filename, MAX_ELEMENTS(filename), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), i); - unlink(filename); - } - } else { - unlink(filename); - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file \"%s\"!", filename); - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to get partition details from the root HFS0 header!"); - } - - fsStorageClose(&gameCardStorage); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); - } + // The IStorage instance returned for partition == 0 contains the gamecard header, the gamecard certificate, the root HFS0 header and: + // * The "update" (0) partition and the "normal" (1) partition (for gamecard type 0x01) + // * The "update" (0) partition, the "logo" (1) partition and the "normal" (2) partition (for gamecard type 0x02) + // The IStorage instance returned for partition == 1 contains the "secure" partition (which can either be 2 or 3 depending on the gamecard type) + // This makes sure we just dump the *actual* raw HFS0 partition, without preceding data, padding, etc. + // Oddly enough, IFileSystem instances actually point to the specified partition ID filesystem. I don't understand why it doesn't work like that for IStorage, but whatever + // NOTE: Using partition == 2 returns error 0x149002, and using higher values probably do so, too + partitionOffset = gameCardInfo.hfs0Partitions[partition].offset; // Relative to IStorage instance + progressCtx.totalSize = gameCardInfo.hfs0Partitions[partition].size; + storageIndex = (openIStoragePartition)(HFS0_TO_ISTORAGE_IDX(gameCardInfo.hfs0PartitionCnt, partition) + 1); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "HFS0 partition size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); breaks += 2; - free(dumpName); + if (progressCtx.totalSize > freeSpace) + { + 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; + } + + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) + { + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition), splitIndex); + } else { + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Partition %u (%s).hfs0", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition)); + } + + // Check if the dump already exists + if (checkIfFileExists(dumpPath)) + { + // Ask the user if they want to proceed anyway + int cur_breaks = breaks; + + if (!yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); + goto out; + } 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); + } + } + + result = openGameCardStoragePartition(storageIndex); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #%u! (0x%08X)", __func__, storageIndex - 1, result); + goto out; + } + + outFile = fopen(dumpPath, "wb"); + if (!outFile) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); + 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++; + + uiRefreshDisplay(); + + progressCtx.line_offset = (breaks + 2); + timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); + + for (progressCtx.curOffset = 0; progressCtx.curOffset < progressCtx.totalSize; progressCtx.curOffset += n) + { + uiFill(0, ((progressCtx.line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); + + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1); + + if (n > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset); + + result = readGameCardStoragePartition(partitionOffset + progressCtx.curOffset, dumpBuf, n); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk at offset 0x%016lX from IStorage partition #%u! (0x%08X)", __func__, n, partitionOffset + progressCtx.curOffset, storageIndex - 1, result); + break; + } + + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + { + u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_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); + break; + } + } + + fclose(outFile); + outFile = NULL; + + if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize) + { + splitIndex++; + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition), 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); + 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); + 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 ((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 file splitting."); + fat32_error = true; + } + + break; + } + } + + 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."); + break; + } + } + + if (progressCtx.curOffset >= progressCtx.totalSize) success = true; + + // Support empty files + if (!progressCtx.totalSize) + { + uiFill(0, ((progressCtx.line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); + + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1); + + progressCtx.progress = 100; + + printProgressBar(&progressCtx, false, 0); + } + + breaks = (progressCtx.line_offset + 2); + + if (success) + { + timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); + progressCtx.now -= progressCtx.start; + + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); + } else { + setProgressBarError(&progressCtx); + if (fat32_error) breaks += 2; + } + +out: + if (outFile) fclose(outFile); + + if (!success) + { + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) + { + 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); + } + } else { + unlink(dumpPath); + } + } + + closeGameCardStoragePartition(); + + if (dumpName) free(dumpName); + + breaks += 2; return success; } -bool copyFileFromHfs0(u32 partition, const char* source, const char* dest, const u64 file_offset, const u64 size, progress_ctx_t *progressCtx, bool doSplitting) +bool copyFileFromHfs0Partition(u32 partition, const char *dest, const char *source, const u64 fileOffset, const u64 fileSize, progress_ctx_t *progressCtx, bool doSplitting) { + if (!gameCardInfo.rootHfs0Header || !gameCardInfo.hfs0PartitionCnt || partition >= gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].header || !gameCardInfo.hfs0Partitions[partition].header_size || !dest || !strlen(dest) || !source || !strlen(source) || !progressCtx) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to copy file from HFS0 partition!", __func__); + return false; + } + + if (!gameCardInfo.hfs0Partitions[partition].file_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected HFS0 partition is empty!", __func__); + return false; + } + + // IStorage handle must have been retrieved beforehand by the caller function + Result result; bool success = false, fat32_error = false; - char splitFilename[NAME_BUF_LEN] = {'\0'}; + char splitFilename[NAME_BUF_LEN * 3] = {'\0'}; size_t destLen = strlen(dest); FILE *outFile = NULL; u64 off, n = DUMP_BUFFER_SIZE; u8 splitIndex = 0; - - FsGameCardHandle handle; - FsStorage gameCardStorage; + openIStoragePartition storageIndex = (openIStoragePartition)(HFS0_TO_ISTORAGE_IDX(gameCardInfo.hfs0PartitionCnt, partition) + 1); size_t write_res; @@ -3786,432 +3488,299 @@ bool copyFileFromHfs0(u32 partition, const char* source, const char* dest, const uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset - 4), FONT_COLOR_RGB, "Copying \"%s\"...", source); - if ((destLen + 1) < NAME_BUF_LEN) + if ((destLen + 1) >= MAX_CHARACTERS(splitFilename)) { - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_SUCCEEDED(result)) + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: destination path is too long! (%lu bytes)", __func__, destLen); + return false; + } + + if (fileSize > FAT32_FILESIZE_LIMIT && doSplitting) snprintf(splitFilename, MAX_CHARACTERS(splitFilename), "%s.%02u", dest, splitIndex); + + 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__); + goto out; + } + + for (off = 0; off < fileSize; off += n, progressCtx->curOffset += n) + { + uiFill(0, ((progressCtx->line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); + + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", ((fileSize > FAT32_FILESIZE_LIMIT && doSplitting) ? (strrchr(splitFilename, '/') + 1) : (strrchr(dest, '/') + 1))); + + uiRefreshDisplay(); + + if (n > (fileSize - off)) n = (fileSize - off); + + result = readGameCardStoragePartition(fileOffset + off, dumpBuf, n); + if (R_FAILED(result)) { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "GetGameCardHandle succeeded: 0x%08X", handle.value); - breaks++;*/ + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes chunk at offset 0x%016lX from IStorage partition #%u! (0x%08X)", __func__, n, fileOffset + off, storageIndex - 1, result); + break; + } + + if (fileSize > FAT32_FILESIZE_LIMIT && doSplitting && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + { + u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); + u64 old_file_chunk_size = (n - new_file_chunk_size); - // Same ugly hack from dumpRawHfs0Partition() - result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)); - if (R_SUCCEEDED(result)) + if (old_file_chunk_size > 0) { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "OpenGameCardStorage succeeded: 0x%08X", handle.value); - breaks++;*/ - - if (size > FAT32_FILESIZE_LIMIT && doSplitting) snprintf(splitFilename, MAX_ELEMENTS(splitFilename), "%s.%02u", dest, splitIndex); - - outFile = fopen(((size > FAT32_FILESIZE_LIMIT && doSplitting) ? splitFilename : dest), "wb"); - if (outFile) + write_res = fwrite(dumpBuf, 1, old_file_chunk_size, outFile); + if (write_res != old_file_chunk_size) { - for (off = 0; off < size; off += n, progressCtx->curOffset += n) - { - uiFill(0, ((progressCtx->line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", ((size > FAT32_FILESIZE_LIMIT && doSplitting) ? (strrchr(splitFilename, '/') + 1) : (strrchr(dest, '/') + 1))); - - uiRefreshDisplay(); - - if (n > (size - off)) n = (size - off); - - result = fsStorageRead(&gameCardStorage, file_offset + off, dumpBuf, n); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%016lX", result, file_offset + off); - break; - } - - if (size > FAT32_FILESIZE_LIMIT && doSplitting && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) - { - u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, off, splitIndex, write_res); - break; - } - } - - fclose(outFile); - outFile = NULL; - - if (new_file_chunk_size > 0 || (off + n) < size) - { - splitIndex++; - snprintf(splitFilename, MAX_ELEMENTS(splitFilename), "%s.%02u", dest, splitIndex); - - outFile = fopen(splitFilename, "wb"); - if (!outFile) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); - 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, off + old_file_chunk_size, splitIndex, write_res); - 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res); - - if ((off + 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 file splitting."); - fat32_error = true; - } - - break; - } - } - - printProgressBar(progressCtx, true, n); - - if ((off + n) < size || (progressCtx->curOffset + n) < progressCtx->totalSize) - { - if (cancelProcessCheck(progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - break; - } - } - } - - if (off >= size) success = true; - - // Support empty files - if (!size) - { - uiFill(0, ((progressCtx->line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); - - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", ((size > FAT32_FILESIZE_LIMIT && doSplitting) ? (strrchr(splitFilename, '/') + 1) : (strrchr(dest, '/') + 1))); - - if (progressCtx->totalSize == size) progressCtx->progress = 100; - - printProgressBar(progressCtx, false, 0); - } - - if (!success) - { - setProgressBarError(progressCtx); - breaks = (progressCtx->line_offset + 2); - if (fat32_error) breaks += 2; - } - - if (outFile) fclose(outFile); - - if (!success) - { - if (size > FAT32_FILESIZE_LIMIT && doSplitting) - { - for(u8 i = 0; i <= splitIndex; i++) - { - snprintf(splitFilename, MAX_ELEMENTS(splitFilename), "%s.%02u", dest, i); - unlink(splitFilename); - } - } else { - unlink(dest); - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_RGB, "Failed to open output file!"); + 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, off, splitIndex, write_res); + break; + } + } + + fclose(outFile); + outFile = NULL; + + if (new_file_chunk_size > 0 || (off + n) < fileSize) + { + splitIndex++; + snprintf(splitFilename, MAX_CHARACTERS(splitFilename), "%s.%02u", dest, splitIndex); + + outFile = fopen(splitFilename, "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); + break; } - fsStorageClose(&gameCardStorage); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); + 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, off + old_file_chunk_size, splitIndex, write_res); + break; + } + } } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); + 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, off, write_res); + + if ((off + 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 file splitting."); + fat32_error = true; + } + + break; + } + } + + printProgressBar(progressCtx, true, n); + + if (((off + n) < fileSize || (progressCtx->curOffset + n) < progressCtx->totalSize) && cancelProcessCheck(progressCtx)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + break; } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Destination path is too long! (%lu bytes)", destLen); } + if (off >= fileSize) success = true; + + // Support empty files + if (!fileSize) + { + uiFill(0, ((progressCtx->line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); + + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset - 2), FONT_COLOR_RGB, "Output file: \"%s\".", strrchr(dest, '/') + 1); + + if (progressCtx->totalSize == fileSize) progressCtx->progress = 100; + + printProgressBar(progressCtx, false, 0); + } + + if (!success) + { + setProgressBarError(progressCtx); + breaks = (progressCtx->line_offset + 2); + if (fat32_error) breaks += 2; + } + +out: + if (outFile) fclose(outFile); + + if (!success) + { + if (fileSize > FAT32_FILESIZE_LIMIT && doSplitting) + { + for(u8 i = 0; i <= splitIndex; i++) + { + snprintf(splitFilename, MAX_CHARACTERS(splitFilename), "%s.%02u", dest, i); + unlink(splitFilename); + } + } else { + unlink(dest); + } + } + + breaks += 2; + return success; } -bool copyHfs0Contents(u32 partition, hfs0_entry_table *partitionEntryTable, progress_ctx_t *progressCtx, const char *dest, bool splitting) +bool copyHfs0PartitionContents(u32 partition, progress_ctx_t *progressCtx, const char *dest, bool splitting) { - if (!dest || !*dest) + if (!gameCardInfo.rootHfs0Header || !gameCardInfo.hfs0PartitionCnt || partition >= gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].header || !gameCardInfo.hfs0Partitions[partition].header_size || !progressCtx || !dest || !strlen(dest)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: destination directory is empty."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to copy HFS0 partition contents!", __func__); + breaks += 2; return false; } - if (!partitionHfs0Header || !partitionEntryTable) + if (!gameCardInfo.hfs0Partitions[partition].file_cnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "HFS0 partition header information unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected HFS0 partition is empty!", __func__); + breaks += 2; return false; } - if (!progressCtx) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid progress context."); - return false; - } - - char dbuf[NAME_BUF_LEN] = {'\0'}; + Result result; + char dbuf[NAME_BUF_LEN * 2] = {'\0'}; + hfs0_file_entry entry; size_t dest_len = strlen(dest); + openIStoragePartition storageIndex = (openIStoragePartition)(HFS0_TO_ISTORAGE_IDX(gameCardInfo.hfs0PartitionCnt, partition) + 1); - if ((dest_len + 1) >= NAME_BUF_LEN) + u32 i; + bool success = false; + + if ((dest_len + 1) >= MAX_CHARACTERS(dbuf)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Destination directory name is too long! (%lu bytes)", dest_len); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: destination directory name is too long! (%lu bytes)", __func__, dest_len); + breaks += 2; return false; } - strcpy(dbuf, dest); + snprintf(dbuf, MAX_CHARACTERS(dbuf), dest); mkdir(dbuf, 0744); dbuf[dest_len] = '/'; dest_len++; - u32 i; - bool success; + result = openGameCardStoragePartition(storageIndex); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #%u! (0x%08X)", __func__, storageIndex - 1, result); + breaks += 2; + return false; + } timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx->start)); - for(i = 0; i < partitionHfs0FileCount; i++) + for(i = 0; i < gameCardInfo.hfs0Partitions[partition].file_cnt; i++) { - u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionEntryTable[i].filename_offset); - char *filename = ((char*)partitionHfs0Header + filename_offset); - strcpy(dbuf + dest_len, filename); + memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); + char *filename = (char*)(gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (gameCardInfo.hfs0Partitions[partition].file_cnt * sizeof(hfs0_file_entry)) + entry.filename_offset); + + dbuf[dest_len] = '\0'; + strcat(dbuf, filename); removeIllegalCharacters(dbuf + dest_len); - u64 file_offset = (partitionHfs0HeaderSize + partitionEntryTable[i].file_offset); - if (HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition) == 0) file_offset += partitionHfs0HeaderOffset; + u64 fileOffset = (gameCardInfo.hfs0Partitions[partition].offset + gameCardInfo.hfs0Partitions[partition].header_size + entry.file_offset); - success = copyFileFromHfs0(partition, filename, dbuf, file_offset, partitionEntryTable[i].file_size, progressCtx, splitting); + success = copyFileFromHfs0Partition(partition, dbuf, filename, fileOffset, entry.file_size, progressCtx, splitting); if (!success) break; } + closeGameCardStoragePartition(); + return success; } bool dumpHfs0PartitionData(u32 partition, bool doSplitting) { - bool success = false; + if (!gameCardInfo.rootHfs0Header || !gameCardInfo.hfs0PartitionCnt || partition >= gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].header) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to dump HFS0 partition data!", __func__); + breaks += 2; + return false; + } + + if (!gameCardInfo.hfs0Partitions[partition].file_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected HFS0 partition is empty!", __func__); + breaks += 2; + return false; + } + u32 i; - hfs0_entry_table *entryTable = NULL; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; + hfs0_file_entry entry; + char dumpPath[NAME_BUF_LEN] = {'\0'}; progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); - char *dumpName = generateFullDumpName(); + bool success = false; + + char *dumpName = generateGameCardDumpName(false); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; return false; } - workaroundPartitionZeroAccess(); - - if (getPartitionHfs0Header(partition)) + // Calculate total size + for(i = 0; i < gameCardInfo.hfs0Partitions[partition].file_cnt; i++) { - if (partitionHfs0FileCount) - { - entryTable = calloc(partitionHfs0FileCount, sizeof(hfs0_entry_table)); - if (entryTable) - { - memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount); - - // Calculate total size - for(i = 0; i < partitionHfs0FileCount; i++) progressCtx.totalSize += entryTable[i].file_size; - - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Total partition data size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); - breaks += 2; - - if (progressCtx.totalSize <= freeSpace) - { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s - Partition %u (%s)", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Copying partition #%u data to \"%s/\". Hold %s to cancel.", partition, dumpPath, NINTENDO_FONT_B); - 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; - } - - uiRefreshDisplay(); - - progressCtx.line_offset = (breaks + 4); - - success = copyHfs0Contents(partition, entryTable, &progressCtx, dumpPath, doSplitting); - - if (success) - { - breaks = (progressCtx.line_offset + 2); - - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); - progressCtx.now -= progressCtx.start; - - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); - } else { - removeDirectoryWithVerbose(dumpPath, "Deleting output directory. Please wait..."); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - } - - free(entryTable); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Unable to allocate memory for the HFS0 file entries!"); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "The selected partition is empty!"); - } - - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; + memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); + progressCtx.totalSize += entry.file_size; } + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Total partition data size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); breaks += 2; - free(dumpName); - - return success; -} - -bool dumpFileFromHfs0Partition(u32 partition, u32 file, char *filename, bool doSplitting) -{ - if (!partitionHfs0Header) + if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "HFS0 partition header information unavailable!"); - breaks += 2; - return false; + 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; } - if (!filename || !*filename) + 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, "Filename unavailable!"); - breaks += 2; - return false; - } - - progress_ctx_t progressCtx; - memset(&progressCtx, 0, sizeof(progress_ctx_t)); - - char *dumpName = generateFullDumpName(); - if (!dumpName) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - return false; - } - - u64 file_offset = 0; - u64 file_size = 0; - bool proceed = true, success = false; - - if (getHfs0EntryDetails(partitionHfs0Header, partitionHfs0HeaderOffset, partitionHfs0HeaderSize, partitionHfs0FileCount, file, false, partition, &file_offset, &file_size)) - { - progressCtx.totalSize = file_size; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); + 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 (file_size <= freeSpace) - { - char destCopyPath[NAME_BUF_LEN * 2] = {'\0'}; - char fixedFilename[NAME_BUF_LEN] = {'\0'}; - - sprintf(fixedFilename, filename); - removeIllegalCharacters(fixedFilename); - - snprintf(destCopyPath, MAX_ELEMENTS(destCopyPath), "%s%s - Partition %u (%s)", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); - - if ((strlen(destCopyPath) + 1 + strlen(filename)) < NAME_BUF_LEN) - { - mkdir(destCopyPath, 0744); - - strcat(destCopyPath, "/"); - strcat(destCopyPath, fixedFilename); - - breaks++; - - // Check if the dump already exists - if (checkIfFileExists(destCopyPath)) - { - // Ask the user if they want to proceed anyway - int cur_breaks = breaks; - - proceed = yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?"); - if (!proceed) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); - } 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); - } - } - - if (proceed) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - 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; - } - - uiRefreshDisplay(); - - progressCtx.line_offset = (breaks + 4); - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); - - success = copyFileFromHfs0(partition, filename, destCopyPath, file_offset, file_size, &progressCtx, doSplitting); - - if (success) - { - breaks = (progressCtx.line_offset + 2); - - timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); - progressCtx.now -= progressCtx.start; - - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); - } - } - } else { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Destination path is too long! (%lu bytes)", strlen(destCopyPath) + 1 + strlen(filename)); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to get file details from the partition HFS0 header!"); } + breaks++; + + uiRefreshDisplay(); + + progressCtx.line_offset = (breaks + 4); + + success = copyHfs0PartitionContents(partition, &progressCtx, dumpPath, doSplitting); + + if (success) + { + breaks = (progressCtx.line_offset + 2); + + timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); + progressCtx.now -= progressCtx.start; + + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); + } else { + removeDirectoryWithVerbose(dumpPath, "Deleting output directory. Please wait..."); + } + +out: free(dumpName); breaks += 2; @@ -4219,52 +3788,196 @@ bool dumpFileFromHfs0Partition(u32 partition, u32 file, char *filename, bool doS return success; } -bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) +bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, bool doSplitting) { - u64 n; - FILE *outFile; - u8 splitIndex; + if (!gameCardInfo.rootHfs0Header || !gameCardInfo.hfs0PartitionCnt || partition >= gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].header || !gameCardInfo.hfs0Partitions[partition].header_size || !filename || !strlen(filename)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to dump file from HFS0 partition!", __func__); + breaks += 2; + return false; + } + + if (!gameCardInfo.hfs0Partitions[partition].file_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected HFS0 partition is empty!", __func__); + breaks += 2; + return false; + } + + if (fileIndex >= gameCardInfo.hfs0Partitions[partition].file_cnt) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file index!", __func__); + breaks += 2; + return false; + } + + Result result; + hfs0_file_entry entry; + + progress_ctx_t progressCtx; + memset(&progressCtx, 0, sizeof(progress_ctx_t)); + + u64 fileOffset = 0; + openIStoragePartition storageIndex = (openIStoragePartition)(HFS0_TO_ISTORAGE_IDX(gameCardInfo.hfs0PartitionCnt, partition) + 1); + + char destCopyPath[NAME_BUF_LEN * 2] = {'\0'}; + + bool success = false; + + char *dumpName = generateGameCardDumpName(false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } + + memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (fileIndex * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); + + fileOffset = (gameCardInfo.hfs0Partitions[partition].offset + gameCardInfo.hfs0Partitions[partition].header_size + entry.file_offset); + + progressCtx.totalSize = entry.file_size; + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); + breaks += 2; + + if (progressCtx.totalSize > freeSpace) + { + 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; + } + + snprintf(destCopyPath, MAX_CHARACTERS(destCopyPath), "%s%s - Partition %u (%s)", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, partition)); + mkdir(destCopyPath, 0744); + + strcat(destCopyPath, "/"); + size_t cur_len = strlen(destCopyPath); + strcat(destCopyPath, filename); + removeIllegalCharacters(destCopyPath + cur_len); + + // Check if the dump already exists + if (checkIfFileExists(destCopyPath)) + { + // Ask the user if they want to proceed anyway + int cur_breaks = breaks; + + if (!yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); + goto out; + } 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); + } + } + + result = openGameCardStoragePartition(storageIndex); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #%u! (0x%08X)", __func__, storageIndex - 1, result); + 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++; + + uiRefreshDisplay(); + + progressCtx.line_offset = (breaks + 4); + timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); + + success = copyFileFromHfs0Partition(partition, destCopyPath, filename, fileOffset, progressCtx.totalSize, &progressCtx, doSplitting); + + closeGameCardStoragePartition(); + + if (success) + { + breaks = (progressCtx.line_offset + 2); + + timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); + progressCtx.now -= progressCtx.start; + + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); + } else { + breaks -= 2; + } + +out: + free(dumpName); + + breaks += 2; + + return success; +} + +bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg) +{ + if (!exeFsDumpCfg) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid ExeFS configuration struct!", __func__); + breaks += 2; + return false; + } + + bool isFat32 = exeFsDumpCfg->isFat32; + bool useLayeredFSDir = exeFsDumpCfg->useLayeredFSDir; + + u32 i; + u64 n = 0, offset = 0; + FILE *outFile = NULL; + u8 splitIndex = 0; + size_t write_res; bool proceed = true, success = false, fat32_error = false; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}, curDumpPath[NAME_BUF_LEN * 4] = {'\0'}; - char tmp_idx[5]; + char tmp_idx[5] = {'\0'}; + char *dumpName = NULL; + char dumpPath[NAME_BUF_LEN] = {'\0'}, curDumpPath[NAME_BUF_LEN * 2] = {'\0'}; + + progress_ctx_t progressCtx; + memset(&progressCtx, 0, sizeof(progress_ctx_t)); memset(dumpBuf, 0, DUMP_BUFFER_SIZE); - progress_ctx_t progressCtx; - memset(&progressCtx, 0, sizeof(progress_ctx_t)); - - size_t write_res; - - u32 i; - u64 offset; - if ((!usePatch && !titleAppCount) || (usePatch && !titlePatchCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title count!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title count!", __func__); breaks += 2; return false; } if ((!usePatch && titleIndex > (titleAppCount - 1)) || (usePatch && titleIndex > (titlePatchCount - 1))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); breaks += 2; return false; } - char *dumpName = generateNSPDumpName((!usePatch ? DUMP_APP_NSP : DUMP_PATCH_NSP), titleIndex); - if (!dumpName) + if (!useLayeredFSDir) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - return false; + dumpName = generateNSPDumpName((!usePatch ? DUMP_APP_NSP : DUMP_PATCH_NSP), titleIndex, false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } } // Retrieve ExeFS from Program NCA if (!readNcaExeFsSection(titleIndex, usePatch)) { - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; return false; } @@ -4272,33 +3985,48 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) // Calculate total dump size if (!calculateExeFsExtractedDataSize(&(progressCtx.totalSize))) goto out; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Extracted ExeFS dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); uiRefreshDisplay(); breaks++; if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); + 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; } - // Prepare output dump path - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + // Generate output path + if (!useLayeredFSDir) + { + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + } 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)); + mkdir(dumpPath, 0744); + + strcat(dumpPath, "/exefs"); + } + mkdir(dumpPath, 0744); // 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); - uiRefreshDisplay(); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + + uiRefreshDisplay(); + progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); @@ -4313,23 +4041,24 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) // Check if we're dealing with a nameless file if (!strlen(exeFsFilename)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: file entry without name in ExeFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: file entry without name in ExeFS section!", __func__); break; } - snprintf(curDumpPath, MAX_ELEMENTS(curDumpPath), "%s/%s", dumpPath, exeFsFilename); + snprintf(curDumpPath, MAX_CHARACTERS(curDumpPath), "%s/%s", dumpPath, exeFsFilename); removeIllegalCharacters(curDumpPath + strlen(dumpPath) + 1); - if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && doSplitting) + if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && isFat32) { - sprintf(tmp_idx, ".%02u", splitIndex); + mkdir(curDumpPath, 0744); + sprintf(tmp_idx, "/%02u", splitIndex); strcat(curDumpPath, tmp_idx); } outFile = fopen(curDumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, curDumpPath); break; } @@ -4353,7 +4082,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) if (!proceed) break; - if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && doSplitting && (offset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && isFat32 && (offset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { u64 new_file_chunk_size = ((offset + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); @@ -4363,7 +4092,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, offset, splitIndex, write_res); + 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, offset, splitIndex, write_res); proceed = false; break; } @@ -4374,17 +4103,17 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) if (new_file_chunk_size > 0 || (offset + n) < exeFsContext.exefs_entries[i].file_size) { - char *tmp = strrchr(curDumpPath, '.'); + char *tmp = strrchr(curDumpPath, '/'); if (tmp != NULL) *tmp = '\0'; splitIndex++; - sprintf(tmp_idx, ".%02u", splitIndex); + sprintf(tmp_idx, "/%02u", splitIndex); strcat(curDumpPath, tmp_idx); outFile = fopen(curDumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); + 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; } @@ -4394,7 +4123,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, offset + old_file_chunk_size, splitIndex, write_res); + 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, offset + old_file_chunk_size, splitIndex, write_res); proceed = false; break; } @@ -4404,7 +4133,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, offset, write_res); + 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, offset, write_res); if ((offset + n) > FAT32_FILESIZE_LIMIT) { @@ -4419,14 +4148,11 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - break; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + proceed = false; + break; } } @@ -4445,6 +4171,14 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) printProgressBar(&progressCtx, false, 0); } + + // Set archive bit (only for FAT32) + if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && isFat32) + { + char *tmp = strrchr(curDumpPath, '/'); + if (tmp != NULL) *tmp = '\0'; + fsdevSetConcatenationFileAttribute(curDumpPath); + } } if (proceed) @@ -4453,7 +4187,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) { success = true; } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Unexpected underdump error! Wrote %lu bytes, expected %lu bytes.", progressCtx.curOffset, progressCtx.totalSize); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: underdump error! Wrote %lu bytes, expected %lu bytes.", __func__, progressCtx.curOffset, progressCtx.totalSize); } } @@ -4464,7 +4198,7 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); } else { setProgressBarError(&progressCtx); @@ -4475,18 +4209,28 @@ bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting) out: freeExeFsContext(); - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; return success; } -bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool doSplitting) +bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg) { + if (!exeFsDumpCfg) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid ExeFS configuration struct!", __func__); + breaks += 2; + return false; + } + + bool isFat32 = exeFsDumpCfg->isFat32; + bool useLayeredFSDir = exeFsDumpCfg->useLayeredFSDir; + if (!exeFsContext.exefs_header.file_cnt || fileIndex > (exeFsContext.exefs_header.file_cnt - 1) || !exeFsContext.exefs_entries || !exeFsContext.exefs_str_table || exeFsContext.exefs_data_offset <= exeFsContext.exefs_offset || (!usePatch && titleIndex > (titleAppCount - 1)) || (usePatch && titleIndex > (titlePatchCount - 1))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to parse file entry from ExeFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to parse file entry from ExeFS section!", __func__); breaks += 2; return false; } @@ -4494,38 +4238,50 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool u64 n = DUMP_BUFFER_SIZE; FILE *outFile = NULL; u8 splitIndex = 0; + size_t write_res; bool proceed = true, success = false, fat32_error = false, removeFile = true; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; char tmp_idx[5]; - - memset(dumpBuf, 0, DUMP_BUFFER_SIZE); + char *dumpName = NULL; + char dumpPath[NAME_BUF_LEN] = {'\0'}; progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); - size_t write_res; + memset(dumpBuf, 0, DUMP_BUFFER_SIZE); char *exeFsFilename = (exeFsContext.exefs_str_table + exeFsContext.exefs_entries[fileIndex].filename_offset); // Check if we're dealing with a nameless file if (!strlen(exeFsFilename)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: file entry without name in ExeFS section!"); - breaks += 2; - return false; - } - - char *dumpName = generateNSPDumpName((!usePatch ? DUMP_APP_NSP : DUMP_PATCH_NSP), titleIndex); - if (!dumpName) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: file entry without name in ExeFS section!", __func__); breaks += 2; return false; } // Generate output path - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + if (!useLayeredFSDir) + { + dumpName = generateNSPDumpName((!usePatch ? DUMP_APP_NSP : DUMP_PATCH_NSP), titleIndex, false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", EXEFS_DUMP_PATH, dumpName); + } 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)); + mkdir(dumpPath, 0744); + + strcat(dumpPath, "/exefs"); + } + mkdir(dumpPath, 0744); strcat(dumpPath, "/"); @@ -4533,21 +4289,15 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool strcat(dumpPath, exeFsFilename); removeIllegalCharacters(dumpPath + cur_len); - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - sprintf(tmp_idx, ".%02u", splitIndex); - strcat(dumpPath, tmp_idx); - } - progressCtx.totalSize = exeFsContext.exefs_entries[fileIndex].file_size; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); breaks++; if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); + 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; } @@ -4572,23 +4322,39 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool } } + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) + { + // 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); + fsdevDeleteDirectoryRecursively(dumpPath); + + mkdir(dumpPath, 0744); + sprintf(tmp_idx, "/%02u", splitIndex); + strcat(dumpPath, tmp_idx); + } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + // Start dump process uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Copying \"%s\"...", exeFsFilename); breaks += 2; + uiRefreshDisplay(); + outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file!", __func__); goto out; } @@ -4611,7 +4377,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool if (!proceed) break; - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); @@ -4621,7 +4387,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); + 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); break; } } @@ -4631,17 +4397,17 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize) { - char *tmp = strrchr(dumpPath, '.'); + char *tmp = strrchr(dumpPath, '/'); if (tmp != NULL) *tmp = '\0'; splitIndex++; - sprintf(tmp_idx, ".%02u", splitIndex); + sprintf(tmp_idx, "/%02u", splitIndex); strcat(dumpPath, tmp_idx); outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); + 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); break; } @@ -4650,7 +4416,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); + 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); break; } } @@ -4659,7 +4425,7 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); + 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 ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT) { @@ -4673,13 +4439,10 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - break; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + break; } } @@ -4704,50 +4467,44 @@ bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); } else { setProgressBarError(&progressCtx); + if (fat32_error) breaks += 2; } out: if (outFile) fclose(outFile); - if (!success) + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { - if (fat32_error) breaks += 2; + char *tmp = strrchr(dumpPath, '/'); + if (tmp != NULL) *tmp = '\0'; - if (removeFile) + if (success) { - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - for(u8 i = 0; i <= splitIndex; i++) - { - char *tmp = strrchr(dumpPath, '.'); - if (tmp != NULL) *tmp = '\0'; - - sprintf(tmp_idx, ".%02u", splitIndex); - strcat(dumpPath, tmp_idx); - unlink(dumpPath); - } - } else { - unlink(dumpPath); - } + // Set archive bit (only for FAT32) + fsdevSetConcatenationFileAttribute(dumpPath); + } else { + if (removeFile) fsdevDeleteDirectoryRecursively(dumpPath); } + } else { + if (!success && removeFile) unlink(dumpPath); } - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; return success; } -bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx, bool usePatch, bool doSplitting) +bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx, bool usePatch, bool isFat32) { if ((!usePatch && (!romFsContext.romfs_filetable_size || file_offset > romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries)) || (usePatch && (!bktrContext.romfs_filetable_size || file_offset > bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries)) || !romfs_path || !output_path || !progressCtx) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to parse file entry from RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to parse file entry from RomFS section!", __func__); return false; } @@ -4775,18 +4532,21 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path romfs_path[orig_romfs_path_len] = '\0'; output_path[orig_output_path_len] = '\0'; + n = DUMP_BUFFER_SIZE; + splitIndex = 0; + entry = (!usePatch ? (romfs_file*)((u8*)romFsContext.romfs_file_entries + romfs_file_offset) : (romfs_file*)((u8*)bktrContext.romfs_file_entries + romfs_file_offset)); // Check if we're dealing with a nameless file if (!entry->nameLen) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: file entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: file entry without name in RomFS section!", __func__); break; } if ((orig_romfs_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2) || (orig_output_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: RomFS section file path is too long!"); + 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; } @@ -4798,27 +4558,25 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path strncat(output_path, (char*)entry->name, entry->nameLen); removeIllegalCharacters(output_path + orig_output_path_len + 1); + if (entry->dataSize > FAT32_FILESIZE_LIMIT && isFat32) + { + mkdir(output_path, 0744); + sprintf(tmp_idx, "/%02u", splitIndex); + strcat(output_path, tmp_idx); + } + // Start dump process 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, "Copying \"romfs:%s\"...", romfs_path); - if (entry->dataSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - sprintf(tmp_idx, ".%02u", splitIndex); - strcat(output_path, tmp_idx); - } - outFile = fopen(output_path, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_RGB, "Failed to open output file!"); + 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; } - n = DUMP_BUFFER_SIZE; - splitIndex = 0; - for(off = 0; off < entry->dataSize; off += n, progressCtx->curOffset += n) { uiFill(0, ((progressCtx->line_offset - 2) * LINE_HEIGHT) + 8, FB_WIDTH, LINE_HEIGHT * 2, BG_COLOR_RGB); @@ -4842,7 +4600,7 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path if (!proceed) break; - if (entry->dataSize > FAT32_FILESIZE_LIMIT && doSplitting && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + if (entry->dataSize > FAT32_FILESIZE_LIMIT && isFat32 && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); @@ -4852,7 +4610,7 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, off, splitIndex, write_res); + 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, off, splitIndex, write_res); proceed = false; break; } @@ -4863,17 +4621,17 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path if (new_file_chunk_size > 0 || (off + n) < entry->dataSize) { - char *tmp = strrchr(output_path, '.'); + char *tmp = strrchr(output_path, '/'); if (tmp != NULL) *tmp = '\0'; splitIndex++; - sprintf(tmp_idx, ".%02u", splitIndex); + sprintf(tmp_idx, "/%02u", splitIndex); strcat(output_path, tmp_idx); outFile = fopen(output_path, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); + 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; } @@ -4883,7 +4641,7 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, off + old_file_chunk_size, splitIndex, write_res); + 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, off + old_file_chunk_size, splitIndex, write_res); proceed = false; break; } @@ -4893,7 +4651,7 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res); + 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, off, write_res); if ((off + n) > FAT32_FILESIZE_LIMIT) { @@ -4908,14 +4666,11 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path printProgressBar(progressCtx, true, n); - if ((off + n) < entry->dataSize || (progressCtx->curOffset + n) < progressCtx->totalSize) + if (((off + n) < entry->dataSize || (progressCtx->curOffset + n) < progressCtx->totalSize) && cancelProcessCheck(progressCtx)) { - if (cancelProcessCheck(progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - proceed = false; - break; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + proceed = false; + break; } } @@ -4939,6 +4694,14 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path printProgressBar(progressCtx, false, 0); } + // Set archive bit (only for FAT32) + if (entry->dataSize > FAT32_FILESIZE_LIMIT && isFat32) + { + char *tmp = strrchr(output_path, '/'); + if (tmp != NULL) *tmp = '\0'; + fsdevSetConcatenationFileAttribute(output_path); + } + romfs_file_offset = entry->sibling; if (romfs_file_offset == ROMFS_ENTRY_EMPTY) success = true; } @@ -4955,11 +4718,11 @@ bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path return success; } -bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx, bool usePatch, bool dumpSiblingDir, bool doSplitting) +bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx, bool usePatch, bool dumpSiblingDir, bool isFat32) { if ((!usePatch && (!romFsContext.romfs_dirtable_size || dir_offset > romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries)) || (usePatch && (!bktrContext.romfs_dirtable_size || dir_offset > bktrContext.romfs_dirtable_size || !bktrContext.romfs_dir_entries || !bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries)) || !romfs_path || !output_path || !progressCtx) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to parse directory entry from RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to parse directory entry from RomFS section!", __func__); return false; } @@ -4971,13 +4734,13 @@ bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, // Check if we're dealing with a nameless directory that's not the root directory if (!entry->nameLen && dir_offset > 0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: directory entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: directory entry without name in RomFS section!", __func__); return false; } if ((orig_romfs_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2) || (orig_output_path_len + 1 + entry->nameLen) >= (NAME_BUF_LEN * 2)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "Error: RomFS section directory path is too long!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx->line_offset + 2), FONT_COLOR_ERROR_RGB, "%s: RomFS section directory path is too long!", __func__); return false; } @@ -4995,7 +4758,7 @@ bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, if (entry->childFile != ROMFS_ENTRY_EMPTY) { - if (!recursiveDumpRomFsFile(entry->childFile, romfs_path, output_path, progressCtx, usePatch, doSplitting)) + if (!recursiveDumpRomFsFile(entry->childFile, romfs_path, output_path, progressCtx, usePatch, isFat32)) { romfs_path[orig_romfs_path_len] = '\0'; output_path[orig_output_path_len] = '\0'; @@ -5005,7 +4768,7 @@ bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, if (entry->childDir != ROMFS_ENTRY_EMPTY) { - if (!recursiveDumpRomFsDir(entry->childDir, romfs_path, output_path, progressCtx, usePatch, true, doSplitting)) + if (!recursiveDumpRomFsDir(entry->childDir, romfs_path, output_path, progressCtx, usePatch, true, isFat32)) { romfs_path[orig_romfs_path_len] = '\0'; output_path[orig_output_path_len] = '\0'; @@ -5018,41 +4781,55 @@ bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, if (dumpSiblingDir && entry->sibling != ROMFS_ENTRY_EMPTY) { - if (!recursiveDumpRomFsDir(entry->sibling, romfs_path, output_path, progressCtx, usePatch, true, doSplitting)) return false; + if (!recursiveDumpRomFsDir(entry->sibling, romfs_path, output_path, progressCtx, usePatch, true, isFat32)) return false; } return true; } -bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting) +bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg) { + if (!romFsDumpCfg) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RomFS configuration struct!", __func__); + breaks += 2; + return false; + } + + bool isFat32 = romFsDumpCfg->isFat32; + bool useLayeredFSDir = romFsDumpCfg->useLayeredFSDir; + progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); + char *dumpName = NULL; char romFsPath[NAME_BUF_LEN * 2] = {'\0'}, dumpPath[NAME_BUF_LEN * 2] = {'\0'}; bool success = false; if ((curRomFsType == ROMFS_TYPE_APP && !titleAppCount) || (curRomFsType == ROMFS_TYPE_PATCH && !titlePatchCount) || (curRomFsType == ROMFS_TYPE_ADDON && !titleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title count!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title count!", __func__); breaks += 2; return false; } if ((curRomFsType == ROMFS_TYPE_APP && titleIndex > (titleAppCount - 1)) || (curRomFsType == ROMFS_TYPE_PATCH && titleIndex > (titlePatchCount - 1)) || (curRomFsType == ROMFS_TYPE_ADDON && titleIndex > (titleAddOnCount - 1))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); breaks += 2; return false; } - char *dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex); - if (!dumpName) + if (!useLayeredFSDir) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - return false; + dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex, false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } } // Retrieve RomFS from Program NCA @@ -5066,37 +4843,54 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, bool d // Calculate total dump size if (!calculateRomFsFullExtractedSize((curRomFsType == ROMFS_TYPE_PATCH), &(progressCtx.totalSize))) goto out; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Extracted RomFS dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); uiRefreshDisplay(); breaks++; if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); + 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; } - // Prepare output dump path - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + // Generate output path + if (!useLayeredFSDir) + { + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + } 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)); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); + mkdir(dumpPath, 0744); + + strcat(dumpPath, "/romfs"); + } + mkdir(dumpPath, 0744); // 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); - uiRefreshDisplay(); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + + uiRefreshDisplay(); + progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); - success = recursiveDumpRomFsDir(0, romFsPath, dumpPath, &progressCtx, (curRomFsType == ROMFS_TYPE_PATCH), true, doSplitting); + success = recursiveDumpRomFsDir(0, romFsPath, dumpPath, &progressCtx, (curRomFsType == ROMFS_TYPE_PATCH), true, isFat32); if (success) { @@ -5105,7 +4899,7 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, bool d timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); } else { setProgressBarError(&progressCtx); @@ -5117,18 +4911,28 @@ out: freeRomFsContext(); - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; return success; } -bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, bool doSplitting) +bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg) { + if (!romFsDumpCfg) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RomFS configuration struct!", __func__); + breaks += 2; + return false; + } + + bool isFat32 = romFsDumpCfg->isFat32; + bool useLayeredFSDir = romFsDumpCfg->useLayeredFSDir; + if ((curRomFsType != ROMFS_TYPE_PATCH && (!romFsContext.romfs_filetable_size || file_offset > romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries)) || (curRomFsType == ROMFS_TYPE_PATCH && (!bktrContext.romfs_filetable_size || file_offset > bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries)) || (curRomFsType == ROMFS_TYPE_APP && titleIndex > (titleAppCount - 1)) || (curRomFsType == ROMFS_TYPE_PATCH && titleIndex > (titlePatchCount - 1)) || (curRomFsType == ROMFS_TYPE_ADDON && titleIndex > (titleAddOnCount - 1))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to parse file entry from RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to parse file entry from RomFS section!", __func__); breaks += 2; return false; } @@ -5136,43 +4940,71 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType u64 n = DUMP_BUFFER_SIZE; FILE *outFile = NULL; u8 splitIndex = 0; + size_t write_res; bool proceed = true, success = false, fat32_error = false, removeFile = true; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; char tmp_idx[5]; - - memset(dumpBuf, 0, DUMP_BUFFER_SIZE); + char *dumpName = NULL; + char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); - size_t write_res; + memset(dumpBuf, 0, DUMP_BUFFER_SIZE); romfs_file *entry = (curRomFsType != ROMFS_TYPE_PATCH ? (romfs_file*)((u8*)romFsContext.romfs_file_entries + file_offset) : (romfs_file*)((u8*)bktrContext.romfs_file_entries + file_offset)); // Check if we're dealing with a nameless file if (!entry->nameLen) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: file entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: file entry without name in RomFS section!", __func__); breaks += 2; return false; } - char *dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex); - if (!dumpName) + progressCtx.totalSize = entry->dataSize; + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); + breaks++; + + if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: not enough free space available in the SD card!", __func__); breaks += 2; return false; } + breaks++; + // Generate output path - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + if (!useLayeredFSDir) + { + dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex, false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + } 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)); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); + mkdir(dumpPath, 0744); + + strcat(dumpPath, "/romfs"); + } + mkdir(dumpPath, 0744); // Create subdirectories - char *tmp1 = NULL; - char *tmp2 = NULL; + char *tmp1 = NULL, *tmp2 = NULL; size_t cur_len; tmp1 = strchr(curRomFsPath, '/'); @@ -5209,26 +5041,6 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType strncat(dumpPath, (char*)entry->name, entry->nameLen); removeIllegalCharacters(dumpPath + cur_len); - progressCtx.totalSize = entry->dataSize; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); - breaks++; - - if (progressCtx.totalSize > freeSpace) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - goto out; - } - - breaks++; - - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - sprintf(tmp_idx, ".%02u", splitIndex); - strcat(dumpPath, tmp_idx); - } - // Check if the dump already exists if (checkIfFileExists(dumpPath)) { @@ -5248,15 +5060,31 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType } } + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) + { + // 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); + fsdevDeleteDirectoryRecursively(dumpPath); + + mkdir(dumpPath, 0744); + sprintf(tmp_idx, "/%02u", splitIndex); + strcat(dumpPath, tmp_idx); + } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Hold %s to cancel.", NINTENDO_FONT_B); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + + uiRefreshDisplay(); + // Start dump process if (strlen(curRomFsPath) > 1) { @@ -5270,7 +5098,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); goto out; } @@ -5300,7 +5128,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType if (!proceed) break; - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); @@ -5310,7 +5138,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res); + 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); break; } } @@ -5320,17 +5148,17 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize) { - char *tmp = strrchr(dumpPath, '.'); + char *tmp = strrchr(dumpPath, '/'); if (tmp != NULL) *tmp = '\0'; splitIndex++; - sprintf(tmp_idx, ".%02u", splitIndex); + sprintf(tmp_idx, "/%02u", splitIndex); strcat(dumpPath, tmp_idx); outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Failed to open output file for part #%u!", splitIndex); + 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); break; } @@ -5339,7 +5167,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType 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, "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res); + 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); break; } } @@ -5348,7 +5176,7 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType 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, "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res); + 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 ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT) { @@ -5362,13 +5190,10 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType printProgressBar(&progressCtx, true, n); - if ((progressCtx.curOffset + n) < progressCtx.totalSize) + if ((progressCtx.curOffset + n) < progressCtx.totalSize && cancelProcessCheck(&progressCtx)) { - if (cancelProcessCheck(&progressCtx)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); - break; - } + uiDrawString(STRING_X_POS, STRING_Y_POS(progressCtx.line_offset + 2), FONT_COLOR_ERROR_RGB, "Process canceled."); + break; } } @@ -5393,87 +5218,95 @@ bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); } else { setProgressBarError(&progressCtx); + if (fat32_error) breaks += 2; } out: if (outFile) fclose(outFile); - if (!success) + if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32) { - if (fat32_error) breaks += 2; + char *tmp = strrchr(dumpPath, '/'); + if (tmp != NULL) *tmp = '\0'; - if (removeFile) + if (success) { - if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting) - { - for(u8 i = 0; i <= splitIndex; i++) - { - char *tmp = strrchr(dumpPath, '.'); - if (tmp != NULL) *tmp = '\0'; - - sprintf(tmp_idx, ".%02u", splitIndex); - strcat(dumpPath, tmp_idx); - unlink(dumpPath); - } - } else { - unlink(dumpPath); - } + // Set archive bit (only for FAT32) + fsdevSetConcatenationFileAttribute(dumpPath); + } else { + if (removeFile) fsdevDeleteDirectoryRecursively(dumpPath); } + } else { + if (!success && removeFile) unlink(dumpPath); } - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; return success; } -bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting) +bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg) { + if (!romFsDumpCfg) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RomFS configuration struct!", __func__); + breaks += 2; + return false; + } + + bool isFat32 = romFsDumpCfg->isFat32; + bool useLayeredFSDir = romFsDumpCfg->useLayeredFSDir; + progress_ctx_t progressCtx; memset(&progressCtx, 0, sizeof(progress_ctx_t)); + char *dumpName = NULL; char romFsPath[NAME_BUF_LEN * 2] = {'\0'}, dumpPath[NAME_BUF_LEN * 2] = {'\0'}; bool success = false; if ((curRomFsType == ROMFS_TYPE_APP && !titleAppCount) || (curRomFsType == ROMFS_TYPE_PATCH && !titlePatchCount) || (curRomFsType == ROMFS_TYPE_ADDON && !titleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title count!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title count!", __func__); breaks += 2; return false; } if ((curRomFsType == ROMFS_TYPE_APP && titleIndex > (titleAppCount - 1)) || (curRomFsType == ROMFS_TYPE_PATCH && titleIndex > (titlePatchCount - 1)) || (curRomFsType == ROMFS_TYPE_ADDON && titleIndex > (titleAddOnCount - 1))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); breaks += 2; return false; } - char *dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex); - if (!dumpName) + if (!useLayeredFSDir) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); - breaks += 2; - return false; + dumpName = generateNSPDumpName((curRomFsType == ROMFS_TYPE_APP ? DUMP_APP_NSP : (curRomFsType == ROMFS_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP)), titleIndex, false); + if (!dumpName) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); + breaks += 2; + return false; + } } // Calculate total dump size if (!calculateRomFsExtractedDirSize(curRomFsDirOffset, (curRomFsType == ROMFS_TYPE_PATCH), &(progressCtx.totalSize))) goto out; - convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_ELEMENTS(progressCtx.totalSizeStr)); + convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, MAX_CHARACTERS(progressCtx.totalSizeStr)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Extracted RomFS directory size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize); uiRefreshDisplay(); breaks++; if (progressCtx.totalSize > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); + 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; } @@ -5481,18 +5314,31 @@ bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsTy { // Copy the whole current path and remove the last element (current directory) from it // It will be re-added later - snprintf(romFsPath, MAX_ELEMENTS(romFsPath), curRomFsPath); + snprintf(romFsPath, MAX_CHARACTERS(romFsPath), curRomFsPath); char *slash = strrchr(romFsPath, '/'); if (slash) *slash = '\0'; } - // Prepare output dump path - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + // Generate output path + if (!useLayeredFSDir) + { + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s", ROMFS_DUMP_PATH, dumpName); + } 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)); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%016lX", cfwDirStr, titleId); + mkdir(dumpPath, 0744); + + strcat(dumpPath, "/romfs"); + } + mkdir(dumpPath, 0744); // Create subdirectories - char *tmp1 = NULL; - char *tmp2 = NULL; + char *tmp1 = NULL, *tmp2 = NULL; size_t cur_len; tmp1 = strchr(curRomFsPath, '/'); @@ -5526,19 +5372,22 @@ 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); - uiRefreshDisplay(); - breaks += 2; + 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 += 2; + breaks++; } + breaks++; + + uiRefreshDisplay(); + progressCtx.line_offset = (breaks + 4); timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start)); - success = recursiveDumpRomFsDir(curRomFsDirOffset, romFsPath, dumpPath, &progressCtx, (curRomFsType == ROMFS_TYPE_PATCH), false, doSplitting); + success = recursiveDumpRomFsDir(curRomFsDirOffset, romFsPath, dumpPath, &progressCtx, (curRomFsType == ROMFS_TYPE_PATCH), false, isFat32); if (success) { @@ -5547,7 +5396,7 @@ bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsTy timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now)); progressCtx.now -= progressCtx.start; - formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_ELEMENTS(progressCtx.etaInfo)); + formatETAString(progressCtx.now, progressCtx.etaInfo, MAX_CHARACTERS(progressCtx.etaInfo)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed after %s!", progressCtx.etaInfo); } else { setProgressBarError(&progressCtx); @@ -5555,7 +5404,7 @@ bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsTy } out: - free(dumpName); + if (dumpName) free(dumpName); breaks += 2; @@ -5566,114 +5415,108 @@ bool dumpGameCardCertificate() { u32 crc = 0; Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; - bool proceed = true, success = false; + bool success = false; FILE *outFile = NULL; - char filename[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; size_t write_res; memset(dumpBuf, 0, DUMP_BUFFER_SIZE); - char *dumpName = generateFullDumpName(); + char *dumpName = generateGameCardDumpName(false); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); breaks += 2; return false; } - workaroundPartitionZeroAccess(); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dumping gamecard certificate. Please wait."); + breaks++; - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_SUCCEEDED(result)) + if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "GetGameCardHandle succeeded: 0x%08X", handle.value); - breaks++;*/ - - result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0); - if (R_SUCCEEDED(result)) - { - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "OpenGameCardStorage succeeded: 0x%08X", handle.value); - breaks++;*/ - - if (CERT_SIZE <= freeSpace) - { - result = fsStorageRead(&gameCardStorage, CERT_OFFSET, dumpBuf, CERT_SIZE); - if (R_SUCCEEDED(result)) - { - // Calculate CRC32 - crc32(dumpBuf, CERT_SIZE, &crc); - - snprintf(filename, MAX_ELEMENTS(filename), "%s%s - Certificate (%08X).bin", CERT_DUMP_PATH, dumpName, crc); - - // Check if the dump already exists - if (checkIfFileExists(filename)) - { - // Ask the user if they want to proceed anyway - int cur_breaks = breaks; - - proceed = yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?"); - if (!proceed) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); - } 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); - } - } - - if (proceed) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Dumping gamecard certificate to \"%s\"...", strrchr(filename, '/' ) + 1); - 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; - } - - uiRefreshDisplay(); - - outFile = fopen(filename, "wb"); - if (outFile) - { - write_res = fwrite(dumpBuf, 1, CERT_SIZE, outFile); - if (write_res == CERT_SIZE) - { - success = true; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed!"); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to write %u bytes certificate data! (wrote %lu bytes)", CERT_SIZE, write_res); - } - - fclose(outFile); - if (!success) unlink(filename); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to open output file \"%s\"!", filename); - } - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: not enough free space available in the SD card."); - } - - fsStorageClose(&gameCardStorage); - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); + 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 += 2; + breaks++; + + uiRefreshDisplay(); + + result = openGameCardStoragePartition(ISTORAGE_PARTITION_NORMAL); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #0! (0x%08X)", __func__, result); + goto out; + } + + if (CERT_SIZE > freeSpace) + { + 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; + } + + result = readGameCardStoragePartition(CERT_OFFSET, dumpBuf, CERT_SIZE); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %u bytes long certificate at offset 0x%016lX from IStorage partition #0! (0x%08X)", __func__, CERT_SIZE, CERT_OFFSET, result); + goto out; + } + + // Calculate CRC32 + crc32(dumpBuf, CERT_SIZE, &crc); + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s - Certificate (%08X).bin", CERT_DUMP_PATH, dumpName, crc); + + // Check if the dump already exists + if (checkIfFileExists(dumpPath)) + { + // Ask the user if they want to proceed anyway + int cur_breaks = breaks; + + if (!yesNoPrompt("You have already dumped this content. Do you wish to proceed anyway?")) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Process canceled."); + goto out; + } 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); + } + } + + outFile = fopen(dumpPath, "wb"); + if (!outFile) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); + goto out; + } + + write_res = fwrite(dumpBuf, 1, CERT_SIZE, outFile); + if (write_res != CERT_SIZE) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %u bytes certificate data! (wrote %lu bytes)", __func__, CERT_SIZE, write_res); + goto out; + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Process successfully completed!"); + breaks++; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Certificate dumped to: \"%s\".", strrchr(dumpPath, '/' ) + 1); + + success = true; + +out: + if (outFile) fclose(outFile); + + if (!success && strlen(dumpPath)) unlink(dumpPath); + + closeGameCardStoragePartition(); free(dumpName); + breaks += 2; + return success; } @@ -5681,7 +5524,7 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp { if (!tikDumpCfg) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: invalid ticket dump configuration struct!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid ticket dump configuration struct!", __func__); breaks += 2; return false; } @@ -5691,17 +5534,17 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp u32 i = 0; Result result; - FsStorageId curStorageId; - u8 filter; + NcmStorageId curStorageId; + NcmContentMetaType metaType; u32 titleCount = 0, ncmTitleIndex = 0; char *dumpName = NULL; - char dumpPath[NAME_BUF_LEN * 2] = {'\0'}; + char dumpPath[NAME_BUF_LEN] = {'\0'}; - NcmContentRecord *titleContentRecords = NULL; - u32 titleContentRecordsCnt = 0; + NcmContentInfo *titleContentInfos = NULL; + u32 titleContentInfoCnt = 0; - NcmNcaId ncaId; + NcmContentId ncaId; char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; NcmContentStorage ncmStorage; @@ -5724,74 +5567,60 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp if (curTikType != TICKET_TYPE_APP && curTikType != TICKET_TYPE_PATCH && curTikType != TICKET_TYPE_ADDON) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: invalid ticket title type!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid ticket title type!", __func__); goto out; } - if ((curTikType == TICKET_TYPE_APP && !titleAppStorageId) || (curTikType == TICKET_TYPE_PATCH && !titlePatchStorageId) || (curTikType == TICKET_TYPE_ADDON && !titlePatchStorageId)) + if ((curTikType == TICKET_TYPE_APP && !baseAppEntries) || (curTikType == TICKET_TYPE_PATCH && !patchEntries) || (curTikType == TICKET_TYPE_ADDON && !addOnEntries)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: title storage ID unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: title storage ID unavailable!", __func__); goto out; } if ((curTikType == TICKET_TYPE_APP && titleIndex >= titleAppCount) || (curTikType == TICKET_TYPE_PATCH && titleIndex >= titlePatchCount) || (curTikType == TICKET_TYPE_ADDON && titleIndex >= titleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); goto out; } - curStorageId = (curTikType == TICKET_TYPE_APP ? titleAppStorageId[titleIndex] : (curTikType == TICKET_TYPE_PATCH ? titlePatchStorageId[titleIndex] : titleAddOnStorageId[titleIndex])); + curStorageId = (curTikType == TICKET_TYPE_APP ? baseAppEntries[titleIndex].storageId : (curTikType == TICKET_TYPE_PATCH ? patchEntries[titleIndex].storageId : addOnEntries[titleIndex].storageId)); - filter = (curTikType == TICKET_TYPE_APP ? META_DB_REGULAR_APPLICATION : (curTikType == TICKET_TYPE_PATCH ? META_DB_PATCH : META_DB_ADDON)); + ncmTitleIndex = (curTikType == TICKET_TYPE_APP ? baseAppEntries[titleIndex].ncmIndex : (curTikType == TICKET_TYPE_PATCH ? patchEntries[titleIndex].ncmIndex : addOnEntries[titleIndex].ncmIndex)); - if (curStorageId == FsStorageId_GameCard) + metaType = (curTikType == TICKET_TYPE_APP ? NcmContentMetaType_Application : (curTikType == TICKET_TYPE_PATCH ? NcmContentMetaType_Patch : NcmContentMetaType_AddOnContent)); + + if (curStorageId == NcmStorageId_GameCard) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: invalid title storage ID!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title storage ID!", __func__); goto out; } if (sizeof(rsa2048_sha256_ticket) > freeSpace) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: not enough free space available in the SD card!"); + 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; } switch(curStorageId) { - case FsStorageId_SdCard: + case NcmStorageId_SdCard: titleCount = (curTikType == TICKET_TYPE_APP ? sdCardTitleAppCount : (curTikType == TICKET_TYPE_PATCH ? sdCardTitlePatchCount : sdCardTitleAddOnCount)); - ncmTitleIndex = titleIndex; break; - case FsStorageId_NandUser: - if (curTikType == TICKET_TYPE_APP) - { - titleCount = nandUserTitleAppCount; - ncmTitleIndex = (titleIndex - sdCardTitleAppCount); // Substract SD card app count - } else - if (curTikType == TICKET_TYPE_PATCH) - { - titleCount = nandUserTitlePatchCount; - ncmTitleIndex = (titleIndex - sdCardTitlePatchCount); // Substract SD card patch count - } else - if (curTikType == TICKET_TYPE_ADDON) - { - titleCount = nandUserTitleAddOnCount; - ncmTitleIndex = (titleIndex - sdCardTitleAddOnCount); // Substract SD card add-on count - } - + case NcmStorageId_BuiltInUser: + titleCount = (curTikType == TICKET_TYPE_APP ? emmcTitleAppCount : (curTikType == TICKET_TYPE_PATCH ? emmcTitlePatchCount : emmcTitleAddOnCount)); break; default: break; } - dumpName = generateNSPDumpName((nspDumpType)curTikType, titleIndex); + dumpName = generateNSPDumpName((nspDumpType)curTikType, titleIndex, false); if (!dumpName) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: unable to generate output dump name!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to generate output dump name!", __func__); goto out; } - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.tik", TICKET_PATH, dumpName); + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.tik", TICKET_PATH, dumpName); // Check if the dump already exists if (checkIfFileExists(dumpPath)) @@ -5812,33 +5641,40 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp } 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"))); - uiRefreshDisplay(); - breaks += 2; + 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 += 2; + breaks++; } - if (!retrieveNcaContentRecords(curStorageId, filter, titleCount, ncmTitleIndex, &titleContentRecords, &titleContentRecordsCnt)) goto out; + breaks++; - result = ncmOpenContentStorage(curStorageId, &ncmStorage); - if (R_FAILED(result)) + uiRefreshDisplay(); + + if (!retrieveContentInfosFromTitle(curStorageId, metaType, titleCount, ncmTitleIndex, &titleContentInfos, &titleContentInfoCnt)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: ncmOpenContentStorage failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); goto out; } - for(i = 0; i < titleContentRecordsCnt; i++) + result = ncmOpenContentStorage(&ncmStorage, curStorageId); + if (R_FAILED(result)) { - memcpy(&ncaId, &(titleContentRecords[i].ncaId), sizeof(NcmNcaId)); - convertDataToHexString(titleContentRecords[i].ncaId.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: ncmOpenContentStorage failed! (0x%08X)", __func__, result); + goto out; + } + + for(i = 0; i < titleContentInfoCnt; i++) + { + memcpy(&ncaId, &(titleContentInfos[i].content_id), sizeof(NcmContentId)); + convertDataToHexString(titleContentInfos[i].content_id.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: failed to read header from NCA \"%s\"! (0x%08X)", ncaIdStr, result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read header from NCA \"%s\"!", __func__, ncaIdStr); proceed = false; break; } @@ -5863,14 +5699,14 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp { if (!rights_info.has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: the selected %s doesn't use titlekey crypto! Rights ID field is empty in all the NCAs!", (curTikType == TICKET_TYPE_APP ? "base application" : (curTikType == TICKET_TYPE_PATCH ? "update" : "DLC"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected %s doesn't use titlekey crypto! Rights ID field is empty in all the NCAs!", __func__, (curTikType == TICKET_TYPE_APP ? "base application" : (curTikType == TICKET_TYPE_PATCH ? "update" : "DLC"))); goto out; } if (rights_info.missing_tik) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: the selected %s uses titlekey crypto, but no ticket for it is available! This is probably a pre-install.", (curTikType == TICKET_TYPE_APP ? "base application" : (curTikType == TICKET_TYPE_PATCH ? "update" : "DLC"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected %s uses titlekey crypto, but no ticket for it is available! This is probably a pre-install.", __func__, (curTikType == TICKET_TYPE_APP ? "base application" : (curTikType == TICKET_TYPE_PATCH ? "update" : "DLC"))); goto out; } } @@ -5895,14 +5731,14 @@ bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOp outFile = fopen(dumpPath, "wb"); if (!outFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: failed to open output file \"%s\"!", dumpPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open output file \"%s\"!", __func__, dumpPath); goto out; } size_t wr = fwrite(&(rights_info.tik_data), 1, sizeof(rsa2048_sha256_ticket), outFile); if (wr != sizeof(rsa2048_sha256_ticket)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "dumpTicketFromTitle: failed to write %u bytes long ticket data to \"%s\"! Wrote %lu bytes.", sizeof(rsa2048_sha256_ticket), dumpPath, wr); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to write %u bytes long ticket data to \"%s\"! Wrote %lu bytes.", __func__, sizeof(rsa2048_sha256_ticket), dumpPath, wr); removeFile = true; goto out; } @@ -5921,9 +5757,9 @@ out: if (!success && removeFile) unlink(dumpPath); - serviceClose(&(ncmStorage.s)); + ncmContentStorageClose(&ncmStorage); - if (titleContentRecords) free(titleContentRecords); + if (titleContentInfos) free(titleContentInfos); if (dumpName) free(dumpName); diff --git a/source/dumper.h b/source/dumper.h index 2facf36..7927930 100644 --- a/source/dumper.h +++ b/source/dumper.h @@ -6,8 +6,6 @@ #include #include "util.h" -#define ISTORAGE_PARTITION_CNT 2 - #define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF // 4 GiB - 1 (4294967295 bytes) #define SPLIT_FILE_XCI_PART_SIZE (u64)0xFFFF8000 // 4 GiB - 0x8000 (4294934528 bytes) (based on XCI-Cutter) @@ -18,12 +16,6 @@ #define CERT_OFFSET 0x7000 #define CERT_SIZE 0x200 -#define SMOOTHING_FACTOR (double)0.1 - -#define CANCEL_BTN_SEC_HOLD 2 // The cancel button must be held for at least CANCEL_BTN_SEC_HOLD seconds to cancel an ongoing operation - -#define DUMP_NSP_CRC_WAIT 5 // The user must wait for at least DUMP_NSP_CRC_WAIT seconds before the CRC32 checksum calculation process starts after the NSP dump process is finished - typedef struct { bool keepCert; // Original value for the "Keep certificate" option. Overrides the selected setting in the current session bool trimDump; // Original value for the "Trim output dump" option. Overrides the selected setting in the current session @@ -36,7 +28,7 @@ typedef struct { } PACKED sequentialXciCtx; typedef struct { - FsStorageId 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 @@ -54,22 +46,23 @@ typedef struct { bool enabled; nspDumpType titleType; u32 titleIndex; + u64 contentSize; + char *contentSizeStr; char nspFilename[NAME_BUF_LEN]; char truncatedNspFilename[NAME_BUF_LEN]; } batchEntry; -void workaroundPartitionZeroAccess(); -bool dumpCartridgeImage(xciOptions *xciDumpCfg); -bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch); -bool dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg); +bool dumpNXCardImage(xciOptions *xciDumpCfg); +int dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, nspOptions *nspDumpCfg, bool batch); +int dumpNintendoSubmissionPackageBatch(batchOptions *batchDumpCfg); bool dumpRawHfs0Partition(u32 partition, bool doSplitting); bool dumpHfs0PartitionData(u32 partition, bool doSplitting); -bool dumpFileFromHfs0Partition(u32 partition, u32 file, char *filename, bool doSplitting); -bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, bool doSplitting); -bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, bool doSplitting); -bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting); -bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, bool doSplitting); -bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting); +bool dumpFileFromHfs0Partition(u32 partition, u32 fileIndex, char *filename, bool doSplitting); +bool dumpExeFsSectionData(u32 titleIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg); +bool dumpFileFromExeFsSection(u32 titleIndex, u32 fileIndex, bool usePatch, ncaFsOptions *exeFsDumpCfg); +bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg); +bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg); +bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, ncaFsOptions *romFsDumpCfg); bool dumpGameCardCertificate(); bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg); diff --git a/source/es.c b/source/es.c index 20f2f17..27592b7 100644 --- a/source/es.c +++ b/source/es.c @@ -5,171 +5,72 @@ #include #include "es.h" +#include "service_guard.h" static Service g_esSrv; -static u64 g_esRefCnt; -Result esInitialize() -{ - atomicIncrement64(&g_esRefCnt); - - if (serviceIsActive(&g_esSrv)) return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); +NX_GENERATE_SERVICE_GUARD(es); +Result _esInitialize() { return smGetService(&g_esSrv, "es"); } -void esExit() -{ - if (atomicDecrement64(&g_esRefCnt) == 0) serviceClose(&g_esSrv); +void _esCleanup() { + serviceClose(&g_esSrv); } Result esCountCommonTicket(u32 *num_tickets) { - IpcCommand c; - ipcInitialize(&c); - struct { - u64 magic; - u64 cmd_id; - } *raw; + u32 num_tickets; + } out; - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 9; - - Result rc = serviceIpcDispatch(&g_esSrv); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 num_tickets; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) *num_tickets = resp->num_tickets; - } + Result rc = serviceDispatchOut(&g_esSrv, 9, out); + if (R_SUCCEEDED(rc) && num_tickets) *num_tickets = out.num_tickets; return rc; } Result esCountPersonalizedTicket(u32 *num_tickets) { - IpcCommand c; - ipcInitialize(&c); - struct { - u64 magic; - u64 cmd_id; - } *raw; + u32 num_tickets; + } out; - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 10; - - Result rc = serviceIpcDispatch(&g_esSrv); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 num_tickets; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) *num_tickets = resp->num_tickets; - } + Result rc = serviceDispatchOut(&g_esSrv, 10, out); + if (R_SUCCEEDED(rc) && num_tickets) *num_tickets = out.num_tickets; return rc; } Result esListCommonTicket(u32 *numRightsIdsWritten, FsRightsId *outBuf, size_t bufSize) { - IpcCommand c; - ipcInitialize(&c); - ipcAddRecvBuffer(&c, outBuf, bufSize, BufferType_Normal); - struct { - u64 magic; - u64 cmd_id; - } *raw; + u32 num_rights_ids_written; + } out; - raw = ipcPrepareHeader(&c, sizeof(*raw)); + Result rc = serviceDispatchInOut(&g_esSrv, 11, *numRightsIdsWritten, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf, bufSize } }, + ); - raw->magic = SFCI_MAGIC; - raw->cmd_id = 11; - - Result rc = serviceIpcDispatch(&g_esSrv); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 num_rights_ids_written; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) - { - if (numRightsIdsWritten) *numRightsIdsWritten = resp->num_rights_ids_written; - } - } + if (R_SUCCEEDED(rc) && numRightsIdsWritten) *numRightsIdsWritten = out.num_rights_ids_written; return rc; } Result esListPersonalizedTicket(u32 *numRightsIdsWritten, FsRightsId *outBuf, size_t bufSize) { - IpcCommand c; - ipcInitialize(&c); - ipcAddRecvBuffer(&c, outBuf, bufSize, BufferType_Normal); - struct { - u64 magic; - u64 cmd_id; - } *raw; + u32 num_rights_ids_written; + } out; - raw = ipcPrepareHeader(&c, sizeof(*raw)); - raw->magic = SFCI_MAGIC; - raw->cmd_id = 12; + Result rc = serviceDispatchInOut(&g_esSrv, 12, *numRightsIdsWritten, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { outBuf, bufSize } }, + ); - Result rc = serviceIpcDispatch(&g_esSrv); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 num_rights_ids_written; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) - { - if (numRightsIdsWritten) *numRightsIdsWritten = resp->num_rights_ids_written; - } - } + if (R_SUCCEEDED(rc) && numRightsIdsWritten) *numRightsIdsWritten = out.num_rights_ids_written; return rc; } diff --git a/source/fs_ext.c b/source/fs_ext.c index beaff3d..9068e1a 100644 --- a/source/fs_ext.c +++ b/source/fs_ext.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -7,124 +8,41 @@ // IFileSystemProxy Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32 partition) { - IpcCommand c; - ipcInitialize(&c); - struct { - u64 magic; - u64 cmd_id; u32 handle; u32 partition; - } *raw; + } in = { handle->value, partition }; - raw = serviceIpcPrepareHeader(fsGetServiceSession(), &c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 30; - raw->handle = handle->value; - raw->partition = partition; - - Result rc = serviceIpcDispatch(fsGetServiceSession()); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - - struct { - u64 magic; - u64 result; - } *resp; - - serviceIpcParse(fsGetServiceSession(), &r, sizeof(*resp)); - resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) serviceCreateSubservice(&out->s, fsGetServiceSession(), &r, 0); - } - - return rc; + return serviceDispatchIn(fsGetServiceSession(), 30, in, + .out_num_objects = 1, + .out_objects = &out->s + ); } Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out) { - IpcCommand c; - ipcInitialize(&c); - - struct { - u64 magic; - u64 cmd_id; - } *raw; - - raw = serviceIpcPrepareHeader(fsGetServiceSession(), &c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 501; - - Result rc = serviceIpcDispatch(fsGetServiceSession()); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - - struct { - u64 magic; - u64 result; - } *resp; - - serviceIpcParse(fsGetServiceSession(), &r, sizeof(*resp)); - resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) serviceCreateSubservice(&out->s, fsGetServiceSession(), &r, 0); - } - - return rc; + return serviceDispatch(fsGetServiceSession(), 501, + .out_num_objects = 1, + .out_objects = &out->s + ); } // IDeviceOperator Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator* d, const FsGameCardHandle* handle, u32* out_title_version, u64* out_title_id) { - IpcCommand c; - ipcInitialize(&c); + struct { + u32 handle; + } in = { handle->value }; struct { - u64 magic; - u64 cmd_id; - u32 handle; - } *raw; + u32 title_ver; + u64 title_id; + } out; - raw = serviceIpcPrepareHeader(&d->s, &c, sizeof(*raw)); + Result rc = serviceDispatchInOut(&d->s, 203, in, out); - raw->magic = SFCI_MAGIC; - raw->cmd_id = 203; - raw->handle = handle->value; - - Result rc = serviceIpcDispatch(&d->s); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - - struct { - u64 magic; - u64 result; - u32 title_ver; - u64 title_id; - } *resp; - - serviceIpcParse(&d->s, &r, sizeof(*resp)); - resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) - { - if (out_title_version != NULL) *out_title_version = resp->title_ver; - if (out_title_id != NULL) *out_title_id = resp->title_id; - } - } + if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_ver; + if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id; return rc; } diff --git a/source/keys.c b/source/keys.c index 852a459..a24bc0a 100644 --- a/source/keys.c +++ b/source/keys.c @@ -95,7 +95,7 @@ bool retrieveProcessMemory(keyLocation *location) { if (!location || !location->titleID || !location->mask) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve process memory."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve process memory.", __func__); return false; } @@ -110,24 +110,24 @@ bool retrieveProcessMemory(keyLocation *location) // If not a kernel process, get PID from pm:dmnt u64 pid; - result = pmdmntGetTitlePid(&pid, location->titleID); + result = pmdmntGetProcessId(&pid, location->titleID); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: pmdmntGetTitlePid failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: pmdmntGetProcessId failed! (0x%08X)", __func__, result); return false; } result = svcDebugActiveProcess(&debug_handle, pid); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcDebugActiveProcess failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcDebugActiveProcess failed! (0x%08X)", __func__, result); return false; } result = svcGetDebugEvent((u8*)&d, debug_handle); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetDebugEvent failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetDebugEvent failed! (0x%08X)", __func__, result); return false; } } else { @@ -138,7 +138,7 @@ bool retrieveProcessMemory(keyLocation *location) result = svcGetProcessList(&num_processes, pids, 300); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetProcessList failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetProcessList failed! (0x%08X)", __func__, result); return false; } @@ -147,13 +147,12 @@ bool retrieveProcessMemory(keyLocation *location) for(i = 0; i < (num_processes - 1); i++) { if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pids[i])) && R_SUCCEEDED(svcGetDebugEvent((u8*)&d, debug_handle)) && (d[2] == location->titleID)) break; - if (debug_handle) svcCloseHandle(debug_handle); } if (i == (num_processes - 1)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve debug handle for process with Title ID %016lX!", location->titleID); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve debug handle for process with Title ID %016lX!", __func__, location->titleID); if (debug_handle) svcCloseHandle(debug_handle); return false; } @@ -176,7 +175,7 @@ bool retrieveProcessMemory(keyLocation *location) result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result); success = false; break; } @@ -200,7 +199,7 @@ bool retrieveProcessMemory(keyLocation *location) result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result); success = false; break; } @@ -212,7 +211,7 @@ bool retrieveProcessMemory(keyLocation *location) dataTmp = realloc(location->data, location->dataSize + mem_info.size); if (!dataTmp) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to resize key location data buffer to %lu bytes.", location->dataSize + mem_info.size); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to resize key location data buffer to %lu bytes.", __func__, location->dataSize + mem_info.size); success = false; break; } @@ -225,7 +224,7 @@ bool retrieveProcessMemory(keyLocation *location) result = svcReadDebugProcessMemory(location->data + location->dataSize, debug_handle, mem_info.addr, mem_info.size); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcReadDebugProcessMemory failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcReadDebugProcessMemory failed! (0x%08X)", __func__, result); success = false; break; } @@ -251,7 +250,7 @@ bool findKeyInProcessMemory(const keyLocation *location, const keyInfo *findKey, { if (!location || !location->data || !location->dataSize || !findKey || !strlen(findKey->name) || !findKey->size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to locate key in process memory."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate key in process memory.", __func__); return false; } @@ -275,7 +274,7 @@ bool findKeyInProcessMemory(const keyLocation *location, const keyInfo *findKey, } } - if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to locate key \"%s\" in process memory!", findKey->name); + if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate key \"%s\" in process memory!", __func__, findKey->name); return found; } @@ -284,7 +283,7 @@ bool findFSRodataKeys(keyLocation *location) { if (!location || location->titleID != FS_TID || location->mask != SEG_RODATA || !location->data || !location->dataSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to locate keys in FS RODATA segment."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate keys in FS .rodata segment.", __func__); return false; } @@ -325,14 +324,14 @@ bool loadMemoryKeys() result = splCryptoInitialize(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result); return false; } result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_keyset.header_kek); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", __func__, result); splCryptoExit(); return false; } @@ -342,7 +341,7 @@ bool loadMemoryKeys() result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", __func__, result); splCryptoExit(); return false; } @@ -350,7 +349,7 @@ bool loadMemoryKeys() result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", __func__, result); splCryptoExit(); return false; } @@ -368,7 +367,7 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out) { if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !out) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to decrypt NCA key area."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to decrypt NCA key area.", __func__); return false; } @@ -380,7 +379,7 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out) u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type); if (crypto_type > 0x20) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index!", __func__); return false; } @@ -389,14 +388,14 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out) result = splCryptoInitialize(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result); return false; } result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", __func__, result); splCryptoExit(); return false; } @@ -409,7 +408,7 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out) result = splCryptoGenerateAesKey(tmp_kek, dec_nca_header->nca_keys[i], decrypted_nca_keys[i]); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", i, result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", __func__, i, result); success = false; break; } @@ -574,7 +573,7 @@ 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, "Error: key (%s) must be %u hex digits!", 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; } @@ -583,7 +582,7 @@ int parse_hex_key(unsigned char *key, const char *hex, unsigned int len) { if (!ishex(hex[i])) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: key (%s) must be %u hex digits!", 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; } } @@ -679,7 +678,7 @@ bool loadExternalKeys() FILE *keysFile = fopen(KEYS_FILE_PATH, "rb"); if (!keysFile) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", KEYS_FILE_PATH); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", __func__, KEYS_FILE_PATH); return false; } @@ -689,7 +688,7 @@ bool loadExternalKeys() if (ret < 1) { - if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to parse necessary keys from \"%s\"! (keys file empty?)", KEYS_FILE_PATH); + if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse necessary keys from \"%s\"! (keys file empty?)", __func__, KEYS_FILE_PATH); return false; } @@ -700,7 +699,7 @@ bool testKeyPair(const void *E, const void *D, const void *N) { if (!E || !D || !N) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to test RSA key pair."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to test RSA key pair.", __func__); return false; } @@ -717,14 +716,14 @@ bool testKeyPair(const void *E, const void *D, const void *N) result = splUserExpMod(X, N, D, 0x100, Y); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #1) (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #1) (0x%08X)", __func__, result); return false; } result = splUserExpMod(Y, N, E, 4, Z); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #2) (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #2) (0x%08X)", __func__, result); return false; } @@ -732,7 +731,7 @@ bool testKeyPair(const void *E, const void *D, const void *N) { if (X[i] != Z[i]) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid RSA key pair!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RSA key pair!", __func__); return false; } } @@ -775,7 +774,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || (!out_tik && !out_dec_key && !out_enc_key)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve NCA ticket and/or titlekey."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve NCA ticket and/or titlekey.", __func__); return ret; } @@ -793,7 +792,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't use titlekey crypto."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't use titlekey crypto.", __func__); return ret; } @@ -802,7 +801,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (crypto_type >= 0x20) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); return ret; } @@ -840,14 +839,14 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = esInitialize(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the ES service! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the ES service! (0x%08X)", __func__, result); return ret; } result = esCountCommonTicket(&common_count); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountCommonTicket failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountCommonTicket failed! (0x%08X)", __func__, result); esExit(); return ret; } @@ -855,14 +854,14 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = esCountPersonalizedTicket(&personalized_count); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountPersonalizedTicket failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountPersonalizedTicket failed! (0x%08X)", __func__, result); esExit(); return ret; } if (!common_count && !personalized_count) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: no tickets available!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: no tickets available!", __func__); esExit(); return ret; } @@ -872,7 +871,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en common_rights_ids = calloc(common_count, sizeof(FsRightsId)); if (!common_rights_ids) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for common tickets' rights IDs!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for common tickets' rights IDs!", __func__); esExit(); return ret; } @@ -880,7 +879,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = esListCommonTicket(&ids_written, common_rights_ids, common_count * sizeof(FsRightsId)); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListCommonTicket failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListCommonTicket failed! (0x%08X)", __func__, result); free(common_rights_ids); esExit(); return ret; @@ -904,7 +903,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en personalized_rights_ids = calloc(personalized_count, sizeof(FsRightsId)); if (!personalized_rights_ids) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for personalized tickets' rights IDs!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for personalized tickets' rights IDs!", __func__); esExit(); return ret; } @@ -912,7 +911,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = esListPersonalizedTicket(&ids_written, personalized_rights_ids, personalized_count * sizeof(FsRightsId)); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListPersonalizedTicket failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListPersonalizedTicket failed! (0x%08X)", __func__, result); free(personalized_rights_ids); esExit(); return ret; @@ -935,7 +934,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!foundRightsId || (rightsIdType != 1 && rightsIdType != 2)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA rights ID unavailable in this console!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA rights ID unavailable in this console!", __func__); ret = -2; return ret; } @@ -951,7 +950,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = setcalInitialize(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the set:cal service! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the set:cal service! (0x%08X)", __func__, result); return ret; } @@ -961,7 +960,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: setcalGetEticketDeviceKey failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: setcalGetEticketDeviceKey failed! (0x%08X)", __func__, result); return ret; } @@ -974,35 +973,33 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en // The value is stored use big endian byte order if (bswap_32(*((u32*)(&(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200])))) != SIGTYPE_RSA2048_SHA1) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid public RSA exponent for eTicket data! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch."); + 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[ETICKET_DEVKEY_RSA_OFFSET]); + N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]); + E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]); + + if (!setcal_eticket_retrieved) + { if (!testKeyPair(E, D, N)) return ret; - setcal_eticket_retrieved = true; - } else { - D = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET]); - N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]); - E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]); } // FatFs is used to mount the BIS System partition and read the ES savedata files to avoid 0xE02 (file already in use) errors fr = f_open(&eTicketSave, (rightsIdType == 1 ? BIS_COMMON_TIK_SAVE_NAME : BIS_PERSONALIZED_TIK_SAVE_NAME), FA_READ | FA_OPEN_EXISTING); if (fr) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open ES %s eTicket save! (%u)", (rightsIdType == 1 ? "common" : "personalized"), fr); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open ES %s eTicket save! (%u)", __func__, (rightsIdType == 1 ? "common" : "personalized"), fr); return ret; } save_ctx = calloc(1, sizeof(save_ctx_t)); if (!save_ctx) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for ticket savefile context!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for ticket savefile context!"); f_close(&eTicketSave); return ret; } @@ -1013,7 +1010,8 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!save_process(save_ctx)) { - strcat(strbuf, "\nError: failed to process ticket savefile!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to process ticket savefile!", __func__); + strcat(strbuf, tmp); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); free(save_ctx); f_close(&eTicketSave); @@ -1022,7 +1020,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to get file entry for \"%s\" in ticket savefile!", ticket_bin_path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to get file entry for \"%s\" in ticket savefile!", __func__, ticket_bin_path); strcat(strbuf, tmp); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); save_free_contexts(save_ctx); @@ -1033,7 +1031,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to open FAT storage at block 0x%X for \"%s\" in ticket savefile!", entry.value.save_file_info.start_block, ticket_bin_path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT storage at block 0x%X for \"%s\" in ticket savefile!", __func__, entry.value.save_file_info.start_block, ticket_bin_path); strcat(strbuf, tmp); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); save_free_contexts(save_ctx); @@ -1047,7 +1045,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en br = save_allocation_table_storage_read(&fat_storage, dumpBuf, total_br, buf_size); if (br != buf_size) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to read %u bytes chunk at offset 0x%lX from \"%s\" in ticket savefile!", buf_size, total_br, ticket_bin_path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read %u bytes chunk at offset 0x%lX from \"%s\" in ticket savefile!", __func__, buf_size, total_br, ticket_bin_path); strcat(strbuf, tmp); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); proceed = false; @@ -1079,7 +1077,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en result = splUserExpMod(titleKeyBlock, N, D, 0x100, M); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (titleKeyBlock) (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (titleKeyBlock) (0x%08X)", __func__, result); proceed = false; break; } @@ -1094,7 +1092,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en // Verify if it starts with a null string hash if (memcmp(db, null_hash, 0x20) != 0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: titlekey decryption failed! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: titlekey decryption failed! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); proceed = false; break; } @@ -1116,7 +1114,7 @@ int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_en if (!foundEticket) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find a matching eTicket entry for NCA rights ID!"); + 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; return ret; } @@ -1145,7 +1143,7 @@ bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *d { if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !decrypted_nca_keys || !nca_keyset.ext_key_cnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to generate encrypted NCA key area using titlekey!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate encrypted NCA key area using titlekey!", __func__); return false; } @@ -1157,7 +1155,7 @@ bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *d if (crypto_type >= 0x20) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); return false; } diff --git a/source/keys.h b/source/keys.h index 7bf1afe..11cd831 100644 --- a/source/keys.h +++ b/source/keys.h @@ -56,8 +56,8 @@ 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. */ - // Needed to decrypt saves from system and application titles - u8 save_mac_key[0x10]; + // 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; bool loadMemoryKeys(); diff --git a/source/main.c b/source/main.c index 8a7f2b4..b907320 100644 --- a/source/main.c +++ b/source/main.c @@ -2,203 +2,19 @@ #include #include #include -#include -#include "dumper.h" #include "ui.h" #include "util.h" -#include "fs_ext.h" -#include "keys.h" - -/* Extern variables */ - -extern bool keysFileAvailable; - -extern FsDeviceOperator fsOperatorInstance; - -extern FsEventNotifier fsGameCardEventNotifier; -extern Handle fsGameCardEventHandle; -extern Event fsGameCardKernelEvent; -extern UEvent exitEvent; - -extern bool gameCardInserted; - -extern char appLaunchPath[NAME_BUF_LEN]; - -extern nca_keyset_t nca_keyset; int main(int argc, char *argv[]) { - /* Copy launch path */ - if (!envIsNso() && argc > 0) - { - int i; - for(i = 0; i < argc; i++) - { - if (strlen(argv[i]) > 10 && !strncasecmp(argv[i], "sdmc:/", 6) && !strncasecmp(argv[i] + strlen(argv[i]) - 4, ".nro", 4)) - { - snprintf(appLaunchPath, MAX_ELEMENTS(appLaunchPath), argv[i]); - break; - } - } - } - - /* Initialize UI */ - if (!uiInit()) return -1; - - /* Zero out NCA keyset */ - memset(&nca_keyset, 0, sizeof(nca_keyset_t)); - - /* Init ExeFS context */ - initExeFsContext(); - - /* Init RomFS context */ - initRomFsContext(); - - /* Init BKTR context */ - initBktrContext(); - - /* Make sure output directories exist */ - createOutputDirectories(); - - /* Load settings from configuration file */ - loadConfig(); - - /* Check if the Lockpick_RCM keys file is available */ - keysFileAvailable = checkIfFileExists(KEYS_FILE_PATH); - - /* Enable CPU boost mode */ - appletSetCpuBoostMode(ApmCpuBoostMode_Type1); - - Result result; - Thread thread; - int ret = 0; bool exitMainLoop = false; - bool initNcm = false, initNs = false, initCsrng = false, initSpl = false, initPmdmnt = false; - bool openFsDevOp = false, openGcEvtNotifier = false, loadGcKernEvt = false, startGcThread = false; - /* Initialize the ncm service */ - result = ncmInitialize(); - if (R_FAILED(result)) + /* Initialize application resources */ + if (!initApplicationResources(argc, argv)) { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the ncm service! (0x%08X)", result); - ret = -2; - goto out; - } - - initNcm = true; - - /* Initialize the ns service */ - result = nsInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the ns service! (0x%08X)", result); - ret = -3; - goto out; - } - - initNs = true; - - /* Initialize the csrng service */ - result = csrngInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the csrng service! (0x%08X)", result); - ret = -4; - goto out; - } - - initCsrng = true; - - /* Initialize the spl service */ - result = splInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the spl service! (0x%08X)", result); - ret = -5; - goto out; - } - - initSpl = true; - - /* Initialize the pm:dmnt service */ - result = pmdmntInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the pm:dmnt service! (0x%08X)", result); - ret = -6; - goto out; - } - - initPmdmnt = true; - - /* Open device operator */ - result = fsOpenDeviceOperator(&fsOperatorInstance); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to open device operator! (0x%08X)", result); - ret = -7; - goto out; - } - - openFsDevOp = true; - - /* Open gamecard detection event notifier */ - result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to open gamecard detection event notifier! (0x%08X)", result); - ret = -8; - goto out; - } - - openGcEvtNotifier = true; - - /* Retrieve gamecard detection event handle */ - result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to retrieve gamecard detection event handle! (0x%08X)", result); - ret = -9; - goto out; - } - - /* Retrieve initial gamecard status */ - gameCardInserted = isGameCardInserted(); - - /* Load gamecard detection kernel event */ - eventLoadRemote(&fsGameCardKernelEvent, fsGameCardEventHandle, false); - - loadGcKernEvt = true; - - /* Create usermode exit event */ - ueventCreate(&exitEvent, false); - - /* Create gamecard detection thread */ - result = threadCreate(&thread, fsGameCardDetectionThreadFunc, NULL, 0x10000, 0x2C, -2); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to create gamecard detection thread! (0x%08X)", result); - ret = -10; - goto out; - } - - /* Start gamecard detection thread */ - result = threadStart(&thread); - if (R_FAILED(result)) - { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to start gamecard detection thread! (0x%08X)", result); - ret = -11; - goto out; - } - - startGcThread = true; - - /* Mount BIS System partition from the eMMC */ - if (!mountSysEmmcPartition()) - { - ret = -12; + ret = -1; goto out; } @@ -353,57 +169,9 @@ int main(int argc, char *argv[]) if (exitMainLoop) break; } - /* Signal the exit event to terminate the gamecard detection thread */ - ueventSignal(&exitEvent); - - /* Wait for the gamecard detection thread to exit */ - threadWaitForExit(&thread); - out: - if (ret < 0) - { - uiRefreshDisplay(); - delay(5); - } - - /* Unmount BIS System partition from the eMMC */ - unmountSysEmmcPartition(); - - /* Close gamecard detection thread */ - if (startGcThread) threadClose(&thread); - - /* Close gamecard detection kernel event */ - if (loadGcKernEvt) eventClose(&fsGameCardKernelEvent); - - /* Close gamecard detection event notifier */ - if (openGcEvtNotifier) fsEventNotifierClose(&fsGameCardEventNotifier); - - /* Close device operator */ - if (openFsDevOp) fsDeviceOperatorClose(&fsOperatorInstance); - - /* Denitialize the pm:dmnt service */ - if (initPmdmnt) pmdmntExit(); - - /* Denitialize the spl service */ - if (initSpl) splExit(); - - /* Denitialize the csrng service */ - if (initCsrng) csrngExit(); - - /* Denitialize the ns service */ - if (initNs) nsExit(); - - /* Denitialize the ncm service */ - if (initNcm) ncmExit(); - - /* Disable CPU boost mode */ - appletSetCpuBoostMode(ApmCpuBoostMode_Disabled); - - /* Free global resources */ - freeGlobalData(); - - /* Deinitialize UI */ - uiDeinit(); + /* Deinitialize application resources */ + deinitApplicationResources(); return ret; } diff --git a/source/nca.c b/source/nca.c index ac7a85a..af30573 100644 --- a/source/nca.c +++ b/source/nca.c @@ -28,13 +28,13 @@ char *getTitleType(u8 type) switch(type) { - case META_DB_REGULAR_APPLICATION: + case NcmContentMetaType_Application: out = "Application"; break; - case META_DB_PATCH: + case NcmContentMetaType_Patch: out = "Patch"; break; - case META_DB_ADDON: + case NcmContentMetaType_AddOnContent: out = "AddOnContent"; break; default: @@ -51,7 +51,7 @@ char *getContentType(u8 type) switch(type) { - case NcmContentType_CNMT: + case NcmContentType_Meta: out = "Meta"; break; case NcmContentType_Program: @@ -60,16 +60,16 @@ char *getContentType(u8 type) case NcmContentType_Data: out = "Data"; break; - case NcmContentType_Icon: + case NcmContentType_Control: out = "Control"; break; - case NcmContentType_Doc: + case NcmContentType_HtmlDocument: out = "HtmlDocument"; break; - case NcmContentType_Info: + case NcmContentType_LegalInformation: out = "LegalInformation"; break; - case NCA_CONTENT_TYPE_DELTA: + case NcmContentType_DeltaFragment: out = "DeltaFragment"; break; default: @@ -86,11 +86,11 @@ char *getRequiredMinTitleType(u8 type) switch(type) { - case META_DB_REGULAR_APPLICATION: - case META_DB_PATCH: + case NcmContentMetaType_Application: + case NcmContentMetaType_Patch: out = "RequiredSystemVersion"; break; - case META_DB_ADDON: + case NcmContentMetaType_AddOnContent: out = "RequiredApplicationVersion"; break; default: @@ -107,13 +107,13 @@ char *getReferenceTitleIDType(u8 type) switch(type) { - case META_DB_REGULAR_APPLICATION: + case NcmContentMetaType_Application: out = "PatchId"; break; - case META_DB_PATCH: + case NcmContentMetaType_Patch: out = "OriginalId"; break; - case META_DB_ADDON: + case NcmContentMetaType_AddOnContent: out = "ApplicationId"; break; default: @@ -157,7 +157,7 @@ void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_i xml_content_info[i].size, \ xml_content_info[i].hash_str, \ xml_content_info[i].keyblob, \ - (xml_content_info[i].type != NcmContentType_CNMT ? xml_content_info[i].id_offset : 0)); + xml_content_info[i].id_offset); strcat(out, tmp); } @@ -222,7 +222,7 @@ bool loadNcaKeyset() envIsSyscallHinted(0x69) && // svcQueryDebugProcessMemory envIsSyscallHinted(0x6a))) // svcReadDebugProcessMemory { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: please run the application with debug svc permissions!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: please run the application with debug svc permissions!", __func__); return false; } @@ -288,17 +288,56 @@ static void nca_update_bktr_ctr(unsigned char *ctr, u32 ctr_val, u64 ofs) } } -bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt) +bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize) +{ + if (!ncmStorage || !ncaId || !outBuf || !bufSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read data from NCA!", __func__); + return false; + } + + Result result = 0; + bool success = false; + + char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}, nca_path[0x301] = {'\0'}; + convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1); + + result = ncmContentStorageGetPath(ncmStorage, nca_path, MAX_CHARACTERS(nca_path), ncaId); + if (R_FAILED(result) || !strlen(nca_path)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to retrieve content path for NCA \"%s\"! (0x%08X)", __func__, nca_id, result); + return false; + } + + // Check if we're dealing with a gamecard NCA + if (!strncmp(nca_path, "@Gc", 3)) + { + // Retrieve NCA data using raw IStorage reads + // Fixes NCA access problems with gamecards under low HOS versions when using ncmContentStorageReadContentIdFile() + success = readFileFromSecureHfs0PartitionByName(strchr(nca_path, '/') + 1, offset, outBuf, bufSize); + if (!success) breaks++; + } else { + // Retrieve NCA data normally + // This strips NAX0 encryption from SD card NCAs (not used with eMMC NCAs) + result = ncmContentStorageReadContentIdFile(ncmStorage, outBuf, bufSize, ncaId, offset); + success = R_SUCCEEDED(result); + } + + if (!success) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", __func__, bufSize, offset, nca_id, result); + + return success; +} + +bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt) { if (!ncmStorage || !ncaId || !outBuf || !bufSize || !ctx) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to process %s NCA section block!", encrypt ? "decrypted" : "encrypted"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process %s NCA section block!", __func__, (encrypt ? "decrypted" : "encrypted")); return false; } if (!loadNcaKeyset()) return false; - Result result; unsigned char ctr[0x10]; char nca_id[SHA256_HASH_SIZE + 1] = {'\0'}; @@ -311,10 +350,10 @@ bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *nc u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (offset - block_start_offset)) : bufSize); - result = ncmContentStorageReadContentIdFile(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used); - if (R_FAILED(result)) + if (!readNcaDataByContentId(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read encrypted %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", block_size_used, block_start_offset, nca_id, result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted data block from NCA \"%s\"!", __func__, nca_id); return false; } @@ -356,7 +395,7 @@ bktr_relocation_entry_t *bktr_get_relocation(bktr_relocation_block_t *block, u64 // Weak check for invalid offset if (offset > block->total_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: too big offset looked up in BKTR relocation table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: too big offset looked up in BKTR relocation table!", __func__); return NULL; } @@ -393,7 +432,7 @@ bktr_relocation_entry_t *bktr_get_relocation(bktr_relocation_block_t *block, u64 } } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to find offset 0x%016lX in BKTR relocation table!", offset); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR relocation table!", __func__, offset); return NULL; } @@ -442,7 +481,7 @@ bktr_subsection_entry_t *bktr_get_subsection(bktr_subsection_block_t *block, u64 } } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to find offset 0x%016lX in BKTR subsection table!", offset); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to find offset 0x%016lX in BKTR subsection table!", __func__, offset); return NULL; } @@ -450,7 +489,7 @@ bool bktrSectionSeek(u64 offset) { if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to seek within NCA BKTR section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to seek within NCA BKTR section!", __func__); return false; } @@ -480,11 +519,10 @@ bool bktrSectionPhysicalRead(void *outBuf, size_t bufSize) { if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !outBuf || !bufSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to perform physical block read from NCA BKTR section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform physical block read from NCA BKTR section!", __func__); return false; } - Result result; unsigned char ctr[0x10]; bktr_subsection_entry_t *subsec = bktr_get_subsection(bktrContext.subsection_block, bktrContext.bktr_seek); @@ -511,10 +549,10 @@ bool bktrSectionPhysicalRead(void *outBuf, size_t bufSize) { u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); - result = ncmContentStorageReadContentIdFile(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "BKTR: failed to read encrypted %lu bytes block at offset 0x%016lX! (0x%08X)", block_size_used, block_start_offset, result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read encrypted %lu bytes block at offset 0x%016lX!", __func__, block_size_used, block_start_offset); return false; } @@ -553,7 +591,7 @@ bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize) { if (!bktrContext.section_offset || !bktrContext.section_size || !bktrContext.relocation_block || !bktrContext.subsection_block || !romFsContext.section_offset || !romFsContext.section_size || !outBuf || !bufSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to read block from NCA BKTR section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read block from NCA BKTR section!", __func__); return false; } @@ -595,7 +633,7 @@ 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)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA header encryption parameters."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header encryption parameters.", __func__); return false; } @@ -618,7 +656,7 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, true); if (crypt_res != NCA_FULL_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for encrypted NCA header! (%u != %lu)", NCA_FULL_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); return false; } } else @@ -627,7 +665,7 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, true); if (crypt_res != NCA_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for encrypted NCA header! (%u != %lu)", NCA_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); return false; } @@ -636,12 +674,12 @@ bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, true); if (crypt_res != NCA_SECTION_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for encrypted NCA header section #%u! (%u != %lu)", i, NCA_SECTION_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for encrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); return false; } } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid decrypted NCA magic word! (0x%08X)", 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__, bswap_32(input->magic)); return false; } @@ -652,7 +690,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title { if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH || !out || !decrypted_nca_keys) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA header decryption parameters."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA header decryption parameters!", __func__); return false; } @@ -677,7 +715,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, false); if (crypt_res != NCA_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for decrypted NCA header! (%u != %lu)", NCA_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_HEADER_LENGTH, crypt_res); return false; } @@ -686,7 +724,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, false); if (crypt_res != NCA_FULL_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for decrypted NCA header! (%u != %lu)", NCA_FULL_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header! (%u != %lu)", __func__, NCA_FULL_HEADER_LENGTH, crypt_res); return false; } } else @@ -699,7 +737,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, false); if (crypt_res != NCA_SECTION_HEADER_LENGTH) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid output length for decrypted NCA header section #%u! (%u != %lu)", i, NCA_SECTION_HEADER_LENGTH, crypt_res); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid output length for decrypted NCA header section #%u! (%u != %lu)", __func__, i, NCA_SECTION_HEADER_LENGTH, crypt_res); return false; } } else { @@ -707,7 +745,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, "Error: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", 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__, bswap_32(out->magic)); return false; } @@ -783,29 +821,29 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title return true; } -bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data *output) +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) { if (!ncmStorage || !ncaId || !dec_nca_header || !xml_content_info || !output) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to process Program NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to process Program NCA!", __func__); return false; } if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 doesn't hold a PFS0 partition!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); return false; } if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid size for PFS0 partition in Program NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); return false; } if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid AES crypt type for Program NCA section #0! (0x%02X)", dec_nca_header->fs_headers[0].crypt_type); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); return false; } @@ -816,7 +854,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca u64 nca_pfs0_offset; pfs0_header nca_pfs0_header; - pfs0_entry_table *nca_pfs0_entries = NULL; + pfs0_file_entry *nca_pfs0_entries = NULL; u64 nca_pfs0_data_offset; npdm_t npdm_header; @@ -843,7 +881,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !hash_table_offset || hash_table_offset < section_offset || !nca_pfs0_offset || nca_pfs0_offset <= hash_table_offset) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offsets for Program NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); return false; } @@ -865,38 +903,38 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 partition header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); return false; } if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: 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.", 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__, bswap_32(nca_pfs0_header.magic)); return false; } if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); return false; } - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_entry_table)); + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); if (!nca_pfs0_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 partition entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); return false; } - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table), false)) + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 partition entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); free(nca_pfs0_entries); return false; } - nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table)) + (u64)nca_pfs0_header.str_table_size); + nca_pfs0_data_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)) + (u64)nca_pfs0_header.str_table_size); // Looking for META magic for(i = 0; i < nca_pfs0_header.file_cnt; i++) @@ -907,7 +945,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_cur_file_offset, &npdm_header, sizeof(npdm_t), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 entry #%u!", i); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 entry #%u!", __func__, i); free(nca_pfs0_entries); return false; } @@ -925,7 +963,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!found_meta) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find NPDM entry in Program NCA section #0 PFS0 partition!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find NPDM entry in Program NCA section #0 PFS0 partition!", __func__); return false; } @@ -945,7 +983,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca block_data[0] = malloc(block_size[0]); if (!block_data[0]) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0!", __func__); return false; } @@ -953,7 +991,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 NPDM block 0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 0!", __func__); free(block_data[0]); return false; } @@ -987,7 +1025,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca block_data[1] = malloc(block_size[1]); if (!block_data[1]) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1!", __func__); free(block_data[0]); return false; } @@ -995,7 +1033,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 NPDM block 1!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 NPDM block 1!", __func__); free(block_data[0]); free(block_data[1]); return false; @@ -1009,7 +1047,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca hash_table = malloc(dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size); if (!hash_table) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 hash table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 hash table!", __func__); free(block_data[0]); if (block_data[1]) free(block_data[1]); return false; @@ -1018,7 +1056,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 hash table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 hash table!", __func__); free(block_data[0]); if (block_data[1]) free(block_data[1]); free(hash_table); @@ -1039,7 +1077,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!rsa_sign(&(dec_nca_header->magic), NPDM_SIGNATURE_AREA_SIZE, dec_nca_header->npdm_key_sig, NPDM_SIGNATURE_SIZE)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to recreate Program NCA NPDM signature!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to recreate Program NCA NPDM signature!", __func__); free(block_data[0]); if (block_data[1]) free(block_data[1]); free(hash_table); @@ -1050,7 +1088,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[0], block_data[0], block_size[0], true)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to encrypt Program NCA section #0 PFS0 NPDM block 0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0!", __func__); free(block_data[0]); if (block_data[1]) free(block_data[1]); free(hash_table); @@ -1062,7 +1100,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, block_start_offset[1], block_data[1], block_size[1], true)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to encrypt Program NCA section #0 PFS0 NPDM block 1!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1!", __func__); free(block_data[0]); free(block_data[1]); free(hash_table); @@ -1073,7 +1111,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, hash_table_offset, hash_table, dec_nca_header->fs_headers[0].pfs0_superblock.hash_table_size, true)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to encrypt Program NCA section #0 PFS0 hash table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt Program NCA section #0 PFS0 hash table!", __func__); free(block_data[0]); if (block_data[1]) free(block_data[1]); free(hash_table); @@ -1102,11 +1140,11 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca return true; } -bool retrieveCnmtNcaData(FsStorageId 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 replaceKeyArea) +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) { if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve CNMT NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve CNMT NCA!", __func__); return false; } @@ -1124,7 +1162,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy u64 nca_pfs0_str_table_offset; u64 nca_pfs0_data_offset; pfs0_header nca_pfs0_header; - pfs0_entry_table *nca_pfs0_entries = NULL; + pfs0_file_entry *nca_pfs0_entries = NULL; bool found_cnmt = false; @@ -1138,27 +1176,27 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy // Generate filename for our required CNMT file char cnmtFileName[50] = {'\0'}; - snprintf(cnmtFileName, MAX_ELEMENTS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id); + snprintf(cnmtFileName, MAX_CHARACTERS(cnmtFileName), "%s_%016lx.cnmt", getTitleType(xml_program_info->type), xml_program_info->title_id); // Decrypt the NCA header // Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard - if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != FsStorageId_GameCard))) return false; + if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard))) return false; if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: CNMT NCA section #0 doesn't hold a PFS0 partition!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 doesn't hold a PFS0 partition!", __func__); return false; } if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid size for PFS0 partition in CNMT NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in CNMT NCA section #0!", __func__); return false; } if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", dec_header.fs_headers[0].crypt_type); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for CNMT NCA section #0! (0x%02X)", __func__, dec_header.fs_headers[0].crypt_type); return false; } @@ -1173,35 +1211,22 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy } } - if (curStorageId == FsStorageId_GameCard) + // CNMT NCAs never use titlekey crypto + if (has_rights_id) { - if (has_rights_id) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Rights ID field in NCA header not empty!"); - return false; - } - - // Modify distribution type - if (selectedNspDumpType != DUMP_PATCH_NSP) dec_header.distribution = 0; - } else - if (curStorageId == FsStorageId_SdCard || curStorageId == FsStorageId_NandUser) - { - if (has_rights_id && replaceKeyArea) - { - // Generate new encrypted NCA key area using titlekey - if (!generateEncryptedNcaKeyAreaWithTitlekey(&dec_header, xml_content_info[cnmtNcaIndex].decrypted_nca_keys)) return false; - - // Remove rights ID from NCA - memset(dec_header.rights_id, 0, 0x10); - } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Rights ID field in CNMT NCA header not empty!", __func__); + return false; } + // Modify distribution type + if (curStorageId == NcmStorageId_GameCard) dec_header.distribution = 0; + section_offset = ((u64)dec_header.section_entries[0].media_start_offset * (u64)MEDIA_UNIT_SIZE); section_size = (((u64)dec_header.section_entries[0].media_end_offset * (u64)MEDIA_UNIT_SIZE) - section_offset); if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offset/size for CNMT NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for CNMT NCA section #0!", __func__); return false; } @@ -1223,7 +1248,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy section_data = malloc(section_size); if (!section_data) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the decrypted CNMT NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decrypted CNMT NCA section #0!", __func__); return false; } @@ -1234,32 +1259,32 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: 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.", 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__, bswap_32(nca_pfs0_header.magic)); free(section_data); return false; } if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); free(section_data); return false; } - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_entry_table)); + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); if (!nca_pfs0_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries!", __func__); free(section_data); return false; } - memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table)); + memcpy(nca_pfs0_entries, section_data + nca_pfs0_offset + sizeof(pfs0_header), (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry)); - nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table))); + nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); nca_pfs0_data_offset = (nca_pfs0_str_table_offset + (u64)nca_pfs0_header.str_table_size); - // Looking for the CNMT + // Look for the CNMT for(i = 0; i < nca_pfs0_header.file_cnt; i++) { u64 filename_offset = (nca_pfs0_str_table_offset + nca_pfs0_entries[i].filename_offset); @@ -1276,7 +1301,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy if (!found_cnmt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", cnmtFileName); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", __func__, cnmtFileName); free(section_data); return false; } @@ -1295,7 +1320,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid; // 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 (excluding Delta Fragments) + // Also wipe each of the content records we're gonna replace for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA { for(j = 0; j < title_cnmt_header.content_cnt; j++) @@ -1303,29 +1328,25 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy cnmt_content_record cnt_record; memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record)); - if (cnt_record.type == NCA_CONTENT_TYPE_DELTA) continue; + if (memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) != 0) continue; - if (!memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) || xml_content_info[i].type == cnt_record.type) - { - // Save ID offset and content record offset - xml_content_info[i].id_offset = cnt_record.id_offset; - xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record)); - - // Empty CNMT content record - memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record)); - - // Increase counter - k++; - - break; - } + // Save content record offset + xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record)); + + // Empty CNMT content record + memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record)); + + // Increase counter + k++; + + break; } } // Verify counter if (k != (xml_program_info->nca_cnt - 1)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid content record entries in the CNMT NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid content record entries in the CNMT NCA!", __func__); free(section_data); return false; } @@ -1358,7 +1379,7 @@ bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program { if (!ncaBuf || !ncaBufSize || !xml_program_info || xml_program_info->nca_cnt <= 1 || !xml_content_info || !cnmt_mod) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to patch CNMT NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to patch CNMT NCA!", __func__); return false; } @@ -1433,7 +1454,7 @@ bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program if (!encryptNcaHeader(&dec_header, ncaBuf, ncaBufSize)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to encrypt modified CNMT NCA header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to encrypt modified CNMT NCA header!", __func__); return false; } @@ -1448,11 +1469,11 @@ bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program return true; } -bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to read RomFS section from NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); return false; } @@ -1479,7 +1500,7 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, pfs0_header nca_pfs0_header; u64 nca_pfs0_entries_offset; - pfs0_entry_table *nca_pfs0_entries = NULL; + pfs0_file_entry *nca_pfs0_entries = NULL; u64 nca_pfs0_str_table_offset; char *nca_pfs0_str_table = NULL; @@ -1517,16 +1538,16 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_pfs0_entries_offset = (nca_pfs0_offset + sizeof(pfs0_header)); - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_entry_table)); + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); if (!nca_pfs0_entries) continue; - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_entries_offset, nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table), false)) + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_entries_offset, nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) { free(nca_pfs0_entries); return false; } - nca_pfs0_str_table_offset = (nca_pfs0_entries_offset + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table))); + nca_pfs0_str_table_offset = (nca_pfs0_entries_offset + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); nca_pfs0_str_table = calloc(nca_pfs0_header.str_table_size, sizeof(char)); if (!nca_pfs0_str_table) @@ -1565,7 +1586,7 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!found_exefs) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't hold an ExeFS section! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold an ExeFS section! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); return false; } @@ -1574,7 +1595,7 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, // Save data to output struct // The caller function must free these data pointers memcpy(&(exeFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(exeFsContext.ncaId), ncaId, sizeof(NcmNcaId)); + memcpy(&(exeFsContext.ncaId), ncaId, sizeof(NcmContentId)); memcpy(&(exeFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); exeFsContext.exefs_offset = nca_pfs0_offset; exeFsContext.exefs_size = dec_nca_header->fs_headers[exefs_index].pfs0_superblock.pfs0_size; @@ -1588,11 +1609,11 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, return true; } -bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to read RomFS section from NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read RomFS section from NCA!", __func__); return false; } @@ -1634,7 +1655,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!found_romfs) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't hold a RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a RomFS section!", __func__); return false; } @@ -1643,7 +1664,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !section_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offset/size for NCA RomFS section! (#%u)", romfs_index); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section! (#%u)", __func__, romfs_index); return false; } @@ -1664,13 +1685,13 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (bswap_32(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, "Error: invalid IVFC magic word for NCA RomFS section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", 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__, bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic)); return false; } if (dec_nca_header->fs_headers[romfs_index].crypt_type != NCA_FS_HEADER_CRYPT_CTR) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid AES crypt type for NCA RomFS section! (0x%02X)", dec_nca_header->fs_headers[romfs_index].crypt_type); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA RomFS section! (0x%02X)", __func__, dec_nca_header->fs_headers[romfs_index].crypt_type); return false; } @@ -1679,7 +1700,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (romfs_offset < section_offset || romfs_offset >= (section_offset + section_size) || romfs_size < ROMFS_HEADER_SIZE || (romfs_offset + romfs_size) > (section_offset + section_size)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offset/size for NCA RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA RomFS section!", __func__); return false; } @@ -1687,13 +1708,13 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_offset, &romFsHeader, sizeof(romfs_header), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA RomFS section header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section header!", __func__); return false; } if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid header size for NCA RomFS section! (0x%016lX at 0x%016lX)", romFsHeader.headerSize, romfs_offset); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, romfs_offset); return false; } @@ -1705,7 +1726,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (romfs_dirtable_offset >= (section_offset + section_size) || !romfs_dirtable_size || (romfs_dirtable_offset + romfs_dirtable_size) > (section_offset + section_size) || romfs_filetable_offset >= (section_offset + section_size) || !romfs_filetable_size || (romfs_filetable_offset + romfs_filetable_size) > (section_offset + section_size)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid directory/file table for NCA RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA RomFS section!", __func__); return false; } @@ -1713,21 +1734,21 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (romfs_filedata_offset >= (section_offset + section_size)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid file data block offset for NCA RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA RomFS section!", __func__); return false; } romfs_dir_entries = calloc(1, romfs_dirtable_size); if (!romfs_dir_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA RomFS section directory entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section directory entries!", __func__); return false; } if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_dirtable_offset, romfs_dir_entries, romfs_dirtable_size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA RomFS section directory entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section directory entries!", __func__); free(romfs_dir_entries); return false; } @@ -1735,7 +1756,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, romfs_file_entries = calloc(1, romfs_filetable_size); if (!romfs_file_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA RomFS section file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA RomFS section file entries!", __func__); free(romfs_dir_entries); return false; } @@ -1743,7 +1764,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, romfs_filetable_offset, romfs_file_entries, romfs_filetable_size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA RomFS section file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); free(romfs_file_entries); free(romfs_dir_entries); return false; @@ -1752,7 +1773,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, // Save data to output struct // The caller function must free these data pointers memcpy(&(romFsContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(romFsContext.ncaId), ncaId, sizeof(NcmNcaId)); + memcpy(&(romFsContext.ncaId), ncaId, sizeof(NcmContentId)); memcpy(&(romFsContext.aes_ctx), &aes_ctx, sizeof(Aes128CtrContext)); romFsContext.section_offset = section_offset; romFsContext.section_size = section_size; @@ -1769,11 +1790,11 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, return true; } -bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) +bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !romFsContext.section_offset || !romFsContext.section_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_file_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to read BKTR section from NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read BKTR section from NCA!", __func__); return false; } @@ -1787,7 +1808,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, initBktrContext(); memcpy(&(bktrContext.ncmStorage), ncmStorage, sizeof(NcmContentStorage)); - memcpy(&(bktrContext.ncaId), ncaId, sizeof(NcmNcaId)); + memcpy(&(bktrContext.ncaId), ncaId, sizeof(NcmContentId)); for(bktr_index = 0; bktr_index < 4; bktr_index++) { @@ -1800,7 +1821,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!found_bktr) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't hold a BKTR section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't hold a BKTR section!", __func__); return false; } @@ -1809,7 +1830,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!bktrContext.section_offset || bktrContext.section_offset < NCA_FULL_HEADER_LENGTH || !bktrContext.section_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offset/size for NCA BKTR section! (#%u)", bktr_index); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR section! (#%u)", __func__, bktr_index); return false; } @@ -1832,25 +1853,25 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (bswap_32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IVFC magic word for NCA BKTR section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", 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__, bswap_32(bktrContext.superblock.ivfc_header.magic)); return false; } if (dec_nca_header->fs_headers[bktr_index].crypt_type != NCA_FS_HEADER_CRYPT_BKTR) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid AES crypt type for NCA BKTR section! (0x%02X)", dec_nca_header->fs_headers[bktr_index].crypt_type); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for NCA BKTR section! (0x%02X)", __func__, dec_nca_header->fs_headers[bktr_index].crypt_type); return false; } if (bswap_32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || bswap_32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: 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.", 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__, bswap_32(bktrContext.superblock.relocation_header.magic), bswap_32(bktrContext.superblock.subsection_header.magic)); return false; } if ((bktrContext.superblock.relocation_header.offset + bktrContext.superblock.relocation_header.size) != bktrContext.superblock.subsection_header.offset || (bktrContext.superblock.subsection_header.offset + bktrContext.superblock.subsection_header.size) != bktrContext.section_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid layout for NCA BKTR section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid layout for NCA BKTR section!", __func__); return false; } @@ -1858,7 +1879,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, bktrContext.relocation_block = calloc(1, bktrContext.superblock.relocation_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_relocation_entry_t))); if (!bktrContext.relocation_block) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA BKTR relocation header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR relocation header!", __func__); return false; } @@ -1866,7 +1887,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, bktrContext.subsection_block = calloc(1, bktrContext.superblock.subsection_header.size + ((0x3FF0 / sizeof(u64)) * sizeof(bktr_subsection_entry_t)) + sizeof(bktr_subsection_entry_t)); if (!bktrContext.subsection_block) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA BKTR subsection header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR subsection header!", __func__); goto out; } @@ -1874,7 +1895,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.relocation_header.offset, bktrContext.relocation_block, bktrContext.superblock.relocation_header.size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA BKTR relocation header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR relocation header!", __func__); goto out; } @@ -1882,13 +1903,13 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(bktrContext.aes_ctx), bktrContext.section_offset + bktrContext.superblock.subsection_header.offset, bktrContext.subsection_block, bktrContext.superblock.subsection_header.size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA BKTR subsection header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR subsection header!", __func__); goto out; } if (bktrContext.subsection_block->total_size != bktrContext.superblock.subsection_header.offset) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA BKTR subsection header size!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA BKTR subsection header size!", __func__); goto out; } @@ -1933,23 +1954,23 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, bktrContext.romfs_offset = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].logical_offset; bktrContext.romfs_size = dec_nca_header->fs_headers[bktr_index].bktr_superblock.ivfc_header.level_headers[IVFC_MAX_LEVEL - 1].hash_data_size; - // Do not check the RomFS size, because it reflects the full patched RomFS image - if (!bktrContext.romfs_offset || bktrContext.romfs_size < ROMFS_HEADER_SIZE || (bktrContext.section_offset + bktrContext.romfs_offset) > (bktrContext.section_offset + bktrContext.section_size)) + // Do not check the RomFS offset/size, because it reflects the full patched RomFS image + if (!bktrContext.romfs_offset || bktrContext.romfs_size < ROMFS_HEADER_SIZE) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offset/size for NCA BKTR RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offset/size for NCA BKTR RomFS section!", __func__); goto out; } if (!readBktrSectionBlock(bktrContext.romfs_offset, &romFsHeader, sizeof(romfs_header))) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA BKTR RomFS section header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section header!", __func__); goto out; } if (romFsHeader.headerSize != ROMFS_HEADER_SIZE) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid header size for NCA BKTR RomFS section! (0x%016lX at 0x%016lX)", romFsHeader.headerSize, bktrContext.section_offset + bktrContext.romfs_offset); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid header size for NCA BKTR RomFS section! (0x%016lX at 0x%016lX)", __func__, romFsHeader.headerSize, bktrContext.section_offset + bktrContext.romfs_offset); goto out; } @@ -1962,7 +1983,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, // Then again, do not check these offsets/sizes, because they reflect the patched RomFS image if (!bktrContext.romfs_dirtable_offset || !bktrContext.romfs_dirtable_size || !bktrContext.romfs_filetable_offset || !bktrContext.romfs_filetable_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid directory/file table for NCA BKTR RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid directory/file table for NCA BKTR RomFS section!", __func__); goto out; } @@ -1970,35 +1991,35 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, if (!bktrContext.romfs_filedata_offset) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid file data block offset for NCA BKTR RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file data block offset for NCA BKTR RomFS section!", __func__); goto out; } bktrContext.romfs_dir_entries = calloc(1, bktrContext.romfs_dirtable_size); if (!bktrContext.romfs_dir_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA BKTR RomFS section directory entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section directory entries!", __func__); goto out; } if (!readBktrSectionBlock(bktrContext.romfs_dirtable_offset, bktrContext.romfs_dir_entries, bktrContext.romfs_dirtable_size)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA BKTR RomFS section directory entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA BKTR RomFS section directory entries!", __func__); goto out; } bktrContext.romfs_file_entries = calloc(1, bktrContext.romfs_filetable_size); if (!bktrContext.romfs_file_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for NCA BKTR RomFS section file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for NCA BKTR RomFS section file entries!", __func__); goto out; } if (!readBktrSectionBlock(bktrContext.romfs_filetable_offset, bktrContext.romfs_file_entries, bktrContext.romfs_filetable_size)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NCA RomFS section file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NCA RomFS section file entries!", __func__); goto out; } @@ -2037,29 +2058,29 @@ out: return success; } -bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *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, nca_program_mod_data *program_mod_data, char **outBuf, u64 *outBufSize) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !program_mod_data || !outBuf || !outBufSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to generate \"programinfo.xml\"!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate \"programinfo.xml\"!", __func__); return false; } if (dec_nca_header->fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_nca_header->fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 doesn't hold a PFS0 partition!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 doesn't hold a PFS0 partition!", __func__); return false; } if (!dec_nca_header->fs_headers[0].pfs0_superblock.pfs0_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid size for PFS0 partition in Program NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid size for PFS0 partition in Program NCA section #0!", __func__); return false; } if (dec_nca_header->fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_CTR) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid AES crypt type for Program NCA section #0! (0x%02X)", dec_nca_header->fs_headers[0].crypt_type); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid AES crypt type for Program NCA section #0! (0x%02X)", __func__, dec_nca_header->fs_headers[0].crypt_type); return false; } @@ -2071,7 +2092,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId u64 nca_pfs0_offset; pfs0_header nca_pfs0_header; - pfs0_entry_table *nca_pfs0_entries = NULL; + pfs0_file_entry *nca_pfs0_entries = NULL; char *nca_pfs0_str_table = NULL; u64 nca_pfs0_str_table_offset; @@ -2096,7 +2117,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!section_offset || section_offset < NCA_FULL_HEADER_LENGTH || !nca_pfs0_offset) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid offsets for Program NCA section #0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid offsets for Program NCA section #0!", __func__); return false; } @@ -2118,49 +2139,49 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset, &nca_pfs0_header, sizeof(pfs0_header), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 partition header!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition header!", __func__); return false; } if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: 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.", 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__, bswap_32(nca_pfs0_header.magic)); return false; } if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); return false; } - nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_entry_table)); + nca_pfs0_entries = calloc(nca_pfs0_header.file_cnt, sizeof(pfs0_file_entry)); if (!nca_pfs0_entries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 partition entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 partition entries!", __func__); return false; } - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table), false)) + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_offset + sizeof(pfs0_header), nca_pfs0_entries, (u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 partition entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 partition entries!", __func__); goto out; } - nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_entry_table))); + nca_pfs0_str_table_offset = (nca_pfs0_offset + sizeof(pfs0_header) + ((u64)nca_pfs0_header.file_cnt * sizeof(pfs0_file_entry))); nca_pfs0_str_table = calloc((u64)nca_pfs0_header.str_table_size, sizeof(char)); if (!nca_pfs0_str_table) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for Program NCA section #0 PFS0 string table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for Program NCA section #0 PFS0 string table!", __func__); goto out; } if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_str_table_offset, nca_pfs0_str_table, (u64)nca_pfs0_header.str_table_size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read Program NCA section #0 PFS0 string table!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read Program NCA section #0 PFS0 string table!", __func__); goto out; } @@ -2170,7 +2191,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId programInfoXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); if (!programInfoXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the \"programinfo.xml\" contents!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); goto out; } @@ -2194,7 +2215,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!found_npdm) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the \"programinfo.xml\" contents!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"programinfo.xml\" contents!", __func__); goto out; } @@ -2202,13 +2223,13 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset, &npdm_header, sizeof(npdm_t), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read NPDM entry header from Program NCA section #0 PFS0!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read NPDM entry header from Program NCA section #0 PFS0!", __func__); goto out; } if (bswap_32(npdm_header.magic) != META_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NPDM META magic word! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", 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__, bswap_32(npdm_header.magic)); goto out; } @@ -2216,14 +2237,14 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId npdm_acid_section = malloc(npdm_header.acid_size); if (!npdm_acid_section) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the Program NCA NPDM ACID section contents!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Program NCA NPDM ACID section contents!", __func__); goto out; } if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, nca_pfs0_data_offset + nca_pfs0_entries[npdmEntry].file_offset + (u64)npdm_header.acid_offset, npdm_acid_section, (u64)npdm_header.acid_size, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read ACID section from Program NCA NPDM!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read ACID section from Program NCA NPDM!", __func__); goto out; } @@ -2240,21 +2261,21 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId mbedtls_base64_encode(NULL, 0, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size); if (npdm_acid_section_b64_size <= (u64)npdm_header.acid_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid Base64 conversion length for the ACID section from Program NCA NPDM!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid Base64 conversion length for the ACID section from Program NCA NPDM!", __func__); goto out; } npdm_acid_section_b64 = calloc(npdm_acid_section_b64_size + 1, sizeof(char)); if (!npdm_acid_section_b64) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the Base64 converted ACID section from Program NCA NPDM!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the Base64 converted ACID section from Program NCA NPDM!", __func__); goto out; } // Perform the Base64 conversion if (mbedtls_base64_encode((unsigned char*)npdm_acid_section_b64, npdm_acid_section_b64_size + 1, &npdm_acid_section_b64_size, npdm_acid_section, (u64)npdm_header.acid_size) != 0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Base64 conversion failed for the ACID section from Program NCA NPDM!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: Base64 conversion failed for the ACID section from Program NCA NPDM!", __func__); goto out; } @@ -2288,7 +2309,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", sizeof(nso_header_t), curFilename); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); proceed = false; break; } @@ -2324,7 +2345,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &aes_ctx, curFileOffset, &nsoHeader, sizeof(nso_header_t), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", sizeof(nso_header_t), curFilename); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes from \"%s\" in Program NCA section #0 PFS0 partition!", __func__, sizeof(nso_header_t), curFilename); proceed = false; break; } @@ -2897,11 +2918,11 @@ char *getNacpJitConfigurationFlag(u64 flag) return out; } -bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt) +bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !out_nacp_xml || !out_nacp_xml_size || !out_nacp_icons_ctx || !out_nacp_icons_ctx_cnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to generate NACP XML!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate NACP XML!", __func__); return false; } @@ -2932,7 +2953,7 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI bool availableSDC = false, availableRDC = false; - if (!readRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; + if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; // Look for the control.nacp file while(entryOffset < romFsContext.romfs_filetable_size) @@ -2950,14 +2971,14 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI if (!found_nacp) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find \"control.nacp\" file in Control NCA RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"control.nacp\" file in Control NCA RomFS section!", __func__); goto out; } if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, &controlNacp, sizeof(nacp_t), false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read \"control.nacp\" from RomFS section in Control NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"control.nacp\" from RomFS section in Control NCA!", __func__); goto out; } @@ -2965,7 +2986,7 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI nacpXml = calloc(NSP_XML_BUFFER_SIZE, sizeof(char)); if (!nacpXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the NACP XML!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP XML!", __func__); goto out; } @@ -3099,72 +3120,71 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI nacpIcons = calloc(nacpIconCnt, sizeof(nacp_icons_ctx)); if (!nacpIcons) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the NACP icons!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the NACP icons!", __func__); goto out; } for(i = 0; i < 16; i++) { u8 bit = (u8)((controlNacp.SupportedLanguageFlag >> i) & 0x1); - if (bit) + if (!bit) 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); + + entryOffset = 0; + sprintf(tmp, "icon_%s.dat", getNacpLangName(i)); + + while(entryOffset < romFsContext.romfs_filetable_size) { - // Retrieve the icon file for this language and calculate its SHA-256 checksum - found_icon = false; + entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); - memset(languageIconHash, 0, 0x20); - memset(languageIconHashStr, 0, 0x21); - - entryOffset = 0; - sprintf(tmp, "icon_%s.dat", getNacpLangName(i)); - - while(entryOffset < romFsContext.romfs_filetable_size) + if (entry->parent == 0 && entry->nameLen == strlen(tmp) && !strncasecmp((char*)entry->name, tmp, strlen(tmp)) && entry->dataSize <= 0x20000) { - entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + entryOffset); - - if (entry->parent == 0 && entry->nameLen == strlen(tmp) && !strncasecmp((char*)entry->name, tmp, strlen(tmp)) && entry->dataSize <= 0x20000) - { - found_icon = true; - break; - } - - entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); + found_icon = true; + break; } - if (!found_icon) - { - nacpIconCnt--; - continue; - } - - strcat(nacpXml, " \n"); - - sprintf(tmp, " %s\n", getNacpLangName(i)); - strcat(nacpXml, tmp); - - // Fill details for our NACP icon context - sprintf(nacpIcons[j].filename, "%s.nx.%s.jpg", ncaIdStr, getNacpLangName(i)); // Temporary, the NCA ID is subject to change - nacpIcons[j].icon_size = entry->dataSize; - - if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, nacpIcons[j].icon_data, nacpIcons[j].icon_size, false)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read \"%s\" from RomFS section in Control NCA!", tmp); - goto out; - } - - sha256CalculateHash(languageIconHash, nacpIcons[j].icon_data, nacpIcons[j].icon_size); - - // Only retrieve the first half from the SHA-256 checksum - convertDataToHexString(languageIconHash, 0x10, languageIconHashStr, 0x21); - - // Now print the hash - sprintf(tmp, " %s\n", languageIconHashStr); - strcat(nacpXml, tmp); - - strcat(nacpXml, " \n"); - - j++; + entryOffset += round_up(ROMFS_NONAME_FILEENTRY_SIZE + entry->nameLen, 4); } + + if (!found_icon) + { + nacpIconCnt--; + continue; + } + + strcat(nacpXml, " \n"); + + sprintf(tmp, " %s\n", getNacpLangName(i)); + strcat(nacpXml, tmp); + + // Fill details for our NACP icon context + sprintf(nacpIcons[j].filename, "%s.nx.%s.jpg", ncaIdStr, getNacpLangName(i)); // Temporary, the NCA ID is subject to change + nacpIcons[j].icon_size = entry->dataSize; + + if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, nacpIcons[j].icon_data, nacpIcons[j].icon_size, false)) + { + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"%s\" from RomFS section in Control NCA!", __func__, tmp); + goto out; + } + + sha256CalculateHash(languageIconHash, nacpIcons[j].icon_data, nacpIcons[j].icon_size); + + // Only retrieve the first half from the SHA-256 checksum + convertDataToHexString(languageIconHash, 0x10, languageIconHashStr, 0x21); + + // Now print the hash + sprintf(tmp, " %s\n", languageIconHashStr); + strcat(nacpXml, tmp); + + strcat(nacpXml, " \n"); + + j++; } } @@ -3342,11 +3362,11 @@ out: return success; } -bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize) +bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize) { if (!ncmStorage || !ncaId || !dec_nca_header || !decrypted_nca_keys || !outBuf) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve \"legalinfo.xml\"!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve \"legalinfo.xml\"!", __func__); return false; } @@ -3357,7 +3377,7 @@ bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId * u64 legalInfoXmlSize = 0; char *legalInfoXml = NULL; - if (!readRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; + if (!parseRomFsEntryFromNca(ncmStorage, ncaId, dec_nca_header, decrypted_nca_keys)) return false; // Look for the legalinfo.xml file while(entryOffset < romFsContext.romfs_filetable_size) @@ -3375,7 +3395,7 @@ bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId * if (!found_legalinfo) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find \"legalinfo.xml\" file in Manual NCA RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find \"legalinfo.xml\" file in Manual NCA RomFS section!", __func__); goto out; } @@ -3384,14 +3404,14 @@ bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId * legalInfoXml = calloc(legalInfoXmlSize, sizeof(char)); if (!legalInfoXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the \"legalinfo.xml\" contents!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the \"legalinfo.xml\" contents!", __func__); goto out; } if (!processNcaCtrSectionBlock(ncmStorage, ncaId, &(romFsContext.aes_ctx), romFsContext.romfs_filedata_offset + entry->dataOff, legalInfoXml, legalInfoXmlSize, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read \"legalinfo.xml\" from RomFS section in Manual NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read \"legalinfo.xml\" from RomFS section in Manual NCA!", __func__); goto out; } diff --git a/source/nca.h b/source/nca.h index 0d1fb43..399cb1b 100644 --- a/source/nca.h +++ b/source/nca.h @@ -13,8 +13,6 @@ #define NCA_SECTION_HEADER_CNT 4 #define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT)) -#define NCA_CONTENT_TYPE_DELTA 0x06 - #define NCA_AES_XTS_SECTOR_SIZE 0x200 #define NCA_KEY_AREA_KEY_CNT 4 @@ -94,7 +92,7 @@ typedef struct { u64 file_size; u32 filename_offset; u32 reserved; -} PACKED pfs0_entry_table; +} PACKED pfs0_file_entry; typedef struct { u32 media_start_offset; @@ -238,7 +236,7 @@ typedef struct { } PACKED cnmt_header; typedef struct { - u64 patch_tid; /* Patch TID / Original TID / Application TID */ + u64 patch_tid; /* Patch TID / Application TID */ u32 min_sysver; /* Minimum system/application version */ } PACKED cnmt_extended_header; @@ -332,22 +330,24 @@ typedef struct { } PACKED title_rights_ctx; typedef struct { + NcmStorageId storageId; NcmContentStorage ncmStorage; - NcmNcaId ncaId; + NcmContentId ncaId; Aes128CtrContext aes_ctx; u64 exefs_offset; // Relative to NCA start u64 exefs_size; pfs0_header exefs_header; u64 exefs_entries_offset; // Relative to NCA start - pfs0_entry_table *exefs_entries; + pfs0_file_entry *exefs_entries; u64 exefs_str_table_offset; // Relative to NCA start char *exefs_str_table; u64 exefs_data_offset; // Relative to NCA start } PACKED exefs_ctx_t; typedef struct { + NcmStorageId storageId; NcmContentStorage ncmStorage; - NcmNcaId ncaId; + NcmContentId ncaId; Aes128CtrContext aes_ctx; u64 section_offset; // Relative to NCA start u64 section_size; @@ -406,8 +406,9 @@ typedef struct { } PACKED bktr_subsection_block_t; typedef struct { + NcmStorageId storageId; NcmContentStorage ncmStorage; - NcmNcaId ncaId; + NcmContentId ncaId; Aes128CtrContext aes_ctx; u64 section_offset; // Relative to NCA start u64 section_size; @@ -428,9 +429,16 @@ typedef struct { u64 romfs_filedata_offset; // Relative to section start } PACKED bktr_ctx_t; +// Used in HFS0 / ExeFS / RomFS browsers +typedef struct { + u64 size; + char sizeStr[32]; +} PACKED 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; typedef struct { @@ -514,7 +522,9 @@ void convertNcaSizeToU64(const u8 size[0x6], u64 *out); void convertU64ToNcaSize(const u64 size, u8 out[0x6]); -bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt); +bool readNcaDataByContentId(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, u64 offset, void *outBuf, size_t bufSize); + +bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *ctx, u64 offset, void *outBuf, size_t bufSize, bool encrypt); bool readBktrSectionBlock(u64 offset, void *outBuf, size_t bufSize); @@ -522,22 +532,22 @@ 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 NcmNcaId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data *output); +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 retrieveCnmtNcaData(FsStorageId 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 replaceKeyArea); +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 patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod); -bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); +bool parseExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); -bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); +bool parseRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); -bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); +bool parseBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); -bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *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, nca_program_mod_data *program_mod_data, char **outBuf, u64 *outBufSize); -bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt); +bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **out_nacp_xml, u64 *out_nacp_xml_size, nacp_icons_ctx **out_nacp_icons_ctx, u8 *out_nacp_icons_ctx_cnt); -bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize); +bool retrieveLegalInfoXmlFromNca(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf, u64 *outBufSize); #endif diff --git a/source/nso.c b/source/nso.c index 8f6eb6e..c701538 100644 --- a/source/nso.c +++ b/source/nso.c @@ -47,11 +47,11 @@ void freeNsoBinaryData() nsoBinaryDataSectionSize = 0; } -bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *aes_ctx, u64 nso_base_offset, nso_header_t *nsoHeader) +bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, u64 nso_base_offset, nso_header_t *nsoHeader) { if (!ncmStorage || !ncaId || !aes_ctx || !nso_base_offset || !nsoHeader) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to load .text, .rodata and .data sections from NSO in Program NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to load .text, .rodata and .data sections from NSO in Program NCA!", __func__); return false; } @@ -92,7 +92,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes curCompressedSection = malloc(curCompressedSectionSize); if (!curCompressedSection) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the compressed %s section from NSO in Program NCA!", (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the compressed %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); success = false; break; } @@ -100,7 +100,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes if (!processNcaCtrSectionBlock(ncmStorage, ncaId, aes_ctx, curCompressedSectionOffset, curCompressedSection, curCompressedSectionSize, false)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read 0x%016lX bytes %s section from NSO in Program NCA!", curCompressedSectionSize, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read 0x%016lX bytes %s section from NSO in Program NCA!", __func__, curCompressedSectionSize, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); free(curCompressedSection); success = false; break; @@ -110,7 +110,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes { if (curDecompressedSectionSize <= curCompressedSectionSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid decompressed size for %s section from NSO in Program NCA!", (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid decompressed size for %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); free(curCompressedSection); success = false; break; @@ -120,7 +120,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes curDecompressedSection = malloc(curDecompressedSectionSize); if (!curDecompressedSection) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for the decompressed %s section from NSO in Program NCA!", (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for the decompressed %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); free(curCompressedSection); success = false; break; @@ -128,7 +128,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes if (LZ4_decompress_safe((const char*)curCompressedSection, (char*)curDecompressedSection, (int)curCompressedSectionSize, (int)curDecompressedSectionSize) != (int)curDecompressedSectionSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to decompress %s section from NSO in Program NCA!", (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to decompress %s section from NSO in Program NCA!", __func__, (i == 0 ? ".text" : (i == 1 ? ".rodata" : ".data"))); free(curDecompressedSection); free(curCompressedSection); success = false; @@ -225,7 +225,7 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes nsoBinaryDataSectionOffset = (u64)nsoHeader->data_segment_header.memory_offset; nsoBinaryDataSectionSize = nsoDataSectionSize; } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate %lu bytes for full decompressed NSO in Program NCA!", nsoBinaryDataSize); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate %lu bytes for full decompressed NSO in Program NCA!", __func__, nsoBinaryDataSize); nsoBinaryDataSize = 0; success = false; } @@ -238,11 +238,11 @@ bool loadNsoBinaryData(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes return success; } -bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml) +bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml) { if (!ncmStorage || !ncaId || !aes_ctx || !nso_filename || !strlen(nso_filename) || !nso_base_offset || !nsoHeader || !programInfoXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve middleware list from NSO in Program NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve middleware list from NSO in Program NCA!", __func__); return false; } @@ -289,11 +289,11 @@ bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId return true; } -bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml) +bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml) { if (!ncmStorage || !ncaId || !aes_ctx || !nso_filename || !strlen(nso_filename) || !nso_base_offset || !nsoHeader || !programInfoXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve symbols list from NSO in Program NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve symbols list from NSO in Program NCA!", __func__); return false; } @@ -333,7 +333,7 @@ bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId *n if (bswap_32(mod_magic) != MOD_MAGIC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid MOD0 magic word in decompressed NSO from Program NCA! (0x%08X)", 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__, bswap_32(mod_magic)); goto out; } diff --git a/source/nso.h b/source/nso.h index 0237469..d5b0063 100644 --- a/source/nso.h +++ b/source/nso.h @@ -50,9 +50,9 @@ typedef struct { } PACKED nso_header_t; // Retrieves the middleware list from a NSO stored in a partition from a NCA file -bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml); +bool retrieveMiddlewareListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml); // Retrieves the symbols list from a NSO stored in a partition from a NCA file -bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml); +bool retrieveSymbolsListFromNso(NcmContentStorage *ncmStorage, const NcmContentId *ncaId, Aes128CtrContext *aes_ctx, const char *nso_filename, u64 nso_base_offset, nso_header_t *nsoHeader, char *programInfoXml); #endif diff --git a/source/rsa.c b/source/rsa.c index 963f28b..6695580 100644 --- a/source/rsa.c +++ b/source/rsa.c @@ -56,13 +56,13 @@ bool rsa_sign(void* input, size_t input_size, unsigned char* output, size_t outp memcpy(output, buf, output_size); success = true; } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "rsa_sign: mbedtls_pk_sign failed! (%d)", ret); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: mbedtls_pk_sign failed! (%d)", __func__, ret); } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "rsa_sign: mbedtls_pk_parse_key failed! (%d)", ret); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: mbedtls_pk_parse_key failed! (%d)", __func__, ret); } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "rsa_sign: mbedtls_ctr_drbg_seed failed! (%d)", ret); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: mbedtls_ctr_drbg_seed failed! (%d)", __func__, ret); } mbedtls_ctr_drbg_free(&ctr_drbg); diff --git a/source/save.c b/source/save.c index eeb277f..de88625 100644 --- a/source/save.c +++ b/source/save.c @@ -17,7 +17,7 @@ extern char strbuf[NAME_BUF_LEN]; /* Statically allocated variables */ -static bool loadedCerts = false; +static bool loadedCerts = false, personalizedCertAvailable = false; static const char *cert_CA00000003_path = "/certificate/CA00000003"; static const char *cert_XS00000020_path = "/certificate/XS00000020"; @@ -69,7 +69,7 @@ bool save_duplex_storage_init(duplex_storage_ctx_t *ctx, duplex_fs_layer_info_t { if (!ctx || !layer || !layer->data_a || !layer->data_b || !layer->info.block_size_power || !bitmap || !bitmap_size) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_duplex_storage_init: invalid parameters to initialize duplex storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to initialize duplex storage!", __func__); return false; } @@ -82,7 +82,7 @@ bool save_duplex_storage_init(duplex_storage_ctx_t *ctx, duplex_fs_layer_info_t ctx->bitmap.bitmap = calloc(1, bitmap_size >> 3); if (!ctx->bitmap.bitmap) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_duplex_storage_init: failed to allocate memory for duplex bitmap!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for duplex bitmap!", __func__); return false; } @@ -119,7 +119,7 @@ u32 save_duplex_storage_read(duplex_storage_ctx_t *ctx, void *buffer, u64 offset { if (!ctx || !ctx->block_size || !ctx->bitmap.bitmap || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_duplex_storage_read: invalid parameters to read duplex storage data!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read duplex storage data!", __func__); return 0; } @@ -148,14 +148,14 @@ remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, remap_entr { if (!header || !header->map_segment_count || !map_entries || !num_map_entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_init_segments: invalid parameters to initialize remap segments!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to initialize remap segments!", __func__); return NULL; } remap_segment_ctx_t *segments = calloc(sizeof(remap_segment_ctx_t), header->map_segment_count); if (!segments) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_init_segments: failed to allocate initial memory for remap segments!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate initial memory for remap segments!", __func__); return NULL; } @@ -169,7 +169,7 @@ remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, remap_entr seg->entries = calloc(1, sizeof(remap_entry_ctx_t)); if (!seg->entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_init_segments: failed to allocate memory for remap segment entry #%u!", entry_idx); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for remap segment entry #%u!", __func__, entry_idx); goto out; } @@ -187,7 +187,7 @@ remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, remap_entr seg->entries = calloc(1, sizeof(remap_entry_ctx_t)); if (!seg->entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_init_segments: failed to allocate memory for remap segment entry #%u!", entry_idx); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for remap segment entry #%u!", __func__, entry_idx); goto out; } @@ -247,7 +247,7 @@ remap_entry_ctx_t *save_remap_get_map_entry(remap_storage_ctx_t *ctx, u64 offset { if (!ctx || !ctx->header || !ctx->segments) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_get_map_entry: invalid parameters to retrieve map entry!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve map entry!", __func__); return NULL; } @@ -261,7 +261,7 @@ remap_entry_ctx_t *save_remap_get_map_entry(remap_storage_ctx_t *ctx, u64 offset } } - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_get_map_entry: unable to find map entry for offset 0x%lX!", offset); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to find map entry for offset 0x%lX!", __func__, offset); return NULL; } @@ -269,14 +269,17 @@ u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, size_t c { if (!ctx || (ctx->type == STORAGE_BYTES && !ctx->file) || (ctx->type == STORAGE_DUPLEX && !ctx->duplex) || (ctx->type != STORAGE_BYTES && ctx->type != STORAGE_DUPLEX) || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_read: invalid parameters to read remap data!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read remap data!", __func__); return 0; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + remap_entry_ctx_t *entry = save_remap_get_map_entry(ctx, offset); if (!entry) { - strcat(strbuf, "\nsave_remap_read: failed to retrieve map entry!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve map entry!", __func__); + strcat(strbuf, tmp); return 0; } @@ -298,14 +301,14 @@ u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, size_t c fr = f_lseek(ctx->file, ctx->base_storage_offset + entry->physical_offset + entry_pos); if (fr || f_tell(ctx->file) != (ctx->base_storage_offset + entry->physical_offset + entry_pos)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_read: failed to seek to offset 0x%lX in savefile! (%u)", ctx->base_storage_offset + entry->physical_offset + entry_pos, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to seek to offset 0x%lX in savefile! (%u)", __func__, ctx->base_storage_offset + entry->physical_offset + entry_pos, fr); return out_pos; } fr = f_read(ctx->file, (u8*)buffer + out_pos, bytes_to_read, &br); if (fr || br != bytes_to_read) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_remap_read: failed to read %u bytes chunk from offset 0x%lX in savefile! (%u)", bytes_to_read, ctx->base_storage_offset + entry->physical_offset + entry_pos, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read %u bytes chunk from offset 0x%lX in savefile! (%u)", __func__, bytes_to_read, ctx->base_storage_offset + entry->physical_offset + entry_pos, fr); return (out_pos + br); } @@ -314,7 +317,8 @@ u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, size_t c br = save_duplex_storage_read(ctx->duplex, (u8*)buffer + out_pos, ctx->base_storage_offset + entry->physical_offset + entry_pos, bytes_to_read); if (br != bytes_to_read) { - strcat(strbuf, "\nsave_remap_read: failed to read remap data from duplex storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read remap data from duplex storage!", __func__); + strcat(strbuf, tmp); return (out_pos + br); } break; @@ -336,7 +340,7 @@ u32 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *r { if (!ctx || !ctx->block_size || !remap || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_journal_storage_read: invalid parameters to read journal storage data!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read journal storage data!", __func__); return 0; } @@ -345,6 +349,8 @@ u32 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *r u32 remaining = count; u32 br; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + while(remaining) { u32 block_num = (u32)(in_pos / ctx->block_size); @@ -355,7 +361,8 @@ u32 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *r br = save_remap_read(remap, (u8*)buffer + out_pos, ctx->journal_data_offset + physical_offset, bytes_to_read); if (br != bytes_to_read) { - strcat(strbuf, "\nsave_journal_storage_read: invalid parameters to read journal storage data!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: invalid parameters to read journal storage data!", __func__); + strcat(strbuf, tmp); return (out_pos + br); } @@ -371,7 +378,7 @@ bool save_ivfc_storage_init(hierarchical_integrity_verification_storage_ctx_t *c { if (!ctx || !ctx->levels || !ivfc || !ivfc->num_levels) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_init: invalid parameters to initialize IVFC storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to initialize IVFC storage!", __func__); return false; } @@ -428,7 +435,7 @@ bool save_ivfc_storage_init(hierarchical_integrity_verification_storage_ctx_t *c ctx->level_validities = calloc(sizeof(validity_t*), (ivfc->num_levels - 1)); if (!ctx->level_validities) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_init: failed to allocate memory for level validities!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for level validities!", __func__); goto out; } @@ -445,7 +452,7 @@ bool save_ivfc_storage_init(hierarchical_integrity_verification_storage_ctx_t *c level_data->block_validities = calloc(sizeof(validity_t), level_data->sector_count); if (!level_data->block_validities) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_init: failed to allocate memory for block validities in IVFC level #%u!", i); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for block validities in IVFC level #%u!", __func__, i); goto out; } @@ -486,27 +493,29 @@ size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, u64 offse { if (!ctx || (ctx->type == STORAGE_BYTES && !ctx->save_ctx->file) || (ctx->type != STORAGE_BYTES && ctx->type != STORAGE_REMAP && ctx->type != STORAGE_JOURNAL) || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_level_fread: invalid parameters to read IVFC level data!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read IVFC level data!", __func__); return 0; } UINT br = 0; FRESULT fr; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + switch (ctx->type) { case STORAGE_BYTES: fr = f_lseek(ctx->save_ctx->file, ctx->hash_offset + offset); if (fr || f_tell(ctx->save_ctx->file) != (ctx->hash_offset + offset)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_level_fread: failed to seek to offset 0x%lX in savefile! (%u)", ctx->hash_offset + offset, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to seek to offset 0x%lX in savefile! (%u)", __func__, ctx->hash_offset + offset, fr); return (size_t)br; } fr = f_read(ctx->save_ctx->file, buffer, count, &br); if (fr || br != count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_level_fread: failed to read IVFC level data from offset 0x%lX in savefile! (%u)", ctx->hash_offset + offset, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read IVFC level data from offset 0x%lX in savefile! (%u)", __func__, ctx->hash_offset + offset, fr); return (size_t)br; } @@ -515,7 +524,8 @@ size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, u64 offse br = save_remap_read(&ctx->save_ctx->meta_remap_storage, buffer, ctx->data_offset + offset, count); if (br != count) { - strcat(strbuf, "\nsave_ivfc_level_fread: failed to read IVFC level data from remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read IVFC level data from remap storage!", __func__); + strcat(strbuf, tmp); return (size_t)br; } @@ -524,7 +534,8 @@ size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, u64 offse br = save_journal_storage_read(&ctx->save_ctx->journal_storage, &ctx->save_ctx->data_remap_storage, buffer, ctx->data_offset + offset, count); if (br != count) { - strcat(strbuf, "\nsave_ivfc_level_fread: failed to read IVFC level data from journal storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read IVFC level data from journal storage!", __func__); + strcat(strbuf, tmp); return (size_t)br; } @@ -540,13 +551,13 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf { if (!ctx || !ctx->sector_size || (!ctx->next_level && !ctx->hash_storage && !ctx->base_storage) || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_read: invalid parameters to read IVFC storage data!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read IVFC storage data!", __func__); return false; } if (count > ctx->sector_size) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_read: IVFC read exceeds sector size!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: IVFC read exceeds sector size!", __func__); return false; } @@ -554,7 +565,7 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_read: hash error from previous check found at offset 0x%08X, count 0x%lX!", (u32)offset, count); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: hash error from previous check found at offset 0x%08X, count 0x%lX!", __func__, (u32)offset, count); return false; } @@ -562,17 +573,21 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf u8 zeroes[0x20] = {0}; u64 hash_pos = (block_index * 0x20); + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + if (ctx->next_level) { if (!save_ivfc_storage_read(ctx->next_level, hash_buffer, hash_pos, 0x20, verify)) { - strcat(strbuf, "\nsave_ivfc_storage_read: failed to read hash from next IVFC level!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read hash from next IVFC level!", __func__); + strcat(strbuf, tmp); return false; } } else { if (save_ivfc_level_fread(ctx->hash_storage, hash_buffer, hash_pos, 0x20) != 0x20) { - strcat(strbuf, "\nsave_ivfc_storage_read: failed to read hash from hash storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read hash from hash storage!", __func__); + strcat(strbuf, tmp); return false; } } @@ -586,7 +601,8 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf if (save_ivfc_level_fread(ctx->base_storage, buffer, offset, count) != count) { - strcat(strbuf, "\nsave_ivfc_storage_read: failed to read IVFC level from base storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read IVFC level from base storage!", __func__); + strcat(strbuf, tmp); return false; } @@ -597,7 +613,7 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf u8 *data_buffer = calloc(1, ctx->sector_size + 0x20); if (!data_buffer) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_read: failed to allocate memory for data buffer!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data buffer!", __func__); return false; } @@ -613,7 +629,7 @@ bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buf if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_storage_read: hash error from current check found at offset 0x%08X, count 0x%lX!", (u32)offset, count); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: hash error from current check found at offset 0x%08X, count 0x%lX!", __func__, (u32)offset, count); return false; } @@ -624,7 +640,7 @@ u32 save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, al { if (!ctx || !ctx->base_storage || !entry) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_read_entry_with_length: invalid parameters to read entry!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read entry!", __func__); return 0; } @@ -637,7 +653,7 @@ u32 save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, al { if ((entries[0].prev & 0x80000000) && entries[0].prev != 0x80000000) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_read_entry_with_length: invalid range entry in allocation table!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid range entry in allocation table!", __func__); return 0; } } else { @@ -665,7 +681,7 @@ u32 save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, u32 block { if (!ctx || !ctx->header->allocation_table_block_count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_get_list_length: invalid parameters to calculate FAT list length!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to calculate FAT list length!", __func__); return 0; } @@ -675,12 +691,15 @@ u32 save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, u32 block u32 table_size = ctx->header->allocation_table_block_count; u32 nodes_iterated = 0; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + while(entry.next != 0xFFFFFFFF) { u32 entry_length = save_allocation_table_read_entry_with_length(ctx, &entry); if (!entry_length) { - strcat(strbuf, "\nsave_allocation_table_get_list_length: failed to retrieve FAT entry length!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve FAT entry length!", __func__); + strcat(strbuf, tmp); return 0; } @@ -689,7 +708,7 @@ u32 save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, u32 block if (nodes_iterated > table_size) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_get_list_length: cycle detected in allocation table!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: cycle detected in allocation table!", __func__); return 0; } } @@ -701,7 +720,7 @@ bool save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, { if (!ctx || !table) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_iterator_begin: invalid parameters to initialize FAT interator!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to initialize FAT interator!", __func__); return false; } @@ -712,10 +731,13 @@ bool save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, allocation_table_entry_t entry; entry.next = initial_block; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry); if (!ctx->current_segment_size) { - strcat(strbuf, "\nsave_allocation_table_iterator_begin: failed to retrieve FAT entry length!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve FAT entry length!", __func__); + strcat(strbuf, tmp); return false; } @@ -724,7 +746,7 @@ bool save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, if (ctx->prev_block != 0xFFFFFFFF) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_iterator_begin: attempted to start FAT iteration from invalid block 0x%08X!", initial_block); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: attempted to start FAT iteration from invalid block 0x%08X!", __func__, initial_block); return false; } @@ -735,7 +757,7 @@ bool save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *c { if (!ctx || ctx->next_block == 0xFFFFFFFF) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_iterator_move_next: invalid parameters to move iterator to the next block."); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to move iterator to the next block.", __func__); return false; } @@ -745,10 +767,13 @@ bool save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *c allocation_table_entry_t entry; entry.next = ctx->next_block; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry); if (!ctx->current_segment_size) { - strcat(strbuf, "\nsave_allocation_table_iterator_move_next: failed to retrieve current segment size!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve current segment size!", __func__); + strcat(strbuf, tmp); return false; } @@ -762,7 +787,7 @@ bool save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *c { if (!ctx || ctx->prev_block == 0xFFFFFFFF) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_iterator_move_prev: invalid parameters to move iterator to the previous block."); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to move iterator to the previous block!", __func__); return false; } @@ -771,10 +796,13 @@ bool save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *c allocation_table_entry_t entry; entry.next = ctx->prev_block; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry); if (!ctx->current_segment_size) { - strcat(strbuf, "\nsave_allocation_table_iterator_move_prev: failed to retrieve current segment size!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve current segment size!", __func__); + strcat(strbuf, tmp); return false; } @@ -807,14 +835,17 @@ u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void { if (!ctx || !ctx->fat || !ctx->block_size || !buffer || !count) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_allocation_table_storage_read: invalid parameters to read data from FAT storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read data from FAT storage!", __func__); return 0; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + allocation_table_iterator_ctx_t iterator; if (!save_allocation_table_iterator_begin(&iterator, ctx->fat, ctx->initial_block)) { - strcat(strbuf, "\nsave_allocation_table_storage_read: failed to initialize FAT interator!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize FAT interator!", __func__); + strcat(strbuf, tmp); return 0; } @@ -822,14 +853,12 @@ u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void u32 out_pos = 0; u32 remaining = count; - char tmp[NAME_BUF_LEN / 2] = {'\0'}; - while(remaining) { u32 block_num = (u32)(in_pos / ctx->block_size); if (!save_allocation_table_iterator_seek(&iterator, block_num)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_allocation_table_storage_read: failed to seek to block #%u within offset 0x%lX!", block_num, offset); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to seek to block #%u within offset 0x%lX!", __func__, block_num, offset); strcat(strbuf, tmp); return out_pos; } @@ -849,7 +878,7 @@ u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void if (!save_ivfc_storage_read(&ctx->base_storage->integrity_storages[3], (u8*)buffer + out_pos + i, physical_offset + i, bytes_to_request, ctx->base_storage->data_level->save_ctx->tool_ctx.action & ACTION_VERIFY)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_allocation_table_storage_read: failed to read %u bytes chunk from IVFC storage at physical offset 0x%lX!", bytes_to_request, physical_offset + i); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read %u bytes chunk from IVFC storage at physical offset 0x%lX!", __func__, bytes_to_request, physical_offset + i); strcat(strbuf, tmp); return (out_pos + bytes_to_read - chunk_remaining); } @@ -869,15 +898,18 @@ u32 save_fs_list_get_capacity(save_filesystem_list_ctx_t *ctx) { if (!ctx) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_list_get_capacity: invalid parameters to retrieve FS capacity!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve FS capacity!", __func__); return 0; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + if (!ctx->capacity) { if (save_allocation_table_storage_read(&ctx->storage, &ctx->capacity, 4, 4) != 4) { - strcat(strbuf, "\nsave_fs_list_get_capacity: failed to read FS capacity from FAT storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FS capacity from FAT storage!", __func__); + strcat(strbuf, tmp); return 0; } } @@ -889,14 +921,17 @@ u32 save_fs_list_read_entry(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_ { if (!ctx || !entry) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_list_read_entry: invalid parameters to read FS entry!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to read FS entry!", __func__); return 0; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + u32 ret = save_allocation_table_storage_read(&ctx->storage, entry, index * SAVE_FS_LIST_ENTRY_SIZE, SAVE_FS_LIST_ENTRY_SIZE); if (ret != SAVE_FS_LIST_ENTRY_SIZE) { - strcat(strbuf, "\nsave_fs_list_read_entry: failed to read FS entry from FAT storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FS entry from FAT storage!", __func__); + strcat(strbuf, tmp); return 0; } @@ -907,26 +942,30 @@ bool save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_ { if (!ctx || !value) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_list_get_value: invalid parameters to retrieve value for index!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve value for index!", __func__); return false; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + u32 capacity = save_fs_list_get_capacity(ctx); if (!capacity) { - strcat(strbuf, "\nsave_fs_list_get_value: failed to retrieve FS capacity!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve FS capacity!", __func__); + strcat(strbuf, tmp); return false; } if (index >= capacity) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_list_get_value: provided index exceeds FS capacity!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: provided index exceeds FS capacity!", __func__); return false; } if (!save_fs_list_read_entry(ctx, index, value)) { - strcat(strbuf, "\nsave_fs_list_get_value: failed to read FS entry!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FS entry!", __func__); + strcat(strbuf, tmp); return false; } @@ -942,21 +981,22 @@ u32 save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t if (!ctx || !key) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_get_index_from_key: invalid parameters to retrieve FS index from key!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve FS index from key!", __func__); goto out; } u32 capacity = save_fs_list_get_capacity(ctx); if (!capacity) { - strcat(strbuf, "\nsave_fs_get_index_from_key: failed to retrieve FS capacity!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve FS capacity!", __func__); + strcat(strbuf, tmp); goto out; } save_fs_list_entry_t entry; if (!save_fs_list_read_entry(ctx, ctx->used_list_head_index, &entry)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_fs_get_index_from_key: failed to read FS entry for initial index %u!", ctx->used_list_head_index); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FS entry for initial index %u!", __func__, ctx->used_list_head_index); strcat(strbuf, tmp); goto out; } @@ -968,13 +1008,13 @@ u32 save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t { if (index > capacity) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_get_index_from_key: save entry index %d out of range!", index); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: save entry index %d out of range!", __func__, index); break; } if (!save_fs_list_read_entry(ctx, index, &entry)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_fs_get_index_from_key: failed to read FS entry for index %u!", index); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FS entry for index %u!", __func__, index); strcat(strbuf, tmp); break; } @@ -985,7 +1025,7 @@ u32 save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t index = entry.next; } - if (!index) snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_fs_get_index_from_key: unable to find FS index from key!"); + if (!index) snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to find FS index from key!", __func__); out: *prev_index = 0xFFFFFFFF; @@ -996,7 +1036,7 @@ bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_tab { if (!ctx || !key || !path || !strlen(path)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_hierarchical_file_table_find_path_recursive: invalid parameters to find FS path!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to find FS path!", __func__); return false; } @@ -1029,7 +1069,7 @@ bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_ { if (!ctx || !path || !strlen(path) || !entry) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_hierarchical_file_table_get_file_entry_by_path: invalid parameters to retrieve file entry!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve file entry!", __func__); return false; } @@ -1038,7 +1078,7 @@ bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_ save_entry_key_t key; if (!save_hierarchical_file_table_find_path_recursive(ctx, &key, path)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_hierarchical_file_table_get_file_entry_by_path: unable to locate file \"%s\".", path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: unable to locate file \"%s\".", __func__, path); strcat(strbuf, tmp); return false; } @@ -1046,14 +1086,14 @@ bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_ u32 index = save_fs_get_index_from_key(&ctx->file_table, &key, NULL); if (index == 0xFFFFFFFF) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_hierarchical_file_table_get_file_entry_by_path: unable to get table index for file \"%s\".", path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: unable to get table index for file \"%s\".", __func__, path); strcat(strbuf, tmp); return false; } if (!save_fs_list_get_value(&ctx->file_table, index, entry)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nsave_hierarchical_file_table_get_file_entry_by_path: unable to get file entry for \"%s\" from index.", path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: unable to get file entry for \"%s\" from index.", __func__, path); strcat(strbuf, tmp); return false; } @@ -1065,10 +1105,12 @@ bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ { if (!ctx || !ctx->base_storage || !storage_ctx) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_open_fat_storage: invalid parameters to open savefile FAT storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to open savefile FAT storage!", __func__); return false; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + storage_ctx->base_storage = ctx->base_storage; storage_ctx->fat = &ctx->allocation_table; storage_ctx->block_size = (u32)ctx->header->block_size; @@ -1081,7 +1123,8 @@ bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ u32 fat_list_length = save_allocation_table_get_list_length(storage_ctx->fat, block_index); if (!fat_list_length) { - strcat(strbuf, "\nsave_open_fat_storage: failed to retrieve FAT list length!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve FAT list length!", __func__); + strcat(strbuf, tmp); return false; } @@ -1095,10 +1138,12 @@ bool save_filesystem_init(save_filesystem_ctx_t *ctx, void *fat, save_fs_header_ { if (!ctx || !fat || !save_fs_header || !fat_header) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_init: invalid parameters to initialize savefile FS!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to initialize savefile FS!", __func__); return false; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + ctx->allocation_table.base_storage = fat; ctx->allocation_table.header = fat_header; ctx->allocation_table.free_list_entry_index = 0; @@ -1106,13 +1151,15 @@ bool save_filesystem_init(save_filesystem_ctx_t *ctx, void *fat, save_fs_header_ if (!save_open_fat_storage(ctx, &ctx->file_table.directory_table.storage, fat_header->directory_table_block)) { - strcat(strbuf, "\nsave_filesystem_init: failed to open FAT directory storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT directory storage!", __func__); + strcat(strbuf, tmp); return false; } if (!save_open_fat_storage(ctx, &ctx->file_table.file_table.storage, fat_header->file_table_block)) { - strcat(strbuf, "\nsave_filesystem_init: failed to open FAT file storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT file storage!", __func__); + strcat(strbuf, tmp); return false; } @@ -1128,10 +1175,12 @@ validity_t save_ivfc_validate(hierarchical_integrity_verification_storage_ctx_t { if (!ctx || !ivfc || !ivfc->num_levels) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_verify: invalid parameters to verify savefile FS!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to verify savefile FS!", __func__); return VALIDITY_INVALID; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + validity_t result = VALIDITY_VALID; for(unsigned int i = 0; i < (ivfc->num_levels - 1) && result != VALIDITY_INVALID; i++) @@ -1144,7 +1193,7 @@ validity_t save_ivfc_validate(hierarchical_integrity_verification_storage_ctx_t u8 *buffer = calloc(1, block_size); if (!buffer) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_verify: failed to allocate memory for input buffer!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for input buffer!", __func__); result = VALIDITY_INVALID; break; } @@ -1156,7 +1205,8 @@ validity_t save_ivfc_validate(hierarchical_integrity_verification_storage_ctx_t u32 to_read = ((storage->_length - (block_size * j)) < block_size ? (storage->_length - (block_size * j)) : block_size); if (!save_ivfc_storage_read(storage, buffer, block_size * j, to_read, 1)) { - strcat(strbuf, "\nsave_ivfc_validate: failed to read IVFC storage data!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read IVFC storage data!", __func__); + strcat(strbuf, tmp); result = VALIDITY_INVALID; break; } @@ -1181,7 +1231,7 @@ bool save_ivfc_set_level_validities(hierarchical_integrity_verification_storage_ { if (!ctx || !ivfc || !ivfc->num_levels) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_set_level_validities: invalid parameters to set IVFC level validities!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to set IVFC level validities!", __func__); return false; } @@ -1207,7 +1257,7 @@ bool save_ivfc_set_level_validities(hierarchical_integrity_verification_storage_ if (success && level_validity == VALIDITY_INVALID) success = false; } - if (!success) snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_ivfc_set_level_validities: invalid IVFC level!"); + if (!success) snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid IVFC level!", __func__); return success; } @@ -1216,20 +1266,23 @@ validity_t save_filesystem_verify(save_ctx_t *ctx) { if (!ctx) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_verify: invalid parameters to verify savefile FS!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to verify savefile FS!", __func__); return VALIDITY_INVALID; } + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + validity_t journal_validity = save_ivfc_validate(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header); if (journal_validity == VALIDITY_INVALID) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_verify: invalid core IVFC storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid core IVFC storage!", __func__); return journal_validity; } if (!save_ivfc_set_level_validities(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header)) { - strcat(strbuf, "\nsave_filesystem_verify: invalid IVFC level in core IVFC storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: invalid IVFC level in core IVFC storage!", __func__); + strcat(strbuf, tmp); journal_validity = VALIDITY_INVALID; return journal_validity; } @@ -1239,13 +1292,14 @@ validity_t save_filesystem_verify(save_ctx_t *ctx) validity_t fat_validity = save_ivfc_validate(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header); if (fat_validity == VALIDITY_INVALID) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_filesystem_verify: invalid FAT IVFC storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid FAT IVFC storage!", __func__); return fat_validity; } if (!save_ivfc_set_level_validities(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header)) { - strcat(strbuf, "\nsave_filesystem_verify: invalid IVFC level in FAT IVFC storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: invalid IVFC level in FAT IVFC storage!", __func__); + strcat(strbuf, tmp); fat_validity = VALIDITY_INVALID; return fat_validity; } @@ -1262,7 +1316,7 @@ bool save_process(save_ctx_t *ctx) if (!ctx || !ctx->file) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: invalid parameters to process savefile!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to process savefile!", __func__); return false; } @@ -1270,13 +1324,15 @@ bool save_process(save_ctx_t *ctx) FRESULT fr; bool success = false; + char tmp[NAME_BUF_LEN / 2] = {'\0'}; + /* Try to parse Header A. */ f_rewind(ctx->file); fr = f_read(ctx->file, &ctx->header, sizeof(ctx->header), &br); if (fr || br != sizeof(ctx->header)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to read savefile header A! (%u)", fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read savefile header A! (%u)", __func__, fr); return success; } @@ -1288,14 +1344,14 @@ bool save_process(save_ctx_t *ctx) fr = f_lseek(ctx->file, 0x4000); if (fr || f_tell(ctx->file) != 0x4000) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to seek to offset 0x4000 in savefile! (%u)", fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to seek to offset 0x4000 in savefile! (%u)", __func__, fr); return success; } fr = f_read(ctx->file, &ctx->header, sizeof(ctx->header), &br); if (fr || br != sizeof(ctx->header)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to read savefile header B! (%u)", fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read savefile header B! (%u)", __func__, fr); return success; } @@ -1303,7 +1359,7 @@ bool save_process(save_ctx_t *ctx) if (ctx->header_hash_validity == VALIDITY_INVALID) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: savefile header is invalid!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: savefile header is invalid!", __func__); return success; } } @@ -1323,14 +1379,14 @@ bool save_process(save_ctx_t *ctx) ctx->data_remap_storage.map_entries = calloc(sizeof(remap_entry_ctx_t), ctx->data_remap_storage.header->map_entry_count); if (!ctx->data_remap_storage.map_entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for data remap storage entries!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data remap storage entries!", __func__); return success; } fr = f_lseek(ctx->file, ctx->header.layout.file_map_entry_offset); if (fr || f_tell(ctx->file) != ctx->header.layout.file_map_entry_offset) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to seek to file map entry offset 0x%lX in savefile! (%u)", ctx->header.layout.file_map_entry_offset, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to seek to file map entry offset 0x%lX in savefile! (%u)", __func__, ctx->header.layout.file_map_entry_offset, fr); return success; } @@ -1339,7 +1395,7 @@ bool save_process(save_ctx_t *ctx) fr = f_read(ctx->file, &ctx->data_remap_storage.map_entries[i], 0x20, &br); if (fr || br != 0x20) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to read data remap storage entry #%u! (%u)", i, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read data remap storage entry #%u! (%u)", __func__, i, fr); goto out; } @@ -1351,7 +1407,8 @@ bool save_process(save_ctx_t *ctx) ctx->data_remap_storage.segments = save_remap_init_segments(ctx->data_remap_storage.header, ctx->data_remap_storage.map_entries, ctx->data_remap_storage.header->map_entry_count); if (!ctx->data_remap_storage.segments) { - strcat(strbuf, "\nsave_process: failed to retrieve data remap storage segments!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve data remap storage segments!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1363,26 +1420,28 @@ bool save_process(save_ctx_t *ctx) ctx->duplex_layers[1].data_a = calloc(1, ctx->header.layout.duplex_l1_size); if (!ctx->duplex_layers[1].data_a) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for data_a block in duplex layer #1!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data_a block in duplex layer #1!", __func__); goto out; } if (save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_a, ctx->header.layout.duplex_l1_offset_a, ctx->header.layout.duplex_l1_size) != ctx->header.layout.duplex_l1_size) { - strcat(strbuf, "\nsave_process: failed to read data_a block from duplex layer #1 in data remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read data_a block from duplex layer #1 in data remap storage!", __func__); + strcat(strbuf, tmp); goto out; } ctx->duplex_layers[1].data_b = calloc(1, ctx->header.layout.duplex_l1_size); if (!ctx->duplex_layers[1].data_b) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for data_b block in duplex layer #1!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data_b block in duplex layer #1!", __func__); goto out; } if (save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_b, ctx->header.layout.duplex_l1_offset_b, ctx->header.layout.duplex_l1_size) != ctx->header.layout.duplex_l1_size) { - strcat(strbuf, "\nsave_process: failed to read data_b block from duplex layer #1 in data remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read data_b block from duplex layer #1 in data remap storage!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1391,26 +1450,28 @@ bool save_process(save_ctx_t *ctx) ctx->duplex_layers[2].data_a = calloc(1, ctx->header.layout.duplex_data_size); if (!ctx->duplex_layers[2].data_a) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for data_a block in duplex layer #2!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data_a block in duplex layer #2!", __func__); goto out; } if (save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_a, ctx->header.layout.duplex_data_offset_a, ctx->header.layout.duplex_data_size) != ctx->header.layout.duplex_data_size) { - strcat(strbuf, "\nsave_process: failed to read data_a block from duplex layer #2 in data remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read data_a block from duplex layer #2 in data remap storage!", __func__); + strcat(strbuf, tmp); goto out; } ctx->duplex_layers[2].data_b = calloc(1, ctx->header.layout.duplex_data_size); if (!ctx->duplex_layers[2].data_b) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for data_b block in duplex layer #2!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for data_b block in duplex layer #2!", __func__); goto out; } if (save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_b, ctx->header.layout.duplex_data_offset_b, ctx->header.layout.duplex_data_size) != ctx->header.layout.duplex_data_size) { - strcat(strbuf, "\nsave_process: failed to read data_b block from duplex layer #2 in data remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read data_b block from duplex layer #2 in data remap storage!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1421,7 +1482,8 @@ bool save_process(save_ctx_t *ctx) if (!save_duplex_storage_init(&ctx->duplex_storage.layers[0], &ctx->duplex_layers[1], bitmap, ctx->header.layout.duplex_master_size)) { - strcat(strbuf, "\nsave_process: failed to initialize duplex storage layer #0!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize duplex storage layer #0!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1430,20 +1492,21 @@ bool save_process(save_ctx_t *ctx) bitmap = calloc(1, ctx->duplex_storage.layers[0]._length); if (!bitmap) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for duplex storage layer #0 bitmap!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for duplex storage layer #0 bitmap!", __func__); goto out; } if (save_duplex_storage_read(&ctx->duplex_storage.layers[0], bitmap, 0, ctx->duplex_storage.layers[0]._length) != ctx->duplex_storage.layers[0]._length) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to read duplex storage layer #0 bitmap!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read duplex storage layer #0 bitmap!", __func__); free(bitmap); goto out; } if (!save_duplex_storage_init(&ctx->duplex_storage.layers[1], &ctx->duplex_layers[2], bitmap, ctx->duplex_storage.layers[0]._length)) { - strcat(strbuf, "\nsave_process: failed to initialize duplex storage layer #1!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize duplex storage layer #1!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1460,14 +1523,14 @@ bool save_process(save_ctx_t *ctx) ctx->meta_remap_storage.map_entries = calloc(sizeof(remap_entry_ctx_t), ctx->meta_remap_storage.header->map_entry_count); if (!ctx->meta_remap_storage.map_entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for meta remap storage entries!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for meta remap storage entries!", __func__); goto out; } fr = f_lseek(ctx->file, ctx->header.layout.meta_map_entry_offset); if (fr || f_tell(ctx->file) != ctx->header.layout.meta_map_entry_offset) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to seek to meta map entry offset 0x%lX in savefile! (%u)", ctx->header.layout.meta_map_entry_offset, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to seek to meta map entry offset 0x%lX in savefile! (%u)", __func__, ctx->header.layout.meta_map_entry_offset, fr); goto out; } @@ -1476,7 +1539,7 @@ bool save_process(save_ctx_t *ctx) fr = f_read(ctx->file, &ctx->meta_remap_storage.map_entries[i], 0x20, &br); if (fr || br != 0x20) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to read meta remap storage entry #%u! (%u)", i, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read meta remap storage entry #%u! (%u)", __func__, i, fr); goto out; } @@ -1487,7 +1550,8 @@ bool save_process(save_ctx_t *ctx) ctx->meta_remap_storage.segments = save_remap_init_segments(ctx->meta_remap_storage.header, ctx->meta_remap_storage.map_entries, ctx->meta_remap_storage.header->map_entry_count); if (!ctx->meta_remap_storage.segments) { - strcat(strbuf, "\nsave_process: failed to retrieve meta remap storage segments!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to retrieve meta remap storage segments!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1495,13 +1559,14 @@ bool save_process(save_ctx_t *ctx) ctx->journal_map_info.map_storage = calloc(1, ctx->header.layout.journal_map_table_size); if (!ctx->journal_map_info.map_storage) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for journal map info!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for journal map info!", __func__); goto out; } if (save_remap_read(&ctx->meta_remap_storage, ctx->journal_map_info.map_storage, ctx->header.layout.journal_map_table_offset, ctx->header.layout.journal_map_table_size) != ctx->header.layout.journal_map_table_size) { - strcat(strbuf, "\nsave_process: failed to read map storage from journal map info in meta remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read map storage from journal map info in meta remap storage!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1516,7 +1581,7 @@ bool save_process(save_ctx_t *ctx) ctx->journal_storage.map.entries = calloc(sizeof(journal_map_entry_t), ctx->journal_storage.map.header->main_data_block_count); if (!ctx->journal_storage.map.entries) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for journal map storage entries!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for journal map storage entries!", __func__); goto out; } @@ -1537,7 +1602,8 @@ bool save_process(save_ctx_t *ctx) if (!save_ivfc_storage_init(&ctx->core_data_ivfc_storage, ctx->header.layout.ivfc_master_hash_offset_a, &ctx->header.data_ivfc_header)) { - strcat(strbuf, "\nsave_process: failed to initialize core IVFC storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize core IVFC storage!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1547,13 +1613,14 @@ bool save_process(save_ctx_t *ctx) ctx->fat_storage = calloc(1, ctx->header.layout.fat_size); if (!ctx->fat_storage) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for FAT storage!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for FAT storage!", __func__); goto out; } if (save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.layout.fat_offset, ctx->header.layout.fat_size) != ctx->header.layout.fat_size) { - strcat(strbuf, "\nsave_process: failed to read FAT storage from meta remap storage!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FAT storage from meta remap storage!", __func__); + strcat(strbuf, tmp); goto out; } } else { @@ -1561,20 +1628,22 @@ bool save_process(save_ctx_t *ctx) if (!save_ivfc_storage_init(&ctx->fat_ivfc_storage, ctx->header.layout.fat_ivfc_master_hash_a, &ctx->header.fat_ivfc_header)) { - strcat(strbuf, "\nsave_process: failed to initialize FAT storage (IVFC)!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize FAT storage (IVFC)!", __func__); + strcat(strbuf, tmp); goto out; } ctx->fat_storage = calloc(1, ctx->fat_ivfc_storage._length); if (!ctx->fat_storage) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process: failed to allocate memory for FAT storage (IVFC)!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for FAT storage (IVFC)!", __func__); goto out; } if (save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.fat_ivfc_header.level_headers[ctx->header.fat_ivfc_header.num_levels - 2].logical_offset, ctx->fat_ivfc_storage._length) != ctx->fat_ivfc_storage._length) { - strcat(strbuf, "\nsave_process: failed to read FAT storage from meta remap storage (IVFC)!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read FAT storage from meta remap storage (IVFC)!", __func__); + strcat(strbuf, tmp); goto out; } } @@ -1583,7 +1652,8 @@ bool save_process(save_ctx_t *ctx) { if (save_filesystem_verify(ctx) == VALIDITY_INVALID) { - strcat(strbuf, "\nsave_process: savefile FS verification failed!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: savefile FS verification failed!", __func__); + strcat(strbuf, tmp); goto out; } } @@ -1592,7 +1662,8 @@ bool save_process(save_ctx_t *ctx) ctx->save_filesystem_core.base_storage = &ctx->core_data_ivfc_storage; if (!save_filesystem_init(&ctx->save_filesystem_core, ctx->fat_storage, &ctx->header.save_header, &ctx->header.fat_header)) { - strcat(strbuf, "\nsave_process: failed to initialize savefile FS!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to initialize savefile FS!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1608,7 +1679,7 @@ bool save_process_header(save_ctx_t *ctx) { if (!ctx) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process_header: invalid parameters to process savefile header!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to process savefile header!", __func__); return false; } @@ -1617,7 +1688,7 @@ bool save_process_header(save_ctx_t *ctx) ctx->header.save_header.magic != MAGIC_SAVE || ctx->header.main_remap_header.magic != MAGIC_RMAP || \ ctx->header.meta_remap_header.magic != MAGIC_RMAP) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "save_process_header: save header is corrupt!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: save header is corrupt!", __func__); return false; } @@ -1795,7 +1866,7 @@ bool readCertsFromSystemSave() fr = f_open(&certSave, BIS_CERT_SAVE_NAME, FA_READ | FA_OPEN_EXISTING); if (fr != FR_OK) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: failed to open \"%s\" savefile from BIS System partition! (%u)", BIS_CERT_SAVE_NAME, fr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to open \"%s\" savefile from BIS System partition! (%u)", __func__, BIS_CERT_SAVE_NAME, fr); goto out; } @@ -1804,7 +1875,7 @@ bool readCertsFromSystemSave() save_ctx = calloc(1, sizeof(save_ctx_t)); if (!save_ctx) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: failed to allocate memory for savefile context!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to allocate memory for savefile context!", __func__); goto out; } @@ -1815,7 +1886,8 @@ bool readCertsFromSystemSave() initSaveCtx = save_process(save_ctx); if (!initSaveCtx) { - strcat(strbuf, "\nError: failed to process system savefile!"); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to process system savefile!", __func__); + strcat(strbuf, tmp); goto out; } @@ -1834,7 +1906,7 @@ bool readCertsFromSystemSave() if (i < 2) { - snprintf(cert_path, MAX_ELEMENTS(cert_path), (i == 0 ? cert_CA00000003_path : cert_XS00000020_path)); + snprintf(cert_path, MAX_CHARACTERS(cert_path), (i == 0 ? cert_CA00000003_path : cert_XS00000020_path)); memcpy(cert_expected_hash, (i == 0 ? cert_CA00000003_hash : cert_XS00000020_hash), SHA256_HASH_SIZE); getFileEntry = save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, cert_path, &entry); @@ -1845,11 +1917,11 @@ bool readCertsFromSystemSave() switch(j) { case 0: // < 9.0.0 - snprintf(cert_path, MAX_ELEMENTS(cert_path), cert_XS00000021_path); + snprintf(cert_path, MAX_CHARACTERS(cert_path), cert_XS00000021_path); memcpy(cert_expected_hash, cert_XS00000021_hash, SHA256_HASH_SIZE); break; case 1: // >= 9.0.0 - snprintf(cert_path, MAX_ELEMENTS(cert_path), cert_XS00000024_path); + snprintf(cert_path, MAX_CHARACTERS(cert_path), cert_XS00000024_path); memcpy(cert_expected_hash, cert_XS00000024_hash, SHA256_HASH_SIZE); break; default: @@ -1865,14 +1937,21 @@ bool readCertsFromSystemSave() if (!getFileEntry) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to get file entry for \"%s\" in system savefile!", cert_path); - strcat(strbuf, tmp); + // Only print the error if we're dealing with either the root or common certificates. We may be dealing with a console without a personalized certificate + if (i < 2) + { + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to get file entry for \"%s\" in system savefile!", __func__, cert_path); + strcat(strbuf, tmp); + } else { + success = loadedCerts = true; + } + goto out; } if (!save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block)) { - snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to open FAT storage at block 0x%X for \"%s\" in system savefile!", entry.value.save_file_info.start_block, cert_path); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT storage at block 0x%X for \"%s\" in system savefile!", __func__, entry.value.save_file_info.start_block, cert_path); strcat(strbuf, tmp); goto out; } @@ -1880,14 +1959,14 @@ bool readCertsFromSystemSave() internalCertSize = entry.value.save_file_info.length; if (internalCertSize != cert_expected_size) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: invalid size for \"%s\" in system savefile! (%lu != %lu)", cert_path, internalCertSize, cert_expected_size); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid size for \"%s\" in system savefile! (%lu != %lu)", __func__, cert_path, internalCertSize, cert_expected_size); goto out; } br = save_allocation_table_storage_read(&fat_storage, cert_data_ptr, 0, cert_expected_size); if (br != (UINT)cert_expected_size) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: failed to read \"%s\" from system savefile!", cert_path); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to read \"%s\" from system savefile!", __func__, cert_path); goto out; } @@ -1895,12 +1974,12 @@ bool readCertsFromSystemSave() if (memcmp(tmp_hash, cert_expected_hash, 0x20) != 0) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: invalid hash for \"%s\" in system savefile!", cert_path); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid hash for \"%s\" in system savefile!", __func__, cert_path); goto out; } } - success = loadedCerts = true; + success = loadedCerts = personalizedCertAvailable = true; out: if (save_ctx) @@ -1918,12 +1997,18 @@ bool retrieveCertData(u8 *out_cert, bool personalized) { if (!out_cert) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Error: invalid parameters to retrieve %s ticket certificate chain.", (!personalized ? "common" : "personalized")); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to retrieve %s ticket certificate chain.", __func__, (!personalized ? "common" : "personalized")); return false; } if (!readCertsFromSystemSave()) return false; + if (personalized && !personalizedCertAvailable) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: personalized ticket RSA certificate requested but unavailable in ES system savefile!", __func__); + return false; + } + memcpy(out_cert, cert_root_data, ETICKET_CA_CERT_SIZE); memcpy(out_cert + ETICKET_CA_CERT_SIZE, (!personalized ? cert_common_data : cert_personalized_data), ETICKET_XS_CERT_SIZE); diff --git a/source/service_guard.h b/source/service_guard.h new file mode 100644 index 0000000..da077cc --- /dev/null +++ b/source/service_guard.h @@ -0,0 +1,52 @@ +#pragma once +#include + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) diff --git a/source/set_ext.c b/source/set_ext.c index 47d1221..294df7d 100644 --- a/source/set_ext.c +++ b/source/set_ext.c @@ -5,54 +5,24 @@ #include #include "set_ext.h" +#include "service_guard.h" static Service g_setcalSrv; -static u64 g_refCntCal; -Result setcalInitialize(void) -{ - atomicIncrement64(&g_refCntCal); - - if (serviceIsActive(&g_setcalSrv)) return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); - +NX_GENERATE_SERVICE_GUARD(setcal); + +Result _setcalInitialize() { return smGetService(&g_setcalSrv, "set:cal"); } -void setcalExit(void) -{ - if (atomicDecrement64(&g_refCntCal) == 0) serviceClose(&g_setcalSrv); +void _setcalCleanup() { + serviceClose(&g_setcalSrv); } Result setcalGetEticketDeviceKey(void *key) { - IpcCommand c; - ipcInitialize(&c); - ipcAddRecvBuffer(&c, key, 0x244, 0); - - struct { - u64 magic; - u64 cmd_id; - } *raw; - - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 21; - - Result rc = serviceIpcDispatch(&g_setcalSrv); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - } *resp = r.Raw; - - rc = resp->result; - } - - return rc; + return serviceDispatch(&g_setcalSrv, 21, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { key, 0x244 } }, + ); } diff --git a/source/ui.c b/source/ui.c index 1279546..d45826c 100644 --- a/source/ui.c +++ b/source/ui.c @@ -27,64 +27,29 @@ extern bool keysFileAvailable; extern AppletType programAppletType; -extern bool runningSxOs; +extern gamecard_ctx_t gameCardInfo; -extern bool gameCardInserted; +extern u32 titleAppCount, titlePatchCount, titleAddOnCount; +extern u32 sdCardTitleAppCount, sdCardTitlePatchCount, sdCardTitleAddOnCount; +extern u32 emmcTitleAppCount, emmcTitlePatchCount, emmcTitleAddOnCount; -extern char gameCardSizeStr[32], trimmedCardSizeStr[32]; +extern base_app_ctx_t *baseAppEntries; +extern patch_addon_ctx_t *patchEntries, *addOnEntries; -extern u8 *hfs0_header; -extern u64 hfs0_offset, hfs0_size; -extern u32 hfs0_partition_cnt; - -extern u8 *partitionHfs0Header; -extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize; -extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize; - -extern u32 titleAppCount; -extern u64 *titleAppTitleID; -extern u32 *titleAppVersion; -extern FsStorageId *titleAppStorageId; - -extern u32 titlePatchCount; -extern u64 *titlePatchTitleID; -extern u32 *titlePatchVersion; -extern FsStorageId *titlePatchStorageId; - -extern u32 titleAddOnCount; -extern u64 *titleAddOnTitleID; -extern u32 *titleAddOnVersion; -extern FsStorageId *titleAddOnStorageId; - -extern char **titleName; -extern char **titleAuthor; -extern char **titleAppVersionStr; -extern u8 **titleIcon; - -extern u32 sdCardTitleAppCount; -extern u32 sdCardTitlePatchCount; -extern u32 sdCardTitleAddOnCount; - -extern u32 nandUserTitleAppCount; -extern u32 nandUserTitlePatchCount; -extern u32 nandUserTitleAddOnCount; - -extern char gameCardUpdateVersionStr[128]; - -extern char *filenameBuffer; extern char *filenames[FILENAME_MAX_CNT]; extern int filenamesCount; extern char curRomFsPath[NAME_BUF_LEN]; extern romfs_browser_entry *romFsBrowserEntries; -extern u8 *dumpBuf; -extern u8 *ncaCtrBuf; +extern browser_entry_size_info *hfs0ExeFsEntriesSizes; extern orphan_patch_addon_entry *orphanEntries; extern char strbuf[NAME_BUF_LEN]; +extern char cfwDirStr[32]; + /* Statically allocated variables */ static PlFontData sharedFonts[PlSharedFontType_Total]; @@ -103,11 +68,9 @@ int scroll = 0; int breaks = 0; int font_height = 0; -int titleListCursor = 0; -int titleListScroll = 0; - -int orphanListCursor = 0; -int orphanListScroll = 0; +int titleListCursor = 0, titleListScroll = 0; +int orphanListCursor = 0, orphanListScroll = 0; +int browserCursor = 0, browserScroll = 0; curMenuType menuType; static bool orphanMode = false; @@ -137,11 +100,13 @@ bool highlight = false; static char statusMessage[2048] = {'\0'}; static int statusMessageFadeout = 0; -u64 freeSpace = 0; -static char freeSpaceStr[64] = {'\0'}; +extern u64 freeSpace; +extern char freeSpaceStr[32]; static UIState uiState; +static bool fb_init = false, romfs_init = false, ft_lib_init = false, ft_faces_init[PlSharedFontType_Total]; + static const char *dirNormalIconPath = "romfs:/browser/dir_normal.jpg"; static u8 *dirNormalIconBuf = NULL; @@ -166,7 +131,7 @@ u8 *disabledNormalIconBuf = NULL; static const char *disabledHighlightIconPath = "romfs:/browser/disabled_highlight.jpg"; u8 *disabledHighlightIconBuf = NULL; -static const char *appHeadline = "NXDumpTool v" APP_VERSION ".\nOriginal codebase by MCMrARM.\nUpdated and maintained by DarkMatterCore.\n\n"; +static const char *appHeadline = "NXDumpTool v" APP_VERSION ". Built on " __DATE__ " - " __TIME__ ".\nMade by DarkMatterCore.\n\n"; static const char *appControlsCommon = "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_A " ] Select | [ " NINTENDO_FONT_B " ] Back | [ " NINTENDO_FONT_PLUS " ] Exit"; static const char *appControlsGameCardMultiApp = "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_A " ] Select | [ " NINTENDO_FONT_B " ] Back | [ " NINTENDO_FONT_L " / " NINTENDO_FONT_R " / " NINTENDO_FONT_ZL " / " NINTENDO_FONT_ZR " ] Show info from another base application | [ " NINTENDO_FONT_PLUS " ] Exit"; static const char *appControlsNoContent = "[ " NINTENDO_FONT_B " ] Back | [ " NINTENDO_FONT_PLUS " ] Exit"; @@ -174,30 +139,35 @@ static const char *appControlsSdCardEmmcFull = "[ " NINTENDO_FONT_DPAD " / " NIN static const char *appControlsSdCardEmmcNoApp = "[ " NINTENDO_FONT_B " ] Back | [ " NINTENDO_FONT_X " ] Batch mode | [ " NINTENDO_FONT_Y " ] Dump installed content with missing base application | [ " NINTENDO_FONT_PLUS " ] Exit"; static const char *appControlsRomFs = "[ " NINTENDO_FONT_DPAD " / " NINTENDO_FONT_LSTICK " / " NINTENDO_FONT_RSTICK " ] Move | [ " NINTENDO_FONT_A " ] Select | [ " NINTENDO_FONT_B " ] Back | [ " NINTENDO_FONT_Y " ] Dump current directory | [ " NINTENDO_FONT_PLUS " ] Exit"; -static const char *mainMenuItems[] = { "Dump gamecard content", "Dump SD card / eMMC (NANDUSER) content", "Update options" }; -static const char *gameCardMenuItems[] = { "Cartridge Image (XCI) dump", "Nintendo Submission Package (NSP) dump", "HFS0 options", "ExeFS options", "RomFS options", "Dump gamecard certificate" }; -static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Create directory with archive bit set: ", "Keep certificate: ", "Trim output dump: ", "CRC32 checksum calculation + dump verification: " }; +static const char *mainMenuItems[] = { "Dump gamecard content", "Dump installed SD card / eMMC content", "Update options" }; +static const char *gameCardMenuItems[] = { "NX Card Image (XCI) dump", "Nintendo Submission Package (NSP) dump", "HFS0 options", "ExeFS options", "RomFS options", "Dump gamecard certificate" }; +static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Create directory with archive bit set: ", "Keep certificate: ", "Trim output dump: ", "CRC32 checksum calculation + dump verification: ", "Dump verification method: ", "Output naming scheme: " }; static const char *nspDumpGameCardMenuItems[] = { "Dump base application NSP", "Dump bundled update NSP", "Dump bundled DLC NSP" }; static const char *nspDumpSdCardEmmcMenuItems[] = { "Dump base application NSP", "Dump installed update NSP", "Dump installed DLC NSP" }; -static const char *nspAppDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "CRC32 checksum calculation: ", "Remove console specific data: ", "Generate ticket-less dump: ", "Change NPDM RSA key/sig in Program NCA: ", "Base application to dump: " }; -static const char *nspPatchDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "CRC32 checksum calculation: ", "Remove console specific data: ", "Generate ticket-less dump: ", "Change NPDM RSA key/sig in Program NCA: ", "Update to dump: " }; -static const char *nspAddOnDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "CRC32 checksum calculation: ", "Remove console specific data: ", "Generate ticket-less dump: ", "DLC to dump: " }; +static const char *nspAppDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "Verify dump using No-Intro database: ", "Remove console specific data: ", "Generate ticket-less dump: ", "Change NPDM RSA key/sig in Program NCA: ", "Base application to dump: ", "Output naming scheme: " }; +static const char *nspPatchDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "Verify dump using No-Intro database: ", "Remove console specific data: ", "Generate ticket-less dump: ", "Change NPDM RSA key/sig in Program NCA: ", "Dump delta fragments: ", "Update to dump: ", "Output naming scheme: " }; +static const char *nspAddOnDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "Verify dump using No-Intro database: ", "Remove console specific data: ", "Generate ticket-less dump: ", "DLC to dump: ", "Output naming scheme: " }; static const char *hfs0MenuItems[] = { "Raw HFS0 partition dump", "HFS0 partition data dump", "Browse HFS0 partitions" }; static const char *hfs0PartitionDumpType1MenuItems[] = { "Dump HFS0 partition 0 (Update)", "Dump HFS0 partition 1 (Normal)", "Dump HFS0 partition 2 (Secure)" }; static const char *hfs0PartitionDumpType2MenuItems[] = { "Dump HFS0 partition 0 (Update)", "Dump HFS0 partition 1 (Logo)", "Dump HFS0 partition 2 (Normal)", "Dump HFS0 partition 3 (Secure)" }; static const char *hfs0BrowserType1MenuItems[] = { "Browse HFS0 partition 0 (Update)", "Browse HFS0 partition 1 (Normal)", "Browse HFS0 partition 2 (Secure)" }; static const char *hfs0BrowserType2MenuItems[] = { "Browse HFS0 partition 0 (Update)", "Browse HFS0 partition 1 (Logo)", "Browse HFS0 partition 2 (Normal)", "Browse HFS0 partition 3 (Secure)" }; -static const char *exeFsMenuItems[] = { "ExeFS section data dump", "Browse ExeFS section", "Use update: " }; +static const char *exeFsMenuItems[] = { "ExeFS section data dump", "Browse ExeFS section", "Split files bigger than 4 GiB (FAT32 support): ", "Save data to CFW directory (LayeredFS): ", "Use update: " }; static const char *exeFsSectionDumpMenuItems[] = { "Start ExeFS data dump process", "Base application to dump: ", "Use update: " }; static const char *exeFsSectionBrowserMenuItems[] = { "Browse ExeFS section", "Base application to browse: ", "Use update: " }; -static const char *romFsMenuItems[] = { "RomFS section data dump", "Browse RomFS section", "Use update/DLC: " }; +static const char *romFsMenuItems[] = { "RomFS section data dump", "Browse RomFS section", "Split files bigger than 4 GiB (FAT32 support): ", "Save data to CFW directory (LayeredFS): ", "Use update/DLC: " }; static const char *romFsSectionDumpMenuItems[] = { "Start RomFS data dump process", "Base application to dump: ", "Use update/DLC: " }; static const char *romFsSectionBrowserMenuItems[] = { "Browse RomFS section", "Base application to browse: ", "Use update/DLC: " }; static const char *sdCardEmmcMenuItems[] = { "Nintendo Submission Package (NSP) dump", "ExeFS options", "RomFS options", "Ticket options" }; -static const char *batchModeMenuItems[] = { "Start batch dump process", "Dump base applications: ", "Dump updates: ", "Dump DLCs: ", "Split output dumps (FAT32 support): ", "Remove console specific data: ", "Generate ticket-less dumps: ", "Change NPDM RSA key/sig in Program NCA: ", "Skip already dumped titles: ", "Remember dumped titles: ", "Source storage: " }; +static const char *batchModeMenuItems[] = { "Start batch dump process", "Dump base applications: ", "Dump updates: ", "Dump DLCs: ", "Split output dumps (FAT32 support): ", "Remove console specific data: ", "Generate ticket-less dumps: ", "Change NPDM RSA key/sig in Program NCA: ", "Dump delta fragments from updates: ", "Skip already dumped titles: ", "Remember dumped titles: ", "Halt dump process on errors: ", "Output naming scheme: ", "Source storage: " }; static const char *ticketMenuItems[] = { "Start ticket dump", "Remove console specific data: ", "Use ticket from title: " }; static const char *updateMenuItems[] = { "Update NSWDB.COM XML database", "Update application" }; +static const char *xciChecksumLookupMethods[] = { "NSWDB.COM XML database (offline)", "No-Intro database lookup (online)" }; + +static const char *xciNamingSchemes[] = { "TitleName v[TitleVersion] ([TitleID])", "TitleName [[TitleID]][v[TitleVersion]]" }; +static const char *nspNamingSchemes[] = { "TitleName v[TitleVersion] ([TitleID]) ([TitleType])", "TitleName [[TitleID]][v[TitleVersion]][[TitleType]]" }; + void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b) { /* Perform validity checks */ @@ -293,7 +263,7 @@ bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expe { if (!rawJpg || !rawJpgSize || !expectedWidth || !expectedHeight || !desiredWidth || !desiredHeight || !outBuf) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: invalid parameters to process JPG image buffer."); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to process JPG image buffer!", __func__); return false; } @@ -308,62 +278,70 @@ bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expe u8 *jpgScaledBuf = NULL; _jpegDecompressor = tjInitDecompress(); - if (_jpegDecompressor) + if (!_jpegDecompressor) { - ret = tjDecompressHeader2(_jpegDecompressor, rawJpg, rawJpgSize, &w, &h, &samp); - if (ret != -1) - { - if (w == expectedWidth && h == expectedHeight) - { - scalingFactors = tjGetScalingFactors(&numScalingFactors); - if (scalingFactors) - { - for(i = 0; i < numScalingFactors; i++) - { - if (TJSCALED(expectedWidth, scalingFactors[i]) == desiredWidth && TJSCALED(expectedHeight, scalingFactors[i]) == desiredHeight) - { - foundScalingFactor = true; - break; - } - } - - if (foundScalingFactor) - { - pitch = TJPAD(desiredWidth * tjPixelSize[TJPF_RGB]); - - jpgScaledBuf = malloc(pitch * desiredHeight); - if (jpgScaledBuf) - { - ret = tjDecompress2(_jpegDecompressor, rawJpg, rawJpgSize, jpgScaledBuf, desiredWidth, 0, desiredHeight, TJPF_RGB, TJFLAG_ACCURATEDCT); - if (ret != -1) - { - *outBuf = jpgScaledBuf; - success = true; - } else { - free(jpgScaledBuf); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: tjDecompress2 failed (%d).", ret); - } - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: unable to allocated memory for the scaled RGB image output."); - } - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: unable to find a valid scaling factor."); - } - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: error retrieving scaling factors."); - } - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: invalid image width/height."); - } - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: tjDecompressHeader2 failed (%d).", ret); - } - - tjDestroy(_jpegDecompressor); - } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromMem: tjInitDecompress failed."); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: tjInitDecompress failed!", __func__); + return success; } + ret = tjDecompressHeader2(_jpegDecompressor, rawJpg, rawJpgSize, &w, &h, &samp); + if (ret == -1) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: tjDecompressHeader2 failed! (%d)", __func__, ret); + goto out; + } + + if (w != expectedWidth || h != expectedHeight) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid image width/height!", __func__); + goto out; + } + + scalingFactors = tjGetScalingFactors(&numScalingFactors); + if (!scalingFactors) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to retrieve scaling factors!", __func__); + goto out; + } + + for(i = 0; i < numScalingFactors; i++) + { + if (TJSCALED(expectedWidth, scalingFactors[i]) == desiredWidth && TJSCALED(expectedHeight, scalingFactors[i]) == desiredHeight) + { + foundScalingFactor = true; + break; + } + } + + if (!foundScalingFactor) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to find a valid scaling factor!", __func__); + goto out; + } + + pitch = TJPAD(desiredWidth * tjPixelSize[TJPF_RGB]); + + jpgScaledBuf = malloc(pitch * desiredHeight); + if (!jpgScaledBuf) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to allocate memory for the scaled RGB image output!", __func__); + goto out; + } + + ret = tjDecompress2(_jpegDecompressor, rawJpg, rawJpgSize, jpgScaledBuf, desiredWidth, 0, desiredHeight, TJPF_RGB, TJFLAG_ACCURATEDCT); + if (ret == -1) + { + free(jpgScaledBuf); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: tjDecompress2 failed! (%d)", __func__, ret); + goto out; + } + + *outBuf = jpgScaledBuf; + success = true; + +out: + tjDestroy(_jpegDecompressor); + return success; } @@ -371,7 +349,7 @@ bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeig { if (!filename || !desiredWidth || !desiredHeight || !outBuf) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromFile: invalid parameters to process JPG image file.\n"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid parameters to process JPG image file!", __func__); return false; } @@ -382,7 +360,7 @@ bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeig fp = fopen(filename, "rb"); if (!fp) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromFile: failed to open file \"%s\".\n", filename); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: failed to open file \"%s\"!", __func__, filename); return false; } @@ -392,7 +370,7 @@ bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeig if (!filesize) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromFile: file \"%s\" is empty.\n", filename); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: file \"%s\" is empty!", __func__, filename); fclose(fp); return false; } @@ -400,18 +378,17 @@ bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeig buf = malloc(filesize); if (!buf) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromFile: error allocating memory for image \"%s\".\n", filename); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: error allocating memory for image \"%s\"!", __func__, filename); fclose(fp); return false; } read = fread(buf, 1, filesize, fp); - fclose(fp); if (read != filesize) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "uiLoadJpgFromFile: error reading image \"%s\".\n", filename); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: error reading image \"%s\"!", __func__, filename); free(buf); return false; } @@ -420,8 +397,6 @@ bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeig free(buf); - if (!ret) strcat(strbuf, "\n"); - return ret; } @@ -480,7 +455,7 @@ void uiDrawString(int x, int y, u8 r, u8 g, u8 b, const char *fmt, ...) va_list args; va_start(args, fmt); - vsnprintf(string, MAX_ELEMENTS(string), fmt, args); + vsnprintf(string, MAX_CHARACTERS(string), fmt, args); va_end(args); u32 tmpx = (x <= 8 ? 8 : (x + 8)); @@ -560,7 +535,7 @@ u32 uiGetStrWidth(const char *fmt, ...) va_list args; va_start(args, fmt); - vsnprintf(string, MAX_ELEMENTS(string), fmt, args); + vsnprintf(string, MAX_CHARACTERS(string), fmt, args); va_end(args); FT_Error ret = 0; @@ -619,11 +594,11 @@ void uiRefreshDisplay() void uiStatusMsg(const char *fmt, ...) { - statusMessageFadeout = 5000; + statusMessageFadeout = 2500; va_list args; va_start(args, fmt); - vsnprintf(statusMessage, MAX_ELEMENTS(statusMessage), fmt, args); + vsnprintf(statusMessage, MAX_CHARACTERS(statusMessage), fmt, args); va_end(args); } @@ -643,6 +618,12 @@ void uiUpdateStatusMsg() } } +void uiClearStatusMsg() +{ + statusMessageFadeout = 0; + statusMessage[0] = '\0'; +} + void uiPleaseWait(u8 wait) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Please wait..."); @@ -650,16 +631,6 @@ void uiPleaseWait(u8 wait) if (wait) delay(wait); } -void uiUpdateFreeSpace() -{ - getSdCardFreeSpace(&freeSpace); - - char tmp[32] = {'\0'}; - convertSize(freeSpace, tmp, MAX_ELEMENTS(tmp)); - - snprintf(freeSpaceStr, MAX_ELEMENTS(freeSpaceStr), "Free SD card space: %s.", tmp); -} - void uiClearScreen() { uiFill(0, 0, FB_WIDTH, FB_HEIGHT, BG_COLOR_RGB); @@ -681,7 +652,7 @@ void uiPrintOption(int x, int y, int endPosition, bool leftArrow, bool rightArro va_list args; va_start(args, fmt); - vsnprintf(option, MAX_ELEMENTS(option), fmt, args); + vsnprintf(option, MAX_CHARACTERS(option), fmt, args); va_end(args); u32 optionStrWidth = uiGetStrWidth(option); @@ -727,42 +698,14 @@ void uiTruncateOptionStr(char *str, int x, int y, int endPosition) } } -void error_screen(const char *fmt, ...) +bool uiInit() { - consoleInit(NULL); - - va_list va; - va_start(va, fmt); - vprintf(fmt, va); - va_end(va); - - printf("Press any button to exit.\n"); - - while(appletMainLoop()) - { - hidScanInput(); - - u32 keysDown = hidKeysDown(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; - - consoleUpdate(NULL); - } - - consoleExit(NULL); -} - -int uiInit() -{ - Result rc = 0; + Result result = 0; FT_Error ret = 0; u32 i; - int status = 0; - bool pl_init = false, romfs_init = false, ft_lib_init = false, ft_faces_init[PlSharedFontType_Total]; - - memset(ft_faces_init, 0, PlSharedFontType_Total); + bool success = false; + char tmp[256] = {'\0'}; /* Set initial UI state */ uiState = stateMainMenu; @@ -770,29 +713,19 @@ int uiInit() cursor = 0; scroll = 0; - /* Check if we're running under SX OS */ - runningSxOs = checkSxOsServices(); - - /* Initialize pl service */ - rc = plInitialize(); - if (R_FAILED(rc)) - { - error_screen("plInitialize() failed (0x%08X).\n", rc); - goto out; - } - - pl_init = true; + /* Clear FreeType init flags */ + memset(ft_faces_init, 0, PlSharedFontType_Total); /* Retrieve shared fonts */ for(i = 0; i < PlSharedFontType_Total; i++) { - rc = plGetSharedFontByType(&sharedFonts[i], i); - if (R_FAILED(rc)) break; + result = plGetSharedFontByType(&sharedFonts[i], i); + if (R_FAILED(result)) break; } - if (R_FAILED(rc)) + if (R_FAILED(result)) { - error_screen("plGetSharedFontByType() failed to retrieve shared font #%u (0x%08X).\n", i, rc); + consoleErrorScreen("%s: plGetSharedFontByType() failed to retrieve shared font #%u! (0x%08X)", __func__, i, result); goto out; } @@ -800,7 +733,7 @@ int uiInit() ret = FT_Init_FreeType(&library); if (ret) { - error_screen("FT_Init_FreeType() failed (%d).\n", ret); + consoleErrorScreen("%s: FT_Init_FreeType() failed! (%d)", __func__, ret); goto out; } @@ -816,7 +749,7 @@ int uiInit() if (ret) { - error_screen("FT_New_Memory_Face() failed to create memory face for shared font #%u (%d).\n", i, ret); + consoleErrorScreen("%s: FT_New_Memory_Face() failed to create memory face for shared font #%u! (%d)", __func__, i, ret); goto out; } @@ -829,44 +762,18 @@ int uiInit() if (ret) { - error_screen("FT_Set_Char_Size() failed to set character size for shared font #%u (%d).\n", i, ret); + consoleErrorScreen("%s: FT_Set_Char_Size() failed to set character size for shared font #%u! (%d)", __func__, i, ret); goto out; } /* Store font height */ font_height = (sharedFontsFaces[0]->size->metrics.height / 64); - /* Prepare additional data needed by the UI functions */ - - /* Allocate memory for the filename buffer */ - filenameBuffer = calloc(FILENAME_BUFFER_SIZE, sizeof(char)); - if (!filenameBuffer) - { - error_screen("Failed to allocate memory for the filename buffer.\n"); - goto out; - } - - /* Allocate memory for the dump buffer */ - dumpBuf = calloc(DUMP_BUFFER_SIZE, sizeof(u8)); - if (!dumpBuf) - { - error_screen("Failed to allocate memory for the dump buffer.\n"); - goto out; - } - - /* Allocate memory for the NCA AES-CTR operation buffer */ - ncaCtrBuf = calloc(NCA_CTR_BUFFER_SIZE, sizeof(u8)); - if (!ncaCtrBuf) - { - error_screen("Failed to allocate memory for the NCA AES-CTR operation buffer.\n"); - goto out; - } - /* Mount Application's RomFS */ - rc = romfsInit(); - if (R_FAILED(rc)) + result = romfsInit(); + if (R_FAILED(result)) { - error_screen("romfsInit() failed (0x%08X).\n", rc); + consoleErrorScreen("%s: romfsInit() failed! (0x%08X)", __func__, result); goto out; } @@ -874,57 +781,65 @@ int uiInit() if (!uiLoadJpgFromFile(dirNormalIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &dirNormalIconBuf)) { - strcat(strbuf, "Failed to load directory icon (normal).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load directory icon (normal)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(dirHighlightIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &dirHighlightIconBuf)) { - strcat(strbuf, "Failed to load directory icon (highlighted).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load directory icon (highlighted)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(fileNormalIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &fileNormalIconBuf)) { - strcat(strbuf, "Failed to load file icon (normal).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load file icon (normal)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(fileHighlightIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &fileHighlightIconBuf)) { - strcat(strbuf, "Failed to load file icon (highlighted).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load file icon (highlighted)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(enabledNormalIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &enabledNormalIconBuf)) { - strcat(strbuf, "Failed to load enabled icon (normal).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load enabled icon (normal)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(enabledHighlightIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &enabledHighlightIconBuf)) { - strcat(strbuf, "Failed to load enabled icon (highlighted).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load enabled icon (highlighted)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(disabledNormalIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &disabledNormalIconBuf)) { - strcat(strbuf, "Failed to load disabled icon (normal).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load disabled icon (normal)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } if (!uiLoadJpgFromFile(disabledHighlightIconPath, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, &disabledHighlightIconBuf)) { - strcat(strbuf, "Failed to load disabled icon (highlighted).\n"); - error_screen(strbuf); + snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to load disabled icon (highlighted)!", __func__); + strcat(strbuf, tmp); + consoleErrorScreen(strbuf); goto out; } @@ -935,98 +850,45 @@ int uiInit() /* Create framebuffer */ framebufferCreate(&fb, nwindowGetDefault(), FB_WIDTH, FB_HEIGHT, PIXEL_FORMAT_RGBA_8888, 2); framebufferMakeLinear(&fb); - - /* Disable screen dimming and auto sleep */ - appletSetMediaPlaybackState(true); - - /* 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); + fb_init = true; /* Clear screen */ uiClearScreen(); - /* Update free space */ - uiUpdateFreeSpace(); - /* Set output status */ - status = 1; + success = true; out: - if (!status) - { - if (disabledHighlightIconBuf) free(disabledHighlightIconBuf); - if (disabledNormalIconBuf) free(disabledNormalIconBuf); - if (enabledHighlightIconBuf) free(enabledHighlightIconBuf); - if (enabledNormalIconBuf) free(enabledNormalIconBuf); - if (fileHighlightIconBuf) free(fileHighlightIconBuf); - if (fileNormalIconBuf) free(fileNormalIconBuf); - if (dirHighlightIconBuf) free(dirHighlightIconBuf); - if (dirNormalIconBuf) free(dirNormalIconBuf); - - if (romfs_init) romfsExit(); - - if (ncaCtrBuf) free(ncaCtrBuf); - - if (dumpBuf) free(dumpBuf); - - if (filenameBuffer) free(filenameBuffer); - - for(i = 0; i < PlSharedFontType_Total; i++) - { - if (ft_faces_init[i]) FT_Done_Face(sharedFontsFaces[i]); - } - - if (ft_lib_init) FT_Done_FreeType(library); - - if (pl_init) plExit(); - } - - return status; + return success; } void uiDeinit() { - u32 i; - - /* 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(); - - /* Enable screen dimming and auto sleep */ - appletSetMediaPlaybackState(false); - /* Free framebuffer object */ - framebufferClose(&fb); + if (fb_init) framebufferClose(&fb); /* Free enabled/disabled icons (batch mode summary list) */ - free(disabledHighlightIconBuf); - free(disabledNormalIconBuf); - free(enabledHighlightIconBuf); - free(enabledNormalIconBuf); + if (disabledHighlightIconBuf) free(disabledHighlightIconBuf); + if (disabledNormalIconBuf) free(disabledNormalIconBuf); + if (enabledHighlightIconBuf) free(enabledHighlightIconBuf); + if (enabledNormalIconBuf) free(enabledNormalIconBuf); /* Free directory/file icons */ - free(fileHighlightIconBuf); - free(fileNormalIconBuf); - free(dirHighlightIconBuf); - free(dirNormalIconBuf); + if (fileHighlightIconBuf) free(fileHighlightIconBuf); + if (fileNormalIconBuf) free(fileNormalIconBuf); + if (dirHighlightIconBuf) free(dirHighlightIconBuf); + if (dirNormalIconBuf) free(dirNormalIconBuf); - /* Free NCA AES-CTR operation buffer */ - free(ncaCtrBuf); - - /* Free dump buffer */ - free(dumpBuf); - - /* Free filename buffer */ - free(filenameBuffer); + /* Unmount Application's RomFS */ + if (romfs_init) romfsExit(); /* Free FreeType resources */ - for(i = 0; i < PlSharedFontType_Total; i++) FT_Done_Face(sharedFontsFaces[i]); - FT_Done_FreeType(library); + for(u32 i = 0; i < PlSharedFontType_Total; i++) + { + if (ft_faces_init[i]) FT_Done_Face(sharedFontsFaces[i]); + } - /* Deinitialize pl service */ - plExit(); + if (ft_lib_init) FT_Done_FreeType(library); } void uiSetState(UIState state) @@ -1056,6 +918,19 @@ void uiSetState(UIState state) orphanListCursor = 0; orphanListScroll = 0; } + } else + if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser || uiState == stateRomFsSectionBrowser) + { + if ((uiState == stateHfs0Browser && state != stateHfs0BrowserMenu) || (uiState == stateExeFsSectionBrowser && state != stateExeFsSectionBrowserMenu && state != stateExeFsMenu) || (uiState == stateRomFsSectionBrowser && state != stateRomFsSectionBrowserMenu && state != stateRomFsMenu && state != stateRomFsSectionBrowserChangeDir)) + { + // Store current cursor/scroll values + browserCursor = cursor; + browserScroll = scroll; + } else { + // Reset browser cursor/scroll values + browserCursor = 0; + browserScroll = 0; + } } uiState = state; @@ -1071,6 +946,12 @@ void uiSetState(UIState state) // Override cursor/scroll values cursor = orphanListCursor; scroll = orphanListScroll; + } else + if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser || uiState == stateRomFsSectionBrowser) + { + // Override cursor/scroll values + cursor = browserCursor; + scroll = browserScroll; } else { cursor = 0; scroll = 0; @@ -1094,15 +975,13 @@ UIResult uiProcess() const char **menu = NULL; int menuItemsCount = 0; - u32 keysDown = 0, keysHeld = 0; + u64 keysDown = 0, keysHeld = 0; int scrollAmount = 0; bool scrollWithKeysDown = false; u32 patch, addon, xpos, ypos, startYPos; - char versionStr[128] = {'\0'}; - int maxElements = (uiState == stateSdCardEmmcMenu ? SDCARD_MAX_ELEMENTS : (uiState == stateSdCardEmmcOrphanPatchAddOnMenu ? ORPHAN_MAX_ELEMENTS : (uiState == stateHfs0Browser ? HFS0_MAX_ELEMENTS : ((uiState == stateExeFsSectionBrowser || uiState == stateRomFsSectionBrowser) ? ROMFS_MAX_ELEMENTS : (uiState == stateSdCardEmmcBatchModeMenu ? BATCH_MAX_ELEMENTS : COMMON_MAX_ELEMENTS))))); const char *upwardsArrow = UPWARDS_ARROW; @@ -1125,7 +1004,7 @@ UIResult uiProcess() { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, appControlsRomFs); } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, (!gameCardInserted ? appControlsNoContent : (titleAppCount > 1 ? appControlsGameCardMultiApp : appControlsCommon))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, (!gameCardInfo.isInserted ? appControlsNoContent : (titleAppCount > 1 ? appControlsGameCardMultiApp : appControlsCommon))); } break; case MENUTYPE_SDCARD_EMMC: @@ -1137,7 +1016,7 @@ UIResult uiProcess() { if (titleAppCount) { - if (uiState == stateSdCardEmmcMenu && ((titlePatchCount && checkOrphanPatchOrAddOn(false)) || (titleAddOnCount && checkOrphanPatchOrAddOn(true)))) + if (uiState == stateSdCardEmmcMenu && (calculateOrphanPatchOrAddOnCount(false) || calculateOrphanPatchOrAddOnCount(true))) { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, appControlsSdCardEmmcFull); breaks += 2; @@ -1173,39 +1052,43 @@ UIResult uiProcess() } breaks += 2; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, freeSpaceStr); + } + + if (uiState != stateSdCardEmmcBatchDump) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Free SD card space: %s (%lu bytes).", freeSpaceStr, freeSpace); breaks += 2; } if (menuType == MENUTYPE_GAMECARD) { - if (!gameCardInserted || hfs0_header == NULL || (hfs0_partition_cnt != GAMECARD_TYPE1_PARTITION_CNT && hfs0_partition_cnt != GAMECARD_TYPE2_PARTITION_CNT) || !titleAppCount || titleAppTitleID == NULL) + if (!gameCardInfo.isInserted || !gameCardInfo.rootHfs0Header || (gameCardInfo.hfs0PartitionCnt != GAMECARD_TYPE1_PARTITION_CNT && gameCardInfo.hfs0PartitionCnt != GAMECARD_TYPE2_PARTITION_CNT) || !titleAppCount || !baseAppEntries) { - if (gameCardInserted) + if (gameCardInfo.isInserted) { - if (hfs0_header != NULL) + if (gameCardInfo.rootHfs0Header) { - if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) + if (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT || gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE2_PARTITION_CNT) { forcedXciDump = true; - if (titleAppCount > 0) + if (titleAppCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve the gamecard Title ID!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve title entries from the inserted gamecard!"); - if (strlen(gameCardUpdateVersionStr)) + if (strlen(gameCardInfo.updateVersionStr)) { breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Bundled FW Update: %s", gameCardUpdateVersionStr); + 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 cartridge, make sure your console is at least on this FW version."); + 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!"); } } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unknown root HFS0 header partition count! (%u)", hfs0_partition_cnt); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unknown root HFS0 header partition count! (%u)", gameCardInfo.hfs0PartitionCnt); } } else { uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to get root HFS0 header data!"); @@ -1216,11 +1099,14 @@ UIResult uiProcess() breaks += 2; - if (forcedXciDump) + if (gameCardInfo.isInserted) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Press " NINTENDO_FONT_Y " to dump the cartridge image to \"gamecard.xci\"."); - } else { - 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."); + if (forcedXciDump) + { + 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."); + } } uiUpdateStatusMsg(); @@ -1253,18 +1139,16 @@ UIResult uiProcess() // Set default options xciOptions xciDumpCfg; + memset(&xciDumpCfg, 0, sizeof(xciOptions)); xciDumpCfg.isFat32 = true; - xciDumpCfg.setXciArchiveBit = false; xciDumpCfg.keepCert = true; - xciDumpCfg.trimDump = false; - xciDumpCfg.calcCrc = false; - dumpCartridgeImage(&xciDumpCfg); + dumpNXCardImage(&xciDumpCfg); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); } return res; @@ -1317,59 +1201,46 @@ UIResult uiProcess() { if ((menuType == MENUTYPE_GAMECARD && uiState != stateHfs0Browser && uiState != stateExeFsSectionBrowser && uiState != stateRomFsSectionBrowser) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && uiState != stateSdCardEmmcMenu && uiState != stateSdCardEmmcBatchModeMenu && uiState != stateExeFsSectionBrowser && uiState != stateRomFsSectionBrowser)) { - if (menuType == MENUTYPE_GAMECARD) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Gamecard is inserted!"); - breaks += 2; - - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Root HFS0 header offset: 0x%016lX", hfs0_offset); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Root HFS0 header size: 0x%016lX", hfs0_size); - breaks++;*/ - } - /* Print application info */ xpos = STRING_X_POS; ypos = STRING_Y_POS(breaks); startYPos = ypos; /* Draw icon */ - if (titleIcon != NULL && titleIcon[selectedAppInfoIndex] != NULL) + if (baseAppEntries[selectedAppInfoIndex].icon != NULL) { - uiDrawIcon(titleIcon[selectedAppInfoIndex], NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); + uiDrawIcon(baseAppEntries[selectedAppInfoIndex].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); xpos += (NACP_ICON_DOWNSCALED + 8); ypos += 8; } - if (titleName != NULL && titleName[selectedAppInfoIndex] != NULL && strlen(titleName[selectedAppInfoIndex])) + if (strlen(baseAppEntries[selectedAppInfoIndex].name)) { - uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Name: %s", titleName[selectedAppInfoIndex]); + uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Name: %s", baseAppEntries[selectedAppInfoIndex].name); ypos += LINE_HEIGHT; } - if (titleAuthor != NULL && titleAuthor[selectedAppInfoIndex] != NULL && strlen(titleAuthor[selectedAppInfoIndex])) + if (strlen(baseAppEntries[selectedAppInfoIndex].author)) { - uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Publisher: %s", titleAuthor[selectedAppInfoIndex]); + uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Publisher: %s", baseAppEntries[selectedAppInfoIndex].author); ypos += LINE_HEIGHT; } - uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Title ID: %016lX", titleAppTitleID[selectedAppInfoIndex]); + uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Title ID: %016lX", baseAppEntries[selectedAppInfoIndex].titleId); if (titlePatchCount > 0) { u32 patchCnt = 0; - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s update(s): v", (menuType == MENUTYPE_GAMECARD ? "Bundled" : "Installed")); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s update(s): v", (menuType == MENUTYPE_GAMECARD ? "Bundled" : "Installed")); for(patch = 0; patch < titlePatchCount; patch++) { - if ((titleAppTitleID[selectedAppInfoIndex] | APPLICATION_PATCH_BITMASK) == titlePatchTitleID[patch] && ((menuType == MENUTYPE_GAMECARD && titleAppStorageId[selectedAppInfoIndex] == titlePatchStorageId[patch]) || menuType == MENUTYPE_SDCARD_EMMC)) + if (checkIfPatchOrAddOnBelongsToBaseApplication(patch, selectedAppInfoIndex, false) && ((menuType == MENUTYPE_GAMECARD && baseAppEntries[selectedAppInfoIndex].storageId == patchEntries[patch].storageId) || menuType == MENUTYPE_SDCARD_EMMC)) { if (patchCnt > 0) strcat(strbuf, ", v"); - convertTitleVersionToDecimal(titlePatchVersion[patch], versionStr, MAX_ELEMENTS(versionStr)); - strcat(strbuf, versionStr); + strcat(strbuf, patchEntries[patch].versionStr); patchCnt++; } @@ -1380,11 +1251,7 @@ UIResult uiProcess() ypos += LINE_HEIGHT; - if (titleAppVersionStr != NULL && titleAppVersionStr[selectedAppInfoIndex] != NULL && strlen(titleAppVersionStr[selectedAppInfoIndex])) - { - uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Version: %s", titleAppVersionStr[selectedAppInfoIndex]); - if (!titleAddOnCount) ypos += LINE_HEIGHT; - } + uiDrawString(xpos, ypos, FONT_COLOR_SUCCESS_RGB, "Version: %s", baseAppEntries[selectedAppInfoIndex].versionStr); if (titleAddOnCount > 0) { @@ -1392,16 +1259,14 @@ UIResult uiProcess() for(addon = 0; addon < titleAddOnCount; addon++) { - if ((titleAppTitleID[selectedAppInfoIndex] & APPLICATION_ADDON_BITMASK) == (titleAddOnTitleID[addon] & APPLICATION_ADDON_BITMASK)) addOnCnt++; + if (checkIfPatchOrAddOnBelongsToBaseApplication(addon, selectedAppInfoIndex, true) && ((menuType == MENUTYPE_GAMECARD && baseAppEntries[selectedAppInfoIndex].storageId == addOnEntries[addon].storageId) || menuType == MENUTYPE_SDCARD_EMMC)) addOnCnt++; } - if (addOnCnt > 0) - { - uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), ypos, FONT_COLOR_SUCCESS_RGB, "%s DLC(s): %u", (menuType == MENUTYPE_GAMECARD ? "Bundled" : "Installed"), addOnCnt); - ypos += LINE_HEIGHT; - } + if (addOnCnt > 0) uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), ypos, FONT_COLOR_SUCCESS_RGB, "%s DLC(s): %u", (menuType == MENUTYPE_GAMECARD ? "Bundled" : "Installed"), addOnCnt); } + ypos += LINE_HEIGHT; + ypos += 8; if (xpos > 8 && (ypos - NACP_ICON_DOWNSCALED) < startYPos) ypos += (NACP_ICON_DOWNSCALED - (ypos - startYPos)); ypos += LINE_HEIGHT; @@ -1410,15 +1275,25 @@ UIResult uiProcess() if (menuType == MENUTYPE_GAMECARD) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Size: %s | Used space: %s", gameCardSizeStr, trimmedCardSizeStr); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Capacity: %s | Used space: %s", gameCardInfo.sizeStr, gameCardInfo.trimmedSizeStr); - if (titleAppCount > 1) uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Base application count: %u | Base application currently displayed: %u", titleAppCount, selectedAppInfoIndex + 1); + if (titleAppCount > 1) + { + if (baseAppEntries[selectedAppInfoIndex].contentSize) + { + uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Base application count: %u | Base application displayed: %u | Content size: %s", titleAppCount, selectedAppInfoIndex + 1, baseAppEntries[selectedAppInfoIndex].contentSizeStr); + } else { + uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Base application count: %u | Base application displayed: %u", titleAppCount, selectedAppInfoIndex + 1); + } + } else { + if (baseAppEntries[selectedAppInfoIndex].contentSize) uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Content size: %s", baseAppEntries[selectedAppInfoIndex].contentSizeStr); + } breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt)); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Partition count: %u (%s)", gameCardInfo.hfs0PartitionCnt, GAMECARD_TYPE(gameCardInfo.hfs0PartitionCnt)); - if (strlen(gameCardUpdateVersionStr)) uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Bundled FW update: %s", gameCardUpdateVersionStr); + if (strlen(gameCardInfo.updateVersionStr)) uiDrawString((FB_WIDTH / 2) - (FB_WIDTH / 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Bundled FW update: %s", gameCardInfo.updateVersionStr); breaks++; @@ -1428,6 +1303,14 @@ UIResult uiProcess() if (titleAddOnCount > 0) uiDrawString((titlePatchCount > 0 ? ((FB_WIDTH / 2) - (FB_WIDTH / 8)) : 8), STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Total bundled DLC(s): %u", titleAddOnCount); + breaks++; + } + } else + if (menuType == MENUTYPE_SDCARD_EMMC) + { + if (baseAppEntries[selectedAppInfoIndex].contentSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Content size: %s", baseAppEntries[selectedAppInfoIndex].contentSizeStr); breaks++; } } @@ -1442,36 +1325,44 @@ UIResult uiProcess() u32 patchCnt = 0, addOnCnt = 0; u32 patchCntConsoleData = 0, addOnCntConsoleData = 0; - snprintf(dumpedContentInfoStr, MAX_ELEMENTS(dumpedContentInfoStr), "Content already dumped: "); + snprintf(dumpedContentInfoStr, MAX_CHARACTERS(dumpedContentInfoStr), "Content already dumped: "); if (menuType == MENUTYPE_GAMECARD) { - char *xciName = generateFullDumpName(); - if (xciName) + for(i = 0; i < 2; i++) { - // First check if an unsplitted XCI dump is available - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, xciName); + dumpName = generateGameCardDumpName(i == 1); + if (!dumpName) continue; + + // First check if a full XCI dump is available + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xci", XCI_DUMP_PATH, dumpName); if (!(dumpedXci = checkIfFileExists(dumpPath))) { - // Check if a splitted XCI dump is available - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.xc0", XCI_DUMP_PATH, xciName); + // Check if a split XCI dump is available + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.xc0", XCI_DUMP_PATH, dumpName); dumpedXci = checkIfFileExists(dumpPath); } - free(xciName); - xciName = NULL; + free(dumpName); + dumpName = NULL; - if (dumpedXci) dumpedXciCertificate = checkIfDumpedXciContainsCertificate(dumpPath); + if (dumpedXci) + { + dumpedXciCertificate = checkIfDumpedXciContainsCertificate(dumpPath); + break; + } } } // Now search for dumped NSPs // Look for a dumped base application - dumpName = generateNSPDumpName(DUMP_APP_NSP, selectedAppInfoIndex); - if (dumpName) + for(i = 0; i < 2; i++) { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + dumpName = generateNSPDumpName(DUMP_APP_NSP, selectedAppInfoIndex, (i == 1)); + if (!dumpName) continue; + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); free(dumpName); dumpName = NULL; @@ -1480,55 +1371,54 @@ UIResult uiProcess() { dumpedBase = true; dumpedBaseConsoleData = checkIfDumpedNspContainsConsoleData(dumpPath); + break; } } // Look for dumped updates - if (titlePatchCount > 0) + for(patch = 0; patch < titlePatchCount; patch++) { - for(patch = 0; patch < titlePatchCount; patch++) + if (!checkIfPatchOrAddOnBelongsToBaseApplication(patch, selectedAppInfoIndex, false)) continue; + + for(i = 0; i < 2; i++) { - if ((titleAppTitleID[selectedAppInfoIndex] | APPLICATION_PATCH_BITMASK) == titlePatchTitleID[patch]) + dumpName = generateNSPDumpName(DUMP_PATCH_NSP, patch, (i == 1)); + if (!dumpName) continue; + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + + free(dumpName); + dumpName = NULL; + + if (checkIfFileExists(dumpPath)) { - dumpName = generateNSPDumpName(DUMP_PATCH_NSP, patch); - if (dumpName) - { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - - free(dumpName); - dumpName = NULL; - - if (checkIfFileExists(dumpPath)) - { - patchCnt++; - if (checkIfDumpedNspContainsConsoleData(dumpPath)) patchCntConsoleData++; - } - } + patchCnt++; + if (checkIfDumpedNspContainsConsoleData(dumpPath)) patchCntConsoleData++; + break; } } } // Look for dumped DLCs - if (titleAddOnCount > 0) + for(addon = 0; addon < titleAddOnCount; addon++) { - for(addon = 0; addon < titleAddOnCount; addon++) + if (!checkIfPatchOrAddOnBelongsToBaseApplication(addon, selectedAppInfoIndex, true)) continue; + + for(i = 0; i < 2; i++) { - if ((titleAppTitleID[selectedAppInfoIndex] & APPLICATION_ADDON_BITMASK) == (titleAddOnTitleID[addon] & APPLICATION_ADDON_BITMASK)) + dumpName = generateNSPDumpName(DUMP_ADDON_NSP, addon, (i == 1)); + if (!dumpName) continue; + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + + free(dumpName); + dumpName = NULL; + + if (checkIfFileExists(dumpPath)) { - dumpName = generateNSPDumpName(DUMP_ADDON_NSP, addon); - if (dumpName) - { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); - - free(dumpName); - dumpName = NULL; - - if (checkIfFileExists(dumpPath)) - { - addOnCnt++; - if (checkIfDumpedNspContainsConsoleData(dumpPath)) addOnCntConsoleData++; - } - } + addOnCnt++; + if (checkIfDumpedNspContainsConsoleData(dumpPath)) addOnCntConsoleData++; + break; } } } @@ -1539,85 +1429,71 @@ UIResult uiProcess() } else { if (dumpedXci) { - strcat(dumpedContentInfoStr, "XCI"); - - if (dumpedXciCertificate) - { - strcat(dumpedContentInfoStr, " (with cert)"); - } else { - strcat(dumpedContentInfoStr, " (without cert)"); - } + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "XCI (%s cert)", (dumpedXciCertificate ? "with" : "without")); + strcat(dumpedContentInfoStr, tmpStr); } if (dumpedBase) { if (dumpedXci) strcat(dumpedContentInfoStr, ", "); - - strcat(dumpedContentInfoStr, "BASE"); - - if (dumpedBaseConsoleData) - { - strcat(dumpedContentInfoStr, " (with console data)"); - } else { - strcat(dumpedContentInfoStr, " (without console data)"); - } + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "BASE (%s console data)", (dumpedBaseConsoleData ? "with" : "without")); + strcat(dumpedContentInfoStr, tmpStr); } if (patchCnt) { + if (dumpedXci || dumpedBase) strcat(dumpedContentInfoStr, ", "); + if (patchCntConsoleData) { if (patchCntConsoleData == patchCnt) { if (patchCnt > 1) { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u UPD (all with console data)", patchCnt); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u UPD (all with console data)", patchCnt); } else { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "UPD (with console data)"); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "UPD (with console data)"); } } else { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u UPD (%u with console data)", patchCnt, patchCntConsoleData); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u UPD (%u with console data)", patchCnt, patchCntConsoleData); } } else { if (patchCnt > 1) { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u UPD (all without console data)", patchCnt); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u UPD (all without console data)", patchCnt); } else { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "UPD (without console data)"); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "UPD (without console data)"); } } - if (dumpedBase) strcat(dumpedContentInfoStr, ", "); - strcat(dumpedContentInfoStr, tmpStr); } if (addOnCnt) { + if (dumpedXci || dumpedBase || patchCnt) strcat(dumpedContentInfoStr, ", "); + if (addOnCntConsoleData) { if (addOnCntConsoleData == addOnCnt) { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u DLC (%s console data)", addOnCnt, (addOnCnt > 1 ? "all with" : "with")); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u DLC (%s console data)", addOnCnt, (addOnCnt > 1 ? "all with" : "with")); } else { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u DLC (%u with console data)", addOnCnt, addOnCntConsoleData); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u DLC (%u with console data)", addOnCnt, addOnCntConsoleData); } } else { - snprintf(tmpStr, MAX_ELEMENTS(tmpStr), "%u DLC (%s console data)", addOnCnt, (addOnCnt > 1 ? "all without" : "without")); + snprintf(tmpStr, MAX_CHARACTERS(tmpStr), "%u DLC (%s console data)", addOnCnt, (addOnCnt > 1 ? "all without" : "without")); } - if (dumpedBase || patchCnt) strcat(dumpedContentInfoStr, ", "); - strcat(dumpedContentInfoStr, tmpStr); } } } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, dumpedContentInfoStr); - breaks += 2; } else - if (menuType == MENUTYPE_SDCARD_EMMC && orphanMode && (uiState == stateSdCardEmmcTitleMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu || uiState == stateRomFsMenu || uiState == stateTicketMenu)) + if (menuType == MENUTYPE_SDCARD_EMMC && orphanMode && (uiState == stateSdCardEmmcTitleMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu || uiState == stateExeFsMenu || uiState == stateRomFsMenu || uiState == stateTicketMenu)) { if (strlen(orphanEntries[orphanListCursor].name)) { @@ -1625,38 +1501,43 @@ UIResult uiProcess() breaks++; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Title ID: %016lX", (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? titlePatchTitleID[selectedPatchIndex] : titleAddOnTitleID[selectedAddOnIndex])); + patch_addon_ctx_t *ptr = (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? &(patchEntries[selectedPatchIndex]) : &(addOnEntries[selectedAddOnIndex])); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Title ID: %016lX", ptr->titleId); breaks++; - convertTitleVersionToDecimal((orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? titlePatchVersion[selectedPatchIndex] : titleAddOnVersion[selectedAddOnIndex]), versionStr, MAX_ELEMENTS(versionStr)); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Version: %s", versionStr); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Version: %s", ptr->versionStr); breaks++; + if (ptr->contentSize) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Content size: %s", ptr->contentSizeStr); + breaks++; + } + if (!strlen(dumpedContentInfoStr)) { // Look for dumped content in the SD card char *dumpName = NULL; char dumpPath[NAME_BUF_LEN] = {'\0'}; + bool dumpedOrphan = false; + nspDumpType orphanDumpType = (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? DUMP_PATCH_NSP : DUMP_ADDON_NSP); + u32 orphanIndex = (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? selectedPatchIndex : selectedAddOnIndex); - snprintf(dumpedContentInfoStr, MAX_ELEMENTS(dumpedContentInfoStr), "Title already dumped: "); + snprintf(dumpedContentInfoStr, MAX_CHARACTERS(dumpedContentInfoStr), "Title already dumped: "); - if (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH) + for(i = 0; i < 2; i++) { - dumpName = generateNSPDumpName(DUMP_PATCH_NSP, selectedPatchIndex); - } else - if (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON) - { - dumpName = generateNSPDumpName(DUMP_ADDON_NSP, selectedAddOnIndex); - } - - if (dumpName) - { - snprintf(dumpPath, MAX_ELEMENTS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); + dumpName = generateNSPDumpName(orphanDumpType, orphanIndex, (i == 1)); + if (!dumpName) continue; + + snprintf(dumpPath, MAX_CHARACTERS(dumpPath), "%s%s.nsp", NSP_DUMP_PATH, dumpName); free(dumpName); dumpName = NULL; - if (checkIfFileExists(dumpPath)) + dumpedOrphan = checkIfFileExists(dumpPath); + if (dumpedOrphan) { strcat(dumpedContentInfoStr, "Yes"); @@ -1666,12 +1547,12 @@ UIResult uiProcess() } else { strcat(dumpedContentInfoStr, " (without console data)"); } - } else { - strcat(dumpedContentInfoStr, "No"); + + break; } - } else { - strcat(dumpedContentInfoStr, "No"); } + + if (!dumpedOrphan) strcat(dumpedContentInfoStr, "No"); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, dumpedContentInfoStr); @@ -1748,15 +1629,15 @@ UIResult uiProcess() break; case stateRawHfs0PartitionDumpMenu: case stateHfs0PartitionDataDumpMenu: - menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems : hfs0PartitionDumpType2MenuItems); - menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? MAX_ELEMENTS(hfs0PartitionDumpType1MenuItems) : MAX_ELEMENTS(hfs0PartitionDumpType2MenuItems)); + menu = (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems : hfs0PartitionDumpType2MenuItems); + menuItemsCount = (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? MAX_ELEMENTS(hfs0PartitionDumpType1MenuItems) : MAX_ELEMENTS(hfs0PartitionDumpType2MenuItems)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (uiState == stateRawHfs0PartitionDumpMenu ? hfs0MenuItems[0] : hfs0MenuItems[1])); break; case stateHfs0BrowserMenu: - menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems : hfs0BrowserType2MenuItems); - menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? MAX_ELEMENTS(hfs0BrowserType1MenuItems) : MAX_ELEMENTS(hfs0BrowserType2MenuItems)); + menu = (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems : hfs0BrowserType2MenuItems); + menuItemsCount = (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? MAX_ELEMENTS(hfs0BrowserType1MenuItems) : MAX_ELEMENTS(hfs0BrowserType2MenuItems)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, hfs0MenuItems[2]); @@ -1765,7 +1646,7 @@ UIResult uiProcess() menu = (const char**)filenames; menuItemsCount = filenamesCount; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems[selectedPartitionIndex] : hfs0BrowserType2MenuItems[selectedPartitionIndex])); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems[selectedPartitionIndex] : hfs0BrowserType2MenuItems[selectedPartitionIndex])); breaks += 2; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Entry count: %d | Current entry: %d", menuItemsCount, cursor + 1); @@ -1799,12 +1680,14 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, exeFsMenuItems[1]); 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")); + breaks++; + if (!exeFsUpdateFlag) { - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", exeFsSectionBrowserMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", exeFsSectionBrowserMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); } else { - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to browse: ", strbuf, MAX_CHARACTERS(strbuf)); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); @@ -1848,17 +1731,19 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, romFsMenuItems[1]); 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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to browse: ", strbuf, MAX_CHARACTERS(strbuf)); break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC to browse: ", strbuf, MAX_CHARACTERS(strbuf)); break; default: break; @@ -1872,17 +1757,19 @@ UIResult uiProcess() if (strlen(curRomFsPath) <= 1 || (strlen(curRomFsPath) > 1 && cursor > 0)) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Entry count: %d | Current entry: %d", menuItemsCount - 1, (strlen(curRomFsPath) <= 1 ? (cursor + 1) : cursor)); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d | Current entry: %d", filenamesCount - 1, (strlen(curRomFsPath) <= 1 ? (cursor + 1) : cursor)); } else { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Entry count: %d", menuItemsCount - 1); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Entry count: %d", filenamesCount - 1); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, strbuf); break; case stateSdCardEmmcMenu: - menu = (const char**)titleName; - menuItemsCount = (int)titleAppCount; + generateSdCardEmmcTitleList(); + + menu = (const char**)filenames; + menuItemsCount = filenamesCount; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, mainMenuItems[1]); @@ -1899,11 +1786,14 @@ UIResult uiProcess() menu = sdCardEmmcMenuItems; menuItemsCount = MAX_ELEMENTS(sdCardEmmcMenuItems); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (!orphanMode ? mainMenuItems[1] : "Dump orphan DLC content")); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (!orphanMode ? mainMenuItems[1] : (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH ? "Dump orphan update content" : "Dump orphan DLC content"))); break; case stateSdCardEmmcOrphanPatchAddOnMenu: - if (orphanEntries == NULL) generateOrphanPatchOrAddOnList(); + // Generate orphan content list + // If orphanEntries == NULL or if orphanEntriesCnt == 0, both variables will be regenerated + // Otherwise, this will only fill filenameBuffer + generateOrphanPatchOrAddOnList(); menu = (const char**)filenames; menuItemsCount = filenamesCount; @@ -1969,6 +1859,13 @@ UIResult uiProcess() continue; } + // Avoid printing the "Dump verification method" option if "CRC32 checksum calculation + dump verification" is disabled + if (uiState == stateXciDumpMenu && i == 6 && !dumpCfg.xciDumpCfg.calcCrc) + { + j--; + continue; + } + // Avoid printing the "Dump bundled update NSP" / "Dump installed update NSP" option in the NSP dump menu if we're dealing with a gamecard and it doesn't include any bundled updates, or if we're dealing with a SD/eMMC title without installed updates // Also avoid printing the "Dump bundled DLC NSP" / "Dump installed DLC NSP" option in the NSP dump menu if we're dealing with a gamecard and it doesn't include any bundled DLCs, or if we're dealing with a SD/eMMC title without installed DLCs if (uiState == stateNspDumpMenu && ((i == 1 && (!titlePatchCount || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)))) || (i == 2 && (!titleAddOnCount || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) @@ -1977,9 +1874,17 @@ UIResult uiProcess() continue; } - // Avoid printing the "Remove console specific data" option in the NSP dump menus if we're dealing with a gamecard title + // Avoid printing the "Verify dump using No-Intro database" option in the NSP dump menus if we're dealing with a gamecard title + // Also, avoid printing the "Remove console specific data" option in the NSP dump menus if we're dealing with a gamecard title // Also, avoid printing the "Generate ticket-less dump" option in the NSP dump menus if we're dealing with a gamecard Application/AddOn title - if (menuType == MENUTYPE_GAMECARD && (((uiState == stateNspAppDumpMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu) && i == 3) || ((uiState == stateNspAppDumpMenu || uiState == stateNspAddOnDumpMenu) && i == 4))) + if (menuType == MENUTYPE_GAMECARD && (((uiState == stateNspAppDumpMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu) && (i == 2 || i == 3)) || ((uiState == stateNspAppDumpMenu || uiState == stateNspAddOnDumpMenu) && i == 4))) + { + j--; + continue; + } + + // Avoid printing the "Dump delta fragments" option in the update NSP dump menu if we're dealing with a gamecard update + if (menuType == MENUTYPE_GAMECARD && uiState == stateNspPatchDumpMenu && i == 6) { j--; continue; @@ -1993,7 +1898,7 @@ UIResult uiProcess() } // Avoid printing 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 && i == 1) || (!titlePatchCount && i == 2) || (!titleAddOnCount && i == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && ((!sdCardTitleAppCount && i == 1) || (!sdCardTitlePatchCount && i == 2) || (!sdCardTitleAddOnCount && i == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && ((!nandUserTitleAppCount && i == 1) || (!nandUserTitlePatchCount && i == 2) || (!nandUserTitleAddOnCount && i == 3))))) + if (uiState == stateSdCardEmmcBatchModeMenu && ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && ((!titleAppCount && i == 1) || (!titlePatchCount && i == 2) || (!titleAddOnCount && i == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && ((!sdCardTitleAppCount && i == 1) || (!sdCardTitlePatchCount && i == 2) || (!sdCardTitleAddOnCount && i == 3))) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && ((!emmcTitleAppCount && i == 1) || (!emmcTitlePatchCount && i == 2) || (!emmcTitleAddOnCount && i == 3))))) { j--; continue; @@ -2006,16 +1911,23 @@ UIResult uiProcess() continue; } + // Avoid printing the "Dump delta fragments from updates" option in the batch mode menu if the "Dump updates" option is disabled + if (uiState == stateSdCardEmmcBatchModeMenu && i == 8 && !dumpCfg.batchDumpCfg.dumpPatchTitles) + { + j--; + continue; + } + // Avoid printing the "Source storage" option in the batch mode menu if we only have titles available in a single source storage device - if (uiState == stateSdCardEmmcBatchModeMenu && i == 10 && ((!sdCardTitleAppCount && !sdCardTitlePatchCount && !sdCardTitleAddOnCount) || (!nandUserTitleAppCount && !nandUserTitlePatchCount && !nandUserTitleAddOnCount))) + if (uiState == stateSdCardEmmcBatchModeMenu && i == 13 && ((!sdCardTitleAppCount && !sdCardTitlePatchCount && !sdCardTitleAddOnCount) || (!emmcTitleAppCount && !emmcTitlePatchCount && !emmcTitleAddOnCount))) { j--; continue; } // Avoid printing the "Use update" option in the ExeFS menu if we're dealing with a gamecard and either its base application count is greater than 1 or it has no available patches - // Also avoid printing it if we're dealing with a SD/eMMC title and it has no available patches - if (uiState == stateExeFsMenu && i == 2 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || !checkIfBaseApplicationHasPatchOrAddOn(0, false))) || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)))) + // Also avoid printing it if we're dealing with a SD/eMMC title and it has no available patches, or if we're dealing with an orphan Patch + if (uiState == stateExeFsMenu && i == 4 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || !checkIfBaseApplicationHasPatchOrAddOn(0, false))) || (menuType == MENUTYPE_SDCARD_EMMC && ((!orphanMode && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)) || orphanMode)))) { j--; continue; @@ -2030,7 +1942,7 @@ UIResult uiProcess() // Avoid printing the "Use update/DLC" option in the RomFS menu if we're dealing with a gamecard and either its base application count is greater than 1 or it has no available patches/DLCs // Also avoid printing it if we're dealing with a SD/eMMC title and it has no available patches/DLCs (or if its an orphan title) - if (uiState == stateRomFsMenu && i == 2 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || (!checkIfBaseApplicationHasPatchOrAddOn(0, false) && !checkIfBaseApplicationHasPatchOrAddOn(0, true)))) || (menuType == MENUTYPE_SDCARD_EMMC && (orphanMode || (!checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) + if (uiState == stateRomFsMenu && i == 4 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || (!checkIfBaseApplicationHasPatchOrAddOn(0, false) && !checkIfBaseApplicationHasPatchOrAddOn(0, true)))) || (menuType == MENUTYPE_SDCARD_EMMC && (orphanMode || (!checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) { j--; continue; @@ -2043,9 +1955,9 @@ UIResult uiProcess() continue; } - // Avoid printing the "ExeFS options" element in the SD card / eMMC title menu if we're dealing with an orphan DLC - // Also avoid printing the "RomFS options" element in the SD card / eMMC title menu if we're dealing with an orphan Patch - if (uiState == stateSdCardEmmcTitleMenu && orphanMode && ((orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON && i == 1) || (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH && (i == 1 || i == 2)))) + // Avoid printing the "RomFS options" element in the SD card / eMMC title menu if we're dealing with an orphan Patch + // Also avoid printing the "ExeFS options" element in the SD card / eMMC title menu if we're dealing with an orphan DLC + if (uiState == stateSdCardEmmcTitleMenu && orphanMode && ((orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH && i == 2) || (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON && i == 1))) { j--; continue; @@ -2069,9 +1981,9 @@ UIResult uiProcess() if (uiState == stateSdCardEmmcMenu) { - if (titleIcon != NULL && titleIcon[i] != NULL) + if (baseAppEntries[i].icon != NULL) { - uiDrawIcon(titleIcon[i], NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); + uiDrawIcon(baseAppEntries[i].icon, NACP_ICON_DOWNSCALED, NACP_ICON_DOWNSCALED, xpos, ypos + 8); xpos += (NACP_ICON_DOWNSCALED + 8); } @@ -2080,7 +1992,15 @@ UIResult uiProcess() } else if (uiState == stateHfs0Browser || uiState == stateExeFsSectionBrowser || uiState == stateRomFsSectionBrowser) { - u8 *icon = (highlight ? (uiState == stateRomFsSectionBrowser ? (romFsBrowserEntries[i].type == ROMFS_ENTRY_DIR ? dirHighlightIconBuf : fileHighlightIconBuf) : fileHighlightIconBuf) : (uiState == stateRomFsSectionBrowser ? (romFsBrowserEntries[i].type == ROMFS_ENTRY_DIR ? dirNormalIconBuf : fileNormalIconBuf) : fileNormalIconBuf)); + u8 *icon = NULL; + + if (uiState == stateRomFsSectionBrowser) + { + u32 idx = (strlen(curRomFsPath) <= 1 ? (i + 1) : i); // Adjust index if we're at the root directory + icon = (highlight ? (romFsBrowserEntries[idx].type == ROMFS_ENTRY_DIR ? dirHighlightIconBuf : fileHighlightIconBuf) : (romFsBrowserEntries[idx].type == ROMFS_ENTRY_DIR ? dirNormalIconBuf : fileNormalIconBuf)); + } else { + icon = (highlight ? fileHighlightIconBuf : fileNormalIconBuf); + } uiDrawIcon(icon, BROWSER_ICON_DIMENSION, BROWSER_ICON_DIMENSION, xpos, ypos + 8); @@ -2090,8 +2010,30 @@ UIResult uiProcess() if (highlight) { uiDrawString(xpos, ypos, HIGHLIGHT_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) + { + 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); + } } 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) + { + 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); + } } xpos = OPTIONS_X_START_POS; @@ -2119,6 +2061,12 @@ UIResult uiProcess() case 5: // CRC32 checksum calculation + dump verification uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.xciDumpCfg.calcCrc, !dumpCfg.xciDumpCfg.calcCrc, (dumpCfg.xciDumpCfg.calcCrc ? 0 : 255), (dumpCfg.xciDumpCfg.calcCrc ? 255 : 0), 0, (dumpCfg.xciDumpCfg.calcCrc ? "Yes" : "No")); break; + case 6: // Dump verification method + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, dumpCfg.xciDumpCfg.useNoIntroLookup, !dumpCfg.xciDumpCfg.useNoIntroLookup, FONT_COLOR_RGB, (dumpCfg.xciDumpCfg.useNoIntroLookup ? xciChecksumLookupMethods[1] : xciChecksumLookupMethods[0])); + break; + case 7: // Output naming scheme + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, dumpCfg.xciDumpCfg.useBrackets, !dumpCfg.xciDumpCfg.useBrackets, FONT_COLOR_RGB, (dumpCfg.xciDumpCfg.useBrackets ? xciNamingSchemes[1] : xciNamingSchemes[0])); + break; default: break; } @@ -2132,8 +2080,8 @@ UIResult uiProcess() case 1: // Split output dump (FAT32 support) uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.nspDumpCfg.isFat32, !dumpCfg.nspDumpCfg.isFat32, (dumpCfg.nspDumpCfg.isFat32 ? 0 : 255), (dumpCfg.nspDumpCfg.isFat32 ? 255 : 0), 0, (dumpCfg.nspDumpCfg.isFat32 ? "Yes" : "No")); break; - case 2: // CRC32 checksum calculation - uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.nspDumpCfg.calcCrc, !dumpCfg.nspDumpCfg.calcCrc, (dumpCfg.nspDumpCfg.calcCrc ? 0 : 255), (dumpCfg.nspDumpCfg.calcCrc ? 255 : 0), 0, (dumpCfg.nspDumpCfg.calcCrc ? "Yes" : "No")); + case 2: // Verify dump using No-Intro database + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.nspDumpCfg.useNoIntroLookup, !dumpCfg.nspDumpCfg.useNoIntroLookup, (dumpCfg.nspDumpCfg.useNoIntroLookup ? 0 : 255), (dumpCfg.nspDumpCfg.useNoIntroLookup ? 255 : 0), 0, (dumpCfg.nspDumpCfg.useNoIntroLookup ? "Yes" : "No")); break; case 3: // Remove console specific data uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.nspDumpCfg.removeConsoleData, !dumpCfg.nspDumpCfg.removeConsoleData, (dumpCfg.nspDumpCfg.removeConsoleData ? 0 : 255), (dumpCfg.nspDumpCfg.removeConsoleData ? 255 : 0), 0, (dumpCfg.nspDumpCfg.removeConsoleData ? "Yes" : "No")); @@ -2152,7 +2100,15 @@ UIResult uiProcess() { // Find a matching application to print its name and Title ID // Otherwise, just print the Title ID - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, (menuType == MENUTYPE_GAMECARD), NULL, titleSelectorStr, MAX_ELEMENTS(titleSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, (menuType == MENUTYPE_GAMECARD), NULL, titleSelectorStr, MAX_CHARACTERS(titleSelectorStr)); + + if (addOnEntries[selectedAddOnIndex].contentSize) + { + strcat(titleSelectorStr, " ("); + strcat(titleSelectorStr, addOnEntries[selectedAddOnIndex].contentSizeStr); + strcat(titleSelectorStr, ")"); + } + uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2162,14 +2118,21 @@ UIResult uiProcess() uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, leftArrowCondition, rightArrowCondition, FONT_COLOR_RGB, titleSelectorStr); } break; - case 6: // Application/update to dump + case 6: // Application to dump || Dump delta fragments || Output naming scheme (DLC) if (uiState == stateNspAppDumpMenu) { if (!strlen(titleSelectorStr)) { // Print application name - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(titleSelectorStr, MAX_ELEMENTS(titleSelectorStr), "%s v%s", titleName[selectedAppIndex], versionStr); + snprintf(titleSelectorStr, MAX_CHARACTERS(titleSelectorStr), "%s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); + + if (baseAppEntries[selectedAppIndex].contentSize) + { + strcat(titleSelectorStr, " ("); + strcat(titleSelectorStr, baseAppEntries[selectedAppIndex].contentSizeStr); + strcat(titleSelectorStr, ")"); + } + uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2177,20 +2140,50 @@ UIResult uiProcess() rightArrowCondition = (menuType == MENUTYPE_GAMECARD && titleAppCount > 1 && selectedAppIndex < (titleAppCount - 1)); } else if (uiState == stateNspPatchDumpMenu) + { + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.nspDumpCfg.dumpDeltaFragments, !dumpCfg.nspDumpCfg.dumpDeltaFragments, (dumpCfg.nspDumpCfg.dumpDeltaFragments ? 0 : 255), (dumpCfg.nspDumpCfg.dumpDeltaFragments ? 255 : 0), 0, (dumpCfg.nspDumpCfg.dumpDeltaFragments ? "Yes" : "No")); + } else + if (uiState == stateNspAddOnDumpMenu) + { + leftArrowCondition = dumpCfg.nspDumpCfg.useBrackets; + rightArrowCondition = !dumpCfg.nspDumpCfg.useBrackets; + } + + if (uiState != stateNspPatchDumpMenu) uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, leftArrowCondition, rightArrowCondition, FONT_COLOR_RGB, (uiState == stateNspAddOnDumpMenu ? (dumpCfg.nspDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0]) : titleSelectorStr)); + + break; + case 7: // Output naming scheme (base application) || Update to dump + if (uiState == stateNspAppDumpMenu) + { + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, dumpCfg.nspDumpCfg.useBrackets, !dumpCfg.nspDumpCfg.useBrackets, FONT_COLOR_RGB, (dumpCfg.nspDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0])); + } else + if (uiState == stateNspPatchDumpMenu) { if (!strlen(titleSelectorStr)) { // Find a matching application to print its name // Otherwise, just print the Title ID - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, (menuType == MENUTYPE_GAMECARD), NULL, titleSelectorStr, MAX_ELEMENTS(titleSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, (menuType == MENUTYPE_GAMECARD), NULL, titleSelectorStr, MAX_CHARACTERS(titleSelectorStr)); + + if (patchEntries[selectedPatchIndex].contentSize) + { + strcat(titleSelectorStr, " ("); + strcat(titleSelectorStr, patchEntries[selectedPatchIndex].contentSizeStr); + strcat(titleSelectorStr, ")"); + } + uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } leftArrowCondition = ((menuType == MENUTYPE_GAMECARD && titlePatchCount > 0 && selectedPatchIndex > 0) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && retrievePreviousPatchOrAddOnIndexFromBaseApplication(selectedPatchIndex, selectedAppInfoIndex, false) != selectedPatchIndex)); rightArrowCondition = ((menuType == MENUTYPE_GAMECARD && titlePatchCount > 0 && selectedPatchIndex < (titlePatchCount - 1)) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && retrieveNextPatchOrAddOnIndexFromBaseApplication(selectedPatchIndex, selectedAppInfoIndex, false) != selectedPatchIndex)); + + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, leftArrowCondition, rightArrowCondition, FONT_COLOR_RGB, titleSelectorStr); } - uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, leftArrowCondition, rightArrowCondition, FONT_COLOR_RGB, titleSelectorStr); + break; + case 8: // Output naming scheme (update) + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, dumpCfg.nspDumpCfg.useBrackets, !dumpCfg.nspDumpCfg.useBrackets, FONT_COLOR_RGB, (dumpCfg.nspDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0])); break; default: break; @@ -2198,9 +2191,9 @@ UIResult uiProcess() if (i == 2) { - if (dumpCfg.nspDumpCfg.calcCrc) + if (dumpCfg.nspDumpCfg.useNoIntroLookup) { - uiDrawString(FB_WIDTH / 2, ypos, FONT_COLOR_RGB, "This takes extra time after the NSP dump has been completed!"); + uiDrawString(FB_WIDTH / 2, ypos, FONT_COLOR_RGB, "This requires a working Internet connection!"); } else { if (highlight) { @@ -2238,13 +2231,22 @@ UIResult uiProcess() case 7: // Change NPDM RSA key/sig in Program NCA uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.batchDumpCfg.npdmAcidRsaPatch, !dumpCfg.batchDumpCfg.npdmAcidRsaPatch, (dumpCfg.batchDumpCfg.npdmAcidRsaPatch ? 0 : 255), (dumpCfg.batchDumpCfg.npdmAcidRsaPatch ? 255 : 0), 0, (dumpCfg.batchDumpCfg.npdmAcidRsaPatch ? "Yes" : "No")); break; - case 8: // Skip dumped titles + case 8: // Dump delta fragments from updates + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.batchDumpCfg.dumpDeltaFragments, !dumpCfg.batchDumpCfg.dumpDeltaFragments, (dumpCfg.batchDumpCfg.dumpDeltaFragments ? 0 : 255), (dumpCfg.batchDumpCfg.dumpDeltaFragments ? 255 : 0), 0, (dumpCfg.batchDumpCfg.dumpDeltaFragments ? "Yes" : "No")); + break; + case 9: // Skip dumped titles uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.batchDumpCfg.skipDumpedTitles, !dumpCfg.batchDumpCfg.skipDumpedTitles, (dumpCfg.batchDumpCfg.skipDumpedTitles ? 0 : 255), (dumpCfg.batchDumpCfg.skipDumpedTitles ? 255 : 0), 0, (dumpCfg.batchDumpCfg.skipDumpedTitles ? "Yes" : "No")); break; - case 9: // Remember dumped titles + case 10: // Remember dumped titles uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.batchDumpCfg.rememberDumpedTitles, !dumpCfg.batchDumpCfg.rememberDumpedTitles, (dumpCfg.batchDumpCfg.rememberDumpedTitles ? 0 : 255), (dumpCfg.batchDumpCfg.rememberDumpedTitles ? 255 : 0), 0, (dumpCfg.batchDumpCfg.rememberDumpedTitles ? "Yes" : "No")); break; - case 10: // Source storage + case 11: // Halt dump process on errors + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.batchDumpCfg.haltOnErrors, !dumpCfg.batchDumpCfg.haltOnErrors, (dumpCfg.batchDumpCfg.haltOnErrors ? 0 : 255), (dumpCfg.batchDumpCfg.haltOnErrors ? 255 : 0), 0, (dumpCfg.batchDumpCfg.haltOnErrors ? "Yes" : "No")); + break; + case 12: // Output naming scheme + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS_NSP, dumpCfg.batchDumpCfg.useBrackets, !dumpCfg.batchDumpCfg.useBrackets, FONT_COLOR_RGB, (dumpCfg.batchDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0])); + break; + case 13: // Source storage leftArrowCondition = (dumpCfg.batchDumpCfg.batchModeSrc != BATCH_SOURCE_ALL); rightArrowCondition = (dumpCfg.batchDumpCfg.batchModeSrc != BATCH_SOURCE_EMMC); @@ -2263,17 +2265,23 @@ UIResult uiProcess() switch(i) { - case 2: // Use update + case 2: // Split files bigger than 4 GiB (FAT32 support) + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.exeFsDumpCfg.isFat32, !dumpCfg.exeFsDumpCfg.isFat32, (dumpCfg.exeFsDumpCfg.isFat32 ? 0 : 255), (dumpCfg.exeFsDumpCfg.isFat32 ? 255 : 0), 0, (dumpCfg.exeFsDumpCfg.isFat32 ? "Yes" : "No")); + break; + case 3: // Save data to CFW directory (LayeredFS) + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.exeFsDumpCfg.useLayeredFSDir, !dumpCfg.exeFsDumpCfg.useLayeredFSDir, (dumpCfg.exeFsDumpCfg.useLayeredFSDir ? 0 : 255), (dumpCfg.exeFsDumpCfg.useLayeredFSDir ? 255 : 0), 0, (dumpCfg.exeFsDumpCfg.useLayeredFSDir ? "Yes" : "No")); + break; + case 4: // Use update if (exeFsUpdateFlag) { if (!strlen(exeFsAndRomFsSelectorStr)) { // Find a matching application to print its name // Otherwise, just print the Title ID - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); // Concatenate patch source storage - strcat(exeFsAndRomFsSelectorStr, (titlePatchStorageId[selectedPatchIndex] == FsStorageId_GameCard ? " (gamecard)" : (titlePatchStorageId[selectedPatchIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (patchEntries[selectedPatchIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (patchEntries[selectedPatchIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); uiTruncateOptionStr(exeFsAndRomFsSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2304,8 +2312,7 @@ UIResult uiProcess() if (!strlen(titleSelectorStr)) { // Print application name - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(titleSelectorStr, MAX_ELEMENTS(titleSelectorStr), "%s v%s", titleName[selectedAppIndex], versionStr); + snprintf(titleSelectorStr, MAX_CHARACTERS(titleSelectorStr), "%s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2322,10 +2329,10 @@ UIResult uiProcess() { // Find a matching application to print its name // Otherwise, just print the Title ID - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); // Concatenate patch source storage - strcat(exeFsAndRomFsSelectorStr, (titlePatchStorageId[selectedPatchIndex] == FsStorageId_GameCard ? " (gamecard)" : (titlePatchStorageId[selectedPatchIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (patchEntries[selectedPatchIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (patchEntries[selectedPatchIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); uiTruncateOptionStr(exeFsAndRomFsSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2354,7 +2361,13 @@ UIResult uiProcess() switch(i) { - case 2: // Use update/DLC + case 2: // Split files bigger than 4 GiB (FAT32 support) + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.romFsDumpCfg.isFat32, !dumpCfg.romFsDumpCfg.isFat32, (dumpCfg.romFsDumpCfg.isFat32 ? 0 : 255), (dumpCfg.romFsDumpCfg.isFat32 ? 255 : 0), 0, (dumpCfg.romFsDumpCfg.isFat32 ? "Yes" : "No")); + break; + case 3: // Save data to CFW directory (LayeredFS) + uiPrintOption(xpos, ypos, OPTIONS_X_END_POS, dumpCfg.romFsDumpCfg.useLayeredFSDir, !dumpCfg.romFsDumpCfg.useLayeredFSDir, (dumpCfg.romFsDumpCfg.useLayeredFSDir ? 0 : 255), (dumpCfg.romFsDumpCfg.useLayeredFSDir ? 255 : 0), 0, (dumpCfg.romFsDumpCfg.useLayeredFSDir ? "Yes" : "No")); + break; + case 4: // Use update/DLC if (curRomFsType != ROMFS_TYPE_APP) { if (!strlen(exeFsAndRomFsSelectorStr)) @@ -2366,14 +2379,14 @@ UIResult uiProcess() switch(curRomFsType) { case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); strcat(exeFsAndRomFsSelectorStr, " (UPD)"); - strcat(exeFsAndRomFsSelectorStr, (titlePatchStorageId[selectedPatchIndex] == FsStorageId_GameCard ? " (gamecard)" : (titlePatchStorageId[selectedPatchIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (patchEntries[selectedPatchIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (patchEntries[selectedPatchIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, (menuType == MENUTYPE_GAMECARD && titleAppCount > 1), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); strcat(exeFsAndRomFsSelectorStr, " (DLC)"); - strcat(exeFsAndRomFsSelectorStr, (titleAddOnStorageId[selectedAddOnIndex] == FsStorageId_GameCard ? " (gamecard)" : (titleAddOnStorageId[selectedAddOnIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (addOnEntries[selectedAddOnIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (addOnEntries[selectedAddOnIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); break; default: break; @@ -2408,8 +2421,7 @@ UIResult uiProcess() if (!strlen(titleSelectorStr)) { // Print application name - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(titleSelectorStr, MAX_ELEMENTS(titleSelectorStr), "%s v%s", titleName[selectedAppIndex], versionStr); + snprintf(titleSelectorStr, MAX_CHARACTERS(titleSelectorStr), "%s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2431,14 +2443,14 @@ UIResult uiProcess() switch(curRomFsType) { case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); strcat(exeFsAndRomFsSelectorStr, " (UPD)"); - strcat(exeFsAndRomFsSelectorStr, (titlePatchStorageId[selectedPatchIndex] == FsStorageId_GameCard ? " (gamecard)" : (titlePatchStorageId[selectedPatchIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (patchEntries[selectedPatchIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (patchEntries[selectedPatchIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_ELEMENTS(exeFsAndRomFsSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, (menuType == MENUTYPE_GAMECARD), NULL, exeFsAndRomFsSelectorStr, MAX_CHARACTERS(exeFsAndRomFsSelectorStr)); strcat(exeFsAndRomFsSelectorStr, " (DLC)"); - strcat(exeFsAndRomFsSelectorStr, (titleAddOnStorageId[selectedAddOnIndex] == FsStorageId_GameCard ? " (gamecard)" : (titleAddOnStorageId[selectedAddOnIndex] == FsStorageId_SdCard ? " (SD card)" : "(eMMC)"))); + strcat(exeFsAndRomFsSelectorStr, (addOnEntries[selectedAddOnIndex].storageId == NcmStorageId_GameCard ? " (gamecard)" : (addOnEntries[selectedAddOnIndex].storageId == NcmStorageId_SdCard ? " (SD card)" : " (eMMC)"))); break; default: break; @@ -2481,11 +2493,11 @@ UIResult uiProcess() switch(curTikType) { case TICKET_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, false, NULL, titleSelectorStr, MAX_ELEMENTS(titleSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, false, NULL, titleSelectorStr, MAX_CHARACTERS(titleSelectorStr)); strcat(titleSelectorStr, " (UPD)"); break; case TICKET_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, false, NULL, titleSelectorStr, MAX_ELEMENTS(titleSelectorStr)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, false, NULL, titleSelectorStr, MAX_CHARACTERS(titleSelectorStr)); strcat(titleSelectorStr, " (DLC)"); break; default: @@ -2501,8 +2513,7 @@ UIResult uiProcess() if (!strlen(titleSelectorStr)) { // Print application TID - convertTitleVersionToDecimal(titleAppVersion[selectedAppInfoIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(titleSelectorStr, MAX_ELEMENTS(titleSelectorStr), "%016lX v%s (BASE)", titleAppTitleID[selectedAppInfoIndex], versionStr); + snprintf(titleSelectorStr, MAX_CHARACTERS(titleSelectorStr), "%016lX v%s (BASE)", baseAppEntries[selectedAppInfoIndex].titleId, baseAppEntries[selectedAppInfoIndex].versionStr); uiTruncateOptionStr(titleSelectorStr, xpos, ypos, OPTIONS_X_END_POS_NSP); } @@ -2530,26 +2541,46 @@ UIResult uiProcess() uiDrawString((FB_WIDTH / 2) - (arrowWidth / 2), ypos, FONT_COLOR_RGB, downwardsArrow); } - // Print warning about missing Lockpick_RCM keys file - if (uiState == stateMainMenu && !keysFileAvailable) + if (uiState == stateMainMenu) { j++; if ((scroll + maxElements) < menuItemsCount) j++; - 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++; + // 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 = ((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 = ((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 = ((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"); + } - 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 = ((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 = ((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"); + // Print warning about running the application under applet mode + if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) + { + if (!keysFileAvailable) j += 2; + + ypos = ((breaks * LINE_HEIGHT) + (j * (font_height + 12))); + uiDrawString(STRING_X_POS, ypos, FONT_COLOR_ERROR_RGB, "Warning: running under applet mode."); + j++; + + 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 = ((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)."); + } } // Print information about the "Change NPDM RSA key/sig in Program NCA" option @@ -2566,24 +2597,60 @@ UIResult uiProcess() 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 hint about dumping RomFS content from DLCs - if ((uiState == stateRomFsMenu && ((menuType == MENUTYPE_GAMECARD && titleAppCount <= 1 && checkIfBaseApplicationHasPatchOrAddOn(0, true)) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))) || ((uiState == stateRomFsSectionDataDumpMenu || uiState == stateRomFsSectionBrowserMenu) && (menuType == MENUTYPE_GAMECARD && titleAppCount > 1 && checkIfBaseApplicationHasPatchOrAddOn(selectedAppIndex, true)))) + // 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 { + breaks += 2; + + if (uiState == stateRomFsSectionBrowser) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "No entries available in the selected directory! Press " NINTENDO_FONT_B " to go back."); + } else if (uiState == stateSdCardEmmcOrphanPatchAddOnMenu) { - breaks += 2; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No orphan updates/DLCs available in the SD card / eMMC storage!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "No orphan updates/DLCs available in the SD card / eMMC storage!"); } } + bool curGcStatus = gameCardInfo.isInserted; + while(true) { uiUpdateStatusMsg(); @@ -2594,7 +2661,7 @@ UIResult uiProcess() keysDown = hidKeysDown(CONTROLLER_P1_AUTO); keysHeld = hidKeysHeld(CONTROLLER_P1_AUTO); - if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH))) break; + if ((keysDown && !(keysDown & KEY_TOUCH)) || (keysHeld && !(keysHeld & KEY_TOUCH)) || (menuType == MENUTYPE_GAMECARD && gameCardInfo.isInserted != curGcStatus)) break; } // Exit @@ -2653,6 +2720,12 @@ UIResult uiProcess() case 5: // CRC32 checksum calculation + dump verification dumpCfg.xciDumpCfg.calcCrc = false; break; + case 6: // Dump verification method + dumpCfg.xciDumpCfg.useNoIntroLookup = false; + break; + case 7: // Output naming scheme + dumpCfg.xciDumpCfg.useBrackets = false; + break; default: break; } @@ -2681,6 +2754,12 @@ UIResult uiProcess() case 5: // CRC32 checksum calculation + dump verification dumpCfg.xciDumpCfg.calcCrc = true; break; + case 6: // Dump verification method + dumpCfg.xciDumpCfg.useNoIntroLookup = true; + break; + case 7: // Output naming scheme + dumpCfg.xciDumpCfg.useBrackets = true; + break; default: break; } @@ -2748,8 +2827,8 @@ UIResult uiProcess() case 1: // Split output dump (FAT32 support) dumpCfg.nspDumpCfg.isFat32 = false; break; - case 2: // CRC32 checksum calculation - dumpCfg.nspDumpCfg.calcCrc = false; + case 2: // Verify dump using No-Intro database + dumpCfg.nspDumpCfg.useNoIntroLookup = false; break; case 3: // Remove console specific data dumpCfg.nspDumpCfg.removeConsoleData = dumpCfg.nspDumpCfg.tiklessDump = false; @@ -2786,7 +2865,7 @@ UIResult uiProcess() } } break; - case 6: // Application/update to dump + case 6: // Application to dump || Dump delta fragments || Output naming scheme (DLC) if (uiState == stateNspAppDumpMenu) { if (menuType == MENUTYPE_GAMECARD) @@ -2799,6 +2878,20 @@ UIResult uiProcess() } } else if (uiState == stateNspPatchDumpMenu) + { + dumpCfg.nspDumpCfg.dumpDeltaFragments = false; + } else + if (uiState == stateNspAddOnDumpMenu) + { + dumpCfg.nspDumpCfg.useBrackets = false; + } + break; + case 7: // Output naming scheme (base application) || Update to dump + if (uiState == stateNspAppDumpMenu) + { + dumpCfg.nspDumpCfg.useBrackets = false; + } else + if (uiState == stateNspPatchDumpMenu) { if (menuType == MENUTYPE_GAMECARD) { @@ -2822,6 +2915,9 @@ UIResult uiProcess() } } break; + case 8: // Output naming scheme (update) + dumpCfg.nspDumpCfg.useBrackets = false; + break; default: break; } @@ -2838,8 +2934,8 @@ UIResult uiProcess() case 1: // Split output dump (FAT32 support) dumpCfg.nspDumpCfg.isFat32 = true; break; - case 2: // CRC32 checksum calculation - dumpCfg.nspDumpCfg.calcCrc = true; + case 2: // Verify dump using No-Intro database + dumpCfg.nspDumpCfg.useNoIntroLookup = true; break; case 3: // Remove console specific data dumpCfg.nspDumpCfg.removeConsoleData = true; @@ -2874,7 +2970,7 @@ UIResult uiProcess() } } break; - case 6: // Application/update to dump + case 6: // Application to dump || Dump delta fragments || Output naming scheme (DLC) if (uiState == stateNspAppDumpMenu) { if (menuType == MENUTYPE_GAMECARD) @@ -2885,7 +2981,23 @@ UIResult uiProcess() titleSelectorStr[0] = '\0'; } } - } else { + } else + if (uiState == stateNspPatchDumpMenu) + { + dumpCfg.nspDumpCfg.dumpDeltaFragments = true; + } else + if (uiState == stateNspAddOnDumpMenu) + { + dumpCfg.nspDumpCfg.useBrackets = true; + } + break; + case 7: // Output naming scheme (base application) || Update to dump + if (uiState == stateNspAppDumpMenu) + { + dumpCfg.nspDumpCfg.useBrackets = true; + } else + if (uiState == stateNspPatchDumpMenu) + { if (menuType == MENUTYPE_GAMECARD) { if (titlePatchCount > 1 && (selectedPatchIndex + 1) < titlePatchCount) @@ -2908,6 +3020,9 @@ UIResult uiProcess() } } break; + case 8: // Output naming scheme (update) + dumpCfg.nspDumpCfg.useBrackets = true; + break; default: break; } @@ -2964,13 +3079,22 @@ UIResult uiProcess() case 7: // Change NPDM RSA key/sig in Program NCA dumpCfg.batchDumpCfg.npdmAcidRsaPatch = false; break; - case 8: // Skip already dumped titles + case 8: // Dump delta fragments from updates + dumpCfg.batchDumpCfg.dumpDeltaFragments = false; + break; + case 9: // Skip already dumped titles dumpCfg.batchDumpCfg.skipDumpedTitles = false; break; - case 9: // Remember dumped titles + case 10: // Remember dumped titles dumpCfg.batchDumpCfg.rememberDumpedTitles = false; break; - case 10: // Source storage + case 11: // Halt dump process on errors + dumpCfg.batchDumpCfg.haltOnErrors = false; + break; + case 12: // Output naming scheme + dumpCfg.batchDumpCfg.useBrackets = false; + break; + case 13: // Source storage if (dumpCfg.batchDumpCfg.batchModeSrc != BATCH_SOURCE_ALL) { dumpCfg.batchDumpCfg.batchModeSrc--; @@ -2989,9 +3113,9 @@ UIResult uiProcess() } else if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) { - dumpCfg.batchDumpCfg.dumpAppTitles = (nandUserTitleAppCount > 0); - dumpCfg.batchDumpCfg.dumpPatchTitles = (nandUserTitlePatchCount > 0); - dumpCfg.batchDumpCfg.dumpAddOnTitles = (nandUserTitleAddOnCount > 0); + dumpCfg.batchDumpCfg.dumpAppTitles = (emmcTitleAppCount > 0); + dumpCfg.batchDumpCfg.dumpPatchTitles = (emmcTitlePatchCount > 0); + dumpCfg.batchDumpCfg.dumpAddOnTitles = (emmcTitleAddOnCount > 0); } } break; @@ -3029,13 +3153,22 @@ UIResult uiProcess() case 7: // Change NPDM RSA key/sig in Program NCA dumpCfg.batchDumpCfg.npdmAcidRsaPatch = true; break; - case 8: // Skip already dumped titles + case 8: // Dump delta fragments from updates + dumpCfg.batchDumpCfg.dumpDeltaFragments = true; + break; + case 9: // Skip already dumped titles dumpCfg.batchDumpCfg.skipDumpedTitles = true; break; - case 9: // Remember dumped titles + case 10: // Remember dumped titles dumpCfg.batchDumpCfg.rememberDumpedTitles = true; break; - case 10: // Source storage + case 11: // Halt dump process on errors + dumpCfg.batchDumpCfg.haltOnErrors = true; + break; + case 12: // Output naming scheme + dumpCfg.batchDumpCfg.useBrackets = true; + break; + case 13: // Source storage if (dumpCfg.batchDumpCfg.batchModeSrc != BATCH_SOURCE_EMMC) { dumpCfg.batchDumpCfg.batchModeSrc++; @@ -3054,9 +3187,9 @@ UIResult uiProcess() } else if (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC) { - dumpCfg.batchDumpCfg.dumpAppTitles = (nandUserTitleAppCount > 0); - dumpCfg.batchDumpCfg.dumpPatchTitles = (nandUserTitlePatchCount > 0); - dumpCfg.batchDumpCfg.dumpAddOnTitles = (nandUserTitleAddOnCount > 0); + dumpCfg.batchDumpCfg.dumpAppTitles = (emmcTitleAppCount > 0); + dumpCfg.batchDumpCfg.dumpPatchTitles = (emmcTitlePatchCount > 0); + dumpCfg.batchDumpCfg.dumpAddOnTitles = (emmcTitleAddOnCount > 0); } } break; @@ -3108,7 +3241,7 @@ UIResult uiProcess() { if (menuType == MENUTYPE_GAMECARD) { - freeTitlesFromSdCardAndEmmc(META_DB_PATCH); + freeTitlesFromSdCardAndEmmc(NcmContentMetaType_Patch); res = resultShowGameCardMenu; } else { res = resultShowSdCardEmmcTitleMenu; @@ -3120,7 +3253,13 @@ UIResult uiProcess() { switch(cursor) { - case 2: // Use update + case 2: // Split files bigger than 4 GiB (FAT32 support) + dumpCfg.exeFsDumpCfg.isFat32 = false; + break; + case 3: // Save data to CFW directory (LayeredFS) + dumpCfg.exeFsDumpCfg.useLayeredFSDir = false; + break; + case 4: // Use update if ((menuType == MENUTYPE_GAMECARD && titleAppCount == 1 && checkIfBaseApplicationHasPatchOrAddOn(0, false)) || (menuType == MENUTYPE_SDCARD_EMMC && checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false))) { if (exeFsUpdateFlag) @@ -3148,7 +3287,13 @@ UIResult uiProcess() { switch(cursor) { - case 2: // Use update + case 2: // Split files bigger than 4 GiB (FAT32 support) + dumpCfg.exeFsDumpCfg.isFat32 = true; + break; + case 3: // Save data to CFW directory (LayeredFS) + dumpCfg.exeFsDumpCfg.useLayeredFSDir = true; + break; + case 4: // Use update if ((menuType == MENUTYPE_GAMECARD && titleAppCount == 1 && checkIfBaseApplicationHasPatchOrAddOn(0, false)) || (menuType == MENUTYPE_SDCARD_EMMC && checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false))) { u32 appIndex = (menuType == MENUTYPE_GAMECARD ? 0 : selectedAppInfoIndex); @@ -3311,8 +3456,8 @@ UIResult uiProcess() { if (menuType == MENUTYPE_GAMECARD) { - freeTitlesFromSdCardAndEmmc(META_DB_PATCH); - freeTitlesFromSdCardAndEmmc(META_DB_ADDON); + freeTitlesFromSdCardAndEmmc(NcmContentMetaType_Patch); + freeTitlesFromSdCardAndEmmc(NcmContentMetaType_AddOnContent); res = resultShowGameCardMenu; } else { res = resultShowSdCardEmmcTitleMenu; @@ -3324,7 +3469,13 @@ UIResult uiProcess() { switch(cursor) { - case 2: // Use update/DLC + case 2: // Split files bigger than 4 GiB (FAT32 support) + dumpCfg.romFsDumpCfg.isFat32 = false; + break; + case 3: // Save data to CFW directory (LayeredFS) + dumpCfg.romFsDumpCfg.useLayeredFSDir = false; + break; + case 4: // Use update/DLC if ((menuType == MENUTYPE_GAMECARD && titleAppCount == 1 && (checkIfBaseApplicationHasPatchOrAddOn(0, false) || checkIfBaseApplicationHasPatchOrAddOn(0, true))) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && (checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) || checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))) { if (curRomFsType != ROMFS_TYPE_APP) @@ -3371,7 +3522,13 @@ UIResult uiProcess() { switch(cursor) { - case 2: // Use update/DLC + case 2: // Split files bigger than 4 GiB (FAT32 support) + dumpCfg.romFsDumpCfg.isFat32 = true; + break; + case 3: // Save data to CFW directory (LayeredFS) + dumpCfg.romFsDumpCfg.useLayeredFSDir = true; + break; + case 4: // Use update/DLC if ((menuType == MENUTYPE_GAMECARD && titleAppCount == 1 && (checkIfBaseApplicationHasPatchOrAddOn(0, false) || checkIfBaseApplicationHasPatchOrAddOn(0, true))) || (menuType == MENUTYPE_SDCARD_EMMC && !orphanMode && (checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) || checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))) { u32 appIndex = (menuType == MENUTYPE_GAMECARD ? 0 : selectedAppInfoIndex); @@ -3766,7 +3923,7 @@ UIResult uiProcess() case 3: if (keysFileAvailable) { - loadTitlesFromSdCardAndEmmc(META_DB_PATCH); + loadTitlesFromSdCardAndEmmc(NcmContentMetaType_Patch); res = resultShowExeFsMenu; @@ -3780,8 +3937,8 @@ UIResult uiProcess() case 4: if (keysFileAvailable) { - loadTitlesFromSdCardAndEmmc(META_DB_PATCH); - loadTitlesFromSdCardAndEmmc(META_DB_ADDON); + loadTitlesFromSdCardAndEmmc(NcmContentMetaType_Patch); + loadTitlesFromSdCardAndEmmc(NcmContentMetaType_AddOnContent); res = resultShowRomFsMenu; @@ -3861,9 +4018,12 @@ UIResult uiProcess() } else if (uiState == stateHfs0Browser) { - // Save selected file index - selectedFileIndex = (u32)cursor; - res = resultHfs0BrowserCopyFile; + if (menu && menuItemsCount) + { + // Save selected file index + selectedFileIndex = (u32)cursor; + res = resultHfs0BrowserCopyFile; + } } else if (uiState == stateExeFsSectionBrowser) { @@ -3881,7 +4041,7 @@ UIResult uiProcess() // Save selected file index selectedFileIndex = (u32)cursor; if (strlen(curRomFsPath) <= 1) selectedFileIndex++; // Adjust index if we're at the root directory - res = (romFsBrowserEntries[cursor].type == ROMFS_ENTRY_DIR ? resultRomFsSectionBrowserChangeDir : resultRomFsSectionBrowserCopyFile); + res = (romFsBrowserEntries[selectedFileIndex].type == ROMFS_ENTRY_DIR ? resultRomFsSectionBrowserChangeDir : resultRomFsSectionBrowserCopyFile); } } else if (uiState == stateSdCardEmmcMenu) @@ -3909,14 +4069,17 @@ UIResult uiProcess() } break; case 1: + res = resultShowExeFsMenu; + if (!orphanMode) { - res = resultShowExeFsMenu; - // Reset options to their default values exeFsUpdateFlag = false; selectedPatchIndex = 0; + } else { + exeFsUpdateFlag = true; } + break; case 2: res = resultShowRomFsMenu; @@ -4003,17 +4166,14 @@ UIResult uiProcess() } else if (uiState == stateHfs0Browser) { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; + freeHfs0ExeFsEntriesSizes(); res = resultShowHfs0BrowserMenu; } else if (uiState == stateExeFsSectionBrowser) { + freeHfs0ExeFsEntriesSizes(); + freeExeFsContext(); res = ((menuType == MENUTYPE_GAMECARD && titleAppCount > 1) ? resultShowExeFsSectionBrowserMenu : resultShowExeFsMenu); @@ -4026,18 +4186,12 @@ UIResult uiProcess() selectedFileIndex = 0; res = resultRomFsSectionBrowserChangeDir; } else { - if (romFsBrowserEntries != NULL) - { - free(romFsBrowserEntries); - romFsBrowserEntries = NULL; - } + freeRomFsBrowserEntries(); if (curRomFsType == ROMFS_TYPE_PATCH) freeBktrContext(); freeRomFsContext(); - if (menuType == MENUTYPE_SDCARD_EMMC && orphanMode) generateOrphanPatchOrAddOnList(); - res = ((menuType == MENUTYPE_GAMECARD && titleAppCount > 1) ? resultShowRomFsSectionBrowserMenu : resultShowRomFsMenu); } } else @@ -4051,8 +4205,6 @@ UIResult uiProcess() } else if (uiState == stateSdCardEmmcOrphanPatchAddOnMenu) { - freeOrphanPatchOrAddOnList(); - res = resultShowSdCardEmmcMenu; orphanMode = false; } @@ -4061,7 +4213,7 @@ UIResult uiProcess() // Special action #1 if (keysDown & KEY_Y) { - if (uiState == stateSdCardEmmcMenu && ((titleAppCount && ((titlePatchCount && checkOrphanPatchOrAddOn(false)) || (titleAddOnCount && checkOrphanPatchOrAddOn(true)))) || (!titleAppCount && (titlePatchCount || titleAddOnCount)))) + if (uiState == stateSdCardEmmcMenu && (calculateOrphanPatchOrAddOnCount(false) || calculateOrphanPatchOrAddOnCount(true))) { // SD/eMMC menu: Dump installed content with missing base application res = resultShowSdCardEmmcOrphanPatchAddOnMenu; @@ -4166,6 +4318,19 @@ UIResult uiProcess() } } + // Avoid placing the cursor on the "Dump verification method" option if "CRC32 checksum calculation + dump verification" is disabled + if (uiState == stateXciDumpMenu && cursor == 6 && !dumpCfg.xciDumpCfg.calcCrc) + { + if (scrollAmount > 0) + { + cursor++; + } else + if (scrollAmount < 0) + { + cursor--; + } + } + // Avoid placing the cursor on the "Dump bundled update NSP" / "Dump installed update NSP" option in the NSP dump menu if we're dealing with a gamecard and it doesn't include any bundled updates, or if we're dealing with a SD/eMMC title without installed updates // Also avoid placing the cursor on the "Dump bundled DLC NSP" / "Dump installed DLC NSP" option in the NSP dump menu if we're dealing with a gamecard and it doesn't include any bundled DLCs, or if we're dealing with a SD/eMMC title without installed DLCs if (uiState == stateNspDumpMenu && ((cursor == 1 && (!titlePatchCount || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)))) || (cursor == 2 && (!titleAddOnCount || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) @@ -4206,9 +4371,10 @@ UIResult uiProcess() } } - // Avoid placing the cursor on the "Remove console specific data" option in the NSP dump menus if we're dealing with a gamecard title + // Avoid placing the cursor on the "Verify dump using No-Intro database" option in the NSP dump menus if we're dealing with a gamecard title + // Also, avoid placing the cursor on the "Remove console specific data" option in the NSP dump menus if we're dealing with a gamecard title // Also, avoid placing the cursor on the "Generate ticket-less dump" option in the NSP dump menus if we're dealing with a gamecard Application/AddOn title - if (menuType == MENUTYPE_GAMECARD && (((uiState == stateNspAppDumpMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu) && cursor == 3) || ((uiState == stateNspAppDumpMenu || uiState == stateNspAddOnDumpMenu) && cursor == 4))) + if (menuType == MENUTYPE_GAMECARD && (((uiState == stateNspAppDumpMenu || uiState == stateNspPatchDumpMenu || uiState == stateNspAddOnDumpMenu) && (cursor == 2 || cursor == 3)) || ((uiState == stateNspAppDumpMenu || uiState == stateNspAddOnDumpMenu) && cursor == 4))) { if (scrollAmount > 0) { @@ -4216,7 +4382,20 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 2; + cursor = 1; + } + } + + // Avoid printing the "Dump delta fragments" option in the update NSP dump menu if we're dealing with a gamecard update + if (menuType == MENUTYPE_GAMECARD && uiState == stateNspPatchDumpMenu && cursor == 6) + { + if (scrollAmount > 0) + { + cursor++; + } else + if (scrollAmount < 0) + { + cursor--; } } @@ -4234,7 +4413,7 @@ 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 && ((!nandUserTitleAppCount && cursor == 1) || (!nandUserTitlePatchCount && cursor == 2) || (!nandUserTitleAddOnCount && cursor == 3))))) + 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) { @@ -4259,12 +4438,25 @@ UIResult uiProcess() } } - // Avoid placing the cursor on the "Source storage" option in the batch mode menu if we only have titles available in a single source storage device - if (uiState == stateSdCardEmmcBatchModeMenu && cursor == 10 && ((!sdCardTitleAppCount && !sdCardTitlePatchCount && !sdCardTitleAddOnCount) || (!nandUserTitleAppCount && !nandUserTitlePatchCount && !nandUserTitleAddOnCount))) + // Avoid placing the cursor on the "Dump delta fragments from updates" option in the batch mode menu if the "Dump updates" option is disabled + if (uiState == stateSdCardEmmcBatchModeMenu && cursor == 8 && !dumpCfg.batchDumpCfg.dumpPatchTitles) { if (scrollAmount > 0) { - cursor = (scrollWithKeysDown ? 0 : 9); + cursor++; + } else + if (scrollAmount < 0) + { + cursor--; + } + } + + // Avoid placing the cursor on the "Source storage" option in the batch mode menu if we only have titles available in a single source storage device + if (uiState == stateSdCardEmmcBatchModeMenu && cursor == 13 && ((!sdCardTitleAppCount && !sdCardTitlePatchCount && !sdCardTitleAddOnCount) || (!emmcTitleAppCount && !emmcTitlePatchCount && !emmcTitleAddOnCount))) + { + if (scrollAmount > 0) + { + cursor = (scrollWithKeysDown ? 0 : 12); } else if (scrollAmount < 0) { @@ -4273,8 +4465,8 @@ UIResult uiProcess() } // Avoid placing the cursor on the "Use update" option in the ExeFS menu if we're dealing with a gamecard and either its base application count is greater than 1 or it has no available patches - // Also avoid placing the cursor on it if we're dealing with a SD/eMMC title and it has no available patches - if (uiState == stateExeFsMenu && cursor == 2 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || !checkIfBaseApplicationHasPatchOrAddOn(0, false))) || (menuType == MENUTYPE_SDCARD_EMMC && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)))) + // Also avoid placing the cursor on it if we're dealing with a SD/eMMC title and it has no available patches, or if we're dealing with an orphan Patch + if (uiState == stateExeFsMenu && cursor == 4 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || !checkIfBaseApplicationHasPatchOrAddOn(0, false))) || (menuType == MENUTYPE_SDCARD_EMMC && ((!orphanMode && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false)) || orphanMode)))) { if (scrollAmount > 0) { @@ -4282,7 +4474,7 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 0; + cursor = (scrollWithKeysDown ? 1 : 0); } } @@ -4295,13 +4487,13 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 0; + cursor = (scrollWithKeysDown ? 1 : 0); } } // Avoid placing the cursor on the "Use update/DLC" option in the RomFS menu if we're dealing with a gamecard and either its base application count is greater than 1 or it has no available patches/DLCs // Also avoid placing the cursor on it if we're dealing with a SD/eMMC title and it has no available patches/DLCs (or if its an orphan title) - if (uiState == stateRomFsMenu && cursor == 2 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || (!checkIfBaseApplicationHasPatchOrAddOn(0, false) && !checkIfBaseApplicationHasPatchOrAddOn(0, true)))) || (menuType == MENUTYPE_SDCARD_EMMC && (orphanMode || (!checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) + if (uiState == stateRomFsMenu && cursor == 4 && ((menuType == MENUTYPE_GAMECARD && (titleAppCount > 1 || (!checkIfBaseApplicationHasPatchOrAddOn(0, false) && !checkIfBaseApplicationHasPatchOrAddOn(0, true)))) || (menuType == MENUTYPE_SDCARD_EMMC && (orphanMode || (!checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, false) && !checkIfBaseApplicationHasPatchOrAddOn(selectedAppInfoIndex, true)))))) { if (scrollAmount > 0) { @@ -4309,7 +4501,7 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 0; + cursor = (scrollWithKeysDown ? 1 : 0); } } @@ -4322,21 +4514,21 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 0; + cursor = (scrollWithKeysDown ? 1 : 0); } } - // Avoid placing the cursor on the "ExeFS options" element in the SD card / eMMC title menu if we're dealing with an orphan DLC - // Also avoid placing the cursor on the "RomFS options" element in the SD card / eMMC title menu if we're dealing with an orphan Patch - if (uiState == stateSdCardEmmcTitleMenu && orphanMode && ((orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON && cursor == 1) || (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH && (cursor == 1 || cursor == 2)))) + // Avoid placing the cursor on the "RomFS options" element in the SD card / eMMC title menu if we're dealing with an orphan Patch + // Also avoid placing the cursor on the "ExeFS options" element in the SD card / eMMC title menu if we're dealing with an orphan DLC + if (uiState == stateSdCardEmmcTitleMenu && orphanMode && ((orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_PATCH && cursor == 2) || (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON && cursor == 1))) { if (scrollAmount > 0) { - cursor = (orphanEntries[orphanListCursor].type == ORPHAN_ENTRY_TYPE_ADDON ? 2 : 3); + cursor++; } else if (scrollAmount < 0) { - cursor = 0; + cursor--; } } @@ -4349,7 +4541,7 @@ UIResult uiProcess() } else if (scrollAmount < 0) { - cursor = 0; + cursor = (scrollWithKeysDown ? 1 : 0); } } } @@ -4357,34 +4549,52 @@ UIResult uiProcess() } else if (uiState == stateDumpXci) { + char tmp[128] = {'\0'}; + strbuf[0] = '\0'; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, gameCardMenuItems[0]); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[1], (dumpCfg.xciDumpCfg.isFat32 ? "Yes" : "No")); - breaks++; + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", xciDumpMenuItems[1], (dumpCfg.xciDumpCfg.isFat32 ? "Yes" : "No")); if (dumpCfg.xciDumpCfg.isFat32) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[2], (dumpCfg.xciDumpCfg.setXciArchiveBit ? "Yes" : "No")); - breaks++; + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", xciDumpMenuItems[2], (dumpCfg.xciDumpCfg.setXciArchiveBit ? "Yes" : "No")); + strcat(strbuf, tmp); } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[3], (dumpCfg.xciDumpCfg.keepCert ? "Yes" : "No")); breaks++; uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[4], (dumpCfg.xciDumpCfg.trimDump ? "Yes" : "No")); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[5], (dumpCfg.xciDumpCfg.calcCrc ? "Yes" : "No")); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", xciDumpMenuItems[5], (dumpCfg.xciDumpCfg.calcCrc ? "Yes" : "No")); + + if (dumpCfg.xciDumpCfg.calcCrc) + { + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", xciDumpMenuItems[6], (dumpCfg.xciDumpCfg.useNoIntroLookup ? xciChecksumLookupMethods[1] : xciChecksumLookupMethods[0])); + strcat(strbuf, tmp); + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); + breaks++; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", xciDumpMenuItems[7], (dumpCfg.xciDumpCfg.useBrackets ? xciNamingSchemes[1] : xciNamingSchemes[0])); breaks += 2; uiRefreshDisplay(); - dumpCartridgeImage(&(dumpCfg.xciDumpCfg)); + dumpNXCardImage(&(dumpCfg.xciDumpCfg)); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowXciDumpMenu; dumpedContentInfoStr[0] = '\0'; @@ -4396,46 +4606,68 @@ UIResult uiProcess() menu = (selectedNspDumpType == DUMP_APP_NSP ? nspAppDumpMenuItems : (selectedNspDumpType == DUMP_PATCH_NSP ? nspPatchDumpMenuItems : nspAddOnDumpMenuItems)); + char tmp[128] = {'\0'}; + strbuf[0] = '\0'; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[1], (dumpCfg.nspDumpCfg.isFat32 ? "Yes" : "No")); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[2], (dumpCfg.nspDumpCfg.calcCrc ? "Yes" : "No")); - breaks++; + if (menuType != MENUTYPE_GAMECARD) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[2], (dumpCfg.nspDumpCfg.useNoIntroLookup ? "Yes" : "No")); + breaks++; + } if (menuType == MENUTYPE_GAMECARD && selectedNspDumpType == DUMP_PATCH_NSP) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[4], (dumpCfg.nspDumpCfg.tiklessDump ? "Yes" : "No")); - breaks++; + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", menu[4], (dumpCfg.nspDumpCfg.tiklessDump ? "Yes" : "No")); } else if (menuType == MENUTYPE_SDCARD_EMMC) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[3], (dumpCfg.nspDumpCfg.removeConsoleData ? "Yes" : "No")); - breaks++; + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", menu[3], (dumpCfg.nspDumpCfg.removeConsoleData ? "Yes" : "No")); if (dumpCfg.nspDumpCfg.removeConsoleData) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[4], (dumpCfg.nspDumpCfg.tiklessDump ? "Yes" : "No")); - breaks++; + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[4], (dumpCfg.nspDumpCfg.tiklessDump ? "Yes" : "No")); + strcat(strbuf, tmp); } } if (selectedNspDumpType == DUMP_APP_NSP || selectedNspDumpType == DUMP_PATCH_NSP) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[5], (dumpCfg.nspDumpCfg.npdmAcidRsaPatch ? "Yes" : "No")); - breaks++; + if ((menuType == MENUTYPE_GAMECARD && selectedNspDumpType == DUMP_PATCH_NSP) || menuType == MENUTYPE_SDCARD_EMMC) strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[5], (dumpCfg.nspDumpCfg.npdmAcidRsaPatch ? "Yes" : "No")); + strcat(strbuf, tmp); - if (selectedNspDumpType == DUMP_APP_NSP) + if (selectedNspDumpType == DUMP_PATCH_NSP) { - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", menu[6], titleName[selectedAppIndex], versionStr); - } else { - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, menu[6], strbuf, MAX_ELEMENTS(strbuf)); + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[6], (dumpCfg.nspDumpCfg.dumpDeltaFragments ? "Yes" : "No")); + strcat(strbuf, tmp); } - } else { - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, menu[5], strbuf, MAX_ELEMENTS(strbuf)); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); + breaks++; + + if (selectedNspDumpType == DUMP_APP_NSP) + { + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", menu[6], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); + } else + if (selectedNspDumpType == DUMP_PATCH_NSP) + { + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, menu[7], strbuf, MAX_CHARACTERS(strbuf)); + } else + if (selectedNspDumpType == DUMP_ADDON_NSP) + { + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, menu[5], strbuf, MAX_CHARACTERS(strbuf)); + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); + breaks++; + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", (selectedNspDumpType == DUMP_ADDON_NSP ? menu[6] : (selectedNspDumpType == DUMP_APP_NSP ? menu[7] : menu[8])), (dumpCfg.nspDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0])); breaks += 2; uiRefreshDisplay(); @@ -4444,7 +4676,7 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = (selectedNspDumpType == DUMP_APP_NSP ? resultShowNspAppDumpMenu : (selectedNspDumpType == DUMP_PATCH_NSP ? resultShowNspPatchDumpMenu : resultShowNspAddOnDumpMenu)); @@ -4457,65 +4689,91 @@ UIResult uiProcess() menu = batchModeMenuItems; - if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titleAppCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitleAppCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && nandUserTitleAppCount)) + char tmp[128] = {'\0'}; + strbuf[0] = '\0'; + + if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titleAppCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitleAppCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && emmcTitleAppCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[1], (dumpCfg.batchDumpCfg.dumpAppTitles ? "Yes" : "No")); - breaks++; + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[1], (dumpCfg.batchDumpCfg.dumpAppTitles ? "Yes" : "No")); + strcat(strbuf, tmp); } - if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titlePatchCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitlePatchCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && nandUserTitlePatchCount)) + if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titlePatchCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitlePatchCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && emmcTitlePatchCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[2], (dumpCfg.batchDumpCfg.dumpPatchTitles ? "Yes" : "No")); - breaks++; + if (strlen(strbuf)) strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[2], (dumpCfg.batchDumpCfg.dumpPatchTitles ? "Yes" : "No")); + strcat(strbuf, tmp); } - if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titleAddOnCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitleAddOnCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && nandUserTitleAddOnCount)) + if ((dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL && titleAddOnCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD && sdCardTitleAddOnCount) || (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_EMMC && emmcTitleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[3], (dumpCfg.batchDumpCfg.dumpAddOnTitles ? "Yes" : "No")); + if (strlen(strbuf)) strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[3], (dumpCfg.batchDumpCfg.dumpAddOnTitles ? "Yes" : "No")); + strcat(strbuf, tmp); + } + + if (strlen(strbuf)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); breaks++; } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[4], (dumpCfg.batchDumpCfg.isFat32 ? "Yes" : "No")); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[5], (dumpCfg.batchDumpCfg.removeConsoleData ? "Yes" : "No")); - breaks++; + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s", menu[5], (dumpCfg.batchDumpCfg.removeConsoleData ? "Yes" : "No")); if (dumpCfg.batchDumpCfg.removeConsoleData) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[6], (dumpCfg.batchDumpCfg.tiklessDump ? "Yes" : "No")); + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[6], (dumpCfg.batchDumpCfg.tiklessDump ? "Yes" : "No")); + strcat(strbuf, tmp); + } + + strcat(strbuf, " | "); + snprintf(tmp, MAX_CHARACTERS(tmp), "%s%s", menu[7], (dumpCfg.batchDumpCfg.npdmAcidRsaPatch ? "Yes" : "No")); + strcat(strbuf, tmp); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); + breaks++; + + if (dumpCfg.batchDumpCfg.dumpPatchTitles) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[8], (dumpCfg.batchDumpCfg.dumpDeltaFragments ? "Yes" : "No")); breaks++; } - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[7], (dumpCfg.batchDumpCfg.npdmAcidRsaPatch ? "Yes" : "No")); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s | %s%s | %s%s", menu[9], (dumpCfg.batchDumpCfg.skipDumpedTitles ? "Yes" : "No"), menu[10], (dumpCfg.batchDumpCfg.rememberDumpedTitles ? "Yes" : "No"), menu[11], (dumpCfg.batchDumpCfg.haltOnErrors ? "Yes" : "No")); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[8], (dumpCfg.batchDumpCfg.skipDumpedTitles ? "Yes" : "No")); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[12], (dumpCfg.batchDumpCfg.useBrackets ? nspNamingSchemes[1] : nspNamingSchemes[0])); breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[9], (dumpCfg.batchDumpCfg.rememberDumpedTitles ? "Yes" : "No")); - breaks++; - - if ((sdCardTitleAppCount || sdCardTitlePatchCount || sdCardTitleAddOnCount) && (nandUserTitleAppCount || nandUserTitlePatchCount || nandUserTitleAddOnCount)) + if ((sdCardTitleAppCount || sdCardTitlePatchCount || sdCardTitleAddOnCount) && (emmcTitleAppCount || emmcTitlePatchCount || emmcTitleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[10], (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL ? "All (SD card + eMMC)" : (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD ? "SD card" : "eMMC"))); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "%s%s", menu[13], (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_ALL ? "All (SD card + eMMC)" : (dumpCfg.batchDumpCfg.batchModeSrc == BATCH_SOURCE_SDCARD ? "SD card" : "eMMC"))); breaks++; } breaks++; uiRefreshDisplay(); - dumpNintendoSubmissionPackageBatch(&(dumpCfg.batchDumpCfg)); + int ret = dumpNintendoSubmissionPackageBatch(&(dumpCfg.batchDumpCfg)); - waitForButtonPress(); - - uiUpdateFreeSpace(); - - res = resultShowSdCardEmmcBatchModeMenu; + if (ret == -2) + { + uiRefreshDisplay(); + res = resultExit; + } else { + waitForButtonPress(); + + updateFreeSpace(); + res = resultShowSdCardEmmcBatchModeMenu; + } } else if (uiState == stateDumpRawHfs0Partition) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems[selectedPartitionIndex] : hfs0PartitionDumpType2MenuItems[selectedPartitionIndex])); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Raw %s", (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems[selectedPartitionIndex] : hfs0PartitionDumpType2MenuItems[selectedPartitionIndex])); breaks += 2; uiRefreshDisplay(); @@ -4524,12 +4782,12 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowRawHfs0PartitionDumpMenu; } else if (uiState == stateDumpHfs0PartitionData) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems[selectedPartitionIndex] : hfs0PartitionDumpType2MenuItems[selectedPartitionIndex])); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Data %s", (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0PartitionDumpType1MenuItems[selectedPartitionIndex] : hfs0PartitionDumpType2MenuItems[selectedPartitionIndex])); breaks += 2; uiRefreshDisplay(); @@ -4538,12 +4796,12 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowHfs0PartitionDataDumpMenu; } else if (uiState == stateHfs0BrowserGetList) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems[selectedPartitionIndex] : hfs0BrowserType2MenuItems[selectedPartitionIndex])); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, (gameCardInfo.hfs0PartitionCnt == GAMECARD_TYPE1_PARTITION_CNT ? hfs0BrowserType1MenuItems[selectedPartitionIndex] : hfs0BrowserType2MenuItems[selectedPartitionIndex])); breaks += 2; uiPleaseWait(0); @@ -4559,7 +4817,7 @@ 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(hfs0_partition_cnt, selectedPartitionIndex)); + 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)); breaks += 2; uiRefreshDisplay(); @@ -4568,7 +4826,7 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowHfs0Browser; } else if (uiState == stateDumpExeFsSectionData) @@ -4576,12 +4834,14 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, exeFsMenuItems[0]); 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")); + breaks++; + if (!exeFsUpdateFlag) { - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", exeFsSectionDumpMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", exeFsSectionDumpMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); } else { - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to dump: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to dump: ", strbuf, MAX_CHARACTERS(strbuf)); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); @@ -4589,11 +4849,13 @@ UIResult uiProcess() uiRefreshDisplay(); - dumpExeFsSectionData((!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex), exeFsUpdateFlag, true); + u32 curIndex = (!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex); + + dumpExeFsSectionData(curIndex, exeFsUpdateFlag, &(dumpCfg.exeFsDumpCfg)); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = ((menuType == MENUTYPE_GAMECARD && titleAppCount > 1) ? resultShowExeFsSectionDataDumpMenu : resultShowExeFsMenu); } else @@ -4602,12 +4864,14 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, exeFsMenuItems[1]); 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")); + breaks++; + if (!exeFsUpdateFlag) { - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", exeFsSectionBrowserMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", exeFsSectionBrowserMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); } else { - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to browse: ", strbuf, MAX_CHARACTERS(strbuf)); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); @@ -4618,7 +4882,9 @@ UIResult uiProcess() bool exefs_fail = false; - if (readNcaExeFsSection((!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex), exeFsUpdateFlag)) + u32 curIndex = (!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex); + + if (readNcaExeFsSection(curIndex, exeFsUpdateFlag)) { if (getExeFsFileList()) { @@ -4643,12 +4909,14 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (ExeFS)", filenames[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")); + breaks++; + if (!exeFsUpdateFlag) { - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Base application: %s v%s", titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Base application: %s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); } else { - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update: ", strbuf, MAX_CHARACTERS(strbuf)); } uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, strbuf); @@ -4656,11 +4924,13 @@ UIResult uiProcess() uiRefreshDisplay(); - dumpFileFromExeFsSection((!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex), selectedFileIndex, exeFsUpdateFlag, true); + u32 curIndex = (!exeFsUpdateFlag ? selectedAppIndex : selectedPatchIndex); + + dumpFileFromExeFsSection(curIndex, selectedFileIndex, exeFsUpdateFlag, &(dumpCfg.exeFsDumpCfg)); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowExeFsSectionBrowser; } else if (uiState == stateDumpRomFsSectionData) @@ -4670,19 +4940,21 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, romFsMenuItems[0]); 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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", romFsSectionDumpMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", romFsSectionDumpMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); curIndex = selectedAppIndex; break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to dump: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to dump: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedPatchIndex; break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC to dump: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC to dump: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedAddOnIndex; break; default: @@ -4694,11 +4966,11 @@ UIResult uiProcess() uiRefreshDisplay(); - dumpRomFsSectionData(curIndex, curRomFsType, true); + dumpRomFsSectionData(curIndex, curRomFsType, &(dumpCfg.romFsDumpCfg)); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = ((menuType == MENUTYPE_GAMECARD && titleAppCount > 1) ? resultShowRomFsSectionDataDumpMenu : resultShowRomFsMenu); } else @@ -4709,19 +4981,21 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, romFsMenuItems[1]); 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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); curIndex = selectedAppIndex; break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to browse: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedPatchIndex; break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC to browse: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedAddOnIndex; break; default: @@ -4762,17 +5036,19 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, romFsMenuItems[1]); 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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s v%s", romFsSectionBrowserMenuItems[1], baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update to browse: ", strbuf, MAX_CHARACTERS(strbuf)); break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC to browse: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC to browse: ", strbuf, MAX_CHARACTERS(strbuf)); break; default: break; @@ -4799,14 +5075,8 @@ UIResult uiProcess() if (romfs_fail) { - if (romFsBrowserEntries != NULL) - { - free(romFsBrowserEntries); - romFsBrowserEntries = NULL; - } - + freeRomFsBrowserEntries(); if (curRomFsType == ROMFS_TYPE_PATCH) freeBktrContext(); - freeRomFsContext(); breaks += 2; @@ -4821,19 +5091,21 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual File Dump: %s (RomFS)", filenames[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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Base application: %s v%s", titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Base application: %s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); curIndex = selectedAppIndex; break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedPatchIndex; break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedAddOnIndex; break; default: @@ -4847,15 +5119,16 @@ UIResult uiProcess() if (romFsBrowserEntries[selectedFileIndex].type == ROMFS_ENTRY_FILE) { - dumpFileFromRomFsSection(curIndex, romFsBrowserEntries[selectedFileIndex].offset, curRomFsType, true); + dumpFileFromRomFsSection(curIndex, romFsBrowserEntries[selectedFileIndex].offset, curRomFsType, &(dumpCfg.romFsDumpCfg)); } else { // Unexpected condition uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Error: the selected entry is not a file!"); + breaks += 2; } waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowRomFsSectionBrowser; } else if (uiState == stateRomFsSectionBrowserCopyDir) @@ -4865,19 +5138,21 @@ UIResult uiProcess() uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, "Manual Directory Dump: romfs:%s (RomFS)", curRomFsPath); 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")); + breaks++; + switch(curRomFsType) { case ROMFS_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "Base application: %s v%s", titleName[selectedAppIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "Base application: %s v%s", baseAppEntries[selectedAppIndex].name, baseAppEntries[selectedAppIndex].versionStr); curIndex = selectedAppIndex; break; case ROMFS_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, "Update: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, "Update: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedPatchIndex; break; case ROMFS_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, "DLC: ", strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, "DLC: ", strbuf, MAX_CHARACTERS(strbuf)); curIndex = selectedAddOnIndex; break; default: @@ -4889,23 +5164,23 @@ UIResult uiProcess() uiRefreshDisplay(); - dumpCurrentDirFromRomFsSection(curIndex, curRomFsType, true); + dumpCurrentDirFromRomFsSection(curIndex, curRomFsType, &(dumpCfg.romFsDumpCfg)); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowRomFsSectionBrowser; } else if (uiState == stateDumpGameCardCertificate) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, gameCardMenuItems[4]); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_TITLE_RGB, gameCardMenuItems[5]); breaks += 2; dumpGameCardCertificate(); waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowGameCardMenu; } else if (uiState == stateDumpTicket) @@ -4921,15 +5196,14 @@ UIResult uiProcess() switch(curTikType) { case TICKET_TYPE_APP: - convertTitleVersionToDecimal(titleAppVersion[selectedAppInfoIndex], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s%s | %016lX v%s (BASE)", menu[2], titleName[selectedAppInfoIndex], titleAppTitleID[selectedAppInfoIndex], versionStr); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s%s | %016lX v%s (BASE)", menu[2], baseAppEntries[selectedAppInfoIndex].name, baseAppEntries[selectedAppInfoIndex].titleId, baseAppEntries[selectedAppInfoIndex].versionStr); break; case TICKET_TYPE_PATCH: - retrieveDescriptionForPatchOrAddOn(titlePatchTitleID[selectedPatchIndex], titlePatchVersion[selectedPatchIndex], false, true, menu[2], strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedPatchIndex, false, true, menu[2], strbuf, MAX_CHARACTERS(strbuf)); strcat(strbuf, " (UPD)"); break; case TICKET_TYPE_ADDON: - retrieveDescriptionForPatchOrAddOn(titleAddOnTitleID[selectedAddOnIndex], titleAddOnVersion[selectedAddOnIndex], true, true, menu[2], strbuf, MAX_ELEMENTS(strbuf)); + retrieveDescriptionForPatchOrAddOn(selectedAddOnIndex, true, true, menu[2], strbuf, MAX_CHARACTERS(strbuf)); strcat(strbuf, " (DLC)"); break; default: @@ -4945,7 +5219,7 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowTicketMenu; } else if (uiState == stateUpdateNSWDBXml) @@ -4957,7 +5231,7 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowUpdateMenu; } else if (uiState == stateUpdateApplication) @@ -4969,7 +5243,7 @@ UIResult uiProcess() waitForButtonPress(); - uiUpdateFreeSpace(); + updateFreeSpace(); res = resultShowUpdateMenu; } diff --git a/source/ui.h b/source/ui.h index 519aca4..ab142e2 100644 --- a/source/ui.h +++ b/source/ui.h @@ -34,7 +34,7 @@ #define ROMFS_MAX_ELEMENTS 12 #define SDCARD_MAX_ELEMENTS 3 #define ORPHAN_MAX_ELEMENTS 12 -#define BATCH_MAX_ELEMENTS 12 +#define BATCH_MAX_ELEMENTS 14 #define OPTIONS_X_START_POS (35 * CHAR_PT_SIZE) #define OPTIONS_X_END_POS (OPTIONS_X_START_POS + (6 * CHAR_PT_SIZE)) @@ -185,17 +185,17 @@ void uiStatusMsg(const char *fmt, ...); void uiUpdateStatusMsg(); -void uiPleaseWait(u8 wait); +void uiClearStatusMsg(); -void uiUpdateFreeSpace(); +void uiPleaseWait(u8 wait); void uiClearScreen(); void uiPrintHeadline(); -void uiDeinit(); +bool uiInit(); -int uiInit(); +void uiDeinit(); void uiSetState(UIState state); diff --git a/source/util.c b/source/util.c index 07f9c2d..2ccadd1 100644 --- a/source/util.c +++ b/source/util.c @@ -15,9 +15,11 @@ #include #include #include +#include #include "dumper.h" #include "fs_ext.h" +#include "keys.h" #include "ui.h" #include "util.h" #include "fatfs/ff.h" @@ -32,16 +34,19 @@ extern int scroll; extern curMenuType menuType; +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 *nswReleasesChildrenImageSize = "imagesize"; const char *nswReleasesChildrenTitleID = "titleid"; const char *nswReleasesChildrenImgCrc = "imgcrc"; const char *nswReleasesChildrenReleaseName = "releasename"; @@ -52,77 +57,32 @@ 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; + dumpOptions dumpCfg; bool keysFileAvailable = false; -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; - -FsDeviceOperator fsOperatorInstance; -FsEventNotifier fsGameCardEventNotifier; -Handle fsGameCardEventHandle; -Event fsGameCardKernelEvent; -UEvent exitEvent; +static pthread_t gameCardDetectionThread; +static UEvent exitEvent; AppletType programAppletType; -bool runningSxOs = false; +char cfwDirStr[32] = {'\0'}; -bool gameCardInserted; - -u64 gameCardSize = 0, trimmedCardSize = 0; -char gameCardSizeStr[32] = {'\0'}, trimmedCardSizeStr[32] = {'\0'}; - -u64 gameCardUpdateTitleID = 0; -u32 gameCardUpdateVersion = 0; -char gameCardUpdateVersionStr[128] = {'\0'}; - -u8 *hfs0_header = NULL; -u64 hfs0_offset = 0, hfs0_size = 0; -u32 hfs0_partition_cnt = 0; - -u8 *partitionHfs0Header = NULL; -u64 partitionHfs0HeaderOffset = 0, partitionHfs0HeaderSize = 0; -u32 partitionHfs0FileCount = 0, partitionHfs0StrTableSize = 0; - -u32 titleAppCount = 0; -u64 *titleAppTitleID = NULL; -u32 *titleAppVersion = NULL; -FsStorageId *titleAppStorageId = NULL; - -u32 titlePatchCount = 0; -u64 *titlePatchTitleID = NULL; -u32 *titlePatchVersion = NULL; -FsStorageId *titlePatchStorageId = NULL; - -u32 titleAddOnCount = 0; -u64 *titleAddOnTitleID = NULL; -u32 *titleAddOnVersion = NULL; -FsStorageId *titleAddOnStorageId = NULL; - -u32 sdCardTitleAppCount = 0; -u32 sdCardTitlePatchCount = 0; -u32 sdCardTitleAddOnCount = 0; - -u32 nandUserTitleAppCount = 0; -u32 nandUserTitlePatchCount = 0; -u32 nandUserTitleAddOnCount = 0; - -static bool gamecardInfoLoaded = false, sdCardAndEmmcTitleInfoLoaded = false; +gamecard_ctx_t gameCardInfo; +u32 titleAppCount = 0, titlePatchCount = 0, titleAddOnCount = 0; +u32 sdCardTitleAppCount = 0, sdCardTitlePatchCount = 0, sdCardTitleAddOnCount = 0; +u32 emmcTitleAppCount = 0, emmcTitlePatchCount = 0, emmcTitleAddOnCount = 0; u32 gameCardSdCardEmmcPatchCount = 0, gameCardSdCardEmmcAddOnCount = 0; -char **titleName = NULL; -char **fixedTitleName = NULL; -char **titleAuthor = NULL; -char **titleAppVersionStr = NULL; -u8 **titleIcon = NULL; +base_app_ctx_t *baseAppEntries = NULL; +patch_addon_ctx_t *patchEntries = NULL, *addOnEntries = NULL; + +static volatile bool gameCardInfoLoaded = false; +static bool sdCardAndEmmcTitleInfoLoaded = false; exefs_ctx_t exeFsContext; romfs_ctx_t romFsContext; @@ -132,7 +92,16 @@ char curRomFsPath[NAME_BUF_LEN] = {'\0'}; u32 curRomFsDirOffset = 0; romfs_browser_entry *romFsBrowserEntries = NULL; +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; + u8 *dumpBuf = NULL; +u8 *gcReadBuf = NULL; u8 *ncaCtrBuf = NULL; orphan_patch_addon_entry *orphanEntries = NULL; @@ -140,11 +109,16 @@ u32 orphanEntriesCnt = 0; char strbuf[NAME_BUF_LEN] = {'\0'}; -char appLaunchPath[NAME_BUF_LEN] = {'\0'}; +char appLaunchPath[NAME_BUF_LEN / 2] = {'\0'}; FsStorage fatFsStorage; static bool openBis = false, mountBisFatFs = false; +u64 freeSpace = 0; +char freeSpaceStr[32] = {'\0'}; + +browser_entry_size_info *hfs0ExeFsEntriesSizes = NULL; + void loadConfig() { // Set default configuration values @@ -154,13 +128,19 @@ void loadConfig() dumpCfg.xciDumpCfg.calcCrc = true; dumpCfg.nspDumpCfg.isFat32 = true; + dumpCfg.nspDumpCfg.useNoIntroLookup = true; dumpCfg.nspDumpCfg.npdmAcidRsaPatch = true; dumpCfg.batchDumpCfg.isFat32 = true; dumpCfg.batchDumpCfg.npdmAcidRsaPatch = true; dumpCfg.batchDumpCfg.skipDumpedTitles = true; + dumpCfg.batchDumpCfg.haltOnErrors = true; dumpCfg.batchDumpCfg.batchModeSrc = BATCH_SOURCE_ALL; + dumpCfg.exeFsDumpCfg.isFat32 = true; + + dumpCfg.romFsDumpCfg.isFat32 = true; + FILE *configFile = fopen(configPath, "rb"); if (!configFile) return; @@ -208,45 +188,183 @@ void saveConfig() if (write_res != sizeof(dumpOptions)) unlink(configPath); } -bool isGameCardInserted() +static bool isGameCardInserted() { - bool inserted; - if (R_FAILED(fsDeviceOperatorIsGameCardInserted(&fsOperatorInstance, &inserted))) return false; + bool inserted = false; + fsDeviceOperatorIsGameCardInserted(&(gameCardInfo.fsOperatorInstance), &inserted); return inserted; } -void fsGameCardDetectionThreadFunc(void *arg) +static void changeAtomicBool(volatile bool *ptr, bool value) { - int idx; - Result rc; + if (!ptr) return; + + if (value) + { + __atomic_test_and_set(ptr, __ATOMIC_SEQ_CST); + } else { + __atomic_clear(ptr, __ATOMIC_SEQ_CST); + } +} + +static void *fsGameCardDetectionThreadFunc(void *arg) +{ + Result result = 0; + int idx = 0; + + Waiter gameCardEventWaiter = waiterForEvent(&(gameCardInfo.fsGameCardKernelEvent)); + Waiter exitEventWaiter = waiterForUEvent(&exitEvent); + + changeAtomicBool(&gameCardInfoLoaded, false); + + /* Retrieve initial gamecard status */ + bool curGcStatus = isGameCardInserted(); + changeAtomicBool(&(gameCardInfo.isInserted), curGcStatus); while(true) { - rc = waitMulti(&idx, -1, waiterForEvent(&fsGameCardKernelEvent), waiterForUEvent(&exitEvent)); - if (R_SUCCEEDED(rc)) - { - if (idx == 0) - { - // Retrieve current gamecard status - gameCardInserted = isGameCardInserted(); - eventClear(&fsGameCardKernelEvent); - } else { - break; - } - } + // Wait until an event is triggered + result = waitMulti(&idx, -1, gameCardEventWaiter, exitEventWaiter); + if (R_FAILED(result)) continue; + + // Exit event triggered + if (idx == 1) break; + + // Retrieve current gamecard status + // Only proceed if we're dealing with a status change + curGcStatus = isGameCardInserted(); + changeAtomicBool(&(gameCardInfo.isInserted), curGcStatus); + if (!curGcStatus && gameCardInfoLoaded) changeAtomicBool(&gameCardInfoLoaded, false); } - waitMulti(&idx, 0, waiterForEvent(&fsGameCardKernelEvent), waiterForUEvent(&exitEvent)); + waitMulti(&idx, 0, gameCardEventWaiter, exitEventWaiter); + + return 0; +} + +static bool createGameCardDetectionThread() +{ + int ret1 = 0, ret2 = 0; + pthread_attr_t attr; + + ret1 = pthread_attr_init(&attr); + if (ret1 != 0) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to initialize thread attributes! (%d)", __func__, ret1); + return false; + } + + ret1 = pthread_create(&gameCardDetectionThread, &attr, &fsGameCardDetectionThreadFunc, NULL); + if (ret1 != 0) uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to create thread! (%d)", __func__, ret1); + + ret2 = pthread_attr_destroy(&attr); + if (ret2 != 0) uiDrawString(STRING_X_POS, (ret1 == 0 ? 8 : STRING_Y_POS(1)), FONT_COLOR_ERROR_RGB, "%s: failed to destroy thread attributes! (%d)", __func__, ret2); + + if (ret1 != 0 || ret2 != 0) return false; + + return true; +} + +static void closeGameCardHandle() +{ + svcCloseHandle(gameCardInfo.fsGameCardHandle.value); + gameCardInfo.fsGameCardHandle.value = 0; +} + +void closeGameCardStoragePartition() +{ + if (!gameCardInfo.curIStorageIndex || gameCardInfo.curIStorageIndex >= ISTORAGE_PARTITION_INVALID) return; + + fsStorageClose(&(gameCardInfo.fsGameCardStorage)); + memset(&(gameCardInfo.fsGameCardStorage), 0, sizeof(FsStorage)); + + closeGameCardHandle(); + + gameCardInfo.curIStorageIndex = ISTORAGE_PARTITION_NONE; +} + +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(); + + u8 i; + u32 idx = (u32)(partitionIndex - 1); + Result res1 = 0, res2 = 0, out = 0; + + // 10 tries + for(i = 0; i < 10; i++) + { + // First try to retrieve the IStorage partition handle using the current gamecard handle + res1 = fsOpenGameCardStorage(&(gameCardInfo.fsGameCardStorage), &(gameCardInfo.fsGameCardHandle), idx); + if (R_SUCCEEDED(res1)) break; + + // If the previous call failed, we may have an invalid handle, so let's close the current one and try to retrieve a new one + closeGameCardHandle(); + res2 = fsDeviceOperatorGetGameCardHandle(&(gameCardInfo.fsOperatorInstance), &(gameCardInfo.fsGameCardHandle)); + } + + if (R_SUCCEEDED(res1) && R_SUCCEEDED(res2)) + { + // Update current IStorage index + gameCardInfo.curIStorageIndex = partitionIndex; + } else { + // res2 takes precedence over res1 + out = (R_FAILED(res2) ? res2 : res1); + + // Close leftover gamecard handle + closeGameCardHandle(); + } + + // If everything worked properly, a functional gamecard handle + IStorage handle are guaranteed up to this point + return out; +} + +Result readGameCardStoragePartition(u64 off, void *buf, size_t len) +{ + if (!gameCardInfo.curIStorageIndex || gameCardInfo.curIStorageIndex >= ISTORAGE_PARTITION_INVALID || !buf || !len) return MAKERESULT(Module_Libnx, LibnxError_IoError); + + // Optimization for reads that are already aligned to MEDIA_UNIT_SIZE bytes + if (!(off % MEDIA_UNIT_SIZE) && !(len % MEDIA_UNIT_SIZE)) return fsStorageRead(&(gameCardInfo.fsGameCardStorage), off, buf, len); + + Result result; + u8 *outBuf = (u8*)buf; + + u64 block_start_offset = (off - (off % MEDIA_UNIT_SIZE)); + u64 block_end_offset = (u64)round_up(off + len, MEDIA_UNIT_SIZE); + u64 block_size = (block_end_offset - block_start_offset); + + u64 block_size_used = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size); + u64 output_block_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (off - block_start_offset)) : len); + + result = fsStorageRead(&(gameCardInfo.fsGameCardStorage), block_start_offset, gcReadBuf, block_size_used); + if (R_FAILED(result)) return result; + + memcpy(outBuf, gcReadBuf + (off - block_start_offset), output_block_size); + + if (block_size > GAMECARD_READ_BUFFER_SIZE) return readGameCardStoragePartition(off + output_block_size, outBuf + output_block_size, len - output_block_size); + + return result; +} + +static Result getGameCardStoragePartitionSize(u64 *out) +{ + if (!gameCardInfo.curIStorageIndex || gameCardInfo.curIStorageIndex >= ISTORAGE_PARTITION_INVALID || !out) return MAKERESULT(Module_Libnx, LibnxError_IoError); + + return fsStorageGetSize(&(gameCardInfo.fsGameCardStorage), (s64*)out); } bool mountSysEmmcPartition() { FATFS fs; - Result result = fsOpenBisStorage(&fatFsStorage, FsBisStorageId_System); + Result result = fsOpenBisStorage(&fatFsStorage, FsBisPartitionId_System); if (R_FAILED(result)) { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Error: failed to open BIS System partition! (0x%08X)", result); + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to open BIS System partition! (0x%08X)", __func__, result); return false; } @@ -255,7 +373,7 @@ bool mountSysEmmcPartition() FRESULT fr = f_mount(&fs, BIS_MOUNT_NAME, 1); if (fr != FR_OK) { - uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Error: failed to mount BIS System partition! (%u)", fr); + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to mount BIS System partition! (%u)", __func__, fr); return false; } @@ -270,12 +388,14 @@ void unmountSysEmmcPartition() if (openBis) fsStorageClose(&fatFsStorage); } -bool isServiceRunning(const char *serviceName) +static bool isServiceRunning(const char *name) { - if (!serviceName || !strlen(serviceName)) return false; + if (!name || !strlen(name)) return false; Handle handle; - bool running = R_FAILED(smRegisterService(&handle, serviceName, false, 1)); + SmServiceName serviceName = smEncodeName(name); + Result result = smRegisterService(&handle, serviceName, false, 1); + bool running = R_FAILED(result); svcCloseHandle(handle); @@ -284,59 +404,132 @@ bool isServiceRunning(const char *serviceName) return running; } -bool checkSxOsServices() +static void retrieveRunningCfwDir() { - return (isServiceRunning("tx") && !isServiceRunning("rnx")); + bool txService = isServiceRunning("tx"); + bool rnxService = isServiceRunning("rnx"); + + if (!txService && !rnxService) + { + // Atmosphere + snprintf(cfwDirStr, MAX_CHARACTERS(cfwDirStr), CFW_PATH_ATMOSPHERE); + } else + if (txService && !rnxService) + { + // SX OS + snprintf(cfwDirStr, MAX_CHARACTERS(cfwDirStr), CFW_PATH_SXOS); + } else { + // ReiNX + snprintf(cfwDirStr, MAX_CHARACTERS(cfwDirStr), CFW_PATH_REINX); + } } void delay(u8 seconds) { if (!seconds) return; - u64 nanoseconds = seconds * (u64)1000000000; + u64 nanoseconds = (seconds * (u64)1000000000); svcSleepThread(nanoseconds); uiRefreshDisplay(); } -void formatETAString(u64 curTime, char *output, u32 outSize) +static void createOutputDirectories() { - if (!output || !outSize) return; - - u64 i, hour = 0, min = 0, sec = 0; - - for(i = 0; i < curTime; i++) - { - sec++; - if (sec == 60) - { - sec = 0; - min++; - if (min == 60) - { - min = 0; - hour++; - } - } - } - - snprintf(output, outSize, "%02luH%02luM%02luS", hour, min, sec); + 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); + mkdir(EXEFS_DUMP_PATH, 0744); + mkdir(ROMFS_DUMP_PATH, 0744); + mkdir(CERT_DUMP_PATH, 0744); + mkdir(BATCH_OVERRIDES_PATH, 0744); + mkdir(TICKET_PATH, 0744); } -void freeStringsPtr(char **var) +static bool getSdCardFreeSpace(u64 *out) { - if (var) - { - u64 i; - for(i = 0; var[i]; i++) free(var[i]); - free(var); - } + Result result; + FsFileSystem *sdfs = NULL; + u64 size = 0; + + sdfs = fsdevGetDeviceFileSystem("sdmc:"); + if (!sdfs) + { + uiStatusMsg("%s: fsdevGetDefaultFileSystem failed!", __func__); + return false; + } + + result = fsFsGetFreeSpace(sdfs, "/", (s64*)&size); + if (R_FAILED(result)) + { + uiStatusMsg("%s: fsFsGetFreeSpace failed! (0x%08X)", __func__, result); + return false; + } + + *out = size; + + return true; +} + +void convertSize(u64 size, char *out, size_t outSize) +{ + if (!out || !outSize) return; + + double bytes = (double)size; + + if (bytes < 1000.0) + { + snprintf(out, outSize, "%.0lf B", bytes); + } else + if (bytes < (10.0 * KiB)) + { + snprintf(out, outSize, "%.2lf KiB", floor((bytes * 100.0) / KiB) / 100.0); + } else + if (bytes < (100.0 * KiB)) + { + snprintf(out, outSize, "%.1lf KiB", floor((bytes * 10.0) / KiB) / 10.0); + } else + if (bytes < (1000.0 * KiB)) + { + snprintf(out, outSize, "%.0lf KiB", floor(bytes / KiB)); + } else + if (bytes < (10.0 * MiB)) + { + snprintf(out, outSize, "%.2lf MiB", floor((bytes * 100.0) / MiB) / 100.0); + } else + if (bytes < (100.0 * MiB)) + { + snprintf(out, outSize, "%.1lf MiB", floor((bytes * 10.0) / MiB) / 10.0); + } else + if (bytes < (1000.0 * MiB)) + { + snprintf(out, outSize, "%.0lf MiB", floor(bytes / MiB)); + } else + if (bytes < (10.0 * GiB)) + { + snprintf(out, outSize, "%.2lf GiB", floor((bytes * 100.0) / GiB) / 100.0); + } else + if (bytes < (100.0 * GiB)) + { + snprintf(out, outSize, "%.1lf GiB", floor((bytes * 10.0) / GiB) / 10.0); + } else { + snprintf(out, outSize, "%.0lf GiB", floor(bytes / GiB)); + } +} + +void updateFreeSpace() +{ + getSdCardFreeSpace(&freeSpace); + convertSize(freeSpace, freeSpaceStr, MAX_CHARACTERS(freeSpaceStr)); } void initExeFsContext() { + exeFsContext.storageId = NcmStorageId_None; memset(&(exeFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(exeFsContext.ncaId), 0, sizeof(NcmNcaId)); + memset(&(exeFsContext.ncaId), 0, sizeof(NcmContentId)); memset(&(exeFsContext.aes_ctx), 0, sizeof(Aes128CtrContext)); exeFsContext.exefs_offset = 0; exeFsContext.exefs_size = 0; @@ -350,8 +543,11 @@ void initExeFsContext() void freeExeFsContext() { + if (exeFsContext.storageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); + exeFsContext.storageId = NcmStorageId_None; + // Remember to close this NCM service resource - serviceClose(&(exeFsContext.ncmStorage.s)); + ncmContentStorageClose(&(exeFsContext.ncmStorage)); memset(&(exeFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); if (exeFsContext.exefs_entries != NULL) @@ -369,8 +565,9 @@ void freeExeFsContext() void initRomFsContext() { + romFsContext.storageId = NcmStorageId_None; memset(&(romFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(romFsContext.ncaId), 0, sizeof(NcmNcaId)); + memset(&(romFsContext.ncaId), 0, sizeof(NcmContentId)); memset(&(romFsContext.aes_ctx), 0, sizeof(Aes128CtrContext)); romFsContext.section_offset = 0; romFsContext.section_size = 0; @@ -387,8 +584,11 @@ void initRomFsContext() void freeRomFsContext() { + if (romFsContext.storageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); + romFsContext.storageId = NcmStorageId_None; + // Remember to close this NCM service resource - serviceClose(&(romFsContext.ncmStorage.s)); + ncmContentStorageClose(&(romFsContext.ncmStorage)); memset(&(romFsContext.ncmStorage), 0, sizeof(NcmContentStorage)); if (romFsContext.romfs_dir_entries != NULL) @@ -406,8 +606,9 @@ void freeRomFsContext() void initBktrContext() { + bktrContext.storageId = NcmStorageId_None; memset(&(bktrContext.ncmStorage), 0, sizeof(NcmContentStorage)); - memset(&(bktrContext.ncaId), 0, sizeof(NcmNcaId)); + memset(&(bktrContext.ncaId), 0, sizeof(NcmContentId)); memset(&(bktrContext.aes_ctx), 0, sizeof(Aes128CtrContext)); bktrContext.section_offset = 0; bktrContext.section_size = 0; @@ -430,8 +631,11 @@ void initBktrContext() void freeBktrContext() { + if (bktrContext.storageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); + bktrContext.storageId = NcmStorageId_None; + // Remember to close this NCM service resource - serviceClose(&(bktrContext.ncmStorage.s)); + ncmContentStorageClose(&(bktrContext.ncmStorage)); memset(&(bktrContext.ncmStorage), 0, sizeof(NcmContentStorage)); if (bktrContext.relocation_block != NULL) @@ -459,142 +663,124 @@ void freeBktrContext() } } -void freeGameCardInfo() +static void freeGameCardInfo() { - if (hfs0_header != NULL) + u32 i; + + memset(&(gameCardInfo.header), 0, sizeof(gamecard_header_t)); + + if (gameCardInfo.rootHfs0Header) { - free(hfs0_header); - hfs0_header = NULL; + free(gameCardInfo.rootHfs0Header); + gameCardInfo.rootHfs0Header = NULL; } - hfs0_offset = 0; - hfs0_size = 0; - hfs0_partition_cnt = 0; - - if (partitionHfs0Header != NULL) + if (gameCardInfo.hfs0Partitions) { - free(partitionHfs0Header); - partitionHfs0Header = NULL; + for(i = 0; i < gameCardInfo.hfs0PartitionCnt; i++) + { + if (gameCardInfo.hfs0Partitions[i].header) free(gameCardInfo.hfs0Partitions[i].header); + } + + free(gameCardInfo.hfs0Partitions); + gameCardInfo.hfs0Partitions = NULL; } - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; + gameCardInfo.hfs0PartitionCnt = 0; - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + gameCardInfo.size = 0; + memset(gameCardInfo.sizeStr, 0, sizeof(gameCardInfo.sizeStr)); - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + gameCardInfo.trimmedSize = 0; + memset(gameCardInfo.trimmedSizeStr, 0, sizeof(gameCardInfo.trimmedSizeStr)); - gameCardUpdateTitleID = 0; - gameCardUpdateVersion = 0; - memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr)); + for(i = 0; i < ISTORAGE_PARTITION_CNT; i++) gameCardInfo.IStoragePartitionSizes[i] = 0; + + gameCardInfo.updateTitleId = 0; + gameCardInfo.updateVersion = 0; + memset(gameCardInfo.updateVersionStr, 0, sizeof(gameCardInfo.updateVersionStr)); + + closeGameCardStoragePartition(); } -void freeTitleInfo() +static void freeOrphanPatchOrAddOnList() { + if (orphanEntries != NULL) + { + free(orphanEntries); + orphanEntries = NULL; + } + + orphanEntriesCnt = 0; +} + +static void freeTitleInfo() +{ + u32 i; + + if (baseAppEntries && titleAppCount) + { + for(i = 0; i < titleAppCount; i++) + { + if (baseAppEntries[i].icon) free(baseAppEntries[i].icon); + } + } + + if (baseAppEntries) + { + free(baseAppEntries); + baseAppEntries = NULL; + } + + if (patchEntries) + { + free(patchEntries); + patchEntries = NULL; + } + + if (addOnEntries) + { + free(addOnEntries); + addOnEntries = NULL; + } + titleAppCount = 0; - - if (titleAppTitleID != NULL) - { - free(titleAppTitleID); - titleAppTitleID = NULL; - } - - if (titleAppVersion != NULL) - { - free(titleAppVersion); - titleAppVersion = NULL; - } - - if (titleAppVersionStr != NULL) - { - freeStringsPtr(titleAppVersionStr); - titleAppVersionStr = NULL; - } - - if (titleAppStorageId != NULL) - { - free(titleAppStorageId); - titleAppStorageId = NULL; - } - titlePatchCount = 0; - - if (titlePatchTitleID != NULL) - { - free(titlePatchTitleID); - titlePatchTitleID = NULL; - } - - if (titlePatchVersion != NULL) - { - free(titlePatchVersion); - titlePatchVersion = NULL; - } - - if (titlePatchStorageId != NULL) - { - free(titlePatchStorageId); - titlePatchStorageId = NULL; - } - titleAddOnCount = 0; - if (titleAddOnTitleID != NULL) - { - free(titleAddOnTitleID); - titleAddOnTitleID = NULL; - } - - if (titleAddOnVersion != NULL) - { - free(titleAddOnVersion); - titleAddOnVersion = NULL; - } - - if (titleAddOnStorageId != NULL) - { - free(titleAddOnStorageId); - titleAddOnStorageId = NULL; - } - sdCardTitleAppCount = 0; sdCardTitlePatchCount = 0; sdCardTitleAddOnCount = 0; - nandUserTitleAppCount = 0; - nandUserTitlePatchCount = 0; - nandUserTitleAddOnCount = 0; + emmcTitleAppCount = 0; + emmcTitlePatchCount = 0; + emmcTitleAddOnCount = 0; - if (titleName != NULL) - { - freeStringsPtr(titleName); - titleName = NULL; - } + gameCardSdCardEmmcPatchCount = 0; + gameCardSdCardEmmcAddOnCount = 0; - if (fixedTitleName != NULL) + freeOrphanPatchOrAddOnList(); +} + +void freeRomFsBrowserEntries() +{ + if (romFsBrowserEntries != NULL) { - freeStringsPtr(fixedTitleName); - fixedTitleName = NULL; - } - - if (titleAuthor != NULL) - { - freeStringsPtr(titleAuthor); - titleAuthor = NULL; - } - - if (titleIcon != NULL) - { - freeStringsPtr((char**)titleIcon); - titleIcon = NULL; + free(romFsBrowserEntries); + romFsBrowserEntries = NULL; } } -void freeGlobalData() +void freeHfs0ExeFsEntriesSizes() +{ + if (hfs0ExeFsEntriesSizes) + { + free(hfs0ExeFsEntriesSizes); + hfs0ExeFsEntriesSizes = NULL; + } +} + +static void freeGlobalData() { freeGameCardInfo(); @@ -606,20 +792,409 @@ void freeGlobalData() freeBktrContext(); - if (romFsBrowserEntries != NULL) - { - free(romFsBrowserEntries); - romFsBrowserEntries = NULL; - } + freeRomFsBrowserEntries(); - freeOrphanPatchOrAddOnList(); + freeHfs0ExeFsEntriesSizes(); } -bool listTitlesByType(NcmContentMetaDatabase *ncmDb, u8 filter) +void consoleErrorScreen(const char *fmt, ...) { - if (!ncmDb || (filter != META_DB_REGULAR_APPLICATION && filter != META_DB_PATCH && filter != META_DB_ADDON)) + consoleInit(NULL); + + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + + printf("\nPress any button to exit.\n"); + + while(appletMainLoop()) { - uiStatusMsg("listTitlesByType: invalid parameters (0x%02X filter).", filter); + hidScanInput(); + + u64 keysDown = hidKeysDown(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; + + consoleUpdate(NULL); + } + + consoleExit(NULL); +} + +static bool initServices() +{ + Result result; + + /* Initialize ncm service */ + result = ncmInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize ncm service! (0x%08X)", __func__, result); + return false; + } + + initNcm = true; + + /* Initialize ns service */ + result = nsInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize ns service! (0x%08X)", __func__, result); + return false; + } + + initNs = true; + + /* Initialize csrng service */ + result = csrngInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize csrng service! (0x%08X)", __func__, result); + return false; + } + + initCsrng = true; + + /* Initialize spl service */ + result = splInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize spl service! (0x%08X)", __func__, result); + return false; + } + + initSpl = true; + + /* Initialize pm:dmnt service */ + result = pmdmntInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize pm:dmnt service! (0x%08X)", __func__, result); + return false; + } + + initPmdmnt = true; + + /* Initialize pl service */ + result = plInitialize(); + if (R_FAILED(result)) + { + consoleErrorScreen("%s: failed to initialize pl service! (0x%08X)", __func__, result); + return false; + } + + initPl = true; + + return true; +} + +static void deinitServices() +{ + /* Denitialize pl service */ + if (initPl) plExit(); + + /* Denitialize pm:dmnt service */ + if (initPmdmnt) pmdmntExit(); + + /* Denitialize spl service */ + if (initSpl) splExit(); + + /* Denitialize csrng service */ + if (initCsrng) csrngExit(); + + /* Denitialize ns service */ + if (initNs) nsExit(); + + /* Denitialize ncm service */ + if (initNcm) ncmExit(); +} + +bool initApplicationResources(int argc, char **argv) +{ + Result result = 0; + bool success = false; + + /* Copy launch path */ + if (argc > 0 && argv && !envIsNso()) + { + for(int i = 0; i < argc; i++) + { + 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]); + break; + } + } + } + + /* Zero out gamecard info struct */ + memset(&gameCardInfo, 0, sizeof(gamecard_ctx_t)); + + /* Zero out NCA keyset */ + memset(&nca_keyset, 0, sizeof(nca_keyset_t)); + + /* Init ExeFS context */ + initExeFsContext(); + + /* Init RomFS context */ + initRomFsContext(); + + /* Init BKTR context */ + initBktrContext(); + + /* Make sure output directories exist */ + createOutputDirectories(); + + /* Check if the Lockpick_RCM keys file is available */ + keysFileAvailable = checkIfFileExists(KEYS_FILE_PATH); + + /* 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 */ + 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) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the dump buffer!", __func__); + goto out; + } + + /* Allocate memory for the gamecard read buffer */ + gcReadBuf = calloc(GAMECARD_READ_BUFFER_SIZE, sizeof(u8)); + if (!gcReadBuf) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the gamecard read buffer!", __func__); + goto out; + } + + /* Allocate memory for the NCA AES-CTR operation buffer */ + ncaCtrBuf = calloc(NCA_CTR_BUFFER_SIZE, sizeof(u8)); + if (!ncaCtrBuf) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for the NCA AES-CTR operation buffer!", __func__); + goto out; + } + + /* Open device operator */ + result = fsOpenDeviceOperator(&(gameCardInfo.fsOperatorInstance)); + if (R_FAILED(result)) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to open device operator! (0x%08X)", __func__, result); + goto out; + } + + openFsDevOp = true; + + /* Open gamecard detection event notifier */ + result = fsOpenGameCardDetectionEventNotifier(&(gameCardInfo.fsGameCardEventNotifier)); + if (R_FAILED(result)) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to open gamecard detection event notifier! (0x%08X)", __func__, result); + goto out; + } + + openGcEvtNotifier = true; + + /* Retrieve gamecard detection kernel event */ + result = fsEventNotifierGetEventHandle(&(gameCardInfo.fsGameCardEventNotifier), &(gameCardInfo.fsGameCardKernelEvent), true); + if (R_FAILED(result)) + { + uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "%s: failed to retrieve gamecard detection event handle! (0x%08X)", __func__, result); + goto out; + } + + loadGcKernEvt = true; + + /* Create usermode exit event */ + ueventCreate(&exitEvent, false); + + /* Create and start gamecard detection thread */ + if (!createGameCardDetectionThread()) goto out; + + gcThreadInit = true; + + /* Load settings from configuration file */ + loadConfig(); + + /* Set output status */ + success = true; + +out: + if (!success) + { + uiRefreshDisplay(); + delay(5); + } + + return success; +} + +void deinitApplicationResources() +{ + /* Free global resources */ + freeGlobalData(); + + /* Save current settings to configuration file */ + saveConfig(); + + if (gcThreadInit) + { + /* Signal the exit event to terminate the gamecard detection thread */ + ueventSignal(&exitEvent); + + /* Wait for the gamecard detection thread to exit */ + pthread_join(gameCardDetectionThread, NULL); + } + + /* Close gamecard detection kernel event */ + if (loadGcKernEvt) eventClose(&(gameCardInfo.fsGameCardKernelEvent)); + + /* Close gamecard detection event notifier */ + if (openGcEvtNotifier) fsEventNotifierClose(&(gameCardInfo.fsGameCardEventNotifier)); + + /* Close device operator */ + if (openFsDevOp) fsDeviceOperatorClose(&(gameCardInfo.fsOperatorInstance)); + + /* Free NCA AES-CTR operation buffer */ + if (ncaCtrBuf) free(ncaCtrBuf); + + /* Free gamecard read buffer */ + if (gcReadBuf) free(gcReadBuf); + + /* Free general purpose dump buffer */ + if (dumpBuf) free(dumpBuf); + + /* Free filename buffer */ + if (filenameBuffer) free(filenameBuffer); + + /* Unmount BIS System partition from the eMMC */ + unmountSysEmmcPartition(); + + /* Disable CPU boost mode */ + appletSetCpuBoostMode(ApmCpuBoostMode_Disabled); + + /* 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(); + + /* Deinitialize services */ + deinitServices(); +} + +void formatETAString(u64 curTime, char *out, size_t outSize) +{ + if (!out || !outSize) return; + + u64 i, hour = 0, min = 0, sec = 0; + + for(i = 0; i < curTime; i++) + { + sec++; + if (sec == 60) + { + sec = 0; + min++; + if (min == 60) + { + min = 0; + hour++; + } + } + } + + 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); +} + +static void convertTitleVersionToDotNotation(u32 titleVersion, u8 *outMajor, u8 *outMinor, u8 *outMicro, u16 *outBugfix) +{ + if (!outMajor || !outMinor || !outMicro || !outBugfix) return; + + *outMajor = (u8)((titleVersion >> 26) & 0x3F); + *outMinor = (u8)((titleVersion >> 20) & 0x3F); + *outMicro = (u8)((titleVersion >> 16) & 0xF); + *outBugfix = (u16)titleVersion; +} + +static void generateVersionDottedStr(u32 titleVersion, char *outBuf, size_t outBufSize) +{ + if (!outBuf || !outBufSize) return; + + u8 major = 0, minor = 0, micro = 0; + u16 bugfix = 0; + + convertTitleVersionToDotNotation(titleVersion, &major, &minor, µ, &bugfix); + snprintf(outBuf, outBufSize, "%u (%u.%u.%u.%u)", titleVersion, major, minor, micro, bugfix); +} + +static bool listTitlesByType(NcmContentMetaDatabase *ncmDb, NcmContentMetaType metaType) +{ + if (!ncmDb || (metaType != NcmContentMetaType_Application && metaType != NcmContentMetaType_Patch && metaType != NcmContentMetaType_AddOnContent)) + { + uiStatusMsg("%s: invalid parameters to list titles by type from storage!", __func__); return false; } @@ -627,26 +1202,25 @@ bool listTitlesByType(NcmContentMetaDatabase *ncmDb, u8 filter) Result result; - NcmApplicationContentMetaKey *titleList = NULL; - NcmApplicationContentMetaKey *titleListTmp = NULL; + NcmApplicationContentMetaKey *titleList = NULL, *titleListTmp = NULL; size_t titleListSize = sizeof(NcmApplicationContentMetaKey); u32 i, written = 0, total = 0; - u64 *titleIDs = NULL, *tmpTIDs = NULL; - u32 *versions = NULL, *tmpVersions = NULL; + base_app_ctx_t *tmpAppEntries = NULL; + patch_addon_ctx_t *tmpPatchAddOnEntries = NULL; titleList = calloc(1, titleListSize); if (!titleList) { - uiStatusMsg("listTitlesByType: unable to allocate memory for the ApplicationContentMetaKey struct (0x%02X filter).", filter); + uiStatusMsg("%s: unable to allocate memory for the ApplicationContentMetaKey struct! (meta type: 0x%02X).", __func__, (u8)metaType); goto out; } - result = ncmContentMetaDatabaseListApplication(ncmDb, filter, titleList, titleListSize, &written, &total); + result = ncmContentMetaDatabaseListApplication(ncmDb, (s32*)&total, (s32*)&written, titleList, 1, metaType); if (R_FAILED(result)) { - uiStatusMsg("listTitlesByType: ncmContentMetaDatabaseListApplication failed! (0x%08X) (0x%02X filter).", result, filter); + uiStatusMsg("%s: ncmContentMetaDatabaseListApplication failed! (0x%08X) (meta type: 0x%02X).", __func__, result, (u8)metaType); goto out; } @@ -666,140 +1240,123 @@ bool listTitlesByType(NcmContentMetaDatabase *ncmDb, u8 filter) titleList = titleListTmp; memset(titleList, 0, titleListSize); - result = ncmContentMetaDatabaseListApplication(ncmDb, filter, titleList, titleListSize, &written, &total); + result = ncmContentMetaDatabaseListApplication(ncmDb, (s32*)&total, (s32*)&written, titleList, (s32)total, metaType); if (R_SUCCEEDED(result)) { if (written != total) { - uiStatusMsg("listTitlesByType: title count mismatch in ncmContentMetaDatabaseListApplication (%u != %u) (0x%02X filter).", written, total, filter); + uiStatusMsg("%s: title count mismatch in ncmContentMetaDatabaseListApplication! (%u != %u) (meta type: 0x%02X).", __func__, written, total, (u8)metaType); proceed = false; } } else { - uiStatusMsg("listTitlesByType: ncmContentMetaDatabaseListApplication failed! (0x%08X) (0x%02X filter).", result, filter); + uiStatusMsg("%s: ncmContentMetaDatabaseListApplication failed! (0x%08X) (meta type: 0x%02X).", __func__, result, (u8)metaType); proceed = false; } } else { - uiStatusMsg("listTitlesByType: error reallocating output buffer for ncmContentMetaDatabaseListApplication (%u %s) (0x%02X filter).", total, (total == 1 ? "entry" : "entries"), filter); + uiStatusMsg("%s: error reallocating output buffer for ncmContentMetaDatabaseListApplication! (%u %s) (meta type: 0x%02X).", __func__, total, (total == 1 ? "entry" : "entries"), (u8)metaType); proceed = false; } } if (!proceed) goto out; - titleIDs = calloc(total, sizeof(u64)); - versions = calloc(total, sizeof(u32)); - - if (!titleIDs || !versions) - { - memError = true; - goto out; - } - - for(i = 0; i < total; i++) - { - titleIDs[i] = titleList[i].metaRecord.titleId; - versions[i] = titleList[i].metaRecord.version; - } - - free(titleList); - titleList = NULL; - - if (filter == META_DB_REGULAR_APPLICATION) + if (metaType == NcmContentMetaType_Application) { // If ptr == NULL, realloc will essentially act as a malloc - tmpTIDs = realloc(titleAppTitleID, (titleAppCount + total) * sizeof(u64)); - tmpVersions = realloc(titleAppVersion, (titleAppCount + total) * sizeof(u32)); - - if (tmpTIDs && tmpVersions) + tmpAppEntries = realloc(baseAppEntries, (titleAppCount + total) * sizeof(base_app_ctx_t)); + if (tmpAppEntries) { - titleAppTitleID = tmpTIDs; - tmpTIDs = NULL; - memcpy(titleAppTitleID + titleAppCount, titleIDs, total * sizeof(u64)); + baseAppEntries = tmpAppEntries; + tmpAppEntries = NULL; - titleAppVersion = tmpVersions; - tmpVersions = NULL; - memcpy(titleAppVersion + titleAppCount, versions, total * sizeof(u32)); + memset(baseAppEntries + titleAppCount, 0, total * sizeof(base_app_ctx_t)); + + for(i = 0; i < total; i++) + { + baseAppEntries[titleAppCount + i].titleId = titleList[i].key.id; + baseAppEntries[titleAppCount + i].version = titleList[i].key.version; + baseAppEntries[titleAppCount + i].ncmIndex = i; + generateVersionDottedStr(titleList[i].key.version, baseAppEntries[titleAppCount + i].versionStr, VERSION_STR_LEN); + } titleAppCount += total; success = true; } else { - if (tmpTIDs) titleAppTitleID = tmpTIDs; - if (tmpVersions) titleAppVersion = tmpVersions; memError = true; } } else - if (filter == META_DB_PATCH) + if (metaType == NcmContentMetaType_Patch) { // If ptr == NULL, realloc will essentially act as a malloc - tmpTIDs = realloc(titlePatchTitleID, (titlePatchCount + total) * sizeof(u64)); - tmpVersions = realloc(titlePatchVersion, (titlePatchCount + total) * sizeof(u32)); - - if (tmpTIDs && tmpVersions) + tmpPatchAddOnEntries = realloc(patchEntries, (titlePatchCount + total) * sizeof(patch_addon_ctx_t)); + if (tmpPatchAddOnEntries) { - titlePatchTitleID = tmpTIDs; - tmpTIDs = NULL; - memcpy(titlePatchTitleID + titlePatchCount, titleIDs, total * sizeof(u64)); + patchEntries = tmpPatchAddOnEntries; + tmpPatchAddOnEntries = NULL; - titlePatchVersion = tmpVersions; - tmpVersions = NULL; - memcpy(titlePatchVersion + titlePatchCount, versions, total * sizeof(u32)); + memset(patchEntries + titlePatchCount, 0, total * sizeof(patch_addon_ctx_t)); + + for(i = 0; i < total; i++) + { + patchEntries[titlePatchCount + i].titleId = titleList[i].key.id; + patchEntries[titlePatchCount + i].version = titleList[i].key.version; + patchEntries[titlePatchCount + i].ncmIndex = i; + generateVersionDottedStr(titleList[i].key.version, patchEntries[titlePatchCount + i].versionStr, VERSION_STR_LEN); + } titlePatchCount += total; success = true; } else { - if (tmpTIDs) titlePatchTitleID = tmpTIDs; - if (tmpVersions) titlePatchVersion = tmpVersions; memError = true; } } else - if (filter == META_DB_ADDON) + if (metaType == NcmContentMetaType_AddOnContent) { // If ptr == NULL, realloc will essentially act as a malloc - tmpTIDs = realloc(titleAddOnTitleID, (titleAddOnCount + total) * sizeof(u64)); - tmpVersions = realloc(titleAddOnVersion, (titleAddOnCount + total) * sizeof(u32)); - - if (tmpTIDs && tmpVersions) + tmpPatchAddOnEntries = realloc(addOnEntries, (titleAddOnCount + total) * sizeof(patch_addon_ctx_t)); + if (tmpPatchAddOnEntries) { - titleAddOnTitleID = tmpTIDs; - tmpTIDs = NULL; - memcpy(titleAddOnTitleID + titleAddOnCount, titleIDs, total * sizeof(u64)); + addOnEntries = tmpPatchAddOnEntries; + tmpPatchAddOnEntries = NULL; - titleAddOnVersion = tmpVersions; - tmpVersions = NULL; - memcpy(titleAddOnVersion + titleAddOnCount, versions, total * sizeof(u32)); + memset(addOnEntries + titleAddOnCount, 0, total * sizeof(patch_addon_ctx_t)); + + for(i = 0; i < total; i++) + { + addOnEntries[titleAddOnCount + i].titleId = titleList[i].key.id; + addOnEntries[titleAddOnCount + i].version = titleList[i].key.version; + addOnEntries[titleAddOnCount + i].ncmIndex = i; + generateVersionDottedStr(titleList[i].key.version, addOnEntries[titleAddOnCount + i].versionStr, VERSION_STR_LEN); + } titleAddOnCount += total; success = true; } else { - if (tmpTIDs) titleAddOnTitleID = tmpTIDs; - if (tmpVersions) titleAddOnVersion = tmpVersions; memError = true; } } out: - if (memError) uiStatusMsg("listTitlesByType: failed to allocate memory for TID/version buffer! (0x%02X filter).", filter); + if (memError) uiStatusMsg("%s: failed to reallocate entry buffer! (meta type: 0x%02X).", __func__, (u8)metaType); - if (titleIDs) free(titleIDs); - if (versions) free(versions); if (titleList) free(titleList); return success; } -bool getTitleIDAndVersionList(FsStorageId curStorageId, bool loadBaseApps, bool loadPatches, bool loadAddOns) +static bool getTitleIDAndVersionList(NcmStorageId storageId, bool loadBaseApps, bool loadPatches, bool loadAddOns) { - if ((curStorageId != FsStorageId_GameCard && curStorageId != FsStorageId_SdCard && curStorageId != FsStorageId_NandUser) || (!loadBaseApps && !loadPatches && !loadAddOns)) + if ((storageId != NcmStorageId_GameCard && storageId != NcmStorageId_SdCard && storageId != NcmStorageId_BuiltInUser) || (!loadBaseApps && !loadPatches && !loadAddOns)) { - uiStatusMsg("getTitleIDAndVersionList: invalid parameters to retrieve Title ID + version list!"); + uiStatusMsg("%s: invalid parameters to retrieve Title ID + version list!", __func__); return false; } /* Check if the SD card is really mounted */ - if (curStorageId == FsStorageId_SdCard && fsdevGetDefaultFileSystem() == NULL) return true; + if (storageId == NcmStorageId_SdCard && fsdevGetDeviceFileSystem("sdmc:") == NULL) return true; bool listApp = false, listPatch = false, listAddOn = false, success = false; @@ -807,107 +1364,72 @@ bool getTitleIDAndVersionList(FsStorageId curStorageId, bool loadBaseApps, bool NcmContentMetaDatabase ncmDb; u32 i; - FsStorageId *tmpStorages = NULL; u32 curAppCount = titleAppCount, curPatchCount = titlePatchCount, curAddOnCount = titleAddOnCount; - result = ncmOpenContentMetaDatabase(curStorageId, &ncmDb); + result = ncmOpenContentMetaDatabase(&ncmDb, storageId); if (R_FAILED(result)) { - if (curStorageId == FsStorageId_SdCard && result == 0x21005) + if (storageId == NcmStorageId_SdCard && result == 0x21005) { // If the SD card is mounted, but is isn't currently used by HOS because of some weird reason, just filter this particular error and continue // This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted return true; } else { - uiStatusMsg("getTitleIDAndVersionList: ncmOpenContentMetaDatabase failed for storage ID %u! (0x%08X)", result, curStorageId); + uiStatusMsg("%s: ncmOpenContentMetaDatabase failed for storage ID %u! (0x%08X)", __func__, result, storageId); return false; } } if (loadBaseApps) { - listApp = listTitlesByType(&ncmDb, META_DB_REGULAR_APPLICATION); + listApp = listTitlesByType(&ncmDb, NcmContentMetaType_Application); if (listApp && titleAppCount > curAppCount) { - tmpStorages = realloc(titleAppStorageId, titleAppCount * sizeof(FsStorageId)); - if (tmpStorages) - { - titleAppStorageId = tmpStorages; - - tmpStorages = NULL; - - for(i = curAppCount; i < titleAppCount; i++) titleAppStorageId[i] = curStorageId; - } else { - titleAppCount = curAppCount; - listApp = false; - } + for(i = curAppCount; i < titleAppCount; i++) baseAppEntries[i].storageId = storageId; } } if (loadPatches) { - listPatch = listTitlesByType(&ncmDb, META_DB_PATCH); + listPatch = listTitlesByType(&ncmDb, NcmContentMetaType_Patch); if (listPatch && titlePatchCount > curPatchCount) { - tmpStorages = realloc(titlePatchStorageId, titlePatchCount * sizeof(FsStorageId)); - if (tmpStorages) - { - titlePatchStorageId = tmpStorages; - - tmpStorages = NULL; - - for(i = curPatchCount; i < titlePatchCount; i++) titlePatchStorageId[i] = curStorageId; - } else { - titlePatchCount = curPatchCount; - listPatch = false; - } + for(i = curPatchCount; i < titlePatchCount; i++) patchEntries[i].storageId = storageId; } } if (loadAddOns) { - listAddOn = listTitlesByType(&ncmDb, META_DB_ADDON); + listAddOn = listTitlesByType(&ncmDb, NcmContentMetaType_AddOnContent); if (listAddOn && titleAddOnCount > curAddOnCount) { - tmpStorages = realloc(titleAddOnStorageId, titleAddOnCount * sizeof(FsStorageId)); - if (tmpStorages) - { - titleAddOnStorageId = tmpStorages; - - tmpStorages = NULL; - - for(i = curAddOnCount; i < titleAddOnCount; i++) titleAddOnStorageId[i] = curStorageId; - } else { - titleAddOnCount = curAddOnCount; - listAddOn = false; - } + for(i = curAddOnCount; i < titleAddOnCount; i++) addOnEntries[i].storageId = storageId; } } success = (listApp || listPatch || listAddOn); - serviceClose(&(ncmDb.s)); + ncmContentMetaDatabaseClose(&ncmDb); return success; } -bool loadTitlesFromSdCardAndEmmc(u8 titleType) +bool loadTitlesFromSdCardAndEmmc(NcmContentMetaType metaType) { - if (menuType != MENUTYPE_GAMECARD || !titleAppCount || !titleAppTitleID || (titleType != META_DB_PATCH && titleType != META_DB_ADDON)) return false; + if (menuType != MENUTYPE_GAMECARD || (metaType != NcmContentMetaType_Patch && metaType != NcmContentMetaType_AddOnContent)) return false; - if ((titleType == META_DB_PATCH && gameCardSdCardEmmcPatchCount) || (titleType == META_DB_ADDON && gameCardSdCardEmmcAddOnCount)) return true; + if ((metaType == NcmContentMetaType_Patch && gameCardSdCardEmmcPatchCount) || (metaType == NcmContentMetaType_AddOnContent && gameCardSdCardEmmcAddOnCount)) return true; u8 i; - u32 curPatchCount = titlePatchCount; - u32 curAddOnCount = titleAddOnCount; + u32 curPatchCount = titlePatchCount, curAddOnCount = titleAddOnCount; for(i = 0; i < 2; i++) { - FsStorageId curStorageId = (i == 0 ? FsStorageId_SdCard : FsStorageId_NandUser); + NcmStorageId curStorageId = (i == 0 ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser); - if (!getTitleIDAndVersionList(curStorageId, false, (titleType == META_DB_PATCH), (titleType == META_DB_ADDON))) continue; + if (!getTitleIDAndVersionList(curStorageId, false, (metaType == NcmContentMetaType_Patch), (metaType == NcmContentMetaType_AddOnContent))) continue; - if (titleType == META_DB_PATCH) + if (metaType == NcmContentMetaType_Patch) { if (titlePatchCount > curPatchCount) { @@ -915,15 +1437,15 @@ bool loadTitlesFromSdCardAndEmmc(u8 titleType) gameCardSdCardEmmcPatchCount = newPatchCount; - if (curStorageId == FsStorageId_SdCard) + if (curStorageId == NcmStorageId_SdCard) { sdCardTitlePatchCount = newPatchCount; } else { - nandUserTitlePatchCount = newPatchCount; + emmcTitlePatchCount = newPatchCount; } } } else - if (titleType == META_DB_ADDON) + if (metaType == NcmContentMetaType_AddOnContent) { if (titleAddOnCount > curAddOnCount) { @@ -931,119 +1453,75 @@ bool loadTitlesFromSdCardAndEmmc(u8 titleType) gameCardSdCardEmmcAddOnCount = newAddOnCount; - if (curStorageId == FsStorageId_SdCard) + if (curStorageId == NcmStorageId_SdCard) { sdCardTitleAddOnCount = newAddOnCount; } else { - nandUserTitleAddOnCount = newAddOnCount; + emmcTitleAddOnCount = newAddOnCount; } } } } - if ((titleType == META_DB_PATCH && gameCardSdCardEmmcPatchCount) || (titleType == META_DB_ADDON && gameCardSdCardEmmcAddOnCount)) return true; + if ((metaType == NcmContentMetaType_Patch && gameCardSdCardEmmcPatchCount) || (metaType == NcmContentMetaType_AddOnContent && gameCardSdCardEmmcAddOnCount)) return true; return false; } -void freeTitlesFromSdCardAndEmmc(u8 titleType) +void freeTitlesFromSdCardAndEmmc(NcmContentMetaType metaType) { - if (menuType != MENUTYPE_GAMECARD || !titleAppCount || !titleAppTitleID || (titleType != META_DB_PATCH && titleType != META_DB_ADDON) || (titleType == META_DB_PATCH && (!titlePatchCount || !titlePatchTitleID || !titlePatchVersion || !titlePatchStorageId || !gameCardSdCardEmmcPatchCount)) || (titleType == META_DB_ADDON && (!titleAddOnCount || !titleAddOnTitleID || !titleAddOnVersion || !titleAddOnStorageId || !gameCardSdCardEmmcAddOnCount))) return; + if (menuType != MENUTYPE_GAMECARD || (metaType != NcmContentMetaType_Patch && metaType != NcmContentMetaType_AddOnContent) || (metaType == NcmContentMetaType_Patch && (!titlePatchCount || !gameCardSdCardEmmcPatchCount)) || (metaType == NcmContentMetaType_AddOnContent && (!titleAddOnCount || !gameCardSdCardEmmcAddOnCount))) return; - u64 *tmpTIDs = NULL; - u32 *tmpVersions = NULL; - FsStorageId *tmpStorages = NULL; + patch_addon_ctx_t *tmpPatchAddOnEntries = NULL; - if (titleType == META_DB_PATCH) + if (metaType == NcmContentMetaType_Patch) { if ((titlePatchCount - gameCardSdCardEmmcPatchCount) > 0) { - tmpTIDs = realloc(titlePatchTitleID, (titlePatchCount - gameCardSdCardEmmcPatchCount) * sizeof(u64)); - tmpVersions = realloc(titlePatchVersion, (titlePatchCount - gameCardSdCardEmmcPatchCount) * sizeof(u32)); - tmpStorages = realloc(titlePatchStorageId, (titlePatchCount - gameCardSdCardEmmcPatchCount) * sizeof(FsStorageId)); - - if (tmpTIDs != NULL && tmpVersions != NULL && tmpStorages != NULL) + tmpPatchAddOnEntries = realloc(patchEntries, (titlePatchCount - gameCardSdCardEmmcPatchCount) * sizeof(patch_addon_ctx_t)); + if (tmpPatchAddOnEntries != NULL) { - titlePatchTitleID = tmpTIDs; - - titlePatchVersion = tmpVersions; - - titlePatchStorageId = tmpStorages; - } else { - if (tmpTIDs != NULL) titlePatchTitleID = tmpTIDs; - if (tmpVersions != NULL) titlePatchVersion = tmpVersions; - if (tmpStorages != NULL) titlePatchStorageId = tmpStorages; + patchEntries = tmpPatchAddOnEntries; + tmpPatchAddOnEntries = NULL; } } else { - free(titlePatchTitleID); - titlePatchTitleID = NULL; - - free(titlePatchVersion); - titlePatchVersion = NULL; - - free(titlePatchStorageId); - titlePatchStorageId = NULL; + free(patchEntries); + patchEntries = NULL; } titlePatchCount -= gameCardSdCardEmmcPatchCount; gameCardSdCardEmmcPatchCount = 0; sdCardTitlePatchCount = 0; - nandUserTitlePatchCount = 0; + emmcTitlePatchCount = 0; } else { if ((titleAddOnCount - gameCardSdCardEmmcAddOnCount) > 0) { - tmpTIDs = realloc(titleAddOnTitleID, (titleAddOnCount - gameCardSdCardEmmcAddOnCount) * sizeof(u64)); - tmpVersions = realloc(titleAddOnVersion, (titleAddOnCount - gameCardSdCardEmmcAddOnCount) * sizeof(u32)); - tmpStorages = realloc(titleAddOnStorageId, (titleAddOnCount - gameCardSdCardEmmcAddOnCount) * sizeof(FsStorageId)); - - if (tmpTIDs != NULL && tmpVersions != NULL && tmpStorages != NULL) + tmpPatchAddOnEntries = realloc(addOnEntries, (titleAddOnCount - gameCardSdCardEmmcAddOnCount) * sizeof(patch_addon_ctx_t)); + if (tmpPatchAddOnEntries != NULL) { - titleAddOnTitleID = tmpTIDs; - - titleAddOnVersion = tmpVersions; - - titleAddOnStorageId = tmpStorages; - } else { - if (tmpTIDs != NULL) titleAddOnTitleID = tmpTIDs; - if (tmpVersions != NULL) titleAddOnVersion = tmpVersions; - if (tmpStorages != NULL) titleAddOnStorageId = tmpStorages; + addOnEntries = tmpPatchAddOnEntries; + tmpPatchAddOnEntries = NULL; } } else { - free(titleAddOnTitleID); - titleAddOnTitleID = NULL; - - free(titleAddOnVersion); - titleAddOnVersion = NULL; - - free(titleAddOnStorageId); - titleAddOnStorageId = NULL; + free(addOnEntries); + addOnEntries = NULL; } titleAddOnCount -= gameCardSdCardEmmcAddOnCount; gameCardSdCardEmmcAddOnCount = 0; sdCardTitleAddOnCount = 0; - nandUserTitleAddOnCount = 0; + emmcTitleAddOnCount = 0; } } -void convertTitleVersionToDecimal(u32 version, char *versionBuf, size_t versionBufSize) -{ - u8 major = (u8)((version >> 26) & 0x3F); - u8 minor = (u8)((version >> 20) & 0x3F); - u8 micro = (u8)((version >> 16) & 0xF); - u16 bugfix = (u16)version; - - snprintf(versionBuf, versionBufSize, "%u (%u.%u.%u.%u)", version, major, minor, micro, bugfix); -} - -bool getTitleControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize, u8 **iconBuf) +static bool getCachedBaseApplicationNacpMetadata(u64 titleID, char *nameBuf, size_t nameBufSize, char *authorBuf, size_t authorBufSize, u8 **iconBuf) { // At least the name must be retrieved - if (!titleID || !nameBuf || !nameBufSize || (authorBuf != NULL && !authorBufSize)) + if (!nameBuf || !nameBufSize || (authorBuf && !authorBufSize)) { - uiStatusMsg("getTitleControlNacp: invalid parameters to retrieve Control.nacp."); + uiStatusMsg("%s: invalid parameters to retrieve Control.nacp!", __func__); return false; } @@ -1056,7 +1534,7 @@ bool getTitleControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *auth buf = calloc(1, sizeof(NsApplicationControlData)); if (buf) { - result = nsGetApplicationControlData(1, titleID, buf, sizeof(NsApplicationControlData), &outsize); + result = nsGetApplicationControlData(NsApplicationControlSource_Storage, titleID, buf, sizeof(NsApplicationControlData), &outsize); if (R_SUCCEEDED(result)) { if (outsize >= sizeof(buf->nacp)) @@ -1064,11 +1542,11 @@ bool getTitleControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *auth result = nacpGetLanguageEntry(&buf->nacp, &langentry); if (R_SUCCEEDED(result)) { - strncpy(nameBuf, langentry->name, nameBufSize); - if (authorBuf != NULL && authorBufSize) strncpy(authorBuf, langentry->author, authorBufSize); + snprintf(nameBuf, nameBufSize, langentry->name); + if (authorBuf && authorBufSize) snprintf(authorBuf, authorBufSize, langentry->author); getNameAndAuthor = true; } else { - uiStatusMsg("getTitleControlNacp: GetLanguageEntry failed! (0x%08X)", result); + uiStatusMsg("%s: GetLanguageEntry failed! (0x%08X)", __func__, result); } if (iconBuf != NULL) @@ -1079,15 +1557,15 @@ bool getTitleControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *auth success = (iconBuf != NULL ? (getNameAndAuthor && getIcon) : getNameAndAuthor); } else { - uiStatusMsg("getTitleControlNacp: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes", outsize, sizeof(buf->nacp)); + uiStatusMsg("%s: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes", __func__, outsize, sizeof(buf->nacp)); } } else { - uiStatusMsg("getTitleControlNacp: GetApplicationControlData failed! (0x%08X)", result); + uiStatusMsg("%s: GetApplicationControlData failed! (0x%08X)", __func__, result); } free(buf); } else { - uiStatusMsg("getTitleControlNacp: Unable to allocate memory for the ns service operations."); + uiStatusMsg("%s: unable to allocate memory for the ns service operations!", __func__); } return success; @@ -1095,6 +1573,8 @@ bool getTitleControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *auth void removeIllegalCharacters(char *name) { + if (!name || !strlen(name)) return; + u32 i, len = strlen(name); for (i = 0; i < len; i++) { @@ -1102,212 +1582,294 @@ void removeIllegalCharacters(char *name) } } -void createOutputDirectories() -{ - 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); - mkdir(EXEFS_DUMP_PATH, 0744); - mkdir(ROMFS_DUMP_PATH, 0744); - mkdir(CERT_DUMP_PATH, 0744); - mkdir(BATCH_OVERRIDES_PATH, 0744); - mkdir(TICKET_PATH, 0744); -} - void strtrim(char *str) { - if (!str || !*str) return; + if (!str || !strlen(str)) return; char *start = str; - char *end = start + strlen(str); + char *end = (start + strlen(str)); while(--end >= start) { - if (!isspace(*end)) break; + if (!isspace((unsigned char)*end)) break; } *(++end) = '\0'; - while(isspace(*start)) start++; + while(isspace((unsigned char)*start)) start++; if (start != str) memmove(str, start, end - start + 1); } -bool getRootHfs0Header() +bool retrieveGameCardInfo() { - u32 magic; Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; - char gamecard_header[GAMECARD_HEADER_SIZE] = {'\0'}; + bool success = false; - hfs0_partition_cnt = 0; + u32 i; + hfs0_header header; + hfs0_file_entry entry; - workaroundPartitionZeroAccess(); + u8 major = 0, minor = 0, micro = 0; + u16 bugfix = 0; - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); + // Open normal IStorage partition + result = openGameCardStoragePartition(ISTORAGE_PARTITION_NORMAL); if (R_FAILED(result)) { - uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result); + uiStatusMsg("%s: failed to open normal IStorage partition! (0x%08X)", __func__, result); return false; } - result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0); + // Retrieve normal IStorage partition size + result = getGameCardStoragePartitionSize(&(gameCardInfo.IStoragePartitionSizes[0])); if (R_FAILED(result)) { - uiStatusMsg("getRootHfs0Header: OpenGameCardStorage failed! (0x%08X)", result); + uiStatusMsg("%s: failed to retrieve size for normal IStorage partition! (0x%08X)", __func__, result); return false; } - result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE); + // Read gamecard header + result = readGameCardStoragePartition(0, &(gameCardInfo.header), sizeof(gamecard_header_t)); if (R_FAILED(result)) { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result); - fsStorageClose(&gameCardStorage); - return false; + uiStatusMsg("%s: failed to read %lu bytes long gamecard header! (0x%08X)", __func__, sizeof(gamecard_header_t), result); + goto out; } - u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR]; + if (bswap_32(gameCardInfo.header.magic) != GAMECARD_HEADER_MAGIC) + { + uiStatusMsg("%s: invalid gamecard header magic word! (0x%08X)", __func__, bswap_32(gameCardInfo.header.magic)); + goto out; + } - switch(cardSize) + switch(gameCardInfo.header.size) { case 0xFA: // 1 GiB - gameCardSize = GAMECARD_SIZE_1GiB; + gameCardInfo.size = GAMECARD_SIZE_1GiB; break; case 0xF8: // 2 GiB - gameCardSize = GAMECARD_SIZE_2GiB; + gameCardInfo.size = GAMECARD_SIZE_2GiB; break; case 0xF0: // 4 GiB - gameCardSize = GAMECARD_SIZE_4GiB; + gameCardInfo.size = GAMECARD_SIZE_4GiB; break; case 0xE0: // 8 GiB - gameCardSize = GAMECARD_SIZE_8GiB; + gameCardInfo.size = GAMECARD_SIZE_8GiB; break; case 0xE1: // 16 GiB - gameCardSize = GAMECARD_SIZE_16GiB; + gameCardInfo.size = GAMECARD_SIZE_16GiB; break; case 0xE2: // 32 GiB - gameCardSize = GAMECARD_SIZE_32GiB; + gameCardInfo.size = GAMECARD_SIZE_32GiB; break; default: - uiStatusMsg("getRootHfs0Header: Invalid gamecard size value: 0x%02X", cardSize); - fsStorageClose(&gameCardStorage); - return false; + uiStatusMsg("%s: invalid gamecard size value! (0x%02X)", __func__, gameCardInfo.header.size); + goto out; } - convertSize(gameCardSize, gameCardSizeStr, MAX_ELEMENTS(gameCardSizeStr)); + convertSize(gameCardInfo.size, gameCardInfo.sizeStr, MAX_CHARACTERS(gameCardInfo.sizeStr)); - memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64)); - trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE)); - convertSize(trimmedCardSize, trimmedCardSizeStr, MAX_ELEMENTS(trimmedCardSizeStr)); + gameCardInfo.trimmedSize = (sizeof(gamecard_header_t) + (gameCardInfo.header.validDataEndAddr * MEDIA_UNIT_SIZE)); + convertSize(gameCardInfo.trimmedSize, gameCardInfo.trimmedSizeStr, MAX_CHARACTERS(gameCardInfo.trimmedSizeStr)); - memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64)); - memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64)); - - hfs0_header = malloc(hfs0_size); - if (!hfs0_header) + gameCardInfo.rootHfs0Header = calloc(1, gameCardInfo.header.rootHfs0HeaderSize); + if (!gameCardInfo.rootHfs0Header) { - uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!"); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - hfs0_offset = 0; - hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; + uiStatusMsg("%s: unable to allocate memory for the root HFS0 header!", __func__); + goto out; } - result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size); + result = readGameCardStoragePartition(gameCardInfo.header.rootHfs0HeaderOffset, gameCardInfo.rootHfs0Header, gameCardInfo.header.rootHfs0HeaderSize); if (R_FAILED(result)) { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = 0; - hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; + uiStatusMsg("%s: failed to read %lu bytes long root HFS0 header! (0x%08X)", __func__, gameCardInfo.header.rootHfs0HeaderSize, result); + goto out; } - memcpy(&magic, hfs0_header, sizeof(u32)); - magic = bswap_32(magic); - if (magic != HFS0_MAGIC) + memcpy(&header, gameCardInfo.rootHfs0Header, sizeof(hfs0_header)); + + if (bswap_32(header.magic) != HFS0_MAGIC) { - uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = 0; - hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; + uiStatusMsg("%s: invalid magic word in root HFS0 header! (0x%08X)", __func__, bswap_32(header.magic)); + goto out; } - memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32)); - - fsStorageClose(&gameCardStorage); - - return true; -} - -void getGameCardUpdateInfo() -{ - Result result; - FsGameCardHandle handle; - - gameCardUpdateTitleID = 0; - gameCardUpdateVersion = 0; - - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_FAILED(result)) + if (!header.file_cnt) { - uiStatusMsg("getGameCardUpdateInfo: GetGameCardHandle failed! (0x%08X)", result); - return; + uiStatusMsg("%s: invalid file count in root HFS0 header!", __func__); + goto out; + } + + if (!header.str_table_size) + { + uiStatusMsg("%s: invalid string table size in root HFS0 header!", __func__); + goto out; + } + + gameCardInfo.hfs0PartitionCnt = header.file_cnt; + + // Retrieve partition data + gameCardInfo.hfs0Partitions = calloc(gameCardInfo.hfs0PartitionCnt, sizeof(hfs0_partition_info)); + if (!gameCardInfo.hfs0Partitions) + { + uiStatusMsg("%s: unable to allocate memory for HFS0 partition headers!", __func__); + goto out; + } + + for(i = 0; i < gameCardInfo.hfs0PartitionCnt; i++) + { + memcpy(&entry, gameCardInfo.rootHfs0Header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); + + if (!entry.file_size) + { + uiStatusMsg("%s: invalid size for %s HFS0 partition!", __func__, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i)); + goto out; + } + + gameCardInfo.hfs0Partitions[i].size = entry.file_size; + + // Check if we're dealing with the secure HFS0 partition + if (i == (gameCardInfo.hfs0PartitionCnt - 1)) + { + // The partition offset must be zero, because the secure HFS0 partition is stored at the start of the secure IStorage partition + gameCardInfo.hfs0Partitions[i].offset = 0; + + // Open secure IStorage partition + result = openGameCardStoragePartition(ISTORAGE_PARTITION_SECURE); + if (R_FAILED(result)) + { + uiStatusMsg("%s: failed to open secure IStorage partition! (0x%08X)", __func__, result); + goto out; + } + + if (strncmp(cfwDirStr, CFW_PATH_SXOS, strlen(CFW_PATH_SXOS)) != 0) + { + // Retrieve secure IStorage partition size + result = getGameCardStoragePartitionSize(&(gameCardInfo.IStoragePartitionSizes[1])); + if (R_FAILED(result)) + { + uiStatusMsg("%s: failed to retrieve size for secure IStorage partition! (0x%08X)", __func__, result); + goto out; + } + } else { + // Total size for the secure IStorage partition is maxed out under SX OS, so let's try to calculate it manually + gameCardInfo.IStoragePartitionSizes[1] = ((gameCardInfo.size - ((gameCardInfo.size / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - gameCardInfo.IStoragePartitionSizes[0]); + } + } else { + // The partition offset is relative to the start of the normal IStorage partition (true gamecard image start) + gameCardInfo.hfs0Partitions[i].offset = (gameCardInfo.header.rootHfs0HeaderOffset + gameCardInfo.header.rootHfs0HeaderSize + entry.file_offset); + } + + // Partially read the current HFS0 partition header + result = readGameCardStoragePartition(gameCardInfo.hfs0Partitions[i].offset, &header, sizeof(hfs0_header)); + if (R_FAILED(result)) + { + uiStatusMsg("%s: failed to read %lu bytes long chunk from %s HFS0 partition! (0x%08X)", __func__, sizeof(hfs0_header), GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i), result); + goto out; + } + + // Check the HFS0 magic word + if (bswap_32(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)); + goto out; + } + + if (!header.str_table_size) + { + uiStatusMsg("%s: invalid string table size in %s HFS0 partition header!", __func__, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i)); + goto out; + } + + // Calculate the size for the HFS0 partition header and round it to a MEDIA_UNIT_SIZE bytes boundary + gameCardInfo.hfs0Partitions[i].header_size = (sizeof(hfs0_header) + (header.file_cnt * sizeof(hfs0_file_entry)) + header.str_table_size); + gameCardInfo.hfs0Partitions[i].header_size = round_up(gameCardInfo.hfs0Partitions[i].header_size, MEDIA_UNIT_SIZE); + + gameCardInfo.hfs0Partitions[i].file_cnt = header.file_cnt; + gameCardInfo.hfs0Partitions[i].str_table_size = header.str_table_size; + + gameCardInfo.hfs0Partitions[i].header = calloc(1, gameCardInfo.hfs0Partitions[i].header_size); + if (!gameCardInfo.hfs0Partitions[i].header) + { + uiStatusMsg("%s: unable to allocate memory for %s HFS0 partition header!", __func__, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i)); + goto out; + } + + // Finally, read the full HFS0 partition header + result = readGameCardStoragePartition(gameCardInfo.hfs0Partitions[i].offset, gameCardInfo.hfs0Partitions[i].header, gameCardInfo.hfs0Partitions[i].header_size); + if (R_FAILED(result)) + { + uiStatusMsg("%s: failed to read %lu bytes long %s HFS0 partition header! (0x%08X)", __func__, gameCardInfo.hfs0Partitions[i].header_size, GAMECARD_PARTITION_NAME(gameCardInfo.hfs0PartitionCnt, i), result); + goto out; + } } // Get bundled FW version update - result = fsDeviceOperatorUpdatePartitionInfo(&fsOperatorInstance, &handle, &gameCardUpdateVersion, &gameCardUpdateTitleID); + result = fsDeviceOperatorUpdatePartitionInfo(&(gameCardInfo.fsOperatorInstance), &(gameCardInfo.fsGameCardHandle), &(gameCardInfo.updateVersion), &(gameCardInfo.updateTitleId)); if (R_SUCCEEDED(result)) { - if (gameCardUpdateTitleID == GAMECARD_UPDATE_TITLEID) + if (gameCardInfo.updateTitleId == GAMECARD_UPDATE_TITLEID) { - u8 major = (u8)((gameCardUpdateVersion >> 26) & 0x3F); - u8 minor = (u8)((gameCardUpdateVersion >> 20) & 0x3F); - u8 micro = (u8)((gameCardUpdateVersion >> 16) & 0xF); - u16 bugfix = (u16)gameCardUpdateVersion; - - snprintf(gameCardUpdateVersionStr, MAX_ELEMENTS(gameCardUpdateVersionStr), "%u.%u.%u (bugfix %u) - v%u", major, minor, micro, bugfix, gameCardUpdateVersion); + convertTitleVersionToDotNotation(gameCardInfo.updateVersion, &major, &minor, µ, &bugfix); + snprintf(gameCardInfo.updateVersionStr, MAX_CHARACTERS(gameCardInfo.updateVersionStr), "%u.%u.%u (v%u)", major, minor, micro, gameCardInfo.updateVersion); } else { - uiStatusMsg("getGameCardUpdateInfo: update Title ID mismatch! %016lX != %016lX", gameCardUpdateTitleID, GAMECARD_UPDATE_TITLEID); + uiStatusMsg("%s: update Title ID mismatch! (%016lX != %016lX)", __func__, gameCardInfo.updateTitleId, GAMECARD_UPDATE_TITLEID); } } else { - uiStatusMsg("getGameCardUpdateInfo: UpdatePartitionInfo failed! (0x%08X)", result); + uiStatusMsg("%s: UpdatePartitionInfo failed! (0x%08X)", __func__, result); } + + success = true; + +out: + if (success) + { + closeGameCardStoragePartition(); + } else { + freeGameCardInfo(); + } + + return success; +} + +u64 calculateSizeFromContentRecords(NcmStorageId curStorageId, NcmContentMetaType metaType, u32 ncmTitleCount, u32 ncmTitleIndex) +{ + if ((curStorageId != NcmStorageId_GameCard && curStorageId != NcmStorageId_SdCard && curStorageId != NcmStorageId_BuiltInUser) || (metaType != NcmContentMetaType_Application && metaType != NcmContentMetaType_Patch && metaType != NcmContentMetaType_AddOnContent) || ncmTitleIndex >= ncmTitleCount) return 0; + + NcmContentInfo *titleContentInfos = NULL; + u32 i, titleContentInfoCnt = 0; + u64 tmp = 0, outSize = 0; + + if (!retrieveContentInfosFromTitle(curStorageId, metaType, ncmTitleCount, ncmTitleIndex, &titleContentInfos, &titleContentInfoCnt)) return 0; + + for(i = 0; i < titleContentInfoCnt; i++) + { + if (titleContentInfos[i].content_type >= NcmContentType_DeltaFragment) continue; + + convertNcaSizeToU64(titleContentInfos[i].size, &tmp); + outSize += tmp; + } + + free(titleContentInfos); + + return outSize; +} + +int baseAppCmp(const void *a, const void *b) +{ + base_app_ctx_t *baseApp1 = (base_app_ctx_t*)a; + base_app_ctx_t *baseApp2 = (base_app_ctx_t*)b; + + return strcasecmp(baseApp1->name, baseApp2->name); +} + +int orphanEntryCmp(const void *a, const void *b) +{ + orphan_patch_addon_entry *orphanEntry1 = (orphan_patch_addon_entry*)a; + orphan_patch_addon_entry *orphanEntry2 = (orphan_patch_addon_entry*)b; + + return strcasecmp(orphanEntry1->orphanListStr, orphanEntry2->orphanListStr); } void loadTitleInfo() @@ -1315,59 +1877,48 @@ void loadTitleInfo() if (menuType == MENUTYPE_MAIN) { freeGlobalData(); - gamecardInfoLoaded = sdCardAndEmmcTitleInfoLoaded = false; + changeAtomicBool(&gameCardInfoLoaded, false); + sdCardAndEmmcTitleInfoLoaded = false; return; } - bool proceed = false, freeBuf = false; + bool proceed = false; if (menuType == MENUTYPE_GAMECARD) { - if (gameCardInserted) - { - if (hfs0_header != NULL || gamecardInfoLoaded) return; - - /* Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services */ - uiPleaseWait(GAMECARD_WAIT_TIME); - - proceed = getRootHfs0Header(); - gamecardInfoLoaded = true; - - if (!proceed) - { - uiPrintHeadline(); - return; - } - - getGameCardUpdateInfo(); - - freeTitleInfo(); - - proceed = getTitleIDAndVersionList(FsStorageId_GameCard, true, true, true); - } else { - freeGlobalData(); - return; - } + if (gameCardInfo.isInserted && gameCardInfoLoaded) return; + + freeGlobalData(); + + if (!gameCardInfo.isInserted) return; + + /* Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services */ + uiPleaseWait(GAMECARD_WAIT_TIME); + + proceed = retrieveGameCardInfo(); + changeAtomicBool(&gameCardInfoLoaded, true); + + if (proceed) proceed = getTitleIDAndVersionList(NcmStorageId_GameCard, true, true, true); } else if (menuType == MENUTYPE_SDCARD_EMMC) { - if (titleAppCount || titlePatchCount || titleAddOnCount || sdCardAndEmmcTitleInfoLoaded) return; + if (sdCardAndEmmcTitleInfoLoaded) return; uiPleaseWait(0); freeTitleInfo(); - if (getTitleIDAndVersionList(FsStorageId_SdCard, true, true, true)) + if (getTitleIDAndVersionList(NcmStorageId_SdCard, true, true, true)) { sdCardTitleAppCount = titleAppCount; sdCardTitlePatchCount = titlePatchCount; sdCardTitleAddOnCount = titleAddOnCount; - if (getTitleIDAndVersionList(FsStorageId_NandUser, true, true, true)) + if (getTitleIDAndVersionList(NcmStorageId_BuiltInUser, true, true, true)) { - nandUserTitleAppCount = (titleAppCount - sdCardTitleAppCount); - nandUserTitlePatchCount = (titlePatchCount - sdCardTitlePatchCount); - nandUserTitleAddOnCount = (titleAddOnCount - sdCardTitleAddOnCount); + emmcTitleAppCount = (titleAppCount - sdCardTitleAppCount); + emmcTitlePatchCount = (titlePatchCount - sdCardTitlePatchCount); + emmcTitleAddOnCount = (titleAddOnCount - sdCardTitleAddOnCount); proceed = true; } @@ -1376,364 +1927,198 @@ void loadTitleInfo() sdCardAndEmmcTitleInfoLoaded = true; } - if (proceed && titleAppCount > 0) + if (proceed) { - titleName = calloc(titleAppCount + 1, sizeof(char*)); - fixedTitleName = calloc(titleAppCount + 1, sizeof(char*)); - titleAuthor = calloc(titleAppCount + 1, sizeof(char*)); - titleAppVersionStr = calloc(titleAppCount + 1, sizeof(char*)); - titleIcon = calloc(titleAppCount + 1, sizeof(u8*)); + u32 i, ncmTitleCount; - if (titleName != NULL && fixedTitleName != NULL && titleAuthor != NULL && titleAppVersionStr != NULL && titleIcon != NULL) + for(i = 0; i < titleAppCount; i++) { - u32 i; - for(i = 0; i < titleAppCount; i++) + // Retrieve base application name, author and icon + if (getCachedBaseApplicationNacpMetadata(baseAppEntries[i].titleId, baseAppEntries[i].name, MAX_CHARACTERS(baseAppEntries[i].name), baseAppEntries[i].author, MAX_CHARACTERS(baseAppEntries[i].author), &(baseAppEntries[i].icon))) { - titleName[i] = calloc(NACP_APPNAME_LEN + 1, sizeof(char)); - fixedTitleName[i] = calloc(NACP_APPNAME_LEN + 1, sizeof(char)); - titleAuthor[i] = calloc(NACP_AUTHOR_LEN + 1, sizeof(char)); - titleAppVersionStr[i] = calloc(VERSION_STR_LEN + 1, sizeof(char)); - - if (titleName[i] != NULL && fixedTitleName[i] != NULL && titleAuthor[i] != NULL && titleAppVersionStr[i] != NULL) - { - convertTitleVersionToDecimal(titleAppVersion[i], titleAppVersionStr[i], VERSION_STR_LEN); - - if (getTitleControlNacp(titleAppTitleID[i], titleName[i], NACP_APPNAME_LEN, titleAuthor[i], NACP_AUTHOR_LEN, &(titleIcon[i]))) - { - strtrim(titleName[i]); - - strtrim(titleAuthor[i]); - - snprintf(fixedTitleName[i], NACP_APPNAME_LEN, titleName[i]); - removeIllegalCharacters(fixedTitleName[i]); - } else { - freeBuf = true; - break; - } - } else { - uiStatusMsg("loadTitleInfo: error allocating memory for title information (application #%u).", i + 1); - freeBuf = true; - break; - } + strtrim(baseAppEntries[i].name); + strtrim(baseAppEntries[i].author); + snprintf(baseAppEntries[i].fixedName, MAX_CHARACTERS(baseAppEntries[i].fixedName), baseAppEntries[i].name); + removeIllegalCharacters(baseAppEntries[i].fixedName); } - } else { - uiStatusMsg("loadTitleInfo: error allocating memory for title information."); - freeBuf = true; + + // Retrieve base application content size + ncmTitleCount = (baseAppEntries[i].storageId == NcmStorageId_GameCard ? titleAppCount : (baseAppEntries[i].storageId == NcmStorageId_SdCard ? sdCardTitleAppCount : emmcTitleAppCount)); + baseAppEntries[i].contentSize = calculateSizeFromContentRecords(baseAppEntries[i].storageId, NcmContentMetaType_Application, ncmTitleCount, baseAppEntries[i].ncmIndex); + convertSize(baseAppEntries[i].contentSize, baseAppEntries[i].contentSizeStr, MAX_CHARACTERS(baseAppEntries[i].contentSizeStr)); } - if (freeBuf) freeTitleInfo(); + // Sort base applications by name + if (titleAppCount) qsort(baseAppEntries, titleAppCount, sizeof(base_app_ctx_t), baseAppCmp); + + for(i = 0; i < titlePatchCount; i++) + { + // Retrieve patch content size + ncmTitleCount = (patchEntries[i].storageId == NcmStorageId_GameCard ? titlePatchCount : (patchEntries[i].storageId == NcmStorageId_SdCard ? sdCardTitlePatchCount : emmcTitlePatchCount)); + patchEntries[i].contentSize = calculateSizeFromContentRecords(patchEntries[i].storageId, NcmContentMetaType_Patch, ncmTitleCount, patchEntries[i].ncmIndex); + convertSize(patchEntries[i].contentSize, patchEntries[i].contentSizeStr, MAX_CHARACTERS(patchEntries[i].contentSizeStr)); + } + + for(i = 0; i < titleAddOnCount; i++) + { + // Retrieve add-on content size + ncmTitleCount = (addOnEntries[i].storageId == NcmStorageId_GameCard ? titleAddOnCount : (addOnEntries[i].storageId == NcmStorageId_SdCard ? sdCardTitleAddOnCount : emmcTitleAddOnCount)); + addOnEntries[i].contentSize = calculateSizeFromContentRecords(addOnEntries[i].storageId, NcmContentMetaType_AddOnContent, ncmTitleCount, addOnEntries[i].ncmIndex); + convertSize(addOnEntries[i].contentSize, addOnEntries[i].contentSizeStr, MAX_CHARACTERS(addOnEntries[i].contentSizeStr)); + } + + // Generate orphan content list + // If orphanEntries == NULL or if orphanEntriesCnt == 0, both variables will be regenerated + // Otherwise, this will only fill filenameBuffer + if (menuType == MENUTYPE_SDCARD_EMMC) generateOrphanPatchOrAddOnList(); } uiPrintHeadline(); } -bool getHfs0EntryDetails(u8 *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size) +void truncateBrowserEntryName(char *str) { - if (hfs0Header == NULL) return false; + if (!str || !strlen(str)) return; - if (entry_idx > (num_entries - 1)) return false; + u32 strWidth = uiGetStrWidth(str); - if ((HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * num_entries)) > hfs0HeaderSize) return false; - - hfs0_entry_table *entryTable = calloc(num_entries, sizeof(hfs0_entry_table)); - if (!entryTable) return false; - - memcpy(entryTable, hfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * num_entries); - - // Determine the partition index that's going to be used for offset calculation - // If we're dealing with a root HFS0 header, just use entry_idx - // Otherwise, partitionIndex must be used, because entry_idx represents the file entry we must look for in the provided HFS0 partition header - u32 part_idx = (isRoot ? entry_idx : partitionIndex); - - switch(part_idx) + if ((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 8))) { - case 0: // Update (contained within IStorage instance with partition ID 0) - case 1: // Normal or Logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0) - // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start - // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) - *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); - break; - case 2: - // Check if we're dealing with a type 0x01 gamecard - if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT) - { - // Secure (contained within IStorage instance with partition ID 1) - // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance - // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation - *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); - } else { - // Normal (contained within IStorage instance with partition ID 0) - // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start - // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) - *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); - } - break; - case 3: // Secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1) - // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance - // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation - *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); - break; - default: - break; - } - - // Store the file size for the desired HFS0 entry - *out_size = entryTable[entry_idx].file_size; - - free(entryTable); - - return true; -} - -bool getPartitionHfs0Header(u32 partition) -{ - if (hfs0_header == NULL) return false; - - if (partitionHfs0Header != NULL) - { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - } - - u8 buf[MEDIA_UNIT_SIZE] = {0}; - Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; - u64 partitionSize = 0; - u32 magic = 0; - bool success = false; - - if (!getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionHfs0HeaderOffset, &partitionSize)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to get partition details from the root HFS0 header!"); - return success; - } - - workaroundPartitionZeroAccess(); - - result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "GetGameCardHandle failed! (0x%08X)", result); - return success; - } - - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "GetGameCardHandle succeeded: 0x%08X", handle.value); - breaks++;*/ - - // Same ugly hack from dumpRawHfs0Partition() - result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "OpenGameCardStorage failed! (0x%08X)", result); - return success; - } - - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "OpenGameCardStorage succeeded: 0x%08X", handle); - breaks++;*/ - - // First read MEDIA_UNIT_SIZE bytes - result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, buf, MEDIA_UNIT_SIZE); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); - goto out; - } - - // Check the HFS0 magic word - memcpy(&magic, buf, sizeof(u32)); - magic = bswap_32(magic); - if (magic != HFS0_MAGIC) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); - goto out; - } - - // Calculate the size for the partition HFS0 header - memcpy(&partitionHfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32)); - memcpy(&partitionHfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32)); - partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionHfs0StrTableSize); - - /*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u HFS0 header offset (relative to IStorage instance): 0x%016lX", partition, partitionHfs0HeaderOffset); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u file count: %u", partition, partitionHfs0FileCount); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u string table size: %u bytes", partition, partitionHfs0StrTableSize); - breaks++; - - uiRefreshDisplay();*/ - - // Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary - partitionHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE); - - partitionHfs0Header = malloc(partitionHfs0HeaderSize); - if (!partitionHfs0Header) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to allocate memory for the HFS0 header from partition #%u!", partition); - goto out; - } - - // Check if we were dealing with the correct header size all along - if (partitionHfs0HeaderSize == MEDIA_UNIT_SIZE) - { - // Just copy what we already have - memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE); - success = true; - } else { - // Read the whole HFS0 header - result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, partitionHfs0Header, partitionHfs0HeaderSize); - if (R_SUCCEEDED(result)) + while((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 8))) { - success = true; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); - } - } - -out: - if (success) - { - //uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Partition #%u HFS0 header successfully retrieved!", partition); - } else { - if (partitionHfs0Header) - { - free(partitionHfs0Header); - partitionHfs0Header = NULL; + str[strlen(str) - 1] = '\0'; + strWidth = uiGetStrWidth(str); } - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; + strcat(str, "..."); } - - fsStorageClose(&gameCardStorage); - - return success; } bool getHfs0FileList(u32 partition) { - if (!getPartitionHfs0Header(partition)) + if (partition >= gameCardInfo.hfs0PartitionCnt) { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid HFS0 partition index!", __func__); breaks += 2; return false; } - if (!partitionHfs0Header) + if (!gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[partition].header || !gameCardInfo.hfs0Partitions[partition].header_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "HFS0 partition header information unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: HFS0 partition header information unavailable!", __func__); breaks += 2; return false; } - if (!partitionHfs0FileCount) + if (!gameCardInfo.hfs0Partitions[partition].file_cnt || !gameCardInfo.hfs0Partitions[partition].str_table_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "The selected partition is empty!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: the selected HFS0 partition is empty!", __func__); breaks += 2; return false; } - if (partitionHfs0FileCount > FILENAME_MAX_CNT) + if (gameCardInfo.hfs0Partitions[partition].file_cnt > FILENAME_MAX_CNT) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "HFS0 partition contains more than %u files! (%u entries)", FILENAME_MAX_CNT, partitionHfs0FileCount); + 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; } - hfs0_entry_table *entryTable = calloc(partitionHfs0FileCount, sizeof(hfs0_entry_table)); - if (!entryTable) + 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)); + if (!hfs0ExeFsEntriesSizes) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Unable to allocate memory for the HFS0 file entries!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for HFS0 entries size info!", __func__); breaks += 2; return false; } - memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount); - - memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); - - int i; - int max_elements = (int)partitionHfs0FileCount; - char *nextFilename = filenameBuffer; - - filenamesCount = 0; - - for(i = 0; i < max_elements; i++) + for(i = 0; i < gameCardInfo.hfs0Partitions[partition].file_cnt; i++) { - u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + entryTable[i].filename_offset); - addStringToFilenameBuffer((char*)partitionHfs0Header + filename_offset, &nextFilename); + memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); + + char *cur_filename = (char*)(gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (gameCardInfo.hfs0Partitions[partition].file_cnt * sizeof(hfs0_file_entry)) + entry.filename_offset); + + snprintf(curName, MAX_CHARACTERS(curName), cur_filename); + + // Fix entry name length + truncateBrowserEntryName(curName); + + addStringToFilenameBuffer(curName); + + // Save entry size + hfs0ExeFsEntriesSizes[i].size = entry.file_size; + convertSize(hfs0ExeFsEntriesSizes[i].size, hfs0ExeFsEntriesSizes[i].sizeStr, MAX_CHARACTERS(hfs0ExeFsEntriesSizes[i].sizeStr)); } - free(entryTable); - - breaks += 2; - return true; } -// Used to retrieve tik/cert files from the HFS0 Secure partition -bool getFileFromHfs0PartitionByName(FsStorage *gameCardStorage, const char *filename, u8 *outBuf, u64 outBufSize) +// Used to retrieve data from files in the HFS0 Secure partition +// An IStorage instance must have been opened beforehand +bool readFileFromSecureHfs0PartitionByName(const char *filename, u64 offset, void *outBuf, size_t bufSize) { - if (!partitionHfs0Header || !partitionHfs0FileCount || !partitionHfs0HeaderSize || !gameCardStorage || !filename || !outBuf || !outBufSize) + if (!gameCardInfo.hfs0PartitionCnt || !gameCardInfo.hfs0Partitions || !gameCardInfo.hfs0Partitions[gameCardInfo.hfs0PartitionCnt - 1].header || !gameCardInfo.hfs0Partitions[gameCardInfo.hfs0PartitionCnt - 1].header_size || !gameCardInfo.hfs0Partitions[gameCardInfo.hfs0PartitionCnt - 1].file_cnt || !gameCardInfo.hfs0Partitions[gameCardInfo.hfs0PartitionCnt - 1].str_table_size || !filename || !strlen(filename) || !outBuf || !bufSize) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve file from HFS0 partition!"); - return NULL; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to read file from Secure HFS0 partition!", __func__); + return false; } - bool success = false, proceed = true, found = false; - u32 i; Result result; - hfs0_entry_table tmp_hfs0_entry; + hfs0_file_entry entry; - for(i = 0; i < partitionHfs0FileCount; i++) + bool proceed = true, found = false; + + u32 partition = (gameCardInfo.hfs0PartitionCnt - 1); // Select the Secure HFS0 partition + + for(i = 0; i < gameCardInfo.hfs0Partitions[partition].file_cnt; i++) { - memcpy(&tmp_hfs0_entry, partitionHfs0Header + (u64)HFS0_ENTRY_TABLE_ADDR + ((u64)i * sizeof(hfs0_entry_table)), sizeof(hfs0_entry_table)); + memcpy(&entry, gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (i * sizeof(hfs0_file_entry)), sizeof(hfs0_file_entry)); - if (strncasecmp((char*)partitionHfs0Header + (u64)HFS0_ENTRY_TABLE_ADDR + ((u64)partitionHfs0FileCount * sizeof(hfs0_entry_table)) + (u64)tmp_hfs0_entry.filename_offset, filename, strlen(filename)) != 0) continue; + char *cur_filename = (char*)(gameCardInfo.hfs0Partitions[partition].header + sizeof(hfs0_header) + (gameCardInfo.hfs0Partitions[partition].file_cnt * sizeof(hfs0_file_entry)) + entry.filename_offset); + + if (strncasecmp(cur_filename, filename, strlen(filename)) != 0) continue; found = true; - if (outBufSize > tmp_hfs0_entry.file_size) + if (!entry.file_size || (offset + bufSize) > entry.file_size) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: file \"%s\" is smaller than expected! (0x%016lX < 0x%016lX)", filename, tmp_hfs0_entry.file_size, outBufSize); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid file size for \"%s\"!", __func__, filename); proceed = false; - break; } - result = fsStorageRead(gameCardStorage, partitionHfs0HeaderSize + tmp_hfs0_entry.file_offset, outBuf, outBufSize); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to read file \"%s\" from the HFS0 partition!", filename); - proceed = false; - break; - } - - success = true; - break; } - if (proceed && !found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find file \"%s\" in the HFS0 partition!", filename); + if (!proceed || !found) + { + if (proceed && !found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find file \"%s\" in Secure HFS0 partition!", __func__, filename); + return false; + } - return success; + result = readGameCardStoragePartition(gameCardInfo.hfs0Partitions[partition].header_size + entry.file_offset + offset, outBuf, bufSize); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read file \"%s\"! (0x%08X)", __func__, filename, result); + return false; + } + + return true; } bool calculateExeFsExtractedDataSize(u64 *out) { if (!exeFsContext.exefs_header.file_cnt || !exeFsContext.exefs_entries || !out) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to calculate extracted data size for the ExeFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to calculate extracted data size for the ExeFS section!", __func__); return false; } @@ -1751,7 +2136,7 @@ bool calculateRomFsFullExtractedSize(bool usePatch, u64 *out) { if ((!usePatch && (!romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries)) || (usePatch && (!bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries)) || !out) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to calculate extracted data size for the RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to calculate extracted data size for the RomFS section!", __func__); return false; } @@ -1779,7 +2164,7 @@ bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out) { if ((!usePatch && (!romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries || dir_offset > romFsContext.romfs_dirtable_size)) || (usePatch && (!bktrContext.romfs_dirtable_size || !bktrContext.romfs_dir_entries || !bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries || dir_offset > bktrContext.romfs_dirtable_size)) || !out) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to calculate extracted size for the current RomFS directory!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to calculate extracted size for the current RomFS directory!", __func__); return false; } @@ -1791,7 +2176,7 @@ bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out) // Check if we're dealing with a nameless directory that's not the root directory if (!dirEntry->nameLen && dir_offset > 0) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: directory entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: directory entry without name in RomFS section!", __func__); return false; } @@ -1832,174 +2217,140 @@ bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out) return true; } -bool retrieveNcaContentRecords(FsStorageId curStorageId, u8 filter, u32 titleCount, u32 titleIndex, NcmContentRecord **outContentRecords, u32 *outContentRecordsCnt) +bool retrieveContentInfosFromTitle(NcmStorageId storageId, NcmContentMetaType metaType, u32 titleCount, u32 titleIndex, NcmContentInfo **outContentInfos, u32 *outContentInfoCnt) { Result result; NcmContentMetaDatabase ncmDb; memset(&ncmDb, 0, sizeof(NcmContentMetaDatabase)); - NcmContentMetaRecordsHeader contentRecordsHeader; - memset(&contentRecordsHeader, 0, sizeof(NcmContentMetaRecordsHeader)); - u64 contentRecordsHeaderReadSize = 0; + NcmContentMetaHeader cnmtHeader; + memset(&cnmtHeader, 0, sizeof(NcmContentMetaHeader)); + u64 cnmtHeaderReadSize = 0; NcmApplicationContentMetaKey *titleList = NULL; size_t titleListSize = (sizeof(NcmApplicationContentMetaKey) * titleCount); - NcmContentRecord *titleContentRecords = NULL; - u32 titleContentRecordsCnt = 0; + NcmContentInfo *titleContentInfos = NULL; + u32 titleContentInfoCnt = 0; u32 written = 0, total = 0; bool success = false; - if (curStorageId != FsStorageId_GameCard && curStorageId != FsStorageId_SdCard && curStorageId != FsStorageId_NandUser) + if (storageId != NcmStorageId_GameCard && storageId != NcmStorageId_SdCard && storageId != NcmStorageId_BuiltInUser) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: invalid title storage ID!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid title storage ID!", __func__); goto out; } - if (filter != META_DB_REGULAR_APPLICATION && filter != META_DB_PATCH && filter != META_DB_ADDON) + if (metaType != NcmContentMetaType_Application && metaType != NcmContentMetaType_Patch && metaType != NcmContentMetaType_AddOnContent) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: invalid title filter!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid title meta type!", __func__); goto out; } if (!titleCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: invalid title type count!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid title type count!", __func__); goto out; } if (titleIndex >= titleCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: invalid title index!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid title index!", __func__); goto out; } - if (!outContentRecords || !outContentRecordsCnt) + if (!outContentInfos || !outContentInfoCnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: invalid output parameters!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: invalid output parameters!", __func__); goto out; } - // If we're dealing with a gamecard, call workaroundPartitionZeroAccess() and read the secure partition header. Otherwise, ncmContentStorageReadContentIdFile() will fail with error 0x00171002 - if (curStorageId == FsStorageId_GameCard && !partitionHfs0Header) - { - u32 partition = (hfs0_partition_cnt - 1); // Select the secure partition - - workaroundPartitionZeroAccess(); - - if (!getPartitionHfs0Header(partition)) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: failed to read the Secure HFS0 partition header!"); - goto out; - } - - if (!partitionHfs0FileCount) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: the Secure HFS0 partition is empty!"); - goto out; - } - } - titleList = calloc(1, titleListSize); if (!titleList) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: unable to allocate memory for the ApplicationContentMetaKey struct!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to allocate memory for the ApplicationContentMetaKey struct!", __func__); goto out; } - result = ncmOpenContentMetaDatabase(curStorageId, &ncmDb); + result = ncmOpenContentMetaDatabase(&ncmDb, storageId); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: ncmOpenContentMetaDatabase failed! (0x%08X)", result); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: ncmOpenContentMetaDatabase failed! (0x%08X)", __func__, result); goto out; } - result = ncmContentMetaDatabaseListApplication(&ncmDb, filter, titleList, titleListSize, &written, &total); + result = ncmContentMetaDatabaseListApplication(&ncmDb, (s32*)&total, (s32*)&written, titleList, (s32)titleCount, metaType); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: ncmContentMetaDatabaseListApplication failed! (0x%08X)", __func__, result); goto out; } if (!written || !total) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: ncmContentMetaDatabaseListApplication wrote no entries to output buffer!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: ncmContentMetaDatabaseListApplication wrote no entries to output buffer!", __func__); goto out; } if (written != total) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: title count mismatch in ncmContentMetaDatabaseListApplication! (%u != %u)", written, total); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: title count mismatch in ncmContentMetaDatabaseListApplication! (%u != %u)", __func__, written, total); goto out; } if (titleIndex >= total) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: provided title index exceeds title count from ncmContentMetaDatabaseListApplication!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: provided title index exceeds title count from ncmContentMetaDatabaseListApplication!", __func__); goto out; } - result = ncmContentMetaDatabaseGet(&ncmDb, &(titleList[titleIndex].metaRecord), sizeof(NcmContentMetaRecordsHeader), &contentRecordsHeader, &contentRecordsHeaderReadSize); + result = ncmContentMetaDatabaseGet(&ncmDb, &(titleList[titleIndex].key), &cnmtHeaderReadSize, &cnmtHeader, sizeof(NcmContentMetaHeader)); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: ncmContentMetaDatabaseGet failed! (0x%08X)", result); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: ncmContentMetaDatabaseGet failed! (0x%08X)", __func__, result); goto out; } - titleContentRecordsCnt = (u32)(contentRecordsHeader.numContentRecords); + titleContentInfoCnt = (u32)(cnmtHeader.content_count); - titleContentRecords = calloc(titleContentRecordsCnt, sizeof(NcmContentRecord)); - if (!titleContentRecords) + titleContentInfos = calloc(titleContentInfoCnt, sizeof(NcmContentInfo)); + if (!titleContentInfos) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: unable to allocate memory for the ContentRecord struct!"); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: unable to allocate memory for the title content information struct!", __func__); goto out; } written = 0; - result = ncmContentMetaDatabaseListContentInfo(&ncmDb, &(titleList[titleIndex].metaRecord), 0, titleContentRecords, titleContentRecordsCnt * sizeof(NcmContentRecord), &written); + result = ncmContentMetaDatabaseListContentInfo(&ncmDb, (s32*)&written, titleContentInfos, (s32)titleContentInfoCnt, &(titleList[titleIndex].key), 0); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: ncmContentMetaDatabaseListContentInfo failed! (0x%08X)", result); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: ncmContentMetaDatabaseListContentInfo failed! (0x%08X)", __func__, result); goto out; } - if (written != titleContentRecordsCnt) + if (written != titleContentInfoCnt) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "retrieveNcaContentRecords: title content record count mismatch in ncmContentMetaDatabaseListContentInfo! (%u != %u)", written, titleContentRecordsCnt); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "%s: title content count mismatch in ncmContentMetaDatabaseListContentInfo! (%u != %u)", __func__, written, titleContentInfoCnt); goto out; } success = true; // Update output parameters - *outContentRecords = titleContentRecords; - *outContentRecordsCnt = titleContentRecordsCnt; + *outContentInfos = titleContentInfos; + *outContentInfoCnt = titleContentInfoCnt; out: - if (!success && titleContentRecords) free(titleContentRecords); + if (!success && titleContentInfos) free(titleContentInfos); - serviceClose(&(ncmDb.s)); + ncmContentMetaDatabaseClose(&ncmDb); if (titleList) free(titleList); - if (curStorageId == FsStorageId_GameCard) - { - if (partitionHfs0Header) - { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - } - } - return success; } @@ -2026,14 +2377,14 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) u32 i = 0; Result result; - FsStorageId curStorageId; - u8 filter; + NcmStorageId curStorageId = NcmStorageId_None; + NcmContentMetaType metaType; u32 titleCount = 0, ncmTitleIndex = 0; - NcmContentRecord *titleContentRecords = NULL; - u32 titleContentRecordsCnt = 0; + NcmContentInfo *titleContentInfos = NULL; + u32 titleContentInfoCnt = 0; - NcmNcaId ncaId; + NcmContentId ncaId; char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; NcmContentStorage ncmStorage; @@ -2046,68 +2397,66 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) bool success = false, foundProgram = false; - if ((!usePatch && !titleAppStorageId) || (usePatch && !titlePatchStorageId)) + if ((!usePatch && !baseAppEntries) || (usePatch && !patchEntries)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaExeFsSection: title storage ID unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: title storage ID unavailable!", __func__); goto out; } if ((!usePatch && titleIndex >= titleAppCount) || (usePatch && titleIndex >= titlePatchCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaExeFsSection: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); goto out; } - curStorageId = (!usePatch ? titleAppStorageId[titleIndex] : titlePatchStorageId[titleIndex]); + curStorageId = (!usePatch ? baseAppEntries[titleIndex].storageId : patchEntries[titleIndex].storageId); - filter = (!usePatch ? META_DB_REGULAR_APPLICATION : META_DB_PATCH); + ncmTitleIndex = (!usePatch ? baseAppEntries[titleIndex].ncmIndex : patchEntries[titleIndex].ncmIndex); + + metaType = (!usePatch ? NcmContentMetaType_Application : NcmContentMetaType_Patch); switch(curStorageId) { - case FsStorageId_GameCard: + case NcmStorageId_GameCard: titleCount = (!usePatch ? titleAppCount : titlePatchCount); - ncmTitleIndex = titleIndex; break; - case FsStorageId_SdCard: + case NcmStorageId_SdCard: titleCount = (!usePatch ? sdCardTitleAppCount : sdCardTitlePatchCount); - - if (menuType == MENUTYPE_SDCARD_EMMC) - { - ncmTitleIndex = titleIndex; - } else { - // Patches loaded using loadTitlesFromSdCardAndEmmc() - ncmTitleIndex = (titleIndex - (titlePatchCount - gameCardSdCardEmmcPatchCount)); // Substract gamecard patch count - } - break; - case FsStorageId_NandUser: - titleCount = (!usePatch ? nandUserTitleAppCount : nandUserTitlePatchCount); - - if (menuType == MENUTYPE_SDCARD_EMMC) - { - ncmTitleIndex = (titleIndex - (!usePatch ? sdCardTitleAppCount : sdCardTitlePatchCount)); // Substract SD card title count - } else { - // Patches loaded using loadTitlesFromSdCardAndEmmc() - ncmTitleIndex = (titleIndex - ((titlePatchCount - gameCardSdCardEmmcPatchCount) + sdCardTitlePatchCount)); // Substract gamecard + SD card patch count - } - + case NcmStorageId_BuiltInUser: + titleCount = (!usePatch ? emmcTitleAppCount : emmcTitlePatchCount); break; default: break; } + // If we're dealing with a gamecard, open the Secure HFS0 partition (IStorage partition #1) + if (curStorageId == NcmStorageId_GameCard) + { + result = openGameCardStoragePartition(ISTORAGE_PARTITION_SECURE); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #1! (0x%08X)", __func__, result); + goto out; + } + } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Looking for the Program NCA (%s)...", (!usePatch ? "base application" : "update")); uiRefreshDisplay(); breaks++; - if (!retrieveNcaContentRecords(curStorageId, filter, titleCount, ncmTitleIndex, &titleContentRecords, &titleContentRecordsCnt)) goto out; - - for(i = 0; i < titleContentRecordsCnt; i++) + if (!retrieveContentInfosFromTitle(curStorageId, metaType, titleCount, ncmTitleIndex, &titleContentInfos, &titleContentInfoCnt)) { - if (titleContentRecords[i].type == NcmContentType_Program) + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); + goto out; + } + + for(i = 0; i < titleContentInfoCnt; i++) + { + if (titleContentInfos[i].content_type == NcmContentType_Program) { - memcpy(&ncaId, &(titleContentRecords[i].ncaId), sizeof(NcmNcaId)); - convertDataToHexString(titleContentRecords[i].ncaId.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); + 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; } @@ -2115,7 +2464,7 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) if (!foundProgram) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find Program NCA!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find Program NCA!", __func__); goto out; } @@ -2127,24 +2476,24 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) uiRefreshDisplay(); breaks++;*/ - result = ncmOpenContentStorage(curStorageId, &ncmStorage); + result = ncmOpenContentStorage(&ncmStorage, curStorageId); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaExeFsSection: ncmOpenContentStorage failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: ncmOpenContentStorage failed! (0x%08X)", __func__, result); goto out; } - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaExeFsSection: failed to read header from Program NCA! (0x%08X)", result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read header from Program NCA!", __func__); goto out; } // Decrypt the NCA header - if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, NULL, decrypted_nca_keys, (curStorageId != FsStorageId_GameCard || (curStorageId == FsStorageId_GameCard && usePatch)))) goto out; + if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, NULL, decrypted_nca_keys, (curStorageId != NcmStorageId_GameCard || (curStorageId == NcmStorageId_GameCard && usePatch)))) goto out; - if (curStorageId == FsStorageId_GameCard && !usePatch) + if (curStorageId == NcmStorageId_GameCard && !usePatch) { bool has_rights_id = false; @@ -2159,22 +2508,23 @@ bool readNcaExeFsSection(u32 titleIndex, bool usePatch) if (has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Rights ID field in Program NCA header not empty!"); + 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 = readExeFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); + success = parseExeFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); + if (success) exeFsContext.storageId = curStorageId; out: if (!success) { - breaks += 2; - serviceClose(&(ncmStorage.s)); + ncmContentStorageClose(&ncmStorage); + if (curStorageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); } - if (titleContentRecords) free(titleContentRecords); + if (titleContentInfos) free(titleContentInfos); return success; } @@ -2184,14 +2534,14 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) u32 i = 0; Result result; - FsStorageId curStorageId; - u8 filter; + NcmStorageId curStorageId = NcmStorageId_None; + NcmContentMetaType metaType; u32 titleCount = 0, ncmTitleIndex = 0; - NcmContentRecord *titleContentRecords = NULL; - u32 titleContentRecordsCnt = 0; + NcmContentInfo *titleContentInfos = NULL; + u32 titleContentInfoCnt = 0; - NcmNcaId ncaId; + NcmContentId ncaId; char ncaIdStr[SHA256_HASH_SIZE + 1] = {'\0'}; NcmContentStorage ncmStorage; @@ -2206,97 +2556,70 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (curRomFsType != ROMFS_TYPE_APP && curRomFsType != ROMFS_TYPE_PATCH && curRomFsType != ROMFS_TYPE_ADDON) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaRomFsSection: invalid RomFS title type!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RomFS title type!", __func__); goto out; } - if ((curRomFsType == ROMFS_TYPE_APP && !titleAppStorageId) || (curRomFsType == ROMFS_TYPE_PATCH && !titlePatchStorageId) || (curRomFsType == ROMFS_TYPE_ADDON && !titlePatchStorageId)) + if ((curRomFsType == ROMFS_TYPE_APP && !baseAppEntries) || (curRomFsType == ROMFS_TYPE_PATCH && !patchEntries) || (curRomFsType == ROMFS_TYPE_ADDON && !addOnEntries)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaRomFsSection: title storage ID unavailable!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: title storage ID unavailable!", __func__); goto out; } if ((curRomFsType == ROMFS_TYPE_APP && titleIndex >= titleAppCount) || (curRomFsType == ROMFS_TYPE_PATCH && titleIndex >= titlePatchCount) || (curRomFsType == ROMFS_TYPE_ADDON && titleIndex >= titleAddOnCount)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaRomFsSection: invalid title index!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid title index!", __func__); goto out; } - curStorageId = (curRomFsType == ROMFS_TYPE_APP ? titleAppStorageId[titleIndex] : (curRomFsType == ROMFS_TYPE_PATCH ? titlePatchStorageId[titleIndex] : titleAddOnStorageId[titleIndex])); + curStorageId = (curRomFsType == ROMFS_TYPE_APP ? baseAppEntries[titleIndex].storageId : (curRomFsType == ROMFS_TYPE_PATCH ? patchEntries[titleIndex].storageId : addOnEntries[titleIndex].storageId)); - filter = (curRomFsType == ROMFS_TYPE_APP ? META_DB_REGULAR_APPLICATION : (curRomFsType == ROMFS_TYPE_PATCH ? META_DB_PATCH : META_DB_ADDON)); + ncmTitleIndex = (curRomFsType == ROMFS_TYPE_APP ? baseAppEntries[titleIndex].ncmIndex : (curRomFsType == ROMFS_TYPE_PATCH ? patchEntries[titleIndex].ncmIndex : addOnEntries[titleIndex].ncmIndex)); + + metaType = (curRomFsType == ROMFS_TYPE_APP ? NcmContentMetaType_Application : (curRomFsType == ROMFS_TYPE_PATCH ? NcmContentMetaType_Patch : NcmContentMetaType_AddOnContent)); switch(curStorageId) { - case FsStorageId_GameCard: + case NcmStorageId_GameCard: titleCount = (curRomFsType == ROMFS_TYPE_APP ? titleAppCount : (curRomFsType == ROMFS_TYPE_PATCH ? titlePatchCount : titleAddOnCount)); - ncmTitleIndex = titleIndex; break; - case FsStorageId_SdCard: + case NcmStorageId_SdCard: titleCount = (curRomFsType == ROMFS_TYPE_APP ? sdCardTitleAppCount : (curRomFsType == ROMFS_TYPE_PATCH ? sdCardTitlePatchCount : sdCardTitleAddOnCount)); - - if (menuType == MENUTYPE_SDCARD_EMMC) - { - ncmTitleIndex = titleIndex; - } else { - // Titles loaded using loadTitlesFromSdCardAndEmmc() - if (curRomFsType == ROMFS_TYPE_PATCH) - { - ncmTitleIndex = (titleIndex - (titlePatchCount - gameCardSdCardEmmcPatchCount)); // Substract gamecard patch count - } else - if (curRomFsType == ROMFS_TYPE_ADDON) - { - ncmTitleIndex = (titleIndex - (titleAddOnCount - gameCardSdCardEmmcAddOnCount)); // Substract gamecard add-on count - } - } - break; - case FsStorageId_NandUser: - titleCount = (curRomFsType == ROMFS_TYPE_APP ? nandUserTitleAppCount : (curRomFsType == ROMFS_TYPE_PATCH ? nandUserTitlePatchCount : nandUserTitleAddOnCount)); - - if (menuType == MENUTYPE_SDCARD_EMMC) - { - if (curRomFsType == ROMFS_TYPE_APP) - { - ncmTitleIndex = (titleIndex - sdCardTitleAppCount); // Substract SD card app count - } else - if (curRomFsType == ROMFS_TYPE_PATCH) - { - ncmTitleIndex = (titleIndex - sdCardTitlePatchCount); // Substract SD card patch count - } else - if (curRomFsType == ROMFS_TYPE_ADDON) - { - ncmTitleIndex = (titleIndex - sdCardTitleAddOnCount); // Substract SD card add-on count - } - } else { - // Titles loaded using loadTitlesFromSdCardAndEmmc() - if (curRomFsType == ROMFS_TYPE_PATCH) - { - ncmTitleIndex = (titleIndex - ((titlePatchCount - gameCardSdCardEmmcPatchCount) + sdCardTitlePatchCount)); // Substract gamecard + SD card patch count - } else - if (curRomFsType == ROMFS_TYPE_ADDON) - { - ncmTitleIndex = (titleIndex - ((titleAddOnCount - gameCardSdCardEmmcAddOnCount) + sdCardTitleAddOnCount)); // Substract gamecard + SD card add-on count - } - } - + case NcmStorageId_BuiltInUser: + titleCount = (curRomFsType == ROMFS_TYPE_APP ? emmcTitleAppCount : (curRomFsType == ROMFS_TYPE_PATCH ? emmcTitlePatchCount : emmcTitleAddOnCount)); break; default: break; } + // If we're dealing with a gamecard, open the Secure HFS0 partition (IStorage partition #1) + if (curStorageId == NcmStorageId_GameCard) + { + result = openGameCardStoragePartition(ISTORAGE_PARTITION_SECURE); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open IStorage partition #1! (0x%08X)", __func__, result); + goto out; + } + } + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Looking for the %s NCA (%s)...", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program"), (curRomFsType == ROMFS_TYPE_APP ? "base application" : (curRomFsType == ROMFS_TYPE_PATCH ? "update" : "DLC"))); uiRefreshDisplay(); breaks++; - if (!retrieveNcaContentRecords(curStorageId, filter, titleCount, ncmTitleIndex, &titleContentRecords, &titleContentRecordsCnt)) goto out; - - for(i = 0; i < titleContentRecordsCnt; i++) + if (!retrieveContentInfosFromTitle(curStorageId, metaType, titleCount, ncmTitleIndex, &titleContentInfos, &titleContentInfoCnt)) { - if (((curRomFsType == ROMFS_TYPE_APP || curRomFsType == ROMFS_TYPE_PATCH) && titleContentRecords[i].type == NcmContentType_Program) || (curRomFsType == ROMFS_TYPE_ADDON && titleContentRecords[i].type == NcmContentType_Data)) + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); + 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, &(titleContentRecords[i].ncaId), sizeof(NcmNcaId)); - convertDataToHexString(titleContentRecords[i].ncaId.c, SHA256_HASH_SIZE / 2, ncaIdStr, SHA256_HASH_SIZE + 1); + 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; } @@ -2304,7 +2627,7 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (!foundNca) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find %s NCA!", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); + 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; } @@ -2316,24 +2639,24 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) uiRefreshDisplay(); breaks++;*/ - result = ncmOpenContentStorage(curStorageId, &ncmStorage); + result = ncmOpenContentStorage(&ncmStorage, curStorageId); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaRomFsSection: ncmOpenContentStorage failed! (0x%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: ncmOpenContentStorage failed! (0x%08X)", __func__, result); goto out; } - result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH); - if (R_FAILED(result)) + if (!readNcaDataByContentId(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "readNcaRomFsSection: failed to read header from %s NCA! (0x%08X)", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program"), result); + breaks++; + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to read header from %s NCA!", __func__, (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); goto out; } // Decrypt the NCA header - if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header, NULL, decrypted_nca_keys, (curStorageId != FsStorageId_GameCard || (curStorageId == FsStorageId_GameCard && curRomFsType == ROMFS_TYPE_PATCH)))) goto out; + 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 (curStorageId == FsStorageId_GameCard && curRomFsType != ROMFS_TYPE_PATCH) + if (curStorageId == NcmStorageId_GameCard && curRomFsType != ROMFS_TYPE_PATCH) { bool has_rights_id = false; @@ -2348,7 +2671,7 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (has_rights_id) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Rights ID field in %s NCA header not empty!", (curRomFsType == ROMFS_TYPE_ADDON ? "Data" : "Program")); + 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; } } @@ -2356,7 +2679,8 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (curRomFsType != ROMFS_TYPE_PATCH) { // Read directory and file tables from the RomFS section - success = readRomFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); + success = parseRomFsEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); + if (success) romFsContext.storageId = curStorageId; } else { // Look for the base application title index u32 appIndex; @@ -2372,7 +2696,7 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (i == titleAppCount) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find base application title index for the selected update!"); + 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; } @@ -2380,19 +2704,18 @@ bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType) if (!readNcaRomFsSection(appIndex, ROMFS_TYPE_APP)) goto out; // Read BKTR entry data in the Program NCA from the update - if (!readBktrEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys)) goto out; - - success = true; + success = parseBktrEntryFromNca(&ncmStorage, &ncaId, &dec_nca_header, decrypted_nca_keys); + if (success) bktrContext.storageId = curStorageId; } out: if (!success) { - breaks += 2; - serviceClose(&(ncmStorage.s)); + ncmContentStorageClose(&ncmStorage); + if (curStorageId == NcmStorageId_GameCard) closeGameCardStoragePartition(); } - if (titleContentRecords) free(titleContentRecords); + if (titleContentInfos) free(titleContentInfos); return success; } @@ -2401,27 +2724,44 @@ bool getExeFsFileList() { if (!exeFsContext.exefs_header.file_cnt || !exeFsContext.exefs_entries || !exeFsContext.exefs_str_table) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve ExeFS section filelist!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve ExeFS section filelist!", __func__); return false; } if (exeFsContext.exefs_header.file_cnt > FILENAME_MAX_CNT) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "ExeFS section contains more than %u entries! (%u entries)", FILENAME_MAX_CNT, exeFsContext.exefs_header.file_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'}; - memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); - filenamesCount = 0; + clearFilenameBuffer(); - char *nextFilename = filenameBuffer; + freeHfs0ExeFsEntriesSizes(); + + hfs0ExeFsEntriesSizes = calloc(exeFsContext.exefs_header.file_cnt, sizeof(browser_entry_size_info)); + if (!hfs0ExeFsEntriesSizes) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for ExeFS entries size info!", __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); - addStringToFilenameBuffer(cur_filename, &nextFilename); + + snprintf(curName, MAX_CHARACTERS(curName), cur_filename); + + // Fix entry name length + truncateBrowserEntryName(curName); + + addStringToFilenameBuffer(curName); + + // Save entry size + hfs0ExeFsEntriesSizes[i].size = exeFsContext.exefs_entries[i].file_size; + convertSize(hfs0ExeFsEntriesSizes[i].size, hfs0ExeFsEntriesSizes[i].sizeStr, MAX_CHARACTERS(hfs0ExeFsEntriesSizes[i].sizeStr)); } return true; @@ -2431,7 +2771,7 @@ bool getRomFsParentDir(u32 dir_offset, bool usePatch, u32 *out) { if ((!usePatch && (!romFsContext.romfs_dirtable_size || dir_offset > romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries)) || (usePatch && (!bktrContext.romfs_dirtable_size || dir_offset > bktrContext.romfs_dirtable_size || !bktrContext.romfs_dir_entries))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve parent RomFS section directory!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve parent RomFS section directory!", __func__); return false; } @@ -2446,7 +2786,7 @@ bool generateCurrentRomFsPath(u32 dir_offset, bool usePatch) { if ((!usePatch && (!romFsContext.romfs_dirtable_size || dir_offset > romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries)) || (usePatch && (!bktrContext.romfs_dirtable_size || dir_offset > bktrContext.romfs_dirtable_size || !bktrContext.romfs_dir_entries))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to generate current RomFS section path!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate current RomFS section path!", __func__); return false; } @@ -2457,7 +2797,7 @@ bool generateCurrentRomFsPath(u32 dir_offset, bool usePatch) if (!entry->nameLen) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: directory entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: directory entry without name in RomFS section!", __func__); return false; } @@ -2489,17 +2829,13 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) u64 dirTableSize; u64 fileTableSize; - if (romFsBrowserEntries != NULL) - { - free(romFsBrowserEntries); - romFsBrowserEntries = NULL; - } + freeRomFsBrowserEntries(); memset(curRomFsPath, 0, NAME_BUF_LEN); if ((!usePatch && (!romFsContext.romfs_dirtable_size || dir_offset > romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries)) || (usePatch && (!bktrContext.romfs_dirtable_size || dir_offset > bktrContext.romfs_dirtable_size || !bktrContext.romfs_dir_entries || !bktrContext.romfs_filetable_size || !bktrContext.romfs_file_entries))) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve RomFS section filelist!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve RomFS section filelist!", __func__); return false; } @@ -2519,7 +2855,7 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) if (!entry->nameLen) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: directory entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: directory entry without name in RomFS section!", __func__); return false; } @@ -2538,7 +2874,7 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) if (!entry->nameLen) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: file entry without name in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: file entry without name in RomFS section!", __func__); return false; } @@ -2550,9 +2886,16 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) totalEntryCnt = (dirEntryCnt + fileEntryCnt); + 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, "Current RomFS dir contains more than %u entries! (%u entries)", FILENAME_MAX_CNT, totalEntryCnt); + 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; } @@ -2560,21 +2903,14 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) romFsBrowserEntries = calloc(totalEntryCnt, sizeof(romfs_browser_entry)); if (!romFsBrowserEntries) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for file/dir attributes in RomFS section!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to allocate memory for file/dir attributes in RomFS section!", __func__); return false; } - memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); - filenamesCount = 0; - - char *nextFilename = filenameBuffer; - - char curName[NAME_BUF_LEN] = {'\0'}; - // Add parent directory entry ("..") romFsBrowserEntries[0].type = ROMFS_ENTRY_DIR; romFsBrowserEntries[0].offset = romFsParentDir; - addStringToFilenameBuffer("..", &nextFilename); + addStringToFilenameBuffer(".."); // First add the directory entries if ((!romFsParentDir && dirEntryCnt > 1) || (romFsParentDir && dirEntryCnt > 0)) @@ -2594,20 +2930,9 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) snprintf(curName, entry->nameLen + 1, (char*)entry->name); // Fix entry name length - u32 strWidth = uiGetStrWidth(curName); + truncateBrowserEntryName(curName); - if ((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - while((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - curName[strlen(curName) - 1] = '\0'; - strWidth = uiGetStrWidth(curName); - } - - strcat(curName, "..."); - } - - addStringToFilenameBuffer(curName, &nextFilename); + addStringToFilenameBuffer(curName); i++; } @@ -2630,24 +2955,15 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) { romFsBrowserEntries[i].type = ROMFS_ENTRY_FILE; romFsBrowserEntries[i].offset = entryOffset; + romFsBrowserEntries[i].sizeInfo.size = entry->dataSize; + convertSize(entry->dataSize, romFsBrowserEntries[i].sizeInfo.sizeStr, MAX_CHARACTERS(romFsBrowserEntries[i].sizeInfo.sizeStr)); snprintf(curName, entry->nameLen + 1, (char*)entry->name); // Fix entry name length - u32 strWidth = uiGetStrWidth(curName); + truncateBrowserEntryName(curName); - if ((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - while((BROWSER_ICON_DIMENSION + 16 + strWidth) >= (FB_WIDTH - (font_height * 5))) - { - curName[strlen(curName) - 1] = '\0'; - strWidth = uiGetStrWidth(curName); - } - - strcat(curName, "..."); - } - - addStringToFilenameBuffer(curName, &nextFilename); + addStringToFilenameBuffer(curName); i++; } @@ -2656,120 +2972,47 @@ bool getRomFsFileList(u32 dir_offset, bool usePatch) } } +out: // Update current RomFS directory offset curRomFsDirOffset = dir_offset; return true; } -int getSdCardFreeSpace(u64 *out) +char *generateGameCardDumpName(bool useBrackets) { - Result result; - FsFileSystem *sdfs = NULL; - u64 size = 0; - int rc = 0; - - sdfs = fsdevGetDefaultFileSystem(); - if (!sdfs) - { - uiStatusMsg("getSdCardFreeSpace: fsdevGetDefaultFileSystem failed!"); - return rc; - } - - result = fsFsGetFreeSpace(sdfs, "/", &size); - if (R_SUCCEEDED(result)) - { - *out = size; - rc = 1; - } else { - uiStatusMsg("getSdCardFreeSpace: fsFsGetFreeSpace failed! (0x%08X)", result); - } - - return rc; -} - -void convertSize(u64 size, char *out, int bufsize) -{ - char buffer[16]; - double bytes = (double)size; - - if (bytes < 1000.0) - { - snprintf(buffer, sizeof(buffer), "%.0lf B", bytes); - } else - if (bytes < 10.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf KiB", floor((bytes*100.0)/KiB)/100.0); - } else - if (bytes < 100.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf KiB", floor((bytes*10.0)/KiB)/10.0); - } else - if (bytes < 1000.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.0lf KiB", floor(bytes/KiB)); - } else - if (bytes < 10.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf MiB", floor((bytes*100.0)/MiB)/100.0); - } else - if (bytes < 100.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf MiB", floor((bytes*10.0)/MiB)/10.0); - } else - if (bytes < 1000.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.0lf MiB", floor(bytes/MiB)); - } else - if (bytes < 10.0*GiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf GiB", floor((bytes*100.0)/GiB)/100.0); - } else - if (bytes < 100.0*GiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf GiB", floor((bytes*10.0)/GiB)/10.0); - } else { - snprintf(buffer, sizeof(buffer), "%.0lf GiB", floor(bytes/GiB)); - } - - snprintf(out, bufsize, "%s", buffer); -} - -void addStringToFilenameBuffer(const char *string, char **nextFilename) -{ - filenames[filenamesCount++] = *nextFilename; - snprintf(*nextFilename, FILENAME_LENGTH, string); - *nextFilename += FILENAME_LENGTH; -} - -char *generateFullDumpName() -{ - if (!titleAppCount || !fixedTitleName || !titleAppTitleID || !titleAppVersion) return NULL; + if (menuType != MENUTYPE_GAMECARD || !titleAppCount || !baseAppEntries) return NULL; u32 i, j; - char tmp[512] = {'\0'}; + char tmp[NAME_BUF_LEN / 4] = {'\0'}; char *fullname = NULL; char *fullnameTmp = NULL; - size_t strsize = NAME_BUF_LEN; - fullname = calloc(strsize, sizeof(char)); + size_t strsize = (NAME_BUF_LEN / 2); + + fullname = calloc(strsize + 1, sizeof(char)); if (!fullname) return NULL; for(i = 0; i < titleAppCount; i++) { - u32 highestVersion = titleAppVersion[i]; + u32 highestVersion = baseAppEntries[i].version; // Check if our current gamecard has any bundled updates for this application. If so, use the highest update version available - if (titlePatchCount > 0 && titlePatchTitleID != NULL && titlePatchVersion != NULL) + if (titlePatchCount && patchEntries) { for(j = 0; j < titlePatchCount; j++) { - if (titlePatchTitleID[j] == (titleAppTitleID[i] | APPLICATION_PATCH_BITMASK) && titlePatchVersion[j] > highestVersion) highestVersion = titlePatchVersion[j]; + if (checkIfPatchOrAddOnBelongsToBaseApplication(j, i, false) && patchEntries[j].version > highestVersion) highestVersion = patchEntries[j].version; } } - snprintf(tmp, MAX_ELEMENTS(tmp), "%s v%u (%016lX)", fixedTitleName[i], highestVersion, titleAppTitleID[i]); + if (useBrackets) + { + snprintf(tmp, MAX_CHARACTERS(tmp), "%s [%016lX][v%u]", baseAppEntries[i].fixedName, baseAppEntries[i].titleId, highestVersion); + } else { + snprintf(tmp, MAX_CHARACTERS(tmp), "%s v%u (%016lX)", baseAppEntries[i].fixedName, highestVersion, baseAppEntries[i].titleId); + } if ((strlen(fullname) + strlen(tmp) + 4) > strsize) { @@ -2781,6 +3024,7 @@ char *generateFullDumpName() if (fullnameTmp) { fullname = fullnameTmp; + fullnameTmp = NULL; memset(fullname + fullname_len, 0, strlen(tmp) + 4); } else { free(fullname); @@ -2797,284 +3041,219 @@ char *generateFullDumpName() return fullname; } -char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex) +char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex, bool useBrackets) { - if (!titleAppCount || !fixedTitleName || !titleAppTitleID || (selectedNspDumpType == DUMP_APP_NSP && !titleAppVersion) || (selectedNspDumpType == DUMP_PATCH_NSP && (!titlePatchCount || !titlePatchTitleID || !titlePatchVersion)) || (selectedNspDumpType == DUMP_ADDON_NSP && (!titleAddOnCount || !titleAddOnTitleID || !titleAddOnVersion))) return NULL; - - u32 app; - bool foundApp = false; + 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; - bool nameless = true; + size_t strsize = (NAME_BUF_LEN / 2); - size_t strsize = NAME_BUF_LEN; - char *fullname = calloc(strsize, sizeof(char)); + patch_addon_ctx_t *ptr = NULL; + + char *fullname = calloc(strsize + 1, sizeof(char)); if (!fullname) return NULL; - if (selectedNspDumpType == DUMP_APP_NSP) + switch(selectedNspDumpType) { - snprintf(fullname, strsize, "%s v%u (%016lX) (BASE)", fixedTitleName[titleIndex], titleAppVersion[titleIndex], titleAppTitleID[titleIndex]); - } else - if (selectedNspDumpType == DUMP_PATCH_NSP) - { - for(app = 0; app < titleAppCount; app++) - { - if (titlePatchTitleID[titleIndex] == (titleAppTitleID[app] | APPLICATION_PATCH_BITMASK)) + case DUMP_APP_NSP: + if (useBrackets) { - foundApp = true; - break; + snprintf(fullname, strsize, "%s [%016lX][v%u][BASE]", baseAppEntries[titleIndex].fixedName, baseAppEntries[titleIndex].titleId, baseAppEntries[titleIndex].version); + } else { + snprintf(fullname, strsize, "%s v%u (%016lX) (BASE)", baseAppEntries[titleIndex].fixedName, baseAppEntries[titleIndex].version, baseAppEntries[titleIndex].titleId); } - } - - if (foundApp) - { - snprintf(fullname, strsize, "%s v%u (%016lX) (UPD)", fixedTitleName[app], titlePatchVersion[titleIndex], titlePatchTitleID[titleIndex]); - } else { - if (orphanEntries != NULL && orphanEntriesCnt) + break; + case DUMP_PATCH_NSP: + case DUMP_ADDON_NSP: + ptr = (selectedNspDumpType == DUMP_PATCH_NSP ? &(patchEntries[titleIndex]) : &(addOnEntries[titleIndex])); + + // Look for the parent base application name + if (titleAppCount && baseAppEntries) { - for(i = 0; i < orphanEntriesCnt; i++) + for(i = 0; i < titleAppCount; i++) { - if (orphanEntries[i].type == ORPHAN_ENTRY_TYPE_PATCH && orphanEntries[i].index == titleIndex && strlen(orphanEntries[i].fixedName)) + if (checkIfPatchOrAddOnBelongsToBaseApplication(titleIndex, i, (selectedNspDumpType == DUMP_ADDON_NSP))) { - snprintf(fullname, strsize, "%s v%u (%016lX) (UPD)", orphanEntries[i].fixedName, titlePatchVersion[titleIndex], titlePatchTitleID[titleIndex]); - nameless = false; + if (useBrackets) + { + snprintf(fullname, strsize, "%s [%016lX][v%u][%s]", baseAppEntries[i].fixedName, ptr->titleId, ptr->version, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); + } else { + snprintf(fullname, strsize, "%s v%u (%016lX) (%s)", baseAppEntries[i].fixedName, ptr->version, ptr->titleId, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); + } + break; } } } - if (nameless) snprintf(fullname, strsize, "%016lX v%u (UPD)", titlePatchTitleID[titleIndex], titlePatchVersion[titleIndex]); - } - } else - if (selectedNspDumpType == DUMP_ADDON_NSP) - { - for(app = 0; app < titleAppCount; app++) - { - if ((titleAddOnTitleID[titleIndex] & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[app] & APPLICATION_ADDON_BITMASK)) + if (!strlen(fullname)) { - foundApp = true; - break; - } - } - - if (foundApp) - { - snprintf(fullname, strsize, "%s v%u (%016lX) (DLC)", fixedTitleName[app], titleAddOnVersion[titleIndex], titleAddOnTitleID[titleIndex]); - } else { - if (orphanEntries != NULL && orphanEntriesCnt) - { - for(i = 0; i < orphanEntriesCnt; i++) + // Look for the parent base application name in orphan entries + if (orphanEntries && orphanEntriesCnt) { - if (orphanEntries[i].type == ORPHAN_ENTRY_TYPE_ADDON && orphanEntries[i].index == titleIndex && strlen(orphanEntries[i].fixedName)) + for(i = 0; i < orphanEntriesCnt; i++) { - snprintf(fullname, strsize, "%s v%u (%016lX) (DLC)", orphanEntries[i].fixedName, titleAddOnVersion[titleIndex], titleAddOnTitleID[titleIndex]); - nameless = false; - break; + if (orphanEntries[i].index == titleIndex && strlen(orphanEntries[i].fixedName) && ((selectedNspDumpType == DUMP_PATCH_NSP && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_PATCH) || (selectedNspDumpType == DUMP_ADDON_NSP && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_ADDON))) + { + if (useBrackets) + { + snprintf(fullname, strsize, "%s [%016lX][v%u][%s]", orphanEntries[i].fixedName, ptr->titleId, ptr->version, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); + } else { + snprintf(fullname, strsize, "%s v%u (%016lX) (%s)", orphanEntries[i].fixedName, ptr->version, ptr->titleId, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); + } + + break; + } + } + } + + if (!strlen(fullname)) + { + // Nothing worked, just print the Title ID + version + if (useBrackets) + { + snprintf(fullname, strsize, "[%016lX][v%u][%s]", ptr->titleId, ptr->version, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); + } else { + snprintf(fullname, strsize, "%016lX v%u (%s)", ptr->titleId, ptr->version, (selectedNspDumpType == DUMP_PATCH_NSP ? "UPD" : "DLC")); } } } - if (nameless) snprintf(fullname, strsize, "%016lX v%u (DLC)", titleAddOnTitleID[titleIndex], titleAddOnVersion[titleIndex]); - } - } else { - free(fullname); - fullname = NULL; + break; + default: + free(fullname); + fullname = NULL; + break; } return fullname; } -void retrieveDescriptionForPatchOrAddOn(u64 titleID, u32 version, bool addOn, bool addAppName, const char *prefix, char *outBuf, size_t outBufSize) +void retrieveDescriptionForPatchOrAddOn(u32 titleIndex, bool addOn, bool addAppName, const char *prefix, char *outBuf, size_t outBufSize) { - if (!outBuf || !outBufSize) return; + if ((!addOn && (!titlePatchCount || !patchEntries || titleIndex >= titlePatchCount)) || (addOn && (!titleAddOnCount || !addOnEntries || titleIndex >= titleAddOnCount)) || !outBuf || !outBufSize) return; - char versionStr[128] = {'\0'}; - convertTitleVersionToDecimal(version, versionStr, MAX_ELEMENTS(versionStr)); + u32 i; + bool addPrefix = (prefix && strlen(prefix)); + patch_addon_ctx_t *ptr = (!addOn ? &(patchEntries[titleIndex]) : &(addOnEntries[titleIndex])); - if (!titleAppCount || !titleAppTitleID || !titleName || !*titleName || !addAppName) + // Check if we need to add the base application name + if (!addAppName || !titleAppCount || !baseAppEntries) { - if (prefix) + if (addPrefix) { - snprintf(outBuf, outBufSize, "%s%016lX v%s", prefix, titleID, versionStr); + snprintf(outBuf, outBufSize, "%s%016lX v%s", prefix, ptr->titleId, ptr->versionStr); } else { - snprintf(outBuf, outBufSize, "%016lX v%s", titleID, versionStr); + snprintf(outBuf, outBufSize, "%016lX v%s", ptr->titleId, ptr->versionStr); } return; } - u32 app; - bool foundApp = false; - - u32 i; - bool nameless = true; - - for(app = 0; app < titleAppCount; app++) + // Look for the parent base application name + for(i = 0; i < titleAppCount; i++) { - if ((!addOn && titleID == (titleAppTitleID[app] | APPLICATION_PATCH_BITMASK)) || (addOn && (titleID & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[app] & APPLICATION_ADDON_BITMASK))) + if (checkIfPatchOrAddOnBelongsToBaseApplication(titleIndex, i, addOn)) { - foundApp = true; - break; + if (addPrefix) + { + snprintf(outBuf, outBufSize, "%s%s | %016lX v%s", prefix, baseAppEntries[i].name, ptr->titleId, ptr->versionStr); + } else { + snprintf(outBuf, outBufSize, "%s | %016lX v%s", baseAppEntries[i].name, ptr->titleId, ptr->versionStr); + } + + return; } } - if (foundApp) + // Look for the parent base application name in orphan entries + if (orphanEntries != NULL && orphanEntriesCnt) { - if (prefix) + for(i = 0; i < orphanEntriesCnt; i++) { - snprintf(outBuf, outBufSize, "%s%s | %016lX v%s", prefix, titleName[app], titleID, versionStr); - } else { - snprintf(outBuf, outBufSize, "%s | %016lX v%s", titleName[app], titleID, versionStr); - } - } else { - if (orphanEntries != NULL && orphanEntriesCnt) - { - for(i = 0; i < orphanEntriesCnt; i++) + if (orphanEntries[i].index == titleIndex && strlen(orphanEntries[i].name) && ((!addOn && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_PATCH) || (addOn && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_ADDON))) { - if (strlen(orphanEntries[i].name) && ((!addOn && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_PATCH && titleID == titlePatchTitleID[orphanEntries[i].index]) || (addOn && orphanEntries[i].type == ORPHAN_ENTRY_TYPE_ADDON && titleID == titleAddOnTitleID[orphanEntries[i].index]))) + if (addPrefix) { - if (prefix) - { - snprintf(outBuf, outBufSize, "%s%s | %016lX v%s", prefix, orphanEntries[i].name, titleID, versionStr); - } else { - snprintf(outBuf, outBufSize, "%s | %016lX v%s", orphanEntries[i].name, titleID, versionStr); - } - - nameless = false; - break; + snprintf(outBuf, outBufSize, "%s%s | %016lX v%s", prefix, orphanEntries[i].name, ptr->titleId, ptr->versionStr); + } else { + snprintf(outBuf, outBufSize, "%s | %016lX v%s", orphanEntries[i].name, ptr->titleId, ptr->versionStr); } + + return; } } - - if (nameless) - { - if (prefix) - { - snprintf(outBuf, outBufSize, "%s%016lX v%s", prefix, titleID, versionStr); - } else { - snprintf(outBuf, outBufSize, "%016lX v%s", titleID, versionStr); - } - } + } + + // Nothing worked, just print the Title ID + version + if (addPrefix) + { + snprintf(outBuf, outBufSize, "%s%016lX v%s", prefix, ptr->titleId, ptr->versionStr); + } else { + snprintf(outBuf, outBufSize, "%016lX v%s", ptr->titleId, ptr->versionStr); } } -bool checkOrphanPatchOrAddOn(bool addOn) +u32 calculateOrphanPatchOrAddOnCount(bool addOn) { - if (!titleAppCount || !titleAppTitleID || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return false; + if ((!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return 0; + + if ((!titleAppCount || !baseAppEntries) && ((!addOn && titlePatchCount && patchEntries) || (addOn && titleAddOnCount && addOnEntries))) return (!addOn ? titlePatchCount : titleAddOnCount); u32 i, j; - u32 count = (!addOn ? titlePatchCount : titleAddOnCount); + u32 titleCount = (!addOn ? titlePatchCount : titleAddOnCount); + u32 orphanCnt = 0; - for(i = 0; i < count; i++) + for(i = 0; i < titleCount; i++) { bool foundMatch = false; for(j = 0; j < titleAppCount; j++) { - if ((!addOn && titlePatchTitleID[i] == (titleAppTitleID[j] | APPLICATION_PATCH_BITMASK)) || (addOn && (titleAddOnTitleID[i] & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[j] & APPLICATION_ADDON_BITMASK))) + if ((!addOn && patchEntries[i].titleId == (baseAppEntries[j].titleId | APPLICATION_PATCH_BITMASK)) || (addOn && (addOnEntries[i].titleId & APPLICATION_ADDON_BITMASK) == (baseAppEntries[j].titleId & APPLICATION_ADDON_BITMASK))) { foundMatch = true; break; } } - if (!foundMatch) return true; + if (foundMatch) continue; + + orphanCnt++; } - return false; -} - -void freeOrphanPatchOrAddOnList() -{ - if (orphanEntries != NULL) - { - free(orphanEntries); - orphanEntries = NULL; - } - - orphanEntriesCnt = 0; + return orphanCnt; } void generateOrphanPatchOrAddOnList() { Result result; - size_t nsAppRecordCnt = 0; + u32 nsAppRecordCnt = 0; bool foundMatch; - char versionStr[128] = {'\0'}; - u32 orphanPatchCount = 0, orphanAddOnCount = 0, orphanEntryIndex = 0; + u32 i, j, k; - u32 i, j; - char *nextFilename = filenameBuffer; - filenamesCount = 0; + u32 orphanEntryIndex = 0; + u32 orphanPatchCount = calculateOrphanPatchOrAddOnCount(false); + u32 orphanAddOnCount = calculateOrphanPatchOrAddOnCount(true); + + if (!orphanPatchCount && !orphanAddOnCount) return; + + if (orphanEntries && orphanEntriesCnt && orphanEntriesCnt == (orphanPatchCount + orphanAddOnCount)) goto out; freeOrphanPatchOrAddOnList(); - if ((!titlePatchCount || !titlePatchTitleID || !titlePatchVersion) && (!titleAddOnCount || !titleAddOnTitleID || !titleAddOnVersion)) return; - - // Retrieve all cached Application IDs - NsApplicationRecord *appRecords = calloc(1024, sizeof(NsApplicationRecord)); + // Retrieve all cached Application IDs (assuming no one has more than 2048 cached base applications...) + NsApplicationRecord *appRecords = calloc(2048, sizeof(NsApplicationRecord)); if (!appRecords) return; - result = nsListApplicationRecord(appRecords, 1024, 0, &nsAppRecordCnt); + result = nsListApplicationRecord(appRecords, 2048, 0, (s32*)&nsAppRecordCnt); if (R_FAILED(result)) { free(appRecords); return; } - if (titlePatchCount) - { - // Retrieve orphan patch count - for(i = 0; i < titlePatchCount; i++) - { - foundMatch = false; - - if (titleAppCount && titleAppTitleID) - { - for(j = 0; j < titleAppCount; j++) - { - if (titlePatchTitleID[i] == (titleAppTitleID[j] | APPLICATION_PATCH_BITMASK)) - { - foundMatch = true; - break; - } - } - } - - if (!foundMatch) orphanPatchCount++; - } - } - - if (titleAddOnCount) - { - // Retrieve orphan add-on count - for(i = 0; i < titleAddOnCount; i++) - { - foundMatch = false; - - if (titleAppCount && titleAppTitleID) - { - for(j = 0; j < titleAppCount; j++) - { - if ((titleAddOnTitleID[i] & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[j] & APPLICATION_ADDON_BITMASK)) - { - foundMatch = true; - break; - } - } - } - - if (!foundMatch) orphanAddOnCount++; - } - } - - if (!orphanPatchCount && !orphanAddOnCount) - { - free(appRecords); - return; - } - // Allocate memory for our orphan entries orphanEntries = calloc(orphanPatchCount + orphanAddOnCount, sizeof(orphan_patch_addon_entry)); if (!orphanEntries) @@ -3083,18 +3262,20 @@ void generateOrphanPatchOrAddOnList() return; } - if (orphanPatchCount) + // Save orphan patch & add-on data + for(i = 0; i < 2; i++) { - // Save orphan patch data - for(i = 0; i < titlePatchCount; i++) + u32 titleCount = (i == 0 ? titlePatchCount : titleAddOnCount); + + for(j = 0; j < titleCount; j++) { foundMatch = false; - if (titleAppCount && titleAppTitleID) + if (titleAppCount && baseAppEntries) { - for(j = 0; j < titleAppCount; j++) + for(k = 0; k < titleAppCount; k++) { - if (titlePatchTitleID[i] == (titleAppTitleID[j] | APPLICATION_PATCH_BITMASK)) + if (checkIfPatchOrAddOnBelongsToBaseApplication(j, k, (i == 1))) { foundMatch = true; break; @@ -3102,111 +3283,63 @@ void generateOrphanPatchOrAddOnList() } } - if (!foundMatch) - { - // Look for a matching Application ID in our NS records - for(j = 0; j < nsAppRecordCnt; j++) - { - if (titlePatchTitleID[i] == (appRecords[j].titleID | APPLICATION_PATCH_BITMASK)) - { - if (getTitleControlNacp(appRecords[j].titleID, orphanEntries[orphanEntryIndex].name, NACP_APPNAME_LEN, NULL, 0, NULL)) - { - snprintf(orphanEntries[orphanEntryIndex].fixedName, NACP_APPNAME_LEN, orphanEntries[orphanEntryIndex].name); - removeIllegalCharacters(orphanEntries[orphanEntryIndex].fixedName); - } - - break; - } - } - - if (strlen(orphanEntries[orphanEntryIndex].name)) - { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s v%u (%016lX) (Update)", orphanEntries[orphanEntryIndex].name, titlePatchVersion[i], titlePatchTitleID[i]); - } else { - convertTitleVersionToDecimal(titlePatchVersion[i], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%016lX v%s (Update)", titlePatchTitleID[i], versionStr); - } - - addStringToFilenameBuffer(strbuf, &nextFilename); - - orphanEntries[orphanEntryIndex].index = i; - orphanEntries[orphanEntryIndex].type = ORPHAN_ENTRY_TYPE_PATCH; - - orphanEntryIndex++; - } - } - } - - if (orphanAddOnCount) - { - // Save orphan add-on data - for(i = 0; i < titleAddOnCount; i++) - { - foundMatch = false; + if (foundMatch) continue; - if (titleAppCount && titleAppTitleID) + patch_addon_ctx_t *ptr = (i == 0 ? &(patchEntries[j]) : &(addOnEntries[j])); + + // Look for a matching Application ID in our NS records + for(k = 0; k < nsAppRecordCnt; k++) { - for(j = 0; j < titleAppCount; j++) + if ((i == 0 && ptr->titleId == (appRecords[k].application_id | APPLICATION_PATCH_BITMASK)) || (i == 1 && (ptr->titleId & APPLICATION_ADDON_BITMASK) == (appRecords[k].application_id & APPLICATION_ADDON_BITMASK))) { - if ((titleAddOnTitleID[i] & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[j] & APPLICATION_ADDON_BITMASK)) + if (getCachedBaseApplicationNacpMetadata(appRecords[k].application_id, orphanEntries[orphanEntryIndex].name, MAX_CHARACTERS(orphanEntries[orphanEntryIndex].name), NULL, 0, NULL)) { - foundMatch = true; - break; + strtrim(orphanEntries[orphanEntryIndex].name); + snprintf(orphanEntries[orphanEntryIndex].fixedName, MAX_CHARACTERS(orphanEntries[orphanEntryIndex].fixedName), orphanEntries[orphanEntryIndex].name); + removeIllegalCharacters(orphanEntries[orphanEntryIndex].fixedName); } + + break; } } - if (!foundMatch) + if (strlen(orphanEntries[orphanEntryIndex].name)) { - // Look for a matching Application ID in our NS records - for(j = 0; j < nsAppRecordCnt; j++) - { - if ((titleAddOnTitleID[i] & APPLICATION_ADDON_BITMASK) == (appRecords[j].titleID & APPLICATION_ADDON_BITMASK)) - { - if (getTitleControlNacp(appRecords[j].titleID, orphanEntries[orphanEntryIndex].name, NACP_APPNAME_LEN, NULL, 0, NULL)) - { - snprintf(orphanEntries[orphanEntryIndex].fixedName, NACP_APPNAME_LEN, orphanEntries[orphanEntryIndex].name); - removeIllegalCharacters(orphanEntries[orphanEntryIndex].fixedName); - } - - break; - } - } - - if (strlen(orphanEntries[orphanEntryIndex].name)) - { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%s v%u (%016lX) (DLC)", orphanEntries[orphanEntryIndex].name, titleAddOnVersion[i], titleAddOnTitleID[i]); - } else { - convertTitleVersionToDecimal(titleAddOnVersion[i], versionStr, MAX_ELEMENTS(versionStr)); - snprintf(strbuf, MAX_ELEMENTS(strbuf), "%016lX v%s (DLC)", titleAddOnTitleID[i], versionStr); - } - - addStringToFilenameBuffer(strbuf, &nextFilename); - - orphanEntries[orphanEntryIndex].index = i; - orphanEntries[orphanEntryIndex].type = ORPHAN_ENTRY_TYPE_ADDON; - - orphanEntryIndex++; + snprintf(orphanEntries[orphanEntryIndex].orphanListStr, MAX_CHARACTERS(orphanEntries[orphanEntryIndex].orphanListStr), "%s v%u (%016lX) (%s)", orphanEntries[orphanEntryIndex].name, ptr->version, ptr->titleId, (i == 0 ? "Update" : "DLC")); + } else { + snprintf(orphanEntries[orphanEntryIndex].orphanListStr, MAX_CHARACTERS(orphanEntries[orphanEntryIndex].orphanListStr), "%016lX v%u (%s)", ptr->titleId, ptr->version, (i == 0 ? "Update" : "DLC")); } + + orphanEntries[orphanEntryIndex].index = j; + orphanEntries[orphanEntryIndex].type = (i == 0 ? ORPHAN_ENTRY_TYPE_PATCH : ORPHAN_ENTRY_TYPE_ADDON); + + orphanEntryIndex++; } } orphanEntriesCnt = (orphanPatchCount + orphanAddOnCount); - filenamesCount = orphanEntriesCnt; free(appRecords); + + // Sort orphan titles by name + qsort(orphanEntries, orphanEntriesCnt, sizeof(orphan_patch_addon_entry), orphanEntryCmp); + +out: + clearFilenameBuffer(); + + for(i = 0; i < orphanEntriesCnt; i++) addStringToFilenameBuffer(orphanEntries[i].orphanListStr); } bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn) { - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return false; + if (!titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return false; u32 i; u32 count = (!addOn ? titlePatchCount : titleAddOnCount); for(i = 0; i < count; i++) { - if ((!addOn && (titleAppTitleID[appIndex] | APPLICATION_PATCH_BITMASK) == titlePatchTitleID[i]) || (addOn && (titleAppTitleID[appIndex] & APPLICATION_ADDON_BITMASK) == (titleAddOnTitleID[i] & APPLICATION_ADDON_BITMASK))) return true; + if ((!addOn && (baseAppEntries[appIndex].titleId | APPLICATION_PATCH_BITMASK) == patchEntries[i].titleId) || (addOn && (baseAppEntries[appIndex].titleId & APPLICATION_ADDON_BITMASK) == (addOnEntries[i].titleId & APPLICATION_ADDON_BITMASK))) return true; } return false; @@ -3214,16 +3347,16 @@ bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn) bool checkIfPatchOrAddOnBelongsToBaseApplication(u32 titleIndex, u32 appIndex, bool addOn) { - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || (!addOn && (!titlePatchCount || titleIndex > (titlePatchCount - 1) || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || titleIndex > (titleAddOnCount - 1) || !titleAddOnTitleID))) return false; + if (!titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries || titleIndex >= titlePatchCount)) || (addOn && (!titleAddOnCount || !addOnEntries || titleIndex >= titleAddOnCount))) return false; - if ((!addOn && titlePatchTitleID[titleIndex] == (titleAppTitleID[appIndex] | APPLICATION_PATCH_BITMASK)) || (addOn && (titleAddOnTitleID[titleIndex] & APPLICATION_ADDON_BITMASK) == (titleAppTitleID[appIndex] & APPLICATION_ADDON_BITMASK))) return true; + if ((!addOn && patchEntries[titleIndex].titleId == (baseAppEntries[appIndex].titleId | APPLICATION_PATCH_BITMASK)) || (addOn && (addOnEntries[titleIndex].titleId & APPLICATION_ADDON_BITMASK) == (baseAppEntries[appIndex].titleId & APPLICATION_ADDON_BITMASK))) return true; return false; } u32 retrieveFirstPatchOrAddOnIndexFromBaseApplication(u32 appIndex, bool addOn) { - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return 0; + if (!titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return 0; u32 titleIndex; u32 count = (!addOn ? titlePatchCount : titleAddOnCount); @@ -3242,11 +3375,11 @@ u32 retrievePreviousPatchOrAddOnIndexFromBaseApplication(u32 startTitleIndex, u3 u32 retTitleIndex = startTitleIndex; u32 curTitleIndex = 0; - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || !startTitleIndex || startTitleIndex >= count || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return retTitleIndex; + if (!startTitleIndex || startTitleIndex >= count || !titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return retTitleIndex; for(curTitleIndex = startTitleIndex; curTitleIndex > 0; curTitleIndex--) { - if (checkIfPatchOrAddOnBelongsToBaseApplication((curTitleIndex - 1), appIndex, addOn)) + if (checkIfPatchOrAddOnBelongsToBaseApplication(curTitleIndex - 1, appIndex, addOn)) { retTitleIndex = (curTitleIndex - 1); break; @@ -3262,7 +3395,7 @@ u32 retrieveNextPatchOrAddOnIndexFromBaseApplication(u32 startTitleIndex, u32 ap u32 retTitleIndex = startTitleIndex; u32 curTitleIndex = 0; - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || startTitleIndex >= count || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return retTitleIndex; + if (startTitleIndex >= count || !titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return retTitleIndex; for(curTitleIndex = (startTitleIndex + 1); curTitleIndex < count; curTitleIndex++) { @@ -3278,7 +3411,7 @@ u32 retrieveNextPatchOrAddOnIndexFromBaseApplication(u32 startTitleIndex, u32 ap u32 retrieveLastPatchOrAddOnIndexFromBaseApplication(u32 appIndex, bool addOn) { - if (!titleAppCount || appIndex > (titleAppCount - 1) || !titleAppTitleID || (!addOn && (!titlePatchCount || !titlePatchTitleID)) || (addOn && (!titleAddOnCount || !titleAddOnTitleID))) return 0; + if (!titleAppCount || !baseAppEntries || appIndex >= titleAppCount || (!addOn && (!titlePatchCount || !patchEntries)) || (addOn && (!titleAddOnCount || !addOnEntries))) return 0; u32 titleIndex; u32 count = (!addOn ? titlePatchCount : titleAddOnCount); @@ -3302,7 +3435,7 @@ void waitForButtonPress() hidScanInput(); - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + u64 keysDown = hidKeysDown(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; @@ -3320,18 +3453,18 @@ void printProgressBar(progress_ctx_t *progressCtx, bool calcData, u64 chunkSize) // Workaround to properly calculate speed for sequential dumps u64 speedCurOffset = (progressCtx->seqDumpCurOffset ? progressCtx->seqDumpCurOffset : progressCtx->curOffset); - progressCtx->lastSpeed = (((double)(speedCurOffset + chunkSize) / (double)DUMP_BUFFER_SIZE) / (double)(progressCtx->now - progressCtx->start)); + progressCtx->lastSpeed = (((double)(speedCurOffset + chunkSize) / (double)MiB) / (double)(progressCtx->now - progressCtx->start)); progressCtx->averageSpeed = ((SMOOTHING_FACTOR * progressCtx->lastSpeed) + ((1 - SMOOTHING_FACTOR) * progressCtx->averageSpeed)); if (!isnormal(progressCtx->averageSpeed)) progressCtx->averageSpeed = SMOOTHING_FACTOR; // Very low values - progressCtx->remainingTime = (u64)(((double)(progressCtx->totalSize - (progressCtx->curOffset + chunkSize)) / (double)DUMP_BUFFER_SIZE) / progressCtx->averageSpeed); + progressCtx->remainingTime = (u64)(((double)(progressCtx->totalSize - (progressCtx->curOffset + chunkSize)) / (double)MiB) / progressCtx->averageSpeed); progressCtx->progress = (u8)(((progressCtx->curOffset + chunkSize) * 100) / progressCtx->totalSize); } - formatETAString(progressCtx->remainingTime, progressCtx->etaInfo, MAX_ELEMENTS(progressCtx->etaInfo)); + formatETAString(progressCtx->remainingTime, progressCtx->etaInfo, MAX_CHARACTERS(progressCtx->etaInfo)); - convertSize(progressCtx->curOffset + chunkSize, progressCtx->curOffsetStr, MAX_ELEMENTS(progressCtx->curOffsetStr)); + convertSize(progressCtx->curOffset + chunkSize, progressCtx->curOffsetStr, MAX_CHARACTERS(progressCtx->curOffsetStr)); uiFill(0, (progressCtx->line_offset * LINE_HEIGHT) + 8, FB_WIDTH / 4, LINE_HEIGHT * 2, BG_COLOR_RGB); uiDrawString(font_height * 2, STRING_Y_POS(progressCtx->line_offset), FONT_COLOR_RGB, "%.2lf MiB/s [ETA: %s]", progressCtx->averageSpeed, progressCtx->etaInfo); @@ -3443,7 +3576,7 @@ bool yesNoPrompt(const char *message) hidScanInput(); - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + u64 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); if (keysDown & KEY_A) { @@ -3509,7 +3642,7 @@ bool checkIfDumpedNspContainsConsoleData(const char *nspPath) size_t read_bytes; pfs0_header nspHeader; - pfs0_entry_table *nspEntries = NULL; + pfs0_file_entry *nspEntries = NULL; char *nspStrTable = NULL; u32 i; @@ -3539,22 +3672,22 @@ 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_entry_table) * (u64)nspHeader.file_cnt) + (u64)nspHeader.str_table_size)) + 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)) { fclose(nspFile); return false; } - nspEntries = calloc((u64)nspHeader.file_cnt, sizeof(pfs0_entry_table)); + nspEntries = calloc((u64)nspHeader.file_cnt, sizeof(pfs0_file_entry)); if (!nspEntries) { fclose(nspFile); return false; } - read_bytes = fread(nspEntries, 1, sizeof(pfs0_entry_table) * (u64)nspHeader.file_cnt, nspFile); + read_bytes = fread(nspEntries, 1, sizeof(pfs0_file_entry) * (u64)nspHeader.file_cnt, nspFile); - if (read_bytes != (sizeof(pfs0_entry_table) * (u64)nspHeader.file_cnt)) + if (read_bytes != (sizeof(pfs0_file_entry) * (u64)nspHeader.file_cnt)) { free(nspEntries); fclose(nspFile); @@ -3585,7 +3718,7 @@ bool checkIfDumpedNspContainsConsoleData(const char *nspPath) if (!strncasecmp(curFilename + strlen(curFilename) - 4, ".tik", 4)) { - tikOffset = (sizeof(pfs0_header) + (sizeof(pfs0_entry_table) * (u64)nspHeader.file_cnt) + (u64)nspHeader.str_table_size + nspEntries[i].file_offset); + tikOffset = (sizeof(pfs0_header) + (sizeof(pfs0_file_entry) * (u64)nspHeader.file_cnt) + (u64)nspHeader.str_table_size + nspEntries[i].file_offset); tikSize = nspEntries[i].file_size; foundTik = true; break; @@ -3611,7 +3744,7 @@ bool checkIfDumpedNspContainsConsoleData(const char *nspPath) sha256CalculateHash(titlekey_block_0x190_hash, tikData.titlekey_block + 0x10, 0xF0); - if (!strncmp(tikData.sig_issuer, "Root-CA00000003-XS00000021", 26) || memcmp(titlekey_block_0x190_hash, titlekey_block_0x190_empty_hash, 0x20) != 0 || tikData.titlekey_type != ETICKET_TITLEKEY_COMMON || tikData.ticket_id != 0 || tikData.device_id != 0 || tikData.account_id != 0) return true; + if (strncmp(tikData.sig_issuer, "Root-CA00000003-XS00000020", 26) != 0 || memcmp(titlekey_block_0x190_hash, titlekey_block_0x190_empty_hash, 0x20) != 0 || tikData.titlekey_type != ETICKET_TITLEKEY_COMMON || tikData.ticket_id != 0 || tikData.device_id != 0 || tikData.account_id != 0) return true; return false; } @@ -3633,99 +3766,64 @@ void removeDirectoryWithVerbose(const char *path, const char *msg) breaks -= 2; } -bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u64 gc_tid, u32 crc) +static bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u64 gc_tid, u32 crc) { if (!doc || !cur) return false; - xmlChar *key; + xmlChar *key = NULL; xmlNodePtr node = cur; - u8 imageSize = (u8)(gameCardSize / GAMECARD_SIZE_1GiB); - - u8 xmlImageSize = 0; - u64 xmlTitleID = 0; u32 xmlCrc = 0; char xmlReleaseName[256] = {'\0'}; bool found = false; - while(node != NULL) + while(node) { - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImageSize))) + if ((!xmlStrcmp(node->name, (const xmlChar*)nswReleasesChildrenImgCrc))) { key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - if (key) - { - xmlImageSize = (u8)atoi((const char*)key); - xmlFree(key); - } + if (key) xmlCrc = strtoul((const char*)key, NULL, 16); } else - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenTitleID))) + if ((!xmlStrcmp(node->name, (const xmlChar*)nswReleasesChildrenReleaseName))) { key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - if (key) - { - xmlTitleID = strtoull((const char*)key, NULL, 16); - xmlFree(key); - } - } else - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImgCrc))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - if (key) - { - xmlCrc = strtoul((const char*)key, NULL, 16); - xmlFree(key); - } + if (key) snprintf(xmlReleaseName, MAX_CHARACTERS(xmlReleaseName), "%s", (const char*)key); } - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenReleaseName))) + + if (key) { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - if (key) - { - snprintf(xmlReleaseName, MAX_ELEMENTS(xmlReleaseName), "%s", (char*)key); - xmlFree(key); - } + xmlFree(key); + key = NULL; } node = node->next; } - /*if (xmlImageSize && xmlTitleID && strlen(xmlReleaseName)) + if (strlen(xmlReleaseName) && xmlCrc == crc) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "XML Image Size: %u.", xmlImageSize); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "XML Title ID: %016lX.", xmlTitleID); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "XML Image CRC32: %08X.", xmlCrc); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "XML Release Name: %s.", xmlReleaseName); - breaks += 2; - }*/ - - if (xmlImageSize == imageSize && xmlTitleID == gc_tid && xmlCrc == crc) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Found matching Scene release: \"%s\" (CRC32: %08X). This is a good dump!", xmlReleaseName, xmlCrc); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Found matching Scene release: \"%s\" (CRC32: %08X). This is likely a good dump!", xmlReleaseName, xmlCrc); found = true; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Dump doesn't match Scene release: \"%s\"! (CRC32: %08X)", xmlReleaseName, xmlCrc); } - breaks++; - return found; } -xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath) +static xmlXPathObjectPtr getXPathNodeSet(xmlDocPtr doc, char *xpathExpr) { + if (!doc || !xpathExpr || !strlen(xpathExpr)) return NULL; + xmlXPathContextPtr context = NULL; xmlXPathObjectPtr result = NULL; context = xmlXPathNewContext(doc); - result = xmlXPathEvalExpression(xpath, context); + if (!context) return NULL; + + result = xmlXPathEvalExpression((xmlChar*)xpathExpr, context); + + xmlXPathFreeContext(context); + + if (!result) return NULL; if (xmlXPathNodeSetIsEmpty(result->nodesetval)) { @@ -3738,73 +3836,51 @@ xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath) void gameCardDumpNSWDBCheck(u32 crc) { - if (!titleAppCount || !titleAppTitleID || !hfs0_partition_cnt) return; + if (menuType != MENUTYPE_GAMECARD || !titleAppCount || !baseAppEntries || !gameCardInfo.hfs0PartitionCnt) return; - u32 i; + u32 i, j; xmlDocPtr doc = NULL; bool found = false; doc = xmlParseFile(nswReleasesXmlPath); if (!doc) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open and/or parse \"%s\"!", nswReleasesXmlPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open and/or parse \"%s\"!", __func__, nswReleasesXmlPath); return; } for(i = 0; i < titleAppCount; i++) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, titleAppTitleID[i]); + snprintf(strbuf, MAX_CHARACTERS(strbuf), "//%s/%s[.//%s[contains(.,'%016lX')]]", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, baseAppEntries[i].titleId); - xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf); - if (nodeSet) + xmlXPathObjectPtr nodeSet = getXPathNodeSet(doc, strbuf); + if (!nodeSet) continue; + + for(j = 0; j < nodeSet->nodesetval->nodeNr; j++) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Found %d %s with Title ID \"%016lX\".", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), titleAppTitleID[i]); - breaks++; - - uiRefreshDisplay(); - - u32 i; - for(i = 0; i < nodeSet->nodesetval->nodeNr; i++) - { - xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode; - - found = parseNSWDBRelease(doc, node, titleAppTitleID[i], crc); - if (found) break; - } - - xmlXPathFreeObject(nodeSet); - - if (!found) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No checksum matches found in XML document for Title ID \"%016lX\"!", titleAppTitleID[i]); - if ((i + 1) < titleAppCount) breaks += 2; - } else { - breaks--; - break; - } - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No records with Title ID \"%016lX\" found within the XML document!", titleAppTitleID[i]); - if ((i + 1) < titleAppCount) breaks += 2; + xmlNodePtr node = nodeSet->nodesetval->nodeTab[j]->xmlChildrenNode; + found = parseNSWDBRelease(doc, node, baseAppEntries[i].titleId, crc); + if (found) break; } + + xmlXPathFreeObject(nodeSet); + + if (found) break; } xmlFreeDoc(doc); - if (!found) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "This could either be a bad dump or an undumped cartridge."); - } + if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No match found in NSWDB.COM XML database! This could either be a bad dump or an undumped gamecard."); } -Result networkInit() +static Result networkInit() { Result result = socketInitializeDefault(); if (R_SUCCEEDED(result)) curl_global_init(CURL_GLOBAL_ALL); return result; } -void networkDeinit() +static void networkDeinit() { curl_global_cleanup(); socketExit(); @@ -3849,11 +3925,11 @@ static size_t writeCurlBuffer(char *buffer, size_t size, size_t number_of_items, return bsz; } -bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool forceHttps, bool verbose) +static bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool forceHttps, bool verbose) { if (!curl || !url || !strlen(url)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to perform CURL request!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform CURL request!", __func__); return false; } @@ -3894,12 +3970,64 @@ bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool forceHt if (verbose) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Successfully downloaded %.0lf bytes!", size); success = true; } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: CURL request failed for \"%s\" endpoint!\nHTTP status code: %ld", url, http_code); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CURL request failed for \"%s\" endpoint!\nHTTP status code: %ld", __func__, url, http_code); } return success; } +void noIntroDumpCheck(bool isDigital, u32 crc) +{ + Result result; + CURL *curl = NULL; + char noIntroUrl[128] = {'\0'}; + + // Build URL + // 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); + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Performing CRC32 checksum lookup against No-Intro, please wait..."); + uiRefreshDisplay(); + breaks++; + + result = networkInit(); + if (R_FAILED(result)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result); + goto out; + } + + curl = curl_easy_init(); + if (!curl) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__); + goto out; + } + + if (!performCurlRequest(curl, noIntroUrl, NULL, true, false)) goto out; + + if (!strlen(result_buf) || !strncmp(result_buf, "unknown crc32", 13)) + { + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No match found in No-Intro database! This could either be a bad dump or an undumped %s.", (isDigital ? "digital title" : "gamecard")); + goto out; + } + + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Found matching No-Intro database entry: \"%s\". This is likely a good dump!", result_buf); + +out: + if (result_buf) + { + free(result_buf); + result_buf = NULL; + } + + if (curl) curl_easy_cleanup(curl); + + if (R_SUCCEEDED(result)) networkDeinit(); +} + void updateNSWDBXml() { Result result; @@ -3910,21 +4038,21 @@ void updateNSWDBXml() result = networkInit(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize socket! (%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result); goto out; } curl = curl_easy_init(); if (!curl) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize CURL context!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__); goto out; } nswdbXml = fopen(nswReleasesXmlTmpPath, "wb"); if (!nswdbXml) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open \"%s\" in write mode!", nswReleasesXmlTmpPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, nswReleasesXmlTmpPath); goto out; } @@ -3960,7 +4088,7 @@ out: breaks += 2; } -int versionNumCmp(char *ver1, char *ver2) +static int versionNumCmp(char *ver1, char *ver2) { int i, curPart, res; char *token = NULL; @@ -4074,7 +4202,7 @@ static const char *retrieveJsonObjStrMember(struct json_object *jobj, char *memb { if (!jobj || !memberName || !strlen(memberName)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve string member from JSON object!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve string member from JSON object!", __func__); return NULL; } @@ -4083,20 +4211,20 @@ static const char *retrieveJsonObjStrMember(struct json_object *jobj, char *memb if (!json_object_object_get_ex(jobj, memberName, &memberObj)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve object \"%s\" from JSON response!", memberName); + 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; } if (json_object_get_type(memberObj) != json_type_string) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid type for object \"%s\" in JSON response! (expected \"string\")", memberName); + 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; } memberObjStr = json_object_get_string(memberObj); if (!memberObjStr || !strlen(memberObjStr)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: object \"%s\" from JSON response is empty!", memberName); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: object \"%s\" from JSON response is empty!", __func__, memberName); memberObjStr = NULL; } @@ -4107,7 +4235,7 @@ static struct json_object *retrieveJsonObjArrayElementWithIndex(struct json_obje { if (!jobj || !memberName || !strlen(memberName)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve indexed element in array member from JSON object!"); + 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__); return NULL; } @@ -4115,18 +4243,18 @@ static struct json_object *retrieveJsonObjArrayElementWithIndex(struct json_obje if (!json_object_object_get_ex(jobj, memberName, &memberObj)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to retrieve object \"%s\" from JSON response!", memberName); + 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; } if (json_object_get_type(memberObj) != json_type_array) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid type for object \"%s\" in JSON response! (expected \"array\")", memberName); + 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, "Error: unable to parse object at index %lu from \"%s\" array in JSON response!"); + 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__); return memberObjArrayElement; } @@ -4135,7 +4263,7 @@ bool updateApplication() { if (envIsNso()) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to update application. Not running as a NRO."); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to update application. Not running as a NRO.", __func__); breaks += 2; return false; } @@ -4151,19 +4279,19 @@ bool updateApplication() const char *nameObjStr = NULL, *dlUrlObjStr = NULL; char nroPath[NAME_BUF_LEN] = {'\0'}; - snprintf(nroPath, MAX_ELEMENTS(nroPath), "%s.tmp", (strlen(appLaunchPath) ? appLaunchPath : nxDumpToolPath)); + snprintf(nroPath, MAX_CHARACTERS(nroPath), "%s.tmp", (strlen(appLaunchPath) ? appLaunchPath : nxDumpToolPath)); result = networkInit(); if (R_FAILED(result)) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize socket! (%08X)", result); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result); goto out; } curl = curl_easy_init(); if (!curl) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize CURL context!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__); goto out; } @@ -4182,14 +4310,14 @@ bool updateApplication() jobj = json_tokener_parse(result_buf); if (!jobj) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to parse JSON response!"); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse JSON response!", __func__); goto out; } nameObjStr = retrieveJsonObjStrMember(jobj, "name"); if (!nameObjStr) goto out; - snprintf(releaseTag, MAX_ELEMENTS(releaseTag), nameObjStr); + snprintf(releaseTag, MAX_CHARACTERS(releaseTag), nameObjStr); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Latest release: %s.", releaseTag); breaks++; @@ -4248,7 +4376,7 @@ bool updateApplication() nxDumpToolNro = fopen(nroPath, "wb"); if (!nxDumpToolNro) { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open \"%s\" in write mode!", nroPath); + uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, nroPath); goto out; } @@ -4265,7 +4393,7 @@ out: if (success) { - snprintf(strbuf, MAX_ELEMENTS(strbuf), nroPath); + snprintf(strbuf, MAX_CHARACTERS(strbuf), nroPath); nroPath[strlen(nroPath) - 4] = '\0'; unlink(nroPath); diff --git a/source/util.h b/source/util.h index a90a265..4e7e3d2 100644 --- a/source/util.h +++ b/source/util.h @@ -19,24 +19,24 @@ #define KEYS_FILE_PATH APP_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 KiB (1024.0) #define MiB (1024.0 * KiB) #define GiB (1024.0 * MiB) #define NAME_BUF_LEN 4096 -#define SOCK_BUFFERSIZE 65536 +#define DUMP_BUFFER_SIZE (u64)0x400000 // 4 MiB (4194304 bytes) -#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB (1048576 bytes) +#define GAMECARD_READ_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes) -#define NCA_CTR_BUFFER_SIZE DUMP_BUFFER_SIZE // 1 MiB (1048576 bytes) +#define NCA_CTR_BUFFER_SIZE DUMP_BUFFER_SIZE // 4 MiB (4194304 bytes) #define NSP_XML_BUFFER_SIZE (u64)0xA00000 // 10 MiB (10485760 bytes) -#define META_DB_REGULAR_APPLICATION 0x80 -#define META_DB_PATCH 0x81 -#define META_DB_ADDON 0x82 - #define APPLICATION_PATCH_BITMASK (u64)0x800 #define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000 @@ -48,32 +48,13 @@ #define NACP_AUTHOR_LEN 0x100 #define VERSION_STR_LEN 0x40 -#define GAMECARD_WAIT_TIME 3 // 3 seconds - -#define GAMECARD_HEADER_SIZE 0x200 -#define GAMECARD_SIZE_ADDR 0x10D -#define GAMECARD_DATAEND_ADDR 0x118 - -#define GAMECARD_ECC_BLOCK_SIZE (u64)0x200 // 512 bytes -#define GAMECARD_ECC_DATA_SIZE (u64)0x24 // 36 bytes - -#define HFS0_OFFSET_ADDR 0x130 -#define HFS0_SIZE_ADDR 0x138 -#define HFS0_MAGIC (u32)0x48465330 // "HFS0" -#define HFS0_FILE_COUNT_ADDR 0x04 -#define HFS0_STR_TABLE_SIZE_ADDR 0x08 -#define HFS0_ENTRY_TABLE_ADDR 0x10 - #define MEDIA_UNIT_SIZE 0x200 -#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "update" (2) -#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "update" (3) -#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown")) -#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown"))) -#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown")))) -#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown")) +#define ISTORAGE_PARTITION_CNT 2 -#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < 2 ? 0 : 1) : ((y) < 3 ? 0 : 1)) +#define GAMECARD_WAIT_TIME 3 // 3 seconds + +#define GAMECARD_HEADER_MAGIC (u32)0x48454144 // "HEAD" #define GAMECARD_SIZE_1GiB (u64)0x40000000 #define GAMECARD_SIZE_2GiB (u64)0x80000000 @@ -82,9 +63,22 @@ #define GAMECARD_SIZE_16GiB (u64)0x400000000 #define GAMECARD_SIZE_32GiB (u64)0x800000000 -/* Reference: https://switchbrew.org/wiki/Title_list */ #define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816 +#define GAMECARD_ECC_BLOCK_SIZE (u64)0x200 // 512 bytes +#define GAMECARD_ECC_DATA_SIZE (u64)0x24 // 36 bytes + +#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2) +#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3) +#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown")) +#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown"))) +#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown")))) +#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown")) + +#define HFS0_MAGIC (u32)0x48465330 // "HFS0" + +#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < (GAMECARD_TYPE1_PARTITION_CNT - 1) ? 0 : 1) : ((y) < (GAMECARD_TYPE2_PARTITION_CNT - 1) ? 0 : 1)) + #define NACP_ICON_SQUARE_DIMENSION 256 #define NACP_ICON_DOWNSCALED 96 @@ -95,12 +89,117 @@ #define ORPHAN_ENTRY_TYPE_ADDON 2 #define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array +#define MAX_CHARACTERS(x) (MAX_ELEMENTS((x)) - 1) // Returns the max number of characters that can be stored in char array while also leaving space for a NULL terminator #define BIS_MOUNT_NAME "sys:" #define BIS_CERT_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e0" #define BIS_COMMON_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e1" #define BIS_PERSONALIZED_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e2" +#define SMOOTHING_FACTOR (double)0.1 + +#define CANCEL_BTN_SEC_HOLD 2 // The cancel button must be held for at least CANCEL_BTN_SEC_HOLD seconds to cancel an ongoing operation + +typedef struct { + u8 signature[0x100]; + u32 magic; + u32 secureAreaStartAddr; + u32 backupAreaStartAddr; + u8 titleKeyIndex; + u8 size; + u8 headerVersion; + u8 flags; + u64 packageId; + u64 validDataEndAddr; + u8 iv[0x10]; + u64 rootHfs0HeaderOffset; + u64 rootHfs0HeaderSize; + u8 rootHfs0HeaderHash[SHA256_HASH_SIZE]; + u8 initialDataHash[SHA256_HASH_SIZE]; + u32 securityMode; + u32 t1KeyIndex; + u32 keyIndex; + u32 normalAreaEndAddr; + u8 encryptedInfoBlock[0x70]; +} PACKED gamecard_header_t; + +typedef struct { + u64 offset; + u64 size; + u8 *header; + u64 header_size; + u32 file_cnt; + u32 str_table_size; +} PACKED hfs0_partition_info; + +typedef enum { + ISTORAGE_PARTITION_NONE = 0, + ISTORAGE_PARTITION_NORMAL, + ISTORAGE_PARTITION_SECURE, + ISTORAGE_PARTITION_INVALID +} openIStoragePartition; + +typedef struct { + FsDeviceOperator fsOperatorInstance; + FsEventNotifier fsGameCardEventNotifier; + Event fsGameCardKernelEvent; + FsGameCardHandle fsGameCardHandle; + FsStorage fsGameCardStorage; + openIStoragePartition curIStorageIndex; + volatile bool isInserted; + gamecard_header_t header; + u8 *rootHfs0Header; + u32 hfs0PartitionCnt; + hfs0_partition_info *hfs0Partitions; + u64 size; + char sizeStr[32]; + u64 trimmedSize; + char trimmedSizeStr[32]; + u64 IStoragePartitionSizes[ISTORAGE_PARTITION_CNT]; + u64 updateTitleId; + u32 updateVersion; + char updateVersionStr[64]; +} PACKED gamecard_ctx_t; + +typedef struct { + u64 titleId; + u32 version; + u32 ncmIndex; + NcmStorageId storageId; + char name[NACP_APPNAME_LEN]; + char fixedName[NACP_APPNAME_LEN]; + char author[NACP_AUTHOR_LEN]; + char versionStr[VERSION_STR_LEN]; + u8 *icon; + u64 contentSize; + char contentSizeStr[32]; +} PACKED base_app_ctx_t; + +typedef struct { + u64 titleId; + u32 version; + u32 ncmIndex; + NcmStorageId storageId; + char versionStr[VERSION_STR_LEN]; + u64 contentSize; + char contentSizeStr[32]; +} PACKED patch_addon_ctx_t; + +typedef struct { + u32 index; + u8 type; // 1 = Patch, 2 = AddOn + char name[NACP_APPNAME_LEN]; + char fixedName[NACP_APPNAME_LEN]; + char orphanListStr[NACP_APPNAME_LEN * 2]; +} PACKED orphan_patch_addon_entry; + +typedef struct { + u32 magic; + u32 file_cnt; + u32 str_table_size; + u32 reserved; +} PACKED hfs0_header; + typedef struct { u64 file_offset; u64 file_size; @@ -108,7 +207,7 @@ typedef struct { u32 hashed_region_size; u64 reserved; u8 hashed_region_sha256[0x20]; -} PACKED hfs0_entry_table; +} PACKED hfs0_file_entry; typedef struct { int line_offset; @@ -142,27 +241,24 @@ typedef enum { TICKET_TYPE_ADDON } selectedTicketType; -typedef struct { - u32 index; - u8 type; // 1 = Patch, 2 = AddOn - char name[NACP_APPNAME_LEN + 1]; - char fixedName[NACP_APPNAME_LEN + 1]; -} PACKED orphan_patch_addon_entry; - typedef struct { bool isFat32; bool setXciArchiveBit; bool keepCert; bool trimDump; bool calcCrc; + bool useNoIntroLookup; + bool useBrackets; } PACKED xciOptions; typedef struct { bool isFat32; - bool calcCrc; + bool useNoIntroLookup; bool removeConsoleData; bool tiklessDump; bool npdmAcidRsaPatch; + bool dumpDeltaFragments; + bool useBrackets; } PACKED nspOptions; typedef enum { @@ -180,8 +276,11 @@ typedef struct { bool removeConsoleData; bool tiklessDump; bool npdmAcidRsaPatch; + bool dumpDeltaFragments; bool skipDumpedTitles; bool rememberDumpedTitles; + bool haltOnErrors; + bool useBrackets; batchModeSourceStorage batchModeSrc; } PACKED batchOptions; @@ -189,68 +288,68 @@ typedef struct { bool removeConsoleData; } PACKED ticketOptions; +typedef struct { + bool isFat32; + bool useLayeredFSDir; +} PACKED ncaFsOptions; + typedef struct { xciOptions xciDumpCfg; nspOptions nspDumpCfg; batchOptions batchDumpCfg; ticketOptions tikDumpCfg; + ncaFsOptions exeFsDumpCfg; + ncaFsOptions romFsDumpCfg; } PACKED dumpOptions; void loadConfig(); - void saveConfig(); -bool isGameCardInserted(); - -void fsGameCardDetectionThreadFunc(void *arg); - -bool mountSysEmmcPartition(); - -void unmountSysEmmcPartition(); - -bool isServiceRunning(const char *serviceName); - -bool checkSxOsServices(); +void closeGameCardStoragePartition(); +Result openGameCardStoragePartition(openIStoragePartition partitionIndex); +Result readGameCardStoragePartition(u64 off, void *buf, size_t len); void delay(u8 seconds); -void formatETAString(u64 curTime, char *output, u32 outSize); +void convertSize(u64 size, char *out, size_t outSize); +void updateFreeSpace(); void initExeFsContext(); - void freeExeFsContext(); void initRomFsContext(); - void freeRomFsContext(); void initBktrContext(); - void freeBktrContext(); -void freeGlobalData(); +void freeRomFsBrowserEntries(); +void freeHfs0ExeFsEntriesSizes(); -bool loadTitlesFromSdCardAndEmmc(u8 titleType); +void consoleErrorScreen(const char *fmt, ...); +bool initApplicationResources(int argc, char **argv); +void deinitApplicationResources(); -void freeTitlesFromSdCardAndEmmc(u8 titleType); +void formatETAString(u64 curTime, char *out, size_t outSize); -void convertTitleVersionToDecimal(u32 version, char *versionBuf, size_t versionBufSize); +void addStringToFilenameBuffer(const char *string); + +void generateSdCardEmmcTitleList(); + +bool loadTitlesFromSdCardAndEmmc(NcmContentMetaType metaType); +void freeTitlesFromSdCardAndEmmc(NcmContentMetaType metaType); void removeIllegalCharacters(char *name); -void createOutputDirectories(); - void strtrim(char *str); void loadTitleInfo(); -bool getHfs0EntryDetails(u8 *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size); - -bool getPartitionHfs0Header(u32 partition); +void truncateBrowserEntryName(char *str); bool getHfs0FileList(u32 partition); -bool getFileFromHfs0PartitionByName(FsStorage *gameCardStorage, const char *filename, u8 *outBuf, u64 outBufSize); +bool readFileFromSecureHfs0PartitionByName(const char *filename, u64 offset, void *outBuf, size_t bufSize); bool calculateExeFsExtractedDataSize(u64 *out); @@ -258,7 +357,7 @@ bool calculateRomFsFullExtractedSize(bool usePatch, u64 *out); bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out); -bool retrieveNcaContentRecords(FsStorageId curStorageId, u8 filter, u32 titleCount, u32 titleIndex, NcmContentRecord **outContentRecords, u32 *outContentRecordsCnt); +bool retrieveContentInfosFromTitle(NcmStorageId storageId, NcmContentMetaType metaType, u32 titleCount, u32 titleIndex, NcmContentInfo **outContentInfos, u32 *outContentInfoCnt); void removeConsoleDataFromTicket(title_rights_ctx *rights_info); @@ -270,21 +369,13 @@ bool getExeFsFileList(); bool getRomFsFileList(u32 dir_offset, bool usePatch); -int getSdCardFreeSpace(u64 *out); +char *generateGameCardDumpName(bool useBrackets); -void convertSize(u64 size, char *out, int bufsize); +char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex, bool useBrackets); -void addStringToFilenameBuffer(const char *string, char **nextFilename); +void retrieveDescriptionForPatchOrAddOn(u32 titleIndex, bool addOn, bool addAppName, const char *prefix, char *outBuf, size_t outBufSize); -char *generateFullDumpName(); - -char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex); - -void retrieveDescriptionForPatchOrAddOn(u64 titleID, u32 version, bool addOn, bool addAppName, const char *prefix, char *outBuf, size_t outBufSize); - -bool checkOrphanPatchOrAddOn(bool addOn); - -void freeOrphanPatchOrAddOnList(); +u32 calculateOrphanPatchOrAddOnCount(bool addOn); void generateOrphanPatchOrAddOnList(); @@ -322,6 +413,8 @@ void removeDirectoryWithVerbose(const char *path, const char *msg); void gameCardDumpNSWDBCheck(u32 crc); +void noIntroDumpCheck(bool isDigital, u32 crc); + void updateNSWDBXml(); bool updateApplication();