From dd03b69609e53544eea8aaa9547cd910bd8e7454 Mon Sep 17 00:00:00 2001 From: Spoike Date: Sat, 19 Feb 2022 20:48:40 +0000 Subject: [PATCH] Move our gltf's json parser into the engine-proper, implement QC builtins to make json parsing available to qc. API defined by Joshua Ashton. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6188 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- CMakeLists.txt | 3 + engine/Makefile | 3 +- engine/client/pr_csqc.c | 13 + engine/client/pr_menu.c | 13 + engine/common/common.h | 48 +++ engine/common/json.c | 675 ++++++++++++++++++++++++++++++++++++++ engine/common/pr_bgcmd.c | 250 ++++++++++++++ engine/common/pr_common.h | 11 + engine/server/pr_cmds.c | 29 ++ plugins/Makefile | 2 +- plugins/models/gltf.c | 549 +------------------------------ 11 files changed, 1049 insertions(+), 547 deletions(-) create mode 100644 engine/common/json.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 86f1d3b20..706ab93a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -527,6 +527,7 @@ SET(FTE_COMMON_FILES engine/common/com_mesh.c engine/common/com_bih.c engine/common/common.c + engine/common/json.c engine/common/crc.c engine/common/cvar.c engine/common/fs.c @@ -883,6 +884,7 @@ ELSE() ADD_EXECUTABLE(iqmtool iqm/iqm.cpp plugins/models/gltf.c + engine/common/json.c engine/client/image.c imgtool.c iqm/iqm.h @@ -1262,6 +1264,7 @@ IF(FTE_PLUG_MODELS) plugins/plugin.c plugins/models/models.c plugins/models/gltf.c + engine/common/json.c plugins/models/exportiqm.c ) SET_TARGET_PROPERTIES(plug_models PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN;${FTE_LIB_DEFINES}") diff --git a/engine/Makefile b/engine/Makefile index 578322a2d..8fe183241 100644 --- a/engine/Makefile +++ b/engine/Makefile @@ -901,6 +901,7 @@ COMMON_OBJS = \ com_bih.o \ com_mesh.o \ common.o \ + json.o \ cvar.o \ cmd.o \ crc.o \ @@ -2456,7 +2457,7 @@ $(RELEASE_DIR)/httpserver$(BITS)$(EXEPOSTFIX): $(HTTP_OBJECTS) $(CC) -o $@ -Icommon -Iclient -Iqclib -Igl -Iserver -DWEBSERVER -DWEBSVONLY -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -DNO_PNG $(HTTP_OBJECTS) httpserver: $(RELEASE_DIR)/httpserver$(BITS)$(EXEPOSTFIX) -IQM_OBJECTS=../iqm/iqm.cpp ../imgtool.c client/image.c ../plugins/models/gltf.c +IQM_OBJECTS=../iqm/iqm.cpp ../imgtool.c client/image.c common/json.c ../plugins/models/gltf.c $(RELEASE_DIR)/iqmtool$(BITS)$(EXEPOSTFIX): $(IQM_OBJECTS) ifeq (win,$(findstring win,$(FTE_TARGET))) $(CC) -o $@ $(IQM_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver $(ALL_CFLAGS) $(CLIENTLIBFLAGS) -DIQMTOOL $(BASELDFLAGS) $(CLIENTLDDEPS) --static -static-libgcc -static-libstdc++ -lstdc++ -lm -Os diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index c532da62c..7d5f61c63 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -7069,6 +7069,19 @@ static struct { {"sqlversion", PF_NoCSQC, 257}, // #257 string(float serveridx) sqlversion (FTE_SQL) {"sqlreadfloat", PF_NoCSQC, 258}, // #258 float(float serveridx, float queryidx, float row, float column) sqlreadfloat (FTE_SQL) + {"json_parse", PF_json_parse, 0}, + {"json_free", PF_memfree, 0}, + {"json_get_value_type", PF_json_get_value_type, 0}, + {"json_get_integer", PF_json_get_integer, 0}, + {"json_get_float", PF_json_get_float, 0}, + {"json_get_string", PF_json_get_string, 0}, + {"json_find_object_child", PF_json_find_object_child, 0}, + {"json_get_length", PF_json_get_length, 0}, + {"json_get_child_at_index", PF_json_get_child_at_index, 0}, + {"json_get_name", PF_json_get_name, 0}, + + {"js_run_script", PF_js_run_script, 0}, + {"stoi", PF_stoi, 259}, {"itos", PF_itos, 260}, {"stoh", PF_stoh, 261}, diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index bb7fbcd2d..802454fc8 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -2323,6 +2323,19 @@ static struct { {"vtos", PF_vtos, 19}, {"etos", PF_etos, 20}, {"stof", PF_stof, 21}, + + {"json_parse", PF_json_parse, 0}, + {"json_free", PF_memfree, 0}, + {"json_get_value_type", PF_json_get_value_type, 0}, + {"json_get_integer", PF_json_get_integer, 0}, + {"json_get_float", PF_json_get_float, 0}, + {"json_get_string", PF_json_get_string, 0}, + {"json_find_object_child", PF_json_find_object_child, 0}, + {"json_get_length", PF_json_get_length, 0}, + {"json_get_child_at_index", PF_json_get_child_at_index, 0}, + {"json_get_name", PF_json_get_name, 0}, + + {"js_run_script", PF_js_run_script, 0}, {"stoi", PF_stoi, 0}, {"itos", PF_itos, 0}, diff --git a/engine/common/common.h b/engine/common/common.h index 0e777ed35..0acb24c24 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -1042,3 +1042,51 @@ typedef struct char str[128]; } date_t; void COM_TimeOfDay(date_t *date); + +//json.c +typedef struct json_s +{ + enum + { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null + } type; + const char *bodystart; + const char *bodyend; + + struct json_s *parent; + struct json_s *child; + struct json_s *sibling; + union + { + struct json_s **childlink; + struct json_s **array; + }; + size_t arraymax; //note that child+siblings are kinda updated with arrays too, just not orphaned cleanly... + qboolean used; //set to say when something actually read/walked it, so we can flag unsupported things gracefully + char name[1]; +} json_t; +//main functions +json_t *JSON_Parse(const char *json); //simple parsing. returns NULL if there's any kind of parsing error. +void JSON_Destroy(json_t *t); //call this on the root once you're done +json_t *JSON_FindChild(json_t *t, const char *child); //find a named child in an object (or an array, if you're lazy) +json_t *JSON_GetIndexed(json_t *t, unsigned int idx); //find an indexed child in an array (or object, though slower) +double JSON_ReadFloat(json_t *t, double fallback); //read a numeric value. +size_t JSON_ReadBody(json_t *t, char *out, size_t outsize); //read a string value. +//exotic fancy functions +json_t *JSON_ParseNode(json_t *t, const char *namestart, const char *nameend, const char *json, int *jsonpos, int jsonlen); //fancy parsing. +//helpers +json_t *JSON_FindIndexedChild(json_t *t, const char *child, unsigned int idx); //just a helper. +qboolean JSON_Equals(json_t *t, const char *child, const char *expected); //compares a bit faster. +quintptr_t JSON_GetUInteger(json_t *t, const char *child, unsigned int fallback); //grabs a child node's uint value +qintptr_t JSON_GetInteger(json_t *t, const char *child, int fallback); //grabs a child node's int value +qintptr_t JSON_GetIndexedInteger(json_t *t, unsigned int idx, int fallback); //grabs an int from an array +double JSON_GetFloat(json_t *t, const char *child, double fallback); //grabs a child node's numeric value +double JSON_GetIndexedFloat(json_t *t, unsigned int idx, double fallback); //grabs a numeric value from an array +const char *JSON_GetString(json_t *t, const char *child, char *buffer, size_t buffersize, const char *fallback); //grabs a child node's string value. do your own damn indexing for an array. +//there's no write logic. Its probably easier to just snprintf it or something anyway. diff --git a/engine/common/json.c b/engine/common/json.c new file mode 100644 index 000000000..69a1f722a --- /dev/null +++ b/engine/common/json.c @@ -0,0 +1,675 @@ +#include "quakedef.h" + +//node destruction +static void JSON_Orphan(json_t *t) +{ + if (t->parent) + { + json_t *p = t->parent, **l = &p->child; + if (p->arraymax) + { + size_t idx = atoi(t->name); + if (idx <= p->arraymax) + p->array[idx] = NULL; + //FIXME: sibling links are screwed. be careful iterrating after a removal. + } + else + { + while (*l) + { + if (*l == t) + { + *l = t->sibling; + if (*l) + p->childlink = l; + break; + } + l = &(*l)->sibling; + } + } + t->parent = NULL; + t->sibling = NULL; + } +} +void JSON_Destroy(json_t *t) +{ + if (t) + { + if (t->arraymax) + { + size_t idx; + for (idx = 0; idx < t->arraymax; idx++) + if (t->array[idx]) + JSON_Destroy(t->array[idx]); + free(t->array); + } + else + { + while(t->child) + JSON_Destroy(t->child); + } + JSON_Orphan(t); + free(t); + } +} + +//node creation +static json_t *JSON_CreateNode(json_t *parent, const char *namestart, const char *nameend, const char *bodystart, const char *bodyend, int type) +{ + json_t *j; + qboolean dupbody = false; + if (namestart && !nameend) + nameend = namestart+strlen(namestart); + if (bodystart && !bodyend) + { + dupbody = true; + bodyend = bodystart+strlen(bodystart); + } + //FIXME: escapes within names are a thing. a stupid thing, but still a thing. + j = malloc(sizeof(*j) + nameend-namestart + (dupbody?1+bodyend-bodystart:0)); + memcpy(j->name, namestart, nameend-namestart); + j->name[nameend-namestart] = 0; + j->bodystart = bodystart; + j->bodyend = bodyend; + + j->child = NULL; + j->sibling = NULL; + j->arraymax = 0; + j->type = type; + if (type == json_type_array) + { //pre-initialise the array a bit. + j->arraymax = 32; + j->array = calloc(j->arraymax, sizeof(*j->array)); + } + else + j->childlink = &j->child; + j->parent = parent; + if (parent) + { + if (parent->arraymax) + { + size_t idx = atoi(j->name); + if (idx >= parent->arraymax) + { + size_t oldmax = parent->arraymax; + parent->arraymax = max(idx+1, parent->arraymax*2); + parent->array = realloc(parent->array, sizeof(*parent->array)*parent->arraymax); + while (oldmax < parent->arraymax) + parent->array[oldmax++] = NULL; //make sure there's no gaps. + } + parent->array[idx] = j; + if (!idx) + parent->child = j; + else if (parent->array[idx-1]) + parent->array[idx-1]->sibling = j; + } + else + { + *parent->childlink = j; + parent->childlink = &j->sibling; + } + j->used = false; + } + else + j->used = true; + + if (dupbody) + { + char *bod = j->name + (nameend-namestart)+1; + j->bodystart = bod; + j->bodyend = j->bodystart + (bodyend-bodystart); + memcpy(bod, bodystart, bodyend-bodystart); + bod[bodyend-bodystart] = 0; + } + return j; +} + +//node parsing +static void JSON_SkipWhite(const char *msg, int *pos, int max) +{ + while (*pos < max) + { + //if its simple whitespace then keep skipping over it + if (msg[*pos] == ' ' || + msg[*pos] == '\t' || + msg[*pos] == '\r' || + msg[*pos] == '\n' ) + { + *pos+=1; + continue; + } + + //BEGIN NON-STANDARD - Note that comments are NOT part of json, but people insist on using them anyway (c-style, like javascript). + else if (msg[*pos] == '/' && *pos+1 < max) + { + if (msg[*pos+1] == '/') + { //C++ style single-line comments that continue till the next line break + *pos+=2; + while (*pos < max) + { + if (msg[*pos] == '\r' || msg[*pos] == '\n') + break; //ends on first line break (the break is then whitespace will will be skipped naturally) + *pos+=1; //not yet + } + continue; + } + else if (msg[*pos+1] == '*') + { /*C style multi-line comment*/ + *pos+=2; + while (*pos+1 < max) + { + if (msg[*pos] == '*' && msg[*pos+1] == '/') + { + *pos+=2; //skip past the terminator ready for whitespace or trailing comments directly after + break; + } + *pos+=1; //not yet + } + continue; + } + } + //END NON-STANDARD + break; //not whitespace/comment/etc. + } +} +//handles the not-null-terminated nature of our bodies. +double JSON_ReadFloat(json_t *t, double fallback) +{ + if (t) + { + char tmp[MAX_QPATH]; + size_t l = t->bodyend-t->bodystart; + if (l > MAX_QPATH-1) + l = MAX_QPATH-1; + memcpy(tmp, t->bodystart, l); + tmp[l] = 0; + return atof(tmp); + } + return fallback; +} + +#if defined(FTEPLUGIN) || defined(IQMTOOL) //grr, stupid copypasta. +unsigned int utf8_encode(void *out, unsigned int unicode, int maxlen) +{ + unsigned int bcount = 1; + unsigned int lim = 0x80; + unsigned int shift; + if (!unicode) + { //modified utf-8 encodes encapsulated nulls as over-long. + bcount = 2; + } + else + { + while (unicode >= lim) + { + if (bcount == 1) + lim <<= 4; + else if (bcount < 7) + lim <<= 5; + else + lim <<= 6; + bcount++; + } + } + + //error if needed + if (maxlen < bcount) + return 0; + + //output it. + if (bcount == 1) + { + *((unsigned char *)out) = (unsigned char)(unicode&0x7f); + out = (char*)out + 1; + } + else + { + shift = bcount*6; + shift = shift-6; + *((unsigned char *)out) = (unsigned char)((unicode>>shift)&(0x0000007f>>bcount)) | ((0xffffff00 >> bcount) & 0xff); + out = (char*)out + 1; + do + { + shift = shift-6; + *((unsigned char *)out) = (unsigned char)((unicode>>shift)&0x3f) | 0x80; + out = (char*)out + 1; + } + while(shift); + } + return bcount; +} +#endif +static int dehex(int chr, unsigned int *ret, int shift) +{ + if (chr >= '0' && chr <= '9') + *ret |= (chr-'0') << shift; + else if (chr >= 'A' && chr <= 'F') + *ret |= (chr-'A'+10) << shift; + else if (chr >= 'a' && chr <= 'f') + *ret |= (chr-'a'+10) << shift; + else + return 0; + return 1; +} + +//writes the body to a null-terminated string, handling escapes as needed. +//returns required body length (without terminator) (NOTE: return value is not escape-aware, so this is an over-estimate). +size_t JSON_ReadBody(json_t *t, char *out, size_t outsize) +{ +// size_t bodysize; + if (!t) + { + if (out) + *out = 0; + return 0; + } + if (out && outsize) + { + char *outend = out+outsize-1; //compensate for null terminator + const char *in = t->bodystart; + while (in < t->bodyend && out < outend) + { + if (*in == '\\') + { + if (++in < t->bodyend) + { + switch(*in++) + { + case '\"': *out++ = '\"'; break; + case '\\': *out++ = '\\'; break; + case '/': *out++ = '/'; break; //json is not C... + case 'b': *out++ = '\b'; break; + case 'f': *out++ = '\f'; break; + case 'n': *out++ = '\n'; break; + case 'r': *out++ = '\r'; break; + case 't': *out++ = '\t'; break; + case 'u': + { + unsigned int code = 0, low = 0; + if (dehex(out[0], &code, 12) && //javscript escapes are strictly 16bit... + dehex(out[1], &code, 8) && + dehex(out[2], &code, 4) && + dehex(out[3], &code, 0) ) + { + in += 4; + //and as its actually UTF-16 we need to waste more cpu cycles on this insanity when its a high-surrogate. + if (code >= 0xd800u && code < 0xdc00u && out[4] == '\\' && out[5] == 'u' && + dehex(out[6], &low, 12) && + dehex(out[7], &low, 8) && + dehex(out[8], &low, 4) && + dehex(out[9], &low, 0) && low >= 0xdc00 && low < 0xde00) + { + in += 6; + code = 0x10000 + (code-0xd800)*0x400 + (low-0xdc00); + } + + out += utf8_encode(out, code, outend-out); + break; + } + } + //fall through. + default: + //unknown escape. will warn when actually reading it. + *out++ = '\\'; + if (out < outend) + *out++ = in[-1]; + break; + } + } + else + *out++ = '\\'; //error... + } + else + *out++ = *in++; + } + *out = 0; + } + return t->bodyend-t->bodystart; +} + +static qboolean JSON_ParseString(char const*msg, int *pos, int max, char const**start, char const** end) +{ + if (*pos < max && msg[*pos] == '\"') + { //quoted string + //FIXME: no handling of backslash followed by one of "\/bfnrtu + *pos+=1; + *start = msg+*pos; + while (*pos < max) + { + if (msg[*pos] == '\"') + break; + if (msg[*pos] == '\\') + { //escapes are expanded elsewhere, we're just skipping over them here. + switch(msg[*pos+1]) + { + case '\"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + *pos+=2; + break; + case 'u': + *pos+=2; + //*pos+=4; //4 hex digits, not escapes so just wait till later before parsing them properly. + break; + default: + //unknown escape. will warn when actually reading it. + *pos+=1; + break; + } + } + else + *pos+=1; + } + if (*pos < max && msg[*pos] == '\"') + { + *end = msg+*pos; + *pos+=1; + return true; + } + } + else + { //name + *start = msg+*pos; + while (*pos < max + && msg[*pos] != ' ' + && msg[*pos] != '\t' + && msg[*pos] != '\r' + && msg[*pos] != '\n' + && msg[*pos] != ':' + && msg[*pos] != ',' + && msg[*pos] != '}' + && msg[*pos] != '{' + && msg[*pos] != '[' + && msg[*pos] != ']') + { + *pos+=1; + } + *end = msg+*pos; + if (*start != *end) + return true; + } + *end = *start; + return false; +} +json_t *JSON_ParseNode(json_t *t, const char *namestart, const char *nameend, const char *json, int *jsonpos, int jsonlen) +{ + const char *childstart, *childend; + JSON_SkipWhite(json, jsonpos, jsonlen); + + if (*jsonpos < jsonlen) + { + if (json[*jsonpos] == '{') + { + *jsonpos+=1; + JSON_SkipWhite(json, jsonpos, jsonlen); + + t = JSON_CreateNode(t, namestart, nameend, NULL, NULL, json_type_object); + + while (*jsonpos < jsonlen && json[*jsonpos] == '\"') + { + if (!JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) + break; + JSON_SkipWhite(json, jsonpos, jsonlen); + if (*jsonpos < jsonlen && json[*jsonpos] == ':') + { + *jsonpos+=1; + if (!JSON_ParseNode(t, childstart, childend, json, jsonpos, jsonlen)) + break; + } + JSON_SkipWhite(json, jsonpos, jsonlen); + + if (*jsonpos < jsonlen && json[*jsonpos] == ',') + { + *jsonpos+=1; + JSON_SkipWhite(json, jsonpos, jsonlen); + continue; + } + break; + } + + if (*jsonpos < jsonlen && json[*jsonpos] == '}') + { + *jsonpos+=1; + return t; + } + JSON_Destroy(t); + } + else if (json[*jsonpos] == '[') + { + char idxname[MAX_QPATH]; + unsigned int idx = 0; + *jsonpos+=1; + JSON_SkipWhite(json, jsonpos, jsonlen); + + t = JSON_CreateNode(t, namestart, nameend, NULL, NULL, json_type_array); + + for(;;) + { + Q_snprintfz(idxname, sizeof(idxname), "%u", idx++); + if (!JSON_ParseNode(t, idxname, NULL, json, jsonpos, jsonlen)) + break; + + if (*jsonpos < jsonlen && json[*jsonpos] == ',') + { + *jsonpos+=1; + JSON_SkipWhite(json, jsonpos, jsonlen); + continue; + } + break; + } + + JSON_SkipWhite(json, jsonpos, jsonlen); + if (*jsonpos < jsonlen && json[*jsonpos] == ']') + { + *jsonpos+=1; + return t; + } + JSON_Destroy(t); + } + else + { + if (json[*jsonpos] == '\"') + { + if (JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) + return JSON_CreateNode(t, namestart, nameend, childstart, childend, json_type_string); + } + else + { + if (JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) + { + if (childend-childstart == 4 && !strncasecmp(childstart, "true", 4)) + return JSON_CreateNode(t, namestart, nameend, childstart, childend, json_type_true); + else if (childend-childstart == 5 && !strncasecmp(childstart, "false", 5)) + return JSON_CreateNode(t, namestart, nameend, childstart, childend, json_type_false); + else if (childend-childstart == 4 && !strncasecmp(childstart, "null", 4)) + return JSON_CreateNode(t, namestart, nameend, childstart, childend, json_type_null); + else + return JSON_CreateNode(t, namestart, nameend, childstart, childend, json_type_number); + } + } + } + } + return NULL; +} +json_t *JSON_Parse(const char *json) +{ + size_t jsonlen = strlen(json); + int pos = (json[0] == '\xef' && json[1] == '\xbb' && json[2] == '\xbf')?3:0; //skip a utf-8 bom, if present, to be a bit more permissive. + json_t *n = JSON_ParseNode(NULL, NULL, NULL, json, &pos, jsonlen); + JSON_SkipWhite(json, &pos, jsonlen); + if (pos == jsonlen) + return n; + JSON_Destroy(n); //trailing junk?... fail it. + return NULL; +} + + + +//we don't really understand arrays here (we just treat them as tables) so eg "foo.0.bar" to find t->foo[0]->bar +json_t *JSON_FindChild(json_t *t, const char *child) +{ + if (t) + { + size_t nl; + const char *dot = strchr(child, '.'); + if (dot) + nl = dot-child; + else + nl = strlen(child); + if (t->arraymax) + { + size_t idx = atoi(child); + if (idx < t->arraymax) + { + t = t->array[idx]; + if (t) + goto found; + } + } + else + { + for (t = t->child; t; t = t->sibling) + { + if (!strncmp(t->name, child, nl) && (t->name[nl] == '.' || !t->name[nl])) + { +found: + child+=nl; + t->used = true; + if (*child == '.') + return JSON_FindChild(t, child+1); + return t; + } + } + } + } + return NULL; +} +json_t *JSON_GetIndexed(json_t *t, unsigned int idx) +{ + if (t) + { + if (t->arraymax) + { + if (idx < t->arraymax) + { + t = t->array[idx]; + if (t) + { + t->used = true; + return t; + } + } + } + else + { + for (t = t->child; t; t = t->sibling, idx--) + { + if (!idx) + { + t->used = true; + return t; + } + } + } + } + return NULL; +} + + +//helpers... +json_t *JSON_FindIndexedChild(json_t *t, const char *child, unsigned int idx) +{ + if (child) + t = JSON_FindChild(t, child); + return JSON_GetIndexed(t, idx); +} +qboolean JSON_Equals(json_t *t, const char *child, const char *expected) +{ + if (child) + t = JSON_FindChild(t, child); + if (t && t->bodyend-t->bodystart == strlen(expected)) + return !strncmp(t->bodystart, expected, t->bodyend-t->bodystart); + return false; +} +quintptr_t JSON_GetUInteger(json_t *t, const char *child, unsigned int fallback) +{ + if (child) + t = JSON_FindChild(t, child); + if (t) + { //copy it to another buffer. can probably skip that tbh. + char tmp[MAX_QPATH]; + char *trail; + size_t l = t->bodyend-t->bodystart; + quintptr_t r; + if (l > MAX_QPATH-1) + l = MAX_QPATH-1; + memcpy(tmp, t->bodystart, l); + tmp[l] = 0; + if (!strcmp(tmp, "false")) //special cases, for booleans + return 0u; + if (!strcmp(tmp, "true")) //special cases, for booleans + return 1u; + r = (quintptr_t)strtoull(tmp, &trail, 0); + if (!*trail) + return r; + } + return fallback; +} +qintptr_t JSON_GetInteger(json_t *t, const char *child, int fallback) +{ + if (child) + t = JSON_FindChild(t, child); + if (t) + { //copy it to another buffer. can probably skip that tbh. + char tmp[MAX_QPATH]; + char *trail; + size_t l = t->bodyend-t->bodystart; + qintptr_t r; + if (l > MAX_QPATH-1) + l = MAX_QPATH-1; + memcpy(tmp, t->bodystart, l); + tmp[l] = 0; + if (!strcmp(tmp, "false")) //special cases, for booleans + return 0; + if (!strcmp(tmp, "true")) //special cases, for booleans + return 1; + r = (qintptr_t)strtoll(tmp, &trail, 0); + if (!*trail) + return r; + } + return fallback; +} +qintptr_t JSON_GetIndexedInteger(json_t *t, unsigned int idx, int fallback) +{ + char idxname[MAX_QPATH]; + Q_snprintfz(idxname, sizeof(idxname), "%u", idx); + return JSON_GetInteger(t, idxname, fallback); +} +double JSON_GetFloat(json_t *t, const char *child, double fallback) +{ + if (child) + t = JSON_FindChild(t, child); + return JSON_ReadFloat(t, fallback); +} +double JSON_GetIndexedFloat(json_t *t, unsigned int idx, double fallback) +{ + char idxname[MAX_QPATH]; + Q_snprintfz(idxname, sizeof(idxname), "%u", idx); + return JSON_GetFloat(t, idxname, fallback); +} +const char *JSON_GetString(json_t *t, const char *child, char *buffer, size_t buffersize, const char *fallback) +{ + if (child) + t = JSON_FindChild(t, child); + if (t) + { //copy it to another buffer. can probably skip that tbh. + JSON_ReadBody(t, buffer, buffersize); + return buffer; + } + return fallback; +} diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 647a690d8..a3b8e6c79 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -685,6 +685,256 @@ void VARGS PR_CB_Free(void *mem) BZ_Free(mem); } +//////////////////////////////////////////////////// +//JSON stuff. +typedef struct qcjson_s +{ + int type; + string_t name; + union + { + struct + { + int childptr; + unsigned int count; //for arrays and objects. + }; + double num; //for number types. + string_t strofs; //for strings. + } u; +} qcjson_t; +static qcjson_t json_null = {json_type_null}; //dummy safe node that we poke on bad inputs. +#define JSONFromQC(qcptr) ((unsigned int)qcptr >= prinst->stringtablesize-sizeof(qcjson_t))?PR_BIError (prinst, "PR_JSONFromQC: bad pointer"),&json_null:qcptr?(qcjson_t*)((char*)prinst->stringtable + qcptr):&json_null +#define RETURN_JSON(r) G_INT(OFS_RETURN) = (const char*)r - prinst->stringtable + +#define JSON_PERSIST_STRINGDATA //slower but safer +static void PR_JSON_Count(json_t *r, size_t *nodes, size_t *strings) //counts the nodes and bytes for string data. +{ + *nodes+=1; + if (*r->name) + *strings += strlen(r->name)+1; + safeswitch(r->type) + { + case json_type_object: + case json_type_array: + for(r = r->child; r; r = r->sibling) + PR_JSON_Count(r, nodes, strings); + break; + case json_type_string: +#ifndef JSON_PERSIST_STRINGDATA + *strings += JSON_ReadBody(r, NULL, 0)+1; + break; +#endif + case json_type_true: + case json_type_false: + case json_type_null: + case json_type_number: + safedefault: + break; + } +} +static void PR_JSON_Linearise(pubprogfuncs_t *prinst, json_t *r, qcjson_t *out, qcjson_t **nodes, char **strings) +{ + json_t *c; + size_t children = 0; + out->type = r->type; + + //give it a name + if (*r->name) + { + out->name = *strings - prinst->stringtable; + memcpy(*strings, r->name, strlen(r->name)+1); + *strings += strlen(r->name)+1; + } + + //make sure its values are valid. + safeswitch(out->type) + { + case json_type_string: +#ifdef JSON_PERSIST_STRINGDATA + { //allocate a tempstring for each, so that they last beyond node destruction. + size_t sz = JSON_ReadBody(r, NULL, 0); + char *tmp = alloca(sz+1); + JSON_ReadBody(r, tmp, sz+1); + out->u.strofs = PR_TempString(prinst, tmp); + } +#else + out->u.strofs = *strings - prinst->stringtable; + JSON_ReadBody(r, *strings, prinst->stringtablesize - out->u.strofs-1); + *strings += strlen(*strings)+1; +#endif + break; + case json_type_object: + case json_type_array: + out->u.childptr = (char*)*nodes - prinst->stringtable; + for(c = r->child; c; c = c->sibling) + children++; + out->u.count = children; + out = *nodes; + *nodes+=children; + for(c = r->child; c; c = c->sibling, out++) + PR_JSON_Linearise(prinst, c, out, nodes, strings); + break; + case json_type_false: + case json_type_null: + out->u.num = false; + break; + case json_type_true: + out->u.num = true; + break; + case json_type_number: + safedefault: + out->u.num = JSON_ReadFloat(r, 0); + break; + } +} +void QCBUILTIN PF_json_parse(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + json_t *inroot = JSON_Parse(PR_GetStringOfs(prinst, OFS_PARM0)); + qcjson_t *outroot, *outnodes; + char *outstrings; + size_t n = 0, s = 0; + if (inroot) + { + //linearise the json nodes for easy access (just use qc pointers instead of needing lots of separate handles) + PR_JSON_Count(inroot, &n, &s); + outnodes = prinst->AddressableAlloc(prinst, sizeof(*outroot)*n + s); + outstrings = (char*)(outnodes + n); + + outroot = outnodes++; + PR_JSON_Linearise(prinst, inroot, outroot, &outnodes, &outstrings); + + JSON_Destroy(inroot); //our input string becomes irrelevant at this point + + RETURN_JSON(outroot); + } + else + G_INT(OFS_RETURN) = 0; +} + +void QCBUILTIN PF_json_get_value_type(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + G_INT(OFS_RETURN) = handle->type; +} +void QCBUILTIN PF_json_get_name(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + G_INT(OFS_RETURN) = handle->name; +} +void QCBUILTIN PF_json_get_integer(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + switch (handle->type) + { + case json_type_number: + case json_type_true: + case json_type_false: + G_INT(OFS_RETURN) = handle->u.num; + break; + case json_type_string: + G_INT(OFS_RETURN) = atoi(PR_GetString(prinst, handle->u.strofs)); + break; + default: + G_INT(OFS_RETURN) = 0; + break; + } +} +void QCBUILTIN PF_json_get_float(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + switch (handle->type) + { + case json_type_number: + case json_type_true: + case json_type_false: + G_FLOAT(OFS_RETURN) = handle->u.num; + break; + case json_type_string: + G_FLOAT(OFS_RETURN) = atof(PR_GetString(prinst, handle->u.strofs)); + break; + default: + G_FLOAT(OFS_RETURN) = 0; + break; + } +} +void QCBUILTIN PF_json_get_string(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + if (handle->type != json_type_string) + G_INT(OFS_RETURN) = 0; + else + G_INT(OFS_RETURN) = handle->u.strofs; +} +void QCBUILTIN PF_json_get_child_at_index(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + size_t idx = G_INT(OFS_PARM1); + G_INT(OFS_RETURN) = 0; //assume the worst + switch(handle->type) + { + case json_type_array: + case json_type_object: + if (idx < handle->u.count) + G_INT(OFS_RETURN) = handle->u.childptr + idx*sizeof(*handle); //don't really need to validate this here. + break; + default: + break; + } +} +void QCBUILTIN PF_json_get_length(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *handle = JSONFromQC(G_INT(OFS_PARM0)); + switch (handle->type) + { + case json_type_object: + case json_type_array: + G_INT(OFS_RETURN) = handle->u.count; + break; + default: + G_INT(OFS_RETURN) = 0; + break; + } +} +void QCBUILTIN PF_json_find_object_child(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + qcjson_t *parent = JSONFromQC(G_INT(OFS_PARM0)); + const char *childname = PR_GetStringOfs(prinst, OFS_PARM1); + unsigned int idx; + G_INT(OFS_RETURN) = 0; //assume the worst + switch (parent->type) + { + case json_type_object: + case json_type_array: + for (idx = 0; idx < parent->u.count; idx++) + { + qcjson_t *childnode = JSONFromQC(parent->u.childptr + idx*sizeof(*childnode)); + if (!strcmp(childname, PR_GetString(prinst, childnode->name))) + { + RETURN_JSON(childnode); + break; + } + } + break; + default: + break; + } +} + +#ifdef FTE_TARGET_WEB +#include +#endif +void QCBUILTIN PF_js_run_script(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ +#ifdef FTE_TARGET_WEB + const char *jscript = PR_GetStringOfs(prinst, OFS_PARM0); + const char *ret; + ret = emscripten_run_script_string(jscript); + if (ret) + G_INT(OFS_RETURN) = PR_TempString(prinst, ret); + else +#endif + G_INT(OFS_RETURN) = 0; +} //////////////////////////////////////////////////// //model functions diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 9951ec151..d1222a85f 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -560,6 +560,17 @@ void QCBUILTIN PF_hash_getcb (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl void QCBUILTIN PF_hash_delete (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_hash_getkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_parse (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_value_type (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_integer (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_float (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_string (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_find_object_child (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_length (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_child_at_index (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_json_get_name (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_js_run_script (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); + void QCBUILTIN PF_memalloc (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_memfree (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_memcpy (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 3808ae7ac..5fa1efb23 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -11367,6 +11367,23 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"sqlreadblob", PF_sqlreadblob, 0, 0, 0, 0, "int(float serveridx, float queryidx, float row, float column, __variant *ptr, int maxsize)"}, {"sqlescapeblob", PF_sqlescapeblob, 0, 0, 0, 0, "string(float serveridx, __variant *ptr, int maxsize)"}, + //basic API is from Joshua Ashton, though uses FTE's json parser instead. + {"json_parse", PF_json_parse, 0, 0, 0, 0, D("typedef struct json_s *json_t;\n" + "accessor jsonnode : json_t;\n" + "jsonnode(string)", "Parses the given JSON string.")}, + {"json_free", PF_memfree, 0, 0, 0, 0, D("void(jsonnode)", "Frees a json tree and all of its children. Must only be called on the root node.")}, + {"json_get_value_type",PF_json_get_value_type,0,0, 0, 0, D("enum json_type_e : int\n{\n\tJSON_TYPE_STRING,\n\tJSON_TYPE_NUMBER,\n\tJSON_TYPE_OBJECT,\n\tJSON_TYPE_ARRAY,\n\tJSON_TYPE_TRUE,\n\tJSON_TYPE_FALSE,\n\tJSON_TYPE_NULL\n};\n" + "json_type_e(jsonnode node)", "Get type of a JSON value.")}, + {"json_get_integer",PF_json_get_integer,0, 0, 0, 0, D("int(jsonnode node)", "Get an integer from a json node.")}, + {"json_get_float", PF_json_get_float, 0, 0, 0, 0, D("float(jsonnode node)", "Get a float from a json node.")}, + {"json_get_string", PF_json_get_string, 0, 0, 0, 0, D("string(jsonnode node)", "Get a string from a value. Returns a null string if its not a string type.")}, + {"json_find_object_child",PF_json_find_object_child,0,0,0, 0, D("jsonnode(jsonnode node, string)", "Find a child of a json object by name. Returns NULL if the handle couldn't be found.")}, + {"json_get_length", PF_json_get_length, 0, 0, 0, 0, D("int(jsonnode node)", "Get the length of a json array or object. Returns 0 if its not an array.")}, + {"json_get_child_at_index",PF_json_get_child_at_index,0,0,0, 0, D("jsonnode(jsonnode node, int childindex)", "Get the nth child of a json array or object. Returns NULL if the index is out of range.")}, + {"json_get_name", PF_json_get_name, 0, 0, 0, 0, D("string(jsonnode node)", "Gets the object's name (useful if you're using json_get_child_at_index to walk an object's children for whatever reason).")}, + + {"js_run_script", PF_js_run_script, 0, 0, 0, 0, D("string(string javascript)", "Runs the provided javascript snippet. This builtin functions only in emscripten builds, returning a null string on other systems (or if the script evaluates to null).")}, + {"stoi", PF_stoi, 0, 0, 0, 259, D("int(string)", "Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string.")}, {"itos", PF_itos, 0, 0, 0, 260, D("string(int)", "Converts the passed true integer into a base10 string.")}, {"stoh", PF_stoh, 0, 0, 0, 261, D("int(string)", "Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P")}, @@ -14263,6 +14280,18 @@ void PR_DumpPlatform_f(void) "};\n"); VFS_PRINTF(f, "#endif\n"); + VFS_PRINTF(f, + "accessor jsonnode : json_t\n{\n" + "\tinline get json_type_e type = json_get_value_type;\n" + "\tinline get string s = json_get_string;\n" + "\tinline get float f = json_get_float;\n" + "\tinline get __int i = json_get_integer;\n" + "\tinline get __int length = json_get_length;\n" + "\tinline get jsonnode a[__int key] = json_get_child_at_index;\n" //FIXME: remove this name when fteqcc can cope with dupes, for array[idx] + "\tinline get jsonnode[string key] = json_find_object_child;\n" + "\tinline get string name = json_get_name;\n" + "};\n"); + VFS_PRINTF(f, "#undef DEP_CSQC\n"); VFS_PRINTF(f, "#undef FTEDEP\n"); VFS_PRINTF(f, "#undef DEP\n"); diff --git a/plugins/Makefile b/plugins/Makefile index d175fe97c..fe2463d97 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -415,7 +415,7 @@ NATIVE_PLUGINS+=ezhud ###################################### #not really relevant now that gltf was made an internal plugin -$(PLUG_PREFIX)models$(PLUG_NATIVE_EXT): models/gltf.c models/exportiqm.c models/models.c plugin.c +$(PLUG_PREFIX)models$(PLUG_NATIVE_EXT): models/gltf.c models/exportiqm.c models/models.c plugin.c ../engine/common/json.c $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -o $@ -shared $(PLUG_CFLAGS) -Imodels $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) $(call EMBEDMETA,models,$@,Models Plugin,Kinda redundant now that the engine has gltf2 loading) #NATIVE_PLUGINS+=models diff --git a/plugins/models/gltf.c b/plugins/models/gltf.c index b52b150a6..b43d3f55a 100644 --- a/plugins/models/gltf.c +++ b/plugins/models/gltf.c @@ -74,547 +74,6 @@ void VARGS Q_snprintfcat (char *dest, size_t size, const char *fmt, ...) } -typedef struct json_s -{ - const char *bodystart; - const char *bodyend; - - struct json_s *parent; - struct json_s *child; - struct json_s *sibling; - union - { - struct json_s **childlink; - struct json_s **array; - }; - size_t arraymax; //note that child+siblings are kinda updated with arrays too, just not orphaned cleanly... - qboolean used; //set to say when something actually read/walked it, so we can flag unsupported things gracefully - char name[1]; -} json_t; - -//node destruction -static void JSON_Orphan(json_t *t) -{ - if (t->parent) - { - json_t *p = t->parent, **l = &p->child; - if (p->arraymax) - { - size_t idx = atoi(t->name); - if (idx <= p->arraymax) - p->array[idx] = NULL; - //FIXME: sibling links are screwed. be careful iterrating after a removal. - } - else - { - while (*l) - { - if (*l == t) - { - *l = t->sibling; - if (*l) - p->childlink = l; - break; - } - l = &(*l)->sibling; - } - } - t->parent = NULL; - t->sibling = NULL; - } -} -static void JSON_Destroy(json_t *t) -{ - if (t) - { - if (t->arraymax) - { - size_t idx; - for (idx = 0; idx < t->arraymax; idx++) - if (t->array[idx]) - JSON_Destroy(t->array[idx]); - free(t->array); - } - else - { - while(t->child) - JSON_Destroy(t->child); - } - JSON_Orphan(t); - free(t); - } -} - -//node creation -static json_t *JSON_CreateNode(json_t *parent, const char *namestart, const char *nameend, const char *bodystart, const char *bodyend, qboolean array) -{ - json_t *j; - qboolean dupbody = false; - if (namestart && !nameend) - nameend = namestart+strlen(namestart); - if (bodystart && !bodyend) - { - dupbody = true; - bodyend = bodystart+strlen(bodystart); - } - j = malloc(sizeof(*j) + nameend-namestart + (dupbody?1+bodyend-bodystart:0)); - memcpy(j->name, namestart, nameend-namestart); - j->name[nameend-namestart] = 0; - j->bodystart = bodystart; - j->bodyend = bodyend; - - j->child = NULL; - j->sibling = NULL; - j->arraymax = 0; - if (array) - { //pre-initialise the array a bit. - j->arraymax = 32; - j->array = calloc(j->arraymax, sizeof(*j->array)); - } - else - j->childlink = &j->child; - j->parent = parent; - if (parent) - { - if (parent->arraymax) - { - size_t idx = atoi(j->name); - if (idx >= parent->arraymax) - { - size_t oldmax = parent->arraymax; - parent->arraymax = max(idx+1, parent->arraymax*2); - parent->array = realloc(parent->array, sizeof(*parent->array)*parent->arraymax); - while (oldmax < parent->arraymax) - parent->array[oldmax++] = NULL; //make sure there's no gaps. - } - parent->array[idx] = j; - if (!idx) - parent->child = j; - else if (parent->array[idx-1]) - parent->array[idx-1]->sibling = j; - } - else - { - *parent->childlink = j; - parent->childlink = &j->sibling; - } - j->used = false; - } - else - j->used = true; - - if (dupbody) - { - char *bod = j->name + (nameend-namestart)+1; - j->bodystart = bod; - j->bodyend = j->bodystart + (bodyend-bodystart); - memcpy(bod, bodystart, bodyend-bodystart); - bod[bodyend-bodystart] = 0; - } - return j; -} - -//node parsing -static void JSON_SkipWhite(const char *msg, int *pos, int max) -{ - while (*pos < max) - { - //if its simple whitespace then keep skipping over it - if (msg[*pos] == ' ' || - msg[*pos] == '\t' || - msg[*pos] == '\r' || - msg[*pos] == '\n' ) - { - *pos+=1; - continue; - } - - //BEGIN NON-STANDARD - Note that comments are NOT part of json, but people insist on using them anyway (c-style, like javascript). - else if (msg[*pos] == '/' && *pos+1 < max) - { - if (msg[*pos+1] == '/') - { //C++ style single-line comments that continue till the next line break - *pos+=2; - while (*pos < max) - { - if (msg[*pos] == '\r' || msg[*pos] == '\n') - break; //ends on first line break (the break is then whitespace will will be skipped naturally) - *pos+=1; //not yet - } - continue; - } - else if (msg[*pos+1] == '*') - { /*C style multi-line comment*/ - *pos+=2; - while (*pos+1 < max) - { - if (msg[*pos] == '*' && msg[*pos+1] == '/') - { - *pos+=2; //skip past the terminator ready for whitespace or trailing comments directly after - break; - } - *pos+=1; //not yet - } - continue; - } - } - //END NON-STANDARD - break; //not whitespace/comment/etc. - } -} -//writes the body to a null-terminated string, handling escapes as needed. -//returns required body length (without terminator) (NOTE: return value is not escape-aware, so this is an over-estimate). -static size_t JSON_ReadBody(json_t *t, char *out, size_t outsize) -{ -// size_t bodysize; - if (!t) - { - if (out) - *out = 0; - return 0; - } - if (out && outsize) - { - char *outend = out+outsize-1; //compensate for null terminator - const char *in = t->bodystart; - while (in < t->bodyend && out < outend) - { - if (*in == '\\') - { - if (++in < t->bodyend) - { - switch(*in++) - { - case '\"': *out++ = '\"'; break; - case '\\': *out++ = '\\'; break; - case '/': *out++ = '/'; break; //json is not C... - case 'b': *out++ = '\b'; break; - case 'f': *out++ = '\f'; break; - case 'n': *out++ = '\n'; break; - case 'r': *out++ = '\r'; break; - case 't': *out++ = '\t'; break; -// case 'u': -// out += utf8_encode(out, code, outend-out); -// break; - default: - //unknown escape. will warn when actually reading it. - *out++ = '\\'; - if (out < outend) - *out++ = in[-1]; - break; - } - } - else - *out++ = '\\'; //error... - } - else - *out++ = *in++; - } - *out = 0; - } - return t->bodyend-t->bodystart; -} - -static qboolean JSON_ParseString(char const*msg, int *pos, int max, char const**start, char const** end) -{ - if (*pos < max && msg[*pos] == '\"') - { //quoted string - //FIXME: no handling of backslash followed by one of "\/bfnrtu - *pos+=1; - *start = msg+*pos; - while (*pos < max) - { - if (msg[*pos] == '\"') - break; - if (msg[*pos] == '\\') - { //escapes are expanded elsewhere, we're just skipping over them here. - switch(msg[*pos+1]) - { - case '\"': - case '\\': - case '/': - case 'b': - case 'f': - case 'n': - case 'r': - case 't': - *pos+=2; - break; - case 'u': - *pos+=2; - //*pos+=4; //4 hex digits, not escapes so just wait till later before parsing them properly. - break; - default: - //unknown escape. will warn when actually reading it. - *pos+=1; - break; - } - } - else - *pos+=1; - } - if (*pos < max && msg[*pos] == '\"') - { - *end = msg+*pos; - *pos+=1; - return true; - } - } - else - { //name - *start = msg+*pos; - while (*pos < max - && msg[*pos] != ' ' - && msg[*pos] != '\t' - && msg[*pos] != '\r' - && msg[*pos] != '\n' - && msg[*pos] != ':' - && msg[*pos] != ',' - && msg[*pos] != '}' - && msg[*pos] != '{' - && msg[*pos] != '[' - && msg[*pos] != ']') - { - *pos+=1; - } - *end = msg+*pos; - if (*start != *end) - return true; - } - *end = *start; - return false; -} -static json_t *JSON_Parse(json_t *t, const char *namestart, const char *nameend, const char *json, int *jsonpos, int jsonlen) -{ - const char *childstart, *childend; - JSON_SkipWhite(json, jsonpos, jsonlen); - - if (*jsonpos < jsonlen) - { - if (json[*jsonpos] == '{') - { - *jsonpos+=1; - JSON_SkipWhite(json, jsonpos, jsonlen); - - t = JSON_CreateNode(t, namestart, nameend, NULL, NULL, false); - - while (*jsonpos < jsonlen && json[*jsonpos] == '\"') - { - if (!JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) - break; - JSON_SkipWhite(json, jsonpos, jsonlen); - if (*jsonpos < jsonlen && json[*jsonpos] == ':') - { - *jsonpos+=1; - if (!JSON_Parse(t, childstart, childend, json, jsonpos, jsonlen)) - break; - } - JSON_SkipWhite(json, jsonpos, jsonlen); - - if (*jsonpos < jsonlen && json[*jsonpos] == ',') - { - *jsonpos+=1; - JSON_SkipWhite(json, jsonpos, jsonlen); - continue; - } - break; - } - - if (*jsonpos < jsonlen && json[*jsonpos] == '}') - { - *jsonpos+=1; - return t; - } - JSON_Destroy(t); - } - else if (json[*jsonpos] == '[') - { - char idxname[MAX_QPATH]; - unsigned int idx = 0; - *jsonpos+=1; - JSON_SkipWhite(json, jsonpos, jsonlen); - - t = JSON_CreateNode(t, namestart, nameend, NULL, NULL, true); - - for(;;) - { - Q_snprintf(idxname, sizeof(idxname), "%u", idx++); - if (!JSON_Parse(t, idxname, NULL, json, jsonpos, jsonlen)) - break; - - if (*jsonpos < jsonlen && json[*jsonpos] == ',') - { - *jsonpos+=1; - JSON_SkipWhite(json, jsonpos, jsonlen); - continue; - } - break; - } - - JSON_SkipWhite(json, jsonpos, jsonlen); - if (*jsonpos < jsonlen && json[*jsonpos] == ']') - { - *jsonpos+=1; - return t; - } - JSON_Destroy(t); - } - else - { - if (JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) - return JSON_CreateNode(t, namestart, nameend, childstart, childend, false); - } - } - return NULL; -} - -//we don't understand arrays here (we just treat them as tables) so eg "foo.0.bar" to find t->foo[0]->bar -static json_t *JSON_FindChild(json_t *t, const char *child) -{ - if (t) - { - size_t nl; - const char *dot = strchr(child, '.'); - if (dot) - nl = dot-child; - else - nl = strlen(child); - if (t->arraymax) - { - size_t idx = atoi(child); - if (idx < t->arraymax) - { - t = t->array[idx]; - if (t) - goto found; - } - } - else - { - for (t = t->child; t; t = t->sibling) - { - if (!strncmp(t->name, child, nl) && (t->name[nl] == '.' || !t->name[nl])) - { -found: - child+=nl; - t->used = true; - if (*child == '.') - return JSON_FindChild(t, child+1); - if (!*child) - return t; - break; - } - } - } - } - return NULL; -} -static json_t *JSON_FindIndexedChild(json_t *t, const char *child, unsigned int idx) -{ - char idxname[MAX_QPATH]; - if (child) - Q_snprintf(idxname, sizeof(idxname), "%s.%u", child, idx); - else - Q_snprintf(idxname, sizeof(idxname), "%u", idx); - return JSON_FindChild(t, idxname); -} -static qboolean JSON_Equals(json_t *t, const char *child, const char *expected) -{ - if (child) - t = JSON_FindChild(t, child); - if (t && t->bodyend-t->bodystart == strlen(expected)) - return !strncmp(t->bodystart, expected, t->bodyend-t->bodystart); - return false; -} -static quintptr_t JSON_GetUInteger(json_t *t, const char *child, unsigned int fallback) -{ - if (child) - t = JSON_FindChild(t, child); - if (t) - { //copy it to another buffer. can probably skip that tbh. - char tmp[MAX_QPATH]; - char *trail; - size_t l = t->bodyend-t->bodystart; - quintptr_t r; - if (l > MAX_QPATH-1) - l = MAX_QPATH-1; - memcpy(tmp, t->bodystart, l); - tmp[l] = 0; - if (!strcmp(tmp, "false")) //special cases, for booleans - return 0u; - if (!strcmp(tmp, "true")) //special cases, for booleans - return 1u; - r = (quintptr_t)strtoull(tmp, &trail, 0); - if (!*trail) - return r; - } - return fallback; -} -static qintptr_t JSON_GetInteger(json_t *t, const char *child, int fallback) -{ - if (child) - t = JSON_FindChild(t, child); - if (t) - { //copy it to another buffer. can probably skip that tbh. - char tmp[MAX_QPATH]; - char *trail; - size_t l = t->bodyend-t->bodystart; - qintptr_t r; - if (l > MAX_QPATH-1) - l = MAX_QPATH-1; - memcpy(tmp, t->bodystart, l); - tmp[l] = 0; - if (!strcmp(tmp, "false")) //special cases, for booleans - return 0; - if (!strcmp(tmp, "true")) //special cases, for booleans - return 1; - r = (qintptr_t)strtoll(tmp, &trail, 0); - if (!*trail) - return r; - } - return fallback; -} -#ifndef SERVERONLY//ffs -static qintptr_t JSON_GetIndexedInteger(json_t *t, unsigned int idx, int fallback) -{ - char idxname[MAX_QPATH]; - Q_snprintf(idxname, sizeof(idxname), "%u", idx); - return JSON_GetInteger(t, idxname, fallback); -} -#endif -static double JSON_GetFloat(json_t *t, const char *child, double fallback) -{ - if (child) - t = JSON_FindChild(t, child); - if (t) - { //copy it to another buffer. can probably skip that tbh. - char tmp[MAX_QPATH]; - size_t l = t->bodyend-t->bodystart; - if (l > MAX_QPATH-1) - l = MAX_QPATH-1; - memcpy(tmp, t->bodystart, l); - tmp[l] = 0; - return atof(tmp); - } - return fallback; -} -static double JSON_GetIndexedFloat(json_t *t, unsigned int idx, double fallback) -{ - char idxname[MAX_QPATH]; - Q_snprintf(idxname, sizeof(idxname), "%u", idx); - return JSON_GetFloat(t, idxname, fallback); -} -static const char *JSON_GetString(json_t *t, const char *child, char *buffer, size_t buffersize, const char *fallback) -{ - if (child) - t = JSON_FindChild(t, child); - if (t) - { //copy it to another buffer. can probably skip that tbh. - JSON_ReadBody(t, buffer, buffersize); - return buffer; - } - return fallback; -} static void JSON_GetPath(json_t *t, qboolean ignoreroot, char *buffer, size_t buffersize) { @@ -784,8 +243,8 @@ typedef struct gltf_s int *bonemap;//[MAX_BONES]; //remap skinned bones. I hate that we have to do this. struct gltfbone_s { - char name[32]; - char jointname[32]; //gltf1 only + char name[64]; + char jointname[64]; //gltf1 only int parent; int camera; double amatrix[16]; @@ -2759,7 +2218,7 @@ static qboolean GLTF_ProcessNode(gltf_t *gltf, json_t *nodeid, double pmatrix[16 joints->used = true; if (gltf->ver <= 1) { //urgh - char jointname[64]; + char jointname[sizeof(gltf->bones[b].jointname)]; JSON_ReadBody(joints, jointname, sizeof(jointname)); //this is matched to nodes[b].jointName rather than (textual) b, so we can't use our helpers. for (b = 0; b < gltf->numbones; b++) { @@ -3310,7 +2769,7 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, gltf.bonemap = malloc(sizeof(*gltf.bonemap)*MAX_BONES); gltf.bones = malloc(sizeof(*gltf.bones)*MAX_BONES); memset(gltf.bones, 0, sizeof(*gltf.bones)*MAX_BONES); - gltf.r = JSON_Parse(NULL, mod->name, NULL, json, &pos, jsonsize); + gltf.r = JSON_ParseNode(NULL, mod->name, NULL, json, &pos, jsonsize); gltf.mod = mod; gltf.buffers[0].data = buffer; gltf.buffers[0].length = buffersize;