diff --git a/epicardium/main.c b/epicardium/main.c index 56639ea56ead01fc06138e0f2f712518193c475f..b3b94732576d696a1cfbf11f6207ca81c6cc73c0 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -1,6 +1,7 @@ #include "modules/modules.h" #include "modules/log.h" #include "modules/filesystem.h" +#include "modules/config.h" #include "card10-version.h" #include "FreeRTOS.h" @@ -20,6 +21,8 @@ int main(void) LOG_DEBUG("startup", "Initializing hardware ..."); hardware_early_init(); + load_config(); + /* * Version Splash */ diff --git a/epicardium/meson.build b/epicardium/meson.build index cf67023b9f7143336875d973108d8ee0a080ffd1..a6a6c05261c16ced283c4d8591b335237f6acd37 100644 --- a/epicardium/meson.build +++ b/epicardium/meson.build @@ -70,7 +70,7 @@ subdir('ble/') subdir('l0der/') -epicardium_cargs = [] +epicardium_cargs = ['-D_POSIX_C_SOURCE=200809'] if get_option('jailbreak_card10') epicardium_cargs += [ '-DJAILBREAK_CARD10=1', diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c new file mode 100644 index 0000000000000000000000000000000000000000..47396e2195b8b262f41279dbdad4e588430efdef --- /dev/null +++ b/epicardium/modules/config.c @@ -0,0 +1,347 @@ +#include "modules/log.h" +#include "modules/config.h" +#include "modules/filesystem.h" + +#include <assert.h> +#include <stdbool.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#define CONFIG_MAX_LINE_LENGTH 80 + +enum OptionType { + OptionType_Boolean, + OptionType_Int, + OptionType_Float, + OptionType_String, +}; + +struct config_option { + const char *name; + enum OptionType type; + union { + bool boolean; + long integer; + double floating_point; + char *string; + } value; +}; + +static struct config_option s_options[_EpicOptionCount] = { +/* clang-format off */ + #define INIT_Boolean(v) { .boolean = (v) } + #define INIT_Int(v) { .integer = (v) } + #define INIT_Float(v) { .floating_point = (v) } + #define INIT_String(v) { .string = (v) } + #define INIT_(tp, v) INIT_ ## tp (v) + #define INIT(tp, v) INIT_ (tp, v) + + #define CARD10_SETTING(identifier, spelling, tp, default_value) \ + [Option ## identifier] = { .name = (spelling), \ + .type = OptionType_ ## tp, \ + .value = INIT(tp, (default_value)) }, + + #include "modules/config.def" + /* clang-format on */ +}; + +static struct config_option *findOption(const char *key) +{ + for (int i = 0; i < _EpicOptionCount; ++i) { + if (!strcmp(key, s_options[i].name)) { + return &s_options[i]; + } + } + return NULL; +} + +static bool set_bool(struct config_option *opt, const char *value) +{ + bool val; + if (!strcmp(value, "1")) { + val = true; + } else if (!strcmp(value, "true")) { + val = true; + } else if (!strcmp(value, "0")) { + val = false; + } else if (!strcmp(value, "false")) { + val = false; + } else { + return false; + } + opt->value.boolean = val; + LOG_DEBUG( + "card10.cfg", + "setting '%s' to %s", + opt->name, + val ? "true" : "false" + ); + return true; +} + +static bool set_int(struct config_option *opt, const char *value) +{ + char *endptr; + size_t len = strlen(value); + int v = strtol(value, &endptr, 0); + if (endptr != (value + len)) { + return false; + } + opt->value.integer = v; + LOG_DEBUG("card10.cfg", "setting '%s' to %d (0x%08x)", opt->name, v, v); + return true; +} + +static bool set_float(struct config_option *opt, const char *value) +{ + char *endptr; + size_t len = strlen(value); + double v = strtod(value, &endptr); + if (endptr != (value + len)) { + return false; + } + opt->value.floating_point = v; + LOG_DEBUG("card10.cfg", "setting '%s' to %f", opt->name, v); + return true; +} + +const char *elide(const char *str) +{ + static char ret[21]; + size_t len = strlen(str); + if (len <= 20) { + return str; + } + strncpy(ret, str, 17); + ret[17] = '.'; + ret[18] = '.'; + ret[19] = '.'; + ret[20] = '\0'; + return ret; +} + +static bool set_string(struct config_option *opt, const char *value) +{ + //this leaks, but the lifetime of these ends when epicardium exits, so... + char *leaks = strdup(value); + opt->value.string = leaks; + LOG_DEBUG("card10.cfg", "setting '%s' to %s", opt->name, elide(leaks)); + return true; +} + +static void configure(const char *key, const char *value, int lineNumber) +{ + struct config_option *opt = findOption(key); + if (!opt) { + //invalid key + LOG_WARN( + "card10.cfg", + "line %d: ignoring unknown option '%s'", + lineNumber, + key + ); + return; + } + bool ok = false; + switch (opt->type) { + case OptionType_Boolean: + ok = set_bool(opt, value); + break; + case OptionType_Int: + ok = set_int(opt, value); + break; + case OptionType_Float: + ok = set_float(opt, value); + break; + case OptionType_String: + ok = set_string(opt, value); + break; + default: + assert(0 && "unreachable"); + } + if (!ok) { + LOG_WARN( + "card10.cfg", + "line %d: ignoring invalid value '%s' for option '%s'", + lineNumber, + value, + key + ); + } +} + +static void doline(char *line, char *eol, int lineNumber) +{ + //skip leading whitespace + while (*line && isspace(*line)) + ++line; + + char *key = line; + if (*key == '#') { + //skip comments + return; + } + + char *eq = strchr(line, '='); + if (!eq) { + if (*key) { + LOG_WARN( + "card10.cfg", + "line %d (%s): syntax error", + lineNumber, + elide(line) + ); + } + return; + } + + char *e_key = eq - 1; + //skip trailing whitespace in key + while (e_key > key && isspace(*e_key)) + --e_key; + e_key[1] = '\0'; + if (*key == '\0') { + LOG_WARN("card10.cfg", "line %d: empty key", lineNumber); + return; + } + + char *value = eq + 1; + //skip leading whitespace + while (*value && isspace(*value)) + ++value; + + char *e_val = eol - 1; + //skip trailing whitespace + while (e_val > value && isspace(*e_val)) + --e_val; + if (*value == '\0') { + LOG_WARN( + "card10.cfg", + "line %d: empty value for option '%s'", + lineNumber, + key + ); + return; + } + + configure(key, value, lineNumber); +} + +bool config_get_boolean(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Boolean); + return opt->value.boolean; +} + +long config_get_integer(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Int); + return opt->value.integer; +} + +double config_get_float(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Float); + return opt->value.floating_point; +} + +const char *config_get_string(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_String); + return opt->value.string; +} + +void load_config(void) +{ + LOG_DEBUG("card10.cfg", "loading..."); + int fd = epic_file_open("card10.cfg", "r"); + if (fd < 0) { + LOG_DEBUG( + "card10.cfg", + "loading failed: %s (%d)", + strerror(-fd), + fd + ); + return; + } + char buf[CONFIG_MAX_LINE_LENGTH]; + int lineNumber = 0; + int nread; + do { + //zero-terminate in case file is empty + buf[0] = '\0'; + nread = epic_file_read(fd, buf, sizeof(buf)); + if (nread < sizeof(buf)) { + //add fake EOL to ensure termination + buf[nread] = '\n'; + } + char *line = buf; + char *eol = NULL; + int last_eol = 0; + while (line) { + //line points one character past the las (if any) '\n' hence '- 1' + last_eol = line - buf - 1; + eol = strchr(line, '\n'); + ++lineNumber; + if (eol) { + *eol = '\0'; + doline(line, eol, lineNumber); + line = eol + 1; + } else { + if (line == buf) { + //line did not fit into buf + LOG_WARN( + "card10.cfg", + "line:%d: too long - aborting", + lineNumber + ); + return; + } else { + int seek_back = last_eol - nread; + LOG_DEBUG( + "card10.cfg", + "nread, last_eol, seek_back: %d,%d,%d", + nread, + last_eol, + seek_back + ); + assert(seek_back <= 0); + if (seek_back) { + int rc = epic_file_seek( + fd, + seek_back, + SEEK_CUR + ); + if (rc < 0) { + LOG_ERR("card10.cfg", + "seek failed, aborting"); + return; + } + char newline; + rc = epic_file_read( + fd, &newline, 1 + ); + if (rc < 0 || newline != '\n') { + LOG_ERR("card10.cfg", + "seek failed, aborting"); + LOG_DEBUG( + "card10.cfg", + "seek failed at read-back of newline: rc: %d read: %d", + rc, + (int)newline + ); + return; + } + } + break; + } + } + } + } while (nread == sizeof(buf)); +} diff --git a/epicardium/modules/config.def b/epicardium/modules/config.def new file mode 100644 index 0000000000000000000000000000000000000000..455aaedd8d61f821c1d08ee99bb45c3a61d983ba --- /dev/null +++ b/epicardium/modules/config.def @@ -0,0 +1,11 @@ +#ifndef CARD10_SETTING +# define CARD10_SETTING(identifier, spelling, type, default_value) +#endif + +CARD10_SETTING(ExecuteElf, "execute_elf", Boolean, false) +//CARD10_SETTING(Nick, "nick", String, "an0n") +//CARD10_SETTING(Timeout, "timeout", Integer, 123) +//CARD10_SETTING(Dampening, "dampening", Float, 420) + + +#undef CARD10_SETTING diff --git a/epicardium/modules/config.h b/epicardium/modules/config.h new file mode 100644 index 0000000000000000000000000000000000000000..b72b08a5205bf9344bc3e3d805423dc826f30ccc --- /dev/null +++ b/epicardium/modules/config.h @@ -0,0 +1,21 @@ +#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED +#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED + +#include <stdbool.h> + +enum EpicConfigOption { + #define CARD10_SETTING(identifier, spelling, type, default_value) Option ## identifier, + #include "modules/config.def" + _EpicOptionCount +}; + +//initialize configuration values and load card10.cfg +void load_config(void); + +bool config_get_boolean(enum EpicConfigOption option); +long config_get_integer(enum EpicConfigOption option); +double config_get_float(enum EpicConfigOption option); +const char* config_get_string(enum EpicConfigOption option); + + +#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c index 375cfef61dd9972fbb418f72659a1e30e69ef615..650664d2e97ee15ecbd35199e176ded925a78bfb 100644 --- a/epicardium/modules/lifecycle.c +++ b/epicardium/modules/lifecycle.c @@ -1,6 +1,7 @@ #include "epicardium.h" #include "modules/log.h" #include "modules/modules.h" +#include "modules/config.h" #include "api/dispatcher.h" #include "api/interrupt-sender.h" #include "l0der/l0der.h" @@ -49,6 +50,7 @@ static volatile struct load_info async_load = { /* Whether to write the menu script before attempting to load. */ static volatile bool write_menu = false; +static bool execute_elfs = false; /* Helpers {{{ */ @@ -88,9 +90,7 @@ static int load_stat(char *name) */ static int do_load(struct load_info *info) { -#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1) struct l0dable_info l0dable; -#endif int res; if (*info->name == '\0') { @@ -129,18 +129,22 @@ static int do_load(struct load_info *info) case PL_PYTHON_INTERP: core1_load(PYCARDIUM_IVT, info->name); break; -#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1) case PL_L0DABLE: - res = l0der_load_path(info->name, &l0dable); - if (res != 0) { - LOG_ERR("lifecycle", "l0der failed: %d\n", res); - xSemaphoreGive(api_mutex); - return -ENOEXEC; + if (execute_elfs) { + res = l0der_load_path(info->name, &l0dable); + if (res != 0) { + LOG_ERR("lifecycle", "l0der failed: %d\n", res); + xSemaphoreGive(api_mutex); + return -ENOEXEC; + } + core1_load(l0dable.isr_vector, ""); + } else { + LOG_WARN( + "lifecycle", + "Execution of .elf l0dables is disabled" + ); } - core1_load(l0dable.isr_vector, ""); - break; -#endif default: LOG_ERR("lifecyle", "Attempted to load invalid payload (%s)", @@ -379,6 +383,8 @@ void vLifecycleTask(void *pvParameters) hardware_init(); + execute_elfs = config_get_boolean(OptionExecuteElf); + /* When triggered, reset core 1 to menu */ while (1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 693ca9a8f94a432ff64d770f91d9e9005c88d42a..628249a305098911b6dfe3c861b8759d6d5a5a62 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -21,5 +21,6 @@ module_sources = files( 'trng.c', 'vibra.c', 'watchdog.c', - 'usb.c' + 'usb.c', + 'config.c', )