From 801a5ea0d2930ac440f30ace34768da21e5e4bee Mon Sep 17 00:00:00 2001 From: Martin Donlon Date: Sat, 24 Sep 2022 13:32:39 -0700 Subject: [PATCH] If errors are detected in INI settings display an info message for 5 seconds at core startup. (#696) Added `cfg_error` function for reporting errors in the ini. Report errors when parsing video_mode information. Report out of bounds settings. Report unknown settings. Detect numeric parse failures. --- cfg.cpp | 166 +++++++++++++++++++++++++++++++++++++--------------- cfg.h | 3 + user_io.cpp | 7 +++ video.cpp | 6 +- 4 files changed, 133 insertions(+), 49 deletions(-) diff --git a/cfg.cpp b/cfg.cpp index 3df2de3..218b338 100644 --- a/cfg.cpp +++ b/cfg.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -26,8 +27,8 @@ typedef struct const char* name; void* var; ini_vartypes_t type; - int min; - int max; + int64_t min; + int64_t max; } ini_var_t; static const ini_var_t ini_vars[] = @@ -37,7 +38,7 @@ static const ini_var_t ini_vars[] = { "FORCED_SCANDOUBLER", (void*)(&(cfg.forced_scandoubler)), UINT8, 0, 1 }, { "VGA_SCALER", (void*)(&(cfg.vga_scaler)), UINT8, 0, 1 }, { "VGA_SOG", (void*)(&(cfg.vga_sog)), UINT8, 0, 1 }, - { "KEYRAH_MODE", (void*)(&(cfg.keyrah_mode)), UINT32, 0, (int)0xFFFFFFFF }, + { "KEYRAH_MODE", (void*)(&(cfg.keyrah_mode)), UINT32, 0, 0xFFFFFFFF }, { "RESET_COMBO", (void*)(&(cfg.reset_combo)), UINT8, 0, 3 }, { "KEY_MENU_AS_RGUI", (void*)(&(cfg.key_menu_as_rgui)), UINT8, 0, 1 }, { "VIDEO_MODE", (void*)(cfg.video_conf), STRING, 0, sizeof(cfg.video_conf) - 1 }, @@ -76,7 +77,7 @@ static const ini_var_t ini_vars[] = { "SHARED_FOLDER", (void*)(&(cfg.shared_folder)), STRING, 0, sizeof(cfg.shared_folder) - 1 }, { "NO_MERGE_VID", (void*)(&(cfg.no_merge_vid)), UINT16, 0, 0xFFFF }, { "NO_MERGE_PID", (void*)(&(cfg.no_merge_pid)), UINT16, 0, 0xFFFF }, - { "NO_MERGE_VIDPID", (void*)(cfg.no_merge_vidpid), UINT32ARR, 0, (int)0xFFFFFFFF }, + { "NO_MERGE_VIDPID", (void*)(cfg.no_merge_vidpid), UINT32ARR, 0, 0xFFFFFFFF }, { "CUSTOM_ASPECT_RATIO_1", (void*)(&(cfg.custom_aspect_ratio[0])), STRING, 0, sizeof(cfg.custom_aspect_ratio[0]) - 1 }, { "CUSTOM_ASPECT_RATIO_2", (void*)(&(cfg.custom_aspect_ratio[1])), STRING, 0, sizeof(cfg.custom_aspect_ratio[1]) - 1 }, { "SPINNER_VID", (void*)(&(cfg.spinner_vid)), UINT16, 0, 0xFFFF }, @@ -226,6 +227,65 @@ static int ini_get_section(char* buf, const char *vmode) return 0; } +static void ini_parse_numeric(const ini_var_t *var, const char *text, void *out) +{ + uint32_t u32 = 0; + int32_t i32 = 0; + float f32 = 0.0f; + char *endptr = nullptr; + + bool out_of_range = true; + + switch(var->type) + { + case UINT8: + case UINT16: + case UINT32: + case UINT32ARR: + u32 = strtoul(text, &endptr, 0); + if (u32 < var->min) u32 = var->min; + else if (u32 > var->max) u32 = var->max; + else out_of_range = false; + break; + + case INT8: + case INT16: + case INT32: + i32 = strtol(text, &endptr, 0); + if (i32 < var->min) i32 = var->min; + else if (i32 > var->max) i32 = var->max; + else out_of_range = false; + break; + + case FLOAT: + f32 = strtof(text, &endptr); + if (f32 < var->min) f32 = var->min; + else if (f32 > var->max) f32 = var->max; + else out_of_range = false; + break; + + default: + out_of_range = false; + break; + } + + if (*endptr) cfg_error("%s: \'%s\' not a number", var->name, text); + else if (out_of_range) cfg_error("%s: \'%s\' out of range", var->name, text); + + switch (var->type) + { + case UINT8: *(uint8_t*)out = u32; break; + case INT8: *(int8_t*)out = u32; break; + case UINT16: *(uint16_t*)out = u32; break; + case UINT32ARR: *(uint32_t*)out = u32; break; + case INT16: *(int16_t*)out = u32; break; + case UINT32: *(uint32_t*)out = u32; break; + case INT32: *(int32_t*)out = u32; break; + case FLOAT: *(float*)out = f32; break; + default: break; + } +} + static void ini_parse_var(char* buf) { // find var @@ -248,62 +308,35 @@ static void ini_parse_var(char* buf) if (!strcasecmp(buf, ini_vars[j].name)) var_id = j; } - // get data - if (var_id != -1) + if (var_id == -1) + { + cfg_error("%s: unknown option", buf); + } + else // get data { i++; while (buf[i] == '=' || CHAR_IS_SPACE(buf[i])) i++; ini_parser_debugf("Got VAR '%s' with VALUE %s", buf, buf+i); - switch (ini_vars[var_id].type) + const ini_var_t *var = &ini_vars[var_id]; + + switch (var->type) { - case UINT8: - *(uint8_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0); - if (*(uint8_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(uint8_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(uint8_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(uint8_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case INT8: - *(int8_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0); - if (*(int8_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int8_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(int8_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int8_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case UINT16: - *(uint16_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0); - if (*(uint16_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(uint16_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(uint16_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(uint16_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; + case STRING: + memset(var->var, 0, var->max); + strncpy((char*)(var->var), &(buf[i]), var->max); break; + case UINT32ARR: { - uint32_t *arr = (uint32_t*)ini_vars[var_id].var; + uint32_t *arr = (uint32_t*)var->var; uint32_t pos = ++arr[0]; - arr[pos] = strtoul(&(buf[i]), NULL, 0); - if (arr[pos] > (uint32_t)ini_vars[var_id].max) arr[pos] = (uint32_t)ini_vars[var_id].max; - if (arr[pos] < (uint32_t)ini_vars[var_id].min) arr[pos] = (uint32_t)ini_vars[var_id].min; + ini_parse_numeric(var, &buf[i], &arr[pos]); } break; - case INT16: - *(int16_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0); - if (*(int16_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int16_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(int16_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int16_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case UINT32: - *(uint32_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0); - if (*(uint32_t*)(ini_vars[var_id].var) > (uint32_t)ini_vars[var_id].max) *(uint32_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(uint32_t*)(ini_vars[var_id].var) < (uint32_t)ini_vars[var_id].min) *(uint32_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case INT32: - *(int32_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0); - if (*(int32_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int32_t*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(int32_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int32_t*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case FLOAT: - *(float*)(ini_vars[var_id].var) = strtof(&(buf[i]), NULL); - if (*(float*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(float*)(ini_vars[var_id].var) = ini_vars[var_id].max; - if (*(float*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(float*)(ini_vars[var_id].var) = ini_vars[var_id].min; - break; - case STRING: - memset(ini_vars[var_id].var, 0, ini_vars[var_id].max); - strncpy((char*)(ini_vars[var_id].var), &(buf[i]), ini_vars[var_id].max); + + default: + ini_parse_numeric(var, &buf[i], var->var); break; } } @@ -356,6 +389,11 @@ static void ini_parse(int alt, const char *vmode) FileClose(&ini_file); } +static constexpr int CFG_ERRORS_MAX = 4; +static constexpr int CFG_ERRORS_STRLEN = 128; +static char cfg_errors[CFG_ERRORS_MAX][CFG_ERRORS_STRLEN]; +static int cfg_error_count = 0; + const char* cfg_get_name(uint8_t alt) { static char name[64]; @@ -385,6 +423,7 @@ void cfg_parse() cfg.dvi_mode = 2; has_video_sections = false; using_video_section = false; + cfg_error_count = 0; ini_parse(altcfg(), video_get_core_mode_name(1)); if (has_video_sections && !using_video_section) { @@ -397,3 +436,34 @@ bool cfg_has_video_sections() { return has_video_sections; } + + +void cfg_error(const char *fmt, ...) +{ + if (cfg_error_count >= CFG_ERRORS_MAX) return; + + va_list args; + va_start(args, fmt); + vsnprintf(cfg_errors[cfg_error_count], CFG_ERRORS_STRLEN, fmt, args); + va_end(args); + + printf("ERROR CFG: %s\n", cfg_errors[cfg_error_count]); + + cfg_error_count += 1; +} + +bool cfg_check_errors(char *msg, size_t max_len) +{ + msg[0] = '\0'; + + if (cfg_error_count == 0) return false; + + int pos = snprintf(msg, max_len, "%d INI Error%s\n---", cfg_error_count, cfg_error_count > 1 ? "s" : ""); + + for (int i = 0; i < cfg_error_count; i++) + { + pos += snprintf(msg + pos, max_len - pos, "\n%s\n", cfg_errors[i]); + } + + return true; +} \ No newline at end of file diff --git a/cfg.h b/cfg.h index 2078df3..ebc23e6 100644 --- a/cfg.h +++ b/cfg.h @@ -87,4 +87,7 @@ void cfg_parse(); const char* cfg_get_name(uint8_t alt); bool cfg_has_video_sections(); +void cfg_error(const char *fmt, ...); +bool cfg_check_errors(char *msg, size_t max_len); + #endif // __CFG_H__ diff --git a/user_io.cpp b/user_io.cpp index 071bdf2..f7eb3df 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -1505,6 +1505,13 @@ void user_io_init(const char *path, const char *xml) // release reset if (!is_minimig() && !is_st()) user_io_status_set("[0]", 0); if (xml && isXmlName(xml) == 1) arcade_check_error(); + + char cfg_errs[512]; + if (cfg_check_errors(cfg_errs, sizeof(cfg_errs))) + { + Info(cfg_errs, 5000); + sleep(5); + } break; } diff --git a/video.cpp b/video.cpp index 4d1ae14..a43f5fa 100644 --- a/video.cpp +++ b/video.cpp @@ -1851,6 +1851,8 @@ static int parse_custom_video_mode(char* vcfg, vmode_custom_t *v) char work[1024]; char *next; + if (!vcfg[0]) return -1; + memset(v, 0, sizeof(vmode_custom_t)); v->param.rb = 1; // default reduced blanking to true @@ -1866,7 +1868,7 @@ static int parse_custom_video_mode(char* vcfg, vmode_custom_t *v) } } - if (cnt == 2) + if (cnt == 2 && token_cnt > 2) { valf = strtod(tokens[cnt], &next); if (!*next) cnt++; @@ -1885,6 +1887,7 @@ static int parse_custom_video_mode(char* vcfg, vmode_custom_t *v) else { printf("Error parsing video_mode parameter %d \"%s\": \"%s\"\n", i, flag, vcfg); + cfg_error("Invalid video_mode\n> %s", vcfg); return -1; } } @@ -1921,6 +1924,7 @@ static int parse_custom_video_mode(char* vcfg, vmode_custom_t *v) else { printf("Error parsing video_mode parameter: ""%s""\n", vcfg); + cfg_error("Invalid video_mode\n> %s", vcfg); return -1; } -- 2.45.2