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
This commit is contained in:
549
source/apl/apl.c
549
source/apl/apl.c
@@ -17,6 +17,8 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "../warmboot/warmboot_extractor.h"
|
||||
// #include <dirent.h>
|
||||
// #include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -374,6 +376,553 @@ void m_entry_fixMacSpecialFolders(){
|
||||
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");
|
||||
|
||||
@@ -11,3 +11,4 @@ void m_entry_ViewCredits();
|
||||
void m_entry_fixAll();
|
||||
void m_entry_stillNoBootInfo();
|
||||
void m_entry_fixMacSpecialFolders();
|
||||
void m_entry_fixMarikoWarmbootSleep(void);
|
||||
|
||||
Reference in New Issue
Block a user