/*
* This file is part of fastboot 3DS
* Copyright (C) 2017 derrek, profi200, d0k3
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include "types.h"
#include "arm11/hardware/hid.h"
#include "arm11/hardware/mcu.h"
#include "arm11/menu/battery.h"
#include "arm11/menu/bootinfo.h"
#include "arm11/menu/bootslot.h"
#include "arm11/menu/menu.h"
#include "arm11/menu/menu_util.h"
#include "arm11/menu/menu_color.h"
#include "arm11/config.h"
#include "arm11/console.h"
#include "arm11/debug.h"
#include "arm11/fmt.h"
#include "fs.h" // for drive names
static BatteryState battery;
void menuBuildDescString(char* desc, u32 flags, u32 index, const char* desc_raw)
{
char* desc_ptr = desc;
// copy raw description
desc_ptr += ee_sprintf(desc_ptr, desc_raw);
// handle flags
u32 slot = -1;
if (flags & MENU_FLAG_SLOTS)
{
slot = index;
}
else
{
MENU_GET_FLAGSLOT(slot, flags);
slot--;
}
// boot mode description
if ((flags & MENU_FLAG_BOOTMODE) && (index == N_BOOTSLOTS))
{
if (configDataExist(KBootMode))
{
char* modestr = (char*) configCopyText(KBootMode);
if (!modestr) panicMsg("Config error");
desc_ptr += ee_sprintf(desc_ptr, "\n \ncurrent: %s", modestr);
free (modestr);
}
else desc_ptr += ee_sprintf(desc_ptr, "\n \ncurrent: - not set up -");
}
// boot slot description
else if (slot < N_BOOTSLOTS)
{
// get strings for path and keycombo
char slot_path_store[40+1];
char* slot_path = NULL;
char* keycombo = NULL;
if(configDataExist(KBootOption1 + slot))
{
slot_path = slot_path_store;
truncateString(slot_path, (char*) configGetData(KBootOption1 + slot), 52, 8);
}
if(configDataExist(KBootOption1Buttons + slot))
keycombo = (char*) configCopyText(KBootOption1Buttons + slot);
// write description to desc string
desc_ptr += ee_sprintf(desc_ptr, "\n \npath: %s\nboot: %s",
slot_path ? slot_path : "- slot disabled -",
!slot_path ? "- slot disabled -" : keycombo ? keycombo : "bootloader (auto)");
if (keycombo) free(keycombo);
}
// wordwrap the whole string
stringWordWrap(desc, WORDWRAP_WIDTH);
}
void menuShowDesc(MenuInfo* curr_menu, PrintConsole* desc_con, u32 index)
{
// select and clear description console
consoleSelect(desc_con);
consoleClear();
// print title at the top
#ifdef NDEBUG
const char* title = "uwuboot :3 " VERS_STRING;
#else
const char* title = "uwuboot :3 (debug me senpai) (cuter!!)";
#endif
consoleSetCursor(desc_con, (desc_con->windowWidth - strlen(title)) >> 1, 0);
ee_printf(ESC_SCHEME_ACCENT0 "%s" ESC_RESET, title);
u8 rtc[8];
static const char *funny[] = {
"im so funny", "tetoris is the best song ever", "join splatfestival", "why did i do this to myself", "when is RNEX gonna be done", "use starlight, not luma",
"scrappy wappy~~~", "i fucking hate nintendo", "SPLATTACK", "use uwuboot", "owo", "i coded this in visual studio code at my grandma", "HEEEEELP, HEEELP MEEE",
"no idea what i'm doing rn", "my nose is full of the fucks i give", "fuck you eno", "fuck you daris", "euone my name is mr franc", "mr phillip ahh", "alright settle down settle down"
};
MCU_getRTCTime(rtc);
int randomnum = rtc[0] % 20;
// get bootinfo
const char* mount_paths[] = { FS_DRIVE_NAMES };
bootInfo bootinfo;
getBootInfo(&bootinfo);
// print bootinfo
consoleSetCursor(desc_con, 0, 3);
ee_printf("this 2DS belongs to Andrea Toska\n");
ee_printf("call if lost: +355 68 669 3856\n");
ee_printf("Fucks I give: %s%lu%s" ESC_RESET "\n\n",
((battery.percent <= 20) && !battery.charging) ? ESC_SCHEME_BAD : ESC_SCHEME_GOOD,
battery.percent, (battery.charging) ? "+" : "");
ee_printf("%s\n", funny[randomnum]);
// print config
u32 conw = desc_con->windowWidth;
u32 curY = 3;
// print config location
consoleSetCursor(desc_con, conw - 16, curY++);
if(!configIsLoaded())
{
ee_printf("Config not found");
}
else
{
FsDevice loc_conf = configGetStorageLocation();
ee_printf("Config from %s", (loc_conf == FS_DEVICE_SDMC) ? "SDMC" : "NAND");
}
// print boot mode
consoleSetCursor(desc_con, conw - 16, curY++);
if(configDataExist(KBootMode))
{
char* bootmode = configCopyText(KBootMode);
ee_printf("%7.7s bootmode", bootmode);
if (bootmode) free(bootmode);
}
else
{
ee_printf("bootmode not set");
}
// print slot config
curY++;
for(u32 i = 0; i < N_BOOTSLOTS; i++)
{
consoleSetCursor(desc_con, conw - 16, curY++);
ee_printf(ESC_SCHEME_WEAK "slot %lu: " ESC_RESET, i + 1);
if(configDataExist(KBootOption1 + i))
ee_printf((configDataExist(KBootOption1Buttons + i)) ? "keycombo" : "autoboot");
else ee_printf(" - ");
}
// reboot slot
u32 prevSlot = readStoredBootslot();
consoleSetCursor(desc_con, conw - 16, curY++);
ee_printf(ESC_SCHEME_WEAK "reboot: " ESC_RESET);
if ((prevSlot >= 1) && (prevSlot <= N_BOOTSLOTS))
ee_printf(" slot %lu ", prevSlot);
else
ee_printf("autoboot");
// print dev mode config
curY++;
if(!configDevModeEnabled())
{
consoleSetCursor(desc_con, conw - 14, curY);
ee_printf(ESC_SCHEME_BAD "debug me daddy" ESC_RESET);
}
// get description, check if available
MenuEntry* entry = &(curr_menu->entries[index]);
char* name = entry->name;
char* desc = entry->desc;
// done if no description available
if (!name || !desc)
return;
// build description string
char desc_ww[512];
menuBuildDescString(desc_ww, curr_menu->flags, index, desc);
// get width, height
int desc_width = stringGetWidth(desc_ww);
int desc_height = stringGetHeight(desc_ww);
desc_width = (desc_width > desc_con->windowWidth) ? desc_con->windowWidth : desc_width;
desc_height = (desc_height > desc_con->windowHeight) ? desc_con->windowHeight : desc_height;
// write to console
int desc_x = (desc_con->windowWidth - desc_width) >> 1;
int desc_y = (desc_con->windowHeight - 1 - desc_height);
consoleSetCursor(desc_con, desc_x, desc_y++);
ee_printf(ESC_SCHEME_ACCENT1 "%s:\n" ESC_RESET, name);
ee_printf(ESC_SCHEME_WEAK);
for (char* str = strtok(desc_ww, "\n"); str != NULL; str = strtok(NULL, "\n"))
{
consoleSetCursor(desc_con, desc_x, desc_y++);
ee_printf(str);
}
ee_printf(ESC_RESET);
}
/**
* @brief Draws the menu to the given console.
* @param curr_menu A struct containing info about the current menu.
* @param menu_con Console that the menu is displayed on.
* @param index Current placement of the cursor.
*/
void menuDraw(MenuInfo* curr_menu, PrintConsole* menu_con, u32 index)
{
// const u32 menu_block_height = curr_menu->n_entries + 5; // +5 for title and button instructions
int menu_x = (menu_con->windowWidth - MENU_WIDTH) >> 1;
// int menu_y = MENU_DISP_Y + ((menu_con->windowHeight - menu_block_height) >> 1);
int menu_y = MENU_OFFSET_TITLE;
int menu_preset = 0xFF;
// get menu preset
if (curr_menu->preset)
menu_preset = curr_menu->preset();
// select menu console
consoleSelect(menu_con);
consoleClear();
// menu title
consoleSetCursor(menu_con, menu_x, menu_y++);
ee_printf(ESC_SCHEME_ACCENT1 "%s" ESC_RESET, curr_menu->name);
menu_y++;
// menu entries
for (u32 i = 0; i < curr_menu->n_entries; i++)
{
MenuEntry* entry = &(curr_menu->entries[i]);
char* name = entry->name;
bool is_selected = (i == index);
char symbol = ' ';
if (curr_menu->flags & MENU_FLAG_CONFIG)
symbol = ((menu_preset >> i) & 0x1) ? '\xFB' : ' ';
else
symbol = ((menu_preset >> i) & 0x1) ? '+' : '-';
consoleSetCursor(menu_con, menu_x, menu_y++);
// ee_printf(((menu_preset >> i) & 0x1) ? ESC_SCHEME_STD : ESC_SCHEME_WEAK);
ee_printf(ESC_SCHEME_STD);
if (is_selected) ee_printf(ESC_INVERT);
ee_printf("[%c] %-*.*s", symbol, MENU_WIDTH-4, MENU_WIDTH-4, name);
ee_printf(ESC_RESET);
}
// button instructions
menu_y = MENU_OFFSET_BUTTONS;
consoleSetCursor(menu_con, menu_x, menu_y++);
ee_printf(ESC_SCHEME_WEAK "%-*.*s" ESC_RESET, MENU_WIDTH, MENU_WIDTH, "[A]:Choose [B]:Back");
}
/**
* @brief Processes and displays the menu and user input.
* @param menu_con Console that the menu is displayed on.
* @param desc_con Console that the description is displayed on.
* @param info A complete description of the menu.
*/
u32 menuProcess(PrintConsole* menu_con, PrintConsole* desc_con, MenuInfo* info)
{
MenuInfo* curr_menu = info;
MenuInfo* last_menu = NULL;
MenuInfo* prev_menu[MENU_MAX_DEPTH];
u32 prev_index[MENU_MAX_DEPTH];
u32 menu_lvl = 0;
u32 index = 0;
u32 last_index = (u32) -1;
u64 n_vblanks = 0;
getBatteryState(&battery);
// main menu processing loop
u32 menu_ret = MENU_OK;
while (menu_ret < MENU_RET_CONTINUE)
{
// handle power button
if(hidGetExtraKeys(0) & (KEY_POWER | KEY_POWER_HELD))
break; // deinits & poweroff outside of this function
// handle battery state
if ((++n_vblanks % 250) == 0)
getBatteryState(&battery);
// update menu and description (on demand)
if ((index != last_index) || (curr_menu != last_menu) || (n_vblanks % 250 == 0)) {
menuDraw(curr_menu, menu_con, index);
menuShowDesc(curr_menu, desc_con, index);
last_index = index;
last_menu = curr_menu;
updateScreens(); // update screens (VBlank included)
} else GFX_waitForEvent(GFX_EVENT_PDC0, true); // VBlank
hidScanInput();
const u32 kDown = hidKeysDown();
const u32 extraKeys = hidGetExtraKeys(0);
// const u32 kHeld = hidKeysHeld();
if (extraKeys & KEY_SHELL)
{
sleepmode();
}
else if ((kDown & KEY_A) && (curr_menu->entries[index].function == NULL))
{
u32 next_menu_idx = curr_menu->entries[index].param;
// store previous menu and index for return
if (menu_lvl < MENU_MAX_DEPTH)
{
prev_menu[menu_lvl] = curr_menu;
prev_index[menu_lvl] = index;
menu_lvl++;
}
// enter submenu
curr_menu = info + next_menu_idx;
index = 0;
}
else if (kDown & KEY_A)
{
// call menu entry function
MenuEntry* entry = &(curr_menu->entries[index]);
menu_ret = (*(entry->function))(desc_con, menu_con, entry->param);
// wait until HOME released
do
{
GFX_waitForEvent(GFX_EVENT_PDC0, true);
hidScanInput();
} while (hidGetExtraKeys(0) & KEY_HOME);
// force redraw (somewhat hacky)
last_menu = NULL;
}
else if ((kDown & KEY_B) && (menu_lvl > 0))
{
// return to previous menu
menu_lvl--;
curr_menu = prev_menu[menu_lvl];
index = prev_index[menu_lvl];
}
else if ((extraKeys & KEY_HOME) && (menu_lvl > 0))
{
// return to HOME menu
menu_lvl = 0;
curr_menu = info;
index = 0;
}
else if (kDown & KEY_DDOWN)
{
// cursor down
index = (index == curr_menu->n_entries - 1) ? 0 : index + 1;
}
else if (kDown & KEY_DUP)
{
// cursor up
index = (index == 0) ? curr_menu->n_entries - 1 : index - 1;
}
}
return menu_ret;
}