1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-29 21:52:22 +00:00
nxdumptool/source/dumper.c
2019-04-23 01:14:57 -04:00

1702 lines
90 KiB
C

#include <stdio.h>
#include <malloc.h>
#include <dirent.h>
#include <memory.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>
#include "crc32_fast.h"
#include "dumper.h"
#include "fsext.h"
#include "ui.h"
#include "util.h"
/* Extern variables */
extern u64 freeSpace;
extern int breaks;
extern int font_height;
extern u64 gameCardSize, trimmedCardSize;
extern char gameCardSizeStr[32], trimmedCardSizeStr[32];
extern char *hfs0_header;
extern u64 hfs0_offset, hfs0_size;
extern u32 hfs0_partition_cnt;
extern char *partitionHfs0Header;
extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize;
extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize;
extern u64 gameCardTitleID;
extern u32 gameCardVersion;
extern char fixedGameCardName[0x201];
extern u64 gameCardUpdateTitleID;
extern u32 gameCardUpdateVersion;
extern char gameCardUpdateVersionStr[128];
extern char *filenameBuffer;
extern int filenamesCount;
extern AppletType programAppletType;
/* Statically allocated variables */
static char strbuf[NAME_BUF_LEN * 2] = {'\0'};
void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator)
{
FsGameCardHandle handle;
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) return;
FsStorage gameCardStorage;
if (R_FAILED(fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) return;
fsStorageClose(&gameCardStorage);
}
bool getRootHfs0Header(FsDeviceOperator* fsOperator)
{
u32 magic;
Result result;
FsGameCardHandle handle;
FsStorage gameCardStorage;
hfs0_partition_cnt = 0;
workaroundPartitionZeroAccess(fsOperator);
if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result);
return false;
}
// Get bundled FW version update
if (R_SUCCEEDED(fsDeviceOperatorUpdatePartitionInfo(fsOperator, &handle, &gameCardUpdateVersion, &gameCardUpdateTitleID)))
{
if (gameCardUpdateTitleID == GAMECARD_UPDATE_TITLEID)
{
char decimalVersion[64] = {'\0'};
convertTitleVersionToDecimal(gameCardUpdateVersion, decimalVersion, sizeof(decimalVersion));
switch(gameCardUpdateVersion)
{
case SYSUPDATE_100:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "1.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_200:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_210:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_220:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.2.0 - v%s", decimalVersion);
break;
case SYSUPDATE_230:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.3.0 - v%s", decimalVersion);
break;
case SYSUPDATE_300:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_301:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_302:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.2 - v%s", decimalVersion);
break;
case SYSUPDATE_400:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_401:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_410:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_500:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_501:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_502:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.2 - v%s", decimalVersion);
break;
case SYSUPDATE_510:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_600:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_601:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_610:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_620:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.2.0 - v%s", decimalVersion);
break;
case SYSUPDATE_700:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_701:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_800:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "8.0.0 - v%s", decimalVersion);
break;
default:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "UNKNOWN - v%s", decimalVersion);
break;
}
} else {
gameCardUpdateTitleID = 0;
gameCardUpdateVersion = 0;
}
}
if (R_FAILED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0)))
{
uiStatusMsg("getRootHfs0Header: OpenGameCardStorage failed! (0x%08X)", result);
return false;
}
char *gamecard_header = (char*)malloc(GAMECARD_HEADER_SIZE);
if (!gamecard_header)
{
uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the gamecard header!");
fsStorageClose(&gameCardStorage);
return false;
}
if (R_FAILED(result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE)))
{
uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result);
free(gamecard_header);
fsStorageClose(&gameCardStorage);
return false;
}
u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR];
switch(cardSize)
{
case 0xFA: // 1 GiB
gameCardSize = GAMECARD_SIZE_1GiB;
break;
case 0xF8: // 2 GiB
gameCardSize = GAMECARD_SIZE_2GiB;
break;
case 0xF0: // 4 GiB
gameCardSize = GAMECARD_SIZE_4GiB;
break;
case 0xE0: // 8 GiB
gameCardSize = GAMECARD_SIZE_8GiB;
break;
case 0xE1: // 16 GiB
gameCardSize = GAMECARD_SIZE_16GiB;
break;
case 0xE2: // 32 GiB
gameCardSize = GAMECARD_SIZE_32GiB;
break;
default:
uiStatusMsg("getRootHfs0Header: Invalid game card size value: 0x%02X", cardSize);
free(gamecard_header);
fsStorageClose(&gameCardStorage);
return false;
}
convertSize(gameCardSize, gameCardSizeStr, sizeof(gameCardSizeStr) / sizeof(gameCardSizeStr[0]));
memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64));
trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE));
convertSize(trimmedCardSize, trimmedCardSizeStr, sizeof(trimmedCardSizeStr) / sizeof(trimmedCardSizeStr[0]));
memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64));
memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64));
free(gamecard_header);
hfs0_header = (char*)malloc(hfs0_size);
if (!hfs0_header)
{
uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!");
gameCardSize = 0;
memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr));
trimmedCardSize = 0;
memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr));
hfs0_offset = hfs0_size = 0;
fsStorageClose(&gameCardStorage);
return false;
}
if (R_FAILED(result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size)))
{
uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result);
gameCardSize = 0;
memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr));
trimmedCardSize = 0;
memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr));
free(hfs0_header);
hfs0_header = NULL;
hfs0_offset = hfs0_size = 0;
fsStorageClose(&gameCardStorage);
return false;
}
memcpy(&magic, hfs0_header, sizeof(u32));
magic = bswap_32(magic);
if (magic != HFS0_MAGIC)
{
uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC);
gameCardSize = 0;
memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr));
trimmedCardSize = 0;
memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr));
free(hfs0_header);
hfs0_header = NULL;
hfs0_offset = hfs0_size = 0;
fsStorageClose(&gameCardStorage);
return false;
}
memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32));
fsStorageClose(&gameCardStorage);
return true;
}
bool getHfs0EntryDetails(char *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size)
{
if (hfs0Header == NULL) return false;
if (entry_idx > (num_entries - 1)) return false;
hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * num_entries);
if (!entryTable) return false;
memcpy(entryTable, hfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * num_entries);
// Determine the partition index that's going to be used for offset calculation
// If we're dealing with a root HFS0 header, just use entry_idx
// Otherwise, partitionIndex must be used, because entry_idx represents the file entry we must look for in the provided HFS0 partition header
u32 part_idx = (isRoot ? entry_idx : partitionIndex);
switch(part_idx)
{
case 0: // Update (contained within IStorage instance with partition ID 0)
case 1: // Normal or Logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0)
// Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start
// Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function)
*out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset);
break;
case 2:
// Check if we're dealing with a type 0x01 gamecard
if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT)
{
// Secure (contained within IStorage instance with partition ID 1)
// Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance
// Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation
*out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset));
} else {
// Normal (contained within IStorage instance with partition ID 0)
// Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start
// Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function)
*out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset);
}
break;
case 3: // Secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1)
// Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance
// Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation
*out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset));
break;
default:
break;
}
// Store the file size for the desired HFS0 entry
*out_size = entryTable[entry_idx].file_size;
free(entryTable);
return true;
}
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc)
{
u64 partitionOffset = 0, fileOffset = 0, xciDataSize = 0, totalSize = 0, n;
u64 partitionSizes[ISTORAGE_PARTITION_CNT];
char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'};
u32 partition;
Result result;
FsGameCardHandle handle;
FsStorage gameCardStorage;
bool proceed = true, success = false;
FILE *outFile = NULL;
char *buf = NULL;
u8 splitIndex = 0;
u8 progress = 0;
u32 crc1 = 0, crc2 = 0;
u64 start, now, remainingTime;
struct tm *timeinfo;
char etaInfo[32] = {'\0'};
double lastSpeed = 0.0, averageSpeed = 0.0;
for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Getting partition #%u size...", partition);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
uiDrawString(strbuf, 0, breaks * font_height, 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, 0, breaks * font_height, 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, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageGetSize failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
proceed = false;
}
fsStorageClose(&gameCardStorage);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
proceed = false;
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 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, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (trimDump)
{
totalSize = trimmedCardSize;
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(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 {
totalSize = xciDataSize;
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr);
}
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes).", totalSizeStr, totalSize);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
if (totalSize <= freeSpace)
{
breaks++;
if (totalSize > SPLIT_FILE_MIN && isFat32)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex);
} else {
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci", fixedGameCardName, gameCardVersion, gameCardTitleID);
}
outFile = fopen(filename, "wb");
if (outFile)
{
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Writing output to \"%s\". Hold B to cancel.", filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
{
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
breaks += 2;
}
timeGetCurrentTime(TimeType_LocalSystemClock, &start);
for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++)
{
n = DUMP_BUFFER_SIZE;
uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping partition #%u...", partition);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition)))
{
for(partitionOffset = 0; partitionOffset < partitionSizes[partition]; partitionOffset += n, fileOffset += n)
{
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, 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
// Remove game card certificate
if (fileOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE);
if (calcCrc)
{
if (!trimDump)
{
if (dumpCert)
{
if (fileOffset == 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 (totalSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB))
{
u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB));
u64 old_file_chunk_size = (n - new_file_chunk_size);
if (old_file_chunk_size > 0)
{
if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
}
fclose(outFile);
splitIndex++;
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, 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, 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
if (new_file_chunk_size > 0)
{
if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset + old_file_chunk_size);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
}
} else {
if (fwrite(buf, 1, n, outFile) != n)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
}
timeGetCurrentTime(TimeType_LocalSystemClock, &now);
lastSpeed = (((double)(fileOffset + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start));
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
remainingTime = (u64)(((double)(totalSize - (fileOffset + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed);
timeinfo = localtime((time_t*)&remainingTime);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
progress = (u8)(((fileOffset + n) * 100) / totalSize);
uiFill(0, ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
uiDrawString(strbuf, font_height * 2, (breaks + 2) * font_height, 255, 255, 255);
uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0);
uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((fileOffset + n) * (FB_WIDTH / 2)) / totalSize), font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
convertSize(fileOffset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255);
uiRefreshDisplay();
if ((fileOffset + n) < totalSize && ((fileOffset / DUMP_BUFFER_SIZE) % 10) == 0)
{
hidScanInput();
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (keysDown & KEY_B)
{
uiDrawString("Process canceled", 0, (breaks + 5) * font_height, 255, 0, 0);
proceed = false;
break;
}
}
}
if (fileOffset >= totalSize) success = true;
// Support empty files
if (!partitionSizes[partition])
{
uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, ((fileOffset * (FB_WIDTH / 2)) / totalSize), font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
convertSize(fileOffset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255);
uiRefreshDisplay();
}
if (!proceed)
{
uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((fileOffset + n) * (FB_WIDTH / 2)) / totalSize), font_height, 255, 0, 0);
breaks += 5;
}
fsStorageClose(&gameCardStorage);
} else {
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed for partition #%u! (0x%08X)", partition, result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
proceed = false;
}
} else {
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed for partition #%u! (0x%08X)", partition, result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
proceed = false;
}
if (!proceed) break;
}
free(buf);
} else {
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0);
}
if (success)
{
fclose(outFile);
breaks += 5;
now -= start;
timeinfo = localtime((time_t*)&now);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo);
uiDrawString(strbuf, 0, breaks * font_height, 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, 0, breaks * font_height, 0, 255, 0);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (without certificate): %08X", crc2);
uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc2);
uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0);
}
breaks += 2;
uiDrawString("Starting verification process using XML database from NSWDB.COM...", 0, breaks * font_height, 255, 255, 255);
breaks++;
uiRefreshDisplay();
gameCardDumpNSWDBCheck(crc2);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1);
uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0);
breaks++;
uiDrawString("Dump verification disabled (not compatible with trimmed dumps).", 0, breaks * font_height, 255, 255, 255);
}
}
} else {
if (outFile) fclose(outFile);
if (totalSize > SPLIT_FILE_MIN && isFat32)
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, i);
remove(filename);
}
} else {
remove(filename);
}
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
}
}
breaks += 2;
return success;
}
bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting)
{
Result result;
u64 size, partitionOffset;
bool success = false;
char *buf;
u64 off, n = DUMP_BUFFER_SIZE;
FsGameCardHandle handle;
FsStorage gameCardStorage;
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'};
u8 progress = 0;
FILE *outFile = NULL;
u8 splitIndex = 0;
u64 start, now, remainingTime;
struct tm *timeinfo;
char etaInfo[32] = {'\0'};
double lastSpeed = 0.0, averageSpeed = 0.0;
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
uiDrawString(strbuf, 0, breaks * font_height, 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, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionOffset, &size))
{
convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes)", totalSizeStr, size);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition offset (relative to IStorage instance): 0x%016lX", partitionOffset);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
if (size <= freeSpace)
{
if (size > SPLIT_FILE_MIN && doSplitting)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex);
} else {
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
}
outFile = fopen(filename, "wb");
if (outFile)
{
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u to \"%.*s\". Hold B to cancel.", partition, (int)((size > SPLIT_FILE_MIN && doSplitting) ? (strlen(filename) - 3) : strlen(filename)), filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
{
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
breaks += 2;
}
uiRefreshDisplay();
timeGetCurrentTime(TimeType_LocalSystemClock, &start);
for (off = 0; off < size; off += n)
{
if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off);
if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset + off, buf, n)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset + off);
uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB))
{
u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB));
u64 old_file_chunk_size = (n - new_file_chunk_size);
if (old_file_chunk_size > 0)
{
if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off);
uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
}
fclose(outFile);
splitIndex++;
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, 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, 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
if (new_file_chunk_size > 0)
{
if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size);
uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
}
} else {
if (fwrite(buf, 1, n, outFile) != n)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off);
uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
}
timeGetCurrentTime(TimeType_LocalSystemClock, &now);
lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start));
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed);
timeinfo = localtime((time_t*)&remainingTime);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
progress = (u8)(((off + n) * 100) / size);
uiFill(0, (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
uiDrawString(strbuf, font_height * 2, breaks * font_height, 255, 255, 255);
uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0);
uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), breaks * font_height, 255, 255, 255);
uiRefreshDisplay();
if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0)
{
hidScanInput();
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (keysDown & KEY_B)
{
uiDrawString("Process canceled", 0, (breaks + 3) * font_height, 255, 0, 0);
break;
}
}
}
if (off >= size) success = true;
// Support empty files
if (!size)
{
uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, FB_WIDTH / 2, font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
uiDrawString("100%% [0 B / 0 B]", FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), breaks * font_height, 255, 255, 255);
uiRefreshDisplay();
}
if (success)
{
now -= start;
timeinfo = localtime((time_t*)&now);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo);
uiDrawString(strbuf, 0, (breaks + 3) * font_height, 0, 255, 0);
} else {
uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 255, 0, 0);
}
breaks += 3;
free(buf);
} else {
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0);
}
if (outFile) fclose(outFile);
if (!success)
{
if (size > SPLIT_FILE_MIN && doSplitting)
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), i);
remove(filename);
}
} else {
remove(filename);
}
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 255, 0, 0);
}
fsStorageClose(&gameCardStorage);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
breaks += 2;
return success;
}
bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition)
{
if (hfs0_header == NULL) return false;
if (partitionHfs0Header != NULL)
{
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderOffset = 0;
partitionHfs0HeaderSize = 0;
partitionHfs0FileCount = 0;
partitionHfs0StrTableSize = 0;
}
char *buf = NULL;
Result result;
FsGameCardHandle handle;
FsStorage gameCardStorage;
u64 partitionSize = 0;
u32 magic = 0;
bool success = false;
if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionHfs0HeaderOffset, &partitionSize))
{
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
// Same ugly hack from dumpRawPartition()
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);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
buf = (char*)malloc(MEDIA_UNIT_SIZE);
if (buf)
{
// First read MEDIA_UNIT_SIZE bytes
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, buf, MEDIA_UNIT_SIZE)))
{
// Check the HFS0 magic word
memcpy(&magic, buf, sizeof(u32));
magic = bswap_32(magic);
if (magic == HFS0_MAGIC)
{
// Calculate the size for the partition HFS0 header
memcpy(&partitionHfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32));
memcpy(&partitionHfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32));
partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionHfs0StrTableSize);
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header offset (relative to IStorage instance): 0x%016lX", partition, partitionHfs0HeaderOffset);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, partitionHfs0FileCount);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, partitionHfs0StrTableSize);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
uiRefreshDisplay();*/
// Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary
partitionHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE);
partitionHfs0Header = (char*)malloc(partitionHfs0HeaderSize);
if (partitionHfs0Header)
{
// Check if we were dealing with the correct header size all along
if (partitionHfs0HeaderSize == MEDIA_UNIT_SIZE)
{
// Just copy what we already have
memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE);
success = true;
} else {
// Read the whole HFS0 header
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, partitionHfs0Header, partitionHfs0HeaderSize)))
{
success = true;
} else {
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderOffset = 0;
partitionHfs0HeaderSize = 0;
partitionHfs0FileCount = 0;
partitionHfs0StrTableSize = 0;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
}
if (success)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
}
} else {
partitionHfs0HeaderOffset = 0;
partitionHfs0HeaderSize = 0;
partitionHfs0FileCount = 0;
partitionHfs0StrTableSize = 0;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
free(buf);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
fsStorageClose(&gameCardStorage);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 255, 0, 0);
}
breaks += 2;
return success;
}
bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* source, const char* dest, const u64 file_offset, const u64 size, bool doSplitting, bool calcEta)
{
Result result;
bool success = false;
char splitFilename[NAME_BUF_LEN] = {'\0'};
size_t destLen = strlen(dest);
FILE *outFile = NULL;
char *buf = NULL;
u64 off, n = DUMP_BUFFER_SIZE;
u8 splitIndex = 0;
u8 progress = 0;
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'};
FsGameCardHandle handle;
FsStorage gameCardStorage;
u64 start, now, remainingTime;
struct tm *timeinfo;
char etaInfo[32] = {'\0'};
double lastSpeed = 0.0, averageSpeed = 0.0;
uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source);
uiDrawString(strbuf, 0, (breaks + 1) * font_height, 255, 255, 255);
uiRefreshDisplay();
if ((destLen + 1) < NAME_BUF_LEN)
{
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
// Same ugly hack from dumpRawPartition()
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, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
if (size > SPLIT_FILE_MIN && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex);
outFile = fopen(((size > SPLIT_FILE_MIN && doSplitting) ? splitFilename : dest), "wb");
if (outFile)
{
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
{
if (calcEta) timeGetCurrentTime(TimeType_LocalSystemClock, &start);
for (off = 0; off < size; off += n)
{
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, 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB))
{
u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB));
u64 old_file_chunk_size = (n - new_file_chunk_size);
if (old_file_chunk_size > 0)
{
if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
}
fclose(outFile);
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, 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
if (new_file_chunk_size > 0)
{
if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
}
} else {
if (fwrite(buf, 1, n, outFile) != n)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off);
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
}
if (calcEta)
{
timeGetCurrentTime(TimeType_LocalSystemClock, &now);
lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start));
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed);
timeinfo = localtime((time_t*)&remainingTime);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
uiFill(0, ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
uiDrawString(strbuf, font_height * 2, (breaks + 3) * font_height, 255, 255, 255);
}
progress = (u8)(((off + n) * 100) / size);
uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0);
uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 3) * font_height, 255, 255, 255);
uiRefreshDisplay();
if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0)
{
hidScanInput();
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (keysDown & KEY_B)
{
uiDrawString("Process canceled", 0, (breaks + 5) * font_height, 255, 0, 0);
break;
}
}
}
if (off >= size) success = true;
// Support empty files
if (!size)
{
uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 255, 0);
uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
uiDrawString("100%% [0 B / 0 B]", FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 3) * font_height, 255, 255, 255);
uiRefreshDisplay();
}
if (!success)
{
uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 255, 0, 0);
breaks += 5;
}
free(buf);
} else {
breaks += 3;
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0);
}
if (outFile) fclose(outFile);
if (success)
{
if (calcEta)
{
breaks += 5;
now -= start;
timeinfo = localtime((time_t*)&now);
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo);
uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0);
}
} else {
if (size > SPLIT_FILE_MIN && doSplitting)
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, i);
remove(splitFilename);
}
} else {
remove(dest);
}
}
} else {
breaks += 3;
uiDrawString("Failed to open output file!", 0, breaks * font_height, 255, 255, 255);
}
fsStorageClose(&gameCardStorage);
} else {
breaks += 3;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
breaks += 3;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
breaks += 3;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", destLen);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
return success;
}
bool copyHfs0Contents(FsDeviceOperator* fsOperator, u32 partition, hfs0_entry_table *partitionEntryTable, const char *dest, bool splitting)
{
if (!dest || !*dest)
{
uiDrawString("Error: destination directory is empty.", 0, breaks * font_height, 255, 0, 0);
return false;
}
if (!partitionHfs0Header || !partitionEntryTable)
{
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 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, 0, breaks * font_height, 255, 0, 0);
return false;
}
strcpy(dbuf, dest);
mkdir(dbuf, 0744);
dbuf[dest_len] = '/';
dest_len++;
u32 i;
bool success;
for(i = 0; i < partitionHfs0FileCount; i++)
{
u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionEntryTable[i].filename_offset);
char *filename = (partitionHfs0Header + filename_offset);
strcpy(dbuf + dest_len, filename);
u64 file_offset = (partitionHfs0HeaderSize + partitionEntryTable[i].file_offset);
if (HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition) == 0) file_offset += partitionHfs0HeaderOffset;
success = copyFileFromHfs0(fsOperator, partition, filename, dbuf, file_offset, partitionEntryTable[i].file_size, splitting, false);
if (!success) break;
}
return success;
}
bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
{
bool success = false;
u64 total_size = 0;
u32 i;
hfs0_entry_table *entryTable = NULL;
char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'};
workaroundPartitionZeroAccess(fsOperator);
if (getPartitionHfs0Header(fsOperator, partition))
{
if (partitionHfs0FileCount)
{
entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount);
if (entryTable)
{
memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount);
// Calculate total size
for(i = 0; i < partitionHfs0FileCount; i++) total_size += entryTable[i].file_size;
convertSize(total_size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total partition data size: %s (%lu bytes)", totalSizeStr, total_size);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks++;
if (total_size <= freeSpace)
{
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying partition #%u data to \"%s/\". Hold B to cancel.", partition, dumpPath);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
{
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
breaks += 2;
}
uiRefreshDisplay();
success = copyHfs0Contents(fsOperator, partition, entryTable, dumpPath, true);
if (success)
{
breaks += 5;
uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0);
} else {
removeDirectory(dumpPath);
}
} else {
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
}
free(entryTable);
} else {
uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0);
}
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderSize = 0;
partitionHfs0FileCount = 0;
partitionHfs0StrTableSize = 0;
}
breaks += 2;
return success;
}
bool getHfs0FileList(FsDeviceOperator* fsOperator, u32 partition)
{
if (!getPartitionHfs0Header(fsOperator, partition)) return false;
if (!partitionHfs0Header)
{
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0);
return false;
}
if (!partitionHfs0FileCount)
{
uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0);
return false;
}
if (partitionHfs0FileCount > FILENAME_MAX_CNT)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition contains more than %u files! (%u entries)", FILENAME_MAX_CNT, partitionHfs0FileCount);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return false;
}
hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount);
if (!entryTable)
{
uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0);
return false;
}
memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount);
memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE);
int i;
int max_elements = (int)partitionHfs0FileCount;
char *nextFilename = filenameBuffer;
filenamesCount = 0;
for(i = 0; i < max_elements; i++)
{
u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + entryTable[i].filename_offset);
addStringToFilenameBuffer(partitionHfs0Header + filename_offset, &nextFilename);
}
free(entryTable);
return true;
}
bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename)
{
if (!partitionHfs0Header)
{
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0);
return false;
}
if (!filename || !*filename)
{
uiDrawString("Filename unavailable!", 0, breaks * font_height, 255, 0, 0);
return false;
}
u64 file_offset = 0;
u64 file_size = 0;
bool success = false;
if (getHfs0EntryDetails(partitionHfs0Header, partitionHfs0HeaderOffset, partitionHfs0HeaderSize, partitionHfs0FileCount, file, false, partition, &file_offset, &file_size))
{
if (file_size <= freeSpace)
{
char destCopyPath[NAME_BUF_LEN] = {'\0'};
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
if ((strlen(destCopyPath) + 1 + strlen(filename)) < NAME_BUF_LEN)
{
mkdir(destCopyPath, 0744);
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)/%s", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), filename);
uiDrawString("Hold B to cancel.", 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
{
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
breaks += 2;
}
uiRefreshDisplay();
success = copyFileFromHfs0(fsOperator, partition, filename, destCopyPath, file_offset, file_size, true, true);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", strlen(destCopyPath) + 1 + strlen(filename));
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: not enough free space available in the SD card (%lu bytes required).", file_size);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to get file details from the partition HFS0 header!", 0, breaks * font_height, 255, 0, 0);
}
return success;
}
bool dumpGameCertificate(FsDeviceOperator* fsOperator)
{
u32 crc;
Result result;
FsGameCardHandle handle;
FsStorage gameCardStorage;
bool success = false;
FILE *outFile = NULL;
char filename[NAME_BUF_LEN] = {'\0'};
char *buf = NULL;
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
uiDrawString(strbuf, 0, breaks * font_height, 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, 0, breaks * font_height, 255, 255, 255);
breaks++;*/
if (CERT_SIZE <= freeSpace)
{
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
{
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]), "sdmc:/%s v%u (%016lX) - Certificate (%08X).bin", fixedGameCardName, gameCardVersion, gameCardTitleID, crc);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to file \"%s\"...", filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
breaks += 2;
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
{
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
breaks += 2;
}
uiRefreshDisplay();
outFile = fopen(filename, "wb");
if (outFile)
{
if (fwrite(buf, 1, CERT_SIZE, outFile) == CERT_SIZE)
{
success = true;
uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0);
} else {
uiDrawString("Failed to write certificate data!", 0, breaks * font_height, 255, 0, 0);
}
fclose(outFile);
if (!success) remove(filename);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
free(buf);
} else {
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0);
}
} else {
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
}
fsStorageClose(&gameCardStorage);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
}
breaks += 2;
return success;
}