Compare commits

..
2 Commits
Author SHA1 Message Date
Niklas080208 31aeeaabbb Disabled build on push 2026-02-15 16:34:38 +01:00
niklascfwandCursor 89994995af Add pre-update/clean backup feature
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
48 changed files with 510 additions and 4155 deletions

No files matched your search

-39
View File
@@ -1,39 +0,0 @@
name: Build Desktop Installer
on:
push:
paths:
- 'desktop/**'
- 'source/deletion_lists_*.h'
- 'tools/generate_deletion_lists.py'
- '.github/workflows/build-desktop.yml'
workflow_dispatch:
jobs:
build:
name: Build (${{ matrix.rid }})
runs-on: windows-latest
strategy:
matrix:
rid: [win-x64, win-arm64]
steps:
- uses: actions/checkout@v5
- name: Generate deletion lists
run: python tools/generate_deletion_lists.py
- name: Publish
run: >
dotnet publish desktop/OmniNX.DesktopInstaller/OmniNX.DesktopInstaller.csproj
-c Release
-r ${{ matrix.rid }}
-p:SelfContained=false
-p:PublishSingleFile=true
-o desktop/out/${{ matrix.rid }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: OmniNX-Desktop-Installer-${{ matrix.rid }}
path: desktop/out/${{ matrix.rid }}/OmniNX-Desktop-Installer.exe
+1 -1
View File
@@ -1,7 +1,7 @@
name: Build name: Build
on: on:
push: #push:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
-8
View File
@@ -20,11 +20,3 @@ Thumbs.db
# Version file is tracked, ignore local edits # Version file is tracked, ignore local edits
# VERSION # VERSION
# Vendored TegraExplorer tree
source/TegraExplorer/
# Desktop installer (.NET)
desktop/**/bin/
desktop/**/obj/
desktop/out/
-90
View File
@@ -1,90 +0,0 @@
# Debug configuration (`debug.ini`)
Optional file to **skip parts of the install** for testing (e.g. run only deletion and inspect the SD card). If the file is **missing**, the installer ignores debug entirely and runs the full flow.
## Location
```
sd:/config/omninx/debug.ini
```
Create the folder `sd:/config/omninx/` on the SD card if it does not exist. Same parent directory as `manifest.ini` and `config.ini`.
## Format
- Section name must be exactly **`[Debug]`** (case-sensitive in the parser).
- One key per line: `name=value`.
- **Enabling a skip:** set the value so it **starts** with one of: `1`, `t`, `T`, `y`, `Y`
Examples: `1`, `true`, `yes`, `True` — all enable that skip.
- **Disabling:** `0`, `false`, `no`, or omit the key.
## Parameters
### Clean install mode (`INSTALL_MODE_CLEAN`)
| Key | What is skipped |
|-----|-----------------|
| `skip_clean_backup` | Step 1 — backup of `sd:/switch/DBI/dbi.config`, `sd:/switch/prod.keys` to `sd:/temp_backup` |
| `skip_clean_wipe` | Step 2 — all cleanup from `deletion_lists_clean.h` (deletion / wipe) |
| `skip_clean_restore` | Step 3 — restore from `sd:/temp_backup` and post-restore paths (e.g. delete `sd:/switch/tinfoil/db`) |
| `skip_clean_install` | Step 4 — copy files from the OmniNX staging folder to the SD root |
| `skip_clean_staging_cleanup` | After install: do not remove the staging directory or other variant staging folders |
### Update mode (`INSTALL_MODE_UPDATE`)
| Key | What is skipped |
|-----|-----------------|
| `skip_update_cleanup` | Step 1 — selective deletion from `deletion_lists_update.h` |
| `skip_update_install` | Step 2 — copy from staging to SD |
| `skip_update_staging_cleanup` | End: do not remove staging / other variant folders |
| `skip_update_horizon_oc` | **OC variant only:** HorizonOC config backup before update, restore and cleanup after (entire HorizonOC update side path) |
### Both modes
| Key | What is skipped |
|-----|-----------------|
| `skip_hekate_8gb_post_copy` | After a successful copy: the optional `hekate_8GB` / RAM menu and related file moves (see `install_hekate_8gb_post_copy` in `install.c`) |
## Behaviour
- On startup, if `debug.ini` exists and parses successfully, the payload prints a **DEBUG-MODUS** banner listing which skips are active.
- If the file is absent, unreadable, or has no `[Debug]` section, **no** skips apply — behaviour matches a release install.
- Skipping steps can leave the SD in an inconsistent state; this is **for developers/testers only**.
## Examples
### Wipe only (test deletion, no backup/restore/copy)
Inspect leftovers after the clean-install wipe, without backing up, restoring, installing, or cleaning staging:
```ini
[Debug]
skip_clean_backup=1
skip_clean_restore=1
skip_clean_install=1
skip_clean_staging_cleanup=1
skip_hekate_8gb_post_copy=1
```
Do **not** set `skip_clean_wipe` (leave unset or `0`).
### Update: deletion only
```ini
[Debug]
skip_update_install=1
skip_update_staging_cleanup=1
skip_hekate_8gb_post_copy=1
```
### OC update without touching HorizonOC backup/restore
```ini
[Debug]
skip_update_horizon_oc=1
```
## Implementation
- Parsed in `install.c`: `install_debug_load()`, `perform_installation()`.
- Uses the same INI parser as `config.ini` (`ini_parse` from BDK).
+32 -21
View File
@@ -2,8 +2,6 @@
This document provides a detailed, step-by-step breakdown of everything that is checked and done during the OmniNX installation/update process. This document provides a detailed, step-by-step breakdown of everything that is checked and done during the OmniNX installation/update process.
For **optional debug step skips** (`sd:/config/omninx/debug.ini`), see **[DEBUG_INI.md](DEBUG_INI.md)**.
## Overview ## Overview
The OmniNX Installer Payload operates in two modes: The OmniNX Installer Payload operates in two modes:
@@ -90,9 +88,7 @@ The OmniNX Installer Payload operates in two modes:
**Trigger**: `INSTALL_MODE_UPDATE` (OmniNX already installed) **Trigger**: `INSTALL_MODE_UPDATE` (OmniNX already installed)
### Step 1: Cleanup (Selective Deletion) ### Step 1: Cleanup (Selective Deletion)
**Location**: `install_update.c` (`update_mode_cleanup`), `install.c` (`delete_path_lists_grouped`) **Location**: `install.c:321-380`
Before deletion, the payload reads optional **`sd:/config/omninx/preserve.ini`** (section `[Preserve]`). Paths enabled there are skipped, as are delete targets that would remove them (parent/child overlap). See **[PRESERVE_INI.md](PRESERVE_INI.md)**. Clean install does not use this file.
#### 10. Clean Atmosphere Subdirectories #### 10. Clean Atmosphere Subdirectories
**Location**: `install.c:325-327`, `deletion_lists.h:9-34` **Location**: `install.c:325-327`, `deletion_lists.h:9-34`
@@ -309,12 +305,17 @@ channel_pack={variant} # Same as current_pack
#### 32. Create Backup Directory #### 32. Create Backup Directory
- Creates `sd:/temp_backup/` directory - Creates `sd:/temp_backup/` directory
#### 33. Backup DBI settings #### 33. Backup DBI
- **Source**: `sd:/switch/DBI/dbi.config` (file only, not the whole `DBI` tree) - **Source**: `sd:/switch/DBI/`
- **Destination**: `sd:/temp_backup/dbi.config` - **Destination**: `sd:/temp_backup/DBI/`
- Only if the source file exists - Only if source exists
#### 34. Backup prod.keys #### 34. Backup Tinfoil
- **Source**: `sd:/switch/tinfoil/`
- **Destination**: `sd:/temp_backup/tinfoil/`
- Only if source exists
#### 35. Backup prod.keys
- **Source**: `sd:/switch/prod.keys` - **Source**: `sd:/switch/prod.keys`
- **Destination**: `sd:/temp_backup/prod.keys` - **Destination**: `sd:/temp_backup/prod.keys`
- Only if source exists - Only if source exists
@@ -324,22 +325,22 @@ channel_pack={variant} # Same as current_pack
### Step 2: Wipe Directories ### Step 2: Wipe Directories
**Location**: `install.c:543-602` **Location**: `install.c:543-602`
#### 35. Delete Entire Directories #### 36. Delete Entire Directories
Recursively deletes (if they exist): Recursively deletes (if they exist):
- `sd:/atmosphere/` (entire directory tree) - `sd:/atmosphere/` (entire directory tree)
- `sd:/bootloader/` (entire directory tree) - `sd:/bootloader/` (entire directory tree)
- `sd:/config/` (entire directory tree) - `sd:/config/` (entire directory tree)
- `sd:/switch/` (entire directory tree) - `sd:/switch/` (entire directory tree)
#### 36. Delete Root Files #### 37. Delete Root Files
- Uses same deletion list as update mode (Step 19) - Uses same deletion list as update mode (Step 19)
- Deletes `boot.dat`, `boot.ini`, `exosphere.ini`, etc. - Deletes `boot.dat`, `boot.ini`, `exosphere.ini`, etc.
#### 37. Delete Miscellaneous Items #### 38. Delete Miscellaneous Items
- Uses same deletion lists as update mode (Steps 20-21) - Uses same deletion lists as update mode (Steps 20-21)
- Deletes `argon/`, `games/`, `SaltySD/`, etc. - Deletes `argon/`, `games/`, `SaltySD/`, etc.
#### 38. Recreate Switch Directory #### 39. Recreate Switch Directory
- Creates empty `sd:/switch/` directory for restoration - Creates empty `sd:/switch/` directory for restoration
--- ---
@@ -347,17 +348,27 @@ Recursively deletes (if they exist):
### Step 3: Restore User Data ### Step 3: Restore User Data
**Location**: `backup.c:55-95` **Location**: `backup.c:55-95`
#### 39. Restore DBI settings #### 40. Restore DBI
- **Source**: `sd:/temp_backup/dbi.config` - **Source**: `sd:/temp_backup/DBI/`
- **Destination**: `sd:/switch/DBI/dbi.config` (creates `sd:/switch/DBI/` if needed) - **Destination**: `sd:/switch/DBI/`
- No longer copies the full `DBI` folder; old `.nro` cleanup is unnecessary - **Cleanup**: After restoration, deletes old DBI NRO files:
- `sd:/switch/DBI/DBI_810_EN.nro`
- `sd:/switch/DBI/DBI_810_DE.nro`
- `sd:/switch/DBI/DBI_845_EN.nro`
- `sd:/switch/DBI/DBI_845_DE.nro`
- `sd:/switch/DBI/DBI.nro`
#### 40. Restore prod.keys #### 41. Restore Tinfoil
- **Source**: `sd:/temp_backup/tinfoil/`
- **Destination**: `sd:/switch/tinfoil/`
- **Cleanup**: After restoration, deletes `sd:/switch/tinfoil/tinfoil.nro`
#### 42. Restore prod.keys
- **Source**: `sd:/temp_backup/prod.keys` - **Source**: `sd:/temp_backup/prod.keys`
- **Destination**: `sd:/switch/prod.keys` - **Destination**: `sd:/switch/prod.keys`
#### 41. Cleanup Backup Directory #### 43. Cleanup Backup Directory
**Location**: `backup.c` (`cleanup_backup`) **Location**: `backup.c:98-103`
- Deletes `sd:/temp_backup/` directory after restoration - Deletes `sd:/temp_backup/` directory after restoration
--- ---
+1 -23
View File
@@ -31,9 +31,6 @@ OBJS += $(patsubst $(BDKDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
$(patsubst $(BDKDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \ $(patsubst $(BDKDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \
$(call rwildcard, $(BDKDIR), *.S *.c))) $(call rwildcard, $(BDKDIR), *.S *.c)))
# Optional vendored tree under source/TegraExplorer/ is not part of this payload build.
OBJS := $(filter-out $(BUILDDIR)/$(TARGET)/TegraExplorer/%,$(OBJS))
GFX_INC := '"../$(SOURCEDIR)/gfx.h"' GFX_INC := '"../$(SOURCEDIR)/gfx.h"'
FFCFG_INC := '"../$(SOURCEDIR)/libs/fatfs/ffconf.h"' FFCFG_INC := '"../$(SOURCEDIR)/libs/fatfs/ffconf.h"'
@@ -49,11 +46,7 @@ LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defs
################################################################################ ################################################################################
RAMTEST_BIN := $(OUTPUTDIR)/RAM-Test.bin .PHONY: all clean release
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)))
@@ -63,7 +56,6 @@ 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
@@ -75,20 +67,6 @@ $(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 $@
-59
View File
@@ -1,59 +0,0 @@
# User preserve list (`preserve.ini`)
**Update mode only.** During OmniNX update cleanup (selective deletion), any path listed here with a truthy value is **not** deleted. Paths that would remove a preserved path (parent directory) are skipped as well.
- **File:** `sd:/config/omninx/preserve.ini`
- If missing or invalid, behavior is unchanged (full update deletion list applies).
## Format
Section **`[Preserve]`** (case-sensitive). Each line is **path as key**, **enable flag as value**:
```ini
[Preserve]
'sd:/ROMs'=1
sd:/switch/MyHomebrewFolder=0
sd:/backup/tools=1
```
### Path (key)
- Must start with **`sd:/`** (after optional quoting).
- **Optional** matching **single** or **double** quotes around the key are stripped (helps Ultrahand `set-ini-val` and readability).
- Trailing slashes are normalized away (except the volume root).
- **`sd:/` alone** is rejected (too broad).
- Minimum useful length after `sd:/` is enforced (at least one more character).
### Value (enable)
**On** if the value starts with `1`, `t`, `T`, `y`, or `Y` (e.g. `1`, `true`, `yes`). Anything else is **off** (path ignored).
### Limits
- Up to **96** preserved paths; longer keys truncated internally (~224 bytes per path).
- Duplicate paths after normalization are skipped.
- The list is allocated on the **heap** only while update cleanup runs (not a large static buffer in IPL IRAM), to avoid crowding hardware-adjacent memory used by Joy-Con UART and similar peripherals.
## Overlap rules
For each delete candidate from `deletion_lists_update.h`, deletion is skipped if it **overlaps** any enabled preserve path:
- Same path, or
- Delete target is **inside** a preserved tree (preserved path is a prefix), or
- Delete target is an **ancestor** of a preserved path (deleting it would remove the preserved data).
So preserving `sd:/ROMs` also blocks deleting `sd:/ROMs/title` if that were on the delete list, and blocks deleting `sd:/` if `sd:/ROMs` is preserved.
## Ultrahand
You can toggle entries with:
```text
set-ini-val sd:/config/omninx/preserve.ini Preserve 'sd:/ROMs' 1
```
and `0` to disable. Use the same quoted path in the key argument as in your INI.
## Clean install
**Not used** in clean install mode. `preserve.ini` is only read for **update** cleanup (`update_mode_cleanup`).
+3 -5
View File
@@ -12,7 +12,7 @@ Based on [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payl
- **Automatic Variant Detection**: Detects which OmniNX pack variant is present (Standard/Light/OC) - **Automatic Variant Detection**: Detects which OmniNX pack variant is present (Standard/Light/OC)
- **Smart Installation Modes**: - **Smart Installation Modes**:
- **Update Mode**: Selective deletion when OmniNX is already installed - **Update Mode**: Selective deletion when OmniNX is already installed
- **Clean Install**: Full wipe with backup/restore of user data (DBI `dbi.config`, prod.keys) - **Clean Install**: Full wipe with backup/restore of user data (DBI, Tinfoil, prod.keys)
- **Version Detection**: Detects installed OmniNX version via marker files (`1.0.0s`, `1.0.0l`, `1.0.0oc`) - **Version Detection**: Detects installed OmniNX version via marker files (`1.0.0s`, `1.0.0l`, `1.0.0oc`)
- **Progress Display**: Visual status messages during installation - **Progress Display**: Visual status messages during installation
- **Error Handling**: Detailed error reporting on screen - **Error Handling**: Detailed error reporting on screen
@@ -22,15 +22,12 @@ Based on [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payl
For detailed information about the installation process, see: For detailed information about the installation process, see:
- **[INSTALLATION_PROCESS.md](INSTALLATION_PROCESS.md)** - Complete step-by-step breakdown of everything checked and done during installation/update - **[INSTALLATION_PROCESS.md](INSTALLATION_PROCESS.md)** - Complete step-by-step breakdown of everything checked and done during installation/update
- **[DEBUG_INI.md](DEBUG_INI.md)** - Optional `sd:/config/omninx/debug.ini` parameters to skip install steps (for testing)
- **[PRESERVE_INI.md](PRESERVE_INI.md)** - Optional `sd:/config/omninx/preserve.ini` to skip deleting chosen paths in **update** mode
## Installation Modes ## Installation Modes
### Update Mode (OmniNX Detected) ### Update Mode (OmniNX Detected)
- Detected when version marker files (`1.0.0s`, `1.0.0l`, or `1.0.0oc`) are found - Detected when version marker files (`1.0.0s`, `1.0.0l`, or `1.0.0oc`) are found
- Performs selective deletion of specific directories/files - Performs selective deletion of specific directories/files
- Optional **`sd:/config/omninx/preserve.ini`**: user-listed `sd:/…` paths (with `=1`) are excluded from that cleanup (see [PRESERVE_INI.md](PRESERVE_INI.md))
- Preserves user data, savegames, and installed games - Preserves user data, savegames, and installed games
- Updates only necessary CFW components - Updates only necessary CFW components
@@ -38,7 +35,8 @@ For detailed information about the installation process, see:
- Detected when no version marker files are found - Detected when no version marker files are found
- Performs full wipe of `/atmosphere`, `/bootloader`, `/config`, and `/switch` - Performs full wipe of `/atmosphere`, `/bootloader`, `/config`, and `/switch`
- **Backs up and restores**: - **Backs up and restores**:
- `sd:/switch/DBI/dbi.config` → preserved (not the whole `DBI` folder) - `sd:/switch/DBI` → preserved
- `sd:/switch/tinfoil` → preserved
- `sd:/switch/prod.keys` → preserved - `sd:/switch/prod.keys` → preserved
- Fresh installation of all CFW components - Fresh installation of all CFW components
+1 -1
View File
@@ -1 +1 @@
2.1.1 1.0.0
-6
View File
@@ -721,12 +721,6 @@ void jc_deinit()
clock_disable_uart(UART_C); clock_disable_uart(UART_C);
} }
/* Drop current button state without power-cycling the Joy-Cons. */
void jc_clear_input(void)
{
jc_gamepad.buttons = 0;
}
static void jc_init_conn(joycon_ctxt_t *jc) static void jc_init_conn(joycon_ctxt_t *jc)
{ {
if (((u32)get_tmr_ms() - jc->last_received_time) > 1000) if (((u32)get_tmr_ms() - jc->last_received_time) > 1000)
-1
View File
@@ -92,7 +92,6 @@ typedef struct _jc_gamepad_rpt_t
void jc_power_supply(u8 uart, bool enable); void jc_power_supply(u8 uart, bool enable);
void jc_init_hw(); void jc_init_hw();
void jc_deinit(); void jc_deinit();
void jc_clear_input(void);
jc_gamepad_rpt_t *joycon_poll(); jc_gamepad_rpt_t *joycon_poll();
jc_gamepad_rpt_t *jc_get_bt_pairing_info(bool *is_l_hos, bool *is_r_hos); jc_gamepad_rpt_t *jc_get_bt_pairing_info(bool *is_l_hos, bool *is_r_hos);
-18
View File
@@ -1,18 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniNX.DesktopInstaller", "OmniNX.DesktopInstaller\OmniNX.DesktopInstaller.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

@@ -1,410 +0,0 @@
// <auto-generated />
// Generated by tools/generate_deletion_lists.py — do not edit manually.
namespace OmniNX.DesktopInstaller.Data;
public static partial class DeletionLists
{
public static class Clean
{
public static readonly string[] clean_atmosphere_dirs_to_delete =
{
@"atmosphere",
};
public static readonly string[] clean_atmosphere_contents_dirs_to_delete =
{
};
public static readonly string[] clean_atmosphere_files_to_delete =
{
};
public static readonly string[] clean_bootloader_dirs_to_delete =
{
@"bootloader\boot",
@"bootloader\bootlogo",
@"bootloader\ini2",
@"bootloader\payloads",
@"bootloader\reboot",
@"bootloader\res",
@"bootloader\sys",
};
public static readonly string[] clean_bootloader_files_to_delete =
{
@"bootloader\ArgonNX.bin",
@"bootloader\bootlogo.bmp",
@"bootloader\hekate_ipl.ini",
@"bootloader\patches.ini",
@"bootloader\update.bin",
@"bootloader\ini\EmuMMC ohne Mods.ini",
};
public static readonly string[] clean_config_dirs_to_delete =
{
@"config\aio-switch-updater",
@"config\blue_pack_updater",
@"config\kefir-updater",
@"config\nx-hbmenu",
@"config\sys-con",
@"config\sys-patch",
@"config\uberhand",
@"config\ultrahand",
};
public static readonly string[] clean_switch_dirs_to_delete =
{
@"switch",
};
public static readonly string[] clean_switch_files_to_delete =
{
};
public static readonly string[] clean_root_files_to_delete =
{
@"boot.dat",
@"boot.ini",
@"exosphere.bin",
@"exosphere.ini",
@"hbmenu.nro",
@"install.bat",
@"license",
@"loader.bin",
@"mc-mitm.log",
@"payload.bin",
@"update.bin",
@"version",
};
public static readonly string[] clean_misc_dirs_to_delete =
{
@"argon",
@"games",
@"NSPs (Tools)",
@"Patched Apps",
@"SaltySD\flags",
@"SaltySD\patches",
@"scripts",
@"TegraExplorer",
@"themes\systemData",
@"tools",
@"warmboot_mariko",
};
public static readonly string[] clean_misc_files_to_delete =
{
@"fusee-primary.bin",
@"fusee.bin",
@"SaltySD\exceptions.txt",
@"SaltySD\saltysd_bootstrap.elf",
@"SaltySD\saltysd_bootstrap32_3k.elf",
@"SaltySD\saltysd_bootstrap32_5k.elf",
@"SaltySD\saltysd_core.elf",
@"SaltySD\saltysd_core32.elf",
};
public static readonly string[] clean_post_restore_dirs_to_delete =
{
@"switch\tinfoil\db",
};
public static readonly string[] old_version_files_to_delete =
{
@"1.4.0-pre",
@"1.4.0-pre-c",
@"1.4.0-pre-d",
@"1.4.1",
@"1.5.0",
@"1.6.0",
@"1.6.1",
@"1.6.2",
};
}
public static class Update
{
public static readonly string[] atmosphere_dirs_to_delete =
{
@"atmosphere\config",
@"atmosphere\crash_reports",
@"atmosphere\erpt_reports",
@"atmosphere\exefs_patches\audio_mastervolume",
@"atmosphere\exefs_patches\CrunchPatch",
@"atmosphere\exefs_patches\SaltyNX_Fixes",
@"atmosphere\exefs_patches\Crunchyroll Patch 1.10.0",
@"atmosphere\exefs_patches\bluetooth_patches",
@"atmosphere\exefs_patches\bootlogo",
@"atmosphere\exefs_patches\btm_patches",
@"atmosphere\exefs_patches\es_patches",
@"atmosphere\exefs_patches\hid_patches",
@"atmosphere\exefs_patches\logo",
@"atmosphere\exefs_patches\nfim_ctest",
@"atmosphere\exefs_patches\nim_ctest",
@"atmosphere\exefs_patches\nvnflinger_cmu",
@"atmosphere\extrazz",
@"atmosphere\fatal_errors",
@"atmosphere\fatal_reports",
@"atmosphere\flags",
@"atmosphere\hbl_html",
@"atmosphere\hosts",
@"atmosphere\kips",
@"atmosphere\kip1",
@"atmosphere\kip_patches",
@"atmosphere\logs",
};
public static readonly string[] atmosphere_contents_dirs_to_delete =
{
@"atmosphere\contents\0000000000534C56",
@"atmosphere\contents\00FF0000B378D640",
@"atmosphere\contents\00FF0000636C6BFF",
@"atmosphere\contents\00FF0000A53BB665",
@"atmosphere\contents\0100000000000008",
@"atmosphere\contents\010000000000000D",
@"atmosphere\contents\010000000000002B",
@"atmosphere\contents\0100000000000032",
@"atmosphere\contents\0100000000000034",
@"atmosphere\contents\0100000000000036",
@"atmosphere\contents\0100000000000037",
@"atmosphere\contents\010000000000003C",
@"atmosphere\contents\0100000000000042",
@"atmosphere\contents\0100000000000895",
@"atmosphere\contents\0100000000000F12",
@"atmosphere\contents\0100000000001000",
@"atmosphere\contents\0100000000001007",
@"atmosphere\contents\0100000000001013",
@"atmosphere\contents\010000000000DA7A",
@"atmosphere\contents\010000000000bd00",
@"atmosphere\contents\01006a800016e000",
@"atmosphere\contents\01009D901BC56000",
@"atmosphere\contents\0100A3900C3E2000",
@"atmosphere\contents\0100F43008C44000",
@"atmosphere\contents\050000BADDAD0000",
@"atmosphere\contents\4200000000000000",
@"atmosphere\contents\420000000000000B",
@"atmosphere\contents\420000000000000E",
@"atmosphere\contents\4200000000000010",
@"atmosphere\contents\4200000000000FFF",
@"atmosphere\contents\420000000007E51A",
@"atmosphere\contents\420000000007E51B",
@"atmosphere\contents\690000000000000D",
};
public static readonly string[] atmosphere_files_to_delete =
{
@"atmosphere\config\exosphere.ini",
@"atmosphere\config\stratosphere.ini",
@"atmosphere\hbl.nsp",
@"atmosphere\package3",
@"atmosphere\reboot_payload.bin",
@"atmosphere\stratosphere.romfs",
@"atmosphere\kips\loader.kip",
@"atmosphere\kips\hoc.kip",
};
public static readonly string[] bootloader_dirs_to_delete =
{
@"bootloader\boot",
@"bootloader\bootlogo",
@"bootloader\ini2",
@"bootloader\payloads",
@"bootloader\reboot",
@"bootloader\res",
@"bootloader\sys",
};
public static readonly string[] bootloader_files_to_delete =
{
@"bootloader\ArgonNX.bin",
@"bootloader\bootlogo.bmp",
@"bootloader\hekate_ipl.ini",
@"bootloader\nyx.ini",
@"bootloader\patches.ini",
@"bootloader\update.bin",
@"bootloader\ini\EmuMMC ohne Mods.ini",
};
public static readonly string[] config_dirs_to_delete =
{
@"config\aio-switch-updater",
@"config\blue_pack_updater",
@"config\kefir-updater",
@"config\nx-hbmenu",
@"config\quickntp",
@"config\sys-con",
@"config\sys-patch",
@"config\uberhand",
@"config\ultrahand",
};
public static readonly string[] switch_dirs_to_delete =
{
@"switch\.packages",
@"switch\.overlays",
@"switch\90DNS_tester",
@"switch\aio-switch-updater",
@"switch\amsPLUS-downloader",
@"switch\appstore",
@"switch\AtmoXL-Titel-Installer",
@"switch\Benchmark-Toolbox",
@"switch\breeze",
@"switch\cheats-updater",
@"switch\chiaki",
@"switch\ChoiDujourNX",
@"switch\crash_ams",
@"switch\Daybreak",
@"switch\DNS-Block_Tester",
@"switch\DNS_mitm Tester",
@"switch\EdiZon",
@"switch\Fizeau",
@"switch\FTPD",
@"switch\Furmark-NX",
@"switch\fw-downloader",
@"switch\gamecard_installer",
@"switch\Goldleaf",
@"switch\haze",
@"switch\JKSV",
@"switch\kefir-updater",
@"switch\ldnmitm_config",
@"switch\Linkalho",
@"switch\Moonlight-Switch",
@"switch\Neumann",
@"switch\NX-Activity-Log",
@"switch\NX-Save-Sync",
@"switch\NX-Shell",
@"switch\NX-Update-Checker ",
@"switch\NXGallery",
@"switch\NXRemoteLauncher",
@"switch\NXThemesInstaller",
@"switch\nxdumptool",
@"switch\nxmtp",
@"switch\Payload_launcher",
@"switch\Reboot",
@"switch\reboot_to_argonNX",
@"switch\reboot_to_hekate",
@"switch\Shutdown_System",
@"switch\SimpleModAlchemist",
@"switch\SimpleModDownloader",
@"switch\SimpleModManager",
@"switch\sphaira",
@"switch\studious-pancake",
@"switch\Switch-Time",
@"switch\SwitchIdent",
@"switch\Switch_themes_Installer",
@"switch\swr-ini-tool",
@"switch\Sys-Clk Manager",
@"switch\Sys-Con",
@"switch\sys-clk-manager",
@"switch\themezer-nx",
@"switch\ThemezerNX",
@"switch\themezernx",
@"switch\tinwoo",
@"switch\Ultrahand-Reload",
};
public static readonly string[] switch_files_to_delete =
{
@"switch\90DNS_tester\90DNS_tester.nro",
@"switch\Benchmark-Toolbox\Benchmark-Toolbox.nro",
@"switch\breeze.nro",
@"switch\cheats-updater.nro",
@"switch\chiaki.nro",
@"switch\ChoiDujourNX.nro",
@"switch\daybreak.nro",
@"switch\DBI.nro",
@"switch\DBI\DBI.nro",
@"switch\DBI\DBI_810_DE.nro",
@"switch\DBI\DBI_810_EN.nro",
@"switch\DBI\DBI_845_DE.nro",
@"switch\DBI\DBI_845_EN.nro",
@"switch\DBI\DBI_849_DE.nro",
@"switch\DBI\DBI_849_EN.nro",
@"switch\DBI_810_DE\DBI_810.nro",
@"switch\DBI_810_DE\DBI_810_DE.nro",
@"switch\DBI_810_EN\DBI_810_EN.nro",
@"switch\DBI_RU\DBI_RU.nro",
@"switch\DBI\DBI_EN.nro",
@"switch\DBI_DE\DBI_DE.nro",
@"switch\DNS-Block_Tester\dns_tester.nro",
@"switch\DNS_mitm Tester.nro",
@"switch\EdiZon.nro",
@"switch\Fizeau.nro",
@"switch\Furmark-NX\Furmark-NX.nro",
@"switch\Goldleaf.nro",
@"switch\haze.nro",
@"switch\JKSV.nro",
@"switch\ldnmitm_config.nro",
@"switch\linkalho.nro",
@"switch\Moonlight-Switch.nro",
@"switch\Neumann.nro",
@"switch\NX-Shell.nro",
@"switch\NXGallery.nro",
@"switch\NXThemesInstaller.nro",
@"switch\nxdumptool.nro",
@"switch\nxtc.bin",
@"switch\reboot_to_payload.nro",
@"switch\SimpleModAlchemist\Simple_Mod_Alchemist.nro",
@"switch\SimpleModDownloader.nro",
@"switch\SimpleModManager.nro",
@"switch\sphaira.nro",
@"switch\swr-ini-tool\swr-ini-tool.nro",
@"switch\SwitchIdent.nro",
@"switch\Switch_themes_Installer\NXThemesInstaller.nro",
@"switch\Sys-Clk Manager\sys-clk-manager.nro",
@"switch\Sys-Con.nro",
@"switch\sys-clk-manager.nro",
@"switch\ThemezerNX\themezer-nx.nro",
@"switch\tinfoil.nro",
@"switch\tinfoil\tinfoil.nro",
@"switch\tinwoo.nro",
@"switch\tinwoo\tinwoo.nro",
@"switch\Ultrahand-Reload\Ultrahand-Reload.nro",
};
public static readonly string[] root_files_to_delete =
{
@"boot.dat",
@"boot.ini",
@"exosphere.bin",
@"exosphere.ini",
@"hbmenu.nro",
@"install.bat",
@"license",
@"loader.bin",
@"mc-mitm.log",
@"payload.bin",
@"update.bin",
@"version",
};
public static readonly string[] misc_dirs_to_delete =
{
@"argon",
@"games",
@"NSPs (Tools)",
@"Patched Apps",
@"SaltySD\flags",
@"scripts",
@"switch\tinfoil\db",
@"themes\systemData",
@"tools",
@"warmboot_mariko",
};
public static readonly string[] misc_files_to_delete =
{
@"fusee-primary.bin",
@"fusee.bin",
@"SaltySD\exceptions.txt",
@"SaltySD\saltysd_bootstrap.elf",
@"SaltySD\saltysd_bootstrap32_3k.elf",
@"SaltySD\saltysd_bootstrap32_5k.elf",
@"SaltySD\saltysd_core.elf",
@"SaltySD\saltysd_core32.elf",
};
}
}
-581
View File
@@ -1,581 +0,0 @@
using OmniNX.DesktopInstaller.Models;
using OmniNX.DesktopInstaller.Services;
using System.Runtime.InteropServices;
namespace OmniNX.DesktopInstaller;
public sealed class MainForm : Form
{
private readonly GiteaClient _gitea = new();
private readonly VersionDetectService _versionDetect = new();
private readonly InstallEngine _installEngine = new();
private ComboBox _driveCombo = null!;
private Button _refreshDrivesButton = null!;
private Label _statusLabel = null!;
private Label _releaseLabel = null!;
private Button _checkReleaseButton = null!;
private RadioButton _variantLight = null!;
private RadioButton _variantStandard = null!;
private RadioButton _variantOc = null!;
private RadioButton _ram4Gb = null!;
private RadioButton _ram8Gb = null!;
private RadioButton _hekateStandard = null!;
private RadioButton _hekatePro = null!;
private CheckBox _forceCleanCheck = null!;
private Button _installButton = null!;
private ProgressBar _progressBar = null!;
private TextBox _logBox = null!;
private ReleaseInfo? _latestRelease;
private CancellationTokenSource? _operationCts;
public MainForm()
{
Text = "OmniNX Desktop Installer";
MinimumSize = new Size(820, 640);
Size = new Size(900, 700);
StartPosition = FormStartPosition.CenterScreen;
Font = new Font("Segoe UI", 9F);
Icon = LoadFormIcon() ?? Icon.ExtractAssociatedIcon(Application.ExecutablePath);
BuildLayout();
RefreshDrives();
_ = LoadLatestReleaseAsync();
}
private void BuildLayout()
{
var root = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 8,
Padding = new Padding(12),
};
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.Controls.Add(BuildHeaderPanel(), 0, 0);
root.Controls.Add(BuildDrivePanel(), 0, 1);
root.Controls.Add(BuildVariantPanel(), 0, 2);
root.Controls.Add(BuildRamPanel(), 0, 3);
root.Controls.Add(BuildHekatePanel(), 0, 4);
root.Controls.Add(BuildOptionsPanel(), 0, 5);
root.Controls.Add(BuildActionPanel(), 0, 6);
root.Controls.Add(BuildLogPanel(), 0, 7);
Controls.Add(root);
}
private Control BuildHeaderPanel()
{
var panel = new TableLayoutPanel
{
AutoSize = true,
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 2,
};
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var title = new Label
{
Text = "OmniNX Desktop Installer",
Font = new Font(Font.FontFamily, 14F, FontStyle.Bold),
AutoSize = true,
Anchor = AnchorStyles.Left,
Margin = new Padding(0, 0, 16, 4),
};
_releaseLabel = new Label
{
AutoSize = true,
Text = "Release: checking...",
Margin = new Padding(0, 0, 8, 8),
Anchor = AnchorStyles.Left,
};
_checkReleaseButton = new Button { Text = "Refresh release", AutoSize = true, Margin = new Padding(0, 0, 0, 8) };
_checkReleaseButton.Click += async (_, _) => await LoadLatestReleaseAsync();
var releaseRow = new FlowLayoutPanel
{
AutoSize = true,
WrapContents = false,
Dock = DockStyle.Fill,
Margin = new Padding(0),
};
releaseRow.Controls.Add(_releaseLabel);
releaseRow.Controls.Add(_checkReleaseButton);
panel.Controls.Add(title, 0, 0);
panel.Controls.Add(releaseRow, 0, 1);
return panel;
}
private static Image? LoadEmbeddedImage(string resourceName)
{
var stream = typeof(MainForm).Assembly.GetManifestResourceStream(resourceName);
return stream is null ? null : Image.FromStream(stream);
}
private static Icon? LoadFormIcon()
{
using var image = LoadEmbeddedImage("OmniNX.DesktopInstaller.Assets.logo-128.png");
if (image is null)
return null;
using var bitmap = new Bitmap(image, new Size(32, 32));
var handle = bitmap.GetHicon();
try
{
using var temp = Icon.FromHandle(handle);
return (Icon)temp.Clone();
}
finally
{
DestroyIcon(handle);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
private Control BuildDrivePanel()
{
var group = new GroupBox { Text = "SD Card / Target Drive", Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(10) };
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 3, AutoSize = true };
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
_driveCombo = new ComboBox { Dock = DockStyle.Fill, DropDownStyle = ComboBoxStyle.DropDownList };
_driveCombo.SelectedIndexChanged += (_, _) => UpdateInstallationStatus();
_refreshDrivesButton = new Button { Text = "Refresh", AutoSize = true };
_refreshDrivesButton.Click += (_, _) => RefreshDrives();
_statusLabel = new Label
{
Dock = DockStyle.Fill,
AutoSize = true,
ForeColor = Color.DimGray,
Text = "Select a removable drive (SD card reader).",
};
layout.Controls.Add(new Label { Text = "Drive:", AutoSize = true, Anchor = AnchorStyles.Left }, 0, 0);
layout.Controls.Add(_driveCombo, 1, 0);
layout.Controls.Add(_refreshDrivesButton, 2, 0);
layout.SetColumnSpan(_statusLabel, 3);
layout.Controls.Add(_statusLabel, 0, 1);
group.Controls.Add(layout);
return group;
}
private Control BuildVariantPanel()
{
var group = new GroupBox { Text = "OmniNX Pack Variant", Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(10) };
var flow = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true };
_variantLight = new RadioButton { Text = "Light", AutoSize = true, Margin = new Padding(0, 0, 24, 0) };
_variantStandard = new RadioButton { Text = "Standard", AutoSize = true, Checked = true, Margin = new Padding(0, 0, 24, 0) };
_variantOc = new RadioButton { Text = "OC", AutoSize = true };
flow.Controls.AddRange([_variantLight, _variantStandard, _variantOc]);
group.Controls.Add(flow);
return group;
}
private Control BuildRamPanel()
{
var group = new GroupBox { Text = "RAM / Hekate Payload", Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(10) };
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, ColumnCount = 1 };
_ram4Gb = new RadioButton
{
Text = "4 GB console (default)",
AutoSize = true,
Checked = true,
};
_ram8Gb = new RadioButton
{
Text = "8 GB console (RAM mod) — copies 8GB hekate payload to payload.bin and bootloader/update.bin",
AutoSize = true,
};
var warn = new Label
{
Text = "Warning: Only select 8 GB if the console has an 8 GB RAM mod. Wrong selection can cause boot issues.",
AutoSize = true,
ForeColor = Color.DarkOrange,
MaximumSize = new Size(820, 0),
Margin = new Padding(0, 6, 0, 0),
};
layout.Controls.Add(_ram4Gb);
layout.Controls.Add(_ram8Gb);
layout.Controls.Add(warn);
group.Controls.Add(layout);
return group;
}
private Control BuildHekatePanel()
{
var group = new GroupBox { Text = "Hekate / Atmosphere", Dock = DockStyle.Fill, AutoSize = true, Padding = new Padding(10) };
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, ColumnCount = 1 };
_hekateStandard = new RadioButton
{
Text = "Standard Hekate / Atmosphere",
AutoSize = true,
Checked = true,
};
_hekatePro = new RadioButton
{
Text = "Hekate-Pro / Atmosphere-Pro (only for emuMMC on eMMC)",
AutoSize = true,
};
var hint = new Label
{
Text = "Pro variant replaces bootloader/atmosphere files from extras/hekate-ams-pro/.",
AutoSize = true,
ForeColor = Color.DimGray,
Margin = new Padding(0, 6, 0, 0),
};
layout.Controls.Add(_hekateStandard);
layout.Controls.Add(_hekatePro);
layout.Controls.Add(hint);
group.Controls.Add(layout);
return group;
}
private Control BuildOptionsPanel()
{
_forceCleanCheck = new CheckBox
{
Text = "Force clean install (full wipe with backup/restore) even if OmniNX is already installed",
AutoSize = true,
MaximumSize = new Size(820, 0),
};
return _forceCleanCheck;
}
private Control BuildActionPanel()
{
var panel = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill, WrapContents = false };
_installButton = new Button
{
Text = "Download && Install",
AutoSize = true,
Height = 36,
Font = new Font(Font.FontFamily, 10F, FontStyle.Bold),
};
_installButton.Click += async (_, _) => await RunInstallAsync();
_progressBar = new ProgressBar { Width = 500, Height = 24, Margin = new Padding(16, 6, 0, 0) };
panel.Controls.Add(_installButton);
panel.Controls.Add(_progressBar);
return panel;
}
private Control BuildLogPanel()
{
var group = new GroupBox { Text = "Log", Dock = DockStyle.Fill, Padding = new Padding(10) };
_logBox = new TextBox
{
Dock = DockStyle.Fill,
Multiline = true,
ScrollBars = ScrollBars.Vertical,
ReadOnly = true,
Font = new Font("Consolas", 9F),
BackColor = Color.FromArgb(24, 24, 24),
ForeColor = Color.Gainsboro,
};
group.Controls.Add(_logBox);
return group;
}
private void RefreshDrives()
{
DriveService.DriveItem? selected = _driveCombo.SelectedItem as DriveService.DriveItem;
_driveCombo.Items.Clear();
foreach (var drive in DriveService.GetCandidateDrives())
_driveCombo.Items.Add(drive);
if (selected is not null)
{
for (var i = 0; i < _driveCombo.Items.Count; i++)
{
if (_driveCombo.Items[i] is DriveService.DriveItem d &&
d.Drive.Name.Equals(selected.Drive.Name, StringComparison.OrdinalIgnoreCase))
{
_driveCombo.SelectedIndex = i;
return;
}
}
}
if (_driveCombo.Items.Count > 0)
_driveCombo.SelectedIndex = 0;
else
UpdateInstallationStatus();
}
private DriveInfo? SelectedDrive =>
(_driveCombo.SelectedItem as DriveService.DriveItem)?.Drive;
private OmniNxVariant SelectedVariant
{
get
{
if (_variantOc.Checked) return OmniNxVariant.Oc;
if (_variantLight.Checked) return OmniNxVariant.Light;
return OmniNxVariant.Standard;
}
}
private void UpdateInstallationStatus()
{
var drive = SelectedDrive;
if (drive is null)
{
_statusLabel.Text = "No drive selected.";
_statusLabel.ForeColor = Color.DimGray;
return;
}
try
{
var status = _versionDetect.DetectInstallation(drive.RootDirectory.FullName);
if (status.IsInstalled)
{
_statusLabel.Text =
$"OmniNX detected: {VariantHelper.GetDisplayName(status.Variant)} v{status.Version} — " +
(_forceCleanCheck.Checked ? "will clean install" : "will update");
_statusLabel.ForeColor = Color.ForestGreen;
}
else
{
_statusLabel.Text = "No OmniNX installation detected — will perform clean install.";
_statusLabel.ForeColor = Color.SteelBlue;
}
}
catch (Exception ex)
{
_statusLabel.Text = $"Could not read drive: {ex.Message}";
_statusLabel.ForeColor = Color.Firebrick;
}
}
private async Task LoadLatestReleaseAsync()
{
try
{
SetUiEnabled(false);
_releaseLabel.Text = "Release: fetching...";
_latestRelease = await _gitea.GetLatestReleaseAsync();
_releaseLabel.Text = $"Latest release: {_latestRelease.TagName} ({_latestRelease.Name})";
Log($"Latest release: {_latestRelease.TagName}");
}
catch (Exception ex)
{
_releaseLabel.Text = "Release: fetch failed";
Log($"Failed to fetch release: {ex.Message}", error: true);
}
finally
{
SetUiEnabled(true);
}
}
private async Task RunInstallAsync()
{
var drive = SelectedDrive;
if (drive is null)
{
MessageBox.Show(this, "Please select a target drive.", "OmniNX Installer",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (_latestRelease is null)
{
MessageBox.Show(this, "Release information is not loaded yet. Click 'Refresh release' first.",
"OmniNX Installer", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var variant = SelectedVariant;
var asset = _gitea.FindPackAsset(_latestRelease, variant);
if (asset is null)
{
MessageBox.Show(this,
$"No zip asset found for {VariantHelper.GetDisplayName(variant)} in release {_latestRelease.TagName}.",
"OmniNX Installer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
var driveRoot = drive.RootDirectory.FullName;
var status = _versionDetect.DetectInstallation(driveRoot);
var mode = _versionDetect.DetermineMode(status, _forceCleanCheck.Checked);
var modeText = mode == InstallMode.Update ? "UPDATE" : "CLEAN INSTALL";
var confirm = MessageBox.Show(this,
$"Target drive: {drive.Name}\n" +
$"Variant: {VariantHelper.GetDisplayName(variant)}\n" +
$"Mode: {modeText}\n" +
$"Release: {_latestRelease.TagName}\n" +
$"RAM: {(_ram8Gb.Checked ? "8 GB" : "4 GB")}\n" +
$"Hekate/AMS: {(_hekatePro.Checked ? "Pro" : "Standard")}\n\n" +
"All data on paths targeted by the installer may be modified or deleted.\n" +
"Continue?",
"Confirm installation",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);
if (confirm != DialogResult.Yes)
return;
_operationCts?.Cancel();
_operationCts = new CancellationTokenSource();
var token = _operationCts.Token;
try
{
SetUiEnabled(false);
_progressBar.Value = 0;
_logBox.Clear();
var cacheDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"OmniNX-Desktop-Installer",
"cache");
PathUtils.EnsureDirectory(cacheDir);
var zipPath = Path.Combine(cacheDir, asset.Name);
if (!File.Exists(zipPath))
{
Log($"Downloading {asset.Name} ({asset.Size / (1024 * 1024)} MB)...");
await _gitea.DownloadAssetAsync(asset, zipPath, new Progress<(long, long)>(p =>
{
var (downloaded, total) = p;
if (total > 0)
{
var pct = (int)(downloaded * 100 / total);
_progressBar.Value = Math.Clamp(pct, 0, 100);
}
}), token);
Log("Download complete.");
}
else
{
Log($"Using cached pack: {zipPath}");
}
var extractRoot = Path.Combine(cacheDir, "extract", _latestRelease.TagName);
var staging = ZipExtractor.ExtractPack(zipPath, extractRoot, new Progress<string>(msg => Log(msg)));
Log($"Pack staging: {staging}");
var options = new InstallOptions
{
DriveRoot = driveRoot,
Variant = variant,
Mode = mode,
Use8GbRam = _ram8Gb.Checked,
UseHekateAmsPro = _hekatePro.Checked,
ForceCleanInstall = _forceCleanCheck.Checked,
StagingPath = staging,
};
var progress = new Progress<InstallProgressReport>(report =>
{
Log(report.Message, report.IsError);
if (report.Percent is int pct)
_progressBar.Value = Math.Clamp(pct, 0, 100);
});
await _installEngine.RunAsync(options, progress, token);
_progressBar.Value = 100;
MessageBox.Show(this,
mode == InstallMode.Update
? "OmniNX update completed successfully."
: "OmniNX installation completed successfully.",
"OmniNX Installer",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
UpdateInstallationStatus();
}
catch (OperationCanceledException)
{
Log("Operation cancelled.", error: true);
}
catch (Exception ex)
{
Log($"Installation failed: {ex.Message}", error: true);
MessageBox.Show(this, ex.Message, "Installation failed",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
SetUiEnabled(true);
}
}
private void Log(string message, bool error = false)
{
if (InvokeRequired)
{
BeginInvoke(() => Log(message, error));
return;
}
var prefix = $"[{DateTime.Now:HH:mm:ss}] ";
_logBox.AppendText(prefix + message + Environment.NewLine);
if (error)
_statusLabel.ForeColor = Color.Firebrick;
}
private void SetUiEnabled(bool enabled)
{
if (InvokeRequired)
{
BeginInvoke(() => SetUiEnabled(enabled));
return;
}
_installButton.Enabled = enabled;
_checkReleaseButton.Enabled = enabled;
_refreshDrivesButton.Enabled = enabled;
_driveCombo.Enabled = enabled;
_variantLight.Enabled = enabled;
_variantStandard.Enabled = enabled;
_variantOc.Enabled = enabled;
_ram4Gb.Enabled = enabled;
_ram8Gb.Enabled = enabled;
_hekateStandard.Enabled = enabled;
_hekatePro.Enabled = enabled;
_forceCleanCheck.Enabled = enabled;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_forceCleanCheck.CheckedChanged += (_, _) => UpdateInstallationStatus();
}
}
@@ -1,54 +0,0 @@
namespace OmniNX.DesktopInstaller.Models;
public enum OmniNxVariant
{
None = 0,
Light,
Standard,
Oc,
}
public enum InstallMode
{
Clean,
Update,
}
public sealed class InstallOptions
{
public required string DriveRoot { get; init; }
public required OmniNxVariant Variant { get; init; }
public InstallMode Mode { get; init; }
public bool Use8GbRam { get; init; }
public bool UseHekateAmsPro { get; init; }
public bool ForceCleanInstall { get; init; }
public string? StagingPath { get; init; }
}
public sealed class OmniNxStatus
{
public bool IsInstalled { get; init; }
public OmniNxVariant Variant { get; init; }
public string Version { get; init; } = string.Empty;
}
public sealed class ReleaseInfo
{
public required string TagName { get; init; }
public required string Name { get; init; }
public required IReadOnlyList<ReleaseAsset> Assets { get; init; }
}
public sealed class ReleaseAsset
{
public required string Name { get; init; }
public required string DownloadUrl { get; init; }
public long Size { get; init; }
}
public sealed class InstallProgressReport
{
public required string Message { get; init; }
public int? Percent { get; init; }
public bool IsError { get; init; }
}
@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Assets\app.ico</ApplicationIcon>
<AssemblyName>OmniNX-Desktop-Installer</AssemblyName>
<RootNamespace>OmniNX.DesktopInstaller</RootNamespace>
<Version>1.0.0</Version>
<Authors>OmniNX</Authors>
<Description>PC-based OmniNX installer for mass deployments and modchip installers</Description>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>false</SelfContained>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\logo-128.png" />
</ItemGroup>
</Project>
@@ -1,11 +0,0 @@
namespace OmniNX.DesktopInstaller;
static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
@@ -1,118 +0,0 @@
namespace OmniNX.DesktopInstaller.Services;
public sealed class BackupService
{
private const string TempBackup = @"temp_backup";
private const string DbiConfig = @"switch\DBI\dbi.config";
private const string ProdKeys = @"switch\prod.keys";
private const string CyberFoilDir = @"switch\CyberFoil";
private const string CheckpointDir = @"switch\Checkpoint";
private const string HorizonOcConfig = @"config\horizon-oc\config.ini";
private const string HorizonOcBackupDir = @".omninx_oc_update";
public void BackupUserData(string driveRoot, IProgress<string>? progress = null)
{
var backupRoot = PathUtils.ToDrivePath(driveRoot, TempBackup);
PathUtils.EnsureDirectory(backupRoot);
progress?.Report("Backing up CyberFoil, Checkpoint, DBI (dbi.config), prod.keys...");
CopyIfExists(PathUtils.ToDrivePath(driveRoot, DbiConfig),
Path.Combine(backupRoot, "dbi.config"));
CopyIfExists(PathUtils.ToDrivePath(driveRoot, ProdKeys),
Path.Combine(backupRoot, "prod.keys"));
var cyberfoil = PathUtils.ToDrivePath(driveRoot, CyberFoilDir);
if (Directory.Exists(cyberfoil))
{
var dst = Path.Combine(backupRoot, "CyberFoil");
PathUtils.EnsureDirectory(dst);
CopyIfExists(Path.Combine(cyberfoil, "cyberfoil.nro"), Path.Combine(dst, "cyberfoil.nro"));
CopyIfExists(Path.Combine(cyberfoil, "config.json"), Path.Combine(dst, "config.json"));
var remotes = Path.Combine(cyberfoil, "remotes");
if (Directory.Exists(remotes))
PathUtils.CopyDirectory(remotes, Path.Combine(dst, "remotes"));
}
var checkpoint = PathUtils.ToDrivePath(driveRoot, CheckpointDir);
if (Directory.Exists(checkpoint))
PathUtils.CopyDirectory(checkpoint, Path.Combine(backupRoot, "Checkpoint"));
}
public void RestoreUserData(string driveRoot, IProgress<string>? progress = null)
{
var backupRoot = PathUtils.ToDrivePath(driveRoot, TempBackup);
if (!Directory.Exists(backupRoot))
return;
progress?.Report("Restoring CyberFoil, Checkpoint, DBI (dbi.config), prod.keys...");
PathUtils.EnsureDirectory(PathUtils.ToDrivePath(driveRoot, @"switch"));
CopyIfExists(Path.Combine(backupRoot, "dbi.config"),
PathUtils.ToDrivePath(driveRoot, DbiConfig));
CopyIfExists(Path.Combine(backupRoot, "prod.keys"),
PathUtils.ToDrivePath(driveRoot, ProdKeys));
var cyberBackup = Path.Combine(backupRoot, "CyberFoil");
if (Directory.Exists(cyberBackup))
{
var dst = PathUtils.ToDrivePath(driveRoot, CyberFoilDir);
PathUtils.EnsureDirectory(dst);
CopyIfExists(Path.Combine(cyberBackup, "cyberfoil.nro"), Path.Combine(dst, "cyberfoil.nro"));
CopyIfExists(Path.Combine(cyberBackup, "config.json"), Path.Combine(dst, "config.json"));
var remotes = Path.Combine(cyberBackup, "remotes");
if (Directory.Exists(remotes))
PathUtils.CopyDirectory(remotes, Path.Combine(dst, "remotes"));
}
var checkpointBackup = Path.Combine(backupRoot, "Checkpoint");
if (Directory.Exists(checkpointBackup))
PathUtils.CopyDirectory(checkpointBackup, PathUtils.ToDrivePath(driveRoot, @"switch"));
}
public void CleanupBackup(string driveRoot)
{
var backupRoot = PathUtils.ToDrivePath(driveRoot, TempBackup);
if (Directory.Exists(backupRoot))
PathUtils.DeletePath(backupRoot);
}
public void BackupHorizonOcConfig(string driveRoot, IProgress<string>? progress = null)
{
var src = PathUtils.ToDrivePath(driveRoot, HorizonOcConfig);
if (!File.Exists(src))
return;
progress?.Report("HorizonOC: backing up config.ini...");
var dstDir = PathUtils.ToDrivePath(driveRoot, HorizonOcBackupDir);
PathUtils.EnsureDirectory(dstDir);
PathUtils.CopyFile(src, Path.Combine(dstDir, "config.ini"));
}
public void RestoreHorizonOcConfig(string driveRoot, IProgress<string>? progress = null)
{
var src = Path.Combine(PathUtils.ToDrivePath(driveRoot, HorizonOcBackupDir), "config.ini");
if (!File.Exists(src))
return;
progress?.Report("HorizonOC: restoring config.ini...");
PathUtils.EnsureDirectory(PathUtils.ToDrivePath(driveRoot, @"config\horizon-oc"));
PathUtils.CopyFile(src, PathUtils.ToDrivePath(driveRoot, HorizonOcConfig));
}
public void CleanupHorizonOcBackup(string driveRoot)
{
var dir = PathUtils.ToDrivePath(driveRoot, HorizonOcBackupDir);
if (Directory.Exists(dir))
PathUtils.DeletePath(dir);
}
private static void CopyIfExists(string src, string dst)
{
if (!File.Exists(src))
return;
PathUtils.CopyFile(src, dst);
}
}
@@ -1,132 +0,0 @@
namespace OmniNX.DesktopInstaller.Services;
public sealed class ConfigIniService
{
private const string ConfigIniRelative = @"config\omninx\config.ini";
private const string LegacyRamConfigRelative = @"config\omninx\ram_config.ini";
public bool TryReadBool(string driveRoot, string section, string key, out bool value)
{
value = false;
var path = PathUtils.ToDrivePath(driveRoot, ConfigIniRelative);
if (!File.Exists(path))
return false;
var inSection = false;
foreach (var rawLine in File.ReadLines(path))
{
var line = rawLine.Trim();
if (line.Length == 0)
continue;
if (line.StartsWith('[') && line.EndsWith(']'))
{
inSection = line[1..^1].Equals(section, StringComparison.Ordinal);
continue;
}
if (!inSection)
continue;
var eq = line.IndexOf('=');
if (eq <= 0)
continue;
var k = line[..eq].Trim();
if (!k.Equals(key, StringComparison.Ordinal))
continue;
var val = line[(eq + 1)..].Trim();
if (val == "1")
{
value = true;
return true;
}
if (val == "0")
{
value = false;
return true;
}
return false;
}
return false;
}
public void MigrateLegacyRamConfig(string driveRoot)
{
var legacy = PathUtils.ToDrivePath(driveRoot, LegacyRamConfigRelative);
if (!File.Exists(legacy))
return;
if (TryReadLegacyBool(legacy, "Ram", "8gb", out var use8Gb))
{
var havePro = TryReadBool(driveRoot, "HekateAms", "pro", out var usePro);
WriteExtras(driveRoot, needRam: true, use8Gb, needHekateAms: havePro, havePro ? usePro : false);
}
PathUtils.DeletePath(legacy);
}
public void WriteExtras(string driveRoot, bool needRam, bool use8Gb, bool needHekateAms, bool usePro)
{
var path = PathUtils.ToDrivePath(driveRoot, ConfigIniRelative);
PathUtils.EnsureDirectory(Path.GetDirectoryName(path)!);
using var writer = new StreamWriter(path, append: false);
if (needRam)
{
writer.WriteLine("[Ram]");
writer.WriteLine($"8gb={(use8Gb ? 1 : 0)}");
writer.WriteLine();
}
if (needHekateAms)
{
writer.WriteLine("[HekateAms]");
writer.WriteLine($"pro={(usePro ? 1 : 0)}");
}
}
private static bool TryReadLegacyBool(string path, string section, string key, out bool value)
{
value = false;
var inSection = false;
foreach (var rawLine in File.ReadLines(path))
{
var line = rawLine.Trim();
if (line.StartsWith('[') && line.EndsWith(']'))
{
inSection = line[1..^1].Equals(section, StringComparison.Ordinal);
continue;
}
if (!inSection)
continue;
var eq = line.IndexOf('=');
if (eq <= 0)
continue;
if (!line[..eq].Trim().Equals(key, StringComparison.Ordinal))
continue;
var val = line[(eq + 1)..].Trim();
if (val == "1")
{
value = true;
return true;
}
if (val == "0")
{
value = false;
return true;
}
}
return false;
}
}
@@ -1,187 +0,0 @@
using CleanLists = OmniNX.DesktopInstaller.Data.DeletionLists.Clean;
using UpdateLists = OmniNX.DesktopInstaller.Data.DeletionLists.Update;
namespace OmniNX.DesktopInstaller.Services;
public sealed class DeletionService
{
private readonly PreserveService _preserve = new();
public void RunCleanDeletion(string driveRoot, IProgress<string>? progress = null)
{
DeleteGroup(driveRoot, "atmosphere", CleanLists.clean_atmosphere_dirs_to_delete, progress);
DeleteGroup(driveRoot, "bootloader", CleanLists.clean_bootloader_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_bootloader_files_to_delete, progress);
DeleteGroup(driveRoot, "config", CleanLists.clean_config_dirs_to_delete, progress);
DeleteGroup(driveRoot, "switch", CleanLists.clean_switch_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_root_files_to_delete, progress);
DeleteGroup(driveRoot, "misc", CleanLists.clean_misc_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_misc_files_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.old_version_files_to_delete, progress);
PathUtils.EnsureDirectory(PathUtils.ToDrivePath(driveRoot, @"switch"));
}
public void RunUpdateDeletion(string driveRoot, IProgress<string>? progress = null)
{
_preserve.Load(driveRoot, progress);
DeleteGroup(driveRoot, "atmosphere", UpdateLists.atmosphere_dirs_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "atmosphere/contents", UpdateLists.atmosphere_contents_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.atmosphere_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "bootloader", UpdateLists.bootloader_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.bootloader_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "config", UpdateLists.config_dirs_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "switch", UpdateLists.switch_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.switch_files_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.root_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "misc", UpdateLists.misc_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.misc_files_to_delete, progress, _preserve);
_preserve.Clear();
}
public void RunPostRestoreCleanup(string driveRoot)
{
DeleteGroup(driveRoot, "switch (post-restore)", CleanLists.clean_post_restore_dirs_to_delete, null);
}
private static void DeleteGroup(
string driveRoot,
string label,
string[] paths,
IProgress<string>? progress,
PreserveService? preserve = null)
{
if (paths.Length == 0)
return;
progress?.Report($"Deleting {label}...");
foreach (var rel in paths)
{
var full = PathUtils.ToDrivePath(driveRoot, rel);
if (!PathUtils.PathExists(full))
continue;
if (preserve?.IsBlocked(full, driveRoot) == true)
{
progress?.Report($" Skipped (preserve): {rel}");
continue;
}
progress?.Report($" Delete: {rel}");
PathUtils.DeletePath(full);
}
}
private static void DeleteFiles(
string driveRoot,
string[] paths,
IProgress<string>? progress,
PreserveService? preserve = null)
{
foreach (var rel in paths)
{
var full = PathUtils.ToDrivePath(driveRoot, rel);
if (!File.Exists(full))
continue;
if (preserve?.IsBlocked(full, driveRoot) == true)
{
progress?.Report($" Skipped (preserve): {rel}");
continue;
}
progress?.Report($" Delete: {rel}");
PathUtils.DeletePath(full);
}
}
}
public sealed class PreserveService
{
private readonly List<string> _paths = new();
public void Load(string driveRoot, IProgress<string>? progress = null)
{
_paths.Clear();
var iniPath = PathUtils.ToDrivePath(driveRoot, @"config\omninx\preserve.ini");
if (!File.Exists(iniPath))
return;
var inSection = false;
foreach (var rawLine in File.ReadLines(iniPath))
{
var line = rawLine.Trim();
if (line.StartsWith('[') && line.EndsWith(']'))
{
inSection = line[1..^1].Equals("Preserve", StringComparison.Ordinal);
continue;
}
if (!inSection || !line.Contains('='))
continue;
var eq = line.IndexOf('=');
var key = line[..eq].Trim().Trim('\'', '"');
var val = line[(eq + 1)..].Trim();
if (!IsTruthy(val))
continue;
var normalized = NormalizePreservePath(key, driveRoot);
if (normalized is null)
continue;
if (!_paths.Contains(normalized, StringComparer.OrdinalIgnoreCase))
_paths.Add(normalized);
}
if (_paths.Count > 0)
progress?.Report($"preserve.ini: {_paths.Count} path(s) excluded from update cleanup.");
}
public bool IsBlocked(string deleteFullPath, string driveRoot)
{
if (_paths.Count == 0)
return false;
var normalizedDelete = Path.GetFullPath(deleteFullPath).TrimEnd(Path.DirectorySeparatorChar);
foreach (var preserve in _paths)
{
var normalizedPreserve = Path.GetFullPath(preserve).TrimEnd(Path.DirectorySeparatorChar);
if (normalizedDelete.Equals(normalizedPreserve, StringComparison.OrdinalIgnoreCase))
return true;
if (IsPrefix(normalizedPreserve, normalizedDelete))
return true;
if (IsPrefix(normalizedDelete, normalizedPreserve))
return true;
}
return false;
}
public void Clear() => _paths.Clear();
private static string? NormalizePreservePath(string key, string driveRoot)
{
var path = key.Replace("sd:/", string.Empty).Replace("sd:\\", string.Empty)
.Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar);
if (path.Length < 2)
return null;
return PathUtils.ToDrivePath(driveRoot, path);
}
private static bool IsPrefix(string prefix, string path)
{
if (!path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return false;
return path.Length == prefix.Length ||
path[prefix.Length] == Path.DirectorySeparatorChar;
}
private static bool IsTruthy(string val)
{
if (string.IsNullOrEmpty(val))
return false;
var c = char.ToUpperInvariant(val[0]);
return c is '1' or 'T' or 'Y';
}
}
@@ -1,97 +0,0 @@
namespace OmniNX.DesktopInstaller.Services;
public sealed class ExtrasService
{
private readonly ConfigIniService _config = new();
public (bool needRam, bool needHekateAms) DetectExtras(string driveRoot)
{
var extras = PathUtils.ToDrivePath(driveRoot, "extras");
var needRam = File.Exists(Path.Combine(extras, "hekate_8gb.bin")) ||
File.Exists(Path.Combine(extras, "hekate-pro_8gb.bin"));
var needHekateAms = Directory.Exists(Path.Combine(extras, "hekate-ams-pro"));
return (needRam, needHekateAms);
}
public void ApplyExtras(
string driveRoot,
bool use8GbRam,
bool useHekateAmsPro,
IProgress<string>? progress = null)
{
_config.MigrateLegacyRamConfig(driveRoot);
var extras = PathUtils.ToDrivePath(driveRoot, "extras");
if (!Directory.Exists(extras))
return;
var (needRam, needHekateAms) = DetectExtras(driveRoot);
if (!needRam && !needHekateAms)
return;
progress?.Report("Saving RAM / Hekate-AMS selection to config.ini...");
_config.WriteExtras(driveRoot, needRam, use8GbRam, needHekateAms, useHekateAmsPro);
if (useHekateAmsPro && needHekateAms)
{
progress?.Report("Installing Hekate-Pro / Atmosphere-Pro...");
ApplyHekateAmsPro(extras, driveRoot, progress);
}
else if (needHekateAms)
{
progress?.Report("Keeping standard Hekate / Atmosphere.");
}
if (needRam)
{
progress?.Report("Applying RAM payload setting...");
Apply8GbPayload(extras, driveRoot, use8GbRam, useHekateAmsPro);
}
progress?.Report("Removing extras/...");
PathUtils.DeletePath(extras);
}
private static void ApplyHekateAmsPro(string extrasDir, string driveRoot, IProgress<string>? progress)
{
var src = Path.Combine(extrasDir, "hekate-ams-pro");
if (!Directory.Exists(src))
return;
foreach (var entry in Directory.GetFileSystemEntries(src))
{
var name = Path.GetFileName(entry);
var dst = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), name);
if (Directory.Exists(entry))
PathUtils.CopyDirectory(entry, dst);
else
PathUtils.CopyFile(entry, dst);
progress?.Report($" Copied: {name}");
}
}
private static void Apply8GbPayload(string extrasDir, string driveRoot, bool use8Gb, bool usePro)
{
var hekate8 = Path.Combine(extrasDir, "hekate_8gb.bin");
var hekatePro8 = Path.Combine(extrasDir, "hekate-pro_8gb.bin");
if (!use8Gb)
{
if (File.Exists(hekate8)) File.Delete(hekate8);
if (File.Exists(hekatePro8)) File.Delete(hekatePro8);
return;
}
var src = usePro ? hekatePro8 : hekate8;
if (!File.Exists(src))
return;
var payloadDst = PathUtils.ToDrivePath(driveRoot, "payload.bin");
var updateDst = PathUtils.ToDrivePath(driveRoot, @"bootloader\update.bin");
PathUtils.CopyFile(src, payloadDst);
PathUtils.CopyFile(src, updateDst);
if (File.Exists(hekate8)) File.Delete(hekate8);
if (File.Exists(hekatePro8)) File.Delete(hekatePro8);
}
}
@@ -1,100 +0,0 @@
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public sealed class GiteaClient
{
public const string DefaultBaseUrl = "https://git.niklascfw.de";
private readonly HttpClient _http;
private readonly string _baseUrl;
public GiteaClient(HttpClient? http = null, string? baseUrl = null)
{
_http = http ?? new HttpClient();
_http.DefaultRequestHeaders.UserAgent.ParseAdd("OmniNX-Desktop-Installer/1.0");
_baseUrl = (baseUrl ?? DefaultBaseUrl).TrimEnd('/');
}
public async Task<ReleaseInfo> GetLatestReleaseAsync(CancellationToken cancellationToken = default)
{
var url = $"{_baseUrl}/api/v1/repos/OmniNX/OmniNX/releases/latest";
var response = await _http.GetFromJsonAsync<GiteaReleaseDto>(url, cancellationToken)
?? throw new InvalidOperationException("Empty release response from Gitea.");
return MapRelease(response);
}
public async Task<string> DownloadAssetAsync(
ReleaseAsset asset,
string destinationPath,
IProgress<(long downloaded, long total)>? progress = null,
CancellationToken cancellationToken = default)
{
PathUtils.EnsureDirectory(Path.GetDirectoryName(destinationPath)!);
using var response = await _http.GetAsync(asset.DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var total = response.Content.Headers.ContentLength ?? asset.Size;
await using var input = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var output = File.Create(destinationPath);
var buffer = new byte[81920];
long downloaded = 0;
int read;
while ((read = await input.ReadAsync(buffer, cancellationToken)) > 0)
{
await output.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
downloaded += read;
progress?.Report((downloaded, total));
}
return destinationPath;
}
public ReleaseAsset? FindPackAsset(ReleaseInfo release, OmniNxVariant variant)
{
var prefix = VariantHelper.GetZipAssetPrefix(variant);
return release.Assets.FirstOrDefault(a =>
a.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
a.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase));
}
private static ReleaseInfo MapRelease(GiteaReleaseDto dto) => new()
{
TagName = dto.TagName ?? string.Empty,
Name = dto.Name ?? dto.TagName ?? string.Empty,
Assets = dto.Assets?.Select(a => new ReleaseAsset
{
Name = a.Name ?? string.Empty,
DownloadUrl = a.BrowserDownloadUrl ?? string.Empty,
Size = a.Size,
}).ToList() ?? [],
};
private sealed class GiteaReleaseDto
{
[JsonPropertyName("tag_name")]
public string? TagName { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("assets")]
public List<GiteaAssetDto>? Assets { get; set; }
}
private sealed class GiteaAssetDto
{
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("browser_download_url")]
public string? BrowserDownloadUrl { get; set; }
[JsonPropertyName("size")]
public long Size { get; set; }
}
}
@@ -1,186 +0,0 @@
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public sealed class InstallEngine
{
private readonly DeletionService _deletion = new();
private readonly BackupService _backup = new();
private readonly ExtrasService _extras = new();
public async Task RunAsync(
InstallOptions options,
IProgress<InstallProgressReport> progress,
CancellationToken cancellationToken = default)
{
var driveRoot = PathUtils.NormalizeDriveRoot(options.DriveRoot);
var staging = options.StagingPath
?? throw new InvalidOperationException("Staging path is required.");
if (!Directory.Exists(staging))
throw new DirectoryNotFoundException($"Staging path not found: {staging}");
Report(progress, $"Install mode: {options.Mode}");
Report(progress, $"Pack variant: {VariantHelper.GetDisplayName(options.Variant)}");
if (options.Mode == InstallMode.Update)
{
await RunUpdateAsync(driveRoot, staging, options, progress, cancellationToken);
}
else
{
await RunCleanAsync(driveRoot, staging, options, progress, cancellationToken);
}
Report(progress, options.Mode == InstallMode.Update
? "Update completed successfully."
: "Installation completed successfully.");
}
private async Task RunUpdateAsync(
string driveRoot,
string staging,
InstallOptions options,
IProgress<InstallProgressReport> progress,
CancellationToken cancellationToken)
{
if (options.Variant == OmniNxVariant.Oc)
{
Report(progress, "HorizonOC: backing up config.ini...");
_backup.BackupHorizonOcConfig(driveRoot, StringProgress(progress));
}
Report(progress, "Step 1: Cleanup...");
await Task.Run(() => _deletion.RunUpdateDeletion(driveRoot, StringProgress(progress)), cancellationToken);
Report(progress, "Step 2: Copying files...");
await Task.Run(() => CopyPackFiles(staging, driveRoot, options.Variant, progress), cancellationToken);
Report(progress, "Applying RAM / Hekate-AMS extras...");
await Task.Run(() => _extras.ApplyExtras(driveRoot, options.Use8GbRam, options.UseHekateAmsPro,
StringProgress(progress)), cancellationToken);
if (options.Variant == OmniNxVariant.Oc)
{
Report(progress, "HorizonOC: restoring config.ini...");
await Task.Run(() =>
{
_backup.RestoreHorizonOcConfig(driveRoot, StringProgress(progress));
_backup.CleanupHorizonOcBackup(driveRoot);
}, cancellationToken);
}
}
private async Task RunCleanAsync(
string driveRoot,
string staging,
InstallOptions options,
IProgress<InstallProgressReport> progress,
CancellationToken cancellationToken)
{
Report(progress, "Step 1: Backing up user data...");
await Task.Run(() => _backup.BackupUserData(driveRoot, StringProgress(progress)), cancellationToken);
Report(progress, "Step 2: Cleaning old installation...");
await Task.Run(() => _deletion.RunCleanDeletion(driveRoot, StringProgress(progress)), cancellationToken);
Report(progress, "Step 3: Restoring user data...");
await Task.Run(() =>
{
_backup.RestoreUserData(driveRoot, StringProgress(progress));
_deletion.RunPostRestoreCleanup(driveRoot);
_backup.CleanupBackup(driveRoot);
}, cancellationToken);
Report(progress, "Step 4: Copying files...");
await Task.Run(() => CopyPackFiles(staging, driveRoot, options.Variant, progress), cancellationToken);
Report(progress, "Applying RAM / Hekate-AMS extras...");
await Task.Run(() => _extras.ApplyExtras(driveRoot, options.Use8GbRam, options.UseHekateAmsPro,
StringProgress(progress)), cancellationToken);
}
private static void CopyPackFiles(
string staging,
string driveRoot,
OmniNxVariant variant,
IProgress<InstallProgressReport> progress)
{
CopyFolderIfExists(staging, driveRoot, "atmosphere", progress);
CopyFolderIfExists(staging, driveRoot, "bootloader", progress);
CopyFolderIfExists(staging, driveRoot, "config", progress);
CopyFolderIfExists(staging, driveRoot, "switch", progress);
CopyFolderIfExists(staging, driveRoot, "themes", progress);
CopyFolderIfExists(staging, driveRoot, "warmboot_mariko", progress);
if (variant == OmniNxVariant.Oc)
CopyFolderIfExists(staging, driveRoot, "SaltySD", progress);
CopyFolderIfExists(staging, driveRoot, "extras", progress);
CopyRootFiles(staging, driveRoot, progress);
}
private static void CopyFolderIfExists(
string staging,
string driveRoot,
string folder,
IProgress<InstallProgressReport> progress)
{
var src = Path.Combine(staging, folder);
if (!Directory.Exists(src))
return;
var dst = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), folder);
Report(progress, $"Copying {folder}/...");
PathUtils.CopyDirectory(src, dst, new Progress<(int current, int total, string file)>(copyProgress =>
{
var (current, total, file) = copyProgress;
var pct = total > 0 ? current * 100 / total : 0;
progress.Report(new InstallProgressReport
{
Message = $" {folder}/ {file}",
Percent = pct,
});
}));
}
private static void CopyRootFiles(
string staging,
string driveRoot,
IProgress<InstallProgressReport> progress)
{
Report(progress, "Copying root files...");
var skipDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"atmosphere", "bootloader", "config", "switch", "themes",
"warmboot_mariko", "SaltySD", "extras",
};
foreach (var file in Directory.GetFiles(staging))
{
var name = Path.GetFileName(file);
if (name.StartsWith('.'))
continue;
var dst = Path.Combine(PathUtils.NormalizeDriveRoot(driveRoot), name);
PathUtils.CopyFile(file, dst);
Report(progress, $" {name}");
}
foreach (var dir in Directory.GetDirectories(staging))
{
var name = Path.GetFileName(dir);
if (skipDirs.Contains(name))
continue;
Report(progress, $"Skipping unexpected directory at pack root: {name}");
}
}
private static void Report(IProgress<InstallProgressReport> progress, string message, bool isError = false)
{
progress.Report(new InstallProgressReport { Message = message, IsError = isError });
}
private static IProgress<string> StringProgress(IProgress<InstallProgressReport> progress) =>
new Progress<string>(msg => Report(progress, msg));
}
@@ -1,133 +0,0 @@
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public static class VariantHelper
{
public static string GetDisplayName(OmniNxVariant variant) => variant switch
{
OmniNxVariant.Light => "Light",
OmniNxVariant.Standard => "Standard",
OmniNxVariant.Oc => "OC",
_ => "None",
};
public static string GetStagingFolderName(OmniNxVariant variant) => variant switch
{
OmniNxVariant.Light => "OmniNX Light",
OmniNxVariant.Standard => "OmniNX Standard",
OmniNxVariant.Oc => "OmniNX OC",
_ => string.Empty,
};
public static string GetZipAssetPrefix(OmniNxVariant variant) => variant switch
{
OmniNxVariant.Light => "OmniNX-Light",
OmniNxVariant.Standard => "OmniNX-Standard",
OmniNxVariant.Oc => "OmniNX-OC",
_ => string.Empty,
};
public static OmniNxVariant ParsePackName(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return OmniNxVariant.None;
return value.Trim() switch
{
"Light" => OmniNxVariant.Light,
"Standard" => OmniNxVariant.Standard,
"OC" => OmniNxVariant.Oc,
_ => OmniNxVariant.None,
};
}
}
public static class PathUtils
{
public static string NormalizeDriveRoot(string driveRoot)
{
var root = driveRoot.Trim();
if (!root.EndsWith(Path.DirectorySeparatorChar))
root += Path.DirectorySeparatorChar;
return root;
}
public static string ToDrivePath(string driveRoot, string sdRelative)
{
var rel = sdRelative.Replace("sd:/", string.Empty).Replace("sd:\\", string.Empty)
.Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(NormalizeDriveRoot(driveRoot), rel);
}
public static string RelativeFromDrive(string driveRoot, string fullPath)
{
var root = NormalizeDriveRoot(driveRoot);
if (fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
return fullPath[root.Length..];
return fullPath;
}
public static bool PathExists(string path)
{
return Directory.Exists(path) || File.Exists(path);
}
public static void EnsureDirectory(string path)
{
Directory.CreateDirectory(path);
}
public static void CopyFile(string src, string dst, bool overwrite = true)
{
var dir = Path.GetDirectoryName(dst);
if (!string.IsNullOrEmpty(dir))
Directory.CreateDirectory(dir);
File.Copy(src, dst, overwrite);
}
public static void CopyDirectory(string src, string dst, IProgress<(int current, int total, string file)>? progress = null)
{
if (!Directory.Exists(src))
return;
var files = Directory.GetFiles(src, "*", SearchOption.AllDirectories);
var total = files.Length;
var current = 0;
foreach (var file in files)
{
var rel = Path.GetRelativePath(src, file);
var target = Path.Combine(dst, rel);
CopyFile(file, target);
current++;
progress?.Report((current, total, rel));
}
}
public static void DeletePath(string path)
{
if (File.Exists(path))
{
File.SetAttributes(path, FileAttributes.Normal);
File.Delete(path);
return;
}
if (Directory.Exists(path))
Directory.Delete(path, recursive: true);
}
public static int CountItemsRecursive(string path)
{
if (File.Exists(path))
return 1;
if (!Directory.Exists(path))
return 0;
var count = 1;
foreach (var entry in Directory.EnumerateFileSystemEntries(path, "*", SearchOption.AllDirectories))
count++;
return count;
}
}
@@ -1,79 +0,0 @@
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public sealed class VersionDetectService
{
private const string ManifestRelative = @"config\omninx\manifest.ini";
public OmniNxStatus DetectInstallation(string driveRoot)
{
var manifest = PathUtils.ToDrivePath(driveRoot, ManifestRelative);
if (!File.Exists(manifest))
return new OmniNxStatus { IsInstalled = false, Variant = OmniNxVariant.None };
var variant = OmniNxVariant.None;
var version = string.Empty;
var inSection = false;
foreach (var rawLine in File.ReadLines(manifest))
{
var line = rawLine.Trim();
if (line.StartsWith('[') && line.EndsWith(']'))
{
inSection = line[1..^1].Equals("OmniNX", StringComparison.Ordinal);
continue;
}
if (!inSection || !line.Contains('='))
continue;
var eq = line.IndexOf('=');
var key = line[..eq].Trim();
var val = line[(eq + 1)..].Trim();
if (key.Equals("current_pack", StringComparison.Ordinal))
variant = VariantHelper.ParsePackName(val);
else if (key.Equals("version", StringComparison.Ordinal))
version = val;
}
return new OmniNxStatus
{
IsInstalled = variant != OmniNxVariant.None,
Variant = variant,
Version = version,
};
}
public InstallMode DetermineMode(OmniNxStatus status, bool forceClean)
{
if (forceClean)
return InstallMode.Clean;
return status.IsInstalled ? InstallMode.Update : InstallMode.Clean;
}
}
public static class DriveService
{
public sealed record DriveItem(DriveInfo Drive)
{
public override string ToString() => FormatDriveLabel(Drive);
}
public static IReadOnlyList<DriveItem> GetCandidateDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.IsReady && d.DriveType == DriveType.Removable)
.OrderBy(d => d.Name)
.Select(d => new DriveItem(d))
.ToList();
}
public static string FormatDriveLabel(DriveInfo drive)
{
var label = string.IsNullOrWhiteSpace(drive.VolumeLabel) ? "No Label" : drive.VolumeLabel;
var sizeGb = drive.TotalSize / (1024.0 * 1024 * 1024);
return $"{drive.Name} [{label}] ({sizeGb:F1} GB, {drive.DriveFormat})";
}
}
@@ -1,40 +0,0 @@
using System.IO.Compression;
using OmniNX.DesktopInstaller.Models;
namespace OmniNX.DesktopInstaller.Services;
public static class ZipExtractor
{
public static string ExtractPack(string zipPath, string extractRoot, IProgress<string>? progress = null)
{
PathUtils.EnsureDirectory(extractRoot);
var targetDir = Path.Combine(extractRoot, Path.GetFileNameWithoutExtension(zipPath));
if (Directory.Exists(targetDir))
Directory.Delete(targetDir, recursive: true);
progress?.Report($"Extracting {Path.GetFileName(zipPath)}...");
ZipFile.ExtractToDirectory(zipPath, targetDir, overwriteFiles: true);
return ResolveStagingPath(targetDir);
}
public static string ResolveStagingPath(string extractRoot)
{
foreach (var variant in new[] { OmniNxVariant.Standard, OmniNxVariant.Light, OmniNxVariant.Oc })
{
var candidate = Path.Combine(extractRoot, VariantHelper.GetStagingFolderName(variant));
if (Directory.Exists(candidate))
return candidate;
}
if (Directory.Exists(Path.Combine(extractRoot, "atmosphere")) ||
Directory.Exists(Path.Combine(extractRoot, "bootloader")))
return extractRoot;
var subdirs = Directory.GetDirectories(extractRoot);
if (subdirs.Length == 1)
return ResolveStagingPath(subdirs[0]);
throw new InvalidOperationException(
"Could not locate OmniNX pack root in extracted archive. Expected 'OmniNX Light', 'OmniNX Standard', or 'OmniNX OC' folder.");
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="OmniNX.DesktopInstaller"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
+112 -116
View File
@@ -6,14 +6,97 @@
#include "fs.h" #include "fs.h"
#include <libs/fatfs/ff.h> #include <libs/fatfs/ff.h>
#include <string.h> #include <string.h>
#include <stdarg.h>
#include <utils/sprintf.h> #include <utils/sprintf.h>
#define BACKUP_PATH_MAX 270
// Check if file/directory exists // Check if file/directory exists
static bool path_exists(const char *path) { static bool path_exists(const char *path) {
FILINFO fno; FILINFO fno;
return (f_stat(path, &fno) == FR_OK); 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 // Backup user data before clean install
int backup_user_data(void) { int backup_user_data(void) {
int res; int res;
@@ -24,9 +107,17 @@ int backup_user_data(void) {
return res; return res;
} }
// Backup DBI settings file only (not .nro installers) // Backup DBI if it exists
if (path_exists(DBI_CONFIG_PATH)) { if (path_exists("sd:/switch/DBI")) {
res = file_copy(DBI_CONFIG_PATH, TEMP_BACKUP_DBI_CONFIG); res = folder_copy("sd:/switch/DBI", TEMP_BACKUP_PATH);
if (res != FR_OK) {
return res;
}
}
// Backup Tinfoil if it exists
if (path_exists("sd:/switch/tinfoil")) {
res = folder_copy("sd:/switch/tinfoil", TEMP_BACKUP_PATH);
if (res != FR_OK) { if (res != FR_OK) {
return res; return res;
} }
@@ -40,43 +131,6 @@ int backup_user_data(void) {
} }
} }
// Backup CyberFoil (nro, config, remotes) if installed
if (path_exists(CYBERFOIL_DIR)) {
res = f_mkdir(TEMP_BACKUP_CYBERFOIL);
if (res != FR_OK && res != FR_EXIST) {
return res;
}
if (path_exists(CYBERFOIL_NRO)) {
res = file_copy(CYBERFOIL_NRO, TEMP_BACKUP_CYBERFOIL_NRO);
if (res != FR_OK) {
return res;
}
}
if (path_exists(CYBERFOIL_CONFIG)) {
res = file_copy(CYBERFOIL_CONFIG, TEMP_BACKUP_CYBERFOIL_CONFIG);
if (res != FR_OK) {
return res;
}
}
if (path_exists(CYBERFOIL_REMOTES)) {
res = folder_copy(CYBERFOIL_REMOTES, TEMP_BACKUP_CYBERFOIL);
if (res != FR_OK) {
return res;
}
}
}
// Backup Checkpoint (saves and app data) if installed
if (path_exists(CHECKPOINT_DIR)) {
res = folder_copy(CHECKPOINT_DIR, TEMP_BACKUP_PATH);
if (res != FR_OK) {
return res;
}
}
return FR_OK; return FR_OK;
} }
@@ -90,15 +144,25 @@ int restore_user_data(void) {
return res; return res;
} }
// Restore DBI settings if backed up // Restore DBI if backup exists
if (path_exists(TEMP_BACKUP_DBI_CONFIG)) { if (path_exists("sd:/temp_backup/DBI")) {
res = f_mkdir("sd:/switch/DBI"); res = folder_copy("sd:/temp_backup/DBI", "sd:/switch");
if (res != FR_OK && res != FR_EXIST) { if (res == FR_OK) {
return res; // Delete old DBI .nro files
f_unlink("sd:/switch/DBI/DBI_810_EN.nro");
f_unlink("sd:/switch/DBI/DBI_810_DE.nro");
f_unlink("sd:/switch/DBI/DBI_845_EN.nro");
f_unlink("sd:/switch/DBI/DBI_845_DE.nro");
f_unlink("sd:/switch/DBI/DBI.nro");
} }
res = file_copy(TEMP_BACKUP_DBI_CONFIG, DBI_CONFIG_PATH); }
if (res != FR_OK) {
return res; // Restore Tinfoil if backup exists
if (path_exists("sd:/temp_backup/tinfoil")) {
res = folder_copy("sd:/temp_backup/tinfoil", "sd:/switch");
if (res == FR_OK) {
// Delete old tinfoil.nro
f_unlink("sd:/switch/tinfoil/tinfoil.nro");
} }
} }
@@ -110,43 +174,6 @@ int restore_user_data(void) {
} }
} }
// Restore CyberFoil if backed up
if (path_exists(TEMP_BACKUP_CYBERFOIL)) {
res = f_mkdir(CYBERFOIL_DIR);
if (res != FR_OK && res != FR_EXIST) {
return res;
}
if (path_exists(TEMP_BACKUP_CYBERFOIL_NRO)) {
res = file_copy(TEMP_BACKUP_CYBERFOIL_NRO, CYBERFOIL_NRO);
if (res != FR_OK) {
return res;
}
}
if (path_exists(TEMP_BACKUP_CYBERFOIL_CONFIG)) {
res = file_copy(TEMP_BACKUP_CYBERFOIL_CONFIG, CYBERFOIL_CONFIG);
if (res != FR_OK) {
return res;
}
}
if (path_exists(TEMP_BACKUP_CYBERFOIL_REMOTES)) {
res = folder_copy(TEMP_BACKUP_CYBERFOIL_REMOTES, CYBERFOIL_DIR);
if (res != FR_OK) {
return res;
}
}
}
// Restore Checkpoint if backed up
if (path_exists(TEMP_BACKUP_CHECKPOINT)) {
res = folder_copy(TEMP_BACKUP_CHECKPOINT, "sd:/switch");
if (res != FR_OK) {
return res;
}
}
return FR_OK; return FR_OK;
} }
@@ -157,34 +184,3 @@ 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);
}
+5 -30
View File
@@ -7,31 +7,10 @@
#include <utils/types.h> #include <utils/types.h>
#define TEMP_BACKUP_PATH "sd:/temp_backup" #define TEMP_BACKUP_PATH "sd:/temp_backup"
/** DBI settings; preserved across clean install (not whole DBI folder). */ #define BACKUP_BASE_PATH "sd:/backup/OmniNX"
#define DBI_CONFIG_PATH "sd:/switch/DBI/dbi.config" #define PRE_OMNINX_LABEL "pre-omninx"
#define TEMP_BACKUP_DBI_CONFIG TEMP_BACKUP_PATH "/dbi.config"
/** CyberFoil homebrew; preserved across clean install (nro, config, remotes only). */ // Backup user data (DBI, Tinfoil, prod.keys) before clean install
#define CYBERFOIL_DIR "sd:/switch/CyberFoil"
#define CYBERFOIL_NRO CYBERFOIL_DIR "/cyberfoil.nro"
#define CYBERFOIL_CONFIG CYBERFOIL_DIR "/config.json"
#define CYBERFOIL_REMOTES CYBERFOIL_DIR "/remotes"
#define TEMP_BACKUP_CYBERFOIL TEMP_BACKUP_PATH "/CyberFoil"
#define TEMP_BACKUP_CYBERFOIL_NRO TEMP_BACKUP_CYBERFOIL "/cyberfoil.nro"
#define TEMP_BACKUP_CYBERFOIL_CONFIG TEMP_BACKUP_CYBERFOIL "/config.json"
#define TEMP_BACKUP_CYBERFOIL_REMOTES TEMP_BACKUP_CYBERFOIL "/remotes"
/** Checkpoint save manager; preserved across clean install (full folder). */
#define CHECKPOINT_DIR "sd:/switch/Checkpoint"
#define TEMP_BACKUP_CHECKPOINT TEMP_BACKUP_PATH "/Checkpoint"
/** 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 (CyberFoil, Checkpoint, DBI dbi.config, prod.keys) before clean install
int backup_user_data(void); int backup_user_data(void);
// Restore user data after clean install // Restore user data after clean install
@@ -40,9 +19,5 @@ 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 // Backup paths from varargs lists to backup_base (pass path arrays, terminate with NULL)
int backup_horizon_oc_config_for_oc_update(void); int backup_deletion_lists(const char *backup_base, ...);
// 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);
+181 -19
View File
@@ -1,23 +1,86 @@
/* /*
* OmniNX Installer - Deletion Lists for Clean Install Mode * OmniNX Installer - Deletion Lists for Clean Install Mode
* Deletion policy aligned with NiklasCFW pack clean install (TegraExplorer script): * Selective deletion when no OmniNX install was found.
* full sd:/atmosphere, bootloader/config subsets, sd:/switch, root + misc, then * Only listed paths are removed; does not wipe whole card or user configs.
* post-restore paths (e.g. tinfoil db). CyberFoil/Checkpoint/DBI/prod.keys backup is in backup.c.
*/ */
#pragma once #pragma once
// Entire atmosphere/ tree (NiklasCFW: deldir("sd:/atmosphere")) // Atmosphere subdirectories to delete
static const char* clean_atmosphere_dirs_to_delete[] = { static const char* clean_atmosphere_dirs_to_delete[] = {
"sd:/atmosphere", "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 NULL
}; };
// Atmosphere contents directories (title IDs; only under atmosphere/contents/)
static const char* clean_atmosphere_contents_dirs_to_delete[] = { static const char* clean_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 NULL
}; };
// Atmosphere files to delete
static const char* clean_atmosphere_files_to_delete[] = { static const char* clean_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 NULL
}; };
@@ -38,6 +101,7 @@ static const char* clean_bootloader_files_to_delete[] = {
"sd:/bootloader/ArgonNX.bin", "sd:/bootloader/ArgonNX.bin",
"sd:/bootloader/bootlogo.bmp", "sd:/bootloader/bootlogo.bmp",
"sd:/bootloader/hekate_ipl.ini", "sd:/bootloader/hekate_ipl.ini",
"sd:/bootloader/nyx.ini",
"sd:/bootloader/patches.ini", "sd:/bootloader/patches.ini",
"sd:/bootloader/update.bin", "sd:/bootloader/update.bin",
"sd:/bootloader/ini/EmuMMC ohne Mods.ini", "sd:/bootloader/ini/EmuMMC ohne Mods.ini",
@@ -50,6 +114,7 @@ static const char* clean_config_dirs_to_delete[] = {
"sd:/config/blue_pack_updater", "sd:/config/blue_pack_updater",
"sd:/config/kefir-updater", "sd:/config/kefir-updater",
"sd:/config/nx-hbmenu", "sd:/config/nx-hbmenu",
"sd:/config/quickntp",
"sd:/config/sys-con", "sd:/config/sys-con",
"sd:/config/sys-patch", "sd:/config/sys-patch",
"sd:/config/uberhand", "sd:/config/uberhand",
@@ -57,13 +122,118 @@ static const char* clean_config_dirs_to_delete[] = {
NULL NULL
}; };
// Entire switch/ tree (clean install recreates sd:/switch after wipe) // Switch directories to delete
static const char* clean_switch_dirs_to_delete[] = { static const char* clean_switch_dirs_to_delete[] = {
"sd:/switch", "sd:/switch/.overlays",
"sd:/switch/.packages",
"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 NULL
}; };
// Switch files (NRO) to delete
static const char* clean_switch_files_to_delete[] = { static const char* clean_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 NULL
}; };
@@ -91,10 +261,8 @@ static const char* clean_misc_dirs_to_delete[] = {
"sd:/NSPs (Tools)", "sd:/NSPs (Tools)",
"sd:/Patched Apps", "sd:/Patched Apps",
"sd:/SaltySD/flags", "sd:/SaltySD/flags",
"sd:/SaltySD/patches",
"sd:/scripts", "sd:/scripts",
"sd:/TegraExplorer", "sd:/switch/tinfoil/db",
"sd:/themes/systemData",
"sd:/tools", "sd:/tools",
"sd:/warmboot_mariko", "sd:/warmboot_mariko",
NULL NULL
@@ -113,21 +281,15 @@ static const char* clean_misc_files_to_delete[] = {
NULL NULL
}; };
// After DBI/prod.keys restore (NiklasCFW: deldir("sd:/switch/tinfoil/db"))
static const char* clean_post_restore_dirs_to_delete[] = {
"sd:/switch/tinfoil/db",
NULL
};
// 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",
"sd:/1.6.2",
NULL NULL
}; };
+5 -21
View File
@@ -10,16 +10,14 @@ static const char* atmosphere_dirs_to_delete[] = {
"sd:/atmosphere/config", "sd:/atmosphere/config",
"sd:/atmosphere/crash_reports", "sd:/atmosphere/crash_reports",
"sd:/atmosphere/erpt_reports", "sd:/atmosphere/erpt_reports",
"sd:/atmosphere/exefs_patches/audio_mastervolume",
"sd:/atmosphere/exefs_patches/CrunchPatch", "sd:/atmosphere/exefs_patches/CrunchPatch",
"sd:/atmosphere/exefs_patches/SaltyNX_Fixes",
"sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0", "sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0",
"sd:/atmosphere/exefs_patches/bluetooth_patches", "sd:/atmosphere/exefs_patches/bluetooth_patches",
"sd:/atmosphere/exefs_patches/bootlogo", "sd:/atmosphere/exefs_patches/bootlogo",
"sd:/atmosphere/exefs_patches/btm_patches", "sd:/atmosphere/exefs_patches/btm_patches",
"sd:/atmosphere/exefs_patches/es_patches", "sd:/atmosphere/exefs_patches/es_patches",
"sd:/atmosphere/exefs_patches/hid_patches", "sd:/atmosphere/exefs_patches/hid_patches",
"sd:/atmosphere/exefs_patches/logo", "sd:/atmosphere/exefs_patches/logo1",
"sd:/atmosphere/exefs_patches/nfim_ctest", "sd:/atmosphere/exefs_patches/nfim_ctest",
"sd:/atmosphere/exefs_patches/nim_ctest", "sd:/atmosphere/exefs_patches/nim_ctest",
"sd:/atmosphere/exefs_patches/nvnflinger_cmu", "sd:/atmosphere/exefs_patches/nvnflinger_cmu",
@@ -82,8 +80,6 @@ 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
}; };
@@ -126,27 +122,25 @@ static const char* config_dirs_to_delete[] = {
}; };
// Switch directories to delete // Switch directories to delete
// NOTE: .packages is intentionally excluded - UltraHand package cache, preserve during updates
static const char* switch_dirs_to_delete[] = { static const char* switch_dirs_to_delete[] = {
"sd:/switch/.packages",
"sd:/switch/.overlays", "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",
"sd:/switch/appstore", "sd:/switch/appstore",
"sd:/switch/AtmoXL-Titel-Installer", "sd:/switch/AtmoXL-Titel-Installer",
"sd:/switch/Benchmark-Toolbox",
"sd:/switch/breeze", "sd:/switch/breeze",
"sd:/switch/checkpoint",
"sd:/switch/cheats-updater", "sd:/switch/cheats-updater",
"sd:/switch/chiaki", "sd:/switch/chiaki",
"sd:/switch/ChoiDujourNX", "sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams", "sd:/switch/crash_ams",
"sd:/switch/Daybreak", "sd:/switch/Daybreak",
"sd:/switch/DNS-Block_Tester",
"sd:/switch/DNS_mitm Tester", "sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon", "sd:/switch/EdiZon",
"sd:/switch/Fizeau", "sd:/switch/Fizeau",
"sd:/switch/FTPD", "sd:/switch/FTPD",
"sd:/switch/Furmark-NX",
"sd:/switch/fw-downloader", "sd:/switch/fw-downloader",
"sd:/switch/gamecard_installer", "sd:/switch/gamecard_installer",
"sd:/switch/Goldleaf", "sd:/switch/Goldleaf",
@@ -171,7 +165,6 @@ static const char* switch_dirs_to_delete[] = {
"sd:/switch/reboot_to_argonNX", "sd:/switch/reboot_to_argonNX",
"sd:/switch/reboot_to_hekate", "sd:/switch/reboot_to_hekate",
"sd:/switch/Shutdown_System", "sd:/switch/Shutdown_System",
"sd:/switch/SimpleModAlchemist",
"sd:/switch/SimpleModDownloader", "sd:/switch/SimpleModDownloader",
"sd:/switch/SimpleModManager", "sd:/switch/SimpleModManager",
"sd:/switch/sphaira", "sd:/switch/sphaira",
@@ -179,22 +172,19 @@ 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/swr-ini-tool", "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",
"sd:/switch/themezer-nx", "sd:/switch/themezer-nx",
"sd:/switch/ThemezerNX",
"sd:/switch/themezernx", "sd:/switch/themezernx",
"sd:/switch/tinwoo", "sd:/switch/tinwoo",
"sd:/switch/Ultrahand-Reload",
NULL NULL
}; };
// Switch files (NRO) to delete // Switch files (NRO) to delete
static const char* switch_files_to_delete[] = { static const char* switch_files_to_delete[] = {
"sd:/switch/90DNS_tester/90DNS_tester.nro", "sd:/switch/90DNS_tester/90DNS_tester.nro",
"sd:/switch/Benchmark-Toolbox/Benchmark-Toolbox.nro",
"sd:/switch/breeze.nro", "sd:/switch/breeze.nro",
"sd:/switch/cheats-updater.nro", "sd:/switch/cheats-updater.nro",
"sd:/switch/chiaki.nro", "sd:/switch/chiaki.nro",
@@ -214,11 +204,9 @@ static const char* switch_files_to_delete[] = {
"sd:/switch/DBI_RU/DBI_RU.nro", "sd:/switch/DBI_RU/DBI_RU.nro",
"sd:/switch/DBI/DBI_EN.nro", "sd:/switch/DBI/DBI_EN.nro",
"sd:/switch/DBI_DE/DBI_DE.nro", "sd:/switch/DBI_DE/DBI_DE.nro",
"sd:/switch/DNS-Block_Tester/dns_tester.nro",
"sd:/switch/DNS_mitm Tester.nro", "sd:/switch/DNS_mitm Tester.nro",
"sd:/switch/EdiZon.nro", "sd:/switch/EdiZon.nro",
"sd:/switch/Fizeau.nro", "sd:/switch/Fizeau.nro",
"sd:/switch/Furmark-NX/Furmark-NX.nro",
"sd:/switch/Goldleaf.nro", "sd:/switch/Goldleaf.nro",
"sd:/switch/haze.nro", "sd:/switch/haze.nro",
"sd:/switch/JKSV.nro", "sd:/switch/JKSV.nro",
@@ -232,22 +220,19 @@ static const char* switch_files_to_delete[] = {
"sd:/switch/nxdumptool.nro", "sd:/switch/nxdumptool.nro",
"sd:/switch/nxtc.bin", "sd:/switch/nxtc.bin",
"sd:/switch/reboot_to_payload.nro", "sd:/switch/reboot_to_payload.nro",
"sd:/switch/SimpleModAlchemist/Simple_Mod_Alchemist.nro",
"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",
"sd:/switch/ThemezerNX/themezer-nx.nro",
"sd:/switch/tinfoil.nro", "sd:/switch/tinfoil.nro",
"sd:/switch/tinfoil/tinfoil.nro", "sd:/switch/tinfoil/tinfoil.nro",
"sd:/switch/tinwoo.nro", "sd:/switch/tinwoo.nro",
"sd:/switch/tinwoo/tinwoo.nro", "sd:/switch/tinwoo/tinwoo.nro",
"sd:/switch/Ultrahand-Reload/Ultrahand-Reload.nro",
NULL NULL
}; };
@@ -277,7 +262,6 @@ static const char* misc_dirs_to_delete[] = {
"sd:/SaltySD/flags", "sd:/SaltySD/flags",
"sd:/scripts", "sd:/scripts",
"sd:/switch/tinfoil/db", "sd:/switch/tinfoil/db",
"sd:/themes/systemData",
"sd:/tools", "sd:/tools",
"sd:/warmboot_mariko", "sd:/warmboot_mariko",
NULL NULL
-46
View File
@@ -1,46 +0,0 @@
/*
* 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
View File
@@ -1,6 +0,0 @@
#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);
+56 -981
View File
File diff suppressed because it is too large. Load diff
+3 -8
View File
@@ -13,13 +13,11 @@ 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) // Main installation function
void installer_launch_hekate_payload(void);
// Main installation function (optional debug: see DEBUG_INI.md in repo root)
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode); int perform_installation(omninx_variant_t pack_variant, install_mode_t mode);
// Update mode operations (install_update.c) // Update mode operations (install_update.c)
int backup_before_update(const char *version);
int update_mode_cleanup(omninx_variant_t variant); int update_mode_cleanup(omninx_variant_t variant);
int update_mode_install(omninx_variant_t variant); int update_mode_install(omninx_variant_t variant);
int cleanup_staging_directory(omninx_variant_t pack_variant); int cleanup_staging_directory(omninx_variant_t pack_variant);
@@ -27,21 +25,18 @@ int cleanup_staging_directory(omninx_variant_t pack_variant);
int cleanup_other_staging_directories(omninx_variant_t installed_variant); int cleanup_other_staging_directories(omninx_variant_t installed_variant);
// Clean install operations (install_clean.c) // Clean install operations (install_clean.c)
int backup_before_clean(void);
int clean_mode_backup(void); int clean_mode_backup(void);
int clean_mode_wipe(void); int clean_mode_wipe(void);
int clean_mode_restore(void); int clean_mode_restore(void);
int clean_mode_install(omninx_variant_t variant); int clean_mode_install(omninx_variant_t variant);
void install_preserve_update_cleanup_begin(void);
void install_preserve_update_cleanup_end(void);
// Shared helpers (install.c) - used by install_update.c and install_clean.c // Shared helpers (install.c) - used by install_update.c and install_clean.c
void install_set_color(u32 color); void install_set_color(u32 color);
void install_check_and_clear_screen_if_needed(void); 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);
+23 -3
View File
@@ -11,6 +11,7 @@
#include "gfx.h" #include "gfx.h"
#include "version.h" #include "version.h"
#include <libs/fatfs/ff.h> #include <libs/fatfs/ff.h>
#include <utils/sprintf.h>
#include <string.h> #include <string.h>
#undef COLOR_CYAN #undef COLOR_CYAN
@@ -31,10 +32,30 @@
#define path_exists install_path_exists #define path_exists install_path_exists
#define count_directory_items install_count_directory_items #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 // Clean mode: Backup user data
int clean_mode_backup(void) { int clean_mode_backup(void) {
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf(" Sichere: CyberFoil, Checkpoint, DBI (dbi.config), prod.keys\n"); gfx_printf(" Sichere: DBI, Tinfoil, prod.keys\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
int res = backup_user_data(); int res = backup_user_data();
if (res == FR_OK) { if (res == FR_OK) {
@@ -95,14 +116,13 @@ int clean_mode_wipe(void) {
// Clean mode: Restore user data // Clean mode: Restore user data
int clean_mode_restore(void) { int clean_mode_restore(void) {
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf(" Stelle wieder her: CyberFoil, Checkpoint, DBI (dbi.config), prod.keys\n"); gfx_printf(" Stelle wieder her: DBI, Tinfoil, prod.keys\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
int res = restore_user_data(); int res = restore_user_data();
if (res == FR_OK) { if (res == FR_OK) {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf(" [OK] Wiederherstellung abgeschlossen\n"); gfx_printf(" [OK] Wiederherstellung abgeschlossen\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
delete_path_list(clean_post_restore_dirs_to_delete, "switch/ (nach Restore)");
} }
cleanup_backup(); cleanup_backup();
return res; return res;
+48 -15
View File
@@ -4,6 +4,7 @@
*/ */
#include "install.h" #include "install.h"
#include "backup.h"
#include "deletion_lists_update.h" #include "deletion_lists_update.h"
#include "fs.h" #include "fs.h"
#include "version.h" #include "version.h"
@@ -12,6 +13,10 @@
#include <string.h> #include <string.h>
#include <utils/sprintf.h> #include <utils/sprintf.h>
#ifndef VERSION
#define VERSION "1.0.0"
#endif
#undef COLOR_CYAN #undef COLOR_CYAN
#undef COLOR_WHITE #undef COLOR_WHITE
#undef COLOR_GREEN #undef COLOR_GREEN
@@ -30,11 +35,28 @@
#define path_exists install_path_exists #define path_exists install_path_exists
#define count_directory_items install_count_directory_items #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 // Update mode: Cleanup specific directories/files
int update_mode_cleanup(omninx_variant_t variant) { int update_mode_cleanup(omninx_variant_t variant) {
(void)variant; (void)variant;
install_preserve_update_cleanup_begin();
check_and_clear_screen_if_needed(); check_and_clear_screen_if_needed();
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
@@ -64,8 +86,6 @@ int update_mode_cleanup(omninx_variant_t variant) {
misc_files_to_delete, misc_files_to_delete,
NULL); NULL);
install_preserve_update_cleanup_end();
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf(" Bereinigung abgeschlossen!\n"); gfx_printf(" Bereinigung abgeschlossen!\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
@@ -78,6 +98,7 @@ 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;
@@ -105,10 +126,6 @@ int update_mode_install(omninx_variant_t variant) {
res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/"); res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
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/themes", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "themes/");
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);
res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/"); res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/");
if (res != FR_OK && res != FR_NO_FILE) return res; if (res != FR_OK && res != FR_NO_FILE) return res;
@@ -119,17 +136,33 @@ 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/extras", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "extras/");
if (res != FR_OK && res != FR_NO_FILE) return res;
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf(" Kopiere Root-Dateien...\n"); gfx_printf(" Kopiere Root-Dateien...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
res = install_copy_staging_root_files(staging, "sd:/"); s_printf(src_path, "%s/boot.dat", staging);
if (res != FR_OK) s_printf(dst_path, "sd:/boot.dat");
return res; if (path_exists(src_path)) file_copy(src_path, dst_path);
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");
+3 -3
View File
@@ -243,9 +243,9 @@
#define FF_FS_NORTC 1 #define FF_FS_NORTC 1
#define FF_NORTC_MON 10 #define FF_NORTC_MON 1
#define FF_NORTC_MDAY 16 #define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2026 #define FF_NORTC_YEAR 2020
/* The option FF_FS_NORTC switches timestamp function. If the system does not have /* The option FF_FS_NORTC switches timestamp function. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable / any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp / the timestamp function. Every object modified by FatFs will have a fixed timestamp
+35 -182
View File
@@ -6,6 +6,10 @@
* Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue * Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue
*/ */
#ifndef VERSION
#define VERSION "1.0.0"
#endif
#include <string.h> #include <string.h>
#include <display/di.h> #include <display/di.h>
@@ -31,7 +35,6 @@
#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"
@@ -112,12 +115,6 @@ 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);
@@ -141,7 +138,7 @@ static int launch_payload(const char *path) {
if (sd_mount()) { if (sd_mount()) {
FIL fp; FIL fp;
if (f_open(&fp, path, FA_READ)) { if (f_open(&fp, path, FA_READ)) {
gfx_printf("Hekate nicht gefunden: %s\n", path); gfx_printf("Payload nicht gefunden: %s\n", path);
return 1; return 1;
} }
@@ -185,12 +182,6 @@ 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) {
@@ -252,13 +243,11 @@ 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);
} }
@@ -275,17 +264,9 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button; Capture = screenshot // Check joycon A button
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) { if (jc && jc->a) {
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;
} }
@@ -297,7 +278,7 @@ void ipl_main(void) {
if (file_exists(PAYLOAD_PATH)) { if (file_exists(PAYLOAD_PATH)) {
gfx_printf("\n"); gfx_printf("\n");
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("Hekate wird gestartet...\n"); gfx_printf("Payload wird gestartet...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
msleep(500); msleep(500);
launch_payload(PAYLOAD_PATH); launch_payload(PAYLOAD_PATH);
@@ -343,14 +324,12 @@ 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 && !menu_aborted) { while (!confirmed) {
// 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);
@@ -366,14 +345,6 @@ 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) {
@@ -399,20 +370,6 @@ 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();
@@ -438,19 +395,14 @@ 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 Vol+ und Vol- (Konsole) zum Abbrechen.\n"); gfx_printf("Oder druecke + und - 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) { if (jc && jc->plus && jc->minus) {
if (jc->cap)
take_screenshot();
}
if (cancel_combo_pressed(pbtn)) {
user_cancelled = true; user_cancelled = true;
break; break;
} }
@@ -476,90 +428,7 @@ void ipl_main(void) {
gfx_con_setpos(0, 0); gfx_con_setpos(0, 0);
print_header(); print_header();
// UHS class check: recommend U2 / V30 / A2 for emuMMC (clean install only)
if (mode == INSTALL_MODE_CLEAN) {
#define UHS_U2_MIN 2
#define UHS_V30_MIN 30
#define UHS_A2_MIN 2
bool uhs_ok = (sd_storage.ssr.uhs_grade >= UHS_U2_MIN &&
sd_storage.ssr.video_class >= UHS_V30_MIN &&
sd_storage.ssr.app_class >= UHS_A2_MIN);
if (!uhs_ok) {
jc_init_hw();
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
set_color(COLOR_RED);
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
gfx_printf("!! WARNUNG - SD-KARTEN-KLASSE !!\n");
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
set_color(COLOR_WHITE);
gfx_printf("Diese SD-Karte erfuellt NICHT die empfohlenen\n");
gfx_printf("Mindestanforderungen (U2 | V30 | A2).\n\n");
set_color(COLOR_YELLOW);
gfx_printf("Aktuelle Werte: UHS%d | V%d | A%d\n\n",
(u32)sd_storage.ssr.uhs_grade,
(u32)sd_storage.ssr.video_class,
(u32)sd_storage.ssr.app_class);
set_color(COLOR_WHITE);
gfx_printf("Moegliche Folgen bei schwachen Karten:\n");
gfx_printf(" - Langsamere emuMMC / Spiel-Ladezeiten\n");
gfx_printf(" - Hoheres Risiko von Korruption oder Abstuerzen\n");
gfx_printf(" - Instabilitaet beim Schreiben groesserer Daten\n\n");
set_color(COLOR_CYAN);
gfx_printf("Empfohlen: U2 | V30 | A2 oder besser.\n");
gfx_printf("Empfohlene Karten: Samsung EVO Plus, EVO Select und PRO Plus.\n\n");
set_color(COLOR_GREEN);
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);
while (btn_read() & BTN_POWER) { msleep(50); }
bool acknowledged = false;
bool uhs_aborted = false;
while (!acknowledged && !uhs_aborted) {
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) acknowledged = true;
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) acknowledged = true;
}
if (cancel_combo_pressed(btn_state)) {
uhs_aborted = true;
break;
}
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
while (btn_read() & BTN_POWER) { msleep(50); }
bool released = false;
while (!released) {
jc_gamepad_rpt_t *jc = joycon_poll();
released = (!jc || !jc->a) && !(btn_read() & BTN_POWER);
if (!released) msleep(50);
}
msleep(300); // Short delay so the next screen is visible before accepting input
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
}
}
// Show information // Show information
set_color(COLOR_YELLOW);
gfx_printf("Hinweis: Verbindungen zu Nintendo-Servern sind\n");
gfx_printf("in OmniNX standardmaessig unterdrueckt.\n\n");
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation"); gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");
gfx_printf("Paket-Variante: %s\n", get_variant_name(pack_variant)); gfx_printf("Paket-Variante: %s\n", get_variant_name(pack_variant));
@@ -568,17 +437,6 @@ void ipl_main(void) {
} else { } else {
gfx_printf("Aktuelle Installation: Keine\n"); gfx_printf("Aktuelle Installation: Keine\n");
} }
char new_version[32];
new_version[0] = '\0';
const char *pack_manifest = get_manifest_path(pack_variant);
if (pack_manifest)
read_manifest_version(pack_manifest, new_version, sizeof(new_version));
const char *cur_ver = !current.is_installed ? "Keine"
: (current.version_file[0] ? current.version_file : "Unbekannt");
const char *new_ver = new_version[0] ? new_version : "Unbekannt";
gfx_printf("Version: %s -> %s\n", cur_ver, new_ver);
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
gfx_printf("\n"); gfx_printf("\n");
@@ -595,7 +453,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("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n"); gfx_printf("Druecke + und - gleichzeitig zum Abbrechen (zurueck zu 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
@@ -615,20 +473,19 @@ void ipl_main(void) {
break; break;
} }
// Check joycon buttons; Capture = screenshot // Check joycon buttons
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 (cancel_combo_pressed(btn_state)) { if (jc->plus && jc->minus) {
cancelled = true; cancelled = true;
break; break;
} }
}
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
} }
@@ -663,28 +520,32 @@ void ipl_main(void) {
int result = perform_installation(pack_variant, mode); int result = perform_installation(pack_variant, mode);
// 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(1500); 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);
gfx_con_setpos(0, 0); gfx_con_setpos(0, 0);
// Summary // Summary
const char *op_name = (mode == INSTALL_MODE_UPDATE) ? " Update" : " Installation"; set_color(COLOR_CYAN);
gfx_printf("========================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("========================================\n\n");
set_color(COLOR_WHITE);
if (result == FR_OK && total_errors == 0) { if (result == FR_OK && total_errors == 0) {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("=======================================\n"); gfx_printf("========================================\n");
gfx_printf(" %s abgeschlossen!\n", op_name); gfx_printf(" Installation abgeschlossen!\n");
gfx_printf("=======================================\n"); gfx_printf("========================================\n");
} else { } else {
set_color(COLOR_RED); set_color(COLOR_RED);
gfx_printf("=======================================\n"); gfx_printf("========================================\n");
gfx_printf(" %s beendet\n", op_name); gfx_printf(" Installation beendet\n");
if (total_errors > 0) { if (total_errors > 0) {
gfx_printf(" %d Fehler\n", total_errors); gfx_printf(" %d Fehler\n", total_errors);
} }
gfx_printf("=======================================\n"); gfx_printf("========================================\n");
} }
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
@@ -712,32 +573,28 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button; Capture = screenshot // Check joycon A button
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) { if (jc && jc->a) {
if (jc->cap)
take_screenshot();
if (jc->a) {
button_pressed = true; button_pressed = true;
break; break;
} }
}
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
} }
gfx_printf("\n"); gfx_printf("\n");
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("Hekate wird gestartet...\n"); gfx_printf("Payload wird gestartet...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
msleep(500); // Brief delay before launch msleep(500); // Brief delay before launch
if (file_exists(PAYLOAD_PATH)) { if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH); launch_payload(PAYLOAD_PATH);
} else { } else {
// Hekate (update.bin) not found, show error // Payload not found, show error
set_color(COLOR_RED); set_color(COLOR_RED);
gfx_printf("\nFEHLER: Hekate nicht gefunden!\n"); gfx_printf("\nFEHLER: Payload nicht gefunden!\n");
gfx_printf("Pfad: %s\n", PAYLOAD_PATH); gfx_printf("Pfad: %s\n", PAYLOAD_PATH);
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
gfx_printf("\nDruecke A oder Power zum Neustart...\n"); gfx_printf("\nDruecke A oder Power zum Neustart...\n");
@@ -758,14 +615,10 @@ void ipl_main(void) {
} }
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) { if (jc && jc->a) {
if (jc->cap)
take_screenshot();
if (jc->a) {
button_pressed = true; button_pressed = true;
break; break;
} }
}
msleep(50); msleep(50);
} }
-5
View File
@@ -184,11 +184,6 @@ 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
View File
@@ -1,111 +0,0 @@
/*
* 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
View File
@@ -1,13 +0,0 @@
/*
* 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);
+1 -5
View File
@@ -78,12 +78,8 @@ static omninx_variant_t read_manifest_variant(const char *manifest_path) {
} }
// Read manifest.ini and extract version string // Read manifest.ini and extract version string
void read_manifest_version(const char *manifest_path, char *version_buf, size_t buf_size) { static void read_manifest_version(const char *manifest_path, char *version_buf, size_t buf_size) {
if (!version_buf || buf_size == 0)
return;
version_buf[0] = '\0'; version_buf[0] = '\0';
if (!manifest_path)
return;
link_t sections; link_t sections;
list_init(&sections); list_init(&sections);
-4
View File
@@ -4,7 +4,6 @@
#pragma once #pragma once
#include <utils/types.h> #include <utils/types.h>
#include <stddef.h>
// OmniNX variants // OmniNX variants
typedef enum { typedef enum {
@@ -38,6 +37,3 @@ const char* get_staging_path(omninx_variant_t variant);
// Get manifest.ini path for variant (in staging directory) // Get manifest.ini path for variant (in staging directory)
const char* get_manifest_path(omninx_variant_t variant); const char* get_manifest_path(omninx_variant_t variant);
// Read version string from a manifest.ini (empty string on failure)
void read_manifest_version(const char *manifest_path, char *version_buf, size_t buf_size);
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env python3
"""Generate C# deletion list arrays from payload header files."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "source"
OUT = ROOT / "desktop" / "OmniNX.DesktopInstaller" / "Data" / "DeletionLists.Generated.cs"
HEADERS = {
"Clean": SOURCE / "deletion_lists_clean.h",
"Update": SOURCE / "deletion_lists_update.h",
}
def parse_arrays(text: str) -> dict[str, list[str]]:
arrays: dict[str, list[str]] = {}
pattern = re.compile(
r"static const char\*\s+(\w+)\[\]\s*=\s*\{([^}]*)\};",
re.MULTILINE | re.DOTALL,
)
for match in pattern.finditer(text):
name = match.group(1)
body = match.group(2)
paths = []
for line in body.splitlines():
line = line.strip()
if not line or line.startswith("//"):
continue
m = re.match(r'"([^"]+)"', line)
if m:
paths.append(m.group(1))
arrays[name] = paths
return arrays
def emit_cs(mode: str, arrays: dict[str, list[str]]) -> str:
lines = [f" public static class {mode}"]
lines.append(" {")
for name, paths in arrays.items():
lines.append(f" public static readonly string[] {name} =")
lines.append(" {")
for p in paths:
rel = p.replace("sd:/", "").replace("sd:\\", "").replace("/", "\\")
lines.append(f' @"{rel}",')
lines.append(" };")
lines.append("")
lines.append(" }")
return "\n".join(lines)
def main() -> None:
sections = []
for mode, header in HEADERS.items():
text = header.read_text(encoding="utf-8")
arrays = parse_arrays(text)
sections.append(emit_cs(mode, arrays))
OUT.parent.mkdir(parents=True, exist_ok=True)
content = """// <auto-generated />
// Generated by tools/generate_deletion_lists.py — do not edit manually.
namespace OmniNX.DesktopInstaller.Data;
public static partial class DeletionLists
{
"""
content += "\n\n".join(sections)
content += "\n}\n"
OUT.write_text(content, encoding="utf-8")
print(f"Wrote {OUT}")
if __name__ == "__main__":
main()
-70
View File
@@ -1,70 +0,0 @@
/*
* 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);
}