From 6b8447a7ca5bafba9ee84deebcd494b13a6233c7 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 14 Oct 2022 05:39:58 -0400 Subject: [PATCH] rg_settings: Store configuration sections in separate files This will be less prone to corruption and user error. It will also easily allow per-rom config --- README.md | 14 +-- components/retro-go/rg_gui.c | 7 +- components/retro-go/rg_settings.c | 203 ++++++++++++++---------------- components/retro-go/rg_settings.h | 9 +- components/retro-go/rg_system.c | 80 ++++++------ components/retro-go/rg_system.h | 5 +- launcher/main/applications.c | 6 +- 7 files changed, 146 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index e411e2fc..49603fc4 100644 --- a/README.md +++ b/README.md @@ -77,24 +77,16 @@ Some emulators support loading a BIOS. The files should be placed as follows: ## Wifi -To use wifi you will need to add your config to `/retro-go/config/retro-go.json` file. +To use wifi you will need to add your config to `/retro-go/config/wifi.json` file. It should look like this: ````json { - "global": { - ... - }, - "wifi": { - "ssid": "my-network", - "password": "my-password" - } + "ssid": "my-network", + "password": "my-password" } ```` -If you are unfamiliar with JSON syntax you can run your config file [through a validator](https://jsonlint.com/) -to confirm that it is correct. - ### Time synchronization Time synchronization happens in the launcher immediately after a successful connection to the network. This is done via NTP by contacting `pool.ntp.org` and cannot be disabled at this time. diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 727665d0..55fec78f 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -1122,8 +1122,7 @@ int rg_gui_about_menu(const rg_gui_option_t *extra_options) switch (sel) { case 1000: - rg_system_set_boot_app(RG_APP_FACTORY); - rg_system_restart(); + rg_system_switch_app(RG_APP_FACTORY, RG_APP_FACTORY, 0, 0); break; case 2000: if (rg_gui_confirm("Reset all settings?", NULL, false)) { @@ -1302,7 +1301,7 @@ int rg_gui_game_menu(void) switch (sel) { case 1000: if ((slot = rg_gui_savestate_menu("Save", 0, 0)) >= 0) rg_emu_save_state(slot); break; - case 2000: if ((slot = rg_gui_savestate_menu("Save", 0, 0)) >= 0) {rg_emu_save_state(slot); exit(0);} break; + case 2000: if ((slot = rg_gui_savestate_menu("Save", 0, 0)) >= 0) {rg_emu_save_state(slot); rg_system_switch_app(RG_APP_LAUNCHER, 0, 0, 0);} break; case 3001: if ((slot = rg_gui_savestate_menu("Load", 0, 0)) >= 0) rg_emu_load_state(slot); break; case 3002: rg_emu_reset(false); break; case 3003: rg_emu_reset(true); break; @@ -1311,7 +1310,7 @@ int rg_gui_game_menu(void) #endif case 5500: rg_gui_options_menu(); break; case 6000: rg_gui_about_menu(NULL); break; - case 7000: exit(0); break; + case 7000: rg_system_switch_app(RG_APP_LAUNCHER, 0, 0, 0); break; } rg_audio_set_mute(false); diff --git a/components/retro-go/rg_settings.c b/components/retro-go/rg_settings.c index 4490a80a..cbe0d03f 100644 --- a/components/retro-go/rg_settings.c +++ b/components/retro-go/rg_settings.c @@ -6,140 +6,145 @@ #include #include -static const char *config_file_path = RG_BASE_PATH_CONFIG "/retro-go.json"; static cJSON *config_root = NULL; -static int unsaved_changes = 0; -static cJSON *json_root(const char *name) +static FILE *open_config_file(const char *name, const char *mode) +{ + char pathbuf[RG_PATH_MAX]; + snprintf(pathbuf, RG_PATH_MAX, "%s/%s.json", RG_BASE_PATH_CONFIG, name); + RG_LOGI("Opening %s for %s", pathbuf, mode); + FILE *fp = fopen(pathbuf, mode); + if (!fp) + { + rg_storage_mkdir(rg_dirname(pathbuf)); + rg_storage_delete(pathbuf); + fp = fopen(pathbuf, mode); + } + return fp; +} + +static cJSON *json_root(const char *name, bool mode) { RG_ASSERT(config_root, "json_root called before settings were initialized!"); if (name == NS_GLOBAL) name = "global"; - else if (name == NS_WIFI) - name = "wifi"; else if (name == NS_APP) name = rg_system_get_app()->configNs; else if (name == NS_FILE) - name = rg_system_get_app()->romPath; + name = rg_basename(rg_system_get_app()->romPath); + else if (name == NS_WIFI) + name = "wifi"; + else if (name == NS_BOOT) + name = "boot"; - cJSON *myroot; - - if (!name) + cJSON *branch = cJSON_GetObjectItem(config_root, name); + if (!branch) { - myroot = config_root; - } - else if (!(myroot = cJSON_GetObjectItem(config_root, name))) - { - myroot = cJSON_AddObjectToObject(config_root, name); - } - else if (!cJSON_IsObject(myroot)) - { - myroot = cJSON_CreateObject(); - cJSON_ReplaceItemInObject(config_root, name, myroot); + branch = cJSON_AddObjectToObject(config_root, name); + cJSON_AddStringToObject(branch, "namespace", name); + cJSON_AddNumberToObject(branch, "changed", 0); + FILE *fp = open_config_file(name, "rb"); + if (fp) + { + fseek(fp, 0, SEEK_END); + long length = ftell(fp); + fseek(fp, 0, SEEK_SET); + char *buffer = calloc(1, length + 1); + if (fread(buffer, 1, length, fp)) + cJSON_AddItemToObject(branch, "values", cJSON_Parse(buffer)); + free(buffer); + fclose(fp); + } } - return myroot; + cJSON *root = cJSON_GetObjectItem(branch, "values"); + if (!cJSON_IsObject(root)) + { + cJSON_DeleteItemFromObject(branch, "values"); + root = cJSON_AddObjectToObject(branch, "values"); + } + + if (mode) + { + cJSON_SetNumberHelper(cJSON_GetObjectItem(branch, "changed"), 1); + } + + return root; +} + +static void update_value(const char *section, const char *key, cJSON *new_value) +{ + cJSON *obj = cJSON_GetObjectItem(json_root(section, 0), key); + if (obj == NULL) + cJSON_AddItemToObject(json_root(section, 1), key, new_value); + else if (!cJSON_Compare(obj, new_value, true)) + cJSON_ReplaceItemInObject(json_root(section, 1), key, new_value); + else + cJSON_Delete(new_value); } void rg_settings_init(void) { - FILE *fp = fopen(config_file_path, "rb"); - if (fp) - { - fseek(fp, 0, SEEK_END); - long length = ftell(fp); - fseek(fp, 0, SEEK_SET); - char *buffer = calloc(1, length + 1); - if (fread(buffer, 1, length, fp)) - config_root = cJSON_Parse(buffer); - free(buffer); - fclose(fp); - } - - if (!config_root) - { - RG_LOGW("Failed to load settings from %s.\n", config_file_path); - config_root = cJSON_CreateObject(); - return; - } - - RG_LOGI("Settings loaded from %s.\n", config_file_path); + config_root = cJSON_CreateObject(); + json_root(NS_GLOBAL, 0); + json_root(NS_BOOT, 0); } void rg_settings_commit(void) { - if (!unsaved_changes) + if (!config_root) return; - RG_LOGI("Saving %d change(s)...\n", unsaved_changes); - - char *buffer = cJSON_Print(config_root); - if (!buffer) + for (cJSON *branch = config_root->child; branch; branch = branch->next) { - RG_LOGE("cJSON_Print() failed.\n"); - return; + char *name = cJSON_GetStringValue(cJSON_GetObjectItem(branch, "namespace")); + int changed = cJSON_GetNumberValue(cJSON_GetObjectItem(branch, "changed")); + + if (!changed) + continue; + + char *buffer = cJSON_Print(cJSON_GetObjectItem(branch, "values")); + if (!buffer) + continue; + + FILE *fp = open_config_file(name, "wb"); + if (fp) + { + if (fputs(buffer, fp) >= 0) + cJSON_SetNumberHelper(cJSON_GetObjectItem(branch, "changed"), 0); + fclose(fp); + } + + cJSON_free(buffer); } - FILE *fp = fopen(config_file_path, "wb"); - if (!fp) - { - rg_storage_delete(config_file_path); - rg_storage_mkdir(rg_dirname(config_file_path)); - fp = fopen(config_file_path, "wb"); - } - if (fp) - { - if (fputs(buffer, fp) >= 0) - unsaved_changes = 0; - fclose(fp); - } - - if (unsaved_changes > 0) - RG_LOGE("Save failed! %p\n", fp); - else - rg_storage_commit(); - - cJSON_free(buffer); + rg_storage_commit(); } void rg_settings_reset(void) { RG_LOGI("Clearing settings...\n"); + rg_storage_delete(RG_BASE_PATH_CONFIG); cJSON_Delete(config_root); config_root = cJSON_CreateObject(); - unsaved_changes++; - rg_settings_commit(); } double rg_settings_get_number(const char *section, const char *key, double default_value) { - cJSON *obj = cJSON_GetObjectItem(json_root(section), key); + cJSON *obj = cJSON_GetObjectItem(json_root(section, 0), key); return obj ? obj->valuedouble : default_value; } void rg_settings_set_number(const char *section, const char *key, double value) { - cJSON *root = json_root(section); - cJSON *obj = cJSON_GetObjectItem(root, key); - - if (!cJSON_IsNumber(obj)) - { - cJSON_Delete(cJSON_DetachItemViaPointer(root, obj)); - cJSON_AddNumberToObject(root, key, value); - unsaved_changes++; - } - else if (obj->valuedouble != value) - { - cJSON_SetNumberHelper(obj, value); - unsaved_changes++; - } + update_value(section, key, cJSON_CreateNumber(value)); } char *rg_settings_get_string(const char *section, const char *key, const char *default_value) { - cJSON *obj = cJSON_GetObjectItem(json_root(section), key); + cJSON *obj = cJSON_GetObjectItem(json_root(section, 0), key); if (cJSON_IsString(obj)) return strdup(obj->valuestring); return default_value ? strdup(default_value) : NULL; @@ -147,32 +152,10 @@ char *rg_settings_get_string(const char *section, const char *key, const char *d void rg_settings_set_string(const char *section, const char *key, const char *value) { - cJSON *root = json_root(section); - cJSON *obj = cJSON_GetObjectItem(root, key); - cJSON *newobj = value ? cJSON_CreateString(value) : cJSON_CreateNull(); - - if (obj == NULL) - { - cJSON_AddItemToObject(root, key, newobj); - unsaved_changes++; - } - else if (!cJSON_Compare(obj, newobj, true)) - { - cJSON_ReplaceItemInObject(root, key, newobj); - unsaved_changes++; - } - else - { - cJSON_Delete(newobj); - } + update_value(section, key, value ? cJSON_CreateString(value) : cJSON_CreateNull()); } void rg_settings_delete(const char *section, const char *key) { - cJSON *root = json_root(section); - if (key) - cJSON_DeleteItemFromObject(root, key); - else if (root != config_root) - cJSON_Delete(cJSON_DetachItemViaPointer(config_root, root)); - unsaved_changes++; + cJSON_DeleteItemFromObject(json_root(section, 1), key); } diff --git a/components/retro-go/rg_settings.h b/components/retro-go/rg_settings.h index bc745341..32ad1608 100644 --- a/components/retro-go/rg_settings.h +++ b/components/retro-go/rg_settings.h @@ -4,10 +4,11 @@ #include #include -#define NS_GLOBAL ((char *)-1) -#define NS_APP ((char *)-2) -#define NS_FILE ((char *)-3) -#define NS_WIFI ((char *)-4) +#define NS_GLOBAL ((char *)0) +#define NS_APP ((char *)1) +#define NS_FILE ((char *)2) +#define NS_WIFI ((char *)3) +#define NS_BOOT ((char *)4) void rg_settings_init(void); void rg_settings_commit(void); diff --git a/components/retro-go/rg_system.c b/components/retro-go/rg_system.c index c56c6c43..289d3a61 100644 --- a/components/retro-go/rg_system.c +++ b/components/retro-go/rg_system.c @@ -107,7 +107,7 @@ void rg_system_load_time(void) } else #endif - if ((fp = fopen(RG_BASE_PATH_CONFIG "/clock.bin", "rb"))) + if ((fp = fopen(RG_BASE_PATH_CACHE "/clock.bin", "rb"))) { fread(&time_sec, sizeof(time_sec), 1, fp); fclose(fp); @@ -125,7 +125,7 @@ void rg_system_save_time(void) time_t time_sec = time(NULL); FILE *fp; // We always save to storage in case the RTC disappears. - if ((fp = fopen(RG_BASE_PATH_CONFIG "/clock.bin", "wb"))) + if ((fp = fopen(RG_BASE_PATH_CACHE "/clock.bin", "wb"))) { fwrite(&time_sec, sizeof(time_sec), 1, fp); fclose(fp); @@ -150,11 +150,7 @@ static void exit_handler(void) { RG_LOGI("Exit handler called.\n"); if (!exitCalled) - { - exitCalled = true; - rg_system_set_boot_app(RG_APP_LAUNCHER); - rg_system_restart(); - } + rg_system_switch_app(RG_APP_LAUNCHER, 0, 0, 0); } static inline void begin_panic_trace(const char *context, const char *message) @@ -309,12 +305,10 @@ static void enter_recovery_mode(void) rg_settings_reset(); break; case 1: - rg_system_set_boot_app(RG_APP_FACTORY); - rg_system_restart(); + rg_system_switch_app(RG_APP_FACTORY, RG_APP_FACTORY, 0, 0); case 2: default: - rg_system_set_boot_app(RG_APP_LAUNCHER); - rg_system_restart(); + rg_system_switch_app(RG_APP_FACTORY, RG_APP_LAUNCHER, 0, 0); } } } @@ -381,7 +375,7 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg printf(" built for: %s. aud=%d disp=%d pad=%d sd=%d cfg=%d\n", RG_TARGET_NAME, 0, 0, 0, 0, 0); printf("========================================================\n\n"); - #ifndef RG_TARGET_SDL2 +#ifndef RG_TARGET_SDL2 esp_reset_reason_t r_reason = esp_reset_reason(); esp_chip_info_t chip_info; esp_chip_info(&chip_info); @@ -391,7 +385,7 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg app.bootType = RG_RST_PANIC; else if (r_reason == ESP_RST_SW) app.bootType = RG_RST_RESTART; - #endif +#endif update_memory_statistics(); RG_LOGI("Internal memory: free=%d, total=%d\n", statistics.freeMemoryInt, statistics.totalMemoryInt); @@ -408,9 +402,9 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg } else { - app.configNs = rg_settings_get_string(NS_GLOBAL, SETTING_BOOT_NAME, app.name); - app.bootArgs = rg_settings_get_string(NS_GLOBAL, SETTING_BOOT_ARGS, ""); - app.bootFlags = rg_settings_get_number(NS_GLOBAL, SETTING_BOOT_FLAGS, 0); + app.configNs = rg_settings_get_string(NS_BOOT, SETTING_BOOT_NAME, app.name); + app.bootArgs = rg_settings_get_string(NS_BOOT, SETTING_BOOT_ARGS, ""); + app.bootFlags = rg_settings_get_number(NS_BOOT, SETTING_BOOT_FLAGS, 0); app.saveSlot = (app.bootFlags & RG_BOOT_SLOT_MASK) >> 4; app.romPath = app.bootArgs; } @@ -449,13 +443,15 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg RG_LOGW("Aborting: panic!\n"); rg_display_clear(C_BLUE); rg_gui_alert("System Panic!", message); - rg_system_set_boot_app(RG_APP_LAUNCHER); - rg_system_restart(); + rg_system_switch_app(RG_APP_LAUNCHER, 0, 0, 0); } panicTrace.magicWord = 0; +#ifndef RG_TARGET_SDL2 if (app.bootFlags & RG_BOOT_ONCE) - rg_system_set_boot_app(RG_APP_LAUNCHER); + esp_ota_set_boot_partition(esp_partition_find_first( + ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, RG_APP_LAUNCHER)); +#endif rg_system_set_timezone(rg_settings_get_string(NS_GLOBAL, SETTING_TIMEZONE, "EST+5")); rg_system_load_time(); @@ -646,7 +642,7 @@ static void emu_update_save_slot(uint8_t slot) app.bootFlags &= ~RG_BOOT_SLOT_MASK; app.bootFlags |= app.saveSlot << 4; app.bootFlags |= RG_BOOT_RESUME; - rg_settings_set_number(NS_GLOBAL, SETTING_BOOT_FLAGS, app.bootFlags); + rg_settings_set_number(NS_BOOT, SETTING_BOOT_FLAGS, app.bootFlags); } rg_storage_commit(); @@ -848,6 +844,7 @@ void rg_system_shutdown(void) void rg_system_sleep(void) { RG_LOGI("Going to sleep!\n"); + exitCalled = true; shutdown_cleanup(); rg_task_delay(1000); esp_deep_sleep_start(); @@ -855,43 +852,40 @@ void rg_system_sleep(void) void rg_system_restart(void) { + RG_LOGI("Restarting system.\n"); exitCalled = true; shutdown_cleanup(); esp_restart(); } -void rg_system_start_app(const char *app, const char *name, const char *args, uint32_t flags) +void rg_system_switch_app(const char *partition, const char *name, const char *args, uint32_t flags) { - rg_settings_set_string(NS_GLOBAL, SETTING_BOOT_NAME, name); - rg_settings_set_string(NS_GLOBAL, SETTING_BOOT_ARGS, args); - rg_settings_set_number(NS_GLOBAL, SETTING_BOOT_FLAGS, flags); - rg_system_save_time(); - rg_settings_commit(); - rg_system_set_boot_app(app); - rg_system_restart(); -} + RG_LOGI("Switching to app %s (%s)!\n", partition, name ?: "-"); + exitCalled = true; -bool rg_system_find_app(const char *app) -{ - return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, app) != NULL; -} + if (app.initialized) + { + rg_settings_set_string(NS_BOOT, SETTING_BOOT_NAME, name); + rg_settings_set_string(NS_BOOT, SETTING_BOOT_ARGS, args); + rg_settings_set_number(NS_BOOT, SETTING_BOOT_FLAGS, flags); + rg_system_save_time(); + rg_settings_commit(); + } -void rg_system_set_boot_app(const char *app) -{ - const esp_partition_t* partition = esp_partition_find_first( - ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, app); - - if (partition == NULL) - RG_PANIC("Unable to set boot app: App not found!"); - - esp_err_t err = esp_ota_set_boot_partition(partition); + esp_err_t err = esp_ota_set_boot_partition(esp_partition_find_first( + ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, partition)); if (err != ESP_OK) { RG_LOGE("esp_ota_set_boot_partition returned 0x%02X!\n", err); RG_PANIC("Unable to set boot app!"); } - RG_LOGI("Boot partition set to %d '%s'\n", partition->subtype, partition->label); + rg_system_restart(); +} + +bool rg_system_have_app(const char *app) +{ + return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, app) != NULL; } void rg_system_panic(const char *context, const char *message) diff --git a/components/retro-go/rg_system.h b/components/retro-go/rg_system.h index 6681b3db..76912e00 100644 --- a/components/retro-go/rg_system.h +++ b/components/retro-go/rg_system.h @@ -191,9 +191,8 @@ void rg_system_panic(const char *context, const char *message) __attribute__((no void rg_system_shutdown(void) __attribute__((noreturn)); void rg_system_sleep(void) __attribute__((noreturn)); void rg_system_restart(void) __attribute__((noreturn)); -void rg_system_start_app(const char *app, const char *name, const char *args, uint32_t flags) __attribute__((noreturn)); -void rg_system_set_boot_app(const char *app); -bool rg_system_find_app(const char *app); +void rg_system_switch_app(const char *part, const char *name, const char *args, uint32_t flags) __attribute__((noreturn)); +bool rg_system_have_app(const char *app); void rg_system_set_led(int value); int rg_system_get_led(void); void rg_system_tick(int busyTime); diff --git a/launcher/main/applications.c b/launcher/main/applications.c index e3bf1a39..88dcfcf0 100644 --- a/launcher/main/applications.c +++ b/launcher/main/applications.c @@ -135,7 +135,7 @@ static void application_start(retro_file_t *file, int load_state) flags |= (load_state << 4) & RG_BOOT_SLOT_MASK; } bookmark_add(BOOK_TYPE_RECENT, file); // This could relocate *file, but we no longer need it - rg_system_start_app(part, name, path, flags); + rg_system_switch_app(part, name, path, flags); } static void crc_cache_init(void) @@ -648,7 +648,7 @@ void application_show_file_menu(retro_file_t *file, bool advanced) static void application(const char *desc, const char *name, const char *exts, const char *part, uint16_t crc_offset) { - if (!rg_system_find_app(part)) + if (!rg_system_have_app(part)) { RG_LOGI("Application '%s' (%s) not present, skipping\n", desc, part); return; @@ -667,7 +667,7 @@ static void application(const char *desc, const char *name, const char *exts, co snprintf(app->paths.covers, RG_PATH_MAX, RG_BASE_PATH_COVERS "/%s", app->short_name); snprintf(app->paths.saves, RG_PATH_MAX, RG_BASE_PATH_SAVES "/%s", app->short_name); snprintf(app->paths.roms, RG_PATH_MAX, RG_BASE_PATH_ROMS "/%s", app->short_name); - app->available = rg_system_find_app(app->partition); + app->available = rg_system_have_app(app->partition); app->files = calloc(10, sizeof(retro_file_t)); app->crc_offset = crc_offset;