Replaces the old vendored BDK with the one from hekate v6.5.5 (CTCaer's 2025/2026 refactors: sdmmc, SE, minerva v1.6, display, heap, return value homogenisation). OmniNX additions re-applied on top: bq24193_charger_connected() and jc_clear_input(); s_vprintf now ships with the BDK. Ported to the new API: - sd_mount(), sdmmc_storage_* and ini_parse() now return 0 on success. The installer checked ini_parse() != 1, which would have made it miss manifest.ini and run every install as a clean install. - SD helpers come from bdk/storage/sd.c (source/nx_sd.* removed), ffsystem.c from the BDK, diskio.c/ffconf.h from hekate (ffconf keeps the sd:/ volume prefix). - minerva_init() needs the minerva storage; the installer still drops to 800 MHz afterwards. - Payload launch follows hekate (sd_file_read, hw_deinit); coreboot path removed upstream. - Stack moved to 0x96000000: the old 0x83100000 is inside the new heap. - GUI/USB/compression parts of the BDK are not built.
692 lines
22 KiB
C
692 lines
22 KiB
C
/*
|
|
* OmniNX Installer Payload
|
|
* Minimal payload to install OmniNX CFW Pack files outside of Horizon OS
|
|
*
|
|
* Based on HATS Installer by sthetix
|
|
* Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <display/di.h>
|
|
#include "gfx.h"
|
|
#include <libs/fatfs/ff.h>
|
|
#include <mem/heap.h>
|
|
#include <mem/minerva.h>
|
|
#include <power/max77620.h>
|
|
#include <power/max17050.h>
|
|
#include <power/bq24193.h>
|
|
#include <soc/bpmp.h>
|
|
#include <soc/fuse.h>
|
|
#include <soc/hw_init.h>
|
|
#include <soc/pmc.h>
|
|
#include <soc/t210.h>
|
|
#include <storage/sd.h>
|
|
#include <storage/sdmmc.h>
|
|
#include <utils/btn.h>
|
|
#include <utils/util.h>
|
|
#include <soc/timer.h>
|
|
#include <utils/sprintf.h>
|
|
#include <input/joycon.h>
|
|
|
|
#include "fs.h"
|
|
#include "version.h"
|
|
#include "install.h"
|
|
#include "screenshot.h"
|
|
|
|
// Configuration
|
|
#define PAYLOAD_PATH "sd:/bootloader/update.bin"
|
|
|
|
// Payload launch defines (same as hekate)
|
|
#define RELOC_META_OFF 0x7C
|
|
#define PATCHED_RELOC_SZ 0x94
|
|
#define PATCHED_RELOC_STACK 0x40007000
|
|
#define PATCHED_RELOC_ENTRY 0x40010000
|
|
#define EXT_PAYLOAD_ADDR 0xC0000000
|
|
#define RCM_PAYLOAD_ADDR (EXT_PAYLOAD_ADDR + ALIGN(PATCHED_RELOC_SZ, 0x10))
|
|
|
|
// Stack in DRAM: the tree walkers recurse with path buffers, too deep for the
|
|
// 4KB IRAM stack hekate uses. 0x96000000 is in the unused RAM disk gap, clear
|
|
// of the heap (0x82000000-0x92000000) and the payload buffer at 0xC0000000.
|
|
#define INSTALLER_STACK_TOP 0x96000000
|
|
|
|
// Hekate config structure (simplified)
|
|
typedef struct _hekate_config
|
|
{
|
|
u32 autoboot;
|
|
u32 autoboot_list;
|
|
u32 bootwait;
|
|
u32 backlight;
|
|
u32 autohosoff;
|
|
u32 autonogc;
|
|
u32 updater2p;
|
|
u32 bootprotect;
|
|
bool t210b01;
|
|
bool se_keygen_done;
|
|
bool sept_run;
|
|
bool aes_slots_new;
|
|
bool emummc_force_disable;
|
|
bool rcm_patched;
|
|
u32 errors;
|
|
} hekate_config;
|
|
|
|
// Boot config structures (required by bdk)
|
|
hekate_config h_cfg;
|
|
boot_cfg_t __attribute__((section ("._boot_cfg"))) b_cfg;
|
|
volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR;
|
|
|
|
static int total_errors = 0;
|
|
|
|
// Use BDK colors (CYAN/WHITE overridden for consistency; GREEN/YELLOW from types.h)
|
|
#undef COLOR_CYAN
|
|
#undef COLOR_WHITE
|
|
#define COLOR_CYAN 0xFF00FFFF
|
|
#define COLOR_WHITE 0xFFFFFFFF
|
|
|
|
static void set_default_configuration(void)
|
|
{
|
|
h_cfg.autoboot = 0;
|
|
h_cfg.autoboot_list = 0;
|
|
h_cfg.bootwait = 3;
|
|
h_cfg.se_keygen_done = 0;
|
|
h_cfg.backlight = 100;
|
|
h_cfg.autohosoff = 0;
|
|
h_cfg.autonogc = 1;
|
|
h_cfg.updater2p = 0;
|
|
h_cfg.bootprotect = 0;
|
|
h_cfg.errors = 0;
|
|
h_cfg.sept_run = 0;
|
|
h_cfg.aes_slots_new = false;
|
|
h_cfg.rcm_patched = fuse_check_patched_rcm();
|
|
h_cfg.emummc_force_disable = false;
|
|
h_cfg.t210b01 = hw_get_chip_id() == GP_HIDREV_MAJOR_T210B01;
|
|
}
|
|
|
|
static void set_color(u32 color) {
|
|
gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol);
|
|
}
|
|
|
|
static void print_header(void) {
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("===================================\n");
|
|
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
|
|
gfx_printf("===================================\n\n");
|
|
set_color(COLOR_WHITE);
|
|
}
|
|
|
|
/* Console Vol+ and Vol- together (exit / cancel where documented) */
|
|
static inline bool cancel_combo_pressed(u8 btn)
|
|
{
|
|
return (btn & (BTN_VOL_UP | BTN_VOL_DOWN)) == (BTN_VOL_UP | BTN_VOL_DOWN);
|
|
}
|
|
|
|
|
|
static void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) {
|
|
memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ);
|
|
|
|
volatile reloc_meta_t *relocator = (reloc_meta_t *)(payload_src + RELOC_META_OFF);
|
|
|
|
relocator->start = payload_dst - ALIGN(PATCHED_RELOC_SZ, 0x10);
|
|
relocator->stack = PATCHED_RELOC_STACK;
|
|
relocator->end = payload_dst + payload_size;
|
|
relocator->ep = payload_dst;
|
|
}
|
|
|
|
// Load and launch a payload (hekate update.bin). Returns only on failure.
|
|
static int launch_payload(const char *path) {
|
|
if (!path)
|
|
return 1;
|
|
|
|
u32 size = 0;
|
|
void *buf = sd_file_read(path, &size);
|
|
if (!buf) {
|
|
gfx_printf("Hekate nicht gefunden: %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
// Must fit IRAM (same limit as hekate).
|
|
if (size > 0x30000) {
|
|
free(buf);
|
|
gfx_printf("Payload zu gross: %s\n", path);
|
|
return 1;
|
|
}
|
|
|
|
sd_end();
|
|
|
|
memcpy((void *)RCM_PAYLOAD_ADDR, buf, size);
|
|
reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, ALIGN(size, 0x10));
|
|
|
|
hw_deinit(false);
|
|
|
|
void (*ext_payload_ptr)() = (void *)EXT_PAYLOAD_ADDR;
|
|
(*ext_payload_ptr)();
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int file_exists(const char *path) {
|
|
FILINFO fno;
|
|
return (f_stat(path, &fno) == FR_OK);
|
|
}
|
|
|
|
void installer_launch_hekate_payload(void) {
|
|
if (file_exists(PAYLOAD_PATH))
|
|
launch_payload(PAYLOAD_PATH);
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
|
|
extern void pivot_stack(u32 stack_top);
|
|
|
|
void ipl_main(void) {
|
|
// Hardware initialization
|
|
hw_init();
|
|
pivot_stack(INSTALLER_STACK_TOP);
|
|
heap_init((void *)IPL_HEAP_START);
|
|
|
|
// Set bootloader's default configuration
|
|
set_default_configuration();
|
|
|
|
// Mount SD Card (BDK: 0 = success)
|
|
if (sd_mount()) {
|
|
// Can't show error without display, just reboot
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
|
|
// Train DRAM, then stay at 800 MHz like before (minerva_init ends at 1600)
|
|
if (!minerva_init((minerva_str_t *)&nyx_str->minerva))
|
|
minerva_change_freq(FREQ_800);
|
|
|
|
// Initialize display
|
|
display_init();
|
|
u32 *fb = display_init_window_a_pitch();
|
|
gfx_init_ctxt(fb, 720, 1280, 720);
|
|
gfx_con_init();
|
|
display_backlight_pwm_init();
|
|
display_backlight_brightness(100, 1000);
|
|
|
|
// Overclock BPMP
|
|
bpmp_clk_rate_set(BPMP_CLK_DEFAULT_BOOST);
|
|
|
|
print_header();
|
|
|
|
// Detect current OmniNX installation
|
|
omninx_status_t current = detect_omninx_installation();
|
|
|
|
// Detect which pack variant(s) are on SD card
|
|
omninx_variant_t variants_present[3];
|
|
int num_variants = detect_present_variants(variants_present, 3);
|
|
|
|
if (num_variants == 0) {
|
|
set_color(COLOR_RED);
|
|
gfx_printf("FEHLER: Kein OmniNX-Paket auf der SD-Karte gefunden!\n");
|
|
gfx_printf("Erwartet wird eines der folgenden:\n");
|
|
gfx_printf(" - sd:/OmniNX Standard/\n");
|
|
gfx_printf(" - sd:/OmniNX Light/\n");
|
|
gfx_printf(" - sd:/OmniNX OC/\n\n");
|
|
set_color(COLOR_WHITE);
|
|
|
|
// Initialize joycons for button input
|
|
jc_init_hw();
|
|
|
|
// Wait for button confirmation
|
|
bool button_pressed = false;
|
|
|
|
// Launch payload if available
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
|
gfx_printf("um Hekate zu starten...\n");
|
|
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
|
|
set_color(COLOR_WHITE);
|
|
} else {
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
|
gfx_printf("um den Neustart zu starten...\n");
|
|
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
|
|
set_color(COLOR_WHITE);
|
|
}
|
|
|
|
// First, wait for power button to be released if it's currently pressed
|
|
while (btn_read() & BTN_POWER) {
|
|
msleep(50);
|
|
}
|
|
|
|
while (!button_pressed) {
|
|
// Check power button
|
|
u8 btn_state = btn_read();
|
|
if (btn_state & BTN_POWER) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
|
|
// Check joycon A button; Capture = screenshot
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
if (jc->a) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (cancel_combo_pressed(btn_state)) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
|
|
msleep(50); // Small delay to avoid busy-waiting
|
|
}
|
|
|
|
// Launch payload if available, otherwise reboot
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
gfx_printf("\n");
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Hekate wird gestartet...\n");
|
|
set_color(COLOR_WHITE);
|
|
msleep(500);
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If multiple variants present, show selection menu; otherwise use the single one
|
|
omninx_variant_t pack_variant;
|
|
if (num_variants == 1) {
|
|
pack_variant = variants_present[0];
|
|
} else {
|
|
const char *labels[3];
|
|
for (int i = 0; i < num_variants; i++)
|
|
labels[i] = get_variant_name(variants_present[i]);
|
|
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
print_header();
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Mehrere OmniNX-Pakete gefunden.\n");
|
|
gfx_printf("Waehle die zu installierende Variante:\n\n");
|
|
set_color(COLOR_WHITE);
|
|
|
|
int selected = install_menu_select(labels, num_variants);
|
|
if (selected < 0) {
|
|
gfx_printf("\n");
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Abgebrochen. Starte Hekate...\n");
|
|
set_color(COLOR_WHITE);
|
|
msleep(500);
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
return;
|
|
}
|
|
|
|
pack_variant = variants_present[selected];
|
|
gfx_clear_grey(0x1B);
|
|
print_header();
|
|
}
|
|
|
|
// Determine installation mode
|
|
install_mode_t mode = current.is_installed ? INSTALL_MODE_UPDATE : INSTALL_MODE_CLEAN;
|
|
|
|
// Battery run protection: below 10% requires charger
|
|
#define BATT_LOW_THRESHOLD 10
|
|
int batt_raw = 0;
|
|
int batt_pct = 100;
|
|
if (max17050_get_property(MAX17050_RepSOC, &batt_raw) == 0) {
|
|
batt_pct = batt_raw >> 8; // RepSOC: high byte = percent
|
|
}
|
|
while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected()) {
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
print_header();
|
|
set_color(COLOR_RED);
|
|
gfx_printf("Akkustand zu niedrig! (%d%%)\n\n", batt_pct);
|
|
gfx_printf("Unter %d%% Akku darf die Installation nur\n", BATT_LOW_THRESHOLD);
|
|
gfx_printf("mit angeschlossenem Ladegeraet durchgefuehrt werden.\n\n");
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Stecke das Ladegeraet an und warte...\n");
|
|
gfx_printf("Oder Vol+ und Vol- (Konsole) zum Abbrechen.\n");
|
|
set_color(COLOR_WHITE);
|
|
jc_init_hw();
|
|
while (btn_read() & BTN_POWER) { msleep(50); }
|
|
bool user_cancelled = false;
|
|
while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected() && !user_cancelled) {
|
|
u8 pbtn = btn_read();
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
}
|
|
if (cancel_combo_pressed(pbtn)) {
|
|
user_cancelled = true;
|
|
break;
|
|
}
|
|
if (max17050_get_property(MAX17050_RepSOC, &batt_raw) == 0) {
|
|
batt_pct = batt_raw >> 8;
|
|
}
|
|
msleep(200);
|
|
}
|
|
if (user_cancelled) {
|
|
gfx_printf("\nAbgebrochen. Starte Hekate...\n");
|
|
msleep(500);
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Charger detected or battery OK - clear the low-battery screen before continuing
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
print_header();
|
|
|
|
// UHS class check: recommend U2 / V30 / A2 for emuMMC (clean install only)
|
|
if (mode == INSTALL_MODE_CLEAN) {
|
|
#define UHS_U2_MIN 2
|
|
#define UHS_V30_MIN 30
|
|
#define UHS_A2_MIN 2
|
|
bool uhs_ok = (sd_storage.ssr.uhs_grade >= UHS_U2_MIN &&
|
|
sd_storage.ssr.video_class >= UHS_V30_MIN &&
|
|
sd_storage.ssr.app_class >= UHS_A2_MIN);
|
|
if (!uhs_ok) {
|
|
jc_init_hw();
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
set_color(COLOR_RED);
|
|
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
|
|
gfx_printf("!! WARNUNG - SD-KARTEN-KLASSE !!\n");
|
|
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
|
|
set_color(COLOR_WHITE);
|
|
gfx_printf("Diese SD-Karte erfuellt NICHT die empfohlenen\n");
|
|
gfx_printf("Mindestanforderungen (U2 | V30 | A2).\n\n");
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Aktuelle Werte: UHS%d | V%d | A%d\n\n",
|
|
(u32)sd_storage.ssr.uhs_grade,
|
|
(u32)sd_storage.ssr.video_class,
|
|
(u32)sd_storage.ssr.app_class);
|
|
set_color(COLOR_WHITE);
|
|
gfx_printf("Moegliche Folgen bei schwachen Karten:\n");
|
|
gfx_printf(" - Langsamere emuMMC / Spiel-Ladezeiten\n");
|
|
gfx_printf(" - Hoheres Risiko von Korruption oder Abstuerzen\n");
|
|
gfx_printf(" - Instabilitaet beim Schreiben groesserer Daten\n\n");
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Empfohlen: U2 | V30 | A2 oder besser.\n");
|
|
gfx_printf("Empfohlene Karten: Samsung EVO Plus, EVO Select und PRO Plus.\n\n");
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("Druecke A (oder Power), um trotzdem fortzufahren.\n");
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
|
|
set_color(COLOR_WHITE);
|
|
while (btn_read() & BTN_POWER) { msleep(50); }
|
|
bool acknowledged = false;
|
|
bool uhs_aborted = false;
|
|
while (!acknowledged && !uhs_aborted) {
|
|
u8 btn_state = btn_read();
|
|
if (btn_state & BTN_POWER) acknowledged = true;
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
if (jc->a) acknowledged = true;
|
|
}
|
|
if (cancel_combo_pressed(btn_state)) {
|
|
uhs_aborted = true;
|
|
break;
|
|
}
|
|
msleep(50);
|
|
}
|
|
if (uhs_aborted) {
|
|
gfx_printf("\nAbgebrochen. Starte Hekate...\n");
|
|
msleep(500);
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
return;
|
|
}
|
|
// Wait for A and Power to be released so the same press doesn't start the install
|
|
while (btn_read() & BTN_POWER) { msleep(50); }
|
|
bool released = false;
|
|
while (!released) {
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
released = (!jc || !jc->a) && !(btn_read() & BTN_POWER);
|
|
if (!released) msleep(50);
|
|
}
|
|
msleep(300); // Short delay so the next screen is visible before accepting input
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
print_header();
|
|
}
|
|
}
|
|
|
|
// Show information
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Hinweis: Verbindungen zu Nintendo-Servern sind\n");
|
|
gfx_printf("in OmniNX standardmaessig unterdrueckt.\n\n");
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");
|
|
gfx_printf("Paket-Variante: %s\n", get_variant_name(pack_variant));
|
|
if (current.is_installed) {
|
|
gfx_printf("Aktuelle Installation: %s\n", get_variant_name(current.variant));
|
|
} else {
|
|
gfx_printf("Aktuelle Installation: Keine\n");
|
|
}
|
|
|
|
char new_version[32];
|
|
new_version[0] = '\0';
|
|
const char *pack_manifest = get_manifest_path(pack_variant);
|
|
if (pack_manifest)
|
|
read_manifest_version(pack_manifest, new_version, sizeof(new_version));
|
|
|
|
const char *cur_ver = !current.is_installed ? "Keine"
|
|
: (current.version_file[0] ? current.version_file : "Unbekannt");
|
|
const char *new_ver = new_version[0] ? new_version : "Unbekannt";
|
|
gfx_printf("Version: %s -> %s\n", cur_ver, new_ver);
|
|
set_color(COLOR_WHITE);
|
|
gfx_printf("\n");
|
|
|
|
// Initialize joycons for button input
|
|
jc_init_hw();
|
|
|
|
// Intro section - wait for user confirmation
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Bereit zum Installieren/Aktualisieren.\n");
|
|
set_color(COLOR_WHITE);
|
|
gfx_printf("\n");
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
|
gfx_printf("um die Installation zu starten...\n");
|
|
set_color(COLOR_WHITE);
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
|
|
set_color(COLOR_WHITE);
|
|
|
|
// Wait for A/Power to start, or +/- to cancel
|
|
bool button_pressed = false;
|
|
bool cancelled = false;
|
|
|
|
// First, wait for power button to be released if it's currently pressed
|
|
while (btn_read() & BTN_POWER) {
|
|
msleep(50);
|
|
}
|
|
|
|
while (!button_pressed && !cancelled) {
|
|
// Check power button
|
|
u8 btn_state = btn_read();
|
|
if (btn_state & BTN_POWER) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
|
|
// Check joycon buttons; Capture = screenshot
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
if (jc->a) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (cancel_combo_pressed(btn_state)) {
|
|
cancelled = true;
|
|
break;
|
|
}
|
|
|
|
msleep(50); // Small delay to avoid busy-waiting
|
|
}
|
|
|
|
if (cancelled) {
|
|
gfx_printf("\n");
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Abgebrochen. Starte Hekate...\n");
|
|
set_color(COLOR_WHITE);
|
|
msleep(500);
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Clear the prompt and start installation
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
print_header();
|
|
|
|
// Perform the installation
|
|
set_color(COLOR_YELLOW);
|
|
gfx_printf("Installation wird gestartet...\n");
|
|
set_color(COLOR_WHITE);
|
|
set_color(COLOR_ORANGE);
|
|
gfx_printf("Hinweis: Manchmal kann es haengen. Einfach warten.\n\n");
|
|
set_color(COLOR_WHITE);
|
|
|
|
int result = perform_installation(pack_variant, mode);
|
|
|
|
// Wait 3 seconds before clearing screen to allow errors to be visible
|
|
msleep(1500);
|
|
// take_screenshot(); // Take a screenshot of the installation summary
|
|
|
|
// Clear screen for final summary to ensure it's visible
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
|
|
// Summary
|
|
const char *op_name = (mode == INSTALL_MODE_UPDATE) ? " Update" : " Installation";
|
|
if (result == FR_OK && total_errors == 0) {
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("=======================================\n");
|
|
gfx_printf(" %s abgeschlossen!\n", op_name);
|
|
gfx_printf("=======================================\n");
|
|
} else {
|
|
set_color(COLOR_RED);
|
|
gfx_printf("=======================================\n");
|
|
gfx_printf(" %s beendet\n", op_name);
|
|
if (total_errors > 0) {
|
|
gfx_printf(" %d Fehler\n", total_errors);
|
|
}
|
|
gfx_printf("=======================================\n");
|
|
}
|
|
set_color(COLOR_WHITE);
|
|
|
|
// Wait for user input before launching payload
|
|
gfx_printf("\n");
|
|
set_color(COLOR_GREEN);
|
|
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
|
gfx_printf("um Hekate zu starten...\n");
|
|
set_color(COLOR_WHITE);
|
|
|
|
// Wait for either A button or Power button (reuse button_pressed variable)
|
|
button_pressed = false;
|
|
|
|
// First, wait for power button to be released if it's currently pressed
|
|
while (btn_read() & BTN_POWER) {
|
|
msleep(50);
|
|
}
|
|
|
|
while (!button_pressed) {
|
|
// Check power button - detect press (transition from not pressed to pressed)
|
|
u8 btn_state = btn_read();
|
|
if (btn_state & BTN_POWER) {
|
|
// Power button is pressed
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
|
|
// Check joycon A button; Capture = screenshot
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
if (jc->a) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
msleep(50); // Small delay to avoid busy-waiting
|
|
}
|
|
|
|
gfx_printf("\n");
|
|
set_color(COLOR_CYAN);
|
|
gfx_printf("Hekate wird gestartet...\n");
|
|
set_color(COLOR_WHITE);
|
|
msleep(500); // Brief delay before launch
|
|
|
|
if (file_exists(PAYLOAD_PATH)) {
|
|
launch_payload(PAYLOAD_PATH);
|
|
} else {
|
|
// Hekate (update.bin) not found, show error
|
|
set_color(COLOR_RED);
|
|
gfx_printf("\nFEHLER: Hekate nicht gefunden!\n");
|
|
gfx_printf("Pfad: %s\n", PAYLOAD_PATH);
|
|
set_color(COLOR_WHITE);
|
|
gfx_printf("\nDruecke A oder Power zum Neustart...\n");
|
|
|
|
// Wait for button again
|
|
button_pressed = false;
|
|
|
|
// Wait for power button to be released if pressed
|
|
while (btn_read() & BTN_POWER) {
|
|
msleep(50);
|
|
}
|
|
|
|
while (!button_pressed) {
|
|
u8 btn_state = btn_read();
|
|
if (btn_state & BTN_POWER) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
|
|
jc_gamepad_rpt_t *jc = joycon_poll();
|
|
if (jc) {
|
|
if (jc->cap)
|
|
take_screenshot();
|
|
if (jc->a) {
|
|
button_pressed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
msleep(50);
|
|
}
|
|
|
|
power_set_state(POWER_OFF_REBOOT);
|
|
}
|
|
|
|
// Should never reach here
|
|
while (1)
|
|
bpmp_halt();
|
|
}
|