/* * OmniNX Installer - Installation Logic */ #include "install.h" #include "backup.h" #include "deletion_lists.h" #include "fs.h" #include "version.h" #include "gfx.h" #include #include #include #ifndef VERSION #define VERSION "1.0.0" #endif // Forward declaration static void combine_path(char *result, size_t size, const char *base, const char *add); // 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 void set_color(u32 color) { gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol); } // Check if we need to clear screen (when getting close to bottom) static void 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); // Reprint same header as initial launch set_color(COLOR_CYAN); gfx_printf("========================================\n"); gfx_printf(" OmniNX Installer Payload v%s\n", VERSION); gfx_printf("========================================\n\n"); set_color(COLOR_WHITE); } } // Check if file/directory exists static bool path_exists(const char *path) { FILINFO fno; return (f_stat(path, &fno) == FR_OK); } // Count total items (files + directories) in a directory tree recursively static int 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 += count_directory_items(sub_path); } } f_closedir(&dir); return count; } // Helper to combine paths (handles trailing slashes properly) static void 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); } } // 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) 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 combine_path(src_full, sizeof(src_full), src, fno.fname); 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); set_color(COLOR_CYAN); gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, *copied, total); set_color(COLOR_WHITE); *last_percent = percent; } } if (res != FR_OK) break; } f_closedir(&dir); return res; } // Progress-aware folder copy (improved version) static 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 (!path_exists(src)) { set_color(COLOR_ORANGE); gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name); set_color(COLOR_WHITE); return FR_NO_FILE; } // Count total items first total = 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]; 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 set_color(COLOR_CYAN); gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total); 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) { set_color(COLOR_GREEN); gfx_printf(" Kopiere: %s [100%%] (%d/%d) - Fertig!\n", display_name, copied, total); set_color(COLOR_WHITE); } else { 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); set_color(COLOR_WHITE); // Fallback: try using the original folder_copy function set_color(COLOR_ORANGE); gfx_printf(" Versuche alternative Kopiermethode...\n"); set_color(COLOR_WHITE); int fallback_res = folder_copy(src, dst); if (fallback_res == FR_OK) { set_color(COLOR_GREEN); gfx_printf(" Alternative Kopie erfolgreich!\n"); set_color(COLOR_WHITE); return FR_OK; } } return res; } // Delete a list of paths int delete_path_list(const char* paths[], const char* description) { int res; int deleted = 0; int failed = 0; for (int i = 0; paths[i] != NULL; i++) { if (path_exists(paths[i])) { FILINFO fno; f_stat(paths[i], &fno); if (fno.fattrib & AM_DIR) { res = folder_delete(paths[i]); } else { res = f_unlink(paths[i]); } if (res == FR_OK || res == FR_NO_FILE) { deleted++; } else { failed++; } } } return (failed == 0) ? FR_OK : FR_DISK_ERR; } // Delete old version markers (legacy files from old system) int cleanup_old_version_markers(omninx_variant_t current_variant) { // Delete all old version files (they're no longer used) for (int i = 0; old_version_files_to_delete[i] != NULL; i++) { const char* path = old_version_files_to_delete[i]; if (path_exists(path)) { f_unlink(path); } } return FR_OK; } // Update mode: Cleanup specific directories/files int update_mode_cleanup(omninx_variant_t variant) { check_and_clear_screen_if_needed(); set_color(COLOR_CYAN); gfx_printf(" Bereinige: atmosphere/\n"); set_color(COLOR_WHITE); // Delete atmosphere subdirectories delete_path_list(atmosphere_dirs_to_delete, "atmosphere subdirs"); // Delete atmosphere root directories (title IDs) delete_path_list(atmosphere_root_dirs_to_delete, "atmosphere root dirs"); // Delete atmosphere contents directories (title IDs) delete_path_list(atmosphere_contents_dirs_to_delete, "atmosphere contents dirs"); // Delete atmosphere files delete_path_list(atmosphere_files_to_delete, "atmosphere files"); set_color(COLOR_CYAN); gfx_printf(" Bereinige: bootloader/\n"); set_color(COLOR_WHITE); // Delete bootloader directories delete_path_list(bootloader_dirs_to_delete, "bootloader dirs"); // Delete bootloader files delete_path_list(bootloader_files_to_delete, "bootloader files"); set_color(COLOR_CYAN); gfx_printf(" Bereinige: config/\n"); set_color(COLOR_WHITE); // Delete config directories delete_path_list(config_dirs_to_delete, "config dirs"); set_color(COLOR_CYAN); gfx_printf(" Bereinige: switch/\n"); set_color(COLOR_WHITE); // Delete switch directories delete_path_list(switch_dirs_to_delete, "switch dirs"); // Delete switch files delete_path_list(switch_files_to_delete, "switch files"); set_color(COLOR_CYAN); gfx_printf(" Bereinige: Root-Dateien\n"); set_color(COLOR_WHITE); // Delete root files delete_path_list(root_files_to_delete, "root files"); // Delete miscellaneous directories delete_path_list(misc_dirs_to_delete, "misc dirs"); // Delete miscellaneous files delete_path_list(misc_files_to_delete, "misc files"); set_color(COLOR_GREEN); gfx_printf(" Bereinigung abgeschlossen!\n"); set_color(COLOR_WHITE); return FR_OK; } // Update mode: Copy files from staging int update_mode_install(omninx_variant_t variant) { int res; const char* staging = get_staging_path(variant); char src_path[256]; char dst_path[256]; if (!staging) { return FR_INVALID_PARAMETER; } check_and_clear_screen_if_needed(); set_color(COLOR_YELLOW); gfx_printf("Dateien werden kopiert...\n"); set_color(COLOR_WHITE); // Copy directories (use progress-aware copy for large directories) s_printf(src_path, "%s/atmosphere", staging); res = folder_copy_with_progress_v2(src_path, "sd:/", "atmosphere/"); if (res != FR_OK && res != FR_NO_FILE) return res; s_printf(src_path, "%s/bootloader", staging); res = folder_copy_with_progress_v2(src_path, "sd:/", "bootloader/"); if (res != FR_OK && res != FR_NO_FILE) return res; s_printf(src_path, "%s/config", staging); res = folder_copy_with_progress_v2(src_path, "sd:/", "config/"); 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/"); if (res != FR_OK && res != FR_NO_FILE) return res; s_printf(src_path, "%s/warmboot_mariko", staging); res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/"); if (res != FR_OK && res != FR_NO_FILE) return res; // OC variant includes SaltySD (this is the large one with ~2500 files) if (variant == VARIANT_OC) { s_printf(src_path, "%s/SaltySD", staging); res = folder_copy_with_progress_v2(src_path, "sd:/", "SaltySD/"); if (res != FR_OK && res != FR_NO_FILE) return res; } // Copy root files set_color(COLOR_CYAN); gfx_printf(" Kopiere Root-Dateien...\n"); set_color(COLOR_WHITE); s_printf(src_path, "%s/boot.dat", staging); s_printf(dst_path, "sd:/boot.dat"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } s_printf(src_path, "%s/boot.ini", staging); s_printf(dst_path, "sd:/boot.ini"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } s_printf(src_path, "%s/exosphere.ini", staging); s_printf(dst_path, "sd:/exosphere.ini"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } s_printf(src_path, "%s/hbmenu.nro", staging); s_printf(dst_path, "sd:/hbmenu.nro"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } s_printf(src_path, "%s/loader.bin", staging); s_printf(dst_path, "sd:/loader.bin"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } s_printf(src_path, "%s/payload.bin", staging); s_printf(dst_path, "sd:/payload.bin"); if (path_exists(src_path)) { file_copy(src_path, dst_path); } // Create manifest.ini file set_color(COLOR_CYAN); gfx_printf(" Erstelle manifest.ini...\n"); set_color(COLOR_WHITE); // Ensure config/omninx directory exists s_printf(dst_path, "sd:/config/omninx"); f_mkdir(dst_path); // Determine pack name and update channel const char* pack_name; int update_channel; switch (variant) { case VARIANT_STANDARD: pack_name = "standard"; update_channel = 2; break; case VARIANT_LIGHT: pack_name = "light"; update_channel = 0; break; case VARIANT_OC: pack_name = "oc"; update_channel = 1; break; default: pack_name = "unknown"; update_channel = 0; break; } // Create manifest.ini s_printf(dst_path, "sd:/config/omninx/manifest.ini"); FIL fp; if (f_open(&fp, dst_path, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) { f_printf(&fp, "[OmniNX]\n"); f_printf(&fp, "current_pack=%s\n", pack_name); f_printf(&fp, "version=%s\n", VERSION); f_printf(&fp, "update_channel=%d\n", update_channel); f_printf(&fp, "channel_pack=%s\n", pack_name); f_close(&fp); set_color(COLOR_GREEN); gfx_printf(" [OK] manifest.ini erstellt\n"); set_color(COLOR_WHITE); } else { set_color(COLOR_ORANGE); gfx_printf(" [WARN] manifest.ini konnte nicht erstellt werden\n"); set_color(COLOR_WHITE); } // Cleanup old version markers (legacy files) cleanup_old_version_markers(variant); set_color(COLOR_GREEN); gfx_printf(" Kopie abgeschlossen!\n"); set_color(COLOR_WHITE); return FR_OK; } // Clean mode: Backup user data int clean_mode_backup(void) { set_color(COLOR_CYAN); gfx_printf(" Sichere: DBI, Tinfoil, prod.keys\n"); set_color(COLOR_WHITE); int res = backup_user_data(); if (res == FR_OK) { set_color(COLOR_GREEN); gfx_printf(" [OK] Sicherung abgeschlossen\n"); set_color(COLOR_WHITE); } return res; } // Clean mode: Wipe directories int clean_mode_wipe(void) { int res; // Delete entire directories if (path_exists("sd:/atmosphere")) { set_color(COLOR_CYAN); gfx_printf(" Loesche: atmosphere/\n"); set_color(COLOR_WHITE); res = folder_delete("sd:/atmosphere"); if (res != FR_OK && res != FR_NO_FILE) return res; } if (path_exists("sd:/bootloader")) { set_color(COLOR_CYAN); gfx_printf(" Loesche: bootloader/\n"); set_color(COLOR_WHITE); res = folder_delete("sd:/bootloader"); if (res != FR_OK && res != FR_NO_FILE) return res; } if (path_exists("sd:/config")) { set_color(COLOR_CYAN); gfx_printf(" Loesche: config/\n"); set_color(COLOR_WHITE); res = folder_delete("sd:/config"); if (res != FR_OK && res != FR_NO_FILE) return res; } if (path_exists("sd:/switch")) { set_color(COLOR_CYAN); gfx_printf(" Loesche: switch/\n"); set_color(COLOR_WHITE); res = folder_delete("sd:/switch"); if (res != FR_OK && res != FR_NO_FILE) return res; } // Delete root files set_color(COLOR_CYAN); gfx_printf(" Bereinige: Root-Dateien\n"); set_color(COLOR_WHITE); delete_path_list(root_files_to_delete, "root files"); // Delete miscellaneous directories delete_path_list(misc_dirs_to_delete, "misc dirs"); // Delete miscellaneous files delete_path_list(misc_files_to_delete, "misc files"); // Recreate switch directory set_color(COLOR_CYAN); gfx_printf(" Erstelle: switch/\n"); set_color(COLOR_WHITE); f_mkdir("sd:/switch"); set_color(COLOR_GREEN); gfx_printf(" Bereinigung abgeschlossen!\n"); set_color(COLOR_WHITE); return FR_OK; } // Clean mode: Restore user data int clean_mode_restore(void) { set_color(COLOR_CYAN); gfx_printf(" Stelle wieder her: DBI, Tinfoil, prod.keys\n"); set_color(COLOR_WHITE); int res = restore_user_data(); if (res == FR_OK) { set_color(COLOR_GREEN); gfx_printf(" [OK] Wiederherstellung abgeschlossen\n"); set_color(COLOR_WHITE); } cleanup_backup(); // Clean up temp backup return res; } // Clean mode: Install files int clean_mode_install(omninx_variant_t variant) { // Same as update mode install return update_mode_install(variant); } // Remove staging directory after installation int cleanup_staging_directory(omninx_variant_t pack_variant) { const char* staging = get_staging_path(pack_variant); if (!staging) { return FR_INVALID_PARAMETER; } check_and_clear_screen_if_needed(); if (path_exists(staging)) { set_color(COLOR_YELLOW); gfx_printf("\nEntferne Installationsordner...\n"); set_color(COLOR_WHITE); set_color(COLOR_CYAN); gfx_printf(" Loesche: %s\n", staging); set_color(COLOR_WHITE); int res = folder_delete(staging); if (res == FR_OK) { set_color(COLOR_GREEN); gfx_printf(" [OK] Installationsordner entfernt\n"); set_color(COLOR_WHITE); } else { set_color(COLOR_ORANGE); gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res); set_color(COLOR_WHITE); } return res; } return FR_OK; } // 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 set_color(COLOR_YELLOW); gfx_printf("Schritt 1: Bereinigung...\n"); set_color(COLOR_WHITE); res = update_mode_cleanup(pack_variant); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); gfx_printf("\n"); set_color(COLOR_YELLOW); gfx_printf("Schritt 2: Dateien kopieren...\n"); set_color(COLOR_WHITE); res = update_mode_install(pack_variant); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); // Remove staging directory res = cleanup_staging_directory(pack_variant); return res; } else { // Clean mode: backup, wipe, restore, install set_color(COLOR_YELLOW); gfx_printf("Schritt 1: Sichere Benutzerdaten...\n"); set_color(COLOR_WHITE); res = clean_mode_backup(); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); gfx_printf("\n"); set_color(COLOR_YELLOW); gfx_printf("Schritt 2: Bereinige alte Installation...\n"); set_color(COLOR_WHITE); res = clean_mode_wipe(); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); gfx_printf("\n"); set_color(COLOR_YELLOW); gfx_printf("Schritt 3: Stelle Benutzerdaten wieder her...\n"); set_color(COLOR_WHITE); res = clean_mode_restore(); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); gfx_printf("\n"); set_color(COLOR_YELLOW); gfx_printf("Schritt 4: Dateien kopieren...\n"); set_color(COLOR_WHITE); res = clean_mode_install(pack_variant); if (res != FR_OK) return res; check_and_clear_screen_if_needed(); // Remove staging directory res = cleanup_staging_directory(pack_variant); return res; } }