From 268aa704a536d57659a1059ec03c83cadae145d3 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 17:05:52 -0500 Subject: [PATCH 01/20] Fixed some formatting issues --- components/retro-go/rg_gui.c | 31 ++++++++++++++++++------------- components/retro-go/rg_gui.h | 9 +++++---- components/retro-go/rg_input.c | 10 +++++----- components/retro-go/rg_settings.c | 2 +- components/retro-go/rg_storage.c | 12 ++++++------ components/retro-go/rg_utils.c | 24 ++++++++++++++---------- components/retro-go/rg_utils.h | 23 +++++++++++++++++------ 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 0dea4fe58..8c2a30ac1 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -31,8 +31,8 @@ static struct bool initialized; } gui; -static const char *SETTING_FONTTYPE = "FontType"; -static const char *SETTING_THEME = "Theme"; +static const char *SETTING_FONTTYPE = "FontType"; +static const char *SETTING_THEME = "Theme"; void rg_gui_init(void) @@ -131,7 +131,7 @@ void rg_gui_copy_buffer(int left, int top, int width, int height, int stride, co for (int y = 0; y < height; ++y) { uint16_t *dst = gui.screen_buffer + (top + y) * gui.screen_width + left; - const uint16_t *src = (void*)buffer + y * stride; + const uint16_t *src = (void *)buffer + y * stride; for (int x = 0; x < width; ++x) if (src[x] != C_TRANSPARENT) dst[x] = src[x]; @@ -707,11 +707,13 @@ int rg_gui_dialog(const char *title, const rg_gui_option_t *options_const, int s for (size_t i = 0; i < options_count; i++) { rg_gui_option_t *option = &options[i]; - if (option->value && text_buffer) { + if (option->value && text_buffer) + { option->value = strcpy(text_buffer_ptr, option->value); text_buffer_ptr += strlen(text_buffer_ptr) + 24; } - if (option->label && text_buffer) { + if (option->label && text_buffer) + { option->label = strcpy(text_buffer_ptr, option->label); text_buffer_ptr += strlen(text_buffer_ptr) + 24; } @@ -822,9 +824,9 @@ bool rg_gui_confirm(const char *title, const char *message, bool default_yes) { const rg_gui_option_t options[] = { {0, (char *)message, NULL, -1, NULL}, - {0, "", NULL, -1, NULL}, - {1, "Yes", NULL, 1, NULL}, - {0, "No ", NULL, 1, NULL}, + {0, "", NULL, -1, NULL}, + {1, "Yes", NULL, 1, NULL}, + {0, "No ", NULL, 1, NULL}, RG_DIALOG_CHOICE_LAST }; return rg_gui_dialog(title, message ? options : options + 1, default_yes ? -2 : -1) == 1; @@ -834,8 +836,8 @@ void rg_gui_alert(const char *title, const char *message) { const rg_gui_option_t options[] = { {0, (char *)message, NULL, -1, NULL}, - {0, "", NULL, -1, NULL}, - {1, "OK", NULL, 1, NULL}, + {0, "", NULL, -1, NULL}, + {1, "OK", NULL, 1, NULL}, RG_DIALOG_CHOICE_LAST }; rg_gui_dialog(title, message ? options : options + 1, -1); @@ -1016,7 +1018,8 @@ static rg_gui_event_t speedup_update_cb(rg_gui_option_t *option, rg_gui_event_t static rg_gui_event_t disk_activity_cb(rg_gui_option_t *option, rg_gui_event_t event) { - if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) { + if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) + { rg_storage_set_activity_led(!rg_storage_get_activity_led()); } strcpy(option->value, rg_storage_get_activity_led() ? "On " : "Off"); @@ -1027,10 +1030,12 @@ static rg_gui_event_t font_type_cb(rg_gui_option_t *option, rg_gui_event_t event { if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) { - if (event == RG_DIALOG_PREV && !rg_gui_set_font_type(gui.style.font_type - 1)) { + if (event == RG_DIALOG_PREV && !rg_gui_set_font_type(gui.style.font_type - 1)) + { rg_gui_set_font_type(0); } - if (event == RG_DIALOG_NEXT && !rg_gui_set_font_type(gui.style.font_type + 1)) { + if (event == RG_DIALOG_NEXT && !rg_gui_set_font_type(gui.style.font_type + 1)) + { rg_gui_set_font_type(0); } } diff --git a/components/retro-go/rg_gui.h b/components/retro-go/rg_gui.h index fdd4938d9..469fbbff2 100644 --- a/components/retro-go/rg_gui.h +++ b/components/retro-go/rg_gui.h @@ -64,11 +64,12 @@ struct rg_gui_option_s rg_gui_callback_t update_cb; }; -#define RG_DIALOG_FLAG_DISABLED 0 // (1 << 0) -#define RG_DIALOG_FLAG_NORMAL 1 // (1 << 1) -#define RG_DIALOG_FLAG_SKIP -1 // (1 << 2) +#define RG_DIALOG_FLAG_DISABLED (0) // (1 << 0) +#define RG_DIALOG_FLAG_NORMAL (1) // (1 << 1) +#define RG_DIALOG_FLAG_SKIP (-1) // (1 << 2) -#define RG_DIALOG_CHOICE_LAST {0, NULL, NULL, 0, NULL} +#define RG_DIALOG_OPTION_LAST {0, NULL, NULL, 0, NULL} +#define RG_DIALOG_CHOICE_LAST RG_DIALOG_OPTION_LAST #define RG_DIALOG_SEPARATOR {0, "----------", NULL, RG_DIALOG_FLAG_SKIP, NULL} #define RG_DIALOG_CANCELLED -0x7654321 diff --git a/components/retro-go/rg_input.c b/components/retro-go/rg_input.c index e6cd8d6c0..8781cdbe2 100644 --- a/components/retro-go/rg_input.c +++ b/components/retro-go/rg_input.c @@ -236,7 +236,7 @@ void rg_input_init(void) if (input_task_running) return; -#if RG_GAMEPAD_DRIVER == 1 // GPIO +#if RG_GAMEPAD_DRIVER == 1 // GPIO const char *driver = "GPIO"; @@ -257,7 +257,7 @@ void rg_input_init(void) gpio_set_direction(RG_GPIO_GAMEPAD_B, GPIO_MODE_INPUT); gpio_set_pull_mode(RG_GPIO_GAMEPAD_B, GPIO_PULLUP_ONLY); -#elif RG_GAMEPAD_DRIVER == 2 // Serial +#elif RG_GAMEPAD_DRIVER == 2 // Serial const char *driver = "SERIAL"; @@ -268,14 +268,14 @@ void rg_input_init(void) gpio_set_level(RG_GPIO_GAMEPAD_LATCH, 0); gpio_set_level(RG_GPIO_GAMEPAD_CLOCK, 1); -#elif RG_GAMEPAD_DRIVER == 3 // I2C +#elif RG_GAMEPAD_DRIVER == 3 // I2C const char *driver = "I2C"; rg_i2c_init(); gamepad_read(); // First read contains garbage -#elif RG_GAMEPAD_DRIVER == 4 // I2C w/AW9523 +#elif RG_GAMEPAD_DRIVER == 4 // I2C w/AW9523 const char *driver = "AW9523-I2C"; @@ -297,7 +297,7 @@ void rg_input_init(void) #elif RG_GAMEPAD_DRIVER == 6 // SDL2 - const char *driver = "SDL2"; + const char *driver = "SDL2"; #else diff --git a/components/retro-go/rg_settings.c b/components/retro-go/rg_settings.c index 232e12293..df0940b2a 100644 --- a/components/retro-go/rg_settings.c +++ b/components/retro-go/rg_settings.c @@ -77,7 +77,7 @@ static cJSON *json_root(const char *name, bool mode) if (mode) { - cJSON_SetNumberHelper(cJSON_GetObjectItem(branch, "changed"), 1); + cJSON_SetNumberHelper(cJSON_GetObjectItem(branch, "changed"), 1); } return root; diff --git a/components/retro-go/rg_storage.c b/components/retro-go/rg_storage.c index 6197f60c7..689c87404 100644 --- a/components/retro-go/rg_storage.c +++ b/components/retro-go/rg_storage.c @@ -323,12 +323,12 @@ rg_scandir_t *rg_storage_scandir(const char *path, bool (*validator)(const char strncpy(result->name, basename, sizeof(result->name) - 1); result->is_valid = 1; - #if defined(DT_REG) && defined(DT_DIR) - result->is_file = ent->d_type == DT_REG; - result->is_dir = ent->d_type == DT_DIR; - #else - flags |= RG_SCANDIR_STAT; - #endif + #if defined(DT_REG) && defined(DT_DIR) + result->is_file = ent->d_type == DT_REG; + result->is_dir = ent->d_type == DT_DIR; + #else + flags |= RG_SCANDIR_STAT; + #endif if ((flags & RG_SCANDIR_STAT) && stat(fullpath, &statbuf) == 0) { diff --git a/components/retro-go/rg_utils.c b/components/retro-go/rg_utils.c index 08e7d9e6f..e042a6559 100644 --- a/components/retro-go/rg_utils.c +++ b/components/retro-go/rg_utils.c @@ -88,7 +88,7 @@ uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, uint32_t len) { #ifndef RG_TARGET_SDL2 // This is part of the ROM but finding the correct header is annoying as it differs per SOC... - extern uint32_t crc32_le(uint32_t crc, const uint8_t * buf, uint32_t len); + extern uint32_t crc32_le(uint32_t crc, const uint8_t *buf, uint32_t len); return crc32_le(crc, buf, len); #else // Derived from: http://www.hackersdelight.org/hdcodetxt/crc.c.txt @@ -96,13 +96,13 @@ uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, uint32_t len) for (size_t i = 0; i < len; ++i) { crc = crc ^ buf[i]; - for (int j = 7; j >= 0; j--) // Do eight times. + for (int j = 7; j >= 0; j--) // Do eight times. { uint32_t mask = -(crc & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } } - return ~crc; + return ~crc; #endif } @@ -121,7 +121,7 @@ const char *const_string(const char *str) str = strdup(str); - strings = realloc(strings, (strings_count + 1) * sizeof(char*)); + strings = realloc(strings, (strings_count + 1) * sizeof(char *)); RG_ASSERT(strings && str, "alloc failed"); strings[strings_count++] = str; @@ -138,10 +138,14 @@ void *rg_alloc(size_t size, uint32_t caps) size_t available = 0; void *ptr; - if (caps & MEM_SLOW) strcat(caps_list, "SPIRAM|"); - if (caps & MEM_FAST) strcat(caps_list, "INTERNAL|"); - if (caps & MEM_DMA) strcat(caps_list, "DMA|"); - if (caps & MEM_EXEC) strcat(caps_list, "IRAM|"); + if (caps & MEM_SLOW) + strcat(caps_list, "SPIRAM|"); + if (caps & MEM_FAST) + strcat(caps_list, "INTERNAL|"); + if (caps & MEM_DMA) + strcat(caps_list, "DMA|"); + if (caps & MEM_EXEC) + strcat(caps_list, "IRAM|"); strcat(caps_list, (caps & MEM_32BIT) ? "32BIT" : "8BIT"); #ifndef RG_TARGET_SDL2 @@ -157,8 +161,8 @@ void *rg_alloc(size_t size, uint32_t caps) // Loosen the caps and try again if ((ptr = heap_caps_calloc(1, size, esp_caps & ~(MALLOC_CAP_SPIRAM | MALLOC_CAP_INTERNAL)))) { - RG_LOGW("SIZE=%u, CAPS=%s, PTR=%p << CAPS not fully met! (available: %d)\n", - size, caps_list, ptr, available); + RG_LOGW("SIZE=%u, CAPS=%s, PTR=%p << CAPS not fully met! (available: %d)\n", size, caps_list, ptr, + available); return ptr; } } diff --git a/components/retro-go/rg_utils.h b/components/retro-go/rg_utils.h index 10e61bf6d..46a192b74 100644 --- a/components/retro-go/rg_utils.h +++ b/components/retro-go/rg_utils.h @@ -5,12 +5,23 @@ #include #define RG_TIMER_INIT() int64_t _rgts_ = rg_system_timer(), _rgtl_ = rg_system_timer(); -#define RG_TIMER_LAP(name) \ +#define RG_TIMER_LAP(name) \ RG_LOGX("Lap %s: %.2f Total: %.2f\n", #name, (rg_system_timer() - _rgtl_) / 1000.f, \ - (rg_system_timer() - _rgts_) / 1000.f); _rgtl_ = rg_system_timer(); + (rg_system_timer() - _rgts_) / 1000.f); \ + _rgtl_ = rg_system_timer(); -#define RG_MIN(a, b) ({__typeof__(a) _a = (a); __typeof__(b) _b = (b);_a < _b ? _a : _b; }) -#define RG_MAX(a, b) ({__typeof__(a) _a = (a); __typeof__(b) _b = (b);_a > _b ? _a : _b; }) +#define RG_MIN(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#define RG_MAX(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) #define RG_COUNT(array) (sizeof(array) / sizeof((array)[0])) char *rg_strtolower(char *str); @@ -21,7 +32,7 @@ const char *rg_basename(const char *path); const char *rg_extension(const char *path); const char *rg_relpath(const char *path); const char *const_string(const char *str); -uint32_t rg_crc32(uint32_t crc, const uint8_t* buf, uint32_t len); +uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, uint32_t len); void *rg_alloc(size_t size, uint32_t caps); #define MEM_ANY (0) @@ -32,4 +43,4 @@ void *rg_alloc(size_t size, uint32_t caps); #define MEM_32BIT (16) #define MEM_EXEC (32) -#define PTR_IN_SPIRAM(ptr) ((void*)(ptr) >= (void*)0x3F800000 && (void*)(ptr) < (void*)0x3FC00000) +#define PTR_IN_SPIRAM(ptr) ((void *)(ptr) >= (void *)0x3F800000 && (void *)(ptr) < (void *)0x3FC00000) From 899626f7c76715aa35b2c13b07f5a8047471ae2d Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 21:44:28 -0500 Subject: [PATCH 02/20] rg_gui: Fixed rg_gui_about_menu ignoring *extra_options --- components/retro-go/rg_gui.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 8c2a30ac1..0ef42aabf 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -1102,21 +1102,26 @@ void rg_gui_about_menu(const rg_gui_option_t *extra_options) { char build_ver[32], build_date[32], build_user[32], network_str[64]; - const rg_gui_option_t options[] = { - {0, "Version", build_ver, 1, NULL}, - {0, "Date", build_date, 1, NULL}, - {0, "By", build_user, 1, NULL}, - #ifdef RG_ENABLE_NETWORKING - {0, "Network", network_str, 1, NULL}, - #endif - RG_DIALOG_SEPARATOR, - {1000, "Reboot to firmware", NULL, 1, NULL}, - {2000, "Reset settings", NULL, 1, NULL}, - {3000, "Clear cache", NULL, 1, NULL}, - {4000, "Debug", NULL, 1, NULL}, - {0000, "Close", NULL, 1, NULL}, - RG_DIALOG_CHOICE_LAST - }; + size_t extra_options_count = get_dialog_items_count(extra_options); + + rg_gui_option_t options[16 + extra_options_count]; + rg_gui_option_t *opt = &options[0]; + + *opt++ = (rg_gui_option_t){0, "Version", build_ver, 1, NULL}; + *opt++ = (rg_gui_option_t){0, "Date", build_date, 1, NULL}; + *opt++ = (rg_gui_option_t){0, "By", build_user, 1, NULL}; +#ifdef RG_ENABLE_NETWORKING + *opt++ = (rg_gui_option_t){0, "Network", network_str, 1, NULL}; +#endif + *opt++ = (rg_gui_option_t)RG_DIALOG_SEPARATOR; + for (size_t i = 0; i < extra_options_count; i++) + *opt++ = extra_options[i]; + *opt++ = (rg_gui_option_t){1000, "Reboot to firmware", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t){2000, "Reset settings", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t){3000, "Clear cache", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t){4000, "Debug", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t){0000, "Close", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; const rg_app_t *app = rg_system_get_app(); From 3aeccaf11cb39702728b22818041392918188075 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 21:44:32 -0500 Subject: [PATCH 03/20] rg_network: Added experimental HTTP api --- base.sdkconfig | 6 +++ components/retro-go/CMakeLists.txt | 6 +-- components/retro-go/rg_network.c | 59 ++++++++++++++++++++++++++++++ components/retro-go/rg_network.h | 18 +++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/base.sdkconfig b/base.sdkconfig index 314c5a765..483afb399 100644 --- a/base.sdkconfig +++ b/base.sdkconfig @@ -156,3 +156,9 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32 # CONFIG_HTTPD_LOG_PURGE_DATA is not set # CONFIG_HTTPD_WS_SUPPORT is not set # end of HTTP Server + + +# TLS +# Not ideal but I don't want to deal with CAs right now :( +CONFIG_ESP_TLS_INSECURE=y +CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y diff --git a/components/retro-go/CMakeLists.txt b/components/retro-go/CMakeLists.txt index ae2d63c91..596a2ea14 100644 --- a/components/retro-go/CMakeLists.txt +++ b/components/retro-go/CMakeLists.txt @@ -1,9 +1,9 @@ set(COMPONENT_SRCDIRS ". fonts libs/netplay libs/lodepng") set(COMPONENT_ADD_INCLUDEDIRS ". libs/netplay libs/lodepng") -if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") - set(COMPONENT_REQUIRES "spi_flash fatfs nvs_flash app_update driver esp_timer esp_adc json") +if($ENV{RG_ENABLE_NETWORKING}) + set(COMPONENT_REQUIRES "spi_flash fatfs nvs_flash esp_http_client app_update esp_adc_cal esp32 json") else() - set(COMPONENT_REQUIRES "spi_flash fatfs nvs_flash app_update esp_adc_cal esp32 json") + set(COMPONENT_REQUIRES "spi_flash fatfs app_update esp_adc_cal esp32 json") endif() register_component() diff --git a/components/retro-go/rg_network.c b/components/retro-go/rg_network.c index d53ad4787..d3fdcdf0c 100644 --- a/components/retro-go/rg_network.c +++ b/components/retro-go/rg_network.c @@ -24,6 +24,7 @@ static const char *SETTING_WIFI_SLOT = "slot"; #ifdef RG_ENABLE_NETWORKING +#include #include #include #include @@ -293,3 +294,61 @@ bool rg_network_init(void) #endif return false; } + +rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) +{ +#ifdef RG_ENABLE_NETWORKING + esp_http_client_config_t http_config = {.url = url}; + esp_http_client_handle_t http_client = esp_http_client_init(&http_config); + + if (!http_client) + goto fail; + + if (esp_http_client_open(http_client, 0) != ESP_OK) + { + RG_LOGE("Error opening connection"); + goto fail; + } + + if (esp_http_client_fetch_headers(http_client) < 0) + { + RG_LOGE("Error fetching headers"); + goto fail; + } + + rg_http_req_t *req = calloc(1, sizeof(rg_http_req_t)); + req->status_code = esp_http_client_get_status_code(http_client); + req->total_bytes = esp_http_client_get_content_length(http_client); + req->read_bytes = 0; + req->client = (void *)http_client; + return req; + +fail: + esp_http_client_cleanup(http_client); +#endif + return NULL; +} + +int rg_network_http_read(rg_http_req_t *req, void *buffer, size_t buffer_len) +{ +#ifdef RG_ENABLE_NETWORKING + int len = esp_http_client_read_response(req->client, buffer, buffer_len); + if (len > 0) + req->read_bytes += len; + else + esp_http_client_close(req->client); + return len; +#endif + return -1; +} + +void rg_network_http_close(rg_http_req_t *req) +{ + if (!req) + return; +#ifdef RG_ENABLE_NETWORKING + esp_http_client_cleanup(req->client); + req->client = NULL; + free(req); +#endif +} diff --git a/components/retro-go/rg_network.h b/components/retro-go/rg_network.h index 594885c8c..7a018fd99 100644 --- a/components/retro-go/rg_network.h +++ b/components/retro-go/rg_network.h @@ -37,3 +37,21 @@ bool rg_network_wifi_start(void); void rg_network_wifi_stop(void); bool rg_network_sync_time(const char *host, int *out_delta); rg_network_t rg_network_get_info(void); + +typedef struct +{ + +} rg_http_cfg_t; + +typedef struct +{ + rg_http_cfg_t config; + int status_code; + int total_bytes; + int read_bytes; + void *client; +} rg_http_req_t; + +rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg); +int rg_network_http_read(rg_http_req_t *req, void *buffer, size_t buffer_len); +void rg_network_http_close(rg_http_req_t *req); From e75720dfa9d2c0191494c57e0c43d46253d827b7 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 21:45:32 -0500 Subject: [PATCH 04/20] Launcher: Testing a built-in updater Currently it only lists the available releases from github. I just want to be sure it works reliably before I work on the downloading/updating part. --- launcher/main/main.c | 21 +++++++++- launcher/main/updater.c | 89 +++++++++++++++++++++++++++++++++++++++++ launcher/main/updater.h | 1 + 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 launcher/main/updater.c create mode 100644 launcher/main/updater.h diff --git a/launcher/main/main.c b/launcher/main/main.c index c494e23b3..434e6d082 100644 --- a/launcher/main/main.c +++ b/launcher/main/main.c @@ -16,6 +16,7 @@ #include "gui.h" #include "webui.h" #include "timezones.h" +#include "updater.h" #define MAX_AP_LIST 5 @@ -220,10 +221,26 @@ static rg_gui_event_t startup_app_cb(rg_gui_option_t *option, rg_gui_event_t eve return RG_DIALOG_VOID; } +static rg_gui_event_t updater_cb(rg_gui_option_t *option, rg_gui_event_t event) +{ + if (event == RG_DIALOG_ENTER) + updater_show_dialog(); + return RG_DIALOG_VOID; +} + +static void show_about_menu(void) +{ + const rg_gui_option_t options[] = { + {0, "Check for updates", NULL, 1, &updater_cb}, + RG_DIALOG_OPTION_LAST, + }; + rg_gui_about_menu(options); +} + static rg_gui_event_t about_app_cb(rg_gui_option_t *option, rg_gui_event_t event) { if (event == RG_DIALOG_ENTER) - rg_gui_about_menu(NULL); + show_about_menu(); return RG_DIALOG_VOID; } @@ -324,7 +341,7 @@ static void retro_loop(void) { #if RG_GAMEPAD_HAS_OPTION_BTN if (joystick == RG_KEY_MENU) - rg_gui_about_menu(NULL); + show_about_menu(); else #endif rg_gui_options_menu(); diff --git a/launcher/main/updater.c b/launcher/main/updater.c new file mode 100644 index 000000000..9b0c074dc --- /dev/null +++ b/launcher/main/updater.c @@ -0,0 +1,89 @@ +#include "rg_system.h" +#include "gui.h" + +#include + +typedef struct +{ + char name[32]; + char url[96]; + size_t size; +} release_t; + +typedef struct +{ + size_t count; + release_t releases[]; +} releases_t; + +static releases_t *get_releases_from_github(void) +{ + size_t max_length = 256 * 1024; + char *buffer = malloc(max_length); + rg_http_req_t *req = NULL; + releases_t *res = NULL; + cJSON *releases = NULL; + + if (!(req = rg_network_http_open("https://api.github.com/repos/ducalex/retro-go/releases", NULL))) + goto cleanup; + + if (rg_network_http_read(req, buffer, max_length) < 16) + goto cleanup; + + if (!(releases = cJSON_Parse(buffer))) + goto cleanup; + + int num_releases = cJSON_GetArraySize(releases); + res = malloc(sizeof(releases_t) + (num_releases * sizeof(release_t))); + res->count = num_releases; + + for (int i = 0; i < num_releases; i++) + { + cJSON *el = cJSON_GetArrayItem(releases, i); + cJSON *name = cJSON_GetObjectItem(el, "name"); + cJSON *url = cJSON_GetObjectItem(el, "url"); + snprintf(res->releases[i].name, 32, "%s", cJSON_GetStringValue(name)); + snprintf(res->releases[i].url, 96, "%s", cJSON_GetStringValue(url)); + } + +cleanup: + rg_network_http_close(req); + cJSON_Delete(releases); + free(buffer); + return res; +} + +void updater_show_dialog(void) +{ + rg_gui_draw_hourglass(); + + releases_t *releases = get_releases_from_github(); + rg_gui_option_t *options = NULL; + + if (!releases || releases->count == 0) + { + rg_gui_alert("Error", "Received empty list"); + goto cleanup; + } + + options = malloc(sizeof(rg_gui_option_t) * (releases->count + 1)); + rg_gui_option_t *option = options; + + for (size_t i = 0; i < releases->count; i++) + { + *option++ = (rg_gui_option_t){i, releases->releases[i].name, NULL, 1, NULL}; + } + *option++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + + int sel = rg_gui_dialog("Download release:", options, 0); + + if (sel != RG_DIALOG_CANCELLED) + { + char *url = releases->releases[sel].url; + RG_LOGI("Downloading '%s' to '/odroid/firmware'...", url); + } + +cleanup: + free(releases); + free(options); +} diff --git a/launcher/main/updater.h b/launcher/main/updater.h new file mode 100644 index 000000000..979c4e8d9 --- /dev/null +++ b/launcher/main/updater.h @@ -0,0 +1 @@ +void updater_show_dialog(void); From 1a88194bce09bf0aa57c0323502ad6c2c4021f2a Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 21:49:02 -0500 Subject: [PATCH 05/20] rg_network: Added null check in http api --- components/retro-go/rg_network.c | 6 ++++-- launcher/main/updater.c | 37 ++++++++++++++------------------ 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/components/retro-go/rg_network.c b/components/retro-go/rg_network.c index d3fdcdf0c..72a6f81e6 100644 --- a/components/retro-go/rg_network.c +++ b/components/retro-go/rg_network.c @@ -297,6 +297,7 @@ bool rg_network_init(void) rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) { + RG_ASSERT(url, "bad param"); #ifdef RG_ENABLE_NETWORKING esp_http_client_config_t http_config = {.url = url}; esp_http_client_handle_t http_client = esp_http_client_init(&http_config); @@ -331,6 +332,7 @@ rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) int rg_network_http_read(rg_http_req_t *req, void *buffer, size_t buffer_len) { + RG_ASSERT(req && buffer, "bad param"); #ifdef RG_ENABLE_NETWORKING int len = esp_http_client_read_response(req->client, buffer, buffer_len); if (len > 0) @@ -344,9 +346,9 @@ int rg_network_http_read(rg_http_req_t *req, void *buffer, size_t buffer_len) void rg_network_http_close(rg_http_req_t *req) { - if (!req) - return; #ifdef RG_ENABLE_NETWORKING + if (req == NULL) + return; esp_http_client_cleanup(req->client); req->client = NULL; free(req); diff --git a/launcher/main/updater.c b/launcher/main/updater.c index 9b0c074dc..3ef5065e2 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -58,32 +58,27 @@ void updater_show_dialog(void) rg_gui_draw_hourglass(); releases_t *releases = get_releases_from_github(); - rg_gui_option_t *options = NULL; - if (!releases || releases->count == 0) + if (releases && releases->count > 0) { - rg_gui_alert("Error", "Received empty list"); - goto cleanup; - } - - options = malloc(sizeof(rg_gui_option_t) * (releases->count + 1)); - rg_gui_option_t *option = options; - - for (size_t i = 0; i < releases->count; i++) - { - *option++ = (rg_gui_option_t){i, releases->releases[i].name, NULL, 1, NULL}; + rg_gui_option_t options[releases->count + 1]; + rg_gui_option_t *opt = options; + + for (size_t i = 0; i < releases->count; i++) + *opt++ = (rg_gui_option_t){i, releases->releases[i].name, NULL, 1, NULL}; + *opt++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + + int sel = rg_gui_dialog("Download release:", options, 0); + if (sel != RG_DIALOG_CANCELLED) + { + char *url = releases->releases[sel].url; + RG_LOGI("Downloading '%s' to '/odroid/firmware'...", url); + } } - *option++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; - - int sel = rg_gui_dialog("Download release:", options, 0); - - if (sel != RG_DIALOG_CANCELLED) + else { - char *url = releases->releases[sel].url; - RG_LOGI("Downloading '%s' to '/odroid/firmware'...", url); + rg_gui_alert("Error", "Received empty list"); } -cleanup: free(releases); - free(options); } From 55bcbe84ca774a41fc230410423a179bda8c7580 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 22:02:50 -0500 Subject: [PATCH 06/20] Launcher: Show date in the update dialog --- launcher/main/updater.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/launcher/main/updater.c b/launcher/main/updater.c index 3ef5065e2..9463bed79 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -5,6 +5,7 @@ typedef struct { + char date[12]; char name[32]; char url[96]; size_t size; @@ -12,10 +13,11 @@ typedef struct typedef struct { - size_t count; + size_t length; release_t releases[]; } releases_t; + static releases_t *get_releases_from_github(void) { size_t max_length = 256 * 1024; @@ -33,17 +35,19 @@ static releases_t *get_releases_from_github(void) if (!(releases = cJSON_Parse(buffer))) goto cleanup; - int num_releases = cJSON_GetArraySize(releases); + // Limit to 10 because our dialog system isn't super reliable with large lists :( + size_t num_releases = RG_MIN(cJSON_GetArraySize(releases), 10); + RG_LOGI("num_releases = %d", num_releases); + res = malloc(sizeof(releases_t) + (num_releases * sizeof(release_t))); - res->count = num_releases; + res->length = num_releases; - for (int i = 0; i < num_releases; i++) + for (size_t i = 0; i < num_releases; i++) { cJSON *el = cJSON_GetArrayItem(releases, i); - cJSON *name = cJSON_GetObjectItem(el, "name"); - cJSON *url = cJSON_GetObjectItem(el, "url"); - snprintf(res->releases[i].name, 32, "%s", cJSON_GetStringValue(name)); - snprintf(res->releases[i].url, 96, "%s", cJSON_GetStringValue(url)); + snprintf(res->releases[i].date, 10, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "published_at"))); + snprintf(res->releases[i].name, 32, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "name"))); + snprintf(res->releases[i].url, 96, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "url"))); } cleanup: @@ -57,22 +61,24 @@ void updater_show_dialog(void) { rg_gui_draw_hourglass(); - releases_t *releases = get_releases_from_github(); + releases_t *res = get_releases_from_github(); - if (releases && releases->count > 0) + if (res && res->length > 0) { - rg_gui_option_t options[releases->count + 1]; + rg_gui_option_t options[res->length + 1]; rg_gui_option_t *opt = options; - for (size_t i = 0; i < releases->count; i++) - *opt++ = (rg_gui_option_t){i, releases->releases[i].name, NULL, 1, NULL}; + for (size_t i = 0; i < res->length; i++) + { + release_t *release = &res->releases[i]; + *opt++ = (rg_gui_option_t){i, release->name, release->date, 1, NULL}; + } *opt++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; int sel = rg_gui_dialog("Download release:", options, 0); if (sel != RG_DIALOG_CANCELLED) { - char *url = releases->releases[sel].url; - RG_LOGI("Downloading '%s' to '/odroid/firmware'...", url); + RG_LOGI("Downloading '%s' to '/odroid/firmware'...", res->releases[sel].url); } } else @@ -80,5 +86,5 @@ void updater_show_dialog(void) rg_gui_alert("Error", "Received empty list"); } - free(releases); + free(res); } From 3310d3059379ccb69a9dee6974bd5207a30b4d5a Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 14 Dec 2022 22:56:32 -0500 Subject: [PATCH 07/20] rg_network: Stop reading when content_length is reached. The underlaying esp api SHOULD stop, but there are edge cases. --- components/retro-go/rg_network.c | 14 ++++++--- components/retro-go/rg_network.h | 4 +-- launcher/main/updater.c | 53 ++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/components/retro-go/rg_network.c b/components/retro-go/rg_network.c index 72a6f81e6..a5d854b94 100644 --- a/components/retro-go/rg_network.c +++ b/components/retro-go/rg_network.c @@ -303,7 +303,10 @@ rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) esp_http_client_handle_t http_client = esp_http_client_init(&http_config); if (!http_client) + { + RG_LOGE("Error creating client"); goto fail; + } if (esp_http_client_open(http_client, 0) != ESP_OK) { @@ -319,8 +322,8 @@ rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) rg_http_req_t *req = calloc(1, sizeof(rg_http_req_t)); req->status_code = esp_http_client_get_status_code(http_client); - req->total_bytes = esp_http_client_get_content_length(http_client); - req->read_bytes = 0; + req->content_length = esp_http_client_get_content_length(http_client); + req->received_bytes = 0; req->client = (void *)http_client; return req; @@ -334,14 +337,17 @@ int rg_network_http_read(rg_http_req_t *req, void *buffer, size_t buffer_len) { RG_ASSERT(req && buffer, "bad param"); #ifdef RG_ENABLE_NETWORKING + // if (req->content_length >= 0 && req->received_bytes >= req->content_length) + // return 0; int len = esp_http_client_read_response(req->client, buffer, buffer_len); if (len > 0) - req->read_bytes += len; + req->received_bytes += len; else esp_http_client_close(req->client); return len; -#endif +#else return -1; +#endif } void rg_network_http_close(rg_http_req_t *req) diff --git a/components/retro-go/rg_network.h b/components/retro-go/rg_network.h index 7a018fd99..dd00b4555 100644 --- a/components/retro-go/rg_network.h +++ b/components/retro-go/rg_network.h @@ -47,8 +47,8 @@ typedef struct { rg_http_cfg_t config; int status_code; - int total_bytes; - int read_bytes; + int content_length; + int received_bytes; void *client; } rg_http_req_t; diff --git a/launcher/main/updater.c b/launcher/main/updater.c index 9463bed79..7d0737512 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -18,10 +18,52 @@ typedef struct } releases_t; +static int download_file(const char *url, const char *filename) +{ + rg_http_req_t *req = NULL; + FILE *fp = NULL; + void *buffer = NULL; + int received = 0; + int written = 0; + int len; + int ret = -1; + + if (!(req = rg_network_http_open(url, NULL))) + goto cleanup; + + if (!(fp = fopen(filename, "wb"))) + goto cleanup; + + if (!(buffer = malloc(16 * 1024))) + goto cleanup; + + while ((len = rg_network_http_read(req, buffer, 16 * 1024)) > 0) + { + written += fwrite(buffer, 1, len, fp); + received += len; + // we'll probably need to feed the watchdog here... + } + + if (received == written) + { + ret = 0; + } + else + { + // oh oh + } + +cleanup: + rg_network_http_close(req); + free(buffer); + fclose(fp); + + return ret; +} + static releases_t *get_releases_from_github(void) { - size_t max_length = 256 * 1024; - char *buffer = malloc(max_length); + char *buffer = NULL; rg_http_req_t *req = NULL; releases_t *res = NULL; cJSON *releases = NULL; @@ -29,7 +71,12 @@ static releases_t *get_releases_from_github(void) if (!(req = rg_network_http_open("https://api.github.com/repos/ducalex/retro-go/releases", NULL))) goto cleanup; - if (rg_network_http_read(req, buffer, max_length) < 16) + size_t buffer_length = RG_MAX(256 * 1024, req->content_length); + + if (!(buffer = malloc(buffer_length))) + goto cleanup; + + if (rg_network_http_read(req, buffer, buffer_length) < 16) goto cleanup; if (!(releases = cJSON_Parse(buffer))) From dcef2f6ffa40730bf3d3673c46de1743c4520ed8 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Thu, 15 Dec 2022 12:54:00 -0500 Subject: [PATCH 08/20] Launcher: consistency in main.c --- components/retro-go/rg_gui.h | 4 ++-- launcher/main/main.c | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/components/retro-go/rg_gui.h b/components/retro-go/rg_gui.h index 469fbbff2..787e4aa63 100644 --- a/components/retro-go/rg_gui.h +++ b/components/retro-go/rg_gui.h @@ -68,8 +68,8 @@ struct rg_gui_option_s #define RG_DIALOG_FLAG_NORMAL (1) // (1 << 1) #define RG_DIALOG_FLAG_SKIP (-1) // (1 << 2) -#define RG_DIALOG_OPTION_LAST {0, NULL, NULL, 0, NULL} -#define RG_DIALOG_CHOICE_LAST RG_DIALOG_OPTION_LAST +#define RG_DIALOG_CHOICE_LAST {0, NULL, NULL, 0, NULL} +#define RG_DIALOG_END RG_DIALOG_CHOICE_LAST #define RG_DIALOG_SEPARATOR {0, "----------", NULL, RG_DIALOG_FLAG_SKIP, NULL} #define RG_DIALOG_CANCELLED -0x7654321 diff --git a/launcher/main/main.c b/launcher/main/main.c index 434e6d082..427eebf7f 100644 --- a/launcher/main/main.c +++ b/launcher/main/main.c @@ -40,10 +40,11 @@ static rg_gui_event_t toggle_tabs_cb(rg_gui_option_t *option, rg_gui_event_t eve if (event == RG_DIALOG_ENTER) { rg_gui_option_t options[gui.tabs_count + 1]; + rg_gui_option_t *opt = options; for (size_t i = 0; i < gui.tabs_count; ++i) - options[i] = (rg_gui_option_t){i, gui.tabs[i]->name, "...", 1, &toggle_tab_cb}; - options[gui.tabs_count] = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + *opt++ = (rg_gui_option_t){i, gui.tabs[i]->name, "...", 1, &toggle_tab_cb}; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; rg_gui_dialog("Tabs Visibility", options, 0); gui_redraw(); @@ -53,13 +54,14 @@ static rg_gui_event_t toggle_tabs_cb(rg_gui_option_t *option, rg_gui_event_t eve static rg_gui_event_t timezone_cb(rg_gui_option_t *option, rg_gui_event_t event) { - rg_gui_option_t options[timezones_count + 1]; - if (event == RG_DIALOG_ENTER) { + rg_gui_option_t options[timezones_count + 1]; + rg_gui_option_t *opt = options; + for (size_t i = 0; i < timezones_count; i++) - options[i] = (rg_gui_option_t){i, timezones[i].name, NULL, 1, NULL}; - options[timezones_count] = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + *opt++ = (rg_gui_option_t){i, timezones[i].name, NULL, 1, NULL}; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; int sel = rg_gui_dialog("Timezone", options, 0); if (sel != RG_DIALOG_CANCELLED) @@ -123,18 +125,18 @@ static rg_gui_event_t wifi_select_cb(rg_gui_option_t *option, rg_gui_event_t eve if (event == RG_DIALOG_ENTER) { rg_gui_option_t options[MAX_AP_LIST + 2]; - size_t index = 0; + rg_gui_option_t *opt = options; for (size_t i = 0; i < MAX_AP_LIST; i++) { char slot[6]; sprintf(slot, "ssid%d", i); char *ap_name = rg_settings_get_string(NS_WIFI, slot, NULL); - options[index++] = (rg_gui_option_t){i, ap_name ?: "(empty)", NULL, ap_name ? 1 : 0, NULL}; + *opt++ = (rg_gui_option_t){i, ap_name ?: "(empty)", NULL, ap_name ? 1 : 0, NULL}; } char *ap_name = rg_settings_get_string(NS_WIFI, "ssid", NULL); - options[index++] = (rg_gui_option_t){-1, ap_name ?: "(empty)", NULL, ap_name ? 1 : 0, NULL}; - options[index++] = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + *opt++ = (rg_gui_option_t){-1, ap_name ?: "(empty)", NULL, ap_name ? 1 : 0, NULL}; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; int sel = rg_gui_dialog("Select saved AP", options, rg_settings_get_number(NS_WIFI, SETTING_WIFI_SLOT, 0)); if (sel != RG_DIALOG_CANCELLED) @@ -185,7 +187,7 @@ static rg_gui_event_t wifi_options_cb(rg_gui_option_t *option, rg_gui_event_t ev RG_DIALOG_SEPARATOR, {0, "File server" , "...", 1, &webui_switch_cb}, {0, "Time sync" , "On", 0, NULL}, - RG_DIALOG_CHOICE_LAST, + RG_DIALOG_END, }; rg_gui_dialog("Wifi Options", options, 0); } @@ -232,7 +234,7 @@ static void show_about_menu(void) { const rg_gui_option_t options[] = { {0, "Check for updates", NULL, 1, &updater_cb}, - RG_DIALOG_OPTION_LAST, + RG_DIALOG_END, }; rg_gui_about_menu(options); } @@ -477,7 +479,7 @@ void app_main(void) RG_DIALOG_SEPARATOR, {0, "About Retro-Go", NULL, 1, &about_app_cb}, #endif - RG_DIALOG_CHOICE_LAST + RG_DIALOG_END, }; app = rg_system_init(32000, &handlers, options); From a8cf750e15b7809ece25d93ec0a244e00ec31570 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Thu, 15 Dec 2022 13:10:21 -0500 Subject: [PATCH 09/20] Launcher: Use the json object directly in the updater. This is less elegant but it is less error-prone for now (rg doesn't have nice data structures to help us)... --- components/retro-go/rg_gui.h | 2 +- launcher/main/applications.c | 4 +- launcher/main/updater.c | 108 ++++++++++++++++++----------------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/components/retro-go/rg_gui.h b/components/retro-go/rg_gui.h index 787e4aa63..18303260a 100644 --- a/components/retro-go/rg_gui.h +++ b/components/retro-go/rg_gui.h @@ -69,8 +69,8 @@ struct rg_gui_option_s #define RG_DIALOG_FLAG_SKIP (-1) // (1 << 2) #define RG_DIALOG_CHOICE_LAST {0, NULL, NULL, 0, NULL} -#define RG_DIALOG_END RG_DIALOG_CHOICE_LAST #define RG_DIALOG_SEPARATOR {0, "----------", NULL, RG_DIALOG_FLAG_SKIP, NULL} +#define RG_DIALOG_END RG_DIALOG_CHOICE_LAST #define RG_DIALOG_CANCELLED -0x7654321 diff --git a/launcher/main/applications.c b/launcher/main/applications.c index ccc5d0d37..2abe5fb2b 100644 --- a/launcher/main/applications.c +++ b/launcher/main/applications.c @@ -546,7 +546,7 @@ static void show_file_info(retro_file_t *file) RG_DIALOG_SEPARATOR, {5, "Delete file", NULL, 1, NULL}, {1, "Close", NULL, 1, NULL}, - RG_DIALOG_CHOICE_LAST + RG_DIALOG_END, }; sprintf(filesize, "%ld KB", st.st_size / 1024); @@ -598,7 +598,7 @@ void application_show_file_menu(retro_file_t *file, bool advanced) {2, "Delete save", NULL, has_save || has_sram, NULL}, RG_DIALOG_SEPARATOR, {4, "Properties", NULL, 1, NULL}, - RG_DIALOG_CHOICE_LAST + RG_DIALOG_END, }; int sel = rg_gui_dialog(NULL, choices, has_save ? 0 : 1); diff --git a/launcher/main/updater.c b/launcher/main/updater.c index 7d0737512..19f5d7f52 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -3,23 +3,13 @@ #include -typedef struct -{ - char date[12]; - char name[32]; - char url[96]; - size_t size; -} release_t; - -typedef struct +static int download_file(const char *url, const char *filename) { - size_t length; - release_t releases[]; -} releases_t; + RG_ASSERT(url && filename, "bad param"); + RG_LOGI("Downloading: '%s' to '%s'", url, filename); + rg_gui_draw_hourglass(); -static int download_file(const char *url, const char *filename) -{ rg_http_req_t *req = NULL; FILE *fp = NULL; void *buffer = NULL; @@ -42,6 +32,7 @@ static int download_file(const char *url, const char *filename) written += fwrite(buffer, 1, len, fp); received += len; // we'll probably need to feed the watchdog here... + RG_LOGI("Received: %d / Written: %d", received, written); } if (received == written) @@ -61,14 +52,18 @@ static int download_file(const char *url, const char *filename) return ret; } -static releases_t *get_releases_from_github(void) +static cJSON *fetch_json(const char *url) { - char *buffer = NULL; + RG_ASSERT(url, "bad param"); + + RG_LOGI("Fetching: '%s'", url); + rg_gui_draw_hourglass(); + rg_http_req_t *req = NULL; - releases_t *res = NULL; - cJSON *releases = NULL; + char *buffer = NULL; + cJSON *json = NULL; - if (!(req = rg_network_http_open("https://api.github.com/repos/ducalex/retro-go/releases", NULL))) + if (!(req = rg_network_http_open(url, NULL))) goto cleanup; size_t buffer_length = RG_MAX(256 * 1024, req->content_length); @@ -79,59 +74,68 @@ static releases_t *get_releases_from_github(void) if (rg_network_http_read(req, buffer, buffer_length) < 16) goto cleanup; - if (!(releases = cJSON_Parse(buffer))) + if (!(json = cJSON_Parse(buffer))) goto cleanup; - // Limit to 10 because our dialog system isn't super reliable with large lists :( - size_t num_releases = RG_MIN(cJSON_GetArraySize(releases), 10); - RG_LOGI("num_releases = %d", num_releases); - - res = malloc(sizeof(releases_t) + (num_releases * sizeof(release_t))); - res->length = num_releases; - - for (size_t i = 0; i < num_releases; i++) - { - cJSON *el = cJSON_GetArrayItem(releases, i); - snprintf(res->releases[i].date, 10, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "published_at"))); - snprintf(res->releases[i].name, 32, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "name"))); - snprintf(res->releases[i].url, 96, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(el, "url"))); - } - cleanup: rg_network_http_close(req); - cJSON_Delete(releases); free(buffer); - return res; + return json; } void updater_show_dialog(void) { - rg_gui_draw_hourglass(); + cJSON *releases = fetch_json("https://api.github.com/repos/ducalex/retro-go/releases"); + size_t releases_length = RG_MIN(cJSON_GetArraySize(releases), 16); + + rg_gui_option_t options[releases_length + 1]; + rg_gui_option_t *opt = options; - releases_t *res = get_releases_from_github(); + if (releases_length == 0) + { + rg_gui_alert("Error", "Received empty list"); + goto cleanup; + } + + for (int i = 0; i < releases_length; i++) + { + cJSON *release = cJSON_GetArrayItem(releases, i); + *opt++ = (rg_gui_option_t){i, cJSON_GetStringValue(cJSON_GetObjectItem(release, "name")), NULL, 1, NULL}; + } + *opt++ = (rg_gui_option_t)RG_DIALOG_END; - if (res && res->length > 0) + int sel = rg_gui_dialog("Available releases", options, 0); + if (sel != RG_DIALOG_CANCELLED) { - rg_gui_option_t options[res->length + 1]; + cJSON *release = cJSON_GetArrayItem(releases, sel); + char *release_name = cJSON_GetStringValue(cJSON_GetObjectItem(release, "name")); + char *release_date = cJSON_GetStringValue(cJSON_GetObjectItem(release, "published_at")); + cJSON *assets = cJSON_GetObjectItem(release, "assets"); + size_t assets_length = cJSON_GetArraySize(assets); + + rg_gui_option_t options[assets_length + 4]; rg_gui_option_t *opt = options; - for (size_t i = 0; i < res->length; i++) + *opt++ = (rg_gui_option_t){0, "Date", release_date, -1, NULL}; + *opt++ = (rg_gui_option_t){0, "Files:", NULL, -1, NULL}; + + for (int i = 0; i < assets_length; i++) { - release_t *release = &res->releases[i]; - *opt++ = (rg_gui_option_t){i, release->name, release->date, 1, NULL}; + cJSON *asset = cJSON_GetArrayItem(assets, i); + *opt++ = (rg_gui_option_t){i, cJSON_GetStringValue(cJSON_GetObjectItem(asset, "name")), NULL, 1, NULL}; } - *opt++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; - int sel = rg_gui_dialog("Download release:", options, 0); + int sel = rg_gui_dialog(release_name, options, -1); if (sel != RG_DIALOG_CANCELLED) { - RG_LOGI("Downloading '%s' to '/odroid/firmware'...", res->releases[sel].url); + cJSON *asset = cJSON_GetArrayItem(assets, sel); + char *asset_url = cJSON_GetStringValue(cJSON_GetObjectItem(asset, "browser_download_url")); + char *dest_path = "/sd/odroid/firmware/retro_go-update.fw"; + download_file(asset_url, dest_path); } } - else - { - rg_gui_alert("Error", "Received empty list"); - } - free(res); +cleanup: + cJSON_Delete(releases); } From cfa842fa65efe2a8b17aa5b1c46cf9a2b66deb1a Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Thu, 15 Dec 2022 20:22:04 -0500 Subject: [PATCH 10/20] rg_storage: Replaced st_mtim.sec with st_mtime (Rel #51) --- components/retro-go/rg_storage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/retro-go/rg_storage.c b/components/retro-go/rg_storage.c index 689c87404..9914ea6a7 100644 --- a/components/retro-go/rg_storage.c +++ b/components/retro-go/rg_storage.c @@ -335,7 +335,7 @@ rg_scandir_t *rg_storage_scandir(const char *path, bool (*validator)(const char result->is_file = S_ISREG(statbuf.st_mode); result->is_dir = S_ISDIR(statbuf.st_mode); result->size = statbuf.st_size; - result->mtime = statbuf.st_mtim.tv_sec; + result->mtime = statbuf.st_mtime; } } memset(&results[count], 0, sizeof(rg_scandir_t)); From cca87a4db3cdea25753e6f164988fdd11a2dbc08 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 16 Dec 2022 17:56:08 -0500 Subject: [PATCH 11/20] rg_input: Added function rg_input_get_key_name to map a key bitmask to its name --- components/retro-go/rg_input.c | 23 +++++++++++++++++++++++ components/retro-go/rg_input.h | 4 +++- snes9x-go/main/keymap.h | 17 ++++++++--------- snes9x-go/main/main.c | 25 ++++++++++++++----------- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/components/retro-go/rg_input.c b/components/retro-go/rg_input.c index 8781cdbe2..e4a4e95a0 100644 --- a/components/retro-go/rg_input.c +++ b/components/retro-go/rg_input.c @@ -364,3 +364,26 @@ bool rg_input_read_battery(float *percent, float *volts) return true; } + +const char *rg_input_get_key_name(rg_key_t key) +{ + switch (key) + { + case RG_KEY_UP: return "Up"; + case RG_KEY_RIGHT: return "Right"; + case RG_KEY_DOWN: return "Down"; + case RG_KEY_LEFT: return "Left"; + case RG_KEY_SELECT: return "Select"; + case RG_KEY_START: return "Start"; + case RG_KEY_MENU: return "Menu"; + case RG_KEY_OPTION: return "Option"; + case RG_KEY_A: return "A"; + case RG_KEY_B: return "B"; + case RG_KEY_X: return "X"; + case RG_KEY_Y: return "Y"; + case RG_KEY_L: return "Left Shoulder"; + case RG_KEY_R: return "Right Shoulder"; + case RG_KEY_NONE: return "None"; + default: return "Unknown"; + } +} \ No newline at end of file diff --git a/components/retro-go/rg_input.h b/components/retro-go/rg_input.h index fd3a9096a..4abb62e96 100644 --- a/components/retro-go/rg_input.h +++ b/components/retro-go/rg_input.h @@ -19,14 +19,16 @@ typedef enum RG_KEY_Y = (1 << 11), RG_KEY_L = (1 << 12), RG_KEY_R = (1 << 13), + RG_KEY_COUNT = 14, RG_KEY_ANY = 0xFFFF, RG_KEY_ALL = 0xFFFF, - RG_KEY_COUNT = 14, + RG_KEY_NONE = 0, } rg_key_t; void rg_input_init(void); void rg_input_deinit(void); bool rg_input_key_is_pressed(rg_key_t key); void rg_input_wait_for_key(rg_key_t key, bool pressed); +const char *rg_input_get_key_name(rg_key_t key); uint32_t rg_input_read_gamepad(void); bool rg_input_read_battery(float *percent, float *volts); diff --git a/snes9x-go/main/keymap.h b/snes9x-go/main/keymap.h index 442f238eb..d4b120a5d 100644 --- a/snes9x-go/main/keymap.h +++ b/snes9x-go/main/keymap.h @@ -3,7 +3,6 @@ typedef struct { char name[16]; - size_t size; struct { uint16_t snes9x_mask; uint16_t local_mask; @@ -12,7 +11,7 @@ typedef struct } keymap_t; static const keymap_t KEYMAPS[] = { - {"Type A", 12, { + {"Type A", { {SNES_A_MASK, RG_KEY_A, 0}, {SNES_B_MASK, RG_KEY_B, 0}, {SNES_X_MASK, RG_KEY_START, 0}, @@ -26,7 +25,7 @@ static const keymap_t KEYMAPS[] = { {SNES_LEFT_MASK, RG_KEY_LEFT, 0}, {SNES_RIGHT_MASK, RG_KEY_RIGHT, 0}, }}, - {"Type B", 12, { + {"Type B", { {SNES_A_MASK, RG_KEY_START, 0}, {SNES_B_MASK, RG_KEY_A, 0}, {SNES_X_MASK, RG_KEY_SELECT, 0}, @@ -40,9 +39,13 @@ static const keymap_t KEYMAPS[] = { {SNES_LEFT_MASK, RG_KEY_LEFT, 0}, {SNES_RIGHT_MASK, RG_KEY_RIGHT, 0}, }}, - {"Type C", 8, { + {"Type C", { {SNES_A_MASK, RG_KEY_A, 0}, {SNES_B_MASK, RG_KEY_B, 0}, + {SNES_X_MASK, 0, 0}, + {SNES_Y_MASK, 0, 0}, + {SNES_TL_MASK, 0, 0}, + {SNES_TR_MASK, 0, 0}, {SNES_START_MASK, RG_KEY_START, 0}, {SNES_SELECT_MASK, RG_KEY_SELECT, 0}, {SNES_UP_MASK, RG_KEY_UP, 0}, @@ -54,10 +57,6 @@ static const keymap_t KEYMAPS[] = { static const size_t KEYMAPS_COUNT = (sizeof(KEYMAPS) / sizeof(keymap_t)); -static const char *CONSOLE_BUTTONS[] = { - "UP", "RIGHT", "DOWN", "LEFT", "SELECT", "START", "A", "B", "MENU", "OPTION", "NONE", "NONE" -}; - static const char *SNES_BUTTONS[] = { - "NONE", "NONE", "NONE", "NONE", "TR", "TL", "X", "A", "RIGHT", "LEFT", "DOWN", "UP", "START", "SELECT", "Y", "B" + "None", "None", "None", "None", "R", "L", "X", "A", "Right", "Left", "Down", "Up", "Start", "Select", "Y", "B" }; diff --git a/snes9x-go/main/main.c b/snes9x-go/main/main.c index 05faa89b7..71c6d1c27 100644 --- a/snes9x-go/main/main.c +++ b/snes9x-go/main/main.c @@ -132,9 +132,9 @@ static rg_gui_event_t menu_keymap_cb(rg_gui_option_t *option, rg_gui_event_t eve while (!dismissed) { - rg_gui_option_t options[keymap.size + 4]; + rg_gui_option_t options[20]; rg_gui_option_t *option = options; - char values[keymap.size][20]; + char values[16][20]; char profile[32]; option->label = "Profile"; @@ -155,21 +155,24 @@ static rg_gui_event_t menu_keymap_cb(rg_gui_option_t *option, rg_gui_event_t eve option->update_cb = &change_keymap_cb; option++; - for (int i = 0; i < keymap.size; i++) + for (int i = 0; i < RG_COUNT(keymap.keys); i++) { - // keys[i].key_id contains a bitmask, convert to bit number - int local_button = log2(keymap.keys[i].local_mask); - int mod_button = log2(keymap.keys[i].mod_mask); - int snes9x_button = log2(keymap.keys[i].snes9x_mask); + int local_button = keymap.keys[i].local_mask; + int mod_button = keymap.keys[i].mod_mask; + int snes9x_button = log2(keymap.keys[i].snes9x_mask); // convert bitmask to bit number + + // Empty mapping + if (snes9x_button < 4) + continue; // For now we don't display the D-PAD because it doesn't fit on large font - if (local_button < 4) + if (local_button & (RG_KEY_UP|RG_KEY_DOWN|RG_KEY_LEFT|RG_KEY_RIGHT)) continue; if (keymap.keys[i].mod_mask) - sprintf(values[i], "%s + %s", CONSOLE_BUTTONS[mod_button], CONSOLE_BUTTONS[local_button]); + sprintf(values[i], "%s + %s", rg_input_get_key_name(mod_button), rg_input_get_key_name(local_button)); else - sprintf(values[i], "%s", CONSOLE_BUTTONS[local_button]); + sprintf(values[i], "%s", rg_input_get_key_name(local_button)); option->label = SNES_BUTTONS[snes9x_button]; option->value = values[i]; @@ -214,7 +217,7 @@ uint32_t S9xReadJoypad(int32_t port) uint32_t joystick = rg_input_read_gamepad(); uint32_t joypad = 0; - for (int i = 0; i < keymap.size; ++i) + for (int i = 0; i < RG_COUNT(keymap.keys); ++i) { uint32_t bitmask = keymap.keys[i].local_mask | keymap.keys[i].mod_mask; if (bitmask && bitmask == (joystick & bitmask)) From 3bff5af48ff06016a0f698974d4c603b4fb381cb Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 16 Dec 2022 20:36:16 -0500 Subject: [PATCH 12/20] rg_network: Added redirection support to the HTTP client We must handle redirections ourselves because we're not using `esp_http_client_perform`. --- components/retro-go/rg_network.c | 21 +++++++++-- components/retro-go/rg_network.h | 1 + launcher/main/updater.c | 63 ++++++++++++++++++++++---------- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/components/retro-go/rg_network.c b/components/retro-go/rg_network.c index a5d854b94..4bbcae70f 100644 --- a/components/retro-go/rg_network.c +++ b/components/retro-go/rg_network.c @@ -299,15 +299,17 @@ rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) { RG_ASSERT(url, "bad param"); #ifdef RG_ENABLE_NETWORKING - esp_http_client_config_t http_config = {.url = url}; + esp_http_client_config_t http_config = {.url = url, .buffer_size = 1024, .buffer_size_tx = 1024}; esp_http_client_handle_t http_client = esp_http_client_init(&http_config); + rg_http_req_t *req = calloc(1, sizeof(rg_http_req_t)); - if (!http_client) + if (!http_client || !req) { RG_LOGE("Error creating client"); goto fail; } +try_again: if (esp_http_client_open(http_client, 0) != ESP_OK) { RG_LOGE("Error opening connection"); @@ -320,15 +322,26 @@ rg_http_req_t *rg_network_http_open(const char *url, const rg_http_cfg_t *cfg) goto fail; } - rg_http_req_t *req = calloc(1, sizeof(rg_http_req_t)); req->status_code = esp_http_client_get_status_code(http_client); req->content_length = esp_http_client_get_content_length(http_client); - req->received_bytes = 0; req->client = (void *)http_client; + + if (req->status_code == 301 || req->status_code == 302) + { + if (req->redirections < 5) + { + esp_http_client_set_redirection(http_client); + esp_http_client_close(http_client); + req->redirections++; + goto try_again; + } + } + return req; fail: esp_http_client_cleanup(http_client); + free(req); #endif return NULL; } diff --git a/components/retro-go/rg_network.h b/components/retro-go/rg_network.h index dd00b4555..f157754b9 100644 --- a/components/retro-go/rg_network.h +++ b/components/retro-go/rg_network.h @@ -49,6 +49,7 @@ typedef struct int status_code; int content_length; int received_bytes; + int redirections; void *client; } rg_http_req_t; diff --git a/launcher/main/updater.c b/launcher/main/updater.c index 19f5d7f52..b2636c2a2 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -1,23 +1,31 @@ #include "rg_system.h" #include "gui.h" +#include #include +#define GITHUB_RELEASES_URL "https://api.github.com/repos/ducalex/retro-go/releases" + +#ifdef RG_TARGET_ODROID_GO +#define DOWNLOAD_LOCATION RG_STORAGE_ROOT "/odroid/firmware" +#else +#define DOWNLOAD_LOCATION RG_STORAGE_ROOT "/espgbc/firmware" +#endif + static int download_file(const char *url, const char *filename) { RG_ASSERT(url && filename, "bad param"); - RG_LOGI("Downloading: '%s' to '%s'", url, filename); - rg_gui_draw_hourglass(); - rg_http_req_t *req = NULL; FILE *fp = NULL; void *buffer = NULL; int received = 0; - int written = 0; int len; int ret = -1; + RG_LOGI("Downloading: '%s' to '%s'", url, filename); + rg_gui_draw_dialog("Connecting...", NULL, 0); + if (!(req = rg_network_http_open(url, NULL))) goto cleanup; @@ -27,22 +35,21 @@ static int download_file(const char *url, const char *filename) if (!(buffer = malloc(16 * 1024))) goto cleanup; + rg_gui_draw_dialog("Receiving...", NULL, 0); + while ((len = rg_network_http_read(req, buffer, 16 * 1024)) > 0) { - written += fwrite(buffer, 1, len, fp); received += len; - // we'll probably need to feed the watchdog here... - RG_LOGI("Received: %d / Written: %d", received, written); + fwrite(buffer, 1, len, fp); + sprintf(buffer, "Received %d / %d", received, req->content_length); + rg_gui_draw_dialog(buffer, NULL, 0); + rg_system_tick(0); } - if (received == written) - { + if (req->content_length == received) + ret = 0; + else if (req->content_length == -1) ret = 0; - } - else - { - // oh oh - } cleanup: rg_network_http_close(req); @@ -85,7 +92,7 @@ static cJSON *fetch_json(const char *url) void updater_show_dialog(void) { - cJSON *releases = fetch_json("https://api.github.com/repos/ducalex/retro-go/releases"); + cJSON *releases = fetch_json(GITHUB_RELEASES_URL); size_t releases_length = RG_MIN(cJSON_GetArraySize(releases), 16); rg_gui_option_t options[releases_length + 1]; @@ -117,7 +124,7 @@ void updater_show_dialog(void) rg_gui_option_t *opt = options; *opt++ = (rg_gui_option_t){0, "Date", release_date, -1, NULL}; - *opt++ = (rg_gui_option_t){0, "Files:", NULL, -1, NULL}; + *opt++ = (rg_gui_option_t){0, "Downloads:", NULL, -1, NULL}; for (int i = 0; i < assets_length; i++) { @@ -130,9 +137,27 @@ void updater_show_dialog(void) if (sel != RG_DIALOG_CANCELLED) { cJSON *asset = cJSON_GetArrayItem(assets, sel); - char *asset_url = cJSON_GetStringValue(cJSON_GetObjectItem(asset, "browser_download_url")); - char *dest_path = "/sd/odroid/firmware/retro_go-update.fw"; - download_file(asset_url, dest_path); + char *asset_name = strdup(cJSON_GetStringValue(cJSON_GetObjectItem(asset, "name"))); + char *asset_url = strdup(cJSON_GetStringValue(cJSON_GetObjectItem(asset, "browser_download_url"))); + char *dest_path = malloc(RG_PATH_MAX); + snprintf(dest_path, RG_PATH_MAX, "%s/%s", DOWNLOAD_LOCATION, asset_name); + + // This is a bit ugly but at this point we need to free some internal heap because mbedtls + // doesn't seem to want to use SPIRAM :( + cJSON_Delete(releases); + + // Clean the screen, make it easier to see progress + gui_redraw(); + + if (download_file(asset_url, dest_path) == 0) + rg_gui_alert("Download complete!", dest_path); + else + rg_gui_alert("Download failed!", dest_path); + + free(asset_name); + free(asset_url); + free(dest_path); + return; } } From e3e86a32cbc2fcb2a823f64a89c07620d6edcbcc Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 16 Dec 2022 22:27:38 -0500 Subject: [PATCH 13/20] Launcher: Reduce memory usage in the updater The HTTP client seems to need some internal heap (for mbedtls) so getting rid of the JSON (>90KB) ASAP helps with stability. --- launcher/main/updater.c | 136 ++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 55 deletions(-) diff --git a/launcher/main/updater.c b/launcher/main/updater.c index b2636c2a2..c24260432 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -12,6 +12,20 @@ #define DOWNLOAD_LOCATION RG_STORAGE_ROOT "/espgbc/firmware" #endif +typedef struct +{ + char name[32]; + char url[256]; +} asset_t; + +typedef struct +{ + char name[32]; + char date[32]; + asset_t *assets; + size_t assets_count; +} release_t; + static int download_file(const char *url, const char *filename) { RG_ASSERT(url && filename, "bad param"); @@ -90,77 +104,89 @@ static cJSON *fetch_json(const char *url) return json; } -void updater_show_dialog(void) +static rg_gui_event_t view_release_cb(rg_gui_option_t *option, rg_gui_event_t event) { - cJSON *releases = fetch_json(GITHUB_RELEASES_URL); - size_t releases_length = RG_MIN(cJSON_GetArraySize(releases), 16); + if (event == RG_DIALOG_ENTER) + { + release_t *release = (release_t *)option->arg; - rg_gui_option_t options[releases_length + 1]; - rg_gui_option_t *opt = options; + rg_gui_option_t options[release->assets_count + 4]; + rg_gui_option_t *opt = options; - if (releases_length == 0) - { - rg_gui_alert("Error", "Received empty list"); - goto cleanup; - } + *opt++ = (rg_gui_option_t){0, "Date", release->date, -1, NULL}; + *opt++ = (rg_gui_option_t){0, "Files:", NULL, -1, NULL}; - for (int i = 0; i < releases_length; i++) - { - cJSON *release = cJSON_GetArrayItem(releases, i); - *opt++ = (rg_gui_option_t){i, cJSON_GetStringValue(cJSON_GetObjectItem(release, "name")), NULL, 1, NULL}; + for (int i = 0; i < release->assets_count; i++) + *opt++ = (rg_gui_option_t){i, release->assets[i].name, NULL, 1, NULL}; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; + + int sel = rg_gui_dialog(release->name, options, -1); + if (sel != RG_DIALOG_CANCELLED) + { + char dest_path[RG_PATH_MAX]; + snprintf(dest_path, RG_PATH_MAX, "%s/%s", DOWNLOAD_LOCATION, release->assets[sel].name); + + int ret = download_file(release->assets[sel].url, dest_path); + gui_redraw(); + + if (ret != 0) + rg_gui_alert("Download failed!", "..."); + else if (rg_gui_confirm("Download complete!", "Reboot to flash?", true)) + rg_system_switch_app(RG_APP_FACTORY, RG_APP_FACTORY, NULL, 0); + } + gui_redraw(); } - *opt++ = (rg_gui_option_t)RG_DIALOG_END; - int sel = rg_gui_dialog("Available releases", options, 0); - if (sel != RG_DIALOG_CANCELLED) - { - cJSON *release = cJSON_GetArrayItem(releases, sel); - char *release_name = cJSON_GetStringValue(cJSON_GetObjectItem(release, "name")); - char *release_date = cJSON_GetStringValue(cJSON_GetObjectItem(release, "published_at")); - cJSON *assets = cJSON_GetObjectItem(release, "assets"); - size_t assets_length = cJSON_GetArraySize(assets); + return RG_DIALOG_VOID; +} - rg_gui_option_t options[assets_length + 4]; - rg_gui_option_t *opt = options; +void updater_show_dialog(void) +{ + cJSON *releases_json = fetch_json(GITHUB_RELEASES_URL); + size_t releases_count = RG_MIN(cJSON_GetArraySize(releases_json), 20); - *opt++ = (rg_gui_option_t){0, "Date", release_date, -1, NULL}; - *opt++ = (rg_gui_option_t){0, "Downloads:", NULL, -1, NULL}; + release_t *releases = calloc(releases_count, sizeof(release_t)); + for (int i = 0; i < releases_count; ++i) + { + cJSON *release_json = cJSON_GetArrayItem(releases_json, i); + cJSON *assets_json = cJSON_GetObjectItem(release_json, "assets"); + size_t assets_count = cJSON_GetArraySize(assets_json); + + snprintf(releases[i].name, 32, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(release_json, "name"))); + snprintf(releases[i].date, 32, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(release_json, "published_at"))); + releases[i].assets = calloc(assets_count, sizeof(asset_t)); + releases[i].assets_count = assets_count; - for (int i = 0; i < assets_length; i++) + for (int j = 0; j < assets_count; ++j) { - cJSON *asset = cJSON_GetArrayItem(assets, i); - *opt++ = (rg_gui_option_t){i, cJSON_GetStringValue(cJSON_GetObjectItem(asset, "name")), NULL, 1, NULL}; + cJSON *asset_json = cJSON_GetArrayItem(assets_json, j); + asset_t *asset = &releases[i].assets[j]; + snprintf(asset->name, 32, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(asset_json, "name"))); + snprintf(asset->url, 256, "%s", cJSON_GetStringValue(cJSON_GetObjectItem(asset_json, "browser_download_url"))); } - *opt++ = (rg_gui_option_t)RG_DIALOG_END; + } - int sel = rg_gui_dialog(release_name, options, -1); - if (sel != RG_DIALOG_CANCELLED) - { - cJSON *asset = cJSON_GetArrayItem(assets, sel); - char *asset_name = strdup(cJSON_GetStringValue(cJSON_GetObjectItem(asset, "name"))); - char *asset_url = strdup(cJSON_GetStringValue(cJSON_GetObjectItem(asset, "browser_download_url"))); - char *dest_path = malloc(RG_PATH_MAX); - snprintf(dest_path, RG_PATH_MAX, "%s/%s", DOWNLOAD_LOCATION, asset_name); + cJSON_Delete(releases_json); - // This is a bit ugly but at this point we need to free some internal heap because mbedtls - // doesn't seem to want to use SPIRAM :( - cJSON_Delete(releases); + gui_redraw(); - // Clean the screen, make it easier to see progress - gui_redraw(); + if (releases_count > 0) + { + rg_gui_option_t options[releases_count + 1]; + rg_gui_option_t *opt = options; - if (download_file(asset_url, dest_path) == 0) - rg_gui_alert("Download complete!", dest_path); - else - rg_gui_alert("Download failed!", dest_path); + for (int i = 0; i < releases_count; ++i) + *opt++ = (rg_gui_option_t){(intptr_t)&releases[i], releases[i].name, NULL, 1, &view_release_cb}; + *opt++ = (rg_gui_option_t)RG_DIALOG_END; - free(asset_name); - free(asset_url); - free(dest_path); - return; - } + rg_gui_dialog("Available Releases", options, 0); + } + else + { + rg_gui_alert("Available Releases", "Received empty list!"); } -cleanup: - cJSON_Delete(releases); + for (int i = 0; i < releases_count; ++i) + free(releases[i].assets); + free(releases); } From e6ff41422f0a995040fc76ceebce5ddd3536f5cf Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 21 Dec 2022 14:18:20 -0500 Subject: [PATCH 14/20] rg_audio: Only show dummy sink when no other sinks are available Dummy sink option causes confusion for users so it's better to hide it. This change is untested. Future me needs to remember checking for issues before merging back to master. --- components/retro-go/rg_audio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/retro-go/rg_audio.c b/components/retro-go/rg_audio.c index e059c6b68..58b749772 100644 --- a/components/retro-go/rg_audio.c +++ b/components/retro-go/rg_audio.c @@ -31,7 +31,9 @@ #endif static const rg_audio_sink_t sinks[] = { +#if !RG_AUDIO_USE_INT_DAC && !RG_AUDIO_USE_EXT_DAC {RG_AUDIO_SINK_DUMMY, 0, "Dummy" }, +#endif #if RG_AUDIO_USE_INT_DAC {RG_AUDIO_SINK_I2S_DAC, 0, "Speaker"}, #endif @@ -77,7 +79,7 @@ void rg_audio_init(int sampleRate) ACQUIRE_DEVICE(1000); - int sinkType = (int)rg_settings_get_number(NS_GLOBAL, SETTING_OUTPUT, sinks[RG_COUNT(sinks) > 1 ? 1 : 0].type); + int sinkType = (int)rg_settings_get_number(NS_GLOBAL, SETTING_OUTPUT, sinks[0].type); for (size_t i = 0; i < RG_COUNT(sinks); ++i) { if (!audio.sink || sinks[i].type == sinkType) From 0fbbea97b73acf9c3c29e816e5973da1a5429df2 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 21 Dec 2022 14:24:42 -0500 Subject: [PATCH 15/20] Launcher: Updater: Use calloc instead of malloc to ensure 0-terminated json --- launcher/main/updater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/main/updater.c b/launcher/main/updater.c index c24260432..99d517d7f 100644 --- a/launcher/main/updater.c +++ b/launcher/main/updater.c @@ -89,7 +89,7 @@ static cJSON *fetch_json(const char *url) size_t buffer_length = RG_MAX(256 * 1024, req->content_length); - if (!(buffer = malloc(buffer_length))) + if (!(buffer = calloc(1, buffer_length + 1))) goto cleanup; if (rg_network_http_read(req, buffer, buffer_length) < 16) From a7921908fd53d99ab55cc4280c9e9917c31d1536 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 23 Dec 2022 17:43:47 -0500 Subject: [PATCH 16/20] GEN: Alloc framebuffer before VRAM to ensure it's internal There are more accesses to the framebuffer during a frame, so this gives us a free 10% speed boost. --- .../components/gwenesis/src/vdp/gwenesis_vdp_gfx.c | 2 +- gwenesis/main/main.c | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/gwenesis/components/gwenesis/src/vdp/gwenesis_vdp_gfx.c b/gwenesis/components/gwenesis/src/vdp/gwenesis_vdp_gfx.c index cd2b7de29..5babcf7b1 100644 --- a/gwenesis/components/gwenesis/src/vdp/gwenesis_vdp_gfx.c +++ b/gwenesis/components/gwenesis/src/vdp/gwenesis_vdp_gfx.c @@ -93,8 +93,8 @@ static int Window_firstcol; static int Window_lastcol; // 16 bits access to VRAM +// #define FETCH16VRAM(A) ({size_t addr = (A); (VRAM[addr+1]) | (VRAM[addr] << 8);}) #define FETCH16VRAM(A) ( (VRAM[(A)+1]) | (VRAM[(A)] << 8) ) - #define VDP_GFX_DISABLE_LOGGING 1 #if !VDP_GFX_DISABLE_LOGGING diff --git a/gwenesis/main/main.c b/gwenesis/main/main.c index 91b055e48..471797e04 100644 --- a/gwenesis/main/main.c +++ b/gwenesis/main/main.c @@ -239,22 +239,21 @@ void app_main(void) RG_DIALOG_CHOICE_LAST }; - app = rg_system_init(AUDIO_SAMPLE_RATE, &handlers, options); + app = rg_system_init(AUDIO_SAMPLE_RATE / 2, &handlers, options); yfm_enabled = rg_settings_get_number(NS_APP, SETTING_YFM_EMULATION, 1); - sn76489_enabled = rg_settings_get_number(NS_APP, SETTING_SN76489_EMULATION, 1); + sn76489_enabled = rg_settings_get_number(NS_APP, SETTING_SN76489_EMULATION, 0); // yfm_resample = rg_settings_get_number(NS_APP, SETTING_YFM_RESAMPLE, 1); z80_enabled = rg_settings_get_number(NS_APP, SETTING_Z80_EMULATION, 1); frameskip = rg_settings_get_number(NS_APP, SETTING_FRAMESKIP, frameskip); - VRAM = rg_alloc(VRAM_MAX_SIZE, MEM_FAST); - updates[0].buffer = rg_alloc(320 * 240, MEM_FAST); - // updates[1].buffer = rg_alloc(320 * 240 * 2, MEM_FAST); + // updates[1].buffer = rg_alloc(320 * 240, MEM_FAST); + + VRAM = rg_alloc(VRAM_MAX_SIZE, MEM_FAST); // rg_task_create("gen_sound", &sound_task, NULL, 2048, 7, 1); // rg_audio_set_sample_rate(yfm_resample ? 26634 : 53267); - rg_audio_set_sample_rate(26634); RG_LOGI("Genesis start\n"); From e1f3176ca9eb66204995ec9e5352d22f3409b1a4 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 23 Dec 2022 18:10:46 -0500 Subject: [PATCH 17/20] Updated CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 952d7b026..7c542078f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Retro-Go 1.37 (2022-12-??) +- SNES: Fixed controls menu labels +- GEN: Small performance improvement +- Launcher: Added tool to download updates + + # Retro-Go 1.36.3 (2022-12-14) - SNES: Fixed freeze in controls menu (hopefully for real this time...) From b679618d8e76cc6de19e62113f86a80a5b5ac14a Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Mon, 26 Dec 2022 19:49:26 -0500 Subject: [PATCH 18/20] rg_gui: Added a "System Information" submenu to the about dialog It shows basic hardware status. Most of it was already shown in the debug menu, but that one is meant to be nicer and more concise. --- components/retro-go/rg_gui.c | 134 +++++++++++++++++++++++------------ launcher/main/main.c | 3 + 2 files changed, 90 insertions(+), 47 deletions(-) diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 0ef42aabf..9e33549a5 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -1098,9 +1098,54 @@ void rg_gui_options_menu(void) rg_audio_set_mute(false); } +void rg_gui_sysinfo_menu(void) +{ + char screen_str[32], network_str[64], memory_str[32]; + char storage_str[32], localtime_str[32], uptime[32]; + + const rg_gui_option_t options[] = { + {0, "Console", RG_TARGET_NAME, 1, NULL}, + {0, "Screen", screen_str, 1, NULL}, + {0, "Memory", memory_str, 1, NULL}, + {0, "Network", network_str, 1, NULL}, + // {0, "Storage", storage_str, 1, NULL}, + {0, "RTC", localtime_str, 1, NULL}, + {0, "Uptime", uptime, 1, NULL}, + RG_DIALOG_SEPARATOR, + {0, "Close", NULL, 1, NULL}, + RG_DIALOG_CHOICE_LAST + }; + + const rg_display_t *display = rg_display_get_info(); + rg_stats_t stats = rg_system_get_counters(); + time_t now = time(NULL); + + snprintf(screen_str, 32, "%dx%d (%d)", display->screen.width, display->screen.height, display->screen.format); + snprintf(memory_str, 32, "%dKB + %dKB", stats.totalMemoryInt / 1024, stats.totalMemoryExt / 1024); + snprintf(uptime, 32, "%ds", (int)(rg_system_timer() / 1000000)); + snprintf(storage_str, 32, "%s", "N/A"); + strftime(localtime_str, 32, "%F %T", localtime(&now)); + +#ifdef RG_ENABLE_NETWORKING + rg_network_t net = rg_network_get_info(); + if (net.state == RG_NETWORK_CONNECTED) + snprintf(network_str, 64, "%s\n%s", net.name, net.ip_addr); + else if (net.state == RG_NETWORK_CONNECTING) + snprintf(network_str, 64, "%s\n%s", net.name, "connecting..."); + else if (net.name[0]) + snprintf(network_str, 64, "%s\n%s", net.name, "disconnected"); + else + snprintf(network_str, 64, "%s", "disconnected"); +#else + strcpy(network_str, "No adapter"); +#endif + + rg_gui_dialog("System Information", options, -1); +} + void rg_gui_about_menu(const rg_gui_option_t *extra_options) { - char build_ver[32], build_date[32], build_user[32], network_str[64]; + char build_ver[32], build_date[32], build_user[32]; size_t extra_options_count = get_dialog_items_count(extra_options); @@ -1110,17 +1155,12 @@ void rg_gui_about_menu(const rg_gui_option_t *extra_options) *opt++ = (rg_gui_option_t){0, "Version", build_ver, 1, NULL}; *opt++ = (rg_gui_option_t){0, "Date", build_date, 1, NULL}; *opt++ = (rg_gui_option_t){0, "By", build_user, 1, NULL}; -#ifdef RG_ENABLE_NETWORKING - *opt++ = (rg_gui_option_t){0, "Network", network_str, 1, NULL}; -#endif *opt++ = (rg_gui_option_t)RG_DIALOG_SEPARATOR; + *opt++ = (rg_gui_option_t){1000, "System information", NULL, 1, NULL}; for (size_t i = 0; i < extra_options_count; i++) *opt++ = extra_options[i]; - *opt++ = (rg_gui_option_t){1000, "Reboot to firmware", NULL, 1, NULL}; *opt++ = (rg_gui_option_t){2000, "Reset settings", NULL, 1, NULL}; - *opt++ = (rg_gui_option_t){3000, "Clear cache", NULL, 1, NULL}; - *opt++ = (rg_gui_option_t){4000, "Debug", NULL, 1, NULL}; - *opt++ = (rg_gui_option_t){0000, "Close", NULL, 1, NULL}; + *opt++ = (rg_gui_option_t){3000, "Debug", NULL, 1, NULL}; *opt++ = (rg_gui_option_t)RG_DIALOG_CHOICE_LAST; const rg_app_t *app = rg_system_get_app(); @@ -1139,39 +1179,28 @@ void rg_gui_about_menu(const rg_gui_option_t *extra_options) strcat(build_ver, ")"); } - rg_network_t net = rg_network_get_info(); - if (net.state == RG_NETWORK_CONNECTED) - snprintf(network_str, 64, "%s\n%s", net.name, net.ip_addr); - else if (net.state == RG_NETWORK_CONNECTING) - snprintf(network_str, 64, "%s\n%s", net.name, "connecting..."); - else if (net.name[0]) - snprintf(network_str, 64, "%s\n%s", net.name, "disconnected"); - else - snprintf(network_str, 64, "%s", "disconnected"); - - int sel = rg_gui_dialog("Retro-Go", options, -1); - - rg_settings_commit(); - rg_system_save_time(); - - switch (sel) + while (true) { - case 1000: - rg_system_switch_app(RG_APP_FACTORY, RG_APP_FACTORY, 0, 0); - break; - case 2000: - if (rg_gui_confirm("Reset all settings?", NULL, false)) { - rg_settings_reset(); - rg_system_restart(); - } - break; - case 3000: - rg_storage_delete(RG_BASE_PATH_CACHE); - rg_system_restart(); - break; - case 4000: - rg_gui_debug_menu(NULL); - break; + switch (rg_gui_dialog("Retro-Go", options, 4)) + { + case 1000: + rg_gui_sysinfo_menu(); + break; + case 2000: + if (rg_gui_confirm("Reset all settings?", NULL, false)) { + rg_storage_delete(RG_BASE_PATH_CACHE); + rg_settings_reset(); + rg_system_restart(); + return; + } + break; + case 3000: + rg_gui_debug_menu(NULL); + break; + default: + return; + } + rg_system_event(RG_EVENT_REDRAW, NULL); } } @@ -1192,10 +1221,12 @@ void rg_gui_debug_menu(const rg_gui_option_t *extra_options) {0, "Timezone ", timezone, 1, NULL}, {0, "Uptime ", uptime, 1, NULL}, RG_DIALOG_SEPARATOR, - {1000, "Save screenshot", NULL, 1, NULL}, - {2000, "Save trace", NULL, 1, NULL}, - {3000, "Cheats", NULL, 1, NULL}, - {4000, "Crash", NULL, 1, NULL}, + {1, "Reboot to firmware", NULL, 1, NULL}, + {2, "Clear cache", NULL, 1, NULL}, + {3, "Save screenshot", NULL, 1, NULL}, + {4, "Save trace", NULL, 1, NULL}, + {5, "Cheats", NULL, 1, NULL}, + {6, "Crash", NULL, 1, NULL}, RG_DIALOG_CHOICE_LAST }; @@ -1215,13 +1246,22 @@ void rg_gui_debug_menu(const rg_gui_option_t *extra_options) switch (rg_gui_dialog("Debugging", options, 0)) { - case 1000: + case 1: + rg_system_switch_app(RG_APP_FACTORY, RG_APP_FACTORY, 0, 0); + break; + case 2: + rg_storage_delete(RG_BASE_PATH_CACHE); + rg_system_restart(); + break; + case 3: rg_emu_screenshot(RG_STORAGE_ROOT "/screenshot.png", 0, 0); break; - case 2000: + case 4: rg_system_save_trace(RG_STORAGE_ROOT "/trace.txt", 0); break; - case 4000: + case 5: + break; + case 6: RG_PANIC("Crash test!"); break; } diff --git a/launcher/main/main.c b/launcher/main/main.c index 427eebf7f..2041ddac1 100644 --- a/launcher/main/main.c +++ b/launcher/main/main.c @@ -226,7 +226,10 @@ static rg_gui_event_t startup_app_cb(rg_gui_option_t *option, rg_gui_event_t eve static rg_gui_event_t updater_cb(rg_gui_option_t *option, rg_gui_event_t event) { if (event == RG_DIALOG_ENTER) + { updater_show_dialog(); + gui_redraw(); + } return RG_DIALOG_VOID; } From 60251517de765ccb49714f3a95341588ac2fdb1a Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Mon, 26 Dec 2022 20:35:28 -0500 Subject: [PATCH 19/20] Added note about multi-ssid in README.md --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0471d110e..9f0531bd7 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,7 @@ The roms must be packed with [LCD-Game-Shrinker](https://github.com/bzhxx/LCD-Ga ## Wifi -To use wifi you will need to add your config to `/retro-go/config/wifi.json` file. -It should look like this: +To use wifi you will need to create a `/retro-go/config/wifi.json` config file. Its content should look like this: ````json { @@ -90,6 +89,18 @@ It should look like this: } ```` +Multiple networks can be defined using the following format (then selectable in the Options menu): +````json +{ + "ssid0": "my-network", + "password0": "my-password", + "ssid1": "my-network", + "password1": "my-password", + "ssid2": "my-network", + "password2": "my-password" +} +```` + ### 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. From d974d535af4b01b0d46e4965fe86abcd5f1e280c Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Fri, 30 Dec 2022 14:19:16 -0500 Subject: [PATCH 20/20] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c542078f..ee110b6c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Retro-Go 1.37 (2022-12-??) +# Retro-Go 1.37 (2022-12-30) - SNES: Fixed controls menu labels - GEN: Small performance improvement - Launcher: Added tool to download updates