mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 00:52:10 +00:00
Update to v1.0.3.
This commit is contained in:
parent
db3f5698e7
commit
730912a626
8 changed files with 388 additions and 221 deletions
2
Makefile
2
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
|
||||
|
|
|
@ -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
52
source/crc32_fast.c
Normal 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
10
source/crc32_fast.h
Normal 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
|
243
source/dumper.c
243
source/dumper.c
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
289
source/ui.c
289
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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue