mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
4108 lines
201 KiB
C
4108 lines
201 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <dirent.h>
|
|
#include <memory.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
|
|
#include "crc32_fast.h"
|
|
#include "dumper.h"
|
|
#include "fs_ext.h"
|
|
#include "ui.h"
|
|
#include "nca.h"
|
|
#include "keys.h"
|
|
|
|
/* Extern variables */
|
|
|
|
extern FsDeviceOperator fsOperatorInstance;
|
|
|
|
extern nca_keyset_t nca_keyset;
|
|
|
|
extern u64 freeSpace;
|
|
|
|
extern int breaks;
|
|
extern int font_height;
|
|
|
|
extern u64 trimmedCardSize;
|
|
extern char trimmedCardSizeStr[32];
|
|
|
|
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 FsStorageId *titleAppStorageId;
|
|
|
|
extern u32 titlePatchCount;
|
|
extern FsStorageId *titlePatchStorageId;
|
|
|
|
extern u32 titleAddOnCount;
|
|
extern FsStorageId *titleAddOnStorageId;
|
|
|
|
extern u32 sdCardTitleAppCount;
|
|
extern u32 sdCardTitlePatchCount;
|
|
extern u32 sdCardTitleAddOnCount;
|
|
|
|
extern u32 nandUserTitleAppCount;
|
|
extern u32 nandUserTitlePatchCount;
|
|
extern u32 nandUserTitleAddOnCount;
|
|
|
|
extern AppletType programAppletType;
|
|
|
|
extern exefs_ctx_t exeFsContext;
|
|
extern romfs_ctx_t romFsContext;
|
|
|
|
extern char curRomFsPath[NAME_BUF_LEN];
|
|
|
|
extern char strbuf[NAME_BUF_LEN * 4];
|
|
|
|
void workaroundPartitionZeroAccess()
|
|
{
|
|
FsGameCardHandle handle;
|
|
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle))) return;
|
|
|
|
FsStorage gameCardStorage;
|
|
if (R_FAILED(fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) return;
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
}
|
|
|
|
bool dumpCartridgeImage(bool isFat32, bool setXciArchiveBit, bool dumpCert, bool trimDump, bool calcCrc)
|
|
{
|
|
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'};
|
|
u32 partition;
|
|
Result result;
|
|
FsGameCardHandle handle;
|
|
FsStorage gameCardStorage;
|
|
bool proceed = true, success = false, fat32_error = false;
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex = 0;
|
|
u32 crc1 = 0, crc2 = 0;
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
char tmp_idx[5];
|
|
|
|
size_t write_res;
|
|
|
|
char *dumpName = generateDumpFullName();
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++)
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Getting partition #%u size...", partition);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (R_SUCCEEDED(result = fsStorageGetSize(&gameCardStorage, &(partitionSizes[partition]))))
|
|
{
|
|
xciDataSize += partitionSizes[partition];
|
|
convertSize(partitionSizes[partition], partitionSizesStr[partition], sizeof(partitionSizesStr[partition]) / sizeof(partitionSizesStr[partition][0]));
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u size: %s (%lu bytes).", partition, partitionSizesStr[partition], partitionSizes[partition]);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;*/
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageGetSize failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
}
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
}
|
|
|
|
uiRefreshDisplay();
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
convertSize(xciDataSize, xciDataSizeStr, sizeof(xciDataSizeStr) / sizeof(xciDataSizeStr[0]));
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI data size: %s (%lu bytes).", xciDataSizeStr, xciDataSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;*/
|
|
|
|
if (trimDump)
|
|
{
|
|
progressCtx.totalSize = trimmedCardSize;
|
|
snprintf(progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]), "%s", trimmedCardSizeStr);
|
|
|
|
// Change dump size for the last IStorage partition
|
|
u64 partitionSizesSum = 0;
|
|
for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i];
|
|
|
|
partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (trimmedCardSize - partitionSizesSum);
|
|
} else {
|
|
progressCtx.totalSize = xciDataSize;
|
|
snprintf(progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]), "%s", xciDataSizeStr);
|
|
}
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize <= freeSpace)
|
|
{
|
|
breaks++;
|
|
|
|
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, sizeof(filename) / sizeof(filename[0]), "%s%s.xci", XCI_DUMP_PATH, dumpName);
|
|
} else {
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex);
|
|
}
|
|
} else {
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xci", XCI_DUMP_PATH, dumpName);
|
|
}
|
|
|
|
// Check if the dump already exists
|
|
if (checkIfFileExists(filename))
|
|
{
|
|
// 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)
|
|
{
|
|
uiDrawString("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && setXciArchiveBit)
|
|
{
|
|
// 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);
|
|
removeDirectory(filename);
|
|
|
|
mkdir(filename, 0744);
|
|
|
|
sprintf(tmp_idx, "/%02u", splitIndex);
|
|
strcat(filename, tmp_idx);
|
|
}
|
|
|
|
outFile = fopen(filename, "wb");
|
|
if (outFile)
|
|
{
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (buf)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start));
|
|
|
|
for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++)
|
|
{
|
|
n = DUMP_BUFFER_SIZE;
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition)))
|
|
{
|
|
for(partitionOffset = 0; partitionOffset < partitionSizes[partition]; partitionOffset += n, progressCtx.curOffset += n)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(filename, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping IStorage partition #%u...", partition);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (DUMP_BUFFER_SIZE > (partitionSizes[partition] - partitionOffset)) n = (partitionSizes[partition] - partitionOffset);
|
|
|
|
if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset, buf, n)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX for partition #%u", result, partitionOffset, partition);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Remove gamecard certificate
|
|
if (progressCtx.curOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE);
|
|
|
|
if (calcCrc)
|
|
{
|
|
if (!trimDump)
|
|
{
|
|
if (dumpCert)
|
|
{
|
|
if (progressCtx.curOffset == 0)
|
|
{
|
|
// Update CRC32 (with gamecard certificate)
|
|
crc32(buf, n, &crc1);
|
|
|
|
// Backup gamecard certificate to an array
|
|
char tmpCert[CERT_SIZE] = {'\0'};
|
|
memcpy(tmpCert, buf + CERT_OFFSET, CERT_SIZE);
|
|
|
|
// Remove gamecard certificate from buffer
|
|
memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE);
|
|
|
|
// Update CRC32 (without gamecard certificate)
|
|
crc32(buf, n, &crc2);
|
|
|
|
// Restore gamecard certificate to buffer
|
|
memcpy(buf + CERT_OFFSET, tmpCert, CERT_SIZE);
|
|
} else {
|
|
// Update CRC32 (with gamecard certificate)
|
|
crc32(buf, n, &crc1);
|
|
|
|
// Update CRC32 (without gamecard certificate)
|
|
crc32(buf, n, &crc2);
|
|
}
|
|
} else {
|
|
// Update CRC32
|
|
crc32(buf, n, &crc2);
|
|
}
|
|
} else {
|
|
// Update CRC32
|
|
crc32(buf, n, &crc1);
|
|
}
|
|
}
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_XCI_PART_SIZE))
|
|
{
|
|
u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_XCI_PART_SIZE));
|
|
u64 old_file_chunk_size = (n - new_file_chunk_size);
|
|
|
|
if (old_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
splitIndex++;
|
|
|
|
if (setXciArchiveBit)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xci/%02u", XCI_DUMP_PATH, dumpName, splitIndex);
|
|
} else {
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, splitIndex);
|
|
}
|
|
|
|
outFile = fopen(filename, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (progressCtx.curOffset >= progressCtx.totalSize) success = true;
|
|
|
|
// Support empty files
|
|
if (!partitionSizes[partition])
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(filename, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping IStorage partition #%u...", partition);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
printProgressBar(&progressCtx, false, 0);
|
|
}
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed for partition #%u! (0x%08X)", partition, result);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed for partition #%u! (0x%08X)", partition, result);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
}
|
|
|
|
if (!proceed)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
if (fat32_error) breaks += 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
} else {
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (success)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
|
|
if (calcCrc)
|
|
{
|
|
breaks++;
|
|
|
|
if (!trimDump)
|
|
{
|
|
if (dumpCert)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (with certificate): %08X", crc1);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
breaks++;
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (without certificate): %08X", crc2);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc2);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
}
|
|
|
|
breaks += 2;
|
|
uiDrawString("Starting verification process using XML database from NSWDB.COM...", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;
|
|
|
|
uiRefreshDisplay();
|
|
|
|
gameCardDumpNSWDBCheck(crc2);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
breaks++;
|
|
|
|
uiDrawString("Dump verification disabled (not compatible with trimmed dumps).", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
}
|
|
}
|
|
|
|
// Set archive bit (only for FAT32 and if the required option is enabled)
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && setXciArchiveBit)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xci", XCI_DUMP_PATH, dumpName);
|
|
if (R_FAILED(result = fsdevSetArchiveBit(filename)))
|
|
{
|
|
breaks += 2;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Warning: failed to set archive bit on output directory! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
} else {
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
if (setXciArchiveBit)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xci", XCI_DUMP_PATH, dumpName);
|
|
removeDirectory(filename);
|
|
} else {
|
|
for(u8 i = 0; i <= splitIndex; i++)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s.xc%u", XCI_DUMP_PATH, dumpName, i);
|
|
unlink(filename);
|
|
}
|
|
}
|
|
} else {
|
|
unlink(filename);
|
|
}
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
} else {
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
|
|
breaks += 2;
|
|
|
|
free(dumpName);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpNintendoSubmissionPackage(nspDumpType selectedNspDumpType, u32 titleIndex, bool isFat32, bool calcCrc, bool removeConsoleData, bool tiklessDump)
|
|
{
|
|
Result result;
|
|
u32 i = 0, j = 0;
|
|
u32 written = 0;
|
|
u32 total = 0;
|
|
u32 titleCount = 0;
|
|
u32 ncmTitleIndex = 0;
|
|
u32 titleNcaCount = 0;
|
|
u32 partition = 0;
|
|
|
|
FsStorageId curStorageId;
|
|
|
|
FsGameCardHandle handle;
|
|
|
|
FsStorage gameCardStorage;
|
|
memset(&gameCardStorage, 0, sizeof(FsStorage));
|
|
|
|
NcmContentMetaDatabase ncmDb;
|
|
memset(&ncmDb, 0, sizeof(NcmContentMetaDatabase));
|
|
|
|
NcmContentMetaRecordsHeader contentRecordsHeader;
|
|
memset(&contentRecordsHeader, 0, sizeof(NcmContentMetaRecordsHeader));
|
|
|
|
u64 contentRecordsHeaderReadSize = 0;
|
|
|
|
NcmContentStorage ncmStorage;
|
|
memset(&ncmStorage, 0, sizeof(NcmContentStorage));
|
|
|
|
NcmApplicationContentMetaKey *titleList = NULL;
|
|
NcmContentRecord *titleContentRecords = NULL;
|
|
size_t titleListSize = sizeof(NcmApplicationContentMetaKey);
|
|
|
|
cnmt_xml_program_info xml_program_info;
|
|
cnmt_xml_content_info *xml_content_info = NULL;
|
|
|
|
NcmNcaId ncaId;
|
|
u8 ncaHeader[NCA_FULL_HEADER_LENGTH] = {0};
|
|
nca_header_t dec_nca_header;
|
|
|
|
nca_program_mod_data ncaProgramMod;
|
|
memset(&ncaProgramMod, 0, sizeof(nca_program_mod_data));
|
|
|
|
ncaProgramMod.hash_table = NULL;
|
|
ncaProgramMod.block_data[0] = NULL;
|
|
ncaProgramMod.block_data[1] = NULL;
|
|
|
|
nca_cnmt_mod_data ncaCnmtMod;
|
|
memset(&ncaCnmtMod, 0, sizeof(nca_cnmt_mod_data));
|
|
|
|
title_rights_ctx rights_info;
|
|
memset(&rights_info, 0, sizeof(title_rights_ctx));
|
|
|
|
u32 cnmtNcaIndex = 0;
|
|
u8 *cnmtNcaBuf = NULL;
|
|
bool cnmtFound = false;
|
|
char *cnmtXml = NULL;
|
|
|
|
u32 nacpNcaIndex = 0;
|
|
char *nacpXml = NULL;
|
|
|
|
u32 nspFileCount = 0;
|
|
pfs0_header nspPfs0Header;
|
|
pfs0_entry_table *nspPfs0EntryTable = NULL;
|
|
char *nspPfs0StrTable = NULL;
|
|
u64 nspPfs0StrTableSize = 0;
|
|
u64 full_nsp_header_size = 0;
|
|
|
|
u64 hash_table_dump_buffer_start = 0;
|
|
u64 hash_table_dump_buffer_end = 0;
|
|
u64 block0_dump_buffer_start = 0;
|
|
u64 block0_dump_buffer_end = 0;
|
|
u64 block1_dump_buffer_start = 0;
|
|
u64 block1_dump_buffer_end = 0;
|
|
|
|
Sha256Context nca_hash_ctx;
|
|
|
|
char dumpPath[NAME_BUF_LEN * 2] = {'\0'};
|
|
|
|
u64 n, nca_offset;
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex = 0;
|
|
u32 crc = 0;
|
|
bool proceed = true, success = false, dumping = false, fat32_error = false, removeFile = true;
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
char tmp_idx[5];
|
|
|
|
size_t write_res;
|
|
|
|
int initial_breaks = breaks;
|
|
|
|
if ((selectedNspDumpType == DUMP_APP_NSP && !titleAppStorageId) || (selectedNspDumpType == DUMP_PATCH_NSP && !titlePatchStorageId) || (selectedNspDumpType == DUMP_ADDON_NSP && !titleAddOnStorageId))
|
|
{
|
|
uiDrawString("Error: title storage ID unavailable!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
curStorageId = (selectedNspDumpType == DUMP_APP_NSP ? titleAppStorageId[titleIndex] : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchStorageId[titleIndex] : titleAddOnStorageId[titleIndex]));
|
|
|
|
if (curStorageId != FsStorageId_GameCard && curStorageId != FsStorageId_SdCard && curStorageId != FsStorageId_NandUser)
|
|
{
|
|
uiDrawString("Error: invalid title storage ID!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
switch(curStorageId)
|
|
{
|
|
case FsStorageId_GameCard:
|
|
titleCount = (selectedNspDumpType == DUMP_APP_NSP ? titleAppCount : (selectedNspDumpType == DUMP_PATCH_NSP ? titlePatchCount : titleAddOnCount));
|
|
ncmTitleIndex = titleIndex;
|
|
break;
|
|
case FsStorageId_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)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!titleCount)
|
|
{
|
|
uiDrawString("Error: invalid title type count!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
if (ncmTitleIndex > (titleCount - 1))
|
|
{
|
|
uiDrawString("Error: invalid title index!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
titleListSize *= titleCount;
|
|
|
|
char *dumpName = generateNSPDumpName(selectedNspDumpType, titleIndex);
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// If we're dealing with a gamecard, call workaroundPartitionZeroAccess() and read the secure partition header. Otherwise, ncmContentStorageReadContentIdFile() will fail with error 0x00171002
|
|
// Also open an IStorage instance for the HFS0 Secure partition, since we may also need it if we're dealing with a Patch with titlekey crypto, in order to retrieve the tik file
|
|
if (curStorageId == FsStorageId_GameCard)
|
|
{
|
|
partition = (hfs0_partition_cnt - 1); // Select the secure partition
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (!getPartitionHfs0Header(partition)) goto out;
|
|
|
|
if (!partitionHfs0FileCount)
|
|
{
|
|
uiDrawString("The Secure HFS0 partition is empty!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
uiDrawString("Retrieving information from encrypted NCA content files...", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks++;
|
|
|
|
titleList = calloc(1, titleListSize);
|
|
if (!titleList)
|
|
{
|
|
uiDrawString("Error: unable to allocate memory for the ApplicationContentMetaKey struct!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = ncmOpenContentMetaDatabase(curStorageId, &ncmDb)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmOpenContentMetaDatabase failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
u8 filter = ((u8)selectedNspDumpType + META_DB_REGULAR_APPLICATION);
|
|
|
|
if (R_FAILED(result = ncmContentMetaDatabaseListApplication(&ncmDb, filter, titleList, titleListSize, &written, &total)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (!written || !total)
|
|
{
|
|
uiDrawString("Error: ncmContentMetaDatabaseListApplication wrote no entries to output buffer!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (written != total)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: title count mismatch in ncmContentMetaDatabaseListApplication (%u != %u)", written, total);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = ncmContentMetaDatabaseGet(&ncmDb, &(titleList[ncmTitleIndex].metaRecord), sizeof(NcmContentMetaRecordsHeader), &contentRecordsHeader, &contentRecordsHeaderReadSize)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentMetaDatabaseGet failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
titleNcaCount = (u32)(contentRecordsHeader.numContentRecords);
|
|
|
|
titleContentRecords = calloc(titleNcaCount, sizeof(NcmContentRecord));
|
|
if (!titleContentRecords)
|
|
{
|
|
uiDrawString("Error: unable to allocate memory for the ContentRecord struct!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = ncmContentMetaDatabaseListContentInfo(&ncmDb, &(titleList[ncmTitleIndex].metaRecord), 0, titleContentRecords, titleNcaCount * sizeof(NcmContentRecord), &written)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentMetaDatabaseListContentInfo failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Fill information for our CNMT XML
|
|
memset(&xml_program_info, 0, sizeof(cnmt_xml_program_info));
|
|
xml_program_info.type = titleList[ncmTitleIndex].metaRecord.type;
|
|
xml_program_info.title_id = titleList[ncmTitleIndex].metaRecord.titleId;
|
|
xml_program_info.version = titleList[ncmTitleIndex].metaRecord.version;
|
|
xml_program_info.nca_cnt = titleNcaCount;
|
|
|
|
xml_content_info = calloc(titleNcaCount, sizeof(cnmt_xml_content_info));
|
|
if (!xml_content_info)
|
|
{
|
|
uiDrawString("Error: unable to allocate memory for the CNMT XML content info struct!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = ncmOpenContentStorage(curStorageId, &ncmStorage)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmOpenContentStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Fill our CNMT XML content records, leaving the CNMT NCA at the end
|
|
u32 titleRecordIndex;
|
|
for(i = 0, titleRecordIndex = 0; titleRecordIndex < titleNcaCount; i++, titleRecordIndex++)
|
|
{
|
|
if (!cnmtFound && titleContentRecords[titleRecordIndex].type == NcmContentType_CNMT)
|
|
{
|
|
cnmtFound = true;
|
|
cnmtNcaIndex = titleRecordIndex;
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
// Skip Delta Fragments or any other unknown content types if we're not dealing with Patch titles dumped from installed SD/eMMC (with tiklessDump disabled)
|
|
if (titleContentRecords[titleRecordIndex].type >= NCA_CONTENT_TYPE_DELTA && (curStorageId == FsStorageId_GameCard || selectedNspDumpType != DUMP_PATCH_NSP || tiklessDump))
|
|
{
|
|
xml_program_info.nca_cnt--;
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
// 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, 16); // Temporary
|
|
convertDataToHexString(titleContentRecords[titleRecordIndex].ncaId.c, 16, xml_content_info[i].nca_id_str, 33); // Temporary
|
|
convertNcaSizeToU64(titleContentRecords[titleRecordIndex].size, &(xml_content_info[i].size));
|
|
convertDataToHexString(xml_content_info[i].hash, 32, xml_content_info[i].hash_str, 65); // Temporary
|
|
|
|
memcpy(&ncaId, &(titleContentRecords[titleRecordIndex].ncaId), sizeof(NcmNcaId));
|
|
|
|
if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read header from NCA \"%s\"! (0x%08X)", xml_content_info[i].nca_id_str, result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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)))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
bool has_rights_id = false;
|
|
|
|
for(j = 0; j < 0x10; j++)
|
|
{
|
|
if (dec_nca_header.rights_id[j] != 0)
|
|
{
|
|
has_rights_id = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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 (selectedNspDumpType == DUMP_APP_NSP || selectedNspDumpType == DUMP_ADDON_NSP)
|
|
{
|
|
if (has_rights_id)
|
|
{
|
|
uiDrawString("Error: Rights ID field in NCA header not empty!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Modify distribution type
|
|
dec_nca_header.distribution = 0;
|
|
|
|
// Patch ACID pubkey and recreate NCA NPDM signature if we're dealing with the Program NCA
|
|
if (xml_content_info[i].type == NcmContentType_Program)
|
|
{
|
|
if (!processProgramNca(&ncmStorage, &ncaId, &dec_nca_header, &(xml_content_info[i]), &ncaProgramMod))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
if (selectedNspDumpType == DUMP_PATCH_NSP)
|
|
{
|
|
if (!has_rights_id)
|
|
{
|
|
// We're could be dealing with a custom XCI mounted through SX OS, so let's change back the content distribution method
|
|
dec_nca_header.distribution = 0;
|
|
} else {
|
|
if (!rights_info.retrieved_tik)
|
|
{
|
|
// Retrieve tik file
|
|
if (!getPartitionHfs0FileByName(&gameCardStorage, rights_info.tik_filename, (u8*)(&(rights_info.tik_data)), ETICKET_TIK_FILE_SIZE))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
memcpy(rights_info.enc_titlekey, rights_info.tik_data.titlekey_block, 0x10);
|
|
|
|
rights_info.retrieved_tik = true;
|
|
}
|
|
|
|
// Mess with the NCA header if we're dealing with a content with a populated rights ID field and if tiklessDump is true (removeConsoleData is ignored)
|
|
if (tiklessDump)
|
|
{
|
|
// Load external keys
|
|
if (!loadExternalKeys())
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Decrypt titlekey
|
|
u8 crypto_type = xml_content_info[i].keyblob;
|
|
if (crypto_type) crypto_type--;
|
|
|
|
Aes128Context titlekey_aes_ctx;
|
|
aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false);
|
|
aes128DecryptBlock(&titlekey_aes_ctx, rights_info.dec_titlekey, rights_info.enc_titlekey);
|
|
|
|
memset(xml_content_info[i].decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
|
|
memcpy(xml_content_info[i].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info.dec_titlekey, 0x10);
|
|
|
|
// Generate new encrypted NCA key area using titlekey
|
|
if (!generateEncryptedNcaKeyAreaWithTitlekey(&dec_nca_header, xml_content_info[i].decrypted_nca_keys))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Remove rights ID from NCA
|
|
memset(dec_nca_header.rights_id, 0, 0x10);
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
if (curStorageId == FsStorageId_SdCard || curStorageId == FsStorageId_NandUser)
|
|
{
|
|
// 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
|
|
if (has_rights_id && removeConsoleData && tiklessDump)
|
|
{
|
|
// Generate new encrypted NCA key area using titlekey
|
|
if (!generateEncryptedNcaKeyAreaWithTitlekey(&dec_nca_header, xml_content_info[i].decrypted_nca_keys))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Remove rights ID from NCA
|
|
memset(dec_nca_header.rights_id, 0, 0x10);
|
|
}
|
|
}
|
|
|
|
// Generate NACP XML
|
|
if (!nacpXml && xml_content_info[i].type == NcmContentType_Icon)
|
|
{
|
|
nacpNcaIndex = i;
|
|
|
|
if (!generateNacpXmlFromNca(&ncmStorage, &ncaId, &dec_nca_header, xml_content_info[i].decrypted_nca_keys, &nacpXml))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reencrypt header
|
|
if (!encryptNcaHeader(&dec_nca_header, xml_content_info[i].encrypted_header_mod, NCA_FULL_HEADER_LENGTH))
|
|
{
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!proceed) goto out;
|
|
|
|
if (proceed && !cnmtFound)
|
|
{
|
|
uiDrawString("Error: unable to find CNMT NCA!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Update NCA counter just in case we found any delta fragments
|
|
titleNcaCount = xml_program_info.nca_cnt;
|
|
|
|
// Fill information for our CNMT XML
|
|
xml_content_info[titleNcaCount - 1].type = titleContentRecords[cnmtNcaIndex].type;
|
|
memcpy(xml_content_info[titleNcaCount - 1].nca_id, titleContentRecords[cnmtNcaIndex].ncaId.c, 16); // Temporary
|
|
convertDataToHexString(titleContentRecords[cnmtNcaIndex].ncaId.c, 16, xml_content_info[titleNcaCount - 1].nca_id_str, 33); // Temporary
|
|
convertNcaSizeToU64(titleContentRecords[cnmtNcaIndex].size, &(xml_content_info[titleNcaCount - 1].size));
|
|
convertDataToHexString(xml_content_info[titleNcaCount - 1].hash, 32, xml_content_info[titleNcaCount - 1].hash_str, 65); // Temporary
|
|
|
|
memcpy(&ncaId, &(titleContentRecords[cnmtNcaIndex].ncaId), sizeof(NcmNcaId));
|
|
|
|
// Update CNMT index
|
|
cnmtNcaIndex = (titleNcaCount - 1);
|
|
|
|
cnmtNcaBuf = malloc(xml_content_info[cnmtNcaIndex].size);
|
|
if (!cnmtNcaBuf)
|
|
{
|
|
uiDrawString("Error: unable to allocate memory for CNMT NCA data!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read CNMT NCA \"%s\"! (0x%08X)", xml_content_info[cnmtNcaIndex].nca_id_str, result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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;
|
|
|
|
// Generate a placeholder CNMT XML. It's length will be used to calculate the final output dump size
|
|
/*breaks++;
|
|
uiDrawString("Generating placeholder CNMT XML...", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks++;*/
|
|
|
|
// Make sure that the output buffer for our CNMT XML is big enough
|
|
cnmtXml = calloc(NAME_BUF_LEN * 4, sizeof(char));
|
|
if (!cnmtXml)
|
|
{
|
|
uiDrawString("Error: unable to allocate memory for the CNMT XML!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
generateCnmtXml(&xml_program_info, xml_content_info, cnmtXml);
|
|
|
|
bool includeCertAndTik = (rights_info.has_rights_id && !tiklessDump);
|
|
|
|
if (includeCertAndTik)
|
|
{
|
|
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
|
|
if (removeConsoleData && rights_info.tik_data.titlekey_type == ETICKET_TITLEKEY_PERSONALIZED)
|
|
{
|
|
memset(rights_info.tik_data.signature, 0xFF, 0x100);
|
|
|
|
memset(rights_info.tik_data.sig_issuer, 0, 0x40);
|
|
sprintf(rights_info.tik_data.sig_issuer, "Root-CA00000003-XS00000020");
|
|
|
|
memset(rights_info.tik_data.titlekey_block, 0, 0x100);
|
|
memcpy(rights_info.tik_data.titlekey_block, rights_info.enc_titlekey, 0x10);
|
|
|
|
rights_info.tik_data.titlekey_type = ETICKET_TITLEKEY_COMMON;
|
|
rights_info.tik_data.ticket_id = 0;
|
|
rights_info.tik_data.device_id = 0;
|
|
rights_info.tik_data.account_id = 0;
|
|
}
|
|
}
|
|
|
|
// Retrieve cert file
|
|
if (!retrieveCertData(rights_info.cert_data, (rights_info.tik_data.titlekey_type == ETICKET_TITLEKEY_PERSONALIZED))) goto out;
|
|
|
|
// File count = NCA count + CNMT XML + cert + tik
|
|
nspFileCount = (titleNcaCount + 3);
|
|
|
|
// Calculate PFS0 String Table size
|
|
nspPfs0StrTableSize = (((nspFileCount - 4) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2) + NSP_CERT_FILENAME_LENGTH + NSP_TIK_FILENAME_LENGTH);
|
|
} else {
|
|
// File count = NCA count + CNMT XML
|
|
nspFileCount = (titleNcaCount + 1);
|
|
|
|
// Calculate PFS0 String Table size
|
|
nspPfs0StrTableSize = (((nspFileCount - 2) * NSP_NCA_FILENAME_LENGTH) + (NSP_CNMT_FILENAME_LENGTH * 2));
|
|
}
|
|
|
|
// Add our NACP XML if we created it
|
|
if (nacpXml)
|
|
{
|
|
nspFileCount++;
|
|
nspPfs0StrTableSize += NSP_NACP_FILENAME_LENGTH;
|
|
}
|
|
|
|
// Start NSP creation
|
|
/*breaks++;
|
|
uiDrawString("Generating placeholder PFS0 header...", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
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));
|
|
if (!nspPfs0EntryTable)
|
|
{
|
|
uiDrawString("Unable to allocate memory for the PFS0 file entries!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Make sure we have enough space
|
|
nspPfs0StrTable = calloc(nspPfs0StrTableSize * 2, sizeof(char));
|
|
if (!nspPfs0StrTable)
|
|
{
|
|
uiDrawString("Unable to allocate memory for the PFS0 string table!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Determine our full NSP header size
|
|
full_nsp_header_size = (sizeof(pfs0_header) + (nspFileCount * sizeof(pfs0_entry_table)) + nspPfs0StrTableSize);
|
|
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) + (nspFileCount * sizeof(pfs0_entry_table))));
|
|
|
|
// Calculate total dump size
|
|
progressCtx.totalSize = full_nsp_header_size;
|
|
progressCtx.totalSize += strlen(cnmtXml);
|
|
if (nacpXml) progressCtx.totalSize += strlen(nacpXml);
|
|
if (includeCertAndTik) progressCtx.totalSize += (ETICKET_CERT_FILE_SIZE + ETICKET_TIK_FILE_SIZE);
|
|
for(i = 0; i < titleNcaCount; i++) progressCtx.totalSize += xml_content_info[i].size;
|
|
|
|
breaks++;
|
|
convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total NSP dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize > freeSpace)
|
|
{
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Temporary, we'll use this to check if the dump already exists (it should have the archive bit set if so)
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp", NSP_DUMP_PATH, dumpName);
|
|
|
|
// Check if the dump already exists
|
|
if (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)
|
|
{
|
|
uiDrawString("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
removeFile = false;
|
|
goto out;
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
// Since we may actually be dealing with an existing directory with the archive bit set or unset, let's try both
|
|
// Better safe than sorry
|
|
unlink(dumpPath);
|
|
removeDirectory(dumpPath);
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
mkdir(dumpPath, 0744);
|
|
|
|
sprintf(tmp_idx, "/%02u", splitIndex);
|
|
strcat(dumpPath, tmp_idx);
|
|
}
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", dumpPath);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
buf = calloc(DUMP_BUFFER_SIZE, sizeof(u8));
|
|
if (!buf)
|
|
{
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
breaks++;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
// Write placeholder zeroes
|
|
write_res = fwrite(buf, 1, full_nsp_header_size + strlen(cnmtXml), outFile);
|
|
if (write_res != (full_nsp_header_size + strlen(cnmtXml)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes placeholder data to file offset 0x%016lX! (wrote %lu bytes)", full_nsp_header_size + strlen(cnmtXml), (u64)0, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
progressCtx.curOffset = (full_nsp_header_size + strlen(cnmtXml));
|
|
|
|
// Write our NACP XML
|
|
if (nacpXml)
|
|
{
|
|
write_res = fwrite(nacpXml, 1, strlen(nacpXml), outFile);
|
|
if (write_res != strlen(nacpXml))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes NACP XML to file offset 0x%016lX! (wrote %lu bytes)", strlen(nacpXml), progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
progressCtx.curOffset += strlen(nacpXml);
|
|
}
|
|
|
|
if (includeCertAndTik)
|
|
{
|
|
memcpy(buf, rights_info.cert_data, ETICKET_CERT_FILE_SIZE);
|
|
memcpy(buf + ETICKET_CERT_FILE_SIZE, &(rights_info.tik_data), ETICKET_TIK_FILE_SIZE);
|
|
|
|
// Write cert / tik
|
|
write_res = fwrite(buf, 1, ETICKET_CERT_FILE_SIZE + ETICKET_TIK_FILE_SIZE, outFile);
|
|
if (write_res != (ETICKET_CERT_FILE_SIZE + ETICKET_TIK_FILE_SIZE))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %u bytes cert + tik to file offset 0x%016lX! (wrote %lu bytes)", ETICKET_CERT_FILE_SIZE + ETICKET_TIK_FILE_SIZE, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
progressCtx.curOffset += (ETICKET_CERT_FILE_SIZE + ETICKET_TIK_FILE_SIZE);
|
|
}
|
|
|
|
// Calculate DUMP_BUFFER_SIZE block numbers for the modified Program NCA data blocks
|
|
if (selectedNspDumpType != DUMP_PATCH_NSP && ncaProgramMod.block_mod_cnt > 0)
|
|
{
|
|
hash_table_dump_buffer_start = ((ncaProgramMod.hash_table_offset / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
hash_table_dump_buffer_end = (((ncaProgramMod.hash_table_offset + ncaProgramMod.hash_table_size) / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
|
|
block0_dump_buffer_start = ((ncaProgramMod.block_offset[0] / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
block0_dump_buffer_end = (((ncaProgramMod.block_offset[0] + ncaProgramMod.block_size[0]) / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
|
|
if (ncaProgramMod.block_mod_cnt == 2)
|
|
{
|
|
block1_dump_buffer_start = ((ncaProgramMod.block_offset[1] / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
block1_dump_buffer_end = (((ncaProgramMod.block_offset[1] + ncaProgramMod.block_size[1]) / DUMP_BUFFER_SIZE) * DUMP_BUFFER_SIZE);
|
|
}
|
|
}
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start));
|
|
|
|
dumping = true;
|
|
|
|
// Dump all NCAs excluding the CNMT NCA
|
|
for(i = 0; i < (titleNcaCount - 1); i++)
|
|
{
|
|
n = DUMP_BUFFER_SIZE;
|
|
|
|
memcpy(ncaId.c, xml_content_info[i].nca_id, 16);
|
|
|
|
sha256ContextCreate(&nca_hash_ctx);
|
|
|
|
for(nca_offset = 0; nca_offset < xml_content_info[i].size; nca_offset += n, progressCtx.curOffset += n)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping NCA content \"%s\"...", xml_content_info[i].nca_id_str);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (DUMP_BUFFER_SIZE > (xml_content_info[i].size - nca_offset)) n = (xml_content_info[i].size - nca_offset);
|
|
|
|
if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, nca_offset, buf, n)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
// Replace NCA header with our modified one
|
|
if (nca_offset == 0) memcpy(buf, xml_content_info[i].encrypted_header_mod, NCA_FULL_HEADER_LENGTH);
|
|
|
|
// Replace modified Program NCA data blocks
|
|
if (curStorageId == FsStorageId_GameCard && xml_content_info[i].type == NcmContentType_Program && selectedNspDumpType != DUMP_PATCH_NSP && ncaProgramMod.block_mod_cnt > 0)
|
|
{
|
|
u64 program_nca_prev_write;
|
|
u64 program_nca_next_write;
|
|
|
|
if (nca_offset == hash_table_dump_buffer_start || nca_offset == hash_table_dump_buffer_end)
|
|
{
|
|
if (hash_table_dump_buffer_start == hash_table_dump_buffer_end)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.hash_table_offset - hash_table_dump_buffer_start), ncaProgramMod.hash_table, ncaProgramMod.hash_table_size);
|
|
} else {
|
|
program_nca_prev_write = (DUMP_BUFFER_SIZE - (ncaProgramMod.hash_table_offset - hash_table_dump_buffer_start));
|
|
program_nca_next_write = (ncaProgramMod.hash_table_size - program_nca_prev_write);
|
|
|
|
if (nca_offset == hash_table_dump_buffer_start)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.hash_table_offset - hash_table_dump_buffer_start), ncaProgramMod.hash_table, program_nca_prev_write);
|
|
} else {
|
|
memcpy(buf, ncaProgramMod.hash_table + program_nca_prev_write, program_nca_next_write);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nca_offset == block0_dump_buffer_start || nca_offset == block0_dump_buffer_end)
|
|
{
|
|
if (block0_dump_buffer_start == block0_dump_buffer_end)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.block_offset[0] - block0_dump_buffer_start), ncaProgramMod.block_data[0], ncaProgramMod.block_size[0]);
|
|
} else {
|
|
program_nca_prev_write = (DUMP_BUFFER_SIZE - (ncaProgramMod.block_offset[0] - block0_dump_buffer_start));
|
|
program_nca_next_write = (ncaProgramMod.block_size[0] - program_nca_prev_write);
|
|
|
|
if (nca_offset == block0_dump_buffer_start)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.block_offset[0] - block0_dump_buffer_start), ncaProgramMod.block_data[0], program_nca_prev_write);
|
|
} else {
|
|
memcpy(buf, ncaProgramMod.block_data[0] + program_nca_prev_write, program_nca_next_write);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ncaProgramMod.block_mod_cnt == 2 && (nca_offset == block1_dump_buffer_start || nca_offset == block1_dump_buffer_end))
|
|
{
|
|
if (block1_dump_buffer_start == block1_dump_buffer_end)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.block_offset[1] - block1_dump_buffer_start), ncaProgramMod.block_data[1], ncaProgramMod.block_size[1]);
|
|
} else {
|
|
program_nca_prev_write = (DUMP_BUFFER_SIZE - (ncaProgramMod.block_offset[1] - block1_dump_buffer_start));
|
|
program_nca_next_write = (ncaProgramMod.block_size[1] - program_nca_prev_write);
|
|
|
|
if (nca_offset == block1_dump_buffer_start)
|
|
{
|
|
memcpy(buf + (ncaProgramMod.block_offset[1] - block1_dump_buffer_start), ncaProgramMod.block_data[1], program_nca_prev_write);
|
|
} else {
|
|
memcpy(buf, ncaProgramMod.block_data[1] + program_nca_prev_write, program_nca_next_write);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update SHA-256 calculation
|
|
sha256ContextUpdate(&nca_hash_ctx, buf, n);
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE))
|
|
{
|
|
u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE));
|
|
u64 old_file_chunk_size = (n - new_file_chunk_size);
|
|
|
|
if (old_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
splitIndex++;
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex);
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proceed)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
break;
|
|
}
|
|
|
|
// Support empty files
|
|
if (!xml_content_info[i].size)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), strrchr(dumpPath, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping NCA content \"%s\"...", xml_content_info[i].nca_id_str);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
printProgressBar(&progressCtx, false, 0);
|
|
}
|
|
|
|
// Update content info
|
|
sha256ContextGetHash(&nca_hash_ctx, xml_content_info[i].hash);
|
|
convertDataToHexString(xml_content_info[i].hash, 32, xml_content_info[i].hash_str, 65);
|
|
memcpy(xml_content_info[i].nca_id, xml_content_info[i].hash, 16);
|
|
convertDataToHexString(xml_content_info[i].nca_id, 16, xml_content_info[i].nca_id_str, 33);
|
|
}
|
|
|
|
if (!proceed) goto out;
|
|
|
|
dumping = false;
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiDrawString("Writing PFS0 header, CNMT XML and CNMT NCA...", 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
// Now we can patch our CNMT NCA and generate our proper CNMT XML
|
|
if (!patchCnmtNca(cnmtNcaBuf, xml_content_info[cnmtNcaIndex].size, &xml_program_info, xml_content_info, &ncaCnmtMod))
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
goto out;
|
|
}
|
|
|
|
generateCnmtXml(&xml_program_info, xml_content_info, cnmtXml);
|
|
|
|
// Fill our Entry and String Tables
|
|
u64 file_offset = 0;
|
|
u32 filename_offset = 0;
|
|
|
|
for(i = 0; i < nspFileCount; i++)
|
|
{
|
|
// If dealing with a title with rights ID, reserve the first four entries for the CNMT XML, NACP XML (if available), cert and tik
|
|
// Otherwise, just reserve the first entry for the CNMT XML
|
|
|
|
char ncaFileName[50] = {'\0'};
|
|
u64 cur_file_size = 0;
|
|
|
|
if (i == 0)
|
|
{
|
|
// CNMT XML
|
|
sprintf(ncaFileName, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str);
|
|
cur_file_size = strlen(cnmtXml);
|
|
} else {
|
|
if (nacpXml && i == 1)
|
|
{
|
|
// NACP XML
|
|
sprintf(ncaFileName, "%s.nacp.xml", xml_content_info[nacpNcaIndex].nca_id_str);
|
|
cur_file_size = strlen(nacpXml);
|
|
} else {
|
|
if (includeCertAndTik && ((!nacpXml && (i == 1 || i == 2)) || (nacpXml && (i == 2 || i == 3))))
|
|
{
|
|
// cert / tik
|
|
sprintf(ncaFileName, "%s", (((!nacpXml && i == 1) || (nacpXml && i == 2)) ? rights_info.cert_filename : rights_info.tik_filename));
|
|
cur_file_size = (((!nacpXml && i == 1) || (nacpXml && i == 2)) ? ETICKET_CERT_FILE_SIZE : ETICKET_TIK_FILE_SIZE);
|
|
} else {
|
|
u32 cnt_idx = (i - (includeCertAndTik ? 3 : 1) - (nacpXml ? 1 : 0));
|
|
sprintf(ncaFileName, "%s.%s", xml_content_info[cnt_idx].nca_id_str, (cnt_idx == cnmtNcaIndex ? "cnmt.nca" : "nca"));
|
|
cur_file_size = xml_content_info[cnt_idx].size;
|
|
}
|
|
}
|
|
}
|
|
|
|
nspPfs0EntryTable[i].file_size = cur_file_size;
|
|
nspPfs0EntryTable[i].file_offset = file_offset;
|
|
nspPfs0EntryTable[i].filename_offset = filename_offset;
|
|
|
|
strcpy(nspPfs0StrTable + filename_offset, ncaFileName);
|
|
|
|
file_offset += nspPfs0EntryTable[i].file_size;
|
|
filename_offset += (strlen(ncaFileName) + 1);
|
|
}
|
|
|
|
// Write our full PFS0 header + CNMT XML
|
|
memcpy(buf, &nspPfs0Header, sizeof(pfs0_header));
|
|
memcpy(buf + sizeof(pfs0_header), nspPfs0EntryTable, nspFileCount * sizeof(pfs0_entry_table));
|
|
memcpy(buf + sizeof(pfs0_header) + (nspFileCount * sizeof(pfs0_entry_table)), nspPfs0StrTable, nspPfs0Header.str_table_size);
|
|
memcpy(buf + full_nsp_header_size, cnmtXml, strlen(cnmtXml));
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
if (outFile)
|
|
{
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
}
|
|
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, 0);
|
|
|
|
outFile = fopen(dumpPath, "rb+");
|
|
if (!outFile)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
uiDrawString("Failed to re-open output file for part #0!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
} else {
|
|
rewind(outFile);
|
|
}
|
|
|
|
write_res = fwrite(buf, 1, full_nsp_header_size + strlen(cnmtXml), outFile);
|
|
if (write_res != (full_nsp_header_size + strlen(cnmtXml)))
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes PFS0 header + CNMT XML to file offset 0x%016lX! (wrote %lu bytes)", full_nsp_header_size + strlen(cnmtXml), (u64)0, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Now let's write our modified CNMT NCA
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
if (outFile)
|
|
{
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
}
|
|
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex);
|
|
|
|
outFile = fopen(dumpPath, "rb+");
|
|
if (!outFile)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to re-open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
fseek(outFile, 0, SEEK_END);
|
|
|
|
// This is a pain
|
|
u64 cur_file_size = (progressCtx.curOffset - (splitIndex * SPLIT_FILE_NSP_PART_SIZE));
|
|
if ((cur_file_size + xml_content_info[cnmtNcaIndex].size) > SPLIT_FILE_NSP_PART_SIZE)
|
|
{
|
|
u64 new_file_chunk_size = ((progressCtx.curOffset + xml_content_info[cnmtNcaIndex].size) - ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE));
|
|
u64 old_file_chunk_size = (xml_content_info[cnmtNcaIndex].size - new_file_chunk_size);
|
|
|
|
if (old_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(cnmtNcaBuf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes CNMT NCA chunk #1 from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, progressCtx.curOffset, splitIndex, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
splitIndex++;
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex);
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
write_res = fwrite(cnmtNcaBuf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes CNMT NCA chunk #2 from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, progressCtx.curOffset + old_file_chunk_size, splitIndex, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(cnmtNcaBuf, 1, xml_content_info[cnmtNcaIndex].size, outFile);
|
|
if (write_res != xml_content_info[cnmtNcaIndex].size)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes CNMT NCA to file offset 0x%016lX! (wrote %lu bytes)", xml_content_info[cnmtNcaIndex].size, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
} else {
|
|
fseek(outFile, 0, SEEK_END);
|
|
|
|
write_res = fwrite(cnmtNcaBuf, 1, xml_content_info[cnmtNcaIndex].size, outFile);
|
|
if (write_res != xml_content_info[cnmtNcaIndex].size)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes CNMT NCA to file offset 0x%016lX! (wrote %lu bytes)", xml_content_info[cnmtNcaIndex].size, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + xml_content_info[cnmtNcaIndex].size) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
breaks += 2;
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
progressCtx.curOffset += xml_content_info[cnmtNcaIndex].size;
|
|
|
|
if (progressCtx.curOffset < progressCtx.totalSize)
|
|
{
|
|
setProgressBarError(&progressCtx);
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Unexpected underdump error! Wrote %lu bytes, expected %lu bytes.", progressCtx.curOffset, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
success = true;
|
|
|
|
// 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, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (calcCrc)
|
|
{
|
|
breaks += 2;
|
|
uiDrawString("CRC32 checksum calculation will begin in 5 seconds...", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
|
|
delay(5);
|
|
|
|
breaks = initial_breaks;
|
|
uiFill(0, (breaks * (font_height + (font_height / 4))) + 8, FB_WIDTH, FB_HEIGHT - (breaks * (font_height + (font_height / 4))), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Calculating CRC32 checksum. Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (outFile)
|
|
{
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
}
|
|
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%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) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "File: \"%s\".", strrchr(dumpPath, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (DUMP_BUFFER_SIZE > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset);
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (progressCtx.curOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE))
|
|
{
|
|
u64 new_file_chunk_size = ((progressCtx.curOffset + n) - ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE));
|
|
u64 old_file_chunk_size = (n - new_file_chunk_size);
|
|
|
|
if (old_file_chunk_size > 0)
|
|
{
|
|
read_res = fread(buf, 1, old_file_chunk_size, outFile);
|
|
if (read_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
splitIndex++;
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp/%02u", NSP_DUMP_PATH, dumpName, splitIndex);
|
|
|
|
outFile = fopen(dumpPath, "rb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to re-open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
read_res = fread(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (read_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
read_res = fread(buf, 1, n, outFile);
|
|
if (read_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read %lu bytes chunk from offset 0x%016lX! (read %lu bytes)", n, progressCtx.curOffset, read_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update CRC32
|
|
crc32(buf, n, &crc);
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
if (proceed)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
breaks++;
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "NSP dump CRC32 checksum: %08X", crc);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
}
|
|
} else {
|
|
uiDrawString("Failed to re-open output file in read mode!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
|
|
// Set archive bit (only for FAT32)
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp", NSP_DUMP_PATH, dumpName);
|
|
if (R_FAILED(result = fsdevSetArchiveBit(dumpPath)))
|
|
{
|
|
breaks += 2;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Warning: failed to set archive bit on output directory! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (buf) free(buf);
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success)
|
|
{
|
|
if (dumping)
|
|
{
|
|
breaks += 6;
|
|
if (fat32_error) breaks += 2;
|
|
}
|
|
|
|
if (removeFile)
|
|
{
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s.nsp", NSP_DUMP_PATH, dumpName);
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && isFat32)
|
|
{
|
|
removeDirectory(dumpPath);
|
|
} else {
|
|
unlink(dumpPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nspPfs0StrTable) free(nspPfs0StrTable);
|
|
|
|
if (nspPfs0EntryTable) free(nspPfs0EntryTable);
|
|
|
|
if (cnmtXml) free(cnmtXml);
|
|
|
|
if (cnmtNcaBuf) free(cnmtNcaBuf);
|
|
|
|
if (ncaProgramMod.block_mod_cnt == 2 && ncaProgramMod.block_data[1]) free(ncaProgramMod.block_data[1]);
|
|
|
|
if (ncaProgramMod.block_data[0]) free(ncaProgramMod.block_data[0]);
|
|
|
|
if (ncaProgramMod.hash_table) free(ncaProgramMod.hash_table);
|
|
|
|
if (nacpXml) free(nacpXml);
|
|
|
|
serviceClose(&(ncmStorage.s));
|
|
|
|
if (xml_content_info) free(xml_content_info);
|
|
|
|
if (titleContentRecords) free(titleContentRecords);
|
|
|
|
serviceClose(&(ncmDb.s));
|
|
|
|
if (titleList) free(titleList);
|
|
|
|
if (curStorageId == FsStorageId_GameCard)
|
|
{
|
|
fsStorageClose(&gameCardStorage);
|
|
|
|
if (partitionHfs0Header)
|
|
{
|
|
free(partitionHfs0Header);
|
|
partitionHfs0Header = NULL;
|
|
partitionHfs0HeaderOffset = 0;
|
|
partitionHfs0HeaderSize = 0;
|
|
partitionHfs0FileCount = 0;
|
|
partitionHfs0StrTableSize = 0;
|
|
}
|
|
}
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpRawHfs0Partition(u32 partition, bool doSplitting)
|
|
{
|
|
Result result;
|
|
u64 partitionOffset;
|
|
bool proceed = true, success = false, fat32_error = false;
|
|
u8 *buf = NULL;
|
|
u64 n = DUMP_BUFFER_SIZE;
|
|
FsGameCardHandle handle;
|
|
FsStorage gameCardStorage;
|
|
char filename[NAME_BUF_LEN * 2] = {'\0'};
|
|
FILE *outFile = NULL;
|
|
u8 splitIndex = 0;
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
size_t write_res;
|
|
|
|
char *dumpName = generateDumpFullName();
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
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 points 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
|
|
|
|
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionOffset, &(progressCtx.totalSize)))
|
|
{
|
|
convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition offset (relative to IStorage instance): 0x%016lX", partitionOffset);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;*/
|
|
|
|
if (progressCtx.totalSize <= freeSpace)
|
|
{
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%s%s - Partition %u (%s).hfs0.%02u", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex);
|
|
} else {
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%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("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
outFile = fopen(filename, "wb");
|
|
if (outFile)
|
|
{
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (buf)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u. Hold %s to cancel.", partition, NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(filename, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (DUMP_BUFFER_SIZE > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset);
|
|
|
|
if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset + progressCtx.curOffset, buf, n)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset + progressCtx.curOffset);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
splitIndex++;
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%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)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (progressCtx.curOffset >= progressCtx.totalSize) success = true;
|
|
|
|
// Support empty files
|
|
if (!progressCtx.totalSize)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(filename, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
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, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
if (fat32_error) breaks += 2;
|
|
}
|
|
|
|
free(buf);
|
|
} else {
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success)
|
|
{
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting)
|
|
{
|
|
for(u8 i = 0; i <= splitIndex; i++)
|
|
{
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%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 {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
} else {
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
uiDrawString("Error: unable to get partition details from the root HFS0 header!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
breaks += 2;
|
|
|
|
free(dumpName);
|
|
|
|
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)
|
|
{
|
|
Result result;
|
|
bool success = false, fat32_error = false;
|
|
char splitFilename[NAME_BUF_LEN] = {'\0'};
|
|
size_t destLen = strlen(dest);
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u64 off, n = DUMP_BUFFER_SIZE;
|
|
u8 splitIndex = 0;
|
|
|
|
FsGameCardHandle handle;
|
|
FsStorage gameCardStorage;
|
|
|
|
size_t write_res;
|
|
|
|
uiFill(0, ((progressCtx->line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if ((destLen + 1) < NAME_BUF_LEN)
|
|
{
|
|
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
// Same ugly hack from dumpRawHfs0Partition()
|
|
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (size > FAT32_FILESIZE_LIMIT && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex);
|
|
|
|
outFile = fopen(((size > FAT32_FILESIZE_LIMIT && doSplitting) ? splitFilename : dest), "wb");
|
|
if (outFile)
|
|
{
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (buf)
|
|
{
|
|
for (off = 0; off < size; off += n, progressCtx->curOffset += n)
|
|
{
|
|
uiFill(0, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", ((size > FAT32_FILESIZE_LIMIT && doSplitting) ? (strrchr(splitFilename, '/') + 1) : (strrchr(dest, '/') + 1)));
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off);
|
|
|
|
if (R_FAILED(result = fsStorageRead(&gameCardStorage, file_offset + off, buf, n)))
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, file_offset + off);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (off + n) < size)
|
|
{
|
|
splitIndex++;
|
|
snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex);
|
|
|
|
outFile = fopen(splitFilename, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((off + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx->line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(progressCtx, true, n);
|
|
|
|
if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (off >= size) success = true;
|
|
|
|
// Support empty files
|
|
if (!size)
|
|
{
|
|
uiFill(0, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", ((size > FAT32_FILESIZE_LIMIT && doSplitting) ? (strrchr(splitFilename, '/') + 1) : (strrchr(dest, '/') + 1)));
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (progressCtx->totalSize == size) progressCtx->progress = 100;
|
|
|
|
printProgressBar(progressCtx, false, 0);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
setProgressBarError(progressCtx);
|
|
if (fat32_error) breaks += 2;
|
|
}
|
|
|
|
free(buf);
|
|
} else {
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success)
|
|
{
|
|
if (size > FAT32_FILESIZE_LIMIT && doSplitting)
|
|
{
|
|
for(u8 i = 0; i <= splitIndex; i++)
|
|
{
|
|
snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, i);
|
|
unlink(splitFilename);
|
|
}
|
|
} else {
|
|
unlink(dest);
|
|
}
|
|
}
|
|
} else {
|
|
uiDrawString("Failed to open output file!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
}
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", destLen);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool copyHfs0Contents(u32 partition, hfs0_entry_table *partitionEntryTable, progress_ctx_t *progressCtx, const char *dest, bool splitting)
|
|
{
|
|
if (!dest || !*dest)
|
|
{
|
|
uiDrawString("Error: destination directory is empty.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
if (!partitionHfs0Header || !partitionEntryTable)
|
|
{
|
|
uiDrawString("HFS0 partition header information unavailable!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
if (!progressCtx)
|
|
{
|
|
uiDrawString("Error: invalid progress context.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
char dbuf[NAME_BUF_LEN] = {'\0'};
|
|
size_t dest_len = strlen(dest);
|
|
|
|
if ((dest_len + 1) >= NAME_BUF_LEN)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination directory name is too long! (%lu bytes)", dest_len);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
strcpy(dbuf, dest);
|
|
mkdir(dbuf, 0744);
|
|
|
|
dbuf[dest_len] = '/';
|
|
dest_len++;
|
|
|
|
u32 i;
|
|
bool success;
|
|
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx->start));
|
|
|
|
for(i = 0; i < partitionHfs0FileCount; 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);
|
|
|
|
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;
|
|
|
|
success = copyFileFromHfs0(partition, filename, dbuf, file_offset, partitionEntryTable[i].file_size, progressCtx, splitting);
|
|
if (!success) break;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpHfs0PartitionData(u32 partition)
|
|
{
|
|
bool success = false;
|
|
u32 i;
|
|
hfs0_entry_table *entryTable = NULL;
|
|
char dumpPath[NAME_BUF_LEN * 2] = {'\0'};
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
char *dumpName = generateDumpFullName();
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (getPartitionHfs0Header(partition))
|
|
{
|
|
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, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total partition data size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (progressCtx.totalSize <= freeSpace)
|
|
{
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s - Partition %u (%s)", HFS0_DUMP_PATH, dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying partition #%u data to \"%s/\". Hold %s to cancel.", partition, dumpPath, NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
uiRefreshDisplay();
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
|
|
success = copyHfs0Contents(partition, entryTable, &progressCtx, dumpPath, true);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
if (success)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
removeDirectory(dumpPath);
|
|
}
|
|
} else {
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
free(entryTable);
|
|
} else {
|
|
uiDrawString("Unable to allocate memory for the HFS0 file entries!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
uiDrawString("The selected partition is empty!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
free(partitionHfs0Header);
|
|
partitionHfs0Header = NULL;
|
|
partitionHfs0HeaderOffset = 0;
|
|
partitionHfs0HeaderSize = 0;
|
|
partitionHfs0FileCount = 0;
|
|
partitionHfs0StrTableSize = 0;
|
|
}
|
|
|
|
breaks += 2;
|
|
|
|
free(dumpName);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpFileFromHfs0Partition(u32 partition, u32 file, char *filename)
|
|
{
|
|
if (!partitionHfs0Header)
|
|
{
|
|
uiDrawString("HFS0 partition header information unavailable!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
if (!filename || !*filename)
|
|
{
|
|
uiDrawString("Filename unavailable!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
char *dumpName = generateDumpFullName();
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
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, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "%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("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
uiRefreshDisplay();
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start));
|
|
|
|
success = copyFileFromHfs0(partition, filename, destCopyPath, file_offset, file_size, &progressCtx, true);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
if (success)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
}
|
|
}
|
|
} else {
|
|
breaks++;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", strlen(destCopyPath) + 1 + strlen(filename));
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
uiDrawString("Error: unable to get file details from the partition HFS0 header!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpExeFsSectionData(u32 appIndex, bool doSplitting)
|
|
{
|
|
u64 n;
|
|
FILE *outFile;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex;
|
|
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];
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
size_t write_res;
|
|
|
|
u32 i;
|
|
u64 offset;
|
|
|
|
if (!titleAppCount)
|
|
{
|
|
uiDrawString("Error: invalid application count!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
if (appIndex > (titleAppCount - 1))
|
|
{
|
|
uiDrawString("Error: invalid application index!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
char *dumpName = generateNSPDumpName(DUMP_APP_NSP, appIndex);
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Remove " (BASE)"
|
|
dumpName[strlen(dumpName) - 7] = '\0';
|
|
|
|
// Retrieve ExeFS from Program NCA
|
|
if (!readProgramNcaExeFsOrRomFs(appIndex, false))
|
|
{
|
|
free(dumpName);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Calculate total dump size
|
|
if (!calculateExeFsExtractedDataSize(&(progressCtx.totalSize))) goto out;
|
|
|
|
breaks++;
|
|
convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Extracted ExeFS dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize > freeSpace)
|
|
{
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Prepare output dump path
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s", EXEFS_DUMP_PATH, dumpName);
|
|
mkdir(dumpPath, 0744);
|
|
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (!buf)
|
|
{
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Start dump process
|
|
breaks++;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start));
|
|
|
|
for(i = 0; i < exeFsContext.exefs_header.file_cnt; i++)
|
|
{
|
|
n = DUMP_BUFFER_SIZE;
|
|
outFile = NULL;
|
|
splitIndex = 0;
|
|
|
|
char *exeFsFilename = (exeFsContext.exefs_str_table + exeFsContext.exefs_entries[i].filename_offset);
|
|
|
|
// Check if we're dealing with a nameless file
|
|
if (!strlen(exeFsFilename))
|
|
{
|
|
uiDrawString("Error: file entry without name in ExeFS section!", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
snprintf(curDumpPath, sizeof(curDumpPath) / sizeof(curDumpPath[0]), "%s/%s", dumpPath, exeFsFilename);
|
|
removeIllegalCharacters(curDumpPath + strlen(dumpPath) + 1);
|
|
|
|
if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && doSplitting)
|
|
{
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(curDumpPath, tmp_idx);
|
|
}
|
|
|
|
outFile = fopen(curDumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
uiDrawString("Failed to open output file!", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
uiFill(0, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", exeFsFilename);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
for(offset = 0; offset < exeFsContext.exefs_entries[i].file_size; offset += n, progressCtx.curOffset += n)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(curDumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (DUMP_BUFFER_SIZE > (exeFsContext.exefs_entries[i].file_size - offset)) n = (exeFsContext.exefs_entries[i].file_size - offset);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
proceed = processNcaCtrSectionBlock(&(exeFsContext.ncmStorage), &(exeFsContext.ncaId), exeFsContext.exefs_data_offset + exeFsContext.exefs_entries[i].file_offset + offset, buf, n, &(exeFsContext.aes_ctx), false);
|
|
breaks = (progressCtx.line_offset - 4);
|
|
|
|
if (!proceed) break;
|
|
|
|
if (exeFsContext.exefs_entries[i].file_size > FAT32_FILESIZE_LIMIT && doSplitting && (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);
|
|
|
|
if (old_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (offset + n) < exeFsContext.exefs_entries[i].file_size)
|
|
{
|
|
char *tmp = strrchr(curDumpPath, '.');
|
|
if (tmp != NULL) *tmp = '\0';
|
|
|
|
splitIndex++;
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(curDumpPath, tmp_idx);
|
|
|
|
outFile = fopen(curDumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, offset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((offset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
proceed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!proceed) break;
|
|
|
|
// Support empty files
|
|
if (!exeFsContext.exefs_entries[i].file_size)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(curDumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (progressCtx.totalSize == exeFsContext.exefs_entries[i].file_size) progressCtx.progress = 100;
|
|
|
|
printProgressBar(&progressCtx, true, 0);
|
|
}
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
if (progressCtx.curOffset >= progressCtx.totalSize)
|
|
{
|
|
success = true;
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Unexpected underdump error! Wrote %lu bytes, expected %lu bytes.", progressCtx.curOffset, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
if (success)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
removeDirectory(dumpPath);
|
|
if (fat32_error) breaks += 2;
|
|
}
|
|
|
|
out:
|
|
if (buf) free(buf);
|
|
|
|
freeExeFsContext();
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpFileFromExeFsSection(u32 appIndex, u32 fileIndex, bool doSplitting)
|
|
{
|
|
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 || appIndex > (titleAppCount - 1))
|
|
{
|
|
uiDrawString("Error: invalid parameters to parse file entry from ExeFS section!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
u64 n = DUMP_BUFFER_SIZE;
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex = 0;
|
|
bool proceed = true, success = false, fat32_error = false, removeFile = true;
|
|
|
|
char dumpPath[NAME_BUF_LEN * 2] = {'\0'};
|
|
char tmp_idx[5];
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
size_t write_res;
|
|
|
|
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("Error: file entry without name in ExeFS section!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
char *dumpName = generateNSPDumpName(DUMP_APP_NSP, appIndex);
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Remove " (BASE)"
|
|
dumpName[strlen(dumpName) - 7] = '\0';
|
|
|
|
// Generate output path
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s", EXEFS_DUMP_PATH, dumpName);
|
|
mkdir(dumpPath, 0744);
|
|
|
|
strcat(dumpPath, "/");
|
|
size_t cur_len = strlen(dumpPath);
|
|
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, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize > freeSpace)
|
|
{
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
breaks++;
|
|
|
|
// Check if the dump already exists
|
|
if (checkIfFileExists(dumpPath))
|
|
{
|
|
// 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("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
removeFile = false;
|
|
goto out;
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
// Start dump process
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", exeFsFilename);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
uiDrawString("Failed to open output file!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (!buf)
|
|
{
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
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) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (DUMP_BUFFER_SIZE > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
proceed = processNcaCtrSectionBlock(&(exeFsContext.ncmStorage), &(exeFsContext.ncaId), exeFsContext.exefs_data_offset + exeFsContext.exefs_entries[fileIndex].file_offset + progressCtx.curOffset, buf, n, &(exeFsContext.aes_ctx), false);
|
|
breaks = (progressCtx.line_offset - 2);
|
|
|
|
if (!proceed) 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(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
char *tmp = strrchr(dumpPath, '.');
|
|
if (tmp != NULL) *tmp = '\0';
|
|
|
|
splitIndex++;
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(dumpPath, tmp_idx);
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (progressCtx.curOffset >= progressCtx.totalSize) success = true;
|
|
|
|
// Support empty files
|
|
if (!progressCtx.totalSize)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
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, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
}
|
|
|
|
out:
|
|
if (buf) free(buf);
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success)
|
|
{
|
|
if (fat32_error) breaks += 2;
|
|
|
|
if (removeFile)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool recursiveDumpRomFsFile(u32 file_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx, bool doSplitting)
|
|
{
|
|
if (!romFsContext.romfs_filetable_size || file_offset > romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries || !romfs_path || !output_path || !progressCtx)
|
|
{
|
|
uiDrawString("Error: invalid parameters to parse file entry from RomFS section!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
size_t orig_romfs_path_len = strlen(romfs_path);
|
|
size_t orig_output_path_len = strlen(output_path);
|
|
|
|
u64 n = DUMP_BUFFER_SIZE;
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex = 0;
|
|
bool proceed = true, success = false, fat32_error = false;
|
|
|
|
u64 off = 0;
|
|
|
|
size_t write_res;
|
|
|
|
char tmp_idx[5];
|
|
|
|
romfs_file *entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + file_offset);
|
|
|
|
// Check if we're dealing with a nameless file
|
|
if (!entry->nameLen)
|
|
{
|
|
uiDrawString("Error: file entry without name in RomFS section!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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("Error: RomFS section file path is too long!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
// Generate current path
|
|
strcat(romfs_path, "/");
|
|
strncat(romfs_path, (char*)entry->name, entry->nameLen);
|
|
|
|
strcat(output_path, "/");
|
|
strncat(output_path, (char*)entry->name, entry->nameLen);
|
|
removeIllegalCharacters(output_path + orig_output_path_len + 1);
|
|
|
|
// Start dump process
|
|
uiFill(0, ((progressCtx->line_offset - 4) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"romfs:%s\"...", romfs_path);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
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("Failed to open output file!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
goto out;
|
|
}
|
|
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (!buf)
|
|
{
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
goto out;
|
|
}
|
|
|
|
for(off = 0; off < entry->dataSize; off += n, progressCtx->curOffset += n)
|
|
{
|
|
uiFill(0, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(output_path, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (DUMP_BUFFER_SIZE > (entry->dataSize - off)) n = (entry->dataSize - off);
|
|
|
|
breaks = (progressCtx->line_offset + 2);
|
|
proceed = processNcaCtrSectionBlock(&(romFsContext.ncmStorage), &(romFsContext.ncaId), romFsContext.romfs_filedata_offset + entry->dataOff + off, buf, n, &(romFsContext.aes_ctx), false);
|
|
breaks = (progressCtx->line_offset - 4);
|
|
|
|
if (!proceed) break;
|
|
|
|
if (entry->dataSize > 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(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (off + n) < entry->dataSize)
|
|
{
|
|
char *tmp = strrchr(output_path, '.');
|
|
if (tmp != NULL) *tmp = '\0';
|
|
|
|
splitIndex++;
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(output_path, tmp_idx);
|
|
|
|
outFile = fopen(output_path, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((off + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx->line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(progressCtx, true, n);
|
|
|
|
if ((off + n) < entry->dataSize && ((off / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (off >= entry->dataSize) success = true;
|
|
|
|
// Support empty files
|
|
if (!entry->dataSize)
|
|
{
|
|
uiFill(0, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(output_path, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx->line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
if (progressCtx->totalSize == entry->dataSize) progressCtx->progress = 100;
|
|
|
|
printProgressBar(progressCtx, false, 0);
|
|
}
|
|
|
|
out:
|
|
if (buf) free(buf);
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success && fat32_error) breaks += 2;
|
|
|
|
romfs_path[orig_romfs_path_len] = '\0';
|
|
output_path[orig_output_path_len] = '\0';
|
|
|
|
if (success)
|
|
{
|
|
if (entry->sibling != ROMFS_ENTRY_EMPTY) success = recursiveDumpRomFsFile(entry->sibling, romfs_path, output_path, progressCtx, true);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool recursiveDumpRomFsDir(u32 dir_offset, char *romfs_path, char *output_path, progress_ctx_t *progressCtx)
|
|
{
|
|
if (!romFsContext.romfs_dirtable_size || dir_offset > romFsContext.romfs_dirtable_size || !romFsContext.romfs_dir_entries || !romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries || !romfs_path || !output_path || !progressCtx)
|
|
{
|
|
uiDrawString("Error: invalid parameters to parse directory entry from RomFS section!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
size_t orig_romfs_path_len = strlen(romfs_path);
|
|
size_t orig_output_path_len = strlen(output_path);
|
|
|
|
romfs_dir *entry = (romfs_dir*)((u8*)romFsContext.romfs_dir_entries + dir_offset);
|
|
|
|
// Check if we're dealing with a nameless directory that's not the root directory
|
|
if (!entry->nameLen && dir_offset > 0)
|
|
{
|
|
uiDrawString("Error: directory entry without name in RomFS section!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
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("Error: RomFS section directory path is too long!", 8, ((progressCtx->line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
// Generate current path
|
|
if (entry->nameLen)
|
|
{
|
|
strcat(romfs_path, "/");
|
|
strncat(romfs_path, (char*)entry->name, entry->nameLen);
|
|
|
|
strcat(output_path, "/");
|
|
strncat(output_path, (char*)entry->name, entry->nameLen);
|
|
removeIllegalCharacters(output_path + orig_output_path_len + 1);
|
|
mkdir(output_path, 0744);
|
|
}
|
|
|
|
if (entry->childFile != ROMFS_ENTRY_EMPTY)
|
|
{
|
|
if (!recursiveDumpRomFsFile(entry->childFile, romfs_path, output_path, progressCtx, true))
|
|
{
|
|
romfs_path[orig_romfs_path_len] = '\0';
|
|
output_path[orig_output_path_len] = '\0';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (entry->childDir != ROMFS_ENTRY_EMPTY)
|
|
{
|
|
if (!recursiveDumpRomFsDir(entry->childDir, romfs_path, output_path, progressCtx))
|
|
{
|
|
romfs_path[orig_romfs_path_len] = '\0';
|
|
output_path[orig_output_path_len] = '\0';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
romfs_path[orig_romfs_path_len] = '\0';
|
|
output_path[orig_output_path_len] = '\0';
|
|
|
|
if (entry->sibling != ROMFS_ENTRY_EMPTY)
|
|
{
|
|
if (!recursiveDumpRomFsDir(entry->sibling, romfs_path, output_path, progressCtx)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool dumpRomFsSectionData(u32 appIndex)
|
|
{
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
char romFsPath[NAME_BUF_LEN * 2] = {'\0'}, dumpPath[NAME_BUF_LEN * 2] = {'\0'};
|
|
|
|
bool success = false;
|
|
|
|
if (!titleAppCount)
|
|
{
|
|
uiDrawString("Error: invalid application count!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
if (appIndex > (titleAppCount - 1))
|
|
{
|
|
uiDrawString("Error: invalid application index!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
char *dumpName = generateNSPDumpName(DUMP_APP_NSP, appIndex);
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Remove " (BASE)"
|
|
dumpName[strlen(dumpName) - 7] = '\0';
|
|
|
|
// Retrieve RomFS from Program NCA
|
|
if (!readProgramNcaExeFsOrRomFs(appIndex, true))
|
|
{
|
|
free(dumpName);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Calculate total dump size
|
|
if (!calculateRomFsExtractedDataSize(&(progressCtx.totalSize))) goto out;
|
|
|
|
breaks++;
|
|
convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Extracted RomFS dump size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize > freeSpace)
|
|
{
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
// Prepare output dump path
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s", ROMFS_DUMP_PATH, dumpName);
|
|
mkdir(dumpPath, 0744);
|
|
|
|
// Start dump process
|
|
breaks++;
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
uiRefreshDisplay();
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
progressCtx.line_offset = (breaks + 4);
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.start));
|
|
|
|
success = recursiveDumpRomFsDir(0, romFsPath, dumpPath, &progressCtx);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
|
|
if (success)
|
|
{
|
|
timeGetCurrentTime(TimeType_LocalSystemClock, &(progressCtx.now));
|
|
progressCtx.now -= progressCtx.start;
|
|
|
|
formatETAString(progressCtx.now, progressCtx.etaInfo, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
removeDirectory(dumpPath);
|
|
}
|
|
|
|
out:
|
|
freeRomFsContext();
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpFileFromRomFsSection(u32 appIndex, u32 file_offset, bool doSplitting)
|
|
{
|
|
if (!romFsContext.romfs_filetable_size || file_offset > romFsContext.romfs_filetable_size || !romFsContext.romfs_file_entries || appIndex > (titleAppCount - 1))
|
|
{
|
|
uiDrawString("Error: invalid parameters to parse file entry from RomFS section!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
u64 n = DUMP_BUFFER_SIZE;
|
|
FILE *outFile = NULL;
|
|
u8 *buf = NULL;
|
|
u8 splitIndex = 0;
|
|
bool proceed = true, success = false, fat32_error = false, removeFile = true;
|
|
|
|
char dumpPath[NAME_BUF_LEN * 2] = {'\0'};
|
|
char tmp_idx[5];
|
|
|
|
progress_ctx_t progressCtx;
|
|
memset(&progressCtx, 0, sizeof(progress_ctx_t));
|
|
|
|
size_t write_res;
|
|
|
|
romfs_file *entry = (romfs_file*)((u8*)romFsContext.romfs_file_entries + file_offset);
|
|
|
|
// Check if we're dealing with a nameless file
|
|
if (!entry->nameLen)
|
|
{
|
|
uiDrawString("Error: file entry without name in RomFS section!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
char *dumpName = generateNSPDumpName(DUMP_APP_NSP, appIndex);
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
// Remove " (BASE)"
|
|
dumpName[strlen(dumpName) - 7] = '\0';
|
|
|
|
// Generate output path
|
|
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "%s%s", ROMFS_DUMP_PATH, dumpName);
|
|
mkdir(dumpPath, 0744);
|
|
|
|
// Create subdirectories
|
|
char *tmp1 = NULL;
|
|
char *tmp2 = NULL;
|
|
size_t cur_len;
|
|
|
|
tmp1 = strchr(curRomFsPath, '/');
|
|
|
|
while(tmp1 != NULL)
|
|
{
|
|
tmp1++;
|
|
|
|
if (!strlen(tmp1)) break;
|
|
|
|
strcat(dumpPath, "/");
|
|
|
|
cur_len = strlen(dumpPath);
|
|
|
|
tmp2 = strchr(tmp1, '/');
|
|
if (tmp2 != NULL)
|
|
{
|
|
strncat(dumpPath, tmp1, tmp2 - tmp1);
|
|
|
|
tmp1 = tmp2;
|
|
} else {
|
|
strcat(dumpPath, tmp1);
|
|
|
|
tmp1 = NULL;
|
|
}
|
|
|
|
removeIllegalCharacters(dumpPath + cur_len);
|
|
|
|
mkdir(dumpPath, 0744);
|
|
}
|
|
|
|
strcat(dumpPath, "/");
|
|
cur_len = strlen(dumpPath);
|
|
strncat(dumpPath, (char*)entry->name, entry->nameLen);
|
|
removeIllegalCharacters(dumpPath + cur_len);
|
|
|
|
if (progressCtx.totalSize > FAT32_FILESIZE_LIMIT && doSplitting)
|
|
{
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(dumpPath, tmp_idx);
|
|
}
|
|
|
|
progressCtx.totalSize = entry->dataSize;
|
|
convertSize(progressCtx.totalSize, progressCtx.totalSizeStr, sizeof(progressCtx.totalSizeStr) / sizeof(progressCtx.totalSizeStr[0]));
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "File size: %s (%lu bytes).", progressCtx.totalSizeStr, progressCtx.totalSize);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;
|
|
|
|
if (progressCtx.totalSize > freeSpace)
|
|
{
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
breaks++;
|
|
|
|
// Check if the dump already exists
|
|
if (checkIfFileExists(dumpPath))
|
|
{
|
|
// 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("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
removeFile = false;
|
|
goto out;
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Hold %s to cancel.", NINTENDO_FONT_B);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
// Start dump process
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"romfs:%s/%.*s\"...", curRomFsPath, entry->nameLen, entry->name);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
uiDrawString("Failed to open output file!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
buf = malloc(DUMP_BUFFER_SIZE);
|
|
if (!buf)
|
|
{
|
|
uiDrawString("Failed to allocate memory for the dump process!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
goto out;
|
|
}
|
|
|
|
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) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
uiRefreshDisplay();
|
|
|
|
if (DUMP_BUFFER_SIZE > (progressCtx.totalSize - progressCtx.curOffset)) n = (progressCtx.totalSize - progressCtx.curOffset);
|
|
|
|
breaks = (progressCtx.line_offset + 2);
|
|
proceed = processNcaCtrSectionBlock(&(romFsContext.ncmStorage), &(romFsContext.ncaId), romFsContext.romfs_filedata_offset + entry->dataOff + progressCtx.curOffset, buf, n, &(romFsContext.aes_ctx), false);
|
|
breaks = (progressCtx.line_offset - 2);
|
|
|
|
if (!proceed) 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(buf, 1, old_file_chunk_size, outFile);
|
|
if (write_res != old_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(outFile);
|
|
outFile = NULL;
|
|
|
|
if (new_file_chunk_size > 0 || (progressCtx.curOffset + n) < progressCtx.totalSize)
|
|
{
|
|
char *tmp = strrchr(dumpPath, '.');
|
|
if (tmp != NULL) *tmp = '\0';
|
|
|
|
splitIndex++;
|
|
sprintf(tmp_idx, ".%02u", splitIndex);
|
|
strcat(dumpPath, tmp_idx);
|
|
|
|
outFile = fopen(dumpPath, "wb");
|
|
if (!outFile)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
|
|
if (new_file_chunk_size > 0)
|
|
{
|
|
write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile);
|
|
if (write_res != new_file_chunk_size)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "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(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
write_res = fwrite(buf, 1, n, outFile);
|
|
if (write_res != n)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, progressCtx.curOffset, write_res);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
|
|
if ((progressCtx.curOffset + n) > FAT32_FILESIZE_LIMIT)
|
|
{
|
|
uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 8, ((progressCtx.line_offset + 4) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
fat32_error = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
printProgressBar(&progressCtx, true, n);
|
|
|
|
if ((progressCtx.curOffset + n) < progressCtx.totalSize && ((progressCtx.curOffset / DUMP_BUFFER_SIZE) % 10) == 0)
|
|
{
|
|
hidScanInput();
|
|
|
|
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
|
if (keysDown & KEY_B)
|
|
{
|
|
uiDrawString("Process canceled.", 8, ((progressCtx.line_offset + 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (progressCtx.curOffset >= progressCtx.totalSize) success = true;
|
|
|
|
// Support empty files
|
|
if (!progressCtx.totalSize)
|
|
{
|
|
uiFill(0, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + 8, FB_WIDTH, (font_height + (font_height / 4)) * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output file: \"%s\".", strrchr(dumpPath, '/') + 1);
|
|
uiDrawString(strbuf, 8, ((progressCtx.line_offset - 2) * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
|
|
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, sizeof(progressCtx.etaInfo) / sizeof(progressCtx.etaInfo[0]));
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", progressCtx.etaInfo);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
setProgressBarError(&progressCtx);
|
|
}
|
|
|
|
out:
|
|
if (buf) free(buf);
|
|
|
|
if (outFile) fclose(outFile);
|
|
|
|
if (!success)
|
|
{
|
|
if (fat32_error) breaks += 2;
|
|
|
|
if (removeFile)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(dumpName);
|
|
|
|
breaks += 2;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool dumpGameCardCertificate()
|
|
{
|
|
u32 crc = 0;
|
|
Result result;
|
|
FsGameCardHandle handle;
|
|
FsStorage gameCardStorage;
|
|
bool proceed = true, success = false;
|
|
FILE *outFile = NULL;
|
|
char filename[NAME_BUF_LEN * 2] = {'\0'};
|
|
u8 buf[CERT_SIZE];
|
|
size_t write_res;
|
|
|
|
char *dumpName = generateDumpFullName();
|
|
if (!dumpName)
|
|
{
|
|
uiDrawString("Error: unable to generate output dump name!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
return false;
|
|
}
|
|
|
|
workaroundPartitionZeroAccess();
|
|
|
|
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0)))
|
|
{
|
|
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks++;*/
|
|
|
|
if (CERT_SIZE <= freeSpace)
|
|
{
|
|
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, CERT_OFFSET, buf, CERT_SIZE)))
|
|
{
|
|
// Calculate CRC32
|
|
crc32(buf, CERT_SIZE, &crc);
|
|
|
|
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "%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("Process canceled.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
} else {
|
|
// Remove the prompt from the screen
|
|
breaks = cur_breaks;
|
|
uiFill(0, 8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8), FB_WIDTH, FB_HEIGHT - (8 + (breaks * (font_height + (font_height / 4))) + (font_height / 8)), BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
|
}
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping gamecard certificate to \"%s\"...", strrchr(filename, '/' ) + 1);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 255, 255);
|
|
breaks += 2;
|
|
|
|
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
|
{
|
|
uiDrawString("Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
breaks += 2;
|
|
}
|
|
|
|
uiRefreshDisplay();
|
|
|
|
outFile = fopen(filename, "wb");
|
|
if (outFile)
|
|
{
|
|
write_res = fwrite(buf, 1, CERT_SIZE, outFile);
|
|
if (write_res == CERT_SIZE)
|
|
{
|
|
success = true;
|
|
uiDrawString("Process successfully completed!", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 0, 255, 0);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %u bytes certificate data! (wrote %lu bytes)", CERT_SIZE, write_res);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
fclose(outFile);
|
|
if (!success) unlink(filename);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
uiDrawString("Error: not enough free space available in the SD card.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
fsStorageClose(&gameCardStorage);
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
} else {
|
|
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
|
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
|
|
}
|
|
|
|
breaks += 2;
|
|
|
|
free(dumpName);
|
|
|
|
return success;
|
|
}
|