mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-29 21:52:22 +00:00
1702 lines
90 KiB
C
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;
|
|
}
|