diff --git a/include/arm11/menu/menu.h b/include/arm11/menu/menu.h index da745ec..fdb4d9d 100644 --- a/include/arm11/menu/menu.h +++ b/include/arm11/menu/menu.h @@ -24,7 +24,6 @@ #define MENU_MAX_ENTRIES 8 #define MENU_MAX_DEPTH 4 -#define MENU_DISP_Y -2 #define MENU_OFFSET_TITLE 5 #define MENU_OFFSET_BUTTONS 18 #define MENU_WIDTH 28 diff --git a/include/arm11/menu/menu_fb3ds.h b/include/arm11/menu/menu_fb3ds.h index 0f80a34..6da175d 100644 --- a/include/arm11/menu/menu_fb3ds.h +++ b/include/arm11/menu/menu_fb3ds.h @@ -85,11 +85,11 @@ } }, { // 2 - "Boot Slot Setup", 3, NULL, + "Boot Slot Setup", 3, &menuPresetBootSlot, { - { "Setup [slot 1]...", DESC_SET_SLOT_1, &DummyFunc, 0 }, - { "Setup [slot 2]...", DESC_SET_SLOT_2, &DummyFunc, 1 }, - { "Setup [slot 3]...", DESC_SET_SLOT_3, &DummyFunc, 2 } + { "Setup [slot 1]...", DESC_SET_SLOT_1, &menuSetupBootSlot, 0 }, + { "Setup [slot 2]...", DESC_SET_SLOT_2, &menuSetupBootSlot, 1 }, + { "Setup [slot 3]...", DESC_SET_SLOT_3, &menuSetupBootSlot, 2 } } }, { // 3 diff --git a/include/arm11/menu/menu_fsel.h b/include/arm11/menu/menu_fsel.h index 883ccf4..56b29cc 100644 --- a/include/arm11/menu/menu_fsel.h +++ b/include/arm11/menu/menu_fsel.h @@ -19,5 +19,11 @@ */ #include "types.h" +#include "arm11/console.h" -u32 menuFileSelector(char* result, const char* start, const char* pattern); +#define BRWS_MAX_ENTRIES 8 +#define BRWS_OFFSET_TITLE 5 +#define BRWS_OFFSET_BUTTONS 18 +#define BRWS_WIDTH 28 + +bool menuFileSelector(char* res_path, PrintConsole* menu_con, const char* start, const char* pattern); diff --git a/include/arm11/menu/menu_func.h b/include/arm11/menu/menu_func.h index 3a159da..e17aaac 100644 --- a/include/arm11/menu/menu_func.h +++ b/include/arm11/menu/menu_func.h @@ -21,7 +21,9 @@ #include "types.h" u32 menuPresetBootMode(void); +u32 menuPresetBootSlot(void); u32 menuSetBootMode(PrintConsole* con, u32 param); +u32 menuSetupBootSlot(PrintConsole* con, u32 param); u32 DummyFunc(PrintConsole* con, u32 param); u32 SetView(PrintConsole* con, u32 param); u32 ShowCredits(PrintConsole* con, u32 param); diff --git a/source/arm11/menu/menu.c b/source/arm11/menu/menu.c index 08e43eb..f0fd5ab 100644 --- a/source/arm11/menu/menu.c +++ b/source/arm11/menu/menu.c @@ -150,7 +150,7 @@ consoleSetCursor(menu_con, menu_x, menu_y++); ee_printf(is_selected ? "\x1b[47;30m%.3s %-*.*s\x1b[0m" : "%.3s %-*.*s", - ((menu_preset >> i) & 0x1) ? "[X]" : "[ ]", MENU_WIDTH-3, MENU_WIDTH-3, name); + ((menu_preset >> i) & 0x1) ? "[X]" : "[ ]", MENU_WIDTH-4, MENU_WIDTH-4, name); } // button instructions diff --git a/source/arm11/menu/menu_fsel.c b/source/arm11/menu/menu_fsel.c index 8bb1ec3..bc27a71 100644 --- a/source/arm11/menu/menu_fsel.c +++ b/source/arm11/menu/menu_fsel.c @@ -23,14 +23,16 @@ #include "types.h" #include "fs.h" #include "util.h" +#include "arm11/menu/menu_fsel.h" #include "arm11/hardware/hid.h" #include "fsutils.h" #include "arm11/console.h" +#include "arm11/power.h" #include "arm11/debug.h" #include "arm11/fmt.h" -#define MAX_DIR_ENTRIES 0x200 // 512 -> worst case, approx 128kiB (yes, this is limited) -#define N_DIR_READ 0x10 // 16 at a time +#define MAX_DIR_ENTRIES 0x100 // 256 -> worst case approx 64kiB (yes, this is limited) +#define N_DIR_READ 0x10 // 16 at a time typedef struct { u32 fsize; // size of the file @@ -49,49 +51,76 @@ } +void truncateString(char* dest, const char* orig, int nsize, int tpos) +{ + int osize = strlen(orig); + + if (nsize < 0) + { + return; + } else if (nsize <= 3) + { + snprintf(dest, nsize, orig); + } else if (nsize >= osize) + { + snprintf(dest, nsize + 1, orig); + } else + { + if (tpos + 3 > nsize) tpos = nsize - 3; + snprintf(dest, nsize + 1, "%-.*s...%-.*s", tpos, orig, nsize - (3 + tpos), orig + osize - (nsize - (3 + tpos))); + } +} + + // inspired by http://www.geeksforgeeks.org/wildcard-character-matching/ static bool matchName(const char* path, const char* pattern) { - // handling non asterisk chars - for (; *pattern != '*'; pattern++, path++) + // handling non asterisk chars + for (; *pattern != '*'; pattern++, path++) { - if ((*pattern == '\0') && (*path == '\0')) + if ((*pattern == '\0') && (*path == '\0')) { - return true; // end reached simultaneously, match found - } + // end reached simultaneously, match found + return true; + } else if ((*pattern == '\0') || (*path == '\0')) { - return false; // end reached on only one, failure - } + // end reached on only one, failure + return false; + } else if ((*pattern != '?') && (tolower(*pattern) != tolower(*path))) { - return false; // chars don't match, failure - } - } + // chars don't match, failure + return false; + } + } - // handling the asterisk (matches one or more chars in path) - if ((*(pattern+1) == '?') || (*(pattern+1) == '*')) + // handling the asterisk (matches one or more chars in path) + if ((*(pattern+1) == '?') || (*(pattern+1) == '*')) { - return false; // stupid user/dev shenanigans, failure - } + // stupid user/dev shenanigans, failure + return false; + } else if (*path == '\0') { - return false; // asterisk, but end reached on path, failure - } + // asterisk, but end reached on path, failure + return false; + } else if (*(pattern+1) == '\0') { - return true; // nothing after the asterisk, match found - } + // nothing after the asterisk, match found + return true; + } else { // we couldn't really go without recursion here - for (path++; *path != '\0'; path++) + for (path++; *path != '\0'; path++) { - if (matchName(path, pattern + 1) == true) return true; - } - } - - return false; + if (matchName(path, pattern + 1) == true) return true; + } + } + + return false; } @@ -111,32 +140,32 @@ { for(s32 s = 0; s < n_entries; s++) { - DirBufferEntry* cmp0 = &(dir_buffer[s]); - DirBufferEntry* min0 = cmp0; + DirBufferEntry* cmp0 = &(dir_buffer[s]); + DirBufferEntry* min0 = cmp0; - for(s32 c = s + 1; c < n_entries; c++) + for(s32 c = s + 1; c < n_entries; c++) { - DirBufferEntry* cmp1 = &(dir_buffer[c]); - if(min0->is_dir != cmp1->is_dir) + DirBufferEntry* cmp1 = &(dir_buffer[c]); + if(min0->is_dir != cmp1->is_dir) { - if (min0->is_dir) - min0 = cmp1; - continue; - } - if(strnicmp(min0->fname, cmp1->fname, 256) > 0) - { - min0 = cmp1; + if (cmp1->is_dir) + min0 = cmp1; + continue; } - } + if(strnicmp(min0->fname, cmp1->fname, 256) > 0) + { + min0 = cmp1; + } + } - if(min0 != cmp0) + if(min0 != cmp0) { - DirBufferEntry swap; // swap entries - memcpy(&swap, cmp0, sizeof(DirBufferEntry)); - memcpy(cmp0, min0, sizeof(DirBufferEntry)); - memcpy(min0, &swap, sizeof(DirBufferEntry)); - } - } + DirBufferEntry swap; // swap entries + memcpy(&swap, cmp0, sizeof(DirBufferEntry)); + memcpy(cmp0, min0, sizeof(DirBufferEntry)); + memcpy(min0, &swap, sizeof(DirBufferEntry)); + } + } } @@ -148,10 +177,27 @@ // set dir_buffer to all zeroes memset(dir_buffer, 0, sizeof(DirBufferEntry) * MAX_DIR_ENTRIES); + + // special handling when in root + if(!*path) + { + const char* root_paths[] = { "sdmc:", "nand:", "twln:", "twlp:" }; + n_entries = sizeof(root_paths) / sizeof(const char*); + + for(s32 i = 0; i < n_entries; i++) + { + dir_buffer[i].fsize = 0; + dir_buffer[i].is_dir = 1; + dir_buffer[i].fname = mallocpyString(root_paths[i]); + } + + return n_entries; + } // open directory dhandle = fOpenDir(path); - if(dhandle < 0) return -1; + if(dhandle < 0) + return -1; s32 n_read = 0; while((n_read = fReadDir(dhandle, finfo, N_DIR_READ)) > 0) @@ -161,8 +207,8 @@ for(s32 i = 0; i < n_read; i++) { - if(pattern && !matchName(finfo[i].fname, pattern)) - continue; // not a match with the provided pattern + if(!(finfo[i].fattrib & AM_DIR) && pattern && !matchName(finfo[i].fname, pattern)) + continue; // not a match with the provided pattern and not a dir // take over data dir_buffer[n_entries].fsize = finfo[i].fsize; @@ -183,18 +229,163 @@ break; // WARNING: no errors here - list just isn't complete } + fCloseDir(dhandle); + + if (n_entries > 0) + sortDirBuffer(dir_buffer, n_entries); + return n_entries; } -u32 menuFileSelector(char* path, const char* start, const char* pattern) +/** + * @brief Draws the file listing to the given console. + * @param curr_path Current path displayed on screen. + * @param dir_buffer An array of structs containing the file listing. + * @param n_entries Number of elements in the above struct. + * @param menu_con Console that the file browser is displayed on. + * @param index Current placement of the cursor. + * @param scroll Current scroll offset, will be written to by this function. + */ +void browserDraw(const char* curr_path, DirBufferEntry* dir_buffer, s32 n_entries, PrintConsole* menu_con, s32 index, s32* scroll) +{ + int brws_x = (menu_con->consoleWidth - BRWS_WIDTH) >> 1; + int brws_y = BRWS_OFFSET_TITLE; + char temp_str[BRWS_WIDTH + 1]; + + // fix scroll (if required) + if (index < *scroll) + { + *scroll = index; + } else if (*scroll + BRWS_MAX_ENTRIES <= index) + { + *scroll = index - (BRWS_MAX_ENTRIES - 1); + } + + // select menu console + consoleSelect(menu_con); + consoleClear(); + + // browser title + consoleSetCursor(menu_con, brws_x, brws_y++); + truncateString(temp_str, curr_path, BRWS_WIDTH, 8); + if (!*temp_str) strncpy(temp_str, "[root]", BRWS_WIDTH); + ee_printf(temp_str); + brws_y++; + + // menu entries + for (s32 i = 0; i < BRWS_MAX_ENTRIES; i++) + { + s32 pos = i + *scroll; + if (pos >= n_entries) + break; + + DirBufferEntry* entry = &(dir_buffer[pos]); + bool is_selected = (pos == index); + + consoleSetCursor(menu_con, brws_x, brws_y++); + truncateString(temp_str, entry->fname, BRWS_WIDTH - 5, 8); + ee_printf(is_selected ? "\x1b[47;30m%.3s %-*.*s\x1b[0m" : "%.3s %-*.*s", + (entry->is_dir) ? "[D]" : "[F]", BRWS_WIDTH-4, BRWS_WIDTH-4, temp_str); + } + + // button instructions + brws_y = BRWS_OFFSET_BUTTONS; + consoleSetCursor(menu_con, brws_x, brws_y++); + ee_printf("%-*.*s", BRWS_WIDTH, BRWS_WIDTH, "[A]:Choose [B]:Back"); +} + + +/** + * @brief Shows a file select dialogue. + * @param res_path The selected path will end up here. + * @param menu_con Console that the file browser is displayed on. + * @param start Starting path (must be a dir). + * @param patter Only files matching this wildcard pattern will be displayed. + */ +bool menuFileSelector(char* res_path, PrintConsole* menu_con, const char* start, const char* pattern) { DirBufferEntry* dir_buffer; - bool result = false; + bool result = true; // <--- should be handled differently dir_buffer = (DirBufferEntry*) malloc(MAX_DIR_ENTRIES * sizeof(DirBufferEntry)); if (!dir_buffer) return false; + // res_path has to be at least 256 byte long (including '\0') and + // is also used as temporary buffer + *res_path = '\0'; // root dir if start is NULL + if(start) strncpy(res_path, start, 256); + + bool is_dir = true; // we are not finished while we have a dir in res_path + while(is_dir) + { + s32 n_entries = readDirToBuffer(dir_buffer, res_path, pattern); + s32 last_index = (u32) -1; + s32 scroll = 0; + s32 index = 0; + + if(n_entries < 0) + panicMsg("Error reading dir!"); + + while(true) + { + // update file browser (on demand) + if (index != last_index) { + browserDraw(res_path, dir_buffer, n_entries, menu_con, index, &scroll); + last_index = index; + + GX_textureCopy((u64*)RENDERBUF_TOP, 0, (u64*)GFX_getFramebuffer(SCREEN_TOP), + 0, SCREEN_SIZE_TOP + SCREEN_SIZE_SUB); + GFX_swapFramebufs(); + } + GFX_waitForEvent(GFX_EVENT_PDC0, true); // VBlank + + if(hidGetPowerButton(true)) // handle power button + { + power_off(); + } + + hidScanInput(); + const u32 kDown = hidKeysDown(); + // const u32 kHeld = hidKeysHeld(); + + if (kDown & KEY_A) + { + // build new res_path + char* fname = &(res_path[strlen(res_path)]); + if (fname > res_path) *(fname++) = '/'; + strncpy(fname, dir_buffer[index].fname, (256 - (fname - res_path))); + + // is this a dir? + is_dir = dir_buffer[index].is_dir; + break; + } + else if ((kDown & KEY_B) && *res_path) + { + // return to previous path + char* slash = strrchr(res_path, '/'); + + if (slash) + *slash = '\0'; + else + *res_path = '\0'; + break; + } + else if ((kDown & KEY_DDOWN) && n_entries) + { + // cursor down + index = (index == n_entries - 1) ? 0 : index + 1; + } + else if ((kDown & KEY_DUP) && n_entries) + { + // cursor up + index = (index == 0) ? n_entries - 1 : index - 1; + } + } + + freeDirBufferContent(dir_buffer, n_entries); + } + free(dir_buffer); return result; diff --git a/source/arm11/menu/menu_func.c b/source/arm11/menu/menu_func.c index 619a4a9..25e848e 100644 --- a/source/arm11/menu/menu_func.c +++ b/source/arm11/menu/menu_func.c @@ -20,6 +20,7 @@ #include #include #include "types.h" +#include "arm11/menu/menu_fsel.h" #include "arm11/hardware/hid.h" #include "arm11/console.h" #include "arm11/config.h" @@ -61,11 +62,39 @@ return 0; } +u32 menuPresetBootSlot(void) +{ + u32 res = 0; + + for (u32 i = 0; i < 3; i++) + { + if (configDataExist(KBootOption1 + i)) + { + res |= 1 << i; + } + } + + return res; +} + u32 menuSetBootMode(PrintConsole* con, u32 param) { (void) con; + u32 res = (configSetKeyData(KBootMode, ¶m)) ? 0 : 1; - return (configSetKeyData(KBootMode, ¶m)) ? 0 : 1; + writeConfigFile(); + return res; +} + +u32 menuSetupBootSlot(PrintConsole* con, u32 param) +{ + char res_path[256]; + + menuFileSelector(res_path, con, NULL, "*.firm"); + u32 res = (configSetKeyData(KBootOption1 + param, res_path)) ? 0 : 1; + + writeConfigFile(); + return res; } u32 DummyFunc(PrintConsole* con, u32 param)