diff --git a/popnhax/config.cc b/popnhax/config.cc
index 54ff3e2..160a748 100644
--- a/popnhax/config.cc
+++ b/popnhax/config.cc
@@ -3,6 +3,7 @@
#include "util/crc32.h"
#include "util/log.h"
+#include "util/psmap.h"
#include "config.h"
#define F_OK 0
@@ -220,6 +221,272 @@ static bool config_make_opt(const char *xml_filename){
return true;
}
+static bool type_to_str(enum psmap_property_type type, char* expected_type)
+{
+ bool res = true;
+ switch (type)
+ {
+ case PSMAP_PROPERTY_TYPE_S8:
+ strcpy(expected_type, "s8");
+ break;
+ case PSMAP_PROPERTY_TYPE_U8:
+ strcpy(expected_type, "u8");
+ break;
+ case PSMAP_PROPERTY_TYPE_S16:
+ strcpy(expected_type, "s16");
+ break;
+ case PSMAP_PROPERTY_TYPE_U16:
+ strcpy(expected_type, "u16");
+ break;
+ case PSMAP_PROPERTY_TYPE_S32:
+ strcpy(expected_type, "s32");
+ break;
+ case PSMAP_PROPERTY_TYPE_U32:
+ strcpy(expected_type, "u32");
+ break;
+ case PSMAP_PROPERTY_TYPE_S64:
+ strcpy(expected_type, "s64");
+ break;
+ case PSMAP_PROPERTY_TYPE_U64:
+ strcpy(expected_type, "u64");
+ break;
+ case PSMAP_PROPERTY_TYPE_STR:
+ strcpy(expected_type, "str");
+ break;
+ case PSMAP_PROPERTY_TYPE_FLOAT:
+ strcpy(expected_type, "float");
+ break;
+ case PSMAP_PROPERTY_TYPE_ATTR:
+ strcpy(expected_type, "attr");
+ break;
+ case PSMAP_PROPERTY_TYPE_BOOL:
+ strcpy(expected_type, "bool");
+ break;
+ default:
+ strcpy(expected_type, "INVALID");
+ res = false;
+ break;
+ }
+ return res;
+}
+
+static int64_t get_min(enum psmap_property_type type)
+{
+ switch (type)
+ {
+ case PSMAP_PROPERTY_TYPE_S8:
+ return -128;
+ case PSMAP_PROPERTY_TYPE_S16:
+ return -32768;
+ case PSMAP_PROPERTY_TYPE_S32:
+ return -2147483648;
+ case PSMAP_PROPERTY_TYPE_S64:
+ return LLONG_MIN;
+ case PSMAP_PROPERTY_TYPE_U8:
+ case PSMAP_PROPERTY_TYPE_U16:
+ case PSMAP_PROPERTY_TYPE_U32:
+ case PSMAP_PROPERTY_TYPE_U64:
+ return 0;
+ default:
+ return -1;
+ }
+}
+static int64_t get_max(enum psmap_property_type type)
+{
+ switch (type)
+ {
+ case PSMAP_PROPERTY_TYPE_S8:
+ return 127;
+ case PSMAP_PROPERTY_TYPE_S16:
+ return 32767;
+ case PSMAP_PROPERTY_TYPE_S32:
+ return 2147483647;
+ case PSMAP_PROPERTY_TYPE_S64:
+ return 9223372036854775807;
+ case PSMAP_PROPERTY_TYPE_U8:
+ return 255;
+ case PSMAP_PROPERTY_TYPE_U16:
+ return 65535;
+ case PSMAP_PROPERTY_TYPE_U32:
+ return 4294967295;
+ default:
+ return -1;
+ }
+}
+
+static bool is_valid(const char *option_line, uint8_t type, uint32_t member_width, char **reason)
+{
+ char tmp_reason[256] = {0};
+ char open[64] = {0};
+ char expected_type[8] = {0};
+ char option_type[64] = {0};
+ char value[64] = {0};
+ char close[64] = {0};
+ if (!type_to_str((enum psmap_property_type) type, expected_type))
+ {
+ sprintf(tmp_reason, "bad type value"); // shouldn't happen as it's psmap struct type
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+
+ /* attempt to scan line */
+ int num = sscanf(option_line, " %s __type=\"%[^<>\"]\">%[^><\"]%[^\"\r\n] ", open, option_type, value, close);
+ if ( num == 2 && strcmp(option_type, "str")==0 )
+ {
+ num = sscanf(option_line, " %s __type=\"%[^<>\"]\">%[^\"\r\n] ", open, option_type, close);
+ if (num != 3)
+ {
+ sprintf(tmp_reason, "malformed line (should be option_value)");
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ }
+ else if ( num != 4 )
+ {
+ sprintf(tmp_reason, "malformed line (should be option_value)");
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+
+ /* tag errors */
+ if ( open[0] != '<' )
+ {
+ sprintf(tmp_reason, "malformed opening tag (should be <%s __type\"option_type\">)", open);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ if ( close[0] != '<' || close[1] != '/' || close[strlen(close)-1] != '>' )
+ {
+ sprintf(tmp_reason, "malformed closing tag (should be %s>)", open+1);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ if ( close[strlen(open)+1] != '>' || strncmp(open+1, close+2, strlen(open+1)) != 0 )
+ {
+ sprintf(tmp_reason, "opening tag doesn't match closing tag (%s> vs %s)", open, close);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+
+ /* type errors */
+ if ( strcmp(option_type, expected_type) != 0 )
+ {
+ sprintf(tmp_reason, "unexpected type (expected %s, got %s)", expected_type, option_type);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+
+ if ( type == PSMAP_PROPERTY_TYPE_STR )
+ {
+ if ( strlen(value) > member_width-1 )
+ {
+ sprintf(tmp_reason, "value %s too long (max length is %d, got %d)", value, member_width-1, strlen(value));
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ return true;
+ }
+
+ if ( type == PSMAP_PROPERTY_TYPE_ATTR )
+ return true; // can't do further checks
+
+ if ( type == PSMAP_PROPERTY_TYPE_FLOAT )
+ {
+ char *endPtr;
+ (void)strtof(value, &endPtr);
+ if ( endPtr != (value+strlen(value)) || errno == ERANGE )
+ {
+ sprintf(tmp_reason, "invalid %s value %s (should be a valid IEEE 754 floating point number)", option_type, value);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ }
+
+ if ( type == PSMAP_PROPERTY_TYPE_BOOL )
+ {
+ if ( strlen(value) != 1 || (value[0] != '0' && value[0] != '1') )
+ {
+ sprintf(tmp_reason, "invalid bool value (should be 0 or 1, got %s)", value);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ return true;
+ }
+
+ if ( type == PSMAP_PROPERTY_TYPE_U64 )
+ {
+ char *endPtr;
+ (void)strtoull(value, &endPtr, 10);
+ if ( endPtr != (value+strlen(value)) || errno == ERANGE )
+ {
+ sprintf(tmp_reason, "invalid %s value %s (should be an integer between 0 and 18446744073709551615)", option_type, value);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ }
+ else // only numeric types up to S64 remain
+ {
+ char *endPtr;
+ int64_t parsed = strtoull(value, &endPtr, 10);
+ if ( type == PSMAP_PROPERTY_TYPE_S64 && errno == ERANGE )
+ {
+ sprintf(tmp_reason, "invalid %s value (should be an integer between -9223372036854775808 and 9223372036854775807)", option_type);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ else if ( endPtr != (value+strlen(value)) )
+ {
+ sprintf(tmp_reason, "invalid value %s (should be an integer and nothing else)", value);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+
+ int64_t min = get_min((enum psmap_property_type)type);
+ int64_t max = get_max((enum psmap_property_type)type);
+ if ( parsed < min || parsed > max )
+ {
+ sprintf(tmp_reason, "invalid value (should be an integer between %lld and %lld, got %s)", min, max, value);
+ *reason = strdup(tmp_reason);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void config_diag(const char *opt_filepath, const struct property_psmap *psmap) {
+ int i = 0;
+ int shift = strlen("/popnhax/");
+ FILE *opt = fopen(opt_filepath, "r");
+ if ( opt == NULL )
+ {
+ LOG("FATAL ERROR: cannot open %s\n", opt_filepath);
+ }
+
+ while ( psmap[i].type != 0xFF )
+ {
+ bool required = !psmap[i].has_default;
+ const char *option_name = psmap[i].path+shift;
+ //LOG("checking %s option %s\n", required ? "REQUIRED" : "OPTIONAL", option_name);
+ char *option_line = get_option(opt, (char*)option_name); //will return NULL if option_name is NULL
+ if (option_line)
+ {
+ char *reason;
+ if (!is_valid(option_line, psmap[i].type, psmap[i].member_width, &reason))
+ {
+ LOG("\t%s", option_line);
+ LOG("ERROR: %s: %s\n", option_name, reason);
+ }
+ }
+ else if (required)
+ {
+ LOG("ERROR: required option %s not found\n", option_name);
+ }
+ free(option_line);
+ i++;
+ }
+}
+
// take care of updating .xml/.opt files if needed
bool config_process(const char *xml_filepath){
// look for _update marker file
@@ -239,4 +506,4 @@ bool config_process(const char *xml_filepath){
bool res = config_make_opt(xml_filepath);
return res;
-}
+}
\ No newline at end of file
diff --git a/popnhax/config.h b/popnhax/config.h
index 36b3ff6..061aade 100644
--- a/popnhax/config.h
+++ b/popnhax/config.h
@@ -73,5 +73,6 @@ struct popnhax_config {
};
bool config_process(const char *filepath); // take care of updating .xml/.opt files if needed
+void config_diag(const char *xml_filepath, const struct property_psmap *psmap);
#endif
diff --git a/popnhax/dllmain.cc b/popnhax/dllmain.cc
index d997e19..b07cbda 100644
--- a/popnhax/dllmain.cc
+++ b/popnhax/dllmain.cc
@@ -7917,7 +7917,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
if (!_load_config(g_config_fn, &config, config_psmap))
{
- LOG("FATAL ERROR: Could not parse %s\n", g_config_fn);
+ LOG("popnhax: FATAL ERROR: failed to load %s. Running advanced diagnostic...\n", g_config_fn);
+ config_diag(g_config_fn, config_psmap);
exit(1);
}
diff --git a/util/psmap.h b/util/psmap.h
new file mode 100644
index 0000000..8bcc494
--- /dev/null
+++ b/util/psmap.h
@@ -0,0 +1,47 @@
+#ifndef __PSMAP_H__
+#define __PSMAP_H__
+
+struct property_psmap {
+ uint8_t type;
+ uint8_t has_default;
+ uint16_t field_offset;
+ uint32_t member_width;
+ const char *path;
+ uintptr_t default_value;
+} __attribute__((packed));
+
+enum psmap_property_type {
+ PSMAP_PROPERTY_TYPE_S8 = 0x02,
+ PSMAP_PROPERTY_TYPE_U8 = 0x03,
+ PSMAP_PROPERTY_TYPE_S16 = 0x04,
+ PSMAP_PROPERTY_TYPE_U16 = 0x05,
+ PSMAP_PROPERTY_TYPE_S32 = 0x06,
+ PSMAP_PROPERTY_TYPE_U32 = 0x07,
+ PSMAP_PROPERTY_TYPE_S64 = 0x08,
+ PSMAP_PROPERTY_TYPE_U64 = 0x09,
+ PSMAP_PROPERTY_TYPE_STR = 0x0A,
+ PSMAP_PROPERTY_TYPE_FLOAT = 0x0D,
+ PSMAP_PROPERTY_TYPE_ATTR = 0x2D,
+ PSMAP_PROPERTY_TYPE_BOOL = 0x32,
+};
+
+#ifndef __offsetof
+#define __offsetof(_s, _f) __builtin_offsetof(_s, _f)
+#endif
+
+#ifndef __fieldwidth
+#define __fieldwidth(_s, _f) sizeof((((_s *)NULL)->_f))
+#endif
+
+#define PSMAP_BEGIN(_name, _type) const _type struct property_psmap _name[] = {
+#define PSMAP_MEMBER_REQ(_type, _struct, _member, _path) \
+ {_type, false, __offsetof(_struct, _member), __fieldwidth(_struct, _member), _path, 0},
+#define PSMAP_MEMBER_OPT(_type, _struct, _member, _path, _def) \
+ {_type, true, __offsetof(_struct, _member), __fieldwidth(_struct, _member), \
+ _path, (uintptr_t)_def},
+#define PSMAP_END \
+ { 0xff, false, 0, 0, "NULL", 0 } \
+ } \
+ ;
+
+#endif
\ No newline at end of file
diff --git a/util/xmlprop.hpp b/util/xmlprop.hpp
index ec67784..8d98483 100644
--- a/util/xmlprop.hpp
+++ b/util/xmlprop.hpp
@@ -5,49 +5,7 @@
#include "imports/avs.h"
#include "util/membuf.h"
-
-struct property_psmap {
- uint8_t type;
- uint8_t has_default;
- uint16_t field_offset;
- uint32_t member_width;
- const char *path;
- uintptr_t default_value;
-} __attribute__((packed));
-
-enum psmap_property_type {
- PSMAP_PROPERTY_TYPE_S8 = 0x02,
- PSMAP_PROPERTY_TYPE_U8 = 0x03,
- PSMAP_PROPERTY_TYPE_S16 = 0x04,
- PSMAP_PROPERTY_TYPE_U16 = 0x05,
- PSMAP_PROPERTY_TYPE_S32 = 0x06,
- PSMAP_PROPERTY_TYPE_U32 = 0x07,
- PSMAP_PROPERTY_TYPE_S64 = 0x08,
- PSMAP_PROPERTY_TYPE_U64 = 0x09,
- PSMAP_PROPERTY_TYPE_STR = 0x0A,
- PSMAP_PROPERTY_TYPE_FLOAT = 0x0D,
- PSMAP_PROPERTY_TYPE_ATTR = 0x2D,
- PSMAP_PROPERTY_TYPE_BOOL = 0x32,
-};
-
-#ifndef __offsetof
-#define __offsetof(_s, _f) __builtin_offsetof(_s, _f)
-#endif
-
-#ifndef __fieldwidth
-#define __fieldwidth(_s, _f) sizeof((((_s *)NULL)->_f))
-#endif
-
-#define PSMAP_BEGIN(_name, _type) const _type struct property_psmap _name[] = {
-#define PSMAP_MEMBER_REQ(_type, _struct, _member, _path) \
- {_type, false, __offsetof(_struct, _member), __fieldwidth(_struct, _member), _path, 0},
-#define PSMAP_MEMBER_OPT(_type, _struct, _member, _path, _def) \
- {_type, true, __offsetof(_struct, _member), __fieldwidth(_struct, _member), \
- _path, (uintptr_t)_def},
-#define PSMAP_END \
- { 0xff, false, 0, 0, "NULL", 0 } \
- } \
- ;
+#include "util/psmap.h"
int reader_callback(uint32_t context, void *bytes, size_t nbytes) {
return fread(bytes, 1, nbytes, (FILE *)TlsGetValue(context));
@@ -127,13 +85,7 @@ struct property *load_prop_file(const char *filename) {
bool _load_config(const char *filename, void *dest, const struct property_psmap *psmap) {
struct property *config_xml = load_prop_file(filename);
- if (!config_xml) {
- printf("Couldn't load xml file: %s\n", filename);
- return false;
- }
-
- if (!(property_psmap_import(config_xml, nullptr, dest, psmap))) {
- printf("Couldn't parse psmap\n");
+ if (!config_xml || !(property_psmap_import(config_xml, nullptr, dest, psmap))) {
return false;
}