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

This commit is contained in:
Pablo Curiel 2018-06-30 02:37:42 -04:00
parent 46c0f9fe47
commit fae387a182
9 changed files with 879 additions and 167 deletions

View file

@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules
VERSION_MAJOR := 1
VERSION_MINOR := 0
VERSION_MICRO := 4
VERSION_MICRO := 5
APP_TITLE := gcdumptool
APP_AUTHOR := MCMrARM, DarkMatterCore
@ -55,14 +55,17 @@ ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__
CFLAGS += $(INCLUDE) -D__SWITCH__ -D__LINUX_ERRNO_EXTENSIONS__
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags`
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags`
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx -lxml2 -lm
LIBS := -lcurl -lz -lnx -ljson-c -lxml2 -lm
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing

View file

@ -7,7 +7,7 @@ Main features
* Generates XCI cartridge dumps (with optional certificate removal and optional trimming).
* CRC32 checksum calculation for XCI dumps.
* Full XCI dump verification using XML database from nswdb.com (NSWreleases.xml).
* XCI dump renaming based on the XML database from nswdb.com (NSWreleases.xml).
* XML database update via libcurl.
* 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).
@ -24,12 +24,28 @@ 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).
* Björn Samuelsson, for his public domain CRC32 checksum calculation code for C (crc32_fast.c).
* AnalogMan, for his constant support and ideas.
* The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem.
Changelog
--------------
**v1.0.5:**
* Fixed game card version reading (now using the ncm service instead of retrieving it from the cached Control.nacp).
* Added ability to read and identify FW update versions bundled with game cards.
* In case an error occurs while reading the game card Title ID, the application will also display the FW version update bundled with it along with an explanation.
* Removed output XCI dump renaming based on the XML database from nswdb.com.
* Output naming scheme changed. Characters out of the ASCII range are replaced with underscores:
- XCI dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]).xci".
- Raw partition dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Partition [PartitionIndex] ([PartitionName]).hfs0".
- Partition data dump (directory): "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Partition [PartitionIndex] ([PartitionName])/".
- Certificate dump: "sdmc:/[GameName] v[GameVersion] ([TitleID]) - Certificate ([CRC32]).bin".
* Manual file dumps will now be saved to their corresponding directory instead of the SD card root.
* Added a XML database update option in the main menu.
* Added an update application option in the main menu. It isn't working at this moment because libcurl has problems dealing with secure connections, and as such the option has been disabled (pressing A on it does nothing). Nonetheless, the code to parse JSON responses from the GitHub API is pretty much ready, so it's just a matter of time.
**v1.0.4:**
* exFAT mode turned on by default.

View file

@ -26,9 +26,18 @@ extern char *hfs0_header;
extern u64 hfs0_offset, hfs0_size;
extern u32 hfs0_partition_cnt;
extern u64 gameCardTitleID;
//extern char *partitionHfs0Header;
//extern u64 partitionHfs0HeaderSize;
static char strbuf[512] = {'\0'};
extern u64 gameCardTitleID;
extern u32 gameCardVersion;
extern char fixedGameCardName[0x201];
extern u64 gameCardUpdateTitleID;
extern u32 gameCardUpdateVersion;
extern char gameCardUpdateVersionStr[128];
static char strbuf[NAME_BUF_LEN * 2] = {'\0'};
void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator)
{
@ -57,6 +66,71 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
return false;
}
// Get bundled FW version update
if (R_SUCCEEDED(fsDeviceOperatorUpdatePartitionInfo(fsOperator, handle, &gameCardUpdateVersion, &gameCardUpdateTitleID)))
{
if (gameCardUpdateTitleID == GAMECARD_UPDATE_TITLEID)
{
char decimalVersion[64] = {'\0'};
convertTitleVersionToDecimal(gameCardUpdateVersion, decimalVersion, sizeof(decimalVersion));
switch(gameCardUpdateVersion)
{
case SYSUPDATE_100:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "1.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_200:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_210:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_220:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.2.0 - v%s", decimalVersion);
break;
case SYSUPDATE_230:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.3.0 - v%s", decimalVersion);
break;
case SYSUPDATE_300:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_301:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_302:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.2 - v%s", decimalVersion);
break;
case SYSUPDATE_400:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_401:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_410:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.1.0 - v%s", decimalVersion);
break;
case SYSUPDATE_500:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.0 - v%s", decimalVersion);
break;
case SYSUPDATE_501:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.1 - v%s", decimalVersion);
break;
case SYSUPDATE_502:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.2 - v%s", decimalVersion);
break;
case SYSUPDATE_510:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.1.0 - v%s", decimalVersion);
break;
default:
snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "UNKNOWN - v%s", decimalVersion);
break;
}
} else {
gameCardUpdateTitleID = 0;
gameCardUpdateVersion = 0;
}
}
if (R_FAILED(result = fsOpenGameCardStorage(&gameCardStorage, handle, 0)))
{
uiStatusMsg("getRootHfs0Header: OpenGameCardStorage failed! (0x%08X)", result);
@ -237,11 +311,120 @@ bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size)
return true;
}
/*bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition)
{
if (hfs0_header == NULL) return false;
if (partitionHfs0Header != NULL)
{
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderSize = 0;
}
char *buf = NULL;
Result result;
FsStorage gameCardStorage;
u64 partitionSize = 0, partitionOffset = 0, roundedHfs0HeaderSize = 0;
u32 handle, hfs0FileCount = 0, hfs0StrTableSize = 0;
bool success = false;
if (getHsf0PartitionDetails(partition, &partitionOffset, &partitionSize))
{
workaroundPartitionZeroAccess(fsOperator);
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
// Same ugly hack from dumpRawPartition()
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, handle, (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (partition < 2 ? 0 : 1) : (partition < 3 ? 0 : 1)))))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
buf = (char*)malloc(MEDIA_UNIT_SIZE);
if (buf)
{
// First read MEDIA_UNIT_SIZE bytes
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionOffset, buf, MEDIA_UNIT_SIZE)))
{
memcpy(&hfs0FileCount, buf + 4, sizeof(u32));
memcpy(&hfs0StrTableSize, buf + 4, sizeof(u32));
partitionHfs0HeaderSize = (0x10 + (0x40 * hfs0FileCount) + hfs0StrTableSize);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, hfs0FileCount);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, hfs0StrTableSize);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
breaks++;
memset(buf, 0, DUMP_BUFFER_SIZE);
roundedHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE);
partitionHfs0Header = (char*)malloc(roundedHfs0HeaderSize);
if (partitionHfs0Header)
{
// Then read the whole HFS0 header
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionOffset, partitionHfs0Header, roundedHfs0HeaderSize)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
success = true;
} else {
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderSize = 0;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
}
} else {
partitionHfs0HeaderSize = 0;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset);
uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0);
}
free(buf);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
fsStorageClose(&gameCardStorage);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
}
breaks += 2;
return success;
}*/
bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc)
{
u64 partitionOffset = 0, fileOffset = 0, xciDataSize = 0, totalSize = 0, n;
u64 partitionSizes[ISTORAGE_PARTITION_CNT];
char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[256] = {'\0'}, timeStamp[16] = {'\0'};
char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'};
u32 handle, partition;
Result result;
FsStorage gameCardStorage;
@ -335,13 +518,11 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
{
breaks++;
getCurrentTimestamp(timeStamp, sizeof(timeStamp) / sizeof(timeStamp[0]));
if (totalSize > SPLIT_FILE_MIN && isFat32)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, splitIndex);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex);
} else {
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci", gameCardTitleID, timeStamp);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci", fixedGameCardName, gameCardVersion, gameCardTitleID);
}
outFile = fopen(filename, "wb");
@ -446,7 +627,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
fclose(outFile);
splitIndex++;
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, splitIndex);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex);
outFile = fopen(filename, "wb");
if (!outFile)
@ -596,26 +777,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
uiDrawString("Starting verification process using XML database from nswdb.com...", 0, breaks * 8, 255, 255, 255);
breaks++;
char releaseName[256] = {'\0'}, newFilename[256] = {'\0'};
if (gameCardDumpNSWDBCheck(crc2, releaseName, sizeof(releaseName) / sizeof(releaseName[0])))
{
if (totalSize > SPLIT_FILE_MIN && isFat32)
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, i);
snprintf(newFilename, sizeof(newFilename) / sizeof(newFilename[0]), "sdmc:/%s.xci.%02u", RemoveIllegalCharacters(releaseName), i);
rename(filename, newFilename);
}
} else {
snprintf(newFilename, sizeof(newFilename) / sizeof(newFilename[0]), "sdmc:/%s.xci", RemoveIllegalCharacters(releaseName));
rename(filename, newFilename);
}
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output XCI dump renamed to \"%.*s\"", (int)((totalSize > SPLIT_FILE_MIN && isFat32) ? (strlen(newFilename) - 3) : strlen(newFilename)), newFilename);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
}
gameCardDumpNSWDBCheck(crc2);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1);
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
@ -631,7 +793,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_%s.xci.%02u", gameCardTitleID, timeStamp, i);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, i);
remove(filename);
}
} else {
@ -661,7 +823,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
char *buf;
u64 off, n = DUMP_BUFFER_SIZE;
FsStorage gameCardStorage;
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[256] = {'\0'}, timeStamp[16] = {'\0'};
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'};
u8 progress = 0;
FILE *outFile = NULL;
u8 splitIndex = 0;
@ -679,26 +841,6 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
if (partition == 0)
{
u32 title_ver;
u64 title_id;
if (R_SUCCEEDED(fsDeviceOperatorUpdatePartitionInfo(fsOperator, handle, &title_ver, &title_id)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "System Title Version: v%u", title_ver);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "System Title ID: %016lX", title_id);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
} else {
uiDrawString("Unable to get the system title-version from partition #0. Not a big problem, though", 0, breaks * 8, 255, 0, 0);
}
breaks++;
}
// Ugly hack
// The IStorage instance returned for partition == 0 contains the gamecard header, the gamecard certificate, the root HFS0 header and:
// * The "update" (0) partition and the "normal" (1) partition (for gamecard type 0x01)
@ -727,13 +869,11 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
if (size <= freeSpace)
{
getCurrentTimestamp(timeStamp, sizeof(timeStamp) / sizeof(timeStamp[0]));
if (size > SPLIT_FILE_MIN && doSplitting)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_part%u_%s.hfs0.%02u", gameCardTitleID, partition, timeStamp, splitIndex);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex);
} else {
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_part%u_%s.hfs0", gameCardTitleID, partition, timeStamp);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
}
outFile = fopen(filename, "wb");
@ -779,7 +919,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
fclose(outFile);
splitIndex++;
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_part%u_%s.hfs0.%02u", gameCardTitleID, partition, timeStamp, splitIndex);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex);
outFile = fopen(filename, "wb");
if (!outFile)
@ -887,7 +1027,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
{
for(u8 i = 0; i <= splitIndex; i++)
{
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_part%u_%s.hfs0.%02u", gameCardTitleID, partition, timeStamp, i);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), i);
remove(filename);
}
} else {
@ -1340,9 +1480,9 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
int ret;
bool success = false;
u64 total_size;
char dumpPath[128] = {'\0'}, timeStamp[16] = {'\0'}, totalSizeStr[32] = {'\0'};
char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'};
if (partition == 0 || partition == 1) workaroundPartitionZeroAccess(fsOperator);
workaroundPartitionZeroAccess(fsOperator);
if (openPartitionFs(&fs, fsOperator, partition))
{
@ -1362,10 +1502,9 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
if (total_size <= freeSpace)
{
getCurrentTimestamp(timeStamp, sizeof(timeStamp) / sizeof(timeStamp[0]));
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%016lX_part%u_%s", gameCardTitleID, partition, timeStamp);
snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying partition #%u data to \"%s\". Hold B to cancel.", partition, dumpPath);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying partition #%u data to \"%s/\". Hold B to cancel.", partition, dumpPath);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
@ -1406,7 +1545,7 @@ bool mountViewPartition(FsDeviceOperator *fsOperator, u32 partition)
int ret;
bool success = false;
if (partition == 0 || partition == 1) workaroundPartitionZeroAccess(fsOperator);
workaroundPartitionZeroAccess(fsOperator);
if (openPartitionFs(&fs, fsOperator, partition))
{
@ -1432,12 +1571,12 @@ bool mountViewPartition(FsDeviceOperator *fsOperator, u32 partition)
bool dumpGameCertificate(FsDeviceOperator* fsOperator)
{
u32 handle;
u32 handle, crc;
Result result;
FsStorage gameCardStorage;
bool success = false;
FILE *outFile = NULL;
char timeStamp[16] = {'\0'}, filename[256] = {'\0'};
char filename[NAME_BUF_LEN] = {'\0'};
char *buf = NULL;
workaroundPartitionZeroAccess(fsOperator);
@ -1456,46 +1595,47 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator)
if (CERT_SIZE <= freeSpace)
{
getCurrentTimestamp(timeStamp, sizeof(timeStamp) / sizeof(timeStamp[0]));
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%016lX_cert_%s.bin", gameCardTitleID, timeStamp);
outFile = fopen(filename, "wb");
if (outFile)
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to file \"%s\"...", filename);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
buf = (char*)malloc(DUMP_BUFFER_SIZE);
if (buf)
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, CERT_OFFSET, buf, CERT_SIZE)))
{
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, CERT_OFFSET, buf, CERT_SIZE)))
// Calculate CRC32
crc32(buf, CERT_SIZE, &crc);
snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Certificate (%08X).bin", fixedGameCardName, gameCardVersion, gameCardTitleID, crc);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to file \"%s\"...", filename);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
outFile = fopen(filename, "wb");
if (outFile)
{
if (fwrite(buf, 1, CERT_SIZE, outFile) == CERT_SIZE)
{
success = true;
uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write certificate data");
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
uiDrawString("Failed to write certificate data!", 0, breaks * 8, 255, 0, 0);
}
fclose(outFile);
if (!success) remove(filename);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
free(buf);
} else {
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
fclose(outFile);
if (!success) remove(filename);
free(buf);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0);
}
} else {
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0);

View file

@ -28,6 +28,9 @@
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "update" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "update" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))
#define GAMECARD_SIZE_1GiB (u64)0x40000000
#define GAMECARD_SIZE_2GiB (u64)0x80000000
@ -36,7 +39,26 @@
#define GAMECARD_SIZE_16GiB (u64)0x400000000
#define GAMECARD_SIZE_32GiB (u64)0x800000000
#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816
#define SYSUPDATE_100 (u32)450
#define SYSUPDATE_200 (u32)65796
#define SYSUPDATE_210 (u32)131162
#define SYSUPDATE_220 (u32)196628
#define SYSUPDATE_230 (u32)262164
#define SYSUPDATE_300 (u32)201327002
#define SYSUPDATE_301 (u32)201392178
#define SYSUPDATE_302 (u32)201457684
#define SYSUPDATE_400 (u32)268435656
#define SYSUPDATE_401 (u32)268501002
#define SYSUPDATE_410 (u32)269484082
#define SYSUPDATE_500 (u32)335544750
#define SYSUPDATE_501 (u32)335609886
#define SYSUPDATE_502 (u32)335675432
#define SYSUPDATE_510 (u32)336592976
#define bswap_32(a) ((((a) << 24) & 0xff000000) | (((a) << 8) & 0xff0000) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))
#define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary
#define SMOOTHING_FACTOR (double)0.05

View file

@ -19,8 +19,16 @@ char *hfs0_header = NULL;
u64 hfs0_offset = 0, hfs0_size = 0;
u32 hfs0_partition_cnt = 0;
//char *partitionHfs0Header = NULL;
//u64 partitionHfs0HeaderSize = 0;
u64 gameCardTitleID = 0;
char gameCardName[0x201] = {'\0'}, gameCardAuthor[0x101] = {'\0'}, gameCardVersion[0x11] = {'\0'};
u32 gameCardVersion = 0;
char gameCardName[0x201] = {'\0'}, fixedGameCardName[0x201] = {'\0'}, gameCardAuthor[0x101] = {'\0'}, gameCardVersionStr[64] = {'\0'};
u64 gameCardUpdateTitleID = 0;
u32 gameCardUpdateVersion = 0;
char gameCardUpdateVersionStr[128] = {'\0'};
u32 currentFBWidth, currentFBHeight;
u8 *currentFB;
@ -64,9 +72,21 @@ int main(int argc, char **argv)
// Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services
uiPleaseWait();
getRootHfs0Header(&fsOperatorInstance);
getGameCardTitleID(&gameCardTitleID);
getGameCardControlNacp(gameCardTitleID, gameCardName, sizeof(gameCardName), gameCardAuthor, sizeof(gameCardAuthor), gameCardVersion, sizeof(gameCardVersion));
if (getRootHfs0Header(&fsOperatorInstance))
{
if (getGameCardTitleIDAndVersion(&gameCardTitleID, &gameCardVersion))
{
convertTitleVersionToDecimal(gameCardVersion, gameCardVersionStr, sizeof(gameCardVersionStr));
getGameCardControlNacp(gameCardTitleID, gameCardName, sizeof(gameCardName), gameCardAuthor, sizeof(gameCardAuthor));
strtrim(gameCardName);
if (strlen(gameCardName))
{
snprintf(fixedGameCardName, sizeof(fixedGameCardName) / sizeof(fixedGameCardName[0]), "%s", gameCardName);
removeIllegalCharacters(fixedGameCardName);
}
}
}
uiPrintHeadline();
uiUpdateStatusMsg();
@ -85,11 +105,25 @@ int main(int argc, char **argv)
hfs0_offset = hfs0_size = 0;
hfs0_partition_cnt = 0;
/*if (partitionHfs0Header != NULL)
{
free(partitionHfs0Header);
partitionHfs0Header = NULL;
partitionHfs0HeaderSize = 0;
}*/
gameCardTitleID = 0;
gameCardVersion = 0;
memset(gameCardName, 0, sizeof(gameCardName));
memset(fixedGameCardName, 0, sizeof(fixedGameCardName));
memset(gameCardAuthor, 0, sizeof(gameCardAuthor));
memset(gameCardVersion, 0, sizeof(gameCardVersion));
memset(gameCardVersionStr, 0, sizeof(gameCardVersionStr));
gameCardUpdateTitleID = 0;
gameCardUpdateVersion = 0;
memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr));
}
}
@ -135,6 +169,12 @@ int main(int argc, char **argv)
case resultDumpGameCardCertificate:
uiSetState(stateDumpGameCardCertificate);
break;
case resultUpdateNSWDBXml:
uiSetState(stateUpdateNSWDBXml);
break;
case resultUpdateApplication:
uiSetState(stateUpdateApplication);
break;
case resultExit:
exitLoop = true;
break;
@ -149,7 +189,7 @@ int main(int argc, char **argv)
nsExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08x)", result);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
delay(5);
ret = -4;
@ -157,7 +197,7 @@ int main(int argc, char **argv)
ncmExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08x)", result);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
delay(5);
ret = -3;
@ -165,7 +205,7 @@ int main(int argc, char **argv)
fsDeviceOperatorClose(&fsOperatorInstance);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08x)", result);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
delay(5);
ret = -2;
@ -173,7 +213,7 @@ int main(int argc, char **argv)
fsExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the fsp-srv service! (0x%08x)", result);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the fsp-srv service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
delay(5);
ret = -1;
@ -181,6 +221,8 @@ int main(int argc, char **argv)
if (hfs0_header != NULL) free(hfs0_header);
//if (partitionHfs0Header != NULL) free(partitionHfs0Header);
uiDeinit();
gfxExit();

View file

@ -26,7 +26,10 @@ extern u64 hfs0_offset, hfs0_size;
extern u32 hfs0_partition_cnt;
extern u64 gameCardTitleID;
extern char gameCardName[0x201], gameCardAuthor[0x101], gameCardVersion[0x11];
extern u32 gameCardVersion;
extern char gameCardName[0x201], fixedGameCardName[0x201], gameCardAuthor[0x101], gameCardVersionStr[64];
extern char gameCardUpdateVersionStr[128];
static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true;
@ -59,12 +62,12 @@ 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[] = { "Full XCI Dump", "Raw Partition Dump", "Partition Data Dump", "View Game Card Files", "Dump Game Card Certificate" };
static const char *mainMenuItems[] = { "Full XCI Dump", "Raw Partition Dump", "Partition Data Dump", "View Game Card Files", "Dump Game Card Certificate", "Update nswdb.com XML database", "Update application (not working at this moment)" };
static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Dump certificate: ", "Trim output dump: ", "CRC32 checksum calculation + dump verification: " };
static const char *partitionDumpType1MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Normal)", "Dump Partition 2 (Secure)" };
static const char *partitionDumpType2MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Logo)", "Dump Partition 2 (Normal)", "Dump Partition 3 (Secure)" };
static const char *viewGameCardFsType1MenuItems[] = { "View Files from Partition 0 (SysUpdate)", "View Files from Partition 1 (Normal)", "View Files from Partition 2 (Secure)" };
static const char *viewGameCardFsType2MenuItems[] = { "View Files from Partition 0 (SysUpdate)", "View Files from Partition 1 (Logo)", "View Files from Partition 2 (Normal)", "View Files from Partition 3 (Secure)" };
static const char *partitionDumpType1MenuItems[] = { "Dump Partition 0 (Update)", "Dump Partition 1 (Normal)", "Dump Partition 2 (Secure)" };
static const char *partitionDumpType2MenuItems[] = { "Dump Partition 0 (Update)", "Dump Partition 1 (Logo)", "Dump Partition 2 (Normal)", "Dump Partition 3 (Secure)" };
static const char *viewGameCardFsType1MenuItems[] = { "View Files from Partition 0 (Update)", "View Files from Partition 1 (Normal)", "View Files from Partition 2 (Secure)" };
static const char *viewGameCardFsType2MenuItems[] = { "View Files from Partition 0 (Update)", "View Files from Partition 1 (Logo)", "View Files from Partition 2 (Normal)", "View Files from Partition 3 (Secure)" };
static unsigned char asciiData[128][8] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x3E, 0x41, 0x55, 0x41, 0x55, 0x49, 0x3E},
@ -362,27 +365,21 @@ UIResult uiLoop(u32 keysDown)
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;*/
if (strlen(gameCardName) && strlen(gameCardAuthor))
{
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Name: %s", gameCardName);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Developer: %s", gameCardAuthor);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
}
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Name: %s", gameCardName);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Developer: %s", gameCardAuthor);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Title ID: %016lX", gameCardTitleID);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
if (strlen(gameCardVersion))
{
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Version: %s", gameCardVersion);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
}
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Version: %s", gameCardVersionStr);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Size: %s", gameCardSizeStr);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
@ -394,6 +391,13 @@ UIResult uiLoop(u32 keysDown)
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt));
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
if (strlen(gameCardUpdateVersionStr))
{
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Game Card FW Version: %s", gameCardUpdateVersionStr);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
}
} else {
if (gameCardInserted)
{
@ -402,6 +406,16 @@ UIResult uiLoop(u32 keysDown)
if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT)
{
uiDrawString("Error: unable to retrieve the game card Title ID!", 0, breaks * 8, 255, 0, 0);
if (strlen(gameCardUpdateVersionStr))
{
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Game Card FW Version: %s", gameCardUpdateVersionStr);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
uiDrawString("In order to be able to dump data from this cartridge, make sure your console is at least on this FW version.", 0, breaks * 8, 255, 255, 255);
}
} else {
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Error: unknown root HFS0 header partition count! (%u)", hfs0_partition_cnt);
uiDrawString(titlebuf, 0, breaks * 8, 255, 0, 0);
@ -639,6 +653,12 @@ UIResult uiLoop(u32 keysDown)
case 4:
res = resultDumpGameCardCertificate;
break;
case 5:
res = resultUpdateNSWDBXml;
break;
case 6:
//res = resultUpdateApplication;
break;
default:
break;
}
@ -818,7 +838,10 @@ UIResult uiLoop(u32 keysDown)
{
if (fileCopyPath[i] == '/')
{
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%.*s", (int)(strlen(fileCopyPath) - i), fileCopyPath + i + 1);
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, selectedOption, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, selectedOption));
mkdir(destCopyPath, 0744);
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)/%.*s", fixedGameCardName, gameCardVersion, gameCardTitleID, selectedOption, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, selectedOption), (int)(strlen(fileCopyPath) - i), fileCopyPath + i + 1);
break;
}
}
@ -850,6 +873,30 @@ UIResult uiLoop(u32 keysDown)
waitForButtonPress();
uiUpdateFreeSpace();
res = resultShowMainMenu;
} else
if (uiState == stateUpdateNSWDBXml)
{
uiDrawString(mainMenuItems[5], 0, breaks * 8, 115, 115, 255);
breaks += 2;
updateNSWDBXml();
waitForButtonPress();
uiUpdateFreeSpace();
res = resultShowMainMenu;
} else
if (uiState == stateUpdateApplication)
{
uiDrawString(mainMenuItems[6], 0, breaks * 8, 115, 115, 255);
breaks += 2;
updateApplication();
waitForButtonPress();
uiUpdateFreeSpace();
res = resultShowMainMenu;
}

View file

@ -20,6 +20,8 @@ typedef enum {
resultShowViewGameCardFsBrowser,
resultViewGameCardFsBrowserCopyFile,
resultDumpGameCardCertificate,
resultUpdateNSWDBXml,
resultUpdateApplication,
resultExit
} UIResult;
@ -35,7 +37,9 @@ typedef enum {
stateViewGameCardFsGetList,
stateViewGameCardFsBrowser,
stateViewGameCardFsBrowserCopyFile,
stateDumpGameCardCertificate
stateDumpGameCardCertificate,
stateUpdateNSWDBXml,
stateUpdateApplication
} UIState;
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b);

View file

@ -5,11 +5,15 @@
#include <math.h>
#include <time.h>
#include <dirent.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/socket.h>
#include <switch/services/ns.h>
#include <libxml/globals.h>
#include <libxml/xpath.h>
#include <libxml2/libxml/globals.h>
#include <libxml2/libxml/xpath.h>
#include <curl/curl.h>
#include <json-c/json.h>
#include "dumper.h"
#include "fsext.h"
@ -23,6 +27,8 @@ extern u64 gameCardSize;
extern u64 gameCardTitleID;
extern u32 hfs0_partition_cnt;
const char *nswReleasesXmlUrl = "http://nswdb.com/xml.php";
const char *nswReleasesXmlTmpPath = "sdmc:/NSWreleases.xml.tmp";
const char *nswReleasesXmlPath = "sdmc:/NSWreleases.xml";
const char *nswReleasesRootElement = "releases";
const char *nswReleasesChildren = "release";
@ -32,6 +38,16 @@ const char *nswReleasesChildrenImgCrc = "imgcrc";
const char *nswReleasesChildrenReleaseName = "releasename";
const char *nswReleasesChildrenCard = "card";
const char *githubReleasesApiUrl = "https://api.github.com/repos/DarkMatterCore/gcdumptool/releases/latest";
const char *gcDumpToolTmpPath = "sdmc:/switch/gcdumptool.nro.tmp";
const char *gcDumpToolPath = "sdmc:/switch/gcdumptool.nro";
const char *userAgent = "gcdumptool/" APP_VERSION " (Nintendo Switch)";
static char *result_buf = NULL;
static size_t result_sz = 0;
static size_t result_written = 0;
bool isGameCardInserted(FsDeviceOperator* o)
{
bool inserted;
@ -56,7 +72,7 @@ void delay(u8 seconds)
while(time(NULL) < (timer + seconds)) syncDisplay();
}
bool getGameCardTitleID(u64 *titleID)
bool getGameCardTitleIDAndVersion(u64 *titleID, u32 *version)
{
bool success = false;
@ -75,29 +91,40 @@ bool getGameCardTitleID(u64 *titleID)
if (R_SUCCEEDED(result = ncmMetaDatabaseListApplication(&ncmDb, appList, sizeof(ncmApplicationMetaKey), 0)))
{
*titleID = appList->meta_record.titleID;
*version = appList->meta_record.titleVersion;
success = true;
} else {
uiStatusMsg("getGameCardTitleID: MetaDatabaseListApplication failed! (0x%08x)", result);
uiStatusMsg("getGameCardTitleIDAndVersion: MetaDatabaseListApplication failed! (0x%08X)", result);
}
} else {
uiStatusMsg("getGameCardTitleID: GetContentMetaDatabase failed! (0x%08x)", result);
uiStatusMsg("getGameCardTitleIDAndVersion: GetContentMetaDatabase failed! (0x%08X)", result);
}
} else {
uiStatusMsg("getGameCardTitleID: OpenContentMetaDatabase failed! (0x%08x)", result);
uiStatusMsg("getGameCardTitleIDAndVersion: OpenContentMetaDatabase failed! (0x%08X)", result);
}
// Seems to cause problems
//if (R_FAILED(result = ncmCloseContentMetaDatabase(FsStorageId_GameCard))) uiStatusMsg("getGameCardTitleID: CloseContentMetaDatabase failed! (0x%08x)", result);
//if (R_FAILED(result = ncmCloseContentMetaDatabase(FsStorageId_GameCard))) uiStatusMsg("getGameCardTitleIDAndVersion: CloseContentMetaDatabase failed! (0x%08X)", result);
free(appList);
} else {
uiStatusMsg("getGameCardTitleID: Unable to allocate memory for the NCM service operations.");
uiStatusMsg("getGameCardTitleIDAndVersion: Unable to allocate memory for the NCM service operations.");
}
return success;
}
bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize, char *versionBuf, int versionBufSize)
void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize)
{
u8 major = (u8)((version >> 26) & 0x3F);
u8 middle = (u8)((version >> 20) & 0x3F);
u8 minor = (u8)((version >> 16) & 0xF);
u16 build = (u16)version;
snprintf(versionBuf, versionBufSize, "%u (%u.%u.%u.%u)", version, major, middle, minor, build);
}
bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize)
{
if (titleID == 0) return false;
@ -120,17 +147,16 @@ bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *a
{
strncpy(nameBuf, langentry->name, nameBufSize - 1);
strncpy(authorBuf, langentry->author, authorBufSize - 1);
strncpy(versionBuf, buf->nacp.version, versionBufSize - 1);
success = true;
} else {
uiStatusMsg("getGameCardControlNacp: GetLanguageEntry failed! (0x%08x)", result);
uiStatusMsg("getGameCardControlNacp: GetLanguageEntry failed! (0x%08X)", result);
}
} else {
uiStatusMsg("getGameCardControlNacp: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes", outsize, sizeof(buf->nacp));
}
} else {
uiStatusMsg("getGameCardControlNacp: GetApplicationControlData failed! (0x%08x)", result);
uiStatusMsg("getGameCardControlNacp: GetApplicationControlData failed! (0x%08X)", result);
}
free(buf);
@ -208,17 +234,6 @@ void convertSize(u64 size, char *out, int bufsize)
snprintf(out, bufsize, "%s", buffer);
}
void getCurrentTimestamp(char *out, int bufsize)
{
time_t timer = time(NULL);
struct tm *timeinfo = localtime(&timer);
char buffer[32] = {'\0'};
strftime(buffer, sizeof(buffer) / sizeof(buffer[0]), "%Y%m%d-%H%M%S", timeinfo);
snprintf(out, bufsize, "%s", buffer);
}
void waitForButtonPress()
{
uiDrawString("Press any button to continue", 0, breaks * 8, 255, 255, 255);
@ -291,7 +306,7 @@ void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenames
qsort(filenames + 1, (*filenamesCount) - 1, sizeof(char*), &sortAlpha);
}
bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc, u32 cnt, char *releaseName, int bufsize)
bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc)
{
xmlChar *key;
xmlNodePtr node = cur;
@ -333,7 +348,7 @@ bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc, u32 cnt, char *re
xmlCrc = strtoul((const char*)key, NULL, 16);
xmlFree(key);
} else
}
if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenReleaseName)))
{
key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
@ -362,8 +377,6 @@ bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc, u32 cnt, char *re
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found matching Scene release: \"%s\" (CRC32: %08X). This is a good dump!", xmlReleaseName, xmlCrc);
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
snprintf(releaseName, bufsize, "%s", xmlReleaseName);
found = true;
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump doesn't match Scene release: \"%s\"! (CRC32: %08X)", xmlReleaseName, xmlCrc);
@ -392,9 +405,9 @@ xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath)
return result;
}
bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize)
void gameCardDumpNSWDBCheck(u32 crc)
{
if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return false;
if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return;
xmlDocPtr doc = NULL;
bool found = false;
@ -416,7 +429,7 @@ bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize)
{
xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode;
found = parseNSWDBRelease(doc, node, crc, i, releaseName, bufsize);
found = parseNSWDBRelease(doc, node, crc);
if (found) break;
}
@ -438,16 +451,434 @@ bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize)
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open and/or parse \"%s\"!", nswReleasesXmlPath);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
return found;
}
char *RemoveIllegalCharacters(char *name)
Result networkInit()
{
Result result = socketInitializeDefault();
if (R_SUCCEEDED(result)) curl_global_init(CURL_GLOBAL_ALL);
return result;
}
void networkDeinit()
{
curl_global_cleanup();
socketExit();
}
size_t writeCurlFile(char *buffer, size_t size, size_t number_of_items, void *input_stream)
{
size_t total_size = (size * number_of_items);
if (fwrite(buffer, 1, total_size, input_stream) != total_size) return 0;
return total_size;
}
static size_t writeCurlBuffer(char *buffer, size_t size, size_t number_of_items, void *input_stream)
{
(void) input_stream;
const size_t bsz = (size * number_of_items);
if (result_sz == 0 || !result_buf)
{
result_sz = 0x1000;
result_buf = (char*)malloc(result_sz);
if (!result_buf) return 0;
}
bool need_realloc = false;
while (result_written + bsz > result_sz)
{
result_sz <<= 1;
need_realloc = true;
}
if (need_realloc)
{
char *new_buf = (char*)realloc(result_buf, result_sz);
if (!new_buf) return 0;
result_buf = new_buf;
}
memcpy(result_buf + result_written, buffer, bsz);
result_written += bsz;
return bsz;
}
int versionNumCmp(char *ver1, char *ver2)
{
typedef struct {
int major;
int minor;
int build;
} version_t;
version_t versionNum1, versionNum2;
memset(&versionNum1, 0, sizeof(version_t));
memset(&versionNum2, 0, sizeof(version_t));
int i, curPart, res;
char *token, *rest;
// Parse version string 1
i = 0;
rest = ver1;
while((token = strtok_r(rest, ".", &rest)))
{
curPart = atoi(token);
switch(i)
{
case 0:
versionNum1.major = curPart;
break;
case 1:
versionNum1.minor = curPart;
break;
case 2:
versionNum1.build = curPart;
break;
default:
break;
}
i++;
if (i >= 3) break;
}
// Parse version string 2
i = 0;
rest = ver2;
while((token = strtok_r(rest, ".", &rest)))
{
curPart = atoi(token);
switch(i)
{
case 0:
versionNum2.major = curPart;
break;
case 1:
versionNum2.minor = curPart;
break;
case 2:
versionNum2.build = curPart;
break;
default:
break;
}
i++;
if (i >= 3) break;
}
// Compare version_t structs
if (versionNum1.major == versionNum2.major)
{
if (versionNum1.minor == versionNum2.minor)
{
if (versionNum1.build == versionNum2.build)
{
res = 0;
} else
if (versionNum1.build < versionNum2.build)
{
res = -1;
} else {
res = 1;
}
} else
if (versionNum1.minor < versionNum2.minor)
{
res = -1;
} else {
res = 1;
}
} else
if (versionNum1.major < versionNum2.major)
{
res = -1;
} else {
res = 1;
}
return res;
}
void updateNSWDBXml()
{
Result result;
CURL *curl;
CURLcode res;
long http_code = 0;
double size = 0.0;
char strbuf[512] = {'\0'};
bool success = false;
if (R_SUCCEEDED(result = networkInit()))
{
curl = curl_easy_init();
if (curl)
{
FILE *nswdbXml = fopen(nswReleasesXmlTmpPath, "wb");
if (nswdbXml)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Downloading XML database from \"%s\", please wait...", nswReleasesXmlUrl);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(curl, CURLOPT_URL, nswReleasesXmlUrl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlFile);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, nswdbXml);
curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size);
if (res == CURLE_OK && http_code >= 200 && http_code <= 299 && size > 0)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Successfully downloaded %.0lf bytes!", size);
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
success = true;
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to request XML database! HTTP status code: %ld", http_code);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
fclose(nswdbXml);
if (success)
{
remove(nswReleasesXmlPath);
rename(nswReleasesXmlTmpPath, nswReleasesXmlPath);
} else {
remove(nswReleasesXmlTmpPath);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open \"%s\" in write mode!", nswReleasesXmlTmpPath);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
curl_easy_cleanup(curl);
} else {
uiDrawString("Error: failed to initialize CURL context!", 0, breaks * 8, 255, 0, 0);
}
networkDeinit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize socket! (%08X)", result);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
breaks += 2;
}
void updateApplication()
{
Result result;
CURL *curl;
CURLcode res;
long http_code = 0;
double size = 0.0;
char strbuf[1024] = {'\0'}, downloadUrl[512] = {'\0'}, releaseTag[32] = {'\0'};
bool success = false;
struct json_object *jobj, *name, *assets;
FILE *gcDumpToolNro = NULL;
if (R_SUCCEEDED(result = networkInit()))
{
curl = curl_easy_init();
if (curl)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Requesting latest release information from \"%s\"...", githubReleasesApiUrl);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(curl, CURLOPT_URL, githubReleasesApiUrl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlBuffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size);
if (res == CURLE_OK && http_code >= 200 && http_code <= 299 && size > 0)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Parsing response JSON data from \"%s\"...", githubReleasesApiUrl);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
jobj = json_tokener_parse(result_buf);
if (jobj != NULL)
{
if (json_object_object_get_ex(jobj, "name", &name) && json_object_get_type(name) == json_type_string)
{
snprintf(releaseTag, sizeof(releaseTag) / sizeof(releaseTag[0]), json_object_get_string(name));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Latest release: %s", releaseTag);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
// Compare versions
if (releaseTag[0] == 'v' || releaseTag[0] == 'V' || releaseTag[0] == 'r' || releaseTag[0] == 'R') memmove(releaseTag, releaseTag + 1, strlen(releaseTag));
if (versionNumCmp(releaseTag, APP_VERSION) > 0)
{
if (json_object_object_get_ex(jobj, "assets", &assets) && json_object_get_type(assets) == json_type_array)
{
assets = json_object_array_get_idx(assets, 0);
if (assets != NULL)
{
if (json_object_object_get_ex(assets, "browser_download_url", &assets) && json_object_get_type(assets) == json_type_string)
{
snprintf(downloadUrl, sizeof(downloadUrl) / sizeof(downloadUrl[0]), json_object_get_string(assets));
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Download URL: \"%s\"", downloadUrl);
uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255);
breaks++;
syncDisplay();
gcDumpToolNro = fopen(gcDumpToolTmpPath, "wb");
if (gcDumpToolNro)
{
curl_easy_reset(curl);
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(curl, CURLOPT_URL, downloadUrl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlFile);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, gcDumpToolNro);
curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size);
if (res == CURLE_OK && http_code >= 200 && http_code <= 299 && size > 0)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Successfully downloaded %.0lf bytes!", size);
uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0);
success = true;
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to request latest update binary! HTTP status code: %ld", http_code);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
fclose(gcDumpToolNro);
if (success)
{
remove(gcDumpToolPath);
rename(gcDumpToolTmpPath, gcDumpToolPath);
} else {
remove(gcDumpToolTmpPath);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open \"%s\" in write mode!", gcDumpToolTmpPath);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to parse download URL from JSON response!", 0, breaks * 8, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to parse object at index 0 from \"assets\" array in JSON response!", 0, breaks * 8, 255, 0, 0);
}
} else {
uiDrawString("Error: unable to parse \"assets\" array from JSON response!", 0, breaks * 8, 255, 0, 0);
}
} else {
uiDrawString("You already have the latest version!", 0, breaks * 8, 255, 255, 255);
}
} else {
uiDrawString("Error: unable to parse version tag from JSON response!", 0, breaks * 8, 255, 0, 0);
}
json_object_put(jobj);
} else {
uiDrawString("Error: unable to parse JSON response!", 0, breaks * 8, 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to request latest release information! HTTP status code: %ld", http_code);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
if (result_buf) free(result_buf);
curl_easy_cleanup(curl);
} else {
uiDrawString("Error: failed to initialize CURL context!", 0, breaks * 8, 255, 0, 0);
}
networkDeinit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize socket! (%08X)", result);
uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0);
}
breaks += 2;
}
void removeIllegalCharacters(char *name)
{
u32 i, len = strlen(name);
for (i = 0; i < len; i++)
{
if (memchr("?[]/\\=+<>:;\",*|^", name[i], sizeof("?[]/\\=+<>:;\",*|^") - 1) || name[i] < 0x20 || name[i] > 0x7E) name[i] = '_';
}
return name;
}
void strtrim(char *str)
{
if (!str || !*str) return;
char *start = str;
char *end = start + strlen(str);
while(--end >= start)
{
if (!isspace(*end)) break;
}
*(++end) = '\0';
while(isspace(*start)) start++;
if (start != str) memmove(str, start, end - start + 1);
}

View file

@ -5,8 +5,9 @@
#include <switch.h>
#define APP_VERSION "1.0.4"
#define APP_VERSION "1.0.5"
#define NAME_BUF_LEN 4096
#define SOCK_BUFFERSIZE 65536
bool isGameCardInserted(FsDeviceOperator* o);
@ -14,16 +15,16 @@ void syncDisplay();
void delay(u8 seconds);
bool getGameCardTitleID(u64 *titleID);
bool getGameCardTitleIDAndVersion(u64 *titleID, u32 *version);
bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize, char *versionBuf, int versionBufSize);
void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize);
bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize);
int getSdCardFreeSpace(u64 *out);
void convertSize(u64 size, char *out, int bufsize);
void getCurrentTimestamp(char *out, int bufsize);
void waitForButtonPress();
bool isDirectory(char *path);
@ -32,8 +33,14 @@ void addString(char **filenames, int *filenamesCount, char **nextFilename, const
void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenamesCount, const char *directory, bool skipParent);
bool gameCardDumpNSWDBCheck(u32 crc, char *releaseName, int bufsize);
void gameCardDumpNSWDBCheck(u32 crc);
char *RemoveIllegalCharacters(char *name);
void updateNSWDBXml();
void updateApplication();
void removeIllegalCharacters(char *name);
void strtrim(char *str);
#endif