mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2025-01-09 19:17:23 -03:00
Update to v1.0.4.
This commit is contained in:
parent
730912a626
commit
a4bfaf8cf2
8 changed files with 474 additions and 191 deletions
4
Makefile
4
Makefile
|
@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
|||
|
||||
VERSION_MAJOR := 1
|
||||
VERSION_MINOR := 0
|
||||
VERSION_MICRO := 3
|
||||
VERSION_MICRO := 4
|
||||
|
||||
APP_TITLE := gcdumptool
|
||||
APP_AUTHOR := MCMrARM, DarkMatterCore
|
||||
|
@ -62,7 +62,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
|||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lnx
|
||||
LIBS := -lnx -lxml2 -lm
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
|
|
14
README.md
14
README.md
|
@ -4,8 +4,10 @@ Nintendo Switch Game Card Dump Tool
|
|||
Main features
|
||||
--------------
|
||||
|
||||
* Generates XCI cartridge dumps (with optional certificate removal and optional 0xFF padding to match the full game card size).
|
||||
* Generates XCI cartridge dumps (with optional certificate removal and optional trimming).
|
||||
* CRC32 checksum calculation for XCI dumps.
|
||||
* Full XCI dump verification using XML database from nswdb.com (NSWreleases.xml).
|
||||
* XCI dump renaming based on the XML database from nswdb.com (NSWreleases.xml).
|
||||
* Precise HFS0 raw partition dumping (using the root HFS0 header from the game card).
|
||||
* Partition filesystem data dumping.
|
||||
* Partition filesystem browser (with manual file dump support).
|
||||
|
@ -13,6 +15,7 @@ Main features
|
|||
* Free SD card space checks in place.
|
||||
* File splitting support for all operations, using 2 GiB parts.
|
||||
* Game card Title ID and Control.nacp retrieval support using NCM and NS services.
|
||||
* Dump speed and ETA calculation.
|
||||
|
||||
Thanks to
|
||||
--------------
|
||||
|
@ -27,6 +30,15 @@ Thanks to
|
|||
Changelog
|
||||
--------------
|
||||
|
||||
**v1.0.4:**
|
||||
|
||||
* exFAT mode turned on by default.
|
||||
* Replaced padding option with a trim output dump option (same as XCI-Cutter).
|
||||
* Added dump speed and ETA calculation.
|
||||
* Added XCI dump verification using XML database from nswdb.com (NSWreleases.xml). The file must be saved to the SD card root directory. Also, keep in mind that dump verification is only performed if you choose to create a full dump (with or without cert), not a trimmed one.
|
||||
* Made CRC32 checksum calculation + XCI dump verification a configurable option.
|
||||
* Output XCI dumps will get renamed to their corresponding Scene release if a match is found using the XML database from nswdb.com (e.g. "sdmc:/0100000000010000_20180625-234930.xci" -> "sdmc:/Super.Mario.Odyssey.NSW-BigBlueBox.xci").
|
||||
|
||||
**v1.0.3:**
|
||||
|
||||
* Made the 0xFF padding feature a configurable option.
|
||||
|
|
392
source/dumper.c
392
source/dumper.c
|
@ -5,6 +5,7 @@
|
|||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "crc32_fast.h"
|
||||
#include "dumper.h"
|
||||
|
@ -18,8 +19,8 @@ extern int breaks;
|
|||
|
||||
extern u32 currentFBWidth, currentFBHeight;
|
||||
|
||||
extern u64 gameCardSize;
|
||||
extern char gameCardSizeStr[32];
|
||||
extern u64 gameCardSize, trimmedCardSize;
|
||||
extern char gameCardSizeStr[32], trimmedCardSizeStr[32];
|
||||
|
||||
extern char *hfs0_header;
|
||||
extern u64 hfs0_offset, hfs0_size;
|
||||
|
@ -115,6 +116,10 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
|
|||
|
||||
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));
|
||||
|
||||
|
@ -134,6 +139,9 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
|
|||
gameCardSize = 0;
|
||||
memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr));
|
||||
|
||||
trimmedCardSize = 0;
|
||||
memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr));
|
||||
|
||||
hfs0_offset = hfs0_size = 0;
|
||||
|
||||
fsStorageClose(&gameCardStorage);
|
||||
|
@ -148,6 +156,9 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
|
|||
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;
|
||||
|
@ -167,6 +178,9 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
|
|||
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;
|
||||
|
@ -223,11 +237,11 @@ bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool addPadding)
|
||||
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc)
|
||||
{
|
||||
u64 partitionOffset = 0, fileOffset = 0, xciDataSize = 0, totalSize = 0, paddingSize = 0, n;
|
||||
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'}, paddingSizeStr[32] = {'\0'}, filename[128] = {'\0'}, timeStamp[16] = {'\0'};
|
||||
char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[256] = {'\0'}, timeStamp[16] = {'\0'};
|
||||
u32 handle, partition;
|
||||
Result result;
|
||||
FsStorage gameCardStorage;
|
||||
|
@ -235,8 +249,13 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
FILE *outFile = NULL;
|
||||
char *buf = NULL;
|
||||
u8 splitIndex = 0;
|
||||
int progress = 0;
|
||||
u32 crc = 0;
|
||||
u8 progress = 0;
|
||||
u32 crc1 = 0, crc2 = 0;
|
||||
|
||||
time_t 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++)
|
||||
{
|
||||
|
@ -291,25 +310,25 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
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 * 8, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (addPadding)
|
||||
if (trimDump)
|
||||
{
|
||||
totalSize = gameCardSize;
|
||||
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", gameCardSizeStr);
|
||||
totalSize = trimmedCardSize;
|
||||
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", trimmedCardSizeStr);
|
||||
|
||||
paddingSize = (totalSize - xciDataSize);
|
||||
convertSize(paddingSize, paddingSizeStr, sizeof(paddingSizeStr) / sizeof(paddingSizeStr[0]));
|
||||
// Change dump size for the last IStorage partition
|
||||
u64 partitionSizesSum = 0;
|
||||
for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i];
|
||||
|
||||
breaks += 2;
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "0xFF padding size: %s (%lu bytes)", paddingSizeStr, paddingSize);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (trimmedCardSize - partitionSizesSum);
|
||||
} else {
|
||||
totalSize = xciDataSize;
|
||||
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr);
|
||||
}
|
||||
|
||||
convertSize(totalSize, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes)", totalSizeStr, totalSize);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
if (totalSize <= freeSpace)
|
||||
|
@ -335,6 +354,8 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
time(&start);
|
||||
|
||||
for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++)
|
||||
{
|
||||
n = DUMP_BUFFER_SIZE;
|
||||
|
@ -366,8 +387,45 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
// Remove game card certificate
|
||||
if (fileOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE);
|
||||
|
||||
// Update CRC32
|
||||
crc32(buf, n, &crc);
|
||||
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))
|
||||
{
|
||||
|
@ -419,16 +477,29 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
}
|
||||
}
|
||||
|
||||
progress = (int)(((fileOffset + n) * 100) / totalSize);
|
||||
time(&now);
|
||||
|
||||
lastSpeed = ((double)((fileOffset + n) / DUMP_BUFFER_SIZE) / difftime(now, start));
|
||||
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
|
||||
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
|
||||
|
||||
remainingTime = (time_t)((double)((totalSize - (fileOffset + n)) / DUMP_BUFFER_SIZE) / averageSpeed);
|
||||
timeinfo = localtime(&remainingTime);
|
||||
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
|
||||
|
||||
progress = (u8)(((fileOffset + n) * 100) / totalSize);
|
||||
|
||||
uiFill(0, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0);
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / totalSize), 16, 0, 255, 0);
|
||||
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
convertSize(fileOffset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%d%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
|
||||
|
@ -446,19 +517,18 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
}
|
||||
}
|
||||
|
||||
if (fileOffset >= xciDataSize) success = true;
|
||||
if (fileOffset >= totalSize) success = true;
|
||||
|
||||
// Support empty files
|
||||
if (!partitionSizes[partition])
|
||||
{
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 255, 0);
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, ((fileOffset * (currentFBWidth / 2)) / totalSize), 16, 0, 255, 0);
|
||||
|
||||
convertSize(fileOffset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%d%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
}
|
||||
|
@ -486,136 +556,74 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
if (!proceed) break;
|
||||
}
|
||||
|
||||
if (!success) free(buf);
|
||||
free(buf);
|
||||
} else {
|
||||
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (addPadding)
|
||||
{
|
||||
// Add file padding
|
||||
memset(buf, 0xFF, DUMP_BUFFER_SIZE);
|
||||
|
||||
uiFill(0, breaks * 8, currentFBWidth, 8, 50, 50, 50);
|
||||
uiFill(0, (breaks + 3) * 8, currentFBWidth, 16, 50, 50, 50);
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Writing 0xFF padding...");
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
|
||||
for(partitionOffset = 0; partitionOffset < paddingSize; partitionOffset += n, fileOffset += n)
|
||||
{
|
||||
if (DUMP_BUFFER_SIZE > (paddingSize - partitionOffset)) n = (paddingSize - partitionOffset);
|
||||
|
||||
// Update CRC32
|
||||
crc32(buf, n, &crc);
|
||||
|
||||
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 + 7) * 8, 255, 0, 0);
|
||||
proceed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(outFile);
|
||||
|
||||
splitIndex++;
|
||||
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, 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 + 7) * 8, 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 + 7) * 8, 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 + 7) * 8, 255, 0, 0);
|
||||
proceed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
progress = (int)(((fileOffset + n) * 100) / totalSize);
|
||||
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0);
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / totalSize), 16, 0, 255, 0);
|
||||
|
||||
convertSize(fileOffset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%d%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
|
||||
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 + 7) * 8, 255, 0, 0);
|
||||
proceed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outFile) fclose(outFile);
|
||||
fclose(outFile);
|
||||
|
||||
breaks += 7;
|
||||
|
||||
if (proceed)
|
||||
now -= start;
|
||||
timeinfo = localtime(&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 * 8, 0, 255, 0);
|
||||
|
||||
if (calcCrc)
|
||||
{
|
||||
uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 hash: %08X", crc);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
} else {
|
||||
success = false;
|
||||
|
||||
if (totalSize > SPLIT_FILE_MIN && isFat32)
|
||||
if (!trimDump)
|
||||
{
|
||||
for(u8 i = 0; i <= splitIndex; i++)
|
||||
if (dumpCert)
|
||||
{
|
||||
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, i);
|
||||
remove(filename);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (with certificate): %08X", crc1);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (without certificate): %08X", crc2);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc2);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
}
|
||||
|
||||
breaks += 2;
|
||||
uiDrawString("Starting verification process using XML database from nswdb.com...", 0, breaks * 8, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
char releaseName[256] = {'\0'}, newFilename[256] = {'\0'};
|
||||
if (gameCardDumpNSWDBCheck(crc2, releaseName, sizeof(releaseName) / sizeof(releaseName[0])))
|
||||
{
|
||||
if (totalSize > SPLIT_FILE_MIN && isFat32)
|
||||
{
|
||||
for(u8 i = 0; i <= splitIndex; i++)
|
||||
{
|
||||
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, i);
|
||||
snprintf(newFilename, sizeof(newFilename) / sizeof(newFilename[0]), "sdmc:/%s.xci.%02u", RemoveIllegalCharacters(releaseName), i);
|
||||
rename(filename, newFilename);
|
||||
}
|
||||
} else {
|
||||
snprintf(newFilename, sizeof(newFilename) / sizeof(newFilename[0]), "sdmc:/%s.xci", RemoveIllegalCharacters(releaseName));
|
||||
rename(filename, newFilename);
|
||||
}
|
||||
|
||||
breaks++;
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output XCI dump renamed to \"%.*s\"", (int)((totalSize > SPLIT_FILE_MIN && isFat32) ? (strlen(newFilename) - 3) : strlen(newFilename)), newFilename);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
}
|
||||
} else {
|
||||
remove(filename);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
breaks++;
|
||||
|
||||
uiDrawString("Dump verification disabled (not compatible with trimmed dumps).", 0, breaks * 8, 255, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
} else {
|
||||
if (outFile) fclose(outFile);
|
||||
|
||||
|
@ -653,11 +661,16 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
char *buf;
|
||||
u64 off, n = DUMP_BUFFER_SIZE;
|
||||
FsStorage gameCardStorage;
|
||||
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[128] = {'\0'}, timeStamp[16] = {'\0'};
|
||||
int progress = 0;
|
||||
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[256] = {'\0'}, timeStamp[16] = {'\0'};
|
||||
u8 progress = 0;
|
||||
FILE *outFile = NULL;
|
||||
u8 splitIndex = 0;
|
||||
|
||||
time_t 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)))
|
||||
|
@ -735,6 +748,8 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
|
||||
syncDisplay();
|
||||
|
||||
time(&start);
|
||||
|
||||
for (off = 0; off < size; off += n)
|
||||
{
|
||||
if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off);
|
||||
|
@ -792,16 +807,29 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
}
|
||||
}
|
||||
|
||||
progress = (int)(((off + n) * 100) / size);
|
||||
time(&now);
|
||||
|
||||
lastSpeed = ((double)((off + n) / DUMP_BUFFER_SIZE) / difftime(now, start));
|
||||
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
|
||||
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
|
||||
|
||||
remainingTime = (time_t)((double)((size - (off + n)) / DUMP_BUFFER_SIZE) / averageSpeed);
|
||||
timeinfo = localtime(&remainingTime);
|
||||
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
|
||||
|
||||
progress = (u8)(((off + n) * 100) / size);
|
||||
|
||||
uiFill(0, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), (breaks * 8) + 4, 255, 255, 255);
|
||||
|
||||
uiFill(currentFBWidth / 4, breaks * 8, currentFBWidth / 2, 16, 0, 0, 0);
|
||||
uiFill(currentFBWidth / 4, breaks * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 0, 255, 0);
|
||||
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%d%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, (breaks * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, (breaks * 8) + 4, 255, 255, 255);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
|
||||
|
@ -827,15 +855,19 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "100%% [0 B / 0 B]");
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, (breaks * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, (breaks * 8) + 4, 255, 255, 255);
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
uiDrawString("Process successfully completed!", 0, (breaks + 4) * 8, 0, 255, 0);
|
||||
now -= start;
|
||||
timeinfo = localtime(&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 + 4) * 8, 0, 255, 0);
|
||||
} else {
|
||||
uiFill(currentFBWidth / 4, breaks * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 255, 0, 0);
|
||||
}
|
||||
|
@ -920,7 +952,7 @@ bool openPartitionFs(FsFileSystem* ret, FsDeviceOperator* fsOperator, u32 partit
|
|||
return success;
|
||||
}
|
||||
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting)
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcEta)
|
||||
{
|
||||
bool success = false;
|
||||
char splitFilename[NAME_BUF_LEN] = {'\0'};
|
||||
|
@ -929,9 +961,14 @@ bool copyFile(const char* source, const char* dest, bool doSplitting)
|
|||
char *buf = NULL;
|
||||
u64 size, off, n = DUMP_BUFFER_SIZE;
|
||||
u8 splitIndex = 0;
|
||||
int progress = 0;
|
||||
u8 progress = 0;
|
||||
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'};
|
||||
|
||||
time_t start, now, remainingTime;
|
||||
struct tm *timeinfo;
|
||||
char etaInfo[32] = {'\0'};
|
||||
double lastSpeed = 0.0, averageSpeed = 0.0;
|
||||
|
||||
uiFill(0, breaks * 8, currentFBWidth, 8, 50, 50, 50);
|
||||
uiFill(0, (breaks + 3) * 8, currentFBWidth, 16, 50, 50, 50);
|
||||
|
||||
|
@ -957,6 +994,8 @@ bool copyFile(const char* source, const char* dest, bool doSplitting)
|
|||
buf = (char*)malloc(DUMP_BUFFER_SIZE);
|
||||
if (buf)
|
||||
{
|
||||
if (calcEta) time(&start);
|
||||
|
||||
for (off = 0; off < size; off += n)
|
||||
{
|
||||
if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off);
|
||||
|
@ -1014,16 +1053,32 @@ bool copyFile(const char* source, const char* dest, bool doSplitting)
|
|||
}
|
||||
}
|
||||
|
||||
progress = (int)(((off + n) * 100) / size);
|
||||
if (calcEta)
|
||||
{
|
||||
time(&now);
|
||||
|
||||
lastSpeed = ((double)((off + n) / DUMP_BUFFER_SIZE) / difftime(now, start));
|
||||
averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed));
|
||||
if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values
|
||||
|
||||
remainingTime = (time_t)((double)((size - (off + n)) / DUMP_BUFFER_SIZE) / averageSpeed);
|
||||
timeinfo = localtime(&remainingTime);
|
||||
strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo);
|
||||
|
||||
uiFill(0, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
}
|
||||
|
||||
progress = (u8)(((off + n) * 100) / size);
|
||||
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0);
|
||||
uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 0, 255, 0);
|
||||
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%d%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
|
||||
|
@ -1049,8 +1104,8 @@ bool copyFile(const char* source, const char* dest, bool doSplitting)
|
|||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "100%% [0 B / 0 B]");
|
||||
|
||||
uiFill((currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, currentFBWidth - ((currentFBWidth / 4) + (currentFBWidth / 2) + 8), 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, (currentFBWidth / 4) + (currentFBWidth / 2) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50);
|
||||
uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255);
|
||||
|
||||
syncDisplay();
|
||||
}
|
||||
|
@ -1069,8 +1124,19 @@ bool copyFile(const char* source, const char* dest, bool doSplitting)
|
|||
|
||||
if (outFile) fclose(outFile);
|
||||
|
||||
if (!success)
|
||||
if (success)
|
||||
{
|
||||
if (calcEta)
|
||||
{
|
||||
breaks += 7;
|
||||
|
||||
now -= start;
|
||||
timeinfo = localtime(&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 * 8, 0, 255, 0);
|
||||
}
|
||||
} else {
|
||||
if (size > SPLIT_FILE_MIN && doSplitting)
|
||||
{
|
||||
for(u8 i = 0; i <= splitIndex; i++)
|
||||
|
@ -1139,7 +1205,7 @@ bool _copyDirectory(char* sbuf, size_t source_len, char* dbuf, size_t dest_len,
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
if (!copyFile(sbuf, dbuf, splitting))
|
||||
if (!copyFile(sbuf, dbuf, splitting, false))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
|
@ -1371,7 +1437,7 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator)
|
|||
FsStorage gameCardStorage;
|
||||
bool success = false;
|
||||
FILE *outFile = NULL;
|
||||
char timeStamp[16] = {'\0'}, filename[128] = {'\0'};
|
||||
char timeStamp[16] = {'\0'}, filename[256] = {'\0'};
|
||||
char *buf = NULL;
|
||||
|
||||
workaroundPartitionZeroAccess(fsOperator);
|
||||
|
|
|
@ -10,11 +10,14 @@
|
|||
#define SPLIT_FILE_MIN (u64)0xEE6B2800 // 4 GB (4000000000 bytes)
|
||||
#define SPLIT_FILE_2GiB (u64)0x80000000
|
||||
|
||||
#define MEDIA_UNIT_SIZE 0x200
|
||||
|
||||
#define CERT_OFFSET 0x7000
|
||||
#define CERT_SIZE 0x200
|
||||
|
||||
#define GAMECARD_HEADER_SIZE 0x200
|
||||
#define GAMECARD_SIZE_ADDR 0x10D
|
||||
#define GAMECARD_DATAEND_ADDR 0x118
|
||||
|
||||
#define HFS0_OFFSET_ADDR 0x130
|
||||
#define HFS0_SIZE_ADDR 0x138
|
||||
|
@ -35,6 +38,8 @@
|
|||
|
||||
#define bswap_32(a) ((((a) << 24) & 0xff000000) | (((a) << 8) & 0xff0000) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))
|
||||
|
||||
#define SMOOTHING_FACTOR (double)0.05
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u64 file_offset;
|
||||
|
@ -48,10 +53,10 @@ typedef struct
|
|||
void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator);
|
||||
bool getRootHfs0Header(FsDeviceOperator* fsOperator);
|
||||
bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size);
|
||||
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool addPadding);
|
||||
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc);
|
||||
bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting);
|
||||
bool openPartitionFs(FsFileSystem* ret, FsDeviceOperator* fsOperator, u32 partition);
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting);
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcEta);
|
||||
bool copyDirectory(const char* source, const char* dest, bool doSplitting);
|
||||
void removeDirectory(const char *path);
|
||||
bool getDirectorySize(const char *path, u64 *out_size);
|
||||
|
|
|
@ -12,8 +12,8 @@ FsDeviceOperator fsOperatorInstance;
|
|||
|
||||
bool gameCardInserted;
|
||||
|
||||
u64 gameCardSize = 0;
|
||||
char gameCardSizeStr[32] = {'\0'};
|
||||
u64 gameCardSize = 0, trimmedCardSize = 0;
|
||||
char gameCardSizeStr[32] = {'\0'}, trimmedCardSizeStr[32] = {'\0'};
|
||||
|
||||
char *hfs0_header = NULL;
|
||||
u64 hfs0_offset = 0, hfs0_size = 0;
|
||||
|
@ -77,6 +77,9 @@ int main(int argc, char **argv)
|
|||
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;
|
||||
|
|
52
source/ui.c
52
source/ui.c
|
@ -19,7 +19,7 @@ extern bool gameCardInserted;
|
|||
extern u32 currentFBWidth, currentFBHeight;
|
||||
extern u8 *currentFB;
|
||||
|
||||
extern char gameCardSizeStr[32];
|
||||
extern char gameCardSizeStr[32], trimmedCardSizeStr[32];
|
||||
|
||||
extern char *hfs0_header;
|
||||
extern u64 hfs0_offset, hfs0_size;
|
||||
|
@ -28,7 +28,7 @@ extern u32 hfs0_partition_cnt;
|
|||
extern u64 gameCardTitleID;
|
||||
extern char gameCardName[0x201], gameCardAuthor[0x101], gameCardVersion[0x11];
|
||||
|
||||
static bool isFat32 = true, dumpCert = false, addPadding = false;
|
||||
static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true;
|
||||
|
||||
static u32 selectedOption;
|
||||
|
||||
|
@ -60,7 +60,7 @@ static const char *appHeadline = "Nintendo Switch Game Card Dump Tool v" APP_VER
|
|||
static const char *appControls = "[D-Pad / Analog Stick] Move | [A] Select | [B] Back | [+] Exit";
|
||||
|
||||
static const char *mainMenuItems[] = { "Full XCI Dump", "Raw Partition Dump", "Partition Data Dump", "View Game Card Files", "Dump Game Card Certificate" };
|
||||
static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Dump certificate: ", "Pad output dump to match full game card size: " };
|
||||
static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Dump certificate: ", "Trim output dump: ", "CRC32 checksum calculation + dump verification: " };
|
||||
static const char *partitionDumpType1MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Normal)", "Dump Partition 2 (Secure)" };
|
||||
static const char *partitionDumpType2MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Logo)", "Dump Partition 2 (Normal)", "Dump Partition 3 (Secure)" };
|
||||
static const char *viewGameCardFsType1MenuItems[] = { "View Files from Partition 0 (SysUpdate)", "View Files from Partition 1 (Normal)", "View Files from Partition 2 (Secure)" };
|
||||
|
@ -388,6 +388,10 @@ UIResult uiLoop(u32 keysDown)
|
|||
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
|
||||
breaks++;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Used space: %s", trimmedCardSizeStr);
|
||||
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
|
||||
breaks++;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt));
|
||||
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
|
||||
} else {
|
||||
|
@ -511,7 +515,7 @@ UIResult uiLoop(u32 keysDown)
|
|||
}
|
||||
|
||||
// Print XCI dump menu settings values
|
||||
if (uiState == stateXciDumpMenu && (i == 1 || i == 2 || i == 3))
|
||||
if (uiState == stateXciDumpMenu && i > 0)
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
|
@ -531,8 +535,16 @@ UIResult uiLoop(u32 keysDown)
|
|||
uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0);
|
||||
}
|
||||
break;
|
||||
case 3: // Add padding
|
||||
if (addPadding)
|
||||
case 3: // Trim output dump
|
||||
if (trimDump)
|
||||
{
|
||||
uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0);
|
||||
} else {
|
||||
uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0);
|
||||
}
|
||||
break;
|
||||
case 4: // CRC32 checksum calculation + dump verification
|
||||
if (calcCrc)
|
||||
{
|
||||
uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0);
|
||||
} else {
|
||||
|
@ -572,8 +584,11 @@ UIResult uiLoop(u32 keysDown)
|
|||
case 2: // Dump certificate
|
||||
dumpCert = false;
|
||||
break;
|
||||
case 3: // Add padding
|
||||
addPadding = false;
|
||||
case 3: // Trim output dump
|
||||
trimDump = false;
|
||||
break;
|
||||
case 4: // CRC32 checksum calculation + dump verification
|
||||
calcCrc = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -591,8 +606,11 @@ UIResult uiLoop(u32 keysDown)
|
|||
case 2: // Dump certificate
|
||||
dumpCert = true;
|
||||
break;
|
||||
case 3: // Add padding
|
||||
addPadding = true;
|
||||
case 3: // Trim output dump
|
||||
trimDump = true;
|
||||
break;
|
||||
case 4: // CRC32 checksum calculation + dump verification
|
||||
calcCrc = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -723,11 +741,15 @@ UIResult uiLoop(u32 keysDown)
|
|||
uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (addPadding ? "Yes" : "No"));
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (trimDump ? "Yes" : "No"));
|
||||
uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[4], (calcCrc ? "Yes" : "No"));
|
||||
uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, addPadding);
|
||||
dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc);
|
||||
|
||||
waitForButtonPress();
|
||||
|
||||
|
@ -804,11 +826,7 @@ UIResult uiLoop(u32 keysDown)
|
|||
uiDrawString("Hold B to cancel", 0, breaks * 8, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (copyFile(fileCopyPath, destCopyPath, true))
|
||||
{
|
||||
breaks += 7;
|
||||
uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0);
|
||||
}
|
||||
copyFile(fileCopyPath, destCopyPath, true, true);
|
||||
} else {
|
||||
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0);
|
||||
}
|
||||
|
|
181
source/util.c
181
source/util.c
|
@ -8,7 +8,10 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <switch/services/ns.h>
|
||||
#include <libxml/globals.h>
|
||||
#include <libxml/xpath.h>
|
||||
|
||||
#include "dumper.h"
|
||||
#include "fsext.h"
|
||||
#include "ncmext.h"
|
||||
#include "ui.h"
|
||||
|
@ -16,6 +19,19 @@
|
|||
|
||||
extern int breaks;
|
||||
|
||||
extern u64 gameCardSize;
|
||||
extern u64 gameCardTitleID;
|
||||
extern u32 hfs0_partition_cnt;
|
||||
|
||||
const char *nswReleasesXmlPath = "sdmc:/NSWreleases.xml";
|
||||
const char *nswReleasesRootElement = "releases";
|
||||
const char *nswReleasesChildren = "release";
|
||||
const char *nswReleasesChildrenImageSize = "imagesize";
|
||||
const char *nswReleasesChildrenTitleID = "titleid";
|
||||
const char *nswReleasesChildrenImgCrc = "imgcrc";
|
||||
const char *nswReleasesChildrenReleaseName = "releasename";
|
||||
const char *nswReleasesChildrenCard = "card";
|
||||
|
||||
bool isGameCardInserted(FsDeviceOperator* o)
|
||||
{
|
||||
bool inserted;
|
||||
|
@ -194,9 +210,7 @@ void convertSize(u64 size, char *out, int bufsize)
|
|||
|
||||
void getCurrentTimestamp(char *out, int bufsize)
|
||||
{
|
||||
time_t timer;
|
||||
time(&timer);
|
||||
|
||||
time_t timer = time(NULL);
|
||||
struct tm *timeinfo = localtime(&timer);
|
||||
|
||||
char buffer[32] = {'\0'};
|
||||
|
@ -276,3 +290,164 @@ void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenames
|
|||
// ".." should stay at the top
|
||||
qsort(filenames + 1, (*filenamesCount) - 1, sizeof(char*), &sortAlpha);
|
||||
}
|
||||
|
||||
bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc, u32 cnt, char *releaseName, int bufsize)
|
||||
{
|
||||
xmlChar *key;
|
||||
xmlNodePtr node = cur;
|
||||
|
||||
u8 imageSize = (u8)(gameCardSize / GAMECARD_SIZE_1GiB);
|
||||
u8 card = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? 1 : 2);
|
||||
|
||||
u8 xmlImageSize = 0;
|
||||
u64 xmlTitleID = 0;
|
||||
u32 xmlCrc = 0;
|
||||
u8 xmlCard = 0;
|
||||
char xmlReleaseName[256] = {'\0'};
|
||||
|
||||
bool found = false;
|
||||
char strbuf[512] = {'\0'};
|
||||
|
||||
while (node != NULL)
|
||||
{
|
||||
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImageSize)))
|
||||
{
|
||||
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
xmlImageSize = (u8)atoi((const char*)key);
|
||||
|
||||
xmlFree(key);
|
||||
} else
|
||||
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenTitleID)))
|
||||
{
|
||||
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
xmlTitleID = strtoull((const char*)key, NULL, 16);
|
||||
|
||||
xmlFree(key);
|
||||
} else
|
||||
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImgCrc)))
|
||||
{
|
||||
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
xmlCrc = strtoul((const char*)key, NULL, 16);
|
||||
|
||||
xmlFree(key);
|
||||
} else
|
||||
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenReleaseName)))
|
||||
{
|
||||
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
snprintf(xmlReleaseName, sizeof(xmlReleaseName) / sizeof(xmlReleaseName[0]), "%s", (char*)key);
|
||||
|
||||
xmlFree(key);
|
||||
} else
|
||||
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenCard)))
|
||||
{
|
||||
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
xmlCard = (u8)atoi((const char*)key);
|
||||
|
||||
xmlFree(key);
|
||||
}
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
//snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image Size: %u\nCartridge Title ID: %016lX\nCartridge Image CRC32: %08X\nCartridge Type: %u\n\nXML Image Size: %u\nXML Title ID: %016lX\nXML Image CRC32: %08X\nXML Release Name: %s\nXML Card Type: %u", imageSize, gameCardTitleID, crc, card, xmlImageSize, xmlTitleID, xmlCrc, xmlReleaseName, xmlCard);
|
||||
//uiDrawString(strbuf, 0, 0, 255, 255, 255);
|
||||
|
||||
if (xmlImageSize == imageSize && xmlTitleID == gameCardTitleID && xmlCrc == crc && xmlCard == card)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found matching Scene release: \"%s\" (CRC32: %08X). This is a good dump!", xmlReleaseName, xmlCrc);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
|
||||
|
||||
snprintf(releaseName, bufsize, "%s", xmlReleaseName);
|
||||
|
||||
found = true;
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump doesn't match Scene release: \"%s\"! (CRC32: %08X)", xmlReleaseName, xmlCrc);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
|
||||
}
|
||||
|
||||
breaks++;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath)
|
||||
{
|
||||
xmlXPathContextPtr context = NULL;
|
||||
xmlXPathObjectPtr result = NULL;
|
||||
|
||||
context = xmlXPathNewContext(doc);
|
||||
result = xmlXPathEvalExpression(xpath, context);
|
||||
|
||||
if (xmlXPathNodeSetIsEmpty(result->nodesetval))
|
||||
{
|
||||
xmlXPathFreeObject(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize)
|
||||
{
|
||||
if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return false;
|
||||
|
||||
xmlDocPtr doc = NULL;
|
||||
bool found = false;
|
||||
char strbuf[512] = {'\0'};
|
||||
|
||||
doc = xmlParseFile(nswReleasesXmlPath);
|
||||
if (doc)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, gameCardTitleID);
|
||||
xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf);
|
||||
if (nodeSet)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found %d %s with Title ID \"%016lX\"", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), gameCardTitleID);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
u32 i;
|
||||
for (i = 0; i < nodeSet->nodesetval->nodeNr; i++)
|
||||
{
|
||||
xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode;
|
||||
|
||||
found = parseNSWDBRelease(doc, node, crc, i, releaseName, bufsize);
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
uiDrawString("No matches found in XML document!", 0, breaks * 8, 255, 0, 0);
|
||||
} else {
|
||||
breaks--;
|
||||
}
|
||||
|
||||
xmlXPathFreeObject(nodeSet);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to find records with Title ID \"%016lX\" within the XML document!", gameCardTitleID);
|
||||
uiDrawString(strbuf, 0, 0, 255, 0, 0);
|
||||
}
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open and/or parse \"%s\"!", nswReleasesXmlPath);
|
||||
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
char *RemoveIllegalCharacters(char *name)
|
||||
{
|
||||
u32 i, len = strlen(name);
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
if (memchr("?[]/\\=+<>:;\",*|^", name[i], sizeof("?[]/\\=+<>:;\",*|^") - 1) || name[i] < 0x20 || name[i] > 0x7E) name[i] = '_';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include <switch.h>
|
||||
|
||||
#define APP_VERSION "1.0.3"
|
||||
#define APP_VERSION "1.0.4"
|
||||
#define NAME_BUF_LEN 4096
|
||||
|
||||
bool isGameCardInserted(FsDeviceOperator* o);
|
||||
|
@ -32,4 +32,8 @@ void addString(char **filenames, int *filenamesCount, char **nextFilename, const
|
|||
|
||||
void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenamesCount, const char *directory, bool skipParent);
|
||||
|
||||
bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize);
|
||||
|
||||
char *RemoveIllegalCharacters(char *name);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue