diff --git a/source/config/config.c b/source/config/config.c
new file mode 100644
index 0000000..f9df696
--- /dev/null
+++ b/source/config/config.c
@@ -0,0 +1,668 @@
+/*
+ * Copyright (c) 2018-2019 CTCaer
+ *
+ * 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 "config.h"
+#include "ini.h"
+#include "../gfx/gfx.h"
+#include "../gfx/tui.h"
+#include "../libs/fatfs/ff.h"
+#include "../soc/t210.h"
+#include "../storage/sdmmc.h"
+#include "../utils/btn.h"
+#include "../utils/list.h"
+#include "../utils/util.h"
+
+extern hekate_config h_cfg;
+extern bool sd_mount();
+extern void sd_unmount();
+
+void set_default_configuration()
+{
+ h_cfg.autoboot = 0;
+ h_cfg.autoboot_list = 0;
+ h_cfg.bootwait = 3;
+ h_cfg.verification = 1;
+ h_cfg.se_keygen_done = 0;
+ h_cfg.sbar_time_keeping = 0;
+ h_cfg.backlight = 100;
+ h_cfg.autohosoff = 0;
+ h_cfg.autonogc = 1;
+ h_cfg.brand = NULL;
+ h_cfg.tagline = NULL;
+ h_cfg.errors = 0;
+ h_cfg.sept_run = EMC(EMC_SCRATCH0) & EMC_SEPT_RUN;
+ h_cfg.rcm_patched = true;
+ h_cfg.emummc_force_disable = false;
+
+ sd_power_cycle_time_start = 0xFFFFFFF;
+}
+
+int create_config_entry()
+{
+ if (!sd_mount())
+ return 1;
+
+ char lbuf[32];
+ FIL fp;
+ bool mainIniFound = false;
+
+ LIST_INIT(ini_sections);
+
+ if (ini_parse(&ini_sections, "bootloader/hekate_ipl.ini", false))
+ mainIniFound = true;
+ else
+ {
+ u8 res = f_open(&fp, "bootloader/hekate_ipl.ini", FA_READ);
+ if (res == FR_NO_FILE || res == FR_NO_PATH)
+ {
+ f_mkdir("bootloader");
+ f_mkdir("bootloader/ini");
+ f_mkdir("bootloader/payloads");
+ f_mkdir("bootloader/sys");
+ }
+ else
+ {
+ if (!res)
+ f_close(&fp);
+ return 1;
+ }
+ }
+
+ if (f_open(&fp, "bootloader/hekate_ipl.ini", FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
+ return 1;
+ // Add config entry.
+ f_puts("[config]\nautoboot=", &fp);
+ itoa(h_cfg.autoboot, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nautoboot_list=", &fp);
+ itoa(h_cfg.autoboot_list, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nbootwait=", &fp);
+ itoa(h_cfg.bootwait, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nverification=", &fp);
+ itoa(h_cfg.verification, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nbacklight=", &fp);
+ itoa(h_cfg.backlight, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nautohosoff=", &fp);
+ itoa(h_cfg.autohosoff, lbuf, 10);
+ f_puts(lbuf, &fp);
+ f_puts("\nautonogc=", &fp);
+ itoa(h_cfg.autonogc, lbuf, 10);
+ f_puts(lbuf, &fp);
+ if (h_cfg.brand)
+ {
+ f_puts("\nbrand=", &fp);
+ f_puts(h_cfg.brand, &fp);
+ }
+ if (h_cfg.tagline)
+ {
+ f_puts("\ntagline=", &fp);
+ f_puts(h_cfg.tagline, &fp);
+ }
+ f_puts("\n", &fp);
+
+ if (mainIniFound)
+ {
+ // Re-construct existing entries.
+ LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_sections, link)
+ {
+ if (!strcmp(ini_sec->name, "config"))
+ continue;
+
+ switch (ini_sec->type)
+ {
+ case INI_CHOICE: // Re-construct Boot entry [ ].
+ f_puts("[", &fp);
+ f_puts(ini_sec->name, &fp);
+ f_puts("]\n", &fp);
+ // Re-construct boot entry's config.
+ LIST_FOREACH_ENTRY(ini_kv_t, kv, &ini_sec->kvs, link)
+ {
+ f_puts(kv->key, &fp);
+ f_puts("=", &fp);
+ f_puts(kv->val, &fp);
+ f_puts("\n", &fp);
+ }
+ break;
+ case INI_CAPTION: // Re-construct caption entry { }.
+ f_puts("{", &fp);
+ f_puts(ini_sec->name, &fp);
+ f_puts("}\n", &fp);
+ break;
+ case INI_NEWLINE: // Re-construct cosmetic newline \n.
+ f_puts("\n", &fp);
+ break;
+ case INI_COMMENT: // Re-construct comment entry #.
+ f_puts("#", &fp);
+ f_puts(ini_sec->name, &fp);
+ f_puts("\n", &fp);
+ break;
+ }
+ }
+ }
+
+ f_close(&fp);
+ sd_unmount();
+
+ return 0;
+}
+
+#pragma GCC push_options
+#pragma GCC optimize ("Os")
+
+static void _save_config()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ if (!create_config_entry())
+ gfx_puts("\nConfiguration was saved!\n");
+ else
+ EPRINTF("\nConfiguration saving failed!");
+ gfx_puts("\nPress any key...");
+}
+
+static void _config_autoboot_list(void *ent)
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ u32 *temp_autoboot = NULL;
+
+ LIST_INIT(ini_sections);
+
+ u8 max_entries = 30;
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 3));
+ u32 *boot_values = (u32 *)malloc(sizeof(u32) * max_entries);
+ char *boot_text = (char *)malloc(512 * max_entries);
+
+ for (u32 j = 0; j < max_entries; j++)
+ boot_values[j] = j;
+
+ if (sd_mount())
+ {
+ if (ini_parse(&ini_sections, "bootloader/ini", true))
+ {
+ // Build configuration menu.
+ 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)
+ {
+ // Skip other ini entries for autoboot.
+ if (ini_sec->type == INI_CHOICE)
+ {
+ if (!strcmp(ini_sec->name, "config"))
+ continue;
+
+ if (strlen(ini_sec->name) > 510)
+ ments[i].caption = ini_sec->name;
+ else
+ {
+ if (h_cfg.autoboot != (i - 1) || !h_cfg.autoboot_list)
+ boot_text[(i - 1) * 512] = ' ';
+
+ else
+ boot_text[(i - 1) * 512] = '*';
+ memcpy(boot_text + (i - 1) * 512 + 1, ini_sec->name, strlen(ini_sec->name) + 1);
+ boot_text[strlen(ini_sec->name) + (i - 1) * 512 + 1] = 0;
+ ments[i].caption = &boot_text[(i - 1) * 512];
+ }
+ ments[i].type = ini_sec->type;
+ ments[i].data = &boot_values[i - 1];
+ i++;
+
+ if ((i - 1) > max_entries)
+ break;
+ }
+ }
+
+ memset(&ments[i], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Select an entry to auto boot", 0, 0};
+ temp_autoboot = (u32 *)tui_do_menu(&menu);
+ if (temp_autoboot != NULL)
+ {
+ h_cfg.autoboot = *(u32 *)temp_autoboot;
+ h_cfg.autoboot_list = 1;
+ _save_config();
+
+ ment_t *tmp = (ment_t *)ent;
+ tmp->data = NULL;
+ }
+ else
+ goto out2;
+ }
+ else
+ {
+ EPRINTF("Could not open 'bootloader/hekate_ipl.ini'.\nMake sure it exists!.");
+ goto out;
+ }
+ }
+
+out:;
+ btn_wait();
+out2:;
+ free(ments);
+ free(boot_values);
+ free(boot_text);
+
+ sd_unmount();
+}
+
+void config_autoboot()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ u32 *temp_autoboot = NULL;
+
+ LIST_INIT(ini_sections);
+
+ u8 max_entries = 30;
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 5));
+ u32 *boot_values = (u32 *)malloc(sizeof(u32) * max_entries);
+ char *boot_text = (char *)malloc(512 * max_entries);
+
+ for (u32 j = 0; j < max_entries; j++)
+ boot_values[j] = j;
+
+ if (sd_mount())
+ {
+ if (ini_parse(&ini_sections, "bootloader/hekate_ipl.ini", false))
+ {
+ // Build configuration menu.
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ ments[2].type = MENT_DATA;
+ if (!h_cfg.autoboot)
+ ments[2].caption = "*Disable";
+ else
+ ments[2].caption = " Disable";
+ ments[2].data = &boot_values[0];
+
+ ments[3].type = MENT_HDLR_RE;
+ if (h_cfg.autoboot_list)
+ ments[3].caption = "*More configs...";
+ else
+ ments[3].caption = " More configs...";
+ ments[3].handler = _config_autoboot_list;
+ ments[3].data = (void *)0xCAFE;
+
+ ments[4].type = MENT_CHGLINE;
+
+ u32 i = 5;
+ 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"))
+ continue;
+
+ if (strlen(ini_sec->name) > 510)
+ ments[i].caption = ini_sec->name;
+ else
+ {
+ if (h_cfg.autoboot != (i - 4) || h_cfg.autoboot_list)
+ boot_text[(i - 4) * 512] = ' ';
+
+ else
+ boot_text[(i - 4) * 512] = '*';
+ memcpy(boot_text + (i - 4) * 512 + 1, ini_sec->name, strlen(ini_sec->name) + 1);
+ boot_text[strlen(ini_sec->name) + (i - 4) * 512 + 1] = 0;
+ ments[i].caption = &boot_text[(i - 4) * 512];
+ }
+ ments[i].type = ini_sec->type;
+ ments[i].data = &boot_values[i - 4];
+ i++;
+
+ if ((i - 4) > max_entries)
+ break;
+ }
+ }
+ if (i < 6 && !h_cfg.autoboot_list)
+ {
+ ments[i].type = MENT_CAPTION;
+ ments[i].caption = "No main configurations found...";
+ ments[i].color = 0xFFFFDD00;
+ i++;
+ }
+
+ memset(&ments[i], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Disable or select entry to auto boot", 0, 0};
+ temp_autoboot = (u32 *)tui_do_menu(&menu);
+ if (temp_autoboot != NULL)
+ {
+ h_cfg.autoboot = *(u32 *)temp_autoboot;
+ h_cfg.autoboot_list = 0;
+ _save_config();
+ }
+ else
+ goto out2;
+ }
+ else
+ {
+ EPRINTF("Could not open 'bootloader/hekate_ipl.ini'.\nMake sure it exists!.");
+ goto out;
+ }
+ }
+
+out:;
+ btn_wait();
+out2:;
+ free(ments);
+ free(boot_values);
+ free(boot_text);
+
+ sd_unmount();
+
+ if (temp_autoboot == NULL)
+ return;
+}
+
+void config_bootdelay()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ u32 delay_entries = 6;
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (delay_entries + 3));
+ u32 *delay_values = (u32 *)malloc(sizeof(u32) * delay_entries);
+ char *delay_text = (char *)malloc(32 * delay_entries);
+
+ for (u32 j = 0; j < delay_entries; j++)
+ delay_values[j] = j;
+
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ ments[2].type = MENT_DATA;
+ if (h_cfg.bootwait)
+ ments[2].caption = " 0 seconds (Bootlogo disabled)";
+ else
+ ments[2].caption = "*0 seconds (Bootlogo disabled)";
+ ments[2].data = &delay_values[0];
+
+ u32 i = 0;
+ for (i = 1; i < delay_entries; i++)
+ {
+ if (h_cfg.bootwait != i)
+ delay_text[i * 32] = ' ';
+ else
+ delay_text[i * 32] = '*';
+ delay_text[i * 32 + 1] = i + '0';
+ memcpy(delay_text + i * 32 + 2, " seconds", 9);
+
+ ments[i + 2].type = MENT_DATA;
+ ments[i + 2].caption = delay_text + i * 32;
+ ments[i + 2].data = &delay_values[i];
+ }
+
+ memset(&ments[i + 2], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Time delay for entering bootloader menu", 0, 0};
+
+ u32 *temp_bootwait = (u32 *)tui_do_menu(&menu);
+ if (temp_bootwait != NULL)
+ {
+ h_cfg.bootwait = *(u32 *)temp_bootwait;
+ _save_config();
+ }
+
+ free(ments);
+ free(delay_values);
+ free(delay_text);
+
+ if (temp_bootwait == NULL)
+ return;
+ btn_wait();
+}
+
+void config_verification()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * 6);
+ u32 *vr_values = (u32 *)malloc(sizeof(u32) * 3);
+ char *vr_text = (char *)malloc(64 * 3);
+
+ for (u32 j = 0; j < 3; j++)
+ {
+ vr_values[j] = j;
+ ments[j + 2].type = MENT_DATA;
+ ments[j + 2].data = &vr_values[j];
+ }
+
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ memcpy(vr_text, " Disable (Fastest - Unsafe)", 28);
+ memcpy(vr_text + 64, " Sparse (Fast - Safe)", 23);
+ memcpy(vr_text + 128, " Full (Slow - Safe)", 23);
+
+ for (u32 i = 0; i < 3; i++)
+ {
+ if (h_cfg.verification != i)
+ vr_text[64 * i] = ' ';
+ else
+ vr_text[64 * i] = '*';
+ ments[2 + i].caption = vr_text + (i * 64);
+ }
+
+ memset(&ments[5], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Backup & Restore verification", 0, 0};
+
+ u32 *temp_verification = (u32 *)tui_do_menu(&menu);
+ if (temp_verification != NULL)
+ {
+ h_cfg.verification = *(u32 *)temp_verification;
+ _save_config();
+ }
+
+ free(ments);
+ free(vr_values);
+ free(vr_text);
+
+ if (temp_verification == NULL)
+ return;
+ btn_wait();
+}
+
+void config_backlight()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ u32 bri_entries = 11;
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (bri_entries + 3));
+ u32 *bri_values = (u32 *)malloc(sizeof(u32) * bri_entries);
+ char *bri_text = (char *)malloc(8 * bri_entries);
+
+ for (u32 j = 1; j < bri_entries; j++)
+ bri_values[j] = j * 10;
+
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ u32 i = 0;
+ for (i = 1; i < bri_entries; i++)
+ {
+ if ((h_cfg.backlight / 20) != i)
+ bri_text[i * 32] = ' ';
+ else
+ bri_text[i * 32] = '*';
+
+ if (i < 10)
+ {
+ bri_text[i * 32 + 1] = i + '0';
+ memcpy(bri_text + i * 32 + 2, "0%", 3);
+ }
+ else
+ memcpy(bri_text + i * 32 + 1, "100%", 5);
+
+ ments[i + 1].type = MENT_DATA;
+ ments[i + 1].caption = bri_text + i * 32;
+ ments[i + 1].data = &bri_values[i];
+ }
+
+ memset(&ments[i + 1], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Backlight brightness", 0, 0};
+
+ u32 *temp_backlight = (u32 *)tui_do_menu(&menu);
+ if (temp_backlight != NULL)
+ {
+ h_cfg.backlight = (*(u32 *)temp_backlight) * 2;
+ _save_config();
+ }
+
+ free(ments);
+ free(bri_values);
+ free(bri_text);
+
+ if (temp_backlight == NULL)
+ return;
+ btn_wait();
+}
+
+void config_auto_hos_poweroff()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * 6);
+ u32 *hp_values = (u32 *)malloc(sizeof(u32) * 3);
+
+ for (u32 j = 0; j < 3; j++)
+ {
+ hp_values[j] = j;
+ ments[j + 2].type = MENT_DATA;
+ ments[j + 2].data = &hp_values[j];
+ }
+
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ if (h_cfg.autohosoff == 1)
+ {
+ ments[2].caption = " Disable";
+ ments[3].caption = "*Enable";
+ ments[4].caption = " Enable (No logo)";
+ }
+ else if (h_cfg.autohosoff >= 2)
+ {
+ ments[2].caption = " Disable";
+ ments[3].caption = " Enable";
+ ments[4].caption = "*Enable (No logo)";
+ }
+ else
+ {
+ ments[2].caption = "*Disable";
+ ments[3].caption = " Enable";
+ ments[4].caption = " Enable (No logo)";
+ }
+
+ memset(&ments[5], 0, sizeof(ment_t));
+ menu_t menu = {ments, "Power off if woke up from HOS", 0, 0};
+
+ u32 *temp_autohosoff = (u32 *)tui_do_menu(&menu);
+ if (temp_autohosoff != NULL)
+ {
+ h_cfg.autohosoff = *(u32 *)temp_autohosoff;
+ _save_config();
+ }
+
+ free(ments);
+ free(hp_values);
+
+ if (temp_autohosoff == NULL)
+ return;
+ btn_wait();
+}
+
+void config_nogc()
+{
+ gfx_clear_grey(0x1B);
+ gfx_con_setpos(0, 0);
+
+ ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * 5);
+ u32 *cb_values = (u32 *)malloc(sizeof(u32) * 2);
+
+ for (u32 j = 0; j < 2; j++)
+ {
+ cb_values[j] = j;
+ ments[j + 2].type = MENT_DATA;
+ ments[j + 2].data = &cb_values[j];
+ }
+
+ ments[0].type = MENT_BACK;
+ ments[0].caption = "Back";
+
+ ments[1].type = MENT_CHGLINE;
+
+ if (h_cfg.autonogc)
+ {
+ ments[2].caption = " Disable";
+ ments[3].caption = "*Auto";
+ }
+ else
+ {
+ ments[2].caption = "*Disable";
+ ments[3].caption = " Auto";
+ }
+
+ memset(&ments[4], 0, sizeof(ment_t));
+ menu_t menu = {ments, "No Gamecard", 0, 0};
+
+ u32 *temp_nogc = (u32 *)tui_do_menu(&menu);
+ if (temp_nogc != NULL)
+ {
+ h_cfg.autonogc = *(u32 *)temp_nogc;
+ _save_config();
+ }
+
+ free(ments);
+ free(cb_values);
+
+ if (temp_nogc == NULL)
+ return;
+ btn_wait();
+}
+
+#pragma GCC pop_options
diff --git a/source/config/config.h b/source/config/config.h
new file mode 100644
index 0000000..8cd34e1
--- /dev/null
+++ b/source/config/config.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2019 CTCaer
+ *
+ * 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 .
+ */
+
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+#include "../utils/types.h"
+
+typedef struct _hekate_config
+{
+ // Non-volatile config.
+ u32 autoboot;
+ u32 autoboot_list;
+ u32 bootwait;
+ u32 verification;
+ u32 backlight;
+ u32 autohosoff;
+ u32 autonogc;
+ char *brand;
+ char *tagline;
+ // Global temporary config.
+ bool se_keygen_done;
+ bool sept_run;
+ bool emummc_force_disable;
+ bool rcm_patched;
+ u32 sbar_time_keeping;
+ u32 errors;
+} hekate_config;
+
+typedef enum
+{
+ ERR_LIBSYS_LP0 = (1 << 0),
+} hsysmodule_t;
+
+void set_default_configuration();
+int create_config_entry();
+void config_autoboot();
+void config_bootdelay();
+void config_verification();
+void config_backlight();
+void config_auto_hos_poweroff();
+void config_nogc();
+
+#endif /* _CONFIG_H_ */
diff --git a/source/config/ini.c b/source/config/ini.c
new file mode 100644
index 0000000..f033994
--- /dev/null
+++ b/source/config/ini.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (C) 2018-2019 CTCaer
+ *
+ * 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 "ini.h"
+#include "../libs/fatfs/ff.h"
+#include "../mem/heap.h"
+#include "../utils/dirlist.h"
+
+static char *_strdup(char *str)
+{
+ if (!str)
+ return NULL;
+
+ // Remove starting space.
+ if (str[0] == ' ' && strlen(str))
+ str++;
+
+ char *res = (char *)malloc(strlen(str) + 1);
+ strcpy(res, str);
+
+ // Remove trailing space.
+ if (strlen(res) && res[strlen(res) - 1] == ' ')
+ res[strlen(res) - 1] = 0;
+
+ return res;
+}
+
+u32 _find_section_name(char *lbuf, u32 lblen, char schar)
+{
+ u32 i;
+ for (i = 0; i < lblen && lbuf[i] != schar && lbuf[i] != '\n' && lbuf[i] != '\r'; i++)
+ ;
+ lbuf[i] = 0;
+
+ return i;
+}
+
+ini_sec_t *_ini_create_section(link_t *dst, ini_sec_t *csec, char *name, u8 type)
+{
+ if (csec)
+ {
+ list_append(dst, &csec->link);
+ csec = NULL;
+ }
+
+ csec = (ini_sec_t *)malloc(sizeof(ini_sec_t));
+ csec->name = _strdup(name);
+ csec->type = type;
+
+ return csec;
+}
+
+int ini_parse(link_t *dst, char *ini_path, bool is_dir)
+{
+ u32 lblen;
+ u32 pathlen = strlen(ini_path);
+ u32 k = 0;
+ char lbuf[512];
+ char *filelist = NULL;
+ FIL fp;
+ ini_sec_t *csec = NULL;
+
+ char *filename = (char *)malloc(256);
+
+ memcpy(filename, ini_path, pathlen + 1);
+
+ // Get all ini filenames.
+ if (is_dir)
+ {
+ filelist = dirlist(filename, "*.ini", false);
+ if (!filelist)
+ {
+ free(filename);
+ return 0;
+ }
+ memcpy(filename + pathlen, "/", 2);
+ pathlen++;
+ }
+
+ do
+ {
+ // Copy ini filename in path string.
+ if (is_dir)
+ {
+ if (filelist[k * 256])
+ {
+ memcpy(filename + pathlen, &filelist[k * 256], strlen(&filelist[k * 256]) + 1);
+ k++;
+ }
+ else
+ break;
+ }
+
+ // Open ini.
+ if (f_open(&fp, filename, FA_READ) != FR_OK)
+ {
+ free(filelist);
+ free(filename);
+
+ return 0;
+ }
+
+ do
+ {
+ // Fetch one line.
+ lbuf[0] = 0;
+ f_gets(lbuf, 512, &fp);
+ lblen = strlen(lbuf);
+
+ // Remove trailing newline.
+ if (lbuf[lblen - 1] == '\n' || lbuf[lblen - 1] == '\r')
+ lbuf[lblen - 1] = 0;
+
+ if (lblen > 2 && lbuf[0] == '[') // Create new section.
+ {
+ _find_section_name(lbuf, lblen, ']');
+
+ csec = _ini_create_section(dst, csec, &lbuf[1], INI_CHOICE);
+ list_init(&csec->kvs);
+ }
+ else if (lblen > 2 && lbuf[0] == '{') //Create new caption.
+ {
+ _find_section_name(lbuf, lblen, '}');
+
+ csec = _ini_create_section(dst, csec, &lbuf[1], INI_CAPTION);
+ csec->color = 0xFF0AB9E6;
+ }
+ else if (lblen > 2 && lbuf[0] == '#') //Create empty lines and comments.
+ {
+ _find_section_name(lbuf, lblen, '\0');
+
+ csec = _ini_create_section(dst, csec, &lbuf[1], INI_COMMENT);
+ }
+ else if (lblen < 2)
+ {
+ csec = _ini_create_section(dst, csec, NULL, INI_NEWLINE);
+ }
+ else if (csec && csec->type == INI_CHOICE) //Extract key/value.
+ {
+ u32 i = _find_section_name(lbuf, lblen, '=');
+
+ ini_kv_t *kv = (ini_kv_t *)malloc(sizeof(ini_kv_t));
+ kv->key = _strdup(&lbuf[0]);
+ kv->val = _strdup(&lbuf[i + 1]);
+ list_append(&csec->kvs, &kv->link);
+ }
+ } while (!f_eof(&fp));
+
+ f_close(&fp);
+
+ if (csec)
+ {
+ list_append(dst, &csec->link);
+ if (is_dir)
+ csec = NULL;
+ }
+ } while (is_dir);
+
+ free(filename);
+ free(filelist);
+
+ return 1;
+}
+
+char *ini_check_payload_section(ini_sec_t *cfg)
+{
+ if (cfg == NULL)
+ return NULL;
+
+ LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg->kvs, link)
+ {
+ if (!strcmp("payload", kv->key))
+ return kv->val;
+ }
+
+ return NULL;
+}
diff --git a/source/config/ini.h b/source/config/ini.h
new file mode 100644
index 0000000..318adf0
--- /dev/null
+++ b/source/config/ini.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018 naehrwert
+ * Copyright (C) 2018 CTCaer
+ *
+ * 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 .
+ */
+
+#ifndef _INI_H_
+#define _INI_H_
+
+#include "../utils/types.h"
+#include "../utils/list.h"
+
+#define INI_CHOICE 3
+#define INI_CAPTION 5
+#define INI_CHGLINE 6
+#define INI_NEWLINE 0xFE
+#define INI_COMMENT 0xFF
+
+typedef struct _ini_kv_t
+{
+ char *key;
+ char *val;
+ link_t link;
+} ini_kv_t;
+
+typedef struct _ini_sec_t
+{
+ char *name;
+ link_t kvs;
+ link_t link;
+ u32 type;
+ u32 color;
+} ini_sec_t;
+
+int ini_parse(link_t *dst, char *ini_path, bool is_dir);
+char *ini_check_payload_section(ini_sec_t *cfg);
+
+#endif
+
diff --git a/source/keys/keys.c b/source/keys/keys.c
index 9f3a135..6572449 100644
--- a/source/keys/keys.c
+++ b/source/keys/keys.c
@@ -15,6 +15,8 @@
*/
#include "keys.h"
+
+#include "../config/config.h"
#include "../gfx/di.h"
#include "../gfx/gfx.h"
#include "../hos/pkg1.h"
@@ -45,16 +47,21 @@ extern bool sd_mount();
extern void sd_unmount();
extern int sd_save_to_file(void *buf, u32 size, const char *filename);
+extern hekate_config h_cfg;
+
u32 _key_count = 0;
sdmmc_storage_t storage;
emmc_part_t *system_part;
+u32 start_time, end_time;
#define TPRINTF(text) \
- end_time = get_tmr_ms(); \
- gfx_printf(text" done @ %d.%03ds\n", (end_time - start_time) / 1000, (end_time - start_time) % 1000)
+ end_time = get_tmr_us(); \
+ gfx_printf(text" done in %d us\n", end_time - start_time); \
+ start_time = get_tmr_us()
#define TPRINTFARGS(text, args...) \
- end_time = get_tmr_ms(); \
- gfx_printf(text" done @ %d.%03ds\n", args, (end_time - start_time) / 1000, (end_time - start_time) % 1000)
+ end_time = get_tmr_us(); \
+ gfx_printf(text" done in %d us\n", args, end_time - start_time); \
+ start_time = get_tmr_us()
#define SAVE_KEY(name, src, len) _save_key(name, src, len, text_buffer)
#define SAVE_KEY_FAMILY(name, src, count, len) _save_key_family(name, src, count, len, text_buffer)
@@ -104,14 +111,15 @@ void dump_keys() {
gfx_printf("[%kLo%kck%kpi%kck%k_R%kCM%k v%d.%d.%d%k]\n\n",
colors[0], colors[1], colors[2], colors[3], colors[4], colors[5], 0xFFFF00FF, LP_VER_MJ, LP_VER_MN, LP_VER_BF, 0xFFCCCCCC);
- u32 start_time = get_tmr_ms(),
- end_time,
- retries = 0;
+ start_time = get_tmr_us();
+ u32 retries = 0;
+ u32 color_idx = 0;
tsec_ctxt_t tsec_ctxt;
sdmmc_t sdmmc;
sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4);
+ TPRINTFARGS("%kMMC init... ", colors[(color_idx++) % 6]);
// Read package1.
u8 *pkg1 = (u8 *)malloc(0x40000);
@@ -151,7 +159,7 @@ void dump_keys() {
f_rename("sd:/sept/payload.bak", "sd:/sept/payload.bin");
}
- if (!(EMC(EMC_SCRATCH0) & EMC_SEPT_RUN)) {
+ if (!h_cfg.sept_run) {
// bundle lp0 fw for sept instead of loading it from SD as hekate does
sdram_lp0_save_params(sdram_get_params_patched());
FIL fp;
@@ -167,8 +175,10 @@ void dump_keys() {
u32 payload_size = *(u32 *)(IPL_LOAD_ADDR + 0x84) - IPL_LOAD_ADDR;
f_write(&fp, (u8 *)IPL_LOAD_ADDR, payload_size, NULL);
f_close(&fp);
- gfx_printf("%kFirmware 7.x or higher detected.\n%kRenamed /sept/payload.bin", colors[0], colors[1]);
- gfx_printf("\n%k to /sept/payload.bak\n%kCopied self to /sept/payload.bin",colors[2], colors[3]);
+ gfx_printf("%k\nFirmware 7.x or higher detected.\n%kRenamed /sept/payload.bin", colors[(color_idx) % 6], colors[(color_idx + 1) % 6]);
+ color_idx += 2;
+ gfx_printf("\n%k to /sept/payload.bak\n%kCopied self to /sept/payload.bin", colors[(color_idx) % 6], colors[(color_idx + 1) % 6]);
+ color_idx += 2;
sdmmc_storage_end(&storage);
if (!reboot_to_sept((u8 *)tsec_ctxt.fw, tsec_ctxt.size, pkg1_id->kb))
goto out_wait;
@@ -207,7 +217,7 @@ get_tsec: ;
goto out_wait;
}
- TPRINTFARGS("%kTSEC key(s)... ", colors[0]);
+ TPRINTFARGS("%kTSEC key(s)... ", colors[(color_idx++) % 6]);
// Master key derivation
if (pkg1_id->kb == KB_FIRMWARE_VERSION_620 && _key_exists(tsec_keys + 0x10)) {
@@ -288,7 +298,7 @@ get_tsec: ;
}
free(keyblob_block);
- TPRINTFARGS("%kMaster keys... ", colors[1]);
+ TPRINTFARGS("%kMaster keys... ", colors[(color_idx++) % 6]);
/* key = unwrap(source, wrapped_key):
key_set(ks, wrapped_key), block_ecb(ks, 0, key, source) -> final key in key
@@ -378,7 +388,7 @@ get_tsec: ;
goto pkg2_done;
}
- TPRINTFARGS("%kDecrypt pkg2... ", colors[2]);
+ TPRINTFARGS("%kDecrypt pkg2... ", colors[(color_idx++) % 6]);
LIST_INIT(kip1_info);
bool new_pkg2;
@@ -399,7 +409,7 @@ get_tsec: ;
}
pkg2_decompress_kip(ki, 2 | 4); // we only need .rodata and .data
- TPRINTFARGS("%kDecompress FS...", colors[3]);
+ TPRINTFARGS("%kDecompress FS...", colors[(color_idx++) % 6]);
u8 hash_index = 0, hash_max = 9, hash_order[10],
key_lengths[10] = {0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x20};
@@ -497,7 +507,7 @@ pkg2_done:
free(pkg2);
free(ki);
- TPRINTFARGS("%kFS keys... ", colors[4]);
+ TPRINTFARGS("%kFS keys... ", colors[(color_idx++) % 6]);
if (_key_exists(fs_keys[0]) && _key_exists(fs_keys[1]) && _key_exists(master_key[0])) {
_generate_kek(8, fs_keys[0], master_key[0], aes_kek_generation_source, aes_key_generation_source);
@@ -699,6 +709,12 @@ pkg2_done:
f_closedir(&dir);
free(dec_header);
+ if (memcmp(pkg1_id->id, "2016", 4)) {
+ TPRINTFARGS("%kES & SSL keys...", colors[(color_idx++) % 6]);
+ } else {
+ TPRINTFARGS("%kSSL keys... ", colors[(color_idx++) % 6]);
+ }
+
if (f_open(&fp, "sd:/Nintendo/Contents/private", FA_READ | FA_OPEN_EXISTING)) {
EPRINTF("Unable to locate SD seed. Skipping.");
goto dismount;
@@ -728,17 +744,13 @@ pkg2_done:
}
f_close(&fp);
+ TPRINTFARGS("%kSD Seed... ", colors[(color_idx++) % 6]);
+
dismount:
f_mount(NULL, "emmc:", 1);
nx_emmc_gpt_free(&gpt);
sdmmc_storage_end(&storage);
- if (memcmp(pkg1_id->id, "2016", 4)) {
- TPRINTFARGS("%kES & SSL keys...", colors[5]);
- } else {
- TPRINTFARGS("%kSSL keys... ", colors[5]);
- }
-
// derive eticket_rsa_kek and ssl_rsa_kek
if (_key_exists(es_keys[0]) && _key_exists(es_keys[1]) && _key_exists(master_key[0])) {
for (u32 i = 0; i < 0x10; i++)
@@ -816,7 +828,8 @@ key_output: ;
//gfx_con.fntsz = 8; gfx_puts(text_buffer); gfx_con.fntsz = 16;
- TPRINTFARGS("\n%kFound %d keys.\n%kLockpick totally", colors[0], _key_count, colors[1]);
+ TPRINTFARGS("\n%kFound %d keys.\n%kLockpick totally", colors[(color_idx) % 6], _key_count, colors[(color_idx + 1) % 6]);
+ color_idx += 2;
f_mkdir("switch");
char keyfile_path[30] = "sd:/switch/";
@@ -825,13 +838,13 @@ key_output: ;
else
sprintf(&keyfile_path[11], "dev.keys");
if (!sd_save_to_file(text_buffer, strlen(text_buffer), keyfile_path) && !f_stat(keyfile_path, &fno)) {
- gfx_printf("%kWrote %d bytes to %s\n", colors[2], (u32)fno.fsize, keyfile_path);
+ gfx_printf("%kWrote %d bytes to %s\n", colors[(color_idx++) % 6], (u32)fno.fsize, keyfile_path);
} else
EPRINTF("Failed to save keys to SD.");
sd_unmount();
out_wait:
- gfx_printf("\n%kVOL + -> Reboot to RCM\n%kVOL - -> Reboot normally\n%kPower -> Power off", colors[3], colors[4], colors[5]);
+ gfx_printf("\n%kVOL + -> Reboot to RCM\n%kVOL - -> Reboot normally\n%kPower -> Power off", colors[(color_idx) % 6], colors[(color_idx + 1) % 6], colors[(color_idx + 2) % 6]);
u32 btn = btn_wait();
if (btn & BTN_VOL_UP)
diff --git a/source/main.c b/source/main.c
index 587d930..fb03dc7 100644
--- a/source/main.c
+++ b/source/main.c
@@ -18,6 +18,7 @@
#include
+#include "config/config.h"
#include "gfx/di.h"
#include "gfx/gfx.h"
#include "libs/fatfs/ff.h"
@@ -36,6 +37,7 @@ sdmmc_storage_t sd_storage;
__attribute__ ((aligned (16))) FATFS sd_fs;
static bool sd_mounted;
+hekate_config h_cfg;
boot_cfg_t __attribute__((section ("._boot_cfg"))) b_cfg;
bool sd_mount()
@@ -154,6 +156,8 @@ void ipl_main()
pivot_stack(IPL_STACK_TOP);
heap_init(IPL_HEAP_START);
+ set_default_configuration();
+
display_init();
u32 *fb = display_init_framebuffer();
gfx_init_ctxt(fb, 720, 1280, 720);
diff --git a/source/utils/dirlist.c b/source/utils/dirlist.c
new file mode 100644
index 0000000..8e6f3cc
--- /dev/null
+++ b/source/utils/dirlist.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2018 CTCaer
+ *
+ * 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 "../libs/fatfs/ff.h"
+#include "../mem/heap.h"
+#include "../utils/types.h"
+
+char *dirlist(const char *directory, const char *pattern, bool includeHiddenFiles)
+{
+ u8 max_entries = 61;
+
+ int res = 0;
+ u32 i = 0, j = 0, k = 0;
+ DIR dir;
+ FILINFO fno;
+
+ char *dir_entries = (char *)calloc(max_entries, 256);
+ char *temp = (char *)calloc(1, 256);
+
+ if (!pattern && !f_opendir(&dir, directory))
+ {
+ for (;;)
+ {
+ res = f_readdir(&dir, &fno);
+ if (res || !fno.fname[0])
+ break;
+ if (!(fno.fattrib & AM_DIR) && (fno.fname[0] != '.') && (includeHiddenFiles || !(fno.fattrib & AM_HID)))
+ {
+ memcpy(dir_entries + (k * 256), fno.fname, strlen(fno.fname) + 1);
+ k++;
+ if (k > (max_entries - 1))
+ break;
+ }
+ }
+ f_closedir(&dir);
+ }
+ else if (pattern && !f_findfirst(&dir, &fno, directory, pattern) && fno.fname[0])
+ {
+ do
+ {
+ if (!(fno.fattrib & AM_DIR) && (fno.fname[0] != '.') && (includeHiddenFiles || !(fno.fattrib & AM_HID)))
+ {
+ memcpy(dir_entries + (k * 256), fno.fname, strlen(fno.fname) + 1);
+ k++;
+ if (k > (max_entries - 1))
+ break;
+ }
+ res = f_findnext(&dir, &fno);
+ } while (fno.fname[0] && !res);
+ f_closedir(&dir);
+ }
+
+ if (!k)
+ {
+ free(temp);
+ free(dir_entries);
+
+ return NULL;
+ }
+
+ // Reorder ini files by ASCII ordering.
+ for (i = 0; i < k - 1 ; i++)
+ {
+ for (j = i + 1; j < k; j++)
+ {
+ if (strcmp(&dir_entries[i * 256], &dir_entries[j * 256]) > 0)
+ {
+ memcpy(temp, &dir_entries[i * 256], strlen(&dir_entries[i * 256]) + 1);
+ memcpy(&dir_entries[i * 256], &dir_entries[j * 256], strlen(&dir_entries[j * 256]) + 1);
+ memcpy(&dir_entries[j * 256], temp, strlen(temp) + 1);
+ }
+ }
+ }
+
+ free(temp);
+
+ return dir_entries;
+}
diff --git a/source/utils/dirlist.h b/source/utils/dirlist.h
new file mode 100644
index 0000000..4fb9af7
--- /dev/null
+++ b/source/utils/dirlist.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018 CTCaer
+ *
+ * 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 "../utils/types.h"
+
+char *dirlist(const char *directory, const char *pattern, bool includeHiddenFiles);