mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 10:42:09 +00:00
1071 lines
39 KiB
C
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;
|
|
}
|