/* * Copyright (c) 2018 naehrwert * * Copyright (c) 2018 Rajko Stojadinovic * Copyright (c) 2018 CTCaer * Copyright (c) 2018 Reisyukaku * Copyright (c) 2018 M4xw * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "soc/clock.h" #include "soc/uart.h" #include "soc/i2c.h" #include "mem/sdram.h" #include "gfx/di.h" #include "mem/mc.h" #include "soc/t210.h" #include "soc/pmc.h" #include "soc/pinmux.h" #include "soc/fuse.h" #include "utils/util.h" #include "gfx/gfx.h" #include "utils/btn.h" #include "sec/tsec.h" #include "soc/kfuse.h" #include "power/max77620.h" #include "power/max7762x.h" #include "soc/gpio.h" #include "storage/sdmmc.h" #include "libs/fatfs/ff.h" #include "gfx/logos.h" #include "gfx/tui.h" #include "mem/heap.h" #include "utils/list.h" #include "storage/nx_emmc.h" #include "sec/se.h" #include "sec/se_t210.h" #include "hos/hos.h" #include "hos/pkg1.h" #include "hos/pkg2.h" #include "storage/mmc.h" #include "libs/compr/blz.h" #include "power/max17050.h" #include "power/bq24193.h" #include "config/config.h" #include "libs/elfload/elfload.h" #include "../common/common_module.h" // TODO: Make it not suck FIL globalElfFile; void *elfBuff; //TODO: ugly. gfx_ctxt_t gfx_ctxt; gfx_con_t gfx_con; //TODO: Create more macros (info, header, debug, etc) with different colors and utilize them for consistency. #define EPRINTF(text) gfx_printf(&gfx_con, "%k"text"%k\n", 0xFFFF0000, 0xFFCCCCCC) #define EPRINTFARGS(text, args...) gfx_printf(&gfx_con, "%k"text"%k\n", 0xFFFF0000, args, 0xFFCCCCCC) #define WPRINTF(text) gfx_printf(&gfx_con, "%k"text"%k\n", 0xFFFFDD00, 0xFFCCCCCC) #define WPRINTFARGS(text, args...) gfx_printf(&gfx_con, "%k"text"%k\n", 0xFFFFDD00, args, 0xFFCCCCCC) //TODO: ugly. sdmmc_t sd_sdmmc; sdmmc_storage_t sd_storage; FATFS sd_fs; bool sd_mounted; #ifdef MENU_LOGO_ENABLE u8 *Kc_MENU_LOGO; #endif //MENU_LOGO_ENABLE hekate_config h_cfg; bool sd_mount() { if (sd_mounted) return true; if (!sdmmc_storage_init_sd(&sd_storage, &sd_sdmmc, SDMMC_1, SDMMC_BUS_WIDTH_4, 11)) { EPRINTF("Failed to init SD card.\nMake sure that it is inserted."); } else { int res = 0; res = f_mount(&sd_fs, "", 1); if (res == FR_OK) { sd_mounted = 1; return true; } else { EPRINTFARGS("Failed to mount SD card (FatFS Error %d).\nMake sure that a FAT partition exists..", res); } } return false; } void sd_unmount() { if (sd_mounted) { f_mount(NULL, "", 1); sdmmc_storage_end(&sd_storage); sd_mounted = false; } } void *sd_file_read(char *path) { FIL fp; if (f_open(&fp, path, FA_READ) != FR_OK) return NULL; u32 size = f_size(&fp); void *buf = malloc(size); u8 *ptr = buf; while (size > 0) { u32 rsize = MIN(size, 512 * 512); if (f_read(&fp, ptr, rsize, NULL) != FR_OK) { free(buf); return NULL; } ptr += rsize; size -= rsize; } f_close(&fp); return buf; } int sd_save_to_file(void *buf, u32 size, const char *filename) { FIL fp; u32 res = 0; res = f_open(&fp, filename, FA_CREATE_ALWAYS | FA_WRITE); if (res) { EPRINTFARGS("Error (%d) creating file\n%s.\n", res, filename); return 1; } f_sync(&fp); f_write(&fp, buf, size, NULL); f_close(&fp); return 0; } void emmcsn_path_impl(char *path, char *sub_dir, char *filename, sdmmc_storage_t *storage) { sdmmc_storage_t storage2; sdmmc_t sdmmc; char emmcSN[9]; bool init_done = false; memcpy(path, "Backup", 7); f_mkdir(path); if (!storage) { if (!sdmmc_storage_init_mmc(&storage2, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) memcpy(emmcSN, "00000000", 9); else { init_done = true; itoa(storage2.cid.serial, emmcSN, 16); } } else itoa(storage->cid.serial, emmcSN, 16); u32 sub_dir_len = strlen(sub_dir); // Can be a null-terminator. u32 filename_len = strlen(filename); // Can be a null-terminator. memcpy(path + strlen(path), "/", 2); memcpy(path + strlen(path), emmcSN, 9); f_mkdir(path); memcpy(path + strlen(path), sub_dir, sub_dir_len + 1); if (sub_dir_len) f_mkdir(path); memcpy(path + strlen(path), "/", 2); memcpy(path + strlen(path), filename, filename_len + 1); if (init_done) sdmmc_storage_end(&storage2); } void panic(u32 val) { // Set panic code. PMC(APBDEV_PMC_SCRATCH200) = val; //PMC(APBDEV_PMC_CRYPTO_OP) = 1; // Disable SE. TMR(0x18C) = 0xC45A; TMR(0x80) = 0xC0000000; TMR(0x180) = 0x8019; TMR(0x188) = 1; while (1) ; } void config_oscillators() { CLOCK(CLK_RST_CONTROLLER_SPARE_REG0) = (CLOCK(CLK_RST_CONTROLLER_SPARE_REG0) & 0xFFFFFFF3) | 4; SYSCTR0(SYSCTR0_CNTFID0) = 19200000; TMR(0x14) = 0x45F; CLOCK(CLK_RST_CONTROLLER_OSC_CTRL) = 0x50000071; PMC(APBDEV_PMC_OSC_EDPD_OVER) = (PMC(APBDEV_PMC_OSC_EDPD_OVER) & 0xFFFFFF81) | 0xE; PMC(APBDEV_PMC_OSC_EDPD_OVER) = (PMC(APBDEV_PMC_OSC_EDPD_OVER) & 0xFFBFFFFF) | 0x400000; PMC(APBDEV_PMC_CNTRL2) = (PMC(APBDEV_PMC_CNTRL2) & 0xFFFFEFFF) | 0x1000; PMC(APBDEV_PMC_SCRATCH188) = (PMC(APBDEV_PMC_SCRATCH188) & 0xFCFFFFFF) | 0x2000000; CLOCK(CLK_RST_CONTROLLER_CLK_SYSTEM_RATE) = 0x10; CLOCK(CLK_RST_CONTROLLER_PLLMB_BASE) &= 0xBFFFFFFF; PMC(APBDEV_PMC_TSC_MULT) = (PMC(APBDEV_PMC_TSC_MULT) & 0xFFFF0000) | 0x249F; //0x249F = 19200000 * (16 / 32.768 kHz) CLOCK(CLK_RST_CONTROLLER_SCLK_BURST_POLICY) = 0x20004444; CLOCK(CLK_RST_CONTROLLER_SUPER_SCLK_DIVIDER) = 0x80000000; CLOCK(CLK_RST_CONTROLLER_CLK_SYSTEM_RATE) = 2; } void config_gpios() { PINMUX_AUX(PINMUX_AUX_UART2_TX) = 0; PINMUX_AUX(PINMUX_AUX_UART3_TX) = 0; PINMUX_AUX(PINMUX_AUX_GPIO_PE6) = PINMUX_INPUT_ENABLE; PINMUX_AUX(PINMUX_AUX_GPIO_PH6) = PINMUX_INPUT_ENABLE; gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); gpio_config(GPIO_PORT_E, GPIO_PIN_6, GPIO_MODE_GPIO); gpio_config(GPIO_PORT_H, GPIO_PIN_6, GPIO_MODE_GPIO); gpio_output_enable(GPIO_PORT_G, GPIO_PIN_0, GPIO_OUTPUT_DISABLE); gpio_output_enable(GPIO_PORT_D, GPIO_PIN_1, GPIO_OUTPUT_DISABLE); gpio_output_enable(GPIO_PORT_E, GPIO_PIN_6, GPIO_OUTPUT_DISABLE); gpio_output_enable(GPIO_PORT_H, GPIO_PIN_6, GPIO_OUTPUT_DISABLE); pinmux_config_i2c(I2C_1); pinmux_config_i2c(I2C_5); pinmux_config_uart(UART_A); // Configure volume up/down as inputs. gpio_config(GPIO_PORT_X, GPIO_PIN_6, GPIO_MODE_GPIO); gpio_config(GPIO_PORT_X, GPIO_PIN_7, GPIO_MODE_GPIO); gpio_output_enable(GPIO_PORT_X, GPIO_PIN_6, GPIO_OUTPUT_DISABLE); gpio_output_enable(GPIO_PORT_X, GPIO_PIN_7, GPIO_OUTPUT_DISABLE); } void config_pmc_scratch() { PMC(APBDEV_PMC_SCRATCH20) &= 0xFFF3FFFF; PMC(APBDEV_PMC_SCRATCH190) &= 0xFFFFFFFE; PMC(APBDEV_PMC_SECURE_SCRATCH21) |= 0x10; } void mbist_workaround() { CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SOR1) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SOR1) | 0x8000) & 0xFFFFBFFF; CLOCK(CLK_RST_CONTROLLER_PLLD_BASE) |= 0x40800000u; CLOCK(CLK_RST_CONTROLLER_RST_DEV_Y_CLR) = 0x40; CLOCK(CLK_RST_CONTROLLER_RST_DEV_X_CLR) = 0x40000; CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = 0x18000000; usleep(2); I2S(0x0A0) |= 0x400; I2S(0x088) &= 0xFFFFFFFE; I2S(0x1A0) |= 0x400; I2S(0x188) &= 0xFFFFFFFE; I2S(0x2A0) |= 0x400; I2S(0x288) &= 0xFFFFFFFE; I2S(0x3A0) |= 0x400; I2S(0x388) &= 0xFFFFFFFE; I2S(0x4A0) |= 0x400; I2S(0x488) &= 0xFFFFFFFE; DISPLAY_A(_DIREG(DC_COM_DSC_TOP_CTL)) |= 4; VIC(0x8C) = 0xFFFFFFFF; usleep(2); CLOCK(CLK_RST_CONTROLLER_RST_DEV_Y_SET) = 0x40; CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = 0x18000000; CLOCK(CLK_RST_CONTROLLER_RST_DEV_X_SET) = 0x40000; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_H) = 0xC0; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_L) = 0x80000130; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_U) = 0x1F00200; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_V) = 0x80400808; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_W) = 0x402000FC; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_X) = 0x23000780; CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_Y) = 0x300; CLOCK(CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA) = 0; CLOCK(CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB) = 0; CLOCK(CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC) = 0; CLOCK(CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD) = 0; CLOCK(CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE) = 0; CLOCK(CLK_RST_CONTROLLER_PLLD_BASE) &= 0x1F7FFFFF; CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_SOR1) &= 0xFFFF3FFF; CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_VI) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_VI) & 0x1FFFFFFF) | 0x80000000; CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_HOST1X) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_HOST1X) & 0x1FFFFFFF) | 0x80000000; CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_NVENC) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_NVENC) & 0x1FFFFFFF) | 0x80000000; } void config_se_brom() { // Bootrom part we skipped. u32 sbk[4] = { FUSE(0x1A4), FUSE(0x1A8), FUSE(0x1AC), FUSE(0x1B0) }; se_aes_key_set(14, sbk, 0x10); // Lock SBK from being read. SE(SE_KEY_TABLE_ACCESS_REG_OFFSET + 14 * 4) = 0x7E; // This memset needs to happen here, else TZRAM will behave weirdly later on. memset((void *)0x7C010000, 0, 0x10000); PMC(APBDEV_PMC_CRYPTO_OP) = 0; SE(SE_INT_STATUS_REG_OFFSET) = 0x1F; // Lock SSK (although it's not set and unused anyways). SE(SE_KEY_TABLE_ACCESS_REG_OFFSET + 15 * 4) = 0x7E; // Clear the boot reason to avoid problems later PMC(APBDEV_PMC_SCRATCH200) = 0x0; PMC(APBDEV_PMC_RST_STATUS) = 0x0; } void config_hw() { // Bootrom stuff we skipped by going through rcm. config_se_brom(); //FUSE(FUSE_PRIVATEKEYDISABLE) = 0x11; SYSREG(AHB_AHB_SPARE_REG) &= 0xFFFFFF9F; PMC(APBDEV_PMC_SCRATCH49) = ((PMC(APBDEV_PMC_SCRATCH49) >> 1) << 1) & 0xFFFFFFFD; mbist_workaround(); clock_enable_se(); // Enable fuse clock. clock_enable_fuse(1); // Disable fuse programming. fuse_disable_program(); mc_enable(); config_oscillators(); APB_MISC(APB_MISC_PP_PINMUX_GLOBAL) = 0; config_gpios(); //clock_enable_uart(UART_C); //uart_init(UART_C, 115200); clock_enable_cl_dvfs(); clock_enable_i2c(I2C_1); clock_enable_i2c(I2C_5); static const clock_t clock_unk1 = { CLK_RST_CONTROLLER_RST_DEVICES_V, CLK_RST_CONTROLLER_CLK_OUT_ENB_V, 0x42C, 0x1F, 0, 0 }; static const clock_t clock_unk2 = { CLK_RST_CONTROLLER_RST_DEVICES_V, CLK_RST_CONTROLLER_CLK_OUT_ENB_V, 0, 0x1E, 0, 0 }; clock_enable(&clock_unk1); clock_enable(&clock_unk2); i2c_init(I2C_1); i2c_init(I2C_5); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_CNFGBBC, 0x40); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_ONOFFCNFG1, 0x78); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_CFG0, 0x38); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_CFG1, 0x3A); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_CFG2, 0x38); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_LDO4, 0xF); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_LDO8, 0xC7); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_SD0, 0x4F); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_SD1, 0x29); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_FPS_SD3, 0x1B); i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_SD0, 42); //42 = (1125000 - 600000) / 12500 -> 1.125V config_pmc_scratch(); CLOCK(CLK_RST_CONTROLLER_SCLK_BURST_POLICY) = (CLOCK(CLK_RST_CONTROLLER_SCLK_BURST_POLICY) & 0xFFFF8888) | 0x3333; mc_config_carveout(); sdram_init(); sdram_lp0_save_params(sdram_get_params()); } void reconfig_hw_workaround(bool extra_reconfig) { // Re-enable clocks to Audio Processing Engine as a workaround to hanging. CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_V) |= 0x400; // Enable AHUB clock. CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_Y) |= 0x40; // Enable APE clock. if (extra_reconfig) { PMC(APBDEV_PMC_PWR_DET_VAL) |= (1 << 12); clock_disable_cl_dvfs(); // Disable Joy-con GPIOs. gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); gpio_config(GPIO_PORT_E, GPIO_PIN_6, GPIO_MODE_SPIO); gpio_config(GPIO_PORT_H, GPIO_PIN_6, GPIO_MODE_SPIO); } } void print_fuseinfo() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); u32 burntFuses = 0; for (u32 i = 0; i < 32; i++) { if ((fuse_read_odm(7) >> i) & 1) burntFuses++; } gfx_printf(&gfx_con, "\nSKU: %X - ", FUSE(0x110)); switch (fuse_read_odm(4) & 3) { case 0: gfx_printf(&gfx_con, "Retail\n"); break; case 3: gfx_printf(&gfx_con, "Dev\n"); break; } gfx_printf(&gfx_con, "Sdram ID: %d\n", (fuse_read_odm(4) >> 3) & 0x1F); gfx_printf(&gfx_con, "Burnt fuses: %d\n", burntFuses); gfx_printf(&gfx_con, "Secure key: %08X%08X%08X%08X\n\n\n", byte_swap_32(FUSE(0x1A4)), byte_swap_32(FUSE(0x1A8)), byte_swap_32(FUSE(0x1AC)), byte_swap_32(FUSE(0x1B0))); gfx_printf(&gfx_con, "%k(Unlocked) fuse cache:\n\n%k", 0xFF00DDFF, 0xFFCCCCCC); gfx_hexdump(&gfx_con, 0x7000F900, (u8 *)0x7000F900, 0x2FC); gfx_puts(&gfx_con, "Press POWER to dump them to SD Card.\nPress VOL to go to the menu.\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { if (sd_mount()) { char path[64]; emmcsn_path_impl(path, "/Dumps", "fuses.bin", NULL); if (!sd_save_to_file((u8 *)0x7000F900, 0x2FC, path)) gfx_puts(&gfx_con, "\nDone!\n"); sd_unmount(); } btn_wait(); } } void print_kfuseinfo() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); gfx_printf(&gfx_con, "%kKFuse contents:\n\n%k", 0xFF00DDFF, 0xFFCCCCCC); u32 buf[KFUSE_NUM_WORDS]; if (!kfuse_read(buf)) EPRINTF("CRC fail."); else gfx_hexdump(&gfx_con, 0, (u8 *)buf, KFUSE_NUM_WORDS * 4); gfx_puts(&gfx_con, "\nPress POWER to dump them to SD Card.\nPress VOL to go to the menu.\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { if (sd_mount()) { char path[64]; emmcsn_path_impl(path, "/Dumps", "kfuses.bin", NULL); if (!sd_save_to_file((u8 *)buf, KFUSE_NUM_WORDS * 4, path)) gfx_puts(&gfx_con, "\nDone!\n"); sd_unmount(); } btn_wait(); } } void print_mmc_info() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); static const u32 SECTORS_TO_MIB_COEFF = 11; sdmmc_storage_t storage; sdmmc_t sdmmc; if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) { EPRINTF("Failed to init eMMC."); goto out; } else { u16 card_type; u32 speed; gfx_printf(&gfx_con, "%kCID:%k\n", 0xFF00DDFF, 0xFFCCCCCC); switch (storage.csd.mmca_vsn) { case 0: /* MMC v1.0 - v1.2 */ case 1: /* MMC v1.4 */ gfx_printf(&gfx_con, " Vendor ID: %03X\n" " Model: %c%c%c%c%c%c%c\n" " HW rev: %X\n" " FW rev: %X\n" " S/N: %03X\n" " Month/Year: %02d/%04d\n\n", storage.cid.manfid, storage.cid.prod_name[0], storage.cid.prod_name[1], storage.cid.prod_name[2], storage.cid.prod_name[3], storage.cid.prod_name[4], storage.cid.prod_name[5], storage.cid.prod_name[6], storage.cid.hwrev, storage.cid.fwrev, storage.cid.serial, storage.cid.month, storage.cid.year); break; case 2: /* MMC v2.0 - v2.2 */ case 3: /* MMC v3.1 - v3.3 */ case 4: /* MMC v4 */ gfx_printf(&gfx_con, " Vendor ID: %X\n" " Card/BGA: %X\n" " OEM ID: %02X\n" " Model: %c%c%c%c%c%c\n" " Prd Rev: %X\n" " S/N: %04X\n" " Month/Year: %02d/%04d\n\n", storage.cid.manfid, storage.cid.card_bga, storage.cid.oemid, storage.cid.prod_name[0], storage.cid.prod_name[1], storage.cid.prod_name[2], storage.cid.prod_name[3], storage.cid.prod_name[4], storage.cid.prod_name[5], storage.cid.prv, storage.cid.serial, storage.cid.month, storage.cid.year); break; default: EPRINTFARGS("eMMC has unknown MMCA version %d", storage.csd.mmca_vsn); break; } if (storage.csd.structure == 0) EPRINTF("Unknown CSD structure."); else { gfx_printf(&gfx_con, "%kExtended CSD V1.%d:%k\n", 0xFF00DDFF, storage.ext_csd.ext_struct, 0xFFCCCCCC); card_type = storage.ext_csd.card_type; u8 card_type_support[96]; u8 pos_type = 0; if (card_type & EXT_CSD_CARD_TYPE_HS_26) { memcpy(card_type_support, "HS26", 4); speed = (26 << 16) | 26; pos_type += 4; } if (card_type & EXT_CSD_CARD_TYPE_HS_52) { memcpy(card_type_support + pos_type, ", HS52", 6); speed = (52 << 16) | 52; pos_type += 6; } if (card_type & EXT_CSD_CARD_TYPE_DDR_1_8V) { memcpy(card_type_support + pos_type, ", DDR52_1.8V", 12); speed = (52 << 16) | 104; pos_type += 12; } if (card_type & EXT_CSD_CARD_TYPE_HS200_1_8V) { memcpy(card_type_support + pos_type, ", HS200_1.8V", 12); speed = (200 << 16) | 200; pos_type += 12; } if (card_type & EXT_CSD_CARD_TYPE_HS400_1_8V) { memcpy(card_type_support + pos_type, ", HS400_1.8V", 12); speed = (200 << 16) | 400; pos_type += 12; } card_type_support[pos_type] = 0; gfx_printf(&gfx_con, " Spec Version: %02X\n" " Extended Rev: 1.%d\n" " Dev Version: %d\n" " Cmd Classes: %02X\n" " Capacity: %s\n" " Max Rate: %d MB/s (%d MHz)\n" " Current Rate: %d MB/s\n" " Type Support: ", storage.csd.mmca_vsn, storage.ext_csd.rev, storage.ext_csd.dev_version, storage.csd.cmdclass, storage.csd.capacity == (4096 * 512) ? "High" : "Low", speed & 0xFFFF, (speed >> 16) & 0xFFFF, storage.csd.busspeed); gfx_con.fntsz = 8; gfx_printf(&gfx_con, "%s", card_type_support); gfx_con.fntsz = 16; gfx_printf(&gfx_con, "\n\n", card_type_support); u32 boot_size = storage.ext_csd.boot_mult << 17; u32 rpmb_size = storage.ext_csd.rpmb_mult << 17; gfx_printf(&gfx_con, "%keMMC Partitions:%k\n", 0xFF00DDFF, 0xFFCCCCCC); gfx_printf(&gfx_con, " 1: %kBOOT0 %k\n Size: %5d KiB (LBA Sectors: 0x%07X)\n", 0xFF96FF00, 0xFFCCCCCC, boot_size / 1024, boot_size / 1024 / 512); gfx_put_small_sep(&gfx_con); gfx_printf(&gfx_con, " 2: %kBOOT1 %k\n Size: %5d KiB (LBA Sectors: 0x%07X)\n", 0xFF96FF00, 0xFFCCCCCC, boot_size / 1024, boot_size / 1024 / 512); gfx_put_small_sep(&gfx_con); gfx_printf(&gfx_con, " 3: %kRPMB %k\n Size: %5d KiB (LBA Sectors: 0x%07X)\n", 0xFF96FF00, 0xFFCCCCCC, rpmb_size / 1024, rpmb_size / 1024 / 512); gfx_put_small_sep(&gfx_con); gfx_printf(&gfx_con, " 0: %kGPP (USER) %k\n Size: %5d MiB (LBA Sectors: 0x%07X)\n\n", 0xFF96FF00, 0xFFCCCCCC, storage.sec_cnt >> SECTORS_TO_MIB_COEFF, storage.sec_cnt); gfx_put_small_sep(&gfx_con); gfx_printf(&gfx_con, "%kGPP (eMMC USER) partition table:%k\n", 0xFF00DDFF, 0xFFCCCCCC); sdmmc_storage_set_mmc_partition(&storage, 0); LIST_INIT(gpt); nx_emmc_gpt_parse(&gpt, &storage); int gpp_idx = 0; LIST_FOREACH_ENTRY(emmc_part_t, part, &gpt, link) { gfx_printf(&gfx_con, " %02d: %k%s%k\n Size: % 5d MiB (LBA Sectors 0x%07X)\n LBA Range: %08X-%08X\n", gpp_idx++, 0xFFAEFD14, part->name, 0xFFCCCCCC, (part->lba_end - part->lba_start + 1) >> SECTORS_TO_MIB_COEFF, part->lba_end - part->lba_start + 1, part->lba_start, part->lba_end); gfx_put_small_sep(&gfx_con); } nx_emmc_gpt_free(&gpt); } } out: sdmmc_storage_end(&storage); btn_wait(); } void print_sdcard_info() { static const u32 SECTORS_TO_MIB_COEFF = 11; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); if (sd_mount()) { u32 capacity; gfx_printf(&gfx_con, "%kCard IDentification:%k\n", 0xFF00DDFF, 0xFFCCCCCC); gfx_printf(&gfx_con, " Vendor ID: %02x\n" " OEM ID: %c%c\n" " Model: %c%c%c%c%c\n" " HW rev: %X\n" " FW rev: %X\n" " S/N: %08x\n" " Month/Year: %02d/%04d\n\n", sd_storage.cid.manfid, (sd_storage.cid.oemid >> 8) & 0xFF, sd_storage.cid.oemid & 0xFF, sd_storage.cid.prod_name[0], sd_storage.cid.prod_name[1], sd_storage.cid.prod_name[2], sd_storage.cid.prod_name[3], sd_storage.cid.prod_name[4], sd_storage.cid.hwrev, sd_storage.cid.fwrev, sd_storage.cid.serial, sd_storage.cid.month, sd_storage.cid.year); gfx_printf(&gfx_con, "%kCard-Specific Data V%d.0:%k\n", 0xFF00DDFF, sd_storage.csd.structure + 1, 0xFFCCCCCC); capacity = sd_storage.csd.capacity >> (20 - sd_storage.csd.read_blkbits); gfx_printf(&gfx_con, " Cmd Classes: %02X\n" " Capacity: %d MiB\n" " Bus Width: %d\n" " Current Rate: %d MB/s (%d MHz)\n" " Speed Class: %d\n" " UHS Grade: U%d\n" " Video Class: V%d\n" " App perf class: A%d\n" " Write Protect: %d\n\n", sd_storage.csd.cmdclass, capacity, sd_storage.ssr.bus_width, sd_storage.csd.busspeed, sd_storage.csd.busspeed * 2, sd_storage.ssr.speed_class, sd_storage.ssr.uhs_grade, sd_storage.ssr.video_class, sd_storage.ssr.app_class, sd_storage.csd.write_protect); gfx_puts(&gfx_con, "Acquiring FAT volume info...\n\n"); f_getfree("", &sd_fs.free_clst, NULL); gfx_printf(&gfx_con, "%kFound %s volume:%k\n Free: %d MiB\n Cluster: %d KiB\n", 0xFF00DDFF, sd_fs.fs_type == FS_EXFAT ? "exFAT" : "FAT32", 0xFFCCCCCC, sd_fs.free_clst * sd_fs.csize >> SECTORS_TO_MIB_COEFF, (sd_fs.csize > 1) ? (sd_fs.csize >> 1) : 512); sd_unmount(); } btn_wait(); } void print_tsec_key() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); sdmmc_storage_t storage; sdmmc_t sdmmc; sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4); // Read package1. u8 *pkg1 = (u8 *)malloc(0x40000); sdmmc_storage_set_mmc_partition(&storage, 1); sdmmc_storage_read(&storage, 0x100000 / NX_EMMC_BLOCKSIZE, 0x40000 / NX_EMMC_BLOCKSIZE, pkg1); sdmmc_storage_end(&storage); const pkg1_id_t *pkg1_id = pkg1_identify(pkg1); if (!pkg1_id) { EPRINTFARGS("Unknown package1 version for reading\nTSEC firmware (= '%s').", (char *)pkg1 + 0x10); goto out_wait; } u8 keys[0x10 * 3]; for (u32 i = 1; i <= 3; i++) { int res = tsec_query(keys + ((i - 1) * 0x10), i, pkg1 + pkg1_id->tsec_off); gfx_printf(&gfx_con, "%kTSEC key %d: %k", 0xFF00DDFF, i, 0xFFCCCCCC); if (res >= 0) { for (u32 j = 0; j < 0x10; j++) gfx_printf(&gfx_con, "%02X", keys[((i - 1) * 0x10) + j]); } else EPRINTFARGS("ERROR %X", res); gfx_putc(&gfx_con, '\n'); } gfx_puts(&gfx_con, "\nPress POWER to dump them to SD Card.\nPress VOL to go to the menu.\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { if (sd_mount()) { char path[64]; emmcsn_path_impl(path, "/Dumps", "tsec_keys.bin", NULL); if (!sd_save_to_file(keys, 0x10 * 3, path)) gfx_puts(&gfx_con, "\nDone!\n"); sd_unmount(); } } else goto out; out_wait: btn_wait(); out: free(pkg1); } void reboot_normal() { sd_unmount(); #ifdef MENU_LOGO_ENABLE free(Kc_MENU_LOGO); #endif //MENU_LOGO_ENABLE panic(0x21); // Bypass fuse programming in package1. } void reboot_rcm() { sd_unmount(); #ifdef MENU_LOGO_ENABLE free(Kc_MENU_LOGO); #endif //MENU_LOGO_ENABLE PMC(APBDEV_PMC_SCRATCH0) = 2; // Reboot into rcm. PMC(0) |= 0x10; while (true) usleep(1); } void power_off() { sd_unmount(); #ifdef MENU_LOGO_ENABLE free(Kc_MENU_LOGO); #endif //MENU_LOGO_ENABLE //TODO: we should probably make sure all regulators are powered off properly. i2c_send_byte(I2C_5, 0x3C, MAX77620_REG_ONOFFCNFG1, MAX77620_ONOFFCNFG1_PWR_OFF); } int dump_emmc_verify(sdmmc_storage_t *storage, u32 lba_curr, char *outFilename, emmc_part_t *part) { FIL fp; u32 prevPct = 200; int res = 0; u8 hashEm[0x20]; u8 hashSd[0x20]; if (f_open(&fp, outFilename, FA_READ) == FR_OK) { u32 totalSectorsVer = (u32)((u64)f_size(&fp) >> (u64)9); u32 numSectorsPerIter = 0; if (totalSectorsVer > 0x200000) numSectorsPerIter = 8192; //4MB Cache else numSectorsPerIter = 512; //256KB Cache u8 *bufEm = (u8 *)calloc(numSectorsPerIter, NX_EMMC_BLOCKSIZE); u8 *bufSd = (u8 *)calloc(numSectorsPerIter, NX_EMMC_BLOCKSIZE); u32 pct = (u64)((u64)(lba_curr - part->lba_start) * 100u) / (u64)(part->lba_end - part->lba_start); tui_pbar(&gfx_con, 0, gfx_con.y, pct, 0xFF96FF00, 0xFF155500); u32 num = 0; while (totalSectorsVer > 0) { num = MIN(totalSectorsVer, numSectorsPerIter); if (!sdmmc_storage_read(storage, lba_curr, num, bufEm)) { gfx_con.fntsz = 16; EPRINTFARGS("\nFailed to read %d blocks (@LBA %08X),\nfrom eMMC!\n\nVerification failed..\n", num, lba_curr); free(bufEm); free(bufSd); f_close(&fp); return 1; } if (f_read(&fp, bufSd, num << 9, NULL)) { gfx_con.fntsz = 16; EPRINTFARGS("\nFailed to read %d blocks (@LBA %08X),\nfrom sd card!\n\nVerification failed..\n", num, lba_curr); free(bufEm); free(bufSd); f_close(&fp); return 1; } switch (h_cfg.verification) { case 1: res = memcmp32sparse((u32 *)bufEm, (u32 *)bufSd, num << 9); break; case 2: default: se_calc_sha256(&hashEm, bufEm, num << 9); se_calc_sha256(&hashSd, bufSd, num << 9); res = memcmp(hashEm, hashSd, 0x20); break; } if (res) { gfx_con.fntsz = 16; EPRINTFARGS("\nSD card and eMMC data (@LBA %08X),\ndo not match!\n\nVerification failed..\n", lba_curr); free(bufEm); free(bufSd); f_close(&fp); return 1; } pct = (u64)((u64)(lba_curr - part->lba_start) * 100u) / (u64)(part->lba_end - part->lba_start); if (pct != prevPct) { tui_pbar(&gfx_con, 0, gfx_con.y, pct, 0xFF96FF00, 0xFF155500); prevPct = pct; } lba_curr += num; totalSectorsVer -= num; } free(bufEm); free(bufSd); f_close(&fp); tui_pbar(&gfx_con, 0, gfx_con.y, pct, 0xFFCCCCCC, 0xFF555555); return 0; } else { gfx_con.fntsz = 16; EPRINTF("\nFile not found or could not be loaded.\n\nVerification failed..\n"); return 1; } } int dump_emmc_part(char *sd_path, sdmmc_storage_t *storage, emmc_part_t *part) { static const u32 FAT32_FILESIZE_LIMIT = 0xFFFFFFFF; static const u32 SECTORS_TO_MIB_COEFF = 11; u32 multipartSplitSize = (1u << 31); u32 totalSectors = part->lba_end - part->lba_start + 1; u32 currPartIdx = 0; u32 numSplitParts = 0; u32 maxSplitParts = 0; bool isSmallSdCard = false; bool partialDumpInProgress = false; int res = 0; char *outFilename = sd_path; u32 sdPathLen = strlen(sd_path); FIL partialIdxFp; char partialIdxFilename[12]; memcpy(partialIdxFilename, "partial.idx", 12); gfx_con.fntsz = 8; gfx_printf(&gfx_con, "\nSD Card free space: %d MiB, Total backup size %d MiB\n\n", sd_fs.free_clst * sd_fs.csize >> SECTORS_TO_MIB_COEFF, totalSectors >> SECTORS_TO_MIB_COEFF); // 1GB parts for sd cards 8GB and less. if ((sd_storage.csd.capacity >> (20 - sd_storage.csd.read_blkbits)) <= 8192) multipartSplitSize = (1u << 30); // Maximum parts fitting the free space available. maxSplitParts = (sd_fs.free_clst * sd_fs.csize) / (multipartSplitSize / 512); // Check if the USER partition or the RAW eMMC fits the sd card free space. if (totalSectors > (sd_fs.free_clst * sd_fs.csize)) { isSmallSdCard = true; gfx_printf(&gfx_con, "%k\nSD card free space is smaller than total backup size.%k\n", 0xFFFFBA00, 0xFFCCCCCC); if (!maxSplitParts) { gfx_con.fntsz = 16; EPRINTF("Not enough free space for Partial Backup."); return 0; } } // Check if we are continuing a previous raw eMMC or USER partition backup in progress. if (f_open(&partialIdxFp, partialIdxFilename, FA_READ) == FR_OK && totalSectors > (FAT32_FILESIZE_LIMIT / NX_EMMC_BLOCKSIZE)) { gfx_printf(&gfx_con, "%kFound Partial Backup in progress. Continuing...%k\n\n", 0xFFAEFD14, 0xFFCCCCCC); partialDumpInProgress = true; // Force partial dumping, even if the card is larger. isSmallSdCard = true; f_read(&partialIdxFp, &currPartIdx, 4, NULL); f_close(&partialIdxFp); if (!maxSplitParts) { gfx_con.fntsz = 16; EPRINTF("Not enough free space for Partial Backup."); return 0; } // Increase maxSplitParts to accommodate previously backed up parts. maxSplitParts += currPartIdx; } else if (isSmallSdCard) gfx_printf(&gfx_con, "%kPartial Backup enabled (with %d MiB parts)...%k\n\n", 0xFFFFBA00, multipartSplitSize >> 20, 0xFFCCCCCC); // Check if filesystem is FAT32 or the free space is smaller and backup in parts. if (((sd_fs.fs_type != FS_EXFAT) && totalSectors > (FAT32_FILESIZE_LIMIT / NX_EMMC_BLOCKSIZE)) | isSmallSdCard) { u32 multipartSplitSectors = multipartSplitSize / NX_EMMC_BLOCKSIZE; numSplitParts = (totalSectors + multipartSplitSectors - 1) / multipartSplitSectors; outFilename[sdPathLen++] = '.'; if (!partialDumpInProgress) { outFilename[sdPathLen] = '0'; if (numSplitParts >= 10) { outFilename[sdPathLen + 1] = '0'; outFilename[sdPathLen + 2] = 0; } else outFilename[sdPathLen + 1] = 0; } // Continue from where we left, if Partial Backup in progress. else { if (numSplitParts >= 10 && currPartIdx < 10) { outFilename[sdPathLen] = '0'; itoa(currPartIdx, &outFilename[sdPathLen + 1], 10); } else itoa(currPartIdx, &outFilename[sdPathLen], 10); } } FIL fp; gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy); gfx_printf(&gfx_con, "Filename: %s\n\n", outFilename); res = f_open(&fp, outFilename, FA_CREATE_ALWAYS | FA_WRITE); if (res) { gfx_con.fntsz = 16; EPRINTFARGS("Error (%d) creating file %s.\n", res, outFilename); return 0; } u32 numSectorsPerIter = 0; if (totalSectors > 0x200000) numSectorsPerIter = 8192; else numSectorsPerIter = 512; u8 *buf = (u8 *)calloc(numSectorsPerIter, NX_EMMC_BLOCKSIZE); u32 lba_curr = part->lba_start; u32 lbaStartPart = part->lba_start; u32 bytesWritten = 0; u32 prevPct = 200; int retryCount = 0; // Continue from where we left, if Partial Backup in progress. if (partialDumpInProgress) { lba_curr += currPartIdx * (multipartSplitSize / NX_EMMC_BLOCKSIZE); totalSectors -= currPartIdx * (multipartSplitSize / NX_EMMC_BLOCKSIZE); lbaStartPart = lba_curr; // Update the start LBA for verification. } u32 num = 0; u32 pct = 0; while (totalSectors > 0) { if (numSplitParts != 0 && bytesWritten >= multipartSplitSize) { f_close(&fp); memset(&fp, 0, sizeof(fp)); currPartIdx++; if (h_cfg.verification) { // Verify part. if (dump_emmc_verify(storage, lbaStartPart, outFilename, part)) { EPRINTF("\nPress any key and try again...\n"); free(buf); return 0; } } if (numSplitParts >= 10 && currPartIdx < 10) { outFilename[sdPathLen] = '0'; itoa(currPartIdx, &outFilename[sdPathLen + 1], 10); } else itoa(currPartIdx, &outFilename[sdPathLen], 10); // Always create partial.idx before next part, in case a fatal error occurs. if (isSmallSdCard) { // Create partial backup index file. if (f_open(&partialIdxFp, partialIdxFilename, FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) { f_write(&partialIdxFp, &currPartIdx, 4, NULL); f_close(&partialIdxFp); } else { gfx_con.fntsz = 16; EPRINTF("\nError creating partial.idx file.\n"); free(buf); return 0; } // More parts to backup that do not currently fit the sd card free space or fatal error. if (currPartIdx >= maxSplitParts) { gfx_puts(&gfx_con, "\n\n1. Press any key to unmount SD Card.\n\ 2. Remove SD Card and move files to free space.\n\ Don\'t move the partial.idx file!\n\ 3. Re-insert SD Card.\n\ 4. Select the SAME option again to continue.\n"); gfx_con.fntsz = 16; free(buf); return 1; } } // Create next part. gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); gfx_printf(&gfx_con, "Filename: %s\n\n", outFilename); lbaStartPart = lba_curr; res = f_open(&fp, outFilename, FA_CREATE_ALWAYS | FA_WRITE); if (res) { gfx_con.fntsz = 16; EPRINTFARGS("Error (%d) creating file %s.\n", res, outFilename); free(buf); return 0; } bytesWritten = 0; } retryCount = 0; num = MIN(totalSectors, numSectorsPerIter); while (!sdmmc_storage_read(storage, lba_curr, num, buf)) { EPRINTFARGS("Error reading %d blocks @ LBA %08X,\nfrom eMMC (try %d), retrying...", num, lba_curr, ++retryCount); msleep(150); if (retryCount >= 3) { gfx_con.fntsz = 16; EPRINTFARGS("\nFailed to read %d blocks @ LBA %08X\nfrom eMMC. Aborting..\n", num, lba_curr); EPRINTF("\nPress any key and try again...\n"); free(buf); f_close(&fp); return 0; } } res = f_write(&fp, buf, NX_EMMC_BLOCKSIZE * num, NULL); if (res) { gfx_con.fntsz = 16; EPRINTFARGS("\nFatal error (%d) when writing to SD Card", res); EPRINTF("\nPress any key and try again...\n"); free(buf); f_close(&fp); return 0; } pct = (u64)((u64)(lba_curr - part->lba_start) * 100u) / (u64)(part->lba_end - part->lba_start); if (pct != prevPct) { tui_pbar(&gfx_con, 0, gfx_con.y, pct, 0xFFCCCCCC, 0xFF555555); prevPct = pct; } lba_curr += num; totalSectors -= num; bytesWritten += num * NX_EMMC_BLOCKSIZE; // Force a flush after a lot of data if not splitting. if (numSplitParts == 0 && bytesWritten >= multipartSplitSize) { f_sync(&fp); bytesWritten = 0; } } tui_pbar(&gfx_con, 0, gfx_con.y, 100, 0xFFCCCCCC, 0xFF555555); // Backup operation ended successfully. free(buf); f_close(&fp); if (h_cfg.verification) { // Verify last part or single file backup. if (dump_emmc_verify(storage, lbaStartPart, outFilename, part)) { EPRINTF("\nPress any key and try again...\n"); free(buf); return 0; } else tui_pbar(&gfx_con, 0, gfx_con.y, 100, 0xFF96FF00, 0xFF155500); } gfx_con.fntsz = 16; // Remove partial backup index file if no fatal errors occurred. if (isSmallSdCard) { f_unlink(partialIdxFilename); gfx_printf(&gfx_con, "%k\n\nYou can now join the files\nand get the complete eMMC RAW GPP backup.", 0xFFCCCCCC); } gfx_puts(&gfx_con, "\n\n"); return 1; } typedef enum { PART_BOOT = (1 << 0), PART_SYSTEM = (1 << 1), PART_USER = (1 << 2), PART_RAW = (1 << 3), PART_GP_ALL = (1 << 7) } emmcPartType_t; static void dump_emmc_selected(emmcPartType_t dumpType) { int res = 0; u32 timer = 0; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); tui_sbar(&gfx_con, true); gfx_con_setpos(&gfx_con, 0, 0); if (!sd_mount()) goto out; gfx_puts(&gfx_con, "Checking for available free space...\n\n"); // Get SD Card free space for Partial Backup. f_getfree("", &sd_fs.free_clst, NULL); sdmmc_storage_t storage; sdmmc_t sdmmc; if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) { EPRINTF("Failed to init eMMC."); goto out; } int i = 0; char sdPath[80]; // Create Restore folders, if they do not exist. emmcsn_path_impl(sdPath, "/Restore", "", &storage); emmcsn_path_impl(sdPath, "/Restore/Partitions", "", &storage); timer = get_tmr_s(); if (dumpType & PART_BOOT) { const u32 BOOT_PART_SIZE = storage.ext_csd.boot_mult << 17; emmc_part_t bootPart; memset(&bootPart, 0, sizeof(bootPart)); bootPart.lba_start = 0; bootPart.lba_end = (BOOT_PART_SIZE / NX_EMMC_BLOCKSIZE) - 1; for (i = 0; i < 2; i++) { memcpy(bootPart.name, "BOOT", 5); bootPart.name[4] = (u8)('0' + i); bootPart.name[5] = 0; gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i, bootPart.name, bootPart.lba_start, bootPart.lba_end, 0xFFCCCCCC); sdmmc_storage_set_mmc_partition(&storage, i + 1); emmcsn_path_impl(sdPath, "", bootPart.name, &storage); res = dump_emmc_part(sdPath, &storage, &bootPart); } } if ((dumpType & PART_SYSTEM) || (dumpType & PART_USER) || (dumpType & PART_RAW)) { sdmmc_storage_set_mmc_partition(&storage, 0); if ((dumpType & PART_SYSTEM) || (dumpType & PART_USER)) { LIST_INIT(gpt); nx_emmc_gpt_parse(&gpt, &storage); LIST_FOREACH_ENTRY(emmc_part_t, part, &gpt, link) { if ((dumpType & PART_USER) == 0 && !strcmp(part->name, "USER")) continue; if ((dumpType & PART_SYSTEM) == 0 && strcmp(part->name, "USER")) continue; gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i++, part->name, part->lba_start, part->lba_end, 0xFFCCCCCC); emmcsn_path_impl(sdPath, "/Partitions", part->name, &storage); res = dump_emmc_part(sdPath, &storage, part); // If a part failed, don't continue. if (!res) break; } nx_emmc_gpt_free(&gpt); } if (dumpType & PART_RAW) { // Get GP partition size dynamically. const u32 RAW_AREA_NUM_SECTORS = storage.sec_cnt; emmc_part_t rawPart; memset(&rawPart, 0, sizeof(rawPart)); rawPart.lba_start = 0; rawPart.lba_end = RAW_AREA_NUM_SECTORS - 1; strcpy(rawPart.name, "rawnand.bin"); { gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i++, rawPart.name, rawPart.lba_start, rawPart.lba_end, 0xFFCCCCCC); emmcsn_path_impl(sdPath, "", rawPart.name, &storage); res = dump_emmc_part(sdPath, &storage, &rawPart); } } } gfx_putc(&gfx_con, '\n'); timer = get_tmr_s() - timer; gfx_printf(&gfx_con, "Time taken: %dm %ds.\n", timer / 60, timer % 60); sdmmc_storage_end(&storage); if (res && h_cfg.verification) gfx_printf(&gfx_con, "\n%kFinished and verified!%k\nPress any key...\n", 0xFF96FF00, 0xFFCCCCCC); else if (res) gfx_printf(&gfx_con, "\nFinished! Press any key...\n"); out: sd_unmount(); btn_wait(); } void dump_emmc_system() { dump_emmc_selected(PART_SYSTEM); } void dump_emmc_user() { dump_emmc_selected(PART_USER); } void dump_emmc_boot() { dump_emmc_selected(PART_BOOT); } void dump_emmc_rawnand() { dump_emmc_selected(PART_RAW); } int restore_emmc_part(char *sd_path, sdmmc_storage_t *storage, emmc_part_t *part) { static const u32 SECTORS_TO_MIB_COEFF = 11; u32 totalSectors = part->lba_end - part->lba_start + 1; u32 lbaStartPart = part->lba_start; int res = 0; char *outFilename = sd_path; gfx_con.fntsz = 8; FIL fp; gfx_printf(&gfx_con, "\nFilename: %s\n", outFilename); res = f_open(&fp, outFilename, FA_READ); if (res) { WPRINTFARGS("Error (%d) while opening backup. Continuing...\n", res); gfx_con.fntsz = 16; return 0; } //TODO: Should we keep this check? else if (((u32)((u64)f_size(&fp) >> (u64)9)) != totalSectors) { gfx_con.fntsz = 16; EPRINTF("Size of the SD Card backup does not match,\neMMC's selected part size.\n"); f_close(&fp); return 0; } else gfx_printf(&gfx_con, "\nTotal restore size: %d MiB.\n\n", ((u32)((u64)f_size(&fp) >> (u64)9)) >> SECTORS_TO_MIB_COEFF); u32 numSectorsPerIter = 0; if (totalSectors > 0x200000) numSectorsPerIter = 8192; //4MB Cache else numSectorsPerIter = 512; //256KB Cache u8 *buf = (u8 *)calloc(numSectorsPerIter, NX_EMMC_BLOCKSIZE); u32 lba_curr = part->lba_start; u32 bytesWritten = 0; u32 prevPct = 200; int retryCount = 0; u32 num = 0; u32 pct = 0; while (totalSectors > 0) { retryCount = 0; num = MIN(totalSectors, numSectorsPerIter); res = f_read(&fp, buf, NX_EMMC_BLOCKSIZE * num, NULL); if (res) { gfx_con.fntsz = 16; EPRINTFARGS("\nFatal error (%d) when reading from SD Card", res); EPRINTF("\nYour device may be in an inoperative state!\n\nPress any key and try again now...\n"); free(buf); f_close(&fp); return 0; } while (!sdmmc_storage_write(storage, lba_curr, num, buf)) { EPRINTFARGS("Error writing %d blocks @ LBA %08X\nto eMMC (try %d), retrying...", num, lba_curr, ++retryCount); msleep(150); if (retryCount >= 3) { gfx_con.fntsz = 16; EPRINTFARGS("\nFailed to write %d blocks @ LBA %08X\nfrom eMMC. Aborting..\n", num, lba_curr); EPRINTF("\nYour device may be in an inoperative state!\n\nPress any key and try again...\n"); free(buf); f_close(&fp); return 0; } } pct = (u64)((u64)(lba_curr - part->lba_start) * 100u) / (u64)(part->lba_end - part->lba_start); if (pct != prevPct) { tui_pbar(&gfx_con, 0, gfx_con.y, pct, 0xFFCCCCCC, 0xFF555555); prevPct = pct; } lba_curr += num; totalSectors -= num; bytesWritten += num * NX_EMMC_BLOCKSIZE; } tui_pbar(&gfx_con, 0, gfx_con.y, 100, 0xFFCCCCCC, 0xFF555555); // Restore operation ended successfully. free(buf); f_close(&fp); if (h_cfg.verification) { // Verify restored data. if (dump_emmc_verify(storage, lbaStartPart, outFilename, part)) { EPRINTF("\nPress any key and try again...\n"); free(buf); return 0; } else tui_pbar(&gfx_con, 0, gfx_con.y, 100, 0xFF96FF00, 0xFF155500); } gfx_con.fntsz = 16; gfx_puts(&gfx_con, "\n\n"); return 1; } static void restore_emmc_selected(emmcPartType_t restoreType) { int res = 0; u32 timer = 0; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); tui_sbar(&gfx_con, true); gfx_con_setpos(&gfx_con, 0, 0); gfx_printf(&gfx_con, "%kThis is a dangerous operation\nand may render your device inoperative!\n\n", 0xFFFFDD00); gfx_printf(&gfx_con, "Are you really sure?\n\n%k", 0xFFCCCCCC); if ((restoreType & PART_BOOT) || (restoreType & PART_GP_ALL)) { gfx_puts(&gfx_con, "The mode you selected will only restore\nthe "); if (restoreType & PART_BOOT) gfx_puts(&gfx_con, "boot "); gfx_puts(&gfx_con, "partitions that it can find.\n"); gfx_puts(&gfx_con, "If it is not found, it will be skipped\nand continue with the next.\n\n"); } gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy); u8 value = 10; while (value > 0) { gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); gfx_printf(&gfx_con, "%kWait... (%ds) %k", 0xFF888888, value, 0xFFCCCCCC); msleep(1000); value--; } gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); gfx_puts(&gfx_con, "Press POWER to Continue.\nPress VOL to go to the menu.\n\n\n"); u32 btn = btn_wait(); if (!(btn & BTN_POWER)) goto out; if (!sd_mount()) goto out; sdmmc_storage_t storage; sdmmc_t sdmmc; if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) { EPRINTF("Failed to init eMMC."); goto out; } int i = 0; char sdPath[80]; timer = get_tmr_s(); if (restoreType & PART_BOOT) { const u32 BOOT_PART_SIZE = storage.ext_csd.boot_mult << 17; emmc_part_t bootPart; memset(&bootPart, 0, sizeof(bootPart)); bootPart.lba_start = 0; bootPart.lba_end = (BOOT_PART_SIZE / NX_EMMC_BLOCKSIZE) - 1; for (i = 0; i < 2; i++) { memcpy(bootPart.name, "BOOT", 4); bootPart.name[4] = (u8)('0' + i); bootPart.name[5] = 0; gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i, bootPart.name, bootPart.lba_start, bootPart.lba_end, 0xFFCCCCCC); sdmmc_storage_set_mmc_partition(&storage, i + 1); emmcsn_path_impl(sdPath, "/Restore", bootPart.name, &storage); res = restore_emmc_part(sdPath, &storage, &bootPart); } } if (restoreType & PART_GP_ALL) { sdmmc_storage_set_mmc_partition(&storage, 0); LIST_INIT(gpt); nx_emmc_gpt_parse(&gpt, &storage); LIST_FOREACH_ENTRY(emmc_part_t, part, &gpt, link) { gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i++, part->name, part->lba_start, part->lba_end, 0xFFCCCCCC); emmcsn_path_impl(sdPath, "/Restore/Partitions/", part->name, &storage); res = restore_emmc_part(sdPath, &storage, part); } nx_emmc_gpt_free(&gpt); } if (restoreType & PART_RAW) { // Get GP partition size dynamically. const u32 RAW_AREA_NUM_SECTORS = storage.sec_cnt; emmc_part_t rawPart; memset(&rawPart, 0, sizeof(rawPart)); rawPart.lba_start = 0; rawPart.lba_end = RAW_AREA_NUM_SECTORS - 1; strcpy(rawPart.name, "rawnand.bin"); { gfx_printf(&gfx_con, "%k%02d: %s (%07X-%07X)%k\n", 0xFF00DDFF, i++, rawPart.name, rawPart.lba_start, rawPart.lba_end, 0xFFCCCCCC); emmcsn_path_impl(sdPath, "/Restore", rawPart.name, &storage); res = restore_emmc_part(sdPath, &storage, &rawPart); } } gfx_putc(&gfx_con, '\n'); timer = get_tmr_s() - timer; gfx_printf(&gfx_con, "Time taken: %dm %ds.\n", timer / 60, timer % 60); sdmmc_storage_end(&storage); if (res && h_cfg.verification) gfx_printf(&gfx_con, "\n%kFinished and verified!%k\nPress any key...\n", 0xFF96FF00, 0xFFCCCCCC); else if (res) gfx_printf(&gfx_con, "\nFinished! Press any key...\n"); out: sd_unmount(); btn_wait(); } void restore_emmc_boot() { restore_emmc_selected(PART_BOOT); } void restore_emmc_rawnand() { restore_emmc_selected(PART_RAW); } void restore_emmc_gpp_parts() { restore_emmc_selected(PART_GP_ALL); } void dump_packages12() { u8 *pkg1 = (u8 *)calloc(1, 0x40000); u8 *warmboot = (u8 *)calloc(1, 0x40000); u8 *secmon = (u8 *)calloc(1, 0x40000); u8 *loader = (u8 *)calloc(1, 0x40000); u8 *pkg2 = NULL; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); if (!sd_mount()) goto out; sdmmc_storage_t storage; sdmmc_t sdmmc; if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) { EPRINTF("Failed to init eMMC."); goto out; } sdmmc_storage_set_mmc_partition(&storage, 1); // Read package1. sdmmc_storage_read(&storage, 0x100000 / NX_EMMC_BLOCKSIZE, 0x40000 / NX_EMMC_BLOCKSIZE, pkg1); const pkg1_id_t *pkg1_id = pkg1_identify(pkg1); const pk11_hdr_t *hdr = (pk11_hdr_t *)(pkg1 + pkg1_id->pkg11_off + 0x20); if (!pkg1_id) { gfx_con.fntsz = 8; EPRINTFARGS("Unknown package1 version for reading\nTSEC firmware (= '%s').", (char *)pkg1 + 0x10); goto out; } if (!h_cfg.se_keygen_done) { // Read keyblob. u8 *keyblob = (u8 *)calloc(NX_EMMC_BLOCKSIZE, 1); sdmmc_storage_read(&storage, 0x180000 / NX_EMMC_BLOCKSIZE + pkg1_id->kb, 1, keyblob); // Decrypt. keygen(keyblob, pkg1_id->kb, (u8 *)pkg1 + pkg1_id->tsec_off); h_cfg.se_keygen_done = 1; free(keyblob); } pkg1_decrypt(pkg1_id, pkg1); pkg1_unpack(warmboot, secmon, loader, pkg1_id, pkg1); // Display info. gfx_printf(&gfx_con, "%kNX Bootloader size: %k0x%05X\n\n", 0xFFC7EA46, 0xFFCCCCCC, hdr->ldr_size); gfx_printf(&gfx_con, "%kSecure monitor addr: %k0x%05X\n", 0xFFC7EA46, 0xFFCCCCCC, pkg1_id->secmon_base); gfx_printf(&gfx_con, "%kSecure monitor size: %k0x%05X\n\n", 0xFFC7EA46, 0xFFCCCCCC, hdr->sm_size); gfx_printf(&gfx_con, "%kWarmboot addr: %k0x%05X\n", 0xFFC7EA46, 0xFFCCCCCC, pkg1_id->warmboot_base); gfx_printf(&gfx_con, "%kWarmboot size: %k0x%05X\n\n", 0xFFC7EA46, 0xFFCCCCCC, hdr->wb_size); char path[64]; // Dump package1.1. emmcsn_path_impl(path, "/pkg1", "pkg1_decr.bin", &storage); if (sd_save_to_file(pkg1, 0x40000, path)) goto out; gfx_puts(&gfx_con, "\nFull package1 dumped to pkg1_decr.bin\n"); // Dump nxbootloader. emmcsn_path_impl(path, "/pkg1", "nxloader.bin", &storage); if (sd_save_to_file(loader, hdr->ldr_size, path)) goto out; gfx_puts(&gfx_con, "NX Bootloader dumped to nxloader.bin\n"); // Dump secmon. emmcsn_path_impl(path, "/pkg1", "secmon.bin", &storage); if (sd_save_to_file(secmon, hdr->sm_size, path)) goto out; gfx_puts(&gfx_con, "Secure Monitor dumped to secmon.bin\n"); // Dump warmboot. emmcsn_path_impl(path, "/pkg1", "warmboot.bin", &storage); if (sd_save_to_file(warmboot, hdr->wb_size, path)) goto out; gfx_puts(&gfx_con, "Warmboot dumped to warmboot.bin\n\n\n"); // Dump package2.1. sdmmc_storage_set_mmc_partition(&storage, 0); // Parse eMMC GPT. LIST_INIT(gpt); nx_emmc_gpt_parse(&gpt, &storage); // Find package2 partition. emmc_part_t *pkg2_part = nx_emmc_part_find(&gpt, "BCPKG2-1-Normal-Main"); if (!pkg2_part) goto out; // Read in package2 header and get package2 real size. u8 *tmp = (u8 *)malloc(NX_EMMC_BLOCKSIZE); nx_emmc_part_read(&storage, pkg2_part, 0x4000 / NX_EMMC_BLOCKSIZE, 1, tmp); u32 *hdr_pkg2_raw = (u32 *)(tmp + 0x100); u32 pkg2_size = hdr_pkg2_raw[0] ^ hdr_pkg2_raw[2] ^ hdr_pkg2_raw[3]; free(tmp); // Read in package2. u32 pkg2_size_aligned = ALIGN(pkg2_size, NX_EMMC_BLOCKSIZE); pkg2 = malloc(pkg2_size_aligned); nx_emmc_part_read(&storage, pkg2_part, 0x4000 / NX_EMMC_BLOCKSIZE, pkg2_size_aligned / NX_EMMC_BLOCKSIZE, pkg2); // Decrypt package2 and parse KIP1 blobs in INI1 section. pkg2_hdr_t *pkg2_hdr = pkg2_decrypt(pkg2); // Display info. u32 kernel_crc32 = crc32c(pkg2_hdr->data, pkg2_hdr->sec_size[PKG2_SEC_KERNEL]); gfx_printf(&gfx_con, "\n%kKernel CRC32C: %k0x%08X\n\n", 0xFFC7EA46, 0xFFCCCCCC, kernel_crc32); gfx_printf(&gfx_con, "%kKernel size: %k0x%05X\n\n", 0xFFC7EA46, 0xFFCCCCCC, pkg2_hdr->sec_size[PKG2_SEC_KERNEL]); gfx_printf(&gfx_con, "%kINI1 size: %k0x%05X\n\n", 0xFFC7EA46, 0xFFCCCCCC, pkg2_hdr->sec_size[PKG2_SEC_INI1]); // Dump pkg2.1. emmcsn_path_impl(path, "/pkg2", "pkg2_decr.bin", &storage); if (sd_save_to_file(pkg2, pkg2_hdr->sec_size[PKG2_SEC_KERNEL] + pkg2_hdr->sec_size[PKG2_SEC_INI1], path)) goto out; gfx_puts(&gfx_con, "\nFull package2 dumped to pkg2_decr.bin\n"); // Dump kernel. emmcsn_path_impl(path, "/pkg2", "kernel.bin", &storage); if (sd_save_to_file(pkg2_hdr->data, pkg2_hdr->sec_size[PKG2_SEC_KERNEL], path)) goto out; gfx_puts(&gfx_con, "Kernel dumped to kernel.bin\n"); // Dump INI1. emmcsn_path_impl(path, "/pkg2", "ini1.bin", &storage); if (sd_save_to_file(pkg2_hdr->data + pkg2_hdr->sec_size[PKG2_SEC_KERNEL], pkg2_hdr->sec_size[PKG2_SEC_INI1], path)) goto out; gfx_puts(&gfx_con, "INI1 kip1 package dumped to ini1.bin\n"); gfx_puts(&gfx_con, "\nDone. Press any key...\n"); out: free(pkg1); free(secmon); free(warmboot); free(loader); free(pkg2); nx_emmc_gpt_free(&gpt); sdmmc_storage_end(&storage); sd_unmount(); btn_wait(); } void mainApplicationCb(const char *test) { gfx_printf(&gfx_con, "\n%s\n\n", test); //btn_wait(); return; } extern heap_t _heap; static void call_elf_ep(moduleEntrypoint_t entrypoint) { pmoduleConfiguration_t moduleConfig = (pmoduleConfiguration_t)malloc(sizeof(struct moduleConfiguration_t)); moduleConfig->gfxCon = &gfx_con; moduleConfig->gfxCtx = &gfx_ctxt; moduleConfig->memcpy = (memcpy_t)&memcpy; moduleConfig->memset = (memset_t)&memset; moduleConfig->sharedHeap = &_heap; entrypoint(mainApplicationCb, moduleConfig); } static void *elfAllocationCallback( el_ctx *ctx, Elf_Addr phys, Elf_Addr virt, Elf_Addr size) { (void)ctx; (void)phys; (void)size; return (void *)virt; } void *buffteg = NULL; static bool elfReadCallback(el_ctx *ctx, void *dest, size_t numberBytes, size_t offset) { (void)ctx; memcpy(dest, buffteg + offset, numberBytes); return true; } // TODO: ??? Add more modules! void launch_elf() { gfx_clear_grey(&gfx_ctxt, 0x1B); gfx_con_setpos(&gfx_con, 0, 0); if (sd_mount()) { gfx_printf(&gfx_con, "Attempting to execute: %s\n", "module_sample.so"); buffteg = sd_file_read("module_sample.so"); sd_unmount(); el_ctx ctx; ctx.pread = elfReadCallback; if (el_init(&ctx)) { gfx_printf(&gfx_con, "%s\n", "Cant init ELF context"); goto elfLoadFinalOut; } elfBuff = memalign(ctx.align, ctx.memsz); if (!elfBuff) { gfx_printf(&gfx_con, "%s\n", "Cant alloc memory"); goto elfLoadFinalOut; } ctx.base_load_vaddr = ctx.base_load_paddr = (uintptr_t)elfBuff; if (el_load(&ctx, elfAllocationCallback)) { gfx_printf(&gfx_con, "%s\n", "el_load"); goto elfFreeOut; } if (el_relocate(&ctx)) { gfx_printf(&gfx_con, "%s\n", "el_relocate"); goto elfFreeOut; } uintptr_t epaddr = ctx.ehdr.e_entry + (uintptr_t)elfBuff; moduleEntrypoint_t ep = (moduleEntrypoint_t)epaddr; call_elf_ep(ep); elfFreeOut: free(elfBuff); elfBuff = NULL; } else { gfx_printf(&gfx_con, "%s\n", "Cant mount SD"); goto elfLoadFinalOut; } elfLoadFinalOut: btn_wait(); return; } void launch_firmware() { u8 max_entries = 61; ini_sec_t *cfg_sec = NULL; LIST_INIT(ini_sections); gfx_clear_grey(&gfx_ctxt, 0x1B); gfx_con_setpos(&gfx_con, 0, 0); if (sd_mount()) { if (ini_parse(&ini_sections, "hekate_ipl.ini")) { // Build configuration menu. ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 3)); ments[0].type = MENT_BACK; ments[0].caption = "Back"; ments[1].type = MENT_CHGLINE; u32 i = 2; LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_sections, link) { if (!strcmp(ini_sec->name, "config") || ini_sec->type == INI_COMMENT || ini_sec->type == INI_NEWLINE) continue; ments[i].type = ini_sec->type; ments[i].caption = ini_sec->name; ments[i].data = ini_sec; if (ini_sec->type == MENT_CAPTION) ments[i].color = ini_sec->color; i++; if (i > max_entries) break; } if (i > 2) { memset(&ments[i], 0, sizeof(ment_t)); menu_t menu = { ments, "Launch configurations", 0, 0 }; cfg_sec = ini_clone_section((ini_sec_t *)tui_do_menu(&gfx_con, &menu)); if (!cfg_sec) { free(ments); ini_free(&ini_sections); sd_unmount(); return; } } else EPRINTF("No launch configurations found."); free(ments); ini_free(&ini_sections); } else EPRINTF("Could not find or open 'hekate_ipl.ini'.\nMake sure it exists in SD Card!."); } if (!cfg_sec) { gfx_printf(&gfx_con, "\nUsing default launch configuration...\n\n"); gfx_puts(&gfx_con, "Press POWER to Continue.\nPress VOL to go to the menu.\n\n\n"); u32 btn = btn_wait(); if (!(btn & BTN_POWER)) goto out; } #ifdef MENU_LOGO_ENABLE free(Kc_MENU_LOGO); #endif //MENU_LOGO_ENABLE if (!hos_launch(cfg_sec)) { #ifdef MENU_LOGO_ENABLE Kc_MENU_LOGO = (u8 *)malloc(0x6000); blz_uncompress_srcdest(Kc_MENU_LOGO_blz, SZ_MENU_LOGO_BLZ, Kc_MENU_LOGO, SZ_MENU_LOGO); #endif //MENU_LOGO_ENABLE EPRINTF("Failed to launch firmware."); } out:; ini_free_section(cfg_sec); sd_unmount(); btn_wait(); } void auto_launch_firmware() { u8 *BOOTLOGO = NULL; struct _bmp_data { u32 size; u32 size_x; u32 size_y; u32 offset; u32 pos_x; u32 pos_y; }; struct _bmp_data bmpData; bool backlightEnabled = false; bool bootlogoFound = false; char *bootlogoCustomEntry = NULL; ini_sec_t *cfg_sec = NULL; LIST_INIT(ini_sections); gfx_con.mute = true; if (sd_mount()) { if (ini_parse(&ini_sections, "hekate_ipl.ini")) { u32 configEntry = 0; u32 boot_entry_id = 0; // Load configuration. LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_sections, link) { // Skip other ini entries for autoboot. if (ini_sec->type == INI_CHOICE) { if (!strcmp(ini_sec->name, "config")) { configEntry = 1; LIST_FOREACH_ENTRY(ini_kv_t, kv, &ini_sec->kvs, link) { if (!strcmp("autoboot", kv->key)) h_cfg.autoboot = atoi(kv->val); else if (!strcmp("bootwait", kv->key)) h_cfg.bootwait = atoi(kv->val); else if (!strcmp("customlogo", kv->key)) h_cfg.customlogo = atoi(kv->val); else if (!strcmp("verification", kv->key)) h_cfg.verification = atoi(kv->val); } boot_entry_id++; continue; } if (h_cfg.autoboot == boot_entry_id && configEntry) { cfg_sec = ini_clone_section(ini_sec); LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg_sec->kvs, link) { if (!strcmp("logopath", kv->key)) bootlogoCustomEntry = kv->val; } break; } boot_entry_id++; } } // Add missing configuration entry. if (!configEntry) create_config_entry(); if (!h_cfg.autoboot) goto out; // Auto boot is disabled. if (!cfg_sec) goto out; // No configurations. } else goto out; // Can't load hekate_ipl.ini. } else goto out; if (h_cfg.customlogo) { u8 *bitmap = NULL; if (bootlogoCustomEntry != NULL) // Check if user set custom logo path at the boot entry. { bitmap = (u8 *)sd_file_read(bootlogoCustomEntry); if (bitmap == NULL) // Custom entry bootlogo not found, trying default custom one. bitmap = (u8 *)sd_file_read("bootlogo.bmp"); } else // User has not set a custom logo path. bitmap = (u8 *)sd_file_read("bootlogo.bmp"); if (bitmap != NULL) { // Get values manually to avoid unaligned access. bmpData.size = bitmap[2] | bitmap[3] << 8 | bitmap[4] << 16 | bitmap[5] << 24; bmpData.offset = bitmap[10] | bitmap[11] << 8 | bitmap[12] << 16 | bitmap[13] << 24; bmpData.size_x = bitmap[18] | bitmap[19] << 8 | bitmap[20] << 16 | bitmap[21] << 24; bmpData.size_y = bitmap[22] | bitmap[23] << 8 | bitmap[24] << 16 | bitmap[25] << 24; // Sanity check. if (bitmap[0] == 'B' && bitmap[1] == 'M' && bitmap[28] == 32 && // bmpData.size_x <= 720 && bmpData.size_y <= 1280) { if ((bmpData.size - bmpData.offset) <= 0x400000) { // Avoid unaligned access from BM 2-byte MAGIC and remove header. BOOTLOGO = (u8 *)malloc(0x400000); memcpy(BOOTLOGO, bitmap + bmpData.offset, bmpData.size - bmpData.offset); free(bitmap); // Center logo if res < 720x1280. bmpData.pos_x = (720 - bmpData.size_x) >> 1; bmpData.pos_y = (1280 - bmpData.size_y) >> 1; // Get background color from 1st pixel. if (bmpData.size_x < 720 || bmpData.size_y < 1280) gfx_clear_color(&gfx_ctxt, *(u32 *)BOOTLOGO); bootlogoFound = true; } } else free(bitmap); } } // Render boot logo. if (bootlogoFound) { gfx_render_bmp_argb(&gfx_ctxt, (u32 *)BOOTLOGO, bmpData.size_x, bmpData.size_y, bmpData.pos_x, bmpData.pos_y); } #ifdef SPLASH_ENABLE else { BOOTLOGO = (void *)malloc(0x4000); blz_uncompress_srcdest(BOOTLOGO_BLZ, SZ_BOOTLOGO_BLZ, BOOTLOGO, SZ_BOOTLOGO); gfx_set_rect_grey(&gfx_ctxt, BOOTLOGO, X_BOOTLOGO, Y_BOOTLOGO, 326, 544); free(BOOTLOGO); } #endif free(BOOTLOGO); display_backlight(true); backlightEnabled = 1; // Wait before booting. If VOL- is pressed go into bootloader menu. u32 btn = btn_wait_timeout(h_cfg.bootwait * 1000, BTN_VOL_DOWN); if (btn & BTN_VOL_DOWN) goto out; ini_free(&ini_sections); #ifdef MENU_LOGO_ENABLE free(Kc_MENU_LOGO); #endif //MENU_LOGO_ENABLE if (!hos_launch(cfg_sec)) { // Failed to launch firmware. #ifdef MENU_LOGO_ENABLE Kc_MENU_LOGO = (u8 *)malloc(ALIGN(SZ_MENU_LOGO, 0x10)); blz_uncompress_srcdest(Kc_MENU_LOGO_blz, SZ_MENU_LOGO_BLZ, Kc_MENU_LOGO, SZ_MENU_LOGO); #endif //MENU_LOGO_ENABLE } out: gfx_clear_grey(&gfx_ctxt, 0x1B); ini_free(&ini_sections); ini_free_section(cfg_sec); sd_unmount(); gfx_con.mute = false; if (!backlightEnabled) display_backlight(true); } void toggle_autorcm() { sdmmc_storage_t storage; sdmmc_t sdmmc; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4)) { EPRINTF("Failed to init eMMC."); goto out; } u8 *tempbuf = (u8 *)malloc(0x200); sdmmc_storage_set_mmc_partition(&storage, 1); int i, sect = 0; for (i = 0; i < 4; i++) { sect = (0x200 + (0x4000 * i)) / NX_EMMC_BLOCKSIZE; sdmmc_storage_read(&storage, sect, 1, tempbuf); tempbuf[0x10] ^= 0x77; // !IMPORTANT: DO NOT CHANGE! XOR by arbitrary number to corrupt. sdmmc_storage_write(&storage, sect, 1, tempbuf); } free(tempbuf); sdmmc_storage_end(&storage); gfx_printf(&gfx_con, "%kAutoRCM mode toggled!%k\n\nPress any key...\n", 0xFF96FF00, 0xFFCCCCCC); out: btn_wait(); } int fix_attributes(char *path, u32 *total, u32 is_root, u32 check_first_run) { FRESULT res; DIR dir; u32 dirLength = 0; static FILINFO fno; if (check_first_run) { // Read file attributes. res = f_stat(path, &fno); if (res != FR_OK) return res; // Check if archive bit is set. if (fno.fattrib & AM_ARC) { *(u32 *)total = *(u32 *)total + 1; f_chmod(path, 0, AM_ARC); } } // Open directory. res = f_opendir(&dir, path); if (res != FR_OK) return res; dirLength = strlen(path); for (;;) { // Clear file or folder path. path[dirLength] = 0; // Read a directory item. res = f_readdir(&dir, &fno); // Break on error or end of dir. if (res != FR_OK || fno.fname[0] == 0) break; // Skip official Nintendo dir. if (is_root && !strcmp(fno.fname, "Nintendo")) continue; // Set new directory or file. memcpy(&path[dirLength], "/", 1); memcpy(&path[dirLength + 1], fno.fname, strlen(fno.fname) + 1); // Check if archive bit is set. if (fno.fattrib & AM_ARC) { *(u32 *)total = *(u32 *)total + 1; f_chmod(path, 0, AM_ARC); } // Is it a directory? if (fno.fattrib & AM_DIR) { // Enter the directory. res = fix_attributes(path, total, 0, 0); if (res != FR_OK) break; } } f_closedir(&dir); return res; } void fix_sd_attr(u32 type) { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); char path[256]; char label[14]; u32 total = 0; if (sd_mount()) { switch (type) { case 0: memcpy(path, "/", 2); memcpy(label, "SD Card", 8); break; case 1: default: memcpy(path, "/switch", 8); memcpy(label, "switch folder", 14); break; } gfx_printf(&gfx_con, "Traversing all %s files!\nThis may take some time, please wait...\n\n", label); fix_attributes(path, &total, !type, type); gfx_printf(&gfx_con, "%kTotal archive bits cleared: %d!%k\n\nDone! Press any key...", 0xFF96FF00, total, 0xFFCCCCCC); sd_unmount(); } btn_wait(); } void fix_sd_all_attr() { fix_sd_attr(0); } void fix_sd_switch_attr() { fix_sd_attr(1); } void print_fuel_gauge_info() { int value = 0; gfx_printf(&gfx_con, "%kFuel Gauge IC Info:\n%k", 0xFF00DDFF, 0xFFCCCCCC); max17050_get_property(MAX17050_Age, &value); gfx_printf(&gfx_con, "Age: %3d%\n", value); max17050_get_property(MAX17050_RepSOC, &value); gfx_printf(&gfx_con, "Capacity now: %3d%\n", value >> 8); max17050_get_property(MAX17050_RepCap, &value); gfx_printf(&gfx_con, "Capacity now: %4d mAh\n", value); max17050_get_property(MAX17050_FullCAP, &value); gfx_printf(&gfx_con, "Capacity full: %4d mAh\n", value); max17050_get_property(MAX17050_DesignCap, &value); gfx_printf(&gfx_con, "Capacity (design): %4d mAh\n", value); max17050_get_property(MAX17050_Current, &value); if (value >= 0) gfx_printf(&gfx_con, "Current now: %d mA\n", value / 1000); else gfx_printf(&gfx_con, "Current now: -%d mA\n", ~value / 1000); max17050_get_property(MAX17050_AvgCurrent, &value); if (value >= 0) gfx_printf(&gfx_con, "Current average: %d mA\n", value / 1000); else gfx_printf(&gfx_con, "Current average: -%d mA\n", ~value / 1000); max17050_get_property(MAX17050_VCELL, &value); gfx_printf(&gfx_con, "Voltage now: %4d mV\n", value); max17050_get_property(MAX17050_OCVInternal, &value); gfx_printf(&gfx_con, "Voltage open-circuit: %4d mV\n", value); max17050_get_property(MAX17050_MinVolt, &value); gfx_printf(&gfx_con, "Min voltage reached: %4d mV\n", value); max17050_get_property(MAX17050_MaxVolt, &value); gfx_printf(&gfx_con, "Max voltage reached: %4d mV\n", value); max17050_get_property(MAX17050_V_empty, &value); gfx_printf(&gfx_con, "Empty voltage (design): %4d mV\n", value); max17050_get_property(MAX17050_TEMP, &value); if (value >= 0) gfx_printf(&gfx_con, "Battery temperature: %d.%d oC\n", value / 10, value % 10); else gfx_printf(&gfx_con, "Battery temperature: -%d.%d oC\n", ~value / 10, (~value) % 10); } void print_battery_charger_info() { int value = 0; gfx_printf(&gfx_con, "%k\n\nBattery Charger IC Info:\n%k", 0xFF00DDFF, 0xFFCCCCCC); bq24193_get_property(BQ24193_InputVoltageLimit, &value); gfx_printf(&gfx_con, "Input voltage limit: %4d mV\n", value); bq24193_get_property(BQ24193_InputCurrentLimit, &value); gfx_printf(&gfx_con, "Input current limit: %4d mA\n", value); bq24193_get_property(BQ24193_SystemMinimumVoltage, &value); gfx_printf(&gfx_con, "Min voltage limit: %4d mV\n", value); bq24193_get_property(BQ24193_FastChargeCurrentLimit, &value); gfx_printf(&gfx_con, "Fast charge current limit: %4d mA\n", value); bq24193_get_property(BQ24193_ChargeVoltageLimit, &value); gfx_printf(&gfx_con, "Charge voltage limit: %4d mV\n", value); bq24193_get_property(BQ24193_ChargeStatus, &value); gfx_printf(&gfx_con, "Charge status: "); switch (value) { case 0: gfx_printf(&gfx_con, "Not charging\n"); break; case 1: gfx_printf(&gfx_con, "Pre-charging\n"); break; case 2: gfx_printf(&gfx_con, "Fast charging\n"); break; case 3: gfx_printf(&gfx_con, "Charge terminated\n"); break; default: gfx_printf(&gfx_con, "Unknown (%d)\n", value); break; } bq24193_get_property(BQ24193_TempStatus, &value); gfx_printf(&gfx_con, "Temperature status: "); switch (value) { case 0: gfx_printf(&gfx_con, "Normal\n"); break; case 2: gfx_printf(&gfx_con, "Warm\n"); break; case 3: gfx_printf(&gfx_con, "Cool\n"); break; case 5: gfx_printf(&gfx_con, "Cold\n"); break; case 6: gfx_printf(&gfx_con, "Hot\n"); break; default: gfx_printf(&gfx_con, "Unknown (%d)\n", value); break; } } void print_battery_info() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); print_fuel_gauge_info(); print_battery_charger_info(); u8 *buf = (u8 *)malloc(0x100 * 2); gfx_printf(&gfx_con, "%k\n\nBattery Fuel Gauge Registers:\n%k", 0xFF00DDFF, 0xFFCCCCCC); for (int i = 0; i < 0x200; i += 2) { i2c_recv_buf_small(buf + i, 2, I2C_1, 0x36, i >> 1); usleep(2500); } gfx_hexdump(&gfx_con, 0, (u8 *)buf, 0x200); gfx_puts(&gfx_con, "\nPress POWER to dump them to SD Card.\nPress VOL to go to the menu.\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { if (sd_mount()) { char path[64]; emmcsn_path_impl(path, "/Dumps", "fuel_gauge.bin", NULL); if (sd_save_to_file((u8 *)buf, 0x200, path)) EPRINTF("\nError creating fuel.bin file."); else gfx_puts(&gfx_con, "\nDone!\n"); sd_unmount(); } btn_wait(); } free(buf); } /* void fix_fuel_gauge_configuration() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); int battVoltage, avgCurrent; max17050_get_property(MAX17050_VCELL, &battVoltage); max17050_get_property(MAX17050_AvgCurrent, &avgCurrent); // Check if still charging. If not, check if battery is >= 95% (4.1V). if (avgCurrent < 0 && battVoltage > 4100) { if ((avgCurrent / 1000) < -10) EPRINTF("You need to be connected to a wall adapter,\nto apply this fix!"); else { gfx_printf(&gfx_con, "%kAre you really sure?\nThis will reset your fuel gauge completely!\n", 0xFFFFDD00); gfx_printf(&gfx_con, "Additionally this will power off your console.\n%k", 0xFFCCCCCC); gfx_puts(&gfx_con, "\nPress POWER to Continue.\nPress VOL to go to the menu.\n\n\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { max17050_fix_configuration(); msleep(1000); gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy); u16 value = 0; gfx_printf(&gfx_con, "%kThe console will power off in 45 seconds.\n%k", 0xFFFFDD00, 0xFFCCCCCC); while (value < 46) { gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); gfx_printf(&gfx_con, "%2ds elapsed", value); msleep(1000); value++; } msleep(2000); power_off(); } return; } } else EPRINTF("You need a fully charged battery\nand connected to a wall adapter,\nto apply this fix!"); msleep(500); btn_wait(); } */ /*void reset_pmic_fuel_gauge_charger_config() { int avgCurrent; gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); gfx_printf(&gfx_con, "%k\nThis will wipe your battery stats completely!\n" "%kAnd it may not power on without physically\nremoving and re-inserting the battery.\n%k" "\nAre you really sure?%k\n", 0xFFFFDD00, 0xFFFF0000, 0xFFFFDD00, 0xFFCCCCCC); gfx_puts(&gfx_con, "\nPress POWER to Continue.\nPress VOL to go to the menu.\n\n\n"); u32 btn = btn_wait(); if (btn & BTN_POWER) { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); gfx_printf(&gfx_con, "%kKeep the USB cable connected!%k\n\n", 0xFFFFDD00, 0xFFCCCCCC); gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy); u8 value = 30; while (value > 0) { gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); gfx_printf(&gfx_con, "%kWait... (%ds) %k", 0xFF888888, value, 0xFFCCCCCC); msleep(1000); value--; } gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy); //Check if still connected. max17050_get_property(MAX17050_AvgCurrent, &avgCurrent); if ((avgCurrent / 1000) < -10) EPRINTF("You need to be connected to a wall adapter\nor PC to apply this fix!"); else { // Apply fix. bq24193_fake_battery_removal(); gfx_printf(&gfx_con, "Done! \n" "%k1. Remove the USB cable\n" "2. Press POWER for 15s.\n" "3. Reconnect the USB to power-on!%k\n", 0xFFFFDD00, 0xFFCCCCCC); } msleep(500); btn_wait(); } }*/ void fix_battery_desync() { gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256); gfx_con_setpos(&gfx_con, 0, 0); max77620_low_battery_monitor_config(); gfx_puts(&gfx_con, "\nDone!\n"); btn_wait(); } void about() { static const char credits[] = "\nhekate (C) 2018 naehrwert, st4rk\n\n" "CTCaer mod (C) 2018 CTCaer\n" " ___________________________________________\n\n" "Thanks to: %kderrek, nedwill, plutoo,\n" " shuffle2, smea, thexyz, yellows8%k\n" " ___________________________________________\n\n" "Greetings to: fincs, hexkyz, SciresM,\n" " Shiny Quagsire, WinterMute\n" " ___________________________________________\n\n" "Open source and free packages used:\n\n" " - FatFs R0.13b,\n" " Copyright (C) 2018, ChaN\n\n" " - bcl-1.2.0,\n" " Copyright (C) 2003-2006, Marcus Geelnard\n\n" " - Atmosphere (SE sha256, prc id patches),\n" " Copyright (C) 2018, Atmosphere-NX\n\n" " - elfload,\n" " Copyright (C) 2014, Owen Shepherd\n" " Copyright (C) 2018, M4xw\n" " ___________________________________________\n\n"; static const char octopus[] = " %k___\n" " .-' `'.\n" " / \\\n" " | ;\n" " | | ___.--,\n" " _.._ |0) = (0) | _.---'`__.-( (_.\n" " __.--'`_.. '.__.\\ '--. \\_.-' ,.--'` `\"\"`\n" " ( ,.--'` ',__ /./; ;, '.__.'` __\n" " _`) ) .---.__.' / | |\\ \\__..--\"\" \"\"\"--.,_\n" " `---' .'.''-._.-'`_./ /\\ '. \\ _.--''````'''--._`-.__.'\n" " | | .' _.-' | | \\ \\ '. `----`\n" " \\ \\/ .' \\ \\ '. '-._)\n" " \\/ / \\ \\ `=.__`'-.\n" " / /\\ `) ) / / `\"\".`\\\n" " , _.-'.'\\ \\ / / ( ( / /\n" " `--'` ) ) .-'.' '.'. | (\n" " (/` ( (` ) ) '-; %k[switchbrew]%k\n" " ` '-; (-'%k"; gfx_clear_grey(&gfx_ctxt, 0x1B); gfx_con_setpos(&gfx_con, 0, 0); gfx_printf(&gfx_con, credits, 0xFF00CCFF, 0xFFCCCCCC); gfx_con.fntsz = 8; gfx_printf(&gfx_con, octopus, 0xFF00CCFF, 0xFF00FFCC, 0xFF00CCFF, 0xFFCCCCCC); btn_wait(); } ment_t ment_options[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_HANDLER("Auto boot", config_autoboot), MDEF_HANDLER("Boot time delay", config_bootdelay), MDEF_HANDLER("Custom boot logo", config_customlogo), MDEF_END() }; menu_t menu_options = { ment_options, "Launch Options", 0, 0 }; ment_t ment_cinfo[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("---- SoC Info ----", 0xFF0AB9E6), MDEF_HANDLER("Print fuse info", print_fuseinfo), MDEF_HANDLER("Print kfuse info", print_kfuseinfo), MDEF_HANDLER("Print TSEC keys", print_tsec_key), MDEF_CHGLINE(), MDEF_CAPTION("-- Storage Info --", 0xFF0AB9E6), MDEF_HANDLER("Print eMMC info", print_mmc_info), MDEF_HANDLER("Print SD Card info", print_sdcard_info), MDEF_CHGLINE(), MDEF_CAPTION("------ Misc ------", 0xFF0AB9E6), MDEF_HANDLER("Print battery info", print_battery_info), MDEF_END() }; menu_t menu_cinfo = { ment_cinfo, "Console Info", 0, 0 }; ment_t ment_autorcm[] = { MDEF_CAPTION("WARNING: This corrupts your BOOT0 partition!", 0xFFE6FF00), MDEF_CHGLINE(), MDEF_CAPTION("Do you want to continue?", 0xFFCCCCCC), MDEF_CHGLINE(), MDEF_BACK(), MDEF_BACK(), MDEF_BACK(), MDEF_BACK(), MDEF_HANDLER("Toggle AutoRCM", toggle_autorcm), MDEF_BACK(), MDEF_BACK(), MDEF_BACK(), MDEF_BACK(), MDEF_END() }; menu_t menu_autorcm = { ment_autorcm, "Toggle AutoRCM ON/OFF", 0, 0 }; ment_t ment_restore[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("------ Full --------", 0xFF0AB9E6), MDEF_HANDLER("Restore eMMC BOOT0/1", restore_emmc_boot), MDEF_HANDLER("Restore eMMC RAW GPP (exFAT only)", restore_emmc_rawnand), MDEF_CHGLINE(), MDEF_CAPTION("-- GPP Partitions --", 0xFF0AB9E6), MDEF_HANDLER("Restore GPP partitions", restore_emmc_gpp_parts), MDEF_END() }; menu_t menu_restore = { ment_restore, "Restore Options", 0, 0 }; ment_t ment_backup[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("------ Full --------", 0xFF0AB9E6), MDEF_HANDLER("Backup eMMC BOOT0/1", dump_emmc_boot), MDEF_HANDLER("Backup eMMC RAW GPP", dump_emmc_rawnand), MDEF_CHGLINE(), MDEF_CAPTION("-- GPP Partitions --", 0xFF0AB9E6), MDEF_HANDLER("Backup eMMC SYS", dump_emmc_system), MDEF_HANDLER("Backup eMMC USER", dump_emmc_user), MDEF_END() }; menu_t menu_backup = { ment_backup, "Backup Options", 0, 0 }; ment_t ment_tools[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("-- Backup & Restore --", 0xFF0AB9E6), MDEF_MENU("Backup", &menu_backup), MDEF_MENU("Restore", &menu_restore), MDEF_HANDLER("Verification options", config_verification), MDEF_CHGLINE(), MDEF_CAPTION("-------- Misc --------", 0xFF0AB9E6), MDEF_HANDLER("Dump package1/2", dump_packages12), MDEF_HANDLER("Fix battery de-sync", fix_battery_desync), MDEF_HANDLER("Unset archive bit (switch folder)", fix_sd_switch_attr), MDEF_HANDLER("Unset archive bit (all sd files)", fix_sd_all_attr), //MDEF_HANDLER("Fix fuel gauge configuration", fix_fuel_gauge_configuration), //MDEF_HANDLER("Reset all battery cfg", reset_pmic_fuel_gauge_charger_config), MDEF_CHGLINE(), MDEF_CAPTION("------ Dangerous -----", 0xFFFF0000), MDEF_MENU("AutoRCM", &menu_autorcm), MDEF_END() }; menu_t menu_tools = { ment_tools, "Tools", 0, 0 }; ment_t ment_top[] = { MDEF_HANDLER("Launch firmware", launch_firmware), MDEF_HANDLER("Launch ELF", launch_elf), MDEF_MENU("Launch options", &menu_options), MDEF_CAPTION("---------------", 0xFF444444), MDEF_MENU("Tools", &menu_tools), MDEF_MENU("Console info", &menu_cinfo), MDEF_CAPTION("---------------", 0xFF444444), MDEF_HANDLER("Reboot (Normal)", reboot_normal), MDEF_HANDLER("Reboot (RCM)", reboot_rcm), MDEF_HANDLER("Power off", power_off), MDEF_CAPTION("---------------", 0xFF444444), MDEF_HANDLER("About", about), MDEF_END() }; menu_t menu_top = { ment_top, "hekate - CTCaer mod v3.2", 0, 0 }; extern void pivot_stack(u32 stack_top); void ipl_main() { config_hw(); //Pivot the stack so we have enough space. pivot_stack(0x90010000); //Tegra/Horizon configuration goes to 0x80000000+, package2 goes to 0xA9800000, we place our heap in between. heap_init(0x90020000); //uart_send(UART_C, (u8 *)0x40000000, 0x10000); //uart_wait_idle(UART_C, UART_TX_IDLE); display_init(); //display_color_screen(0xAABBCCDD); u32 *fb = display_init_framebuffer(); gfx_init_ctxt(&gfx_ctxt, fb, 720, 1280, 768); #ifdef MENU_LOGO_ENABLE Kc_MENU_LOGO = (u8 *)malloc(0x6000); blz_uncompress_srcdest(Kc_MENU_LOGO_blz, SZ_MENU_LOGO_BLZ, Kc_MENU_LOGO, SZ_MENU_LOGO); #endif //MENU_LOGO_ENABLE gfx_con_init(&gfx_con, &gfx_ctxt); // Enable backlight after initializing gfx //display_backlight(true); set_default_configuration(); // Load saved configuration and auto boot if enabled. auto_launch_firmware(); while (1) tui_do_menu(&gfx_con, &menu_top); while (1) ; }