Compare commits

...

34 Commits

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

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

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

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

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

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

Made-with: Cursor
2026-03-01 14:43:58 +01:00
e71fb13bfc Update: preserve UltraHand .offload state (overlays/packages)
All checks were successful
Build / Build (push) Successful in 10s
- Add folder_copy_switch_update_offload_aware() for update-only switch copy
- If item exists in .overlays/.offload or .packages/.offload, update in place; else copy to main
- Remove sd:/switch/.overlays from update delete list so .offload survives
- Fresh install unchanged (no offload logic)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 21:01:36 +01:00
5bcd3987a2 UHS warning: recommend Samsung EVO Plus, EVO Select, PRO Plus
All checks were successful
Build / Build (push) Successful in 11s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 20:28:15 +01:00
15b7cb1f4c Add battery run protection: require charger below 10%
All checks were successful
Build / Build (push) Successful in 10s
- Add bq24193_charger_connected() for charger detection
- Block install when battery < 10% and charger not plugged in
- Show clear screen after charger detected before continuing
- User can cancel with + and - to return to Hekate

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 15:12:06 +01:00
4b124a9998 Add + and - simultaneously to cancel and return to Hekate before install
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 15:03:58 +01:00
89279e27d5 Updated CI
All checks were successful
Build / Build (push) Successful in 10s
2026-02-13 22:13:29 +01:00
4ae286d21a Updated CI
All checks were successful
Build / Build (push) Successful in 11s
2026-02-13 22:11:42 +01:00
880a9af105 Updated CI
Some checks failed
Build / build (push) Failing after 3s
2026-02-13 22:09:31 +01:00
7fac0e50bb Updated CI
Some checks failed
Build / build (push) Failing after 3s
2026-02-13 22:08:40 +01:00
2c32f19262 Removed .packages from Update to be deleted 2026-02-11 17:58:32 +01:00
4effcf513b Updated Notice 2026-02-11 17:56:31 +01:00
6b04eb5d89 Fixed typo 2026-02-11 17:47:00 +01:00
ed93c32131 Updated Readme
- Added notice for proper use of this payload\n- Fixed some explenations
2026-02-11 17:17:37 +01:00
bae5ecebf1 Updated CI
All checks were successful
Build and Release / build-and-release (push) Successful in 10s
2026-02-11 17:11:18 +01:00
ef16df416b Updated CI
All checks were successful
Build and Release / build-and-release (push) Successful in 9s
2026-02-11 17:09:04 +01:00
f81cd8e381 Updated CI
Some checks failed
Build and Release / build-and-release (push) Failing after 13s
2026-02-11 17:07:40 +01:00
20 changed files with 1839 additions and 1025 deletions

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

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
# OmniNX Installer Payload # 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. 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 [TegraExplorer](https://github.com/shchmue/TegraExplorer) and [hekate](https://github.com/CTCaer/hekate) by CTCaer, naehrwert, and shchmue.
@@ -56,11 +58,7 @@ The built payload will be output to `output/OmniNX-Installer.bin`.
### CI / GitHub Actions ### CI / GitHub Actions
Pushing a version tag (e.g. `v1.0.0`) triggers a build and creates a release with the `.bin` file attached. Every push triggers a build. The `.bin` file is produced and attached as a workflow artifact (Actions tab → select run → Artifacts).
**GitHub:** Uses the built-in `GITHUB_TOKEN` automatically.
**Gitea:** Add a `GITHUB_TOKEN` secret (Settings → Secrets and Variables → Actions) with a token that has permission to create releases.
## Usage ## Usage
@@ -119,11 +117,11 @@ Use hekate or another bootloader to launch the payload:
The payload supports three OmniNX variants: The payload supports three OmniNX variants:
- **Standard** (`1.0.0s`): Full CFW pack - **Standard** `sd:/OmniNX Standard/`
- **Light** (`1.0.0l`): Lightweight CFW pack - **Light** `sd:/OmniNX Light/`
- **OC** (`1.0.0oc`): Overclock-enabled CFW pack (includes SaltySD) - **OC** `sd:/OmniNX OC/`
The payload automatically detects which variant is present on the SD card and installs accordingly. The payload detects the pack variant from the staging directory presence and detects the current installation (variant + version) from `sd:/config/omninx/manifest.ini` (`current_pack` and `version` keys).
## Project Structure ## Project Structure

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

46
source/dram_fuse.c Normal file
View File

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

6
source/dram_fuse.h Normal file
View File

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

View File

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

View File

@@ -13,6 +13,9 @@ typedef enum {
INSTALL_MODE_CLEAN // No OmniNX - selective deletion from clean list INSTALL_MODE_CLEAN // No OmniNX - selective deletion from clean list
} install_mode_t; } install_mode_t;
// If a sub-menu aborts to Hekate / update.bin (does not return if launch succeeds)
void installer_launch_hekate_payload(void);
// Main installation function // Main installation function
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode); int perform_installation(omninx_variant_t pack_variant, install_mode_t mode);
@@ -35,6 +38,7 @@ void install_check_and_clear_screen_if_needed(void);
bool install_path_exists(const char *path); bool install_path_exists(const char *path);
int install_count_directory_items(const char *path); int install_count_directory_items(const char *path);
void install_combine_path(char *result, size_t size, const char *base, const char *add); void install_combine_path(char *result, size_t size, const char *base, const char *add);
int install_copy_staging_root_files(const char *staging, const char *dst_root);
int delete_path_list(const char* paths[], const char* description); int delete_path_list(const char* paths[], const char* description);
int delete_path_lists_grouped(const char *folder_display_name, ...); int delete_path_lists_grouped(const char *folder_display_name, ...);
int folder_delete_single_with_progress(const char *path, const char *display_name); int folder_delete_single_with_progress(const char *path, const char *display_name);

View File

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

View File

@@ -18,6 +18,8 @@
#include <mem/heap.h> #include <mem/heap.h>
#include <mem/minerva.h> #include <mem/minerva.h>
#include <power/max77620.h> #include <power/max77620.h>
#include <power/max17050.h>
#include <power/bq24193.h>
#include <soc/bpmp.h> #include <soc/bpmp.h>
#include <soc/fuse.h> #include <soc/fuse.h>
#include <soc/hw_init.h> #include <soc/hw_init.h>
@@ -33,6 +35,7 @@
#include "fs.h" #include "fs.h"
#include "version.h" #include "version.h"
#include "install.h" #include "install.h"
#include "screenshot.h"
// Configuration // Configuration
#define PAYLOAD_PATH "sd:/bootloader/update.bin" #define PAYLOAD_PATH "sd:/bootloader/update.bin"
@@ -113,6 +116,12 @@ static void print_header(void) {
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
} }
/* Console Vol+ and Vol- together (exit / cancel where documented) */
static inline bool cancel_combo_pressed(u8 btn)
{
return (btn & (BTN_VOL_UP | BTN_VOL_DOWN)) == (BTN_VOL_UP | BTN_VOL_DOWN);
}
void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) { void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) {
memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ); memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ);
@@ -180,6 +189,12 @@ static int file_exists(const char *path) {
return (f_stat(path, &fno) == FR_OK); return (f_stat(path, &fno) == FR_OK);
} }
void installer_launch_hekate_payload(void) {
if (file_exists(PAYLOAD_PATH))
launch_payload(PAYLOAD_PATH);
power_set_state(POWER_OFF_REBOOT);
}
extern void pivot_stack(u32 stack_top); extern void pivot_stack(u32 stack_top);
void ipl_main(void) { void ipl_main(void) {
@@ -241,11 +256,13 @@ void ipl_main(void) {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n"); gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um Hekate zu starten...\n"); gfx_printf("um Hekate zu starten...\n");
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
} else { } else {
set_color(COLOR_GREEN); set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n"); gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um den Neustart zu starten...\n"); gfx_printf("um den Neustart zu starten...\n");
gfx_printf("Oder Vol+ und Vol- (Konsole) gleichzeitig.\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
} }
@@ -262,9 +279,17 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button // Check joycon A button; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
}
if (cancel_combo_pressed(btn_state)) {
button_pressed = true; button_pressed = true;
break; break;
} }
@@ -322,12 +347,14 @@ void ipl_main(void) {
gfx_printf("\n"); gfx_printf("\n");
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n"); gfx_printf("D-Pad / Vol+/-: Auswahl | A oder Power: Bestaetigen\n");
gfx_printf("Vol+ und Vol- gleichzeitig: Abbrechen (Hekate)\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
// Edge detection: only move on press (not while held) // Edge detection: only move on press (not while held)
bool prev_up = false, prev_down = false; bool prev_up = false, prev_down = false;
bool menu_aborted = false;
while (!confirmed) { while (!confirmed && !menu_aborted) {
// On selection change: redraw only the two affected lines (no full clear) // On selection change: redraw only the two affected lines (no full clear)
if (selected != prev_selected) { if (selected != prev_selected) {
gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16); gfx_con_setpos(menu_x, menu_variant_start_y + (u32)prev_selected * 16);
@@ -343,6 +370,14 @@ void ipl_main(void) {
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
u8 btn = btn_read(); u8 btn = btn_read();
if (jc && jc->cap)
take_screenshot();
if (cancel_combo_pressed(btn)) {
menu_aborted = true;
break;
}
// D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down) // D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down)
bool cur_up = false, cur_down = false; bool cur_up = false, cur_down = false;
if (jc) { if (jc) {
@@ -368,6 +403,20 @@ void ipl_main(void) {
msleep(50); msleep(50);
} }
if (menu_aborted) {
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Abgebrochen. Starte Hekate...\n");
set_color(COLOR_WHITE);
msleep(500);
if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH);
} else {
power_set_state(POWER_OFF_REBOOT);
}
return;
}
pack_variant = variants_present[selected]; pack_variant = variants_present[selected];
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
print_header(); print_header();
@@ -376,6 +425,139 @@ void ipl_main(void) {
// Determine installation mode // Determine installation mode
install_mode_t mode = current.is_installed ? INSTALL_MODE_UPDATE : INSTALL_MODE_CLEAN; install_mode_t mode = current.is_installed ? INSTALL_MODE_UPDATE : INSTALL_MODE_CLEAN;
// Battery run protection: below 10% requires charger
#define BATT_LOW_THRESHOLD 10
int batt_raw = 0;
int batt_pct = 100;
if (max17050_get_property(MAX17050_RepSOC, &batt_raw) == 0) {
batt_pct = batt_raw >> 8; // RepSOC: high byte = percent
}
while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected()) {
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
set_color(COLOR_RED);
gfx_printf("Akkustand zu niedrig! (%d%%)\n\n", batt_pct);
gfx_printf("Unter %d%% Akku darf die Installation nur\n", BATT_LOW_THRESHOLD);
gfx_printf("mit angeschlossenem Ladegeraet durchgefuehrt werden.\n\n");
set_color(COLOR_YELLOW);
gfx_printf("Stecke das Ladegeraet an und warte...\n");
gfx_printf("Oder 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) {
if (jc->cap)
take_screenshot();
}
if (cancel_combo_pressed(pbtn)) {
user_cancelled = true;
break;
}
if (max17050_get_property(MAX17050_RepSOC, &batt_raw) == 0) {
batt_pct = batt_raw >> 8;
}
msleep(200);
}
if (user_cancelled) {
gfx_printf("\nAbgebrochen. Starte Hekate...\n");
msleep(500);
if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH);
} else {
power_set_state(POWER_OFF_REBOOT);
}
return;
}
}
// Charger detected or battery OK - clear the low-battery screen before continuing
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
// UHS class check: recommend U2 / V30 / A2 for emuMMC (speed and reliability)
#define UHS_U2_MIN 2
#define UHS_V30_MIN 30
#define UHS_A2_MIN 2
bool uhs_ok = (sd_storage.ssr.uhs_grade >= UHS_U2_MIN &&
sd_storage.ssr.video_class >= UHS_V30_MIN &&
sd_storage.ssr.app_class >= UHS_A2_MIN);
if (!uhs_ok) {
jc_init_hw();
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
set_color(COLOR_RED);
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
gfx_printf("!! WARNUNG - SD-KARTEN-KLASSE !!\n");
gfx_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
set_color(COLOR_WHITE);
gfx_printf("Diese SD-Karte erfuellt NICHT die empfohlenen\n");
gfx_printf("Mindestanforderungen (U2 | V30 | A2).\n\n");
set_color(COLOR_YELLOW);
gfx_printf("Aktuelle Werte: UHS%d | V%d | A%d\n\n",
(u32)sd_storage.ssr.uhs_grade,
(u32)sd_storage.ssr.video_class,
(u32)sd_storage.ssr.app_class);
set_color(COLOR_WHITE);
gfx_printf("Moegliche Folgen bei schwachen Karten:\n");
gfx_printf(" - Langsamere emuMMC / Spiel-Ladezeiten\n");
gfx_printf(" - Hoheres Risiko von Korruption oder Abstuerzen\n");
gfx_printf(" - Instabilitaet beim Schreiben groesserer Daten\n\n");
set_color(COLOR_CYAN);
gfx_printf("Empfohlen: U2 | V30 | A2 oder besser.\n");
gfx_printf("Empfohlene Karten: Samsung EVO Plus, EVO Select und PRO Plus.\n\n");
set_color(COLOR_GREEN);
gfx_printf("Druecke A (oder Power), um trotzdem fortzufahren.\n");
set_color(COLOR_CYAN);
gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
set_color(COLOR_WHITE);
while (btn_read() & BTN_POWER) { msleep(50); }
bool acknowledged = false;
bool uhs_aborted = false;
while (!acknowledged && !uhs_aborted) {
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) acknowledged = true;
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc) {
if (jc->cap)
take_screenshot();
if (jc->a) acknowledged = true;
}
if (cancel_combo_pressed(btn_state)) {
uhs_aborted = true;
break;
}
msleep(50);
}
if (uhs_aborted) {
gfx_printf("\nAbgebrochen. Starte Hekate...\n");
msleep(500);
if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH);
} else {
power_set_state(POWER_OFF_REBOOT);
}
return;
}
// Wait for A and Power to be released so the same press doesn't start the install
while (btn_read() & BTN_POWER) { msleep(50); }
bool released = false;
while (!released) {
jc_gamepad_rpt_t *jc = joycon_poll();
released = (!jc || !jc->a) && !(btn_read() & BTN_POWER);
if (!released) msleep(50);
}
msleep(300); // Short delay so the next screen is visible before accepting input
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
}
// Show information // Show information
set_color(COLOR_CYAN); set_color(COLOR_CYAN);
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation"); gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");
@@ -400,33 +582,59 @@ void ipl_main(void) {
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n"); gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um die Installation zu starten...\n"); gfx_printf("um die Installation zu starten...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
set_color(COLOR_CYAN);
gfx_printf("Vol+ und Vol- (Konsole) gleichzeitig: Abbrechen -> Hekate.\n");
set_color(COLOR_WHITE);
// Wait for either A button or Power button // Wait for A/Power to start, or +/- to cancel
bool button_pressed = false; bool button_pressed = false;
bool cancelled = false;
// First, wait for power button to be released if it's currently pressed // First, wait for power button to be released if it's currently pressed
while (btn_read() & BTN_POWER) { while (btn_read() & BTN_POWER) {
msleep(50); msleep(50);
} }
while (!button_pressed) { while (!button_pressed && !cancelled) {
// Check power button - detect press (transition from not pressed to pressed) // Check power button
u8 btn_state = btn_read(); u8 btn_state = btn_read();
if (btn_state & BTN_POWER) { if (btn_state & BTN_POWER) {
button_pressed = true; button_pressed = true;
break; break;
} }
// Check joycon A button // Check joycon buttons; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
button_pressed = true; if (jc->cap)
take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
}
if (cancel_combo_pressed(btn_state)) {
cancelled = true;
break; break;
} }
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
} }
if (cancelled) {
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Abgebrochen. Starte Hekate...\n");
set_color(COLOR_WHITE);
msleep(500);
if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH);
} else {
power_set_state(POWER_OFF_REBOOT);
}
return;
}
// Clear the prompt and start installation // Clear the prompt and start installation
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0); gfx_con_setpos(0, 0);
@@ -437,13 +645,14 @@ void ipl_main(void) {
gfx_printf("Installation wird gestartet...\n"); gfx_printf("Installation wird gestartet...\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
set_color(COLOR_ORANGE); set_color(COLOR_ORANGE);
gfx_printf("Hinweis: Manchmal könnte es hängen. Einfach warten.\n\n"); gfx_printf("Hinweis: Manchmal kann es haengen. Einfach warten.\n\n");
set_color(COLOR_WHITE); set_color(COLOR_WHITE);
int result = perform_installation(pack_variant, mode); int result = perform_installation(pack_variant, mode);
// Wait 3 seconds before clearing screen to allow errors to be visible // Wait 3 seconds before clearing screen to allow errors to be visible
msleep(3000); msleep(3000);
// take_screenshot(); // Take a screenshot of the installation summary
// Clear screen for final summary to ensure it's visible // Clear screen for final summary to ensure it's visible
gfx_clear_grey(0x1B); gfx_clear_grey(0x1B);
@@ -496,11 +705,15 @@ void ipl_main(void) {
break; break;
} }
// Check joycon A button // Check joycon A button; Capture = screenshot
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
button_pressed = true; if (jc->cap)
break; take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
} }
msleep(50); // Small delay to avoid busy-waiting msleep(50); // Small delay to avoid busy-waiting
@@ -538,9 +751,13 @@ void ipl_main(void) {
} }
jc_gamepad_rpt_t *jc = joycon_poll(); jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) { if (jc) {
button_pressed = true; if (jc->cap)
break; take_screenshot();
if (jc->a) {
button_pressed = true;
break;
}
} }
msleep(50); msleep(50);

View File

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

111
source/screenshot.c Normal file
View File

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

13
source/screenshot.h Normal file
View File

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

70
tools/ram_test_main.c Normal file
View File

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