Files
OmniNX-Installer-Payload/source/main.c
niklascfw 15b7cb1f4c
All checks were successful
Build / Build (push) Successful in 10s
Add battery run protection: require charger below 10%
- Add bq24193_charger_connected() for charger detection
- Block install when battery < 10% and charger not plugged in
- Show clear screen after charger detected before continuing
- User can cancel with + and - to return to Hekate

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 15:12:06 +01:00

633 lines
19 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
*/
#ifndef VERSION
#define VERSION "1.0.0"
#endif
#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 "nx_sd.h"
#include <storage/sdmmc.h>
#include <utils/btn.h>
#include <utils/util.h>
#include <utils/sprintf.h>
#include <input/joycon.h>
#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;
// 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 druecke + und - 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) {
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->plus && jc->minus) {
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();
// 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);
set_color(COLOR_CYAN);
gfx_printf("Druecke + und - gleichzeitig zum Abbrechen (zurueck zu 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
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) {
if (jc->a) {
button_pressed = true;
break;
}
// + and - simultaneously = cancel, return to hekate
if (jc->plus && jc->minus) {
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(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();
}