From 730912a626a329c24e5e70be083725fd6564c251 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 23 Jun 2018 20:48:34 -0400 Subject: [PATCH] Update to v1.0.3. --- Makefile | 2 +- README.md | 9 +- source/crc32_fast.c | 52 ++++++++ source/crc32_fast.h | 10 ++ source/dumper.c | 243 +++++++++++++++++++------------------ source/dumper.h | 2 +- source/ui.c | 289 +++++++++++++++++++++++++++++--------------- source/util.h | 2 +- 8 files changed, 388 insertions(+), 221 deletions(-) create mode 100644 source/crc32_fast.c create mode 100644 source/crc32_fast.h diff --git a/Makefile b/Makefile index 6097965..f9e9190 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules VERSION_MAJOR := 1 VERSION_MINOR := 0 -VERSION_MICRO := 2 +VERSION_MICRO := 3 APP_TITLE := gcdumptool APP_AUTHOR := MCMrARM, DarkMatterCore diff --git a/README.md b/README.md index b277994..b62f2df 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Nintendo Switch Game Card Dump Tool Main features -------------- -* Generates full XCI cartridge dumps (with optional certificate removal). All dumps are padded with 0xFF to match the full game card size. +* Generates XCI cartridge dumps (with optional certificate removal and optional 0xFF padding to match the full game card size). +* CRC32 checksum calculation for XCI dumps. * 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). @@ -20,11 +21,17 @@ Thanks to * RSDuck, for their vba-next-switch port. It's UI menu code was taken as a basis for this application. * Foen, for giving me some pretty good hints about how to use the NCM service. * Yellows8, for helping me fix a silly bug in my implementation of some NCM service IPC calls. +* Björn Samuelsson, for his public domain CRC32 checksum calculation for C (crc32_fast.c). * The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem. Changelog -------------- +**v1.0.3:** + +* Made the 0xFF padding feature a configurable option. +* Added CRC32 checksum calculation for XCI dumps. + **v1.0.2:** * Fixed a silly bug in the file splitting code. diff --git a/source/crc32_fast.c b/source/crc32_fast.c new file mode 100644 index 0000000..8f25ec2 --- /dev/null +++ b/source/crc32_fast.c @@ -0,0 +1,52 @@ +/* Standard CRC32 checksum: fast public domain implementation for + * little-endian architectures. Written for compilation with an + * optimizer set to perform loop unwinding. Outputs the checksum for + * each file given as a command line argument. Invalid file names and + * files that cause errors are silently skipped. The program reads + * from stdin if it is called with no arguments. */ + +#include +#include +#include + +#include "crc32_fast.h" + +u32 crc32_for_byte(u32 r) +{ + for(int j = 0; j < 8; ++j) r = (r & 1? 0: (u32)0xEDB88320L) ^ r >> 1; + return r ^ (u32)0xFF000000L; +} + +/* Any unsigned integer type with at least 32 bits may be used as + * accumulator type for fast crc32-calulation, but unsigned long is + * probably the optimal choice for most systems. */ +typedef unsigned long accum_t; + +void init_tables(u32* table, u32* wtable) +{ + for(u64 i = 0; i < 0x100; ++i) table[i] = crc32_for_byte(i); + + for(u64 k = 0; k < sizeof(accum_t); ++k) + { + for(u64 w, i = 0; i < 0x100; ++i) + { + for(u64 j = w = 0; j < sizeof(accum_t); ++j) w = table[(u8)(j == k? w ^ i: w)] ^ w >> 8; + wtable[(k << 8) + i] = w ^ (k? wtable[0]: 0); + } + } +} + +void crc32(const void* data, u64 n_bytes, u32* crc) +{ + static u32 table[0x100], wtable[0x100*sizeof(accum_t)]; + u64 n_accum = n_bytes / sizeof(accum_t); + + if (!*table) init_tables(table, wtable); + for(u64 i = 0; i < n_accum; ++i) + { + accum_t a = *crc ^ ((accum_t*)data)[i]; + for(u64 j = *crc = 0; j < sizeof(accum_t); ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8*j)]; + } + + for(u64 i = n_accum*sizeof(accum_t); i < n_bytes; ++i) *crc = table[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8; +} diff --git a/source/crc32_fast.h b/source/crc32_fast.h new file mode 100644 index 0000000..3e99b94 --- /dev/null +++ b/source/crc32_fast.h @@ -0,0 +1,10 @@ +#pragma once + +#ifndef __CRC32_FAST_H__ +#define __CRC32_FAST_H__ + +#include + +void crc32(const void* data, u64 n_bytes, u32* crc); + +#endif \ No newline at end of file diff --git a/source/dumper.c b/source/dumper.c index f4a5c65..24f3e84 100644 --- a/source/dumper.c +++ b/source/dumper.c @@ -6,6 +6,7 @@ #include #include +#include "crc32_fast.h" #include "dumper.h" #include "fsext.h" #include "ui.h" @@ -222,11 +223,11 @@ bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size) return true; } -bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert) +bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool addPadding) { - u64 partitionOffset = 0, fileOffset = 0, totalSize = 0, paddingSize = 0, n; + u64 partitionOffset = 0, fileOffset = 0, xciDataSize = 0, totalSize = 0, paddingSize = 0, n; u64 partitionSizes[ISTORAGE_PARTITION_CNT]; - char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, totalSizeStr[32] = {'\0'}, curSizeStr[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'}, paddingSizeStr[32] = {'\0'}, filename[128] = {'\0'}, timeStamp[16] = {'\0'}; u32 handle, partition; Result result; FsStorage gameCardStorage; @@ -235,6 +236,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert char *buf = NULL; u8 splitIndex = 0; int progress = 0; + u32 crc = 0; for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) { @@ -258,7 +260,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert if (R_SUCCEEDED(result = fsStorageGetSize(&gameCardStorage, &(partitionSizes[partition])))) { - totalSize += 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 * 8, 255, 255, 255); @@ -286,24 +288,37 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert if (proceed) { - convertSize(totalSize, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI data size: %s (%lu bytes)", totalSizeStr, totalSize); + 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; - paddingSize = (gameCardSize - totalSize); - convertSize(paddingSize, paddingSizeStr, sizeof(paddingSizeStr) / sizeof(paddingSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "0xFF padding size: %s (%lu bytes)", paddingSizeStr, paddingSize); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); + if (addPadding) + { + totalSize = gameCardSize; + snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", gameCardSizeStr); + + paddingSize = (totalSize - xciDataSize); + convertSize(paddingSize, paddingSizeStr, sizeof(paddingSizeStr) / sizeof(paddingSizeStr[0])); + + 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); + } else { + totalSize = xciDataSize; + snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr); + } + + convertSize(totalSize, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); + breaks++; - if (gameCardSize <= freeSpace) + if (totalSize <= freeSpace) { breaks++; getCurrentTimestamp(timeStamp, sizeof(timeStamp) / sizeof(timeStamp[0])); - if (gameCardSize > SPLIT_FILE_MIN && isFat32) + if (totalSize > SPLIT_FILE_MIN && isFat32) { snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, splitIndex); } else { @@ -316,7 +331,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert 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.", (int)((gameCardSize > SPLIT_FILE_MIN && isFat32) ? (strlen(filename) - 3) : strlen(filename)), filename); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Writing output to \"%.*s\". Hold B to cancel.", (int)((totalSize > SPLIT_FILE_MIN && isFat32) ? (strlen(filename) - 3) : strlen(filename)), filename); uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); breaks += 2; @@ -348,7 +363,13 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert break; } - if (gameCardSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < gameCardSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + // Remove game card certificate + if (fileOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE); + + // 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); @@ -398,20 +419,20 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } } - progress = (int)(((fileOffset + n) * 100) / gameCardSize); + 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)) / gameCardSize), 16, 0, 255, 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, gameCardSizeStr); + 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) < gameCardSize && ((fileOffset / DUMP_BUFFER_SIZE) % 10) == 0) + if ((fileOffset + n) < totalSize && ((fileOffset / DUMP_BUFFER_SIZE) % 10) == 0) { hidScanInput(); @@ -425,16 +446,16 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } } - if (fileOffset >= totalSize) success = true; + if (fileOffset >= xciDataSize) 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)) / gameCardSize), 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, gameCardSizeStr); + 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); @@ -444,7 +465,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert if (!proceed) { - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / gameCardSize), 16, 255, 0, 0); + uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / totalSize), 16, 255, 0, 0); breaks += 7; } @@ -472,27 +493,66 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert if (success) { - // 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 (addPadding) { - if (DUMP_BUFFER_SIZE > (paddingSize - partitionOffset)) n = (paddingSize - partitionOffset); + // Add file padding + memset(buf, 0xFF, DUMP_BUFFER_SIZE); - if (gameCardSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < gameCardSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + 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) { - u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); - u64 old_file_chunk_size = (n - new_file_chunk_size); + if (DUMP_BUFFER_SIZE > (paddingSize - partitionOffset)) n = (paddingSize - partitionOffset); - if (old_file_chunk_size > 0) + // Update CRC32 + crc32(buf, n, &crc); + + if (totalSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + 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); @@ -501,103 +561,49 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } } - fclose(outFile); + progress = (int)(((fileOffset + n) * 100) / totalSize); - splitIndex++; - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, splitIndex); + 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); - outFile = fopen(filename, "wb"); - if (!outFile) + 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) { - 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) + hidScanInput(); + + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown & KEY_B) { - 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); + uiDrawString("Process canceled", 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) / gameCardSize); - - uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0); - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / gameCardSize), 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, gameCardSizeStr); - - 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) < gameCardSize && ((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; - } } } - fclose(outFile); + if (outFile) fclose(outFile); breaks += 7; if (proceed) { - if (!dumpCert) - { - if (gameCardSize > SPLIT_FILE_MIN && isFat32) - { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, 0); - } else { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci", gameCardTitleID, timeStamp); - } - - outFile = fopen(filename, "rb+"); - if (!outFile) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to remove game card certificate from finished dump!"); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - - if (proceed) - { - fseek(outFile, CERT_OFFSET, SEEK_SET); - fwrite(buf, 1, CERT_SIZE, outFile); - fclose(outFile); - } - } + uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0); + breaks++; - if (proceed) uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0); + 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 (gameCardSize > SPLIT_FILE_MIN && isFat32) + if (totalSize > SPLIT_FILE_MIN && isFat32) { for(u8 i = 0; i <= splitIndex; i++) { @@ -613,7 +619,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } else { if (outFile) fclose(outFile); - if (gameCardSize > SPLIT_FILE_MIN && isFat32) + if (totalSize > SPLIT_FILE_MIN && isFat32) { for(u8 i = 0; i <= splitIndex; i++) { @@ -841,7 +847,8 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0); } - fclose(outFile); + if (outFile) fclose(outFile); + if (!success) { if (size > SPLIT_FILE_MIN && doSplitting) diff --git a/source/dumper.h b/source/dumper.h index 6cb5533..8d460cf 100644 --- a/source/dumper.h +++ b/source/dumper.h @@ -48,7 +48,7 @@ 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 dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool addPadding); 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); diff --git a/source/ui.c b/source/ui.c index 6606638..6d275e2 100644 --- a/source/ui.c +++ b/source/ui.c @@ -28,6 +28,8 @@ 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 u32 selectedOption; static char statusMessage[2048] = {'\0'}; @@ -57,8 +59,8 @@ static UIState uiState; static const char *appHeadline = "Nintendo Switch Game Card Dump Tool v" APP_VERSION ".\nOriginal code by MCMrARM.\nAdditional modifications by DarkMatterCore.\n\n"; static const char *appControls = "[D-Pad / Analog Stick] Move | [A] Select | [B] Back | [+] Exit"; -static const char *mainMenuItems[] = { "Raw XCI Dump (Full)", "Raw Partition Dump", "Partition Data Dump", "View Game Card Files", "Dump Game Card Certificate" }; -static const char *xciDumpMenuItems[] = { "Full Dump with Certificate (exFAT)", "Full Dump without Certificate (exFAT)", "Full Dump with Certificate (FAT32 - 2 GiB chunks)", "Full Dump without Certificate (FAT32 - 2 GiB chunks)" }; +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 *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)" }; @@ -468,8 +470,8 @@ UIResult uiLoop(u32 keysDown) int scrollAmount = 0; if ((keysDown & KEY_DOWN) || (keysDown & KEY_RSTICK_DOWN) || (keysDown & KEY_LSTICK_DOWN)) scrollAmount = 1; if ((keysDown & KEY_UP) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_LSTICK_UP)) scrollAmount = -1; - if ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT)) scrollAmount = -5; - if ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT)) scrollAmount = 5; + if (uiState != stateXciDumpMenu && ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT))) scrollAmount = -5; + if (uiState != stateXciDumpMenu && ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT))) scrollAmount = 5; if (scrollAmount > 0) { @@ -503,140 +505,229 @@ UIResult uiLoop(u32 keysDown) if (i + scroll == cursor) { uiFill(0, (breaks * 8) + (i * 13), currentFBWidth / 2, 13, 33, 34, 39); - uiDrawString(menu[j], 0, (breaks * 8) + (i * 13) + 2, 0, 255, 197); + uiDrawString(menu[j], 0, (breaks * 8) + (i * 13) + 2, 0, 255, 197); } else { uiDrawString(menu[j], 0, (breaks * 8) + (i * 13) + 2, color, color, color); } + // Print XCI dump menu settings values + if (uiState == stateXciDumpMenu && (i == 1 || i == 2 || i == 3)) + { + switch(i) + { + case 1: // Split output dump (FAT32 support) + if (isFat32) + { + 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 2: // Dump certificate + if (dumpCert) + { + 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 3: // Add padding + if (addPadding) + { + 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; + default: + break; + } + } + i++; if (i >= maxListElements) break; } - // Select - if (keysDown & KEY_A) + if (uiState == stateXciDumpMenu) { - if (uiState == stateMainMenu) + // Select + if ((keysDown & KEY_A) && cursor == 0) + { + selectedOption = (u32)cursor; + res = resultDumpXci; + } + + // Back + if (keysDown & KEY_B) res = resultShowMainMenu; + + // Change option to false + if ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT)) { switch(cursor) { - case 0: - res = resultShowXciDumpMenu; + case 1: // Split output dump (FAT32 support) + isFat32 = false; break; - case 1: - res = resultShowRawPartitionDumpMenu; + case 2: // Dump certificate + dumpCert = false; break; - case 2: - res = resultShowPartitionDataDumpMenu; - break; - case 3: - res = resultShowViewGameCardFsMenu; - break; - case 4: - res = resultDumpGameCardCertificate; + case 3: // Add padding + addPadding = false; break; default: break; } - } else - if (uiState == stateXciDumpMenu) - { - selectedOption = (u32)cursor; - res = resultDumpXci; - } else - if (uiState == stateRawPartitionDumpMenu) - { - selectedOption = (u32)cursor; - res = resultDumpRawPartition; - } else - if (uiState == statePartitionDataDumpMenu) - { - selectedOption = (u32)cursor; - res = resultDumpPartitionData; - } else - if (uiState == stateViewGameCardFsMenu) - { - selectedOption = (u32)cursor; - res = resultShowViewGameCardFsGetList; - } else - if (uiState == stateViewGameCardFsBrowser) - { - char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); - memset(selectedPath, 0, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); - - if (strlen(filenames[cursor]) == 2 && !strcmp(filenames[cursor], "..")) - { - for(i = (strlen(currentDirectory) - 1); i >= 0; i--) - { - if (currentDirectory[i] == '/') - { - strncpy(selectedPath, currentDirectory, i); - selectedPath[i] = '\0'; - break; - } - } - } else { - snprintf(selectedPath, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2, "%s/%s", currentDirectory, filenames[cursor]); - } - - if (isDirectory(selectedPath)) - { - enterDirectory(selectedPath); - } else { - snprintf(fileCopyPath, sizeof(fileCopyPath) / sizeof(fileCopyPath[0]), "%s", selectedPath); - res = resultViewGameCardFsBrowserCopyFile; - } - - free(selectedPath); } - } - - // Back - if (keysDown & KEY_B) - { - if (uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu) + + // Change option to true + if ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT)) { - res = resultShowMainMenu; - } else - if (uiState == stateViewGameCardFsBrowser) - { - if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6) + switch(cursor) { - fsdevUnmountDevice("view"); - - res = resultShowViewGameCardFsMenu; - } else { - char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1); - memset(selectedPath, 0, strlen(currentDirectory) + 1); - - for(i = (strlen(currentDirectory) - 1); i >= 0; i--) + case 1: // Split output dump (FAT32 support) + isFat32 = true; + break; + case 2: // Dump certificate + dumpCert = true; + break; + case 3: // Add padding + addPadding = true; + break; + default: + break; + } + } + } else { + // Select + if (keysDown & KEY_A) + { + if (uiState == stateMainMenu) + { + switch(cursor) { - if (currentDirectory[i] == '/') - { - strncpy(selectedPath, currentDirectory, i); - selectedPath[i] = '\0'; + case 0: + res = resultShowXciDumpMenu; break; + case 1: + res = resultShowRawPartitionDumpMenu; + break; + case 2: + res = resultShowPartitionDataDumpMenu; + break; + case 3: + res = resultShowViewGameCardFsMenu; + break; + case 4: + res = resultDumpGameCardCertificate; + break; + default: + break; + } + } else + if (uiState == stateRawPartitionDumpMenu) + { + selectedOption = (u32)cursor; + res = resultDumpRawPartition; + } else + if (uiState == statePartitionDataDumpMenu) + { + selectedOption = (u32)cursor; + res = resultDumpPartitionData; + } else + if (uiState == stateViewGameCardFsMenu) + { + selectedOption = (u32)cursor; + res = resultShowViewGameCardFsGetList; + } else + if (uiState == stateViewGameCardFsBrowser) + { + char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); + memset(selectedPath, 0, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); + + if (strlen(filenames[cursor]) == 2 && !strcmp(filenames[cursor], "..")) + { + for(i = (strlen(currentDirectory) - 1); i >= 0; i--) + { + if (currentDirectory[i] == '/') + { + strncpy(selectedPath, currentDirectory, i); + selectedPath[i] = '\0'; + break; + } } + } else { + snprintf(selectedPath, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2, "%s/%s", currentDirectory, filenames[cursor]); } - if (isDirectory(selectedPath)) enterDirectory(selectedPath); + if (isDirectory(selectedPath)) + { + enterDirectory(selectedPath); + } else { + snprintf(fileCopyPath, sizeof(fileCopyPath) / sizeof(fileCopyPath[0]), "%s", selectedPath); + res = resultViewGameCardFsBrowserCopyFile; + } free(selectedPath); } } + + // Back + if (keysDown & KEY_B) + { + if (uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu) + { + res = resultShowMainMenu; + } else + if (uiState == stateViewGameCardFsBrowser) + { + if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6) + { + fsdevUnmountDevice("view"); + + res = resultShowViewGameCardFsMenu; + } else { + char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1); + memset(selectedPath, 0, strlen(currentDirectory) + 1); + + for(i = (strlen(currentDirectory) - 1); i >= 0; i--) + { + if (currentDirectory[i] == '/') + { + strncpy(selectedPath, currentDirectory, i); + selectedPath[i] = '\0'; + break; + } + } + + if (isDirectory(selectedPath)) enterDirectory(selectedPath); + + free(selectedPath); + } + } + } } } } } else if (uiState == stateDumpXci) { - uiDrawString(xciDumpMenuItems[selectedOption], 0, breaks * 8, 115, 115, 255); + uiDrawString(mainMenuItems[0], 0, breaks * 8, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[1], (isFat32 ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[2], (dumpCert ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (addPadding ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); breaks += 2; - bool isFat32 = (selectedOption == 2 || selectedOption == 3); - bool dumpCert = (selectedOption == 0 || selectedOption == 2); - - dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert); + dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, addPadding); waitForButtonPress(); diff --git a/source/util.h b/source/util.h index ab55dcc..2d3dbe0 100644 --- a/source/util.h +++ b/source/util.h @@ -5,7 +5,7 @@ #include -#define APP_VERSION "1.0.2" +#define APP_VERSION "1.0.3" #define NAME_BUF_LEN 4096 bool isGameCardInserted(FsDeviceOperator* o);