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
This commit is contained in:
Spoike 2022-02-19 20:48:40 +00:00
parent f99764a887
commit dd03b69609
11 changed files with 1049 additions and 547 deletions

View file

@ -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}")

View file

@ -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

View file

@ -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},

View file

@ -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},

View file

@ -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.

675
engine/common/json.c Normal file
View file

@ -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;
}

View file

@ -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 <emscripten.h>
#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

View file

@ -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);

View file

@ -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");

View file

@ -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

View file

@ -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;