GodMode9/arm9/source/godmode.c
aspargas2 6b6fe4741d minor visual fix
previously, it was possible to select and deselect drives on the root menu by using the shortcut keys to select all files, but this would not functionally do anything
2020-06-30 19:22:18 +02:00

2698 lines
129 KiB
C

#include "godmode.h"
#include "paint9.h"
#include "memmap.h"
#include "support.h"
#include "ui.h"
#include "swkbd.h"
#include "hid.h"
#include "swkbd.h"
#include "touchcal.h"
#include "fs.h"
#include "utils.h"
#include "nand.h"
#include "virtual.h"
#include "vcart.h"
#include "game.h"
#include "disadiff.h"
#include "unittype.h"
#include "entrypoints.h"
#include "bootfirm.h"
#include "png.h"
#include "timer.h"
#include "rtc.h"
#include "power.h"
#include "vram0.h"
#include "i2c.h"
#include "pxi.h"
#ifndef N_PANES
#define N_PANES 3
#endif
#define COLOR_TOP_BAR (PERM_RED ? COLOR_RED : PERM_ORANGE ? COLOR_ORANGE : PERM_BLUE ? COLOR_BRIGHTBLUE : \
PERM_YELLOW ? COLOR_BRIGHTYELLOW : PERM_GREEN ? COLOR_GREEN : COLOR_WHITE)
#define BOOTPAUSE_KEY (BUTTON_R1|BUTTON_UP)
#define BOOTMENU_KEY (BUTTON_R1|BUTTON_LEFT)
#define BOOTFIRM_PATHS "0:/bootonce.firm", "0:/boot.firm", "1:/boot.firm"
#define BOOTFIRM_TEMPS 0x1 // bits mark paths as temporary
#ifdef SALTMODE // ShadowHand's own bootmenu key override
#undef BOOTMENU_KEY
#define BOOTMENU_KEY BUTTON_START
#endif
typedef struct {
char path[256];
u32 cursor;
u32 scroll;
} PaneData;
u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) {
char pathstr[32+1];
TruncateString(pathstr, bootpath, 32, 8);
size_t firm_size = FileGetSize(bootpath);
if (!firm_size) return 1;
if (firm_size > FIRM_MAX_SIZE) {
if (verbose) ShowPrompt(false, "%s\nFIRM too big, can't boot", pathstr); // unlikely
return 1;
}
if (verbose && !ShowPrompt(true, "%s (%dkB)\nWarning: Do not boot FIRMs\nfrom untrusted sources.\n \nBoot FIRM?",
pathstr, firm_size / 1024))
return 1;
void* firm = (void*) malloc(FIRM_MAX_SIZE);
if (!firm) return 1;
if ((FileGetData(bootpath, firm, firm_size, 0) != firm_size) ||
!IsBootableFirm(firm, firm_size)) {
if (verbose) ShowPrompt(false, "%s\nNot a bootable FIRM.", pathstr);
free(firm);
return 1;
}
// encrypted firm handling
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
if (!arm9s) return 1;
FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset);
if (verbose && (ValidateFirmA9LHeader(a9l) == 0) &&
ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) &&
(DecryptFirmFull(firm, firm_size) != 0)) {
free(firm);
return 1;
}
// unsupported location handling
char fixpath[256] = { 0 };
if (verbose && (*bootpath != '0') && (*bootpath != '1')) {
const char* optionstr[2] = { "Make a copy at " OUTPUT_PATH "/temp.firm", "Try to boot anyways" };
u32 user_select = ShowSelectPrompt(2, optionstr, "%s\nWarning: Trying to boot from an\nunsupported location.", pathstr);
if (user_select == 1) {
FileSetData(OUTPUT_PATH "/temp.firm", firm, firm_size, 0, true);
bootpath = OUTPUT_PATH "/temp.firm";
} else if (!user_select) bootpath = "";
}
// fix the boot path ("sdmc"/"nand" for Luma et al, hacky af)
if ((*bootpath == '0') || (*bootpath == '1'))
snprintf(fixpath, 256, "%s%s", (*bootpath == '0') ? "sdmc" : "nand", bootpath + 1);
else strncpy(fixpath, bootpath, 256);
fixpath[255] = '\0';
// boot the FIRM (if we got a proper fixpath)
if (*fixpath) {
if (delete) PathDelete(bootpath);
DeinitExtFS();
DeinitSDCardFS();
PXI_DoCMD(PXI_SET_VMODE, (u32[]){1}, 1);
PXI_DoCMD(PXI_LEGACY_MODE, NULL, 0);
BootFirm((FirmHeader*) firm, fixpath);
while(1);
}
// a return was not intended
free(firm);
return 1;
}
u32 SplashInit(const char* modestr) {
u64 splash_size;
u8* splash = FindVTarFileInfo(VRAM0_SPLASH_PNG, &splash_size);
const char* namestr = FLAVOR " " VERSION;
const char* loadstr = "booting...";
const u32 pos_xb = 10;
const u32 pos_yb = 10;
const u32 pos_xu = SCREEN_WIDTH_BOT - 10 - GetDrawStringWidth(loadstr);
const u32 pos_yu = SCREEN_HEIGHT - 10 - GetDrawStringHeight(loadstr);
ClearScreenF(true, true, COLOR_STD_BG);
if (splash) {
u32 splash_width, splash_height;
u16* bitmap = PNG_Decompress(splash, splash_size, &splash_width, &splash_height);
if (bitmap) {
DrawBitmap(TOP_SCREEN, -1, -1, splash_width, splash_height, bitmap);
free(bitmap);
}
} else {
DrawStringF(TOP_SCREEN, 10, 10, COLOR_STD_FONT, COLOR_TRANSPARENT, "(" VRAM0_SPLASH_PNG " not found)");
}
if (modestr) DrawStringF(TOP_SCREEN, SCREEN_WIDTH_TOP - 10 - GetDrawStringWidth(modestr),
SCREEN_HEIGHT - 10 - GetDrawStringHeight(modestr), COLOR_STD_FONT, COLOR_TRANSPARENT, modestr);
DrawStringF(BOT_SCREEN, pos_xb, pos_yb, COLOR_STD_FONT, COLOR_STD_BG, "%s\n%*.*s\n%s\n \n \n%s\n%s\n \n%s\n%s",
namestr, strnlen(namestr, 64), strnlen(namestr, 64),
"------------------------------", "https://github.com/d0k3/GodMode9",
"Releases:", "https://github.com/d0k3/GodMode9/releases/", // this won't fit with a 8px width font
"Hourlies:", "https://d0k3.secretalgorithm.com/");
DrawStringF(BOT_SCREEN, pos_xu, pos_yu, COLOR_STD_FONT, COLOR_STD_BG, loadstr);
DrawStringF(BOT_SCREEN, pos_xb, pos_yu, COLOR_STD_FONT, COLOR_STD_BG, "built: " DBUILTL);
return 0;
}
#ifndef SCRIPT_RUNNER
static DirStruct* current_dir = NULL;
static DirStruct* clipboard = NULL;
static PaneData* panedata = NULL;
void GetTimeString(char* timestr, bool forced_update, bool full_year) {
static DsTime dstime;
static u64 timer = (u64) -1; // this ensures we don't check the time too often
if (forced_update || (timer == (u64) -1) || (timer_sec(timer) > 30)) {
get_dstime(&dstime);
timer = timer_start();
}
if (timestr) snprintf(timestr, 31, "%s%02lX-%02lX-%02lX %02lX:%02lX", full_year ? "20" : "",
(u32) dstime.bcd_Y, (u32) dstime.bcd_M, (u32) dstime.bcd_D, (u32) dstime.bcd_h, (u32) dstime.bcd_m);
}
void CheckBattery(u32* battery, bool* is_charging) {
if (battery) {
static u32 battery_l = 0;
static u64 timer_b = (u64) -1; // this ensures we don't check too often
if ((timer_b == (u64) -1) || (timer_sec(timer_b) >= 120)) {
battery_l = GetBatteryPercent();
timer_b = timer_start();
}
*battery = battery_l;
}
if (is_charging) {
static bool is_charging_l = false;
static u64 timer_c = (u64) -1;
if ((timer_c == (u64) -1) || (timer_sec(timer_c) >= 1)) {
is_charging_l = IsCharging();
timer_c = timer_start();
}
*is_charging = is_charging_l;
}
}
void DrawBatteryBitmap(u16* screen, u32 b_x, u32 b_y, u32 width, u32 height) {
const u16 color_outline = COLOR_BLACK;
const u16 color_inline = COLOR_LIGHTGREY;
const u16 color_inside = COLOR_LIGHTERGREY;
const u16 color_bg = COLOR_TRANSPARENT;
if ((width < 8) || (height < 6)) return;
u32 battery;
bool is_charging;
CheckBattery(&battery, &is_charging);
u16 color_battery = (is_charging) ? COLOR_BATTERY_CHARGING :
(battery > 70) ? COLOR_BATTERY_FULL : (battery > 30) ? COLOR_BATTERY_MEDIUM : COLOR_BATTERY_LOW;
u32 nub_size = (height < 12) ? 1 : 2;
u32 width_inside = width - 4 - nub_size;
u32 width_battery = (battery >= 100) ? width_inside : ((battery * width_inside) + 50) / 100;
for (u32 y = 0; y < height; y++) {
const u32 mirror_y = (y >= (height+1) / 2) ? height - 1 - y : y;
for (u32 x = 0; x < width; x++) {
const u32 rev_x = width - x - 1;
u16 color = 0;
if (mirror_y == 0) color = (rev_x >= nub_size) ? color_outline : color_bg;
else if (mirror_y == 1) color = ((x == 0) || (rev_x == nub_size)) ? color_outline : (rev_x < nub_size) ? color_bg : color_inline;
else if (mirror_y == 2) color = ((x == 0) || (rev_x <= nub_size)) ? color_outline : ((x == 1) || (rev_x == (nub_size+1))) ? color_inline : color_inside;
else color = ((x == 0) || (rev_x == 0)) ? color_outline : ((x == 1) || (rev_x <= (nub_size+1))) ? color_inline : color_inside;
if ((color == color_inside) && (x < (2 + width_battery))) color = color_battery;
if (color != color_bg) DrawPixel(screen, b_x + x, b_y + y, color);
}
}
}
void DrawTopBar(const char* curr_path) {
const u32 bartxt_start = (FONT_HEIGHT_EXT >= 10) ? 1 : (FONT_HEIGHT_EXT >= 7) ? 2 : 3;
const u32 bartxt_x = 2;
const u32 len_path = SCREEN_WIDTH_TOP - 120;
char tempstr[64];
// top bar - current path
DrawRectangle(TOP_SCREEN, 0, 0, SCREEN_WIDTH_TOP, 12, COLOR_TOP_BAR);
if (*curr_path) TruncateString(tempstr, curr_path, min(63, len_path / FONT_WIDTH_EXT), 8);
else snprintf(tempstr, 16, "[root]");
DrawStringF(TOP_SCREEN, bartxt_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%s", tempstr);
bool show_time = true;
#ifdef SHOW_FREE
if (*curr_path) { // free & total storage
const u32 bartxt_rx = SCREEN_WIDTH_TOP - (19*FONT_WIDTH_EXT) - bartxt_x;
char bytestr0[32];
char bytestr1[32];
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", "LOADING...");
FormatBytes(bytestr0, GetFreeSpace(curr_path));
FormatBytes(bytestr1, GetTotalSpace(curr_path));
snprintf(tempstr, 64, "%s/%s", bytestr0, bytestr1);
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", tempstr);
show_time = false;
}
#elif defined MONITOR_HEAP
if (true) { // allocated mem
const u32 bartxt_rx = SCREEN_WIDTH_TOP - (9*FONT_WIDTH_EXT) - bartxt_x;
char bytestr[32];
FormatBytes(bytestr, mem_allocated());
DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%9.9s", bytestr);
show_time = false;
}
#endif
if (show_time) { // clock & battery
const u32 battery_width = 16;
const u32 battery_height = 9;
const u32 battery_x = SCREEN_WIDTH_TOP - battery_width - bartxt_x;
const u32 battery_y = (12 - battery_height) / 2;
const u32 clock_x = battery_x - (15*FONT_WIDTH_EXT);
char timestr[32];
GetTimeString(timestr, false, false);
DrawStringF(TOP_SCREEN, clock_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%14.14s", timestr);
DrawBatteryBitmap(TOP_SCREEN, battery_x, battery_y, battery_width, battery_height);
}
}
void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pane) {
const u32 n_cb_show = 8;
const u32 info_start = (MAIN_SCREEN == TOP_SCREEN) ? 18 : 2; // leave space for the topbar when required
const u32 instr_x = (SCREEN_WIDTH_MAIN - (34*FONT_WIDTH_EXT)) / 2;
const u32 len_info = (SCREEN_WIDTH_MAIN - ((SCREEN_WIDTH_MAIN >= 400) ? 80 : 20)) / 2;
const u32 str_len_info = min(63, len_info / FONT_WIDTH_EXT);
char tempstr[64];
static u32 state_prev = 0xFFFFFFFF;
u32 state_curr =
((*curr_path) ? (1<<0) : 0) |
((clipboard->n_entries) ? (1<<1) : 0) |
((CheckSDMountState()) ? (1<<2) : 0) |
((GetMountState()) ? (1<<3) : 0) |
((GetWritePermissions() > PERM_BASE) ? (1<<4) : 0) |
(curr_pane<<5);
if (state_prev != state_curr) {
ClearScreenF(true, false, COLOR_STD_BG);
state_prev = state_curr;
}
// left top - current file info
if (curr_pane) snprintf(tempstr, 63, "PANE #%lu", curr_pane);
else snprintf(tempstr, 63, "CURRENT");
DrawStringF(MAIN_SCREEN, 2, info_start, COLOR_STD_FONT, COLOR_STD_BG, "[%s]", tempstr);
// file / entry name
ResizeString(tempstr, curr_entry->name, str_len_info, 8, false);
u32 color_current = COLOR_ENTRY(curr_entry);
DrawStringF(MAIN_SCREEN, 4, info_start + 12, color_current, COLOR_STD_BG, "%s", tempstr);
// size (in Byte) or type desc
if (curr_entry->type == T_DIR) {
ResizeString(tempstr, "(dir)", str_len_info, 8, false);
} else if (curr_entry->type == T_DOTDOT) {
snprintf(tempstr, 21, "%20s", "");
} else if (curr_entry->type == T_ROOT) {
int drvtype = DriveType(curr_entry->path);
char drvstr[32];
snprintf(drvstr, 31, "(%s%s)",
((drvtype & DRV_SDCARD) ? "SD" : (drvtype & DRV_RAMDRIVE) ? "RAMdrive" : (drvtype & DRV_GAME) ? "Game" :
(drvtype & DRV_SYSNAND) ? "SysNAND" : (drvtype & DRV_EMUNAND) ? "EmuNAND" : (drvtype & DRV_IMAGE) ? "Image" :
(drvtype & DRV_XORPAD) ? "XORpad" : (drvtype & DRV_MEMORY) ? "Memory" : (drvtype & DRV_ALIAS) ? "Alias" :
(drvtype & DRV_CART) ? "Gamecart" : (drvtype & DRV_VRAM) ? "VRAM" : (drvtype & DRV_SEARCH) ? "Search" : ""),
((drvtype & DRV_FAT) ? " FAT" : (drvtype & DRV_VIRTUAL) ? " Virtual" : ""));
ResizeString(tempstr, drvstr, str_len_info, 8, false);
} else {
char numstr[32];
char bytestr[32];
FormatNumber(numstr, curr_entry->size);
snprintf(bytestr, 31, "%s Byte", numstr);
ResizeString(tempstr, bytestr, str_len_info, 8, false);
}
DrawStringF(MAIN_SCREEN, 4, info_start + 12 + 10, color_current, COLOR_STD_BG, tempstr);
// path of file (if in search results)
if ((DriveType(curr_path) & DRV_SEARCH) && strrchr(curr_entry->path, '/')) {
char dirstr[256];
strncpy(dirstr, curr_entry->path, 256);
*(strrchr(dirstr, '/')+1) = '\0';
ResizeString(tempstr, dirstr, str_len_info, 8, false);
DrawStringF(MAIN_SCREEN, 4, info_start + 12 + 10 + 10, color_current, COLOR_STD_BG, "%s", tempstr);
} else {
ResizeString(tempstr, "", str_len_info, 8, false);
DrawStringF(MAIN_SCREEN, 4, info_start + 12 + 10 + 10, color_current, COLOR_STD_BG, "%s", tempstr);
}
// right top - clipboard
DrawStringF(MAIN_SCREEN, SCREEN_WIDTH_MAIN - len_info, info_start, COLOR_STD_FONT, COLOR_STD_BG, "%*s",
len_info / FONT_WIDTH_EXT, (clipboard->n_entries) ? "[CLIPBOARD]" : "");
for (u32 c = 0; c < n_cb_show; c++) {
u32 color_cb = COLOR_ENTRY(&(clipboard->entry[c]));
ResizeString(tempstr, (clipboard->n_entries > c) ? clipboard->entry[c].name : "", str_len_info, 8, true);
DrawStringF(MAIN_SCREEN, SCREEN_WIDTH_MAIN - len_info - 4, info_start + 12 + (c*10), color_cb, COLOR_STD_BG, "%s", tempstr);
}
*tempstr = '\0';
if (clipboard->n_entries > n_cb_show) snprintf(tempstr, 60, "+ %lu more", clipboard->n_entries - n_cb_show);
DrawStringF(MAIN_SCREEN, SCREEN_WIDTH_MAIN - len_info - 4, info_start + 12 + (n_cb_show*10), COLOR_DARKGREY, COLOR_STD_BG,
"%*s", len_info / FONT_WIDTH_EXT, tempstr);
// bottom: instruction block
char instr[512];
snprintf(instr, 512, "%s\n%s%s%s%s%s%s%s%s",
FLAVOR " " VERSION, // generic start part
(*curr_path) ? ((clipboard->n_entries == 0) ? "L - MARK files (use with \x18\x19\x1A\x1B)\nX - DELETE / [+R] RENAME file(s)\nY - COPY files / [+R] CREATE entry\n" :
"L - MARK files (use with \x18\x19\x1A\x1B)\nX - DELETE / [+R] RENAME file(s)\nY - PASTE files / [+R] CREATE entry\n") :
((GetWritePermissions() > PERM_BASE) ? "R+Y - Relock write permissions\n" : ""),
(*curr_path) ? "" : (GetMountState()) ? "R+X - Unmount image\n" : "",
(*curr_path) ? "" : (CheckSDMountState()) ? "R+B - Unmount SD card\n" : "R+B - Remount SD card\n",
(*curr_path) ? "R+A - Directory options\n" : "R+A - Drive options\n",
"R+L - Make a Screenshot\n",
"R+\x1B\x1A - Switch to prev/next pane\n",
(clipboard->n_entries) ? "SELECT - Clear Clipboard\n" : "SELECT - Restore Clipboard\n", // only if clipboard is full
"START - Reboot / [+R] Poweroff\nHOME button for HOME menu"); // generic end part
DrawStringF(MAIN_SCREEN, instr_x, SCREEN_HEIGHT - 4 - GetDrawStringHeight(instr), COLOR_STD_FONT, COLOR_STD_BG, instr);
}
void DrawDirContents(DirStruct* contents, u32 cursor, u32* scroll) {
const int str_width = (SCREEN_WIDTH_ALT-3) / FONT_WIDTH_EXT;
const u32 stp_y = min(12, FONT_HEIGHT_EXT + 4);
const u32 start_y = (MAIN_SCREEN == TOP_SCREEN) ? 0 : 12;
const u32 pos_x = 0;
const u32 lines = (SCREEN_HEIGHT-(start_y+2)+(stp_y-1)) / stp_y;
u32 pos_y = start_y + 2;
if (*scroll > cursor) *scroll = cursor;
else if (*scroll + lines <= cursor) *scroll = cursor - lines + 1;
if (*scroll + lines > contents->n_entries)
*scroll = (contents->n_entries > lines) ? contents->n_entries - lines : 0;
for (u32 i = 0; pos_y < SCREEN_HEIGHT; i++) {
char tempstr[str_width + 1];
u32 offset_i = *scroll + i;
u32 color_font = COLOR_WHITE;
if (offset_i < contents->n_entries) {
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,
(curr_entry->type == T_DIR) ? "(dir)" : (curr_entry->type == T_DOTDOT) ? "(..)" : bytestr);
} else snprintf(tempstr, str_width + 1, "%-*.*s", str_width, str_width, "");
DrawStringF(ALT_SCREEN, pos_x, pos_y, color_font, COLOR_STD_BG, "%s", tempstr);
pos_y += stp_y;
}
const u32 flist_height = (SCREEN_HEIGHT - start_y);
const u32 bar_width = 2;
if (contents->n_entries > lines) { // draw position bar at the right
const u32 bar_height_min = 32;
u32 bar_height = (lines * flist_height) / contents->n_entries;
if (bar_height < bar_height_min) bar_height = bar_height_min;
const u32 bar_pos = ((u64) *scroll * (flist_height - bar_height)) / (contents->n_entries - lines) + start_y;
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, start_y, bar_width, (bar_pos - start_y), COLOR_STD_BG);
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, bar_pos + bar_height, bar_width, SCREEN_HEIGHT - (bar_pos + bar_height), COLOR_STD_BG);
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, bar_pos, bar_width, bar_height, COLOR_SIDE_BAR);
} else DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, start_y, bar_width, flist_height, COLOR_STD_BG);
}
u32 SdFormatMenu(const char* slabel) {
const u32 cluster_size_table[5] = { 0x0, 0x0, 0x4000, 0x8000, 0x10000 };
const char* option_emunand_size[7] = { "No EmuNAND", "RedNAND size (min)", "GW EmuNAND size (full)",
"MultiNAND size (2x)", "MultiNAND size (3x)", "MultiNAND size (4x)", "User input..." };
const char* option_cluster_size[4] = { "Auto", "16KB Clusters", "32KB Clusters", "64KB Clusters" };
u32 sysnand_min_size_sectors = GetNandMinSizeSectors(NAND_SYSNAND);
u64 sysnand_min_size_mb = ((sysnand_min_size_sectors * 0x200) + 0xFFFFF) / 0x100000;
u64 sysnand_multi_size_mb = (align(sysnand_min_size_sectors + 1, 0x2000) * 0x200) / 0x100000;
u64 sysnand_size_mb = (((u64)GetNandSizeSectors(NAND_SYSNAND) * 0x200) + 0xFFFFF) / 0x100000;
char label[DRV_LABEL_LEN + 4];
u32 cluster_size = 0;
u64 sdcard_size_mb = 0;
u64 emunand_size_mb = (u64) -1;
u32 user_select;
// check actual SD card size
sdcard_size_mb = GetSDCardSize() / 0x100000;
if (!sdcard_size_mb) {
ShowPrompt(false, "Error: SD card not detected.");
return 1;
}
user_select = ShowSelectPrompt(7, option_emunand_size, "Format SD card (%lluMB)?\nChoose EmuNAND size:", sdcard_size_mb);
if (user_select && (user_select < 4)) {
emunand_size_mb = (user_select == 2) ? sysnand_min_size_mb : (user_select == 3) ? sysnand_size_mb : 0;
} else if ((user_select >= 4) && (user_select <= 6)) {
u32 n = (user_select - 2);
emunand_size_mb = n * sysnand_multi_size_mb;
} else if (user_select == 7) do {
emunand_size_mb = ShowNumberPrompt(sysnand_min_size_mb, "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;
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];
snprintf(label, DRV_LABEL_LEN + 4, "0:%s", (slabel && *slabel) ? slabel : "GM9SD");
if (!ShowKeyboardOrPrompt(label + 2, 11 + 1, "Format SD card (%lluMB)?\nEnter label:", sdcard_size_mb))
return 1;
if (!FormatSDCard(emunand_size_mb, cluster_size, label)) {
ShowPrompt(false, "Format SD: failed!");
return 1;
}
if (emunand_size_mb >= sysnand_min_size_mb) {
u32 emunand_offset = 1;
u32 n_emunands = 1;
if (emunand_size_mb >= 2 * sysnand_size_mb) {
const char* option_emunand_type[4] = { "RedNAND type (multi)", "RedNAND type (single)", "GW EmuNAND type", "Don't set up" };
user_select = ShowSelectPrompt(4, option_emunand_type, "Choose EmuNAND type to set up:");
if (user_select > 3) return 0;
emunand_offset = (user_select == 3) ? 0 : 1;
if (user_select == 1) n_emunands = 4;
} else if (emunand_size_mb >= sysnand_size_mb) {
const char* option_emunand_type[3] = { "RedNAND type", "GW EmuNAND type", "Don't set up" };
user_select = ShowSelectPrompt(3, option_emunand_type, "Choose EmuNAND type to set up:");
if (user_select > 2) return 0;
emunand_offset = (user_select == 2) ? 0 : 1; // 0 -> GW EmuNAND
} else user_select = ShowPrompt(true, "Clone SysNAND to RedNAND?") ? 1 : 0;
if (!user_select) return 0;
u8 ncsd[0x200];
u32 flags = OVERRIDE_PERM;
InitSDCardFS(); // this has to be initialized for EmuNAND to work
for (u32 i = 0; i < n_emunands; i++) {
if ((i * sysnand_multi_size_mb) + sysnand_min_size_mb > emunand_size_mb) break;
SetEmuNandBase((i * sysnand_multi_size_mb * 0x100000 / 0x200) + emunand_offset);
if ((ReadNandSectors(ncsd, 0, 1, 0xFF, NAND_SYSNAND) != 0) ||
(WriteNandSectors(ncsd, 0, 1, 0xFF, NAND_EMUNAND) != 0) ||
(!PathCopy("E:", "S:/nand_minsize.bin", &flags))) {
ShowPrompt(false, "Cloning SysNAND to EmuNAND: failed!");
break;
}
}
DeinitSDCardFS();
}
return 0;
}
u32 FileGraphicsViewer(const char* path) {
const u32 max_size = SCREEN_SIZE(ALT_SCREEN);
u64 filetype = IdentifyFileType(path);
u16* bitmap = NULL;
u8* input = (u8*)malloc(max_size);
u32 w = 0;
u32 h = 0;
u32 ret = 1;
if (!input)
return ret;
u32 input_size = FileGetData(path, input, max_size, 0);
if (input_size && (input_size < max_size)) {
if (filetype & GFX_PNG) {
bitmap = PNG_Decompress(input, input_size, &w, &h);
if (bitmap != NULL) ret = 0;
}
}
if ((ret == 0) && w && h && (w <= SCREEN_WIDTH(ALT_SCREEN)) && (h <= SCREEN_HEIGHT)) {
ClearScreenF(true, true, COLOR_STD_BG);
DrawBitmap(ALT_SCREEN, -1, -1, w, h, bitmap);
ShowString("Press <A> to continue");
while(!(InputWait(0) & (BUTTON_A | BUTTON_B)));
ClearScreenF(true, true, COLOR_STD_BG);
} else ret = 1;
free(bitmap);
free(input);
return ret;
}
u32 FileHexViewer(const char* path) {
const u32 max_data = (SCREEN_HEIGHT / FONT_HEIGHT_EXT) * 16 * ((FONT_WIDTH_EXT > 4) ? 1 : 2);
static u32 mode = 0;
u8* data = NULL;
u8* bottom_cpy = (u8*) malloc(SCREEN_SIZE_BOT); // a copy of the bottom screen framebuffer
u32 fsize = FileGetSize(path);
bool dual_screen = 0;
int x_off = 0, x_hex = 0, x_ascii = 0;
u32 vpad = 0, hlpad = 0, hrpad = 0;
u32 rows = 0, cols = 0;
u32 total_shown = 0;
u32 total_data = 0;
u32 last_mode = 0xFF;
u32 last_offset = (u32) -1;
u32 offset = 0;
u8 found_data[64 + 1] = { 0 };
u32 found_offset = (u32) -1;
u32 found_size = 0;
static const u32 edit_bsize = 0x4000; // should be multiple of 0x200 * 2
bool edit_mode = false;
u8* buffer = (u8*) malloc(edit_bsize);
u8* buffer_cpy = (u8*) malloc(edit_bsize);
u32 edit_start = 0;
int cursor = 0;
if (!bottom_cpy || !buffer || !buffer_cpy) {
if (bottom_cpy) free(bottom_cpy);
if (buffer) free(buffer);
if (buffer_cpy) free(buffer_cpy);
return 1;
}
static bool show_instr = true;
static const char* instr = "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";
if (show_instr) { // show one time instructions
ShowPrompt(false, instr);
show_instr = false;
}
if (MAIN_SCREEN != TOP_SCREEN) ShowString(instr);
memcpy(bottom_cpy, BOT_SCREEN, SCREEN_SIZE_BOT);
data = buffer;
while (true) {
if (mode != last_mode) {
if (FONT_WIDTH_EXT <= 5) {
mode = 0;
vpad = 1;
hlpad = hrpad = 2;
cols = 16;
x_off = (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) -
(((cols - 8) / 2) * FONT_WIDTH_EXT);
dual_screen = true;
} else if (FONT_WIDTH_EXT <= 6) {
if (mode == 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;
} else {
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;
}
} else switch (mode) { // display mode
case 1:
vpad = hlpad = hrpad = 1;
cols = 12;
x_off = 0;
x_ascii = SCREEN_WIDTH_TOP - (FONT_WIDTH_EXT * cols);
x_hex = ((SCREEN_WIDTH_TOP - ((hlpad + (2*FONT_WIDTH_EXT) + hrpad) * cols) -
((cols - 8) * FONT_WIDTH_EXT)) / 2);
dual_screen = false;
break;
case 2:
vpad = 1;
hlpad = 0;
hrpad = 1 + 8 - FONT_WIDTH_EXT;
cols = 16;
x_off = -1;
x_ascii = SCREEN_WIDTH_TOP - (FONT_WIDTH_EXT * cols);
x_hex = 0;
dual_screen = false;
break;
case 3:
vpad = hlpad = hrpad = 1;
cols = 16;
x_off = ((SCREEN_WIDTH_TOP - ((hlpad + (2*FONT_WIDTH_EXT) + hrpad) * cols)
- 12 - (8*FONT_WIDTH_EXT)) / 2);
x_ascii = -1;
x_hex = x_off + (8*FONT_WIDTH_EXT) + 12;
dual_screen = false;
break;
default:
mode = 0;
vpad = hlpad = hrpad = 2;
cols = 8;
x_off = (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;
}
rows = (dual_screen ? 2 : 1) * SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*vpad));
total_shown = rows * cols;
last_mode = mode;
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
if (dual_screen) ClearScreen(BOT_SCREEN, COLOR_STD_BG);
else memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
}
// fix offset (if required)
if (offset % cols) offset -= (offset % cols); // fix offset (align to cols)
if (offset + total_shown - cols > fsize) // if offset too big
offset = (total_shown > fsize) ? 0 : (fsize + cols - total_shown - (fsize % cols));
// get data, using max data size (if new offset)
if (offset != last_offset) {
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 = buffer + (offset - edit_start);
}
last_offset = offset;
}
// display data on screen
for (u32 row = 0; row < rows; row++) {
char ascii[16 + 1] = { 0 };
u32 y = row * (FONT_HEIGHT_EXT + (2*vpad)) + vpad;
u32 curr_pos = row * cols;
u32 cutoff = (curr_pos >= total_data) ? 0 : (total_data >= curr_pos + cols) ? cols : total_data - curr_pos;
u16* screen = TOP_SCREEN;
u32 x0 = 0;
// marked offsets handling
s32 marked0 = 0, marked1 = 0;
if ((found_size > 0) &&
(found_offset + found_size > offset + curr_pos) &&
(found_offset < offset + curr_pos + cols)) {
marked0 = (s32) found_offset - (offset + curr_pos);
marked1 = marked0 + found_size;
if (marked0 < 0) marked0 = 0;
if (marked1 > cols) marked1 = cols;
}
// switch to bottom screen
if (y >= SCREEN_HEIGHT) {
y -= SCREEN_HEIGHT;
screen = BOT_SCREEN;
x0 = 40;
}
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
if (x_off >= 0) DrawStringF(screen, x_off - x0, y, cutoff ? COLOR_HVOFFS : COLOR_HVOFFSI,
COLOR_STD_BG, "%08X", (unsigned int) offset + curr_pos);
if (x_ascii >= 0) {
DrawString(screen, ascii, x_ascii - x0, y, COLOR_HVASCII, COLOR_STD_BG, false);
for (u32 i = marked0; i < marked1; i++)
DrawCharacter(screen, ascii[i % cols], x_ascii - x0 + (FONT_WIDTH_EXT * i), y, COLOR_MARKED, COLOR_STD_BG);
if (edit_mode && ((u32) cursor / cols == row)) DrawCharacter(screen, ascii[cursor % cols],
x_ascii - x0 + FONT_WIDTH_EXT * (cursor % cols), y, COLOR_RED, COLOR_STD_BG);
}
// draw HEX values
for (u32 col = 0; (col < cols) && (x_hex >= 0); col++) {
u32 x = (x_hex + hlpad) + (((2*FONT_WIDTH_EXT) + hrpad + hlpad) * col) - x0;
u32 hex_color = (edit_mode && ((u32) cursor == curr_pos + col)) ? COLOR_RED :
((col >= marked0) && (col < marked1)) ? COLOR_MARKED : COLOR_HVHEX(col);
if (col < cutoff)
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, " ");
}
}
// handle user input
u32 pad_state = InputWait(0);
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;
else if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_Y)) mode++;
else if ((pad_state & BUTTON_A) && total_data) edit_mode = true;
else if (pad_state & (BUTTON_B|BUTTON_START)) break;
else if (found_size && (pad_state & BUTTON_R1) && (pad_state & BUTTON_X)) {
found_offset = FileFindData(path, found_data, found_size, found_offset + 1);
if (found_offset == (u32) -1) {
ShowPrompt(false, "Not found!");
found_size = 0;
} else offset = found_offset;
if (MAIN_SCREEN == TOP_SCREEN) ClearScreen(TOP_SCREEN, COLOR_STD_BG);
else if (dual_screen) ClearScreen(BOT_SCREEN, COLOR_STD_BG);
else memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
} 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:",
(unsigned int) offset);
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) {
if (!found_size) *found_data = 0;
if (ShowKeyboardOrPrompt((char*) found_data, 64 + 1, "Enter search string below.\n(R+X to repeat search)")) {
found_size = strnlen((char*) found_data, 64);
found_offset = FileFindData(path, found_data, found_size, offset);
if (found_offset == (u32) -1) {
ShowPrompt(false, "Not found!");
found_size = 0;
} else offset = found_offset;
}
} else if (user_select == 3) {
u32 size = found_size;
if (ShowDataPrompt(found_data, &size, "Enter search data below.\n(R+X to repeat search)")) {
found_size = size;
found_offset = FileFindData(path, found_data, size, offset);
if (found_offset == (u32) -1) {
ShowPrompt(false, "Not found!");
found_size = 0;
} else offset = found_offset;
}
}
if (MAIN_SCREEN == TOP_SCREEN) ClearScreen(TOP_SCREEN, COLOR_STD_BG);
else if (dual_screen) ClearScreen(BOT_SCREEN, COLOR_STD_BG);
else memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
}
if (edit_mode && CheckWritePermissions(path)) { // setup edit mode
found_size = 0;
found_offset = (u32) -1;
cursor = 0;
edit_start = ((offset - (offset % 0x200) <= (edit_bsize / 2)) || (fsize < edit_bsize)) ? 0 :
offset - (offset % 0x200) - (edit_bsize / 2);
FileGetData(path, buffer, edit_bsize, edit_start);
memcpy(buffer_cpy, buffer, edit_bsize);
data = buffer + (offset - edit_start);
} else edit_mode = false;
} else { // editor mode
if (pad_state & (BUTTON_B|BUTTON_START)) {
edit_mode = false;
// check for user edits
u32 diffs = 0;
for (u32 i = 0; i < edit_bsize; i++) if (buffer[i] != buffer_cpy[i]) diffs++;
if (diffs && ShowPrompt(true, "You made edits in %i place(s).\nWrite changes to file?", diffs))
if (!FileSetData(path, buffer, min(edit_bsize, (fsize - edit_start)), edit_start, false))
ShowPrompt(false, "Failed writing to file!");
data = buffer;
last_offset = (u32) -1; // force reload from file
} 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;
}
}
}
}
}
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
if (MAIN_SCREEN == TOP_SCREEN) memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
else ClearScreen(BOT_SCREEN, COLOR_STD_BG);
free(bottom_cpy);
free(buffer);
free(buffer_cpy);
return 0;
}
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, 0, 0)) {
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 match_sha = have_sha && (memcmp(sha256, sha256_file, 32) == 0);
bool match_prev = (memcmp(sha256, sha256_prev, 32) == 0);
bool write_sha = (!have_sha || !match_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) ? ((match_sha) ? "passed!" : "failed!") : "",
(match_prev) ? "\n \nIdentical with previous file:\n" : "",
(match_prev) ? pathstr_prev : "",
(write_sha) ? "\n \nWrite .SHA file?" : "") && write_sha) {
FileSetData(sha_path, sha256, 32, 0, true);
}
strncpy(pathstr_prev, pathstr, 32 + 1);
memcpy(sha256_prev, sha256, 32);
}
return 0;
}
u32 CmacCalculator(const char* path) {
char pathstr[32 + 1];
TruncateString(pathstr, path, 32, 8);
if (IdentifyFileType(path) != GAME_CMD) {
u8 cmac[16] __attribute__((aligned(4)));
if (CalculateFileCmac(path, cmac) != 0) {
ShowPrompt(false, "Calculating CMAC: failed!");
return 1;
} else {
u8 cmac_file[16];
bool identical = ((ReadFileCmac(path, cmac_file) == 0) && (memcmp(cmac, cmac_file, 16) == 0));
if (ShowPrompt(!identical, "%s\n%016llX%016llX\n%s%s%s",
pathstr, getbe64(cmac + 0), getbe64(cmac + 8),
"CMAC verification: ", (identical) ? "passed!" : "failed!",
(!identical) ? "\n \nFix CMAC in file?" : "") &&
!identical && (WriteFileCmac(path, cmac) != 0)) {
ShowPrompt(false, "Fixing CMAC: failed!");
}
}
} else { // special handling for CMD files
bool correct = (CheckCmdCmac(path) == 0);
if (ShowPrompt(!correct, "%s\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n%s%s%s",
pathstr, "CMAC verification: ", (correct) ? "passed!" : "failed!",
(!correct) ? "\n \nFix CMAC in file?" : "") &&
!correct && (FixCmdCmac(path) != 0)) {
ShowPrompt(false, "Fixing CMAC: failed!");
}
}
return 0;
}
u32 StandardCopy(u32* cursor, u32* scroll) {
DirEntry* curr_entry = &(current_dir->entry[*cursor]);
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++;
}
u32 flags = BUILD_PATH;
if ((n_marked > 1) && ShowPrompt(true, "Copy all %lu selected items?", n_marked)) {
u32 n_success = 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;
flags |= ASK_ALL;
DrawDirContents(current_dir, (*cursor = i), scroll);
if (PathCopy(OUTPUT_PATH, path, &flags)) n_success++;
else { // on failure: show error, break
char currstr[32+1];
TruncateString(currstr, path, 32, 12);
ShowPrompt(false, "%s\nFailed copying item", currstr);
break;
}
current_dir->entry[i].marked = false;
}
if (n_success) ShowPrompt(false, "%lu items copied to %s", n_success, OUTPUT_PATH);
} else {
char pathstr[32+1];
TruncateString(pathstr, curr_entry->path, 32, 8);
if (!PathCopy(OUTPUT_PATH, curr_entry->path, &flags))
ShowPrompt(false, "%s\nFailed copying item", pathstr);
else ShowPrompt(false, "%s\nCopied to %s", pathstr, OUTPUT_PATH);
}
return 0;
}
u32 DirFileAttrMenu(const char* path, const char *name) {
bool drv = (path[2] == '\0');
bool vrt = (!drv); // will be checked below
char namestr[32], datestr[32], attrstr[128], sizestr[192];
FILINFO fno;
u8 new_attrib;
// create truncated name string
TruncateString(namestr, name, 31, 8);
// preparations: create file info, date string
if (!drv) {
if (fvx_stat(path, &fno) != FR_OK) return 1;
vrt = (fno.fattrib & AM_VRT);
new_attrib = fno.fattrib;
snprintf(datestr, 64, "%s: %04d-%02d-%02d %02d:%02d:%02d\n",
(fno.fattrib & AM_DIR) ? "created" : "modified",
1980 + ((fno.fdate >> 9) & 0x7F), (fno.fdate >> 5) & 0xF, fno.fdate & 0x1F,
(fno.ftime >> 11) & 0x1F, (fno.ftime >> 5) & 0x3F, (fno.ftime & 0x1F) << 1);
} else {
*datestr = '\0';
*attrstr = '\0';
new_attrib = 0;
}
// create size string
if (drv || (fno.fattrib & AM_DIR)) { // for dirs and drives
char bytestr[32];
u64 tsize = 0;
u32 tdirs = 0;
u32 tfiles = 0;
// this may take a while...
ShowString("Analyzing %s, please wait...", drv ? "drive" : "dir");
if (!DirInfo(path, &tsize, &tdirs, &tfiles))
return 1;
FormatBytes(bytestr, tsize);
if (drv) { // drive specific
char freestr[32], drvsstr[32], usedstr[32];
FormatBytes(freestr, GetFreeSpace(path));
FormatBytes(drvsstr, GetTotalSpace(path));
FormatBytes(usedstr, GetTotalSpace(path) - GetFreeSpace(path));
snprintf(sizestr, 192, "%lu files & %lu subdirs\n%s total size\n \nspace free: %s\nspace used: %s\nspace total: %s",
tfiles, tdirs, bytestr, freestr, usedstr, drvsstr);
} else { // dir specific
snprintf(sizestr, 192, "%lu files & %lu subdirs\n%s total size",
tfiles, tdirs, bytestr);
}
} else { // for files
char bytestr[32];
FormatBytes(bytestr, fno.fsize);
snprintf(sizestr, 64, "filesize: %s byte", bytestr);
}
while(true) {
if (!drv) {
snprintf(attrstr, 128,
" \n"
"[%c] %sread-only [%c] %shidden\n"
"[%c] %ssystem [%c] %sarchive\n"
"[%c] %svirtual\n"
"%s",
(new_attrib & AM_RDO) ? 'X' : ' ', vrt ? "" : "\x18 ",
(new_attrib & AM_HID) ? 'X' : ' ', vrt ? "" : "\x19 ",
(new_attrib & AM_SYS) ? 'X' : ' ', vrt ? "" : "\x1A ",
(new_attrib & AM_ARC) ? 'X' : ' ', vrt ? "" : "\x1B ",
vrt ? 'X' : ' ', vrt ? "" : " ",
vrt ? "" : " \n(\x18\x19\x1A\x1B to change attributes)\n"
);
}
ShowString(
"%s\n \n" // name
"%s" // date (not for drives)
"%s\n" // size
"%s \n" // attr (not for drives)
"%s\n", // options
namestr, datestr, sizestr, attrstr,
(drv || vrt || (new_attrib == fno.fattrib)) ? "(<A> to continue)" : "(<A> to apply, <B> to cancel)"
);
while(true) {
u32 pad_state = InputWait(0);
if (pad_state & (BUTTON_A | BUTTON_B)) {
if (!drv && !vrt) {
const u8 mask = (AM_RDO | AM_HID | AM_SYS | AM_ARC);
bool apply = (new_attrib != fno.fattrib) && (pad_state & BUTTON_A);
if (apply && !PathAttr(path, new_attrib & mask, mask)) {
ShowPrompt(false, "%s\nFailed to set attributes!", namestr);
}
}
ClearScreenF(true, false, COLOR_STD_BG);
return 0;
}
if (!drv && !vrt && (pad_state & BUTTON_ARROW)) {
switch (pad_state & BUTTON_ARROW) {
case BUTTON_UP:
new_attrib ^= AM_RDO;
break;
case BUTTON_DOWN:
new_attrib ^= AM_HID;
break;
case BUTTON_RIGHT:
new_attrib ^= AM_SYS;
break;
case BUTTON_LEFT:
new_attrib ^= AM_ARC;
break;
}
break;
}
}
}
}
u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pane) {
const char* file_path = (&(current_dir->entry[*cursor]))->path;
const char* file_name = (&(current_dir->entry[*cursor]))->name;
const char* optionstr[16];
// check for file lock
if (!FileUnlock(file_path)) return 1;
u64 filetype = IdentifyFileType(file_path);
u32 drvtype = DriveType(file_path);
bool in_output_path = (strncasecmp(current_path, OUTPUT_PATH, 256) == 0);
// don't handle TMDs inside the game drive, won't work properly anyways
if ((filetype & GAME_TMD) && (drvtype & DRV_GAME)) filetype &= ~GAME_TMD;
// special stuff, only available for known filetypes (see int special below)
bool mountable = (FTYPE_MOUNTABLE(filetype) && !(drvtype & DRV_IMAGE) &&
!((drvtype & (DRV_SYSNAND|DRV_EMUNAND)) && (drvtype & DRV_VIRTUAL) && (filetype & IMG_FAT)));
bool verificable = (FTYPE_VERIFICABLE(filetype));
bool decryptable = (FTYPE_DECRYPTABLE(filetype));
bool encryptable = (FTYPE_ENCRYPTABLE(filetype));
bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT));
bool cia_buildable = (FTYPE_CIABUILD(filetype));
bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype));
bool cxi_dumpable = (FTYPE_CXIDUMP(filetype));
bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path;
bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool titleinfo = (FTYPE_TITLEINFO(filetype));
bool ciacheckable = (FTYPE_CIACHECK(filetype));
bool renamable = (FTYPE_RENAMABLE(filetype));
bool trimable = (FTYPE_TRIMABLE(filetype)) && !(drvtype & DRV_VIRTUAL) && !(drvtype & DRV_ALIAS) &&
!(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_IMAGE);
bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_UNLOCKED && (drvtype & DRV_FAT));
bool hsinjectable = (FTYPE_HASCODE(filetype));
bool extrcodeable = (FTYPE_HASCODE(filetype));
bool restorable = (FTYPE_RESTORABLE(filetype) && IS_UNLOCKED && !(drvtype & DRV_SYSNAND));
bool ebackupable = (FTYPE_EBACKUP(filetype));
bool ncsdfixable = (FTYPE_NCSDFIXABLE(filetype));
bool xorpadable = (FTYPE_XORPAD(filetype));
bool keyinitable = (FTYPE_KEYINIT(filetype)) && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool keyinstallable = (FTYPE_KEYINSTALL(filetype)) && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool scriptable = (FTYPE_SCRIPT(filetype));
bool fontable = (FTYPE_FONT(filetype));
bool viewable = (FTYPE_GFX(filetype));
bool setable = (FTYPE_SETABLE(filetype));
bool bootable = (FTYPE_BOOTABLE(filetype));
bool installable = (FTYPE_INSTALLABLE(filetype));
bool agbexportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool agbimportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
char cxi_path[256] = { 0 }; // special options for TMD
if ((filetype & GAME_TMD) && !(filetype & FLAG_NUSCDN) &&
(GetTmdContentPath(cxi_path, file_path) == 0) &&
(PathExist(cxi_path))) {
u64 filetype_cxi = IdentifyFileType(cxi_path);
mountable = (FTYPE_MOUNTABLE(filetype_cxi) && !(drvtype & DRV_IMAGE));
extrcodeable = (FTYPE_HASCODE(filetype_cxi));
}
bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || trimable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || fontable || viewable || installable || agbexportable || agbimportable;
char pathstr[32+1];
TruncateString(pathstr, file_path, 32, 8);
u32 n_marked = 0;
if ((&(current_dir->entry[*cursor]))->marked) {
for (u32 i = 0; i < current_dir->n_entries; i++)
if (current_dir->entry[i].marked) n_marked++;
}
// main menu processing
int n_opt = 0;
int special = (special_opt) ? ++n_opt : -1;
int hexviewer = ++n_opt;
int textviewer = (filetype & TXT_GENERIC) ? ++n_opt : -1;
int calcsha = ++n_opt;
int calccmac = (CheckCmacPath(file_path) == 0) ? ++n_opt : -1;
int fileinfo = ++n_opt;
int copystd = (!in_output_path) ? ++n_opt : -1;
int inject = ((clipboard->n_entries == 1) &&
(clipboard->entry[0].type == T_FILE) &&
(strncmp(clipboard->entry[0].path, file_path, 256) != 0)) ?
(int) ++n_opt : -1;
int searchdrv = (DriveType(current_path) & DRV_SEARCH) ? ++n_opt : -1;
if (special > 0) optionstr[special-1] =
(filetype & IMG_NAND ) ? "NAND image options..." :
(filetype & IMG_FAT ) ? (transferable) ? "CTRNAND options..." : "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" :
(filetype & GAME_TMD ) ? "TMD file options..." :
(filetype & GAME_BOSS ) ? "BOSS file options..." :
(filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" :
(filetype & GAME_SMDH) ? "Show SMDH title info" :
(filetype & GAME_NDS) ? "NDS image options..." :
(filetype & GAME_GBA) ? "GBA image options..." :
(filetype & GAME_TICKET)? "Ticket options..." :
(filetype & GAME_TAD) ? "TAD image options..." :
(filetype & GAME_3DSX) ? "Show 3DSX title info" :
(filetype & SYS_FIRM ) ? "FIRM image options..." :
(filetype & SYS_AGBSAVE)? (agbimportable) ? "AGBSAVE options..." : "Dump GBA VC save" :
(filetype & SYS_TICKDB) ? "Ticket.db options..." :
(filetype & SYS_DIFF) ? "Mount as DIFF image" :
(filetype & SYS_DISA) ? "Mount as DISA image" :
(filetype & BIN_TIKDB) ? "Titlekey options..." :
(filetype & BIN_KEYDB) ? "AESkeydb options..." :
(filetype & BIN_LEGKEY) ? "Build " KEYDB_NAME :
(filetype & BIN_NCCHNFO)? "NCCHinfo options..." :
(filetype & TXT_SCRIPT) ? "Execute GM9 script" :
(filetype & FONT_PBM) ? "Font options..." :
(filetype & GFX_PNG) ? "View PNG file" :
(filetype & HDR_NAND) ? "Rebuild NCSD header" :
(filetype & NOIMG_NAND) ? "Rebuild NCSD header" : "???";
optionstr[hexviewer-1] = "Show in Hexeditor";
optionstr[calcsha-1] = "Calculate SHA-256";
optionstr[fileinfo-1] = "Show file info";
if (textviewer > 0) optionstr[textviewer-1] = "Show in Textviewer";
if (calccmac > 0) optionstr[calccmac-1] = "Calculate CMAC";
if (copystd > 0) optionstr[copystd-1] = "Copy to " OUTPUT_PATH;
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, (n_marked > 1) ?
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
if (user_select == hexviewer) { // -> show in hex viewer
FileHexViewer(file_path);
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == textviewer) { // -> show in text viewer
FileTextViewer(file_path, scriptable);
return 0;
}
else if (user_select == calcsha) { // -> calculate SHA-256
Sha256Calculator(file_path);
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == calccmac) { // -> calculate CMAC
optionstr[0] = "Check current CMAC only";
optionstr[1] = "Verify CMAC for all";
optionstr[2] = "Fix CMAC for all";
user_select = (n_marked > 1) ? ShowSelectPrompt(3, optionstr, "%s\n%(%lu files selected)", pathstr, n_marked) : 1;
if (user_select == 1) {
CmacCalculator(file_path);
return 0;
} else if ((user_select == 2) || (user_select == 3)) {
bool fix = (user_select == 3);
u32 n_processed = 0;
u32 n_success = 0;
u32 n_fixed = 0;
u32 n_nocmac = 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 (!ShowProgress(n_processed++, n_marked, path)) break;
if (CheckCmacPath(path) != 0) {
n_nocmac++;
continue;
}
if (CheckFileCmac(path) == 0) n_success++;
else if (fix && (FixFileCmac(path) == 0)) n_fixed++;
else { // on failure: set cursor on failed file
*cursor = i;
continue;
}
current_dir->entry[i].marked = false;
}
if (n_fixed) {
if (n_nocmac) ShowPrompt(false, "%lu/%lu/%lu files ok/fixed/total\n%lu/%lu have no CMAC",
n_success, n_fixed, n_marked, n_nocmac, n_marked);
else ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu files fixed",
n_success, n_marked, n_fixed, n_marked);
} else {
if (n_nocmac) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu have no CMAC",
n_success, n_marked, n_nocmac, n_marked);
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);
}
return 0;
}
return FileHandlerMenu(current_path, cursor, scroll, pane);
}
else if (user_select == fileinfo) { // -> show file info
DirFileAttrMenu(file_path, file_name);
return 0;
}
else if (user_select == copystd) { // -> copy to OUTPUT_PATH
StandardCopy(cursor, scroll);
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?\nSpecify offset below.", origstr);
if (offset != (u64) -1) {
if (!FileInjectFile(file_path, clipboard->entry[0].path, (u32) offset, 0, 0, NULL))
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(file_path, '/');
if (last_slash) {
if (N_PANES) { // switch to next pane
memcpy((*pane)->path, current_path, 256); // store current pane state
(*pane)->cursor = *cursor;
(*pane)->scroll = *scroll;
if (++*pane >= panedata + N_PANES) *pane -= N_PANES;
}
snprintf(current_path, last_slash - file_path + 1, "%s", file_path);
GetDirContents(current_dir, current_path);
*scroll = 0;
for (*cursor = 1; *cursor < current_dir->n_entries; (*cursor)++) {
DirEntry* entry = &(current_dir->entry[*cursor]);
if (strncasecmp(entry->path, file_path, 256) == 0) break;
}
if (*cursor >= current_dir->n_entries)
*cursor = 1;
}
return 0;
}
else if (user_select != special) {
return 1;
}
// stuff for special menu starts here
n_opt = 0;
int show_info = (titleinfo) ? ++n_opt : -1;
int ciacheck = (ciacheckable) ? ++n_opt : -1;
int mount = (mountable) ? ++n_opt : -1;
int restore = (restorable) ? ++n_opt : -1;
int ebackup = (ebackupable) ? ++n_opt : -1;
int ncsdfix = (ncsdfixable) ? ++n_opt : -1;
int decrypt = (decryptable) ? ++n_opt : -1;
int encrypt = (encryptable) ? ++n_opt : -1;
int cia_build = (cia_buildable) ? ++n_opt : -1;
int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1;
int cxi_dump = (cxi_dumpable) ? ++n_opt : -1;
int tik_build_enc = (tik_buildable) ? ++n_opt : -1;
int tik_build_dec = (tik_buildable) ? ++n_opt : -1;
int key_build = (key_buildable) ? ++n_opt : -1;
int verify = (verificable) ? ++n_opt : -1;
int ctrtransfer = (transferable) ? ++n_opt : -1;
int hsinject = (hsinjectable) ? ++n_opt : -1;
int extrcode = (extrcodeable) ? ++n_opt : -1;
int trim = (trimable) ? ++n_opt : -1;
int rename = (renamable) ? ++n_opt : -1;
int xorpad = (xorpadable) ? ++n_opt : -1;
int xorpad_inplace = (xorpadable) ? ++n_opt : -1;
int keyinit = (keyinitable) ? ++n_opt : -1;
int keyinstall = (keyinstallable) ? ++n_opt : -1;
int install = (installable) ? ++n_opt : -1;
int boot = (bootable) ? ++n_opt : -1;
int script = (scriptable) ? ++n_opt : -1;
int font = (fontable) ? ++n_opt : -1;
int view = (viewable) ? ++n_opt : -1;
int agbexport = (agbexportable) ? ++n_opt : -1;
int agbimport = (agbimportable) ? ++n_opt : -1;
int setup = (setable) ? ++n_opt : -1;
if (mount > 0) optionstr[mount-1] = (filetype & GAME_TMD) ? "Mount CXI/NDS to drive" : "Mount image to drive";
if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)";
if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup";
if (ncsdfix > 0) optionstr[ncsdfix-1] = "Rebuild NCSD header";
if (show_info > 0) optionstr[show_info-1] = "Show title info";
if (ciacheck > 0) optionstr[ciacheck-1] = "CIA checker tool";
if (decrypt > 0) optionstr[decrypt-1] = (cryptable_inplace) ? "Decrypt file (...)" : "Decrypt file (" OUTPUT_PATH ")";
if (encrypt > 0) optionstr[encrypt-1] = (cryptable_inplace) ? "Encrypt file (...)" : "Encrypt file (" OUTPUT_PATH ")";
if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)";
if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file";
if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC;
if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC;
if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME;
if (verify > 0) optionstr[verify-1] = "Verify file";
if (ctrtransfer > 0) optionstr[ctrtransfer-1] = "Transfer image to CTRNAND";
if (hsinject > 0) optionstr[hsinject-1] = "Inject to H&S";
if (trim > 0) optionstr[trim-1] = "Trim file";
if (rename > 0) optionstr[rename-1] = "Rename file";
if (xorpad > 0) optionstr[xorpad-1] = "Build XORpads (SD output)";
if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)";
if (extrcode > 0) optionstr[extrcode-1] = "Extract " EXEFS_CODE_NAME;
if (keyinit > 0) optionstr[keyinit-1] = "Init " KEYDB_NAME;
if (keyinstall > 0) optionstr[keyinstall-1] = "Install " KEYDB_NAME;
if (install > 0) optionstr[install-1] = "Install FIRM";
if (boot > 0) optionstr[boot-1] = "Boot FIRM";
if (script > 0) optionstr[script-1] = "Execute GM9 script";
if (view > 0) optionstr[view-1] = "View PNG file";
if (font > 0) optionstr[font-1] = "Set as active font";
if (agbexport > 0) optionstr[agbexport-1] = "Dump GBA VC save";
if (agbimport > 0) optionstr[agbimport-1] = "Inject GBA VC save";
if (setup > 0) optionstr[setup-1] = "Set as default";
// auto select when there is only one option
user_select = (n_opt <= 1) ? n_opt : (int) ShowSelectPrompt(n_opt, optionstr, (n_marked > 1) ?
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
if (user_select == mount) { // -> mount file as image
const char* mnt_drv_paths[] = { "7:", "G:", "K:", "T:", "I:", "D:" }; // maybe move that to fsdrive.h
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
clipboard->n_entries = 0; // remove last mounted image clipboard entries
InitImgFS((filetype & GAME_TMD) ? cxi_path : file_path);
const char* drv_path = NULL; // find path of mounted drive
for (u32 i = 0; i < (sizeof(mnt_drv_paths) / sizeof(const char*)); i++) {
if (DriveType((drv_path = mnt_drv_paths[i]))) break;
drv_path = NULL;
}
if (!drv_path) {
ShowPrompt(false, "Mounting image: failed");
InitImgFS(NULL);
} else { // open in next pane?
if (ShowPrompt(true, "%s\nMounted as drive %s\nEnter path now?", pathstr, drv_path)) {
if (N_PANES) {
memcpy((*pane)->path, current_path, 256); // store current pane state
(*pane)->cursor = *cursor;
(*pane)->scroll = *scroll;
if (++*pane >= panedata + N_PANES) *pane -= N_PANES;
}
strncpy(current_path, drv_path, 256);
GetDirContents(current_dir, current_path);
*cursor = 1;
*scroll = 0;
}
}
return 0;
}
else if (user_select == decrypt) { // -> decrypt game file
if (cryptable_inplace) {
optionstr[0] = "Decrypt to " OUTPUT_PATH;
optionstr[1] = "Decrypt inplace";
user_select = (int) ShowSelectPrompt(2, optionstr, (n_marked > 1) ?
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
} else user_select = 1;
bool inplace = (user_select == 2);
if (!user_select) { // do nothing when no choice is made
} else 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;
ShowString("Trying to decrypt %lu files...", n_marked);
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 & TYPE_BASE)) {
n_other++;
continue;
}
if (!(filetype & BIN_KEYDB) && (CheckEncryptedGameFile(path) != 0)) {
n_unencrypted++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, false) == 0)) n_success++;
else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, false) == 0)) n_success++;
else { // on failure: show error, continue
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nDecryption failed\n \nContinue?", lpathstr)) continue;
else break;
}
current_dir->entry[i].marked = false;
}
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 (!(filetype & BIN_KEYDB) && (CheckEncryptedGameFile(file_path) != 0)) {
ShowPrompt(false, "%s\nFile is not encrypted", pathstr);
} else {
u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(file_path, inplace, false) :
CryptGameFile(file_path, inplace, false);
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;
}
else if (user_select == encrypt) { // -> encrypt game file
if (cryptable_inplace) {
optionstr[0] = "Encrypt to " OUTPUT_PATH;
optionstr[1] = "Encrypt inplace";
user_select = (int) ShowSelectPrompt(2, optionstr, (n_marked > 1) ?
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
} else user_select = 1;
bool inplace = (user_select == 2);
if (!user_select) { // do nothing when no choice is made
} else if ((n_marked > 1) && ShowPrompt(true, "Try to encrypt all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
ShowString("Trying to encrypt %lu files...", n_marked);
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 & TYPE_BASE)) {
n_other++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if (!(filetype & BIN_KEYDB) && (CryptGameFile(path, inplace, true) == 0)) n_success++;
else if ((filetype & BIN_KEYDB) && (CryptAesKeyDb(path, inplace, true) == 0)) n_success++;
else { // on failure: show error, continue
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nEncryption failed\n \nContinue?", lpathstr)) continue;
else break;
}
current_dir->entry[i].marked = false;
}
if (n_other) {
ShowPrompt(false, "%lu/%lu files encrypted ok\n%lu/%lu not of same type",
n_success, n_marked, n_other, n_marked);
} else ShowPrompt(false, "%lu/%lu files encrypted ok", n_success, n_marked);
if (!inplace && n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
} else {
u32 ret = (filetype & BIN_KEYDB) ? CryptAesKeyDb(file_path, inplace, true) :
CryptGameFile(file_path, inplace, true);
if (inplace || (ret != 0)) ShowPrompt(false, "%s\nEncryption %s", pathstr, (ret == 0) ? "success" : "failed");
else ShowPrompt(false, "%s\nEncrypted to %s", pathstr, OUTPUT_PATH);
}
return 0;
}
else if ((user_select == cia_build) || (user_select == cia_build_legit) || (user_select == cxi_dump)) { // -> build CIA / dump CXI
char* type = (user_select == cxi_dump) ? "CXI" : "CIA";
bool force_legit = (user_select == cia_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 & TYPE_BASE)) {
n_other++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if (((user_select != cxi_dump) && (BuildCiaFromGameFile(path, force_legit) == 0)) ||
((user_select == cxi_dump) && (DumpCxiSrlFromTmdFile(path) == 0))) n_success++;
else { // on failure: show error, continue
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nBuild %s failed\n \nContinue?", lpathstr, type)) continue;
else break;
}
current_dir->entry[i].marked = false;
}
if (n_other) ShowPrompt(false, "%lu/%lu %ss built ok\n%lu/%lu not of same type",
n_success, n_marked, type, n_other, n_marked);
else ShowPrompt(false, "%lu/%lu %ss built ok", n_success, n_marked, type);
if (n_success) ShowPrompt(false, "%lu files written to %s", n_success, OUTPUT_PATH);
if (n_success && in_output_path) GetDirContents(current_dir, current_path);
if (n_success != (n_marked - n_other)) {
ShowPrompt(false, "%lu file(s) failed conversion.\nVerification is recommended.",
n_marked - (n_success + n_other));
}
} else {
if (((user_select != cxi_dump) && (BuildCiaFromGameFile(file_path, force_legit) == 0)) ||
((user_select == cxi_dump) && (DumpCxiSrlFromTmdFile(file_path) == 0))) {
ShowPrompt(false, "%s\n%s built to %s", pathstr, type, OUTPUT_PATH);
if (in_output_path) GetDirContents(current_dir, current_path);
} else {
ShowPrompt(false, "%s\n%s build failed", pathstr, type);
if ((filetype & (GAME_NCCH|GAME_NCSD)) &&
ShowPrompt(true, "%s\nfile failed conversion.\n \nVerify now?", pathstr)) {
ShowPrompt(false, "%s\nVerification %s", pathstr,
(VerifyGameFile(file_path) == 0) ? "success" : "failed");
}
}
}
return 0;
}
else if (user_select == verify) { // -> verify game / nand file
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 (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH)) &&
!ShowProgress(n_processed++, n_marked, path)) break;
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
n_other++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if ((filetype & IMG_NAND) && (ValidateNandDump(path) == 0)) n_success++;
else if (VerifyGameFile(path) == 0) n_success++;
else { // on failure: show error, continue
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nVerification failed\n \nContinue?", lpathstr)) {
if (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH)))
ShowProgress(0, n_marked, path); // restart progress bar
continue;
} else break;
}
current_dir->entry[i].marked = false;
}
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);
if (filetype & IMG_NAND) {
ShowPrompt(false, "%s\nNAND validation %s", pathstr,
(ValidateNandDump(file_path) == 0) ? "success" : "failed");
} else ShowPrompt(false, "%s\nVerification %s", pathstr,
(VerifyGameFile(file_path) == 0) ? "success" : "failed");
}
return 0;
}
else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database
bool dec = (user_select == tik_build_dec);
const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC;
if (BuildTitleKeyInfo(NULL, dec, false) != 0) return 1; // init database
ShowString("Building %s...", (dec) ? TIKDB_NAME_DEC : TIKDB_NAME_ENC);
if (n_marked > 1) {
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 (!FTYPE_TIKBUILD(IdentifyFileType(path))) {
n_other++;
continue;
}
current_dir->entry[i].marked = false;
if (BuildTitleKeyInfo(path, dec, false) == 0) n_success++; // ignore failures for now
}
if (BuildTitleKeyInfo(NULL, dec, true) == 0) {
if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
path_out, n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
} else ShowPrompt(false, "%s\nBuild database failed.", path_out);
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
(BuildTitleKeyInfo(file_path, dec, true) == 0) ? "success" : "failed");
return 0;
}
else if (user_select == key_build) { // -> (Re)Build AES key database
const char* path_out = OUTPUT_PATH "/" KEYDB_NAME;
if (BuildKeyDb(NULL, false) != 0) return 1; // init database
ShowString("Building %s...", KEYDB_NAME);
if (n_marked > 1) {
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 (!FTYPE_KEYBUILD(IdentifyFileType(path))) {
n_other++;
continue;
}
current_dir->entry[i].marked = false;
if (BuildKeyDb(path, false) == 0) n_success++; // ignore failures for now
}
if (BuildKeyDb(NULL, true) == 0) {
if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
path_out, n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
} else ShowPrompt(false, "%s\nBuild database failed.", path_out);
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
(BuildKeyDb(file_path, true) == 0) ? "success" : "failed");
return 0;
}
else if (user_select == trim) { // -> Game file trimmer
if ((n_marked > 1) && ShowPrompt(true, "Try to trim all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
u32 n_processed = 0;
u64 savings = 0;
char savingsstr[32];
for (u32 i = 0; i < current_dir->n_entries; i++) {
const char* path = current_dir->entry[i].path;
u64 prevsize = 0;
if (!current_dir->entry[i].marked)
continue;
if (!ShowProgress(n_processed++, n_marked, path)) break;
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
n_other++;
continue;
}
prevsize = FileGetSize(path);
if (TrimGameFile(path) == 0) {
n_success++;
savings += prevsize - FileGetSize(path);
} else { // on failure: show error, continue (should not happen)
char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nTrimming failed\n \nContinue?", lpathstr)) {
ShowProgress(0, n_marked, path); // restart progress bar
continue;
} else break;
}
current_dir->entry[i].marked = false;
}
FormatBytes(savingsstr, savings);
if (n_other) ShowPrompt(false, "%lu/%lu files trimmed ok\n%lu/%lu not of same type\n%s saved",
n_success, n_marked, n_other, n_marked, savingsstr);
else ShowPrompt(false, "%lu/%lu files trimmed ok\n%s saved", n_success, n_marked, savingsstr);
if (n_success) GetDirContents(current_dir, current_path);
} else {
u64 trimsize = GetGameFileTrimmedSize(file_path);
u64 currentsize = FileGetSize(file_path);
char tsizestr[32];
char csizestr[32];
char dsizestr[32];
FormatBytes(tsizestr, trimsize);
FormatBytes(csizestr, currentsize);
FormatBytes(dsizestr, currentsize - trimsize);
if (!trimsize || trimsize > currentsize) {
ShowPrompt(false, "%s\nFile can't be trimmed.", pathstr);
} else if (trimsize == currentsize) {
ShowPrompt(false, "%s\nFile is already trimmed.", pathstr);
} else if (ShowPrompt(true, "%s\nCurrent size: %s\nTrimmed size: %s\nDifference: %s\n \nTrim this file?",
pathstr, csizestr, tsizestr, dsizestr)) {
if (TrimGameFile(file_path) != 0) ShowPrompt(false, "%s\nTrimming failed.", pathstr);
else {
ShowPrompt(false, "%s\nTrimmed by %s.", pathstr, dsizestr);
GetDirContents(current_dir, current_path);
}
}
}
return 0;
}
else if (user_select == rename) { // -> Game file renamer
if ((n_marked > 1) && ShowPrompt(true, "Try to rename all %lu selected files?", n_marked)) {
u32 n_success = 0;
ShowProgress(0, 0, "");
for (u32 i = 0; i < current_dir->n_entries; i++) {
DirEntry* entry = &(current_dir->entry[i]);
if (!current_dir->entry[i].marked) continue;
ShowProgress(i+1, current_dir->n_entries, entry->name);
if (!GoodRenamer(entry, false)) continue;
n_success++;
current_dir->entry[i].marked = false;
}
ShowPrompt(false, "%lu/%lu renamed ok", n_success, n_marked);
} else if (!GoodRenamer(&(current_dir->entry[*cursor]), true)) {
ShowPrompt(false, "%s\nCould not rename to good name", pathstr);
}
return 0;
}
else if (user_select == show_info) { // -> Show title info
if (ShowGameFileTitleInfo(file_path) != 0)
ShowPrompt(false, "Title info: not found");
return 0;
}
else if (user_select == ciacheck) { // -> CIA checker tool
ShowCiaCheckerInfo(file_path);
return 0;
}
else if (user_select == hsinject) { // -> Inject to Health & Safety
char* destdrv[2] = { NULL };
n_opt = 0;
if (DriveType("1:")) {
optionstr[n_opt] = "SysNAND H&S inject";
destdrv[n_opt++] = "1:";
}
if (DriveType("4:")) {
optionstr[n_opt] = "EmuNAND H&S inject";
destdrv[n_opt++] = "4:";
}
user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, "%s", pathstr) : n_opt;
if (user_select) {
ShowPrompt(false, "%s\nH&S inject %s", pathstr,
(InjectHealthAndSafety(file_path, destdrv[user_select-1]) == 0) ? "success" : "failed");
}
return 0;
}
else if (user_select == extrcode) { // -> Extract .code
if ((n_marked > 1) && ShowPrompt(true, "Try to extract 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 (!ShowProgress(n_processed++, n_marked, path)) break;
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
n_other++;
continue;
}
DrawDirContents(current_dir, (*cursor = i), scroll);
if (filetype & GAME_TMD) {
char cxi_pathl[256] = { 0 };
if ((GetTmdContentPath(cxi_pathl, path) == 0) && PathExist(cxi_pathl) &&
(ExtractCodeFromCxiFile(cxi_pathl, NULL, NULL) == 0)) {
n_success++;
} else continue;
} else {
if (ExtractCodeFromCxiFile(path, NULL, NULL) == 0) n_success++;
else continue;
}
current_dir->entry[i].marked = false;
}
if (n_other) ShowPrompt(false, "%lu/%lu files extracted ok\n%lu/%lu not of same type",
n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%lu/%lu files extracted ok", n_success, n_marked);
} else {
char extstr[8] = { 0 };
ShowString("%s\nExtracting .code, please wait...", pathstr);
if (ExtractCodeFromCxiFile((filetype & GAME_TMD) ? cxi_path : file_path, NULL, extstr) == 0) {
ShowPrompt(false, "%s\n%s extracted to " OUTPUT_PATH, pathstr, extstr);
} else ShowPrompt(false, "%s\n.code extract failed", pathstr);
}
return 0;
}
else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND
char* destdrv[2] = { NULL };
n_opt = 0;
if (DriveType("1:")) {
optionstr[n_opt] = "Transfer to SysNAND";
destdrv[n_opt++] = "1:";
}
if (DriveType("4:")) {
optionstr[n_opt] = "Transfer to EmuNAND";
destdrv[n_opt++] = "4:";
}
if (n_opt) {
user_select = (n_opt > 1) ? (int) ShowSelectPrompt(n_opt, optionstr, "%s", pathstr) : 1;
if (user_select) {
ShowPrompt(false, "%s\nCTRNAND transfer %s", pathstr,
(TransferCtrNandImage(file_path, destdrv[user_select-1]) == 0) ? "success" : "failed");
}
} else ShowPrompt(false, "%s\nNo valid destination found");
return 0;
}
else if (user_select == restore) { // -> restore SysNAND (A9LH preserving)
ShowPrompt(false, "%s\nNAND restore %s", pathstr,
(SafeRestoreNandDump(file_path) == 0) ? "success" : "failed");
return 0;
}
else if (user_select == ncsdfix) { // -> inject sighaxed NCSD
ShowPrompt(false, "%s\nRebuild NCSD %s", pathstr,
(FixNandHeader(file_path, !(filetype == HDR_NAND)) == 0) ? "success" : "failed");
GetDirContents(current_dir, current_path);
InitExtFS(); // this might have fixed something, so try this
return 0;
}
else if ((user_select == xorpad) || (user_select == xorpad_inplace)) { // -> build xorpads
bool inplace = (user_select == xorpad_inplace);
bool success = (BuildNcchInfoXorpads((inplace) ? current_path : OUTPUT_PATH, file_path) == 0);
ShowPrompt(false, "%s\nNCCHinfo padgen %s%s", pathstr,
(success) ? "success" : "failed",
(!success || inplace) ? "" : "\nOutput dir: " OUTPUT_PATH);
GetDirContents(current_dir, current_path);
for (; *cursor < current_dir->n_entries; (*cursor)++) {
DirEntry* entry = &(current_dir->entry[*cursor]);
if (strncasecmp(entry->name, NCCHINFO_NAME, 32) == 0) break;
}
if (*cursor >= current_dir->n_entries) {
*scroll = 0;
*cursor = 1;
}
return 0;
}
else if (user_select == ebackup) { // -> update embedded backup
ShowString("%s\nUpdating embedded backup...", pathstr);
bool required = (CheckEmbeddedBackup(file_path) != 0);
bool success = (required && (EmbedEssentialBackup(file_path) == 0));
ShowPrompt(false, "%s\nBackup update: %s", pathstr, (!required) ? "not required" :
(success) ? "completed" : "failed!");
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == keyinit) { // -> initialise keys from aeskeydb.bin
if (ShowPrompt(true, "Warning: Keys are not verified.\nContinue on your own risk?"))
ShowPrompt(false, "%s\nAESkeydb init %s", pathstr, (InitKeyDb(file_path) == 0) ? "success" : "failed");
return 0;
}
else if (user_select == keyinstall) { // -> install keys from aeskeydb.bin
ShowPrompt(false, "%s\nAESkeydb install %s", pathstr, (SafeInstallKeyDb(file_path) == 0) ? "success" : "failed");
return 0;
}
else if (user_select == install) { // -> install FIRM
size_t firm_size = FileGetSize(file_path);
u32 slots = 1;
if (GetNandPartitionInfo(NULL, NP_TYPE_FIRM, NP_SUBTYPE_CTR, 1, NAND_SYSNAND) == 0) {
optionstr[0] = "Install to FIRM0";
optionstr[1] = "Install to FIRM1";
optionstr[2] = "Install to both";
// this only works up to FIRM1
slots = ShowSelectPrompt(3, optionstr, "%s (%dkB)\nInstall to SysNAND?", pathstr, firm_size / 1024);
} else slots = ShowPrompt(true, "%s (%dkB)\nInstall to SysNAND?", pathstr, firm_size / 1024) ? 1 : 0;
if (slots) ShowPrompt(false, "%s (%dkB)\nInstall %s", pathstr, firm_size / 1024,
(SafeInstallFirm(file_path, slots) == 0) ? "success" : "failed!");
return 0;
}
else if (user_select == boot) { // -> boot FIRM
BootFirmHandler(file_path, true, false);
return 0;
}
else if (user_select == script) { // execute script
if (ShowPrompt(true, "%s\nWarning: Do not run scripts\nfrom untrusted sources.\n \nExecute script?", pathstr))
ShowPrompt(false, "%s\nScript execute %s", pathstr, ExecuteGM9Script(file_path) ? "success" : "failure");
GetDirContents(current_dir, current_path);
ClearScreenF(true, true, COLOR_STD_BG);
return 0;
}
else if (user_select == font) { // set font
u8* pbm = (u8*) malloc(0x10000); // arbitrary, should be enough by far
if (!pbm) return 1;
u32 pbm_size = FileGetData(file_path, pbm, 0x10000, 0);
if (pbm_size) SetFontFromPbm(pbm, pbm_size);
ClearScreenF(true, true, COLOR_STD_BG);
free(pbm);
return 0;
}
else if (user_select == view) { // view gfx
if (FileGraphicsViewer(file_path) != 0)
ShowPrompt(false, "%s\nError: Cannot view file\n(Hint: maybe it's too big)", pathstr);
return 0;
}
else if (user_select == agbexport) { // export GBA VC save
if (DumpGbaVcSavegame(file_path) == 0)
ShowPrompt(false, "Savegame dumped to " OUTPUT_PATH ".");
else ShowPrompt(false, "Savegame dump failed!");
return 0;
}
else if (user_select == agbimport) { // import GBA VC save
if (clipboard->n_entries != 1) {
ShowPrompt(false, "GBA VC savegame has to\nbe in the clipboard.");
} else {
ShowPrompt(false, "Savegame inject %s.",
(InjectGbaVcSavegame(file_path, clipboard->entry[0].path) == 0) ? "success" : "failed!");
clipboard->n_entries = 0;
}
return 0;
}
else if (user_select == setup) { // set as default (font)
if (filetype & FONT_PBM) {
if (SetAsSupportFile("font.pbm", file_path))
ShowPrompt(false, "%s\nFont will be active on next boot", pathstr);
}
return 0;
}
return FileHandlerMenu(current_path, cursor, scroll, pane);
}
u32 HomeMoreMenu(char* current_path) {
NandPartitionInfo np_info;
if (GetNandPartitionInfo(&np_info, NP_TYPE_BONUS, NP_SUBTYPE_CTR, 0, NAND_SYSNAND) != 0) np_info.count = 0;
const char* optionstr[8];
const char* promptstr = "HOME more... menu.\nSelect action:";
u32 n_opt = 0;
int sdformat = ++n_opt;
int bonus = (np_info.count > 0x2000) ? (int) ++n_opt : -1; // 4MB minsize
int multi = (CheckMultiEmuNand()) ? (int) ++n_opt : -1;
int bsupport = ++n_opt;
int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1;
int clock = ++n_opt;
int bright = ++n_opt;
int calib = ++n_opt;
int sysinfo = ++n_opt;
int readme = (FindVTarFileInfo(VRAM0_README_MD, NULL)) ? (int) ++n_opt : -1;
if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu";
if (bonus > 0) optionstr[bonus - 1] = "Bonus drive setup";
if (multi > 0) optionstr[multi - 1] = "Switch EmuNAND";
if (bsupport > 0) optionstr[bsupport - 1] = "Build support files";
if (hsrestore > 0) optionstr[hsrestore - 1] = "Restore H&S";
if (clock > 0) optionstr[clock - 1] = "Set RTC date&time";
if (bright > 0) optionstr[bright - 1] = "Configure brightness";
if (calib > 0) optionstr[calib - 1] = "Calibrate touchscreen";
if (sysinfo > 0) optionstr[sysinfo - 1] = "System info";
if (readme > 0) optionstr[readme - 1] = "Show ReadMe";
int user_select = ShowSelectPrompt(n_opt, optionstr, promptstr);
if (user_select == sdformat) { // format SD card
bool sd_state = CheckSDMountState();
char slabel[DRV_LABEL_LEN] = { '\0' };
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
GetFATVolumeLabel("0:", slabel); // get SD volume label
DeinitExtFS();
DeinitSDCardFS();
if ((SdFormatMenu(slabel) == 0) || sd_state) {;
while (!InitSDCardFS() &&
ShowPrompt(true, "Initializing SD card failed! Retry?"));
}
ClearScreenF(true, true, COLOR_STD_BG);
AutoEmuNandBase(true);
InitExtFS();
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == bonus) { // setup bonus drive
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & (DRV_BONUS|DRV_IMAGE)))
clipboard->n_entries = 0; // remove bonus drive clipboard entries
if (!SetupBonusDrive()) ShowPrompt(false, "Setup failed!");
ClearScreenF(true, true, COLOR_STD_BG);
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == multi) { // switch EmuNAND offset
while (ShowPrompt(true, "Current EmuNAND offset is %06X.\nSwitch to next offset?", GetEmuNandBase())) {
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_EMUNAND))
clipboard->n_entries = 0; // remove EmuNAND clipboard entries
DismountDriveType(DRV_EMUNAND);
AutoEmuNandBase(false);
InitExtFS();
}
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == bsupport) { // build support files
bool tik_enc_sys = false;
bool tik_enc_emu = false;
if (BuildTitleKeyInfo(NULL, false, false) == 0) {
ShowString("Building " TIKDB_NAME_ENC "...");
tik_enc_sys = (BuildTitleKeyInfo("1:/dbs/ticket.db", false, false) == 0);
tik_enc_emu = (BuildTitleKeyInfo("4:/dbs/ticket.db", false, false) == 0);
if (!tik_enc_sys || BuildTitleKeyInfo(NULL, false, true) != 0)
tik_enc_sys = tik_enc_emu = false;
}
bool tik_dec_sys = false;
bool tik_dec_emu = false;
if (BuildTitleKeyInfo(NULL, true, false) == 0) {
ShowString("Building " TIKDB_NAME_DEC "...");
tik_dec_sys = (BuildTitleKeyInfo("1:/dbs/ticket.db", true, false) == 0);
tik_dec_emu = (BuildTitleKeyInfo("4:/dbs/ticket.db", true, false) == 0);
if (!tik_dec_sys || BuildTitleKeyInfo(NULL, true, true) != 0)
tik_dec_sys = tik_dec_emu = false;
}
bool seed_sys = false;
bool seed_emu = false;
if (BuildSeedInfo(NULL, false) == 0) {
ShowString("Building " SEEDDB_NAME "...");
seed_sys = (BuildSeedInfo("1:", false) == 0);
seed_emu = (BuildSeedInfo("4:", false) == 0);
if (!seed_sys || BuildSeedInfo(NULL, true) != 0)
seed_sys = seed_emu = false;
}
ShowPrompt(false, "Built in " OUTPUT_PATH ":\n \n%18.18-s %s\n%18.18-s %s\n%18.18-s %s",
TIKDB_NAME_ENC, tik_enc_sys ? tik_enc_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
TIKDB_NAME_DEC, tik_dec_sys ? tik_dec_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
SEEDDB_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed");
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == hsrestore) { // restore Health & Safety
n_opt = 0;
int sys = (CheckHealthAndSafetyInject("1:") == 0) ? (int) ++n_opt : -1;
int emu = (CheckHealthAndSafetyInject("4:") == 0) ? (int) ++n_opt : -1;
if (sys > 0) optionstr[sys - 1] = "Restore H&S (SysNAND)";
if (emu > 0) optionstr[emu - 1] = "Restore H&S (EmuNAND)";
user_select = (n_opt > 1) ? ShowSelectPrompt(n_opt, optionstr, promptstr) : n_opt;
if (user_select > 0) {
InjectHealthAndSafety(NULL, (user_select == sys) ? "1:" : "4:");
GetDirContents(current_dir, current_path);
return 0;
}
}
else if (user_select == clock) { // RTC clock setter
DsTime dstime;
get_dstime(&dstime);
if (ShowRtcSetterPrompt(&dstime, "Set RTC date&time:")) {
char timestr[32];
set_dstime(&dstime);
GetTimeString(timestr, true, true);
ShowPrompt(false, "New RTC date&time is:\n%s\n \nHint: HOMEMENU time needs\nmanual adjustment after\nsetting the RTC.",
timestr);
}
return 0;
}
else if (user_select == bright) { // brightness config dialogue
s32 old_brightness, new_brightness;
if (!LoadSupportFile("gm9bright.cfg", &old_brightness, 4))
old_brightness = BRIGHTNESS_AUTOMATIC; // auto by default
new_brightness = ShowBrightnessConfig(old_brightness);
if (old_brightness != new_brightness)
SaveSupportFile("gm9bright.cfg", &new_brightness, 4);
return 0;
}
else if (user_select == calib) { // touchscreen calibration
ShowPrompt(false, "Touchscreen calibration %s!",
(ShowTouchCalibrationDialog()) ? "success" : "failed");
return 0;
}
else if (user_select == sysinfo) { // Myria's system info
char* sysinfo_txt = (char*) malloc(STD_BUFFER_SIZE);
if (!sysinfo_txt) return 1;
MyriaSysinfo(sysinfo_txt);
MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, STD_BUFFER_SIZE), 1, false);
free(sysinfo_txt);
return 0;
}
else if (user_select == readme) { // Display GodMode9 readme
u64 README_md_size;
char* README_md = FindVTarFileInfo(VRAM0_README_MD, &README_md_size);
MemToCViewer(README_md, README_md_size, "GodMode9 ReadMe Table of Contents");
return 0;
} else return 1;
return HomeMoreMenu(current_path);
}
u32 GodMode(int entrypoint) {
const u32 quick_stp = (MAIN_SCREEN == TOP_SCREEN) ? 20 : 19;
u32 exit_mode = GODMODE_EXIT_POWEROFF;
char current_path[256] = { 0x00 };
u32 cursor = 0;
u32 scroll = 0;
int mark_next = -1;
u32 last_write_perm = GetWritePermissions();
u32 last_clipboard_size = 0;
bool bootloader = IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT);
bool bootmenu = bootloader && (BOOTMENU_KEY != BUTTON_START) && CheckButton(BOOTMENU_KEY);
bool godmode9 = !bootloader;
// FIRM from FCRAM handling
FirmHeader* firm_in_mem = (FirmHeader*) __FIRMTMP_ADDR; // should be safe here
if (bootloader) { // check for FIRM in FCRAM, but prevent bootloops
void* addr = (void*) __FIRMRAM_ADDR;
u32 firm_size = GetFirmSize((FirmHeader*) addr);
memcpy(firm_in_mem, "NOPE", 4); // overwrite header to prevent bootloops
if (firm_size && (firm_size <= (__FIRMRAM_END - __FIRMRAM_ADDR))) {
memcpy(firm_in_mem, addr, firm_size);
memcpy(addr, "NOPE", 4); // to prevent bootloops
}
}
// get mode string for splash screen
const char* disp_mode = NULL;
if (bootloader) disp_mode = "bootloader mode\nR+LEFT for menu";
else if (!IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT)) disp_mode = "oldloader mode";
else if (entrypoint == ENTRY_NTRBOOT) disp_mode = "ntrboot mode";
else if (entrypoint == ENTRY_UNKNOWN) disp_mode = "unknown mode";
bool show_splash = true;
#ifdef SALTMODE
show_splash = !bootloader;
#endif
// init font
if (!SetFontFromPbm(NULL, 0)) return exit_mode;
// show splash screen (if enabled)
ClearScreenF(true, true, COLOR_STD_BG);
if (show_splash) SplashInit(disp_mode);
u64 timer = timer_start(); // for splash delay
InitSDCardFS();
AutoEmuNandBase(true);
InitNandCrypto(true); // (entrypoint != ENTRY_B9S);
InitExtFS();
if (!CalibrateTouchFromSupportFile())
CalibrateTouchFromFlash();
// brightness from file?
s32 brightness = -1;
if (LoadSupportFile("gm9bright.cfg", &brightness, 0x4))
SetScreenBrightness(brightness);
// custom font handling
if (CheckSupportFile("font.pbm")) {
u8* pbm = (u8*) malloc(0x10000); // arbitrary, should be enough by far
if (pbm) {
u32 pbm_size = LoadSupportFile("font.pbm", pbm, 0x10000);
if (pbm_size) SetFontFromPbm(pbm, pbm_size);
free(pbm);
}
}
// check for embedded essential backup
if (((entrypoint == ENTRY_NANDBOOT) || (entrypoint == ENTRY_B9S)) &&
!PathExist("S:/essential.exefs") && CheckGenuineNandNcsd() &&
ShowPrompt(true, "Essential files backup not found.\nCreate one now?")) {
if (EmbedEssentialBackup("S:/nand.bin") == 0) {
u32 flags = BUILD_PATH | SKIP_ALL;
PathCopy(OUTPUT_PATH, "S:/essential.exefs", &flags);
ShowPrompt(false, "Backup embedded in SysNAND\nand written to " OUTPUT_PATH ".");
}
}
// check internal clock
if (IS_UNLOCKED) { // we could actually do this on any entrypoint
DsTime dstime;
get_dstime(&dstime);
if ((DSTIMEGET(&dstime, bcd_Y) < 18) &&
ShowPrompt(true, "RTC date&time seems to be\nwrong. Set it now?") &&
ShowRtcSetterPrompt(&dstime, "Set RTC date&time:")) {
char timestr[32];
set_dstime(&dstime);
GetTimeString(timestr, true, true);
ShowPrompt(false, "New RTC date&time is:\n%s\n \nHint: HOMEMENU time needs\nmanual adjustment after\nsetting the RTC.", timestr);
}
}
// check aeskeydb.bin / key state
if ((entrypoint != ENTRY_B9S) && (CheckRecommendedKeyDb(NULL) != 0)) {
ShowPrompt(false, "WARNING:\nNot running from a boot9strap\ncompatible entrypoint. Not\neverything may work as expected.\n \nProvide the recommended\naeskeydb.bin file to make this\nwarning go away.");
}
#if defined(SALTMODE)
show_splash = bootmenu = (bootloader && CheckButton(BOOTMENU_KEY));
if (show_splash) SplashInit("saltmode");
#else // standard behaviour
bootmenu = bootmenu || (bootloader && CheckButton(BOOTMENU_KEY)); // second check for boot menu keys
#endif
while (CheckButton(BOOTPAUSE_KEY)); // don't continue while these keys are held
if (show_splash) while (timer_msec( timer ) < 1000); // show splash for at least 1 sec
// bootmenu handler
if (bootmenu) {
bootloader = false;
while (HID_ReadState() & BUTTON_ANY); // wait until no buttons are pressed
while (!bootloader && !godmode9) {
const char* optionstr[6] = { "Resume GodMode9", "Resume bootloader", "Select payload...", "Select script...",
"Poweroff system", "Reboot system" };
int user_select = ShowSelectPrompt(6, optionstr, FLAVOR " bootloader menu.\nSelect action:");
char loadpath[256];
if (user_select == 1) {
godmode9 = true;
} else if (user_select == 2) {
bootloader = true;
} else if ((user_select == 3) && (FileSelectorSupport(loadpath, "Bootloader payloads menu.\nSelect payload:", PAYLOADS_DIR, "*.firm"))) {
BootFirmHandler(loadpath, false, false);
} else if ((user_select == 4) && (FileSelectorSupport(loadpath, "Bootloader scripts menu.\nSelect script:", SCRIPTS_DIR, "*.gm9"))) {
ExecuteGM9Script(loadpath);
} else if (user_select == 5) {
exit_mode = GODMODE_EXIT_POWEROFF;
} else if (user_select == 6) {
exit_mode = GODMODE_EXIT_REBOOT;
} else if (user_select) continue;
break;
}
}
// bootloader handler
if (bootloader) {
const char* bootfirm_paths[] = { BOOTFIRM_PATHS };
if (IsBootableFirm(firm_in_mem, FIRM_MAX_SIZE)) BootFirm(firm_in_mem, "sdmc:/bootonce.firm");
for (u32 i = 0; i < sizeof(bootfirm_paths) / sizeof(char*); i++) {
BootFirmHandler(bootfirm_paths[i], false, (BOOTFIRM_TEMPS >> i) & 0x1);
}
ShowPrompt(false, "No bootable FIRM found.\nNow resuming GodMode9...");
godmode9 = true;
}
if (godmode9) {
current_dir = (DirStruct*) malloc(sizeof(DirStruct));
clipboard = (DirStruct*) malloc(sizeof(DirStruct));
panedata = (PaneData*) malloc(N_PANES * sizeof(PaneData));
if (!current_dir || !clipboard || !panedata) {
ShowPrompt(false, "Out of memory."); // just to be safe
return exit_mode;
}
GetDirContents(current_dir, "");
clipboard->n_entries = 0;
memset(panedata, 0x00, N_PANES * sizeof(PaneData));
ClearScreenF(true, true, COLOR_STD_BG); // clear splash
}
PaneData* pane = panedata;
while (godmode9) { // this is the main loop
// basic sanity checking
if (!current_dir->n_entries) { // current dir is empty -> revert to root
ShowPrompt(false, "Invalid directory object");
*current_path = '\0';
DeinitExtFS(); // deinit and...
InitExtFS(); // reinitialize extended file system
GetDirContents(current_dir, current_path);
cursor = 0;
if (!current_dir->n_entries) { // should not happen, if it does fail gracefully
ShowPrompt(false, "Invalid root directory.");
return exit_mode;
}
}
if (cursor >= current_dir->n_entries) // cursor beyond allowed range
cursor = current_dir->n_entries - 1;
int curr_drvtype = DriveType(current_path);
DirEntry* curr_entry = &(current_dir->entry[cursor]);
if ((mark_next >= 0) && (curr_entry->type != T_DOTDOT)) {
curr_entry->marked = mark_next;
mark_next = -2;
}
DrawDirContents(current_dir, cursor, &scroll);
DrawUserInterface(current_path, curr_entry, N_PANES ? pane - panedata + 1 : 0);
DrawTopBar(current_path);
// check write permissions
if (~last_write_perm & GetWritePermissions()) {
if (ShowPrompt(true, "Write permissions were changed.\nRelock them?")) SetWritePermissions(last_write_perm, false);
last_write_perm = GetWritePermissions();
continue;
}
// handle user input
u32 pad_state = InputWait(3);
bool switched = (pad_state & BUTTON_R1);
// basic navigation commands
if ((pad_state & BUTTON_A) && (curr_entry->type != T_FILE) && (curr_entry->type != T_DOTDOT)) { // for dirs
if (switched && !(DriveType(curr_entry->path) & DRV_SEARCH)) { // search directory
const char* optionstr[8] = { NULL };
char tpath[16] = { 0 };
if (!*current_path) snprintf(tpath, 15, "%s/title", curr_entry->path);
int n_opt = 0;
int srch_t = ((strncmp(curr_entry->path + 1, ":/title", 7) == 0) ||
(*tpath && PathExist(tpath))) ? ++n_opt : -1;
int srch_f = ++n_opt;
int fixcmac = (!*current_path && (strspn(curr_entry->path, "14AB") == 1)) ? ++n_opt : -1;
int dirnfo = ++n_opt;
int stdcpy = (*current_path && strncmp(current_path, OUTPUT_PATH, 256) != 0) ? ++n_opt : -1;
if (srch_t > 0) optionstr[srch_t-1] = "Search for titles";
if (srch_f > 0) optionstr[srch_f-1] = "Search for files...";
if (fixcmac > 0) optionstr[fixcmac-1] = "Fix CMACs for drive";
if (dirnfo > 0) optionstr[dirnfo-1] = (*current_path) ? "Show directory info" : "Show drive info";
if (stdcpy > 0) optionstr[stdcpy-1] = "Copy to " OUTPUT_PATH;
char namestr[32+1];
TruncateString(namestr, (*current_path) ? curr_entry->path : curr_entry->name, 32, 8);
int user_select = ShowSelectPrompt(n_opt, optionstr, "%s", namestr);
if ((user_select == srch_f) || (user_select == srch_t)) {
char searchstr[256];
snprintf(searchstr, 256, (user_select == srch_t) ? "*.tmd" : "*");
TruncateString(namestr, curr_entry->name, 20, 8);
if ((user_select == srch_t) || ShowKeyboardOrPrompt(searchstr, 256, "Search %s?\nEnter search below.", namestr)) {
SetFSSearch(searchstr, curr_entry->path, (user_select == srch_t));
snprintf(current_path, 256, "Z:");
GetDirContents(current_dir, current_path);
if (current_dir->n_entries) ShowPrompt(false, "Found %lu results.", current_dir->n_entries - 1);
cursor = 1;
scroll = 0;
}
} else if (user_select == fixcmac) {
RecursiveFixFileCmac(curr_entry->path);
ShowPrompt(false, "Fix CMACs for drive finished.");
} else if (user_select == dirnfo) {
if (DirFileAttrMenu(curr_entry->path, curr_entry->name)) {
ShowPrompt(false, "Failed to analyze %s\n",
(current_path[0] == '\0') ? "drive" : "dir"
);
}
} else if (user_select == stdcpy) {
StandardCopy(&cursor, &scroll);
}
} else { // one level up
u32 user_select = 1;
if (curr_drvtype & DRV_SEARCH) { // special menu for search drive
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, "%s", pathstr);
}
if (user_select) {
strncpy(current_path, curr_entry->path, 256);
current_path[255] = '\0';
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;
}
}
} else if ((pad_state & BUTTON_A) && (curr_entry->type == T_FILE)) { // process a file
FileHandlerMenu(current_path, &cursor, &scroll, &pane); // processed externally
} else if (*current_path && ((pad_state & BUTTON_B) || // one level down
((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;
}
}
} else if (switched && (pad_state & BUTTON_B)) { // unmount SD card
if (!CheckSDMountState()) {
while (!InitSDCardFS() &&
ShowPrompt(true, "Initialising SD card failed! Retry?"));
} else {
DeinitSDCardFS();
if (clipboard->n_entries && !PathExist(clipboard->entry[0].path))
clipboard->n_entries = 0; // remove SD clipboard entries
}
ClearScreenF(true, true, COLOR_STD_BG);
AutoEmuNandBase(true);
InitExtFS();
GetDirContents(current_dir, current_path);
if (cursor >= current_dir->n_entries) cursor = 0;
} else if (!switched && (pad_state & BUTTON_DOWN) && (cursor + 1 < current_dir->n_entries)) { // cursor down
if (pad_state & BUTTON_L1) mark_next = curr_entry->marked;
cursor++;
} else if (!switched && (pad_state & BUTTON_UP) && cursor) { // cursor up
if (pad_state & BUTTON_L1) mark_next = curr_entry->marked;
cursor--;
} 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);
} else if (switched && (pad_state & BUTTON_DOWN)) { // force reload file list
GetDirContents(current_dir, current_path);
ClearScreenF(true, true, COLOR_STD_BG);
} else if ((pad_state & BUTTON_RIGHT) && !(pad_state & BUTTON_L1)) { // cursor down (quick)
cursor += quick_stp;
} else if ((pad_state & BUTTON_LEFT) && !(pad_state & BUTTON_L1)) { // cursor up (quick)
cursor = (cursor >= quick_stp) ? cursor - quick_stp : 0;
} else if ((pad_state & BUTTON_RIGHT) && *current_path) { // mark all entries
for (u32 c = 1; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 1;
mark_next = 1;
} else if ((pad_state & BUTTON_LEFT) && *current_path) { // unmark all entries
for (u32 c = 1; c < current_dir->n_entries; c++) current_dir->entry[c].marked = 0;
mark_next = 0;
} else if (switched && (pad_state & BUTTON_L1)) { // switched L -> screenshot
// this is handled in hid.h
} 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;
} else if (pad_state & BUTTON_SELECT) { // clear/restore clipboard
clipboard->n_entries = (clipboard->n_entries > 0) ? 0 : last_clipboard_size;
}
// highly specific commands
if (!*current_path) { // in the root folder...
if (switched && (pad_state & BUTTON_X)) { // unmount image
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
clipboard->n_entries = 0; // remove last mounted image clipboard entries
InitImgFS(NULL);
ClearScreenF(false, true, COLOR_STD_BG);
GetDirContents(current_dir, current_path);
} else if (switched && (pad_state & BUTTON_Y)) {
SetWritePermissions(PERM_BASE, false);
last_write_perm = GetWritePermissions();
ClearScreenF(false, true, COLOR_STD_BG);
}
} else if (!switched) { // standard unswitched command set
if ((curr_drvtype & DRV_VIRTUAL) && (pad_state & BUTTON_X) && (*current_path != 'T')) {
ShowPrompt(false, "Not allowed in virtual path");
} else if (pad_state & BUTTON_X) { // delete a file
u32 n_marked = 0;
if (curr_entry->marked) {
for (u32 c = 0; c < current_dir->n_entries; c++)
if (current_dir->entry[c].marked) n_marked++;
}
if (n_marked) {
if (ShowPrompt(true, "Delete %u path(s)?", n_marked)) {
u32 n_errors = 0;
ShowString("Deleting files, please wait...");
for (u32 c = 0; c < current_dir->n_entries; c++)
if (current_dir->entry[c].marked && !PathDelete(current_dir->entry[c].path))
n_errors++;
ClearScreenF(true, false, COLOR_STD_BG);
if (n_errors) ShowPrompt(false, "Failed deleting %u/%u path(s)", n_errors, n_marked);
}
} else if (curr_entry->type != T_DOTDOT) {
char namestr[36+1];
TruncateString(namestr, curr_entry->name, 28, 12);
if (ShowPrompt(true, "Delete \"%s\"?", namestr)) {
ShowString("Deleting files, please wait...");
if (!PathDelete(curr_entry->path))
ShowPrompt(false, "Failed deleting:\n%s", namestr);
ClearScreenF(true, false, COLOR_STD_BG);
}
}
GetDirContents(current_dir, current_path);
} 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;
DirEntryCpy(&(clipboard->entry[clipboard->n_entries]), &(current_dir->entry[c]));
clipboard->n_entries++;
}
}
if ((clipboard->n_entries == 0) && (curr_entry->type != T_DOTDOT)) {
DirEntryCpy(&(clipboard->entry[0]), curr_entry);
clipboard->n_entries = 1;
}
if (clipboard->n_entries)
last_clipboard_size = clipboard->n_entries;
} else if ((curr_drvtype & DRV_SEARCH) && (pad_state & BUTTON_Y)) {
ShowPrompt(false, "Not allowed in search drive");
} else if ((curr_drvtype & DRV_GAME) && (pad_state & BUTTON_Y)) {
ShowPrompt(false, "Not allowed in virtual game path");
} else if ((curr_drvtype & DRV_XORPAD) && (pad_state & BUTTON_Y)) {
ShowPrompt(false, "Not allowed in XORpad drive");
} 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
const char* optionstr[2] = { "Copy path(s)", "Move path(s)" };
char promptstr[64];
u32 flags = 0;
u32 user_select;
if (clipboard->n_entries == 1) {
char namestr[20+1];
TruncateString(namestr, clipboard->entry[0].name, 20, 12);
snprintf(promptstr, 64, "Paste \"%s\" here?", namestr);
} else snprintf(promptstr, 64, "Paste %lu paths here?", clipboard->n_entries);
user_select = ((DriveType(clipboard->entry[0].path) & curr_drvtype & DRV_STDFAT)) ?
ShowSelectPrompt(2, optionstr, "%s", promptstr) : (ShowPrompt(true, "%s", promptstr) ? 1 : 0);
if (user_select) {
for (u32 c = 0; c < clipboard->n_entries; c++) {
char namestr[36+1];
TruncateString(namestr, clipboard->entry[c].name, 36, 12);
flags &= ~ASK_ALL;
if (c < clipboard->n_entries - 1) flags |= ASK_ALL;
if ((user_select == 1) && !PathCopy(current_path, clipboard->entry[c].path, &flags)) {
if (c + 1 < clipboard->n_entries) {
if (!ShowPrompt(true, "Failed copying path:\n%s\nProcess remaining?", namestr)) break;
} else ShowPrompt(false, "Failed copying path:\n%s", namestr);
} else if ((user_select == 2) && !PathMove(current_path, clipboard->entry[c].path, &flags)) {
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);
}
}
clipboard->n_entries = 0;
GetDirContents(current_dir, current_path);
}
ClearScreenF(true, false, COLOR_STD_BG);
}
} else { // switched command set
if ((curr_drvtype & DRV_VIRTUAL) && (pad_state & (BUTTON_X|BUTTON_Y))) {
ShowPrompt(false, "Not allowed in virtual path");
} else if ((curr_drvtype & DRV_ALIAS) && (pad_state & (BUTTON_X))) {
ShowPrompt(false, "Not allowed in alias path");
} else if ((pad_state & BUTTON_X) && (curr_entry->type != T_DOTDOT)) { // rename a file
char newname[256];
char namestr[20+1];
TruncateString(namestr, curr_entry->name, 20, 12);
snprintf(newname, 255, "%s", curr_entry->name);
if (ShowKeyboardOrPrompt(newname, 256, "Rename %s?\nEnter new name below.", namestr)) {
if (!PathRename(curr_entry->path, newname))
ShowPrompt(false, "Failed renaming path:\n%s", namestr);
else {
GetDirContents(current_dir, current_path);
for (cursor = (current_dir->n_entries) ? current_dir->n_entries - 1 : 0;
(cursor > 1) && (strncmp(current_dir->entry[cursor].name, newname, 256) != 0); cursor--);
}
}
} else if (pad_state & BUTTON_Y) { // create an entry
const char* optionstr[] = { "Create a folder", "Create a dummy file" };
u32 type = ShowSelectPrompt(2, optionstr, "Create a new entry here?\nSelect type.");
if (type) {
const char* typestr = (type == 1) ? "folder" : (type == 2) ? "file" : NULL;
char ename[256];
u64 fsize = 0;
snprintf(ename, 255, (type == 1) ? "newdir" : "dummy.bin");
if ((ShowKeyboardOrPrompt(ename, 256, "Create a new %s here?\nEnter name below.", typestr)) &&
((type != 2) || ((fsize = ShowNumberPrompt(0, "Create a new %s here?\nEnter file size below.", typestr)) != (u64) -1))) {
if (((type == 1) && !DirCreate(current_path, ename)) ||
((type == 2) && !FileCreateDummy(current_path, ename, fsize))) {
char namestr[36+1];
TruncateString(namestr, ename, 36, 12);
ShowPrompt(false, "Failed creating %s:\n%s", typestr, namestr);
} else {
GetDirContents(current_dir, current_path);
for (cursor = (current_dir->n_entries) ? current_dir->n_entries - 1 : 0;
(cursor > 1) && (strncmp(current_dir->entry[cursor].name, ename, 256) != 0); cursor--);
}
}
}
}
}
if (pad_state & BUTTON_START) {
exit_mode = (switched || (pad_state & BUTTON_LEFT)) ? GODMODE_EXIT_POWEROFF : GODMODE_EXIT_REBOOT;
break;
} else if (pad_state & (BUTTON_HOME|BUTTON_POWER)) { // Home menu
const char* optionstr[8];
const char* buttonstr = (pad_state & BUTTON_HOME) ? "HOME" : "POWER";
u32 n_opt = 0;
int poweroff = ++n_opt;
int reboot = ++n_opt;
int brick = (HID_ReadState() & BUTTON_R1) ? ++n_opt : 0;
int scripts = ++n_opt;
int payloads = ++n_opt;
int more = ++n_opt;
if (poweroff > 0) optionstr[poweroff - 1] = "Poweroff system";
if (reboot > 0) optionstr[reboot - 1] = "Reboot system";
if (brick > 0) optionstr[brick - 1] = "Brick my 3DS";
if (scripts > 0) optionstr[scripts - 1] = "Scripts...";
if (payloads > 0) optionstr[payloads - 1] = "Payloads...";
if (more > 0) optionstr[more - 1] = "More...";
int user_select = 0;
while ((user_select = ShowSelectPrompt(n_opt, optionstr, "%s button pressed.\nSelect action:", buttonstr)) &&
(user_select != poweroff) && (user_select != reboot)) {
char loadpath[256];
if ((user_select == more) && (HomeMoreMenu(current_path) == 0)) break; // more... menu
else if (user_select == scripts) {
if (!CheckSupportDir(SCRIPTS_DIR)) {
ShowPrompt(false, "Scripts directory not found.\n(default path: 0:/gm9/" SCRIPTS_DIR ")");
} else if (FileSelectorSupport(loadpath, "HOME scripts... menu.\nSelect script:", SCRIPTS_DIR, "*.gm9")) {
ExecuteGM9Script(loadpath);
GetDirContents(current_dir, current_path);
ClearScreenF(true, true, COLOR_STD_BG);
break;
}
} else if (user_select == payloads) {
if (!CheckSupportDir(PAYLOADS_DIR)) ShowPrompt(false, "Payloads directory not found.\n(default path: 0:/gm9/" PAYLOADS_DIR ")");
else if (FileSelectorSupport(loadpath, "HOME payloads... menu.\nSelect payload:", PAYLOADS_DIR, "*.firm"))
BootFirmHandler(loadpath, false, false);
} else if (user_select == brick) {
Paint9(); // hiding a secret here
ClearScreenF(true, true, COLOR_STD_BG);
break;
}
}
if (user_select == poweroff) {
exit_mode = GODMODE_EXIT_POWEROFF;
break;
} else if (user_select == reboot) {
exit_mode = GODMODE_EXIT_REBOOT;
break;
}
} else if (pad_state & (CART_INSERT|CART_EJECT)) {
if (!InitVCartDrive() && (pad_state & CART_INSERT) &&
(curr_drvtype & DRV_CART)) // reinit virtual cart drive
ShowPrompt(false, "Cart init failed!");
if (!(*current_path) || (curr_drvtype & DRV_CART))
GetDirContents(current_dir, current_path); // refresh dir contents
} else if (pad_state & SD_INSERT) {
while (!InitSDCardFS() && ShowPrompt(true, "Initialising SD card failed! Retry?"));
ClearScreenF(true, true, COLOR_STD_BG);
AutoEmuNandBase(true);
InitExtFS();
GetDirContents(current_dir, current_path);
} else if ((pad_state & SD_EJECT) && CheckSDMountState()) {
ShowPrompt(false, "!Unexpected SD card removal!\n \nTo prevent data loss, unmount\nbefore ejecting the SD card.");
DeinitExtFS();
DeinitSDCardFS();
InitExtFS();
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
GetDirContents(current_dir, current_path);
}
}
DeinitExtFS();
DeinitSDCardFS();
if (current_dir) free(current_dir);
if (clipboard) free(clipboard);
if (panedata) free(panedata);
return exit_mode;
}
#else
u32 ScriptRunner(int entrypoint) {
// init font and show splash
if (!SetFontFromPbm(NULL, 0)) return GODMODE_EXIT_POWEROFF;
SplashInit("scriptrunner mode");
u64 timer = timer_start();
InitSDCardFS();
AutoEmuNandBase(true);
InitNandCrypto(entrypoint != ENTRY_B9S);
InitExtFS();
if (!CalibrateTouchFromSupportFile())
CalibrateTouchFromFlash();
// brightness from file?
s32 brightness = -1;
if (LoadSupportFile("gm9bright.cfg", &brightness, 0x4))
SetScreenBrightness(brightness);
while (CheckButton(BOOTPAUSE_KEY)); // don't continue while these keys are held
while (timer_msec( timer ) < 500); // show splash for at least 0.5 sec
// you didn't really install a scriptrunner to NAND, did you?
if (IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT))
BootFirmHandler("0:/iderped.firm", false, false);
if (PathExist("V:/" VRAM0_AUTORUN_GM9)) {
ClearScreenF(true, true, COLOR_STD_BG); // clear splash
ExecuteGM9Script("V:/" VRAM0_AUTORUN_GM9);
} else if (PathExist("V:/" VRAM0_SCRIPTS)) {
char loadpath[256];
if (FileSelector(loadpath, FLAVOR " scripts menu.\nSelect script:", "V:/" VRAM0_SCRIPTS, "*.gm9", HIDE_EXT, false))
ExecuteGM9Script(loadpath);
} else ShowPrompt(false, "Compiled as script autorunner\nbut no script provided.\n \nDerp!");
// deinit
DeinitExtFS();
DeinitSDCardFS();
return GODMODE_EXIT_REBOOT;
}
#endif