1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

Update to v1.0.3.

This commit is contained in:
Pablo Curiel 2018-06-23 20:48:34 -04:00
parent db3f5698e7
commit 730912a626
8 changed files with 388 additions and 221 deletions

View file

@ -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

View file

@ -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.

52
source/crc32_fast.c Normal file
View file

@ -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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#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;
}

10
source/crc32_fast.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#ifndef __CRC32_FAST_H__
#define __CRC32_FAST_H__
#include <switch/types.h>
void crc32(const void* data, u64 n_bytes, u32* crc);
#endif

View file

@ -6,6 +6,7 @@
#include <sys/stat.h>
#include <unistd.h>
#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)

View file

@ -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);

View file

@ -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();

View file

@ -5,7 +5,7 @@
#include <switch.h>
#define APP_VERSION "1.0.2"
#define APP_VERSION "1.0.3"
#define NAME_BUF_LEN 4096
bool isGameCardInserted(FsDeviceOperator* o);