diff --git a/Makefile b/Makefile index 1fccd92..72fe880 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \ $(ARCH) $(DEFINES) CFLAGS += $(INCLUDE) -D__SWITCH__ -D__LINUX_ERRNO_EXTENSIONS__ +CFLAGS += `freetype-config --cflags` CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags` CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags` CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags` @@ -65,7 +66,7 @@ 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 := -lcurl -lxml2 -lz -lnx -ljson-c -lm +LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lnx -ljson-c -lm `freetype-config --libs` #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/source/crc32_fast.c b/source/crc32_fast.c index 8f25ec2..8ea8321 100644 --- a/source/crc32_fast.c +++ b/source/crc32_fast.c @@ -13,8 +13,8 @@ u32 crc32_for_byte(u32 r) { - for(int j = 0; j < 8; ++j) r = (r & 1? 0: (u32)0xEDB88320L) ^ r >> 1; - return r ^ (u32)0xFF000000L; + for(int j = 0; j < 8; ++j) r = (r & 1? 0: (u32)0xEDB88320L) ^ r >> 1; + return r ^ (u32)0xFF000000L; } /* Any unsigned integer type with at least 32 bits may be used as @@ -24,29 +24,29 @@ typedef unsigned long accum_t; void init_tables(u32* table, u32* wtable) { - for(u64 i = 0; i < 0x100; ++i) table[i] = crc32_for_byte(i); - - for(u64 k = 0; k < sizeof(accum_t); ++k) - { - for(u64 w, i = 0; i < 0x100; ++i) - { - for(u64 j = w = 0; j < sizeof(accum_t); ++j) w = table[(u8)(j == k? w ^ i: w)] ^ w >> 8; - wtable[(k << 8) + i] = w ^ (k? wtable[0]: 0); - } - } + for(u64 i = 0; i < 0x100; ++i) table[i] = crc32_for_byte(i); + + for(u64 k = 0; k < sizeof(accum_t); ++k) + { + for(u64 w, i = 0; i < 0x100; ++i) + { + for(u64 j = w = 0; j < sizeof(accum_t); ++j) w = table[(u8)(j == k? w ^ i: w)] ^ w >> 8; + wtable[(k << 8) + i] = w ^ (k? wtable[0]: 0); + } + } } void crc32(const void* data, u64 n_bytes, u32* crc) { - static u32 table[0x100], wtable[0x100*sizeof(accum_t)]; - u64 n_accum = n_bytes / sizeof(accum_t); - - if (!*table) init_tables(table, wtable); - for(u64 i = 0; i < n_accum; ++i) - { - accum_t a = *crc ^ ((accum_t*)data)[i]; - for(u64 j = *crc = 0; j < sizeof(accum_t); ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8*j)]; - } - - for(u64 i = n_accum*sizeof(accum_t); i < n_bytes; ++i) *crc = table[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8; + static u32 table[0x100], wtable[0x100*sizeof(accum_t)]; + u64 n_accum = n_bytes / sizeof(accum_t); + + if (!*table) init_tables(table, wtable); + for(u64 i = 0; i < n_accum; ++i) + { + accum_t a = *crc ^ ((accum_t*)data)[i]; + for(u64 j = *crc = 0; j < sizeof(accum_t); ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8*j)]; + } + + for(u64 i = n_accum*sizeof(accum_t); i < n_bytes; ++i) *crc = table[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8; } diff --git a/source/dumper.c b/source/dumper.c index 084f4fb..f24677e 100644 --- a/source/dumper.c +++ b/source/dumper.c @@ -16,8 +16,7 @@ extern u64 freeSpace; extern int breaks; - -extern u32 currentFBWidth, currentFBHeight; +extern int font_height; extern u64 gameCardSize, trimmedCardSize; extern char gameCardSizeStr[32], trimmedCardSizeStr[32]; @@ -26,8 +25,8 @@ extern char *hfs0_header; extern u64 hfs0_offset, hfs0_size; extern u32 hfs0_partition_cnt; -//extern char *partitionHfs0Header; -//extern u64 partitionHfs0HeaderSize; +extern char *partitionHfs0Header; +extern u64 partitionHfs0HeaderSize; extern u64 gameCardTitleID; extern u32 gameCardVersion; @@ -41,1619 +40,1660 @@ static char strbuf[NAME_BUF_LEN * 2] = {'\0'}; void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator) { - u32 handle; - if (R_FAILED(fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) return; - - FsStorage gameCardStorage; - if (R_FAILED(fsOpenGameCardStorage(&gameCardStorage, handle, 0))) return; - - fsStorageClose(&gameCardStorage); + FsGameCardHandle handle; + if (R_FAILED(fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) return; + + FsStorage gameCardStorage; + if (R_FAILED(fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) return; + + fsStorageClose(&gameCardStorage); } bool getRootHfs0Header(FsDeviceOperator* fsOperator) { - u32 handle, magic; - Result result; - FsStorage gameCardStorage; - - hfs0_partition_cnt = 0; - - workaroundPartitionZeroAccess(fsOperator); - - if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) - { - uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result); - 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); - return false; - } - - char *gamecard_header = (char*)malloc(GAMECARD_HEADER_SIZE); - if (!gamecard_header) - { - uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the gamecard header!"); - fsStorageClose(&gameCardStorage); - return false; - } - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE))) - { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result); - - free(gamecard_header); - - fsStorageClose(&gameCardStorage); - - return false; - } - - u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR]; - - switch(cardSize) - { - case 0xFA: // 1 GiB - gameCardSize = GAMECARD_SIZE_1GiB; - break; - case 0xF8: // 2 GiB - gameCardSize = GAMECARD_SIZE_2GiB; - break; - case 0xF0: // 4 GiB - gameCardSize = GAMECARD_SIZE_4GiB; - break; - case 0xE0: // 8 GiB - gameCardSize = GAMECARD_SIZE_8GiB; - break; - case 0xE1: // 16 GiB - gameCardSize = GAMECARD_SIZE_16GiB; - break; - case 0xE2: // 32 GiB - gameCardSize = GAMECARD_SIZE_32GiB; - break; - default: - uiStatusMsg("getRootHfs0Header: Invalid game card size value: 0x%02X", cardSize); - - free(gamecard_header); - - fsStorageClose(&gameCardStorage); - - return false; - } - - convertSize(gameCardSize, gameCardSizeStr, sizeof(gameCardSizeStr) / sizeof(gameCardSizeStr[0])); - - memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64)); - trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE)); - convertSize(trimmedCardSize, trimmedCardSizeStr, sizeof(trimmedCardSizeStr) / sizeof(trimmedCardSizeStr[0])); - - memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64)); - memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64)); - - free(gamecard_header); - - /*uiStatusMsg("getRootHfs0Header: Root HFS0 offset: 0x%016lX", hfs0_offset); - delay(1); - - uiStatusMsg("getRootHfs0Header: Root HFS0 size: 0x%016lX", hfs0_size); - delay(1);*/ - - hfs0_header = (char*)malloc(hfs0_size); - if (!hfs0_header) - { - uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!"); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size))) - { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - memcpy(&magic, hfs0_header, sizeof(u32)); - magic = bswap_32(magic); - - if (magic != HFS0_MAGIC) - { - uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32)); - - fsStorageClose(&gameCardStorage); - return true; + u32 magic; + Result result; + FsGameCardHandle handle; + FsStorage gameCardStorage; + + hfs0_partition_cnt = 0; + + workaroundPartitionZeroAccess(fsOperator); + + if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) + { + uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result); + 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; + case SYSUPDATE_600: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_601: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_610: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.1.0 - v%s", decimalVersion); + break; + case SYSUPDATE_620: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.2.0 - v%s", decimalVersion); + break; + case SYSUPDATE_700: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_701: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_800: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "8.0.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); + return false; + } + + char *gamecard_header = (char*)malloc(GAMECARD_HEADER_SIZE); + if (!gamecard_header) + { + uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the gamecard header!"); + fsStorageClose(&gameCardStorage); + return false; + } + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE))) + { + uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result); + free(gamecard_header); + fsStorageClose(&gameCardStorage); + return false; + } + + u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR]; + + switch(cardSize) + { + case 0xFA: // 1 GiB + gameCardSize = GAMECARD_SIZE_1GiB; + break; + case 0xF8: // 2 GiB + gameCardSize = GAMECARD_SIZE_2GiB; + break; + case 0xF0: // 4 GiB + gameCardSize = GAMECARD_SIZE_4GiB; + break; + case 0xE0: // 8 GiB + gameCardSize = GAMECARD_SIZE_8GiB; + break; + case 0xE1: // 16 GiB + gameCardSize = GAMECARD_SIZE_16GiB; + break; + case 0xE2: // 32 GiB + gameCardSize = GAMECARD_SIZE_32GiB; + break; + default: + uiStatusMsg("getRootHfs0Header: Invalid game card size value: 0x%02X", cardSize); + free(gamecard_header); + fsStorageClose(&gameCardStorage); + return false; + } + + convertSize(gameCardSize, gameCardSizeStr, sizeof(gameCardSizeStr) / sizeof(gameCardSizeStr[0])); + + memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64)); + trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE)); + convertSize(trimmedCardSize, trimmedCardSizeStr, sizeof(trimmedCardSizeStr) / sizeof(trimmedCardSizeStr[0])); + + memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64)); + memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64)); + + free(gamecard_header); + + hfs0_header = (char*)malloc(hfs0_size); + if (!hfs0_header) + { + uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!"); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + hfs0_offset = hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size))) + { + uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + free(hfs0_header); + hfs0_header = NULL; + hfs0_offset = hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + memcpy(&magic, hfs0_header, sizeof(u32)); + magic = bswap_32(magic); + if (magic != HFS0_MAGIC) + { + uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + free(hfs0_header); + hfs0_header = NULL; + hfs0_offset = hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32)); + + fsStorageClose(&gameCardStorage); + + return true; } bool getHsf0PartitionDetails(u32 partition, u64 *out_offset, u64 *out_size) { - if (hfs0_header == NULL) return false; - - if (partition > (hfs0_partition_cnt - 1)) return false; - - hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * hfs0_partition_cnt); - if (!entryTable) return false; - - memcpy(entryTable, hfs0_header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * hfs0_partition_cnt); - - switch(partition) - { - 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); - break; - case 2: - if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT) - { - // secure (contained within IStorage instance with partition ID 1) - *out_offset = 0; - } else { - // normal (contained within IStorage instance with partition ID 0) - *out_offset = (hfs0_offset + hfs0_size + entryTable[partition].file_offset); - } - break; - case 3: // secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1) - *out_offset = 0; - break; - default: - break; - } - - *out_size = entryTable[partition].file_size; - - free(entryTable); - - return true; + if (hfs0_header == NULL) return false; + + if (partition > (hfs0_partition_cnt - 1)) return false; + + hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * hfs0_partition_cnt); + if (!entryTable) return false; + + memcpy(entryTable, hfs0_header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * hfs0_partition_cnt); + + switch(partition) + { + 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); + break; + case 2: + if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT) + { + // secure (contained within IStorage instance with partition ID 1) + *out_offset = 0; + } else { + // normal (contained within IStorage instance with partition ID 0) + *out_offset = (hfs0_offset + hfs0_size + entryTable[partition].file_offset); + } + break; + case 3: // secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1) + *out_offset = 0; + break; + default: + break; + } + + *out_size = entryTable[partition].file_size; + + free(entryTable); + + return true; } -/*bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition) +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; -}*/ + 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; - u64 partitionSizes[ISTORAGE_PARTITION_CNT]; - 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; - bool proceed = true, success = false; - FILE *outFile = NULL; - char *buf = NULL; - u8 splitIndex = 0; - u8 progress = 0; - u32 crc1 = 0, crc2 = 0; - - u64 start, now, remainingTime; - struct tm *timeinfo; - char etaInfo[32] = {'\0'}; - double lastSpeed = 0.0, averageSpeed = 0.0; - - for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Getting partition #%u size...", partition); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - 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++; - - if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, handle, partition))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (R_SUCCEEDED(result = fsStorageGetSize(&gameCardStorage, &(partitionSizes[partition])))) - { - xciDataSize += partitionSizes[partition]; - convertSize(partitionSizes[partition], partitionSizesStr[partition], sizeof(partitionSizesStr[partition]) / sizeof(partitionSizesStr[partition][0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u size: %s (%lu bytes)", partition, partitionSizesStr[partition], partitionSizes[partition]); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks += 2; - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageGetSize failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - - fsStorageClose(&gameCardStorage); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - - syncDisplay(); - } - - 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); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks += 2; - - if (trimDump) - { - totalSize = trimmedCardSize; - snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", trimmedCardSizeStr); - - // Change dump size for the last IStorage partition - u64 partitionSizesSum = 0; - for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i]; - - partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (trimmedCardSize - partitionSizesSum); - } else { - totalSize = xciDataSize; - snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr); - } - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Output dump size: %s (%lu bytes)", totalSizeStr, totalSize); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (totalSize <= freeSpace) - { - breaks++; - - if (totalSize > SPLIT_FILE_MIN && isFat32) - { - 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:/%s v%u (%016lX).xci", fixedGameCardName, gameCardVersion, gameCardTitleID); - } - - outFile = fopen(filename, "wb"); - if (outFile) - { - buf = (char*)malloc(DUMP_BUFFER_SIZE); - if (buf) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Writing output to \"%.*s\". Hold B to cancel.", (int)((totalSize > SPLIT_FILE_MIN && isFat32) ? (strlen(filename) - 3) : strlen(filename)), filename); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks += 2; - - timeGetCurrentTime(TimeType_LocalSystemClock, &start); - - for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) - { - n = DUMP_BUFFER_SIZE; - - uiFill(0, breaks * 8, currentFBWidth, 8, 50, 50, 50); - uiFill(0, (breaks + 3) * 8, currentFBWidth, 16, 50, 50, 50); - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping partition #%u...", partition); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - - workaroundPartitionZeroAccess(fsOperator); - - if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) - { - if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, handle, partition))) - { - for(partitionOffset = 0; partitionOffset < partitionSizes[partition]; partitionOffset += n, fileOffset += n) - { - if (DUMP_BUFFER_SIZE > (partitionSizes[partition] - partitionOffset)) n = (partitionSizes[partition] - partitionOffset); - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset, buf, n))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX for partition #%u", result, partitionOffset, partition); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - - // Remove game card certificate - if (fileOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE); - - if (calcCrc) - { - if (!trimDump) - { - if (dumpCert) - { - if (fileOffset == 0) - { - // Update CRC32 (with gamecard certificate) - crc32(buf, n, &crc1); - - // Backup gamecard certificate to an array - char tmpCert[CERT_SIZE] = {'\0'}; - memcpy(tmpCert, buf + CERT_OFFSET, CERT_SIZE); - - // Remove gamecard certificate from buffer - memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE); - - // Update CRC32 (without gamecard certificate) - crc32(buf, n, &crc2); - - // Restore gamecard certificate to buffer - memcpy(buf + CERT_OFFSET, tmpCert, CERT_SIZE); - } else { - // Update CRC32 (with gamecard certificate) - crc32(buf, n, &crc1); - - // Update CRC32 (without gamecard certificate) - crc32(buf, n, &crc2); - } - } else { - // Update CRC32 - crc32(buf, n, &crc2); - } - } else { - // Update CRC32 - crc32(buf, n, &crc1); - } - } - - if (totalSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) - { - u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); - u64 old_file_chunk_size = (n - new_file_chunk_size); - - if (old_file_chunk_size > 0) - { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - } - - fclose(outFile); - - splitIndex++; - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex); - - outFile = fopen(filename, "wb"); - if (!outFile) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - - if (new_file_chunk_size > 0) - { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset + old_file_chunk_size); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - } - } else { - if (fwrite(buf, 1, n, outFile) != n) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - } - - timeGetCurrentTime(TimeType_LocalSystemClock, &now); - - lastSpeed = (((double)(fileOffset + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); - averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); - if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values - - remainingTime = (u64)(((double)(totalSize - (fileOffset + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); - timeinfo = localtime((time_t*)&remainingTime); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - - progress = (u8)(((fileOffset + n) * 100) / totalSize); - - uiFill(0, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); - uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), ((breaks + 3) * 8) + 4, 255, 255, 255); - - uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0); - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / totalSize), 16, 0, 255, 0); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - convertSize(fileOffset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255); - - syncDisplay(); - - if ((fileOffset + n) < totalSize && ((fileOffset / DUMP_BUFFER_SIZE) % 10) == 0) - { - hidScanInput(); - - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - if (keysDown & KEY_B) - { - uiDrawString("Process canceled", 0, (breaks + 7) * 8, 255, 0, 0); - proceed = false; - break; - } - } - } - - if (fileOffset >= totalSize) success = true; - - // Support empty files - if (!partitionSizes[partition]) - { - uiFill(currentFBWidth / 4, (breaks + 3) * 8, ((fileOffset * (currentFBWidth / 2)) / totalSize), 16, 0, 255, 0); - - convertSize(fileOffset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255); - - syncDisplay(); - } - - if (!proceed) - { - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((fileOffset + n) * (currentFBWidth / 2)) / totalSize), 16, 255, 0, 0); - breaks += 7; - } - - fsStorageClose(&gameCardStorage); - } else { - breaks++; - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed for partition #%u! (0x%08X)", partition, result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - } else { - breaks++; - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed for partition #%u! (0x%08X)", partition, result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - proceed = false; - } - - if (!proceed) break; - } - - free(buf); - } else { - uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0); - } - - if (success) - { - fclose(outFile); - - breaks += 7; - - now -= start; - timeinfo = localtime((time_t*)&now); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - - if (calcCrc) - { - breaks++; - - if (!trimDump) - { - if (dumpCert) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (with certificate): %08X", crc1); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (without certificate): %08X", crc2); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc2); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - } - - breaks += 2; - uiDrawString("Starting verification process using XML database from nswdb.com...", 0, breaks * 8, 255, 255, 255); - breaks++; - - gameCardDumpNSWDBCheck(crc2); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - breaks++; - - uiDrawString("Dump verification disabled (not compatible with trimmed dumps).", 0, breaks * 8, 255, 255, 255); - } - } - } else { - if (outFile) fclose(outFile); - - if (totalSize > SPLIT_FILE_MIN && isFat32) - { - for(u8 i = 0; i <= splitIndex; i++) - { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, i); - remove(filename); - } - } else { - remove(filename); - } - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - } else { - uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0); - } - } - - breaks += 2; - - return success; + 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[NAME_BUF_LEN] = {'\0'}; + u32 partition; + Result result; + FsGameCardHandle handle; + FsStorage gameCardStorage; + bool proceed = true, success = false; + FILE *outFile = NULL; + char *buf = NULL; + u8 splitIndex = 0; + u8 progress = 0; + u32 crc1 = 0, crc2 = 0; + + u64 start, now, remainingTime; + struct tm *timeinfo; + char etaInfo[32] = {'\0'}; + double lastSpeed = 0.0, averageSpeed = 0.0; + + for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Getting partition #%u size...", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + 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 = fsOpenGameCardStorage(&gameCardStorage, &handle, 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 (R_SUCCEEDED(result = fsStorageGetSize(&gameCardStorage, &(partitionSizes[partition])))) + { + xciDataSize += partitionSizes[partition]; + convertSize(partitionSizes[partition], partitionSizesStr[partition], sizeof(partitionSizesStr[partition]) / sizeof(partitionSizesStr[partition][0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u size: %s (%lu bytes)", partition, partitionSizesStr[partition], partitionSizes[partition]); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks += 2; + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageGetSize failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + } + + fsStorageClose(&gameCardStorage); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + } + + uiRefreshDisplay(); + } + + 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); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks += 2; + + if (trimDump) + { + totalSize = trimmedCardSize; + snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", trimmedCardSizeStr); + + // Change dump size for the last IStorage partition + u64 partitionSizesSum = 0; + for(int i = 0; i < (ISTORAGE_PARTITION_CNT - 1); i++) partitionSizesSum += partitionSizes[i]; + + partitionSizes[ISTORAGE_PARTITION_CNT - 1] = (trimmedCardSize - partitionSizesSum); + } else { + totalSize = xciDataSize; + snprintf(totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0]), "%s", xciDataSizeStr); + } + + 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++; + + if (totalSize <= freeSpace) + { + breaks++; + + if (totalSize > SPLIT_FILE_MIN && isFat32) + { + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex); + } else { + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci", fixedGameCardName, gameCardVersion, gameCardTitleID); + } + + outFile = fopen(filename, "wb"); + if (outFile) + { + buf = (char*)malloc(DUMP_BUFFER_SIZE); + if (buf) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Writing output to \"%s\". Hold B to cancel.", filename); + uiDrawString(strbuf, 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; + + timeGetCurrentTime(TimeType_LocalSystemClock, &start); + + for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) + { + n = DUMP_BUFFER_SIZE; + + uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping partition #%u...", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + + workaroundPartitionZeroAccess(fsOperator); + + if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) + { + if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, partition))) + { + for(partitionOffset = 0; partitionOffset < partitionSizes[partition]; partitionOffset += n, fileOffset += n) + { + if (DUMP_BUFFER_SIZE > (partitionSizes[partition] - partitionOffset)) n = (partitionSizes[partition] - partitionOffset); + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset, buf, n))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX for partition #%u", result, partitionOffset, partition); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + + // Remove game card certificate + if (fileOffset == 0 && !dumpCert) memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE); + + if (calcCrc) + { + if (!trimDump) + { + if (dumpCert) + { + if (fileOffset == 0) + { + // Update CRC32 (with gamecard certificate) + crc32(buf, n, &crc1); + + // Backup gamecard certificate to an array + char tmpCert[CERT_SIZE] = {'\0'}; + memcpy(tmpCert, buf + CERT_OFFSET, CERT_SIZE); + + // Remove gamecard certificate from buffer + memset(buf + CERT_OFFSET, 0xFF, CERT_SIZE); + + // Update CRC32 (without gamecard certificate) + crc32(buf, n, &crc2); + + // Restore gamecard certificate to buffer + memcpy(buf + CERT_OFFSET, tmpCert, CERT_SIZE); + } else { + // Update CRC32 (with gamecard certificate) + crc32(buf, n, &crc1); + + // Update CRC32 (without gamecard certificate) + crc32(buf, n, &crc2); + } + } else { + // Update CRC32 + crc32(buf, n, &crc2); + } + } else { + // Update CRC32 + crc32(buf, n, &crc1); + } + } + + if (totalSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + { + u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 old_file_chunk_size = (n - new_file_chunk_size); + + if (old_file_chunk_size > 0) + { + if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + + fclose(outFile); + + splitIndex++; + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex); + + outFile = fopen(filename, "wb"); + if (!outFile) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + + if (new_file_chunk_size > 0) + { + if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset + old_file_chunk_size); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + } else { + if (fwrite(buf, 1, n, outFile) != n) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + + timeGetCurrentTime(TimeType_LocalSystemClock, &now); + + lastSpeed = (((double)(fileOffset + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); + averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); + if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values + + remainingTime = (u64)(((double)(totalSize - (fileOffset + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); + timeinfo = localtime((time_t*)&remainingTime); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + + progress = (u8)(((fileOffset + n) * 100) / totalSize); + + uiFill(0, ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); + uiDrawString(strbuf, font_height * 2, (breaks + 2) * font_height, 255, 255, 255); + + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0); + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((fileOffset + n) * (FB_WIDTH / 2)) / totalSize), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(fileOffset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + if ((fileOffset + n) < totalSize && ((fileOffset / DUMP_BUFFER_SIZE) % 10) == 0) + { + hidScanInput(); + + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown & KEY_B) + { + uiDrawString("Process canceled", 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + } + + if (fileOffset >= totalSize) success = true; + + // Support empty files + if (!partitionSizes[partition]) + { + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, ((fileOffset * (FB_WIDTH / 2)) / totalSize), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(fileOffset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + } + + if (!proceed) + { + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((fileOffset + n) * (FB_WIDTH / 2)) / totalSize), font_height, 255, 0, 0); + breaks += 5; + } + + fsStorageClose(&gameCardStorage); + } else { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed for partition #%u! (0x%08X)", partition, result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + } + } else { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed for partition #%u! (0x%08X)", partition, result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + } + + if (!proceed) break; + } + + free(buf); + } else { + uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0); + } + + if (success) + { + fclose(outFile); + + breaks += 5; + + now -= start; + timeinfo = localtime((time_t*)&now); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + + if (calcCrc) + { + breaks++; + + if (!trimDump) + { + if (dumpCert) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (with certificate): %08X", crc1); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum (without certificate): %08X", crc2); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc2); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + } + + breaks += 2; + uiDrawString("Starting verification process using XML database from NSWDB.COM...", 0, breaks * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + gameCardDumpNSWDBCheck(crc2); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XCI dump CRC32 checksum: %08X", crc1); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + uiDrawString("Dump verification disabled (not compatible with trimmed dumps).", 0, breaks * font_height, 255, 255, 255); + } + } + } else { + if (outFile) fclose(outFile); + + if (totalSize > SPLIT_FILE_MIN && isFat32) + { + for(u8 i = 0; i <= splitIndex; i++) + { + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, i); + remove(filename); + } + } else { + remove(filename); + } + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0); + } + } + + breaks += 2; + + return success; } bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting) { - u32 handle; - Result result; - u64 size, partitionOffset; - bool success = false; - char *buf; - u64 off, n = DUMP_BUFFER_SIZE; - FsStorage gameCardStorage; - char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'}; - u8 progress = 0; - FILE *outFile = NULL; - u8 splitIndex = 0; - - u64 start, now, remainingTime; - struct tm *timeinfo; - char etaInfo[32] = {'\0'}; - double lastSpeed = 0.0, averageSpeed = 0.0; - - 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++; - - // 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) - // * 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 - // 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))))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (getHsf0PartitionDetails(partition, &partitionOffset, &size)) - { - convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes)", totalSizeStr, size); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition offset (relative to IStorage instance): 0x%016lX", partitionOffset); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (size <= freeSpace) - { - if (size > SPLIT_FILE_MIN && doSplitting) - { - 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:/%s v%u (%016lX) - Partition %u (%s).hfs0", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); - } - - outFile = fopen(filename, "wb"); - if (outFile) - { - buf = (char*)malloc(DUMP_BUFFER_SIZE); - if (buf) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u to \"%.*s\". Hold B to cancel.", partition, (int)((size > SPLIT_FILE_MIN && doSplitting) ? (strlen(filename) - 3) : strlen(filename)), filename); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks += 3; - - syncDisplay(); - - timeGetCurrentTime(TimeType_LocalSystemClock, &start); - - for (off = 0; off < size; off += n) - { - if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off); - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset + off, buf, n))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset + off); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - - if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) - { - u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); - u64 old_file_chunk_size = (n - new_file_chunk_size); - - if (old_file_chunk_size > 0) - { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - } - - fclose(outFile); - - 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) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - - if (new_file_chunk_size > 0) - { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - } - } else { - if (fwrite(buf, 1, n, outFile) != n) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - } - - timeGetCurrentTime(TimeType_LocalSystemClock, &now); - - lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); - averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); - if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values - - remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); - timeinfo = localtime((time_t*)&remainingTime); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - - progress = (u8)(((off + n) * 100) / size); - - uiFill(0, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); - uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), (breaks * 8) + 4, 255, 255, 255); - - uiFill(currentFBWidth / 4, breaks * 8, currentFBWidth / 2, 16, 0, 0, 0); - uiFill(currentFBWidth / 4, breaks * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 0, 255, 0); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, 255, 255, 255); - - syncDisplay(); - - if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0) - { - hidScanInput(); - - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - if (keysDown & KEY_B) - { - uiDrawString("Process canceled", 0, (breaks + 4) * 8, 255, 0, 0); - break; - } - } - } - - if (off >= size) success = true; - - // Support empty files - if (!size) - { - uiFill(currentFBWidth / 4, breaks * 8, currentFBWidth / 2, 16, 0, 255, 0); - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "100%% [0 B / 0 B]"); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, (breaks * 8) + 4, 255, 255, 255); - - syncDisplay(); - } - - if (success) - { - now -= start; - timeinfo = localtime((time_t*)&now); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); - uiDrawString(strbuf, 0, (breaks + 4) * 8, 0, 255, 0); - } else { - uiFill(currentFBWidth / 4, breaks * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 255, 0, 0); - } - - breaks += 4; - - free(buf); - } else { - uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0); - } - - if (outFile) fclose(outFile); - - if (!success) - { - if (size > SPLIT_FILE_MIN && doSplitting) - { - for(u8 i = 0; i <= splitIndex; 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 { - remove(filename); - } - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - } else { - uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0); - } - } else { - uiDrawString("Error: unable to get partition details from the root HFS0 header!", 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; + Result result; + u64 size, partitionOffset; + bool success = false; + char *buf; + u64 off, n = DUMP_BUFFER_SIZE; + FsGameCardHandle handle; + FsStorage gameCardStorage; + char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'}; + u8 progress = 0; + FILE *outFile = NULL; + u8 splitIndex = 0; + + u64 start, now, remainingTime; + struct tm *timeinfo; + char etaInfo[32] = {'\0'}; + double lastSpeed = 0.0, averageSpeed = 0.0; + + 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++;*/ + + // 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) + // * 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 + // 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))))) + { + /*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)) + { + convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes)", totalSizeStr, size); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition offset (relative to IStorage instance): 0x%016lX", partitionOffset); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + if (size <= freeSpace) + { + if (size > SPLIT_FILE_MIN && doSplitting) + { + 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:/%s v%u (%016lX) - Partition %u (%s).hfs0", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); + } + + outFile = fopen(filename, "wb"); + if (outFile) + { + buf = (char*)malloc(DUMP_BUFFER_SIZE); + if (buf) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u to \"%.*s\". Hold B to cancel.", partition, (int)((size > SPLIT_FILE_MIN && doSplitting) ? (strlen(filename) - 3) : strlen(filename)), filename); + uiDrawString(strbuf, 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; + + uiRefreshDisplay(); + + timeGetCurrentTime(TimeType_LocalSystemClock, &start); + + for (off = 0; off < size; off += n) + { + if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off); + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, partitionOffset + off, buf, n))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionOffset + off); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + + if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + { + u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 old_file_chunk_size = (n - new_file_chunk_size); + + if (old_file_chunk_size > 0) + { + if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + } + + fclose(outFile); + + 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) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + + if (new_file_chunk_size > 0) + { + if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + } + } else { + if (fwrite(buf, 1, n, outFile) != n) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + } + + timeGetCurrentTime(TimeType_LocalSystemClock, &now); + + lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); + averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); + if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values + + remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); + timeinfo = localtime((time_t*)&remainingTime); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + + progress = (u8)(((off + n) * 100) / size); + + uiFill(0, (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); + uiDrawString(strbuf, font_height * 2, breaks * font_height, 255, 255, 255); + + uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0); + uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), breaks * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0) + { + hidScanInput(); + + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown & KEY_B) + { + uiDrawString("Process canceled", 0, (breaks + 3) * font_height, 255, 0, 0); + break; + } + } + } + + if (off >= size) success = true; + + // Support empty files + if (!size) + { + uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, FB_WIDTH / 2, font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), (breaks * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + uiDrawString("100%% [0 B / 0 B]", FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), breaks * font_height, 255, 255, 255); + + uiRefreshDisplay(); + } + + if (success) + { + now -= start; + timeinfo = localtime((time_t*)&now); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); + uiDrawString(strbuf, 0, (breaks + 3) * font_height, 0, 255, 0); + } else { + uiFill(FB_WIDTH / 4, (breaks * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 255, 0, 0); + } + + breaks += 3; + + free(buf); + } else { + uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0); + } + + if (outFile) fclose(outFile); + + if (!success) + { + if (size > SPLIT_FILE_MIN && doSplitting) + { + for(u8 i = 0; i <= splitIndex; 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 { + remove(filename); + } + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } 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 partition details from the root HFS0 header!", 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); + } + + breaks += 2; + + return success; } bool openPartitionFs(FsFileSystem* ret, FsDeviceOperator* fsOperator, u32 partition) { - u32 handle; - Result result; - bool success = false; - - 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++; - - if (R_SUCCEEDED(result = fsOpenGameCardFileSystem(ret, handle, partition))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem succeeded: 0x%08X", result); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - success = true; - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - breaks += 2; - } - } 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; + FsGameCardHandle handle; + Result result; + bool success = false; + + 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))) + { + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem succeeded: 0x%08X", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++;*/ + success = true; + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardFileSystem 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; + } + + return success; } bool copyFile(const char* source, const char* dest, bool doSplitting, bool calcEta) { - bool success = false; - char splitFilename[NAME_BUF_LEN] = {'\0'}; - size_t destLen = strlen(dest); - FILE *inFile, *outFile; - char *buf = NULL; - u64 size, off, n = DUMP_BUFFER_SIZE; - u8 splitIndex = 0; - u8 progress = 0; - char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}; - - u64 start, now, remainingTime; - struct tm *timeinfo; - char etaInfo[32] = {'\0'}; - double lastSpeed = 0.0, averageSpeed = 0.0; - - uiFill(0, breaks * 8, currentFBWidth, 8, 50, 50, 50); - uiFill(0, (breaks + 3) * 8, currentFBWidth, 16, 50, 50, 50); - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - - if ((destLen + 1) < NAME_BUF_LEN) - { - inFile = fopen(source, "rb"); - if (inFile) - { - fseek(inFile, 0L, SEEK_END); - size = ftell(inFile); - rewind(inFile); - - convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - - if (size > SPLIT_FILE_MIN && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); - - outFile = fopen(((size > SPLIT_FILE_MIN && doSplitting) ? splitFilename : dest), "wb"); - if (outFile) - { - buf = (char*)malloc(DUMP_BUFFER_SIZE); - if (buf) - { - if (calcEta) timeGetCurrentTime(TimeType_LocalSystemClock, &start); - - for (off = 0; off < size; off += n) - { - if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off); - - if (fread(buf, 1, n, inFile) != n) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read chunk from offset 0x%016lX", off); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - - if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) - { - u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); - u64 old_file_chunk_size = (n - new_file_chunk_size); - - if (old_file_chunk_size > 0) - { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - } - - fclose(outFile); - - splitIndex++; - snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); - - outFile = fopen(splitFilename, "wb"); - if (!outFile) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - - if (new_file_chunk_size > 0) - { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - } - } else { - if (fwrite(buf, 1, n, outFile) != n) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); - uiDrawString(strbuf, 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - } - - if (calcEta) - { - timeGetCurrentTime(TimeType_LocalSystemClock, &now); - - lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); - averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); - if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values - - remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); - timeinfo = localtime((time_t*)&remainingTime); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - - uiFill(0, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); - uiDrawString(strbuf, (currentFBWidth / 4) - 8 - (strlen(strbuf) * 8), ((breaks + 3) * 8) + 4, 255, 255, 255); - } - - progress = (u8)(((off + n) * 100) / size); - - uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 0, 0); - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 0, 255, 0); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255); - - syncDisplay(); - - if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0) - { - hidScanInput(); - - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - if (keysDown & KEY_B) - { - uiDrawString("Process canceled", 0, (breaks + 7) * 8, 255, 0, 0); - break; - } - } - } - - if (off >= size) success = true; - - // Support empty files - if (!size) - { - uiFill(currentFBWidth / 4, (breaks + 3) * 8, currentFBWidth / 2, 16, 0, 255, 0); - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "100%% [0 B / 0 B]"); - - uiFill(currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, (currentFBWidth / 4) - 8, 8, 50, 50, 50); - uiDrawString(strbuf, currentFBWidth - (currentFBWidth / 4) + 8, ((breaks + 3) * 8) + 4, 255, 255, 255); - - syncDisplay(); - } - - if (!success) - { - uiFill(currentFBWidth / 4, (breaks + 3) * 8, (((off + n) * (currentFBWidth / 2)) / size), 16, 255, 0, 0); - breaks += 7; - } - - free(buf); - } else { - breaks++; - uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * 8, 255, 0, 0); - } - - if (outFile) fclose(outFile); - - if (success) - { - if (calcEta) - { - breaks += 7; - - now -= start; - timeinfo = localtime((time_t*)&now); - strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); - uiDrawString(strbuf, 0, breaks * 8, 0, 255, 0); - } - } else { - if (size > SPLIT_FILE_MIN && doSplitting) - { - for(u8 i = 0; i <= splitIndex; i++) - { - snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, i); - remove(splitFilename); - } - } else { - remove(dest); - } - } - } else { - breaks++; - uiDrawString("Failed to open output file!", 0, breaks * 8, 255, 255, 255); - } - - fclose(inFile); - } else { - breaks++; - uiDrawString("Failed to open input file!", 0, breaks * 8, 255, 255, 255); - } - } else { - breaks++; - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! \"%s\" (%lu)", dest, destLen); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - return success; + bool success = false; + char splitFilename[NAME_BUF_LEN] = {'\0'}; + size_t destLen = strlen(dest); + FILE *inFile, *outFile; + char *buf = NULL; + u64 size, off, n = DUMP_BUFFER_SIZE; + u8 splitIndex = 0; + u8 progress = 0; + char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}; + + u64 start, now, remainingTime; + struct tm *timeinfo; + char etaInfo[32] = {'\0'}; + double lastSpeed = 0.0, averageSpeed = 0.0; + + uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source); + uiDrawString(strbuf, 0, (breaks + 1) * font_height, 255, 255, 255); + + if ((destLen + 1) < NAME_BUF_LEN) + { + inFile = fopen(source, "rb"); + if (inFile) + { + fseek(inFile, 0L, SEEK_END); + size = ftell(inFile); + rewind(inFile); + + convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); + + if (size > SPLIT_FILE_MIN && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); + + outFile = fopen(((size > SPLIT_FILE_MIN && doSplitting) ? splitFilename : dest), "wb"); + if (outFile) + { + buf = (char*)malloc(DUMP_BUFFER_SIZE); + if (buf) + { + if (calcEta) timeGetCurrentTime(TimeType_LocalSystemClock, &start); + + for (off = 0; off < size; off += n) + { + if (DUMP_BUFFER_SIZE > (size - off)) n = (size - off); + + if (fread(buf, 1, n, inFile) != n) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to read chunk from offset 0x%016lX", off); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + + if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + { + u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 old_file_chunk_size = (n - new_file_chunk_size); + + if (old_file_chunk_size > 0) + { + if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + } + + fclose(outFile); + + splitIndex++; + snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); + + outFile = fopen(splitFilename, "wb"); + if (!outFile) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + + if (new_file_chunk_size > 0) + { + if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + } + } else { + if (fwrite(buf, 1, n, outFile) != n) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + } + + if (calcEta) + { + timeGetCurrentTime(TimeType_LocalSystemClock, &now); + + lastSpeed = (((double)(off + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); + averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); + if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values + + remainingTime = (u64)(((double)(size - (off + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); + timeinfo = localtime((time_t*)&remainingTime); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + + uiFill(0, ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); + uiDrawString(strbuf, font_height * 2, (breaks + 3) * font_height, 255, 255, 255); + } + + progress = (u8)(((off + n) * 100) / size); + + uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0); + uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(off + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 3) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + if ((off + n) < size && ((off / DUMP_BUFFER_SIZE) % 10) == 0) + { + hidScanInput(); + + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown & KEY_B) + { + uiDrawString("Process canceled", 0, (breaks + 5) * font_height, 255, 0, 0); + break; + } + } + } + + if (off >= size) success = true; + + // Support empty files + if (!size) + { + uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 3) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + uiDrawString("100%% [0 B / 0 B]", FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 3) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + } + + if (!success) + { + uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 255, 0, 0); + breaks += 5; + } + + free(buf); + } else { + breaks += 3; + uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0); + } + + if (outFile) fclose(outFile); + + if (success) + { + if (calcEta) + { + breaks += 5; + + now -= start; + timeinfo = localtime((time_t*)&now); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + } + } else { + if (size > SPLIT_FILE_MIN && doSplitting) + { + for(u8 i = 0; i <= splitIndex; i++) + { + snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, i); + remove(splitFilename); + } + } else { + remove(dest); + } + } + } else { + breaks += 3; + uiDrawString("Failed to open output file!", 0, breaks * font_height, 255, 255, 255); + } + + fclose(inFile); + } else { + breaks += 3; + uiDrawString("Failed to open input file!", 0, breaks * font_height, 255, 255, 255); + } + } else { + breaks += 3; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination path is too long! \"%s\" (%lu)", dest, 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) { - struct dirent* ent; - bool success = true; - - DIR *dir = opendir(sbuf); - if (dir) - { - 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 * 8, 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 * 8, 255, 0, 0); - breaks++; - success = false; - } - - return success; + struct dirent* ent; + bool success = true; + + DIR *dir = opendir(sbuf); + if (dir) + { + 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 * 8, 255, 0, 0); - breaks++; - return false; - } - - if (dest_len + 1 >= NAME_BUF_LEN) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Destination directory name too long! \"%s\" (%lu)", dest, dest_len); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - breaks++; - return false; - } - - strcpy(sbuf, source); - strcpy(dbuf, dest); - - mkdir(dbuf, 0744); - - return _copyDirectory(sbuf, source_len, dbuf, dest_len, 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++; + return false; + } + + if (dest_len + 1 >= NAME_BUF_LEN) + { + 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++; + return false; + } + + strcpy(sbuf, source); + strcpy(dbuf, dest); + + mkdir(dbuf, 0744); + + return _copyDirectory(sbuf, source_len, dbuf, dest_len, splitting); } void removeDirectory(const char *path) { - struct dirent* ent; - char cur_path[NAME_BUF_LEN] = {'\0'}; - - 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) - { - removeDirectory(cur_path); - } else { - remove(cur_path); - } - } - - closedir(dir); - - rmdir(path); - } + struct dirent* ent; + char cur_path[NAME_BUF_LEN] = {'\0'}; + + 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) + { + removeDirectory(cur_path); + } else { + remove(cur_path); + } + } + + 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; + 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; - char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'}; - - workaroundPartitionZeroAccess(fsOperator); - - if (openPartitionFs(&fs, fsOperator, partition)) - { - ret = fsdevMountDevice("gamecard", fs); - if (ret != -1) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - 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 * 8, 255, 255, 255); - breaks++; - - if (total_size <= freeSpace) - { - 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); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (copyDirectory("gamecard:/", dumpPath, true)) - { - success = true; - breaks += 7; - uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 0); - } else { - removeDirectory(dumpPath); - } - } else { - uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0); - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to get total partition data size!"); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - fsdevUnmountDevice("gamecard"); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice failed! (%d)", ret); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - fsFsClose(&fs); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open partition #%u filesystem!", partition); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - breaks += 2; - - return success; + FsFileSystem fs; + int ret; + bool success = false; + u64 total_size; + char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'}; + + workaroundPartitionZeroAccess(fsOperator); + + if (openPartitionFs(&fs, fsOperator, partition)) + { + ret = fsdevMountDevice("gamecard", fs); + if (ret != -1) + { + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++;*/ + + 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); + breaks++; + + if (total_size <= freeSpace) + { + 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); + uiDrawString(strbuf, 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; + + if (copyDirectory("gamecard:/", dumpPath, true)) + { + success = true; + breaks += 5; + uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0); + } else { + removeDirectory(dumpPath); + } + } else { + uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to get total partition data size!"); + uiDrawString(strbuf, 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); + } + + breaks += 2; + + return success; } bool mountViewPartition(FsDeviceOperator *fsOperator, FsFileSystem *out, u32 partition) { - int ret; - bool success = false; - - workaroundPartitionZeroAccess(fsOperator); - - if (openPartitionFs(out, fsOperator, partition)) - { - ret = fsdevMountDevice("view", *out); - if (ret != -1) - { - //snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret); - //uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - //breaks++; - - success = true; - } else { - fsFsClose(out); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice failed! (%d)", ret); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open partition #%u filesystem!", partition); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - return success; + int ret; + bool success = false; + + workaroundPartitionZeroAccess(fsOperator); + + if (openPartitionFs(out, fsOperator, partition)) + { + ret = fsdevMountDevice("view", *out); + if (ret != -1) + { + //snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice succeeded: %d", ret); + //uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + //breaks++; + + success = true; + } else { + fsFsClose(out); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "fsdevMountDevice failed! (%d)", ret); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open partition #%u filesystem!", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + return success; } bool dumpGameCertificate(FsDeviceOperator* fsOperator) { - u32 handle, crc; - Result result; - FsStorage gameCardStorage; - bool success = false; - FILE *outFile = NULL; - char filename[NAME_BUF_LEN] = {'\0'}; - char *buf = NULL; - - 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++; - - if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, handle, 0))) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - if (CERT_SIZE <= freeSpace) - { - buf = (char*)malloc(DUMP_BUFFER_SIZE); - if (buf) - { - 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 { - 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]), "Failed to open output file \"%s\"!", filename); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - } else { - 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); - } - - free(buf); - } else { - 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); - } - - 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; + u32 crc; + Result result; + FsGameCardHandle handle; + FsStorage gameCardStorage; + bool success = false; + FILE *outFile = NULL; + char filename[NAME_BUF_LEN] = {'\0'}; + char *buf = NULL; + + 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 = fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) + { + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle.value); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++;*/ + + if (CERT_SIZE <= freeSpace) + { + buf = (char*)malloc(DUMP_BUFFER_SIZE); + if (buf) + { + 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 * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + outFile = fopen(filename, "wb"); + if (outFile) + { + if (fwrite(buf, 1, CERT_SIZE, outFile) == CERT_SIZE) + { + success = true; + uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0); + } else { + uiDrawString("Failed to write certificate data!", 0, breaks * font_height, 255, 0, 0); + } + + fclose(outFile); + if (!success) remove(filename); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%08X", result, CERT_OFFSET); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + free(buf); + } else { + uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: not enough free space available in the SD card.", 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); + } + + breaks += 2; + + return success; } diff --git a/source/dumper.h b/source/dumper.h index 7959aed..be7d555 100644 --- a/source/dumper.h +++ b/source/dumper.h @@ -5,71 +5,80 @@ #include -#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB -#define ISTORAGE_PARTITION_CNT 2 -#define SPLIT_FILE_MIN (u64)0xEE6B2800 // 4 GB (4000000000 bytes) -#define SPLIT_FILE_2GiB (u64)0x80000000 +#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB +#define ISTORAGE_PARTITION_CNT 2 +#define SPLIT_FILE_MIN (u64)0xEE6B2800 // 4 GB (4000000000 bytes) +#define SPLIT_FILE_2GiB (u64)0x80000000 -#define MEDIA_UNIT_SIZE 0x200 +#define MEDIA_UNIT_SIZE 0x200 -#define CERT_OFFSET 0x7000 -#define CERT_SIZE 0x200 +#define CERT_OFFSET 0x7000 +#define CERT_SIZE 0x200 -#define GAMECARD_HEADER_SIZE 0x200 -#define GAMECARD_SIZE_ADDR 0x10D -#define GAMECARD_DATAEND_ADDR 0x118 +#define GAMECARD_HEADER_SIZE 0x200 +#define GAMECARD_SIZE_ADDR 0x10D +#define GAMECARD_DATAEND_ADDR 0x118 -#define HFS0_OFFSET_ADDR 0x130 -#define HFS0_SIZE_ADDR 0x138 -#define HFS0_MAGIC 0x48465330 // "HFS0" -#define HFS0_FILE_COUNT_ADDR 0x04 -#define HFS0_ENTRY_TABLE_ADDR 0x10 +#define HFS0_OFFSET_ADDR 0x130 +#define HFS0_SIZE_ADDR 0x138 +#define HFS0_MAGIC 0x48465330 // "HFS0" +#define HFS0_FILE_COUNT_ADDR 0x04 +#define HFS0_STR_TABLE_SIZE_ADDR 0x08 +#define HFS0_ENTRY_TABLE_ADDR 0x10 -#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_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 -#define GAMECARD_SIZE_4GiB (u64)0x100000000 -#define GAMECARD_SIZE_8GiB (u64)0x200000000 -#define GAMECARD_SIZE_16GiB (u64)0x400000000 -#define GAMECARD_SIZE_32GiB (u64)0x800000000 +#define GAMECARD_SIZE_1GiB (u64)0x40000000 +#define GAMECARD_SIZE_2GiB (u64)0x80000000 +#define GAMECARD_SIZE_4GiB (u64)0x100000000 +#define GAMECARD_SIZE_8GiB (u64)0x200000000 +#define GAMECARD_SIZE_16GiB (u64)0x400000000 +#define GAMECARD_SIZE_32GiB (u64)0x800000000 -#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816 +/* Reference: https://switchbrew.org/wiki/Title_list */ +#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 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 SYSUPDATE_600 (u32)402653544 +#define SYSUPDATE_601 (u32)402718730 +#define SYSUPDATE_610 (u32)403701850 +#define SYSUPDATE_620 (u32)404750376 +#define SYSUPDATE_700 (u32)469762248 +#define SYSUPDATE_701 (u32)469827614 +#define SYSUPDATE_800 (u32)536871442 -#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 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.01 +#define SMOOTHING_FACTOR (double)0.01 typedef struct { - u64 file_offset; - u64 file_size; - u32 filename_offset; - u32 hashed_region_size; - u64 reserved; - u8 hashed_region_sha256[0x20]; + u64 file_offset; + u64 file_size; + u32 filename_offset; + u32 hashed_region_size; + u64 reserved; + u8 hashed_region_sha256[0x20]; } PACKED hfs0_entry_table; void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator); diff --git a/source/fsext.c b/source/fsext.c index 81fead6..acab220 100644 --- a/source/fsext.c +++ b/source/fsext.c @@ -5,196 +5,167 @@ #include "fsext.h" // IFileSystemProxy -Result fsOpenGameCardStorage(FsStorage* out, u32 handle, u32 partition) +Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32 partition) { - IpcCommand c; - ipcInitialize(&c); - - struct { - u64 magic; - u64 cmd_id; - u32 handle; - u32 partition; - } *raw; - - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 30; - raw->handle = handle; - raw->partition = partition; - - Result rc = serviceIpcDispatch(fsGetServiceSession()); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) serviceCreate(&out->s, r.Handles[0]); - } - - return rc; + 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 = 30; + 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 fsOpenGameCardFileSystem(FsFileSystem* out, u32 handle, u32 partition) +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 = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 31; - raw->handle = handle; - raw->partition = partition; - - Result rc = serviceIpcDispatch(fsGetServiceSession()); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) serviceCreate(&out->s, r.Handles[0]); - } - - return rc; + 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; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = serviceIpcPrepareHeader(fsGetServiceSession(), &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 501; + + 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; } // IDeviceOperator -Result fsDeviceOperatorIsGameCardInserted(FsDeviceOperator* d, bool* out) +Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator* d, const FsGameCardHandle* handle, u32* out_title_version, u64* out_title_id) { - IpcCommand c; - ipcInitialize(&c); - - struct { - u64 magic; - u64 cmd_id; - } *raw; - - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 200; - - Result rc = serviceIpcDispatch(&d->s); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u8 is_inserted; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) *out = resp->is_inserted != 0; - } - - return rc; -} - -Result fsDeviceOperatorGetGameCardHandle(FsDeviceOperator* d, u32* out) -{ - IpcCommand c; - ipcInitialize(&c); - - struct { - u64 magic; - u64 cmd_id; - } *raw; - - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 202; - - Result rc = serviceIpcDispatch(&d->s); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 handle; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) *out = resp->handle; - } - - return rc; -} - -Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator* d, u32 handle, u32* out_title_version, u64* out_title_id) -{ - IpcCommand c; - ipcInitialize(&c); - - struct { - u64 magic; - u64 cmd_id; - u32 handle; - } *raw; - - raw = ipcPrepareHeader(&c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 203; - raw->handle = handle; - - Result rc = serviceIpcDispatch(&d->s); - - if (R_SUCCEEDED(rc)) - { - IpcParsedCommand r; - ipcParse(&r); - - struct { - u64 magic; - u64 result; - u32 title_ver; - u64 title_id; - } *resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) - { - if (out_title_version != NULL) *out_title_version = resp->title_ver; - if (out_title_id != NULL) *out_title_id = resp->title_id; - } - } - - return rc; + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 handle; + } *raw; + + raw = serviceIpcPrepareHeader(&d->s, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 203; + raw->handle = handle->value; + + Result rc = serviceIpcDispatch(&d->s); + + if (R_SUCCEEDED(rc)) + { + IpcParsedCommand r; + + struct { + u64 magic; + u64 result; + u32 title_ver; + u64 title_id; + } *resp; + + serviceIpcParse(&d->s, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) + { + if (out_title_version != NULL) *out_title_version = resp->title_ver; + if (out_title_id != NULL) *out_title_id = resp->title_id; + } + } + + return rc; } diff --git a/source/fsext.h b/source/fsext.h index 5ce6567..6d0ff50 100644 --- a/source/fsext.h +++ b/source/fsext.h @@ -7,12 +7,11 @@ #include // IFileSystemProxy -Result fsOpenGameCardStorage(FsStorage* out, u32 handle, u32 partition); -Result fsOpenGameCardFileSystem(FsFileSystem* out, u32 handle, u32 partition); +Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32 partition); +Result fsOpenGameCardFileSystem(FsFileSystem* out, const FsGameCardHandle* handle, u32 partition); +Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out); // IDeviceOperator -Result fsDeviceOperatorIsGameCardInserted(FsDeviceOperator* d, bool* out); -Result fsDeviceOperatorGetGameCardHandle(FsDeviceOperator* d, u32* out); -Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator* d, u32 handle, u32* out_title_version, u64* out_title_id); +Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator* d, const FsGameCardHandle* handle, u32* out_title_version, u64* out_title_id); #endif diff --git a/source/main.c b/source/main.c index dd51219..3032e0d 100644 --- a/source/main.c +++ b/source/main.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -6,241 +8,235 @@ #include "dumper.h" #include "ui.h" #include "util.h" +#include "fsext.h" -FsDeviceOperator fsOperatorInstance; +/* Extern variables */ -bool gameCardInserted; +extern FsDeviceOperator fsOperatorInstance; -u64 gameCardSize = 0, trimmedCardSize = 0; -char gameCardSizeStr[32] = {'\0'}, trimmedCardSizeStr[32] = {'\0'}; +extern FsEventNotifier fsGameCardEventNotifier; +extern Handle fsGameCardEventHandle; +extern Event fsGameCardKernelEvent; +extern UEvent exitEvent; -char *hfs0_header = NULL; -u64 hfs0_offset = 0, hfs0_size = 0; -u32 hfs0_partition_cnt = 0; +extern char *hfs0_header; +extern char *partitionHfs0Header; -//char *partitionHfs0Header = NULL; -//u64 partitionHfs0HeaderSize = 0; +extern bool gameCardInserted; -u64 gameCardTitleID = 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; - -int main(int argc, char **argv) +int main(int argc, char *argv[]) { - gfxInitResolutionDefault(); - gfxInitDefault(); - gfxConfigureAutoResolutionDefault(true); - - uiInit(); - - currentFB = gfxGetFramebuffer(¤tFBWidth, ¤tFBHeight); - - int ret = 0; - Result result; - char strbuf[512] = {'\0'}; - - if (R_SUCCEEDED(result = fsInitialize())) - { - if (R_SUCCEEDED(result = fsOpenDeviceOperator(&fsOperatorInstance))) - { - if (R_SUCCEEDED(result = ncmInitialize())) - { - if (R_SUCCEEDED(result = nsInitialize())) - { - if (R_SUCCEEDED(result = timeInitialize())) - { - bool exitLoop = false; - - while(appletMainLoop()) - { - currentFB = gfxGetFramebuffer(¤tFBWidth, ¤tFBHeight); - - uiPrintHeadline(); - - gameCardInserted = isGameCardInserted(&fsOperatorInstance); - - if (gameCardInserted) - { - if (hfs0_header == NULL) - { - // Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services - uiPleaseWait(); - - 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(); - } - } else { - if (hfs0_header != NULL) - { - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - 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(gameCardVersionStr, 0, sizeof(gameCardVersionStr)); - - gameCardUpdateTitleID = 0; - gameCardUpdateVersion = 0; - - memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr)); - } - } - - hidScanInput(); - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - - UIResult result = uiLoop(keysDown); - switch(result) - { - case resultShowMainMenu: - uiSetState(stateMainMenu); - break; - case resultShowXciDumpMenu: - uiSetState(stateXciDumpMenu); - break; - case resultDumpXci: - uiSetState(stateDumpXci); - break; - case resultShowRawPartitionDumpMenu: - uiSetState(stateRawPartitionDumpMenu); - break; - case resultDumpRawPartition: - uiSetState(stateDumpRawPartition); - break; - case resultShowPartitionDataDumpMenu: - uiSetState(statePartitionDataDumpMenu); - break; - case resultDumpPartitionData: - uiSetState(stateDumpPartitionData); - break; - case resultShowViewGameCardFsMenu: - uiSetState(stateViewGameCardFsMenu); - break; - case resultShowViewGameCardFsGetList: - uiSetState(stateViewGameCardFsGetList); - break; - case resultShowViewGameCardFsBrowser: - uiSetState(stateViewGameCardFsBrowser); - break; - case resultViewGameCardFsBrowserCopyFile: - uiSetState(stateViewGameCardFsBrowserCopyFile); - break; - case resultDumpGameCardCertificate: - uiSetState(stateDumpGameCardCertificate); - break; - case resultUpdateNSWDBXml: - uiSetState(stateUpdateNSWDBXml); - break; - case resultUpdateApplication: - uiSetState(stateUpdateApplication); - break; - case resultExit: - exitLoop = true; - break; - default: - break; - } - - if (exitLoop) break; - - syncDisplay(); - } - - timeExit(); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the time service! (0x%08X)", result); - uiDrawString(strbuf, 0, 0, 255, 255, 255); - syncDisplay(); - delay(5); - ret = -5; - } - - nsExit(); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08X)", result); - uiDrawString(strbuf, 0, 0, 255, 255, 255); - syncDisplay(); - delay(5); - ret = -4; - } - - ncmExit(); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08X)", result); - uiDrawString(strbuf, 0, 0, 255, 255, 255); - syncDisplay(); - delay(5); - ret = -3; - } - - fsDeviceOperatorClose(&fsOperatorInstance); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08X)", result); - uiDrawString(strbuf, 0, 0, 255, 255, 255); - syncDisplay(); - delay(5); - ret = -2; - } - - fsExit(); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the fsp-srv service! (0x%08X)", result); - uiDrawString(strbuf, 0, 0, 255, 255, 255); - syncDisplay(); - delay(5); - ret = -1; - } - - if (hfs0_header != NULL) free(hfs0_header); - - //if (partitionHfs0Header != NULL) free(partitionHfs0Header); - - uiDeinit(); - - gfxExit(); - - return ret; + /* Initialize UI */ + if (!uiInit()) return -1; + + int ret = 0; + Result result; + char strbuf[512] = {'\0'}; + + /* Initialize the fsp-srv service */ + result = fsInitialize(); + if (R_SUCCEEDED(result)) + { + /* Open device operator */ + result = fsOpenDeviceOperator(&fsOperatorInstance); + if (R_SUCCEEDED(result)) + { + /* Initialize the ncm service */ + result = ncmInitialize(); + if (R_SUCCEEDED(result)) + { + /* Initialize the ns service */ + result = nsInitialize(); + if (R_SUCCEEDED(result)) + { + /* Initialize the time service */ + result = timeInitialize(); + if (R_SUCCEEDED(result)) + { + /* Open gamecard detection event notifier */ + result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier); + if (R_SUCCEEDED(result)) + { + /* Retrieve kernel event handle */ + result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle); + if (R_SUCCEEDED(result)) + { + /* Retrieve initial gamecard status */ + gameCardInserted = isGameCardInserted(); + + /* Load gamecard detection kernel event */ + eventLoadRemote(&fsGameCardKernelEvent, fsGameCardEventHandle, false); + + /* Create usermode exit event */ + ueventCreate(&exitEvent, false); + + /* Create gamecard detection thread */ + Thread thread; + result = threadCreate(&thread, fsGameCardDetectionThreadFunc, NULL, 0x10000, 0x2C, -2); + if (R_SUCCEEDED(result)) + { + /* Start gamecard detection thread */ + result = threadStart(&thread); + if (R_SUCCEEDED(result)) + { + /* Main application loop */ + bool exitLoop = false; + while(appletMainLoop()) + { + UIResult result = uiProcess(); + switch(result) + { + case resultShowMainMenu: + uiSetState(stateMainMenu); + break; + case resultShowXciDumpMenu: + uiSetState(stateXciDumpMenu); + break; + case resultDumpXci: + uiSetState(stateDumpXci); + break; + case resultShowRawPartitionDumpMenu: + uiSetState(stateRawPartitionDumpMenu); + break; + case resultDumpRawPartition: + uiSetState(stateDumpRawPartition); + break; + case resultShowPartitionDataDumpMenu: + uiSetState(statePartitionDataDumpMenu); + break; + case resultDumpPartitionData: + uiSetState(stateDumpPartitionData); + break; + case resultShowViewGameCardFsMenu: + uiSetState(stateViewGameCardFsMenu); + break; + case resultShowViewGameCardFsGetList: + uiSetState(stateViewGameCardFsGetList); + break; + case resultShowViewGameCardFsBrowser: + uiSetState(stateViewGameCardFsBrowser); + break; + case resultViewGameCardFsBrowserCopyFile: + uiSetState(stateViewGameCardFsBrowserCopyFile); + break; + case resultDumpGameCardCertificate: + uiSetState(stateDumpGameCardCertificate); + break; + case resultUpdateNSWDBXml: + uiSetState(stateUpdateNSWDBXml); + break; + case resultUpdateApplication: + uiSetState(stateUpdateApplication); + break; + case resultExit: + exitLoop = true; + break; + default: + break; + } + + if (exitLoop) break; + } + + /* Signal the exit event to terminate the gamecard detection thread */ + ueventSignal(&exitEvent); + + /* Wait for the gamecard detection thread to exit */ + threadWaitForExit(&thread); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to start gamecard detection thread! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -10; + } + + /* Close gamecard detection thread */ + threadClose(&thread); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to create gamecard detection thread! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -9; + } + + /* Close kernel event */ + eventClose(&fsGameCardKernelEvent); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to retrieve gamecard detection event handle! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -8; + } + + /* Close gamecard event notifier */ + fsEventNotifierClose(&fsGameCardEventNotifier); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open gamecard detection event notifier! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -7; + } + + /* Denitialize the time service */ + timeExit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the time service! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -6; + } + + /* Denitialize the ns service */ + nsExit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -5; + } + + /* Denitialize the ncm service */ + ncmExit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -4; + } + + /* Close device operator */ + fsDeviceOperatorClose(&fsOperatorInstance); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -3; + } + + /* Denitialize the fs-srv service */ + fsExit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the fsp-srv service! (0x%08X)", result); + uiDrawString(strbuf, 0, 0, 255, 255, 255); + uiRefreshDisplay(); + delay(5); + ret = -2; + } + + /* Free resources */ + if (hfs0_header != NULL) free(hfs0_header); + if (partitionHfs0Header != NULL) free(partitionHfs0Header); + + /* Deinitialize UI */ + uiDeinit(); + + return ret; } diff --git a/source/ui.c b/source/ui.c index a4e5eb0..021054f 100644 --- a/source/ui.c +++ b/source/ui.c @@ -7,18 +7,20 @@ #include +#include +#include FT_FREETYPE_H + #include "dumper.h" #include "fsext.h" #include "ui.h" #include "util.h" +/* Extern variables */ + extern FsDeviceOperator fsOperatorInstance; extern bool gameCardInserted; -extern u32 currentFBWidth, currentFBHeight; -extern u8 *currentFB; - extern char gameCardSizeStr[32], trimmedCardSizeStr[32]; extern char *hfs0_header; @@ -31,6 +33,29 @@ extern char gameCardName[0x201], fixedGameCardName[0x201], gameCardAuthor[0x101] extern char gameCardUpdateVersionStr[128]; +extern char currentDirectory[NAME_BUF_LEN]; + +extern char *filenameBuffer; +extern char *filenames[FILENAME_MAX_CNT]; +extern int filenamesCount; + +/* Statically allocated variables */ + +static PlFontData font; +static FT_Library library; +static FT_Face face; +static Framebuffer fb; + +static u32 *framebuf = NULL; +static u32 framebuf_width = 0; + +int cursor = 0; +int scroll = 0; +int breaks = 0; +int font_height = 0; + +static bool highlight = false; + static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true; static u32 selectedOption; @@ -41,114 +66,33 @@ static char statusMessage[2048] = {'\0'}; static int statusMessageFadeout = 0; static char fileCopyPath[NAME_BUF_LEN * 2] = {'\0'}; -static char currentDirectory[NAME_BUF_LEN] = {'\0'}; -static int cursor = 0; -static int scroll = 0; static int headlineCnt = 0; -int breaks = 0; u64 freeSpace = 0; static char freeSpaceStr[64] = {'\0'}; static char titlebuf[NAME_BUF_LEN * 2] = {'\0'}; -static const int maxListElements = 45; - -static char *filenameBuffer = NULL; -static char *filenames[FILENAME_MAX_CNT]; -static int filenamesCount = 0; +static const int maxListElements = 15; 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 *appHeadline = "Nintendo Switch Game Card Dump Tool v" APP_VERSION ".\nOriginal codebase by MCMrARM.\nUpdated and maintained by DarkMatterCore.\n\n"; static const char *appControls = "[D-Pad / Analog Sticks] 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", "Update NSWDB.COM XML database", "Update application (not working at this moment)" }; +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" }; 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 (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}, - {0x00, 0x3E, 0x7F, 0x6B, 0x7F, 0x6B, 0x77, 0x3E}, {0x00, 0x22, 0x77, 0x7F, 0x7F, 0x3E, 0x1C, 0x08}, - {0x00, 0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x1C, 0x08}, {0x00, 0x08, 0x1C, 0x2A, 0x7F, 0x2A, 0x08, 0x1C}, - {0x00, 0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x08, 0x1C}, {0x00, 0x00, 0x1C, 0x3E, 0x3E, 0x3E, 0x1C, 0x00}, - {0xFF, 0xFF, 0xE3, 0xC1, 0xC1, 0xC1, 0xE3, 0xFF}, {0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00}, - {0xFF, 0xFF, 0xE3, 0xDD, 0xDD, 0xDD, 0xE3, 0xFF}, {0x00, 0x0F, 0x03, 0x05, 0x39, 0x48, 0x48, 0x30}, - {0x00, 0x08, 0x3E, 0x08, 0x1C, 0x22, 0x22, 0x1C}, {0x00, 0x18, 0x14, 0x10, 0x10, 0x30, 0x70, 0x60}, - {0x00, 0x0F, 0x19, 0x11, 0x13, 0x37, 0x76, 0x60}, {0x00, 0x08, 0x2A, 0x1C, 0x77, 0x1C, 0x2A, 0x08}, - {0x00, 0x60, 0x78, 0x7E, 0x7F, 0x7E, 0x78, 0x60}, {0x00, 0x03, 0x0F, 0x3F, 0x7F, 0x3F, 0x0F, 0x03}, - {0x00, 0x08, 0x1C, 0x2A, 0x08, 0x2A, 0x1C, 0x08}, {0x00, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x66}, - {0x00, 0x3F, 0x65, 0x65, 0x3D, 0x05, 0x05, 0x05}, {0x00, 0x0C, 0x32, 0x48, 0x24, 0x12, 0x4C, 0x30}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F}, {0x00, 0x08, 0x1C, 0x2A, 0x08, 0x2A, 0x1C, 0x3E}, - {0x00, 0x08, 0x1C, 0x3E, 0x7F, 0x1C, 0x1C, 0x1C}, {0x00, 0x1C, 0x1C, 0x1C, 0x7F, 0x3E, 0x1C, 0x08}, - {0x00, 0x08, 0x0C, 0x7E, 0x7F, 0x7E, 0x0C, 0x08}, {0x00, 0x08, 0x18, 0x3F, 0x7F, 0x3F, 0x18, 0x08}, - {0x00, 0x00, 0x00, 0x70, 0x70, 0x70, 0x7F, 0x7F}, {0x00, 0x00, 0x14, 0x22, 0x7F, 0x22, 0x14, 0x00}, - {0x00, 0x08, 0x1C, 0x1C, 0x3E, 0x3E, 0x7F, 0x7F}, {0x00, 0x7F, 0x7F, 0x3E, 0x3E, 0x1C, 0x1C, 0x08}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18}, - {0x00, 0x36, 0x36, 0x14, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36}, - {0x00, 0x08, 0x1E, 0x20, 0x1C, 0x02, 0x3C, 0x08}, {0x00, 0x60, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x06}, - {0x00, 0x3C, 0x66, 0x3C, 0x28, 0x65, 0x66, 0x3F}, {0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00}, - {0x00, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06}, {0x00, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60}, - {0x00, 0x00, 0x36, 0x1C, 0x7F, 0x1C, 0x36, 0x00}, {0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x60}, {0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60}, {0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00}, - {0x00, 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C}, {0x00, 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E}, - {0x00, 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E}, {0x00, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C}, - {0x00, 0x0C, 0x1C, 0x2C, 0x4C, 0x7E, 0x0C, 0x0C}, {0x00, 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C}, - {0x00, 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C}, {0x00, 0x7E, 0x66, 0x0C, 0x0C, 0x18, 0x18, 0x18}, - {0x00, 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C}, {0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C}, - {0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00}, {0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30}, - {0x00, 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06}, {0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00}, - {0x00, 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60}, {0x00, 0x3C, 0x66, 0x06, 0x1C, 0x18, 0x00, 0x18}, - {0x00, 0x38, 0x44, 0x5C, 0x58, 0x42, 0x3C, 0x00}, {0x00, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66}, - {0x00, 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C}, {0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C}, - {0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C}, {0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E}, - {0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60}, {0x00, 0x3C, 0x66, 0x60, 0x60, 0x6E, 0x66, 0x3C}, - {0x00, 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66}, {0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C}, - {0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x6C, 0x6C, 0x38}, {0x00, 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66}, - {0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E}, {0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63}, - {0x00, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x63, 0x63}, {0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C}, - {0x00, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60}, {0x00, 0x3C, 0x66, 0x66, 0x66, 0x6E, 0x3C, 0x06}, - {0x00, 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66}, {0x00, 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C}, - {0x00, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18}, {0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3E}, - {0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18}, {0x00, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63}, - {0x00, 0x63, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x63}, {0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18}, - {0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E}, {0x00, 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E}, - {0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00}, {0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78}, - {0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F}, - {0x00, 0x0C, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E}, - {0x00, 0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C}, {0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C}, - {0x00, 0x06, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E}, {0x00, 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C}, - {0x00, 0x1C, 0x36, 0x30, 0x30, 0x7C, 0x30, 0x30}, {0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C}, - {0x00, 0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66}, {0x00, 0x00, 0x18, 0x00, 0x18, 0x18, 0x18, 0x3C}, - {0x00, 0x0C, 0x00, 0x0C, 0x0C, 0x6C, 0x6C, 0x38}, {0x00, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66}, - {0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}, {0x00, 0x00, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x6B}, - {0x00, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66}, {0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C}, - {0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60}, {0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x3C, 0x0D, 0x0F}, - {0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x60, 0x60}, {0x00, 0x00, 0x00, 0x3E, 0x40, 0x3C, 0x02, 0x7C}, - {0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18}, {0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E}, - {0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3C, 0x18}, {0x00, 0x00, 0x00, 0x63, 0x6B, 0x6B, 0x6B, 0x3E}, - {0x00, 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66}, {0x00, 0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x3C}, - {0x00, 0x00, 0x00, 0x3C, 0x0C, 0x18, 0x30, 0x3C}, {0x00, 0x0E, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0E}, - {0x00, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18}, {0x00, 0x70, 0x18, 0x18, 0x0C, 0x18, 0x18, 0x70}, - {0x00, 0x00, 0x00, 0x3A, 0x6C, 0x00, 0x00, 0x00}, {0x00, 0x08, 0x1C, 0x36, 0x63, 0x41, 0x41, 0x7F} -}; - -int uiGetIndex(int x, int y) -{ - return (x + y * currentFBWidth) * 4; -} - void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b) { - if (currentFB == NULL) return; - - if (x + width < 0 || y + height < 0 || x >= currentFBWidth || y >= currentFBHeight) return; - + /* Perform validity checks */ + if ((x + width) < 0 || (y + height) < 0 || x >= FB_WIDTH || y >= FB_HEIGHT) return; + if (x < 0) { width += x; @@ -161,761 +105,967 @@ void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b) y = 0; } - if (x + width >= currentFBWidth) width = currentFBWidth - x; + if ((x + width) >= FB_WIDTH) width = (FB_WIDTH - x); - if (y + height >= currentFBHeight) height = currentFBHeight - y; - - u8 colorLine[width * 4]; - for (int ly = 0; ly < width; ly++) - { - colorLine[ly * 4 + 0] = r; - colorLine[ly * 4 + 1] = g; - colorLine[ly * 4 + 2] = b; - colorLine[ly * 4 + 3] = 255; - } - - u8* fbAddr = currentFB + uiGetIndex(x, y); // - (width * 4); - for (int dx = 0; dx < height; dx++) - { - memcpy(fbAddr, colorLine, (size_t)(width * 4)); - fbAddr += currentFBWidth * 4; - } + if ((y + height) >= FB_HEIGHT) height = (FB_HEIGHT - y); + + if (framebuf == NULL) + { + /* Begin new frame */ + u32 stride; + framebuf = (u32*)framebufferBegin(&fb, &stride); + framebuf_width = (stride / sizeof(u32)); + } + + u32 lx, ly; + u32 framex, framey; + + for (ly = 0; ly < height; ly++) + { + for (lx = 0; lx < width; lx++) + { + framex = (x + lx); + framey = (y + ly); + + framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(r, g, b); + } + } } -void uiDrawChar(char c, int x, int y, u8 r, u8 g, u8 b) +void uiDrawChar(FT_Bitmap *bitmap, int x, int y, u8 r, u8 g, u8 b) { - if (currentFB == NULL) return; - - if (c & 0x80) c = '?'; // Unicode chars - - unsigned char* data = asciiData[(int)c]; - for (int cy = 0; cy < 8; cy++) - { - if (y + cy < 0 || y + cy >= currentFBHeight) continue; - - unsigned char l = data[cy]; - for (int cx = 0; cx < 8; cx++) - { - if (x + cx < 0 || x + cx >= currentFBWidth) continue; - - if ((0b10000000 >> cx) & l) - { - u8* ptr = ¤tFB[uiGetIndex(x + cx, y + cy)]; - *(ptr + 0) = r; - *(ptr + 1) = g; - *(ptr + 2) = b; - *(ptr + 3) = 255; - } - } - } + if (framebuf == NULL) return; + + u32 framex, framey; + u32 tmpx, tmpy; + u8 *imageptr = bitmap->buffer; + + u8 src_val; + float opacity; + + u8 fontR; + u8 fontG; + u8 fontB; + + if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY) return; + + for(tmpy = 0; tmpy < bitmap->rows; tmpy++) + { + for (tmpx = 0; tmpx < bitmap->width; tmpx++) + { + framex = (x + tmpx); + framey = (y + tmpy); + + if (framex >= FB_WIDTH || framey >= FB_HEIGHT) continue; + + src_val = imageptr[tmpx]; + if (!src_val) + { + /* Render background color */ + if (highlight) + { + framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(HIGHLIGHT_BG_COLOR_R, HIGHLIGHT_BG_COLOR_G, HIGHLIGHT_BG_COLOR_B); + } else { + framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + } + } else { + /* Calculate alpha (opacity) */ + opacity = (src_val / 255.0); + + if (highlight) + { + fontR = (r * opacity + (1 - opacity) * HIGHLIGHT_BG_COLOR_R); + fontG = (g * opacity + (1 - opacity) * HIGHLIGHT_BG_COLOR_G); + fontB = (b * opacity + (1 - opacity) * HIGHLIGHT_BG_COLOR_B); + } else { + fontR = (r * opacity + (1 - opacity) * BG_COLOR_RGB); + fontG = (g * opacity + (1 - opacity) * BG_COLOR_RGB); + fontB = (b * opacity + (1 - opacity) * BG_COLOR_RGB); + } + + framebuf[(framey * framebuf_width) + framex] = RGBA8_MAXALPHA(fontR, fontG, fontB); + } + } + + imageptr += bitmap->pitch; + } } -void uiDrawString(const char* string, int x, int y, u8 r, u8 g, u8 b) +void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b) { - if (currentFB == NULL) return; - - int len = (int)strlen(string); - int cx = x; - int cy = y; - - for (int i = 0; i < len; i++) - { - char c = string[i]; - if (c == '\n') - { - cx = x; - cy += 8; - } else { - uiDrawChar(c, cx, cy, r, g, b); - cx += 8; - } - - if (cx > (currentFBWidth - 8)) break; - } + u32 tmpx = x; + u32 tmpy = (font_height + y); + FT_Error ret = 0; + FT_UInt glyph_index; + FT_GlyphSlot slot = face->glyph; + + u32 i; + u32 str_size = strlen(string); + uint32_t tmpchar; + ssize_t unitcount = 0; + + if (framebuf == NULL) + { + /* Begin new frame */ + u32 stride; + framebuf = (u32*)framebufferBegin(&fb, &stride); + framebuf_width = (stride / sizeof(u32)); + } + + for(i = 0; i < str_size;) + { + unitcount = decode_utf8(&tmpchar, (const uint8_t*)&string[i]); + if (unitcount <= 0) break; + i += unitcount; + + if (tmpchar == '\n') + { + tmpx = x; + tmpy += font_height; + continue; + } else + if (tmpchar == '\t') + { + tmpx += (font_height * TAB_WIDTH); + continue; + } else + if (tmpchar == '\r') + { + continue; + } + + glyph_index = FT_Get_Char_Index(face, tmpchar); + + ret = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (ret == 0) ret = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + + if (ret) break; + + uiDrawChar(&slot->bitmap, tmpx + slot->bitmap_left, tmpy - slot->bitmap_top, r, g, b); + + tmpx += (slot->advance.x >> 6); + tmpy += (slot->advance.y >> 6); + } } -void uiStatusMsg(const char* format, ...) +void uiRefreshDisplay() +{ + if (framebuf != NULL) + { + framebufferEnd(&fb); + framebuf = NULL; + framebuf_width = 0; + } +} + +void uiStatusMsg(const char *fmt, ...) { statusMessageFadeout = 1000; va_list args; - va_start(args, format); - vsnprintf(statusMessage, sizeof(statusMessage) / sizeof(statusMessage[0]), format, args); + va_start(args, fmt); + vsnprintf(statusMessage, sizeof(statusMessage) / sizeof(statusMessage[0]), fmt, args); va_end(args); - - //printf("Status message: %s\n", statusMessage); } void uiUpdateStatusMsg() { if (!strlen(statusMessage) || !statusMessageFadeout) return; - uiFill(0, currentFBHeight - 12, currentFBWidth, 8, 50, 50, 50); - - int fadeout = (statusMessageFadeout > 255 ? 255 : statusMessageFadeout); - uiDrawString(statusMessage, 4, currentFBHeight - 12, fadeout, fadeout, fadeout); - syncDisplay(); - - statusMessageFadeout -= 4; + 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; + } } void uiPleaseWait() { - breaks = headlineCnt; - uiDrawString("Please wait...", 0, breaks * 8, 115, 115, 255); - syncDisplay(); - delay(2); + breaks = headlineCnt; + uiDrawString("Please wait...", 0, breaks * font_height, 115, 115, 255); + uiRefreshDisplay(); + delay(3); } void uiUpdateFreeSpace() { - getSdCardFreeSpace(&freeSpace); - - char tmp[32] = {'\0'}; - convertSize(freeSpace, tmp, sizeof(tmp) / sizeof(tmp[0])); - - snprintf(freeSpaceStr, sizeof(freeSpaceStr) / sizeof(freeSpaceStr[0]), "Free SD card space: %s.", tmp); -} - -void uiInit() -{ - uiState = stateMainMenu; - cursor = 0; - scroll = 0; - headlineCnt = 0; - - filenameBuffer = (char*)malloc(FILENAME_BUFFER_SIZE); - - int i, headlineLen = strlen(appHeadline); - for(i = 0; i < headlineLen; i++) - { - if (appHeadline[i] == '\n') headlineCnt++; - } - - if (!headlineCnt) headlineCnt = 2; - - uiUpdateFreeSpace(); -} - -void uiDeinit() -{ - if (filenameBuffer) free(filenameBuffer); -} - -void uiSetState(UIState state) -{ - uiState = state; - cursor = 0; - scroll = 0; -} - -UIState uiGetState() -{ - return uiState; + getSdCardFreeSpace(&freeSpace); + + char tmp[32] = {'\0'}; + convertSize(freeSpace, tmp, sizeof(tmp) / sizeof(tmp[0])); + + snprintf(freeSpaceStr, sizeof(freeSpaceStr) / sizeof(freeSpaceStr[0]), "Free SD card space: %s.", tmp); } void uiClearScreen() { - uiFill(0, 0, currentFBWidth, currentFBHeight, 50, 50, 50); + uiFill(0, 0, FB_WIDTH, FB_HEIGHT, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); } void uiPrintHeadline() { - uiClearScreen(); - uiDrawString(appHeadline, 0, 0, 255, 255, 255); + uiClearScreen(); + uiDrawString(appHeadline, 0, font_height, 255, 255, 255); } -static void enterDirectory(const char *path) +int error_screen(const char *fmt, ...) { - 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; + consoleInit(NULL); + + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + + printf("Press [+] to exit.\n"); + + while (appletMainLoop()) + { + hidScanInput(); + if (hidKeysDown(CONTROLLER_P1_AUTO) & KEY_PLUS) break; + consoleUpdate(NULL); + } + + consoleExit(NULL); + + return 0; } -UIResult uiLoop(u32 keysDown) +int uiInit() { - UIResult res = resultNone; - - int i; - breaks = headlineCnt; - - if (uiState == stateMainMenu || uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu || uiState == stateViewGameCardFsBrowser) - { - // Exit - if (keysDown & KEY_PLUS) - { - if (uiState == stateViewGameCardFsBrowser) - { - fsdevUnmountDevice("view"); - fsFsClose(&fs); - } - - return resultExit; - } - - uiDrawString(appControls, 0, breaks * 8, 255, 255, 255); - breaks += 2; - - uiDrawString(freeSpaceStr, 0, breaks * 8, 255, 255, 255); - breaks += 2; - - if (uiState != stateViewGameCardFsBrowser) - { - if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) - { - uiDrawString("Game Card is inserted!", 0, breaks * 8, 0, 255, 0); - breaks += 2; - - /*snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header offset: 0x%016lX", hfs0_offset); - uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header size: 0x%016lX", hfs0_size); - 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++; - - 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); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Used space: %s", trimmedCardSizeStr); - uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0); - breaks++; - - 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]), "Bundled FW update: %s", gameCardUpdateVersionStr); - uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0); - } - } else { - if (gameCardInserted) - { - if (hfs0_header != NULL) - { - 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]), "Bundled FW Update: %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); - } - } else { - uiDrawString("Error: unable to get root HFS0 header data!", 0, breaks * 8, 255, 0, 0); - } - } else { - uiDrawString("Game Card is not inserted!", 0, breaks * 8, 255, 0, 0); - } - - res = resultShowMainMenu; - } - - breaks += 2; - } - - if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) - { - const char **menu = NULL; - int menuItemsCount; - - switch(uiState) - { - case stateMainMenu: - menu = mainMenuItems; - menuItemsCount = sizeof(mainMenuItems) / sizeof(mainMenuItems[0]); - break; - case stateXciDumpMenu: - menu = xciDumpMenuItems; - menuItemsCount = sizeof(xciDumpMenuItems) / sizeof(xciDumpMenuItems[0]); - - uiDrawString(mainMenuItems[0], 0, breaks * 8, 115, 115, 255); - - break; - case stateRawPartitionDumpMenu: - case statePartitionDataDumpMenu: - menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems : partitionDumpType2MenuItems); - menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(partitionDumpType1MenuItems) / sizeof(partitionDumpType1MenuItems[0])) : (sizeof(partitionDumpType2MenuItems) / sizeof(partitionDumpType2MenuItems[0]))); - - uiDrawString((uiState == stateRawPartitionDumpMenu ? mainMenuItems[1] : mainMenuItems[2]), 0, breaks * 8, 115, 115, 255); - - break; - case stateViewGameCardFsMenu: - menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems : viewGameCardFsType2MenuItems); - menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(viewGameCardFsType1MenuItems) / sizeof(viewGameCardFsType1MenuItems[0])) : (sizeof(viewGameCardFsType2MenuItems) / sizeof(viewGameCardFsType2MenuItems[0]))); - - uiDrawString(mainMenuItems[3], 0, breaks * 8, 115, 115, 255); - - break; - case stateViewGameCardFsBrowser: - menu = (const char**)filenames; - menuItemsCount = filenamesCount; - - uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedOption] : viewGameCardFsType2MenuItems[selectedOption]), 0, breaks * 8, 115, 115, 255); - breaks += 2; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Current directory: %s", currentDirectory); - uiDrawString(titlebuf, 0, breaks * 8, 255, 255, 255); - - break; - default: - break; - } - - if (menu && menuItemsCount) - { - if (uiState != stateMainMenu) breaks += 2; - - int scrollAmount = 0; - if ((keysDown & KEY_DOWN) || (keysDown & KEY_RSTICK_DOWN) || (keysDown & KEY_LSTICK_DOWN)) scrollAmount = 1; - if ((keysDown & KEY_UP) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_LSTICK_UP)) scrollAmount = -1; - if (uiState != stateXciDumpMenu && ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT))) scrollAmount = -5; - if (uiState != stateXciDumpMenu && ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT))) scrollAmount = 5; - - if (scrollAmount > 0) - { - for (i = 0; i < scrollAmount; i++) - { - if (cursor < menuItemsCount - 1) - { - cursor++; - if ((cursor - scroll) >= maxListElements) scroll++; - } - } - } else - if (scrollAmount < 0) - { - for (i = 0; i < -scrollAmount; i++) - { - if (cursor > 0) - { - cursor--; - if ((cursor - scroll) < 0) scroll--; - } - } - } - - i = 0; - - for (int j = scroll; j < menuItemsCount; j++) - { - u8 color = 255; - - if (i + scroll == cursor) - { - uiFill(0, (breaks * 8) + (i * 13), currentFBWidth / 2, 13, 33, 34, 39); - uiDrawString(menu[j], 0, (breaks * 8) + (i * 13) + 2, 0, 255, 197); - } else { - uiDrawString(menu[j], 0, (breaks * 8) + (i * 13) + 2, color, color, color); - } - - // Print XCI dump menu settings values - if (uiState == stateXciDumpMenu && i > 0) - { - switch(i) - { - case 1: // Split output dump (FAT32 support) - if (isFat32) - { - uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0); - } else { - uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0); - } - break; - case 2: // Dump certificate - if (dumpCert) - { - uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0); - } else { - uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0); - } - break; - case 3: // Trim output dump - if (trimDump) - { - uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0); - } else { - uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0); - } - break; - case 4: // CRC32 checksum calculation + dump verification - if (calcCrc) - { - uiDrawString("Yes", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 0, 255, 0); - } else { - uiDrawString("No", strlen(menu[i]) * 8, (breaks * 8) + (i * 13) + 2, 255, 0, 0); - } - break; - default: - break; - } - } - - i++; - - if (i >= maxListElements) break; - } - - if (uiState == stateXciDumpMenu) - { - // Select - if ((keysDown & KEY_A) && cursor == 0) - { - selectedOption = (u32)cursor; - res = resultDumpXci; - } - - // Back - if (keysDown & KEY_B) res = resultShowMainMenu; - - // Change option to false - if ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT)) - { - switch(cursor) - { - case 1: // Split output dump (FAT32 support) - isFat32 = false; - break; - case 2: // Dump certificate - dumpCert = false; - break; - case 3: // Trim output dump - trimDump = false; - break; - case 4: // CRC32 checksum calculation + dump verification - calcCrc = false; - break; - default: - break; - } - } - - // Change option to true - if ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT)) - { - switch(cursor) - { - case 1: // Split output dump (FAT32 support) - isFat32 = true; - break; - case 2: // Dump certificate - dumpCert = true; - break; - case 3: // Trim output dump - trimDump = true; - break; - case 4: // CRC32 checksum calculation + dump verification - calcCrc = true; - break; - default: - break; - } - } - } else { - // Select - if (keysDown & KEY_A) - { - if (uiState == stateMainMenu) - { - switch(cursor) - { - case 0: - res = resultShowXciDumpMenu; - break; - case 1: - res = resultShowRawPartitionDumpMenu; - break; - case 2: - res = resultShowPartitionDataDumpMenu; - break; - case 3: - res = resultShowViewGameCardFsMenu; - break; - case 4: - res = resultDumpGameCardCertificate; - break; - case 5: - res = resultUpdateNSWDBXml; - break; - case 6: - //res = resultUpdateApplication; - break; - default: - break; - } - } else - if (uiState == stateRawPartitionDumpMenu) - { - selectedOption = (u32)cursor; - res = resultDumpRawPartition; - } else - if (uiState == statePartitionDataDumpMenu) - { - selectedOption = (u32)cursor; - res = resultDumpPartitionData; - } else - if (uiState == stateViewGameCardFsMenu) - { - selectedOption = (u32)cursor; - res = resultShowViewGameCardFsGetList; - } else - if (uiState == stateViewGameCardFsBrowser) - { - char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); - memset(selectedPath, 0, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); - - if (strlen(filenames[cursor]) == 2 && !strcmp(filenames[cursor], "..")) - { - for(i = (strlen(currentDirectory) - 1); i >= 0; i--) - { - if (currentDirectory[i] == '/') - { - strncpy(selectedPath, currentDirectory, i); - selectedPath[i] = '\0'; - break; - } - } - } else { - snprintf(selectedPath, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2, "%s/%s", currentDirectory, filenames[cursor]); - } - - if (isDirectory(selectedPath)) - { - enterDirectory(selectedPath); - } else { - snprintf(fileCopyPath, sizeof(fileCopyPath) / sizeof(fileCopyPath[0]), "%s", selectedPath); - res = resultViewGameCardFsBrowserCopyFile; - } - - free(selectedPath); - } - } - - // Back - if (keysDown & KEY_B) - { - if (uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu) - { - res = resultShowMainMenu; - } else - if (uiState == stateViewGameCardFsBrowser) - { - if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6) - { - fsdevUnmountDevice("view"); - fsFsClose(&fs); - - res = resultShowViewGameCardFsMenu; - } else { - char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1); - memset(selectedPath, 0, strlen(currentDirectory) + 1); - - for(i = (strlen(currentDirectory) - 1); i >= 0; i--) - { - if (currentDirectory[i] == '/') - { - strncpy(selectedPath, currentDirectory, i); - selectedPath[i] = '\0'; - break; - } - } - - if (isDirectory(selectedPath)) enterDirectory(selectedPath); - - free(selectedPath); - } - } - } - } - } - } - } else - if (uiState == stateDumpXci) - { - uiDrawString(mainMenuItems[0], 0, breaks * 8, 115, 115, 255); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[1], (isFat32 ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[2], (dumpCert ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (trimDump ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[4], (calcCrc ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks += 2; - - dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc); - - waitForButtonPress(); - - uiUpdateFreeSpace(); - res = resultShowXciDumpMenu; - } else - if (uiState == stateDumpRawPartition) - { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption])); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks += 2; - - dumpRawPartition(&fsOperatorInstance, selectedOption, true); - - waitForButtonPress(); - - uiUpdateFreeSpace(); - res = resultShowRawPartitionDumpMenu; - } else - if (uiState == stateDumpPartitionData) - { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption])); - uiDrawString(titlebuf, 0, breaks * 8, 115, 115, 255); - breaks += 2; - - dumpPartitionData(&fsOperatorInstance, selectedOption); - - waitForButtonPress(); - - uiUpdateFreeSpace(); - res = resultShowPartitionDataDumpMenu; - } else - if (uiState == stateViewGameCardFsGetList) - { - uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedOption] : viewGameCardFsType1MenuItems[selectedOption]), 0, breaks * 8, 115, 115, 255); - breaks += 2; - - if (mountViewPartition(&fsOperatorInstance, &fs, selectedOption)) - { - enterDirectory("view:/"); - - res = resultShowViewGameCardFsBrowser; - } else { - breaks += 2; - waitForButtonPress(); - res = resultShowViewGameCardFsMenu; - } - } else - if (uiState == stateViewGameCardFsBrowserCopyFile) - { - uiDrawString("Manual File Dump", 0, breaks * 8, 115, 115, 255); - breaks += 2; - - FILE *inFile = fopen(fileCopyPath, "rb"); - if (inFile) - { - fseek(inFile, 0L, SEEK_END); - u64 input_filesize = ftell(inFile); - fclose(inFile); - - 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 * 8, 255, 255, 255); - breaks += 2; - - copyFile(fileCopyPath, destCopyPath, true, true); - } else { - uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * 8, 255, 0, 0); - } - } else { - uiDrawString("Error: unable to get input file size.", 0, breaks * 8, 255, 0, 0); - } - - breaks += 2; - - waitForButtonPress(); - - uiUpdateFreeSpace(); - res = resultShowViewGameCardFsBrowser; - } else - if (uiState == stateDumpGameCardCertificate) - { - uiDrawString(mainMenuItems[4], 0, breaks * 8, 115, 115, 255); - breaks += 2; - - dumpGameCertificate(&fsOperatorInstance); - - 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; - } - - uiUpdateStatusMsg(); - - return res; + Result rc = 0; + FT_Error ret = 0; + + /* Initialize pl service */ + rc = plInitialize(); + if (R_FAILED(rc)) return error_screen("plInitialize() failed: 0x%x\n", rc); + + /* Retrieve shared font */ + rc = plGetSharedFontByType(&font, PlSharedFontType_Standard); + if (R_FAILED(rc)) + { + plExit(); + return error_screen("plGetSharedFontByType() failed: 0x%x\n", rc); + } + + /* Initialize FreeType */ + ret = FT_Init_FreeType(&library); + if (ret) + { + plExit(); + return error_screen("FT_Init_FreeType() failed: %d\n", ret); + } + + /* Create memory face */ + ret = FT_New_Memory_Face(library, font.address, font.size, 0, &face); + if (ret) + { + FT_Done_FreeType(library); + plExit(); + return error_screen("FT_New_Memory_Face() failed: %d\n", ret); + } + + /* Set font character size */ + ret = FT_Set_Char_Size(face, 0, CHAR_PT_SIZE * 64, SCREEN_DPI_CNT, SCREEN_DPI_CNT); + if (ret) + { + FT_Done_Face(face); + FT_Done_FreeType(library); + plExit(); + return error_screen("FT_Set_Char_Size() failed: %d\n", ret); + } + + /* Store font height and max width */ + font_height = (face->size->metrics.height / 64); + + /* Create framebuffer */ + framebufferCreate(&fb, nwindowGetDefault(), FB_WIDTH, FB_HEIGHT, PIXEL_FORMAT_RGBA_8888, 2); + framebufferMakeLinear(&fb); + + /* Prepare additional data needed by the UI functions */ + uiState = stateMainMenu; + cursor = 0; + scroll = 0; + headlineCnt = 1; + + filenameBuffer = (char*)malloc(FILENAME_BUFFER_SIZE); + + int i, headlineLen = strlen(appHeadline); + for(i = 0; i < headlineLen; i++) + { + if (appHeadline[i] == '\n') headlineCnt++; + } + + if (headlineCnt == 1) headlineCnt += 2; + + uiUpdateFreeSpace(); + + /* Disable screen dimming and auto sleep */ + appletSetMediaPlaybackState(true); + + /* Clear screen */ + uiClearScreen(); + + return 1; +} + +void uiDeinit() +{ + /* Enable screen dimming and auto sleep */ + appletSetMediaPlaybackState(false); + + /* Free filename buffer */ + if (filenameBuffer) free(filenameBuffer); + + /* Free framebuffer object */ + framebufferClose(&fb); + + /* Free FreeType resources */ + FT_Done_Face(face); + FT_Done_FreeType(library); + + /* Deinitialize pl service */ + plExit(); +} + +void uiSetState(UIState state) +{ + uiState = state; + cursor = 0; + scroll = 0; +} + +UIState uiGetState() +{ + return uiState; +} + +UIResult uiProcess() +{ + UIResult res = resultNone; + + int i, j; + breaks = headlineCnt; + + const char **menu = NULL; + int menuItemsCount = 0; + + u32 keysDown; + + uiPrintHeadline(); + loadGameCardInfo(); + + if (uiState == stateMainMenu || uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu || uiState == stateViewGameCardFsBrowser) + { + uiDrawString(appControls, 0, breaks * font_height, 255, 255, 255); + breaks += 2; + + uiDrawString(freeSpaceStr, 0, breaks * font_height, 255, 255, 255); + breaks += 2; + + if (uiState != stateViewGameCardFsBrowser) + { + if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) + { + uiDrawString("Game Card is inserted!", 0, breaks * font_height, 0, 255, 0); + breaks += 2; + + /*snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header offset: 0x%016lX", hfs0_offset); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header size: 0x%016lX", hfs0_size); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++;*/ + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Name: %s", gameCardName); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Developer: %s", gameCardAuthor); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Title ID: %016lX", gameCardTitleID); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Version: %s", gameCardVersionStr); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Size: %s", gameCardSizeStr); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Used space: %s", trimmedCardSizeStr); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt)); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + + if (strlen(gameCardUpdateVersionStr)) + { + breaks++; + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Bundled FW update: %s", gameCardUpdateVersionStr); + uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + } + } else { + if (gameCardInserted) + { + if (hfs0_header != NULL) + { + 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 * font_height, 255, 0, 0); + + if (strlen(gameCardUpdateVersionStr)) + { + breaks++; + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Bundled FW Update: %s", gameCardUpdateVersionStr); + uiDrawString(titlebuf, 0, breaks * font_height, 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 * font_height, 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 * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: unable to get root HFS0 header data!", 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Game Card is not inserted!", 0, breaks * font_height, 255, 0, 0); + } + + res = resultShowMainMenu; + } + + breaks += 2; + } + + if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) + { + switch(uiState) + { + case stateMainMenu: + menu = mainMenuItems; + menuItemsCount = sizeof(mainMenuItems) / sizeof(mainMenuItems[0]); + break; + case stateXciDumpMenu: + menu = xciDumpMenuItems; + menuItemsCount = sizeof(xciDumpMenuItems) / sizeof(xciDumpMenuItems[0]); + + uiDrawString(mainMenuItems[0], 0, breaks * font_height, 115, 115, 255); + + break; + case stateRawPartitionDumpMenu: + case statePartitionDataDumpMenu: + menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems : partitionDumpType2MenuItems); + menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(partitionDumpType1MenuItems) / sizeof(partitionDumpType1MenuItems[0])) : (sizeof(partitionDumpType2MenuItems) / sizeof(partitionDumpType2MenuItems[0]))); + + uiDrawString((uiState == stateRawPartitionDumpMenu ? mainMenuItems[1] : mainMenuItems[2]), 0, breaks * font_height, 115, 115, 255); + + break; + case stateViewGameCardFsMenu: + menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems : viewGameCardFsType2MenuItems); + menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(viewGameCardFsType1MenuItems) / sizeof(viewGameCardFsType1MenuItems[0])) : (sizeof(viewGameCardFsType2MenuItems) / sizeof(viewGameCardFsType2MenuItems[0]))); + + uiDrawString(mainMenuItems[3], 0, breaks * font_height, 115, 115, 255); + + break; + case stateViewGameCardFsBrowser: + 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); + breaks += 2; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "File count: %d | Current file: %d", menuItemsCount, cursor + 1); + uiDrawString(titlebuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + break; + default: + break; + } + + if (menu && menuItemsCount) + { + if (uiState != stateMainMenu) breaks += 2; + + j = 0; + + for(i = scroll; i < menuItemsCount; i++, j++) + { + if (j >= maxListElements) break; + + if ((j + scroll) == cursor) + { + highlight = true; + uiFill(0, (breaks * font_height) + (j * (font_height + 12)), FB_WIDTH / 2, font_height + 12, HIGHLIGHT_BG_COLOR_R, HIGHLIGHT_BG_COLOR_G, HIGHLIGHT_BG_COLOR_B); + uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, HIGHLIGHT_FONT_COLOR_R, HIGHLIGHT_FONT_COLOR_G, HIGHLIGHT_FONT_COLOR_B); + highlight = false; + } else { + uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 255, 255); + } + + // Print XCI dump menu settings values + if (uiState == stateXciDumpMenu && j > 0) + { + if ((j + scroll) == cursor) highlight = true; + + switch(j) + { + case 1: // Split output dump (FAT32 support) + if (isFat32) + { + uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 2: // Dump certificate + if (dumpCert) + { + uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 3: // Trim output dump + if (trimDump) + { + uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 4: // CRC32 checksum calculation + dump verification + if (calcCrc) + { + uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + default: + break; + } + + if ((j + scroll) == cursor) highlight = false; + } + } + } + } + + uiRefreshDisplay(); + + hidScanInput(); + keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + + // Exit + if (keysDown & KEY_PLUS) + { + if (uiState == stateViewGameCardFsBrowser) + { + fsdevUnmountDevice("view"); + fsFsClose(&fs); + } + + res = resultExit; + } + + // Process key inputs only if the UI state hasn't been changed + if (res == resultNone) + { + int scrollAmount = 0; + + if (uiState == stateXciDumpMenu) + { + // Select + if ((keysDown & KEY_A) && cursor == 0) + { + selectedOption = (u32)cursor; + res = resultDumpXci; + } + + // Back + if (keysDown & KEY_B) res = resultShowMainMenu; + + // Change option to false + if (keysDown & KEY_LEFT) + { + switch(cursor) + { + case 1: // Split output dump (FAT32 support) + isFat32 = false; + break; + case 2: // Dump certificate + dumpCert = false; + break; + case 3: // Trim output dump + trimDump = false; + break; + case 4: // CRC32 checksum calculation + dump verification + calcCrc = false; + break; + default: + break; + } + } + + // Change option to true + if (keysDown & KEY_RIGHT) + { + switch(cursor) + { + case 1: // Split output dump (FAT32 support) + isFat32 = true; + break; + case 2: // Dump certificate + dumpCert = true; + break; + case 3: // Trim output dump + trimDump = true; + break; + case 4: // CRC32 checksum calculation + dump verification + calcCrc = true; + break; + default: + break; + } + } + + // Go up + if (keysDown & KEY_UP) scrollAmount = -1; + + // Go down + if (keysDown & KEY_DOWN) scrollAmount = 1; + } else { + // Select + if (keysDown & KEY_A) + { + if (uiState == stateMainMenu) + { + switch(cursor) + { + case 0: + res = resultShowXciDumpMenu; + break; + case 1: + res = resultShowRawPartitionDumpMenu; + break; + case 2: + res = resultShowPartitionDataDumpMenu; + break; + case 3: + res = resultShowViewGameCardFsMenu; + break; + case 4: + res = resultDumpGameCardCertificate; + break; + case 5: + res = resultUpdateNSWDBXml; + break; + case 6: + res = resultUpdateApplication; + break; + default: + break; + } + } else + if (uiState == stateRawPartitionDumpMenu) + { + selectedOption = (u32)cursor; + res = resultDumpRawPartition; + } else + if (uiState == statePartitionDataDumpMenu) + { + selectedOption = (u32)cursor; + res = resultDumpPartitionData; + } else + if (uiState == stateViewGameCardFsMenu) + { + selectedOption = (u32)cursor; + res = resultShowViewGameCardFsGetList; + } else + if (uiState == stateViewGameCardFsBrowser) + { + char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); + memset(selectedPath, 0, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2); + + if (strlen(filenames[cursor]) == 2 && !strcmp(filenames[cursor], "..")) + { + for(i = (strlen(currentDirectory) - 1); i >= 0; i--) + { + if (currentDirectory[i] == '/') + { + strncpy(selectedPath, currentDirectory, i); + selectedPath[i] = '\0'; + break; + } + } + } else { + snprintf(selectedPath, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2, "%s/%s", currentDirectory, filenames[cursor]); + } + + if (isDirectory(selectedPath)) + { + enterDirectory(selectedPath); + } else { + snprintf(fileCopyPath, sizeof(fileCopyPath) / sizeof(fileCopyPath[0]), "%s", selectedPath); + res = resultViewGameCardFsBrowserCopyFile; + } + + free(selectedPath); + } + } + + // Back + if (keysDown & KEY_B) + { + if (uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu) + { + res = resultShowMainMenu; + } else + if (uiState == stateViewGameCardFsBrowser) + { + if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6) + { + fsdevUnmountDevice("view"); + fsFsClose(&fs); + + 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; + + // Go down + if (keysDown & KEY_DOWN) scrollAmount = 1; + if (keysDown & KEY_RIGHT) scrollAmount = 5; + } + + // Calculate scroll only if the UI state hasn't been changed + if (res == resultNone) + { + if (scrollAmount > 0) + { + for(i = 0; i < scrollAmount; i++) + { + if (cursor < menuItemsCount - 1) + { + cursor++; + if ((cursor - scroll) >= maxListElements) scroll++; + } + } + } else + if (scrollAmount < 0) + { + for(i = 0; i < -scrollAmount; i++) + { + if (cursor > 0) + { + cursor--; + if ((cursor - scroll) < 0) scroll--; + } + } + } + } + } + } else + if (uiState == stateDumpXci) + { + uiDrawString(mainMenuItems[0], 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[1], (isFat32 ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[2], (dumpCert ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (trimDump ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[4], (calcCrc ? "Yes" : "No")); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowXciDumpMenu; + } else + if (uiState == stateDumpRawPartition) + { + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption])); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + dumpRawPartition(&fsOperatorInstance, selectedOption, true); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowRawPartitionDumpMenu; + } else + if (uiState == stateDumpPartitionData) + { + snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedOption] : partitionDumpType2MenuItems[selectedOption])); + uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + dumpPartitionData(&fsOperatorInstance, selectedOption); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowPartitionDataDumpMenu; + } else + if (uiState == stateViewGameCardFsGetList) + { + uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedOption] : viewGameCardFsType1MenuItems[selectedOption]), 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + if (mountViewPartition(&fsOperatorInstance, &fs, selectedOption)) + { + enterDirectory("view:/"); + + res = resultShowViewGameCardFsBrowser; + } else { + breaks += 2; + waitForButtonPress(); + res = resultShowViewGameCardFsMenu; + } + } else + if (uiState == stateViewGameCardFsBrowserCopyFile) + { + uiDrawString("Manual File Dump", 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); + + 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); + } + + breaks += 2; + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowViewGameCardFsBrowser; + } else + if (uiState == stateDumpGameCardCertificate) + { + uiDrawString(mainMenuItems[4], 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + dumpGameCertificate(&fsOperatorInstance); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowMainMenu; + } else + if (uiState == stateUpdateNSWDBXml) + { + uiDrawString(mainMenuItems[5], 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + updateNSWDBXml(); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowMainMenu; + } else + if (uiState == stateUpdateApplication) + { + uiDrawString(mainMenuItems[6], 0, breaks * font_height, 115, 115, 255); + breaks += 2; + + updateApplication(); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowMainMenu; + } + + uiUpdateStatusMsg(); + + return res; } diff --git a/source/ui.h b/source/ui.h index 3d63d78..a9ab3df 100644 --- a/source/ui.h +++ b/source/ui.h @@ -3,64 +3,88 @@ #ifndef __UI_H__ #define __UI_H__ -#define FILENAME_BUFFER_SIZE (1024 * 32) // 32 KiB -#define FILENAME_MAX_CNT 2048 +#define FB_WIDTH 1280 +#define FB_HEIGHT 720 + +#define CHAR_PT_SIZE 12 +#define SCREEN_DPI_CNT 96 + +#define BG_COLOR_RGB 50 + +#define HIGHLIGHT_BG_COLOR_R 33 +#define HIGHLIGHT_BG_COLOR_G 34 +#define HIGHLIGHT_BG_COLOR_B 39 + +#define HIGHLIGHT_FONT_COLOR_R 0 +#define HIGHLIGHT_FONT_COLOR_G 255 +#define HIGHLIGHT_FONT_COLOR_B 197 + +#define XCIDUMP_OPTIONS_X_POS (35 * CHAR_PT_SIZE) + +#define TAB_WIDTH 4 typedef enum { - resultNone, - resultShowMainMenu, - resultShowXciDumpMenu, - resultDumpXci, - resultShowRawPartitionDumpMenu, - resultDumpRawPartition, - resultShowPartitionDataDumpMenu, - resultDumpPartitionData, - resultShowViewGameCardFsMenu, - resultShowViewGameCardFsGetList, - resultShowViewGameCardFsBrowser, - resultViewGameCardFsBrowserCopyFile, - resultDumpGameCardCertificate, - resultUpdateNSWDBXml, - resultUpdateApplication, - resultExit + resultNone, + resultShowMainMenu, + resultShowXciDumpMenu, + resultDumpXci, + resultShowRawPartitionDumpMenu, + resultDumpRawPartition, + resultShowPartitionDataDumpMenu, + resultDumpPartitionData, + resultShowViewGameCardFsMenu, + resultShowViewGameCardFsGetList, + resultShowViewGameCardFsBrowser, + resultViewGameCardFsBrowserCopyFile, + resultDumpGameCardCertificate, + resultUpdateNSWDBXml, + resultUpdateApplication, + resultExit } UIResult; typedef enum { - stateMainMenu, - stateXciDumpMenu, - stateDumpXci, - stateRawPartitionDumpMenu, - stateDumpRawPartition, - statePartitionDataDumpMenu, - stateDumpPartitionData, - stateViewGameCardFsMenu, - stateViewGameCardFsGetList, - stateViewGameCardFsBrowser, - stateViewGameCardFsBrowserCopyFile, - stateDumpGameCardCertificate, - stateUpdateNSWDBXml, - stateUpdateApplication + stateMainMenu, + stateXciDumpMenu, + stateDumpXci, + stateRawPartitionDumpMenu, + stateDumpRawPartition, + statePartitionDataDumpMenu, + stateDumpPartitionData, + stateViewGameCardFsMenu, + stateViewGameCardFsGetList, + stateViewGameCardFsBrowser, + stateViewGameCardFsBrowserCopyFile, + stateDumpGameCardCertificate, + stateUpdateNSWDBXml, + stateUpdateApplication } UIState; void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b); -void uiDrawString(const char* string, int x, int y, u8 r, u8 g, u8 b); -void uiStatusMsg(const char* fmt, ...); +void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b); + +void uiRefreshDisplay(); + +void uiStatusMsg(const char *fmt, ...); + void uiUpdateStatusMsg(); void uiPleaseWait(); void uiUpdateFreeSpace(); -void uiInit(); +void uiClearScreen(); + +void uiPrintHeadline(); + +int uiInit(); + void uiDeinit(); void uiSetState(UIState state); + UIState uiGetState(); -void uiClearScreen(); -void uiPrintHeadline(); - -UIResult uiLoop(u32 keysDown); +UIResult uiProcess(); #endif diff --git a/source/util.c b/source/util.c index 7777b23..4fe4bf8 100644 --- a/source/util.c +++ b/source/util.c @@ -20,11 +20,15 @@ #include "ui.h" #include "util.h" -extern int breaks; +/* Extern variables */ -extern u64 gameCardSize; -extern u64 gameCardTitleID; -extern u32 hfs0_partition_cnt; +extern int breaks; +extern int font_height; + +extern int cursor; +extern int scroll; + +/* Constants */ const char *nswReleasesXmlUrl = "http://nswdb.com/xml.php"; const char *nswReleasesXmlTmpPath = "sdmc:/NSWreleases.xml.tmp"; @@ -43,832 +47,975 @@ const char *gcDumpToolPath = "sdmc:/switch/gcdumptool.nro"; const char *userAgent = "gcdumptool/" APP_VERSION " (Nintendo Switch)"; +/* Statically allocated variables */ + static char *result_buf = NULL; static size_t result_sz = 0; static size_t result_written = 0; -bool isGameCardInserted(FsDeviceOperator* o) +char currentDirectory[NAME_BUF_LEN] = {'\0'}; + +char *filenameBuffer = NULL; +char *filenames[FILENAME_MAX_CNT]; +int filenamesCount = 0; + +FsDeviceOperator fsOperatorInstance; +FsEventNotifier fsGameCardEventNotifier; +Handle fsGameCardEventHandle; +Event fsGameCardKernelEvent; +UEvent exitEvent; + +bool gameCardInserted; + +u64 gameCardSize = 0, trimmedCardSize = 0; +char gameCardSizeStr[32] = {'\0'}, trimmedCardSizeStr[32] = {'\0'}; + +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; +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'}; + +bool isGameCardInserted() { bool inserted; - if (R_FAILED(fsDeviceOperatorIsGameCardInserted(o, &inserted))) return false; + if (R_FAILED(fsDeviceOperatorIsGameCardInserted(&fsOperatorInstance, &inserted))) return false; return inserted; } -void syncDisplay() +void fsGameCardDetectionThreadFunc(void *arg) { - gfxFlushBuffers(); - gfxSwapBuffers(); - gfxWaitForVsync(); + int idx; + Result rc; + + while(true) + { + rc = waitMulti(&idx, -1, waiterForEvent(&fsGameCardKernelEvent), waiterForUEvent(&exitEvent)); + if (R_SUCCEEDED(rc)) + { + if (idx == 0) + { + // Retrieve current gamecard status + gameCardInserted = isGameCardInserted(); + eventClear(&fsGameCardKernelEvent); + } else { + break; + } + } + } + + waitMulti(&idx, 0, waiterForEvent(&fsGameCardKernelEvent), waiterForUEvent(&exitEvent)); } void delay(u8 seconds) { - if (!seconds) return; - - u64 nanoseconds = seconds * (u64)1000000000; - svcSleepThread(nanoseconds); - - syncDisplay(); + if (!seconds) return; + + u64 nanoseconds = seconds * (u64)1000000000; + svcSleepThread(nanoseconds); + + uiRefreshDisplay(); } bool getGameCardTitleIDAndVersion(u64 *titleID, u32 *version) { - bool success = false; - - Result result; - NcmContentMetaDatabase ncmDb; - - NcmApplicationContentMetaKey *appList = (NcmApplicationContentMetaKey*)malloc(sizeof(NcmApplicationContentMetaKey)); - if (appList) - { - memset(appList, 0, sizeof(NcmApplicationContentMetaKey)); - - if (R_SUCCEEDED(result = ncmOpenContentMetaDatabase(FsStorageId_GameCard, &ncmDb))) - { - if (R_SUCCEEDED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DATABASE_FILTER, appList, sizeof(NcmApplicationContentMetaKey), NULL, NULL))) - { - *titleID = appList->metaRecord.titleId; - *version = appList->metaRecord.version; - success = true; - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); - } - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: ncmOpenContentMetaDatabase failed! (0x%08X)", result); - } - - free(appList); - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: Unable to allocate memory for the ncm service operations."); - } - - return success; + bool success = false; + + Result result; + NcmContentMetaDatabase ncmDb; + + NcmApplicationContentMetaKey *appList = (NcmApplicationContentMetaKey*)malloc(sizeof(NcmApplicationContentMetaKey)); + if (appList) + { + memset(appList, 0, sizeof(NcmApplicationContentMetaKey)); + + if (R_SUCCEEDED(result = ncmOpenContentMetaDatabase(FsStorageId_GameCard, &ncmDb))) + { + if (R_SUCCEEDED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DATABASE_FILTER, appList, sizeof(NcmApplicationContentMetaKey), NULL, NULL))) + { + *titleID = appList->metaRecord.titleId; + *version = appList->metaRecord.version; + success = true; + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmOpenContentMetaDatabase failed! (0x%08X)", result); + } + + free(appList); + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: Unable to allocate memory for the ncm service operations."); + } + + return success; } 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); + 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; - - bool success = false; - Result result; - size_t outsize = 0; - NsApplicationControlData *buf = NULL; - NacpLanguageEntry *langentry = NULL; - - buf = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData)); - if (buf) - { - memset(buf, 0, sizeof(NsApplicationControlData)); - - if (R_SUCCEEDED(result = nsGetApplicationControlData(1, titleID, buf, sizeof(NsApplicationControlData), &outsize))) - { - if (outsize >= sizeof(buf->nacp)) - { - if (R_SUCCEEDED(result = nacpGetLanguageEntry(&buf->nacp, &langentry))) - { - strncpy(nameBuf, langentry->name, nameBufSize - 1); - strncpy(authorBuf, langentry->author, authorBufSize - 1); - success = true; - } else { - 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); - } - - free(buf); - } else { - uiStatusMsg("getGameCardControlNacp: Unable to allocate memory for the ns service operations."); - } - - return success; -} - -int getSdCardFreeSpace(u64 *out) -{ - struct statvfs st; - int rc; - - rc = statvfs("sdmc:/", &st); - if (rc != 0) - { - uiStatusMsg("getSdCardFreeSpace: Unable to get SD card filesystem stats! statvfs: %d (%s).", errno, strerror(errno)); - } else { - *out = (u64)(st.f_bsize * st.f_bfree); - } - - return rc; -} - -#define KiB (1024.0) -#define MiB (1024.0 * KiB) -#define GiB (1024.0 * MiB) - -void convertSize(u64 size, char *out, int bufsize) -{ - char buffer[16]; - double bytes = (double)size; - - if (bytes < 1000.0) - { - snprintf(buffer, sizeof(buffer), "%.0lf B", bytes); - } else - if (bytes < 10.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf KiB", floor((bytes*100.0)/KiB)/100.0); - } else - if (bytes < 100.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf KiB", floor((bytes*10.0)/KiB)/10.0); - } else - if (bytes < 1000.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.0lf KiB", floor(bytes/KiB)); - } else - if (bytes < 10.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf MiB", floor((bytes*100.0)/MiB)/100.0); - } else - if (bytes < 100.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf MiB", floor((bytes*10.0)/MiB)/10.0); - } else - if (bytes < 1000.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.0lf MiB", floor(bytes/MiB)); - } else - if (bytes < 10.0*GiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf GiB", floor((bytes*100.0)/GiB)/100.0); - } else - if (bytes < 100.0*GiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf GiB", floor((bytes*10.0)/GiB)/10.0); - } else { - snprintf(buffer, sizeof(buffer), "%.0lf GiB", floor(bytes/GiB)); - } - - snprintf(out, bufsize, "%s", buffer); -} - -void waitForButtonPress() -{ - uiDrawString("Press any button to continue", 0, breaks * 8, 255, 255, 255); - - syncDisplay(); - - while(true) - { - hidScanInput(); - - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - if (keysDown && !(keysDown & KEY_TOUCH)) break; - } -} - -bool isDirectory(char *path) -{ - 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; - strcpy(*nextFilename, string); - *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) -{ - struct dirent *ent; - int i, maxFilenamesCount = *filenamesCount; - char *nextFilename = filenameBuffer; - - 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); - if (dir) - { - for(i = 0; i < maxFilenamesCount; i++) - { - 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); - } - - closedir(dir); - } - - free(slash); - - // ".." should stay at the top - qsort(filenames + 1, (*filenamesCount) - 1, sizeof(char*), &sortAlpha); -} - -bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) -{ - xmlChar *key; - xmlNodePtr node = cur; - - u8 imageSize = (u8)(gameCardSize / GAMECARD_SIZE_1GiB); - u8 card = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? 1 : 2); - - u8 xmlImageSize = 0; - u64 xmlTitleID = 0; - u32 xmlCrc = 0; - u8 xmlCard = 0; - char xmlReleaseName[256] = {'\0'}; - - bool found = false; - char strbuf[512] = {'\0'}; - - while (node != NULL) - { - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImageSize))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - - xmlImageSize = (u8)atoi((const char*)key); - - xmlFree(key); - } else - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenTitleID))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - - xmlTitleID = strtoull((const char*)key, NULL, 16); - - xmlFree(key); - } else - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImgCrc))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - - xmlCrc = strtoul((const char*)key, NULL, 16); - - xmlFree(key); - } - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenReleaseName))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - - snprintf(xmlReleaseName, sizeof(xmlReleaseName) / sizeof(xmlReleaseName[0]), "%s", (char*)key); - - xmlFree(key); - } else - if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenCard))) - { - key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - - xmlCard = (u8)atoi((const char*)key); - - xmlFree(key); - } - - node = node->next; - } - - //snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image Size: %u\nCartridge Title ID: %016lX\nCartridge Image CRC32: %08X\nCartridge Type: %u\n\nXML Image Size: %u\nXML Title ID: %016lX\nXML Image CRC32: %08X\nXML Release Name: %s\nXML Card Type: %u", imageSize, gameCardTitleID, crc, card, xmlImageSize, xmlTitleID, xmlCrc, xmlReleaseName, xmlCard); - //uiDrawString(strbuf, 0, 0, 255, 255, 255); - - if (xmlImageSize == imageSize && xmlTitleID == gameCardTitleID && xmlCrc == crc && xmlCard == card) - { - 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); - - found = true; - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump doesn't match Scene release: \"%s\"! (CRC32: %08X)", xmlReleaseName, xmlCrc); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - breaks++; - - return found; -} - -xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath) -{ - xmlXPathContextPtr context = NULL; - xmlXPathObjectPtr result = NULL; - - context = xmlXPathNewContext(doc); - result = xmlXPathEvalExpression(xpath, context); - - if (xmlXPathNodeSetIsEmpty(result->nodesetval)) - { - xmlXPathFreeObject(result); - return NULL; - } - - return result; -} - -void gameCardDumpNSWDBCheck(u32 crc) -{ - if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return; - - xmlDocPtr doc = NULL; - bool found = false; - char strbuf[512] = {'\0'}; - - doc = xmlParseFile(nswReleasesXmlPath); - if (doc) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, gameCardTitleID); - xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf); - if (nodeSet) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found %d %s with Title ID \"%016lX\"", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), gameCardTitleID); - uiDrawString(strbuf, 0, breaks * 8, 255, 255, 255); - breaks++; - - u32 i; - for (i = 0; i < nodeSet->nodesetval->nodeNr; i++) - { - xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode; - - found = parseNSWDBRelease(doc, node, crc); - if (found) break; - } - - if (!found) - { - uiDrawString("No matches found in XML document! This could either be a bad dump or an undumped cartridge.", 0, breaks * 8, 255, 0, 0); - } else { - breaks--; - } - - xmlXPathFreeObject(nodeSet); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to find records with Title ID \"%016lX\" within the XML document!", gameCardTitleID); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } - - xmlFreeDoc(doc); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open and/or parse \"%s\"!", nswReleasesXmlPath); - uiDrawString(strbuf, 0, breaks * 8, 255, 0, 0); - } -} - -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; + if (titleID == 0) return false; + + bool success = false; + Result result; + size_t outsize = 0; + NsApplicationControlData *buf = NULL; + NacpLanguageEntry *langentry = NULL; + + buf = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData)); + if (buf) + { + memset(buf, 0, sizeof(NsApplicationControlData)); + + if (R_SUCCEEDED(result = nsGetApplicationControlData(1, titleID, buf, sizeof(NsApplicationControlData), &outsize))) + { + if (outsize >= sizeof(buf->nacp)) + { + if (R_SUCCEEDED(result = nacpGetLanguageEntry(&buf->nacp, &langentry))) + { + strncpy(nameBuf, langentry->name, nameBufSize - 1); + strncpy(authorBuf, langentry->author, authorBufSize - 1); + success = true; + } else { + 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); + } + + free(buf); + } else { + uiStatusMsg("getGameCardControlNacp: Unable to allocate memory for the ns service operations."); + } + + return success; } 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] = '_'; - } + 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] = '_'; + } } 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); + 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); +} + +void loadGameCardInfo() +{ + if (gameCardInserted) + { + if (hfs0_header == NULL) + { + /* Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services */ + uiPleaseWait(); + + 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(); + } + } else { + if (hfs0_header != NULL) + { + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + free(hfs0_header); + hfs0_header = NULL; + 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(gameCardVersionStr, 0, sizeof(gameCardVersionStr)); + + gameCardUpdateTitleID = 0; + gameCardUpdateVersion = 0; + + memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr)); + } + } +} + +int getSdCardFreeSpace(u64 *out) +{ + struct statvfs st; + int rc; + + rc = statvfs("sdmc:/", &st); + if (rc != 0) + { + uiStatusMsg("getSdCardFreeSpace: Unable to get SD card filesystem stats! statvfs: %d (%s).", errno, strerror(errno)); + } else { + *out = (u64)(st.f_bsize * st.f_bfree); + } + + return rc; +} + +void convertSize(u64 size, char *out, int bufsize) +{ + char buffer[16]; + double bytes = (double)size; + + if (bytes < 1000.0) + { + snprintf(buffer, sizeof(buffer), "%.0lf B", bytes); + } else + if (bytes < 10.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf KiB", floor((bytes*100.0)/KiB)/100.0); + } else + if (bytes < 100.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf KiB", floor((bytes*10.0)/KiB)/10.0); + } else + if (bytes < 1000.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.0lf KiB", floor(bytes/KiB)); + } else + if (bytes < 10.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf MiB", floor((bytes*100.0)/MiB)/100.0); + } else + if (bytes < 100.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf MiB", floor((bytes*10.0)/MiB)/10.0); + } else + if (bytes < 1000.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.0lf MiB", floor(bytes/MiB)); + } else + if (bytes < 10.0*GiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf GiB", floor((bytes*100.0)/GiB)/100.0); + } else + if (bytes < 100.0*GiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf GiB", floor((bytes*10.0)/GiB)/10.0); + } else { + snprintf(buffer, sizeof(buffer), "%.0lf GiB", floor(bytes/GiB)); + } + + snprintf(out, bufsize, "%s", buffer); +} + +void waitForButtonPress() +{ + uiDrawString("Press any button to continue", 0, breaks * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + while(true) + { + hidScanInput(); + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown && !(keysDown & KEY_TOUCH)) break; + } +} + +bool isDirectory(char *path) +{ + 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; + strcpy(*nextFilename, string); + *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) +{ + struct dirent *ent; + int i, maxFilenamesCount = *filenamesCount; + char *nextFilename = filenameBuffer; + + 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); + if (dir) + { + for(i = 0; i < maxFilenamesCount; i++) + { + 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); + } + + closedir(dir); + } + + 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) +{ + xmlChar *key; + xmlNodePtr node = cur; + + u8 imageSize = (u8)(gameCardSize / GAMECARD_SIZE_1GiB); + u8 card = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? 1 : 2); + + u8 xmlImageSize = 0; + u64 xmlTitleID = 0; + u32 xmlCrc = 0; + u8 xmlCard = 0; + char xmlReleaseName[256] = {'\0'}; + + bool found = false; + char strbuf[512] = {'\0'}; + + while (node != NULL) + { + if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImageSize))) + { + key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + + xmlImageSize = (u8)atoi((const char*)key); + + xmlFree(key); + } else + if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenTitleID))) + { + key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + + xmlTitleID = strtoull((const char*)key, NULL, 16); + + xmlFree(key); + } else + if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenImgCrc))) + { + key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + + xmlCrc = strtoul((const char*)key, NULL, 16); + + xmlFree(key); + } + if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenReleaseName))) + { + key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + + snprintf(xmlReleaseName, sizeof(xmlReleaseName) / sizeof(xmlReleaseName[0]), "%s", (char*)key); + + xmlFree(key); + } else + if ((!xmlStrcmp(node->name, (const xmlChar *)nswReleasesChildrenCard))) + { + key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + + xmlCard = (u8)atoi((const char*)key); + + xmlFree(key); + } + + node = node->next; + } + + //snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image Size: %u\nCartridge Title ID: %016lX\nCartridge Image CRC32: %08X\nCartridge Type: %u\n\nXML Image Size: %u\nXML Title ID: %016lX\nXML Image CRC32: %08X\nXML Release Name: %s\nXML Card Type: %u", imageSize, gameCardTitleID, crc, card, xmlImageSize, xmlTitleID, xmlCrc, xmlReleaseName, xmlCard); + //uiDrawString(strbuf, 0, 0, 255, 255, 255); + + if (xmlImageSize == imageSize && xmlTitleID == gameCardTitleID && xmlCrc == crc && xmlCard == card) + { + 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 * font_height, 0, 255, 0); + + found = true; + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump doesn't match Scene release: \"%s\"! (CRC32: %08X)", xmlReleaseName, xmlCrc); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + breaks++; + + return found; +} + +xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath) +{ + xmlXPathContextPtr context = NULL; + xmlXPathObjectPtr result = NULL; + + context = xmlXPathNewContext(doc); + result = xmlXPathEvalExpression(xpath, context); + + if (xmlXPathNodeSetIsEmpty(result->nodesetval)) + { + xmlXPathFreeObject(result); + return NULL; + } + + return result; +} + +void gameCardDumpNSWDBCheck(u32 crc) +{ + if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return; + + xmlDocPtr doc = NULL; + bool found = false; + char strbuf[512] = {'\0'}; + + doc = xmlParseFile(nswReleasesXmlPath); + if (doc) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, gameCardTitleID); + xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf); + if (nodeSet) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found %d %s with Title ID \"%016lX\".", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), gameCardTitleID); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + u32 i; + for (i = 0; i < nodeSet->nodesetval->nodeNr; i++) + { + xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode; + + found = parseNSWDBRelease(doc, node, crc); + if (found) break; + } + + if (!found) + { + uiDrawString("No matches found in XML document! This could either be a bad dump or an undumped cartridge.", 0, breaks * font_height, 255, 0, 0); + } else { + breaks--; + } + + xmlXPathFreeObject(nodeSet); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to find records with Title ID \"%016lX\" within the XML document!", gameCardTitleID); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + xmlFreeDoc(doc); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open and/or parse \"%s\"!", nswReleasesXmlPath); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } +} + +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; +} + +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 * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + 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 * font_height, 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 * font_height, 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 * font_height, 255, 0, 0); + } + + curl_easy_cleanup(curl); + } else { + uiDrawString("Error: failed to initialize CURL context!", 0, breaks * font_height, 255, 0, 0); + } + + networkDeinit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize socket! (%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + breaks += 2; +} + +int versionNumCmp(char *ver1, char *ver2) +{ + int i, curPart, res; + char *token = NULL; + + // Define a struct for comparison purposes + 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)); + + // Create copies of the version strings to avoid modifications by strtok() + char ver1tok[64] = {'\0'}; + snprintf(ver1tok, 63, ver1); + + char ver2tok[64] = {'\0'}; + snprintf(ver2tok, 63, ver2); + + // Parse version string 1 + i = 0; + token = strtok(ver1tok, "."); + while(token != NULL && i < 3) + { + 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; + } + + token = strtok(NULL, "."); + + i++; + } + + // Parse version string 2 + i = 0; + token = strtok(ver2tok, "."); + while(token != NULL && i < 3) + { + 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; + } + + token = strtok(NULL, "."); + + i++; + } + + // 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 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 * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + 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 * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + 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 * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + + // Compare versions + if (releaseTag[0] == 'v' || releaseTag[0] == 'V' || releaseTag[0] == 'r' || releaseTag[0] == 'R') + { + u32 releaseTagLen = strlen(releaseTag); + memmove(releaseTag, releaseTag + 1, releaseTagLen - 1); + releaseTag[releaseTagLen - 1] = '\0'; + } + + 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 * font_height, 255, 255, 255); + breaks++; + + uiDrawString("Please wait...", 0, breaks * font_height, 255, 255, 255); + breaks += 2; + + uiRefreshDisplay(); + + 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 * font_height, 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 * font_height, 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 * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: unable to parse download URL from JSON response!", 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: unable to parse object at index 0 from \"assets\" array in JSON response!", 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: unable to parse \"assets\" array from JSON response!", 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("You already have the latest version!", 0, breaks * font_height, 255, 255, 255); + } + } else { + uiDrawString("Error: unable to parse version tag from JSON response!", 0, breaks * font_height, 255, 0, 0); + } + + json_object_put(jobj); + } else { + uiDrawString("Error: unable to parse JSON response!", 0, breaks * font_height, 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 * font_height, 255, 0, 0); + } + + if (result_buf) free(result_buf); + + curl_easy_cleanup(curl); + } else { + uiDrawString("Error: failed to initialize CURL context!", 0, breaks * font_height, 255, 0, 0); + } + + networkDeinit(); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize socket! (%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + breaks += 2; } diff --git a/source/util.h b/source/util.h index 2e91e3b..24b2758 100644 --- a/source/util.h +++ b/source/util.h @@ -5,17 +5,24 @@ #include -#define APP_VERSION "1.0.6" +#define KiB (1024.0) +#define MiB (1024.0 * KiB) +#define GiB (1024.0 * MiB) -#define NAME_BUF_LEN 4096 +#define APP_VERSION "1.0.6" -#define SOCK_BUFFERSIZE 65536 +#define NAME_BUF_LEN 4096 -#define META_DATABASE_FILTER 0x80 // Regular Application +#define SOCK_BUFFERSIZE 65536 -bool isGameCardInserted(FsDeviceOperator* o); +#define META_DATABASE_FILTER 0x80 // Regular Application -void syncDisplay(); +#define FILENAME_BUFFER_SIZE (1024 * 512) // 512 KiB +#define FILENAME_MAX_CNT 2048 + +bool isGameCardInserted(); + +void fsGameCardDetectionThreadFunc(void *arg); void delay(u8 seconds); @@ -25,6 +32,12 @@ void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufS bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize); +void removeIllegalCharacters(char *name); + +void strtrim(char *str); + +void loadGameCardInfo(); + int getSdCardFreeSpace(u64 *out); void convertSize(u64 size, char *out, int bufsize); @@ -37,14 +50,12 @@ void addString(char **filenames, int *filenamesCount, char **nextFilename, const void getDirectoryContents(char *filenameBuffer, char **filenames, int *filenamesCount, const char *directory, bool skipParent); +void enterDirectory(const char *path); + void gameCardDumpNSWDBCheck(u32 crc); void updateNSWDBXml(); void updateApplication(); -void removeIllegalCharacters(char *name); - -void strtrim(char *str); - #endif