mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2025-01-25 02:33:13 -03:00
Update to v1.0.6.
This commit is contained in:
parent
edd7eee294
commit
d22add43bf
9 changed files with 651 additions and 769 deletions
27
README.md
27
README.md
|
@ -4,18 +4,18 @@ Nintendo Switch Game Card Dump Tool
|
|||
Main features
|
||||
--------------
|
||||
|
||||
* Generates XCI cartridge dumps (with optional certificate removal and optional trimming).
|
||||
* 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).
|
||||
* XML database update via libcurl.
|
||||
* Precise HFS0 raw partition dumping (using the root HFS0 header from the game card).
|
||||
* Full XCI dump verification using XML database from NSWDB.COM (NSWreleases.xml).
|
||||
* XML database and in-app 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).
|
||||
* Partition filesystem browser with manual file dump support.
|
||||
* Manual game card certificate dump.
|
||||
* Free SD card space checks in place.
|
||||
* File splitting support for all operations, using 2 GiB parts.
|
||||
* Game card Title ID and Control.nacp retrieval support using NCM and NS services.
|
||||
* Dump speed and ETA calculation.
|
||||
* Dump speed, ETA calculation and progress bar.
|
||||
|
||||
Thanks to
|
||||
--------------
|
||||
|
@ -31,6 +31,21 @@ Thanks to
|
|||
Changelog
|
||||
--------------
|
||||
|
||||
**v1.0.6:**
|
||||
|
||||
* Updated application codebase in order to make it compatible with the latest devkitA64 and libnx releases.
|
||||
* Removed some fs-srv service functions from fsext.c/h that have been included in libnx (and fixed the ones that haven't).
|
||||
* Revamped the GFX code to replace the 8x8 ASCII font with the shared system font, using the pl service and FreeType.
|
||||
* Enabled (and fixed) the in-app update option. HTTPS compatibility is achieved through the mbedtls portlib.
|
||||
* Disabled screen dimming and auto sleep.
|
||||
* Added file counter to partition browser.
|
||||
* Changed the naming convention for split gamecard dumps to *.xc[part number], in order to make them compatible with SX OS and other tools right away.
|
||||
* Increased the delay after inserting a new gamecard by 1 second.
|
||||
* Added a gamecard detection thread to monitor gamecard state changes in a better way. This thread is hooked to a gamecard detection kernel handle retrieved through an IEventNotifier object.
|
||||
* Replaced partition filesystem mounting through fs-srv service calls with manual HFS0 partition header parsing. This should fix issues when browsing the Logo partition from type 0x02 gamecards.
|
||||
* Blocked HOME button presses when running as a regular/system application instead of an applet. A warning message will be displayed whenever any operation is started if the application is running as an applet.
|
||||
* Added detection for bundled FW versions 6.0.0 - 8.0.0.
|
||||
|
||||
**v1.0.5:**
|
||||
|
||||
* Fixed game card version reading (now using the ncm service instead of retrieving it from the cached Control.nacp).
|
||||
|
|
729
source/dumper.c
729
source/dumper.c
|
@ -13,6 +13,8 @@
|
|||
#include "ui.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Extern variables */
|
||||
|
||||
extern u64 freeSpace;
|
||||
|
||||
extern int breaks;
|
||||
|
@ -26,7 +28,8 @@ extern u64 hfs0_offset, hfs0_size;
|
|||
extern u32 hfs0_partition_cnt;
|
||||
|
||||
extern char *partitionHfs0Header;
|
||||
extern u64 partitionHfs0HeaderSize;
|
||||
extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize;
|
||||
extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize;
|
||||
|
||||
extern u64 gameCardTitleID;
|
||||
extern u32 gameCardVersion;
|
||||
|
@ -36,6 +39,13 @@ extern u64 gameCardUpdateTitleID;
|
|||
extern u32 gameCardUpdateVersion;
|
||||
extern char gameCardUpdateVersionStr[128];
|
||||
|
||||
extern char *filenameBuffer;
|
||||
extern int filenamesCount;
|
||||
|
||||
extern AppletType programAppletType;
|
||||
|
||||
/* Statically allocated variables */
|
||||
|
||||
static char strbuf[NAME_BUF_LEN * 2] = {'\0'};
|
||||
|
||||
void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator)
|
||||
|
@ -279,181 +289,62 @@ bool getRootHfs0Header(FsDeviceOperator* fsOperator)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size)
|
||||
bool getHfs0EntryDetails(char *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size)
|
||||
{
|
||||
if (hfs0_header == NULL) return false;
|
||||
if (hfs0Header == NULL) return false;
|
||||
|
||||
if (partition > (hfs0_partition_cnt - 1)) return false;
|
||||
if (entry_idx > (num_entries - 1)) return false;
|
||||
|
||||
hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * hfs0_partition_cnt);
|
||||
hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * num_entries);
|
||||
if (!entryTable) return false;
|
||||
|
||||
memcpy(entryTable, hfs0_header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * hfs0_partition_cnt);
|
||||
memcpy(entryTable, hfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * num_entries);
|
||||
|
||||
switch(partition)
|
||||
// Determine the partition index that's going to be used for offset calculation
|
||||
// If we're dealing with a root HFS0 header, just use entry_idx
|
||||
// Otherwise, partitionIndex must be used, because entry_idx represents the file entry we must look for in the provided HFS0 partition header
|
||||
u32 part_idx = (isRoot ? entry_idx : partitionIndex);
|
||||
|
||||
switch(part_idx)
|
||||
{
|
||||
case 0: // update (contained within IStorage instance with partition ID 0)
|
||||
case 1: // normal or logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0)
|
||||
*out_offset = (hfs0_offset + hfs0_size + entryTable[partition].file_offset);
|
||||
case 0: // Update (contained within IStorage instance with partition ID 0)
|
||||
case 1: // Normal or Logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0)
|
||||
// Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start
|
||||
// Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function)
|
||||
*out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset);
|
||||
break;
|
||||
case 2:
|
||||
// Check if we're dealing with a type 0x01 gamecard
|
||||
if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT)
|
||||
{
|
||||
// secure (contained within IStorage instance with partition ID 1)
|
||||
*out_offset = 0;
|
||||
// Secure (contained within IStorage instance with partition ID 1)
|
||||
// Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance
|
||||
// Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation
|
||||
*out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset));
|
||||
} else {
|
||||
// normal (contained within IStorage instance with partition ID 0)
|
||||
*out_offset = (hfs0_offset + hfs0_size + entryTable[partition].file_offset);
|
||||
// Normal (contained within IStorage instance with partition ID 0)
|
||||
// Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start
|
||||
// Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function)
|
||||
*out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset);
|
||||
}
|
||||
break;
|
||||
case 3: // secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1)
|
||||
*out_offset = 0;
|
||||
case 3: // Secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1)
|
||||
// Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance
|
||||
// Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation
|
||||
*out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
*out_size = entryTable[partition].file_size;
|
||||
// Store the file size for the desired HFS0 entry
|
||||
*out_size = entryTable[entry_idx].file_size;
|
||||
|
||||
free(entryTable);
|
||||
|
||||
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;
|
||||
FsGameCardHandle handle;
|
||||
FsStorage gameCardStorage;
|
||||
u64 partitionSize = 0, partitionOffset = 0, roundedHfs0HeaderSize = 0;
|
||||
u32 hfs0FileCount = 0, hfs0StrTableSize = 0, magic = 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.value);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 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 * font_height, 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)))
|
||||
{
|
||||
// Check the HFS0 magic word
|
||||
memcpy(&magic, buf, sizeof(u32));
|
||||
magic = bswap_32(magic);
|
||||
if (magic == HFS0_MAGIC)
|
||||
{
|
||||
// Calculate the size for the partition HFS0 header
|
||||
memcpy(&hfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32));
|
||||
memcpy(&hfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32));
|
||||
partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * hfs0FileCount) + hfs0StrTableSize);
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, hfs0FileCount);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, hfs0StrTableSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
// Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary
|
||||
roundedHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE);
|
||||
|
||||
partitionHfs0Header = (char*)malloc(roundedHfs0HeaderSize);
|
||||
if (partitionHfs0Header)
|
||||
{
|
||||
// Check if we were dealing with the correct header size all along
|
||||
if (roundedHfs0HeaderSize == MEDIA_UNIT_SIZE)
|
||||
{
|
||||
// Just copy what we already have
|
||||
memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE);
|
||||
success = true;
|
||||
} else {
|
||||
// Read the whole HFS0 header
|
||||
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionOffset, partitionHfs0Header, roundedHfs0HeaderSize)))
|
||||
{
|
||||
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 * font_height, 255, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
}
|
||||
} 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 * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 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 * font_height, 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 * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
fsStorageClose(&gameCardStorage);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 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;
|
||||
|
@ -499,7 +390,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
{
|
||||
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]);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u size: %s (%lu bytes).", partition, partitionSizesStr[partition], partitionSizes[partition]);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
} else {
|
||||
|
@ -526,7 +417,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
if (proceed)
|
||||
{
|
||||
convertSize(xciDataSize, xciDataSizeStr, sizeof(xciDataSizeStr) / sizeof(xciDataSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI data size: %s (%lu bytes)", xciDataSizeStr, xciDataSize);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI data size: %s (%lu bytes).", xciDataSizeStr, xciDataSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
|
@ -545,7 +436,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr);
|
||||
}
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes)", totalSizeStr, totalSize);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes).", totalSizeStr, totalSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
|
@ -570,8 +461,11 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert
|
|||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
timeGetCurrentTime(TimeType_LocalSystemClock, &start);
|
||||
|
||||
|
@ -885,16 +779,16 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
// * The "update" (0) partition, the "logo" (1) partition and the "normal" (2) partition (for gamecard type 0x02)
|
||||
// The IStorage instance returned for partition == 1 contains the "secure" partition (which can either be 2 or 3 depending on the gamecard type)
|
||||
// This ugly hack makes sure we just dump the *actual* raw HFS0 partition, without preceding data, padding, etc.
|
||||
// Oddly enough, IFileSystem instances actually point to the specified partition ID filesystem. I don't understand why it doesn't work like that for IStorage, but whatever
|
||||
// Oddly enough, IFileSystem instances actually points to the specified partition ID filesystem. I don't understand why it doesn't work like that for IStorage, but whatever
|
||||
// NOTE: Using partition == 2 returns error 0x149002, and using higher values probably do so, too
|
||||
|
||||
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (partition < 2 ? 0 : 1) : (partition < 3 ? 0 : 1)))))
|
||||
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
||||
{
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
|
||||
if (getHsf0PartitionDetails(partition, &partitionOffset, &size))
|
||||
if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionOffset, &size))
|
||||
{
|
||||
convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes)", totalSizeStr, size);
|
||||
|
@ -924,8 +818,11 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
|
@ -1099,50 +996,172 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt
|
|||
return success;
|
||||
}
|
||||
|
||||
bool openPartitionFs(FsFileSystem* ret, FsDeviceOperator* fsOperator, u32 partition)
|
||||
bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition)
|
||||
{
|
||||
FsGameCardHandle handle;
|
||||
if (hfs0_header == NULL) return false;
|
||||
|
||||
if (partitionHfs0Header != NULL)
|
||||
{
|
||||
free(partitionHfs0Header);
|
||||
partitionHfs0Header = NULL;
|
||||
partitionHfs0HeaderOffset = 0;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
}
|
||||
|
||||
char *buf = NULL;
|
||||
Result result;
|
||||
FsGameCardHandle handle;
|
||||
FsStorage gameCardStorage;
|
||||
u64 partitionSize = 0;
|
||||
u32 magic = 0;
|
||||
bool success = false;
|
||||
|
||||
if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionHfs0HeaderOffset, &partitionSize))
|
||||
{
|
||||
workaroundPartitionZeroAccess(fsOperator);
|
||||
|
||||
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
|
||||
{
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
|
||||
if (R_SUCCEEDED(result = fsOpenGameCardFileSystem(ret, &handle, partition)))
|
||||
// Same ugly hack from dumpRawPartition()
|
||||
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
||||
{
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem succeeded: 0x%08X", result);
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
|
||||
buf = (char*)malloc(MEDIA_UNIT_SIZE);
|
||||
if (buf)
|
||||
{
|
||||
// First read MEDIA_UNIT_SIZE bytes
|
||||
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, buf, MEDIA_UNIT_SIZE)))
|
||||
{
|
||||
// Check the HFS0 magic word
|
||||
memcpy(&magic, buf, sizeof(u32));
|
||||
magic = bswap_32(magic);
|
||||
if (magic == HFS0_MAGIC)
|
||||
{
|
||||
// Calculate the size for the partition HFS0 header
|
||||
memcpy(&partitionHfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32));
|
||||
memcpy(&partitionHfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32));
|
||||
partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionHfs0StrTableSize);
|
||||
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header offset (relative to IStorage instance): 0x%016lX", partition, partitionHfs0HeaderOffset);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, partitionHfs0FileCount);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, partitionHfs0StrTableSize);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
|
||||
uiRefreshDisplay();*/
|
||||
|
||||
// Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary
|
||||
partitionHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE);
|
||||
|
||||
partitionHfs0Header = (char*)malloc(partitionHfs0HeaderSize);
|
||||
if (partitionHfs0Header)
|
||||
{
|
||||
// Check if we were dealing with the correct header size all along
|
||||
if (partitionHfs0HeaderSize == MEDIA_UNIT_SIZE)
|
||||
{
|
||||
// Just copy what we already have
|
||||
memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE);
|
||||
success = true;
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem failed! (0x%08X)", result);
|
||||
// Read the whole HFS0 header
|
||||
if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, partitionHfs0Header, partitionHfs0HeaderSize)))
|
||||
{
|
||||
success = true;
|
||||
} else {
|
||||
free(partitionHfs0Header);
|
||||
partitionHfs0Header = NULL;
|
||||
partitionHfs0HeaderOffset = 0;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
}
|
||||
} else {
|
||||
partitionHfs0HeaderOffset = 0;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 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 * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
fsStorageClose(&gameCardStorage);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
} else {
|
||||
uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
breaks += 2;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcEta)
|
||||
bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* source, const char* dest, const u64 file_offset, const u64 size, bool doSplitting, bool calcEta)
|
||||
{
|
||||
Result result;
|
||||
bool success = false;
|
||||
char splitFilename[NAME_BUF_LEN] = {'\0'};
|
||||
size_t destLen = strlen(dest);
|
||||
FILE *inFile, *outFile;
|
||||
FILE *outFile = NULL;
|
||||
char *buf = NULL;
|
||||
u64 size, off, n = DUMP_BUFFER_SIZE;
|
||||
u64 off, n = DUMP_BUFFER_SIZE;
|
||||
u8 splitIndex = 0;
|
||||
u8 progress = 0;
|
||||
char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'};
|
||||
|
||||
FsGameCardHandle handle;
|
||||
FsStorage gameCardStorage;
|
||||
|
||||
u64 start, now, remainingTime;
|
||||
struct tm *timeinfo;
|
||||
char etaInfo[32] = {'\0'};
|
||||
|
@ -1152,15 +1171,22 @@ bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcE
|
|||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source);
|
||||
uiDrawString(strbuf, 0, (breaks + 1) * font_height, 255, 255, 255);
|
||||
uiRefreshDisplay();
|
||||
|
||||
if ((destLen + 1) < NAME_BUF_LEN)
|
||||
{
|
||||
inFile = fopen(source, "rb");
|
||||
if (inFile)
|
||||
if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle)))
|
||||
{
|
||||
fseek(inFile, 0L, SEEK_END);
|
||||
size = ftell(inFile);
|
||||
rewind(inFile);
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
|
||||
// Same ugly hack from dumpRawPartition()
|
||||
if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition))))
|
||||
{
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
|
||||
convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
|
||||
|
||||
|
@ -1178,9 +1204,9 @@ bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcE
|
|||
{
|
||||
if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off);
|
||||
|
||||
if (fread(buf, 1, n, inFile) != n)
|
||||
if (R_FAILED(result = fsStorageRead(&gameCardStorage, file_offset + off, buf, n)))
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read chunk from offset 0x%016lX", off);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, file_offset + off);
|
||||
uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
@ -1329,208 +1355,97 @@ bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcE
|
|||
uiDrawString("Failed to open output file!", 0, breaks * font_height, 255, 255, 255);
|
||||
}
|
||||
|
||||
fclose(inFile);
|
||||
fsStorageClose(&gameCardStorage);
|
||||
} else {
|
||||
breaks += 3;
|
||||
uiDrawString("Failed to open input file!", 0, breaks * font_height, 255, 255, 255);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
breaks += 3;
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! \"%s\" (%lu)", dest, destLen);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
breaks += 3;
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", destLen);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool _copyDirectory(char* sbuf, size_t source_len, char* dbuf, size_t dest_len, bool splitting)
|
||||
bool copyHfs0Contents(FsDeviceOperator* fsOperator, u32 partition, hfs0_entry_table *partitionEntryTable, const char *dest, bool splitting)
|
||||
{
|
||||
struct dirent* ent;
|
||||
bool success = true;
|
||||
|
||||
DIR *dir = opendir(sbuf);
|
||||
if (dir)
|
||||
if (!dest || !*dest)
|
||||
{
|
||||
sbuf[source_len] = '/';
|
||||
dbuf[dest_len] = '/';
|
||||
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
if ((strlen(ent->d_name) == 1 && !strcmp(ent->d_name, ".")) || (strlen(ent->d_name) == 2 && !strcmp(ent->d_name, ".."))) continue;
|
||||
|
||||
size_t d_name_len = strlen(ent->d_name);
|
||||
|
||||
if ((source_len + 1 + d_name_len + 1) >= NAME_BUF_LEN || (dest_len + 1 + d_name_len + 1) >= NAME_BUF_LEN)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Filename too long! \"%s\" (%lu)", ent->d_name, d_name_len);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks++;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
strcpy(sbuf + source_len + 1, ent->d_name);
|
||||
strcpy(dbuf + dest_len + 1, ent->d_name);
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
mkdir(dbuf, 0744);
|
||||
if (!_copyDirectory(sbuf, source_len + 1 + d_name_len, dbuf, dest_len + 1 + d_name_len, splitting))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!copyFile(sbuf, dbuf, splitting, false))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error opening directory \"%s\"", dbuf);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks++;
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copyDirectory(const char* source, const char* dest, bool splitting)
|
||||
{
|
||||
char sbuf[NAME_BUF_LEN], dbuf[NAME_BUF_LEN];
|
||||
size_t source_len = strlen(source), dest_len = strlen(dest);
|
||||
|
||||
if (source_len + 1 >= NAME_BUF_LEN)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Source directory name too long! \"%s\" (%lu)", source, source_len);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks++;
|
||||
uiDrawString("Error: destination directory is empty.", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest_len + 1 >= NAME_BUF_LEN)
|
||||
if (!partitionHfs0Header || !partitionEntryTable)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination directory name too long! \"%s\" (%lu)", dest, dest_len);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
breaks++;
|
||||
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
char dbuf[NAME_BUF_LEN] = {'\0'};
|
||||
size_t dest_len = strlen(dest);
|
||||
|
||||
if ((dest_len + 1) >= NAME_BUF_LEN)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination directory name is too long! (%lu bytes)", dest_len);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
strcpy(sbuf, source);
|
||||
strcpy(dbuf, dest);
|
||||
|
||||
mkdir(dbuf, 0744);
|
||||
|
||||
return _copyDirectory(sbuf, source_len, dbuf, dest_len, splitting);
|
||||
}
|
||||
dbuf[dest_len] = '/';
|
||||
dest_len++;
|
||||
|
||||
void removeDirectory(const char *path)
|
||||
u32 i;
|
||||
bool success;
|
||||
|
||||
for(i = 0; i < partitionHfs0FileCount; i++)
|
||||
{
|
||||
struct dirent* ent;
|
||||
char cur_path[NAME_BUF_LEN] = {'\0'};
|
||||
u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionEntryTable[i].filename_offset);
|
||||
char *filename = (partitionHfs0Header + filename_offset);
|
||||
strcpy(dbuf + dest_len, filename);
|
||||
|
||||
DIR *dir = opendir(path);
|
||||
if (dir)
|
||||
{
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
if ((strlen(ent->d_name) == 1 && !strcmp(ent->d_name, ".")) || (strlen(ent->d_name) == 2 && !strcmp(ent->d_name, ".."))) continue;
|
||||
u64 file_offset = (partitionHfs0HeaderSize + partitionEntryTable[i].file_offset);
|
||||
if (HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition) == 0) file_offset += partitionHfs0HeaderOffset;
|
||||
|
||||
snprintf(cur_path, sizeof(cur_path) / sizeof(cur_path[0]), "%s/%s", path, ent->d_name);
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
removeDirectory(cur_path);
|
||||
} else {
|
||||
remove(cur_path);
|
||||
success = copyFileFromHfs0(fsOperator, partition, filename, dbuf, file_offset, partitionEntryTable[i].file_size, splitting, false);
|
||||
if (!success) break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
rmdir(path);
|
||||
}
|
||||
}
|
||||
|
||||
bool getDirectorySize(const char *path, u64 *out_size)
|
||||
{
|
||||
struct dirent* ent;
|
||||
char cur_path[NAME_BUF_LEN] = {'\0'};
|
||||
bool success = true;
|
||||
u64 total_size = 0, dir_size = 0;
|
||||
FILE *file = NULL;
|
||||
|
||||
DIR *dir = opendir(path);
|
||||
if (dir)
|
||||
{
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
if ((strlen(ent->d_name) == 1 && !strcmp(ent->d_name, ".")) || (strlen(ent->d_name) == 2 && !strcmp(ent->d_name, ".."))) continue;
|
||||
|
||||
snprintf(cur_path, sizeof(cur_path) / sizeof(cur_path[0]), "%s/%s", path, ent->d_name);
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
if (getDirectorySize(cur_path, &dir_size))
|
||||
{
|
||||
total_size += dir_size;
|
||||
dir_size = 0;
|
||||
} else {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
file = fopen(cur_path, "rb");
|
||||
if (file)
|
||||
{
|
||||
fseek(file, 0L, SEEK_END);
|
||||
total_size += ftell(file);
|
||||
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
} else {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success) *out_size = total_size;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
|
||||
{
|
||||
FsFileSystem fs;
|
||||
int ret;
|
||||
bool success = false;
|
||||
u64 total_size;
|
||||
u64 total_size = 0;
|
||||
u32 i;
|
||||
hfs0_entry_table *entryTable = NULL;
|
||||
char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'};
|
||||
|
||||
workaroundPartitionZeroAccess(fsOperator);
|
||||
|
||||
if (openPartitionFs(&fs, fsOperator, partition))
|
||||
if (getPartitionHfs0Header(fsOperator, partition))
|
||||
{
|
||||
ret = fsdevMountDevice("gamecard", fs);
|
||||
if (ret != -1)
|
||||
if (partitionHfs0FileCount)
|
||||
{
|
||||
/*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;*/
|
||||
entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount);
|
||||
if (entryTable)
|
||||
{
|
||||
memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount);
|
||||
|
||||
// Calculate total size
|
||||
for(i = 0; i < partitionHfs0FileCount; i++) total_size += entryTable[i].file_size;
|
||||
|
||||
if (getDirectorySize("gamecard:/", &total_size))
|
||||
{
|
||||
convertSize(total_size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]));
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total partition data size: %s (%lu bytes)", totalSizeStr, total_size);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
|
@ -1544,12 +1459,17 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
|
|||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
if (copyDirectory("gamecard:/", dumpPath, true))
|
||||
uiRefreshDisplay();
|
||||
|
||||
success = copyHfs0Contents(fsOperator, partition, entryTable, dumpPath, true);
|
||||
if (success)
|
||||
{
|
||||
success = true;
|
||||
breaks += 5;
|
||||
uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0);
|
||||
} else {
|
||||
|
@ -1558,21 +1478,20 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
|
|||
} else {
|
||||
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
free(entryTable);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to get total partition data size!");
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
fsdevUnmountDevice("gamecard");
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice failed! (%d)", ret);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
fsFsClose(&fs);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open partition #%u filesystem!", partition);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
free(partitionHfs0Header);
|
||||
partitionHfs0Header = NULL;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
}
|
||||
|
||||
breaks += 2;
|
||||
|
@ -1580,32 +1499,110 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition)
|
|||
return success;
|
||||
}
|
||||
|
||||
bool mountViewPartition(FsDeviceOperator *fsOperator, FsFileSystem *out, u32 partition)
|
||||
bool getHfs0FileList(FsDeviceOperator* fsOperator, u32 partition)
|
||||
{
|
||||
int ret;
|
||||
if (!getPartitionHfs0Header(fsOperator, partition)) return false;
|
||||
|
||||
if (!partitionHfs0Header)
|
||||
{
|
||||
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!partitionHfs0FileCount)
|
||||
{
|
||||
uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (partitionHfs0FileCount > FILENAME_MAX_CNT)
|
||||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition contains more than %u files! (%u entries)", FILENAME_MAX_CNT, partitionHfs0FileCount);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount);
|
||||
if (!entryTable)
|
||||
{
|
||||
uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount);
|
||||
|
||||
memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE);
|
||||
|
||||
int i;
|
||||
int max_elements = (int)partitionHfs0FileCount;
|
||||
char *nextFilename = filenameBuffer;
|
||||
|
||||
filenamesCount = 0;
|
||||
|
||||
for(i = 0; i < max_elements; i++)
|
||||
{
|
||||
u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + entryTable[i].filename_offset);
|
||||
addStringToFilenameBuffer(partitionHfs0Header + filename_offset, &nextFilename);
|
||||
}
|
||||
|
||||
free(entryTable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename)
|
||||
{
|
||||
if (!partitionHfs0Header)
|
||||
{
|
||||
uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filename || !*filename)
|
||||
{
|
||||
uiDrawString("Filename unavailable!", 0, breaks * font_height, 255, 0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 file_offset = 0;
|
||||
u64 file_size = 0;
|
||||
bool success = false;
|
||||
|
||||
workaroundPartitionZeroAccess(fsOperator);
|
||||
|
||||
if (openPartitionFs(out, fsOperator, partition))
|
||||
if (getHfs0EntryDetails(partitionHfs0Header, partitionHfs0HeaderOffset, partitionHfs0HeaderSize, partitionHfs0FileCount, file, false, partition, &file_offset, &file_size))
|
||||
{
|
||||
ret = fsdevMountDevice("view", *out);
|
||||
if (ret != -1)
|
||||
if (file_size <= freeSpace)
|
||||
{
|
||||
//snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret);
|
||||
//uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
//breaks++;
|
||||
char destCopyPath[NAME_BUF_LEN] = {'\0'};
|
||||
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition));
|
||||
|
||||
success = true;
|
||||
if ((strlen(destCopyPath) + 1 + strlen(filename)) < NAME_BUF_LEN)
|
||||
{
|
||||
mkdir(destCopyPath, 0744);
|
||||
snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)/%s", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), filename);
|
||||
|
||||
uiDrawString("Hold B to cancel.", 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
success = copyFileFromHfs0(fsOperator, partition, filename, destCopyPath, file_offset, file_size, true, true);
|
||||
} else {
|
||||
fsFsClose(out);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice failed! (%d)", ret);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! (%lu bytes)", strlen(destCopyPath) + 1 + strlen(filename));
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open partition #%u filesystem!", partition);
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: not enough free space available in the SD card (%lu bytes required).", file_size);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
uiDrawString("Error: unable to get file details from the partition HFS0 header!", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
@ -1649,7 +1646,13 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator)
|
|||
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to file \"%s\"...", filename);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#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 HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < 2 ? 0 : 1) : ((y) < 3 ? 0 : 1))
|
||||
|
||||
#define GAMECARD_SIZE_1GiB (u64)0x40000000
|
||||
#define GAMECARD_SIZE_2GiB (u64)0x80000000
|
||||
#define GAMECARD_SIZE_4GiB (u64)0x100000000
|
||||
|
@ -81,18 +83,12 @@ typedef struct
|
|||
u8 hashed_region_sha256[0x20];
|
||||
} PACKED hfs0_entry_table;
|
||||
|
||||
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 trimDump, bool calcCrc);
|
||||
bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting);
|
||||
bool openPartitionFs(FsFileSystem* ret, FsDeviceOperator* fsOperator, u32 partition);
|
||||
bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcEta);
|
||||
bool copyDirectory(const char* source, const char* dest, bool doSplitting);
|
||||
void removeDirectory(const char *path);
|
||||
bool getDirectorySize(const char *path, u64 *out_size);
|
||||
bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition);
|
||||
bool mountViewPartition(FsDeviceOperator *fsOperator, FsFileSystem *out, u32 partition);
|
||||
bool getHfs0FileList(FsDeviceOperator* fsOperator, u32 partition);
|
||||
bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename);
|
||||
bool dumpGameCertificate(FsDeviceOperator *fsOperator);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -46,47 +46,6 @@ Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32
|
|||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenGameCardFileSystem(FsFileSystem* out, const FsGameCardHandle* handle, u32 partition)
|
||||
{
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 handle;
|
||||
u32 partition;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(fsGetServiceSession(), &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 31;
|
||||
raw->handle = handle->value;
|
||||
raw->partition = partition;
|
||||
|
||||
Result rc = serviceIpcDispatch(fsGetServiceSession());
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
IpcParsedCommand r;
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(fsGetServiceSession(), &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) serviceCreateSubservice(&out->s, fsGetServiceSession(), &r, 0);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out)
|
||||
{
|
||||
IpcCommand c;
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
// IFileSystemProxy
|
||||
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32 partition);
|
||||
Result fsOpenGameCardFileSystem(FsFileSystem* out, const FsGameCardHandle* handle, u32 partition);
|
||||
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out);
|
||||
|
||||
// IDeviceOperator
|
||||
|
|
|
@ -57,7 +57,7 @@ int main(int argc, char *argv[])
|
|||
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Retrieve kernel event handle */
|
||||
/* Retrieve gamecard detection event handle */
|
||||
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
|
@ -161,7 +161,7 @@ int main(int argc, char *argv[])
|
|||
ret = -9;
|
||||
}
|
||||
|
||||
/* Close kernel event */
|
||||
/* Close gamecard detection kernel event */
|
||||
eventClose(&fsGameCardKernelEvent);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to retrieve gamecard detection event handle! (0x%08X)", result);
|
||||
|
@ -171,7 +171,7 @@ int main(int argc, char *argv[])
|
|||
ret = -8;
|
||||
}
|
||||
|
||||
/* Close gamecard event notifier */
|
||||
/* Close gamecard detection event notifier */
|
||||
fsEventNotifierClose(&fsGameCardEventNotifier);
|
||||
} else {
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open gamecard detection event notifier! (0x%08X)", result);
|
||||
|
|
201
source/ui.c
201
source/ui.c
|
@ -19,6 +19,8 @@
|
|||
|
||||
extern FsDeviceOperator fsOperatorInstance;
|
||||
|
||||
extern AppletType programAppletType;
|
||||
|
||||
extern bool gameCardInserted;
|
||||
|
||||
extern char gameCardSizeStr[32], trimmedCardSizeStr[32];
|
||||
|
@ -27,14 +29,16 @@ extern char *hfs0_header;
|
|||
extern u64 hfs0_offset, hfs0_size;
|
||||
extern u32 hfs0_partition_cnt;
|
||||
|
||||
extern char *partitionHfs0Header;
|
||||
extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize;
|
||||
extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize;
|
||||
|
||||
extern u64 gameCardTitleID;
|
||||
extern u32 gameCardVersion;
|
||||
extern char gameCardName[0x201], fixedGameCardName[0x201], gameCardAuthor[0x101], gameCardVersionStr[64];
|
||||
|
||||
extern char gameCardUpdateVersionStr[128];
|
||||
|
||||
extern char currentDirectory[NAME_BUF_LEN];
|
||||
|
||||
extern char *filenameBuffer;
|
||||
extern char *filenames[FILENAME_MAX_CNT];
|
||||
extern int filenamesCount;
|
||||
|
@ -54,19 +58,16 @@ int scroll = 0;
|
|||
int breaks = 0;
|
||||
int font_height = 0;
|
||||
|
||||
static u32 selectedPartitionIndex;
|
||||
static u32 selectedFileIndex;
|
||||
|
||||
static bool highlight = false;
|
||||
|
||||
static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true;
|
||||
|
||||
static u32 selectedOption;
|
||||
|
||||
static FsFileSystem fs;
|
||||
|
||||
static char statusMessage[2048] = {'\0'};
|
||||
static int statusMessageFadeout = 0;
|
||||
|
||||
static char fileCopyPath[NAME_BUF_LEN * 2] = {'\0'};
|
||||
|
||||
static int headlineCnt = 0;
|
||||
|
||||
u64 freeSpace = 0;
|
||||
|
@ -272,19 +273,18 @@ void uiUpdateStatusMsg()
|
|||
{
|
||||
if (!strlen(statusMessage) || !statusMessageFadeout) return;
|
||||
|
||||
uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
||||
|
||||
if ((statusMessageFadeout - 4) > BG_COLOR_RGB)
|
||||
{
|
||||
uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
||||
|
||||
int fadeout = (statusMessageFadeout > 255 ? 255 : statusMessageFadeout);
|
||||
uiDrawString(statusMessage, 0, FB_HEIGHT - (font_height * 2), fadeout, fadeout, fadeout);
|
||||
uiRefreshDisplay();
|
||||
|
||||
statusMessageFadeout -= 4;
|
||||
} else {
|
||||
uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
|
||||
statusMessageFadeout = 0;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
}
|
||||
|
||||
void uiPleaseWait()
|
||||
|
@ -411,6 +411,12 @@ int uiInit()
|
|||
/* Disable screen dimming and auto sleep */
|
||||
appletSetMediaPlaybackState(true);
|
||||
|
||||
/* Get applet type */
|
||||
programAppletType = appletGetAppletType();
|
||||
|
||||
/* Block HOME menu button presses if we're running as a regular application or a system application */
|
||||
if (programAppletType == AppletType_Application || programAppletType == AppletType_SystemApplication) appletBeginBlockingHomeButton(0);
|
||||
|
||||
/* Clear screen */
|
||||
uiClearScreen();
|
||||
|
||||
|
@ -419,6 +425,9 @@ int uiInit()
|
|||
|
||||
void uiDeinit()
|
||||
{
|
||||
/* Unblock HOME menu button presses if we're running as a regular application or a system application */
|
||||
if (programAppletType == AppletType_Application || programAppletType == AppletType_SystemApplication) appletEndBlockingHomeButton();
|
||||
|
||||
/* Enable screen dimming and auto sleep */
|
||||
appletSetMediaPlaybackState(false);
|
||||
|
||||
|
@ -459,6 +468,7 @@ UIResult uiProcess()
|
|||
int menuItemsCount = 0;
|
||||
|
||||
u32 keysDown;
|
||||
u32 keysHeld;
|
||||
|
||||
uiPrintHeadline();
|
||||
loadGameCardInfo();
|
||||
|
@ -588,11 +598,7 @@ UIResult uiProcess()
|
|||
menu = (const char**)filenames;
|
||||
menuItemsCount = filenamesCount;
|
||||
|
||||
uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedOption] : viewGameCardFsType2MenuItems[selectedOption]), 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Current directory: %s", currentDirectory);
|
||||
uiDrawString(titlebuf, 0, breaks * font_height, 255, 255, 255);
|
||||
uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedPartitionIndex] : viewGameCardFsType2MenuItems[selectedPartitionIndex]), 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "File count: %d | Current file: %d", menuItemsCount, cursor + 1);
|
||||
|
@ -677,18 +683,10 @@ UIResult uiProcess()
|
|||
|
||||
hidScanInput();
|
||||
keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
keysHeld = hidKeysHeld(CONTROLLER_P1_AUTO);
|
||||
|
||||
// Exit
|
||||
if (keysDown & KEY_PLUS)
|
||||
{
|
||||
if (uiState == stateViewGameCardFsBrowser)
|
||||
{
|
||||
fsdevUnmountDevice("view");
|
||||
fsFsClose(&fs);
|
||||
}
|
||||
|
||||
res = resultExit;
|
||||
}
|
||||
if (keysDown & KEY_PLUS) res = resultExit;
|
||||
|
||||
// Process key inputs only if the UI state hasn't been changed
|
||||
if (res == resultNone)
|
||||
|
@ -698,11 +696,7 @@ UIResult uiProcess()
|
|||
if (uiState == stateXciDumpMenu)
|
||||
{
|
||||
// Select
|
||||
if ((keysDown & KEY_A) && cursor == 0)
|
||||
{
|
||||
selectedOption = (u32)cursor;
|
||||
res = resultDumpXci;
|
||||
}
|
||||
if ((keysDown & KEY_A) && cursor == 0) res = resultDumpXci;
|
||||
|
||||
// Back
|
||||
if (keysDown & KEY_B) res = resultShowMainMenu;
|
||||
|
@ -752,10 +746,10 @@ UIResult uiProcess()
|
|||
}
|
||||
|
||||
// Go up
|
||||
if (keysDown & KEY_UP) scrollAmount = -1;
|
||||
if ((keysDown & KEY_DUP) || (keysHeld & KEY_LSTICK_UP) || (keysHeld & KEY_RSTICK_UP)) scrollAmount = -1;
|
||||
|
||||
// Go down
|
||||
if (keysDown & KEY_DOWN) scrollAmount = 1;
|
||||
if ((keysDown & KEY_DDOWN) || (keysHeld & KEY_LSTICK_DOWN) || (keysHeld & KEY_RSTICK_DOWN)) scrollAmount = 1;
|
||||
} else {
|
||||
// Select
|
||||
if (keysDown & KEY_A)
|
||||
|
@ -791,49 +785,28 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateRawPartitionDumpMenu)
|
||||
{
|
||||
selectedOption = (u32)cursor;
|
||||
// Save selected partition index
|
||||
selectedPartitionIndex = (u32)cursor;
|
||||
res = resultDumpRawPartition;
|
||||
} else
|
||||
if (uiState == statePartitionDataDumpMenu)
|
||||
{
|
||||
selectedOption = (u32)cursor;
|
||||
// Save selected partition index
|
||||
selectedPartitionIndex = (u32)cursor;
|
||||
res = resultDumpPartitionData;
|
||||
} else
|
||||
if (uiState == stateViewGameCardFsMenu)
|
||||
{
|
||||
selectedOption = (u32)cursor;
|
||||
// Save selected partition index
|
||||
selectedPartitionIndex = (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);
|
||||
// Save selected file index
|
||||
selectedFileIndex = (u32)cursor;
|
||||
res = resultViewGameCardFsBrowserCopyFile;
|
||||
}
|
||||
|
||||
free(selectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Back
|
||||
|
@ -845,40 +818,24 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateViewGameCardFsBrowser)
|
||||
{
|
||||
if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6)
|
||||
{
|
||||
fsdevUnmountDevice("view");
|
||||
fsFsClose(&fs);
|
||||
free(partitionHfs0Header);
|
||||
partitionHfs0Header = NULL;
|
||||
partitionHfs0HeaderOffset = 0;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go up
|
||||
if (keysDown & KEY_UP) scrollAmount = -1;
|
||||
if (keysDown & KEY_LEFT) scrollAmount = -5;
|
||||
if ((keysDown & KEY_DUP) || (keysHeld & KEY_LSTICK_UP) || (keysHeld & KEY_RSTICK_UP)) scrollAmount = -1;
|
||||
if ((keysDown & KEY_DLEFT) || (keysHeld & KEY_LSTICK_LEFT) || (keysHeld & KEY_RSTICK_LEFT)) scrollAmount = -5;
|
||||
|
||||
// Go down
|
||||
if (keysDown & KEY_DOWN) scrollAmount = 1;
|
||||
if (keysDown & KEY_RIGHT) scrollAmount = 5;
|
||||
if ((keysDown & KEY_DDOWN) || (keysHeld & KEY_LSTICK_DOWN) || (keysHeld & KEY_RSTICK_DOWN)) scrollAmount = 1;
|
||||
if ((keysDown & KEY_DRIGHT) || (keysHeld & KEY_LSTICK_RIGHT) || (keysHeld & KEY_RSTICK_RIGHT)) scrollAmount = 5;
|
||||
}
|
||||
|
||||
// Calculate scroll only if the UI state hasn't been changed
|
||||
|
@ -930,6 +887,8 @@ UIResult uiProcess()
|
|||
uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc);
|
||||
|
||||
waitForButtonPress();
|
||||
|
@ -939,11 +898,13 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateDumpRawPartition)
|
||||
{
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption]));
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex]));
|
||||
uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
dumpRawPartition(&fsOperatorInstance, selectedOption, true);
|
||||
uiRefreshDisplay();
|
||||
|
||||
dumpRawPartition(&fsOperatorInstance, selectedPartitionIndex, true);
|
||||
|
||||
waitForButtonPress();
|
||||
|
||||
|
@ -952,11 +913,13 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateDumpPartitionData)
|
||||
{
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption]));
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex]));
|
||||
uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
dumpPartitionData(&fsOperatorInstance, selectedOption);
|
||||
uiRefreshDisplay();
|
||||
|
||||
dumpPartitionData(&fsOperatorInstance, selectedPartitionIndex);
|
||||
|
||||
waitForButtonPress();
|
||||
|
||||
|
@ -965,13 +928,15 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateViewGameCardFsGetList)
|
||||
{
|
||||
uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedOption] : viewGameCardFsType1MenuItems[selectedOption]), 0, breaks * font_height, 115, 115, 255);
|
||||
uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedPartitionIndex] : viewGameCardFsType2MenuItems[selectedPartitionIndex]), 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (mountViewPartition(&fsOperatorInstance, &fs, selectedOption))
|
||||
{
|
||||
enterDirectory("view:/");
|
||||
uiRefreshDisplay();
|
||||
|
||||
if (getHfs0FileList(&fsOperatorInstance, selectedPartitionIndex))
|
||||
{
|
||||
cursor = 0;
|
||||
scroll = 0;
|
||||
res = resultShowViewGameCardFsBrowser;
|
||||
} else {
|
||||
breaks += 2;
|
||||
|
@ -981,45 +946,13 @@ UIResult uiProcess()
|
|||
} else
|
||||
if (uiState == stateViewGameCardFsBrowserCopyFile)
|
||||
{
|
||||
uiDrawString("Manual File Dump", 0, breaks * font_height, 115, 115, 255);
|
||||
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Manual File Dump: %s (Partition %u [%s])", filenames[selectedFileIndex], selectedPartitionIndex, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, selectedPartitionIndex));
|
||||
uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255);
|
||||
breaks += 2;
|
||||
|
||||
FILE *inFile = fopen(fileCopyPath, "rb");
|
||||
if (inFile)
|
||||
{
|
||||
fseek(inFile, 0L, SEEK_END);
|
||||
u64 input_filesize = ftell(inFile);
|
||||
fclose(inFile);
|
||||
uiRefreshDisplay();
|
||||
|
||||
if (input_filesize <= freeSpace)
|
||||
{
|
||||
char destCopyPath[NAME_BUF_LEN] = {'\0'};
|
||||
|
||||
for(i = (strlen(fileCopyPath) - 1); i >= 0; i--)
|
||||
{
|
||||
if (fileCopyPath[i] == '/')
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawString("Hold B to cancel.", 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
|
||||
copyFile(fileCopyPath, destCopyPath, true, true);
|
||||
} else {
|
||||
uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
} else {
|
||||
uiDrawString("Error: unable to get input file size.", 0, breaks * font_height, 255, 0, 0);
|
||||
}
|
||||
dumpFileFromPartition(&fsOperatorInstance, selectedPartitionIndex, selectedFileIndex, filenames[selectedFileIndex]);
|
||||
|
||||
breaks += 2;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
@ -53,8 +54,6 @@ static char *result_buf = NULL;
|
|||
static size_t result_sz = 0;
|
||||
static size_t result_written = 0;
|
||||
|
||||
char currentDirectory[NAME_BUF_LEN] = {'\0'};
|
||||
|
||||
char *filenameBuffer = NULL;
|
||||
char *filenames[FILENAME_MAX_CNT];
|
||||
int filenamesCount = 0;
|
||||
|
@ -65,6 +64,8 @@ Handle fsGameCardEventHandle;
|
|||
Event fsGameCardKernelEvent;
|
||||
UEvent exitEvent;
|
||||
|
||||
AppletType programAppletType;
|
||||
|
||||
bool gameCardInserted;
|
||||
|
||||
u64 gameCardSize = 0, trimmedCardSize = 0;
|
||||
|
@ -75,7 +76,8 @@ u64 hfs0_offset = 0, hfs0_size = 0;
|
|||
u32 hfs0_partition_cnt = 0;
|
||||
|
||||
char *partitionHfs0Header = NULL;
|
||||
u64 partitionHfs0HeaderSize = 0;
|
||||
u64 partitionHfs0HeaderOffset = 0, partitionHfs0HeaderSize = 0;
|
||||
u32 partitionHfs0FileCount = 0, partitionHfs0StrTableSize = 0;
|
||||
|
||||
u64 gameCardTitleID = 0;
|
||||
u32 gameCardVersion = 0;
|
||||
|
@ -287,7 +289,10 @@ void loadGameCardInfo()
|
|||
{
|
||||
free(partitionHfs0Header);
|
||||
partitionHfs0Header = NULL;
|
||||
partitionHfs0HeaderOffset = 0;
|
||||
partitionHfs0HeaderSize = 0;
|
||||
partitionHfs0FileCount = 0;
|
||||
partitionHfs0StrTableSize = 0;
|
||||
}
|
||||
|
||||
gameCardTitleID = 0;
|
||||
|
@ -379,76 +384,44 @@ void waitForButtonPress()
|
|||
{
|
||||
hidScanInput();
|
||||
u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
if (keysDown && !(keysDown & KEY_TOUCH)) break;
|
||||
if (keysDown && !((keysDown & KEY_TOUCH) || (keysDown & KEY_LSTICK_LEFT) || (keysDown & KEY_LSTICK_RIGHT) || (keysDown & KEY_LSTICK_UP) || (keysDown & KEY_LSTICK_DOWN) || \
|
||||
(keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_RSTICK_DOWN))) break;
|
||||
}
|
||||
}
|
||||
|
||||
bool isDirectory(char *path)
|
||||
void addStringToFilenameBuffer(const char *string, char **nextFilename)
|
||||
{
|
||||
DIR* dir = opendir(path);
|
||||
if (!dir) return false;
|
||||
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
|
||||
void addString(char **filenames, int *filenamesCount, char **nextFilename, const char *string)
|
||||
{
|
||||
filenames[(*filenamesCount)++] = *nextFilename;
|
||||
filenames[filenamesCount++] = *nextFilename;
|
||||
strcpy(*nextFilename, string);
|
||||
*nextFilename += strlen(string) + 1;
|
||||
*nextFilename += (strlen(string) + 1);
|
||||
}
|
||||
|
||||
static int sortAlpha(const void* a, const void* b)
|
||||
{
|
||||
return strcasecmp(*((const char**)a), *((const char**)b));
|
||||
}
|
||||
|
||||
void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenamesCount, const char *directory, bool skipParent)
|
||||
void removeDirectory(const char *path)
|
||||
{
|
||||
struct dirent* ent;
|
||||
int i, maxFilenamesCount = *filenamesCount;
|
||||
char *nextFilename = filenameBuffer;
|
||||
char cur_path[NAME_BUF_LEN] = {'\0'};
|
||||
|
||||
char *slash = (char*)malloc(strlen(directory) + 2);
|
||||
memset(slash, 0, strlen(directory) + 2);
|
||||
snprintf(slash, strlen(directory) + 2, "%s/", directory);
|
||||
|
||||
*filenamesCount = 0;
|
||||
|
||||
if (!skipParent) addString(filenames, filenamesCount, &nextFilename, "..");
|
||||
|
||||
DIR* dir = opendir(slash);
|
||||
DIR *dir = opendir(path);
|
||||
if (dir)
|
||||
{
|
||||
for(i = 0; i < maxFilenamesCount; i++)
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
ent = readdir(dir);
|
||||
if (!ent) break;
|
||||
|
||||
if ((strlen(ent->d_name) == 1 && !strcmp(ent->d_name, ".")) || (strlen(ent->d_name) == 2 && !strcmp(ent->d_name, ".."))) continue;
|
||||
|
||||
addString(filenames, filenamesCount, &nextFilename, ent->d_name);
|
||||
snprintf(cur_path, sizeof(cur_path) / sizeof(cur_path[0]), "%s/%s", path, ent->d_name);
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
removeDirectory(cur_path);
|
||||
} else {
|
||||
remove(cur_path);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
rmdir(path);
|
||||
}
|
||||
|
||||
free(slash);
|
||||
|
||||
// ".." should stay at the top
|
||||
qsort(filenames + 1, (*filenamesCount) - 1, sizeof(char*), &sortAlpha);
|
||||
}
|
||||
|
||||
void enterDirectory(const char *path)
|
||||
{
|
||||
snprintf(currentDirectory, sizeof(currentDirectory) / sizeof(currentDirectory[0]), "%s", path);
|
||||
|
||||
filenamesCount = FILENAME_MAX_CNT;
|
||||
getDirectoryContents(filenameBuffer, &filenames[0], &filenamesCount, currentDirectory, (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6));
|
||||
|
||||
cursor = 0;
|
||||
scroll = 0;
|
||||
}
|
||||
|
||||
bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc)
|
||||
|
@ -672,7 +645,13 @@ void updateNSWDBXml()
|
|||
{
|
||||
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Downloading XML database from \"%s\", please wait...", nswReleasesXmlUrl);
|
||||
uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255);
|
||||
breaks++;
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
|
@ -930,6 +909,12 @@ void updateApplication()
|
|||
uiDrawString("Please wait...", 0, breaks * font_height, 255, 255, 255);
|
||||
breaks += 2;
|
||||
|
||||
if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication)
|
||||
{
|
||||
uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0);
|
||||
breaks += 2;
|
||||
}
|
||||
|
||||
uiRefreshDisplay();
|
||||
|
||||
gcDumpToolNro = fopen(gcDumpToolTmpPath, "wb");
|
||||
|
|
|
@ -26,12 +26,8 @@ void fsGameCardDetectionThreadFunc(void *arg);
|
|||
|
||||
void delay(u8 seconds);
|
||||
|
||||
bool getGameCardTitleIDAndVersion(u64 *titleID, u32 *version);
|
||||
|
||||
void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize);
|
||||
|
||||
bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize);
|
||||
|
||||
void removeIllegalCharacters(char *name);
|
||||
|
||||
void strtrim(char *str);
|
||||
|
@ -44,13 +40,9 @@ void convertSize(u64 size, char *out, int bufsize);
|
|||
|
||||
void waitForButtonPress();
|
||||
|
||||
bool isDirectory(char *path);
|
||||
void addStringToFilenameBuffer(const char *string, char **nextFilename);
|
||||
|
||||
void addString(char **filenames, int *filenamesCount, char **nextFilename, const char *string);
|
||||
|
||||
void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenamesCount, const char *directory, bool skipParent);
|
||||
|
||||
void enterDirectory(const char *path);
|
||||
void removeDirectory(const char *path);
|
||||
|
||||
void gameCardDumpNSWDBCheck(u32 crc);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue