1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-23 10:42:09 +00:00
nxdumptool/source/ui.c
Pablo Curiel 59e48748ae Some minor (trivial) changes.
Nothing too substantial. Just committing them before going to Spain.
2018-07-25 21:57:44 -04:00

921 lines
31 KiB
C

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <switch.h>
#include "dumper.h"
#include "fsext.h"
#include "ui.h"
#include "util.h"
extern FsDeviceOperator fsOperatorInstance;
extern bool gameCardInserted;
extern u32 currentFBWidth, currentFBHeight;
extern u8 *currentFB;
extern char gameCardSizeStr[32], trimmedCardSizeStr[32];
extern char *hfs0_header;
extern u64 hfs0_offset, hfs0_size;
extern u32 hfs0_partition_cnt;
extern u64 gameCardTitleID;
extern u32 gameCardVersion;
extern char gameCardName[0x201], fixedGameCardName[0x201], gameCardAuthor[0x101], gameCardVersionStr[64];
extern char gameCardUpdateVersionStr[128];
static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true;
static u32 selectedOption;
static FsFileSystem fs;
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 UIState uiState;
static const char *appHeadline = "Nintendo Switch Game Card Dump Tool v" APP_VERSION ".\nOriginal code by MCMrARM.\nAdditional modifications by DarkMatterCore.\n\n";
static const char *appControls = "[D-Pad / Analog 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 *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;
if (x < 0)
{
width += x;
x = 0;
}
if (y < 0)
{
height += y;
y = 0;
}
if (x + width >= currentFBWidth) width = currentFBWidth - 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;
}
}
void uiDrawChar(char c, 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 = &currentFB[uiGetIndex(x + cx, y + cy)];
*(ptr + 0) = r;
*(ptr + 1) = g;
*(ptr + 2) = b;
*(ptr + 3) = 255;
}
}
}
}
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;
}
}
void uiStatusMsg(const char* format, ...)
{
statusMessageFadeout = 1000;
va_list args;
va_start(args, format);
vsnprintf(statusMessage, sizeof(statusMessage) / sizeof(statusMessage[0]), format, 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;
}
void uiPleaseWait()
{
breaks = headlineCnt;
uiDrawString("Please wait...", 0, breaks * 8, 115, 115, 255);
syncDisplay();
delay(2);
}
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;
}
void uiClearScreen()
{
uiFill(0, 0, currentFBWidth, currentFBHeight, 50, 50, 50);
}
void uiPrintHeadline()
{
uiClearScreen();
uiDrawString(appHeadline, 0, 0, 255, 255, 255);
}
static 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;
}
UIResult uiLoop(u32 keysDown)
{
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;
}