Compare commits

...

19 Commits

Author SHA1 Message Date
726ef77194 update: delete atmosphere kips loader.kip and hoc.kip on OmniNX update
All checks were successful
Build / Build (push) Successful in 15s
Made-with: Cursor
2026-04-09 22:11:31 +02:00
13c35400a1 install: backup and restore HorizonOC config.ini on OC pack update
- Copy sd:/config/horizon-oc/config.ini to sd:/.omninx_oc_update before
  update cleanup/copy; restore after Hekate step, then remove the scratch dir.
- Only when INSTALL_MODE_UPDATE and VARIANT_OC.

Made-with: Cursor
2026-04-09 22:11:30 +02:00
f71b6ab629 install: clarify Hekate RAM menu copy (4GB/8GB labels and warnings)
All checks were successful
Build / Build (push) Successful in 16s
Made-with: Cursor
2026-04-09 20:48:50 +02:00
9b559eb7e6 clean: delete TegraExplorer repair .te scripts on clean install
All checks were successful
Build / Build (push) Successful in 16s
Made-with: Cursor
2026-04-07 23:54:54 +02:00
d3735d17ee install: RAM config ini, selection menu, and silent Hekate 8GB step
- Read sd:/config/omninx/ram_config.ini [Ram] 8gb=0|1 when present;
  otherwise show 4 GB / 8 GB menu, write ini, same input as variant menu.
- Replace fuse-based DRAM autodetect for hekate_8gb.bin handling.
- Reprint installer top bar after RAM menu confirm.

Made-with: Cursor
2026-04-07 23:54:53 +02:00
db7d83304f main: add installer_launch_hekate_payload (update.bin or reboot)
Made-with: Cursor
2026-04-07 23:54:51 +02:00
113542f7fb install: declare installer_launch_hekate_payload for sub-menu exit
Made-with: Cursor
2026-04-07 23:54:50 +02:00
72a2083d76 Updated Deletion list
All checks were successful
Build / Build (push) Successful in 16s
2026-04-05 15:38:32 +02:00
3f6bbc0752 Input: console Vol+/- combo only before install; Joy-Con +/- exit removed
All checks were successful
Build / Build (push) Successful in 16s
- cancel_combo_pressed uses only hardware volume buttons
- After installation summary and payload-missing screen: A or Power only

Made-with: Cursor
2026-03-31 13:18:06 +02:00
d1fc24dae8 Install: Hekate 8GB after copy (fuse >4GiB), dram_fuse helper, RAM test target
All checks were successful
Build / Build (push) Successful in 14s
- Add dram_fuse.c/h for shared fuse-to-MiB mapping
- After successful update/clean install copy: if hekate_8gb.bin exists and
  fuse reports more than 4 GiB, copy to sd:/payload.bin and
  sd:/bootloader/update.bin, then remove source; otherwise remove helper only
- Makefile: make ram-test builds output/RAM-Test.bin (tools/ram_test_main.c)

Made-with: Cursor
2026-03-31 00:05:30 +02:00
ef104bce41 Update deletions: keep Switchfin, remove swr-ini-tool nro
Made-with: Cursor
2026-03-30 20:27:39 +02:00
0015c7e8ac Install: copy all staging root files via directory iteration
All checks were successful
Build / Build (push) Successful in 18s
Made-with: Cursor
2026-03-30 17:49:52 +02:00
6dde73f39e Install: copy themes/ in stage 2 (update and clean)
All checks were successful
Build / Build (push) Successful in 11s
Made-with: Cursor
2026-03-06 23:46:40 +01:00
97e8c3d965 Remove outdated UltraHand comment from deletion_lists_update.h
All checks were successful
Build / Build (push) Successful in 13s
Made-with: Cursor
2026-03-06 22:10:32 +01:00
586afc2f56 Revert "Update: preserve UltraHand .offload state (overlays/packages)"
This reverts commit e71fb13bfc.
2026-03-05 17:24:37 +01:00
f943887b39 Revert "Clean install: use normal switch copy, not .offload-aware"
This reverts commit 335ea03b05.
2026-03-05 17:24:26 +01:00
335ea03b05 Clean install: use normal switch copy, not .offload-aware
All checks were successful
Build / Build (push) Successful in 14s
- update_mode_install(variant, offload_aware_switch): true = update (preserve .offload), false = clean
- Update path calls with true; clean_mode_install calls with false so switch/ is copied normally on fresh install

Made-with: Cursor
2026-03-01 15:15:45 +01:00
b0523ada6c nx_sd: add sd_get_card_mounted() for screenshot and other callers
Made-with: Cursor
2026-03-01 14:44:37 +01:00
9a2307a8ee Add screenshot capture via Joy-Con Capture button
- source/screenshot.c, screenshot.h: take_screenshot() saves framebuffer as BMP
- Path: sd:/switch/screenshot/omninx_installer_YYYYMMDD_HHMMSS.bmp (RTC timestamp)
- 3s cooldown, backlight flash on capture; TegraExplorer-style BGR flip
- main.c: call take_screenshot() when jc->cap in all joycon poll loops

Made-with: Cursor
2026-03-01 14:43:58 +01:00
15 changed files with 1385 additions and 836 deletions

View File

@@ -46,7 +46,11 @@ LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defs
################################################################################ ################################################################################
.PHONY: all clean release RAMTEST_BIN := $(OUTPUTDIR)/RAM-Test.bin
OBJS_RAMTEST := $(filter-out $(BUILDDIR)/$(TARGET)/main.o,$(OBJS)) \
$(BUILDDIR)/$(TARGET)/ram_test_main.o
.PHONY: all clean release ram-test
all: $(OUTPUTDIR)/$(OUTPUT_NAME) all: $(OUTPUTDIR)/$(OUTPUT_NAME)
$(eval BIN_SIZE = $(shell wc -c < $(OUTPUTDIR)/$(OUTPUT_NAME))) $(eval BIN_SIZE = $(shell wc -c < $(OUTPUTDIR)/$(OUTPUT_NAME)))
@@ -56,6 +60,7 @@ all: $(OUTPUTDIR)/$(OUTPUT_NAME)
clean: clean:
@rm -rf $(BUILDDIR) @rm -rf $(BUILDDIR)
@rm -rf $(OUTPUTDIR) @rm -rf $(OUTPUTDIR)
@rm -f $(RAMTEST_BIN)
@rm -rf release @rm -rf release
@rm -f $(TARGET)-*.zip @rm -f $(TARGET)-*.zip
@@ -67,6 +72,20 @@ $(OUTPUTDIR)/$(OUTPUT_NAME): $(BUILDDIR)/$(TARGET)/$(TARGET).elf
$(BUILDDIR)/$(TARGET)/$(TARGET).elf: $(OBJS) $(BUILDDIR)/$(TARGET)/$(TARGET).elf: $(OBJS)
$(CC) $(LDFLAGS) -T $(SOURCEDIR)/link.ld $^ -o $@ $(CC) $(LDFLAGS) -T $(SOURCEDIR)/link.ld $^ -o $@
$(BUILDDIR)/$(TARGET)/ram_test_main.o: tools/ram_test_main.c
@mkdir -p "$(@D)"
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@
$(BUILDDIR)/$(TARGET)/ram_test.elf: $(OBJS_RAMTEST)
$(CC) $(LDFLAGS) -T $(SOURCEDIR)/link.ld $^ -o $@
$(RAMTEST_BIN): $(BUILDDIR)/$(TARGET)/ram_test.elf
@mkdir -p "$(@D)"
$(OBJCOPY) -S -O binary $< $@
@echo "RAM-Test payload: $(RAMTEST_BIN) ($$(wc -c < $@) bytes)"
ram-test: $(RAMTEST_BIN)
$(BUILDDIR)/$(TARGET)/%.o: $(SOURCEDIR)/%.c $(BUILDDIR)/$(TARGET)/%.o: $(SOURCEDIR)/%.c
@mkdir -p "$(@D)" @mkdir -p "$(@D)"
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@ $(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@

View File

@@ -101,3 +101,34 @@ int cleanup_backup(void) {
} }
return FR_OK; return FR_OK;
} }
int backup_horizon_oc_config_for_oc_update(void) {
if (!path_exists(HORIZON_OC_CONFIG_PATH))
return FR_OK;
int res = f_mkdir(HORIZON_OC_UPDATE_BACKUP_DIR);
if (res != FR_OK && res != FR_EXIST)
return res;
return file_copy(HORIZON_OC_CONFIG_PATH, HORIZON_OC_UPDATE_BACKUP_INI);
}
int restore_horizon_oc_config_after_oc_update(void) {
if (!path_exists(HORIZON_OC_UPDATE_BACKUP_INI))
return FR_OK;
int res = f_mkdir("sd:/config");
if (res != FR_OK && res != FR_EXIST)
return res;
res = f_mkdir("sd:/config/horizon-oc");
if (res != FR_OK && res != FR_EXIST)
return res;
return file_copy(HORIZON_OC_UPDATE_BACKUP_INI, HORIZON_OC_CONFIG_PATH);
}
int cleanup_horizon_oc_update_backup(void) {
if (!path_exists(HORIZON_OC_UPDATE_BACKUP_DIR))
return FR_OK;
return folder_delete(HORIZON_OC_UPDATE_BACKUP_DIR);
}

View File

@@ -8,6 +8,12 @@
#define TEMP_BACKUP_PATH "sd:/temp_backup" #define TEMP_BACKUP_PATH "sd:/temp_backup"
/** Live HorizonOC settings (Overclock tool). */
#define HORIZON_OC_CONFIG_PATH "sd:/config/horizon-oc/config.ini"
/** Scratch dir during OC-variant update only; removed after successful restore. */
#define HORIZON_OC_UPDATE_BACKUP_DIR "sd:/.omninx_oc_update"
#define HORIZON_OC_UPDATE_BACKUP_INI HORIZON_OC_UPDATE_BACKUP_DIR "/config.ini"
// Backup user data (DBI, Tinfoil, prod.keys) before clean install // Backup user data (DBI, Tinfoil, prod.keys) before clean install
int backup_user_data(void); int backup_user_data(void);
@@ -16,3 +22,10 @@ int restore_user_data(void);
// Clean up temporary backup directory // Clean up temporary backup directory
int cleanup_backup(void); int cleanup_backup(void);
// HorizonOC: backup config.ini before OC pack update (update mode only); no-op if missing
int backup_horizon_oc_config_for_oc_update(void);
// Restore after file copy; no-op if no backup from this run
int restore_horizon_oc_config_after_oc_update(void);
// Delete sd:/.omninx_oc_update after successful restore
int cleanup_horizon_oc_update_backup(void);

View File

@@ -199,6 +199,8 @@ static const char* clean_switch_files_to_delete[] = {
"sd:/switch/DBI/DBI_845_EN.nro", "sd:/switch/DBI/DBI_845_EN.nro",
"sd:/switch/DBI/DBI_849_DE.nro", "sd:/switch/DBI/DBI_849_DE.nro",
"sd:/switch/DBI/DBI_849_EN.nro", "sd:/switch/DBI/DBI_849_EN.nro",
"sd:/switch/DBI/DBI_874_DE.nro",
"sd:/switch/DBI/DBI_874_EN.nro",
"sd:/switch/DBI_810_DE/DBI_810.nro", "sd:/switch/DBI_810_DE/DBI_810.nro",
"sd:/switch/DBI_810_DE/DBI_810_DE.nro", "sd:/switch/DBI_810_DE/DBI_810_DE.nro",
"sd:/switch/DBI_810_EN/DBI_810_EN.nro", "sd:/switch/DBI_810_EN/DBI_810_EN.nro",
@@ -270,6 +272,10 @@ static const char* clean_misc_dirs_to_delete[] = {
// Miscellaneous files to delete // Miscellaneous files to delete
static const char* clean_misc_files_to_delete[] = { static const char* clean_misc_files_to_delete[] = {
"sd:/TegraExplorer/scripts/Reparatur Skript fuer error 010000000000BD00.te",
"sd:/TegraExplorer/scripts/Reparatur Skript fuer error 0100000000001000.te",
"sd:/TegraExplorer/scripts/Reparatur Skript fuer error 690000000000000D.te",
"sd:/TegraExplorer/scripts/Reparatur Skript fuer error 4200000000000010.te",
"sd:/fusee-primary.bin", "sd:/fusee-primary.bin",
"sd:/fusee.bin", "sd:/fusee.bin",
"sd:/SaltySD/exceptions.txt", "sd:/SaltySD/exceptions.txt",
@@ -283,13 +289,12 @@ static const char* clean_misc_files_to_delete[] = {
// Old version marker files to delete (clean install only) // Old version marker files to delete (clean install only)
static const char* old_version_files_to_delete[] = { static const char* old_version_files_to_delete[] = {
"sd:/1.0.0l",
"sd:/1.0.0s",
"sd:/1.0.0oc",
"sd:/1.4.0-pre", "sd:/1.4.0-pre",
"sd:/1.4.0-pre-c", "sd:/1.4.0-pre-c",
"sd:/1.4.0-pre-d", "sd:/1.4.0-pre-d",
"sd:/1.4.1", "sd:/1.4.1",
"sd:/1.5.0", "sd:/1.5.0",
"sd:/1.6.0",
"sd:/1.6.1",
NULL NULL
}; };

View File

@@ -80,6 +80,8 @@ static const char* atmosphere_files_to_delete[] = {
"sd:/atmosphere/package3", "sd:/atmosphere/package3",
"sd:/atmosphere/reboot_payload.bin", "sd:/atmosphere/reboot_payload.bin",
"sd:/atmosphere/stratosphere.romfs", "sd:/atmosphere/stratosphere.romfs",
"sd:/atmosphere/kips/loader.kip",
"sd:/atmosphere/kips/hoc.kip",
NULL NULL
}; };
@@ -122,8 +124,9 @@ static const char* config_dirs_to_delete[] = {
}; };
// Switch directories to delete // Switch directories to delete
// NOTE: .overlays and .packages excluded - preserve UltraHand .offload hide state during updates
static const char* switch_dirs_to_delete[] = { static const char* switch_dirs_to_delete[] = {
"sd:/switch/.packages",
"sd:/switch/.overlays",
"sd:/switch/90DNS_tester", "sd:/switch/90DNS_tester",
"sd:/switch/aio-switch-updater", "sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader", "sd:/switch/amsPLUS-downloader",
@@ -171,7 +174,6 @@ static const char* switch_dirs_to_delete[] = {
"sd:/switch/Switch-Time", "sd:/switch/Switch-Time",
"sd:/switch/SwitchIdent", "sd:/switch/SwitchIdent",
"sd:/switch/Switch_themes_Installer", "sd:/switch/Switch_themes_Installer",
"sd:/switch/Switchfin",
"sd:/switch/Sys-Clk Manager", "sd:/switch/Sys-Clk Manager",
"sd:/switch/Sys-Con", "sd:/switch/Sys-Con",
"sd:/switch/sys-clk-manager", "sd:/switch/sys-clk-manager",
@@ -222,9 +224,9 @@ static const char* switch_files_to_delete[] = {
"sd:/switch/SimpleModDownloader.nro", "sd:/switch/SimpleModDownloader.nro",
"sd:/switch/SimpleModManager.nro", "sd:/switch/SimpleModManager.nro",
"sd:/switch/sphaira.nro", "sd:/switch/sphaira.nro",
"sd:/switch/swr-ini-tool/swr-ini-tool.nro",
"sd:/switch/SwitchIdent.nro", "sd:/switch/SwitchIdent.nro",
"sd:/switch/Switch_themes_Installer/NXThemesInstaller.nro", "sd:/switch/Switch_themes_Installer/NXThemesInstaller.nro",
"sd:/switch/Switchfin.nro",
"sd:/switch/Sys-Clk Manager/sys-clk-manager.nro", "sd:/switch/Sys-Clk Manager/sys-clk-manager.nro",
"sd:/switch/Sys-Con.nro", "sd:/switch/Sys-Con.nro",
"sd:/switch/sys-clk-manager.nro", "sd:/switch/sys-clk-manager.nro",

46
source/dram_fuse.c Normal file
View File

@@ -0,0 +1,46 @@
/*
* DRAM capacity from fuse (SKU), not physical probe.
*/
#include "dram_fuse.h"
#include <soc/fuse.h>
#include <soc/hw_init.h>
#include <soc/t210.h>
static int mariko_dram_mib(u32 dram_id)
{
switch (dram_id) {
case 9:
case 13:
case 18:
case 21:
case 23:
case 28:
return 8192;
default:
if (dram_id >= 3 && dram_id <= 28)
return 4096;
return -1;
}
}
static int erista_dram_mib(u32 dram_id)
{
if (dram_id == 4)
return 6144;
if (dram_id <= 6)
return 4096;
return -1;
}
int dram_capacity_mib_from_fuse(void)
{
u32 nid = fuse_read_dramid(false);
u32 chip = hw_get_chip_id();
if (chip == GP_HIDREV_MAJOR_T210)
return erista_dram_mib(nid);
if (chip == GP_HIDREV_MAJOR_T210B01)
return mariko_dram_mib(nid);
return -1;
}

6
source/dram_fuse.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <utils/types.h>
/* DRAM capacity in MiB from fuse DRAM ID + SoC (same table as RAM test payload). */
int dram_capacity_mib_from_fuse(void);

View File

@@ -3,13 +3,21 @@
*/ */
#include "install.h" #include "install.h"
#include "backup.h"
#include "fs.h" #include "fs.h"
#include "screenshot.h"
#include "version.h" #include "version.h"
#include "gfx.h" #include "gfx.h"
#include <libs/fatfs/ff.h> #include <libs/fatfs/ff.h>
#include <input/joycon.h>
#include <mem/heap.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <utils/btn.h>
#include <utils/ini.h>
#include <utils/list.h>
#include <utils/sprintf.h> #include <utils/sprintf.h>
#include <utils/util.h>
#define GROUPED_DELETE_MAX_ENTRIES 512 #define GROUPED_DELETE_MAX_ENTRIES 512
@@ -32,10 +40,25 @@
#define COLOR_ORANGE 0xFF00A5FF #define COLOR_ORANGE 0xFF00A5FF
#define COLOR_RED 0xFFFF0000 #define COLOR_RED 0xFFFF0000
static inline bool cancel_combo_pressed(u8 btn)
{
return (btn & (BTN_VOL_UP | BTN_VOL_DOWN)) == (BTN_VOL_UP | BTN_VOL_DOWN);
}
void install_set_color(u32 color) { void install_set_color(u32 color) {
gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol); gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol);
} }
/* Same top bar as main.c print_header() (without full screen clear). */
static void install_print_main_header(void)
{
install_set_color(COLOR_CYAN);
gfx_printf("===================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("===================================\n\n");
install_set_color(COLOR_WHITE);
}
// Check if we need to clear screen (when getting close to bottom) // Check if we need to clear screen (when getting close to bottom)
void install_check_and_clear_screen_if_needed(void) { void install_check_and_clear_screen_if_needed(void) {
// In the gfx system: // In the gfx system:
@@ -50,13 +73,7 @@ void install_check_and_clear_screen_if_needed(void) {
// Clear screen and reset position // Clear screen and reset position
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0); gfx_con_setpos(0, 0);
install_print_main_header();
// Reprint same header as initial launch
install_set_color(COLOR_CYAN);
gfx_printf("===================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("===================================\n\n");
install_set_color(COLOR_WHITE);
} }
} }
@@ -113,6 +130,38 @@ void install_combine_path(char *result, size_t size, const char *base, const cha
} }
} }
/* Copy every regular file at staging root to dst_root; subdirs and volume labels skipped. */
int install_copy_staging_root_files(const char *staging, const char *dst_root) {
DIR dir;
FILINFO fno;
char src_full[256];
char dst_full[256];
int res = f_opendir(&dir, staging);
if (res != FR_OK)
return res;
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0)
break;
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0')))
continue;
if (fno.fattrib & (AM_DIR | AM_VOL))
continue;
install_combine_path(src_full, sizeof(src_full), staging, fno.fname);
install_combine_path(dst_full, sizeof(dst_full), dst_root, fno.fname);
res = file_copy(src_full, dst_full);
if (res != FR_OK) {
f_closedir(&dir);
return res;
}
}
f_closedir(&dir);
return FR_OK;
}
// Recursive folder copy with progress tracking // Recursive folder copy with progress tracking
static int folder_copy_progress_recursive(const char *src, const char *dst, int *copied, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) { static int folder_copy_progress_recursive(const char *src, const char *dst, int *copied, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
DIR dir; DIR dir;
@@ -275,167 +324,6 @@ int folder_copy_with_progress_v2(const char *src, const char *dst, const char *d
return res; return res;
} }
// Copy one directory (e.g. .overlays or .packages) with .offload awareness: if the same
// name exists in dst_offload, copy pack's version into dst_offload (update in place);
// otherwise copy to dst_parent. Used only for update mode to preserve UltraHand hide state.
static int copy_dir_offload_aware(const char *src_parent, const char *dst_parent, const char *dst_offload,
const char *display_name, int *copied, int total, u32 start_x, u32 start_y, int *last_percent)
{
DIR dir;
FILINFO fno;
int res;
char src_full[256];
char dst_main[256];
char dst_off[256];
res = f_opendir(&dir, src_parent);
if (res != FR_OK)
return res;
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0')))
continue;
if (strcmp(fno.fname, ".offload") == 0)
continue;
install_combine_path(src_full, sizeof(src_full), src_parent, fno.fname);
install_combine_path(dst_off, sizeof(dst_off), dst_offload, fno.fname);
if (install_path_exists(dst_off)) {
res = (fno.fattrib & AM_DIR) ? folder_copy(src_full, dst_offload) : file_copy(src_full, dst_off);
} else {
install_combine_path(dst_main, sizeof(dst_main), dst_parent, fno.fname);
res = (fno.fattrib & AM_DIR) ? folder_copy(src_full, dst_parent) : file_copy(src_full, dst_main);
}
(*copied)++;
if (total > 0) {
int percent = (*copied * 100) / total;
if (percent != *last_percent || *copied % 20 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, *copied, total);
install_set_color(COLOR_WHITE);
*last_percent = percent;
}
}
if (res != FR_OK) {
f_closedir(&dir);
return res;
}
}
f_closedir(&dir);
return FR_OK;
}
// Copy switch/ folder for update mode only: preserve SD's .overlays/.offload and
// .packages/.offload; for items that exist there, update in place instead of main area.
int folder_copy_switch_update_offload_aware(const char *src_switch, const char *dst_base, const char *display_name) {
char dst_switch[256];
char src_overlays[256], src_packages[256];
char dst_overlays[256], dst_offload_ovl[256], dst_packages[256], dst_offload_pkg[256];
DIR dir;
FILINFO fno;
int res;
int copied = 0;
int total;
int last_percent = -1;
u32 start_x, start_y;
install_combine_path(dst_switch, sizeof(dst_switch), dst_base, "switch");
if (!install_path_exists(src_switch)) {
install_set_color(COLOR_ORANGE);
gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name);
install_set_color(COLOR_WHITE);
return FR_NO_FILE;
}
total = install_count_directory_items(src_switch);
if (total == 0) {
f_mkdir(dst_switch);
return FR_OK;
}
gfx_con_getpos(&start_x, &start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total);
install_set_color(COLOR_WHITE);
res = f_mkdir(dst_switch);
if (res != FR_OK && res != FR_EXIST) return res;
res = f_opendir(&dir, src_switch);
if (res != FR_OK) return res;
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0')))
continue;
if (strcmp(fno.fname, ".overlays") == 0) {
install_combine_path(src_overlays, sizeof(src_overlays), src_switch, ".overlays");
install_combine_path(dst_overlays, sizeof(dst_overlays), dst_switch, ".overlays");
install_combine_path(dst_offload_ovl, sizeof(dst_offload_ovl), dst_overlays, ".offload");
if (!install_path_exists(src_overlays)) { f_closedir(&dir); continue; }
f_mkdir(dst_overlays);
f_mkdir(dst_offload_ovl);
res = copy_dir_offload_aware(src_overlays, dst_overlays, dst_offload_ovl, display_name,
&copied, total, start_x, start_y, &last_percent);
} else if (strcmp(fno.fname, ".packages") == 0) {
install_combine_path(src_packages, sizeof(src_packages), src_switch, ".packages");
install_combine_path(dst_packages, sizeof(dst_packages), dst_switch, ".packages");
install_combine_path(dst_offload_pkg, sizeof(dst_offload_pkg), dst_packages, ".offload");
if (!install_path_exists(src_packages)) { f_closedir(&dir); continue; }
f_mkdir(dst_packages);
f_mkdir(dst_offload_pkg);
{ /* package.ini into main area */
char src_ini[256], dst_ini[256];
s_printf(src_ini, "%s/package.ini", src_packages);
s_printf(dst_ini, "%s/package.ini", dst_packages);
if (install_path_exists(src_ini)) {
res = file_copy(src_ini, dst_ini);
if (res == FR_OK) copied++;
}
}
res = copy_dir_offload_aware(src_packages, dst_packages, dst_offload_pkg, display_name,
&copied, total, start_x, start_y, &last_percent);
} else {
char src_full[256], dst_full[256];
install_combine_path(src_full, sizeof(src_full), src_switch, fno.fname);
install_combine_path(dst_full, sizeof(dst_full), dst_switch, fno.fname);
if (fno.fattrib & AM_DIR)
res = folder_copy(src_full, dst_switch);
else
res = file_copy(src_full, dst_full);
copied++;
}
if (res != FR_OK) break;
if (total > 0 && (copied % 20 == 0 || copied == total)) {
int percent = (copied * 100) / total;
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, copied, total);
install_set_color(COLOR_WHITE);
}
}
f_closedir(&dir);
gfx_con_setpos(start_x, start_y);
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" Kopiere: %s [100%%] (%d/%d) - Fertig!\n", display_name, copied, total);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_RED);
gfx_printf(" Kopiere: %s - Fehlgeschlagen!\n", display_name);
install_set_color(COLOR_WHITE);
}
return res;
}
// Recursive folder delete with progress tracking (shared implementation) // Recursive folder delete with progress tracking (shared implementation)
int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) { int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
DIR dir; DIR dir;
@@ -747,11 +635,259 @@ int delete_path_list(const char* paths[], const char* description) {
return (failed == 0) ? FR_OK : FR_DISK_ERR; return (failed == 0) ? FR_OK : FR_DISK_ERR;
} }
#define HEKATE_8GB_SRC "sd:/bootloader/hekate_8gb.bin"
#define PAYLOAD_BIN_DST "sd:/payload.bin"
#define UPDATE_BIN_DST "sd:/bootloader/update.bin"
#define RAM_CONFIG_PATH "sd:/config/omninx/ram_config.ini"
enum { RAM_READ_OK = 0, RAM_READ_NEED_MENU = 1 };
static void unlink_ignore_err(const char *path)
{
FILINFO fno;
if (f_stat(path, &fno) != FR_OK)
return;
if (fno.fattrib & AM_RDO)
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
f_unlink(path);
}
static void ram_config_ensure_parent_dirs(void)
{
f_mkdir("sd:/config");
f_mkdir("sd:/config/omninx");
}
static int ram_config_write(bool use_8gb)
{
ram_config_ensure_parent_dirs();
FIL f;
if (f_open(&f, RAM_CONFIG_PATH, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
return FR_DISK_ERR;
static const char sec[] = "[Ram]\n";
static const char line0[] = "8gb=0\n";
static const char line1[] = "8gb=1\n";
UINT bw;
f_write(&f, sec, sizeof(sec) - 1, &bw);
f_write(&f, use_8gb ? line1 : line0, sizeof(line0) - 1, &bw);
f_close(&f);
return FR_OK;
}
static void ram_config_free_sections(link_t *sections)
{
LIST_FOREACH_ENTRY(ini_sec_t, sec, sections, link) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key) free(kv->key);
if (kv->val) free(kv->val);
}
if (sec->name) free(sec->name);
}
}
/* Valid [Ram] 8gb=0|1 → silent; missing file or invalid → menu. */
static int read_ram_config_silent(const char *path, bool *use_8gb)
{
if (!install_path_exists(path))
return RAM_READ_NEED_MENU;
link_t sections;
list_init(&sections);
if (ini_parse(&sections, (char *)path, false) != 1) {
ram_config_free_sections(&sections);
return RAM_READ_NEED_MENU;
}
bool found = false;
bool eight = false;
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
if (sec->name && !strcmp(sec->name, "Ram")) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key && kv->val && !strcmp(kv->key, "8gb")) {
if (!strcmp(kv->val, "0")) {
found = true;
eight = false;
} else if (!strcmp(kv->val, "1")) {
found = true;
eight = true;
}
break;
}
}
break;
}
}
ram_config_free_sections(&sections);
if (!found)
return RAM_READ_NEED_MENU;
*use_8gb = eight;
return RAM_READ_OK;
}
/* Same navigation as main.c multi-variant menu; returns false if user aborts (payload/reboot). */
static bool install_ram_config_menu(bool *use_8gb)
{
jc_init_hw();
while (btn_read() & BTN_POWER)
msleep(50);
int selected = 0;
int prev_selected = 0;
bool confirmed = false;
const int n = 2;
const char *labels[2] = {
"4GB Konsole (Standard)",
"8GB Konsole",
};
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
install_print_main_header();
install_set_color(COLOR_YELLOW);
gfx_printf("Hekate Payload Auswahl:\n\n");
install_set_color(COLOR_WHITE);
gfx_printf("Wenn du einen 8GB RAM-Umbau hast, waehle 8GB Konsole aus.\n");
gfx_printf("Alle anderen waehlen 4GB Konsole aus.\n\n");
install_set_color(COLOR_RED);
gfx_printf("Warnung: Solltest du keinen RAM-Umbau haben, waehle auf keinen\n");
gfx_printf("Fall 8GB aus, da du sonst Boot Probleme bekommst!!!\n\n");
install_set_color(COLOR_WHITE);
u32 menu_x, menu_y;
gfx_con_getpos(&menu_x, &menu_y);
for (int i = 0; i < n; i++) {
if (i == selected) {
install_set_color(COLOR_GREEN);
gfx_printf(" > %s\n", labels[i]);
install_set_color(COLOR_WHITE);
} else {
gfx_printf(" %s\n", labels[i]);
}
}
gfx_printf("\n");
install_set_color(COLOR_CYAN);
gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n");
gfx_printf("Vol+ und Vol- gleichzeitig: Abbrechen (Hekate)\n");
install_set_color(COLOR_WHITE);
bool prev_up = false, prev_down = false;
bool menu_aborted = false;
while (!confirmed && !menu_aborted) {
if (selected != prev_selected) {
gfx_con_setpos(menu_x, menu_y + (u32)prev_selected * 16);
install_set_color(COLOR_WHITE);
gfx_printf(" %s\n", labels[prev_selected]);
gfx_con_setpos(menu_x, menu_y + (u32)selected * 16);
install_set_color(COLOR_GREEN);
gfx_printf(" > %s\n", labels[selected]);
install_set_color(COLOR_WHITE);
prev_selected = selected;
}
jc_gamepad_rpt_t *jc = joycon_poll();
u8 btn = btn_read();
if (jc && jc->cap)
take_screenshot();
if (cancel_combo_pressed(btn)) {
menu_aborted = true;
break;
}
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 + n) % n;
else if (cur_down && !prev_down)
selected = (selected + 1) % n;
else if (jc && jc->a)
confirmed = true;
prev_up = cur_up;
prev_down = cur_down;
if (btn & BTN_POWER)
confirmed = true;
msleep(50);
}
if (menu_aborted) {
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Abgebrochen. Starte Hekate...\n");
install_set_color(COLOR_WHITE);
msleep(500);
installer_launch_hekate_payload();
return false;
}
*use_8gb = (selected == 1);
ram_config_write(*use_8gb);
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
install_print_main_header();
return true;
}
/* After pack copy: use ram_config.ini [Ram] 8gb=0|1 if present; else menu, write file, then apply. No fuse autodetect. */
static void install_hekate_8gb_post_copy(void)
{
if (!install_path_exists(HEKATE_8GB_SRC))
return;
bool use_8gb;
if (read_ram_config_silent(RAM_CONFIG_PATH, &use_8gb) != RAM_READ_OK) {
if (!install_ram_config_menu(&use_8gb))
return;
}
if (use_8gb) {
int r1 = file_copy(HEKATE_8GB_SRC, PAYLOAD_BIN_DST);
int r2 = file_copy(HEKATE_8GB_SRC, UPDATE_BIN_DST);
if (r1 == FR_OK && r2 == FR_OK)
unlink_ignore_err(HEKATE_8GB_SRC);
} else {
unlink_ignore_err(HEKATE_8GB_SRC);
}
}
// Main installation function // Main installation function
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) { int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
int res; int res;
if (mode == INSTALL_MODE_UPDATE) { if (mode == INSTALL_MODE_UPDATE) {
if (pack_variant == VARIANT_OC) {
bool had_horizon_cfg = install_path_exists(HORIZON_OC_CONFIG_PATH);
install_set_color(COLOR_YELLOW);
gfx_printf("HorizonOC: Sichere config.ini...\n");
install_set_color(COLOR_WHITE);
res = backup_horizon_oc_config_for_oc_update();
if (res != FR_OK)
return res;
if (had_horizon_cfg) {
install_set_color(COLOR_GREEN);
gfx_printf(" [OK] Bestehende config.ini gesichert.\n");
install_set_color(COLOR_WHITE);
}
}
// Update mode: selective cleanup then install // Update mode: selective cleanup then install
install_set_color(COLOR_YELLOW); install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 1: Bereinigung...\n"); gfx_printf("Schritt 1: Bereinigung...\n");
@@ -767,6 +903,28 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
res = update_mode_install(pack_variant); res = update_mode_install(pack_variant);
if (res != FR_OK) return res; if (res != FR_OK) return res;
install_hekate_8gb_post_copy();
if (pack_variant == VARIANT_OC) {
bool had_horizon_bak = install_path_exists(HORIZON_OC_UPDATE_BACKUP_INI);
install_check_and_clear_screen_if_needed();
install_set_color(COLOR_YELLOW);
gfx_printf("HorizonOC: Stelle config.ini wieder her...\n");
install_set_color(COLOR_WHITE);
res = restore_horizon_oc_config_after_oc_update();
if (res != FR_OK)
return res;
install_set_color(COLOR_GREEN);
if (had_horizon_bak)
gfx_printf(" [OK] Deine HorizonOC-Einstellungen wurden wiederhergestellt.\n");
else
gfx_printf(" [OK] Keine fruehere config.ini — Standard aus dem Paket bleibt.\n");
install_set_color(COLOR_WHITE);
res = cleanup_horizon_oc_update_backup();
if (res != FR_OK)
return res;
}
install_check_and_clear_screen_if_needed(); install_check_and_clear_screen_if_needed();
// Remove staging directory (installed pack) // Remove staging directory (installed pack)
res = cleanup_staging_directory(pack_variant); res = cleanup_staging_directory(pack_variant);
@@ -806,6 +964,8 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
res = clean_mode_install(pack_variant); res = clean_mode_install(pack_variant);
if (res != FR_OK) return res; if (res != FR_OK) return res;
install_hekate_8gb_post_copy();
install_check_and_clear_screen_if_needed(); install_check_and_clear_screen_if_needed();
// Remove staging directory (installed pack) // Remove staging directory (installed pack)
res = cleanup_staging_directory(pack_variant); res = cleanup_staging_directory(pack_variant);

View File

@@ -13,6 +13,9 @@ typedef enum {
INSTALL_MODE_CLEAN // No OmniNX - selective deletion from clean list INSTALL_MODE_CLEAN // No OmniNX - selective deletion from clean list
} install_mode_t; } install_mode_t;
// If a sub-menu aborts to Hekate / update.bin (does not return if launch succeeds)
void installer_launch_hekate_payload(void);
// Main installation function // Main installation function
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode); int perform_installation(omninx_variant_t pack_variant, install_mode_t mode);
@@ -35,10 +38,9 @@ void install_check_and_clear_screen_if_needed(void);
bool install_path_exists(const char *path); bool install_path_exists(const char *path);
int install_count_directory_items(const char *path); int install_count_directory_items(const char *path);
void install_combine_path(char *result, size_t size, const char *base, const char *add); void install_combine_path(char *result, size_t size, const char *base, const char *add);
int install_copy_staging_root_files(const char *staging, const char *dst_root);
int delete_path_list(const char* paths[], const char* description); int delete_path_list(const char* paths[], const char* description);
int delete_path_lists_grouped(const char *folder_display_name, ...); int delete_path_lists_grouped(const char *folder_display_name, ...);
int folder_delete_single_with_progress(const char *path, const char *display_name); int folder_delete_single_with_progress(const char *path, const char *display_name);
int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent); int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent);
int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name); int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name);
// Update-only: copy switch/ preserving .overlays/.offload and .packages/.offload (UltraHand hide state)
int folder_copy_switch_update_offload_aware(const char *src_switch, const char *dst_base, const char *display_name);

View File

@@ -78,7 +78,6 @@ int update_mode_install(omninx_variant_t variant) {
int res; int res;
const char* staging = get_staging_path(variant); const char* staging = get_staging_path(variant);
char src_path[256]; char src_path[256];
char dst_path[256];
if (!staging) { if (!staging) {
return FR_INVALID_PARAMETER; return FR_INVALID_PARAMETER;
@@ -103,7 +102,11 @@ int update_mode_install(omninx_variant_t variant) {
if (res != FR_OK && res != FR_NO_FILE) return res; if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/switch", staging); s_printf(src_path, "%s/switch", staging);
res = folder_copy_switch_update_offload_aware(src_path, "sd:/", "switch/"); res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/themes", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "themes/");
if (res != FR_OK && res != FR_NO_FILE) return res; if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/warmboot_mariko", staging); s_printf(src_path, "%s/warmboot_mariko", staging);
@@ -120,29 +123,9 @@ int update_mode_install(omninx_variant_t variant) {
gfx_printf(" Kopiere Root-Dateien...\n"); gfx_printf(" Kopiere Root-Dateien...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
s_printf(src_path, "%s/boot.dat", staging); res = install_copy_staging_root_files(staging, "sd:/");
s_printf(dst_path, "sd:/boot.dat"); if (res != FR_OK)
if (path_exists(src_path)) file_copy(src_path, dst_path); return res;
s_printf(src_path, "%s/boot.ini", staging);
s_printf(dst_path, "sd:/boot.ini");
if (path_exists(src_path)) file_copy(src_path, dst_path);
s_printf(src_path, "%s/exosphere.ini", staging);
s_printf(dst_path, "sd:/exosphere.ini");
if (path_exists(src_path)) file_copy(src_path, dst_path);
s_printf(src_path, "%s/hbmenu.nro", staging);
s_printf(dst_path, "sd:/hbmenu.nro");
if (path_exists(src_path)) file_copy(src_path, dst_path);
s_printf(src_path, "%s/loader.bin", staging);
s_printf(dst_path, "sd:/loader.bin");
if (path_exists(src_path)) file_copy(src_path, dst_path);
s_printf(src_path, "%s/payload.bin", staging);
s_printf(dst_path, "sd:/payload.bin");
if (path_exists(src_path)) file_copy(src_path, dst_path);
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf(" Kopie abgeschlossen!\n"); gfx_printf(" Kopie abgeschlossen!\n");

View File

@@ -35,6 +35,7 @@
#include "fs.h" #include "fs.h"
#include "version.h" #include "version.h"
#include "install.h" #include "install.h"
#include "screenshot.h"
// Configuration // Configuration
#define PAYLOAD_PATH "sd:/bootloader/update.bin" #define PAYLOAD_PATH "sd:/bootloader/update.bin"
@@ -115,6 +116,12 @@ static void print_header(void) {
set_color(COLOR_WHITE); 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);
}
void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) { void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) {
memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ); memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ);
@@ -182,6 +189,12 @@ static int file_exists(const char *path) {
return (f_stat(path, &fno) == FR_OK); 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); extern void pivot_stack(u32 stack_top);
void ipl_main(void) { void ipl_main(void) {
@@ -243,11 +256,13 @@ void ipl_main(void) {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n"); gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um Hekate zu starten...\n"); gfx_printf("um Hekate zu starten...\n");
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
} else { } else {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n"); gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um den Neustart zu starten...\n"); gfx_printf("um den Neustart zu starten...\n");
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
} }
@@ -264,9 +279,17 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button // Check joycon A button; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
}
if (cancel_combo_pressed(btn_state)) {
button_pressed = true; button_pressed = true;
break; break;
} }
@@ -324,12 +347,14 @@ void ipl_main(void) {
gfx_printf("\n"); gfx_printf("\n");
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n"); gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n");
gfx_printf("Vol+ und Vol- gleichzeitig: Abbrechen (Hekate)\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
// Edge detection: only move on press (not while held) // Edge detection: only move on press (not while held)
bool prev_up = false, prev_down = false; bool prev_up = false, prev_down = false;
bool menu_aborted = false;
while (!confirmed) { while (!confirmed && !menu_aborted) {
// On selection change: redraw only the two affected lines (no full clear) // On selection change: redraw only the two affected lines (no full clear)
if (selected != prev_selected) { if (selected != prev_selected) {
gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16); gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16);
@@ -345,6 +370,14 @@ void ipl_main(void) {
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
u8 btn = btn_read(); u8 btn = btn_read();
if (jc && jc->cap)
take_screenshot();
if (cancel_combo_pressed(btn)) {
menu_aborted = true;
break;
}
// D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down) // D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down)
bool cur_up = false, cur_down = false; bool cur_up = false, cur_down = false;
if (jc) { if (jc) {
@@ -370,6 +403,20 @@ void ipl_main(void) {
msleep(50); msleep(50);
} }
if (menu_aborted) {
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]; pack_variant = variants_present[selected];
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
print_header(); print_header();
@@ -395,14 +442,19 @@ void ipl_main(void) {
gfx_printf("mit angeschlossenem Ladegeraet durchgefuehrt werden.\n\n"); gfx_printf("mit angeschlossenem Ladegeraet durchgefuehrt werden.\n\n");
set_color(COLOR_YELLOW); set_color(COLOR_YELLOW);
gfx_printf("Stecke das Ladegeraet an und warte...\n"); gfx_printf("Stecke das Ladegeraet an und warte...\n");
gfx_printf("Oder druecke + und - zum Abbrechen.\n"); gfx_printf("Oder Vol+ und Vol- (Konsole) zum Abbrechen.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
jc_init_hw(); jc_init_hw();
while (btn_read() & BTN_POWER) { msleep(50); } while (btn_read() & BTN_POWER) { msleep(50); }
bool user_cancelled = false; bool user_cancelled = false;
while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected() && !user_cancelled) { while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected() && !user_cancelled) {
u8 pbtn = btn_read();
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->plus && jc->minus) { if (jc) {
if (jc->cap)
take_screenshot();
}
if (cancel_combo_pressed(pbtn)) {
user_cancelled = true; user_cancelled = true;
break; break;
} }
@@ -461,16 +513,37 @@ void ipl_main(void) {
gfx_printf("Empfohlene Karten: Samsung EVO Plus, EVO Select und PRO Plus.\n\n"); gfx_printf("Empfohlene Karten: Samsung EVO Plus, EVO Select und PRO Plus.\n\n");
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("Druecke A (oder Power), um trotzdem fortzufahren.\n"); 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); set_color(COLOR_WHITE);
while (btn_read() & BTN_POWER) { msleep(50); } while (btn_read() & BTN_POWER) { msleep(50); }
bool acknowledged = false; bool acknowledged = false;
while (!acknowledged) { bool uhs_aborted = false;
while (!acknowledged && !uhs_aborted) {
u8 btn_state = btn_read(); u8 btn_state = btn_read();
if (btn_state & BTN_POWER) acknowledged = true; if (btn_state & BTN_POWER) acknowledged = true;
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) acknowledged = true; if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) acknowledged = true;
}
if (cancel_combo_pressed(btn_state)) {
uhs_aborted = true;
break;
}
msleep(50); 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 // Wait for A and Power to be released so the same press doesn't start the install
while (btn_read() & BTN_POWER) { msleep(50); } while (btn_read() & BTN_POWER) { msleep(50); }
bool released = false; bool released = false;
@@ -510,7 +583,7 @@ void ipl_main(void) {
gfx_printf("um die Installation zu starten...\n"); gfx_printf("um die Installation zu starten...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("Druecke + und - gleichzeitig zum Abbrechen (zurueck zu Hekate).\n"); gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
// Wait for A/Power to start, or +/- to cancel // Wait for A/Power to start, or +/- to cancel
@@ -530,18 +603,19 @@ void ipl_main(void) {
break; break;
} }
// Check joycon buttons // Check joycon buttons; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) { if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) { if (jc->a) {
button_pressed = true; button_pressed = true;
break; break;
} }
// + and - simultaneously = cancel, return to hekate }
if (jc->plus && jc->minus) { if (cancel_combo_pressed(btn_state)) {
cancelled = true; cancelled = true;
break; break;
}
} }
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
@@ -578,6 +652,7 @@ void ipl_main(void) {
// Wait 3 seconds before clearing screen to allow errors to be visible // Wait 3 seconds before clearing screen to allow errors to be visible
msleep(3000); msleep(3000);
// take_screenshot(); // Take a screenshot of the installation summary
// Clear screen for final summary to ensure it's visible // Clear screen for final summary to ensure it's visible
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
@@ -630,11 +705,15 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button // Check joycon A button; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
button_pressed = true; if (jc->cap)
break; take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
} }
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
@@ -672,9 +751,13 @@ void ipl_main(void) {
} }
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
button_pressed = true; if (jc->cap)
break; take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
} }
msleep(50); msleep(50);

View File

@@ -184,6 +184,11 @@ static void _sd_deinit()
void sd_unmount() { _sd_deinit(); } void sd_unmount() { _sd_deinit(); }
void sd_end() { _sd_deinit(); } void sd_end() { _sd_deinit(); }
bool sd_get_card_mounted(void)
{
return sd_mounted;
}
void *sd_file_read(const char *path, u32 *fsize) void *sd_file_read(const char *path, u32 *fsize)
{ {
FIL fp; FIL fp;

111
source/screenshot.c Normal file
View File

@@ -0,0 +1,111 @@
/*
* Screenshot support for OmniNX Installer Payload
* Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation.
*/
#include "screenshot.h"
#include "gfx.h"
#include "nx_sd.h"
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
#include <display/di.h>
#include <utils/util.h>
#include <utils/sprintf.h>
#include <rtc/max77620-rtc.h>
#include <string.h>
/* BMP file header (54 bytes), matching TegraExplorer tools.h */
typedef struct __attribute__((packed)) _bmp_hdr_t
{
u16 magic;
u32 size;
u32 rsvd;
u32 data_off;
u32 hdr_size;
u32 width;
u32 height;
u16 planes;
u16 pxl_bits;
u32 comp;
u32 img_size;
u32 res_h;
u32 res_v;
u64 rsvd2;
} bmp_hdr_t;
#define BMP_HEADER_SIZE 0x36
#define FB_SIZE 0x384000 /* 1280 * 720 * 4 */
#define SCREEN_W 1280
#define SCREEN_H 720
void take_screenshot(void)
{
static u32 last_timer = 0;
if (!sd_get_card_mounted())
return;
/* 3-second cooldown (same as TegraExplorer) */
u32 now = get_tmr_s();
if (last_timer != 0 && now < last_timer + 3)
return;
last_timer = now;
const char basepath[] = "sd:/switch/screenshot";
char name[48];
char path[80];
rtc_time_t rtc;
max77620_rtc_get_time(&rtc);
s_printf(name, "omninx_installer_%04d%02d%02d_%02d%02d%02d.bmp",
(u32)rtc.year, (u32)rtc.month, (u32)rtc.day,
(u32)rtc.hour, (u32)rtc.min, (u32)rtc.sec);
s_printf(path, "%s/%s", basepath, name);
f_mkdir("sd:/switch");
f_mkdir(basepath);
const u32 file_size = BMP_HEADER_SIZE + FB_SIZE;
u8 *bitmap = (u8 *)malloc(file_size);
u32 *fb_copy = (u32 *)malloc(FB_SIZE);
if (!bitmap || !fb_copy) {
if (bitmap) free(bitmap);
if (fb_copy) free(fb_copy);
return;
}
/* Copy framebuffer with same coordinate flip as TegraExplorer (BGR layout) */
u32 *fb_ptr = gfx_ctxt.fb;
for (int x = SCREEN_W - 1; x >= 0; x--) {
for (int y = SCREEN_H - 1; y >= 0; y--)
fb_copy[y * SCREEN_W + x] = *fb_ptr++;
}
memcpy(bitmap + BMP_HEADER_SIZE, fb_copy, FB_SIZE);
bmp_hdr_t *bmp = (bmp_hdr_t *)bitmap;
bmp->magic = 0x4D42;
bmp->size = file_size;
bmp->rsvd = 0;
bmp->data_off = BMP_HEADER_SIZE;
bmp->hdr_size = 40;
bmp->width = SCREEN_W;
bmp->height = SCREEN_H;
bmp->planes = 1;
bmp->pxl_bits = 32;
bmp->comp = 0;
bmp->img_size = FB_SIZE;
bmp->res_h = 2834;
bmp->res_v = 2834;
bmp->rsvd2 = 0;
sd_save_to_file(bitmap, file_size, path);
free(bitmap);
free(fb_copy);
/* Brief backlight flash (same as TegraExplorer) */
display_backlight_brightness(255, 1000);
msleep(100);
display_backlight_brightness(100, 1000);
}

13
source/screenshot.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* Screenshot support for OmniNX Installer Payload
* Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation.
*/
#pragma once
#include <utils/types.h>
/* Take a screenshot of the current framebuffer and save as BMP to SD.
* Triggered by Capture button (jc->cap). Uses 3-second cooldown.
* Saves to sd:/switch/screenshot/omninx_installer_YYYYMMDD_HHMMSS.bmp */
void take_screenshot(void);

70
tools/ram_test_main.c Normal file
View File

@@ -0,0 +1,70 @@
/*
* Minimal RAM info test payload (fuse DRAM ID + SK table -> MiB).
* Build: make ram-test -> output/RAM-Test.bin
*/
#include <display/di.h>
#include <mem/heap.h>
#include <mem/minerva.h>
#include <memory_map.h>
#include <soc/bpmp.h>
#include <soc/fuse.h>
#include <soc/hw_init.h>
#include <soc/t210.h>
#include <utils/util.h>
#include "dram_fuse.h"
#include "gfx.h"
#undef COLOR_CYAN
#undef COLOR_WHITE
#define COLOR_CYAN 0xFF00FFFF
#define COLOR_WHITE 0xFFFFFFFF
/* Required by BDK */
boot_cfg_t __attribute__((section("._boot_cfg"))) b_cfg;
volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR;
extern void pivot_stack(u32 stack_top);
void ipl_main(void)
{
hw_init();
pivot_stack(IPL_STACK_TOP);
heap_init(IPL_HEAP_START);
minerva_init();
minerva_change_freq(FREQ_800);
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);
bpmp_clk_rate_set(BPMP_CLK_DEFAULT_BOOST);
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
gfx_con_setcol(COLOR_CYAN, gfx_con.fillbg, gfx_con.bgcol);
gfx_printf("RAM test payload\n\n");
gfx_con_setcol(COLOR_WHITE, gfx_con.fillbg, gfx_con.bgcol);
u32 raw = fuse_read_dramid(true);
u32 nid = fuse_read_dramid(false);
u32 chip = hw_get_chip_id();
int mib = dram_capacity_mib_from_fuse();
gfx_printf("SoC: %s\n", chip == GP_HIDREV_MAJOR_T210B01 ? "Mariko (T210B01)" : "Erista (T210)");
gfx_printf("DRAM fuse: raw %d norm %d\n", raw, nid);
if (mib > 0)
gfx_printf("Table MiB: %d (fuse SKU, not probe)\n", mib);
else
gfx_printf("Table MiB: (unknown id for this SoC)\n");
gfx_printf("\nHang — power off or inject payload.\n");
while (1)
msleep(500);
}