/* * Warmboot Extractor * Based on Atmosphere fusee_setup_horizon.cpp * * Copyright (c) 2018-2025 Atmosphère-NX */ #include "warmboot_extractor.h" #include #include #include #include #include #include "../storage/emummc.h" #include "../storage/mountmanager.h" #include #include #include // 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); }