Compare commits
51
Commits
dev
...
428760fef6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428760fef6 | ||
|
|
a0c4250a4d | ||
|
|
2c6d11b785 | ||
|
|
414d2f2c24 | ||
|
|
eef6bbaa33 | ||
|
|
c5b74c52d3 | ||
|
|
d573f6cca4 | ||
|
|
c87fe1f1e8 | ||
|
|
73c2789114 | ||
|
|
405bed7291 | ||
|
|
3a2fb4fb2e | ||
|
|
eb18bf8c72 | ||
|
|
05926f7828 | ||
|
|
7b85d428a8 | ||
|
|
dce83bc3be | ||
|
|
a26531c702 | ||
|
|
c0741beda3 | ||
|
|
d6ec345cce | ||
|
|
9990dfb2ba | ||
|
|
c7ad761592 | ||
|
|
59f103874c | ||
|
|
dce11538d1 | ||
|
|
21f8e0e38a | ||
|
|
0ad2c63123 | ||
|
|
6780936d2d | ||
|
|
82639a7a86 | ||
|
|
bff8bc1910 | ||
|
|
c9bcb29f50 | ||
|
|
dc97677e72 | ||
|
|
4535c4508f | ||
|
|
726ef77194 | ||
|
|
13c35400a1 | ||
|
|
f71b6ab629 | ||
|
|
9b559eb7e6 | ||
|
|
d3735d17ee | ||
|
|
db7d83304f | ||
|
|
113542f7fb | ||
|
|
72a2083d76 | ||
|
|
3f6bbc0752 | ||
|
|
d1fc24dae8 | ||
|
|
ef104bce41 | ||
|
|
0015c7e8ac | ||
|
|
6dde73f39e | ||
|
|
97e8c3d965 | ||
|
|
586afc2f56 | ||
|
|
f943887b39 | ||
|
|
335ea03b05 | ||
|
|
b0523ada6c | ||
|
|
9a2307a8ee | ||
|
|
e71fb13bfc | ||
|
|
5bcd3987a2 |
No files matched your search
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -20,3 +20,11 @@ Thumbs.db
|
||||
# Version file is tracked, ignore local edits
|
||||
# VERSION
|
||||
|
||||
# Vendored TegraExplorer tree
|
||||
source/TegraExplorer/
|
||||
|
||||
# Desktop installer (.NET)
|
||||
desktop/**/bin/
|
||||
desktop/**/obj/
|
||||
desktop/out/
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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).
|
||||
+21
-32
@@ -2,6 +2,8 @@
|
||||
|
||||
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
|
||||
|
||||
The OmniNX Installer Payload operates in two modes:
|
||||
@@ -88,7 +90,9 @@ The OmniNX Installer Payload operates in two modes:
|
||||
**Trigger**: `INSTALL_MODE_UPDATE` (OmniNX already installed)
|
||||
|
||||
### Step 1: Cleanup (Selective Deletion)
|
||||
**Location**: `install.c:321-380`
|
||||
**Location**: `install_update.c` (`update_mode_cleanup`), `install.c` (`delete_path_lists_grouped`)
|
||||
|
||||
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
|
||||
**Location**: `install.c:325-327`, `deletion_lists.h:9-34`
|
||||
@@ -305,17 +309,12 @@ channel_pack={variant} # Same as current_pack
|
||||
#### 32. Create Backup Directory
|
||||
- Creates `sd:/temp_backup/` directory
|
||||
|
||||
#### 33. Backup DBI
|
||||
- **Source**: `sd:/switch/DBI/`
|
||||
- **Destination**: `sd:/temp_backup/DBI/`
|
||||
- Only if source exists
|
||||
#### 33. Backup DBI settings
|
||||
- **Source**: `sd:/switch/DBI/dbi.config` (file only, not the whole `DBI` tree)
|
||||
- **Destination**: `sd:/temp_backup/dbi.config`
|
||||
- Only if the source file exists
|
||||
|
||||
#### 34. Backup Tinfoil
|
||||
- **Source**: `sd:/switch/tinfoil/`
|
||||
- **Destination**: `sd:/temp_backup/tinfoil/`
|
||||
- Only if source exists
|
||||
|
||||
#### 35. Backup prod.keys
|
||||
#### 34. Backup prod.keys
|
||||
- **Source**: `sd:/switch/prod.keys`
|
||||
- **Destination**: `sd:/temp_backup/prod.keys`
|
||||
- Only if source exists
|
||||
@@ -325,22 +324,22 @@ channel_pack={variant} # Same as current_pack
|
||||
### Step 2: Wipe Directories
|
||||
**Location**: `install.c:543-602`
|
||||
|
||||
#### 36. Delete Entire Directories
|
||||
#### 35. Delete Entire Directories
|
||||
Recursively deletes (if they exist):
|
||||
- `sd:/atmosphere/` (entire directory tree)
|
||||
- `sd:/bootloader/` (entire directory tree)
|
||||
- `sd:/config/` (entire directory tree)
|
||||
- `sd:/switch/` (entire directory tree)
|
||||
|
||||
#### 37. Delete Root Files
|
||||
#### 36. Delete Root Files
|
||||
- Uses same deletion list as update mode (Step 19)
|
||||
- Deletes `boot.dat`, `boot.ini`, `exosphere.ini`, etc.
|
||||
|
||||
#### 38. Delete Miscellaneous Items
|
||||
#### 37. Delete Miscellaneous Items
|
||||
- Uses same deletion lists as update mode (Steps 20-21)
|
||||
- Deletes `argon/`, `games/`, `SaltySD/`, etc.
|
||||
|
||||
#### 39. Recreate Switch Directory
|
||||
#### 38. Recreate Switch Directory
|
||||
- Creates empty `sd:/switch/` directory for restoration
|
||||
|
||||
---
|
||||
@@ -348,27 +347,17 @@ Recursively deletes (if they exist):
|
||||
### Step 3: Restore User Data
|
||||
**Location**: `backup.c:55-95`
|
||||
|
||||
#### 40. Restore DBI
|
||||
- **Source**: `sd:/temp_backup/DBI/`
|
||||
- **Destination**: `sd:/switch/DBI/`
|
||||
- **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`
|
||||
#### 39. Restore DBI settings
|
||||
- **Source**: `sd:/temp_backup/dbi.config`
|
||||
- **Destination**: `sd:/switch/DBI/dbi.config` (creates `sd:/switch/DBI/` if needed)
|
||||
- No longer copies the full `DBI` folder; old `.nro` cleanup is unnecessary
|
||||
|
||||
#### 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
|
||||
#### 40. Restore prod.keys
|
||||
- **Source**: `sd:/temp_backup/prod.keys`
|
||||
- **Destination**: `sd:/switch/prod.keys`
|
||||
|
||||
#### 43. Cleanup Backup Directory
|
||||
**Location**: `backup.c:98-103`
|
||||
#### 41. Cleanup Backup Directory
|
||||
**Location**: `backup.c` (`cleanup_backup`)
|
||||
- Deletes `sd:/temp_backup/` directory after restoration
|
||||
|
||||
---
|
||||
|
||||
@@ -1,90 +1,112 @@
|
||||
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
|
||||
|
||||
ifeq ($(strip $(DEVKITARM)),)
|
||||
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
|
||||
endif
|
||||
|
||||
include $(DEVKITARM)/base_rules
|
||||
|
||||
################################################################################
|
||||
|
||||
IPL_LOAD_ADDR := 0x40008000
|
||||
VERSION := $(shell cat VERSION)
|
||||
|
||||
################################################################################
|
||||
|
||||
TARGET := omninx-installer
|
||||
OUTPUT_NAME := OmniNX-Installer.bin
|
||||
BUILDDIR := build
|
||||
OUTPUTDIR := output
|
||||
SOURCEDIR := source
|
||||
BDKDIR := bdk
|
||||
BDKINC := -I./$(BDKDIR)
|
||||
VPATH = $(dir ./$(SOURCEDIR)/) $(dir $(wildcard ./$(SOURCEDIR)/*/)) $(dir $(wildcard ./$(SOURCEDIR)/*/*/))
|
||||
VPATH += $(dir $(wildcard ./$(BDKDIR)/)) $(dir $(wildcard ./$(BDKDIR)/*/)) $(dir $(wildcard ./$(BDKDIR)/*/*/))
|
||||
|
||||
# All source files
|
||||
OBJS = $(patsubst $(SOURCEDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(patsubst $(SOURCEDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(call rwildcard, $(SOURCEDIR), *.S *.c)))
|
||||
OBJS += $(patsubst $(BDKDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(patsubst $(BDKDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(call rwildcard, $(BDKDIR), *.S *.c)))
|
||||
|
||||
GFX_INC := '"../$(SOURCEDIR)/gfx.h"'
|
||||
FFCFG_INC := '"../$(SOURCEDIR)/libs/fatfs/ffconf.h"'
|
||||
|
||||
################################################################################
|
||||
|
||||
CUSTOMDEFINES := -DIPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
|
||||
CUSTOMDEFINES += -DGFX_INC=$(GFX_INC) -DFFCFG_INC=$(FFCFG_INC)
|
||||
CUSTOMDEFINES += -DVERSION='"$(VERSION)"'
|
||||
|
||||
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb -mthumb-interwork
|
||||
CFLAGS = $(ARCH) -Os -nostdlib -ffunction-sections -fdata-sections -fomit-frame-pointer -fno-inline -std=gnu11 -Wall -Wno-missing-braces $(CUSTOMDEFINES)
|
||||
LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defsym=IPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
|
||||
|
||||
################################################################################
|
||||
|
||||
.PHONY: all clean release
|
||||
|
||||
all: $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
$(eval BIN_SIZE = $(shell wc -c < $(OUTPUTDIR)/$(OUTPUT_NAME)))
|
||||
@echo "Payload size is $(BIN_SIZE) bytes"
|
||||
@echo "Max size is 126296 bytes."
|
||||
|
||||
clean:
|
||||
@rm -rf $(BUILDDIR)
|
||||
@rm -rf $(OUTPUTDIR)
|
||||
@rm -rf release
|
||||
@rm -f $(TARGET)-*.zip
|
||||
|
||||
$(OUTPUTDIR)/$(OUTPUT_NAME): $(BUILDDIR)/$(TARGET)/$(TARGET).elf
|
||||
@mkdir -p "$(@D)"
|
||||
$(OBJCOPY) -S -O binary $< $(OUTPUTDIR)/$(TARGET).bin
|
||||
@mv $(OUTPUTDIR)/$(TARGET).bin $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/$(TARGET).elf: $(OBJS)
|
||||
$(CC) $(LDFLAGS) -T $(SOURCEDIR)/link.ld $^ -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(SOURCEDIR)/%.c
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(SOURCEDIR)/%.S
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(BDKDIR)/%.c
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(BDKDIR)/%.S
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
release: $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
@mkdir -p release/bootloader/payloads
|
||||
@cp $(OUTPUTDIR)/$(OUTPUT_NAME) release/bootloader/payloads/$(OUTPUT_NAME)
|
||||
@cd release && zip -r ../$(TARGET)-$(VERSION).zip bootloader
|
||||
@echo "Release package created: $(TARGET)-$(VERSION).zip"
|
||||
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
|
||||
|
||||
ifeq ($(strip $(DEVKITARM)),)
|
||||
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
|
||||
endif
|
||||
|
||||
include $(DEVKITARM)/base_rules
|
||||
|
||||
################################################################################
|
||||
|
||||
IPL_LOAD_ADDR := 0x40008000
|
||||
VERSION := $(shell cat VERSION)
|
||||
|
||||
################################################################################
|
||||
|
||||
TARGET := omninx-installer
|
||||
OUTPUT_NAME := OmniNX-Installer.bin
|
||||
BUILDDIR := build
|
||||
OUTPUTDIR := output
|
||||
SOURCEDIR := source
|
||||
BDKDIR := bdk
|
||||
BDKINC := -I./$(BDKDIR)
|
||||
VPATH = $(dir ./$(SOURCEDIR)/) $(dir $(wildcard ./$(SOURCEDIR)/*/)) $(dir $(wildcard ./$(SOURCEDIR)/*/*/))
|
||||
VPATH += $(dir $(wildcard ./$(BDKDIR)/)) $(dir $(wildcard ./$(BDKDIR)/*/)) $(dir $(wildcard ./$(BDKDIR)/*/*/))
|
||||
|
||||
# All source files
|
||||
OBJS = $(patsubst $(SOURCEDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(patsubst $(SOURCEDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(call rwildcard, $(SOURCEDIR), *.S *.c)))
|
||||
OBJS += $(patsubst $(BDKDIR)/%.S, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(patsubst $(BDKDIR)/%.c, $(BUILDDIR)/$(TARGET)/%.o, \
|
||||
$(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"'
|
||||
FFCFG_INC := '"../$(SOURCEDIR)/libs/fatfs/ffconf.h"'
|
||||
|
||||
################################################################################
|
||||
|
||||
CUSTOMDEFINES := -DIPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
|
||||
CUSTOMDEFINES += -DGFX_INC=$(GFX_INC) -DFFCFG_INC=$(FFCFG_INC)
|
||||
CUSTOMDEFINES += -DVERSION='"$(VERSION)"'
|
||||
|
||||
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb -mthumb-interwork
|
||||
CFLAGS = $(ARCH) -Os -nostdlib -ffunction-sections -fdata-sections -fomit-frame-pointer -fno-inline -std=gnu11 -Wall -Wno-missing-braces $(CUSTOMDEFINES)
|
||||
LDFLAGS = $(ARCH) -nostartfiles -lgcc -Wl,--nmagic,--gc-sections -Xlinker --defsym=IPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
|
||||
|
||||
################################################################################
|
||||
|
||||
RAMTEST_BIN := $(OUTPUTDIR)/RAM-Test.bin
|
||||
OBJS_RAMTEST := $(filter-out $(BUILDDIR)/$(TARGET)/main.o,$(OBJS)) \
|
||||
$(BUILDDIR)/$(TARGET)/ram_test_main.o
|
||||
|
||||
.PHONY: all clean release ram-test
|
||||
|
||||
all: $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
$(eval BIN_SIZE = $(shell wc -c < $(OUTPUTDIR)/$(OUTPUT_NAME)))
|
||||
@echo "Payload size is $(BIN_SIZE) bytes"
|
||||
@echo "Max size is 126296 bytes."
|
||||
|
||||
clean:
|
||||
@rm -rf $(BUILDDIR)
|
||||
@rm -rf $(OUTPUTDIR)
|
||||
@rm -f $(RAMTEST_BIN)
|
||||
@rm -rf release
|
||||
@rm -f $(TARGET)-*.zip
|
||||
|
||||
$(OUTPUTDIR)/$(OUTPUT_NAME): $(BUILDDIR)/$(TARGET)/$(TARGET).elf
|
||||
@mkdir -p "$(@D)"
|
||||
$(OBJCOPY) -S -O binary $< $(OUTPUTDIR)/$(TARGET).bin
|
||||
@mv $(OUTPUTDIR)/$(TARGET).bin $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/$(TARGET).elf: $(OBJS)
|
||||
$(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
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(SOURCEDIR)/%.S
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(BDKDIR)/%.c
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) $(BDKINC) -I$(SOURCEDIR) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/$(TARGET)/%.o: $(BDKDIR)/%.S
|
||||
@mkdir -p "$(@D)"
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
release: $(OUTPUTDIR)/$(OUTPUT_NAME)
|
||||
@mkdir -p release/bootloader/payloads
|
||||
@cp $(OUTPUTDIR)/$(OUTPUT_NAME) release/bootloader/payloads/$(OUTPUT_NAME)
|
||||
@cd release && zip -r ../$(TARGET)-$(VERSION).zip bootloader
|
||||
@echo "Release package created: $(TARGET)-$(VERSION).zip"
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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`).
|
||||
@@ -1,163 +1,165 @@
|
||||
# OmniNX Installer Payload
|
||||
|
||||
> **To properly use this payload, download the latest OmniNX Release:** [https://git.niklascfw.de/OmniNX/OmniNX/releases](https://git.niklascfw.de/OmniNX/OmniNX/releases)
|
||||
|
||||
A minimal payload for installing OmniNX CFW Pack files on Nintendo Switch outside of Horizon OS.
|
||||
|
||||
Based on [TegraExplorer](https://github.com/shchmue/TegraExplorer) and [hekate](https://github.com/CTCaer/hekate) by CTCaer, naehrwert, and shchmue.
|
||||
Based on [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payload) by sthetix.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Variant Detection**: Detects which OmniNX pack variant is present (Standard/Light/OC)
|
||||
- **Smart Installation Modes**:
|
||||
- **Update Mode**: Selective deletion when OmniNX is already installed
|
||||
- **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`)
|
||||
- **Progress Display**: Visual status messages during installation
|
||||
- **Error Handling**: Detailed error reporting on screen
|
||||
- **Payload Chaining**: Automatically launch hekate after installation
|
||||
|
||||
## Documentation
|
||||
|
||||
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 Modes
|
||||
|
||||
### Update Mode (OmniNX Detected)
|
||||
- Detected when version marker files (`1.0.0s`, `1.0.0l`, or `1.0.0oc`) are found
|
||||
- Performs selective deletion of specific directories/files
|
||||
- Preserves user data, savegames, and installed games
|
||||
- Updates only necessary CFW components
|
||||
|
||||
### Clean Install (No OmniNX Detected)
|
||||
- Detected when no version marker files are found
|
||||
- Performs full wipe of `/atmosphere`, `/bootloader`, `/config`, and `/switch`
|
||||
- **Backs up and restores**:
|
||||
- `sd:/switch/DBI` → preserved
|
||||
- `sd:/switch/tinfoil` → preserved
|
||||
- `sd:/switch/prod.keys` → preserved
|
||||
- Fresh installation of all CFW components
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **devkitARM** - ARM toolchain for Nintendo Switch development
|
||||
- **BDK** - Blue Development Kit (included in this repo)
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make
|
||||
```
|
||||
|
||||
The built payload will be output to `output/OmniNX-Installer.bin`.
|
||||
|
||||
### CI / GitHub Actions
|
||||
|
||||
Every push triggers a build. The `.bin` file is produced and attached as a workflow artifact (Actions tab → select run → Artifacts).
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Extract OmniNX Pack to SD Card
|
||||
|
||||
Extract the OmniNX pack zip file directly to your SD card. The pack should contain one of:
|
||||
|
||||
```
|
||||
sd:/OmniNX Standard/
|
||||
├── atmosphere/
|
||||
├── bootloader/
|
||||
├── config/
|
||||
├── switch/
|
||||
├── TegraExplorer/
|
||||
├── warmboot_mariko/
|
||||
├── boot.dat
|
||||
├── boot.ini
|
||||
├── exosphere.ini
|
||||
├── hbmenu.nro
|
||||
├── loader.bin
|
||||
├── payload.bin
|
||||
└── 1.0.0s
|
||||
```
|
||||
|
||||
Or `sd:/OmniNX Light/` or `sd:/OmniNX OC/` for other variants.
|
||||
|
||||
**Important**: Extract both the OmniNX pack zip AND this payload to your SD card.
|
||||
|
||||
### 2. Launch Payload
|
||||
|
||||
Use hekate or another bootloader to launch the payload:
|
||||
|
||||
1. Place `omninx-installer.bin` in `sd:/bootloader/payloads/`
|
||||
2. Launch the payload from hekate's payload menu
|
||||
3. The payload will automatically:
|
||||
- Detect which pack variant is present
|
||||
- Detect if OmniNX is already installed
|
||||
- Perform appropriate installation (update or clean)
|
||||
- Launch hekate after completion
|
||||
|
||||
### 3. What Happens
|
||||
|
||||
1. Payload mounts the SD card
|
||||
2. Detects current OmniNX installation (if any)
|
||||
3. Detects which pack variant is on SD card
|
||||
4. Determines installation mode (update vs clean)
|
||||
5. Performs cleanup based on mode:
|
||||
- **Update**: Selective deletion of specific paths
|
||||
- **Clean**: Full wipe with backup/restore
|
||||
6. Copies files from pack directory to SD root
|
||||
7. Creates version marker file
|
||||
8. Cleans up old version markers
|
||||
9. Launches hekate (`sd:/bootloader/update.bin`)
|
||||
|
||||
## Variant Support
|
||||
|
||||
The payload supports three OmniNX variants:
|
||||
|
||||
- **Standard** – `sd:/OmniNX Standard/`
|
||||
- **Light** – `sd:/OmniNX Light/`
|
||||
- **OC** – `sd:/OmniNX OC/`
|
||||
|
||||
The payload detects the pack variant from the staging directory presence and detects the current installation (variant + version) from `sd:/config/omninx/manifest.ini` (`current_pack` and `version` keys).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
OmniNX-Installer-Payload/
|
||||
├── source/ # Main source code
|
||||
│ ├── main.c # Entry point and main flow
|
||||
│ ├── version.c # Version/variant detection
|
||||
│ ├── version.h
|
||||
│ ├── install.c # Installation logic
|
||||
│ ├── install.h
|
||||
│ ├── backup.c # Backup/restore operations
|
||||
│ ├── backup.h
|
||||
│ ├── deletion_lists.h # Arrays of paths to delete
|
||||
│ ├── fs.c # File system operations
|
||||
│ ├── fs.h
|
||||
│ ├── gfx.c # Graphics utilities
|
||||
│ ├── gfx.h
|
||||
│ ├── nx_sd.c # SD card operations
|
||||
│ ├── nx_sd.h
|
||||
│ ├── link.ld # Linker script
|
||||
│ └── start.S # Startup assembly
|
||||
├── bdk/ # Blue Development Kit
|
||||
├── Makefile # Build configuration
|
||||
├── VERSION # Version file
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is based on TegraExplorer and hekate. Please refer to those projects for their respective licenses.
|
||||
|
||||
## Credits
|
||||
|
||||
- **CTCaer** - [hekate](https://github.com/CTCaer/hekate)
|
||||
- **naehrwert** - Tegra exploration work
|
||||
- **shchmue** - [TegraExplorer](https://github.com/shchmue/TegraExplorer)
|
||||
- **sthetix** - [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payload) (inspiration and base structure)
|
||||
- **Woody2408** - OmniNX CFW Pack creator
|
||||
# OmniNX Installer Payload
|
||||
|
||||
> **To properly use this payload, download the latest OmniNX Release:** [https://git.niklascfw.de/OmniNX/OmniNX/releases](https://git.niklascfw.de/OmniNX/OmniNX/releases)
|
||||
|
||||
A minimal payload for installing OmniNX CFW Pack files on Nintendo Switch outside of Horizon OS.
|
||||
|
||||
Based on [TegraExplorer](https://github.com/shchmue/TegraExplorer) and [hekate](https://github.com/CTCaer/hekate) by CTCaer, naehrwert, and shchmue.
|
||||
Based on [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payload) by sthetix.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Variant Detection**: Detects which OmniNX pack variant is present (Standard/Light/OC)
|
||||
- **Smart Installation Modes**:
|
||||
- **Update Mode**: Selective deletion when OmniNX is already installed
|
||||
- **Clean Install**: Full wipe with backup/restore of user data (DBI `dbi.config`, prod.keys)
|
||||
- **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
|
||||
- **Error Handling**: Detailed error reporting on screen
|
||||
- **Payload Chaining**: Automatically launch hekate after installation
|
||||
|
||||
## Documentation
|
||||
|
||||
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
|
||||
- **[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
|
||||
|
||||
### Update Mode (OmniNX Detected)
|
||||
- Detected when version marker files (`1.0.0s`, `1.0.0l`, or `1.0.0oc`) are found
|
||||
- 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
|
||||
- Updates only necessary CFW components
|
||||
|
||||
### Clean Install (No OmniNX Detected)
|
||||
- Detected when no version marker files are found
|
||||
- Performs full wipe of `/atmosphere`, `/bootloader`, `/config`, and `/switch`
|
||||
- **Backs up and restores**:
|
||||
- `sd:/switch/DBI/dbi.config` → preserved (not the whole `DBI` folder)
|
||||
- `sd:/switch/prod.keys` → preserved
|
||||
- Fresh installation of all CFW components
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **devkitARM** - ARM toolchain for Nintendo Switch development
|
||||
- **BDK** - Blue Development Kit (included in this repo)
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make
|
||||
```
|
||||
|
||||
The built payload will be output to `output/OmniNX-Installer.bin`.
|
||||
|
||||
### CI / GitHub Actions
|
||||
|
||||
Every push triggers a build. The `.bin` file is produced and attached as a workflow artifact (Actions tab → select run → Artifacts).
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Extract OmniNX Pack to SD Card
|
||||
|
||||
Extract the OmniNX pack zip file directly to your SD card. The pack should contain one of:
|
||||
|
||||
```
|
||||
sd:/OmniNX Standard/
|
||||
├── atmosphere/
|
||||
├── bootloader/
|
||||
├── config/
|
||||
├── switch/
|
||||
├── TegraExplorer/
|
||||
├── warmboot_mariko/
|
||||
├── boot.dat
|
||||
├── boot.ini
|
||||
├── exosphere.ini
|
||||
├── hbmenu.nro
|
||||
├── loader.bin
|
||||
├── payload.bin
|
||||
└── 1.0.0s
|
||||
```
|
||||
|
||||
Or `sd:/OmniNX Light/` or `sd:/OmniNX OC/` for other variants.
|
||||
|
||||
**Important**: Extract both the OmniNX pack zip AND this payload to your SD card.
|
||||
|
||||
### 2. Launch Payload
|
||||
|
||||
Use hekate or another bootloader to launch the payload:
|
||||
|
||||
1. Place `omninx-installer.bin` in `sd:/bootloader/payloads/`
|
||||
2. Launch the payload from hekate's payload menu
|
||||
3. The payload will automatically:
|
||||
- Detect which pack variant is present
|
||||
- Detect if OmniNX is already installed
|
||||
- Perform appropriate installation (update or clean)
|
||||
- Launch hekate after completion
|
||||
|
||||
### 3. What Happens
|
||||
|
||||
1. Payload mounts the SD card
|
||||
2. Detects current OmniNX installation (if any)
|
||||
3. Detects which pack variant is on SD card
|
||||
4. Determines installation mode (update vs clean)
|
||||
5. Performs cleanup based on mode:
|
||||
- **Update**: Selective deletion of specific paths
|
||||
- **Clean**: Full wipe with backup/restore
|
||||
6. Copies files from pack directory to SD root
|
||||
7. Creates version marker file
|
||||
8. Cleans up old version markers
|
||||
9. Launches hekate (`sd:/bootloader/update.bin`)
|
||||
|
||||
## Variant Support
|
||||
|
||||
The payload supports three OmniNX variants:
|
||||
|
||||
- **Standard** – `sd:/OmniNX Standard/`
|
||||
- **Light** – `sd:/OmniNX Light/`
|
||||
- **OC** – `sd:/OmniNX OC/`
|
||||
|
||||
The payload detects the pack variant from the staging directory presence and detects the current installation (variant + version) from `sd:/config/omninx/manifest.ini` (`current_pack` and `version` keys).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
OmniNX-Installer-Payload/
|
||||
├── source/ # Main source code
|
||||
│ ├── main.c # Entry point and main flow
|
||||
│ ├── version.c # Version/variant detection
|
||||
│ ├── version.h
|
||||
│ ├── install.c # Installation logic
|
||||
│ ├── install.h
|
||||
│ ├── backup.c # Backup/restore operations
|
||||
│ ├── backup.h
|
||||
│ ├── deletion_lists.h # Arrays of paths to delete
|
||||
│ ├── fs.c # File system operations
|
||||
│ ├── fs.h
|
||||
│ ├── gfx.c # Graphics utilities
|
||||
│ ├── gfx.h
|
||||
│ ├── nx_sd.c # SD card operations
|
||||
│ ├── nx_sd.h
|
||||
│ ├── link.ld # Linker script
|
||||
│ └── start.S # Startup assembly
|
||||
├── bdk/ # Blue Development Kit
|
||||
├── Makefile # Build configuration
|
||||
├── VERSION # Version file
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is based on TegraExplorer and hekate. Please refer to those projects for their respective licenses.
|
||||
|
||||
## Credits
|
||||
|
||||
- **CTCaer** - [hekate](https://github.com/CTCaer/hekate)
|
||||
- **naehrwert** - Tegra exploration work
|
||||
- **shchmue** - [TegraExplorer](https://github.com/shchmue/TegraExplorer)
|
||||
- **sthetix** - [HATS-Installer-Payload](https://github.com/sthetix/HATS-Installer-Payload) (inspiration and base structure)
|
||||
- **Woody2408** - OmniNX CFW Pack creator
|
||||
@@ -721,6 +721,12 @@ void jc_deinit()
|
||||
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)
|
||||
{
|
||||
if (((u32)get_tmr_ms() - jc->last_received_time) > 1000)
|
||||
|
||||
@@ -92,6 +92,7 @@ typedef struct _jc_gamepad_rpt_t
|
||||
void jc_power_supply(u8 uart, bool enable);
|
||||
void jc_init_hw();
|
||||
void jc_deinit();
|
||||
void jc_clear_input(void);
|
||||
jc_gamepad_rpt_t *joycon_poll();
|
||||
jc_gamepad_rpt_t *jc_get_bt_pairing_info(bool *is_l_hos, bool *is_r_hos);
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,410 @@
|
||||
// <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",
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<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>
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace OmniNX.DesktopInstaller;
|
||||
|
||||
static class Program
|
||||
{
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
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));
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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>
|
||||
+116
-29
@@ -24,17 +24,9 @@ int backup_user_data(void) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Backup DBI if it exists
|
||||
if (path_exists("sd:/switch/DBI")) {
|
||||
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);
|
||||
// Backup DBI settings file only (not .nro installers)
|
||||
if (path_exists(DBI_CONFIG_PATH)) {
|
||||
res = file_copy(DBI_CONFIG_PATH, TEMP_BACKUP_DBI_CONFIG);
|
||||
if (res != FR_OK) {
|
||||
return res;
|
||||
}
|
||||
@@ -47,6 +39,43 @@ int backup_user_data(void) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -61,25 +90,15 @@ int restore_user_data(void) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Restore DBI if backup exists
|
||||
if (path_exists("sd:/temp_backup/DBI")) {
|
||||
res = folder_copy("sd:/temp_backup/DBI", "sd:/switch");
|
||||
if (res == FR_OK) {
|
||||
// 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");
|
||||
// Restore DBI settings if backed up
|
||||
if (path_exists(TEMP_BACKUP_DBI_CONFIG)) {
|
||||
res = f_mkdir("sd:/switch/DBI");
|
||||
if (res != FR_OK && res != FR_EXIST) {
|
||||
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");
|
||||
res = file_copy(TEMP_BACKUP_DBI_CONFIG, DBI_CONFIG_PATH);
|
||||
if (res != FR_OK) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +109,43 @@ int restore_user_data(void) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -101,3 +157,34 @@ int cleanup_backup(void) {
|
||||
}
|
||||
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);
|
||||
}
|
||||
+31
-1
@@ -7,8 +7,31 @@
|
||||
#include <utils/types.h>
|
||||
|
||||
#define TEMP_BACKUP_PATH "sd:/temp_backup"
|
||||
/** DBI settings; preserved across clean install (not whole DBI folder). */
|
||||
#define DBI_CONFIG_PATH "sd:/switch/DBI/dbi.config"
|
||||
#define TEMP_BACKUP_DBI_CONFIG TEMP_BACKUP_PATH "/dbi.config"
|
||||
|
||||
// Backup user data (DBI, Tinfoil, prod.keys) before clean install
|
||||
/** CyberFoil homebrew; preserved across clean install (nro, config, remotes only). */
|
||||
#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);
|
||||
|
||||
// Restore user data after clean install
|
||||
@@ -16,3 +39,10 @@ int restore_user_data(void);
|
||||
|
||||
// Clean up temporary backup directory
|
||||
int cleanup_backup(void);
|
||||
|
||||
// HorizonOC: backup config.ini before OC pack update (update mode only); no-op if missing
|
||||
int backup_horizon_oc_config_for_oc_update(void);
|
||||
// Restore after file copy; no-op if no backup from this run
|
||||
int restore_horizon_oc_config_after_oc_update(void);
|
||||
// Delete sd:/.omninx_oc_update after successful restore
|
||||
int cleanup_horizon_oc_update_backup(void);
|
||||
+133
-295
@@ -1,295 +1,133 @@
|
||||
/*
|
||||
* OmniNX Installer - Deletion Lists for Clean Install Mode
|
||||
* Selective deletion when no OmniNX install was found.
|
||||
* Only listed paths are removed; does not wipe whole card or user configs.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Atmosphere subdirectories to delete
|
||||
static const char* clean_atmosphere_dirs_to_delete[] = {
|
||||
"sd:/atmosphere/config",
|
||||
"sd:/atmosphere/crash_reports",
|
||||
"sd:/atmosphere/erpt_reports",
|
||||
"sd:/atmosphere/exefs_patches/CrunchPatch",
|
||||
"sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0",
|
||||
"sd:/atmosphere/exefs_patches/bluetooth_patches",
|
||||
"sd:/atmosphere/exefs_patches/bootlogo",
|
||||
"sd:/atmosphere/exefs_patches/btm_patches",
|
||||
"sd:/atmosphere/exefs_patches/es_patches",
|
||||
"sd:/atmosphere/exefs_patches/hid_patches",
|
||||
"sd:/atmosphere/exefs_patches/logo1",
|
||||
"sd:/atmosphere/exefs_patches/nfim_ctest",
|
||||
"sd:/atmosphere/exefs_patches/nim_ctest",
|
||||
"sd:/atmosphere/exefs_patches/nvnflinger_cmu",
|
||||
"sd:/atmosphere/extrazz",
|
||||
"sd:/atmosphere/fatal_errors",
|
||||
"sd:/atmosphere/fatal_reports",
|
||||
"sd:/atmosphere/flags",
|
||||
"sd:/atmosphere/hbl_html",
|
||||
"sd:/atmosphere/hosts",
|
||||
"sd:/atmosphere/kips",
|
||||
"sd:/atmosphere/kip1",
|
||||
"sd:/atmosphere/kip_patches",
|
||||
"sd:/atmosphere/logs",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Atmosphere contents directories (title IDs; only under atmosphere/contents/)
|
||||
static const char* 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
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// Bootloader directories to delete
|
||||
static const char* clean_bootloader_dirs_to_delete[] = {
|
||||
"sd:/bootloader/boot",
|
||||
"sd:/bootloader/bootlogo",
|
||||
"sd:/bootloader/ini2",
|
||||
"sd:/bootloader/payloads",
|
||||
"sd:/bootloader/reboot",
|
||||
"sd:/bootloader/res",
|
||||
"sd:/bootloader/sys",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Bootloader files to delete
|
||||
static const char* clean_bootloader_files_to_delete[] = {
|
||||
"sd:/bootloader/ArgonNX.bin",
|
||||
"sd:/bootloader/bootlogo.bmp",
|
||||
"sd:/bootloader/hekate_ipl.ini",
|
||||
"sd:/bootloader/nyx.ini",
|
||||
"sd:/bootloader/patches.ini",
|
||||
"sd:/bootloader/update.bin",
|
||||
"sd:/bootloader/ini/EmuMMC ohne Mods.ini",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Config directories to delete
|
||||
static const char* clean_config_dirs_to_delete[] = {
|
||||
"sd:/config/aio-switch-updater",
|
||||
"sd:/config/blue_pack_updater",
|
||||
"sd:/config/kefir-updater",
|
||||
"sd:/config/nx-hbmenu",
|
||||
"sd:/config/quickntp",
|
||||
"sd:/config/sys-con",
|
||||
"sd:/config/sys-patch",
|
||||
"sd:/config/uberhand",
|
||||
"sd:/config/ultrahand",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Switch directories to delete
|
||||
static const char* clean_switch_dirs_to_delete[] = {
|
||||
"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
|
||||
};
|
||||
|
||||
// Switch files (NRO) 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
|
||||
};
|
||||
|
||||
// Root CFW files to delete
|
||||
static const char* clean_root_files_to_delete[] = {
|
||||
"sd:/boot.dat",
|
||||
"sd:/boot.ini",
|
||||
"sd:/exosphere.bin",
|
||||
"sd:/exosphere.ini",
|
||||
"sd:/hbmenu.nro",
|
||||
"sd:/install.bat",
|
||||
"sd:/license",
|
||||
"sd:/loader.bin",
|
||||
"sd:/mc-mitm.log",
|
||||
"sd:/payload.bin",
|
||||
"sd:/update.bin",
|
||||
"sd:/version",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Miscellaneous directories to delete
|
||||
static const char* clean_misc_dirs_to_delete[] = {
|
||||
"sd:/argon",
|
||||
"sd:/games",
|
||||
"sd:/NSPs (Tools)",
|
||||
"sd:/Patched Apps",
|
||||
"sd:/SaltySD/flags",
|
||||
"sd:/scripts",
|
||||
"sd:/switch/tinfoil/db",
|
||||
"sd:/tools",
|
||||
"sd:/warmboot_mariko",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Miscellaneous files to delete
|
||||
static const char* clean_misc_files_to_delete[] = {
|
||||
"sd:/fusee-primary.bin",
|
||||
"sd:/fusee.bin",
|
||||
"sd:/SaltySD/exceptions.txt",
|
||||
"sd:/SaltySD/saltysd_bootstrap.elf",
|
||||
"sd:/SaltySD/saltysd_bootstrap32_3k.elf",
|
||||
"sd:/SaltySD/saltysd_bootstrap32_5k.elf",
|
||||
"sd:/SaltySD/saltysd_core.elf",
|
||||
"sd:/SaltySD/saltysd_core32.elf",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Old version marker files to delete (clean install only)
|
||||
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-c",
|
||||
"sd:/1.4.0-pre-d",
|
||||
"sd:/1.4.1",
|
||||
"sd:/1.5.0",
|
||||
NULL
|
||||
};
|
||||
/*
|
||||
* OmniNX Installer - Deletion Lists for Clean Install Mode
|
||||
* Deletion policy aligned with NiklasCFW pack clean install (TegraExplorer script):
|
||||
* full sd:/atmosphere, bootloader/config subsets, sd:/switch, root + misc, then
|
||||
* post-restore paths (e.g. tinfoil db). CyberFoil/Checkpoint/DBI/prod.keys backup is in backup.c.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Entire atmosphere/ tree (NiklasCFW: deldir("sd:/atmosphere"))
|
||||
static const char* clean_atmosphere_dirs_to_delete[] = {
|
||||
"sd:/atmosphere",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* clean_atmosphere_contents_dirs_to_delete[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* clean_atmosphere_files_to_delete[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
// Bootloader directories to delete
|
||||
static const char* clean_bootloader_dirs_to_delete[] = {
|
||||
"sd:/bootloader/boot",
|
||||
"sd:/bootloader/bootlogo",
|
||||
"sd:/bootloader/ini2",
|
||||
"sd:/bootloader/payloads",
|
||||
"sd:/bootloader/reboot",
|
||||
"sd:/bootloader/res",
|
||||
"sd:/bootloader/sys",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Bootloader files to delete
|
||||
static const char* clean_bootloader_files_to_delete[] = {
|
||||
"sd:/bootloader/ArgonNX.bin",
|
||||
"sd:/bootloader/bootlogo.bmp",
|
||||
"sd:/bootloader/hekate_ipl.ini",
|
||||
"sd:/bootloader/patches.ini",
|
||||
"sd:/bootloader/update.bin",
|
||||
"sd:/bootloader/ini/EmuMMC ohne Mods.ini",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Config directories to delete
|
||||
static const char* clean_config_dirs_to_delete[] = {
|
||||
"sd:/config/aio-switch-updater",
|
||||
"sd:/config/blue_pack_updater",
|
||||
"sd:/config/kefir-updater",
|
||||
"sd:/config/nx-hbmenu",
|
||||
"sd:/config/sys-con",
|
||||
"sd:/config/sys-patch",
|
||||
"sd:/config/uberhand",
|
||||
"sd:/config/ultrahand",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Entire switch/ tree (clean install recreates sd:/switch after wipe)
|
||||
static const char* clean_switch_dirs_to_delete[] = {
|
||||
"sd:/switch",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* clean_switch_files_to_delete[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
// Root CFW files to delete
|
||||
static const char* clean_root_files_to_delete[] = {
|
||||
"sd:/boot.dat",
|
||||
"sd:/boot.ini",
|
||||
"sd:/exosphere.bin",
|
||||
"sd:/exosphere.ini",
|
||||
"sd:/hbmenu.nro",
|
||||
"sd:/install.bat",
|
||||
"sd:/license",
|
||||
"sd:/loader.bin",
|
||||
"sd:/mc-mitm.log",
|
||||
"sd:/payload.bin",
|
||||
"sd:/update.bin",
|
||||
"sd:/version",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Miscellaneous directories to delete
|
||||
static const char* clean_misc_dirs_to_delete[] = {
|
||||
"sd:/argon",
|
||||
"sd:/games",
|
||||
"sd:/NSPs (Tools)",
|
||||
"sd:/Patched Apps",
|
||||
"sd:/SaltySD/flags",
|
||||
"sd:/SaltySD/patches",
|
||||
"sd:/scripts",
|
||||
"sd:/TegraExplorer",
|
||||
"sd:/themes/systemData",
|
||||
"sd:/tools",
|
||||
"sd:/warmboot_mariko",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Miscellaneous files to delete
|
||||
static const char* clean_misc_files_to_delete[] = {
|
||||
"sd:/fusee-primary.bin",
|
||||
"sd:/fusee.bin",
|
||||
"sd:/SaltySD/exceptions.txt",
|
||||
"sd:/SaltySD/saltysd_bootstrap.elf",
|
||||
"sd:/SaltySD/saltysd_bootstrap32_3k.elf",
|
||||
"sd:/SaltySD/saltysd_bootstrap32_5k.elf",
|
||||
"sd:/SaltySD/saltysd_core.elf",
|
||||
"sd:/SaltySD/saltysd_core32.elf",
|
||||
NULL
|
||||
};
|
||||
|
||||
// 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)
|
||||
static const char* old_version_files_to_delete[] = {
|
||||
"sd:/1.4.0-pre",
|
||||
"sd:/1.4.0-pre-c",
|
||||
"sd:/1.4.0-pre-d",
|
||||
"sd:/1.4.1",
|
||||
"sd:/1.5.0",
|
||||
"sd:/1.6.0",
|
||||
"sd:/1.6.1",
|
||||
"sd:/1.6.2",
|
||||
NULL
|
||||
};
|
||||
@@ -10,14 +10,16 @@ static const char* atmosphere_dirs_to_delete[] = {
|
||||
"sd:/atmosphere/config",
|
||||
"sd:/atmosphere/crash_reports",
|
||||
"sd:/atmosphere/erpt_reports",
|
||||
"sd:/atmosphere/exefs_patches/audio_mastervolume",
|
||||
"sd:/atmosphere/exefs_patches/CrunchPatch",
|
||||
"sd:/atmosphere/exefs_patches/SaltyNX_Fixes",
|
||||
"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/logo",
|
||||
"sd:/atmosphere/exefs_patches/nfim_ctest",
|
||||
"sd:/atmosphere/exefs_patches/nim_ctest",
|
||||
"sd:/atmosphere/exefs_patches/nvnflinger_cmu",
|
||||
@@ -80,6 +82,8 @@ static const char* atmosphere_files_to_delete[] = {
|
||||
"sd:/atmosphere/package3",
|
||||
"sd:/atmosphere/reboot_payload.bin",
|
||||
"sd:/atmosphere/stratosphere.romfs",
|
||||
"sd:/atmosphere/kips/loader.kip",
|
||||
"sd:/atmosphere/kips/hoc.kip",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -122,25 +126,27 @@ static const char* config_dirs_to_delete[] = {
|
||||
};
|
||||
|
||||
// Switch directories to delete
|
||||
// NOTE: .packages is intentionally excluded - UltraHand package cache, preserve during updates
|
||||
static const char* switch_dirs_to_delete[] = {
|
||||
"sd:/switch/.packages",
|
||||
"sd:/switch/.overlays",
|
||||
"sd:/switch/90DNS_tester",
|
||||
"sd:/switch/aio-switch-updater",
|
||||
"sd:/switch/amsPLUS-downloader",
|
||||
"sd:/switch/appstore",
|
||||
"sd:/switch/AtmoXL-Titel-Installer",
|
||||
"sd:/switch/Benchmark-Toolbox",
|
||||
"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-Block_Tester",
|
||||
"sd:/switch/DNS_mitm Tester",
|
||||
"sd:/switch/EdiZon",
|
||||
"sd:/switch/Fizeau",
|
||||
"sd:/switch/FTPD",
|
||||
"sd:/switch/Furmark-NX",
|
||||
"sd:/switch/fw-downloader",
|
||||
"sd:/switch/gamecard_installer",
|
||||
"sd:/switch/Goldleaf",
|
||||
@@ -165,6 +171,7 @@ static const char* switch_dirs_to_delete[] = {
|
||||
"sd:/switch/reboot_to_argonNX",
|
||||
"sd:/switch/reboot_to_hekate",
|
||||
"sd:/switch/Shutdown_System",
|
||||
"sd:/switch/SimpleModAlchemist",
|
||||
"sd:/switch/SimpleModDownloader",
|
||||
"sd:/switch/SimpleModManager",
|
||||
"sd:/switch/sphaira",
|
||||
@@ -172,19 +179,22 @@ static const char* switch_dirs_to_delete[] = {
|
||||
"sd:/switch/Switch-Time",
|
||||
"sd:/switch/SwitchIdent",
|
||||
"sd:/switch/Switch_themes_Installer",
|
||||
"sd:/switch/Switchfin",
|
||||
"sd:/switch/swr-ini-tool",
|
||||
"sd:/switch/Sys-Clk Manager",
|
||||
"sd:/switch/Sys-Con",
|
||||
"sd:/switch/sys-clk-manager",
|
||||
"sd:/switch/themezer-nx",
|
||||
"sd:/switch/ThemezerNX",
|
||||
"sd:/switch/themezernx",
|
||||
"sd:/switch/tinwoo",
|
||||
"sd:/switch/Ultrahand-Reload",
|
||||
NULL
|
||||
};
|
||||
|
||||
// Switch files (NRO) to delete
|
||||
static const char* switch_files_to_delete[] = {
|
||||
"sd:/switch/90DNS_tester/90DNS_tester.nro",
|
||||
"sd:/switch/Benchmark-Toolbox/Benchmark-Toolbox.nro",
|
||||
"sd:/switch/breeze.nro",
|
||||
"sd:/switch/cheats-updater.nro",
|
||||
"sd:/switch/chiaki.nro",
|
||||
@@ -204,9 +214,11 @@ static const char* switch_files_to_delete[] = {
|
||||
"sd:/switch/DBI_RU/DBI_RU.nro",
|
||||
"sd:/switch/DBI/DBI_EN.nro",
|
||||
"sd:/switch/DBI_DE/DBI_DE.nro",
|
||||
"sd:/switch/DNS-Block_Tester/dns_tester.nro",
|
||||
"sd:/switch/DNS_mitm Tester.nro",
|
||||
"sd:/switch/EdiZon.nro",
|
||||
"sd:/switch/Fizeau.nro",
|
||||
"sd:/switch/Furmark-NX/Furmark-NX.nro",
|
||||
"sd:/switch/Goldleaf.nro",
|
||||
"sd:/switch/haze.nro",
|
||||
"sd:/switch/JKSV.nro",
|
||||
@@ -220,19 +232,22 @@ static const char* switch_files_to_delete[] = {
|
||||
"sd:/switch/nxdumptool.nro",
|
||||
"sd:/switch/nxtc.bin",
|
||||
"sd:/switch/reboot_to_payload.nro",
|
||||
"sd:/switch/SimpleModAlchemist/Simple_Mod_Alchemist.nro",
|
||||
"sd:/switch/SimpleModDownloader.nro",
|
||||
"sd:/switch/SimpleModManager.nro",
|
||||
"sd:/switch/sphaira.nro",
|
||||
"sd:/switch/swr-ini-tool/swr-ini-tool.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/ThemezerNX/themezer-nx.nro",
|
||||
"sd:/switch/tinfoil.nro",
|
||||
"sd:/switch/tinfoil/tinfoil.nro",
|
||||
"sd:/switch/tinwoo.nro",
|
||||
"sd:/switch/tinwoo/tinwoo.nro",
|
||||
"sd:/switch/Ultrahand-Reload/Ultrahand-Reload.nro",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -262,6 +277,7 @@ static const char* misc_dirs_to_delete[] = {
|
||||
"sd:/SaltySD/flags",
|
||||
"sd:/scripts",
|
||||
"sd:/switch/tinfoil/db",
|
||||
"sd:/themes/systemData",
|
||||
"sd:/tools",
|
||||
"sd:/warmboot_mariko",
|
||||
NULL
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* DRAM capacity from fuse (SKU), not physical probe.
|
||||
*/
|
||||
|
||||
#include "dram_fuse.h"
|
||||
#include <soc/fuse.h>
|
||||
#include <soc/hw_init.h>
|
||||
#include <soc/t210.h>
|
||||
|
||||
static int mariko_dram_mib(u32 dram_id)
|
||||
{
|
||||
switch (dram_id) {
|
||||
case 9:
|
||||
case 13:
|
||||
case 18:
|
||||
case 21:
|
||||
case 23:
|
||||
case 28:
|
||||
return 8192;
|
||||
default:
|
||||
if (dram_id >= 3 && dram_id <= 28)
|
||||
return 4096;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int erista_dram_mib(u32 dram_id)
|
||||
{
|
||||
if (dram_id == 4)
|
||||
return 6144;
|
||||
if (dram_id <= 6)
|
||||
return 4096;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dram_capacity_mib_from_fuse(void)
|
||||
{
|
||||
u32 nid = fuse_read_dramid(false);
|
||||
u32 chip = hw_get_chip_id();
|
||||
|
||||
if (chip == GP_HIDREV_MAJOR_T210)
|
||||
return erista_dram_mib(nid);
|
||||
if (chip == GP_HIDREV_MAJOR_T210B01)
|
||||
return mariko_dram_mib(nid);
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/types.h>
|
||||
|
||||
/* DRAM capacity in MiB from fuse DRAM ID + SoC (same table as RAM test payload). */
|
||||
int dram_capacity_mib_from_fuse(void);
|
||||
+351
-351
@@ -1,351 +1,351 @@
|
||||
/*
|
||||
* OmniNX Installer - Filesystem operations with file logging
|
||||
* Based on HATS Installer
|
||||
*/
|
||||
|
||||
#include "fs.h"
|
||||
#include <libs/fatfs/ff.h>
|
||||
#include <mem/heap.h>
|
||||
#include <string.h>
|
||||
#include <utils/sprintf.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define FS_BUFFER_SIZE 0x100000 // 1MB copy buffer
|
||||
#define LOG_BUFFER_SIZE 512
|
||||
|
||||
// Log file handle
|
||||
static FIL log_file;
|
||||
static bool log_enabled = false;
|
||||
static char log_buf[LOG_BUFFER_SIZE];
|
||||
|
||||
// Initialize log file
|
||||
void log_init(const char *path) {
|
||||
int res = f_open(&log_file, path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res == FR_OK) {
|
||||
log_enabled = true;
|
||||
log_write("=== OmniNX Installer Log ===\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Close log file
|
||||
void log_close(void) {
|
||||
if (log_enabled) {
|
||||
f_sync(&log_file);
|
||||
f_close(&log_file);
|
||||
log_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write to log file (variadic version)
|
||||
void log_write(const char *fmt, ...) {
|
||||
if (!log_enabled) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// Format the message
|
||||
vsnprintf(log_buf, LOG_BUFFER_SIZE, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
// Write to file
|
||||
UINT bw;
|
||||
f_write(&log_file, log_buf, strlen(log_buf), &bw);
|
||||
f_sync(&log_file); // Flush to ensure data is written
|
||||
}
|
||||
|
||||
// Convert FatFS error code to string
|
||||
const char *fs_error_str(int err) {
|
||||
switch (err) {
|
||||
case FR_OK: return "OK";
|
||||
case FR_DISK_ERR: return "DISK_ERR: Low level disk error";
|
||||
case FR_INT_ERR: return "INT_ERR: Internal error";
|
||||
case FR_NOT_READY: return "NOT_READY: Drive not ready";
|
||||
case FR_NO_FILE: return "NO_FILE: File not found";
|
||||
case FR_NO_PATH: return "NO_PATH: Path not found";
|
||||
case FR_INVALID_NAME: return "INVALID_NAME: Invalid path name";
|
||||
case FR_DENIED: return "DENIED: Access denied";
|
||||
case FR_EXIST: return "EXIST: Already exists";
|
||||
case FR_INVALID_OBJECT: return "INVALID_OBJECT: Invalid object";
|
||||
case FR_WRITE_PROTECTED: return "WRITE_PROTECTED: Write protected";
|
||||
case FR_INVALID_DRIVE: return "INVALID_DRIVE: Invalid drive";
|
||||
case FR_NOT_ENABLED: return "NOT_ENABLED: Volume not mounted";
|
||||
case FR_NO_FILESYSTEM: return "NO_FILESYSTEM: No valid FAT";
|
||||
case FR_MKFS_ABORTED: return "MKFS_ABORTED: mkfs aborted";
|
||||
case FR_TIMEOUT: return "TIMEOUT: Timeout";
|
||||
case FR_LOCKED: return "LOCKED: File locked";
|
||||
case FR_NOT_ENOUGH_CORE: return "NOT_ENOUGH_CORE: Out of memory";
|
||||
case FR_TOO_MANY_OPEN_FILES: return "TOO_MANY_OPEN_FILES";
|
||||
case FR_INVALID_PARAMETER: return "INVALID_PARAMETER";
|
||||
default: return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two paths
|
||||
static char *combine_paths(const char *base, const char *add) {
|
||||
size_t base_len = strlen(base);
|
||||
size_t add_len = strlen(add);
|
||||
char *result = malloc(base_len + add_len + 2);
|
||||
|
||||
if (!result) return NULL;
|
||||
|
||||
if (base_len > 0 && base[base_len - 1] == '/') {
|
||||
s_printf(result, "%s%s", base, add);
|
||||
} else {
|
||||
s_printf(result, "%s/%s", base, add);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Copy a single file with logging
|
||||
int file_copy(const char *src, const char *dst) {
|
||||
FIL fin, fout;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("COPY: %s -> %s\n", src, dst);
|
||||
|
||||
res = f_open(&fin, src, FA_READ | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR open src: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
f_stat(src, &fno);
|
||||
u64 file_size = f_size(&fin);
|
||||
log_write(" Size: %d bytes\n", (u32)file_size);
|
||||
|
||||
res = f_open(&fout, dst, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res != FR_OK) {
|
||||
f_close(&fin);
|
||||
log_write(" ERROR open dst: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
u8 *buf = malloc(FS_BUFFER_SIZE);
|
||||
if (!buf) {
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
log_write(" ERROR: Out of memory for buffer\n");
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
u64 remaining = file_size;
|
||||
UINT br, bw;
|
||||
|
||||
while (remaining > 0) {
|
||||
UINT to_copy = (remaining > FS_BUFFER_SIZE) ? FS_BUFFER_SIZE : (UINT)remaining;
|
||||
|
||||
res = f_read(&fin, buf, to_copy, &br);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR read: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (br != to_copy) {
|
||||
log_write(" ERROR: Read %d bytes, expected %d\n", br, to_copy);
|
||||
res = FR_DISK_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
res = f_write(&fout, buf, to_copy, &bw);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR write: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (bw != to_copy) {
|
||||
log_write(" ERROR: Wrote %d bytes, expected %d\n", bw, to_copy);
|
||||
res = FR_DISK_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= to_copy;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
|
||||
if (res == FR_OK) {
|
||||
f_chmod(dst, fno.fattrib, 0x3A);
|
||||
log_write(" OK\n");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively delete a folder with logging
|
||||
int folder_delete(const char *path) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("DELETE: %s\n", path);
|
||||
|
||||
res = f_opendir(&dir, path);
|
||||
if (res != FR_OK) {
|
||||
// Maybe it's a file, try to delete it
|
||||
log_write(" Not a dir, trying as file...\n");
|
||||
res = f_unlink(path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR unlink: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK (file deleted)\n");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
|
||||
char *full_path = combine_paths(path, fno.fname);
|
||||
if (!full_path) {
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_delete(full_path);
|
||||
} else {
|
||||
file_count++;
|
||||
log_write(" DEL: %s\n", fno.fname);
|
||||
|
||||
// Clear read-only attribute if set (common with some CFW packs)
|
||||
if (fno.fattrib & AM_RDO) {
|
||||
f_chmod(full_path, fno.fattrib & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
res = f_unlink(full_path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR: %s\n", fs_error_str(res));
|
||||
}
|
||||
}
|
||||
|
||||
free(full_path);
|
||||
if (res != FR_OK) break;
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
if (res == FR_OK || res == FR_NO_FILE) {
|
||||
log_write(" Removing dir: %s (%d files, %d subdirs)\n", path, file_count, dir_count);
|
||||
|
||||
// Check and clear read-only attribute on directory if set
|
||||
FILINFO dir_info;
|
||||
if (f_stat(path, &dir_info) == FR_OK && (dir_info.fattrib & AM_RDO)) {
|
||||
f_chmod(path, dir_info.fattrib & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
res = f_unlink(path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR rmdir: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively copy a folder with logging
|
||||
int folder_copy(const char *src, const char *dst) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("FOLDER COPY: %s -> %s\n", src, dst);
|
||||
|
||||
res = f_opendir(&dir, src);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR opendir src: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get folder name from src path
|
||||
const char *folder_name = strrchr(src, '/');
|
||||
if (folder_name) {
|
||||
folder_name++;
|
||||
} else {
|
||||
folder_name = src;
|
||||
}
|
||||
|
||||
// Create destination folder
|
||||
char *dst_path = combine_paths(dst, folder_name);
|
||||
if (!dst_path) {
|
||||
f_closedir(&dir);
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
log_write(" Creating: %s\n", dst_path);
|
||||
|
||||
res = f_mkdir(dst_path);
|
||||
if (res == FR_EXIST) {
|
||||
log_write(" (already exists)\n");
|
||||
res = FR_OK;
|
||||
} else if (res != FR_OK) {
|
||||
log_write(" ERROR mkdir: %s\n", fs_error_str(res));
|
||||
f_closedir(&dir);
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
|
||||
// Copy contents
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
|
||||
char *src_full = combine_paths(src, fno.fname);
|
||||
char *dst_full = combine_paths(dst_path, fno.fname);
|
||||
|
||||
if (!src_full || !dst_full) {
|
||||
if (src_full) free(src_full);
|
||||
if (dst_full) free(dst_full);
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_copy(src_full, dst_path);
|
||||
} else {
|
||||
file_count++;
|
||||
res = file_copy(src_full, dst_full);
|
||||
}
|
||||
|
||||
free(src_full);
|
||||
free(dst_full);
|
||||
|
||||
if (res != FR_OK) break;
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
// Copy folder attributes
|
||||
if (res == FR_OK) {
|
||||
FILINFO src_info;
|
||||
if (f_stat(src, &src_info) == FR_OK) {
|
||||
f_chmod(dst_path, src_info.fattrib, 0x3A);
|
||||
}
|
||||
log_write(" Done: %d files, %d subdirs\n", file_count, dir_count);
|
||||
}
|
||||
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
/*
|
||||
* OmniNX Installer - Filesystem operations with file logging
|
||||
* Based on HATS Installer
|
||||
*/
|
||||
|
||||
#include "fs.h"
|
||||
#include <libs/fatfs/ff.h>
|
||||
#include <mem/heap.h>
|
||||
#include <string.h>
|
||||
#include <utils/sprintf.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define FS_BUFFER_SIZE 0x100000 // 1MB copy buffer
|
||||
#define LOG_BUFFER_SIZE 512
|
||||
|
||||
// Log file handle
|
||||
static FIL log_file;
|
||||
static bool log_enabled = false;
|
||||
static char log_buf[LOG_BUFFER_SIZE];
|
||||
|
||||
// Initialize log file
|
||||
void log_init(const char *path) {
|
||||
int res = f_open(&log_file, path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res == FR_OK) {
|
||||
log_enabled = true;
|
||||
log_write("=== OmniNX Installer Log ===\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Close log file
|
||||
void log_close(void) {
|
||||
if (log_enabled) {
|
||||
f_sync(&log_file);
|
||||
f_close(&log_file);
|
||||
log_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write to log file (variadic version)
|
||||
void log_write(const char *fmt, ...) {
|
||||
if (!log_enabled) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// Format the message
|
||||
vsnprintf(log_buf, LOG_BUFFER_SIZE, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
// Write to file
|
||||
UINT bw;
|
||||
f_write(&log_file, log_buf, strlen(log_buf), &bw);
|
||||
f_sync(&log_file); // Flush to ensure data is written
|
||||
}
|
||||
|
||||
// Convert FatFS error code to string
|
||||
const char *fs_error_str(int err) {
|
||||
switch (err) {
|
||||
case FR_OK: return "OK";
|
||||
case FR_DISK_ERR: return "DISK_ERR: Low level disk error";
|
||||
case FR_INT_ERR: return "INT_ERR: Internal error";
|
||||
case FR_NOT_READY: return "NOT_READY: Drive not ready";
|
||||
case FR_NO_FILE: return "NO_FILE: File not found";
|
||||
case FR_NO_PATH: return "NO_PATH: Path not found";
|
||||
case FR_INVALID_NAME: return "INVALID_NAME: Invalid path name";
|
||||
case FR_DENIED: return "DENIED: Access denied";
|
||||
case FR_EXIST: return "EXIST: Already exists";
|
||||
case FR_INVALID_OBJECT: return "INVALID_OBJECT: Invalid object";
|
||||
case FR_WRITE_PROTECTED: return "WRITE_PROTECTED: Write protected";
|
||||
case FR_INVALID_DRIVE: return "INVALID_DRIVE: Invalid drive";
|
||||
case FR_NOT_ENABLED: return "NOT_ENABLED: Volume not mounted";
|
||||
case FR_NO_FILESYSTEM: return "NO_FILESYSTEM: No valid FAT";
|
||||
case FR_MKFS_ABORTED: return "MKFS_ABORTED: mkfs aborted";
|
||||
case FR_TIMEOUT: return "TIMEOUT: Timeout";
|
||||
case FR_LOCKED: return "LOCKED: File locked";
|
||||
case FR_NOT_ENOUGH_CORE: return "NOT_ENOUGH_CORE: Out of memory";
|
||||
case FR_TOO_MANY_OPEN_FILES: return "TOO_MANY_OPEN_FILES";
|
||||
case FR_INVALID_PARAMETER: return "INVALID_PARAMETER";
|
||||
default: return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two paths
|
||||
static char *combine_paths(const char *base, const char *add) {
|
||||
size_t base_len = strlen(base);
|
||||
size_t add_len = strlen(add);
|
||||
char *result = malloc(base_len + add_len + 2);
|
||||
|
||||
if (!result) return NULL;
|
||||
|
||||
if (base_len > 0 && base[base_len - 1] == '/') {
|
||||
s_printf(result, "%s%s", base, add);
|
||||
} else {
|
||||
s_printf(result, "%s/%s", base, add);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Copy a single file with logging
|
||||
int file_copy(const char *src, const char *dst) {
|
||||
FIL fin, fout;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("COPY: %s -> %s\n", src, dst);
|
||||
|
||||
res = f_open(&fin, src, FA_READ | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR open src: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
f_stat(src, &fno);
|
||||
u64 file_size = f_size(&fin);
|
||||
log_write(" Size: %d bytes\n", (u32)file_size);
|
||||
|
||||
res = f_open(&fout, dst, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res != FR_OK) {
|
||||
f_close(&fin);
|
||||
log_write(" ERROR open dst: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
u8 *buf = malloc(FS_BUFFER_SIZE);
|
||||
if (!buf) {
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
log_write(" ERROR: Out of memory for buffer\n");
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
u64 remaining = file_size;
|
||||
UINT br, bw;
|
||||
|
||||
while (remaining > 0) {
|
||||
UINT to_copy = (remaining > FS_BUFFER_SIZE) ? FS_BUFFER_SIZE : (UINT)remaining;
|
||||
|
||||
res = f_read(&fin, buf, to_copy, &br);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR read: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (br != to_copy) {
|
||||
log_write(" ERROR: Read %d bytes, expected %d\n", br, to_copy);
|
||||
res = FR_DISK_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
res = f_write(&fout, buf, to_copy, &bw);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR write: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (bw != to_copy) {
|
||||
log_write(" ERROR: Wrote %d bytes, expected %d\n", bw, to_copy);
|
||||
res = FR_DISK_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= to_copy;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
|
||||
if (res == FR_OK) {
|
||||
f_chmod(dst, fno.fattrib, 0x3A);
|
||||
log_write(" OK\n");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively delete a folder with logging
|
||||
int folder_delete(const char *path) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("DELETE: %s\n", path);
|
||||
|
||||
res = f_opendir(&dir, path);
|
||||
if (res != FR_OK) {
|
||||
// Maybe it's a file, try to delete it
|
||||
log_write(" Not a dir, trying as file...\n");
|
||||
res = f_unlink(path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR unlink: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK (file deleted)\n");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
|
||||
char *full_path = combine_paths(path, fno.fname);
|
||||
if (!full_path) {
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_delete(full_path);
|
||||
} else {
|
||||
file_count++;
|
||||
log_write(" DEL: %s\n", fno.fname);
|
||||
|
||||
// Clear read-only attribute if set (common with some CFW packs)
|
||||
if (fno.fattrib & AM_RDO) {
|
||||
f_chmod(full_path, fno.fattrib & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
res = f_unlink(full_path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR: %s\n", fs_error_str(res));
|
||||
}
|
||||
}
|
||||
|
||||
free(full_path);
|
||||
if (res != FR_OK) break;
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
if (res == FR_OK || res == FR_NO_FILE) {
|
||||
log_write(" Removing dir: %s (%d files, %d subdirs)\n", path, file_count, dir_count);
|
||||
|
||||
// Check and clear read-only attribute on directory if set
|
||||
FILINFO dir_info;
|
||||
if (f_stat(path, &dir_info) == FR_OK && (dir_info.fattrib & AM_RDO)) {
|
||||
f_chmod(path, dir_info.fattrib & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
res = f_unlink(path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR rmdir: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively copy a folder with logging
|
||||
int folder_copy(const char *src, const char *dst) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("FOLDER COPY: %s -> %s\n", src, dst);
|
||||
|
||||
res = f_opendir(&dir, src);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR opendir src: %s\n", fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get folder name from src path
|
||||
const char *folder_name = strrchr(src, '/');
|
||||
if (folder_name) {
|
||||
folder_name++;
|
||||
} else {
|
||||
folder_name = src;
|
||||
}
|
||||
|
||||
// Create destination folder
|
||||
char *dst_path = combine_paths(dst, folder_name);
|
||||
if (!dst_path) {
|
||||
f_closedir(&dir);
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
log_write(" Creating: %s\n", dst_path);
|
||||
|
||||
res = f_mkdir(dst_path);
|
||||
if (res == FR_EXIST) {
|
||||
log_write(" (already exists)\n");
|
||||
res = FR_OK;
|
||||
} else if (res != FR_OK) {
|
||||
log_write(" ERROR mkdir: %s\n", fs_error_str(res));
|
||||
f_closedir(&dir);
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
|
||||
// Copy contents
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
|
||||
char *src_full = combine_paths(src, fno.fname);
|
||||
char *dst_full = combine_paths(dst_path, fno.fname);
|
||||
|
||||
if (!src_full || !dst_full) {
|
||||
if (src_full) free(src_full);
|
||||
if (dst_full) free(dst_full);
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_copy(src_full, dst_path);
|
||||
} else {
|
||||
file_count++;
|
||||
res = file_copy(src_full, dst_full);
|
||||
}
|
||||
|
||||
free(src_full);
|
||||
free(dst_full);
|
||||
|
||||
if (res != FR_OK) break;
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
// Copy folder attributes
|
||||
if (res == FR_OK) {
|
||||
FILINFO src_info;
|
||||
if (f_stat(src, &src_info) == FR_OK) {
|
||||
f_chmod(dst_path, src_info.fattrib, 0x3A);
|
||||
}
|
||||
log_write(" Done: %d files, %d subdirs\n", file_count, dir_count);
|
||||
}
|
||||
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* OmniNX Installer - Filesystem operations
|
||||
* Based on HATS Installer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <utils/types.h>
|
||||
|
||||
// Error code to string
|
||||
const char *fs_error_str(int err);
|
||||
|
||||
// File/folder operations - returns 0 on success
|
||||
int file_copy(const char *src, const char *dst);
|
||||
int folder_copy(const char *src, const char *dst);
|
||||
int folder_delete(const char *path);
|
||||
|
||||
// File logging
|
||||
void log_init(const char *path);
|
||||
void log_close(void);
|
||||
void log_write(const char *fmt, ...);
|
||||
/*
|
||||
* OmniNX Installer - Filesystem operations
|
||||
* Based on HATS Installer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <utils/types.h>
|
||||
|
||||
// Error code to string
|
||||
const char *fs_error_str(int err);
|
||||
|
||||
// File/folder operations - returns 0 on success
|
||||
int file_copy(const char *src, const char *dst);
|
||||
int folder_copy(const char *src, const char *dst);
|
||||
int folder_delete(const char *path);
|
||||
|
||||
// File logging
|
||||
void log_init(const char *path);
|
||||
void log_close(void);
|
||||
void log_write(const char *fmt, ...);
|
||||
+1011
-50
File diff suppressed because it is too large.
Load diff
+8
-1
@@ -13,7 +13,10 @@ typedef enum {
|
||||
INSTALL_MODE_CLEAN // No OmniNX - selective deletion from clean list
|
||||
} install_mode_t;
|
||||
|
||||
// Main installation function
|
||||
// If a sub-menu aborts to Hekate / update.bin (does not return if launch succeeds)
|
||||
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);
|
||||
|
||||
// Update mode operations (install_update.c)
|
||||
@@ -29,12 +32,16 @@ int clean_mode_wipe(void);
|
||||
int clean_mode_restore(void);
|
||||
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
|
||||
void install_set_color(u32 color);
|
||||
void install_check_and_clear_screen_if_needed(void);
|
||||
bool install_path_exists(const char *path);
|
||||
int install_count_directory_items(const char *path);
|
||||
void install_combine_path(char *result, size_t size, const char *base, const char *add);
|
||||
int 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_lists_grouped(const char *folder_display_name, ...);
|
||||
int folder_delete_single_with_progress(const char *path, const char *display_name);
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
// Clean mode: Backup user data
|
||||
int clean_mode_backup(void) {
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf(" Sichere: DBI, Tinfoil, prod.keys\n");
|
||||
gfx_printf(" Sichere: CyberFoil, Checkpoint, DBI (dbi.config), prod.keys\n");
|
||||
set_color(COLOR_WHITE);
|
||||
int res = backup_user_data();
|
||||
if (res == FR_OK) {
|
||||
@@ -95,13 +95,14 @@ int clean_mode_wipe(void) {
|
||||
// Clean mode: Restore user data
|
||||
int clean_mode_restore(void) {
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf(" Stelle wieder her: DBI, Tinfoil, prod.keys\n");
|
||||
gfx_printf(" Stelle wieder her: CyberFoil, Checkpoint, DBI (dbi.config), prod.keys\n");
|
||||
set_color(COLOR_WHITE);
|
||||
int res = restore_user_data();
|
||||
if (res == FR_OK) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" [OK] Wiederherstellung abgeschlossen\n");
|
||||
set_color(COLOR_WHITE);
|
||||
delete_path_list(clean_post_restore_dirs_to_delete, "switch/ (nach Restore)");
|
||||
}
|
||||
cleanup_backup();
|
||||
return res;
|
||||
|
||||
+15
-28
@@ -12,10 +12,6 @@
|
||||
#include <string.h>
|
||||
#include <utils/sprintf.h>
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "1.0.0"
|
||||
#endif
|
||||
|
||||
#undef COLOR_CYAN
|
||||
#undef COLOR_WHITE
|
||||
#undef COLOR_GREEN
|
||||
@@ -37,6 +33,8 @@
|
||||
// Update mode: Cleanup specific directories/files
|
||||
int update_mode_cleanup(omninx_variant_t variant) {
|
||||
(void)variant;
|
||||
install_preserve_update_cleanup_begin();
|
||||
|
||||
check_and_clear_screen_if_needed();
|
||||
|
||||
set_color(COLOR_WHITE);
|
||||
@@ -66,6 +64,8 @@ int update_mode_cleanup(omninx_variant_t variant) {
|
||||
misc_files_to_delete,
|
||||
NULL);
|
||||
|
||||
install_preserve_update_cleanup_end();
|
||||
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" Bereinigung abgeschlossen!\n");
|
||||
set_color(COLOR_WHITE);
|
||||
@@ -78,7 +78,6 @@ int update_mode_install(omninx_variant_t variant) {
|
||||
int res;
|
||||
const char* staging = get_staging_path(variant);
|
||||
char src_path[256];
|
||||
char dst_path[256];
|
||||
|
||||
if (!staging) {
|
||||
return FR_INVALID_PARAMETER;
|
||||
@@ -106,6 +105,10 @@ int update_mode_install(omninx_variant_t variant) {
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/themes", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "themes/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/warmboot_mariko", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
@@ -116,33 +119,17 @@ int update_mode_install(omninx_variant_t variant) {
|
||||
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);
|
||||
gfx_printf(" Kopiere Root-Dateien...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
s_printf(src_path, "%s/boot.dat", staging);
|
||||
s_printf(dst_path, "sd:/boot.dat");
|
||||
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);
|
||||
res = install_copy_staging_root_files(staging, "sd:/");
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" Kopie abgeschlossen!\n");
|
||||
|
||||
+294
-294
@@ -1,294 +1,294 @@
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ FatFs Functional Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 86604 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 2
|
||||
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
|
||||
/
|
||||
/ 0: Disable string functions.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 1
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 1
|
||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||
|
||||
#define FF_FASTFS 0
|
||||
|
||||
#if FF_FASTFS
|
||||
#define FF_USE_FASTSEEK 1
|
||||
#else
|
||||
#define FF_USE_FASTSEEK 0
|
||||
#endif
|
||||
/* This option switches fast seek function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 1
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE 850
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#define FF_USE_LFN 3
|
||||
#define FF_MAX_LFN 255
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be suffcient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
|
||||
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
|
||||
/ This option selects assumption of character encoding ON THE FILE to be
|
||||
/ read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_RPATH 0
|
||||
/* This option configures support for relative path.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() function is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES 4
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 1
|
||||
// Order is important. Any change to order, must also be reflected to diskio drive enum.
|
||||
#define FF_VOLUME_STRS "sd","ram","emmc","bis"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table needs to be defined as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 0
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this function is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ funciton will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
#define FF_MAX_SS 512
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk. But a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
|
||||
/ for variable sector size mode and disk_ioctl() function needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 1
|
||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||
/ option, and f_getfree() function at first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY 0
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 1
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2020
|
||||
/* 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
|
||||
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
|
||||
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_LOCK 0
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
/* #include <somertos.h> // O/S definitions */
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
#define FF_SYNC_t HANDLE
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
|
||||
/ to the same volume is under control of this function.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
|
||||
/ function, must be added to the project. Samples are available in
|
||||
/ option/syscall.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
|
||||
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
|
||||
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
|
||||
/ included somewhere in the scope of ff.h. */
|
||||
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ FatFs Functional Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 86604 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 2
|
||||
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
|
||||
/
|
||||
/ 0: Disable string functions.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 1
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 1
|
||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||
|
||||
#define FF_FASTFS 0
|
||||
|
||||
#if FF_FASTFS
|
||||
#define FF_USE_FASTSEEK 1
|
||||
#else
|
||||
#define FF_USE_FASTSEEK 0
|
||||
#endif
|
||||
/* This option switches fast seek function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 1
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE 850
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#define FF_USE_LFN 3
|
||||
#define FF_MAX_LFN 255
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be suffcient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
|
||||
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
|
||||
/ This option selects assumption of character encoding ON THE FILE to be
|
||||
/ read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_RPATH 0
|
||||
/* This option configures support for relative path.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() function is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES 4
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 1
|
||||
// Order is important. Any change to order, must also be reflected to diskio drive enum.
|
||||
#define FF_VOLUME_STRS "sd","ram","emmc","bis"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table needs to be defined as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 0
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this function is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ funciton will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
#define FF_MAX_SS 512
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk. But a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
|
||||
/ for variable sector size mode and disk_ioctl() function needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 1
|
||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||
/ option, and f_getfree() function at first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY 0
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 1
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_NORTC_MON 10
|
||||
#define FF_NORTC_MDAY 16
|
||||
#define FF_NORTC_YEAR 2026
|
||||
/* 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
|
||||
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
|
||||
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_LOCK 0
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
/* #include <somertos.h> // O/S definitions */
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
#define FF_SYNC_t HANDLE
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
|
||||
/ to the same volume is under control of this function.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
|
||||
/ function, must be added to the project. Samples are available in
|
||||
/ option/syscall.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
|
||||
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
|
||||
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
|
||||
/ included somewhere in the scope of ff.h. */
|
||||
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
+190
-43
@@ -6,10 +6,6 @@
|
||||
* Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue
|
||||
*/
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "1.0.0"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <display/di.h>
|
||||
@@ -35,6 +31,7 @@
|
||||
#include "fs.h"
|
||||
#include "version.h"
|
||||
#include "install.h"
|
||||
#include "screenshot.h"
|
||||
|
||||
// Configuration
|
||||
#define PAYLOAD_PATH "sd:/bootloader/update.bin"
|
||||
@@ -115,6 +112,12 @@ static void print_header(void) {
|
||||
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) {
|
||||
memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ);
|
||||
@@ -138,7 +141,7 @@ static int launch_payload(const char *path) {
|
||||
if (sd_mount()) {
|
||||
FIL fp;
|
||||
if (f_open(&fp, path, FA_READ)) {
|
||||
gfx_printf("Payload nicht gefunden: %s\n", path);
|
||||
gfx_printf("Hekate nicht gefunden: %s\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -182,6 +185,12 @@ static int file_exists(const char *path) {
|
||||
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);
|
||||
|
||||
void ipl_main(void) {
|
||||
@@ -243,11 +252,13 @@ void ipl_main(void) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
||||
gfx_printf("um Hekate zu starten...\n");
|
||||
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
|
||||
set_color(COLOR_WHITE);
|
||||
} else {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
|
||||
gfx_printf("um den Neustart zu starten...\n");
|
||||
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
|
||||
set_color(COLOR_WHITE);
|
||||
}
|
||||
|
||||
@@ -264,13 +275,21 @@ void ipl_main(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check joycon A button
|
||||
// Check joycon A button; Capture = screenshot
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
if (jc && jc->a) {
|
||||
if (jc) {
|
||||
if (jc->cap)
|
||||
take_screenshot();
|
||||
if (jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cancel_combo_pressed(btn_state)) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
msleep(50); // Small delay to avoid busy-waiting
|
||||
}
|
||||
|
||||
@@ -278,7 +297,7 @@ void ipl_main(void) {
|
||||
if (file_exists(PAYLOAD_PATH)) {
|
||||
gfx_printf("\n");
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf("Payload wird gestartet...\n");
|
||||
gfx_printf("Hekate wird gestartet...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
msleep(500);
|
||||
launch_payload(PAYLOAD_PATH);
|
||||
@@ -324,12 +343,14 @@ void ipl_main(void) {
|
||||
gfx_printf("\n");
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n");
|
||||
gfx_printf("Vol+ und Vol- gleichzeitig: Abbrechen (Hekate)\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
// Edge detection: only move on press (not while held)
|
||||
bool prev_up = false, prev_down = false;
|
||||
bool menu_aborted = false;
|
||||
|
||||
while (!confirmed) {
|
||||
while (!confirmed && !menu_aborted) {
|
||||
// On selection change: redraw only the two affected lines (no full clear)
|
||||
if (selected != prev_selected) {
|
||||
gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16);
|
||||
@@ -345,6 +366,14 @@ void ipl_main(void) {
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
u8 btn = btn_read();
|
||||
|
||||
if (jc && jc->cap)
|
||||
take_screenshot();
|
||||
|
||||
if (cancel_combo_pressed(btn)) {
|
||||
menu_aborted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down)
|
||||
bool cur_up = false, cur_down = false;
|
||||
if (jc) {
|
||||
@@ -369,6 +398,20 @@ void ipl_main(void) {
|
||||
|
||||
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];
|
||||
gfx_clear_grey(0x1B);
|
||||
@@ -395,14 +438,19 @@ void ipl_main(void) {
|
||||
gfx_printf("mit angeschlossenem Ladegeraet durchgefuehrt werden.\n\n");
|
||||
set_color(COLOR_YELLOW);
|
||||
gfx_printf("Stecke das Ladegeraet an und warte...\n");
|
||||
gfx_printf("Oder druecke + und - zum Abbrechen.\n");
|
||||
gfx_printf("Oder Vol+ und Vol- (Konsole) zum Abbrechen.\n");
|
||||
set_color(COLOR_WHITE);
|
||||
jc_init_hw();
|
||||
while (btn_read() & BTN_POWER) { msleep(50); }
|
||||
bool user_cancelled = false;
|
||||
while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected() && !user_cancelled) {
|
||||
u8 pbtn = btn_read();
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
if (jc && jc->plus && jc->minus) {
|
||||
if (jc) {
|
||||
if (jc->cap)
|
||||
take_screenshot();
|
||||
}
|
||||
if (cancel_combo_pressed(pbtn)) {
|
||||
user_cancelled = true;
|
||||
break;
|
||||
}
|
||||
@@ -428,7 +476,90 @@ void ipl_main(void) {
|
||||
gfx_con_setpos(0, 0);
|
||||
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
|
||||
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);
|
||||
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");
|
||||
gfx_printf("Paket-Variante: %s\n", get_variant_name(pack_variant));
|
||||
@@ -437,6 +568,17 @@ void ipl_main(void) {
|
||||
} else {
|
||||
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);
|
||||
gfx_printf("\n");
|
||||
|
||||
@@ -453,7 +595,7 @@ void ipl_main(void) {
|
||||
gfx_printf("um die Installation zu starten...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf("Druecke + und - gleichzeitig zum Abbrechen (zurueck zu Hekate).\n");
|
||||
gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
// Wait for A/Power to start, or +/- to cancel
|
||||
@@ -473,18 +615,19 @@ void ipl_main(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check joycon buttons
|
||||
// Check joycon buttons; Capture = screenshot
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
if (jc) {
|
||||
if (jc->cap)
|
||||
take_screenshot();
|
||||
if (jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
}
|
||||
// + and - simultaneously = cancel, return to hekate
|
||||
if (jc->plus && jc->minus) {
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cancel_combo_pressed(btn_state)) {
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
msleep(50); // Small delay to avoid busy-waiting
|
||||
@@ -520,32 +663,28 @@ void ipl_main(void) {
|
||||
int result = perform_installation(pack_variant, mode);
|
||||
|
||||
// Wait 3 seconds before clearing screen to allow errors to be visible
|
||||
msleep(3000);
|
||||
|
||||
msleep(1500);
|
||||
// take_screenshot(); // Take a screenshot of the installation summary
|
||||
|
||||
// Clear screen for final summary to ensure it's visible
|
||||
gfx_clear_grey(0x1B);
|
||||
gfx_con_setpos(0, 0);
|
||||
|
||||
// Summary
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf("========================================\n");
|
||||
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
|
||||
gfx_printf("========================================\n\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
const char *op_name = (mode == INSTALL_MODE_UPDATE) ? " Update" : " Installation";
|
||||
if (result == FR_OK && total_errors == 0) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf("========================================\n");
|
||||
gfx_printf(" Installation abgeschlossen!\n");
|
||||
gfx_printf("========================================\n");
|
||||
gfx_printf("=======================================\n");
|
||||
gfx_printf(" %s abgeschlossen!\n", op_name);
|
||||
gfx_printf("=======================================\n");
|
||||
} else {
|
||||
set_color(COLOR_RED);
|
||||
gfx_printf("========================================\n");
|
||||
gfx_printf(" Installation beendet\n");
|
||||
gfx_printf("=======================================\n");
|
||||
gfx_printf(" %s beendet\n", op_name);
|
||||
if (total_errors > 0) {
|
||||
gfx_printf(" %d Fehler\n", total_errors);
|
||||
}
|
||||
gfx_printf("========================================\n");
|
||||
gfx_printf("=======================================\n");
|
||||
}
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
@@ -573,11 +712,15 @@ void ipl_main(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check joycon A button
|
||||
// Check joycon A button; Capture = screenshot
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
if (jc && jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
if (jc) {
|
||||
if (jc->cap)
|
||||
take_screenshot();
|
||||
if (jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
msleep(50); // Small delay to avoid busy-waiting
|
||||
@@ -585,16 +728,16 @@ void ipl_main(void) {
|
||||
|
||||
gfx_printf("\n");
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf("Payload wird gestartet...\n");
|
||||
gfx_printf("Hekate wird gestartet...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
msleep(500); // Brief delay before launch
|
||||
|
||||
if (file_exists(PAYLOAD_PATH)) {
|
||||
launch_payload(PAYLOAD_PATH);
|
||||
} else {
|
||||
// Payload not found, show error
|
||||
// Hekate (update.bin) not found, show error
|
||||
set_color(COLOR_RED);
|
||||
gfx_printf("\nFEHLER: Payload nicht gefunden!\n");
|
||||
gfx_printf("\nFEHLER: Hekate nicht gefunden!\n");
|
||||
gfx_printf("Pfad: %s\n", PAYLOAD_PATH);
|
||||
set_color(COLOR_WHITE);
|
||||
gfx_printf("\nDruecke A oder Power zum Neustart...\n");
|
||||
@@ -615,9 +758,13 @@ void ipl_main(void) {
|
||||
}
|
||||
|
||||
jc_gamepad_rpt_t *jc = joycon_poll();
|
||||
if (jc && jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
if (jc) {
|
||||
if (jc->cap)
|
||||
take_screenshot();
|
||||
if (jc->a) {
|
||||
button_pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
msleep(50);
|
||||
|
||||
+233
-228
@@ -1,228 +1,233 @@
|
||||
/*
|
||||
* Copyright (c) 2018 naehrwert
|
||||
* Copyright (c) 2018-2019 CTCaer
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <storage/nx_sd.h>
|
||||
#include <storage/sdmmc.h>
|
||||
#include <storage/sdmmc_driver.h>
|
||||
#include <gfx_utils.h>
|
||||
#include <libs/fatfs/ff.h>
|
||||
#include <mem/heap.h>
|
||||
|
||||
bool sd_mounted = false;
|
||||
static u16 sd_errors[3] = { 0 }; // Init and Read/Write errors.
|
||||
static u32 sd_mode = SD_UHS_SDR82;
|
||||
|
||||
sdmmc_t sd_sdmmc;
|
||||
sdmmc_storage_t sd_storage;
|
||||
FATFS sd_fs;
|
||||
|
||||
void sd_error_count_increment(u8 type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SD_ERROR_INIT_FAIL:
|
||||
sd_errors[0]++;
|
||||
break;
|
||||
case SD_ERROR_RW_FAIL:
|
||||
sd_errors[1]++;
|
||||
break;
|
||||
case SD_ERROR_RW_RETRY:
|
||||
sd_errors[2]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u16 *sd_get_error_count()
|
||||
{
|
||||
return sd_errors;
|
||||
}
|
||||
|
||||
bool sd_get_card_removed()
|
||||
{
|
||||
if (!sdmmc_get_sd_inserted())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 sd_get_mode()
|
||||
{
|
||||
return sd_mode;
|
||||
}
|
||||
|
||||
int sd_init_retry(bool power_cycle)
|
||||
{
|
||||
u32 bus_width = SDMMC_BUS_WIDTH_4;
|
||||
u32 type = SDHCI_TIMING_UHS_SDR82;
|
||||
|
||||
// Power cycle SD card.
|
||||
if (power_cycle)
|
||||
{
|
||||
sd_mode--;
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
}
|
||||
|
||||
// Get init parameters.
|
||||
switch (sd_mode)
|
||||
{
|
||||
case SD_INIT_FAIL: // Reset to max.
|
||||
return 0;
|
||||
case SD_1BIT_HS25:
|
||||
bus_width = SDMMC_BUS_WIDTH_1;
|
||||
type = SDHCI_TIMING_SD_HS25;
|
||||
break;
|
||||
case SD_4BIT_HS25:
|
||||
type = SDHCI_TIMING_SD_HS25;
|
||||
break;
|
||||
case SD_UHS_SDR82:
|
||||
type = SDHCI_TIMING_UHS_SDR82;
|
||||
break;
|
||||
default:
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
}
|
||||
|
||||
return sdmmc_storage_init_sd(&sd_storage, &sd_sdmmc, bus_width, type);
|
||||
}
|
||||
|
||||
bool sd_initialize(bool power_cycle)
|
||||
{
|
||||
if (power_cycle)
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
|
||||
int res = !sd_init_retry(false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!res)
|
||||
return true;
|
||||
else if (!sdmmc_get_sd_inserted()) // SD Card is not inserted.
|
||||
{
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
sd_errors[SD_ERROR_INIT_FAIL]++;
|
||||
|
||||
if (sd_mode == SD_INIT_FAIL)
|
||||
break;
|
||||
else
|
||||
res = !sd_init_retry(true);
|
||||
}
|
||||
}
|
||||
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_sd_inited = false;
|
||||
|
||||
bool sd_mount()
|
||||
{
|
||||
if (sd_mounted)
|
||||
return true;
|
||||
|
||||
int res = !sd_initialize(false);
|
||||
is_sd_inited = !res;
|
||||
|
||||
if (res)
|
||||
{
|
||||
gfx_con.mute = false;
|
||||
EPRINTF("Failed to init SD card.");
|
||||
if (!sdmmc_get_sd_inserted())
|
||||
EPRINTF("Make sure that it is inserted.");
|
||||
else
|
||||
EPRINTF("SD Card Reader is not properly seated!");
|
||||
}
|
||||
else
|
||||
{
|
||||
res = f_mount(&sd_fs, "", 1);
|
||||
if (res == FR_OK)
|
||||
{
|
||||
sd_mounted = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx_con.mute = false;
|
||||
EPRINTFARGS("Failed to mount SD card (FatFS Error %d).\nMake sure that a FAT partition exists..", res);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _sd_deinit()
|
||||
{
|
||||
if (sd_mode == SD_INIT_FAIL)
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
|
||||
if (sd_mounted)
|
||||
{
|
||||
f_mount(NULL, "", 1);
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
sd_mounted = false;
|
||||
is_sd_inited = false;
|
||||
}
|
||||
}
|
||||
|
||||
void sd_unmount() { _sd_deinit(); }
|
||||
void sd_end() { _sd_deinit(); }
|
||||
|
||||
void *sd_file_read(const char *path, u32 *fsize)
|
||||
{
|
||||
FIL fp;
|
||||
if (f_open(&fp, path, FA_READ) != FR_OK)
|
||||
return NULL;
|
||||
|
||||
u32 size = f_size(&fp);
|
||||
if (fsize)
|
||||
*fsize = size;
|
||||
|
||||
char *buf = malloc(size + 1);
|
||||
buf[size] = '\0';
|
||||
|
||||
if (f_read(&fp, buf, size, NULL) != FR_OK)
|
||||
{
|
||||
free(buf);
|
||||
f_close(&fp);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f_close(&fp);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
int sd_save_to_file(void *buf, u32 size, const char *filename)
|
||||
{
|
||||
FIL fp;
|
||||
u32 res = 0;
|
||||
res = f_open(&fp, filename, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res)
|
||||
{
|
||||
EPRINTFARGS("Error (%d) creating file\n%s.\n", res, filename);
|
||||
return res;
|
||||
}
|
||||
|
||||
f_write(&fp, buf, size, NULL);
|
||||
f_close(&fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2018 naehrwert
|
||||
* Copyright (c) 2018-2019 CTCaer
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <storage/nx_sd.h>
|
||||
#include <storage/sdmmc.h>
|
||||
#include <storage/sdmmc_driver.h>
|
||||
#include <gfx_utils.h>
|
||||
#include <libs/fatfs/ff.h>
|
||||
#include <mem/heap.h>
|
||||
|
||||
bool sd_mounted = false;
|
||||
static u16 sd_errors[3] = { 0 }; // Init and Read/Write errors.
|
||||
static u32 sd_mode = SD_UHS_SDR82;
|
||||
|
||||
sdmmc_t sd_sdmmc;
|
||||
sdmmc_storage_t sd_storage;
|
||||
FATFS sd_fs;
|
||||
|
||||
void sd_error_count_increment(u8 type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SD_ERROR_INIT_FAIL:
|
||||
sd_errors[0]++;
|
||||
break;
|
||||
case SD_ERROR_RW_FAIL:
|
||||
sd_errors[1]++;
|
||||
break;
|
||||
case SD_ERROR_RW_RETRY:
|
||||
sd_errors[2]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u16 *sd_get_error_count()
|
||||
{
|
||||
return sd_errors;
|
||||
}
|
||||
|
||||
bool sd_get_card_removed()
|
||||
{
|
||||
if (!sdmmc_get_sd_inserted())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 sd_get_mode()
|
||||
{
|
||||
return sd_mode;
|
||||
}
|
||||
|
||||
int sd_init_retry(bool power_cycle)
|
||||
{
|
||||
u32 bus_width = SDMMC_BUS_WIDTH_4;
|
||||
u32 type = SDHCI_TIMING_UHS_SDR82;
|
||||
|
||||
// Power cycle SD card.
|
||||
if (power_cycle)
|
||||
{
|
||||
sd_mode--;
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
}
|
||||
|
||||
// Get init parameters.
|
||||
switch (sd_mode)
|
||||
{
|
||||
case SD_INIT_FAIL: // Reset to max.
|
||||
return 0;
|
||||
case SD_1BIT_HS25:
|
||||
bus_width = SDMMC_BUS_WIDTH_1;
|
||||
type = SDHCI_TIMING_SD_HS25;
|
||||
break;
|
||||
case SD_4BIT_HS25:
|
||||
type = SDHCI_TIMING_SD_HS25;
|
||||
break;
|
||||
case SD_UHS_SDR82:
|
||||
type = SDHCI_TIMING_UHS_SDR82;
|
||||
break;
|
||||
default:
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
}
|
||||
|
||||
return sdmmc_storage_init_sd(&sd_storage, &sd_sdmmc, bus_width, type);
|
||||
}
|
||||
|
||||
bool sd_initialize(bool power_cycle)
|
||||
{
|
||||
if (power_cycle)
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
|
||||
int res = !sd_init_retry(false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!res)
|
||||
return true;
|
||||
else if (!sdmmc_get_sd_inserted()) // SD Card is not inserted.
|
||||
{
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
sd_errors[SD_ERROR_INIT_FAIL]++;
|
||||
|
||||
if (sd_mode == SD_INIT_FAIL)
|
||||
break;
|
||||
else
|
||||
res = !sd_init_retry(true);
|
||||
}
|
||||
}
|
||||
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_sd_inited = false;
|
||||
|
||||
bool sd_mount()
|
||||
{
|
||||
if (sd_mounted)
|
||||
return true;
|
||||
|
||||
int res = !sd_initialize(false);
|
||||
is_sd_inited = !res;
|
||||
|
||||
if (res)
|
||||
{
|
||||
gfx_con.mute = false;
|
||||
EPRINTF("Failed to init SD card.");
|
||||
if (!sdmmc_get_sd_inserted())
|
||||
EPRINTF("Make sure that it is inserted.");
|
||||
else
|
||||
EPRINTF("SD Card Reader is not properly seated!");
|
||||
}
|
||||
else
|
||||
{
|
||||
res = f_mount(&sd_fs, "", 1);
|
||||
if (res == FR_OK)
|
||||
{
|
||||
sd_mounted = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx_con.mute = false;
|
||||
EPRINTFARGS("Failed to mount SD card (FatFS Error %d).\nMake sure that a FAT partition exists..", res);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _sd_deinit()
|
||||
{
|
||||
if (sd_mode == SD_INIT_FAIL)
|
||||
sd_mode = SD_UHS_SDR82;
|
||||
|
||||
if (sd_mounted)
|
||||
{
|
||||
f_mount(NULL, "", 1);
|
||||
sdmmc_storage_end(&sd_storage);
|
||||
sd_mounted = false;
|
||||
is_sd_inited = false;
|
||||
}
|
||||
}
|
||||
|
||||
void sd_unmount() { _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)
|
||||
{
|
||||
FIL fp;
|
||||
if (f_open(&fp, path, FA_READ) != FR_OK)
|
||||
return NULL;
|
||||
|
||||
u32 size = f_size(&fp);
|
||||
if (fsize)
|
||||
*fsize = size;
|
||||
|
||||
char *buf = malloc(size + 1);
|
||||
buf[size] = '\0';
|
||||
|
||||
if (f_read(&fp, buf, size, NULL) != FR_OK)
|
||||
{
|
||||
free(buf);
|
||||
f_close(&fp);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f_close(&fp);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
int sd_save_to_file(void *buf, u32 size, const char *filename)
|
||||
{
|
||||
FIL fp;
|
||||
u32 res = 0;
|
||||
res = f_open(&fp, filename, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res)
|
||||
{
|
||||
EPRINTFARGS("Error (%d) creating file\n%s.\n", res, filename);
|
||||
return res;
|
||||
}
|
||||
|
||||
f_write(&fp, buf, size, NULL);
|
||||
f_close(&fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Screenshot support for OmniNX Installer Payload
|
||||
* Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation.
|
||||
*/
|
||||
|
||||
#include "screenshot.h"
|
||||
#include "gfx.h"
|
||||
#include "nx_sd.h"
|
||||
#include <libs/fatfs/ff.h>
|
||||
#include <mem/heap.h>
|
||||
#include <display/di.h>
|
||||
#include <utils/util.h>
|
||||
#include <utils/sprintf.h>
|
||||
#include <rtc/max77620-rtc.h>
|
||||
#include <string.h>
|
||||
|
||||
/* BMP file header (54 bytes), matching TegraExplorer tools.h */
|
||||
typedef struct __attribute__((packed)) _bmp_hdr_t
|
||||
{
|
||||
u16 magic;
|
||||
u32 size;
|
||||
u32 rsvd;
|
||||
u32 data_off;
|
||||
u32 hdr_size;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u16 planes;
|
||||
u16 pxl_bits;
|
||||
u32 comp;
|
||||
u32 img_size;
|
||||
u32 res_h;
|
||||
u32 res_v;
|
||||
u64 rsvd2;
|
||||
} bmp_hdr_t;
|
||||
|
||||
#define BMP_HEADER_SIZE 0x36
|
||||
#define FB_SIZE 0x384000 /* 1280 * 720 * 4 */
|
||||
#define SCREEN_W 1280
|
||||
#define SCREEN_H 720
|
||||
|
||||
void take_screenshot(void)
|
||||
{
|
||||
static u32 last_timer = 0;
|
||||
|
||||
if (!sd_get_card_mounted())
|
||||
return;
|
||||
|
||||
/* 3-second cooldown (same as TegraExplorer) */
|
||||
u32 now = get_tmr_s();
|
||||
if (last_timer != 0 && now < last_timer + 3)
|
||||
return;
|
||||
last_timer = now;
|
||||
|
||||
const char basepath[] = "sd:/switch/screenshot";
|
||||
char name[48];
|
||||
char path[80];
|
||||
rtc_time_t rtc;
|
||||
|
||||
max77620_rtc_get_time(&rtc);
|
||||
s_printf(name, "omninx_installer_%04d%02d%02d_%02d%02d%02d.bmp",
|
||||
(u32)rtc.year, (u32)rtc.month, (u32)rtc.day,
|
||||
(u32)rtc.hour, (u32)rtc.min, (u32)rtc.sec);
|
||||
s_printf(path, "%s/%s", basepath, name);
|
||||
|
||||
f_mkdir("sd:/switch");
|
||||
f_mkdir(basepath);
|
||||
|
||||
const u32 file_size = BMP_HEADER_SIZE + FB_SIZE;
|
||||
u8 *bitmap = (u8 *)malloc(file_size);
|
||||
u32 *fb_copy = (u32 *)malloc(FB_SIZE);
|
||||
if (!bitmap || !fb_copy) {
|
||||
if (bitmap) free(bitmap);
|
||||
if (fb_copy) free(fb_copy);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy framebuffer with same coordinate flip as TegraExplorer (BGR layout) */
|
||||
u32 *fb_ptr = gfx_ctxt.fb;
|
||||
for (int x = SCREEN_W - 1; x >= 0; x--) {
|
||||
for (int y = SCREEN_H - 1; y >= 0; y--)
|
||||
fb_copy[y * SCREEN_W + x] = *fb_ptr++;
|
||||
}
|
||||
|
||||
memcpy(bitmap + BMP_HEADER_SIZE, fb_copy, FB_SIZE);
|
||||
|
||||
bmp_hdr_t *bmp = (bmp_hdr_t *)bitmap;
|
||||
bmp->magic = 0x4D42;
|
||||
bmp->size = file_size;
|
||||
bmp->rsvd = 0;
|
||||
bmp->data_off = BMP_HEADER_SIZE;
|
||||
bmp->hdr_size = 40;
|
||||
bmp->width = SCREEN_W;
|
||||
bmp->height = SCREEN_H;
|
||||
bmp->planes = 1;
|
||||
bmp->pxl_bits = 32;
|
||||
bmp->comp = 0;
|
||||
bmp->img_size = FB_SIZE;
|
||||
bmp->res_h = 2834;
|
||||
bmp->res_v = 2834;
|
||||
bmp->rsvd2 = 0;
|
||||
|
||||
sd_save_to_file(bitmap, file_size, path);
|
||||
|
||||
free(bitmap);
|
||||
free(fb_copy);
|
||||
|
||||
/* Brief backlight flash (same as TegraExplorer) */
|
||||
display_backlight_brightness(255, 1000);
|
||||
msleep(100);
|
||||
display_backlight_brightness(100, 1000);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Screenshot support for OmniNX Installer Payload
|
||||
* Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/types.h>
|
||||
|
||||
/* Take a screenshot of the current framebuffer and save as BMP to SD.
|
||||
* Triggered by Capture button (jc->cap). Uses 3-second cooldown.
|
||||
* Saves to sd:/switch/screenshot/omninx_installer_YYYYMMDD_HHMMSS.bmp */
|
||||
void take_screenshot(void);
|
||||
+5
-1
@@ -78,8 +78,12 @@ static omninx_variant_t read_manifest_variant(const char *manifest_path) {
|
||||
}
|
||||
|
||||
// Read manifest.ini and extract version string
|
||||
static void read_manifest_version(const char *manifest_path, char *version_buf, size_t buf_size) {
|
||||
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';
|
||||
if (!manifest_path)
|
||||
return;
|
||||
|
||||
link_t sections;
|
||||
list_init(§ions);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <utils/types.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// OmniNX variants
|
||||
typedef enum {
|
||||
@@ -37,3 +38,6 @@ const char* get_staging_path(omninx_variant_t variant);
|
||||
|
||||
// Get manifest.ini path for variant (in staging directory)
|
||||
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);
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Minimal RAM info test payload (fuse DRAM ID + SK table -> MiB).
|
||||
* Build: make ram-test -> output/RAM-Test.bin
|
||||
*/
|
||||
|
||||
#include <display/di.h>
|
||||
#include <mem/heap.h>
|
||||
#include <mem/minerva.h>
|
||||
#include <memory_map.h>
|
||||
#include <soc/bpmp.h>
|
||||
#include <soc/fuse.h>
|
||||
#include <soc/hw_init.h>
|
||||
#include <soc/t210.h>
|
||||
#include <utils/util.h>
|
||||
|
||||
#include "dram_fuse.h"
|
||||
#include "gfx.h"
|
||||
|
||||
#undef COLOR_CYAN
|
||||
#undef COLOR_WHITE
|
||||
#define COLOR_CYAN 0xFF00FFFF
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
|
||||
/* Required by BDK */
|
||||
boot_cfg_t __attribute__((section("._boot_cfg"))) b_cfg;
|
||||
volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR;
|
||||
|
||||
extern void pivot_stack(u32 stack_top);
|
||||
|
||||
void ipl_main(void)
|
||||
{
|
||||
hw_init();
|
||||
pivot_stack(IPL_STACK_TOP);
|
||||
heap_init(IPL_HEAP_START);
|
||||
|
||||
minerva_init();
|
||||
minerva_change_freq(FREQ_800);
|
||||
|
||||
display_init();
|
||||
u32 *fb = display_init_framebuffer_pitch();
|
||||
gfx_init_ctxt(fb, 720, 1280, 720);
|
||||
gfx_con_init();
|
||||
display_backlight_pwm_init();
|
||||
display_backlight_brightness(100, 1000);
|
||||
|
||||
bpmp_clk_rate_set(BPMP_CLK_DEFAULT_BOOST);
|
||||
|
||||
gfx_clear_grey(0x1B);
|
||||
gfx_con_setpos(0, 0);
|
||||
gfx_con_setcol(COLOR_CYAN, gfx_con.fillbg, gfx_con.bgcol);
|
||||
gfx_printf("RAM test payload\n\n");
|
||||
gfx_con_setcol(COLOR_WHITE, gfx_con.fillbg, gfx_con.bgcol);
|
||||
|
||||
u32 raw = fuse_read_dramid(true);
|
||||
u32 nid = fuse_read_dramid(false);
|
||||
u32 chip = hw_get_chip_id();
|
||||
int mib = dram_capacity_mib_from_fuse();
|
||||
|
||||
gfx_printf("SoC: %s\n", chip == GP_HIDREV_MAJOR_T210B01 ? "Mariko (T210B01)" : "Erista (T210)");
|
||||
gfx_printf("DRAM fuse: raw %d norm %d\n", raw, nid);
|
||||
if (mib > 0)
|
||||
gfx_printf("Table MiB: %d (fuse SKU, not probe)\n", mib);
|
||||
else
|
||||
gfx_printf("Table MiB: (unknown id for this SoC)\n");
|
||||
|
||||
gfx_printf("\nHang — power off or inject payload.\n");
|
||||
|
||||
while (1)
|
||||
msleep(500);
|
||||
}
|
||||
Reference in new issue
Block a user