Compare commits

..

19 Commits

Author SHA1 Message Date
e5ab6a2ed0 apl: ASCII ueberschreibe string; warmboot: PKG1 20260123 -> FW 22.0.0
All checks were successful
Build / Build (push) Successful in 22s
Made-with: Cursor
2026-04-10 21:34:05 +02:00
fa5e32f92d hid: map Vol+/- to up/down even with left Joy-Con connected
Made-with: Cursor
2026-04-10 21:29:48 +02:00
0c898a3293 feat: Mariko warmboot extraction, hekate_ipl.ini update, docs
- Add warmboot_extractor (Sthetix-derived) for PKG1 extraction and sd save
- m_entry_fixMarikoWarmbootSleep: generate wb_xx.bin, INI warmboot= for
  [CFW-EmuMMC] or section chooser; blank-line handling for Hekate parsing
- Menu: Mariko Sleep Fix under Bequemlichkeit; theme fix under MainAMS
- README: Funktionen + Danksagungen for warmboot/Fuse-Mismatch workaround
- Fix listdir memory corruption in Mac special-folder cleanup (apl.c)

Made-with: Cursor
2026-03-31 13:20:19 +02:00
c6b6b42eda Use parallel build in CI workflow
All checks were successful
Build / Build (push) Successful in 16s
Add -j4 to make for faster compilation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 21:35:43 +01:00
9cd8953f78 Move theme removal to AMS error codes section
All checks were successful
Build / Build (push) Successful in 34s
Relocated "Delete Themes" entry from the boot problem section into
the Atmosphere error codes section as "Fix 0100000000001000
(Installiertes Theme)" to better reflect that it fixes an AMS
title ID conflict.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 21:34:22 +01:00
45c659f1ea Updated workflow
All checks were successful
Build / Build (push) Successful in 34s
2026-02-13 21:24:35 +01:00
f82ee15f52 Updated workflow
Some checks failed
Build / Build (push) Failing after 34s
2026-02-13 21:23:32 +01:00
2987a3c88e Updated Readme
Some checks failed
Build / Build (push) Failing after 2s
2026-02-13 21:22:20 +01:00
e6676315c7 Updated workflow
Some checks failed
Build / Build (push) Failing after 3s
2026-02-13 21:21:52 +01:00
ef9c1eaf7a Fix data abort crash in Mac special files removal (listdir)
The listdir function had three memory corruption bugs causing a
data abort (Err:19) on the Switch:

1. Heap buffer overflow: CpyStr("sd:/") allocated only 5 bytes but
   listdir appends full subdirectory paths in-place via memcpy.
   Fixed by using a 1024-byte stack buffer instead.

2. Use-after-free: _DeleteFileSimple(path) freed the shared traversal
   buffer, but the loop continued using it. Fixed by inlining f_unlink
   without freeing.

3. Recursive free + missing else: free(path) at the end of listdir
   freed the buffer shared across all recursive frames, and a missing
   else caused recursion into directories that were just deleted.
   Removed the free and added an else guard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 21:16:58 +01:00
763cb43983 Added AMS section and fixed inconsistencies 2026-02-07 23:00:29 +01:00
59f7dde33a Updated gitignore 2026-02-06 21:04:13 +01:00
bc086126e5 Updated Readme 2026-02-06 21:03:23 +01:00
5702202ea9 Updated Readme 2026-02-06 21:02:47 +01:00
36a08cf42f Updated Readme 2026-02-06 21:01:27 +01:00
1a7b0a1667 Added german translation and bugfixes 2026-02-06 21:00:36 +01:00
6ee7d5f892 Updated Workflow 2026-02-06 19:22:32 +01:00
6239b65472 Updated Workflow 2026-02-06 19:20:47 +01:00
29d63ffbf7 Updated Workflow 2026-02-06 19:19:03 +01:00
15 changed files with 1513 additions and 558 deletions

View File

@@ -1,52 +1,30 @@
name: Release
name: Build
on:
issue_comment:
types: [ created ]
push:
workflow_dispatch:
jobs:
build_and_release:
name: Build and release
build:
name: Build
runs-on: ubuntu-latest
container: devkitpro/devkita64:latest
if: contains(github.event.comment.body, '/release-action')
steps:
- name: Update latest libnx
run: |
git config --global --add safe.directory "*"
git clone --recurse-submodules https://github.com/zdm65477730/libnx.git
cd libnx
make install -j$(nproc)
shell: bash
- name: Install dependencies
run: apt-get update && apt-get install -y nodejs
- name: Checkout latest code
uses: actions/checkout@v5
with:
ref: master
clean: true
fetch-depth: 0
fetch-tags: true
submodules: recursive
- name: Setup ENV parameters
run: |
VER_FILE=Makefile
VERSION=$(awk '/^LPVERSION_MAJOR/{print $3}' $VER_FILE).$(awk '/^LPVERSION_MINOR/{print $3}' $VER_FILE).$(awk '/^LPVERSION_BUGFX/{print $3}' $VER_FILE)
echo "TAG=${VERSION}" >> "${GITHUB_ENV}"
echo "RELEASE_NAME=CommonProblemResolver ${VERSION}" >> "${GITHUB_ENV}"
shell: bash
- name: Build
run: |
export DEVKITPRO=/opt/devkitpro
make all
make -j4 all
shell: bash
- name: Upload Release Asset
uses: softprops/action-gh-release@v2.0.9
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_NAME }}
tag_name: ${{ env.TAG }}
draft: false
prerelease: false
generate_release_notes: yes
make_latest: true
files: |
output/CommonProblemResolver.bin
name: AllgemeinerProblemLoeser.bin
path: output/AllgemeinerProblemLoeser.bin

13
.gitignore vendored
View File

@@ -2,3 +2,16 @@
build
output
research
# macOS
.DS_Store
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.apDisk

View File

@@ -9,13 +9,13 @@ include $(DEVKITARM)/base_rules
################################################################################
IPL_LOAD_ADDR := 0x40008000
LPVERSION_MAJOR := 0
LPVERSION_MINOR := 3
LPVERSION_BUGFX := 4
APLVERSION_MAJOR := 0
APLVERSION_MINOR := 3
APLVERSION_BUGFX := 4
################################################################################
TARGET := CommonProblemResolver
TARGET := AllgemeinerProblemLoeser
BUILDDIR := build
OUTPUTDIR := output
SOURCEDIR = source
@@ -37,7 +37,7 @@ FFCFG_INC := '"../$(SOURCEDIR)/libs/fatfs/ffconf.h"'
################################################################################
CUSTOMDEFINES := -DIPL_LOAD_ADDR=$(IPL_LOAD_ADDR)
CUSTOMDEFINES += -DLP_VER_MJ=$(LPVERSION_MAJOR) -DLP_VER_MN=$(LPVERSION_MINOR) -DLP_VER_BF=$(LPVERSION_BUGFX)
CUSTOMDEFINES += -DAPL_VER_MJ=$(APLVERSION_MAJOR) -DAPL_VER_MN=$(APLVERSION_MINOR) -DAPL_VER_BF=$(APLVERSION_BUGFX)
CUSTOMDEFINES += -DGFX_INC=$(GFX_INC) -DFFCFG_INC=$(FFCFG_INC)
# 0: UART_A, 1: UART_B.

View File

@@ -1,34 +1,43 @@
# CommonProblemResolver (CPR)
# AllgemeinerProblemLoeser (APL)
Rescue your switch without a pc!
Rette deine Switch ohne PC!
## Usage
1. Get your favorite payload injector
2. Inject CPR as a payload
**Original-Projekt:** [Common Problem Resolver](https://github.com/Team-Neptune/CommonProblemResolver) von [Team Neptune](https://github.com/Team-Neptune).
Navigate around the menus using the joycons.
- A: Select
- B: Back
- Left Joystick up/down (Dpad or joystick): navigate menus up/down
- Right Joystick up/down: fast menu navigation up/down
- Capture (Minerva only): Take a screenshot
- L3/R3 (Press joysticks in): Recalibrate centerpoint
## Nutzung
If you do not have your joycons connected:
1. Besorge dir einen Payload-Injector deiner Wahl
2. Lade APL als Payload
Navigation in den Menues mit den Joy-Cons:
- A: Auswaehlen
- B: Zurueck
- Linker Stick hoch/runter (D-Pad oder Stick): Menue hoch/runter
- Rechter Stick hoch/runter: Schnelle Menue-Navigation
- Capture (nur Minerva): Screenshot
- L3/R3 (Sticks eindruecken): Mittelpunkt neu kalibrieren
Ohne verbundene Joy-Cons:
- Power -> A
- Vol+ -> Left Joystick up
- Vol- -> Left Joystick down
- Vol+ -> Linker Stick hoch
- Vol- -> Linker Stick runter
## Functions
- Disables all sysmodules on your switch (might fix booting issues after update)
- Delete installed themes (will fix booting issues after update)
- Reverts changes made by clingwrap
## Funktionen
- Deaktiviert automatischen Sysmodule-Start (kann Startprobleme nach Updates beheben)
- Loescht installierte Themes (kann Startprobleme nach Updates beheben)
- Behebt Archiv-Bit (SD-Karte)
- Entfernt Mac-Sonderordner
- Mariko Sleep Fix (Warmboot) bei Fuse-Mismatch (Standby/Sleep-Workaround)
## Support
Join [our discord](https://discord.gg/VkaRjYN) for help.
Fuer Hilfe: [unser Discord](https://discord.gg/VkaRjYN)
## Credits
## Danksagungen
This is heavily based on [TegraExplorer](https://github.com/suchmememanyskill/TegraExplorer) by suchmememanyskill,
which itself is based on [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM), and thus also based on [Hekate](https://github.com/CTCaer/hekate)
- **Deutsche Übersetzung:** [Switch Bros.](https://github.com/Switch-Bros/AllgemeinerProblemLoeser)
- Dieses Projekt basiert stark auf dem [Original Common Problem Resolver](https://github.com/Team-Neptune/CommonProblemResolver) von [Team Neptune](https://github.com/Team-Neptune),
sowie auf [TegraExplorer](https://github.com/suchmememanyskill/TegraExplorer) von suchmememanyskill,
[Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM) und [Hekate](https://github.com/CTCaer/hekate).
- Warmboot Extractor (Sthetix): extrahiert Warmboot-Caches bei Fuse-Mismatch (Standby/Sleep-Workaround auf Mariko)

955
source/apl/apl.c Normal file
View File

@@ -0,0 +1,955 @@
// #include <utils/util.h>
// #include "tools.h"
#include <storage/sd.h>
#include "../fs/readers/folderReader.h"
#include "../fs/fstypes.h"
#include "../fs/fscopy.h"
#include <utils/sprintf.h>
#include <stdlib.h>
#include <string.h>
#include "../gfx/gfx.h"
#include "../gfx/gfxutils.h"
#include "../gfx/menu.h"
#include "../hid/hid.h"
// #include "utils.h"
#include "../utils/utils.h"
#include "../fs/fsutils.h"
#include <unistd.h>
#include <sys/types.h>
#include "../warmboot/warmboot_extractor.h"
// #include <dirent.h>
// #include <stdio.h>
#include <string.h>
#include <sys/stat.h>
void _DeleteFileSimple(char *thing){
//char *thing = CombinePaths(path, entry.name);
int res = f_unlink(thing);
if (res)
DrawError(newErrCode(res));
free(thing);
}
void _RenameFileSimple(char *sourcePath, char *destPath){
int res = f_rename(sourcePath, destPath);
if (res){
DrawError(newErrCode(res));
}
}
ErrCode_t _FolderDelete(const char *path){
int res = 0;
ErrCode_t ret = newErrCode(0);
u32 x, y;
gfx_con_getpos(&x, &y);
Vector_t fileVec = ReadFolder(path, &res);
if (res){
ret = newErrCode(res);
}
else {
vecDefArray(FSEntry_t *, fs, fileVec);
for (int i = 0; i < fileVec.count && !ret.err; i++){
char *temp = CombinePaths(path, fs[i].name);
if (fs[i].isDir){
ret = _FolderDelete(temp);
}
else {
res = f_unlink(temp);
if (res){
ret = newErrCode(res);
}
}
free(temp);
}
}
if (!ret.err){
res = f_unlink(path);
if (res)
ret = newErrCode(res);
}
clearFileVector(&fileVec);
return ret;
}
int _StartsWith(const char *a, const char *b)
{
if(strncmp(a, b, strlen(b)) == 0) return 1;
return 0;
}
int listdir(char *path, u32 hos_folder, int *deleted_count)
{
FRESULT res;
DIR dir;
u32 dirLength = 0;
static FILINFO fno;
// Open directory.
res = f_opendir(&dir, path);
if (res != FR_OK)
return res;
dirLength = strlen(path);
for (;;)
{
// Clear file or folder path.
path[dirLength] = 0;
// Read a directory item.
res = f_readdir(&dir, &fno);
// Break on error or end of dir.
if (res != FR_OK || fno.fname[0] == 0)
break;
// Skip official Nintendo dir if started from root.
if (!hos_folder && !strcmp(fno.fname, "Nintendo"))
continue;
// Set new directory or file.
memcpy(&path[dirLength], "/", 1);
memcpy(&path[dirLength + 1], fno.fname, strlen(fno.fname) + 1);
// Is it a directory?
if (fno.fattrib & AM_DIR)
{
if (
strcmp(fno.fname, ".Trash") == 0 ||
strcmp(fno.fname, ".Trashes") == 0 ||
strcmp(fno.fname, ".DS_Store") == 0 ||
strcmp(fno.fname, ".Spotlight-V100") == 0 ||
strcmp(fno.fname, ".apDisk") == 0 ||
strcmp(fno.fname, ".VolumeIcon.icns") == 0 ||
strcmp(fno.fname, ".fseventsd") == 0 ||
strcmp(fno.fname, ".TemporaryItems") == 0
) {
_FolderDelete(path);
if (deleted_count) (*deleted_count)++;
}
else {
// Only recurse into directories that were not deleted.
listdir(path, 0, deleted_count);
}
if (res != FR_OK)
break;
} else {
if (
strcmp(fno.fname, ".DS_Store") == 0 ||
strcmp(fno.fname, ".Spotlight-V100") == 0 ||
strcmp(fno.fname, ".apDisk") == 0 ||
strcmp(fno.fname, ".VolumeIcon.icns") == 0 ||
strcmp(fno.fname, ".fseventsd") == 0 ||
strcmp(fno.fname, ".TemporaryItems") == 0 ||
_StartsWith(fno.fname, "._")
) {
res = f_unlink(path);
if (res)
DrawError(newErrCode(res));
if (deleted_count) (*deleted_count)++;
}
}
}
f_closedir(&dir);
return res;
}
int _fix_attributes(char *path, u32 *total, u32 hos_folder, u32 check_first_run){
FRESULT res;
DIR dir;
u32 dirLength = 0;
static FILINFO fno;
if (check_first_run)
{
// Read file attributes.
res = f_stat(path, &fno);
if (res != FR_OK)
return res;
// Check if archive bit is set.
if (fno.fattrib & AM_ARC)
{
*(u32 *)total = *(u32 *)total + 1;
f_chmod(path, 0, AM_ARC);
}
}
// Open directory.
res = f_opendir(&dir, path);
if (res != FR_OK)
return res;
dirLength = strlen(path);
for (;;)
{
// Clear file or folder path.
path[dirLength] = 0;
// Read a directory item.
res = f_readdir(&dir, &fno);
// Break on error or end of dir.
if (res != FR_OK || fno.fname[0] == 0)
break;
// Skip official Nintendo dir if started from root.
if (!hos_folder && !strcmp(fno.fname, "Nintendo"))
continue;
// Set new directory or file.
memcpy(&path[dirLength], "/", 1);
memcpy(&path[dirLength + 1], fno.fname, strlen(fno.fname) + 1);
// Check if archive bit is set.
if (fno.fattrib & AM_ARC)
{
*total = *total + 1;
f_chmod(path, 0, AM_ARC);
}
// Is it a directory?
if (fno.fattrib & AM_DIR)
{
// Set archive bit to NCA folders.
if (hos_folder && !strcmp(fno.fname + strlen(fno.fname) - 4, ".nca"))
{
*total = *total + 1;
f_chmod(path, AM_ARC, AM_ARC);
}
// Enter the directory.
res = _fix_attributes(path, total, hos_folder, 0);
if (res != FR_OK)
break;
}
}
f_closedir(&dir);
return res;
}
void m_entry_fixArchiveBit(u32 type){
gfx_clearscreen();
gfx_printf("\n\n-- Behebe Archive Bit\n\n");
char path[256];
char label[16];
u32 total = 0;
if (sd_mount())
{
switch (type)
{
case 0:
strcpy(path, "/");
strcpy(label, "SD-Karte");
break;
case 1:
default:
strcpy(path, "/Nintendo");
strcpy(label, "Nintendo Ordner");
break;
}
gfx_printf("Durchlaufe alle %s Dateien!\nDas kann einige Zeit dauern...\n\n", label);
_fix_attributes(path, &total, type, type);
gfx_printf("%kAnzahl reparierter Archiv bits: %d!%k", 0xFF96FF00, total, 0xFFCCCCCC);
gfx_printf("\n\n Fertig! Druecke eine Taste um zurueckzukehren");
hidWait();
}
}
static void _fixAMSErrorFolder(const char *titleId, const char *label){
gfx_clearscreen();
gfx_printf("\n\n-- Fix Fehlercode %s --\n\n", label);
if (!sd_mount()) {
gfx_printf("SD-Karte konnte nicht gemountet werden.\n");
hidWait();
return;
}
char *path = CpyStr("sd:/atmosphere/contents");
char *full = CombinePaths(path, titleId);
free(path);
if (FileExists(full)) {
gfx_printf("Entferne Ordner %s ...\n", titleId);
FolderDelete(full);
gfx_printf("Ordner erfolgreich geloescht.\n");
} else {
gfx_printf("Ordner %s nicht gefunden (evtl. bereits behoben).\n", titleId);
}
free(full);
gfx_printf("\n\n Fertig! Druecke eine Taste um zurueckzukehren");
hidWait();
}
void m_entry_fixAMSError_4200(void){
_fixAMSErrorFolder("4200000000000010", "4200000000000010");
}
void m_entry_fixAMSError_6900(void){
_fixAMSErrorFolder("690000000000000D", "690000000000000D");
}
void m_entry_fixAMSError_BD00(void){
_fixAMSErrorFolder("010000000000BD00", "010000000000BD00");
}
static int _deleteTheme(char* basePath, char* folderId){
int removed = 0;
char *path = CombinePaths(basePath, folderId);
if (FileExists(path)) {
gfx_printf("-- Theme gefunden: %s\n", path);
FolderDelete(path);
removed = 1;
}
free(path);
return removed;
}
void m_entry_deleteInstalledThemes(){
gfx_clearscreen();
gfx_printf("\n\n-- Loesche installierte Themes.\n\n");
int count = 0;
count += _deleteTheme("sd:/atmosphere/contents", "0100000000001000");
count += _deleteTheme("sd:/atmosphere/contents", "0100000000001007");
count += _deleteTheme("sd:/atmosphere/contents", "0100000000001013");
if (count > 0)
gfx_printf("\n%d Theme(s) geloescht.\n", count);
else
gfx_printf("\nKeine installierten Themes gefunden.\n");
gfx_printf("\n\n Fertig! Druecke eine Taste um zurueckzukehren");
hidWait();
}
void m_entry_deleteBootFlags(){
gfx_clearscreen();
gfx_printf("\n\n-- Automatisches starten der sysmodule deaktivieren.\n\n");
char *storedPath = CpyStr("sd:/atmosphere/contents");
int readRes = 0;
int count = 0;
Vector_t fileVec = ReadFolder(storedPath, &readRes);
if (readRes){
clearFileVector(&fileVec);
free(storedPath);
DrawError(newErrCode(readRes));
} else {
vecDefArray(FSEntry_t*, fsEntries, fileVec);
for (int i = 0; i < fileVec.count; i++){
char *suf = "/flags/boot2.flag";
char *subPath = CombinePaths(storedPath, fsEntries[i].name);
char *flagPath = CombinePaths(subPath, suf);
free(subPath);
if (FileExists(flagPath)) {
gfx_printf("Loesche: %s\n", flagPath);
_DeleteFileSimple(flagPath);
count++;
} else {
free(flagPath);
}
}
clearFileVector(&fileVec);
free(storedPath);
}
if (count > 0)
gfx_printf("\n%d Boot-Flag(s) geloescht.\n", count);
else if (!readRes)
gfx_printf("\nKeine Boot-Flags gefunden.\n");
gfx_printf("\n\n Fertig! Druecke eine Taste um zurueckzukehren");
hidWait();
}
void m_entry_fixMacSpecialFolders(){
gfx_clearscreen();
gfx_printf("\n\n-- Mac-Ordner reparieren (dies kann ein wenig dauern, bitte warten.)\n\n");
int deleted = 0;
char path[1024];
strcpy(path, "sd:/");
listdir(path, 0, &deleted);
if (deleted > 0)
gfx_printf("\n%d Mac-Datei(en)/Ordner entfernt.\n", deleted);
else
gfx_printf("\nKeine Mac-Sonderdateien oder -ordner gefunden.\n");
gfx_printf("\n\n Fertig! Druecke eine Taste um zurueckzukehren");
hidWait();
}
static u32 _hex_nibble_lower(char c)
{
if (c >= '0' && c <= '9')
return (u32)(c - '0');
if (c >= 'a' && c <= 'f')
return (u32)(c - 'a' + 10);
return 0xFFFFFFFFu;
}
static bool _parse_wb_filename_lower(const char *fname, u32 *fuse_out)
{
// Expected: wb_xx.bin where xx is lowercase hex (exactly 2 chars).
// Example: wb_16.bin
if (!fname || !fuse_out)
return false;
// Length check: "wb_" + 2 + ".bin" = 3 + 2 + 4 = 9
if (strlen(fname) != 9)
return false;
if (strncmp(fname, "wb_", 3) != 0)
return false;
if (fname[5] != '.')
return false;
if (strncmp(fname + 6, "bin", 3) != 0)
return false;
u32 hi = _hex_nibble_lower(fname[3]);
u32 lo = _hex_nibble_lower(fname[4]);
if (hi == 0xFFFFFFFFu || lo == 0xFFFFFFFFu)
return false;
*fuse_out = (hi << 4) | lo;
return true;
}
static void _rstrip(char *s)
{
if (!s)
return;
int n = (int)strlen(s);
while (n > 0)
{
char c = s[n - 1];
if (c == '\n' || c == '\r' || c == ' ' || c == '\t')
s[--n] = 0;
else
break;
}
}
static char *_ltrim(char *s)
{
if (!s)
return s;
while (*s == ' ' || *s == '\t')
s++;
return s;
}
static bool _read_line_no_nl(FIL *fp, char *out, u32 out_sz)
{
if (!fp || !out || out_sz < 2)
return false;
u32 idx = 0;
BYTE b;
UINT br = 0;
// Return false on immediate EOF.
for (;;)
{
if (idx >= out_sz - 1)
break;
FRESULT res = f_read(fp, &b, 1, &br);
if (res != FR_OK || br == 0)
break;
if (b == '\n')
break;
if (b == '\r')
continue;
out[idx++] = (char)b;
}
out[idx] = 0;
return idx > 0 || br != 0;
}
static bool _ini_section_exists(const char *ini_path, const char *section_name)
{
FIL fp;
if (f_open(&fp, ini_path, FA_READ) != FR_OK)
return false;
char line[256];
bool found = false;
while (_read_line_no_nl(&fp, line, sizeof(line)))
{
char *t = _ltrim(line);
if (*t == '[')
{
char *end = strchr(t, ']');
if (end)
{
*end = 0;
if (!strcmp(t + 1, section_name))
{
found = true;
break;
}
*end = ']';
}
}
}
f_close(&fp);
return found;
}
static bool _ini_section_warmboot_is(const char *ini_path, const char *section_name, const char *warmboot_value_rel)
{
FIL fp;
if (f_open(&fp, ini_path, FA_READ) != FR_OK)
return false;
char line[256];
bool in_section = false;
bool is_set = false;
while (_read_line_no_nl(&fp, line, sizeof(line)))
{
char *t = _ltrim(line);
if (*t == '[')
{
char *end = strchr(t, ']');
if (end)
{
*end = 0;
in_section = !strcmp(t + 1, section_name);
*end = ']';
}
continue;
}
if (!in_section)
continue;
char *k = _ltrim(line);
if (strncmp(k, "warmboot", 8) != 0)
continue;
// Ensure this is the "warmboot" key (not warmboot2/etc), allowing whitespace before '='.
char *p = k + 8;
while (*p == ' ' || *p == '\t')
p++;
if (*p != '=')
continue;
p++;
p = _ltrim(p);
_rstrip(p);
if (!strcmp(p, warmboot_value_rel))
{
is_set = true;
break;
}
}
f_close(&fp);
return is_set;
}
static bool _ini_update_section_warmboot(const char *ini_path, const char *section_name, const char *warmboot_value_rel)
{
char tmp_path[128];
u32 len = strlen(ini_path);
if (len + 4 >= sizeof(tmp_path))
return false;
strcpy(tmp_path, ini_path);
strcat(tmp_path, ".tmp");
FIL in_fp;
FIL out_fp;
if (f_open(&in_fp, ini_path, FA_READ) != FR_OK)
return false;
if (f_open(&out_fp, tmp_path, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
{
f_close(&in_fp);
return false;
}
char line[256];
bool in_section = false;
bool warmboot_seen = false;
bool wrote_any = false;
// Buffer trailing empty lines in the target section so the inserted/updated
// warmboot=... line stays directly after the last non-empty line.
#define MAX_PENDING_BLANKS 32
char pending_blanks[MAX_PENDING_BLANKS][256];
u32 pending_blank_count = 0;
while (_read_line_no_nl(&in_fp, line, sizeof(line)))
{
char *t = _ltrim(line);
// Section header?
if (*t == '[')
{
if (in_section && !warmboot_seen)
{
f_printf(&out_fp, "warmboot=%s\n", warmboot_value_rel);
warmboot_seen = true;
wrote_any = true;
}
// Flush buffered empty lines after warmboot, so they don't split it.
for (u32 i = 0; i < pending_blank_count; i++)
{
f_printf(&out_fp, "%s\n", pending_blanks[i]);
}
pending_blank_count = 0;
char *end = strchr(t, ']');
if (end)
{
*end = 0;
in_section = !strcmp(t + 1, section_name);
*end = ']';
}
f_printf(&out_fp, "%s\n", line);
wrote_any = true;
continue;
}
if (in_section)
{
char *k = _ltrim(line);
if (strncmp(k, "warmboot", 8) == 0)
{
// Replace existing warmboot=... line inside the section.
// Allow whitespace between key and '='.
char *p = k + 8;
while (*p == ' ' || *p == '\t')
p++;
if (*p == '=')
{
// Keep the buffered empty lines where the original warmboot key appeared.
for (u32 i = 0; i < pending_blank_count; i++)
{
f_printf(&out_fp, "%s\n", pending_blanks[i]);
wrote_any = true;
}
pending_blank_count = 0;
f_printf(&out_fp, "warmboot=%s\n", warmboot_value_rel);
warmboot_seen = true;
wrote_any = true;
continue;
}
}
}
// If we're in the target section and warmboot isn't present yet, buffer empty lines
// so warmboot will not be separated from the last non-empty entry.
if (in_section && !warmboot_seen)
{
if (*t == 0)
{
if (pending_blank_count < MAX_PENDING_BLANKS)
{
strncpy(pending_blanks[pending_blank_count], line, 255);
pending_blanks[pending_blank_count][255] = 0;
pending_blank_count++;
}
else
{
for (u32 i = 0; i < pending_blank_count; i++)
{
f_printf(&out_fp, "%s\n", pending_blanks[i]);
wrote_any = true;
}
pending_blank_count = 0;
f_printf(&out_fp, "%s\n", line);
wrote_any = true;
}
continue;
}
// Non-empty line: flush buffered blanks first.
for (u32 i = 0; i < pending_blank_count; i++)
{
f_printf(&out_fp, "%s\n", pending_blanks[i]);
wrote_any = true;
}
pending_blank_count = 0;
}
f_printf(&out_fp, "%s\n", line);
wrote_any = true;
}
if (in_section && !warmboot_seen)
{
f_printf(&out_fp, "warmboot=%s\n", warmboot_value_rel);
wrote_any = true;
}
// Flush trailing empty lines at EOF (after warmboot) if any.
for (u32 i = 0; i < pending_blank_count; i++)
{
f_printf(&out_fp, "%s\n", pending_blanks[i]);
wrote_any = true;
}
pending_blank_count = 0;
f_close(&in_fp);
f_close(&out_fp);
if (!wrote_any)
return false;
// Replace original with temp.
f_unlink(ini_path);
f_rename(tmp_path, ini_path);
return true;
}
void m_entry_fixMarikoWarmbootSleep(void)
{
gfx_clearscreen();
gfx_printf("\n\n-- Mariko Sleep Fix (Warmboot) --\n\n");
char warmboot_rel[64] = {0};
bool warmboot_generated = false;
// 1) Generate a fresh warmboot for the currently burnt fuses.
// Then we directly update the INI using that result.
gfx_printf("Erzeuge neuen Warmboot Cache...\n\n");
warmboot_info_t wb_info;
wb_extract_error_t wberr = extract_warmboot_from_pkg1_ex(&wb_info);
if (wberr == WB_SUCCESS)
{
char warmboot_sd_path[128];
get_warmboot_path(warmboot_sd_path, sizeof(warmboot_sd_path), wb_info.burnt_fuses);
if (save_warmboot_to_sd(&wb_info, warmboot_sd_path))
{
s_printf(warmboot_rel, "warmboot_mariko/wb_%02x.bin", wb_info.burnt_fuses);
warmboot_generated = true;
gfx_printf("Neues warmboot: %s\n\n", warmboot_rel);
}
else
{
gfx_printf("Speichern auf SD fehlgeschlagen.\n\n");
}
}
else
{
gfx_printf("Warmboot Extraktion fehlgeschlagen: %s\n\n", wb_error_to_string(wberr));
}
if (wb_info.data)
free(wb_info.data);
// 2) Fallback: pick the newest cached wb_xx.bin from sd:/warmboot_mariko.
if (!warmboot_generated)
{
const char *wb_dir = "sd:/warmboot_mariko";
DIR dir;
static FILINFO fno;
if (f_opendir(&dir, wb_dir) != FR_OK)
{
gfx_printf("Ordner nicht gefunden: %s\n\n", wb_dir);
gfx_printf("Bitte erst Warmboot-Extractor ausfuehren:\n");
gfx_printf("https://github.com/sthetix/Warmboot-Extractor\n\n");
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
u32 best_fuse = 0;
u32 best_ts = 0;
bool found_any = false;
for (;;)
{
FRESULT res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0)
break;
if (fno.fattrib & AM_DIR)
continue;
u32 fuse = 0;
if (!_parse_wb_filename_lower(fno.fname, &fuse))
continue;
u32 ts = ((u32)fno.fdate << 16) | (u32)fno.ftime;
if (!found_any || fuse > best_fuse || (fuse == best_fuse && ts > best_ts))
{
best_fuse = fuse;
best_ts = ts;
found_any = true;
}
}
f_closedir(&dir);
if (!found_any)
{
gfx_printf("Keine passenden Dateien gefunden in %s:\n", wb_dir);
gfx_printf("wb_xx.bin (xx = lowercase hex fuse)\n\n");
gfx_printf("Bitte Warmboot-Extractor ausfuehren:\n");
gfx_printf("https://github.com/sthetix/Warmboot-Extractor\n\n");
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
s_printf(warmboot_rel, "warmboot_mariko/wb_%02x.bin", best_fuse);
gfx_printf("Neuestes warmboot: %s\n\n", warmboot_rel);
}
const char *ini_path = "sd:/bootloader/hekate_ipl.ini";
const char *preferred_section = "CFW-EmuMMC";
if (!_ini_section_exists(ini_path, preferred_section))
{
// Fallback: show a section chooser if CFW-EmuMMC doesn't exist.
gfx_printf("Section %s nicht gefunden.\n", preferred_section);
gfx_printf("Bitte waehle eine INI section.\n\n");
// Collect section names.
#define MAX_INI_SECTIONS 20
char section_names[MAX_INI_SECTIONS][64];
int section_count = 0;
FIL ini_fp;
if (f_open(&ini_fp, ini_path, FA_READ) != FR_OK)
{
gfx_printf("Datei nicht gefunden: %s\n", ini_path);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
char line[256];
while (_read_line_no_nl(&ini_fp, line, sizeof(line)))
{
char *t = _ltrim(line);
if (*t != '[')
continue;
char *end = strchr(t, ']');
if (!end)
continue;
*end = 0;
char *name = t + 1;
name = _ltrim(name);
_rstrip(name);
// Skip empty section headers like "[]"
if (!*name)
{
*end = ']';
continue;
}
if (strcmp(name, "config") && section_count < MAX_INI_SECTIONS)
{
strncpy(section_names[section_count], name, 63);
section_names[section_count][63] = 0;
section_count++;
}
*end = ']';
}
f_close(&ini_fp);
if (section_count <= 0)
{
gfx_printf("Keine INI sections gefunden in %s.\n", ini_path);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
MenuEntry_t entries[MAX_INI_SECTIONS + 1];
memset(entries, 0, sizeof(entries));
entries[0].optionUnion = COLORTORGB(COLOR_WHITE);
entries[0].name = "<- Zurueck";
for (int i = 0; i < section_count; i++)
{
entries[i + 1].optionUnion = COLORTORGB(COLOR_YELLOW);
entries[i + 1].name = section_names[i];
}
Vector_t ent = vecFromArray(entries, section_count + 1, sizeof(MenuEntry_t));
int sel = newMenu(&ent, 0, 79, 20, ENABLEB | ALWAYSREDRAW, section_count + 1);
// newMenu returns 0 for "<- Back" and also when the user presses B.
if (sel == 0)
return;
if (sel < 0 || sel > section_count)
return;
const char *selected_section = section_names[sel - 1];
// Continue below with chosen section.
// Clear and re-print banner so the header stays visible (success/fail).
gfx_clearscreen();
gfx_printf("\n\n-- Mariko Sleep Fix (Warmboot) --\n\n");
if (_ini_section_warmboot_is(ini_path, selected_section, warmboot_rel))
{
gfx_printf("\nwarmboot=%s existiert schon in [%s].\n", warmboot_rel, selected_section);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
_ini_update_section_warmboot(ini_path, selected_section, warmboot_rel);
gfx_printf("\nAktualisiert in [%s].\n", selected_section);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
// Preferred path: always update CFW-EmuMMC directly.
if (_ini_section_warmboot_is(ini_path, preferred_section, warmboot_rel))
{
gfx_printf("\nwarmboot=%s existiert schon in [%s].\n", warmboot_rel, preferred_section);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
return;
}
_ini_update_section_warmboot(ini_path, preferred_section, warmboot_rel);
gfx_printf("\nAktualisiert in [%s].\n", preferred_section);
gfx_printf("Druecke eine Taste um zurueckzukehren");
hidWait();
}
void m_entry_stillNoBootInfo(){
gfx_clearscreen();
gfx_printf("\n\n-- Meine Switch startet immer noch nicht.\n\n");
gfx_printf("%kSteckt eine Spiel-Cardrige im Slot?\n", COLOR_WHITE);
gfx_printf("Entferne sie und starte neu.\n\n");
gfx_printf("%kHast du vor kurzem Atmosphere/OmniNX aktualisiert?\n", COLOR_WHITE);
gfx_printf("Stecke die SD-Karte in deinen PC, hol das neue Paket vom NiklasCFW Discord-Server und entpacke die .zip Datei auf deine SD-Karte und ueberschreibe alle Dateien.\nDanach kannst du den OmniNX-Installer-Payload ausfuehren.\n\n");
gfx_printf("%kHast du eine neue SD-Karte gekauft?\n", COLOR_WHITE);
gfx_printf("Vergewissere dich das es keine fake/SanDisk Karte ist.\n\n");
gfx_printf("\n\n Druecke eine Taste um zurueckzukehren");
hidWait();
}
void m_entry_ViewCredits(){
gfx_clearscreen();
gfx_printf("\nAllgemeinerProblemLoeser %d.%d.%d\nVon Team Neptune - (NiklasCFW Fork - Uebersetzt von Switch Bros.)\n\nBasierend auf TegraExplorer von SuchMemeManySkill,\nLockpick_RCM & Hekate, von shchmue & CTCaer\n\n\n", APL_VER_MJ, APL_VER_MN, APL_VER_BF);
hidWait();
}
void m_entry_fixAll(){
gfx_clearscreen();
m_entry_deleteBootFlags();
m_entry_deleteInstalledThemes();
m_entry_stillNoBootInfo();
}

View File

@@ -2,11 +2,13 @@
#include <utils/util.h>
void m_entry_fixArchiveBit(u32 type);
void m_entry_fixAIOUpdate();
void m_entry_fixClingWrap();
void m_entry_fixAMSError_4200(void);
void m_entry_fixAMSError_6900(void);
void m_entry_fixAMSError_BD00(void);
void m_entry_deleteInstalledThemes();
void m_entry_deleteBootFlags();
void m_entry_ViewCredits();
void m_entry_fixAll();
void m_entry_stillNoBootInfo();
void m_entry_fixMacSpecialFolders();
void m_entry_fixMacSpecialFolders();
void m_entry_fixMarikoWarmbootSleep(void);

View File

@@ -1,452 +0,0 @@
// #include <utils/util.h>
// #include "tools.h"
#include <storage/sd.h>
#include "../fs/readers/folderReader.h"
#include "../fs/fstypes.h"
#include "../fs/fscopy.h"
#include <utils/sprintf.h>
#include <stdlib.h>
#include <string.h>
#include "../gfx/gfx.h"
#include "../gfx/gfxutils.h"
#include "../gfx/menu.h"
#include "../hid/hid.h"
// #include "utils.h"
#include "../utils/utils.h"
#include "../fs/fsutils.h"
#include <unistd.h>
#include <sys/types.h>
// #include <dirent.h>
// #include <stdio.h>
#include <string.h>
#include <sys/stat.h>
void _DeleteFileSimple(char *thing){
//char *thing = CombinePaths(path, entry.name);
int res = f_unlink(thing);
if (res)
DrawError(newErrCode(res));
free(thing);
}
void _RenameFileSimple(char *sourcePath, char *destPath){
int res = f_rename(sourcePath, destPath);
if (res){
DrawError(newErrCode(res));
}
}
ErrCode_t _FolderDelete(const char *path){
int res = 0;
ErrCode_t ret = newErrCode(0);
u32 x, y;
gfx_con_getpos(&x, &y);
Vector_t fileVec = ReadFolder(path, &res);
if (res){
ret = newErrCode(res);
}
else {
vecDefArray(FSEntry_t *, fs, fileVec);
for (int i = 0; i < fileVec.count && !ret.err; i++){
char *temp = CombinePaths(path, fs[i].name);
if (fs[i].isDir){
ret = _FolderDelete(temp);
}
else {
res = f_unlink(temp);
if (res){
ret = newErrCode(res);
}
}
free(temp);
}
}
if (!ret.err){
res = f_unlink(path);
if (res)
ret = newErrCode(res);
}
clearFileVector(&fileVec);
return ret;
}
int _StartsWith(const char *a, const char *b)
{
if(strncmp(a, b, strlen(b)) == 0) return 1;
return 0;
}
int listdir(char *path, u32 hos_folder)
{
FRESULT res;
DIR dir;
u32 dirLength = 0;
static FILINFO fno;
// Open directory.
res = f_opendir(&dir, path);
if (res != FR_OK)
return res;
dirLength = strlen(path);
for (;;)
{
// Clear file or folder path.
path[dirLength] = 0;
// Read a directory item.
res = f_readdir(&dir, &fno);
// Break on error or end of dir.
if (res != FR_OK || fno.fname[0] == 0)
break;
// Skip official Nintendo dir if started from root.
if (!hos_folder && !strcmp(fno.fname, "Nintendo"))
continue;
// // Set new directory or file.
memcpy(&path[dirLength], "/", 1);
memcpy(&path[dirLength + 1], fno.fname, strlen(fno.fname) + 1);
// gfx_printf("THING: %s\n", fno.fname);
// gfx_printf("Path: %s\n", dir);
// Is it a directory?
if (fno.fattrib & AM_DIR)
{
if (
strcmp(fno.fname, ".Trash") == 0 ||
strcmp(fno.fname, ".Trashes") == 0 ||
strcmp(fno.fname, ".DS_Store") == 0 ||
strcmp(fno.fname, ".Spotlight-V100") == 0 ||
strcmp(fno.fname, ".apDisk") == 0 ||
strcmp(fno.fname, ".VolumeIcon.icns") == 0 ||
strcmp(fno.fname, ".fseventsd") == 0 ||
strcmp(fno.fname, ".TemporaryItems") == 0
) {
_FolderDelete(path);
}
// Enter the directory.
listdir(path, 0);
if (res != FR_OK)
break;
} else {
if (
strcmp(fno.fname, ".DS_Store") == 0 ||
strcmp(fno.fname, ".Spotlight-V100") == 0 ||
strcmp(fno.fname, ".apDisk") == 0 ||
strcmp(fno.fname, ".VolumeIcon.icns") == 0 ||
strcmp(fno.fname, ".fseventsd") == 0 ||
strcmp(fno.fname, ".TemporaryItems") == 0 ||
_StartsWith(fno.fname, "._")
) {
_DeleteFileSimple(path);
}
}
}
f_closedir(&dir);
free(path);
return res;
}
int _fix_attributes(char *path, u32 *total, u32 hos_folder, u32 check_first_run){
FRESULT res;
DIR dir;
u32 dirLength = 0;
static FILINFO fno;
if (check_first_run)
{
// Read file attributes.
res = f_stat(path, &fno);
if (res != FR_OK)
return res;
// Check if archive bit is set.
if (fno.fattrib & AM_ARC)
{
*(u32 *)total = *(u32 *)total + 1;
f_chmod(path, 0, AM_ARC);
}
}
// Open directory.
res = f_opendir(&dir, path);
if (res != FR_OK)
return res;
dirLength = strlen(path);
for (;;)
{
// Clear file or folder path.
path[dirLength] = 0;
// Read a directory item.
res = f_readdir(&dir, &fno);
// Break on error or end of dir.
if (res != FR_OK || fno.fname[0] == 0)
break;
// Skip official Nintendo dir if started from root.
if (!hos_folder && !strcmp(fno.fname, "Nintendo"))
continue;
// Set new directory or file.
memcpy(&path[dirLength], "/", 1);
memcpy(&path[dirLength + 1], fno.fname, strlen(fno.fname) + 1);
// Check if archive bit is set.
if (fno.fattrib & AM_ARC)
{
*total = *total + 1;
f_chmod(path, 0, AM_ARC);
}
// Is it a directory?
if (fno.fattrib & AM_DIR)
{
// Set archive bit to NCA folders.
if (hos_folder && !strcmp(fno.fname + strlen(fno.fname) - 4, ".nca"))
{
*total = *total + 1;
f_chmod(path, AM_ARC, AM_ARC);
}
// Enter the directory.
res = _fix_attributes(path, total, hos_folder, 0);
if (res != FR_OK)
break;
}
}
f_closedir(&dir);
return res;
}
void m_entry_fixArchiveBit(u32 type){
gfx_clearscreen();
gfx_printf("\n\n-- Fix Archive Bits\n\n");
char path[256];
char label[16];
u32 total = 0;
if (sd_mount())
{
switch (type)
{
case 0:
strcpy(path, "/");
strcpy(label, "SD Card");
break;
case 1:
default:
strcpy(path, "/Nintendo");
strcpy(label, "Nintendo folder");
break;
}
gfx_printf("Traversing all %s files!\nThis may take some time...\n\n", label);
_fix_attributes(path, &total, type, type);
gfx_printf("%kTotal archive bits cleared: %d!%k", 0xFF96FF00, total, 0xFFCCCCCC);
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
}
void m_entry_fixAIOUpdate(){
gfx_clearscreen();
gfx_printf("\n\n-- Fix broken Switch-AiO-Updater update.\n\n");
char *aio_fs_path = CpyStr("sd:/atmosphere/fusee-secondary.bin.aio");
char *aio_p_path = CpyStr("sd:/sept/payload.bin.aio");
char *aio_strt_path = CpyStr("sd:/atmosphere/stratosphere.romfs.aio");
char *o_fs_path = CpyStr("sd:/atmosphere/fusee-secondary.bin");
char *o_p_path = CpyStr("sd:/sept/payload.bin");
char *o_strt_path = CpyStr("sd:/atmosphere/stratosphere.romfs");
if (FileExists(aio_fs_path)) {
gfx_printf("Detected aio updated fusee-secondary file -> replacing original\n\n");
if (FileExists(o_fs_path)) {
_DeleteFileSimple(o_fs_path);
}
_RenameFileSimple(aio_fs_path, o_fs_path);
}
free(aio_fs_path);
free(o_fs_path);
if (FileExists(aio_p_path)) {
gfx_printf("Detected aio updated paload file -> replacing original\n\n");
if (FileExists(o_p_path)) {
_DeleteFileSimple(o_p_path);
}
_RenameFileSimple(aio_p_path, o_p_path);
}
free(aio_p_path);
free(o_p_path);
if (FileExists(aio_strt_path)) {
gfx_printf("Detected aio updated stratosphere file -> replacing original\n\n");
if (FileExists(o_strt_path)) {
_DeleteFileSimple(o_strt_path);
}
_RenameFileSimple(aio_strt_path, o_strt_path);
}
free(aio_strt_path);
free(o_strt_path);
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
void m_entry_fixClingWrap(){
gfx_clearscreen();
gfx_printf("\n\n-- Fixing ClingWrap.\n\n");
char *bpath = CpyStr("sd:/_b0otloader");
char *bopath = CpyStr("sd:/bootloader");
char *kpath = CpyStr("sd:/atmosphere/_k1ps");
char *kopath = CpyStr("sd:/atmosphere/kips");
char *ppath = CpyStr("sd:/bootloader/_patchesCW.ini");
char *popath = CpyStr("sd:/atmosphere/patches.ini");
if (FileExists(bpath)) {
if (FileExists(bopath)) {
FolderDelete(bopath);
}
int res = f_rename(bpath, bopath);
if (res){
DrawError(newErrCode(res));
}
gfx_printf("-- Fixed Bootloader\n");
}
if (FileExists(kpath)) {
if (FileExists(kopath)) {
FolderDelete(kopath);
}
int res = f_rename(kpath, kopath);
if (res){
DrawError(newErrCode(res));
}
gfx_printf("-- Fixed kips\n");
}
if (FileExists(ppath)) {
if (FileExists(popath)) {
_DeleteFileSimple(popath);
}
_RenameFileSimple(ppath,popath);
gfx_printf("-- Fixed patches.ini\n");
}
free(bpath);
free(bopath);
free(kpath);
free(kopath);
free(ppath);
free(popath);
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
void _deleteTheme(char* basePath, char* folderId){
char *path = CombinePaths(basePath, folderId);
if (FileExists(path)) {
gfx_printf("-- Theme found: %s\n", path);
FolderDelete(path);
}
free(path);
}
void m_entry_deleteInstalledThemes(){
gfx_clearscreen();
gfx_printf("\n\n-- Deleting installed themes.\n\n");
_deleteTheme("sd:/atmosphere/contents", "0100000000001000");
_deleteTheme("sd:/atmosphere/contents", "0100000000001007");
_deleteTheme("sd:/atmosphere/contents", "0100000000001013");
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
void m_entry_deleteBootFlags(){
gfx_clearscreen();
gfx_printf("\n\n-- Disabling automatic sysmodule startup.\n\n");
char *storedPath = CpyStr("sd:/atmosphere/contents");
int readRes = 0;
Vector_t fileVec = ReadFolder(storedPath, &readRes);
if (readRes){
clearFileVector(&fileVec);
DrawError(newErrCode(readRes));
} else {
vecDefArray(FSEntry_t*, fsEntries, fileVec);
for (int i = 0; i < fileVec.count; i++){
char *suf = "/flags/boot2.flag";
char *flagPath = CombinePaths(storedPath, fsEntries[i].name);
flagPath = CombinePaths(flagPath, suf);
if (FileExists(flagPath)) {
gfx_printf("Deleting: %s\n", flagPath);
_DeleteFileSimple(flagPath);
}
free(flagPath);
}
}
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
void m_entry_fixMacSpecialFolders(){
gfx_clearscreen();
gfx_printf("\n\n-- Fix mac folders (this can take some time, please wait.)\n\n");
listdir("/", 0);
gfx_printf("\n\rDone, press a key to proceed.");
hidWait();
// browse path
// list files & folders
// if file -> delete
// if folder !== nintendo
// if folder m_entry_fixMacSpecialFolders with new path
}
void m_entry_stillNoBootInfo(){
gfx_clearscreen();
gfx_printf("\n\n-- My switch still does not boot.\n\n");
gfx_printf("%kDo you have a gamecard inserted?\n", COLOR_WHITE);
gfx_printf("Try taking it out and reboot.\n\n");
gfx_printf("%kDid you recently update Atmosphere/DeepSea?\n", COLOR_WHITE);
gfx_printf("Insert your sdcard into a computer, delete 'atmosphere', 'bootloader' & 'sept', download your preffered CFW and put the files back on your switch.\n\n");
gfx_printf("%kDid you just buy a new SD-card?\n", COLOR_WHITE);
gfx_printf("Make sure its not a fake card.\n\n");
gfx_printf("\n\n Done, press a key to proceed.");
hidWait();
}
void m_entry_ViewCredits(){
gfx_clearscreen();
gfx_printf("\nCommon Problem Resolver v%d.%d.%d\nBy Team Neptune\n\nBased on TegraExplorer by SuchMemeManySkill,\nLockpick_RCM & Hekate, from shchmue & CTCaer\n\n\n", LP_VER_MJ, LP_VER_MN, LP_VER_BF);
hidWait();
}
void m_entry_fixAll(){
gfx_clearscreen();
m_entry_deleteBootFlags();
m_entry_deleteInstalledThemes();
m_entry_fixClingWrap();
m_entry_fixAIOUpdate();
m_entry_stillNoBootInfo();
}

View File

@@ -13,7 +13,7 @@ void gfx_printTopInfo() {
bq24193_get_property(BQ24193_ChargeStatus, &current_charge_status);
SETCOLOR(COLOR_DEFAULT, COLOR_WHITE);
gfx_con_setpos(0, 0);
gfx_printf("CPR %d.%d.%d | Battery: %d%% %c\n", LP_VER_MJ, LP_VER_MN, LP_VER_BF, battery >> 8, ((current_charge_status) ? 129 : 32));
gfx_printf("APL %d.%d.%d | Battery: %d%% %c\n", APL_VER_MJ, APL_VER_MN, APL_VER_BF, battery >> 8, ((current_charge_status) ? 129 : 32));
RESETCOLOR;
}

View File

@@ -7,11 +7,13 @@
#define COLOR_GREY 0xFF888888
#define COLOR_DARKGREY 0xFF333333
#define COLOR_ORANGE 0xFFFFA500
#define COLOR_GREEN 0xFF008000
#define COLOR_GREEN 0xFF00FF00
#define COLOR_VIOLET 0xFFEE82EE
#define COLOR_PURPLE 0xFF7C49EF
#define COLOR_BLUE 0xFF0000FF
#define COLOR_CYAN 0xFF00CCFF
#define COLOR_RED 0xFF0000FF
#define COLOR_YELLOW 0xFFFF0000
#define COLOR_YELLOW 0xFFFFFF00
#define COLORTORGB(color) (color & 0x00FFFFFF)
#define SETCOLOR(fg, bg) gfx_con_setcol(fg, 1, bg)

View File

@@ -50,8 +50,8 @@ Input_t *hidRead(){
LbaseY = controller->lstick_y;
}
inputs.up = (controller->up || (controller->lstick_y > LbaseY + 500)) ? 1 : 0;
inputs.down = (controller->down || (controller->lstick_y < LbaseY - 500)) ? 1 : 0;
inputs.up = (controller->up || (controller->lstick_y > LbaseY + 500) || inputs.volp) ? 1 : 0;
inputs.down = (controller->down || (controller->lstick_y < LbaseY - 500) || inputs.volm) ? 1 : 0;
inputs.left = (controller->left || (controller->lstick_x < LbaseX - 500)) ? 1 : 0;
inputs.right = (controller->right || (controller->lstick_x > LbaseX + 500)) ? 1 : 0;
}

View File

@@ -242,9 +242,9 @@ scriptFunction(funcWait){
scriptFunction(funcGetVer){
int *arr = malloc(3 * sizeof(int));
arr[0] = LP_VER_MJ;
arr[1] = LP_VER_MN;
arr[2] = LP_VER_BF;
arr[0] = APL_VER_MJ;
arr[1] = APL_VER_MN;
arr[2] = APL_VER_BF;
Vector_t res = vecFromArray(arr, 3, sizeof(int));
return newVar(IntArrayType, 1, .vectorType = res);
}

View File

@@ -25,20 +25,23 @@
#include <stdlib.h>
#include <string.h>
#include "../cpr/cpr.h"
#include "../apl/apl.h"
extern hekate_config h_cfg;
enum {
MainExplore = 0,
DeleteBootFlags,
DeleteThemes,
FixClingWrap,
FixAIOUpdaterBoot,
FixArchiveBitA,
FixArchiveBitN,
// FixAll,
MainAMS,
DeleteThemes,
FixAMS_4200,
FixAMS_6900,
FixAMS_BD00,
MainConvenience,
FixMarikoWarmbootSleep,
FixMacSpecialFolders,
MainOther,
MainViewStillNoBootInfo,
@@ -48,32 +51,34 @@ enum {
MainRebootRCM,
// MainRebootNormal,
MainRebootHekate,
MainRebootAMS,
};
MenuEntry_t mainMenuEntries[] = {
[MainExplore] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "-- Bootfixes --"},
[DeleteBootFlags] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Disable automatic sysmodule startup"},
[DeleteThemes] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Delete installed themes"},
[FixClingWrap] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Fix ClingWrap"},
[FixAIOUpdaterBoot] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Fix Switch-AiO-Updater update"},
[FixArchiveBitA] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Fix archive bit (all folders except nintendo)"},
[FixArchiveBitN] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Fix archive bit (nintendo folder)"},
[MainExplore] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "-- Behebe Start Probleme --"},
[DeleteBootFlags] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Automatisches starten der sysmodule deaktivieren"},
[FixArchiveBitA] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Behebe Archive Bit (alle Ordner ausser Nintendo)"},
[FixArchiveBitN] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Behebe Archive Bit (Nintendo Ordner)"},
// [FixAll] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Try everything"},
[MainConvenience] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Convenience --"},
[FixMacSpecialFolders] = {.optionUnion = COLORTORGB(COLOR_ORANGE), .name = "Remove special folders created by Mac"},
[MainAMS] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Atmosphere Fehlercodes --"},
[DeleteThemes] = {.optionUnion = COLORTORGB(COLOR_CYAN), .name = "Fix 0100000000001000 (Installiertes Theme)"},
[FixAMS_4200] = {.optionUnion = COLORTORGB(COLOR_CYAN), .name = "Fix 4200000000000010 (ldn_mitm)"},
[FixAMS_6900] = {.optionUnion = COLORTORGB(COLOR_CYAN), .name = "Fix 690000000000000D (sys-con)"},
[FixAMS_BD00] = {.optionUnion = COLORTORGB(COLOR_CYAN), .name = "Fix 010000000000BD00 (MissionControl)"},
[MainOther] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Other --"},
[MainViewStillNoBootInfo] = {.optionUnion = COLORTORGB(COLOR_YELLOW), .name = "My switch still does not boot"},
[MainViewCredits] = {.optionUnion = COLORTORGB(COLOR_YELLOW), .name = "Credits"},
[MainConvenience] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Bequemlichkeit --"},
[FixMarikoWarmbootSleep] = {.optionUnion = COLORTORGB(COLOR_ORANGE), .name = "Mariko Sleep Fix (Warmboot)"},
[FixMacSpecialFolders] = {.optionUnion = COLORTORGB(COLOR_ORANGE), .name = "Entferne spezielle MacOS Dateien"},
[MainExit] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Exit --"},
[MainPowerOff] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Power off"},
[MainRebootRCM] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Reboot to RCM"},
// [MainRebootNormal] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Reboot normally"},
[MainRebootHekate] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Reboot to bootloader/update.bin"},
[MainRebootAMS] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Reboot to atmosphere/reboot_payload.bin"}
[MainOther] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Sonstiges --"},
[MainViewStillNoBootInfo] = {.optionUnion = COLORTORGB(COLOR_YELLOW), .name = "Meine Switch startet nicht mehr"},
[MainViewCredits] = {.optionUnion = COLORTORGB(COLOR_YELLOW), .name = "Ueber"},
[MainExit] = {.optionUnion = COLORTORGB(COLOR_WHITE) | SKIPBIT, .name = "\n-- Beenden --"},
[MainPowerOff] = {.optionUnion = COLORTORGB(COLOR_PURPLE), .name = "Ausschalten"},
[MainRebootRCM] = {.optionUnion = COLORTORGB(COLOR_PURPLE), .name = "Neustart in RCM-Modus"},
// [MainRebootNormal] = {.optionUnion = COLORTORGB(COLOR_VIOLET), .name = "Normaler Neustart"},
[MainRebootHekate] = {.optionUnion = COLORTORGB(COLOR_PURPLE), .name = "Neustart in hekate"}
// [MainBrowseSd] = {.optionUnion = COLORTORGB(COLOR_GREEN), .name = "Browse SD"},
@@ -102,10 +107,6 @@ void HandleSD(){
extern bool is_sd_inited;
extern int launch_payload(char *path);
void RebootToAMS(){
launch_payload("sd:/atmosphere/reboot_payload.bin");
}
void RebootToHekate(){
launch_payload("sd:/bootloader/update.bin");
}
@@ -130,17 +131,18 @@ void archBitHelperN(){
menuPaths mainMenuPaths[] = {
[DeleteBootFlags] = m_entry_deleteBootFlags,
[DeleteThemes] = m_entry_deleteInstalledThemes,
[FixClingWrap] = m_entry_fixClingWrap,
[FixAIOUpdaterBoot] = m_entry_fixAIOUpdate,
[FixArchiveBitA] = archBitHelperA,
[FixArchiveBitN] = archBitHelperN,
[FixAMS_4200] = m_entry_fixAMSError_4200,
[FixAMS_6900] = m_entry_fixAMSError_6900,
[FixAMS_BD00] = m_entry_fixAMSError_BD00,
[FixMacSpecialFolders] = m_entry_fixMacSpecialFolders,
[FixMarikoWarmbootSleep] = m_entry_fixMarikoWarmbootSleep,
// [FixAll] = m_entry_fixAll,
[MainViewStillNoBootInfo] = m_entry_stillNoBootInfo,
[MainRebootHekate] = RebootToHekate,
[MainRebootRCM] = reboot_rcm,
[MainPowerOff] = power_off,
[MainRebootAMS] = RebootToAMS,
[MainViewCredits] = m_entry_ViewCredits,
};
@@ -151,9 +153,9 @@ void EnterMainMenu(){
sd_unmount();
// // -- Exit --
mainMenuEntries[MainRebootAMS].hide = (!sd_get_card_mounted() || !FileExists("sd:/atmosphere/reboot_payload.bin"));
mainMenuEntries[MainRebootHekate].hide = (!sd_get_card_mounted() || !FileExists("sd:/bootloader/update.bin"));
mainMenuEntries[MainRebootRCM].hide = h_cfg.t210b01;
mainMenuEntries[FixMarikoWarmbootSleep].hide = !h_cfg.t210b01;
gfx_clearscreen();
gfx_putc('\n');

View File

@@ -325,11 +325,11 @@ void TakeScreenshot(){
return;
char *name, *path;
const char basepath[] = "sd:/tegraexplorer/screenshots";
const char basepath[] = "sd:/APL/screenshots";
name = malloc(40);
s_printf(name, "Screenshot_%08X.bmp", get_tmr_us());
f_mkdir("sd:/tegraexplorer");
f_mkdir("sd:/APL");
f_mkdir(basepath);
path = CombinePaths(basepath, name);
free(name);

View File

@@ -0,0 +1,360 @@
/*
* Warmboot Extractor
* Based on Atmosphere fusee_setup_horizon.cpp
*
* Copyright (c) 2018-2025 Atmosphère-NX
*/
#include "warmboot_extractor.h"
#include <string.h>
#include <mem/heap.h>
#include <soc/fuse.h>
#include <soc/timer.h>
#include <sec/se.h>
#include "../storage/emummc.h"
#include "../storage/mountmanager.h"
#include <storage/emmc.h>
#include <libs/fatfs/ff.h>
#include <utils/sprintf.h>
// Mariko keyslot for BEK (Boot Encryption Key)
#define KS_MARIKO_BEK 13
// Helper function to check if this is Mariko hardware
bool is_mariko(void)
{
u32 odm4 = fuse_read_odm(4);
// Extract hardware type from ODM4 bits (same logic as Atmosphere fuse_api.cpp)
u32 hw_type = ((odm4 >> 2) & 1) | (((odm4 >> 8) & 1) << 1) | (((odm4 >> 16) & 0xF) << 2);
// Mariko hardware types: Iowa (0x04), Hoag (0x08), Aula (0x10)
if (hw_type == 0x04 || hw_type == 0x08 || hw_type == 0x10)
return true;
if (hw_type == 0x01) // Icosa
return false;
// For 0x02 (Calcio/Copper), fall back to DRAM ID check
return (fuse_read_dramid(false) >= 4);
}
// Read burnt fuse count (ODM6 + ODM7)
u8 get_burnt_fuses(void)
{
u8 fuse_count = 0;
u32 fuse_odm6 = fuse_read_odm(6);
u32 fuse_odm7 = fuse_read_odm(7);
// Count bits in ODM6
for (u32 i = 0; i < 32; i++)
if ((fuse_odm6 >> i) & 1)
fuse_count++;
// Count bits in ODM7
for (u32 i = 0; i < 32; i++)
if ((fuse_odm7 >> i) & 1)
fuse_count++;
return fuse_count;
}
// Generate warmboot cache path based on fuse count
void get_warmboot_path(char *path, size_t path_size, u8 fuse_count)
{
(void)path_size;
// Format matches Atmosphère's convention:
// Mariko: sd:/warmboot_mariko/wb_xx.bin (lowercase hex)
// Erista: not used by Atmosphère, but kept for completeness.
if (is_mariko())
s_printf(path, "sd:/warmboot_mariko/wb_%02x.bin", fuse_count);
else
s_printf(path, "sd:/warmboot_erista/wb_%02x.bin", fuse_count);
}
// Get target firmware version from Package1 header (display only)
static u32 get_target_firmware_from_pkg1(const u8 *package1)
{
switch (package1[0x1F])
{
case 0x01: return 0x100; // 1.0.0
case 0x02: return 0x200; // 2.0.0
case 0x04: return 0x300; // 3.0.0
case 0x07: return 0x400; // 4.0.0
case 0x0B: return 0x500; // 5.0.0
case 0x0E:
if (memcmp(package1 + 0x10, "20180802", 8) == 0) return 0x600; // 6.0.0
if (memcmp(package1 + 0x10, "20181107", 8) == 0) return 0x620; // 6.2.0
break;
case 0x0F: return 0x700; // 7.0.0
case 0x10:
if (memcmp(package1 + 0x10, "20190314", 8) == 0) return 0x800; // 8.0.0
if (memcmp(package1 + 0x10, "20190531", 8) == 0) return 0x810; // 8.1.0
if (memcmp(package1 + 0x10, "20190809", 8) == 0) return 0x900; // 9.0.0
if (memcmp(package1 + 0x10, "20191021", 8) == 0) return 0x910; // 9.1.0
if (memcmp(package1 + 0x10, "20200303", 8) == 0) return 0xA00; // 10.0.0
if (memcmp(package1 + 0x10, "20201030", 8) == 0) return 0xB00; // 11.0.0
if (memcmp(package1 + 0x10, "20210129", 8) == 0) return 0xC00; // 12.0.0
if (memcmp(package1 + 0x10, "20210422", 8) == 0) return 0xC02; // 12.0.2
if (memcmp(package1 + 0x10, "20210607", 8) == 0) return 0xC10; // 12.1.0
if (memcmp(package1 + 0x10, "20210805", 8) == 0) return 0xD00; // 13.0.0
if (memcmp(package1 + 0x10, "20220105", 8) == 0) return 0xD21; // 13.2.1
if (memcmp(package1 + 0x10, "20220209", 8) == 0) return 0xE00; // 14.0.0
if (memcmp(package1 + 0x10, "20220801", 8) == 0) return 0xF00; // 15.0.0
if (memcmp(package1 + 0x10, "20230111", 8) == 0) return 0x1000; // 16.0.0
if (memcmp(package1 + 0x10, "20230906", 8) == 0) return 0x1100; // 17.0.0
if (memcmp(package1 + 0x10, "20240207", 8) == 0) return 0x1200; // 18.0.0
if (memcmp(package1 + 0x10, "20240808", 8) == 0) return 0x1300; // 19.0.0
if (memcmp(package1 + 0x10, "20250206", 8) == 0) return 0x1400; // 20.0.0
if (memcmp(package1 + 0x10, "20251009", 8) == 0) return 0x1500; // 21.0.0
if (memcmp(package1 + 0x10, "20260123", 8) == 0) return 0x1600; // 22.0.0
break;
default:
break;
}
return 0;
}
// Error code to string (display/debug)
const char *wb_error_to_string(wb_extract_error_t err)
{
switch (err)
{
case WB_SUCCESS: return "Success";
case WB_ERR_NULL_INFO: return "NULL wb_info pointer";
case WB_ERR_ERISTA_NOT_SUPPORTED: return "Erista not supported (uses embedded warmboot)";
case WB_ERR_MALLOC_PKG1: return "Failed to allocate Package1 buffer (256KB)";
case WB_ERR_MMC_INIT: return "Failed to initialize eMMC";
case WB_ERR_MMC_PARTITION: return "Failed to set BOOT0 partition";
case WB_ERR_MMC_READ: return "Failed to read Package1 from BOOT0";
case WB_ERR_DECRYPT_VERIFY: return "Package1 decryption failed (BEK missing or wrong)";
case WB_ERR_PK11_MAGIC: return "PK11 magic not found (invalid Package1)";
case WB_ERR_WB_SIZE_INVALID: return "Warmboot size invalid (not 0x800-0x1000)";
case WB_ERR_MALLOC_WB: return "Failed to allocate warmboot buffer";
default: return "Unknown error";
}
}
// Extended extraction with detailed error codes
wb_extract_error_t extract_warmboot_from_pkg1_ex(warmboot_info_t *wb_info)
{
if (!wb_info)
return WB_ERR_NULL_INFO;
// Initialize result
memset(wb_info, 0, sizeof(warmboot_info_t));
wb_info->is_erista = !is_mariko();
wb_info->fuse_count = get_burnt_fuses();
// Erista doesn't need warmboot extraction from Package1
if (wb_info->is_erista)
return WB_ERR_ERISTA_NOT_SUPPORTED;
// From here on, we're dealing with Mariko only
// Allocate buffer for Package1
u8 *pkg1_buffer = (u8 *)malloc(PKG1_SIZE);
if (!pkg1_buffer)
return WB_ERR_MALLOC_PKG1;
u8 *pkg1_buffer_orig = pkg1_buffer;
// Connect eMMC (BOOT0 access)
if (connectMMC(MMC_CONN_EMMC))
{
free(pkg1_buffer_orig);
return WB_ERR_MMC_INIT;
}
msleep(1);
// Set to BOOT0 and read package1
if (!emummc_storage_set_mmc_partition(&emmc_storage, EMMC_BOOT0))
{
disconnectMMC();
free(pkg1_buffer_orig);
return WB_ERR_MMC_PARTITION;
}
if (!emummc_storage_read(&emmc_storage, PKG1_OFFSET / EMMC_BLOCKSIZE, PKG1_SIZE / EMMC_BLOCKSIZE, pkg1_buffer))
{
disconnectMMC();
free(pkg1_buffer_orig);
return WB_ERR_MMC_READ;
}
disconnectMMC();
// On Mariko, Package1 is encrypted and needs decryption.
// Skip 0x170 byte header to get to the encrypted payload.
u8 *pkg1_mariko = pkg1_buffer + 0x170;
// Set IV from package1 + 0x10 (16 bytes)
se_aes_iv_set(KS_MARIKO_BEK, pkg1_mariko + 0x10);
// Decrypt using BEK at keyslot 13
se_aes_crypt_cbc(KS_MARIKO_BEK, 0, /* DECRYPT */
pkg1_mariko + 0x20,
0x40000 - (0x20 + 0x170),
pkg1_mariko + 0x20,
0x40000 - (0x20 + 0x170));
// Verify decryption (first 0x20 bytes should match decrypted header)
if (memcmp(pkg1_mariko, pkg1_mariko + 0x20, 0x20) != 0)
{
free(pkg1_buffer_orig);
return WB_ERR_DECRYPT_VERIFY;
}
// Use pkg1_mariko as the working pointer
pkg1_buffer = pkg1_mariko;
// Detect target firmware (display only)
u32 target_fw = get_target_firmware_from_pkg1(pkg1_buffer);
// Store debug info: Package1 version byte and date string
wb_info->pkg1_version = pkg1_buffer[0x1F];
memcpy(wb_info->pkg1_date, pkg1_buffer + 0x10, 8);
wb_info->pkg1_date[8] = '\0';
// Get burnt fuse count from device
u8 burnt_fuses = get_burnt_fuses();
// For warmboot naming, ALWAYS use burnt_fuses directly.
wb_info->fuse_count = burnt_fuses;
wb_info->burnt_fuses = burnt_fuses;
wb_info->target_firmware = target_fw;
// Determine PK11 offset using firmware hint, then fall back.
u32 pk11_offset = 0x4000;
if (target_fw != 0 && target_fw >= 0x620)
pk11_offset = 0x7000;
u8 *pk11_ptr_u8 = pkg1_buffer + pk11_offset;
bool pk11_ok = memcmp(pk11_ptr_u8, "PK11", 4) == 0;
if (!pk11_ok)
{
pk11_offset = (pk11_offset == 0x4000) ? 0x7000 : 0x4000;
pk11_ptr_u8 = pkg1_buffer + pk11_offset;
pk11_ok = memcmp(pk11_ptr_u8, "PK11", 4) == 0;
}
if (!pk11_ok)
{
free(pkg1_buffer_orig);
return WB_ERR_PK11_MAGIC;
}
wb_info->pk11_offset = pk11_offset;
u32 *pk11_ptr = (u32 *)pk11_ptr_u8;
// Store debug header
for (int i = 0; i < 8; i++)
wb_info->pk11_header[i] = pk11_ptr[i];
// Navigate through PK11 container to find warmboot (Atmosphere logic)
u32 *pk11_data = pk11_ptr + (0x20 / sizeof(u32));
wb_info->sig_found[0] = 0;
wb_info->sig_found[1] = 0;
wb_info->sig_found[2] = 0;
for (int i = 0; i < 3; i++)
{
u32 signature = *pk11_data;
wb_info->sig_found[i] = signature;
switch (signature)
{
case SIG_NX_BOOTLOADER:
pk11_data += pk11_ptr[6] / sizeof(u32);
break;
case SIG_SECURE_MONITOR_1:
case SIG_SECURE_MONITOR_2:
pk11_data += pk11_ptr[4] / sizeof(u32);
break;
default:
i = 3;
break;
}
}
wb_info->debug_ptr_offset = (u32)((u8 *)pk11_data - pk11_ptr_u8);
u32 wb_size = *pk11_data;
// Debug layout type
u32 warmboot_size_header = pk11_ptr[1];
if (warmboot_size_header == wb_size &&
warmboot_size_header >= WARMBOOT_MIN_SIZE &&
warmboot_size_header < WARMBOOT_MAX_SIZE)
wb_info->debug_layout_type = 1;
else
wb_info->debug_layout_type = 2;
if (wb_size < WARMBOOT_MIN_SIZE || wb_size >= WARMBOOT_MAX_SIZE)
{
wb_info->size = wb_size;
free(pkg1_buffer_orig);
return WB_ERR_WB_SIZE_INVALID;
}
wb_info->size = wb_size;
memcpy(wb_info->debug_warmboot_preview, (u8 *)pk11_data, 16);
// Allocate warmboot buffer ([size_u32][warmboot_binary...])
u8 *wb_data = (u8 *)malloc(wb_size);
if (!wb_data)
{
free(pkg1_buffer_orig);
return WB_ERR_MALLOC_WB;
}
memcpy(wb_data, (u8 *)pk11_data, wb_size);
wb_info->data = wb_data;
wb_info->size = wb_size;
free(pkg1_buffer_orig);
return WB_SUCCESS;
}
// Original wrapper for backward compatibility
bool extract_warmboot_from_pkg1(warmboot_info_t *wb_info)
{
return extract_warmboot_from_pkg1_ex(wb_info) == WB_SUCCESS;
}
// Save warmboot to SD card
bool save_warmboot_to_sd(const warmboot_info_t *wb_info, const char *path)
{
if (!wb_info || !wb_info->data || !path)
return false;
FIL fp;
UINT bytes_written = 0;
const char *dir_path = wb_info->is_erista ? "sd:/warmboot_erista" : "sd:/warmboot_mariko";
f_mkdir(dir_path);
if (f_open(&fp, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
return false;
if (f_write(&fp, wb_info->data, wb_info->size, &bytes_written) != FR_OK)
{
f_close(&fp);
return false;
}
f_close(&fp);
return (bytes_written == wb_info->size);
}

View File

@@ -0,0 +1,86 @@
/*
* Warmboot Extractor
* Based on Atmosphere fusee_setup_horizon.cpp
*
* Copyright (c) 2018-2025 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 2, as published
* by the Free Software Foundation.
*/
#ifndef _WARMBOOT_EXTRACTOR_H_
#define _WARMBOOT_EXTRACTOR_H_
#include <stddef.h>
#include <utils/types.h>
// Warmboot binary size constraints
#define WARMBOOT_MIN_SIZE 0x800 // 2048 bytes
#define WARMBOOT_MAX_SIZE 0x1000 // 4096 bytes
// Package1 locations
#define PKG1_OFFSET 0x100000 // 1MB into BOOT0
#define PKG1_SIZE 0x40000 // 256KB
// PK11 magic
#define PK11_MAGIC 0x31314B50 // "PK11" in little endian
// Known payload signatures to skip
#define SIG_NX_BOOTLOADER 0xD5034FDF
#define SIG_SECURE_MONITOR_1 0xE328F0C0
#define SIG_SECURE_MONITOR_2 0xF0C0A7F0
// Warmboot metadata structure
typedef struct {
u32 magic; // "WBT0" (0x30544257)
u32 target_firmware; // Target firmware version
u32 reserved[2]; // Reserved fields
} warmboot_metadata_t;
// Warmboot extraction result
typedef struct {
u8 *data; // Warmboot binary data
u32 size; // Size of warmboot binary
u8 fuse_count; // Burnt fuse count (used for naming: wb_XX.bin)
u8 burnt_fuses; // Actual burnt fuses on device (same as fuse_count)
u32 target_firmware; // Detected target firmware (0 if unknown, for display only)
bool is_erista; // True if Erista, false if Mariko
// Debug fields for troubleshooting
u32 pk11_offset;
u32 pk11_header[8];
u32 sig_found[3];
u32 debug_ptr_offset;
u32 debug_layout_type;
u8 debug_warmboot_preview[16];
u8 pkg1_version; // Package1 version byte at offset 0x1F
u8 pkg1_date[12]; // Package1 date string (8 chars + null)
} warmboot_info_t;
// Extraction error codes
typedef enum {
WB_SUCCESS = 0,
WB_ERR_NULL_INFO,
WB_ERR_ERISTA_NOT_SUPPORTED,
WB_ERR_MALLOC_PKG1,
WB_ERR_MMC_INIT,
WB_ERR_MMC_PARTITION,
WB_ERR_MMC_READ,
WB_ERR_DECRYPT_VERIFY,
WB_ERR_PK11_MAGIC,
WB_ERR_WB_SIZE_INVALID,
WB_ERR_MALLOC_WB,
} wb_extract_error_t;
// Function prototypes
wb_extract_error_t extract_warmboot_from_pkg1_ex(warmboot_info_t *wb_info);
bool extract_warmboot_from_pkg1(warmboot_info_t *wb_info);
bool save_warmboot_to_sd(const warmboot_info_t *wb_info, const char *path);
u8 get_burnt_fuses(void);
bool is_mariko(void);
void get_warmboot_path(char *path, size_t path_size, u8 fuse_count);
const char *wb_error_to_string(wb_extract_error_t err);
#endif /* _WARMBOOT_EXTRACTOR_H_ */