Compare commits

...

24 Commits

Author SHA1 Message Date
Niklas080208
31aeeaabbb Disabled build on push 2026-02-15 16:34:38 +01:00
89994995af Add pre-update/clean backup feature
All checks were successful
Build / Build (push) Successful in 12s
- Backup deletion paths before update to sd:/backup/OmniNX/{version}
- Backup deletion paths before clean install to sd:/backup/OmniNX/pre-omninx
- Best-effort backup: continue install even if some copies fail
- Fix directory creation: ensure parent dirs exist before folder_copy
- Step 1 for update: Backup; Step 1-2 for clean: user data + pre-omninx backup

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 16:05:57 +01:00
15b7cb1f4c Add battery run protection: require charger below 10%
All checks were successful
Build / Build (push) Successful in 10s
- 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
4b124a9998 Add + and - simultaneously to cancel and return to Hekate before install
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 15:03:58 +01:00
89279e27d5 Updated CI
All checks were successful
Build / Build (push) Successful in 10s
2026-02-13 22:13:29 +01:00
4ae286d21a Updated CI
All checks were successful
Build / Build (push) Successful in 11s
2026-02-13 22:11:42 +01:00
880a9af105 Updated CI
Some checks failed
Build / build (push) Failing after 3s
2026-02-13 22:09:31 +01:00
7fac0e50bb Updated CI
Some checks failed
Build / build (push) Failing after 3s
2026-02-13 22:08:40 +01:00
2c32f19262 Removed .packages from Update to be deleted 2026-02-11 17:58:32 +01:00
4effcf513b Updated Notice 2026-02-11 17:56:31 +01:00
6b04eb5d89 Fixed typo 2026-02-11 17:47:00 +01:00
ed93c32131 Updated Readme
- Added notice for proper use of this payload\n- Fixed some explenations
2026-02-11 17:17:37 +01:00
bae5ecebf1 Updated CI
All checks were successful
Build and Release / build-and-release (push) Successful in 10s
2026-02-11 17:11:18 +01:00
ef16df416b Updated CI
All checks were successful
Build and Release / build-and-release (push) Successful in 9s
2026-02-11 17:09:04 +01:00
f81cd8e381 Updated CI
Some checks failed
Build and Release / build-and-release (push) Failing after 13s
2026-02-11 17:07:40 +01:00
04362c6e0c Added CI 2026-02-11 17:06:19 +01:00
8457cdd5a6 Keeping UltraHand packages on Update 2026-02-11 17:06:09 +01:00
d1f78968a4 Added wait warning 2026-02-11 16:50:22 +01:00
b2c245d6ab removed .vscode folder 2026-02-11 16:21:21 +01:00
50618d4607 Changed logging and credits 2026-02-11 16:20:56 +01:00
Niklas080208
4d43ddf1e5 Cleanup UX: slim logging, DBI keep config, no manifest overwrite
- Slim cleanup: one progress line per section (Bereinige: X [p%] (n/n))
  via delete_path_lists_grouped() in clean and update mode
- DBI: remove folder entries from deletion lists; only .nro deleted,
  folder and config preserved
- Remove redundant manifest.ini creation; use manifest from pack config/

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 00:00:22 +01:00
Niklas080208
4a4147686e Added detection if multiple packs found 2026-02-03 22:15:17 +01:00
Niklas080208
fdad061616 Capitalized Variants 2026-02-03 21:22:37 +01:00
Niklas080208
3d3489f1e6 Removed HATS leftovers 2026-02-03 20:40:39 +01:00
21 changed files with 1632 additions and 1154 deletions

32
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Build
on:
#push:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
container: devkitpro/devkitarm:latest
steps:
- name: Install dependencies
run: apt-get update && apt-get install -y nodejs
- name: Checkout latest code
uses: actions/checkout@v5
with:
clean: true
- name: Build
run: |
export DEVKITPRO=/opt/devkitpro
export DEVKITARM=/opt/devkitpro/devkitARM
make -j4
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: OmniNX-Installer.bin
path: output/OmniNX-Installer.bin

17
.vscode/settings.json vendored
View File

@@ -1,17 +0,0 @@
{
"claudeCode.disableLoginPrompt": false,
"claudeCode.environmentVariables": [
{
"name": "ANTHROPIC_BASE_URL",
"value": "https://api.z.ai/api/anthropic"
},
{
"name": "ANTHROPIC_AUTH_TOKEN",
"value": "caceb12fd1a740c68631842764f1b38a.kBX5z6hfW95z8QHj"
},
{
"name": "CLAUDE_CODE_SKIP_AUTH_LOGIN",
"value": "true"
}
]
}

View File

@@ -272,7 +272,7 @@ Creates/overwrites `sd:/config/omninx/manifest.ini`:
**Content Structure**:
```ini
[OmniNX]
current_pack={variant} # "standard", "light", or "oc"
current_pack={variant} # "Standard", "Light", or "OC"
version={VERSION} # e.g., "1.0.0"
update_channel={0|1|2} # light=0, oc=1, standard=2
channel_pack={variant} # Same as current_pack

View File

@@ -1,5 +1,7 @@
# OmniNX Installer Payload
> **To properly use this payload, download the latest OmniNX Release:** [https://git.niklascfw.de/OmniNX/OmniNX/releases](https://git.niklascfw.de/OmniNX/OmniNX/releases)
A minimal payload for installing OmniNX CFW Pack files on Nintendo Switch outside of Horizon OS.
Based on [TegraExplorer](https://github.com/shchmue/TegraExplorer) and [hekate](https://github.com/CTCaer/hekate) by CTCaer, naehrwert, and shchmue.
@@ -52,7 +54,11 @@ make clean
make
```
The built payload will be output to `output/omninx-installer.bin`.
The built payload will be output to `output/OmniNX-Installer.bin`.
### CI / GitHub Actions
Every push triggers a build. The `.bin` file is produced and attached as a workflow artifact (Actions tab → select run → Artifacts).
## Usage
@@ -111,11 +117,11 @@ Use hekate or another bootloader to launch the payload:
The payload supports three OmniNX variants:
- **Standard** (`1.0.0s`): Full CFW pack
- **Light** (`1.0.0l`): Lightweight CFW pack
- **OC** (`1.0.0oc`): Overclock-enabled CFW pack (includes SaltySD)
- **Standard** `sd:/OmniNX Standard/`
- **Light** `sd:/OmniNX Light/`
- **OC** `sd:/OmniNX OC/`
The payload automatically detects which variant is present on the SD card and installs accordingly.
The payload detects the pack variant from the staging directory presence and detects the current installation (variant + version) from `sd:/config/omninx/manifest.ini` (`current_pack` and `version` keys).
## Project Structure

View File

@@ -164,6 +164,12 @@ void bq24193_enable_charger()
i2c_send_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_PORConfig, reg);
}
bool bq24193_charger_connected()
{
u8 status = bq24193_get_reg(BQ24193_Status);
return (status & BQ24193_STATUS_PG_MASK) != 0;
}
void bq24193_fake_battery_removal()
{
// Disable watchdog to keep BATFET disabled.

View File

@@ -19,6 +19,8 @@
#ifndef __BQ24193_H_
#define __BQ24193_H_
#include <utils/types.h>
#define BQ24193_I2C_ADDR 0x6B
// REG 0 masks.
@@ -117,5 +119,6 @@ enum BQ24193_reg_prop {
int bq24193_get_property(enum BQ24193_reg_prop prop, int *value);
void bq24193_enable_charger();
void bq24193_fake_battery_removal();
bool bq24193_charger_connected();
#endif /* __BQ24193_H_ */

View File

@@ -1,13 +0,0 @@
# HATS Tools Universal Config
# Location: sd:/config/hats-tools/config.ini
# Shared between HATS Tools GUI and hats-installer payload
[hats]
install_mode=default ; options: replace, default, clean
# Mode descriptions:
# replace - Only replace files, no deletion (old files may remain)
# default - Delete /atmosphere only, keep /bootloader and /switch
# clean - Delete /atmosphere, /bootloader, and /switch (fresh install)
#
# All modes will remove HATS-*.txt version files from SD root

View File

@@ -6,14 +6,97 @@
#include "fs.h"
#include <libs/fatfs/ff.h>
#include <string.h>
#include <stdarg.h>
#include <utils/sprintf.h>
#define BACKUP_PATH_MAX 270
// Check if file/directory exists
static bool path_exists(const char *path) {
FILINFO fno;
return (f_stat(path, &fno) == FR_OK);
}
// Create all parent directories for a path (mkdir -p style)
static void ensure_parent_dirs(const char *full_path) {
char buf[BACKUP_PATH_MAX];
int n = (int)strlen(full_path);
if (n >= BACKUP_PATH_MAX) return;
memcpy(buf, full_path, n + 1);
for (int i = 4; i < n; i++) {
if (buf[i] == '/') {
buf[i] = '\0';
f_mkdir(buf);
buf[i] = '/';
}
}
}
// Copy a single path (file or dir) to backup base, preserving SD structure
static int backup_single_path(const char *src_path, const char *backup_base) {
FILINFO fno;
if (f_stat(src_path, &fno) != FR_OK)
return FR_OK;
const char *relative = src_path;
if (strncmp(relative, "sd:/", 4) == 0)
relative += 4;
if (fno.fattrib & AM_DIR) {
const char *last_slash = strrchr(relative, '/');
char parent[BACKUP_PATH_MAX];
if (last_slash && last_slash > relative) {
s_printf(parent, "%s/%.*s", backup_base, (int)(last_slash - relative), relative);
/* Create full parent hierarchy (e.g. atmosphere, atmosphere/contents) */
ensure_parent_dirs(parent);
f_mkdir(parent); /* Ensure parent exists; FR_EXIST is fine */
return folder_copy(src_path, parent);
} else {
ensure_parent_dirs(backup_base);
return folder_copy(src_path, backup_base);
}
} else {
char dst_path[BACKUP_PATH_MAX];
s_printf(dst_path, "%s/%s", backup_base, relative);
ensure_parent_dirs(dst_path);
return file_copy(src_path, dst_path);
}
}
// Backup all paths from varargs lists (same structure as delete_path_lists_grouped)
int backup_deletion_lists(const char *backup_base, ...) {
va_list ap;
const char **list;
int res = FR_OK;
/* FatFS f_mkdir only creates the last component; parent must exist first */
ensure_parent_dirs(BACKUP_BASE_PATH);
res = f_mkdir(BACKUP_BASE_PATH);
if (res != FR_OK && res != FR_EXIST)
return res;
ensure_parent_dirs(backup_base);
res = f_mkdir(backup_base);
if (res != FR_OK && res != FR_EXIST)
return res;
va_start(ap, backup_base);
while ((list = va_arg(ap, const char **)) != NULL) {
for (int i = 0; list[i] != NULL; i++) {
if (path_exists(list[i])) {
int copy_res = backup_single_path(list[i], backup_base);
if (copy_res != FR_OK && res == FR_OK)
res = copy_res; // Remember first error but keep going
}
}
}
va_end(ap);
/* Best-effort: return FR_OK so install continues even if some copies failed */
return FR_OK;
}
// Backup user data before clean install
int backup_user_data(void) {
int res;

View File

@@ -7,6 +7,8 @@
#include <utils/types.h>
#define TEMP_BACKUP_PATH "sd:/temp_backup"
#define BACKUP_BASE_PATH "sd:/backup/OmniNX"
#define PRE_OMNINX_LABEL "pre-omninx"
// Backup user data (DBI, Tinfoil, prod.keys) before clean install
int backup_user_data(void);
@@ -16,3 +18,6 @@ int restore_user_data(void);
// Clean up temporary backup directory
int cleanup_backup(void);
// Backup paths from varargs lists to backup_base (pass path arrays, terminate with NULL)
int backup_deletion_lists(const char *backup_base, ...);

View File

@@ -138,11 +138,6 @@ static const char* clean_switch_dirs_to_delete[] = {
"sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams",
"sd:/switch/Daybreak",
"sd:/switch/DBI_658_EN",
"sd:/switch/DBI_810",
"sd:/switch/DBI_810_DE",
"sd:/switch/DBI_810_EN",
"sd:/switch/DBI_RU",
"sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon",
"sd:/switch/Fizeau",
@@ -208,6 +203,8 @@ static const char* clean_switch_files_to_delete[] = {
"sd:/switch/DBI_810_DE/DBI_810_DE.nro",
"sd:/switch/DBI_810_EN/DBI_810_EN.nro",
"sd:/switch/DBI_RU/DBI_RU.nro",
"sd:/switch/DBI/DBI_EN.nro",
"sd:/switch/DBI_DE/DBI_DE.nro",
"sd:/switch/DNS_mitm Tester.nro",
"sd:/switch/EdiZon.nro",
"sd:/switch/Fizeau.nro",

View File

@@ -122,9 +122,9 @@ static const char* config_dirs_to_delete[] = {
};
// Switch directories to delete
// NOTE: .packages is intentionally excluded - UltraHand package cache, preserve during updates
static const char* switch_dirs_to_delete[] = {
"sd:/switch/.overlays",
"sd:/switch/.packages",
"sd:/switch/90DNS_tester",
"sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader",
@@ -137,11 +137,6 @@ static const char* switch_dirs_to_delete[] = {
"sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams",
"sd:/switch/Daybreak",
"sd:/switch/DBI_658_EN",
"sd:/switch/DBI_810",
"sd:/switch/DBI_810_DE",
"sd:/switch/DBI_810_EN",
"sd:/switch/DBI_RU",
"sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon",
"sd:/switch/Fizeau",
@@ -207,6 +202,8 @@ static const char* switch_files_to_delete[] = {
"sd:/switch/DBI_810_DE/DBI_810_DE.nro",
"sd:/switch/DBI_810_EN/DBI_810_EN.nro",
"sd:/switch/DBI_RU/DBI_RU.nro",
"sd:/switch/DBI/DBI_EN.nro",
"sd:/switch/DBI_DE/DBI_DE.nro",
"sd:/switch/DNS_mitm Tester.nro",
"sd:/switch/EdiZon.nro",
"sd:/switch/Fizeau.nro",

View File

@@ -1,5 +1,6 @@
/*
* HATS Installer - Filesystem operations with file logging
* OmniNX Installer - Filesystem operations with file logging
* Based on HATS Installer
*/
#include "fs.h"
@@ -23,7 +24,7 @@ void log_init(const char *path) {
int res = f_open(&log_file, path, FA_WRITE | FA_CREATE_ALWAYS);
if (res == FR_OK) {
log_enabled = true;
log_write("=== HATS Installer Log ===\n\n");
log_write("=== OmniNX Installer Log ===\n\n");
}
}

View File

@@ -1,5 +1,6 @@
/*
* HATS Installer - Filesystem operations
* OmniNX Installer - Filesystem operations
* Based on HATS Installer
*/
#pragma once

View File

@@ -7,9 +7,12 @@
#include "version.h"
#include "gfx.h"
#include <libs/fatfs/ff.h>
#include <stdarg.h>
#include <string.h>
#include <utils/sprintf.h>
#define GROUPED_DELETE_MAX_ENTRIES 512
#ifndef VERSION
#define VERSION "1.0.0"
#endif
@@ -395,6 +398,89 @@ int folder_delete_single_with_progress(const char *path, const char *display_nam
}
// Delete a list of paths with progress tracking
// Delete multiple path lists under one label; one progress line "Bereinige: folder/ [p%] (d/t)".
// Varargs: path lists (const char**), terminated by NULL.
int delete_path_lists_grouped(const char *folder_display_name, ...) {
va_list ap;
const char *entries[GROUPED_DELETE_MAX_ENTRIES];
int n_entries = 0;
int deleted = 0;
int failed = 0;
u32 start_x, start_y;
int last_percent = -1;
int res;
va_start(ap, folder_display_name);
const char **list;
while (n_entries < GROUPED_DELETE_MAX_ENTRIES && (list = va_arg(ap, const char **)) != NULL) {
for (int i = 0; list[i] != NULL && n_entries < GROUPED_DELETE_MAX_ENTRIES; i++) {
if (install_path_exists(list[i]))
entries[n_entries++] = list[i];
}
}
va_end(ap);
if (n_entries == 0)
return FR_OK;
int total_paths = n_entries;
gfx_con_getpos(&start_x, &start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Bereinige: %s [ 0%%] (0/%d)", folder_display_name, total_paths);
install_set_color(COLOR_WHITE);
for (int i = 0; i < n_entries; i++) {
const char *path = entries[i];
FILINFO fno;
if (f_stat(path, &fno) != FR_OK)
continue;
const char *item_name = strrchr(path, '/');
if (item_name) item_name++;
else item_name = path;
if (fno.fattrib & AM_DIR) {
int item_count = install_count_directory_items(path);
if (item_count > 50) {
gfx_printf("\n");
res = folder_delete_single_with_progress(path, item_name);
} else {
res = folder_delete(path);
}
} else {
if (fno.fattrib & AM_RDO)
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
res = f_unlink(path);
}
if (res == FR_OK || res == FR_NO_FILE)
deleted++;
else
failed++;
int percent = (deleted * 100) / total_paths;
if (percent != last_percent || deleted % 5 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d)", folder_display_name, percent, deleted, total_paths);
install_set_color(COLOR_WHITE);
last_percent = percent;
}
}
gfx_con_setpos(start_x, start_y);
if (failed == 0) {
install_set_color(COLOR_GREEN);
gfx_printf(" Bereinige: %s [100%%] (%d/%d) - Fertig!\n", folder_display_name, deleted, total_paths);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_ORANGE);
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d) - %d Fehler\n", folder_display_name, last_percent, deleted, total_paths, failed);
install_set_color(COLOR_WHITE);
}
return (failed == 0) ? FR_OK : FR_DISK_ERR;
}
int delete_path_list(const char* paths[], const char* description) {
int res;
int deleted = 0;
@@ -505,9 +591,29 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
int res;
if (mode == INSTALL_MODE_UPDATE) {
// Update mode: selective cleanup then install
// Update mode: backup, selective cleanup, then install
omninx_status_t status = detect_omninx_installation();
const char *version = (status.version_file[0] != '\0') ? status.version_file : "unknown";
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 1: Bereinigung...\n");
gfx_printf("Schritt 1: Backup vor Update (%s)...\n", version);
install_set_color(COLOR_WHITE);
res = backup_before_update(version);
/* Backup is best-effort; continue install even on failure */
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" [OK] Sicherung nach sd:/backup/OmniNX/%s/\n", version);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_ORANGE);
gfx_printf(" [WARN] Backup fehlgeschlagen, fahre trotzdem fort...\n");
install_set_color(COLOR_WHITE);
}
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 2: Bereinigung...\n");
install_set_color(COLOR_WHITE);
res = update_mode_cleanup(pack_variant);
if (res != FR_OK) return res;
@@ -515,17 +621,20 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 2: Dateien kopieren...\n");
gfx_printf("Schritt 3: Dateien kopieren...\n");
install_set_color(COLOR_WHITE);
res = update_mode_install(pack_variant);
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
// Clean mode: backup user data, backup pre-omninx, wipe, restore, install
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 1: Sichere Benutzerdaten...\n");
install_set_color(COLOR_WHITE);
@@ -535,7 +644,23 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 2: Bereinige alte Installation...\n");
gfx_printf("Schritt 2: Backup alter CFW-Dateien (pre-omninx)...\n");
install_set_color(COLOR_WHITE);
res = backup_before_clean();
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" [OK] Sicherung nach sd:/backup/OmniNX/pre-omninx/\n");
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_ORANGE);
gfx_printf(" [WARN] Backup fehlgeschlagen, fahre trotzdem fort...\n");
install_set_color(COLOR_WHITE);
}
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 3: Bereinige alte Installation...\n");
install_set_color(COLOR_WHITE);
res = clean_mode_wipe();
if (res != FR_OK) return res;
@@ -543,7 +668,7 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 3: Stelle Benutzerdaten wieder her...\n");
gfx_printf("Schritt 4: Stelle Benutzerdaten wieder her...\n");
install_set_color(COLOR_WHITE);
res = clean_mode_restore();
if (res != FR_OK) return res;
@@ -551,14 +676,17 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
install_check_and_clear_screen_if_needed();
gfx_printf("\n");
install_set_color(COLOR_YELLOW);
gfx_printf("Schritt 4: Dateien kopieren...\n");
gfx_printf("Schritt 5: Dateien kopieren...\n");
install_set_color(COLOR_WHITE);
res = clean_mode_install(pack_variant);
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;
}
}

View File

@@ -17,11 +17,15 @@ typedef enum {
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode);
// Update mode operations (install_update.c)
int backup_before_update(const char *version);
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 backup_before_clean(void);
int clean_mode_backup(void);
int clean_mode_wipe(void);
int clean_mode_restore(void);
@@ -34,6 +38,7 @@ bool install_path_exists(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);
int delete_path_list(const char* paths[], const char* description);
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_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);

View File

@@ -9,20 +9,48 @@
#include "deletion_lists_clean.h"
#include "fs.h"
#include "gfx.h"
#include "version.h"
#include <libs/fatfs/ff.h>
#include <utils/sprintf.h>
#include <string.h>
#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
// Backup paths before clean install (sd:/backup/OmniNX/pre-omninx)
int backup_before_clean(void) {
char backup_base[270];
s_printf(backup_base, "%s/%s", BACKUP_BASE_PATH, PRE_OMNINX_LABEL);
return backup_deletion_lists(backup_base,
clean_atmosphere_dirs_to_delete,
clean_atmosphere_contents_dirs_to_delete,
clean_atmosphere_files_to_delete,
clean_bootloader_dirs_to_delete,
clean_bootloader_files_to_delete,
clean_config_dirs_to_delete,
clean_switch_dirs_to_delete,
clean_switch_files_to_delete,
clean_root_files_to_delete,
clean_misc_dirs_to_delete,
clean_misc_files_to_delete,
old_version_files_to_delete,
NULL);
}
// Clean mode: Backup user data
int clean_mode_backup(void) {
@@ -42,41 +70,36 @@ int clean_mode_backup(void) {
int clean_mode_wipe(void) {
check_and_clear_screen_if_needed();
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: atmosphere/\n");
set_color(COLOR_WHITE);
delete_path_list(clean_atmosphere_dirs_to_delete, "atmosphere subdirs");
delete_path_list(clean_atmosphere_contents_dirs_to_delete, "atmosphere contents dirs");
delete_path_list(clean_atmosphere_files_to_delete, "atmosphere files");
delete_path_lists_grouped("atmosphere/",
clean_atmosphere_dirs_to_delete,
clean_atmosphere_contents_dirs_to_delete,
clean_atmosphere_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: bootloader/\n");
set_color(COLOR_WHITE);
delete_path_list(clean_bootloader_dirs_to_delete, "bootloader dirs");
delete_path_list(clean_bootloader_files_to_delete, "bootloader files");
delete_path_lists_grouped("bootloader/",
clean_bootloader_dirs_to_delete,
clean_bootloader_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: config/\n");
set_color(COLOR_WHITE);
delete_path_list(clean_config_dirs_to_delete, "config dirs");
delete_path_lists_grouped("config/",
clean_config_dirs_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: switch/\n");
set_color(COLOR_WHITE);
delete_path_list(clean_switch_dirs_to_delete, "switch dirs");
delete_path_list(clean_switch_files_to_delete, "switch files");
delete_path_lists_grouped("switch/",
clean_switch_dirs_to_delete,
clean_switch_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: Root-Dateien\n");
set_color(COLOR_WHITE);
delete_path_list(clean_root_files_to_delete, "root files");
delete_path_list(clean_misc_dirs_to_delete, "misc dirs");
delete_path_list(clean_misc_files_to_delete, "misc files");
delete_path_lists_grouped("Root-Dateien",
clean_root_files_to_delete,
clean_misc_dirs_to_delete,
clean_misc_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: Alte Versionsmarker\n");
set_color(COLOR_WHITE);
delete_path_list(old_version_files_to_delete, "old version markers");
delete_path_lists_grouped("Alte Versionsmarker",
old_version_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Erstelle: switch/\n");
@@ -109,3 +132,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;
}

View File

@@ -4,6 +4,7 @@
*/
#include "install.h"
#include "backup.h"
#include "deletion_lists_update.h"
#include "fs.h"
#include "version.h"
@@ -34,41 +35,56 @@
#define path_exists install_path_exists
#define count_directory_items install_count_directory_items
// Backup paths before update (sd:/backup/OmniNX/{version})
int backup_before_update(const char *version) {
char backup_base[270];
s_printf(backup_base, "%s/%s", BACKUP_BASE_PATH, version);
return backup_deletion_lists(backup_base,
atmosphere_dirs_to_delete,
atmosphere_contents_dirs_to_delete,
atmosphere_files_to_delete,
bootloader_dirs_to_delete,
bootloader_files_to_delete,
config_dirs_to_delete,
switch_dirs_to_delete,
switch_files_to_delete,
root_files_to_delete,
misc_dirs_to_delete,
misc_files_to_delete,
NULL);
}
// Update mode: Cleanup specific directories/files
int update_mode_cleanup(omninx_variant_t variant) {
(void)variant;
check_and_clear_screen_if_needed();
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: atmosphere/\n");
set_color(COLOR_WHITE);
delete_path_list(atmosphere_dirs_to_delete, "atmosphere subdirs");
delete_path_list(atmosphere_contents_dirs_to_delete, "atmosphere contents dirs");
delete_path_list(atmosphere_files_to_delete, "atmosphere files");
delete_path_lists_grouped("atmosphere/",
atmosphere_dirs_to_delete,
atmosphere_contents_dirs_to_delete,
atmosphere_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: bootloader/\n");
set_color(COLOR_WHITE);
delete_path_list(bootloader_dirs_to_delete, "bootloader dirs");
delete_path_list(bootloader_files_to_delete, "bootloader files");
delete_path_lists_grouped("bootloader/",
bootloader_dirs_to_delete,
bootloader_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: config/\n");
set_color(COLOR_WHITE);
delete_path_list(config_dirs_to_delete, "config dirs");
delete_path_lists_grouped("config/",
config_dirs_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: switch/\n");
set_color(COLOR_WHITE);
delete_path_list(switch_dirs_to_delete, "switch dirs");
delete_path_list(switch_files_to_delete, "switch files");
delete_path_lists_grouped("switch/",
switch_dirs_to_delete,
switch_files_to_delete,
NULL);
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: Root-Dateien\n");
set_color(COLOR_WHITE);
delete_path_list(root_files_to_delete, "root files");
delete_path_list(misc_dirs_to_delete, "misc dirs");
delete_path_list(misc_files_to_delete, "misc files");
delete_path_lists_grouped("Root-Dateien",
root_files_to_delete,
misc_dirs_to_delete,
misc_files_to_delete,
NULL);
set_color(COLOR_GREEN);
gfx_printf(" Bereinigung abgeschlossen!\n");
@@ -148,40 +164,6 @@ int update_mode_install(omninx_variant_t variant) {
s_printf(dst_path, "sd:/payload.bin");
if (path_exists(src_path)) file_copy(src_path, dst_path);
set_color(COLOR_CYAN);
gfx_printf(" Erstelle manifest.ini...\n");
set_color(COLOR_WHITE);
s_printf(dst_path, "sd:/config/omninx");
f_mkdir(dst_path);
const char* pack_name;
int update_channel;
switch (variant) {
case VARIANT_STANDARD: pack_name = "standard"; update_channel = 2; break;
case VARIANT_LIGHT: pack_name = "light"; update_channel = 0; break;
case VARIANT_OC: pack_name = "oc"; update_channel = 1; break;
default: pack_name = "unknown"; update_channel = 0; break;
}
s_printf(dst_path, "sd:/config/omninx/manifest.ini");
FIL fp;
if (f_open(&fp, dst_path, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) {
f_printf(&fp, "[OmniNX]\n");
f_printf(&fp, "current_pack=%s\n", pack_name);
f_printf(&fp, "version=%s\n", VERSION);
f_printf(&fp, "update_channel=%d\n", update_channel);
f_printf(&fp, "channel_pack=%s\n", pack_name);
f_close(&fp);
set_color(COLOR_GREEN);
gfx_printf(" [OK] manifest.ini erstellt\n");
set_color(COLOR_WHITE);
} else {
set_color(COLOR_ORANGE);
gfx_printf(" [WARN] manifest.ini konnte nicht erstellt werden\n");
set_color(COLOR_WHITE);
}
set_color(COLOR_GREEN);
gfx_printf(" Kopie abgeschlossen!\n");
set_color(COLOR_WHITE);

View File

@@ -1,9 +1,9 @@
/*
* HATS Installer - Simplified disk I/O (SD card only)
* Based on TegraExplorer diskio.c by shchmue
* OmniNX Installer - Simplified disk I/O (SD card only)
* Based on HATS Installer, TegraExplorer diskio.c by shchmue
*
* This simplified version removes BIS/eMMC support since the
* HATS installer only needs SD card access.
* OmniNX installer only needs SD card access.
*/
/*-----------------------------------------------------------------------*/

View File

@@ -2,8 +2,8 @@
* 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
* Based on HATS-Installer-Payload by sthetix
*/
#ifndef VERSION
@@ -18,6 +18,8 @@
#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>
@@ -74,7 +76,9 @@ 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)
// 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
@@ -215,10 +219,11 @@ void ipl_main(void) {
// 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();
// 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 (pack_variant == VARIANT_NONE) {
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");
@@ -283,9 +288,146 @@ void ipl_main(void) {
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");
@@ -310,33 +452,58 @@ void ipl_main(void) {
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 either A button or Power button
// 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) {
// Check power button - detect press (transition from not pressed to pressed)
while (!button_pressed && !cancelled) {
// Check power button
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) {
button_pressed = true;
break;
}
// Check joycon A button
// Check joycon buttons
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) {
button_pressed = true;
break;
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);
@@ -344,7 +511,10 @@ void ipl_main(void) {
// Perform the installation
set_color(COLOR_YELLOW);
gfx_printf("Installation wird gestartet...\n\n");
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);

View File

@@ -50,11 +50,11 @@ static omninx_variant_t read_manifest_variant(const char *manifest_path) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key && !strcmp(kv->key, "current_pack")) {
if (kv->val) {
if (!strcmp(kv->val, "standard")) {
if (!strcmp(kv->val, "Standard")) {
variant = VARIANT_STANDARD;
} else if (!strcmp(kv->val, "light")) {
} else if (!strcmp(kv->val, "Light")) {
variant = VARIANT_LIGHT;
} else if (!strcmp(kv->val, "oc")) {
} else if (!strcmp(kv->val, "OC")) {
variant = VARIANT_OC;
}
}
@@ -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) {

View File

@@ -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);