mirror of
https://github.com/GaryOderNichts/recovery_menu.git
synced 2025-06-19 03:05:32 -04:00
425 lines
13 KiB
C
425 lines
13 KiB
C
/*
|
|
* Copyright (C) 2022 GaryOderNichts
|
|
*
|
|
* 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 2 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "menu.h"
|
|
|
|
#include "imports.h"
|
|
#include "gfx.h"
|
|
#include "utils.h"
|
|
#include "fsa.h"
|
|
#include "socket.h"
|
|
#include "netconf.h"
|
|
#include "mcp_misc.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "options/options.h"
|
|
|
|
static void option_Shutdown(void);
|
|
|
|
extern int ppcHeartBeatThreadId;
|
|
|
|
int fsaHandle = -1;
|
|
|
|
static const Menu mainMenuOptions[] = {
|
|
{"Set Coldboot Title", {.callback = option_SetColdbootTitle}},
|
|
{"Dump Syslogs", {.callback = option_DumpSyslogs}},
|
|
{"Dump OTP + SEEPROM", {.callback = option_DumpOtpAndSeeprom}},
|
|
{"Load Network Configuration", {.callback = option_LoadNetConf}},
|
|
{"Start wupserver", {.callback = option_StartWupserver}},
|
|
{"Pair Gamepad", {.callback = option_PairDRC}},
|
|
{"Install WUP", {.callback = option_InstallWUP}},
|
|
{"Edit Parental Controls", {.callback = option_EditParental}},
|
|
{"Debug System Region", {.callback = option_DebugSystemRegion}},
|
|
{"System Information", {.callback = option_SystemInformation}},
|
|
{"Submit System Data", {.callback = option_SubmitSystemData}},
|
|
{"Load BOOT1 payload", {.callback = option_LoadBoot1Payload}},
|
|
{"Shutdown", {.callback = option_Shutdown}},
|
|
};
|
|
|
|
/**
|
|
* Draw the top bar.
|
|
* @param title Title
|
|
*/
|
|
void drawTopBar(const char* title)
|
|
{
|
|
// draw top bar
|
|
gfx_set_font_color(COLOR_PRIMARY);
|
|
gfx_print((SCREEN_WIDTH / 2) + (gfx_get_text_width(title) / 2), 8, 1, title);
|
|
gfx_draw_rect_filled(8, 16 + 8, SCREEN_WIDTH - 8 * 2, 2, COLOR_SECONDARY);
|
|
}
|
|
|
|
static void drawBars(const char* title)
|
|
{
|
|
drawTopBar(title);
|
|
|
|
// draw bottom bar
|
|
gfx_draw_rect_filled(8, SCREEN_HEIGHT - (16 + 8 + 2), SCREEN_WIDTH - 8 * 2, 2, COLOR_SECONDARY);
|
|
gfx_print(16, SCREEN_HEIGHT - CHAR_SIZE_DRC_Y - 4, 0, "EJECT: Navigate");
|
|
gfx_print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - CHAR_SIZE_DRC_Y - 4, GfxPrintFlag_AlignRight, "POWER: Choose");
|
|
}
|
|
|
|
/**
|
|
* Draw a single menu item. Called by drawMenu().
|
|
* @param menuItem Menu item
|
|
* @param selected If non-zero, item is selected
|
|
* @param flags
|
|
* @param x
|
|
* @param y
|
|
*/
|
|
static void drawMenuItem(const Menu* menuItem, int selected, uint32_t flags, uint32_t x, uint32_t y)
|
|
{
|
|
const char *text;
|
|
char buf[64];
|
|
if (!(flags & MenuFlag_ShowTID)) {
|
|
text = menuItem->name;
|
|
} else {
|
|
if (menuItem->tid != 0) {
|
|
snprintf(buf, sizeof(buf), "%s - %08lx-%08lx",
|
|
menuItem->name, (uint32_t)(menuItem->tid >> 32), (uint32_t)(menuItem->tid & 0xFFFFFFFF));
|
|
text = buf;
|
|
} else {
|
|
text = menuItem->name;
|
|
}
|
|
}
|
|
|
|
gfx_draw_rect_filled(x - 1, y - 1,
|
|
gfx_get_text_width(text) + 2, CHAR_SIZE_DRC_Y + 2,
|
|
selected ? COLOR_PRIMARY : COLOR_BACKGROUND);
|
|
|
|
gfx_set_font_color(selected ? COLOR_BACKGROUND : COLOR_PRIMARY);
|
|
gfx_print(x, y, 0, text);
|
|
}
|
|
|
|
/**
|
|
* Draw a menu and wait for the user to select an option.
|
|
* @param title Menu title
|
|
* @param menu Array of menu entries
|
|
* @param count Number of menu entries
|
|
* @param selected Initial selected item index
|
|
* @param flags
|
|
* @param x
|
|
* @param y
|
|
* @return Selected menu entry index
|
|
*/
|
|
int drawMenu(const char* title, const Menu* menu, size_t count,
|
|
int selected, uint32_t flags, uint32_t x, uint32_t y)
|
|
{
|
|
int redraw = 1;
|
|
int prev_selected = -1;
|
|
if (selected < 0 || selected >= count)
|
|
selected = 0;
|
|
|
|
// draw the full menu
|
|
if (!(flags & MenuFlag_NoClearScreen)) {
|
|
gfx_clear(COLOR_BACKGROUND);
|
|
}
|
|
int index = y;
|
|
for (int i = 0; i < count; i++) {
|
|
drawMenuItem(&menu[i], selected == i, flags, x, index);
|
|
index += CHAR_SIZE_DRC_Y + 4;
|
|
}
|
|
|
|
if (flags & MenuFlag_ShowGitHubLink) {
|
|
static const int ypos = SCREEN_HEIGHT - (CHAR_SIZE_DRC_Y * 3);
|
|
gfx_set_font_color(COLOR_PRIMARY);
|
|
static const char link_prefix[] = "Check out the GitHub repository at:";
|
|
gfx_print(16, ypos, 0, link_prefix);
|
|
static const int xpos = 16 + CHAR_SIZE_DRC_X * sizeof(link_prefix);
|
|
gfx_set_font_color(COLOR_LINK);
|
|
gfx_print(xpos, ypos, GfxPrintFlag_Underline, "https://github.com/GaryOderNichts/recovery_menu");
|
|
}
|
|
|
|
uint8_t cur_flag = 0;
|
|
uint8_t flag = 0;
|
|
while (1) {
|
|
SMC_ReadSystemEventFlag(&flag);
|
|
if (cur_flag != flag) {
|
|
if (flag & SYSTEM_EVENT_FLAG_EJECT_BUTTON) {
|
|
setNotificationLED(NOTIF_LED_OFF, 250);
|
|
prev_selected = selected;
|
|
selected++;
|
|
if (selected == count)
|
|
selected = 0;
|
|
redraw = 1;
|
|
} else if (flag & SYSTEM_EVENT_FLAG_POWER_BUTTON) {
|
|
setNotificationLED(NOTIF_LED_OFF, 250);
|
|
return selected;
|
|
}
|
|
cur_flag = flag;
|
|
}
|
|
|
|
if (redraw) {
|
|
if (prev_selected != selected) {
|
|
// Redraw the previously selected menu item.
|
|
if (prev_selected >= 0) {
|
|
index = y + ((CHAR_SIZE_DRC_Y + 4) * prev_selected);
|
|
drawMenuItem(&menu[prev_selected], 0, flags, x, index);
|
|
}
|
|
|
|
// Redraw the selected item.
|
|
index = y + ((CHAR_SIZE_DRC_Y + 4) * selected);
|
|
drawMenuItem(&menu[selected], 1, flags, x, index);
|
|
}
|
|
|
|
gfx_set_font_color(COLOR_PRIMARY);
|
|
drawBars(title);
|
|
redraw = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for the user to press a button.
|
|
*/
|
|
void waitButtonInput(void)
|
|
{
|
|
gfx_set_font_color(COLOR_PRIMARY);
|
|
gfx_draw_rect_filled(8, SCREEN_HEIGHT - (16 + 8 + 2), SCREEN_WIDTH - 8 * 2, 2, COLOR_SECONDARY);
|
|
|
|
gfx_draw_rect_filled(16 - 1, SCREEN_HEIGHT - CHAR_SIZE_DRC_Y - 4,
|
|
SCREEN_WIDTH - 16, CHAR_SIZE_DRC_Y + 2,
|
|
COLOR_BACKGROUND);
|
|
gfx_print(16, SCREEN_HEIGHT - CHAR_SIZE_DRC_Y - 4, 0, "Press EJECT or POWER to proceed...");
|
|
|
|
uint8_t cur_flag = 0;
|
|
uint8_t flag = 0;
|
|
|
|
while (1) {
|
|
SMC_ReadSystemEventFlag(&flag);
|
|
if (cur_flag != flag) {
|
|
if ((flag & SYSTEM_EVENT_FLAG_EJECT_BUTTON) || (flag & SYSTEM_EVENT_FLAG_POWER_BUTTON)) {
|
|
setNotificationLED(NOTIF_LED_OFF, 250);
|
|
return;
|
|
}
|
|
|
|
cur_flag = flag;
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_error(int index, const char *msg)
|
|
{
|
|
gfx_set_font_color(COLOR_ERROR);
|
|
gfx_print(16, index, GfxPrintFlag_ClearBG, msg);
|
|
setNotificationLED(NOTIF_LED_RED, 0);
|
|
waitButtonInput();
|
|
setNotificationLED(NOTIF_LED_PURPLE, 0);
|
|
}
|
|
|
|
void printf_error(int index, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
char buffer[0x100];
|
|
|
|
vsnprintf(buffer, sizeof(buffer), format, args);
|
|
va_end(args);
|
|
|
|
print_error(index, buffer);
|
|
}
|
|
|
|
/**
|
|
* Initialize the network configuration.
|
|
* @param index [in/out] Starting (and ending) Y position.
|
|
* @return 0 on success; non-zero on error.
|
|
*/
|
|
int initNetconf(uint32_t* index)
|
|
{
|
|
gfx_print(16, *index, 0, "Initializing netconf...");
|
|
*index += CHAR_SIZE_DRC_Y;
|
|
|
|
int res = netconf_init();
|
|
if (res <= 0) {
|
|
printf_error(*index, "Failed to initialize netconf: %x", res);
|
|
return res;
|
|
}
|
|
|
|
gfx_printf(16, *index, 0, "Waiting for network connection... %ds", 5);
|
|
|
|
NetConfInterfaceTypeEnum interface = 0xff;
|
|
for (int i = 0; i < 5; i++) {
|
|
if (netconf_get_if_linkstate(NET_CFG_INTERFACE_TYPE_WIFI) == NET_CFG_LINK_STATE_UP) {
|
|
interface = NET_CFG_INTERFACE_TYPE_WIFI;
|
|
break;
|
|
}
|
|
|
|
if (netconf_get_if_linkstate(NET_CFG_INTERFACE_TYPE_ETHERNET) == NET_CFG_LINK_STATE_UP) {
|
|
interface = NET_CFG_INTERFACE_TYPE_ETHERNET;
|
|
break;
|
|
}
|
|
|
|
usleep(1000 * 1000);
|
|
gfx_printf(16, *index, GfxPrintFlag_ClearBG, "Waiting for network connection... %ds", 4 - i);
|
|
}
|
|
|
|
*index += CHAR_SIZE_DRC_Y;
|
|
|
|
if (interface == 0xff) {
|
|
print_error(*index, "No network connection!");
|
|
return 1;
|
|
}
|
|
|
|
gfx_printf(16, *index, 0, "Connected using %s", (interface == NET_CFG_INTERFACE_TYPE_WIFI) ? "WIFI" : "ETHERNET");
|
|
*index += CHAR_SIZE_DRC_Y;
|
|
|
|
uint8_t ip_address[4];
|
|
res = netconf_get_assigned_address(interface, (uint32_t*) ip_address);
|
|
if (res < 0) {
|
|
printf_error(*index, "Failed to get IP address: %x", res);
|
|
return res;
|
|
}
|
|
|
|
gfx_printf(16, *index, 0, "IP address: %u.%u.%u.%u",
|
|
ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
|
|
*index += CHAR_SIZE_DRC_Y;
|
|
|
|
res = socketInit();
|
|
if (res <= 0) {
|
|
printf_error(*index, "Failed to initialize socketlib: %x", res);
|
|
return res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get region code information.
|
|
* @param productArea_id Product area ID: 0-6
|
|
* @param gameRegion Bitfield of game regions
|
|
* @return 0 on success; negative on error.
|
|
*/
|
|
int getRegionInfo(int* productArea_id, int* gameRegion)
|
|
{
|
|
MCPSysProdSettings sysProdSettings;
|
|
|
|
int mcpHandle = IOS_Open("/dev/mcp", 0);
|
|
if (mcpHandle < 0)
|
|
return mcpHandle;
|
|
|
|
int res = MCP_GetSysProdSettings(mcpHandle, &sysProdSettings);
|
|
IOS_Close(mcpHandle);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
// productArea is a single region.
|
|
if (sysProdSettings.product_area == 0)
|
|
return -1;
|
|
|
|
if (productArea_id) {
|
|
*productArea_id = __builtin_ctz(sysProdSettings.product_area);
|
|
}
|
|
if (gameRegion) {
|
|
*gameRegion = sysProdSettings.game_region;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void option_Shutdown(void)
|
|
{
|
|
if (fsaHandle > 0) {
|
|
// flush mlc and slc before forcing shutdown
|
|
FSA_FlushVolume(fsaHandle, "/vol/storage_mlc01");
|
|
FSA_FlushVolume(fsaHandle, "/vol/system");
|
|
|
|
// unmount sd
|
|
FSA_Unmount(fsaHandle, "/vol/storage_recovsd", 2);
|
|
|
|
IOS_Close(fsaHandle);
|
|
}
|
|
|
|
// Finalize utils (doesn't really matter at this point since we don't have a clean shutdown anyways)
|
|
//finalizeUtils();
|
|
|
|
IOS_Shutdown(0);
|
|
}
|
|
|
|
int menuThread(void* arg)
|
|
{
|
|
printf("menuThread running\n");
|
|
|
|
// set LED to purple-orange blinking
|
|
setNotificationLED(NOTIF_LED_PURPLE_BLINKING | NOTIF_LED_ORANGE, 0);
|
|
|
|
// stop ppcHeartbeatThread and reset PPC
|
|
IOS_CancelThread(ppcHeartBeatThreadId, 0);
|
|
resetPPC();
|
|
|
|
// cut power to the disc drive to not eject a disc every eject press
|
|
SMC_SetODDPower(0);
|
|
|
|
// Initialize utils
|
|
initializeUtils();
|
|
|
|
#ifdef DC_INIT
|
|
// (re-)init the graphics subsystem
|
|
GFX_SubsystemInit(0);
|
|
|
|
/* Note: CONFIGURATION_0 is 720p instead of 480p,
|
|
but doesn't shut down the GPU properly? The
|
|
GamePad just stays connected after calling iOS_Shutdown.
|
|
To be safe, let's use CONFIGURATION_1 for now */
|
|
DISPLAY_DCInit(DC_CONFIGURATION_1);
|
|
|
|
/* Note about the display configuration struct:
|
|
The returned framebuffer address seems to be AV out only?
|
|
Writing to the hardcoded addresses in gfx.c works for HDMI though */
|
|
DC_Config dc_config;
|
|
DISPLAY_ReadDCConfig(&dc_config);
|
|
#endif
|
|
|
|
// initialize the font
|
|
if (gfx_init_font() != 0) {
|
|
// failed to initialize font
|
|
// can't do anything without a font, so sleep for 5 secs and shut down
|
|
usleep(1000 * 1000 * 5);
|
|
IOS_Shutdown(0);
|
|
}
|
|
|
|
// open fsa and mount sdcard
|
|
fsaHandle = IOS_Open("/dev/fsa", 0);
|
|
if (fsaHandle > 0) {
|
|
int res = FSA_Mount(fsaHandle, "/dev/sdcard01", "/vol/storage_recovsd", 2, NULL, 0);
|
|
if (res < 0) {
|
|
printf("Failed to mount SD: %x\n", res);
|
|
}
|
|
} else {
|
|
printf("Failed to open FSA: %x\n", fsaHandle);
|
|
}
|
|
|
|
// set LED to purple
|
|
setNotificationLED(NOTIF_LED_PURPLE, 0);
|
|
|
|
// Check to see if we should autoboot
|
|
handleBoot1Autoboot();
|
|
|
|
int selected = 0;
|
|
while (1) {
|
|
selected = drawMenu("Wii U Recovery Menu v" VERSION_STRING " by GaryOderNichts",
|
|
mainMenuOptions, ARRAY_SIZE(mainMenuOptions), selected,
|
|
MenuFlag_ShowGitHubLink, 16, 16+8+2+8);
|
|
if (selected >= 0 && selected < ARRAY_SIZE(mainMenuOptions)) {
|
|
mainMenuOptions[selected].callback();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|