Compare commits

..

15 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
13 changed files with 588 additions and 375 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

View File

@@ -1,71 +0,0 @@
name: Build and Release
on:
push:
tags:
- 'v*'
jobs:
build-and-release:
runs-on: ubuntu-latest
container:
image: devkitpro/devkitarm:20251231
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build payload
env:
DEVKITARM: /opt/devkitpro/devkitARM
run: make
- name: Create release and upload asset
env:
SERVER_URL: ${{ github.server_url }}
API_URL: ${{ github.api_url }}
REPOSITORY: ${{ github.repository }}
GITHUB_REF: ${{ github.ref }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
BIN_PATH="output/OmniNX-Installer.bin"
# API URL: GitHub uses api.github.com, Gitea uses server_url/api/v1
if [[ "$API_URL" != "" ]]; then
API_BASE="$API_URL"
else
API_BASE="$SERVER_URL/api/v1"
fi
# Create release
RELEASE_RESPONSE=$(curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"$TAG\", \"name\": \"$TAG\", \"body\": \"OmniNX Installer Payload $TAG\"}" \
"$API_BASE/repos/$REPOSITORY/releases")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
UPLOAD_URL=$(echo "$RELEASE_RESPONSE" | grep -o '"upload_url":"[^"]*' | head -1 | cut -d'"' -f4)
if [ -z "$RELEASE_ID" ]; then
echo "Failed to create release. Response: $RELEASE_RESPONSE"
exit 1
fi
# Upload asset (GitHub uses upload_url; Gitea uses assets endpoint)
if [[ -n "$UPLOAD_URL" ]]; then
UPLOAD_URL="${UPLOAD_URL%%\{*}"
curl -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$BIN_PATH" \
"${UPLOAD_URL}?name=OmniNX-Installer.bin"
else
curl -X POST \
-H "Authorization: token $TOKEN" \
-F "attachment=@$BIN_PATH" \
"$API_BASE/repos/$REPOSITORY/releases/$RELEASE_ID/assets?name=OmniNX-Installer.bin"
fi

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.
@@ -56,11 +58,7 @@ The built payload will be output to `output/OmniNX-Installer.bin`.
### CI / GitHub Actions
Pushing a version tag (e.g. `v1.0.0`) triggers a build and creates a release with the `.bin` file attached.
**GitHub:** Uses the built-in `GITHUB_TOKEN` automatically.
**Gitea:** Add a `GITHUB_TOKEN` secret (Settings → Secrets and Variables → Actions) with a token that has permission to create releases.
Every push triggers a build. The `.bin` file is produced and attached as a workflow artifact (Actions tab → select run → Artifacts).
## Usage
@@ -119,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

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

@@ -1,280 +1,281 @@
/*
* OmniNX Installer - Deletion Lists for Update Mode
* Selective deletion when OmniNX is already installed.
*/
#pragma once
// Atmosphere subdirectories to delete
static const char* atmosphere_dirs_to_delete[] = {
"sd:/atmosphere/config",
"sd:/atmosphere/crash_reports",
"sd:/atmosphere/erpt_reports",
"sd:/atmosphere/exefs_patches/CrunchPatch",
"sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0",
"sd:/atmosphere/exefs_patches/bluetooth_patches",
"sd:/atmosphere/exefs_patches/bootlogo",
"sd:/atmosphere/exefs_patches/btm_patches",
"sd:/atmosphere/exefs_patches/es_patches",
"sd:/atmosphere/exefs_patches/hid_patches",
"sd:/atmosphere/exefs_patches/logo1",
"sd:/atmosphere/exefs_patches/nfim_ctest",
"sd:/atmosphere/exefs_patches/nim_ctest",
"sd:/atmosphere/exefs_patches/nvnflinger_cmu",
"sd:/atmosphere/extrazz",
"sd:/atmosphere/fatal_errors",
"sd:/atmosphere/fatal_reports",
"sd:/atmosphere/flags",
"sd:/atmosphere/hbl_html",
"sd:/atmosphere/hosts",
"sd:/atmosphere/kips",
"sd:/atmosphere/kip1",
"sd:/atmosphere/kip_patches",
"sd:/atmosphere/logs",
NULL
};
// Atmosphere contents directories (title IDs; only under atmosphere/contents/)
static const char* atmosphere_contents_dirs_to_delete[] = {
"sd:/atmosphere/contents/0000000000534C56",
"sd:/atmosphere/contents/00FF0000B378D640",
"sd:/atmosphere/contents/00FF0000636C6BFF",
"sd:/atmosphere/contents/00FF0000A53BB665",
"sd:/atmosphere/contents/0100000000000008",
"sd:/atmosphere/contents/010000000000000D",
"sd:/atmosphere/contents/010000000000002B",
"sd:/atmosphere/contents/0100000000000032",
"sd:/atmosphere/contents/0100000000000034",
"sd:/atmosphere/contents/0100000000000036",
"sd:/atmosphere/contents/0100000000000037",
"sd:/atmosphere/contents/010000000000003C",
"sd:/atmosphere/contents/0100000000000042",
"sd:/atmosphere/contents/0100000000000895",
"sd:/atmosphere/contents/0100000000000F12",
"sd:/atmosphere/contents/0100000000001000",
"sd:/atmosphere/contents/0100000000001007",
"sd:/atmosphere/contents/0100000000001013",
"sd:/atmosphere/contents/010000000000DA7A",
"sd:/atmosphere/contents/010000000000bd00",
"sd:/atmosphere/contents/01006a800016e000",
"sd:/atmosphere/contents/01009D901BC56000",
"sd:/atmosphere/contents/0100A3900C3E2000",
"sd:/atmosphere/contents/0100F43008C44000",
"sd:/atmosphere/contents/050000BADDAD0000",
"sd:/atmosphere/contents/4200000000000000",
"sd:/atmosphere/contents/420000000000000B",
"sd:/atmosphere/contents/420000000000000E",
"sd:/atmosphere/contents/4200000000000010",
"sd:/atmosphere/contents/4200000000000FFF",
"sd:/atmosphere/contents/420000000007E51A",
"sd:/atmosphere/contents/420000000007E51B",
"sd:/atmosphere/contents/690000000000000D",
NULL
};
// Atmosphere files to delete
static const char* atmosphere_files_to_delete[] = {
"sd:/atmosphere/config/exosphere.ini",
"sd:/atmosphere/config/stratosphere.ini",
"sd:/atmosphere/hbl.nsp",
"sd:/atmosphere/package3",
"sd:/atmosphere/reboot_payload.bin",
"sd:/atmosphere/stratosphere.romfs",
NULL
};
// Bootloader directories to delete
static const char* bootloader_dirs_to_delete[] = {
"sd:/bootloader/boot",
"sd:/bootloader/bootlogo",
"sd:/bootloader/ini2",
"sd:/bootloader/payloads",
"sd:/bootloader/reboot",
"sd:/bootloader/res",
"sd:/bootloader/sys",
NULL
};
// Bootloader files to delete
static const char* bootloader_files_to_delete[] = {
"sd:/bootloader/ArgonNX.bin",
"sd:/bootloader/bootlogo.bmp",
"sd:/bootloader/hekate_ipl.ini",
"sd:/bootloader/nyx.ini",
"sd:/bootloader/patches.ini",
"sd:/bootloader/update.bin",
"sd:/bootloader/ini/EmuMMC ohne Mods.ini",
NULL
};
// Config directories to delete
static const char* config_dirs_to_delete[] = {
"sd:/config/aio-switch-updater",
"sd:/config/blue_pack_updater",
"sd:/config/kefir-updater",
"sd:/config/nx-hbmenu",
"sd:/config/quickntp",
"sd:/config/sys-con",
"sd:/config/sys-patch",
"sd:/config/uberhand",
"sd:/config/ultrahand",
NULL
};
// Switch directories to delete
static const char* switch_dirs_to_delete[] = {
"sd:/switch/.overlays",
"sd:/switch/90DNS_tester",
"sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader",
"sd:/switch/appstore",
"sd:/switch/AtmoXL-Titel-Installer",
"sd:/switch/breeze",
"sd:/switch/checkpoint",
"sd:/switch/cheats-updater",
"sd:/switch/chiaki",
"sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams",
"sd:/switch/Daybreak",
"sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon",
"sd:/switch/Fizeau",
"sd:/switch/FTPD",
"sd:/switch/fw-downloader",
"sd:/switch/gamecard_installer",
"sd:/switch/Goldleaf",
"sd:/switch/haze",
"sd:/switch/JKSV",
"sd:/switch/kefir-updater",
"sd:/switch/ldnmitm_config",
"sd:/switch/Linkalho",
"sd:/switch/Moonlight-Switch",
"sd:/switch/Neumann",
"sd:/switch/NX-Activity-Log",
"sd:/switch/NX-Save-Sync",
"sd:/switch/NX-Shell",
"sd:/switch/NX-Update-Checker ",
"sd:/switch/NXGallery",
"sd:/switch/NXRemoteLauncher",
"sd:/switch/NXThemesInstaller",
"sd:/switch/nxdumptool",
"sd:/switch/nxmtp",
"sd:/switch/Payload_launcher",
"sd:/switch/Reboot",
"sd:/switch/reboot_to_argonNX",
"sd:/switch/reboot_to_hekate",
"sd:/switch/Shutdown_System",
"sd:/switch/SimpleModDownloader",
"sd:/switch/SimpleModManager",
"sd:/switch/sphaira",
"sd:/switch/studious-pancake",
"sd:/switch/Switch-Time",
"sd:/switch/SwitchIdent",
"sd:/switch/Switch_themes_Installer",
"sd:/switch/Switchfin",
"sd:/switch/Sys-Clk Manager",
"sd:/switch/Sys-Con",
"sd:/switch/sys-clk-manager",
"sd:/switch/themezer-nx",
"sd:/switch/themezernx",
"sd:/switch/tinwoo",
NULL
};
// Switch files (NRO) to delete
static const char* switch_files_to_delete[] = {
"sd:/switch/90DNS_tester/90DNS_tester.nro",
"sd:/switch/breeze.nro",
"sd:/switch/cheats-updater.nro",
"sd:/switch/chiaki.nro",
"sd:/switch/ChoiDujourNX.nro",
"sd:/switch/daybreak.nro",
"sd:/switch/DBI.nro",
"sd:/switch/DBI/DBI.nro",
"sd:/switch/DBI/DBI_810_DE.nro",
"sd:/switch/DBI/DBI_810_EN.nro",
"sd:/switch/DBI/DBI_845_DE.nro",
"sd:/switch/DBI/DBI_845_EN.nro",
"sd:/switch/DBI/DBI_849_DE.nro",
"sd:/switch/DBI/DBI_849_EN.nro",
"sd:/switch/DBI_810_DE/DBI_810.nro",
"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",
"sd:/switch/Goldleaf.nro",
"sd:/switch/haze.nro",
"sd:/switch/JKSV.nro",
"sd:/switch/ldnmitm_config.nro",
"sd:/switch/linkalho.nro",
"sd:/switch/Moonlight-Switch.nro",
"sd:/switch/Neumann.nro",
"sd:/switch/NX-Shell.nro",
"sd:/switch/NXGallery.nro",
"sd:/switch/NXThemesInstaller.nro",
"sd:/switch/nxdumptool.nro",
"sd:/switch/nxtc.bin",
"sd:/switch/reboot_to_payload.nro",
"sd:/switch/SimpleModDownloader.nro",
"sd:/switch/SimpleModManager.nro",
"sd:/switch/sphaira.nro",
"sd:/switch/SwitchIdent.nro",
"sd:/switch/Switch_themes_Installer/NXThemesInstaller.nro",
"sd:/switch/Switchfin.nro",
"sd:/switch/Sys-Clk Manager/sys-clk-manager.nro",
"sd:/switch/Sys-Con.nro",
"sd:/switch/sys-clk-manager.nro",
"sd:/switch/tinfoil.nro",
"sd:/switch/tinfoil/tinfoil.nro",
"sd:/switch/tinwoo.nro",
"sd:/switch/tinwoo/tinwoo.nro",
NULL
};
// Root CFW files to delete
static const char* root_files_to_delete[] = {
"sd:/boot.dat",
"sd:/boot.ini",
"sd:/exosphere.bin",
"sd:/exosphere.ini",
"sd:/hbmenu.nro",
"sd:/install.bat",
"sd:/license",
"sd:/loader.bin",
"sd:/mc-mitm.log",
"sd:/payload.bin",
"sd:/update.bin",
"sd:/version",
NULL
};
// Miscellaneous directories to delete
static const char* misc_dirs_to_delete[] = {
"sd:/argon",
"sd:/games",
"sd:/NSPs (Tools)",
"sd:/Patched Apps",
"sd:/SaltySD/flags",
"sd:/scripts",
"sd:/switch/tinfoil/db",
"sd:/tools",
"sd:/warmboot_mariko",
NULL
};
// Miscellaneous files to delete
static const char* misc_files_to_delete[] = {
"sd:/fusee-primary.bin",
"sd:/fusee.bin",
"sd:/SaltySD/exceptions.txt",
"sd:/SaltySD/saltysd_bootstrap.elf",
"sd:/SaltySD/saltysd_bootstrap32_3k.elf",
"sd:/SaltySD/saltysd_bootstrap32_5k.elf",
"sd:/SaltySD/saltysd_core.elf",
"sd:/SaltySD/saltysd_core32.elf",
NULL
};
/*
* OmniNX Installer - Deletion Lists for Update Mode
* Selective deletion when OmniNX is already installed.
*/
#pragma once
// Atmosphere subdirectories to delete
static const char* atmosphere_dirs_to_delete[] = {
"sd:/atmosphere/config",
"sd:/atmosphere/crash_reports",
"sd:/atmosphere/erpt_reports",
"sd:/atmosphere/exefs_patches/CrunchPatch",
"sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0",
"sd:/atmosphere/exefs_patches/bluetooth_patches",
"sd:/atmosphere/exefs_patches/bootlogo",
"sd:/atmosphere/exefs_patches/btm_patches",
"sd:/atmosphere/exefs_patches/es_patches",
"sd:/atmosphere/exefs_patches/hid_patches",
"sd:/atmosphere/exefs_patches/logo1",
"sd:/atmosphere/exefs_patches/nfim_ctest",
"sd:/atmosphere/exefs_patches/nim_ctest",
"sd:/atmosphere/exefs_patches/nvnflinger_cmu",
"sd:/atmosphere/extrazz",
"sd:/atmosphere/fatal_errors",
"sd:/atmosphere/fatal_reports",
"sd:/atmosphere/flags",
"sd:/atmosphere/hbl_html",
"sd:/atmosphere/hosts",
"sd:/atmosphere/kips",
"sd:/atmosphere/kip1",
"sd:/atmosphere/kip_patches",
"sd:/atmosphere/logs",
NULL
};
// Atmosphere contents directories (title IDs; only under atmosphere/contents/)
static const char* atmosphere_contents_dirs_to_delete[] = {
"sd:/atmosphere/contents/0000000000534C56",
"sd:/atmosphere/contents/00FF0000B378D640",
"sd:/atmosphere/contents/00FF0000636C6BFF",
"sd:/atmosphere/contents/00FF0000A53BB665",
"sd:/atmosphere/contents/0100000000000008",
"sd:/atmosphere/contents/010000000000000D",
"sd:/atmosphere/contents/010000000000002B",
"sd:/atmosphere/contents/0100000000000032",
"sd:/atmosphere/contents/0100000000000034",
"sd:/atmosphere/contents/0100000000000036",
"sd:/atmosphere/contents/0100000000000037",
"sd:/atmosphere/contents/010000000000003C",
"sd:/atmosphere/contents/0100000000000042",
"sd:/atmosphere/contents/0100000000000895",
"sd:/atmosphere/contents/0100000000000F12",
"sd:/atmosphere/contents/0100000000001000",
"sd:/atmosphere/contents/0100000000001007",
"sd:/atmosphere/contents/0100000000001013",
"sd:/atmosphere/contents/010000000000DA7A",
"sd:/atmosphere/contents/010000000000bd00",
"sd:/atmosphere/contents/01006a800016e000",
"sd:/atmosphere/contents/01009D901BC56000",
"sd:/atmosphere/contents/0100A3900C3E2000",
"sd:/atmosphere/contents/0100F43008C44000",
"sd:/atmosphere/contents/050000BADDAD0000",
"sd:/atmosphere/contents/4200000000000000",
"sd:/atmosphere/contents/420000000000000B",
"sd:/atmosphere/contents/420000000000000E",
"sd:/atmosphere/contents/4200000000000010",
"sd:/atmosphere/contents/4200000000000FFF",
"sd:/atmosphere/contents/420000000007E51A",
"sd:/atmosphere/contents/420000000007E51B",
"sd:/atmosphere/contents/690000000000000D",
NULL
};
// Atmosphere files to delete
static const char* atmosphere_files_to_delete[] = {
"sd:/atmosphere/config/exosphere.ini",
"sd:/atmosphere/config/stratosphere.ini",
"sd:/atmosphere/hbl.nsp",
"sd:/atmosphere/package3",
"sd:/atmosphere/reboot_payload.bin",
"sd:/atmosphere/stratosphere.romfs",
NULL
};
// Bootloader directories to delete
static const char* bootloader_dirs_to_delete[] = {
"sd:/bootloader/boot",
"sd:/bootloader/bootlogo",
"sd:/bootloader/ini2",
"sd:/bootloader/payloads",
"sd:/bootloader/reboot",
"sd:/bootloader/res",
"sd:/bootloader/sys",
NULL
};
// Bootloader files to delete
static const char* bootloader_files_to_delete[] = {
"sd:/bootloader/ArgonNX.bin",
"sd:/bootloader/bootlogo.bmp",
"sd:/bootloader/hekate_ipl.ini",
"sd:/bootloader/nyx.ini",
"sd:/bootloader/patches.ini",
"sd:/bootloader/update.bin",
"sd:/bootloader/ini/EmuMMC ohne Mods.ini",
NULL
};
// Config directories to delete
static const char* config_dirs_to_delete[] = {
"sd:/config/aio-switch-updater",
"sd:/config/blue_pack_updater",
"sd:/config/kefir-updater",
"sd:/config/nx-hbmenu",
"sd:/config/quickntp",
"sd:/config/sys-con",
"sd:/config/sys-patch",
"sd:/config/uberhand",
"sd:/config/ultrahand",
NULL
};
// 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/90DNS_tester",
"sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader",
"sd:/switch/appstore",
"sd:/switch/AtmoXL-Titel-Installer",
"sd:/switch/breeze",
"sd:/switch/checkpoint",
"sd:/switch/cheats-updater",
"sd:/switch/chiaki",
"sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams",
"sd:/switch/Daybreak",
"sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon",
"sd:/switch/Fizeau",
"sd:/switch/FTPD",
"sd:/switch/fw-downloader",
"sd:/switch/gamecard_installer",
"sd:/switch/Goldleaf",
"sd:/switch/haze",
"sd:/switch/JKSV",
"sd:/switch/kefir-updater",
"sd:/switch/ldnmitm_config",
"sd:/switch/Linkalho",
"sd:/switch/Moonlight-Switch",
"sd:/switch/Neumann",
"sd:/switch/NX-Activity-Log",
"sd:/switch/NX-Save-Sync",
"sd:/switch/NX-Shell",
"sd:/switch/NX-Update-Checker ",
"sd:/switch/NXGallery",
"sd:/switch/NXRemoteLauncher",
"sd:/switch/NXThemesInstaller",
"sd:/switch/nxdumptool",
"sd:/switch/nxmtp",
"sd:/switch/Payload_launcher",
"sd:/switch/Reboot",
"sd:/switch/reboot_to_argonNX",
"sd:/switch/reboot_to_hekate",
"sd:/switch/Shutdown_System",
"sd:/switch/SimpleModDownloader",
"sd:/switch/SimpleModManager",
"sd:/switch/sphaira",
"sd:/switch/studious-pancake",
"sd:/switch/Switch-Time",
"sd:/switch/SwitchIdent",
"sd:/switch/Switch_themes_Installer",
"sd:/switch/Switchfin",
"sd:/switch/Sys-Clk Manager",
"sd:/switch/Sys-Con",
"sd:/switch/sys-clk-manager",
"sd:/switch/themezer-nx",
"sd:/switch/themezernx",
"sd:/switch/tinwoo",
NULL
};
// Switch files (NRO) to delete
static const char* switch_files_to_delete[] = {
"sd:/switch/90DNS_tester/90DNS_tester.nro",
"sd:/switch/breeze.nro",
"sd:/switch/cheats-updater.nro",
"sd:/switch/chiaki.nro",
"sd:/switch/ChoiDujourNX.nro",
"sd:/switch/daybreak.nro",
"sd:/switch/DBI.nro",
"sd:/switch/DBI/DBI.nro",
"sd:/switch/DBI/DBI_810_DE.nro",
"sd:/switch/DBI/DBI_810_EN.nro",
"sd:/switch/DBI/DBI_845_DE.nro",
"sd:/switch/DBI/DBI_845_EN.nro",
"sd:/switch/DBI/DBI_849_DE.nro",
"sd:/switch/DBI/DBI_849_EN.nro",
"sd:/switch/DBI_810_DE/DBI_810.nro",
"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",
"sd:/switch/Goldleaf.nro",
"sd:/switch/haze.nro",
"sd:/switch/JKSV.nro",
"sd:/switch/ldnmitm_config.nro",
"sd:/switch/linkalho.nro",
"sd:/switch/Moonlight-Switch.nro",
"sd:/switch/Neumann.nro",
"sd:/switch/NX-Shell.nro",
"sd:/switch/NXGallery.nro",
"sd:/switch/NXThemesInstaller.nro",
"sd:/switch/nxdumptool.nro",
"sd:/switch/nxtc.bin",
"sd:/switch/reboot_to_payload.nro",
"sd:/switch/SimpleModDownloader.nro",
"sd:/switch/SimpleModManager.nro",
"sd:/switch/sphaira.nro",
"sd:/switch/SwitchIdent.nro",
"sd:/switch/Switch_themes_Installer/NXThemesInstaller.nro",
"sd:/switch/Switchfin.nro",
"sd:/switch/Sys-Clk Manager/sys-clk-manager.nro",
"sd:/switch/Sys-Con.nro",
"sd:/switch/sys-clk-manager.nro",
"sd:/switch/tinfoil.nro",
"sd:/switch/tinfoil/tinfoil.nro",
"sd:/switch/tinwoo.nro",
"sd:/switch/tinwoo/tinwoo.nro",
NULL
};
// Root CFW files to delete
static const char* root_files_to_delete[] = {
"sd:/boot.dat",
"sd:/boot.ini",
"sd:/exosphere.bin",
"sd:/exosphere.ini",
"sd:/hbmenu.nro",
"sd:/install.bat",
"sd:/license",
"sd:/loader.bin",
"sd:/mc-mitm.log",
"sd:/payload.bin",
"sd:/update.bin",
"sd:/version",
NULL
};
// Miscellaneous directories to delete
static const char* misc_dirs_to_delete[] = {
"sd:/argon",
"sd:/games",
"sd:/NSPs (Tools)",
"sd:/Patched Apps",
"sd:/SaltySD/flags",
"sd:/scripts",
"sd:/switch/tinfoil/db",
"sd:/tools",
"sd:/warmboot_mariko",
NULL
};
// Miscellaneous files to delete
static const char* misc_files_to_delete[] = {
"sd:/fusee-primary.bin",
"sd:/fusee.bin",
"sd:/SaltySD/exceptions.txt",
"sd:/SaltySD/saltysd_bootstrap.elf",
"sd:/SaltySD/saltysd_bootstrap32_3k.elf",
"sd:/SaltySD/saltysd_bootstrap32_5k.elf",
"sd:/SaltySD/saltysd_core.elf",
"sd:/SaltySD/saltysd_core32.elf",
NULL
};

View File

@@ -591,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;
@@ -601,7 +621,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 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;
@@ -614,7 +634,7 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
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);
@@ -624,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;
@@ -632,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;
@@ -640,7 +676,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 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;

View File

@@ -17,6 +17,7 @@ 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);
@@ -24,6 +25,7 @@ int cleanup_staging_directory(omninx_variant_t pack_variant);
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);

View File

@@ -11,6 +11,7 @@
#include "gfx.h"
#include "version.h"
#include <libs/fatfs/ff.h>
#include <utils/sprintf.h>
#include <string.h>
#undef COLOR_CYAN
@@ -31,6 +32,26 @@
#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) {
set_color(COLOR_CYAN);

View File

@@ -4,6 +4,7 @@
*/
#include "install.h"
#include "backup.h"
#include "deletion_lists_update.h"
#include "fs.h"
#include "version.h"
@@ -34,6 +35,25 @@
#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;

View File

@@ -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>
@@ -376,6 +378,56 @@ void ipl_main(void) {
// 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");
@@ -400,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);
@@ -437,7 +514,7 @@ void ipl_main(void) {
gfx_printf("Installation wird gestartet...\n");
set_color(COLOR_WHITE);
set_color(COLOR_ORANGE);
gfx_printf("Hinweis: Manchmal könnte es hängen. Einfach warten.\n\n");
gfx_printf("Hinweis: Manchmal kann es haengen. Einfach warten.\n\n");
set_color(COLOR_WHITE);
int result = perform_installation(pack_variant, mode);