Compare commits

...

2 Commits

Author SHA1 Message Date
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
5 changed files with 222 additions and 3 deletions

View File

@@ -122,9 +122,8 @@ static const char* config_dirs_to_delete[] = {
};
// Switch directories to delete
// NOTE: .packages is intentionally excluded - UltraHand package cache, preserve during updates
// NOTE: .overlays and .packages excluded - preserve UltraHand .offload hide state during updates
static const char* switch_dirs_to_delete[] = {
"sd:/switch/.overlays",
"sd:/switch/90DNS_tester",
"sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader",

View File

@@ -275,6 +275,167 @@ int folder_copy_with_progress_v2(const char *src, const char *dst, const char *d
return res;
}
// Copy one directory (e.g. .overlays or .packages) with .offload awareness: if the same
// name exists in dst_offload, copy pack's version into dst_offload (update in place);
// otherwise copy to dst_parent. Used only for update mode to preserve UltraHand hide state.
static int copy_dir_offload_aware(const char *src_parent, const char *dst_parent, const char *dst_offload,
const char *display_name, int *copied, int total, u32 start_x, u32 start_y, int *last_percent)
{
DIR dir;
FILINFO fno;
int res;
char src_full[256];
char dst_main[256];
char dst_off[256];
res = f_opendir(&dir, src_parent);
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 (strcmp(fno.fname, ".offload") == 0)
continue;
install_combine_path(src_full, sizeof(src_full), src_parent, fno.fname);
install_combine_path(dst_off, sizeof(dst_off), dst_offload, fno.fname);
if (install_path_exists(dst_off)) {
res = (fno.fattrib & AM_DIR) ? folder_copy(src_full, dst_offload) : file_copy(src_full, dst_off);
} else {
install_combine_path(dst_main, sizeof(dst_main), dst_parent, fno.fname);
res = (fno.fattrib & AM_DIR) ? folder_copy(src_full, dst_parent) : file_copy(src_full, dst_main);
}
(*copied)++;
if (total > 0) {
int percent = (*copied * 100) / total;
if (percent != *last_percent || *copied % 20 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, *copied, total);
install_set_color(COLOR_WHITE);
*last_percent = percent;
}
}
if (res != FR_OK) {
f_closedir(&dir);
return res;
}
}
f_closedir(&dir);
return FR_OK;
}
// Copy switch/ folder for update mode only: preserve SD's .overlays/.offload and
// .packages/.offload; for items that exist there, update in place instead of main area.
int folder_copy_switch_update_offload_aware(const char *src_switch, const char *dst_base, const char *display_name) {
char dst_switch[256];
char src_overlays[256], src_packages[256];
char dst_overlays[256], dst_offload_ovl[256], dst_packages[256], dst_offload_pkg[256];
DIR dir;
FILINFO fno;
int res;
int copied = 0;
int total;
int last_percent = -1;
u32 start_x, start_y;
install_combine_path(dst_switch, sizeof(dst_switch), dst_base, "switch");
if (!install_path_exists(src_switch)) {
install_set_color(COLOR_ORANGE);
gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name);
install_set_color(COLOR_WHITE);
return FR_NO_FILE;
}
total = install_count_directory_items(src_switch);
if (total == 0) {
f_mkdir(dst_switch);
return FR_OK;
}
gfx_con_getpos(&start_x, &start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total);
install_set_color(COLOR_WHITE);
res = f_mkdir(dst_switch);
if (res != FR_OK && res != FR_EXIST) return res;
res = f_opendir(&dir, src_switch);
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 (strcmp(fno.fname, ".overlays") == 0) {
install_combine_path(src_overlays, sizeof(src_overlays), src_switch, ".overlays");
install_combine_path(dst_overlays, sizeof(dst_overlays), dst_switch, ".overlays");
install_combine_path(dst_offload_ovl, sizeof(dst_offload_ovl), dst_overlays, ".offload");
if (!install_path_exists(src_overlays)) { f_closedir(&dir); continue; }
f_mkdir(dst_overlays);
f_mkdir(dst_offload_ovl);
res = copy_dir_offload_aware(src_overlays, dst_overlays, dst_offload_ovl, display_name,
&copied, total, start_x, start_y, &last_percent);
} else if (strcmp(fno.fname, ".packages") == 0) {
install_combine_path(src_packages, sizeof(src_packages), src_switch, ".packages");
install_combine_path(dst_packages, sizeof(dst_packages), dst_switch, ".packages");
install_combine_path(dst_offload_pkg, sizeof(dst_offload_pkg), dst_packages, ".offload");
if (!install_path_exists(src_packages)) { f_closedir(&dir); continue; }
f_mkdir(dst_packages);
f_mkdir(dst_offload_pkg);
{ /* package.ini into main area */
char src_ini[256], dst_ini[256];
s_printf(src_ini, "%s/package.ini", src_packages);
s_printf(dst_ini, "%s/package.ini", dst_packages);
if (install_path_exists(src_ini)) {
res = file_copy(src_ini, dst_ini);
if (res == FR_OK) copied++;
}
}
res = copy_dir_offload_aware(src_packages, dst_packages, dst_offload_pkg, display_name,
&copied, total, start_x, start_y, &last_percent);
} else {
char src_full[256], dst_full[256];
install_combine_path(src_full, sizeof(src_full), src_switch, fno.fname);
install_combine_path(dst_full, sizeof(dst_full), dst_switch, fno.fname);
if (fno.fattrib & AM_DIR)
res = folder_copy(src_full, dst_switch);
else
res = file_copy(src_full, dst_full);
copied++;
}
if (res != FR_OK) break;
if (total > 0 && (copied % 20 == 0 || copied == total)) {
int percent = (copied * 100) / total;
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, copied, total);
install_set_color(COLOR_WHITE);
}
}
f_closedir(&dir);
gfx_con_setpos(start_x, start_y);
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" Kopiere: %s [100%%] (%d/%d) - Fertig!\n", display_name, copied, total);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_RED);
gfx_printf(" Kopiere: %s - Fehlgeschlagen!\n", display_name);
install_set_color(COLOR_WHITE);
}
return res;
}
// Recursive folder delete with progress tracking (shared implementation)
int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
DIR dir;

View File

@@ -40,3 +40,5 @@ 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_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent);
int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name);
// Update-only: copy switch/ preserving .overlays/.offload and .packages/.offload (UltraHand hide state)
int folder_copy_switch_update_offload_aware(const char *src_switch, const char *dst_base, const char *display_name);

View File

@@ -103,7 +103,7 @@ int update_mode_install(omninx_variant_t variant) {
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/switch", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
res = folder_copy_switch_update_offload_aware(src_path, "sd:/", "switch/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/warmboot_mariko", staging);

View File

@@ -428,6 +428,63 @@ void ipl_main(void) {
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_WHITE);
while (btn_read() & BTN_POWER) { msleep(50); }
bool acknowledged = false;
while (!acknowledged) {
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) acknowledged = true;
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) acknowledged = true;
msleep(50);
}
// Wait for A and Power to be released so the same press doesn't start the install
while (btn_read() & BTN_POWER) { msleep(50); }
bool released = false;
while (!released) {
jc_gamepad_rpt_t *jc = joycon_poll();
released = (!jc || !jc->a) && !(btn_read() & BTN_POWER);
if (!released) msleep(50);
}
msleep(300); // Short delay so the next screen is visible before accepting input
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
}
// Show information
set_color(COLOR_CYAN);
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");