1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-09-20 05:53:25 +01:00
nxdumptool/source/ui.c

731 lines
25 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];
extern char *hfs0_header;
extern u64 hfs0_offset, hfs0_size;
extern u32 hfs0_partition_cnt;
extern u64 gameCardTitleID;
extern char gameCardName[0x201], gameCardAuthor[0x101], gameCardVersion[0x11];
static u32 selectedOption;
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[FILENAMES_COUNT_MAX];
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 Stick] Move | [A] Select | [B] Back | [+] Exit";
static const char *mainMenuItems[] = { "Raw XCI Dump (Full)", "Raw Partition Dump", "Partition Data Dump", "View Game Card Files", "Dump Game Card Certificate" };
static const char *xciDumpMenuItems[] = { "Full Dump with Certificate (exFAT)", "Full Dump without Certificate (exFAT)", "Full Dump with Certificate (FAT32 - 2 GiB chunks)", "Full Dump without Certificate (FAT32 - 2 GiB chunks)" };
static const char *partitionDumpType1MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Normal)", "Dump Partition 2 (Secure)" };
static const char *partitionDumpType2MenuItems[] = { "Dump Partition 0 (SysUpdate)", "Dump Partition 1 (Logo)", "Dump Partition 2 (Normal)", "Dump Partition 3 (Secure)" };
static const char *viewGameCardFsType1MenuItems[] = { "View Files from Partition 0 (SysUpdate)", "View Files from Partition 1 (Normal)", "View Files from Partition 2 (Secure)" };
static const char *viewGameCardFsType2MenuItems[] = { "View Files from Partition 0 (SysUpdate)", "View Files from Partition 1 (Logo)", "View Files from Partition 2 (Normal)", "View Files from Partition 3 (Secure)" };
static 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 = 500;
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 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(FILENAMEBUFFER_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);
if (uiState == stateViewGameCardFsBrowser) fsdevUnmountDevice("view");
}
void uiSetState(UIState state)
{
uiState = state;
cursor = 0;
scroll = 0;
}
UIState uiGetState()
{
return uiState;
}
void uiClearScreen()
{
uiFill(0, 0, currentFBWidth, currentFBHeight, 50, 50, 50);
}
static void enterDirectory(const char *path)
{
snprintf(currentDirectory, sizeof(currentDirectory) / sizeof(currentDirectory[0]), "%s", path);
filenamesCount = FILENAMES_COUNT_MAX;
getDirectoryContents(filenameBuffer, &filenames[0], &filenamesCount, currentDirectory, (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6));
cursor = 0;
scroll = 0;
}
UIResult uiLoop(u32 keysDown)
{
UIResult res = resultNone;
uiClearScreen();
uiDrawString(appHeadline, 0, 0, 255, 255, 255);
int i;
breaks = headlineCnt;
if (uiState == stateMainMenu || uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu || uiState == stateViewGameCardFsBrowser)
{
// Exit
if (keysDown & KEY_PLUS) 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))
{
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++;*/
if (strlen(gameCardName) && strlen(gameCardAuthor))
{
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Name: %s", gameCardName);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Developer: %s", gameCardAuthor);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
}
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Title ID: %016lX", gameCardTitleID);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
if (strlen(gameCardVersion))
{
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Version: %s", gameCardVersion);
uiDrawString(titlebuf, 0, breaks * 8, 0, 255, 0);
breaks++;
}
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Size: %s", gameCardSizeStr);
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);
} else {
if (gameCardInserted)
{
if (hfs0_header != NULL)
{
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))
{
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 ((keysDown & KEY_LEFT) || (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_LSTICK_LEFT)) scrollAmount = -5;
if ((keysDown & KEY_RIGHT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_LSTICK_RIGHT)) scrollAmount = 5;
if (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);
}
i++;
if (i >= maxListElements) break;
}
// 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;
default:
break;
}
} else
if (uiState == stateXciDumpMenu)
{
selectedOption = (u32)cursor;
res = resultDumpXci;
} else
if (uiState == stateRawPartitionDumpMenu)
{
selectedOption = (u32)cursor;
res = resultDumpRawPartition;
} else
if (uiState == statePartitionDataDumpMenu)
{
selectedOption = (u32)cursor;
res = resultDumpPartitionData;
} else
if (uiState == stateViewGameCardFsMenu)
{
selectedOption = (u32)cursor;
res = resultShowViewGameCardFsGetList;
} else
if (uiState == stateViewGameCardFsBrowser)
{
char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2);
memset(selectedPath, 0, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2);
if (strlen(filenames[cursor]) == 2 && !strcmp(filenames[cursor], ".."))
{
for(i = (strlen(currentDirectory) - 1); i >= 0; i--)
{
if (currentDirectory[i] == '/')
{
strncpy(selectedPath, currentDirectory, i);
selectedPath[i] = '\0';
break;
}
}
} else {
snprintf(selectedPath, strlen(currentDirectory) + 1 + strlen(filenames[cursor]) + 2, "%s/%s", currentDirectory, filenames[cursor]);
}
if (isDirectory(selectedPath))
{
enterDirectory(selectedPath);
} else {
snprintf(fileCopyPath, sizeof(fileCopyPath) / sizeof(fileCopyPath[0]), "%s", selectedPath);
res = resultViewGameCardFsBrowserCopyFile;
}
free(selectedPath);
}
}
// Back
if (keysDown & KEY_B)
{
if (uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu)
{
res = resultShowMainMenu;
} else
if (uiState == stateViewGameCardFsBrowser)
{
if (!strcmp(currentDirectory, "view:/") && strlen(currentDirectory) == 6)
{
fsdevUnmountDevice("view");
res = resultShowViewGameCardFsMenu;
} else {
char *selectedPath = (char*)malloc(strlen(currentDirectory) + 1);
memset(selectedPath, 0, strlen(currentDirectory) + 1);
for(i = (strlen(currentDirectory) - 1); i >= 0; i--)
{
if (currentDirectory[i] == '/')
{
strncpy(selectedPath, currentDirectory, i);
selectedPath[i] = '\0';
break;
}
}
if (isDirectory(selectedPath)) enterDirectory(selectedPath);
free(selectedPath);
}
}
}
}
}
} else
if (uiState == stateDumpXci)
{
uiDrawString(xciDumpMenuItems[selectedOption], 0, breaks * 8, 115, 115, 255);
breaks += 2;
bool isFat32 = (selectedOption == 2 || selectedOption == 3);
bool dumpCert = (selectedOption == 0 || selectedOption == 2);
dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert);
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, 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", (int)(strlen(fileCopyPath) - i), fileCopyPath + i + 1);
break;
}
}
uiDrawString("Hold B to cancel", 0, breaks * 8, 255, 255, 255);
breaks += 2;
if (copyFile(fileCopyPath, destCopyPath, true))
{
breaks += 7;
uiDrawString("Process successfully completed!", 0, breaks * 8, 0, 255, 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 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;
}
if (statusMessageFadeout > 0)
{
int fadeout = (statusMessageFadeout > 255 ? 255 : statusMessageFadeout);
uiDrawString(statusMessage, 4, currentFBHeight - 12, fadeout, fadeout, fadeout);
statusMessageFadeout -= 4;
}
return res;
}