- 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
939 lines
30 KiB
C
939 lines
30 KiB
C
/*
|
|
* OmniNX Installer - Installation Logic
|
|
*/
|
|
|
|
#include "install.h"
|
|
#include "fs.h"
|
|
#include "screenshot.h"
|
|
#include "version.h"
|
|
#include "gfx.h"
|
|
#include <libs/fatfs/ff.h>
|
|
#include <input/joycon.h>
|
|
#include <mem/heap.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <utils/btn.h>
|
|
#include <utils/ini.h>
|
|
#include <utils/list.h>
|
|
#include <utils/sprintf.h>
|
|
#include <utils/util.h>
|
|
|
|
#define GROUPED_DELETE_MAX_ENTRIES 512
|
|
|
|
#ifndef VERSION
|
|
#define VERSION "1.0.0"
|
|
#endif
|
|
|
|
|
|
// Color definitions (some already defined in types.h, but we override for consistency)
|
|
#undef COLOR_CYAN
|
|
#undef COLOR_WHITE
|
|
#undef COLOR_GREEN
|
|
#undef COLOR_YELLOW
|
|
#undef COLOR_ORANGE
|
|
#undef COLOR_RED
|
|
#define COLOR_CYAN 0xFF00FFFF
|
|
#define COLOR_WHITE 0xFFFFFFFF
|
|
#define COLOR_GREEN 0xFF00FF00
|
|
#define COLOR_YELLOW 0xFFFFDD00
|
|
#define COLOR_ORANGE 0xFF00A5FF
|
|
#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) {
|
|
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)
|
|
void install_check_and_clear_screen_if_needed(void) {
|
|
// In the gfx system:
|
|
// - gfx_con.x is the vertical position (line number, increments by 16 per line)
|
|
// - gfx_con.y is the horizontal position (pixels from left)
|
|
// - Screen is 720px wide, 1280px tall
|
|
// - Font is 16px, so we have 720/16 = 45 lines before wrapping
|
|
// - When gfx_con.x > 720-16, it wraps to x=0, causing overwrite
|
|
|
|
// Clear when we're past ~35 lines (35 * 16 = 560 pixels) to leave room
|
|
if (gfx_con.x > 35 * 16) {
|
|
// Clear screen and reset position
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
install_print_main_header();
|
|
}
|
|
}
|
|
|
|
// Check if file/directory exists
|
|
bool install_path_exists(const char *path) {
|
|
FILINFO fno;
|
|
return (f_stat(path, &fno) == FR_OK);
|
|
}
|
|
|
|
// Count total items (files + directories) in a directory tree recursively
|
|
int install_count_directory_items(const char *path) {
|
|
DIR dir;
|
|
FILINFO fno;
|
|
int res;
|
|
int count = 0;
|
|
|
|
res = f_opendir(&dir, path);
|
|
if (res != FR_OK) {
|
|
return 0;
|
|
}
|
|
|
|
while (1) {
|
|
res = f_readdir(&dir, &fno);
|
|
if (res != FR_OK || fno.fname[0] == 0) break;
|
|
|
|
// Skip . and ..
|
|
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
|
|
continue;
|
|
}
|
|
|
|
count++; // Count this item
|
|
|
|
// If it's a directory, recursively count its contents
|
|
if (fno.fattrib & AM_DIR) {
|
|
char sub_path[256];
|
|
s_printf(sub_path, "%s/%s", path, fno.fname);
|
|
count += install_count_directory_items(sub_path);
|
|
}
|
|
}
|
|
|
|
f_closedir(&dir);
|
|
return count;
|
|
}
|
|
|
|
// Helper to combine paths (handles trailing slashes properly)
|
|
void install_combine_path(char *result, size_t size, const char *base, const char *add) {
|
|
size_t base_len = strlen(base);
|
|
if (base_len > 0 && base[base_len - 1] == '/') {
|
|
// Base has trailing slash, just append
|
|
s_printf(result, "%s%s", base, add);
|
|
} else {
|
|
// No trailing slash, add one
|
|
s_printf(result, "%s/%s", base, add);
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
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;
|
|
FILINFO fno;
|
|
int res;
|
|
char src_full[256];
|
|
char dst_full[256];
|
|
char dst_dir[256];
|
|
|
|
res = f_opendir(&dir, src);
|
|
if (res != FR_OK) {
|
|
return res;
|
|
}
|
|
|
|
// Get folder name from src path
|
|
const char *folder_name = strrchr(src, '/');
|
|
if (folder_name) {
|
|
folder_name++;
|
|
} else {
|
|
folder_name = src;
|
|
}
|
|
|
|
// Create destination folder path (handle trailing slash in dst)
|
|
install_combine_path(dst_dir, sizeof(dst_dir), dst, folder_name);
|
|
|
|
// Try to create the directory (ignore if it already exists)
|
|
res = f_mkdir(dst_dir);
|
|
if (res == FR_EXIST) {
|
|
res = FR_OK; // Directory already exists, that's fine
|
|
} else if (res != FR_OK) {
|
|
// If mkdir fails, check if it's actually a file (shouldn't happen, but be safe)
|
|
FILINFO fno;
|
|
if (f_stat(dst_dir, &fno) == FR_OK && !(fno.fattrib & AM_DIR)) {
|
|
// Destination exists but is a file, not a directory - this is an error
|
|
f_closedir(&dir);
|
|
return FR_DENIED;
|
|
}
|
|
// If it's a directory, continue (might have been created between check and mkdir)
|
|
if (f_stat(dst_dir, &fno) == FR_OK && (fno.fattrib & AM_DIR)) {
|
|
res = FR_OK;
|
|
} else {
|
|
f_closedir(&dir);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// Copy contents
|
|
while (1) {
|
|
res = f_readdir(&dir, &fno);
|
|
if (res != FR_OK || fno.fname[0] == 0) break;
|
|
|
|
// Skip . and ..
|
|
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
|
|
continue;
|
|
}
|
|
|
|
// Build source and destination paths
|
|
install_combine_path(src_full, sizeof(src_full), src, fno.fname);
|
|
install_combine_path(dst_full, sizeof(dst_full), dst_dir, fno.fname);
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
res = folder_copy_progress_recursive(src_full, dst_dir, copied, total, start_x, start_y, display_name, last_percent);
|
|
// Increment counter for directory (it was counted in total)
|
|
(*copied)++;
|
|
} else {
|
|
res = file_copy(src_full, dst_full);
|
|
(*copied)++;
|
|
}
|
|
|
|
// Update progress every 10 items or when percentage changes
|
|
if ((*copied % 10 == 0 || total == 0) && res == FR_OK) {
|
|
int percent = total > 0 ? (*copied * 100) / total : 0;
|
|
if (percent != *last_percent || *copied % 50 == 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) break;
|
|
}
|
|
|
|
f_closedir(&dir);
|
|
return res;
|
|
}
|
|
|
|
// Progress-aware folder copy (improved version)
|
|
int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name) {
|
|
int copied = 0;
|
|
int total = 0;
|
|
int last_percent = -1;
|
|
u32 start_x, start_y;
|
|
int res;
|
|
|
|
// Check if source exists
|
|
if (!install_path_exists(src)) {
|
|
install_set_color(COLOR_ORANGE);
|
|
gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name);
|
|
install_set_color(COLOR_WHITE);
|
|
return FR_NO_FILE;
|
|
}
|
|
|
|
// Count total items first
|
|
total = install_count_directory_items(src);
|
|
|
|
if (total == 0) {
|
|
// Empty directory, just create destination
|
|
const char *folder_name = strrchr(src, '/');
|
|
if (folder_name) folder_name++;
|
|
else folder_name = src;
|
|
char dst_path[256];
|
|
install_combine_path(dst_path, sizeof(dst_path), dst, folder_name);
|
|
res = f_mkdir(dst_path);
|
|
if (res == FR_OK || res == FR_EXIST) {
|
|
return FR_OK;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Save cursor position
|
|
gfx_con_getpos(&start_x, &start_y);
|
|
|
|
// Show initial status
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total);
|
|
install_set_color(COLOR_WHITE);
|
|
|
|
// Perform the copy with progress updates
|
|
res = folder_copy_progress_recursive(src, dst, &copied, total, start_x, start_y, display_name, &last_percent);
|
|
|
|
// Final update - overwrite the same line
|
|
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);
|
|
gfx_printf(" Fehler: %s (Code=%d)\n", fs_error_str(res), res);
|
|
gfx_printf(" Quelle: %s\n", src);
|
|
gfx_printf(" Ziel: %s\n", dst);
|
|
install_set_color(COLOR_WHITE);
|
|
|
|
// Fallback: try using the original folder_copy function
|
|
install_set_color(COLOR_ORANGE);
|
|
gfx_printf(" Versuche alternative Kopiermethode...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
int fallback_res = folder_copy(src, dst);
|
|
if (fallback_res == FR_OK) {
|
|
install_set_color(COLOR_GREEN);
|
|
gfx_printf(" Alternative Kopie erfolgreich!\n");
|
|
install_set_color(COLOR_WHITE);
|
|
return FR_OK;
|
|
}
|
|
}
|
|
|
|
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;
|
|
FILINFO fno;
|
|
int res;
|
|
|
|
res = f_opendir(&dir, path);
|
|
if (res != FR_OK) {
|
|
// Maybe it's a file, try to delete it
|
|
res = f_unlink(path);
|
|
if (res == FR_OK || res == FR_NO_FILE) {
|
|
(*deleted)++;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
while (1) {
|
|
res = f_readdir(&dir, &fno);
|
|
if (res != FR_OK || fno.fname[0] == 0) break;
|
|
|
|
// Skip . and ..
|
|
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
|
|
continue;
|
|
}
|
|
|
|
char full_path[256];
|
|
install_combine_path(full_path, sizeof(full_path), path, fno.fname);
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
res = folder_delete_progress_recursive(full_path, deleted, total, start_x, start_y, display_name, last_percent);
|
|
(*deleted)++;
|
|
} else {
|
|
// Clear read-only attribute if set
|
|
if (fno.fattrib & AM_RDO) {
|
|
f_chmod(full_path, fno.fattrib & ~AM_RDO, AM_RDO);
|
|
}
|
|
res = f_unlink(full_path);
|
|
if (res == FR_OK || res == FR_NO_FILE) {
|
|
(*deleted)++;
|
|
}
|
|
}
|
|
|
|
// Update progress every 10 items or when percentage changes
|
|
if ((*deleted % 10 == 0 || total == 0) && (res == FR_OK || res == FR_NO_FILE)) {
|
|
int percent = total > 0 ? (*deleted * 100) / total : 0;
|
|
if (percent != *last_percent || *deleted % 50 == 0) {
|
|
gfx_con_setpos(start_x, start_y);
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", display_name, percent, *deleted, total);
|
|
install_set_color(COLOR_WHITE);
|
|
*last_percent = percent;
|
|
}
|
|
}
|
|
|
|
if (res != FR_OK && res != FR_NO_FILE) break;
|
|
}
|
|
|
|
f_closedir(&dir);
|
|
|
|
if (res == FR_OK || res == FR_NO_FILE) {
|
|
// Check and clear read-only attribute on directory if set
|
|
FILINFO dir_info;
|
|
if (f_stat(path, &dir_info) == FR_OK && (dir_info.fattrib & AM_RDO)) {
|
|
f_chmod(path, dir_info.fattrib & ~AM_RDO, AM_RDO);
|
|
}
|
|
res = f_unlink(path);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// Progress-aware delete for a single directory (used for large directories)
|
|
int folder_delete_single_with_progress(const char *path, const char *display_name) {
|
|
int deleted = 0;
|
|
int total = 0;
|
|
int last_percent = -1;
|
|
u32 start_x, start_y;
|
|
int res;
|
|
|
|
// Count total items first
|
|
total = install_count_directory_items(path);
|
|
|
|
if (total == 0) {
|
|
// Empty directory, just delete it
|
|
FILINFO fno;
|
|
if (f_stat(path, &fno) == FR_OK) {
|
|
if (fno.fattrib & AM_RDO) {
|
|
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
|
|
}
|
|
res = f_unlink(path);
|
|
} else {
|
|
res = FR_NO_FILE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Save cursor position
|
|
gfx_con_getpos(&start_x, &start_y);
|
|
|
|
// Show initial status
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", display_name, total);
|
|
install_set_color(COLOR_WHITE);
|
|
|
|
// Use the shared recursive progress function
|
|
res = folder_delete_progress_recursive(path, &deleted, total, start_x, start_y, display_name, &last_percent);
|
|
|
|
// Final update
|
|
gfx_con_setpos(start_x, start_y);
|
|
if (res == FR_OK || res == FR_NO_FILE) {
|
|
install_set_color(COLOR_GREEN);
|
|
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", display_name, deleted, total);
|
|
install_set_color(COLOR_WHITE);
|
|
} else {
|
|
install_set_color(COLOR_RED);
|
|
gfx_printf(" Loesche: %s - Fehlgeschlagen!\n", display_name);
|
|
install_set_color(COLOR_WHITE);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// Delete a list of paths with progress tracking
|
|
// Delete multiple path lists under one label; one progress line "Bereinige: folder/ [p%] (d/t)".
|
|
// Varargs: path lists (const char**), terminated by NULL.
|
|
int delete_path_lists_grouped(const char *folder_display_name, ...) {
|
|
va_list ap;
|
|
const char *entries[GROUPED_DELETE_MAX_ENTRIES];
|
|
int n_entries = 0;
|
|
int deleted = 0;
|
|
int failed = 0;
|
|
u32 start_x, start_y;
|
|
int last_percent = -1;
|
|
int res;
|
|
|
|
va_start(ap, folder_display_name);
|
|
const char **list;
|
|
while (n_entries < GROUPED_DELETE_MAX_ENTRIES && (list = va_arg(ap, const char **)) != NULL) {
|
|
for (int i = 0; list[i] != NULL && n_entries < GROUPED_DELETE_MAX_ENTRIES; i++) {
|
|
if (install_path_exists(list[i]))
|
|
entries[n_entries++] = list[i];
|
|
}
|
|
}
|
|
va_end(ap);
|
|
|
|
if (n_entries == 0)
|
|
return FR_OK;
|
|
|
|
int total_paths = n_entries;
|
|
gfx_con_getpos(&start_x, &start_y);
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Bereinige: %s [ 0%%] (0/%d)", folder_display_name, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
|
|
for (int i = 0; i < n_entries; i++) {
|
|
const char *path = entries[i];
|
|
FILINFO fno;
|
|
if (f_stat(path, &fno) != FR_OK)
|
|
continue;
|
|
|
|
const char *item_name = strrchr(path, '/');
|
|
if (item_name) item_name++;
|
|
else item_name = path;
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
int item_count = install_count_directory_items(path);
|
|
if (item_count > 50) {
|
|
gfx_printf("\n");
|
|
res = folder_delete_single_with_progress(path, item_name);
|
|
} else {
|
|
res = folder_delete(path);
|
|
}
|
|
} else {
|
|
if (fno.fattrib & AM_RDO)
|
|
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
|
|
res = f_unlink(path);
|
|
}
|
|
|
|
if (res == FR_OK || res == FR_NO_FILE)
|
|
deleted++;
|
|
else
|
|
failed++;
|
|
|
|
int percent = (deleted * 100) / total_paths;
|
|
if (percent != last_percent || deleted % 5 == 0) {
|
|
gfx_con_setpos(start_x, start_y);
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d)", folder_display_name, percent, deleted, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
last_percent = percent;
|
|
}
|
|
}
|
|
|
|
gfx_con_setpos(start_x, start_y);
|
|
if (failed == 0) {
|
|
install_set_color(COLOR_GREEN);
|
|
gfx_printf(" Bereinige: %s [100%%] (%d/%d) - Fertig!\n", folder_display_name, deleted, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
} else {
|
|
install_set_color(COLOR_ORANGE);
|
|
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d) - %d Fehler\n", folder_display_name, last_percent, deleted, total_paths, failed);
|
|
install_set_color(COLOR_WHITE);
|
|
}
|
|
return (failed == 0) ? FR_OK : FR_DISK_ERR;
|
|
}
|
|
|
|
int delete_path_list(const char* paths[], const char* description) {
|
|
int res;
|
|
int deleted = 0;
|
|
int failed = 0;
|
|
int total_paths = 0;
|
|
u32 start_x, start_y;
|
|
int last_percent = -1;
|
|
|
|
// Count total paths first
|
|
for (int i = 0; paths[i] != NULL; i++) {
|
|
if (install_path_exists(paths[i])) {
|
|
total_paths++;
|
|
}
|
|
}
|
|
|
|
if (total_paths == 0) {
|
|
return FR_OK; // Nothing to delete
|
|
}
|
|
|
|
// Save cursor position
|
|
gfx_con_getpos(&start_x, &start_y);
|
|
|
|
// Show initial status
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", description, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
|
|
for (int i = 0; paths[i] != NULL; i++) {
|
|
if (install_path_exists(paths[i])) {
|
|
FILINFO fno;
|
|
f_stat(paths[i], &fno);
|
|
|
|
// Extract just the folder/file name for display
|
|
const char *item_name = strrchr(paths[i], '/');
|
|
if (item_name) {
|
|
item_name++; // Skip the '/'
|
|
} else {
|
|
item_name = paths[i];
|
|
}
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
// Check if directory has many items - if so, show detailed progress
|
|
int item_count = install_count_directory_items(paths[i]);
|
|
if (item_count > 50) {
|
|
// Large directory - show individual progress
|
|
// Move to new line for the detailed progress
|
|
gfx_printf("\n");
|
|
res = folder_delete_single_with_progress(paths[i], item_name);
|
|
} else {
|
|
// Small directory - use regular delete
|
|
res = folder_delete(paths[i]);
|
|
}
|
|
} else {
|
|
// Clear read-only attribute if set
|
|
if (fno.fattrib & AM_RDO) {
|
|
f_chmod(paths[i], fno.fattrib & ~AM_RDO, AM_RDO);
|
|
}
|
|
res = f_unlink(paths[i]);
|
|
}
|
|
|
|
if (res == FR_OK || res == FR_NO_FILE) {
|
|
deleted++;
|
|
} else {
|
|
failed++;
|
|
}
|
|
|
|
// Update list-level progress (only if not showing detailed progress)
|
|
if (fno.fattrib & AM_DIR && install_count_directory_items(paths[i]) <= 50) {
|
|
int percent = (deleted * 100) / total_paths;
|
|
if (percent != last_percent || deleted % 5 == 0) {
|
|
gfx_con_setpos(start_x, start_y);
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", description, percent, deleted, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
last_percent = percent;
|
|
}
|
|
} else if (!(fno.fattrib & AM_DIR)) {
|
|
// Update for files
|
|
int percent = (deleted * 100) / total_paths;
|
|
if (percent != last_percent || deleted % 5 == 0) {
|
|
gfx_con_setpos(start_x, start_y);
|
|
install_set_color(COLOR_CYAN);
|
|
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", description, percent, deleted, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
last_percent = percent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Final update
|
|
gfx_con_setpos(start_x, start_y);
|
|
if (failed == 0) {
|
|
install_set_color(COLOR_GREEN);
|
|
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", description, deleted, total_paths);
|
|
install_set_color(COLOR_WHITE);
|
|
} else {
|
|
install_set_color(COLOR_ORANGE);
|
|
gfx_printf(" Loesche: %s [%3d%%] (%d/%d) - %d Fehler\n", description, last_percent, deleted, total_paths, failed);
|
|
install_set_color(COLOR_WHITE);
|
|
}
|
|
|
|
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(§ions);
|
|
|
|
if (ini_parse(§ions, (char *)path, false) != 1) {
|
|
ram_config_free_sections(§ions);
|
|
return RAM_READ_NEED_MENU;
|
|
}
|
|
|
|
bool found = false;
|
|
bool eight = false;
|
|
LIST_FOREACH_ENTRY(ini_sec_t, sec, §ions, 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(§ions);
|
|
|
|
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] = {
|
|
"4 GB Konsole (Standard)",
|
|
"8 GB Konsole",
|
|
};
|
|
|
|
gfx_clear_grey(0x1B);
|
|
gfx_con_setpos(0, 0);
|
|
install_print_main_header();
|
|
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("RAM / Hekate: Konsole-Typ\n\n");
|
|
install_set_color(COLOR_RED);
|
|
gfx_printf("Wenn du dir absolut unsicher bist, waehle unbedingt 4 GB!\n");
|
|
gfx_printf("Eine falsche 8-GB-Wahl kann Probleme verursachen.\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
|
|
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
|
|
int res;
|
|
|
|
if (mode == INSTALL_MODE_UPDATE) {
|
|
// Update mode: selective cleanup then install
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 1: Bereinigung...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = update_mode_cleanup(pack_variant);
|
|
if (res != FR_OK) return res;
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
gfx_printf("\n");
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 2: Dateien kopieren...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = update_mode_install(pack_variant);
|
|
if (res != FR_OK) return res;
|
|
|
|
install_hekate_8gb_post_copy();
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
// Remove staging directory (installed pack)
|
|
res = cleanup_staging_directory(pack_variant);
|
|
if (res != FR_OK) return res;
|
|
// Remove other detected install directories (Standard/Light/OC) that were on SD
|
|
res = cleanup_other_staging_directories(pack_variant);
|
|
return res;
|
|
} else {
|
|
// Clean mode: backup, wipe, restore, install
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 1: Sichere Benutzerdaten...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = clean_mode_backup();
|
|
if (res != FR_OK) return res;
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
gfx_printf("\n");
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 2: Bereinige alte Installation...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = clean_mode_wipe();
|
|
if (res != FR_OK) return res;
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
gfx_printf("\n");
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 3: Stelle Benutzerdaten wieder her...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = clean_mode_restore();
|
|
if (res != FR_OK) return res;
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
gfx_printf("\n");
|
|
install_set_color(COLOR_YELLOW);
|
|
gfx_printf("Schritt 4: Dateien kopieren...\n");
|
|
install_set_color(COLOR_WHITE);
|
|
res = clean_mode_install(pack_variant);
|
|
if (res != FR_OK) return res;
|
|
|
|
install_hekate_8gb_post_copy();
|
|
|
|
install_check_and_clear_screen_if_needed();
|
|
// Remove staging directory (installed pack)
|
|
res = cleanup_staging_directory(pack_variant);
|
|
if (res != FR_OK) return res;
|
|
// Remove other detected install directories (Standard/Light/OC) that were on SD
|
|
res = cleanup_other_staging_directories(pack_variant);
|
|
return res;
|
|
}
|
|
}
|