2016-02-25 16:57:01 +01:00
|
|
|
#include "godmode.h"
|
2016-03-28 17:32:29 +02:00
|
|
|
#include "ui.h"
|
2016-02-25 16:57:01 +01:00
|
|
|
#include "hid.h"
|
2016-12-10 15:32:03 +01:00
|
|
|
#include "fsinit.h"
|
|
|
|
#include "fsdrive.h"
|
|
|
|
#include "fsutil.h"
|
|
|
|
#include "fsperm.h"
|
2016-12-10 15:40:36 +01:00
|
|
|
#include "gameutil.h"
|
2017-01-02 17:37:08 +01:00
|
|
|
#include "nandutil.h"
|
2016-12-11 16:28:51 +01:00
|
|
|
#include "filetype.h"
|
2016-03-22 19:24:21 +01:00
|
|
|
#include "platform.h"
|
2016-03-11 01:29:14 +01:00
|
|
|
#include "nand.h"
|
2016-03-21 18:53:09 +01:00
|
|
|
#include "virtual.h"
|
2016-04-04 22:45:49 +02:00
|
|
|
#include "image.h"
|
2016-02-25 16:57:01 +01:00
|
|
|
|
2016-04-06 16:45:43 +02:00
|
|
|
#define N_PANES 2
|
2016-03-15 22:19:30 +01:00
|
|
|
|
2016-07-01 01:38:57 +02:00
|
|
|
#define COLOR_TOP_BAR ((GetWritePermissions() & (PERM_A9LH&~PERM_SYSNAND)) ? COLOR_DARKRED : (GetWritePermissions() & PERM_SYSNAND) ? COLOR_RED : (GetWritePermissions() & PERM_MEMORY) ? COLOR_BRIGHTBLUE : (GetWritePermissions() & (PERM_EMUNAND|PERM_IMAGE)) ? COLOR_BRIGHTYELLOW : GetWritePermissions() ? COLOR_BRIGHTGREEN : COLOR_WHITE)
|
2016-02-28 13:11:07 +01:00
|
|
|
#define COLOR_SIDE_BAR COLOR_DARKGREY
|
2016-02-27 19:58:41 +01:00
|
|
|
#define COLOR_MARKED COLOR_TINTEDYELLOW
|
|
|
|
#define COLOR_FILE COLOR_TINTEDGREEN
|
|
|
|
#define COLOR_DIR COLOR_TINTEDBLUE
|
|
|
|
#define COLOR_ROOT COLOR_GREY
|
2016-03-21 16:58:35 +01:00
|
|
|
#define COLOR_ENTRY(e) (((e)->marked) ? COLOR_MARKED : ((e)->type == T_DIR) ? COLOR_DIR : ((e)->type == T_FILE) ? COLOR_FILE : ((e)->type == T_ROOT) ? COLOR_ROOT : COLOR_GREY)
|
2016-02-27 19:58:41 +01:00
|
|
|
|
2016-04-08 21:03:40 +02:00
|
|
|
#define COLOR_HVOFFS RGB(0x40, 0x60, 0x50)
|
|
|
|
#define COLOR_HVOFFSI COLOR_DARKESTGREY
|
|
|
|
#define COLOR_HVASCII RGB(0x40, 0x80, 0x50)
|
|
|
|
#define COLOR_HVHEX(i) ((i % 2) ? RGB(0x30, 0x90, 0x30) : RGB(0x30, 0x80, 0x30))
|
|
|
|
|
2016-04-06 16:06:54 +02:00
|
|
|
typedef struct {
|
|
|
|
char path[256];
|
|
|
|
u32 cursor;
|
|
|
|
u32 scroll;
|
|
|
|
} PaneData;
|
2016-03-16 01:26:17 +01:00
|
|
|
|
2016-04-06 16:06:54 +02:00
|
|
|
void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* clipboard, u32 curr_pane) {
|
2016-03-02 19:36:20 +01:00
|
|
|
const u32 n_cb_show = 8;
|
2016-07-18 03:15:04 +02:00
|
|
|
const u32 bartxt_start = (FONT_HEIGHT_EXT == 10) ? 1 : 2;
|
|
|
|
const u32 bartxt_x = 2;
|
|
|
|
const u32 bartxt_rx = SCREEN_WIDTH_TOP - (19*FONT_WIDTH_EXT) - bartxt_x;
|
2016-03-01 02:00:48 +01:00
|
|
|
const u32 info_start = 18;
|
2016-07-18 03:15:04 +02:00
|
|
|
const u32 instr_x = (SCREEN_WIDTH_TOP - (36*FONT_WIDTH_EXT)) / 2;
|
2016-04-02 19:07:46 +02:00
|
|
|
char tempstr[64];
|
2016-03-01 02:00:48 +01:00
|
|
|
|
|
|
|
static u32 state_prev = 0xFFFFFFFF;
|
|
|
|
u32 state_curr =
|
|
|
|
((*curr_path) ? (1<<0) : 0) |
|
2016-04-05 20:41:40 +02:00
|
|
|
((clipboard->n_entries) ? (1<<1) : 0) |
|
2016-12-19 14:32:22 +01:00
|
|
|
((CheckSDMountState()) ? (1<<2) : 0) |
|
|
|
|
((GetMountState()) ? (1<<3) : 0) |
|
2016-05-01 15:41:11 +02:00
|
|
|
(curr_pane<<4);
|
2016-03-01 02:00:48 +01:00
|
|
|
|
|
|
|
if (state_prev != state_curr) {
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
|
|
|
state_prev = state_curr;
|
|
|
|
}
|
|
|
|
|
2016-02-27 19:58:41 +01:00
|
|
|
// top bar - current path & free/total storage
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawRectangle(TOP_SCREEN, 0, 0, SCREEN_WIDTH_TOP, 12, COLOR_TOP_BAR);
|
2016-02-27 19:58:41 +01:00
|
|
|
if (strncmp(curr_path, "", 256) != 0) {
|
2016-04-02 19:07:46 +02:00
|
|
|
char bytestr0[32];
|
|
|
|
char bytestr1[32];
|
2016-07-25 01:09:00 +02:00
|
|
|
TruncateString(tempstr, curr_path, 240 / FONT_WIDTH_EXT, 8);
|
2016-07-18 03:15:04 +02:00
|
|
|
DrawStringF(TOP_SCREEN, bartxt_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, tempstr);
|
|
|
|
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", "LOADING...");
|
2016-02-27 19:58:41 +01:00
|
|
|
FormatBytes(bytestr0, GetFreeSpace(curr_path));
|
|
|
|
FormatBytes(bytestr1, GetTotalSpace(curr_path));
|
|
|
|
snprintf(tempstr, 64, "%s/%s", bytestr0, bytestr1);
|
2016-07-18 03:15:04 +02:00
|
|
|
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", tempstr);
|
2016-02-27 19:58:41 +01:00
|
|
|
} else {
|
2016-07-18 03:15:04 +02:00
|
|
|
DrawStringF(TOP_SCREEN, bartxt_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "[root]");
|
|
|
|
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", "GodMode9");
|
2016-02-27 19:58:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// left top - current file info
|
2016-04-06 16:06:54 +02:00
|
|
|
if (curr_pane) snprintf(tempstr, 63, "PANE #%lu", curr_pane);
|
|
|
|
else snprintf(tempstr, 63, "CURRENT");
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawStringF(TOP_SCREEN, 2, info_start, COLOR_STD_FONT, COLOR_STD_BG, "[%s]", tempstr);
|
2016-04-06 16:06:54 +02:00
|
|
|
// file / entry name
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, curr_entry->name, 160 / FONT_WIDTH_EXT, 8, false);
|
2016-03-02 19:36:20 +01:00
|
|
|
u32 color_current = COLOR_ENTRY(curr_entry);
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawStringF(TOP_SCREEN, 4, info_start + 12, color_current, COLOR_STD_BG, "%s", tempstr);
|
2016-04-06 16:06:54 +02:00
|
|
|
// size (in Byte) or type desc
|
2016-03-21 16:58:35 +01:00
|
|
|
if (curr_entry->type == T_DIR) {
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, "(dir)", 160 / FONT_WIDTH_EXT, 8, false);
|
2016-03-21 16:58:35 +01:00
|
|
|
} else if (curr_entry->type == T_DOTDOT) {
|
2016-03-02 19:36:20 +01:00
|
|
|
snprintf(tempstr, 21, "%20s", "");
|
2016-10-29 16:02:07 +02:00
|
|
|
} else if (curr_entry->type == T_ROOT) {
|
|
|
|
int drvtype = DriveType(curr_entry->path);
|
|
|
|
char drvstr[32];
|
|
|
|
snprintf(drvstr, 31, "(%s%s)",
|
2016-12-02 15:42:05 +01:00
|
|
|
((drvtype & DRV_SDCARD) ? "SD" : (drvtype & DRV_RAMDRIVE) ? "RAMdrive" : (drvtype & DRV_GAME) ? "Game" :
|
2016-10-29 16:02:07 +02:00
|
|
|
(drvtype & DRV_SYSNAND) ? "SysNAND" : (drvtype & DRV_EMUNAND) ? "EmuNAND" : (drvtype & DRV_IMAGE) ? "Image" :
|
2016-12-02 15:42:05 +01:00
|
|
|
(drvtype & DRV_XORPAD) ? "XORpad" : (drvtype & DRV_MEMORY) ? "Memory" : (drvtype & DRV_ALIAS) ? "Alias" :
|
2017-01-13 14:20:42 +01:00
|
|
|
(drvtype & DRV_CART) ? "Gamecart" : (drvtype & DRV_SEARCH) ? "Search" : ""),
|
2016-10-29 16:02:07 +02:00
|
|
|
((drvtype & DRV_FAT) ? " FAT" : (drvtype & DRV_VIRTUAL) ? " Virtual" : ""));
|
|
|
|
ResizeString(tempstr, drvstr, 160 / FONT_WIDTH_EXT, 8, false);
|
|
|
|
}else {
|
2016-04-05 21:13:23 +02:00
|
|
|
char numstr[32];
|
2016-04-02 19:07:46 +02:00
|
|
|
char bytestr[32];
|
2016-04-05 21:13:23 +02:00
|
|
|
FormatNumber(numstr, curr_entry->size);
|
2016-04-06 12:52:23 +02:00
|
|
|
snprintf(bytestr, 31, "%s Byte", numstr);
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, bytestr, 160 / FONT_WIDTH_EXT, 8, false);
|
2016-02-27 19:58:41 +01:00
|
|
|
}
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawStringF(TOP_SCREEN, 4, info_start + 12 + 10, color_current, COLOR_STD_BG, tempstr);
|
2016-07-22 18:32:44 +02:00
|
|
|
// path of file (if in search results)
|
2016-12-10 15:32:03 +01:00
|
|
|
if ((DriveType(curr_path) & DRV_SEARCH) && strrchr(curr_entry->path, '/')) {
|
2016-07-22 18:32:44 +02:00
|
|
|
char dirstr[256];
|
|
|
|
strncpy(dirstr, curr_entry->path, 256);
|
|
|
|
*(strrchr(dirstr, '/')+1) = '\0';
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, dirstr, 160 / FONT_WIDTH_EXT, 8, false);
|
2016-07-22 18:32:44 +02:00
|
|
|
DrawStringF(TOP_SCREEN, 4, info_start + 12 + 10 + 10, color_current, COLOR_STD_BG, tempstr);
|
|
|
|
} else {
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, "", 160 / FONT_WIDTH_EXT, 8, false);
|
2016-07-22 18:32:44 +02:00
|
|
|
DrawStringF(TOP_SCREEN, 4, info_start + 12 + 10 + 10, color_current, COLOR_STD_BG, tempstr);
|
|
|
|
}
|
2016-02-29 22:27:04 +01:00
|
|
|
|
|
|
|
// right top - clipboard
|
2016-07-25 01:09:00 +02:00
|
|
|
DrawStringF(TOP_SCREEN, SCREEN_WIDTH_TOP - 160, info_start, COLOR_STD_FONT, COLOR_STD_BG, "%*s", 160 / FONT_WIDTH_EXT,
|
|
|
|
(clipboard->n_entries) ? "[CLIPBOARD]" : "");
|
2016-03-02 19:36:20 +01:00
|
|
|
for (u32 c = 0; c < n_cb_show; c++) {
|
|
|
|
u32 color_cb = COLOR_ENTRY(&(clipboard->entry[c]));
|
2016-07-25 01:09:00 +02:00
|
|
|
ResizeString(tempstr, (clipboard->n_entries > c) ? clipboard->entry[c].name : "", 160 / FONT_WIDTH_EXT, 8, true);
|
|
|
|
DrawStringF(TOP_SCREEN, SCREEN_WIDTH_TOP - 160 - 4, info_start + 12 + (c*10), color_cb, COLOR_STD_BG, tempstr);
|
2016-02-29 22:27:04 +01:00
|
|
|
}
|
|
|
|
*tempstr = '\0';
|
2016-03-02 19:36:20 +01:00
|
|
|
if (clipboard->n_entries > n_cb_show) snprintf(tempstr, 60, "+ %lu more", clipboard->n_entries - n_cb_show);
|
2016-07-25 01:09:00 +02:00
|
|
|
DrawStringF(TOP_SCREEN, SCREEN_WIDTH_TOP - 160 - 4, info_start + 12 + (n_cb_show*10), COLOR_DARKGREY, COLOR_STD_BG, "%*s",
|
|
|
|
160 / FONT_WIDTH_EXT, tempstr);
|
2016-02-27 19:58:41 +01:00
|
|
|
|
|
|
|
// bottom: inctruction block
|
2016-07-27 00:19:12 +02:00
|
|
|
char instr[512];
|
2016-12-19 14:32:22 +01:00
|
|
|
snprintf(instr, 512, "%s%s\n%s%s%s%s%s%s%s%s",
|
2016-05-30 02:24:47 +02:00
|
|
|
#ifndef SAFEMODE
|
2016-03-15 22:19:30 +01:00
|
|
|
"GodMode9 Explorer v", VERSION, // generic start part
|
2016-05-30 02:24:47 +02:00
|
|
|
#else
|
|
|
|
"SafeMode9 Explorer v", VERSION, // generic start part
|
|
|
|
#endif
|
2016-03-02 14:01:20 +01:00
|
|
|
(*curr_path) ? ((clipboard->n_entries == 0) ? "L - MARK files (use with \x18\x19\x1A\x1B)\nX - DELETE / [+R] RENAME file(s)\nY - COPY file(s) / [+R] CREATE dir\n" :
|
|
|
|
"L - MARK files (use with \x18\x19\x1A\x1B)\nX - DELETE / [+R] RENAME file(s)\nY - PASTE file(s) / [+R] CREATE dir\n") :
|
2016-12-19 14:32:22 +01:00
|
|
|
((GetWritePermissions() > PERM_BASE) ? "R+Y - Relock write permissions\n" : "R+Y - Unlock write permissions\n"),
|
|
|
|
(*curr_path) ? "" : (CheckSDMountState()) ? "R+B - Unmount SD card\n" : "R+B - Remount SD card\n",
|
|
|
|
(*curr_path) ? "" : (GetMountState()) ? "R+X - Unmount image\n" : "",
|
2016-07-22 18:32:44 +02:00
|
|
|
(*curr_path) ? "R+A - Search directory\n" : "R+A - Search drive\n",
|
2016-03-02 14:01:20 +01:00
|
|
|
"R+L - Make a Screenshot\n",
|
2016-04-06 16:06:54 +02:00
|
|
|
"R+\x1B\x1A - Switch to prev/next pane\n",
|
2016-03-02 14:01:20 +01:00
|
|
|
(clipboard->n_entries) ? "SELECT - Clear Clipboard\n" : "SELECT - Restore Clipboard\n", // only if clipboard is full
|
2016-04-23 20:09:43 +02:00
|
|
|
"START - Reboot / [+R] Poweroff"); // generic end part
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawStringF(TOP_SCREEN, instr_x, SCREEN_HEIGHT - 4 - GetDrawStringHeight(instr), COLOR_STD_FONT, COLOR_STD_BG, instr);
|
2016-02-27 19:58:41 +01:00
|
|
|
}
|
|
|
|
|
2016-03-15 22:19:30 +01:00
|
|
|
void DrawDirContents(DirStruct* contents, u32 cursor, u32* scroll) {
|
2016-07-22 04:55:09 +02:00
|
|
|
const int str_width = (SCREEN_WIDTH_BOT-3) / FONT_WIDTH_EXT;
|
2016-03-02 17:22:44 +01:00
|
|
|
const u32 bar_height_min = 32;
|
|
|
|
const u32 bar_width = 2;
|
2016-02-27 19:58:41 +01:00
|
|
|
const u32 start_y = 2;
|
2016-02-26 17:03:25 +01:00
|
|
|
const u32 stp_y = 12;
|
|
|
|
const u32 pos_x = 0;
|
2016-02-27 19:58:41 +01:00
|
|
|
const u32 lines = (SCREEN_HEIGHT-start_y+stp_y-1) / stp_y;
|
|
|
|
u32 pos_y = start_y;
|
2016-03-15 22:19:30 +01:00
|
|
|
|
|
|
|
if (*scroll > cursor) *scroll = cursor;
|
|
|
|
else if (*scroll + lines <= cursor) *scroll = cursor - lines + 1;
|
2016-06-10 14:23:37 +02:00
|
|
|
if (*scroll + lines > contents->n_entries)
|
|
|
|
*scroll = (contents->n_entries > lines) ? contents->n_entries - lines : 0;
|
2016-02-25 16:57:01 +01:00
|
|
|
|
|
|
|
for (u32 i = 0; pos_y < SCREEN_HEIGHT; i++) {
|
|
|
|
char tempstr[str_width + 1];
|
2016-03-15 22:19:30 +01:00
|
|
|
u32 offset_i = *scroll + i;
|
2016-03-02 19:36:20 +01:00
|
|
|
u32 color_font = COLOR_WHITE;
|
2016-02-25 16:57:01 +01:00
|
|
|
if (offset_i < contents->n_entries) {
|
2016-04-05 21:30:05 +02:00
|
|
|
DirEntry* curr_entry = &(contents->entry[offset_i]);
|
|
|
|
char namestr[str_width - 10 + 1];
|
|
|
|
char bytestr[10 + 1];
|
|
|
|
color_font = (cursor != offset_i) ? COLOR_ENTRY(curr_entry) : COLOR_STD_FONT;
|
|
|
|
FormatBytes(bytestr, curr_entry->size);
|
|
|
|
ResizeString(namestr, curr_entry->name, str_width - 10, str_width - 20, false);
|
|
|
|
snprintf(tempstr, str_width + 1, "%s%10.10s", namestr,
|
2016-04-06 12:52:23 +02:00
|
|
|
(curr_entry->type == T_DIR) ? "(dir)" : (curr_entry->type == T_DOTDOT) ? "(..)" : bytestr);
|
2016-03-02 19:36:20 +01:00
|
|
|
} else snprintf(tempstr, str_width + 1, "%-*.*s", str_width, str_width, "");
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawStringF(BOT_SCREEN, pos_x, pos_y, color_font, COLOR_STD_BG, tempstr);
|
2016-02-25 16:57:01 +01:00
|
|
|
pos_y += stp_y;
|
|
|
|
}
|
2016-02-28 13:11:07 +01:00
|
|
|
|
2016-03-02 17:22:44 +01:00
|
|
|
if (contents->n_entries > lines) { // draw position bar at the right
|
2016-02-28 13:11:07 +01:00
|
|
|
u32 bar_height = (lines * SCREEN_HEIGHT) / contents->n_entries;
|
|
|
|
if (bar_height < bar_height_min) bar_height = bar_height_min;
|
2016-03-15 22:19:30 +01:00
|
|
|
u32 bar_pos = ((u64) *scroll * (SCREEN_HEIGHT - bar_height)) / (contents->n_entries - lines);
|
2016-02-28 13:11:07 +01:00
|
|
|
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawRectangle(BOT_SCREEN, SCREEN_WIDTH_BOT - bar_width, 0, bar_width, bar_pos, COLOR_STD_BG);
|
|
|
|
DrawRectangle(BOT_SCREEN, SCREEN_WIDTH_BOT - bar_width, bar_pos + bar_height, bar_width, SCREEN_WIDTH_BOT - (bar_pos + bar_height), COLOR_STD_BG);
|
|
|
|
DrawRectangle(BOT_SCREEN, SCREEN_WIDTH_BOT - bar_width, bar_pos, bar_width, bar_height, COLOR_SIDE_BAR);
|
|
|
|
} else DrawRectangle(BOT_SCREEN, SCREEN_WIDTH_BOT - bar_width, 0, bar_width, SCREEN_HEIGHT, COLOR_STD_BG);
|
2016-02-25 16:57:01 +01:00
|
|
|
}
|
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
u32 SdFormatMenu(void) {
|
2016-07-13 20:58:14 +02:00
|
|
|
const u32 emunand_size_table[6] = { 0x0, 0x0, 0x3AF, 0x4D8, 0x3FF, 0x7FF };
|
2016-09-07 00:18:49 +02:00
|
|
|
const u32 cluster_size_table[5] = { 0x0, 0x0, 0x4000, 0x8000, 0x10000 };
|
|
|
|
const char* option_emunand_size[6] = { "No EmuNAND", "O3DS NAND size", "N3DS NAND size", "1GB (legacy size)", "2GB (legacy size)", "User input..." };
|
|
|
|
const char* option_cluster_size[4] = { "Auto", "16KB Clusters", "32KB Clusters", "64KB Clusters" };
|
|
|
|
u32 cluster_size = 0;
|
2016-07-13 19:59:36 +02:00
|
|
|
u64 sdcard_size_mb = 0;
|
|
|
|
u64 emunand_size_mb = (u64) -1;
|
2016-09-07 00:18:49 +02:00
|
|
|
u32 user_select;
|
2016-07-13 19:59:36 +02:00
|
|
|
|
|
|
|
// check actual SD card size
|
|
|
|
sdcard_size_mb = GetSDCardSize() / 0x100000;
|
|
|
|
if (!sdcard_size_mb) {
|
2016-12-08 13:18:25 +01:00
|
|
|
ShowPrompt(false, "Error: SD card not detected.");
|
2016-07-13 19:59:36 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-09-07 00:18:49 +02:00
|
|
|
user_select = ShowSelectPrompt(6, option_emunand_size, "Format SD card (%lluMB)?\nChoose EmuNAND size:", sdcard_size_mb);
|
2016-07-13 19:59:36 +02:00
|
|
|
if (user_select && (user_select < 6)) {
|
|
|
|
emunand_size_mb = emunand_size_table[user_select];
|
|
|
|
} else if (user_select == 6) do {
|
|
|
|
emunand_size_mb = ShowNumberPrompt(0, "SD card size is %lluMB.\nEnter EmuNAND size (MB) below:", sdcard_size_mb);
|
|
|
|
if (emunand_size_mb == (u64) -1) break;
|
|
|
|
} while (emunand_size_mb > sdcard_size_mb);
|
|
|
|
if (emunand_size_mb == (u64) -1) return 1;
|
|
|
|
|
2016-09-07 00:18:49 +02:00
|
|
|
user_select = ShowSelectPrompt(4, option_cluster_size, "Format SD card (%lluMB)?\nChoose cluster size:", sdcard_size_mb);
|
|
|
|
if (!user_select) return 1;
|
|
|
|
else cluster_size = cluster_size_table[user_select];
|
|
|
|
|
|
|
|
if (!FormatSDCard(emunand_size_mb, cluster_size)) {
|
2016-07-13 19:59:36 +02:00
|
|
|
ShowPrompt(false, "Format SD: failed!");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-11-15 23:12:14 +01:00
|
|
|
if (CheckA9lh()) {
|
2016-07-13 22:22:23 +02:00
|
|
|
InitSDCardFS(); // on A9LH: copy the payload from mem to SD root
|
|
|
|
FileSetData("0:/arm9loaderhax.bin", (u8*) 0x23F00000, 0x40000, 0, true);
|
|
|
|
DeinitSDCardFS();
|
|
|
|
}
|
|
|
|
|
2016-09-07 00:19:12 +02:00
|
|
|
VirtualFile nand;
|
2016-11-15 23:34:21 +01:00
|
|
|
if (!GetVirtualFile(&nand, "S:/nand_minsize.bin"))
|
2016-09-07 00:19:12 +02:00
|
|
|
return 0;
|
2017-01-16 19:59:04 +01:00
|
|
|
InitSDCardFS(); // this has to be initialized for EmuNAND to work
|
2016-09-07 00:19:12 +02:00
|
|
|
if ((nand.size / (1024*1024) <= emunand_size_mb) && ShowPrompt(true, "Clone SysNAND to RedNAND now?")) {
|
|
|
|
if (!PathCopy("E:", "S:/nand_minsize.bin", NULL))
|
|
|
|
ShowPrompt(false, "Cloning SysNAND to EmuNAND: failed!");
|
|
|
|
}
|
2017-01-16 19:59:04 +01:00
|
|
|
DeinitSDCardFS();
|
2016-09-07 00:19:12 +02:00
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:03:40 +02:00
|
|
|
u32 HexViewer(const char* path) {
|
2016-05-19 21:24:49 +02:00
|
|
|
static const u32 max_data = (SCREEN_HEIGHT / 8) * 16;
|
2016-04-08 21:03:40 +02:00
|
|
|
static u32 mode = 0;
|
2016-12-07 15:12:35 +01:00
|
|
|
u8* data = TEMP_BUFFER;
|
|
|
|
u8* bottom_cpy = TEMP_BUFFER + 0xC0000; // a copy of the bottom screen framebuffer
|
2016-04-10 15:55:39 +02:00
|
|
|
u32 fsize = FileGetSize(path);
|
2016-04-08 21:03:40 +02:00
|
|
|
|
2016-05-18 00:05:24 +02:00
|
|
|
bool dual_screen;
|
2016-04-08 21:03:40 +02:00
|
|
|
int x_off, x_hex, x_ascii;
|
|
|
|
u32 vpad, hlpad, hrpad;
|
|
|
|
u32 rows, cols;
|
|
|
|
u32 total_shown;
|
|
|
|
u32 total_data;
|
|
|
|
|
|
|
|
u32 last_mode = 0xFF;
|
2016-05-18 23:46:22 +02:00
|
|
|
u32 last_offset = (u32) -1;
|
2016-04-08 21:03:40 +02:00
|
|
|
u32 offset = 0;
|
|
|
|
|
2016-07-12 18:33:52 +02:00
|
|
|
u32 found_offset = (u32) -1;
|
|
|
|
u32 found_size = 0;
|
|
|
|
|
2016-05-21 22:04:03 +02:00
|
|
|
static const u32 edit_bsize = 0x4000; // should be multiple of 0x200 * 2
|
2016-05-19 21:24:49 +02:00
|
|
|
bool edit_mode = false;
|
2016-12-07 15:12:35 +01:00
|
|
|
u8* edit_buffer = TEMP_BUFFER;
|
|
|
|
u8* edit_buffer_cpy = TEMP_BUFFER + edit_bsize;
|
2016-05-21 22:04:03 +02:00
|
|
|
u32 edit_start;
|
2016-05-19 21:24:49 +02:00
|
|
|
int cursor = 0;
|
|
|
|
|
2016-12-07 23:58:03 +01:00
|
|
|
static bool show_instr = true;
|
|
|
|
if (show_instr) { // show one time instructions
|
|
|
|
ShowPrompt(false, "Hexeditor Controls:\n \n\x18\x19\x1A\x1B(+R) - Scroll\nR+Y - Switch view\nX - Search / goto...\nA - Enter edit mode\nA+\x18\x19\x1A\x1B - Edit value\nB - Exit\n");
|
|
|
|
show_instr = false;
|
|
|
|
}
|
|
|
|
|
2016-05-18 23:46:22 +02:00
|
|
|
memcpy(bottom_cpy, BOT_SCREEN, (SCREEN_HEIGHT * SCREEN_WIDTH_BOT * 3));
|
2016-05-18 00:05:24 +02:00
|
|
|
|
2016-04-08 21:03:40 +02:00
|
|
|
while (true) {
|
|
|
|
if (mode != last_mode) {
|
|
|
|
switch (mode) { // display mode
|
2016-07-18 03:15:04 +02:00
|
|
|
#ifdef FONT_6X10
|
|
|
|
case 1:
|
|
|
|
vpad = 0;
|
|
|
|
hlpad = hrpad = 1;
|
|
|
|
cols = 16;
|
|
|
|
x_off = 0;
|
|
|
|
x_ascii = SCREEN_WIDTH_TOP - (FONT_WIDTH_EXT * cols);
|
|
|
|
x_hex = x_off + (8*FONT_WIDTH_EXT) + 16;
|
|
|
|
dual_screen = false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
mode = 0;
|
|
|
|
vpad = 0;
|
|
|
|
hlpad = hrpad = 3;
|
|
|
|
cols = 8;
|
|
|
|
x_off = 30 + (SCREEN_WIDTH_TOP - SCREEN_WIDTH_BOT) / 2;
|
|
|
|
x_ascii = SCREEN_WIDTH_TOP - x_off - (FONT_WIDTH_EXT * cols);
|
|
|
|
x_hex = (SCREEN_WIDTH_TOP - ((hlpad + (2*FONT_WIDTH_EXT) + hrpad) * cols)) / 2;
|
|
|
|
dual_screen = true;
|
|
|
|
break;
|
|
|
|
#else
|
2016-04-08 21:03:40 +02:00
|
|
|
case 1:
|
|
|
|
vpad = hlpad = hrpad = 1;
|
|
|
|
cols = 12;
|
|
|
|
x_off = 0;
|
|
|
|
x_ascii = SCREEN_WIDTH_TOP - (8 * cols);
|
|
|
|
x_hex = x_off + (8*8) + 12;
|
2016-05-18 00:05:24 +02:00
|
|
|
dual_screen = false;
|
2016-04-08 21:03:40 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
vpad = 1;
|
|
|
|
hlpad = 0;
|
|
|
|
hrpad = 1;
|
|
|
|
cols = 16;
|
|
|
|
x_off = -1;
|
|
|
|
x_ascii = SCREEN_WIDTH_TOP - (8 * cols);
|
|
|
|
x_hex = 0;
|
2016-05-18 00:05:24 +02:00
|
|
|
dual_screen = false;
|
2016-04-08 21:03:40 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
vpad = hlpad = hrpad = 1;
|
|
|
|
cols = 16;
|
|
|
|
x_off = 20;
|
|
|
|
x_ascii = -1;
|
|
|
|
x_hex = x_off + (8*8) + 12;
|
2016-05-18 00:05:24 +02:00
|
|
|
dual_screen = false;
|
2016-04-08 21:03:40 +02:00
|
|
|
break;
|
|
|
|
default:
|
2016-07-18 03:15:04 +02:00
|
|
|
mode = 0;
|
2016-04-08 21:03:40 +02:00
|
|
|
vpad = hlpad = hrpad = 2;
|
|
|
|
cols = 8;
|
|
|
|
x_off = (SCREEN_WIDTH_TOP - SCREEN_WIDTH_BOT) / 2;
|
|
|
|
x_ascii = SCREEN_WIDTH_TOP - x_off - (8 * cols);
|
|
|
|
x_hex = (SCREEN_WIDTH_TOP - ((hlpad + 16 + hrpad) * cols)) / 2;
|
2016-05-18 00:05:24 +02:00
|
|
|
dual_screen = true;
|
2016-04-08 21:03:40 +02:00
|
|
|
break;
|
2016-07-18 03:15:04 +02:00
|
|
|
#endif
|
2016-04-08 21:03:40 +02:00
|
|
|
}
|
2016-07-18 03:15:04 +02:00
|
|
|
rows = (dual_screen ? 2 : 1) * SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*vpad));
|
2016-04-08 21:03:40 +02:00
|
|
|
total_shown = rows * cols;
|
|
|
|
last_mode = mode;
|
2016-05-18 00:05:24 +02:00
|
|
|
ClearScreenF(true, dual_screen, COLOR_STD_BG);
|
2016-05-18 23:46:22 +02:00
|
|
|
if (!dual_screen) memcpy(BOT_SCREEN, bottom_cpy, (SCREEN_HEIGHT * SCREEN_WIDTH_BOT * 3));
|
2016-04-08 21:03:40 +02:00
|
|
|
}
|
2016-04-10 15:55:39 +02:00
|
|
|
// fix offset (if required)
|
2016-06-13 23:51:41 +02:00
|
|
|
if (offset % cols) offset -= (offset % cols); // fix offset (align to cols)
|
|
|
|
if (offset + total_shown > fsize + cols) // if offset too big
|
2016-04-10 15:55:39 +02:00
|
|
|
offset = (total_shown > fsize) ? 0 : (fsize + cols - total_shown - (fsize % cols));
|
2016-05-19 21:24:49 +02:00
|
|
|
// get data, using max data size (if new offset)
|
2016-05-27 01:21:05 +02:00
|
|
|
if (offset != last_offset) {
|
2016-05-21 22:04:03 +02:00
|
|
|
if (!edit_mode) {
|
|
|
|
total_data = FileGetData(path, data, max_data, offset);
|
|
|
|
} else { // edit mode - read from memory
|
|
|
|
if ((offset < edit_start) || (offset + max_data > edit_start + edit_bsize))
|
|
|
|
offset = last_offset; // we don't expect this to happen
|
|
|
|
total_data = (fsize - offset >= max_data) ? max_data : fsize - offset;
|
|
|
|
data = edit_buffer + (offset - edit_start);
|
|
|
|
}
|
2016-05-19 21:24:49 +02:00
|
|
|
last_offset = offset;
|
|
|
|
}
|
2016-04-08 21:03:40 +02:00
|
|
|
|
|
|
|
// display data on screen
|
|
|
|
for (u32 row = 0; row < rows; row++) {
|
|
|
|
char ascii[16 + 1] = { 0 };
|
2016-07-18 03:15:04 +02:00
|
|
|
u32 y = row * (FONT_HEIGHT_EXT + (2*vpad)) + vpad;
|
2016-04-08 21:03:40 +02:00
|
|
|
u32 curr_pos = row * cols;
|
|
|
|
u32 cutoff = (curr_pos >= total_data) ? 0 : (total_data >= curr_pos + cols) ? cols : total_data - curr_pos;
|
2016-07-12 18:33:52 +02:00
|
|
|
u32 marked0 = (found_size && (offset <= found_offset)) ? found_offset - offset : 0;
|
|
|
|
u32 marked1 = marked0 + found_size;
|
2016-05-18 23:46:22 +02:00
|
|
|
u8* screen = TOP_SCREEN;
|
|
|
|
u32 x0 = 0;
|
|
|
|
|
2016-07-12 18:33:52 +02:00
|
|
|
// fix marked0 / marked1 offsets for current row
|
|
|
|
marked0 = (marked0 < curr_pos) ? 0 : (marked0 >= curr_pos + cols) ? cols : marked0 - curr_pos;
|
|
|
|
marked1 = (marked1 < curr_pos) ? 0 : (marked1 >= curr_pos + cols) ? cols : marked1 - curr_pos;
|
|
|
|
|
2016-05-18 23:46:22 +02:00
|
|
|
if (y >= SCREEN_HEIGHT) { // switch to bottom screen
|
|
|
|
y -= SCREEN_HEIGHT;
|
|
|
|
screen = BOT_SCREEN;
|
|
|
|
x0 = 40;
|
|
|
|
}
|
2016-04-08 21:03:40 +02:00
|
|
|
|
|
|
|
memcpy(ascii, data + curr_pos, cols);
|
|
|
|
for (u32 col = 0; col < cols; col++)
|
|
|
|
if ((col >= cutoff) || (ascii[col] == 0x00)) ascii[col] = ' ';
|
|
|
|
|
|
|
|
// draw offset / ASCII representation
|
2016-05-18 23:46:22 +02:00
|
|
|
if (x_off >= 0) DrawStringF(screen, x_off - x0, y, cutoff ? COLOR_HVOFFS : COLOR_HVOFFSI,
|
2016-05-18 00:05:24 +02:00
|
|
|
COLOR_STD_BG, "%08X", (unsigned int) offset + curr_pos);
|
2016-05-19 21:24:49 +02:00
|
|
|
if (x_ascii >= 0) {
|
2016-05-18 23:46:22 +02:00
|
|
|
DrawString(screen, ascii, x_ascii - x0, y, COLOR_HVASCII, COLOR_STD_BG);
|
2016-07-12 18:33:52 +02:00
|
|
|
for (u32 i = marked0; i < marked1; i++)
|
2016-07-18 03:15:04 +02:00
|
|
|
DrawCharacter(screen, ascii[i % cols], x_ascii - x0 + (FONT_WIDTH_EXT * i), y, COLOR_MARKED, COLOR_STD_BG);
|
2016-05-19 21:24:49 +02:00
|
|
|
if (edit_mode && ((u32) cursor / cols == row)) DrawCharacter(screen, ascii[cursor % cols],
|
2016-07-18 03:15:04 +02:00
|
|
|
x_ascii - x0 + FONT_WIDTH_EXT * (cursor % cols), y, COLOR_RED, COLOR_STD_BG);
|
2016-05-19 21:24:49 +02:00
|
|
|
}
|
2016-04-08 21:03:40 +02:00
|
|
|
|
|
|
|
// draw HEX values
|
|
|
|
for (u32 col = 0; (col < cols) && (x_hex >= 0); col++) {
|
2016-07-18 03:15:04 +02:00
|
|
|
u32 x = (x_hex + hlpad) + (((2*FONT_WIDTH_EXT) + hrpad + hlpad) * col) - x0;
|
2016-07-12 18:33:52 +02:00
|
|
|
u32 hex_color = (edit_mode && ((u32) cursor == curr_pos + col)) ? COLOR_RED :
|
|
|
|
((col >= marked0) && (col < marked1)) ? COLOR_MARKED : COLOR_HVHEX(col);
|
2016-04-08 21:03:40 +02:00
|
|
|
if (col < cutoff)
|
2016-05-19 21:24:49 +02:00
|
|
|
DrawStringF(screen, x, y, hex_color, COLOR_STD_BG, "%02X", (unsigned int) data[curr_pos + col]);
|
|
|
|
else DrawStringF(screen, x, y, hex_color, COLOR_STD_BG, " ");
|
2016-04-08 21:03:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle user input
|
|
|
|
u32 pad_state = InputWait();
|
2016-05-19 21:24:49 +02:00
|
|
|
if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_L1)) CreateScreenshot();
|
|
|
|
else if (!edit_mode) { // standard viewer mode
|
|
|
|
u32 step_ud = (pad_state & BUTTON_R1) ? (0x1000 - (0x1000 % cols)) : cols;
|
|
|
|
u32 step_lr = (pad_state & BUTTON_R1) ? (0x10000 - (0x10000 % cols)) : total_shown;
|
|
|
|
if (pad_state & BUTTON_DOWN) offset += step_ud;
|
|
|
|
else if (pad_state & BUTTON_RIGHT) offset += step_lr;
|
|
|
|
else if (pad_state & BUTTON_UP) offset = (offset > step_ud) ? offset - step_ud : 0;
|
|
|
|
else if (pad_state & BUTTON_LEFT) offset = (offset > step_lr) ? offset - step_lr : 0;
|
2016-07-18 03:15:04 +02:00
|
|
|
else if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_Y)) mode++;
|
2016-05-19 21:24:49 +02:00
|
|
|
else if (pad_state & BUTTON_A) edit_mode = true;
|
2016-06-18 15:07:57 +02:00
|
|
|
else if (pad_state & (BUTTON_B|BUTTON_START)) break;
|
2016-07-12 18:33:52 +02:00
|
|
|
else if (found_size && (pad_state & BUTTON_R1) && (pad_state & BUTTON_X)) {
|
|
|
|
u8 data[64] = { 0 };
|
|
|
|
FileGetData(path, data, found_size, found_offset);
|
|
|
|
found_offset = FileFindData(path, data, found_size, found_offset + 1);
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
|
|
|
if (found_offset == (u32) -1) {
|
|
|
|
ShowPrompt(false, "Not found!");
|
|
|
|
found_size = 0;
|
|
|
|
} else offset = found_offset;
|
|
|
|
} else if (pad_state & BUTTON_X) {
|
|
|
|
const char* optionstr[3] = { "Go to offset", "Search for string", "Search for data" };
|
|
|
|
u32 user_select = ShowSelectPrompt(3, optionstr, "Current offset: %08X\nSelect action:",
|
2016-06-13 23:51:41 +02:00
|
|
|
(unsigned int) offset);
|
2016-07-12 18:33:52 +02:00
|
|
|
if (user_select == 1) { // -> goto offset
|
|
|
|
u64 new_offset = ShowHexPrompt(offset, 8, "Current offset: %08X\nEnter new offset below.",
|
|
|
|
(unsigned int) offset);
|
|
|
|
if (new_offset != (u64) -1) offset = new_offset;
|
|
|
|
} else if (user_select == 2) {
|
|
|
|
char string[64 + 1] = { 0 };
|
|
|
|
if (found_size) FileGetData(path, (u8*) string, (found_size <= 64) ? found_size : 64, found_offset);
|
|
|
|
if (ShowStringPrompt(string, 64 + 1, "Enter search string below.\n(R+X to repeat search)", (unsigned int) offset)) {
|
|
|
|
found_size = strnlen(string, 64);
|
|
|
|
found_offset = FileFindData(path, (u8*) string, found_size, offset);
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
|
|
|
if (found_offset == (u32) -1) {
|
|
|
|
ShowPrompt(false, "Not found!");
|
|
|
|
found_size = 0;
|
|
|
|
} else offset = found_offset;
|
|
|
|
}
|
|
|
|
} else if (user_select == 3) {
|
|
|
|
u8 data[64] = { 0 };
|
|
|
|
u32 size = 0;
|
|
|
|
if (found_size) size = FileGetData(path, data, (found_size <= 64) ? found_size : 64, found_offset);
|
|
|
|
if (ShowDataPrompt(data, &size, "Enter search data below.\n(R+X to repeat search)", (unsigned int) offset)) {
|
|
|
|
found_size = size;
|
|
|
|
found_offset = FileFindData(path, data, size, offset);
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
|
|
|
if (found_offset == (u32) -1) {
|
|
|
|
ShowPrompt(false, "Not found!");
|
|
|
|
found_size = 0;
|
|
|
|
} else offset = found_offset;
|
|
|
|
}
|
|
|
|
}
|
2016-06-13 23:51:41 +02:00
|
|
|
}
|
2016-05-27 01:21:05 +02:00
|
|
|
if (edit_mode && CheckWritePermissions(path)) { // setup edit mode
|
2016-07-12 18:33:52 +02:00
|
|
|
found_size = 0;
|
|
|
|
found_offset = (u32) -1;
|
2016-05-21 22:04:03 +02:00
|
|
|
cursor = 0;
|
|
|
|
edit_start = ((offset - (offset % 0x200) <= (edit_bsize / 2)) || (fsize < edit_bsize)) ? 0 :
|
|
|
|
offset - (offset % 0x200) - (edit_bsize / 2);
|
|
|
|
FileGetData(path, edit_buffer, edit_bsize, edit_start);
|
|
|
|
memcpy(edit_buffer_cpy, edit_buffer, edit_bsize);
|
|
|
|
data = edit_buffer + (offset - edit_start);
|
2016-05-27 01:21:05 +02:00
|
|
|
} else edit_mode = false;
|
2016-05-19 21:24:49 +02:00
|
|
|
} else { // editor mode
|
2016-06-18 15:07:57 +02:00
|
|
|
if (pad_state & (BUTTON_B|BUTTON_START)) {
|
2016-05-19 21:24:49 +02:00
|
|
|
edit_mode = false;
|
2016-05-21 22:04:03 +02:00
|
|
|
// check for user edits
|
|
|
|
u32 diffs = 0;
|
|
|
|
for (u32 i = 0; i < edit_bsize; i++) if (edit_buffer[i] != edit_buffer_cpy[i]) diffs++;
|
|
|
|
if (diffs && ShowPrompt(true, "You made edits in %i place(s).\nWrite changes to file?", diffs))
|
2016-06-17 17:12:11 +02:00
|
|
|
if (!FileSetData(path, edit_buffer, min(edit_bsize, (fsize - edit_start)), edit_start, false))
|
2016-05-26 21:09:01 +02:00
|
|
|
ShowPrompt(false, "Failed writing to file!");
|
2016-12-07 15:12:35 +01:00
|
|
|
data = TEMP_BUFFER;
|
2016-05-21 22:04:03 +02:00
|
|
|
last_offset = (u32) -1; // force reload from file
|
2016-05-19 21:24:49 +02:00
|
|
|
} else if (pad_state & BUTTON_A) {
|
|
|
|
if (pad_state & BUTTON_DOWN) data[cursor]--;
|
|
|
|
else if (pad_state & BUTTON_UP) data[cursor]++;
|
|
|
|
else if (pad_state & BUTTON_RIGHT) data[cursor] += 0x10;
|
|
|
|
else if (pad_state & BUTTON_LEFT) data[cursor] -= 0x10;
|
|
|
|
} else {
|
|
|
|
if (pad_state & BUTTON_DOWN) cursor += cols;
|
|
|
|
else if (pad_state & BUTTON_UP) cursor -= cols;
|
|
|
|
else if (pad_state & BUTTON_RIGHT) cursor++;
|
|
|
|
else if (pad_state & BUTTON_LEFT) cursor--;
|
|
|
|
// fix cursor position
|
|
|
|
if (cursor < 0) {
|
|
|
|
if (offset >= cols) {
|
|
|
|
offset -= cols;
|
|
|
|
cursor += cols;
|
|
|
|
} else cursor = 0;
|
|
|
|
} else if (((u32) cursor >= total_data) && (total_data < total_shown)) {
|
|
|
|
cursor = total_data - 1;
|
|
|
|
} else if ((u32) cursor >= total_shown) {
|
|
|
|
if (offset + total_shown == fsize) {
|
|
|
|
cursor = total_shown - 1;
|
|
|
|
} else {
|
|
|
|
offset += cols;
|
|
|
|
cursor = (offset + cursor >= fsize) ? fsize - offset - 1 : cursor - cols;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-08 21:03:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
2016-05-18 23:46:22 +02:00
|
|
|
memcpy(BOT_SCREEN, bottom_cpy, (SCREEN_HEIGHT * SCREEN_WIDTH_BOT * 3));
|
2016-04-08 21:03:40 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-07 23:58:03 +01:00
|
|
|
u32 Sha256Calculator(const char* path) {
|
|
|
|
u32 drvtype = DriveType(path);
|
|
|
|
char pathstr[32 + 1];
|
|
|
|
u8 sha256[32];
|
|
|
|
TruncateString(pathstr, path, 32, 8);
|
|
|
|
if (!FileGetSha256(path, sha256)) {
|
|
|
|
ShowPrompt(false, "Calculating SHA-256: failed!");
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
static char pathstr_prev[32 + 1] = { 0 };
|
|
|
|
static u8 sha256_prev[32] = { 0 };
|
|
|
|
char sha_path[256];
|
|
|
|
u8 sha256_file[32];
|
|
|
|
|
|
|
|
snprintf(sha_path, 256, "%s.sha", path);
|
|
|
|
bool have_sha = (FileGetData(sha_path, sha256_file, 32, 0) == 32);
|
|
|
|
bool write_sha = !have_sha && (drvtype & DRV_SDCARD); // writing only on SD
|
|
|
|
if (ShowPrompt(write_sha, "%s\n%016llX%016llX\n%016llX%016llX%s%s%s%s%s",
|
|
|
|
pathstr, getbe64(sha256 + 0), getbe64(sha256 + 8), getbe64(sha256 + 16), getbe64(sha256 + 24),
|
|
|
|
(have_sha) ? "\nSHA verification: " : "",
|
|
|
|
(have_sha) ? ((memcmp(sha256, sha256_file, 32) == 0) ? "passed!" : "failed!") : "",
|
|
|
|
(memcmp(sha256, sha256_prev, 32) == 0) ? "\n \nIdentical with previous file:\n" : "",
|
|
|
|
(memcmp(sha256, sha256_prev, 32) == 0) ? pathstr_prev : "",
|
|
|
|
(write_sha) ? "\n \nWrite .SHA file?" : "") && !have_sha && write_sha) {
|
|
|
|
FileSetData(sha_path, sha256, 32, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
strncpy(pathstr_prev, pathstr, 32 + 1);
|
|
|
|
memcpy(sha256_prev, sha256, 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:20:00 +01:00
|
|
|
u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* current_dir, DirStruct* clipboard) {
|
|
|
|
DirEntry* curr_entry = &(current_dir->entry[*cursor]);
|
|
|
|
const char* optionstr[8];
|
|
|
|
|
2016-12-19 01:33:30 +01:00
|
|
|
// check for file lock
|
|
|
|
if (!FileUnlock(curr_entry->path)) return 1;
|
|
|
|
|
2016-12-13 00:20:00 +01:00
|
|
|
u32 filetype = IdentifyFileType(curr_entry->path);
|
|
|
|
u32 drvtype = DriveType(curr_entry->path);
|
|
|
|
|
|
|
|
// special stuff, only available on FAT drives (see int special below)
|
2016-12-19 14:32:22 +01:00
|
|
|
bool mountable = ((filetype & FTYPE_MOUNTABLE) && !(drvtype & DRV_IMAGE));
|
2016-12-13 00:20:00 +01:00
|
|
|
bool verificable = (filetype & FYTPE_VERIFICABLE);
|
|
|
|
bool decryptable = (filetype & FYTPE_DECRYPTABLE);
|
|
|
|
bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE)));
|
2016-12-15 11:46:00 +01:00
|
|
|
bool buildable = (filetype & FTYPE_BUILDABLE);
|
|
|
|
bool buildable_legit = (filetype & FTYPE_BUILDABLE_L);
|
2017-01-02 17:37:08 +01:00
|
|
|
bool restorable = CheckA9lh() && (filetype & FTYPE_RESTORABLE);
|
2016-12-13 00:20:00 +01:00
|
|
|
|
|
|
|
char pathstr[32 + 1];
|
|
|
|
TruncateString(pathstr, curr_entry->path, 32, 8);
|
2016-12-13 17:10:39 +01:00
|
|
|
|
2016-12-13 00:20:00 +01:00
|
|
|
// main menu processing
|
|
|
|
int n_opt = 0;
|
|
|
|
int special = (filetype && (drvtype & DRV_FAT)) ? ++n_opt : -1;
|
|
|
|
int hexviewer = ++n_opt;
|
|
|
|
int calcsha = ++n_opt;
|
|
|
|
int inject = ((clipboard->n_entries == 1) &&
|
|
|
|
(clipboard->entry[0].type == T_FILE) &&
|
|
|
|
(drvtype & DRV_FAT) &&
|
|
|
|
(strncmp(clipboard->entry[0].path, curr_entry->path, 256) != 0)) ?
|
|
|
|
(int) ++n_opt : -1;
|
2016-12-20 13:10:07 +01:00
|
|
|
int searchdrv = (DriveType(current_path) & DRV_SEARCH) ? ++n_opt : -1;
|
2016-12-13 00:20:00 +01:00
|
|
|
if (special > 0) optionstr[special-1] =
|
2017-01-02 17:37:08 +01:00
|
|
|
(filetype == IMG_NAND ) ? "NAND image options..." :
|
2016-12-13 00:20:00 +01:00
|
|
|
(filetype == IMG_FAT ) ? "Mount as FAT image" :
|
|
|
|
(filetype == GAME_CIA ) ? "CIA image options..." :
|
|
|
|
(filetype == GAME_NCSD ) ? "NCSD image options..." :
|
|
|
|
(filetype == GAME_NCCH ) ? "NCCH image options..." :
|
|
|
|
(filetype == GAME_EXEFS) ? "Mount as EXEFS image" :
|
|
|
|
(filetype == GAME_ROMFS) ? "Mount as ROMFS image" :
|
2016-12-22 01:35:35 +01:00
|
|
|
(filetype == GAME_TMD) ? "TMD file options..." :
|
|
|
|
(filetype == SYS_FIRM) ? "FIRM image options..." : "???";
|
2016-12-13 00:20:00 +01:00
|
|
|
optionstr[hexviewer-1] = "Show in Hexeditor";
|
|
|
|
optionstr[calcsha-1] = "Calculate SHA-256";
|
|
|
|
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
|
|
|
|
if (searchdrv > 0) optionstr[searchdrv-1] = "Open containing folder";
|
|
|
|
|
|
|
|
int user_select = ShowSelectPrompt(n_opt, optionstr, pathstr);
|
|
|
|
if (user_select == hexviewer) { // -> show in hex viewer
|
|
|
|
HexViewer(curr_entry->path);
|
|
|
|
return 0;
|
|
|
|
} else if (user_select == calcsha) { // -> calculate SHA-256
|
|
|
|
Sha256Calculator(curr_entry->path);
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
return 0;
|
|
|
|
} else if (user_select == inject) { // -> inject data from clipboard
|
|
|
|
char origstr[18 + 1];
|
|
|
|
TruncateString(origstr, clipboard->entry[0].name, 18, 10);
|
|
|
|
u64 offset = ShowHexPrompt(0, 8, "Inject data from %s?\nSpecifiy offset below.", origstr);
|
|
|
|
if (offset != (u64) -1) {
|
|
|
|
if (!FileInjectFile(curr_entry->path, clipboard->entry[0].path, (u32) offset))
|
|
|
|
ShowPrompt(false, "Failed injecting %s", origstr);
|
|
|
|
clipboard->n_entries = 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else if (user_select == searchdrv) { // -> search drive, open containing path
|
|
|
|
char* last_slash = strrchr(curr_entry->path, '/');
|
|
|
|
if (last_slash) {
|
|
|
|
snprintf(current_path, last_slash - curr_entry->path + 1, "%s", curr_entry->path);
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
*cursor = 1;
|
|
|
|
*scroll = 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else if (user_select != special) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stuff for special menu starts here
|
|
|
|
n_opt = 0;
|
|
|
|
int mount = (mountable) ? ++n_opt : -1;
|
2017-01-02 17:37:08 +01:00
|
|
|
int restore = (restorable) ? ++n_opt : -1;
|
2016-12-13 00:20:00 +01:00
|
|
|
int decrypt = (decryptable) ? ++n_opt : -1;
|
|
|
|
int decrypt_inplace = (decryptable_inplace) ? ++n_opt : -1;
|
2016-12-15 11:46:00 +01:00
|
|
|
int build = (buildable) ? ++n_opt : -1;
|
|
|
|
int build_legit = (buildable_legit) ? ++n_opt : -1;
|
2016-12-13 00:20:00 +01:00
|
|
|
int verify = (verificable) ? ++n_opt : -1;
|
|
|
|
if (mount > 0) optionstr[mount-1] = "Mount image to drive";
|
2017-01-02 17:37:08 +01:00
|
|
|
if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)";
|
2016-12-13 00:20:00 +01:00
|
|
|
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
|
|
|
|
if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)";
|
2016-12-15 11:46:00 +01:00
|
|
|
if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
|
|
|
|
if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)";
|
2016-12-13 00:20:00 +01:00
|
|
|
if (verify > 0) optionstr[verify-1] = "Verify file";
|
|
|
|
|
|
|
|
u32 n_marked = 0;
|
|
|
|
if (curr_entry->marked) {
|
|
|
|
for (u32 i = 0; i < current_dir->n_entries; i++)
|
|
|
|
if (current_dir->entry[i].marked) n_marked++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// auto select when there is only one option
|
|
|
|
user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, pathstr) : n_opt;
|
|
|
|
if (user_select == mount) { // -> mount file as image
|
2016-12-13 16:00:14 +01:00
|
|
|
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
|
2016-12-13 00:20:00 +01:00
|
|
|
clipboard->n_entries = 0; // remove last mounted image clipboard entries
|
|
|
|
InitImgFS(curr_entry->path);
|
2016-12-13 16:00:14 +01:00
|
|
|
if (!(DriveType("7:")||DriveType("G:"))) {
|
2016-12-13 00:20:00 +01:00
|
|
|
ShowPrompt(false, "Mounting image: failed");
|
|
|
|
InitImgFS(NULL);
|
|
|
|
} else {
|
|
|
|
*cursor = 0;
|
|
|
|
*current_path = '\0';
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
2016-12-13 16:00:14 +01:00
|
|
|
if (strspn(current_dir->entry[i].path, "7GI") == 0)
|
2016-12-13 00:20:00 +01:00
|
|
|
continue;
|
|
|
|
strncpy(current_path, current_dir->entry[i].path, 256);
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
*cursor = 1;
|
|
|
|
*scroll = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else if ((user_select == decrypt) || (user_select == decrypt_inplace)) { // -> decrypt game file
|
|
|
|
bool inplace = (user_select == decrypt_inplace);
|
|
|
|
if ((n_marked > 1) && ShowPrompt(true, "Try to decrypt all %lu selected files?", n_marked)) {
|
|
|
|
u32 n_success = 0;
|
|
|
|
u32 n_unencrypted = 0;
|
|
|
|
u32 n_other = 0;
|
|
|
|
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
|
|
|
const char* path = current_dir->entry[i].path;
|
|
|
|
if (!current_dir->entry[i].marked)
|
|
|
|
continue;
|
|
|
|
if (IdentifyFileType(path) != filetype) {
|
|
|
|
n_other++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (CheckEncryptedGameFile(path) != 0) {
|
|
|
|
n_unencrypted++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
current_dir->entry[i].marked = false;
|
|
|
|
if (DecryptGameFile(path, inplace) == 0) n_success++;
|
|
|
|
else { // on failure: set cursor on failed title, break;
|
|
|
|
*cursor = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (n_other || n_unencrypted) {
|
|
|
|
ShowPrompt(false, "%lu/%lu files decrypted ok\n%lu/%lu not encrypted\n%lu/%lu not of same type",
|
|
|
|
n_success, n_marked, n_unencrypted, n_marked, n_other, n_marked);
|
|
|
|
} else ShowPrompt(false, "%lu/%lu files decrypted ok", n_success, n_marked);
|
|
|
|
if (!inplace && n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
|
|
|
|
} else {
|
|
|
|
if (CheckEncryptedGameFile(curr_entry->path) != 0) {
|
|
|
|
ShowPrompt(false, "%s\nFile is not encrypted", pathstr);
|
|
|
|
} else {
|
|
|
|
u32 ret = DecryptGameFile(curr_entry->path, inplace);
|
|
|
|
if (inplace || (ret != 0)) ShowPrompt(false, "%s\nDecryption %s", pathstr, (ret == 0) ? "success" : "failed");
|
|
|
|
else ShowPrompt(false, "%s\nDecrypted to %s", pathstr, OUTPUT_PATH);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2016-12-15 11:46:00 +01:00
|
|
|
} else if ((user_select == build) || (user_select == build_legit)) { // -> build CIA
|
|
|
|
bool force_legit = (user_select == build_legit);
|
|
|
|
if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) {
|
|
|
|
u32 n_success = 0;
|
|
|
|
u32 n_other = 0;
|
|
|
|
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
|
|
|
const char* path = current_dir->entry[i].path;
|
|
|
|
if (!current_dir->entry[i].marked)
|
|
|
|
continue;
|
|
|
|
if (IdentifyFileType(path) != filetype) {
|
|
|
|
n_other++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
current_dir->entry[i].marked = false;
|
|
|
|
if (BuildCiaFromGameFile(path, force_legit) == 0) n_success++;
|
|
|
|
else { // on failure: set *cursor on failed title, break;
|
|
|
|
*cursor = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (n_other) ShowPrompt(false, "%lu/%lu CIAs built ok\n%lu/%lu not of same type",
|
|
|
|
n_success, n_marked, n_other, n_marked);
|
|
|
|
else ShowPrompt(false, "%lu/%lu CIAs built ok", n_success, n_marked);
|
|
|
|
if (n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
|
|
|
|
} else {
|
|
|
|
if (BuildCiaFromGameFile(curr_entry->path, force_legit) == 0)
|
|
|
|
ShowPrompt(false, "%s\nCIA built to %s", pathstr, OUTPUT_PATH);
|
|
|
|
else ShowPrompt(false, "%s\nCIA build failed", pathstr);
|
|
|
|
}
|
|
|
|
return 0;
|
2017-01-02 17:37:08 +01:00
|
|
|
} else if (user_select == verify) { // -> verify game / nand file
|
2016-12-13 00:20:00 +01:00
|
|
|
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
|
|
|
u32 n_success = 0;
|
|
|
|
u32 n_other = 0;
|
|
|
|
u32 n_processed = 0;
|
|
|
|
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
|
|
|
const char* path = current_dir->entry[i].path;
|
|
|
|
if (!current_dir->entry[i].marked)
|
|
|
|
continue;
|
|
|
|
if (IdentifyFileType(path) != filetype) {
|
|
|
|
n_other++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!(filetype & (GAME_CIA|GAME_TMD)) &&
|
|
|
|
!ShowProgress(n_processed++, n_marked, path)) break;
|
|
|
|
current_dir->entry[i].marked = false;
|
2017-01-02 17:37:08 +01:00
|
|
|
if (filetype & IMG_NAND) {
|
|
|
|
if (ValidateNandDump(path) == 0) n_success++;
|
|
|
|
} else if (VerifyGameFile(path) == 0) n_success++;
|
2016-12-13 00:20:00 +01:00
|
|
|
else { // on failure: set *cursor on failed title, break;
|
|
|
|
*cursor = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (n_other) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu not of same type",
|
|
|
|
n_success, n_marked, n_other, n_marked);
|
|
|
|
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);
|
|
|
|
} else {
|
|
|
|
ShowString("%s\nVerifying file, please wait...", pathstr);
|
2017-01-02 17:37:08 +01:00
|
|
|
if (filetype & IMG_NAND) {
|
|
|
|
ShowPrompt(false, "%s\nNAND validation %s", pathstr,
|
|
|
|
(ValidateNandDump(curr_entry->path) == 0) ? "success" : "failed");
|
|
|
|
} else ShowPrompt(false, "%s\nVerification %s", pathstr,
|
2016-12-13 00:20:00 +01:00
|
|
|
(VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed");
|
|
|
|
}
|
|
|
|
return 0;
|
2017-01-02 17:37:08 +01:00
|
|
|
} else if (user_select == restore) { // -> restore SysNAND (A9LH preserving)
|
|
|
|
ShowPrompt(false, "%s\nNAND restore %s", pathstr,
|
|
|
|
(SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed");
|
2016-12-13 00:20:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-02-25 16:57:01 +01:00
|
|
|
u32 GodMode() {
|
2016-02-27 19:58:41 +01:00
|
|
|
static const u32 quick_stp = 20;
|
2016-02-25 16:57:01 +01:00
|
|
|
u32 exit_mode = GODMODE_EXIT_REBOOT;
|
2016-02-29 16:14:39 +01:00
|
|
|
|
2016-04-06 16:06:54 +02:00
|
|
|
// reserve 480kB for DirStruct, 64kB for PaneData, just to be safe
|
2016-11-21 20:04:46 +01:00
|
|
|
static DirStruct* current_dir = (DirStruct*) (DIR_BUFFER + 0x00000);
|
|
|
|
static DirStruct* clipboard = (DirStruct*) (DIR_BUFFER + 0x78000);
|
|
|
|
static PaneData* panedata = (PaneData*) (DIR_BUFFER + 0xF0000);
|
2016-04-06 16:06:54 +02:00
|
|
|
PaneData* pane = panedata;
|
2016-02-26 18:52:30 +01:00
|
|
|
char current_path[256] = { 0x00 };
|
2016-02-29 16:14:39 +01:00
|
|
|
|
2016-07-22 04:28:11 +02:00
|
|
|
int mark_next = -1;
|
2016-03-02 14:01:20 +01:00
|
|
|
u32 last_clipboard_size = 0;
|
2016-02-26 18:52:30 +01:00
|
|
|
u32 cursor = 0;
|
2016-03-15 22:19:30 +01:00
|
|
|
u32 scroll = 0;
|
2016-02-25 16:57:01 +01:00
|
|
|
|
2016-03-01 02:00:48 +01:00
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
2016-04-06 16:06:54 +02:00
|
|
|
if ((sizeof(DirStruct) > 0x78000) || (N_PANES * sizeof(PaneData) > 0x10000)) {
|
|
|
|
ShowPrompt(false, "Out of memory!"); // just to be safe
|
|
|
|
return exit_mode;
|
|
|
|
}
|
2016-03-16 15:45:24 +01:00
|
|
|
while (!InitSDCardFS()) {
|
2016-12-19 14:32:22 +01:00
|
|
|
const char* optionstr[] = { "Retry initialising", "Poweroff system", "Reboot system", "No SD mode (exp.)", "SD format menu" };
|
|
|
|
u32 user_select = ShowSelectPrompt(5, optionstr, "Initialising SD card failed!\nSelect action:" );
|
|
|
|
if (user_select == 2) return GODMODE_EXIT_POWEROFF;
|
|
|
|
else if (user_select == 3) return GODMODE_EXIT_REBOOT;
|
|
|
|
else if (user_select == 4) break;
|
|
|
|
else if (user_select == 5) SdFormatMenu();
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
2016-03-11 01:29:14 +01:00
|
|
|
}
|
2016-03-16 18:46:05 +01:00
|
|
|
InitEmuNandBase();
|
2016-03-11 01:29:14 +01:00
|
|
|
InitNandCrypto();
|
2016-04-05 15:20:48 +02:00
|
|
|
InitExtFS();
|
2016-02-25 16:57:01 +01:00
|
|
|
|
2016-04-06 12:52:23 +02:00
|
|
|
// could also check for a9lh via this: ((*(vu32*) 0x101401C0) == 0)
|
2016-03-22 19:24:21 +01:00
|
|
|
if ((GetUnitPlatform() == PLATFORM_N3DS) && !CheckSlot0x05Crypto()) {
|
2016-04-06 12:52:23 +02:00
|
|
|
if (!ShowPrompt(true, "Warning: slot0x05 crypto fail!\nCould not set up slot0x05keyY.\nContinue?")) {
|
2016-04-05 15:20:48 +02:00
|
|
|
DeinitExtFS();
|
2016-03-22 19:24:21 +01:00
|
|
|
DeinitSDCardFS();
|
|
|
|
return exit_mode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 16:14:39 +01:00
|
|
|
GetDirContents(current_dir, "");
|
|
|
|
clipboard->n_entries = 0;
|
2016-04-06 16:06:54 +02:00
|
|
|
memset(panedata, 0x00, 0x10000);
|
2016-03-11 01:29:14 +01:00
|
|
|
while (true) { // this is the main loop
|
2016-10-29 16:02:07 +02:00
|
|
|
int curr_drvtype = DriveType(current_path);
|
|
|
|
|
2016-04-07 14:29:37 +02:00
|
|
|
// basic sanity checking
|
|
|
|
if (!current_dir->n_entries) { // current dir is empty -> revert to root
|
|
|
|
*current_path = '\0';
|
2016-10-29 17:14:24 +02:00
|
|
|
DeinitExtFS(); // deinit and...
|
|
|
|
InitExtFS(); // reinitialize extended file system
|
2016-04-07 14:29:37 +02:00
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
cursor = 0;
|
|
|
|
if (!current_dir->n_entries) { // should not happen, if it does fail gracefully
|
|
|
|
ShowPrompt(false, "Invalid directory object");
|
|
|
|
return exit_mode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cursor >= current_dir->n_entries) // cursor beyond allowed range
|
|
|
|
cursor = current_dir->n_entries - 1;
|
2016-04-07 14:44:25 +02:00
|
|
|
DirEntry* curr_entry = &(current_dir->entry[cursor]);
|
2016-07-22 04:28:11 +02:00
|
|
|
if ((mark_next >= 0) && (curr_entry->type != T_DOTDOT)) {
|
|
|
|
curr_entry->marked = mark_next;
|
|
|
|
mark_next = -2;
|
|
|
|
}
|
2016-04-07 14:44:25 +02:00
|
|
|
DrawUserInterface(current_path, curr_entry, clipboard, N_PANES ? pane - panedata + 1 : 0);
|
2016-03-15 22:19:30 +01:00
|
|
|
DrawDirContents(current_dir, cursor, &scroll);
|
2016-02-26 18:52:30 +01:00
|
|
|
u32 pad_state = InputWait();
|
2016-04-02 19:07:46 +02:00
|
|
|
bool switched = (pad_state & BUTTON_R1);
|
2016-03-01 02:00:48 +01:00
|
|
|
|
2016-04-07 14:44:25 +02:00
|
|
|
// basic navigation commands
|
2016-07-21 00:29:48 +02:00
|
|
|
if ((pad_state & BUTTON_A) && (curr_entry->type != T_FILE) && (curr_entry->type != T_DOTDOT)) { // for dirs
|
2016-12-10 15:32:03 +01:00
|
|
|
if (switched && !(DriveType(curr_entry->path) & DRV_SEARCH)) { // search directory
|
2016-07-21 00:29:48 +02:00
|
|
|
char searchstr[256];
|
|
|
|
char namestr[20+1];
|
2016-07-22 04:55:09 +02:00
|
|
|
snprintf(searchstr, 256, "*");
|
2016-07-21 00:29:48 +02:00
|
|
|
TruncateString(namestr, curr_entry->name, 20, 8);
|
|
|
|
if (ShowStringPrompt(searchstr, 256, "Search %s?\nEnter search below.", namestr)) {
|
2016-07-22 18:32:44 +02:00
|
|
|
SetFSSearch(searchstr, curr_entry->path);
|
2016-07-21 00:29:48 +02:00
|
|
|
snprintf(current_path, 256, "Z:");
|
2016-07-22 18:32:44 +02:00
|
|
|
GetDirContents(current_dir, current_path);
|
2016-07-25 01:09:17 +02:00
|
|
|
if (current_dir->n_entries) ShowPrompt(false, "Found %lu results.", current_dir->n_entries - 1);
|
2016-07-22 18:32:44 +02:00
|
|
|
cursor = 1;
|
|
|
|
scroll = 0;
|
2016-07-21 00:29:48 +02:00
|
|
|
}
|
|
|
|
} else { // one level up
|
2016-07-26 18:50:58 +02:00
|
|
|
u32 user_select = 1;
|
2016-10-29 16:02:07 +02:00
|
|
|
if (curr_drvtype & DRV_SEARCH) { // special menu for search drive
|
2016-07-26 18:50:58 +02:00
|
|
|
const char* optionstr[2] = { "Open this folder", "Open containing folder" };
|
|
|
|
char pathstr[32 + 1];
|
|
|
|
TruncateString(pathstr, curr_entry->path, 32, 8);
|
|
|
|
user_select = ShowSelectPrompt(2, optionstr, pathstr);
|
|
|
|
}
|
|
|
|
if (user_select) {
|
|
|
|
strncpy(current_path, curr_entry->path, 256);
|
|
|
|
if (user_select == 2) {
|
|
|
|
char* last_slash = strrchr(current_path, '/');
|
|
|
|
if (last_slash) *last_slash = '\0';
|
|
|
|
}
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
if (*current_path && (current_dir->n_entries > 1)) {
|
|
|
|
cursor = 1;
|
|
|
|
scroll = 0;
|
|
|
|
} else cursor = 0;
|
|
|
|
}
|
2016-07-21 00:29:48 +02:00
|
|
|
}
|
2016-04-08 21:03:40 +02:00
|
|
|
} else if ((pad_state & BUTTON_A) && (curr_entry->type == T_FILE)) { // process a file
|
2016-12-13 00:20:00 +01:00
|
|
|
FileHandlerMenu(current_path, &cursor, &scroll, current_dir, clipboard); // processed externally
|
2016-04-07 14:44:25 +02:00
|
|
|
} else if (*current_path && ((pad_state & BUTTON_B) || // one level down
|
2016-07-26 18:51:16 +02:00
|
|
|
((pad_state & BUTTON_A) && (curr_entry->type == T_DOTDOT)))) {
|
|
|
|
if (switched) { // use R+B to return to root fast
|
|
|
|
*current_path = '\0';
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
cursor = scroll = 0;
|
|
|
|
} else {
|
|
|
|
char old_path[256];
|
|
|
|
char* last_slash = strrchr(current_path, '/');
|
|
|
|
strncpy(old_path, current_path, 256);
|
|
|
|
if (last_slash) *last_slash = '\0';
|
|
|
|
else *current_path = '\0';
|
|
|
|
GetDirContents(current_dir, current_path);
|
|
|
|
if (*old_path && current_dir->n_entries) {
|
|
|
|
for (cursor = current_dir->n_entries - 1;
|
|
|
|
(cursor > 0) && (strncmp(current_dir->entry[cursor].path, old_path, 256) != 0); cursor--);
|
|
|
|
if (*current_path && !cursor && (current_dir->n_entries > 1)) cursor = 1; // don't set it on the dotdot
|
|
|
|
scroll = 0;
|
|
|
|
}
|
2016-03-15 22:19:30 +01:00
|
|
|
}
|
2016-04-05 20:41:40 +02:00
|
|
|
} else if (switched && (pad_state & BUTTON_B)) { // unmount SD card
|
2016-04-05 15:20:48 +02:00
|
|
|
DeinitExtFS();
|
2016-12-19 14:32:22 +01:00
|
|
|
if (!CheckSDMountState()) {
|
|
|
|
while (!InitSDCardFS() &&
|
|
|
|
ShowPrompt(true, "Reinitialising SD card failed! Retry?"));
|
|
|
|
} else {
|
|
|
|
DeinitSDCardFS();
|
|
|
|
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) &
|
|
|
|
(DRV_SDCARD|DRV_ALIAS|DRV_EMUNAND|DRV_IMAGE)))
|
|
|
|
clipboard->n_entries = 0; // remove SD clipboard entries
|
2016-03-16 01:26:17 +01:00
|
|
|
}
|
2016-12-13 17:10:39 +01:00
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
2016-03-16 18:46:05 +01:00
|
|
|
InitEmuNandBase();
|
2016-04-05 15:20:48 +02:00
|
|
|
InitExtFS();
|
2016-03-16 01:26:17 +01:00
|
|
|
GetDirContents(current_dir, current_path);
|
2016-03-17 19:41:27 +01:00
|
|
|
if (cursor >= current_dir->n_entries) cursor = 0;
|
2016-04-06 16:06:54 +02:00
|
|
|
} else if ((pad_state & BUTTON_DOWN) && (cursor + 1 < current_dir->n_entries)) { // cursor down
|
2016-07-22 04:28:11 +02:00
|
|
|
if (pad_state & BUTTON_L1) mark_next = curr_entry->marked;
|
2016-02-26 18:52:30 +01:00
|
|
|
cursor++;
|
2016-04-06 16:06:54 +02:00
|
|
|
} else if ((pad_state & BUTTON_UP) && cursor) { // cursor up
|
2016-07-22 04:28:11 +02:00
|
|
|
if (pad_state & BUTTON_L1) mark_next = curr_entry->marked;
|
2016-02-26 18:52:30 +01:00
|
|
|
cursor--;
|
2016-04-06 16:06:54 +02:00
|
|
|
} else if (switched && (pad_state & (BUTTON_RIGHT|BUTTON_LEFT))) { // switch pane
|
|
|
|
memcpy(pane->path, current_path, 256); // store state in current pane
|
|
|
|
pane->cursor = cursor;
|
|
|
|
pane->scroll = scroll;
|
|
|
|
(pad_state & BUTTON_LEFT) ? pane-- : pane++; // switch to next
|
|
|
|
if (pane < panedata) pane += N_PANES;
|
|
|
|
else if (pane >= panedata + N_PANES) pane -= N_PANES;
|
|
|
|
memcpy(current_path, pane->path, 256); // get state from next pane
|
|
|
|
cursor = pane->cursor;
|
|
|
|
scroll = pane->scroll;
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-07-22 04:28:11 +02:00
|
|
|
} else if ((pad_state & BUTTON_RIGHT) && !(pad_state & BUTTON_L1)) { // cursor down (quick)
|
2016-02-27 19:58:41 +01:00
|
|
|
cursor += quick_stp;
|
2016-07-22 04:28:11 +02:00
|
|
|
} else if ((pad_state & BUTTON_LEFT) && !(pad_state & BUTTON_L1)) { // cursor up (quick)
|
2016-02-27 19:58:41 +01:00
|
|
|
cursor = (cursor >= quick_stp) ? cursor - quick_stp : 0;
|
2016-02-29 16:47:38 +01:00
|
|
|
} else if (pad_state & BUTTON_RIGHT) { // mark all entries
|
2016-03-02 19:36:20 +01:00
|
|
|
for (u32 c = 1; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 1;
|
2016-07-22 04:28:11 +02:00
|
|
|
mark_next = 1;
|
2016-02-29 16:47:38 +01:00
|
|
|
} else if (pad_state & BUTTON_LEFT) { // unmark all entries
|
2016-03-02 19:36:20 +01:00
|
|
|
for (u32 c = 1; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 0;
|
2016-07-22 04:28:11 +02:00
|
|
|
mark_next = 0;
|
2016-03-01 02:00:48 +01:00
|
|
|
} else if (switched && (pad_state & BUTTON_L1)) { // switched L -> screenshot
|
|
|
|
CreateScreenshot();
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
2016-07-22 04:28:11 +02:00
|
|
|
} else if (*current_path && (pad_state & BUTTON_L1) && (curr_entry->type != T_DOTDOT)) {
|
|
|
|
// unswitched L - mark/unmark single entry
|
|
|
|
if (mark_next < -1) mark_next = -1;
|
|
|
|
else curr_entry->marked ^= 0x1;
|
2016-03-02 14:01:20 +01:00
|
|
|
} else if (pad_state & BUTTON_SELECT) { // clear/restore clipboard
|
|
|
|
clipboard->n_entries = (clipboard->n_entries > 0) ? 0 : last_clipboard_size;
|
2016-02-26 18:52:30 +01:00
|
|
|
}
|
2016-03-01 02:00:48 +01:00
|
|
|
|
|
|
|
// highly specific commands
|
2016-05-29 14:45:12 +02:00
|
|
|
if (!*current_path) { // in the root folder...
|
2016-12-19 14:32:22 +01:00
|
|
|
if (switched && (pad_state & BUTTON_X)) { // unmount image
|
2016-12-13 16:00:14 +01:00
|
|
|
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
|
2016-11-02 15:24:22 +01:00
|
|
|
clipboard->n_entries = 0; // remove last mounted image clipboard entries
|
2016-12-19 14:32:22 +01:00
|
|
|
InitImgFS(NULL);
|
2016-12-13 16:00:14 +01:00
|
|
|
ClearScreenF(false, true, COLOR_STD_BG);
|
2016-04-05 20:41:40 +02:00
|
|
|
GetDirContents(current_dir, current_path);
|
2016-05-29 14:45:12 +02:00
|
|
|
} else if (switched && (pad_state & BUTTON_Y)) {
|
|
|
|
SetWritePermissions((GetWritePermissions() > PERM_BASE) ? PERM_BASE : PERM_ALL, false);
|
2016-03-01 02:00:48 +01:00
|
|
|
}
|
|
|
|
} else if (!switched) { // standard unswitched command set
|
2016-10-29 16:02:07 +02:00
|
|
|
if ((curr_drvtype & DRV_VIRTUAL) && (pad_state & BUTTON_X)) {
|
2016-03-21 18:53:09 +01:00
|
|
|
ShowPrompt(false, "Not allowed in virtual path");
|
|
|
|
} else if (pad_state & BUTTON_X) { // delete a file
|
2016-03-02 14:01:20 +01:00
|
|
|
u32 n_marked = 0;
|
2016-12-26 18:21:05 +01:00
|
|
|
if (curr_entry->marked) {
|
|
|
|
for (u32 c = 0; c < current_dir->n_entries; c++)
|
|
|
|
if (current_dir->entry[c].marked) n_marked++;
|
|
|
|
}
|
2016-03-02 14:01:20 +01:00
|
|
|
if (n_marked) {
|
|
|
|
if (ShowPrompt(true, "Delete %u path(s)?", n_marked)) {
|
|
|
|
u32 n_errors = 0;
|
2016-06-10 16:21:25 +02:00
|
|
|
ShowString("Deleting files, please wait...");
|
2016-03-02 14:01:20 +01:00
|
|
|
for (u32 c = 0; c < current_dir->n_entries; c++)
|
|
|
|
if (current_dir->entry[c].marked && !PathDelete(current_dir->entry[c].path))
|
|
|
|
n_errors++;
|
2016-06-10 16:21:25 +02:00
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
2016-03-02 14:01:20 +01:00
|
|
|
if (n_errors) ShowPrompt(false, "Failed deleting %u/%u path(s)", n_errors, n_marked);
|
|
|
|
}
|
2016-04-07 14:44:25 +02:00
|
|
|
} else if (curr_entry->type != T_DOTDOT) {
|
2016-03-02 17:22:44 +01:00
|
|
|
char namestr[36+1];
|
2016-06-15 16:30:37 +02:00
|
|
|
TruncateString(namestr, curr_entry->name, 28, 12);
|
2016-06-10 16:21:25 +02:00
|
|
|
if (ShowPrompt(true, "Delete \"%s\"?", namestr)) {
|
|
|
|
ShowString("Deleting %s\nPlease wait...", namestr);
|
|
|
|
if (!PathDelete(curr_entry->path))
|
|
|
|
ShowPrompt(false, "Failed deleting:\n%s", namestr);
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
|
|
|
}
|
2016-03-02 14:01:20 +01:00
|
|
|
}
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-03-01 02:00:48 +01:00
|
|
|
} else if ((pad_state & BUTTON_Y) && (clipboard->n_entries == 0)) { // fill clipboard
|
|
|
|
for (u32 c = 0; c < current_dir->n_entries; c++) {
|
|
|
|
if (current_dir->entry[c].marked) {
|
|
|
|
current_dir->entry[c].marked = 0;
|
2016-03-02 19:36:20 +01:00
|
|
|
DirEntryCpy(&(clipboard->entry[clipboard->n_entries]), &(current_dir->entry[c]));
|
2016-03-01 02:00:48 +01:00
|
|
|
clipboard->n_entries++;
|
|
|
|
}
|
|
|
|
}
|
2016-04-07 14:44:25 +02:00
|
|
|
if ((clipboard->n_entries == 0) && (curr_entry->type != T_DOTDOT)) {
|
|
|
|
DirEntryCpy(&(clipboard->entry[0]), curr_entry);
|
2016-03-01 02:00:48 +01:00
|
|
|
clipboard->n_entries = 1;
|
|
|
|
}
|
2016-03-02 19:36:20 +01:00
|
|
|
if (clipboard->n_entries)
|
|
|
|
last_clipboard_size = clipboard->n_entries;
|
2016-10-29 16:02:07 +02:00
|
|
|
} else if ((curr_drvtype & DRV_SEARCH) && (pad_state & BUTTON_Y)) {
|
2016-10-28 21:30:10 +02:00
|
|
|
ShowPrompt(false, "Not allowed in search drive");
|
2016-12-02 12:49:41 +01:00
|
|
|
} else if ((curr_drvtype & DRV_GAME) && (pad_state & BUTTON_Y)) {
|
|
|
|
ShowPrompt(false, "Not allowed in virtual game path");
|
2016-12-02 15:42:05 +01:00
|
|
|
} else if ((curr_drvtype & DRV_XORPAD) && (pad_state & BUTTON_Y)) {
|
|
|
|
ShowPrompt(false, "Not allowed in XORpad drive");
|
2017-01-13 14:20:42 +01:00
|
|
|
} else if ((curr_drvtype & DRV_CART) && (pad_state & BUTTON_Y)) {
|
|
|
|
ShowPrompt(false, "Not allowed in gamecart drive");
|
|
|
|
}else if (pad_state & BUTTON_Y) { // paste files
|
2016-04-29 02:21:36 +02:00
|
|
|
const char* optionstr[2] = { "Copy path(s)", "Move path(s)" };
|
2016-03-02 14:01:20 +01:00
|
|
|
char promptstr[64];
|
2016-07-27 00:19:12 +02:00
|
|
|
u32 flags = 0;
|
2016-04-29 02:21:36 +02:00
|
|
|
u32 user_select;
|
2016-03-02 14:01:20 +01:00
|
|
|
if (clipboard->n_entries == 1) {
|
|
|
|
char namestr[20+1];
|
|
|
|
TruncateString(namestr, clipboard->entry[0].name, 20, 12);
|
2016-06-18 15:26:11 +02:00
|
|
|
snprintf(promptstr, 64, "Paste \"%s\" here?", namestr);
|
|
|
|
} else snprintf(promptstr, 64, "Paste %lu paths here?", clipboard->n_entries);
|
2016-10-29 16:02:07 +02:00
|
|
|
user_select = ((DriveType(clipboard->entry[0].path) & curr_drvtype & DRV_STDFAT)) ?
|
2016-06-18 15:26:11 +02:00
|
|
|
ShowSelectPrompt(2, optionstr, promptstr) : (ShowPrompt(true, promptstr) ? 1 : 0);
|
|
|
|
if (user_select) {
|
2016-03-02 14:01:20 +01:00
|
|
|
for (u32 c = 0; c < clipboard->n_entries; c++) {
|
2016-04-29 02:21:36 +02:00
|
|
|
char namestr[36+1];
|
|
|
|
TruncateString(namestr, clipboard->entry[c].name, 36, 12);
|
2016-07-27 00:19:12 +02:00
|
|
|
flags &= ~ASK_ALL;
|
|
|
|
if (c < clipboard->n_entries - 1) flags |= ASK_ALL;
|
|
|
|
if ((user_select == 1) && !PathCopy(current_path, clipboard->entry[c].path, &flags)) {
|
2016-03-02 14:01:20 +01:00
|
|
|
if (c + 1 < clipboard->n_entries) {
|
2016-03-02 17:22:44 +01:00
|
|
|
if (!ShowPrompt(true, "Failed copying path:\n%s\nProcess remaining?", namestr)) break;
|
|
|
|
} else ShowPrompt(false, "Failed copying path:\n%s", namestr);
|
2016-07-27 00:19:12 +02:00
|
|
|
} else if ((user_select == 2) && !PathMove(current_path, clipboard->entry[c].path, &flags)) {
|
2016-04-29 02:21:36 +02:00
|
|
|
if (c + 1 < clipboard->n_entries) {
|
|
|
|
if (!ShowPrompt(true, "Failed moving path:\n%s\nProcess remaining?", namestr)) break;
|
|
|
|
} else ShowPrompt(false, "Failed moving path:\n%s", namestr);
|
2016-03-02 14:01:20 +01:00
|
|
|
}
|
2016-06-18 15:26:11 +02:00
|
|
|
}
|
|
|
|
clipboard->n_entries = 0;
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-03-02 14:01:20 +01:00
|
|
|
}
|
|
|
|
ClearScreenF(true, false, COLOR_STD_BG);
|
2016-03-01 02:00:48 +01:00
|
|
|
}
|
|
|
|
} else { // switched command set
|
2016-10-29 16:02:07 +02:00
|
|
|
if ((curr_drvtype & DRV_VIRTUAL) && (pad_state & (BUTTON_X|BUTTON_Y))) {
|
2016-03-21 18:53:09 +01:00
|
|
|
ShowPrompt(false, "Not allowed in virtual path");
|
2016-10-29 17:14:02 +02:00
|
|
|
} else if ((curr_drvtype & DRV_ALIAS) && (pad_state & (BUTTON_X))) {
|
2016-10-28 21:30:10 +02:00
|
|
|
ShowPrompt(false, "Not allowed in alias path");
|
2016-04-07 14:44:25 +02:00
|
|
|
} else if ((pad_state & BUTTON_X) && (curr_entry->type != T_DOTDOT)) { // rename a file
|
2016-03-14 23:38:43 +01:00
|
|
|
char newname[256];
|
|
|
|
char namestr[20+1];
|
2016-04-07 14:44:25 +02:00
|
|
|
TruncateString(namestr, curr_entry->name, 20, 12);
|
|
|
|
snprintf(newname, 255, curr_entry->name);
|
2016-06-13 23:51:41 +02:00
|
|
|
if (ShowStringPrompt(newname, 256, "Rename %s?\nEnter new name below.", namestr)) {
|
2016-04-07 14:44:25 +02:00
|
|
|
if (!PathRename(curr_entry->path, newname))
|
2016-03-14 23:38:43 +01:00
|
|
|
ShowPrompt(false, "Failed renaming path:\n%s", namestr);
|
2016-03-15 22:19:30 +01:00
|
|
|
else {
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-04-07 14:44:25 +02:00
|
|
|
for (cursor = (current_dir->n_entries) ? current_dir->n_entries - 1 : 0;
|
2016-03-15 22:19:30 +01:00
|
|
|
(cursor > 1) && (strncmp(current_dir->entry[cursor].name, newname, 256) != 0); cursor--);
|
|
|
|
}
|
2016-03-14 23:38:43 +01:00
|
|
|
}
|
|
|
|
} else if (pad_state & BUTTON_Y) { // create a folder
|
|
|
|
char dirname[256];
|
|
|
|
snprintf(dirname, 255, "newdir");
|
2016-06-13 23:51:41 +02:00
|
|
|
if (ShowStringPrompt(dirname, 256, "Create a new folder here?\nEnter name below.")) {
|
2016-03-14 23:58:25 +01:00
|
|
|
if (!DirCreate(current_path, dirname)) {
|
2016-03-15 22:19:30 +01:00
|
|
|
char namestr[36+1];
|
|
|
|
TruncateString(namestr, dirname, 36, 12);
|
|
|
|
ShowPrompt(false, "Failed creating folder:\n%s", namestr);
|
|
|
|
} else {
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-04-07 14:44:25 +02:00
|
|
|
for (cursor = (current_dir->n_entries) ? current_dir->n_entries - 1 : 0;
|
2016-03-15 22:19:30 +01:00
|
|
|
(cursor > 1) && (strncmp(current_dir->entry[cursor].name, dirname, 256) != 0); cursor--);
|
|
|
|
}
|
2016-03-14 23:58:25 +01:00
|
|
|
}
|
2016-03-14 23:38:43 +01:00
|
|
|
}
|
2016-02-29 16:47:38 +01:00
|
|
|
}
|
2016-03-01 02:00:48 +01:00
|
|
|
|
2016-02-26 18:52:30 +01:00
|
|
|
if (pad_state & BUTTON_START) {
|
2016-05-19 22:26:28 +02:00
|
|
|
exit_mode = (switched || (pad_state & BUTTON_LEFT)) ? GODMODE_EXIT_POWEROFF : GODMODE_EXIT_REBOOT;
|
2016-02-26 18:52:30 +01:00
|
|
|
break;
|
2016-10-17 23:45:22 +02:00
|
|
|
} else if (pad_state & BUTTON_POWER) {
|
|
|
|
exit_mode = GODMODE_EXIT_POWEROFF;
|
|
|
|
break;
|
2016-10-22 18:07:20 +02:00
|
|
|
} else if (pad_state & BUTTON_HOME) { // Home menu
|
2016-12-19 14:32:22 +01:00
|
|
|
const char* optionstr[] = { "Poweroff system", "Reboot system", "SD format menu" };
|
2016-11-01 16:14:18 +01:00
|
|
|
u32 user_select = ShowSelectPrompt(3, optionstr, "HOME button pressed.\nSelect action:" );
|
2016-10-22 18:07:20 +02:00
|
|
|
if (user_select == 1) {
|
|
|
|
exit_mode = GODMODE_EXIT_POWEROFF;
|
|
|
|
break;
|
|
|
|
} else if (user_select == 2) {
|
|
|
|
exit_mode = GODMODE_EXIT_REBOOT;
|
|
|
|
break;
|
2016-12-19 14:32:22 +01:00
|
|
|
} else if (user_select == 3) { // format SD card
|
|
|
|
bool sd_state = CheckSDMountState();
|
2016-11-02 15:24:22 +01:00
|
|
|
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & (DRV_SDCARD|DRV_ALIAS|DRV_EMUNAND|DRV_IMAGE)))
|
|
|
|
clipboard->n_entries = 0; // remove SD clipboard entries
|
2016-11-01 16:14:18 +01:00
|
|
|
DeinitExtFS();
|
|
|
|
DeinitSDCardFS();
|
2016-12-19 14:32:22 +01:00
|
|
|
if ((SdFormatMenu() == 0) || sd_state) {;
|
|
|
|
while (!InitSDCardFS() &&
|
|
|
|
ShowPrompt(true, "Reinitialising SD card failed! Retry?"));
|
2016-11-01 16:14:18 +01:00
|
|
|
}
|
2016-12-13 17:10:39 +01:00
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
2016-11-01 16:14:18 +01:00
|
|
|
InitEmuNandBase();
|
|
|
|
InitExtFS();
|
|
|
|
GetDirContents(current_dir, current_path);
|
2016-10-22 18:07:20 +02:00
|
|
|
}
|
2017-01-16 01:56:04 +01:00
|
|
|
} else if ((pad_state & (CART_INSERT|CART_EJECT)) && (curr_drvtype & DRV_CART)) {
|
|
|
|
GetDirContents(current_dir, current_path); // refresh cart dir contents
|
2016-10-17 23:45:22 +02:00
|
|
|
}
|
2016-02-26 18:52:30 +01:00
|
|
|
}
|
2016-02-25 16:57:01 +01:00
|
|
|
|
2016-04-05 15:20:48 +02:00
|
|
|
DeinitExtFS();
|
2016-03-21 23:18:33 +01:00
|
|
|
DeinitSDCardFS();
|
2016-02-25 16:57:01 +01:00
|
|
|
|
|
|
|
return exit_mode;
|
|
|
|
}
|