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
2019-04-21 12:27:33 -04:00

1071 lines
39 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 <ft2build.h>
#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 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];
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;
static FsFileSystem fs;
static char statusMessage[2048] = {'\0'};
static int statusMessageFadeout = 0;
static char fileCopyPath[NAME_BUF_LEN * 2] = {'\0'};
static int headlineCnt = 0;
u64 freeSpace = 0;
static char freeSpaceStr[64] = {'\0'};
static char titlebuf[NAME_BUF_LEN * 2] = {'\0'};
static const int maxListElements = 15;
static UIState uiState;
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" };
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)" };
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b)
{
/* Perform validity checks */
if ((x + width) < 0 || (y + height) < 0 || x >= FB_WIDTH || y >= FB_HEIGHT) return;
if (x < 0)
{
width += x;
x = 0;
}
if (y < 0)
{
height += y;
y = 0;
}
if ((x + width) >= FB_WIDTH) width = (FB_WIDTH - x);
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(FT_Bitmap *bitmap, int x, int y, u8 r, u8 g, u8 b)
{
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)
{
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 uiRefreshDisplay()
{
if (framebuf != NULL)
{
framebufferEnd(&fb);
framebuf = NULL;
framebuf_width = 0;
}
}
void uiStatusMsg(const char *fmt, ...)
{
statusMessageFadeout = 1000;
va_list args;
va_start(args, fmt);
vsnprintf(statusMessage, sizeof(statusMessage) / sizeof(statusMessage[0]), fmt, args);
va_end(args);
}
void uiUpdateStatusMsg()
{
if (!strlen(statusMessage) || !statusMessageFadeout) return;
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 * 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 uiClearScreen()
{
uiFill(0, 0, FB_WIDTH, FB_HEIGHT, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB);
}
void uiPrintHeadline()
{
uiClearScreen();
uiDrawString(appHeadline, 0, font_height, 255, 255, 255);
}
int error_screen(const char *fmt, ...)
{
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;
}
int uiInit()
{
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;
}