diff --git a/source/install.c b/source/install.c index 9ffcf11..83d03ce 100644 --- a/source/install.c +++ b/source/install.c @@ -521,8 +521,11 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) { if (res != FR_OK) return res; install_check_and_clear_screen_if_needed(); - // Remove staging directory + // Remove staging directory (installed pack) res = cleanup_staging_directory(pack_variant); + if (res != FR_OK) return res; + // Remove other detected install directories (Standard/Light/OC) that were on SD + res = cleanup_other_staging_directories(pack_variant); return res; } else { // Clean mode: backup, wipe, restore, install @@ -557,8 +560,11 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) { if (res != FR_OK) return res; install_check_and_clear_screen_if_needed(); - // Remove staging directory + // Remove staging directory (installed pack) res = cleanup_staging_directory(pack_variant); + if (res != FR_OK) return res; + // Remove other detected install directories (Standard/Light/OC) that were on SD + res = cleanup_other_staging_directories(pack_variant); return res; } } diff --git a/source/install.h b/source/install.h index 687bc1a..31c660c 100644 --- a/source/install.h +++ b/source/install.h @@ -20,6 +20,8 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode); int update_mode_cleanup(omninx_variant_t variant); int update_mode_install(omninx_variant_t variant); int cleanup_staging_directory(omninx_variant_t pack_variant); +// Remove other OmniNX staging directories (Standard/Light/OC) that exist, except the one just installed +int cleanup_other_staging_directories(omninx_variant_t installed_variant); // Clean install operations (install_clean.c) int clean_mode_backup(void); diff --git a/source/install_clean.c b/source/install_clean.c index 296a729..55a8667 100644 --- a/source/install_clean.c +++ b/source/install_clean.c @@ -9,20 +9,27 @@ #include "deletion_lists_clean.h" #include "fs.h" #include "gfx.h" +#include "version.h" #include +#include #undef COLOR_CYAN #undef COLOR_WHITE #undef COLOR_GREEN #undef COLOR_RED +#undef COLOR_YELLOW +#undef COLOR_ORANGE #define COLOR_CYAN 0xFF00FFFF #define COLOR_WHITE 0xFFFFFFFF #define COLOR_GREEN 0xFF00FF00 #define COLOR_RED 0xFFFF0000 +#define COLOR_YELLOW 0xFFFFDD00 +#define COLOR_ORANGE 0xFF00A5FF #define set_color install_set_color #define check_and_clear_screen_if_needed install_check_and_clear_screen_if_needed #define path_exists install_path_exists +#define count_directory_items install_count_directory_items // Clean mode: Backup user data int clean_mode_backup(void) { @@ -109,3 +116,55 @@ int clean_mode_restore(void) { int clean_mode_install(omninx_variant_t variant) { return update_mode_install(variant); } + +// Remove other staging directories (OmniNX Standard/Light/OC) that exist on SD +int cleanup_other_staging_directories(omninx_variant_t installed_variant) { + static const omninx_variant_t all_variants[] = { VARIANT_STANDARD, VARIANT_LIGHT, VARIANT_OC }; + const int n = sizeof(all_variants) / sizeof(all_variants[0]); + int last_res = FR_OK; + + for (int i = 0; i < n; i++) { + omninx_variant_t v = all_variants[i]; + if (v == installed_variant) + continue; + const char *staging = get_staging_path(v); + if (!staging || !path_exists(staging)) + continue; + + check_and_clear_screen_if_needed(); + set_color(COLOR_YELLOW); + gfx_printf("\nEntferne weiteren Installationsordner...\n"); + set_color(COLOR_WHITE); + + int total = count_directory_items(staging); + u32 start_x, start_y; + gfx_con_getpos(&start_x, &start_y); + + const char *folder_name = strrchr(staging, '/'); + if (folder_name) folder_name++; + else folder_name = staging; + + set_color(COLOR_CYAN); + gfx_printf(" Loesche: %s [ 0%%] (0/%d)", folder_name, total); + set_color(COLOR_WHITE); + + int deleted = 0; + int last_percent = -1; + int res = folder_delete_progress_recursive(staging, &deleted, total, start_x, start_y, folder_name, &last_percent); + + gfx_con_setpos(start_x, start_y); + if (res == FR_OK) { + set_color(COLOR_GREEN); + gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", folder_name, deleted, total); + set_color(COLOR_WHITE); + } else { + set_color(COLOR_ORANGE); + gfx_printf(" Loesche: %s - Fehlgeschlagen!\n", folder_name); + gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res); + set_color(COLOR_WHITE); + } + last_res = res; + } + + return last_res; +} diff --git a/source/main.c b/source/main.c index bbc5498..52dc27f 100644 --- a/source/main.c +++ b/source/main.c @@ -1,462 +1,552 @@ -/* - * OmniNX Installer Payload - * Minimal payload to install OmniNX CFW Pack files outside of Horizon OS - * - * Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue - * Based on HATS-Installer-Payload by sthetix - */ - -#ifndef VERSION -#define VERSION "1.0.0" -#endif - -#include - -#include -#include "gfx.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "nx_sd.h" -#include -#include -#include -#include -#include - -#include "fs.h" -#include "version.h" -#include "install.h" - -// Configuration -#define PAYLOAD_PATH "sd:/bootloader/update.bin" - -// Payload launch defines -#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)) -#define COREBOOT_END_ADDR 0xD0000000 - -// 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 void *coreboot_addr; -static int total_errors = 0; - -// Use BDK colors (already defined in types.h) -#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); -} - - -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; - - if (payload_size == 0x7000) { - memcpy((u8 *)(payload_src + ALIGN(PATCHED_RELOC_SZ, 0x10)), coreboot_addr, 0x7000); - } -} - -static int launch_payload(const char *path) { - if (!path) - return 1; - - if (sd_mount()) { - FIL fp; - if (f_open(&fp, path, FA_READ)) { - gfx_printf("Payload nicht gefunden: %s\n", path); - return 1; - } - - void *buf; - u32 size = f_size(&fp); - - if (size < 0x30000) - buf = (void *)RCM_PAYLOAD_ADDR; - else { - coreboot_addr = (void *)(COREBOOT_END_ADDR - size); - buf = coreboot_addr; - } - - if (f_read(&fp, buf, size, NULL)) { - f_close(&fp); - return 1; - } - - f_close(&fp); - sd_unmount(); - - if (size < 0x30000) { - reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, ALIGN(size, 0x10)); - hw_reinit_workaround(false, byte_swap_32(*(u32 *)(buf + size - sizeof(u32)))); - } else { - reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, 0x7000); - hw_reinit_workaround(true, 0); - } - - sdmmc_storage_init_wait_sd(); - - 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); -} - -extern void pivot_stack(u32 stack_top); - -void ipl_main(void) { - // Hardware initialization - hw_init(); - pivot_stack(IPL_STACK_TOP); - heap_init(IPL_HEAP_START); - - // Set bootloader's default configuration - set_default_configuration(); - - // Mount SD Card - if (!sd_mount()) { - // Can't show error without display, just reboot - power_set_state(POWER_OFF_REBOOT); - } - - // Initialize minerva for faster memory - minerva_init(); - minerva_change_freq(FREQ_800); - - // Initialize display - display_init(); - u32 *fb = display_init_framebuffer_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 is on SD card - omninx_variant_t pack_variant = detect_pack_variant(); - - if (pack_variant == VARIANT_NONE) { - 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"); - 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"); - 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 - jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - 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("Payload wird gestartet...\n"); - set_color(COLOR_WHITE); - msleep(500); - launch_payload(PAYLOAD_PATH); - } else { - power_set_state(POWER_OFF_REBOOT); - } - return; - } - - // Determine installation mode - install_mode_t mode = current.is_installed ? INSTALL_MODE_UPDATE : INSTALL_MODE_CLEAN; - - // Show information - 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"); - } - 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); - - // Wait for either A button or Power button - bool 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) { - button_pressed = true; - break; - } - - // Check joycon A button - jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - button_pressed = true; - break; - } - - msleep(50); // Small delay to avoid busy-waiting - } - - // 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\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(3000); - - // Clear screen for final summary to ensure it's visible - gfx_clear_grey(0x1B); - gfx_con_setpos(0, 0); - - // Summary - set_color(COLOR_CYAN); - gfx_printf("========================================\n"); - gfx_printf(" OmniNX Installer Payload v%s\n", VERSION); - gfx_printf("========================================\n\n"); - set_color(COLOR_WHITE); - - if (result == FR_OK && total_errors == 0) { - set_color(COLOR_GREEN); - gfx_printf("========================================\n"); - gfx_printf(" Installation abgeschlossen!\n"); - gfx_printf("========================================\n"); - } else { - set_color(COLOR_RED); - gfx_printf("========================================\n"); - gfx_printf(" Installation beendet\n"); - 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 - jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - button_pressed = true; - break; - } - - msleep(50); // Small delay to avoid busy-waiting - } - - gfx_printf("\n"); - set_color(COLOR_CYAN); - gfx_printf("Payload wird gestartet...\n"); - set_color(COLOR_WHITE); - msleep(500); // Brief delay before launch - - if (file_exists(PAYLOAD_PATH)) { - launch_payload(PAYLOAD_PATH); - } else { - // Payload not found, show error - set_color(COLOR_RED); - gfx_printf("\nFEHLER: Payload 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 && jc->a) { - button_pressed = true; - break; - } - - msleep(50); - } - - power_set_state(POWER_OFF_REBOOT); - } - - // Should never reach here - while (1) - bpmp_halt(); -} +/* + * OmniNX Installer Payload + * Minimal payload to install OmniNX CFW Pack files outside of Horizon OS + * + * Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue + * Based on HATS-Installer-Payload by sthetix + */ + +#ifndef VERSION +#define VERSION "1.0.0" +#endif + +#include + +#include +#include "gfx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nx_sd.h" +#include +#include +#include +#include +#include + +#include "fs.h" +#include "version.h" +#include "install.h" + +// Configuration +#define PAYLOAD_PATH "sd:/bootloader/update.bin" + +// Payload launch defines +#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)) +#define COREBOOT_END_ADDR 0xD0000000 + +// 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 void *coreboot_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); +} + + +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; + + if (payload_size == 0x7000) { + memcpy((u8 *)(payload_src + ALIGN(PATCHED_RELOC_SZ, 0x10)), coreboot_addr, 0x7000); + } +} + +static int launch_payload(const char *path) { + if (!path) + return 1; + + if (sd_mount()) { + FIL fp; + if (f_open(&fp, path, FA_READ)) { + gfx_printf("Payload nicht gefunden: %s\n", path); + return 1; + } + + void *buf; + u32 size = f_size(&fp); + + if (size < 0x30000) + buf = (void *)RCM_PAYLOAD_ADDR; + else { + coreboot_addr = (void *)(COREBOOT_END_ADDR - size); + buf = coreboot_addr; + } + + if (f_read(&fp, buf, size, NULL)) { + f_close(&fp); + return 1; + } + + f_close(&fp); + sd_unmount(); + + if (size < 0x30000) { + reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, ALIGN(size, 0x10)); + hw_reinit_workaround(false, byte_swap_32(*(u32 *)(buf + size - sizeof(u32)))); + } else { + reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, 0x7000); + hw_reinit_workaround(true, 0); + } + + sdmmc_storage_init_wait_sd(); + + 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); +} + +extern void pivot_stack(u32 stack_top); + +void ipl_main(void) { + // Hardware initialization + hw_init(); + pivot_stack(IPL_STACK_TOP); + heap_init(IPL_HEAP_START); + + // Set bootloader's default configuration + set_default_configuration(); + + // Mount SD Card + if (!sd_mount()) { + // Can't show error without display, just reboot + power_set_state(POWER_OFF_REBOOT); + } + + // Initialize minerva for faster memory + minerva_init(); + minerva_change_freq(FREQ_800); + + // Initialize display + display_init(); + u32 *fb = display_init_framebuffer_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"); + 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"); + 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 + jc_gamepad_rpt_t *jc = joycon_poll(); + if (jc && jc->a) { + 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("Payload 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 { + // Initialize joycons for menu input + jc_init_hw(); + while (btn_read() & BTN_POWER) { msleep(50); } + + // Selection menu (TegraExplorer-style: draw once, then only redraw changed lines) + int selected = 0; + int prev_selected = 0; + bool confirmed = false; + + // Draw menu once and remember start position of variant lines + 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); + u32 menu_x, menu_variant_start_y; + gfx_con_getpos(&menu_x, &menu_variant_start_y); + for (int i = 0; i < num_variants; i++) { + if (i == selected) { + set_color(COLOR_GREEN); + gfx_printf(" > %s\n", get_variant_name(variants_present[i])); + set_color(COLOR_WHITE); + } else { + gfx_printf(" %s\n", get_variant_name(variants_present[i])); + } + } + gfx_printf("\n"); + set_color(COLOR_CYAN); + gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n"); + set_color(COLOR_WHITE); + + // Edge detection: only move on press (not while held) + bool prev_up = false, prev_down = false; + + while (!confirmed) { + // On selection change: redraw only the two affected lines (no full clear) + if (selected != prev_selected) { + gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16); + set_color(COLOR_WHITE); + gfx_printf(" %s\n", get_variant_name(variants_present[prev_selected])); + gfx_con_setpos(menu_x, menu_variant_start_y + (u32)selected * 16); + set_color(COLOR_GREEN); + gfx_printf(" > %s\n", get_variant_name(variants_present[selected])); + set_color(COLOR_WHITE); + prev_selected = selected; + } + + jc_gamepad_rpt_t *jc = joycon_poll(); + u8 btn = btn_read(); + + // D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down) + bool cur_up = false, cur_down = false; + if (jc) { + cur_up = (jc->up != 0) && !jc->down; + cur_down = (jc->down != 0) && !jc->up; + } + if (btn & BTN_VOL_UP) cur_up = true; + if (btn & BTN_VOL_DOWN) cur_down = true; + + if (cur_up && !prev_up) + selected = (selected - 1 + num_variants) % num_variants; + else if (cur_down && !prev_down) + selected = (selected + 1) % num_variants; + else if (jc && jc->a) + confirmed = true; + prev_up = cur_up; + prev_down = cur_down; + + if (btn & BTN_POWER) { + confirmed = true; + } + + msleep(50); + } + + 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; + + // Show information + 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"); + } + 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); + + // Wait for either A button or Power button + bool 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) { + button_pressed = true; + break; + } + + // Check joycon A button + jc_gamepad_rpt_t *jc = joycon_poll(); + if (jc && jc->a) { + button_pressed = true; + break; + } + + msleep(50); // Small delay to avoid busy-waiting + } + + // 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\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(3000); + + // Clear screen for final summary to ensure it's visible + gfx_clear_grey(0x1B); + gfx_con_setpos(0, 0); + + // Summary + set_color(COLOR_CYAN); + gfx_printf("========================================\n"); + gfx_printf(" OmniNX Installer Payload v%s\n", VERSION); + gfx_printf("========================================\n\n"); + set_color(COLOR_WHITE); + + if (result == FR_OK && total_errors == 0) { + set_color(COLOR_GREEN); + gfx_printf("========================================\n"); + gfx_printf(" Installation abgeschlossen!\n"); + gfx_printf("========================================\n"); + } else { + set_color(COLOR_RED); + gfx_printf("========================================\n"); + gfx_printf(" Installation beendet\n"); + 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 + jc_gamepad_rpt_t *jc = joycon_poll(); + if (jc && jc->a) { + button_pressed = true; + break; + } + + msleep(50); // Small delay to avoid busy-waiting + } + + gfx_printf("\n"); + set_color(COLOR_CYAN); + gfx_printf("Payload wird gestartet...\n"); + set_color(COLOR_WHITE); + msleep(500); // Brief delay before launch + + if (file_exists(PAYLOAD_PATH)) { + launch_payload(PAYLOAD_PATH); + } else { + // Payload not found, show error + set_color(COLOR_RED); + gfx_printf("\nFEHLER: Payload 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 && jc->a) { + button_pressed = true; + break; + } + + msleep(50); + } + + power_set_state(POWER_OFF_REBOOT); + } + + // Should never reach here + while (1) + bpmp_halt(); +} diff --git a/source/version.c b/source/version.c index 0e0a4ee..d6e6c10 100644 --- a/source/version.c +++ b/source/version.c @@ -156,6 +156,20 @@ omninx_variant_t detect_pack_variant(void) { return VARIANT_NONE; } +// Detect all pack variants present on SD card; returns count, fills out_variants[] +int detect_present_variants(omninx_variant_t *out_variants, int max_count) { + int count = 0; + if (max_count <= 0 || !out_variants) + return 0; + if (file_exists(STAGING_STANDARD) && count < max_count) + out_variants[count++] = VARIANT_STANDARD; + if (file_exists(STAGING_LIGHT) && count < max_count) + out_variants[count++] = VARIANT_LIGHT; + if (file_exists(STAGING_OC) && count < max_count) + out_variants[count++] = VARIANT_OC; + return count; +} + // Get human-readable variant name const char* get_variant_name(omninx_variant_t variant) { switch (variant) { diff --git a/source/version.h b/source/version.h index 0202945..45e40d7 100644 --- a/source/version.h +++ b/source/version.h @@ -23,9 +23,12 @@ typedef struct { // Detect current OmniNX installation status omninx_status_t detect_omninx_installation(void); -// Detect which pack variant is present on SD card +// Detect which pack variant is present on SD card (first found in fixed order) omninx_variant_t detect_pack_variant(void); +// Detect all pack variants present on SD card; returns count, fills out_variants[] (max max_count) +int detect_present_variants(omninx_variant_t *out_variants, int max_count); + // Get human-readable variant name const char* get_variant_name(omninx_variant_t variant);